summaryrefslogtreecommitdiffstats
path: root/src/kmk
diff options
context:
space:
mode:
Diffstat (limited to 'src/kmk')
-rw-r--r--src/kmk/.gitignore62
-rw-r--r--src/kmk/.purify12
-rw-r--r--src/kmk/AUTHORS88
-rw-r--r--src/kmk/COPYING674
-rw-r--r--src/kmk/ChangeLog.14997
-rw-r--r--src/kmk/ChangeLog.26653
-rw-r--r--src/kmk/ChangeLog.35633
-rw-r--r--src/kmk/INSTALL231
-rw-r--r--src/kmk/Makefile.DOS.template587
-rw-r--r--src/kmk/Makefile.am333
-rw-r--r--src/kmk/Makefile.ami308
-rw-r--r--src/kmk/Makefile.kmk770
-rw-r--r--src/kmk/Makefile.os247
-rw-r--r--src/kmk/NEWS1540
-rw-r--r--src/kmk/NMakefile.template132
-rw-r--r--src/kmk/README.Amiga77
-rw-r--r--src/kmk/README.DOS.template340
-rw-r--r--src/kmk/README.OS2.template176
-rw-r--r--src/kmk/README.VMS515
-rw-r--r--src/kmk/README.W32.template314
-rw-r--r--src/kmk/README.customs112
-rw-r--r--src/kmk/README.git292
-rw-r--r--src/kmk/README.template178
-rw-r--r--src/kmk/SCOPTIONS13
-rw-r--r--src/kmk/SMakefile.template218
-rw-r--r--src/kmk/TODO.private117
-rw-r--r--src/kmk/acinclude.m4163
-rw-r--r--src/kmk/alloca.c503
-rw-r--r--src/kmk/alloccache.c259
-rw-r--r--src/kmk/amiga.c117
-rw-r--r--src/kmk/amiga.h18
-rw-r--r--src/kmk/ar.c328
-rw-r--r--src/kmk/arscan.c982
-rw-r--r--src/kmk/build.template81
-rw-r--r--src/kmk/build_w32.bat250
-rw-r--r--src/kmk/commands.c954
-rw-r--r--src/kmk/commands.h70
-rw-r--r--src/kmk/config.ami.template337
-rw-r--r--src/kmk/config.h-vms.template432
-rw-r--r--src/kmk/config.h.W32.template532
-rw-r--r--src/kmk/config.h.darwin491
-rw-r--r--src/kmk/config.h.freebsd433
-rw-r--r--src/kmk/config.h.haiku459
-rw-r--r--src/kmk/config.h.linux498
-rwxr-xr-xsrc/kmk/config.h.netbsd459
-rw-r--r--src/kmk/config.h.os2476
-rw-r--r--src/kmk/config.h.solaris443
-rw-r--r--src/kmk/config.h.win575
-rw-r--r--src/kmk/config/.gitignore12
-rw-r--r--src/kmk/config/ChangeLog.149
-rw-r--r--src/kmk/config/Makefile.am18
-rw-r--r--src/kmk/config/dospaths.m433
-rw-r--r--src/kmk/configh.dos.template113
-rw-r--r--src/kmk/configure.ac534
-rw-r--r--src/kmk/configure.bat60
-rw-r--r--src/kmk/debug.h66
-rw-r--r--src/kmk/default.c774
-rw-r--r--src/kmk/dep.h184
-rw-r--r--src/kmk/dir-nt-bird.c794
-rw-r--r--src/kmk/dir.c1602
-rw-r--r--src/kmk/doc/.gitignore22
-rw-r--r--src/kmk/doc/Makefile.am24
-rw-r--r--src/kmk/dosbuild.bat65
-rw-r--r--src/kmk/electric.c220
-rw-r--r--src/kmk/electric.h66
-rw-r--r--src/kmk/example-spaces.kmk175
-rw-r--r--src/kmk/expand.c1286
-rw-r--r--src/kmk/expreval.c2387
-rw-r--r--src/kmk/file.c1458
-rw-r--r--src/kmk/filedef.h254
-rw-r--r--src/kmk/function.c8250
-rw-r--r--src/kmk/getloadavg.c1026
-rw-r--r--src/kmk/getopt.c1031
-rw-r--r--src/kmk/getopt.h132
-rw-r--r--src/kmk/getopt1.c176
-rw-r--r--src/kmk/gettext.h57
-rw-r--r--src/kmk/glob/COPYING.LIB481
-rw-r--r--src/kmk/glob/ChangeLog191
-rw-r--r--src/kmk/glob/Makefile.am30
-rw-r--r--src/kmk/glob/Makefile.ami67
-rw-r--r--src/kmk/glob/SCOPTIONS13
-rw-r--r--src/kmk/glob/SMakefile67
-rw-r--r--src/kmk/glob/configure.bat43
-rw-r--r--src/kmk/glob/fnmatch.c489
-rw-r--r--src/kmk/glob/fnmatch.h85
-rw-r--r--src/kmk/glob/glob.c1463
-rw-r--r--src/kmk/glob/glob.h215
-rw-r--r--src/kmk/gmk-default.scm53
-rw-r--r--src/kmk/gnumake.h79
-rw-r--r--src/kmk/guile.c159
-rw-r--r--src/kmk/hash.c536
-rw-r--r--src/kmk/hash.h251
-rw-r--r--src/kmk/implicit.c1011
-rw-r--r--src/kmk/incdep.c2250
-rw-r--r--src/kmk/inlined_memchr.h162
-rw-r--r--src/kmk/job.c3991
-rw-r--r--src/kmk/job.h175
-rw-r--r--src/kmk/kbuild-object.c1409
-rw-r--r--src/kmk/kbuild.c3030
-rw-r--r--src/kmk/kbuild.h77
-rw-r--r--src/kmk/kbuildprf.c49
-rw-r--r--src/kmk/kdepdb.c1087
-rw-r--r--src/kmk/kmk_cc_exec.c7613
-rw-r--r--src/kmk/kmk_cc_exec.h48
-rw-r--r--src/kmk/kmkbuiltin.c507
-rw-r--r--src/kmk/kmkbuiltin.h184
-rw-r--r--src/kmk/kmkbuiltin/Makefile.kup0
-rw-r--r--src/kmk/kmkbuiltin/append.c459
-rw-r--r--src/kmk/kmkbuiltin/cat.c416
-rw-r--r--src/kmk/kmkbuiltin/chmod.c288
-rw-r--r--src/kmk/kmkbuiltin/cmp.c153
-rw-r--r--src/kmk/kmkbuiltin/cmp_extern.h49
-rw-r--r--src/kmk/kmkbuiltin/cmp_util.c562
-rw-r--r--src/kmk/kmkbuiltin/common-env-and-cwd-opt.c516
-rw-r--r--src/kmk/kmkbuiltin/cp.c779
-rw-r--r--src/kmk/kmkbuiltin/cp_extern.h55
-rw-r--r--src/kmk/kmkbuiltin/cp_utils.c397
-rw-r--r--src/kmk/kmkbuiltin/darwin.c55
-rw-r--r--src/kmk/kmkbuiltin/echo.c125
-rw-r--r--src/kmk/kmkbuiltin/err.c340
-rw-r--r--src/kmk/kmkbuiltin/err.h38
-rw-r--r--src/kmk/kmkbuiltin/expr.c617
-rw-r--r--src/kmk/kmkbuiltin/fts.c1461
-rw-r--r--src/kmk/kmkbuiltin/ftsfake.h159
-rw-r--r--src/kmk/kmkbuiltin/getopt1_r.c186
-rw-r--r--src/kmk/kmkbuiltin/getopt_r.c1090
-rw-r--r--src/kmk/kmkbuiltin/getopt_r.h182
-rw-r--r--src/kmk/kmkbuiltin/haikufakes.c53
-rw-r--r--src/kmk/kmkbuiltin/haikufakes.h42
-rw-r--r--src/kmk/kmkbuiltin/install.c1248
-rw-r--r--src/kmk/kmkbuiltin/kDepIDB.c860
-rw-r--r--src/kmk/kmkbuiltin/kDepObj.c1250
-rw-r--r--src/kmk/kmkbuiltin/kSubmit.c2116
-rw-r--r--src/kmk/kmkbuiltin/kbuild_protection.c376
-rw-r--r--src/kmk/kmkbuiltin/kbuild_protection.h67
-rw-r--r--src/kmk/kmkbuiltin/kill.c653
-rw-r--r--src/kmk/kmkbuiltin/ln.c287
-rw-r--r--src/kmk/kmkbuiltin/md5sum.c874
-rw-r--r--src/kmk/kmkbuiltin/mkdir.c302
-rw-r--r--src/kmk/kmkbuiltin/mscfakes.c839
-rw-r--r--src/kmk/kmkbuiltin/mscfakes.h183
-rw-r--r--src/kmk/kmkbuiltin/mv.c529
-rw-r--r--src/kmk/kmkbuiltin/openbsd.c54
-rw-r--r--src/kmk/kmkbuiltin/osdep.c48
-rw-r--r--src/kmk/kmkbuiltin/printf.c954
-rw-r--r--src/kmk/kmkbuiltin/redirect.c2066
-rw-r--r--src/kmk/kmkbuiltin/rm.c844
-rw-r--r--src/kmk/kmkbuiltin/rmdir.c251
-rw-r--r--src/kmk/kmkbuiltin/setmode.c506
-rw-r--r--src/kmk/kmkbuiltin/sleep.c179
-rw-r--r--src/kmk/kmkbuiltin/solfakes.c99
-rw-r--r--src/kmk/kmkbuiltin/solfakes.h51
-rw-r--r--src/kmk/kmkbuiltin/strlcpy.c78
-rw-r--r--src/kmk/kmkbuiltin/strmode.c197
-rw-r--r--src/kmk/kmkbuiltin/test.c869
-rw-r--r--src/kmk/kmkbuiltin/touch.c952
-rw-r--r--src/kmk/load.c267
-rw-r--r--src/kmk/loadapi.c82
-rw-r--r--src/kmk/main.c4482
-rw-r--r--src/kmk/maintMakefile414
-rw-r--r--src/kmk/make.1381
-rw-r--r--src/kmk/make.lnk5
-rw-r--r--src/kmk/make_msvc_net2003.sln21
-rw-r--r--src/kmk/make_msvc_net2003.vcproj340
-rw-r--r--src/kmk/makefile.com166
-rw-r--r--src/kmk/makefile.vms180
-rw-r--r--src/kmk/makeint.h1195
-rw-r--r--src/kmk/misc.c1358
-rw-r--r--src/kmk/os.h84
-rw-r--r--src/kmk/output.c1392
-rw-r--r--src/kmk/output.h107
-rw-r--r--src/kmk/po/.gitignore15
-rw-r--r--src/kmk/po/LINGUAS5
-rw-r--r--src/kmk/po/Makevars59
-rw-r--r--src/kmk/po/POTFILES.in48
-rw-r--r--src/kmk/posixos.c480
-rw-r--r--src/kmk/prepare_vms.com59
-rw-r--r--src/kmk/prepare_w32.bat6
-rw-r--r--src/kmk/read.c4101
-rw-r--r--src/kmk/remake.c2124
-rw-r--r--src/kmk/remote-cstms.c300
-rw-r--r--src/kmk/remote-stub.c99
-rw-r--r--src/kmk/rule.c546
-rw-r--r--src/kmk/rule.h58
-rw-r--r--src/kmk/signame.c254
-rw-r--r--src/kmk/strcache.c368
-rw-r--r--src/kmk/strcache2.c1327
-rw-r--r--src/kmk/strcache2.h182
-rw-r--r--src/kmk/subproc.bat24
-rw-r--r--src/kmk/testcase-2ndtargetexp.kmk68
-rw-r--r--src/kmk/testcase-assignments.kmk29
-rw-r--r--src/kmk/testcase-if1of.kmk80
-rw-r--r--src/kmk/testcase-ifeq-escape.kmk18
-rw-r--r--src/kmk/testcase-includedep-esc-sub.kmk113
-rw-r--r--src/kmk/testcase-includedep-esc.kmk133
-rw-r--r--src/kmk/testcase-includedep-sub.kmk28
-rw-r--r--src/kmk/testcase-includedep.kmk90
-rw-r--r--src/kmk/testcase-kBuild-define.kmk141
-rw-r--r--src/kmk/testcase-lazy-deps-vars.kmk72
-rw-r--r--src/kmk/testcase-libpath.kmk20
-rw-r--r--src/kmk/testcase-local.kmk127
-rw-r--r--src/kmk/testcase-math.kmk98
-rw-r--r--src/kmk/testcase-root.kmk30
-rw-r--r--src/kmk/testcase-stack.kmk86
-rw-r--r--src/kmk/testcase-which.kmk5
-rw-r--r--src/kmk/testcase-xargs.kmk59
-rw-r--r--src/kmk/testcase/testcase-export.kmk48
-rw-r--r--src/kmk/tests/.gitignore2
-rw-r--r--src/kmk/tests/COPYING674
-rw-r--r--src/kmk/tests/ChangeLog.11429
-rw-r--r--src/kmk/tests/NEWS178
-rw-r--r--src/kmk/tests/README102
-rw-r--r--src/kmk/tests/config-flags.pm.in19
-rw-r--r--src/kmk/tests/config_flags_pm.com53
-rw-r--r--src/kmk/tests/guile.supp31
-rwxr-xr-xsrc/kmk/tests/mkshadow57
-rwxr-xr-xsrc/kmk/tests/run_make_tests2
-rw-r--r--src/kmk/tests/run_make_tests.com272
-rwxr-xr-xsrc/kmk/tests/run_make_tests.pl507
-rw-r--r--src/kmk/tests/scripts/features/archives213
-rw-r--r--src/kmk/tests/scripts/features/comments35
-rw-r--r--src/kmk/tests/scripts/features/conditionals162
-rw-r--r--src/kmk/tests/scripts/features/default_names44
-rw-r--r--src/kmk/tests/scripts/features/double_colon220
-rw-r--r--src/kmk/tests/scripts/features/echoing64
-rw-r--r--src/kmk/tests/scripts/features/errors107
-rw-r--r--src/kmk/tests/scripts/features/escape74
-rw-r--r--src/kmk/tests/scripts/features/export186
-rw-r--r--src/kmk/tests/scripts/features/ifcond950
-rw-r--r--src/kmk/tests/scripts/features/include243
-rw-r--r--src/kmk/tests/scripts/features/jobserver107
-rw-r--r--src/kmk/tests/scripts/features/load110
-rw-r--r--src/kmk/tests/scripts/features/loadapi116
-rw-r--r--src/kmk/tests/scripts/features/mult_rules78
-rw-r--r--src/kmk/tests/scripts/features/mult_targets46
-rw-r--r--src/kmk/tests/scripts/features/order_only118
-rw-r--r--src/kmk/tests/scripts/features/output-sync349
-rw-r--r--src/kmk/tests/scripts/features/override45
-rw-r--r--src/kmk/tests/scripts/features/parallelism231
-rw-r--r--src/kmk/tests/scripts/features/patspecific_vars148
-rw-r--r--src/kmk/tests/scripts/features/patternrules232
-rw-r--r--src/kmk/tests/scripts/features/quoting32
-rw-r--r--src/kmk/tests/scripts/features/recursion55
-rw-r--r--src/kmk/tests/scripts/features/reinvoke80
-rw-r--r--src/kmk/tests/scripts/features/rule_glob37
-rw-r--r--src/kmk/tests/scripts/features/se_explicit169
-rw-r--r--src/kmk/tests/scripts/features/se_implicit260
-rw-r--r--src/kmk/tests/scripts/features/se_statpat109
-rw-r--r--src/kmk/tests/scripts/features/shell_assignment65
-rw-r--r--src/kmk/tests/scripts/features/statipattrules111
-rw-r--r--src/kmk/tests/scripts/features/targetvars273
-rw-r--r--src/kmk/tests/scripts/features/utf811
-rw-r--r--src/kmk/tests/scripts/features/varnesting35
-rw-r--r--src/kmk/tests/scripts/features/vpath82
-rw-r--r--src/kmk/tests/scripts/features/vpath245
-rw-r--r--src/kmk/tests/scripts/features/vpath341
-rw-r--r--src/kmk/tests/scripts/features/vpathgpath66
-rw-r--r--src/kmk/tests/scripts/features/vpathplus132
-rw-r--r--src/kmk/tests/scripts/functions/abspath81
-rw-r--r--src/kmk/tests/scripts/functions/addprefix44
-rw-r--r--src/kmk/tests/scripts/functions/addsuffix36
-rw-r--r--src/kmk/tests/scripts/functions/andor50
-rw-r--r--src/kmk/tests/scripts/functions/basename44
-rw-r--r--src/kmk/tests/scripts/functions/call92
-rw-r--r--src/kmk/tests/scripts/functions/dir44
-rw-r--r--src/kmk/tests/scripts/functions/error71
-rw-r--r--src/kmk/tests/scripts/functions/eval169
-rw-r--r--src/kmk/tests/scripts/functions/evalcall119
-rw-r--r--src/kmk/tests/scripts/functions/expr74
-rw-r--r--src/kmk/tests/scripts/functions/file161
-rw-r--r--src/kmk/tests/scripts/functions/filter-out42
-rw-r--r--src/kmk/tests/scripts/functions/findstring47
-rw-r--r--src/kmk/tests/scripts/functions/flavor44
-rw-r--r--src/kmk/tests/scripts/functions/for69
-rw-r--r--src/kmk/tests/scripts/functions/foreach97
-rw-r--r--src/kmk/tests/scripts/functions/guile99
-rw-r--r--src/kmk/tests/scripts/functions/if33
-rw-r--r--src/kmk/tests/scripts/functions/if-expr84
-rw-r--r--src/kmk/tests/scripts/functions/insert106
-rw-r--r--src/kmk/tests/scripts/functions/intersects94
-rw-r--r--src/kmk/tests/scripts/functions/join44
-rw-r--r--src/kmk/tests/scripts/functions/lastpos118
-rw-r--r--src/kmk/tests/scripts/functions/length71
-rw-r--r--src/kmk/tests/scripts/functions/length-var75
-rw-r--r--src/kmk/tests/scripts/functions/notdir44
-rw-r--r--src/kmk/tests/scripts/functions/origin54
-rw-r--r--src/kmk/tests/scripts/functions/pos118
-rw-r--r--src/kmk/tests/scripts/functions/printf80
-rw-r--r--src/kmk/tests/scripts/functions/realpath82
-rw-r--r--src/kmk/tests/scripts/functions/root172
-rw-r--r--src/kmk/tests/scripts/functions/select96
-rw-r--r--src/kmk/tests/scripts/functions/shell60
-rw-r--r--src/kmk/tests/scripts/functions/sort51
-rw-r--r--src/kmk/tests/scripts/functions/strip57
-rw-r--r--src/kmk/tests/scripts/functions/substitution38
-rw-r--r--src/kmk/tests/scripts/functions/substr125
-rw-r--r--src/kmk/tests/scripts/functions/suffix57
-rw-r--r--src/kmk/tests/scripts/functions/translate76
-rw-r--r--src/kmk/tests/scripts/functions/value30
-rw-r--r--src/kmk/tests/scripts/functions/warning83
-rw-r--r--src/kmk/tests/scripts/functions/while73
-rw-r--r--src/kmk/tests/scripts/functions/wildcard103
-rw-r--r--src/kmk/tests/scripts/functions/word167
-rw-r--r--src/kmk/tests/scripts/misc/bs-nl227
-rw-r--r--src/kmk/tests/scripts/misc/close_stdout9
-rw-r--r--src/kmk/tests/scripts/misc/fopen-fail18
-rw-r--r--src/kmk/tests/scripts/misc/general151
-rw-r--r--src/kmk/tests/scripts/misc/general250
-rw-r--r--src/kmk/tests/scripts/misc/general3315
-rw-r--r--src/kmk/tests/scripts/misc/general485
-rw-r--r--src/kmk/tests/scripts/misc/utf814
-rw-r--r--src/kmk/tests/scripts/options/dash-B87
-rw-r--r--src/kmk/tests/scripts/options/dash-C71
-rw-r--r--src/kmk/tests/scripts/options/dash-I59
-rw-r--r--src/kmk/tests/scripts/options/dash-W91
-rw-r--r--src/kmk/tests/scripts/options/dash-e24
-rw-r--r--src/kmk/tests/scripts/options/dash-f85
-rw-r--r--src/kmk/tests/scripts/options/dash-k114
-rw-r--r--src/kmk/tests/scripts/options/dash-l56
-rw-r--r--src/kmk/tests/scripts/options/dash-n100
-rw-r--r--src/kmk/tests/scripts/options/dash-q86
-rw-r--r--src/kmk/tests/scripts/options/dash-t58
-rw-r--r--src/kmk/tests/scripts/options/eval29
-rw-r--r--src/kmk/tests/scripts/options/general35
-rw-r--r--src/kmk/tests/scripts/options/print-directory33
-rw-r--r--src/kmk/tests/scripts/options/symlinks68
-rw-r--r--src/kmk/tests/scripts/options/warn-undefined-variables25
-rw-r--r--src/kmk/tests/scripts/targets/DEFAULT53
-rw-r--r--src/kmk/tests/scripts/targets/DELETE_ON_ERROR22
-rw-r--r--src/kmk/tests/scripts/targets/FORCE40
-rw-r--r--src/kmk/tests/scripts/targets/INTERMEDIATE112
-rw-r--r--src/kmk/tests/scripts/targets/ONESHELL88
-rw-r--r--src/kmk/tests/scripts/targets/PHONY54
-rw-r--r--src/kmk/tests/scripts/targets/POSIX56
-rw-r--r--src/kmk/tests/scripts/targets/SECONDARY190
-rw-r--r--src/kmk/tests/scripts/targets/SILENT42
-rw-r--r--src/kmk/tests/scripts/targets/clean50
-rw-r--r--src/kmk/tests/scripts/test_template29
-rw-r--r--src/kmk/tests/scripts/variables/CURDIR20
-rw-r--r--src/kmk/tests/scripts/variables/DEFAULT_GOAL87
-rw-r--r--src/kmk/tests/scripts/variables/GNUMAKEFLAGS42
-rw-r--r--src/kmk/tests/scripts/variables/INCLUDE_DIRS46
-rw-r--r--src/kmk/tests/scripts/variables/LIBPATTERNS38
-rw-r--r--src/kmk/tests/scripts/variables/MAKE24
-rw-r--r--src/kmk/tests/scripts/variables/MAKECMDGOALS52
-rw-r--r--src/kmk/tests/scripts/variables/MAKEFILES53
-rw-r--r--src/kmk/tests/scripts/variables/MAKEFILE_LIST30
-rw-r--r--src/kmk/tests/scripts/variables/MAKEFLAGS45
-rw-r--r--src/kmk/tests/scripts/variables/MAKELEVEL45
-rw-r--r--src/kmk/tests/scripts/variables/MAKE_RESTARTS61
-rw-r--r--src/kmk/tests/scripts/variables/MFILE_LIST30
-rw-r--r--src/kmk/tests/scripts/variables/SHELL103
-rw-r--r--src/kmk/tests/scripts/variables/automatic122
-rw-r--r--src/kmk/tests/scripts/variables/define282
-rw-r--r--src/kmk/tests/scripts/variables/flavors96
-rw-r--r--src/kmk/tests/scripts/variables/must_make81
-rw-r--r--src/kmk/tests/scripts/variables/negative46
-rw-r--r--src/kmk/tests/scripts/variables/private122
-rw-r--r--src/kmk/tests/scripts/variables/special150
-rw-r--r--src/kmk/tests/scripts/variables/undefine73
-rw-r--r--src/kmk/tests/scripts/vms/library73
-rw-r--r--src/kmk/tests/test_driver.pl1498
-rw-r--r--src/kmk/variable.c3475
-rw-r--r--src/kmk/variable.h551
-rw-r--r--src/kmk/version.c33
-rw-r--r--src/kmk/vms_exit.c95
-rw-r--r--src/kmk/vms_export_symbol.c527
-rw-r--r--src/kmk/vms_export_symbol_test.com37
-rw-r--r--src/kmk/vms_progname.c463
-rw-r--r--src/kmk/vmsdir.h76
-rw-r--r--src/kmk/vmsfunctions.c226
-rw-r--r--src/kmk/vmsify.c1005
-rw-r--r--src/kmk/vmsjobs.c1468
-rw-r--r--src/kmk/vpath.c647
-rw-r--r--src/kmk/w32/Makefile.am26
-rw-r--r--src/kmk/w32/Makefile.kup0
-rw-r--r--src/kmk/w32/compat/Makefile.kup0
-rw-r--r--src/kmk/w32/compat/dirent.c212
-rw-r--r--src/kmk/w32/compat/posixfcn.c516
-rw-r--r--src/kmk/w32/imagecache.c219
-rw-r--r--src/kmk/w32/include/dirent.h66
-rw-r--r--src/kmk/w32/include/dlfcn.h29
-rw-r--r--src/kmk/w32/include/pathstuff.h30
-rw-r--r--src/kmk/w32/include/sub_proc.h72
-rw-r--r--src/kmk/w32/include/w32err.h26
-rw-r--r--src/kmk/w32/pathstuff.c321
-rw-r--r--src/kmk/w32/subproc/Makefile.kup0
-rw-r--r--src/kmk/w32/subproc/NMakefile60
-rw-r--r--src/kmk/w32/subproc/misc.c83
-rw-r--r--src/kmk/w32/subproc/proc.h29
-rw-r--r--src/kmk/w32/subproc/sub_proc.c1714
-rw-r--r--src/kmk/w32/subproc/w32err.c85
-rw-r--r--src/kmk/w32/tstFileInfo.c151
-rw-r--r--src/kmk/w32/w32os.c220
-rw-r--r--src/kmk/w32/winchildren.c3729
-rw-r--r--src/kmk/w32/winchildren.h115
396 files changed, 160922 insertions, 0 deletions
diff --git a/src/kmk/.gitignore b/src/kmk/.gitignore
new file mode 100644
index 0000000..0764966
--- /dev/null
+++ b/src/kmk/.gitignore
@@ -0,0 +1,62 @@
+# Development artifacts
+ID
+TAGS
+.*gdbinit
+.gdb_history
+*~
+#*
+.#*
+
+# Configure artifacts
+ABOUT-NLS
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.cache
+config.h
+config.h.in
+config.log
+config.status
+configure
+stamp-h1
+
+# Build artifacts
+.deps
+gmk-default.h
+loadavg
+make
+*.o
+*.exe
+*.dll.a
+*.obj
+*.lib
+*.pdb
+*.sbr
+
+# Windows build artifacts
+WinDebug/
+WinRel/
+GccDebug/
+GccRel/
+
+# Distribution artifacts
+.dep_segment
+.check-git-HEAD
+ChangeLog
+Makefile.DOS
+NMakefile
+README
+README.DOS
+README.OS2
+README.W32
+SMakefile
+build.sh
+build.sh.in
+config.ami
+config.h-vms
+config.h.W32
+configh.dos
+make-[0-9]*/
+make-[0-9]*.tar.*
+checkcfg.*.log
diff --git a/src/kmk/.purify b/src/kmk/.purify
new file mode 100644
index 0000000..dc1b1d9
--- /dev/null
+++ b/src/kmk/.purify
@@ -0,0 +1,12 @@
+# Solaris (2.5.1) has a couple if issues.
+#
+suppress plk malloc; setvbuf "libc*"; main "main.c"
+suppress umr kstat_read; kstat_chain_update; kstat_open; getloadavg
+suppress umr kstat_chain_update; kstat_open; getloadavg
+
+# The command line options stuff leaks a little bit. No big deal.
+#
+suppress mlk malloc; xmalloc "misc.c"; decode_env_switches "main.c"
+suppress plk malloc; xmalloc "misc.c"; decode_env_switches "main.c"
+suppress mlk malloc; xmalloc "misc.c"; concat "misc.c"; decode_env_switches "main.c"
+suppress plk malloc; xmalloc "misc.c"; concat "misc.c"; decode_env_switches "main.c"
diff --git a/src/kmk/AUTHORS b/src/kmk/AUTHORS
new file mode 100644
index 0000000..9b6212f
--- /dev/null
+++ b/src/kmk/AUTHORS
@@ -0,0 +1,88 @@
+-----------------------------------
+
+GNU make development up to version 3.75 by:
+ Roland McGrath <roland@gnu.org>
+
+
+Development starting with GNU make 3.76 by:
+ Paul D. Smith <psmith@gnu.org>
+
+ Additional development starting with GNU make 3.81 by:
+ Boris Kolpackov <boris@kolpackov.net>
+
+
+GNU Make User's Manual
+ Written by:
+ Richard M. Stallman <rms@gnu.org>
+
+ Edited by:
+ Roland McGrath <roland@gnu.org>
+ Bob Chassell <bob@gnu.org>
+ Melissa Weisshaus <melissa@gnu.org>
+ Paul D. Smith <psmith@gnu.org>
+
+-----------------------------------
+GNU make porting efforts:
+
+ Port to VMS by:
+ Klaus Kaempf <kkaempf@progis.de>
+ Hartmut Becker <Hartmut.Becker@hp.com>
+ Archive support/Bug fixes by:
+ John W. Eaton <jwe@bevo.che.wisc.edu>
+ Martin Zinser <zinser@decus.decus.de>
+
+ Port to Amiga by:
+ Aaron Digulla <digulla@fh-konstanz.de>
+
+ Port to MS-DOS (DJGPP), OS/2, and MS-Windows (native/MinGW) by:
+ DJ Delorie <dj@delorie.com>
+ Rob Tulloh <rob_tulloh@tivoli.com>
+ Eli Zaretskii <eliz@gnu.org>
+ Jonathan Grant <jg@jguk.org>
+ Andreas Beuning <andreas.buening@nexgo.de>
+ Earnie Boyd <earnie@uses.sf.net>
+ Troy Runkel <Troy.Runkel@mathworks.com>
+
+-----------------------------------
+Other contributors:
+
+ Janet Carson <janet_carson@tivoli.com>
+ Howard Chu <hyc@highlandsun.com>
+ Ludovic Courtès <ludo@gnu.org>
+ Paul Eggert <eggert@twinsun.com>
+ Ramon Garcia Fernandez <ramon.garcia.f@gmail.com>
+ Klaus Heinz <kamar@ease.rhein-main.de>
+ Michael Joosten
+ Jim Kelton <jim_kelton@tivoli.com>
+ David Lubbren <uhay@rz.uni-karlsruhe.de>
+ Tim Magill <tim.magill@telops.gte.com>
+ Markus Mauhart <qwe123@chello.at>
+ Greg McGary <greg@mcgary.org>
+ Thien-Thi Nguyen <ttn@gnuvola.org>
+ Thomas Riedl <thomas.riedl@siemens.com>
+ Han-Wen Nienhuys <hanwen@cs.uu.nl>
+ Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+ Carl Staelin (Princeton University)
+ Ian Stewartson (Data Logic Limited)
+ David A. Wheeler <dwheeler@dwheeler.com>
+ David Boyce <dsb@boyski.com>
+ Frank Heckenbach <f.heckenbach@fh-soft.de>
+
+With suggestions/comments/bug reports from a cast of ... well ...
+hundreds, anyway :)
+
+-------------------------------------------------------------------------------
+Copyright (C) 1997-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/COPYING b/src/kmk/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/src/kmk/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/src/kmk/ChangeLog.1 b/src/kmk/ChangeLog.1
new file mode 100644
index 0000000..c37b139
--- /dev/null
+++ b/src/kmk/ChangeLog.1
@@ -0,0 +1,4997 @@
+Tue Oct 29 20:57:36 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.
+
+ * remake.c (update_file_1): Check for deps still running before
+ giving up if any dep has failed.
+
+Sat Oct 26 16:20:00 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h [uts]: #undef S_ISREG and S_ISDIR if defined.
+
+Fri Oct 25 19:50:39 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.17.
+
+Thu Oct 24 16:58:36 1991 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * job.c (start_job): Don't check for empty cmds before tweaking the
+ command_ptr. Just let construct_command_argv do it.
+
+Tue Oct 22 20:21:03 1991 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * remake.c, arscan.c [POSIX]: <fcntl.h> instead of <sys/file.h>.
+
+ * make.h [POSIX]: Declare vfork as pid_t.
+
+Mon Oct 21 15:37:30 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.16.
+
+ * job.c (construct_command_argv, construct_command_argv_internal):
+ Take new 2nd arg RESTP. If non-NULL, stop parsing at newline, and
+ store addr of the NL in *RESTP.
+ (start_job): Don't chop expanded cmd lines up; use above code to do it.
+ * function.c (expand_function: `shell'): Pass RESTP==NULL.
+
+Sat Oct 19 15:36:34 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.15.
+
+Fri Oct 18 15:26:55 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (start_job): If on the same cmds->command_lines elt, look
+ at cmds->lines_recurse[CHILD->command_line - 1] instead of
+ [CHILD->command_line].
+
+ * dir.c [sgi]: <sys/dir.h>, not ndir or anything else.
+
+Thu Oct 17 16:28:55 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * file.c (print_file_data_base): Remove unused var.
+
+ * make.h [NeXT]: No #define ANSI_STRING.
+
+Tue Oct 15 20:08:41 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.60.14.
+
+Fri Oct 11 16:23:52 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * make.h: Use PATH_MAX for getwd defn.
+
+ * make.h: Move getcwd/getwd outside of #ifndef POSIX, and make it
+ #if USG||POSIX.
+
+Thu Oct 10 11:53:31 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.60.13.
+
+ * read.c (read_all_makefiles): When processing MAKEFILES, save the
+ malloc'd ptr to be freed, instead of freeing part-way thru it.
+
+ * remake.c (update_file_1): Don't tweak FILE->also_make.
+ (update_file): Do it here. After calling update_file_1, set the
+ command_state, update_status, and updated members of each also_make
+ elt to FILE's values.
+
+Tue Oct 8 14:56:04 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.60.12.
+
+ * remake.c (notice_finished_file): Set command_state of FILE and
+ its also_make chain to cs_finished here.
+ * commands.c (execute_file_commands), job.c (child_handler),
+ remake.c (remake_file): Don't set it before calling
+ notice_finished_file.
+
+ * file.h (struct file): Changed `also_make' to struct dep *.
+ * job.c (delete_child_targets), file.c (print_file_data_base),
+ remake.c (notice_finished_file), implicit.c (pattern_search):
+ Use dep chain instead of array of file names.
+
+Mon Oct 7 17:04:33 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.60.11.
+
+ * arscan.c: Declare open.
+ * misc.c: Declare {get,set}{re,}[ug]id.
+ * variable.c (target_environment): Declare getenv.
+
+Sat Oct 5 15:13:03 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * make.h [NeXT]: <string.h> instead of <strings.h>.
+
+Fri Oct 4 16:05:41 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * default.c (default_suffixes, defualt_suffix_rules): Add .texi
+ just like .texinfo.
+
+ * Version 3.60.10.
+
+ * job.c: Move vfork decl into make.h.
+
+Fri Sep 27 18:45:30 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * compatMakefile (glob/libglob.a): Pass CC value to submake.
+
+Thu Sep 26 00:08:15 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * load.c (load_average): Made not static.
+
+ * load.c [ultrix && vax]: Define LDAV_TYPE and LDAV_CVT for Ultrix 4.2.
+
+Tue Sep 24 00:17:20 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.9.
+
+ * read.c (record_files): Warn about extra cmds even if the target's
+ name begins with a dot. I think the lusers can handle this.
+
+Mon Sep 23 22:33:26 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h, arscan.c: Don't declare bcmp, bzero, or bcopy if they're
+ #define'd.
+ * make.h: Declare write and open.
+
+ * default.c (default_suffixes, default_suffix_rules,
+ default_variables): Add .C just like .cc.
+ * make.texinfo (Catalogue of Rules): Document .C.
+
+ * make.man (-w): Fix gramo.
+
+Fri Sep 20 17:18:16 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h: No text after #endif.
+
+Sun Sep 15 16:20:46 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.60.8.
+
+ * implicit.c (pattern_search): In the second pass, recurse on rule
+ deps that don't have a %. Why did I make it not do this?
+
+Fri Sep 14 18:29:39 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * read.c (record_files): For extra cmds, use the last ones given.
+ If the target's name doesn't begin with a dot (bletch!!), emit a
+ two-line warning, one line giving the old cmds' location and the
+ other the new cmds' location.
+
+ * misc.c (makefile_error, makefile_fatal): New fns.
+ * make.h: Declare them.
+ * Use them instead of error/fatal for all msgs including a file
+ name and line number.
+
+Thu Sep 13 16:35:54 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * make.h: Declare define_default_variables.
+ Declare ar_parse_name, instead of ar_name_parse (M-t).
+
+Mon Sep 10 18:35:40 1991 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * Version 3.60.7.
+
+ * make.texinfo (Variables: Setting): Say whitespace is removed if
+ "immediately after =", rather than simply "after =".
+
+ * job.c: Don't declare wait #ifdef POSIX.
+
+ * make.h [__GNUC__]: #undef alloca and then #define it.
+
+ * main.c (main): When pruning makefiles which might loop from the
+ read_makefiles chain, look at all `prev' entries of double-colon rules.
+
+Fri Sep 7 00:41:53 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * main.c (main): Only remove makefiles with cmds but no deps from
+ the list of makefiles to be rebuilt if they are :: targets.
+ : targets with cmds and no deps are not dangerous.
+
+Wed Sep 5 17:35:51 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (defines): Add comment that some compilers take
+ ENUM_BITFIELDS but produce bogus code.
+ (LOAD_AVG): Fix examples to \ "s.
+ (LOADLIBES): Add comment that SGI Irix needs -lmld for nlist.
+
+Tue Sep 4 20:26:26 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.6.
+
+Fri Aug 30 19:34:04 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (update_file_1): When checking the command_state of
+ deps, check through the prev chain.
+ (update_goal_chain): When a target is finished, start checking its
+ prev (if it has one) instead.
+
+Wed Aug 7 17:32:03 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * rule.c (convert_to_pattern): Allow files with deps to define
+ suffix rules (really this time).
+
+Mon Aug 5 17:09:21 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * misc.c (user_access, make_access): Do saved-IDs (USG) flavor
+ #ifdef POSIX.
+
+ * file.c (enter_file): Strip ./s here.
+ * read.c (parse_file_seq): Not here.
+
+Tue Jul 23 23:34:30 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile: Added comment that -lPW alloca is broken on HPUX.
+
+Thu Jul 18 03:10:41 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.5.
+
+ * read.c (read_makefile): Ignore lines containing chars that are
+ all isspace, not just all isblank.
+
+ * make.texinfo (Copying): @include gpl.texinfo, rather than copying
+ the text.
+ * gpl.texinfo: New file (symlink to /gd/gnu/doc/gpl.texinfo).
+ * GNUmakefile: Put gpl.texinfo in distribution.
+
+Tue Jul 16 12:50:35 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * make.h: #define _GNU_SOURCE before including headers.
+ Include <ctype.h> and define isblank if <ctype.h> doesn't.
+ * commands.c: Don't include <ctype.h> here.
+ * *.c: Use isblank instead of explicit ' ' || '\t'.
+
+Mon Jul 15 17:43:38 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * function.c (expand_function: `filter'/`filter-out'): Fixed to not
+ loop infinitely.
+
+Fri Jul 12 12:18:12 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * function.c (expand_function: `filter'/`filter-out'): Rewritten to
+ handle filter-out of multiple patterns properly. Also no longer
+ mallocs and reallocs for temp array; uses alloca and a linked-list
+ instead.
+
+Wed Jul 10 22:34:54 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.4.
+
+ * make.texinfo: Moved some @groups that were outside @examples to
+ be inside them.
+
+ * load.c [apollo] (load_average): Define using special syscall for
+ Apollo DOMAIN/OS SR10.n.
+
+Thu Jul 4 12:32:53 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * make.texinfo (Missing): Added Unix excessive implicit rule
+ search; mention that POSIX.2 doesn't require any of the missing
+ features.
+ (Top): Updated printed manual price to $15.
+
+Wed Jul 3 18:17:50 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * file.c (rename_file): Carry over last_mtime when merging files.
+ * remake.c (f_mtime): Tail-recurse after renaming VPATH file, to
+ check for saved date in existing renamed-to file.
+
+ * remote-cstms.c (start_remote_job): Use PATH_VAR.
+
+ * commands.c [POSIX || __GNU_LIBRARY__]: Don't declare getpid.
+
+ * compatMakefile (glob-{clean,realclean}): Run clean/realclean in glob.
+ (clean, realclean): Require those.
+
+ * make.h: Always declare environ.
+ Don't declare old glob functions.
+
+ * GNUmakefile: Make no-lib deps for load.c and remote.c.
+
+Tue Jul 2 18:35:20 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.60.3.
+
+Mon Jul 1 16:58:30 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (multi_glob): Don't pass GLOB_QUOTE flag to glob.
+
+ * make.h [POSIX]: Include <unistd.h>, and don't declare things that
+ should be there.
+
+ * main.c (main) [USG && sgi]: malloc a buffer for broken sgi stdio.
+
+Sat Jun 29 11:22:21 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * function.c (expand_function: `shell'): Use alloca for the error
+ msg buffer, instead of assuming an arbitrary max size.
+
+Fri Jun 28 18:15:08 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c [POSIX] (search_path): Do real 1003.1 goop to get NGROUPS_MAX.
+
+Wed Jun 26 11:04:44 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * default.c (define_default_variables): New fn.
+ (install_default_implicit_rules): Code for above fn moved there.
+ * main.c (main): Do define_default_variables before reading the
+ makefile.
+
+Tue Jun 25 17:30:46 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * main.c (main): Quote ; in MAKEOVERRIDES.
+
+Tue Jun 18 13:56:30 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * compatMakefile: Fixed typo in comment.
+
+Tue Jun 11 00:14:59 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.60.2.
+
+Mon Jun 10 14:46:37 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h: Always include <sys/types.h>.
+ [POSIX]: Include <limits.h> and #define MAXPATHLEN to be PATH_MAX.
+
+ * default.c (default_suffix_rules: .texinfo.dvi): Use $(TEXI2DVI).
+ (default_variables): Define TEXI2DVI.
+
+Thu Jun 6 16:49:19 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.60.1.
+
+ * make.h (SIGNAL): Cast handler arg to SIGHANDLER type.
+
+Wed Jun 5 06:00:43 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * read.c (multi_glob): Use POSIX.2 `glob' function.
+ If a glob pattern matches nothing, leave it as is (a la sh, bash).
+ Also, if can't find USER for ~USER, leave it as is (a la bash).
+
+Mon Jun 3 16:36:00 1991 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * compatMakefile: Rewrote comments about -Ds to be easier to use.
+
+ * make.h, arscan.c, remake.c, main.c, dir.c, job.c: Changed tests
+ of _POSIX_SOURCE to POSIX.
+
+ * job.c: Take getdtablesize out of #ifdef __GNU_LIBRARY__.
+ Put separately #ifdef USG.
+
+ * COPYING: Replaced with version 2.
+ * Changed copyright notices to refer to GPL v2.
+
+Thu May 30 00:31:11 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h: Don't declare sigblock for POSIX.
+
+ * main.c (main, log_working_directory) [USG]: Get getcwd failure
+ mode from errno, not passed buffer like BSD getwd.
+
+ * misc.c (child_access): New fn to set access for a child process;
+ like user_access, but you can't change back.
+ * make.h: Declare it.
+ * job.c (exec_command): Use it in place of user_access.
+
+Wed May 29 23:28:48 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (default_variables) [pyr]: PC = pascal.
+
+Tue May 28 20:24:56 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (print_variable): Put a newline before `endef'.
+
+Sat May 25 02:39:52 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.60.
+
+Wed May 22 19:41:37 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.59.5.
+
+Thu May 16 13:59:24 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (main): Do USGr3 setvbuf behavior #ifdef APOLLO.
+ Don't handle SIGCHLD #ifdef USG (Apollo is USG but defines SIGCHLD).
+
+Fri May 10 14:59:33 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * remake.c [sgi]: Don't include <sys/file.h>.
+
+Wed May 8 01:54:08 1991 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * make.h (SIGHANDLER): #define as (void *) #if __STDC__,
+ else (int (*)()).
+ (SIGNAL): Use it to cast return value.
+ * main.c (main): Cast SIG_IGN to SIGHANDLER when comparing.
+ * job.c (block_signals, unblock_signals): Use SIGNAL instead of signal.
+
+ * main.c: Declare mktemp to return char*, not int.
+
+ * job.c (new_job): Don't increment files_remade.
+ * remake.c (notice_finished_file): Do it here.
+
+ * read.c (do_define): Don't clobber DEFINITION[-1] on empty defns.
+ Free storage that is no longer needed.
+
+Wed Apr 24 20:49:48 1991 Roland McGrath (roland at churchy.gnu.ai.mit.edu)
+
+ * misc.c (message): New fn to print informational msgs with
+ leading "make: " or "make[N]: ".
+ * make.h: Declare it.
+ * remake.c (update_file): Use it instead of printf.
+
+Fri Apr 19 05:52:45 1991 Roland McGrath (roland at churchy.gnu.ai.mit.edu)
+
+ * main.c (main): When there are no targets, if there were no
+ makefiles, print a different error message, which mentions makefiles.
+
+Tue Apr 16 03:22:45 1991 Roland McGrath (roland at geech.gnu.ai.mit.edu)
+
+ * remake.c (update_file): Print "nothing to be done" instead of "is
+ up to date" if FILE->cmds == 0.
+
+ * job.c [!WIFEXITED]: Define if not already defined.
+
+Thu Apr 11 18:00:50 1991 Roland McGrath (roland at wookumz.gnu.ai.mit.edu)
+
+ * arscan.c (ar_name_equal): Fixed truncation comparison.
+
+Tue Apr 2 16:17:35 1991 Roland McGrath (roland at churchy.gnu.ai.mit.edu)
+
+ * glob.c: Use common version from djm.
+ * dir.c: Snarfed #ifdef mess for <dirent.h> or whatever from glob.c.
+ (dir_file_exists_p): Ignore directory entries with d_ino==0.
+
+Mon Apr 1 20:49:45 1991 Roland McGrath (roland at albert.gnu.ai.mit.edu)
+
+ * Version 3.59.4.
+
+Fri Mar 29 19:16:18 1991 Roland McGrath (roland at albert.gnu.ai.mit.edu)
+
+ * job.c (free_child): Free CHILD->environment and its elts.
+
+Sat Mar 23 14:08:09 1991 Roland McGrath (roland at albert.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Don't ignore lines containing only
+ comments if they start with a tab. Such lines should be passed to
+ the shell for it to decide about the comments.
+
+ * job.c (free_child): Free CHILD->command_lines and its elts, not
+ CHILD->commands (which is obsolete).
+ * job.h, job.c: Remove obsolete `commands' member of `struct child'.
+
+Sun Mar 17 18:40:53 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * remake.c (update_file): Print a msg for a top-level up-to-date
+ phony target (a different one than for a real file).
+
+ * read.c (conditional_line): Boundary check so we don't check the
+ value of the -1th elt of the stack (which is bogus).
+
+Sat Mar 16 16:58:47 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * read.c (conditional_line): Don't evaluate an if* when we're
+ already ignoring. Instead, just push a new level, with a value of
+ 1, to keep ignoring.
+
+Tue Mar 12 00:16:52 1991 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * Version 3.59.3.
+
+Mon Mar 11 23:56:57 1991 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Quote backslashes
+ when building the shell -c line.
+
+Fri Mar 8 01:40:18 1991 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * job.c (exec_command): Call user_access rather than setgid(getgid()).
+
+ * misc.c (remove_comments): Renamed from collapse_line; took out
+ collapse_continuations call.
+ * make.h: Change decl.
+ * read.c (read_makefile): Collapse continuations on the line buffer
+ immediately after reading it. Call remove_comments rather than
+ collapse_line (which is now defunct).
+
+Thu Feb 21 18:06:51 1991 Roland McGrath (mcgrath at cygint.cygnus.com)
+
+ * misc.c (user_access, make_access): New fns to toggle btwn permissions
+ for user data (files and spawning children), and permissions for make
+ (for taking the load average, mostly).
+ * make.h: Declare them.
+ * job.c (start_job): Call make_access before wait_to_start_job, and
+ user_access after.
+ * main.c (main): Call user_access before doing much.
+
+Mon Feb 3 15:02:03 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * Version 3.59.2.
+
+Tue Jan 29 20:30:50 1991 Roland McGrath (roland at cygint.cygnus.com)
+
+ * read.c (read_all_makefiles): Use allocated_variable_expand to expand
+ `$(MAKEFILES)', since the results are used across calls to
+ read_makefile, which could clobber them.
+
+Wed Jan 23 00:24:10 1991 Roland McGrath (roland at cygint.cygnus.com)
+
+ * main.c (main): Call install_default_implicit_rules after reading
+ makefiles, not before.
+ * default.c (install_default_implicit_rules): If a suffix-rule file
+ entry has cmds, don't give it any from default_suffix_rules.
+
+Fri Jan 17 17:39:49 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * arscan.c: Added support for AIX archives.
+
+ * remake.c: Don't include ar.h.
+ * main.c: Removed unused atol decl.
+ * arscan.c (ar_scan): Declare arg FUNCTION to return long int.
+ * ar.c (ar_touch): Don't perror for an invalid archive.
+ * make.h: Declare lseek as long int.
+
+ * job.c [hpux]: Define getdtablesize a la USG.
+
+Sun Jan 12 21:08:34 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * Version 3.59.1.
+
+Fri Jan 10 03:48:08 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * job.c (search_path): Take new arg, place to put full pathname (rather
+ than mallocing it).
+ (exec_command): Pass it, using auto storage.
+
+ * main.c (print_version): Updated copyright years.
+
+Wed Jan 8 19:46:19 1991 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * job.c [_POSIX_SOURCE]: Just #include <sys/wait.h>, and define macro
+ WAIT_NOHANG in terms of waitpid.
+ [!_POSIX_SOURCE && (HAVE_SYS_WAIT || !USG)]: Don't #include <signal.h>
+ (make.h does).
+ Define macro WAIT_NOHANG in terms of wait3.
+ (child_handler): #ifdef on WAIT_NOHANG, not HAVE_SYS_WAIT || !USG.
+ Use WAIT_NOHANG macro instead of wait3.
+
+ * file.h (struct file.command_state): Remove unused elt.
+
+Wed Dec 26 18:10:26 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * commands.c (set_file_variables): If FILE got its commands from
+ .DEFAULT, make $< == $@ (4.3 BSD/POSIX.2d11 compat).
+
+Mon Dec 24 17:36:27 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * default.c (default_variables): Rename 2nd LINK.s defn to LINK.S.
+
+Fri Dec 14 15:05:25 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * vpath.c (selective_vpath_search): Check for makefile-mentioned before
+ checking for actual existence. The old order loses if the containing
+ directory doesn't exist (but a rule might make it).
+
+ * make.h [__GNUC__]: Don't #define alloca if already #define'd.
+
+ * rule.c (convert_to_pattern): Don't look at the target constructed for
+ the empty rule when making the null-suffix rule. Construct it over
+ again, since the former may have been freed already.
+
+Thu Dec 13 17:21:03 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * make.h [__GNU_LIBRARY__]: Include <unistd.h> to get random fn decls.
+
+Wed Dec 12 17:12:59 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * make.h, arscan.c, glob.c: Only include <memory.h> #ifdef USG.
+
+ * variable.c (define_variable_in_set): Replace env_overrides check that
+ wasn't really redundant (undoing Sep 28 change). Add comment saying
+ why this check is necessary.
+
+ * job.c, main.c [DGUX]: Needs siglist like USG.
+
+Mon Dec 11 01:19:29 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * default.c [M_XENIX]: For rules that are different for Xenix, use the
+ generic Unix version #ifdef __GNUC__.
+
+ * main.c [M_XENIX]: Use USGr3-style setvbuf call.
+
+ * read.c (find_percent): Do backslash folding correctly, not leaving
+ extra crud on the end of the string.
+
+Sun Dec 10 21:48:36 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * job.c: Don't declare wait3 if it's #defined.
+
+ * GNUmakefile, compatMakefile, make.texinfo: Change make-info
+ to make.info.
+
+Thu Dec 7 21:20:01 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * make.h [STDC_HEADERS || __GNU_LIBRARY__ || _POSIX_SOURCE]: Use
+ ANSI <string.h> and names for str/mem functions.
+ Use <stdlib.h> to declare misc fns rather than explicit decls.
+ [_POSIX_SOURCE]: Don't declare kill (<signal.h> will).
+ Include <sys/types.h> before <signal.h> because some braindead
+ nonconformant 1003.1 implementation needs it.
+ * misc.c: Don't declare malloc, realloc. Do it in make.h.
+ * arscan.c, glob.c: Use sequence for string fns from make.h verbatim.
+ * make.h (S_ISDIR, S_ISREG): Declare if necessary.
+ * commands.c (delete_child_targets), job.c (search_path), read.c
+ (construct_include_path): Use S_ISfoo(m) instead of
+ (m & S_IFMT) == S_IFfoo.
+ * dir.c, glob.c [_POSIX_SOURCE]: Use dirent.
+
+Wed Nov 29 22:53:32 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * Version 3.59.
+
+Tue Nov 28 16:00:04 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * arscan.c (ar_name_equal) [APOLLO]: Don't do `.o' hacking. On Apollos
+ the full file name is elsewhere, and there is no length restriction (or
+ so I'm told).
+
+Thu Nov 23 17:33:11 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * load.c [hp300 && BSD] (LDAV_CVT): Define for this system.
+
+Tue Nov 21 07:58:40 1990 Roland McGrath (roland at albert.ai.mit.edu)
+
+ * read.c (record_files): Fix trivial bug with deciding to free storage
+ for a file name.
+
+Thu Nov 16 06:21:38 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * compatMakefile ($(bindir)/make): Install it setgid kmem.
+
+Thu Nov 1 16:12:55 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * GNUmakefile (make-*.tar.Z): Use `h' option to tar (dereference
+ symlinks), to grab texinfo.tex from wherever it lives.
+
+Tue Oct 30 16:15:20 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * Version 3.58.13.
+
+Fri Oct 26 14:33:34 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * GNUmakefile: make-*.tar.Z: Include texinfo.tex.
+
+Tue Oct 23 19:34:33 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * main.c (define_makeflags): When there are no flags to write, make
+ sure the array has two leading nulls, since `MAKEFLAGS' is defined from
+ &flags[1].
+
+ * main.c (default_keep_going_flag): New variable (constant one).
+ (command_switches: -k, -S): Use above for default value.
+ (define_makeflags): Only write flag/flag_off switches if they are on,
+ and either there is no default value, or they are not the default.
+
+Mon Oct 22 16:14:44 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * main.c (struct command_switch): New member `no_makefile'.
+ (command_switches: -n, -q, -t): Set no_makefile == 1.
+ (define_makeflags): Take new arg MAKEFILE: if nonzero, don't use
+ options whose `no_makefile' flags are set.
+ (main): Call define_makeflags with MAKEFILE==1 before remaking
+ makefiles, and again with MAKEFILE==0 before remaking goals.
+
+Tue Oct 2 17:16:45 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * Version 3.58.12.
+
+Mon Oct 1 15:43:23 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * arscan.c [HPUX]: Use PORTAR==1 format.
+
+Sat Sep 29 16:38:05 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * make.h, remake.c, arscan.c: Don't declare `open'.
+
+Fri Sep 28 04:46:23 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * variable.c (define_variable_in_set): Remove redundant -e check.
+
+Wed Sep 26 00:28:59 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * job.c (start_job): Set RECURSIVE from the right elt of
+ CHILD->file->cmds->lines_recurse.
+
+ * commands.c (chop_commands): Don't botch the line count for allocating
+ CMDS->lines_recurse.
+
+ * Version 3.58.11.
+
+ * job.c (start_job): Don't always increment CHILD->command_line! Only
+ do it when CHILD->command_ptr has run out! (Dumb bug. Sigh.)
+
+Thu Sep 20 02:18:51 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * GNUmakefile [ARCH]: Give explicit rule for remote.{c,dep} to use
+ variable `REMOTE' for more flags.
+ ($(prog)): Link in $(LOADLIBES).
+
+Wed Sep 19 02:30:36 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * commands.h (struct commands): New member `ncommand_lines', the number
+ of elts in `command_lines' et al.
+ * commands.c (chop_commands): Set `ncommand_lines' elt of CMDS, and
+ don't put a nil pointer at the end of `command_lines'.
+ * job.h (struct child): New member `command_lines' to hold
+ variable-expanded command lines.
+ * job.c (new_job): Store expanded command lines in `command_lines'
+ member of new child. Don't clobber FILE->cmds.
+ (start_job): Use CHILD->command_lines in place of
+ CHILD->file->cmds->command_lines.
+
+ * variable.h, variable.c, job.c, expand.c: Undo yesterday's change,
+ which is no longer necessary since we have cleverly avoided the issue.
+
+ * job.c (start_job): Don't variable-expand each command line.
+ (new_job): Do them all here, storing the expansions in the array.
+
+Tue Sep 18 01:23:13 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * variable.h (struct variable): Remove `expanding' member.
+ * variable.c (define_variable_in_set): Don't initialize it.
+ * expand.c (struct variable_expanding): New type, a linked list
+ containing `struct variable' pointers.
+ (variables_expanding): New variable, the chain of variables currently
+ being expanded.
+ (recursively_expand): Don't test and set `expanding' member.
+ Instead, run through the `variables_expanding' chain looking for a link
+ referring to V to find self-reference. Add a new link to the chain,
+ describing V, before recursive expansion, and pop it off afterward.
+ * job.c (child_handler): Save `variables_expanding' and clear it before
+ calling start_job, and restore it afterward. This avoids major lossage
+ when the SIGCHLD comes in the middle of variable expansion.
+
+Mon Sep 17 14:46:26 1990 Roland McGrath (roland at geech.ai.mit.edu)
+
+ * job.c, commands.c: Don't define sigmask.
+ * make.h: Put it here instead.
+
+ * variable.c (target_environment): If `.NOEXPORT' was specified as a
+ target, only export command-line and environment variables, and
+ file-origin variables that were in the original environment.
+
+ * make.man: Add missing ?roff control for `-I' option description.
+
+Thu Sep 13 14:10:02 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * load.c [UMAX]: Move #include <sys/sysdefs.h> to [not UMAX_43].
+
+Wed Sep 12 15:10:15 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * expand.c (recursively_expand): Don't use `reading_filename' and
+ `reading_lineno_ptr' if they're nil.
+
+Thu Aug 30 17:32:50 1990 Roland McGrath (roland at geech)
+
+ * Version 3.58.10.
+
+Tue Aug 28 04:06:29 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * job.c [USG] (unknown_children_possible): New variable, set nonzero
+ when it's possible for children not in the `children' chain to die.
+ (block_signals) [USG]: Set it.
+ (unblock_signals) [USG]: Clear it.
+ (child_handler) [USG]: Don't complain about unknown children if
+ `unknown_children_possible' is set.
+
+ * read.c (do_define): Make sure there's enough space for the newline,
+ so we don't write off the end of allocated space.
+
+ * arscan.c (ar_name_equal): Fixed to work when MEM is AR_NAMELEN-1 but
+ NAME is not the same length.
+
+Sat Aug 25 16:17:14 1990 Roland McGrath (roland at geech)
+
+ * job.c (construct_command_argv_internal): Use a static char array for
+ a constant, since old C has no auto aggregate initializers.
+
+Thu Aug 23 16:11:03 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * job.c (search_path): If PATH is nil or "" use a default path.
+
+Wed Aug 22 01:05:32 1990 Roland McGrath (roland at churchy.ai.mit.edu)
+
+ * Version 3.58.9.
+
+ * job.c (exec_command): Don't take PATH and SHELL args. Get them from
+ ENVP.
+ (child_execute_job): Don't take FILE arg, and don't pass path and shell
+ to exec_command.
+ (start_job): Don't pass FILE arg to child_execute_job.
+ * function.c (expand_function: `shell'): Ditto.
+ * main.c (main): Don't pass path and shell to exec_command.
+
+Fri Aug 17 23:17:27 1990 Roland McGrath (roland at geech)
+
+ * job.c (construct_command_argv_internal): New fn broken out of
+ construct_command_argv. Takes strings SHELL and IFS instead of doing
+ variable expansion for them. Recurse to make an argv for SHELL,
+ passing SHELL==0. When SHELL==0, don't recurse for shell argv; make a
+ simple one using /bin/sh.
+ (construct_command_argv): Do the variable expansions and call above.
+
+Thu Aug 16 19:03:14 1990 Roland McGrath (roland at geech)
+
+ * read.c (multi_glob): For ~USER/FILE, if USER isn't found, don't
+ change the file name at all.
+
+Tue Aug 7 18:33:28 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * function.c (expand_function: `suffix'/`notdir'): Don't kill the last
+ space if we never wrote one.
+
+ * function.c (expand_function: `suffix'): Retain the dot, like the
+ documentation says.
+
+Mon Aug 6 14:35:06 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.58.8.
+
+ * main.c (decode_switches): For positive_int and floating cases, move
+ SW past the arg (and don't set it to ""), so another switch can follow.
+
+Fri Aug 3 00:43:15 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * job.c (child_execute_job): Use unblock_signals instead of
+ push_signals_blocked_p (0).
+
+ * main.c (fatal_signal_mask): New variable, mask of signals caught with
+ fatal_error_signal.
+ (main): Set it.
+ * job.c ({block,unblock}_children): Renamed to {block,unblock}_signals.
+ Block/unblock both child signal and signals in fatal_signal_mask.
+ (children_blocked_p_{stack,max,depth}, {push,pop}_children_blocked_p):
+ Renamed from children to signals. Use {block,unblock}_signals instead
+ of {block,unblock}_children.
+ * commands.c (fatal_error_signal), job.c (wait_for_children, new_job,
+ child_execute_job, main, log_working_directory), function.c
+ (expand_function: `shell'), job.h: Rename {push,pop}_children_blocked_p
+ to {push,pop}_signals_blocked_p.
+ * job.c (child_handler): Call {block,unblock}_signals instead of just
+ {block,unblock}_remote_children. We need to block the fatal signals.
+
+Thu Aug 2 22:41:06 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * main.c, function.c: Fixed typos in comments.
+
+ * file.c (print_file_data_base): Fix computation of avg files/bucket.
+
+Tue Jul 31 22:11:14 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.58.7.
+
+Wed Jul 25 16:32:38 1990 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * arscan.c (ar_name_equal): Fixed to really do it right.
+ (ar_member_pos): Fixed order of args.
+ * ar.c (ar_member_date_1): Ditto.
+
+Fri Jul 20 15:30:26 1990 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * arscan.c (ar_name_equal): Rewritten. Accounts for a possible
+ trailing slash in MEM.
+
+ * remake.c (f_mtime): Keep track of whether ARNAME is used and free it
+ if not. Also free MEMNAME.
+ * ar.c (ar_member_date, ar_touch): Ditto.
+
+ * arscan.c (arscan) [HPUX or hpux]: Treat same as USGr3 PORTAR==1.
+
+ * make.h: If NSIG is not defined, but _NSIG is, #define NSIG _NSIG.
+
+ * compatMakefile: Don't use $* in explicit rules.
+
+ * default.c (default_variables: "PREPROCESS.S"): Include $(CPPFLAGS).
+
+ * remake.c (f_mtime): If FILE is an ar ref, get the member modtime.
+
+ * function.c (string_glob): Terminate the string properly when it's
+ empty.
+
+Wed Jul 18 11:26:56 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.58.6.
+
+ * commands.c (set_file_variables): Fixed computation for ^F/?F elt len.
+
+Sat Jul 14 13:41:24 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * job.c (construct_command_argv): Always use
+ allocated_variable_expand_for_file instead of variable_expand_for_file
+ because we might be called from inside a variable expansion (for the
+ `shell' function).
+
+ * function.c (expand_function: `shell'): Free the arglist's storage
+ correctly. construct_command_argv only allocates ARGV and ARGV[0].
+
+ * job.c (children_blocked_p_idx): Renamed to children_blocked_p_depth.
+ (push_children_blocked_p, pop_children_blocked_p): Use ..._depth
+ instead of ..._idx, and do it right!
+
+Wed Jul 11 15:35:43 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.h (SIGNAL): New macro to replace `signal' calls. Does arg and
+ ret value casts to (void *) #ifdef __STDC__ to avoid conflicts btwn
+ ANSI and BSD `signal' and handler types.
+ * main.c (main), job.c (child_handler): Use it.
+
+Fri Jul 6 00:00:38 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * ar.c (ar_member_date, ar_touch): Pass 2nd arg to f_mtime.
+
+ * read.c (read_makefile): Search the include path for MAKEFILES
+ variable makefiles (TYPE == 1), like it says in the manual.
+
+ * file.h (struct file), main.c (struct command_switch): Remove trailing
+ commas from enums.
+
+ * commands.c (execute_file_commands): Remove unused variables.
+ * commands.h: Declare chop_commands.
+ * make.h: Declare uniquize_deps.
+ * main.c (main): Remove unused variable.
+ (decode_switches): Remove unused label.
+ * remake.c: Include "ar.h" for ar_parse_name decl.
+ * implicit.c (try_implicit_rule): Remove unused variable.
+ * function.c (expand_function: `shell'): Declare fork, pipe.
+ * ar.c: Declare ar_name_equal.
+
+ * GNUmakefile: If using gcc, add warning flags to CFLAGS.
+
+ * remake.c: Remove decl of ar_member_date, since it's done in make.h.
+
+ * remake.c (f_mtime): For ar refs, allow the archive to be found via
+ VPATH search if we're searching, and change the ar ref accordingly.
+
+ * ar.c (ar_parse_name): New global fn to parse archive-member
+ references into the archive and member names.
+ (ar_member_date, ar_touch): Use it.
+ * make.h: Declare it.
+
+ * remake.c (f_mtime): After doing rename_file, do check_renamed instead
+ of assuming rename_file will always set FILE->renamed (which it won't).
+
+ * vpath.c (selective_vpath_search): Only accept prospective files that
+ don't actually exist yet are mentioned in a makefile if the file we are
+ searching for isn't a target.
+
+Wed Jul 4 04:11:55 1990 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * remake.c (update_goal_chain): Do check_renamed after calling
+ file_mtime.
+ (check_dep): Ditto after update_file.
+
+ * file.c (rename_file): Prettied up long message for merging cmds.
+
+ * remake.c (update_file_1): Get each dep file's modtime, and allow for
+ it being renamed, before checking for a circular dep, since a renaming
+ may have introduced one.
+
+Tue Jul 3 18:15:01 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * ar.c (ar_touch): Don't free ARNAME since enter_file holds onto the
+ storage.
+
+ * function.c (string_glob): Don't leave a trailing space.
+
+ * read.c (do_define): Allow leading whitespace before `endef'.
+
+Mon Jul 2 14:10:16 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * implicit.c (pattern_search): No longer take NAME arg. Instead take
+ ARCHIVE flag. If ARCHIVE is nonzero, FILE->name is of the form
+ "LIB(MEMBER)"; rule for "(MEMBER)" is searched for, and LASTSLASH is
+ set to nil. Since NAME was only non-nil when it was the archive member
+ name passed by try_implicit_rule, this change easily allows turning off
+ LASTSLASH checking for archive members without excessive kludgery.
+ (try_implicit_rule): Pass ARCHIVE flag instead of file name.
+
+ * Version 3.58.5.
+
+ * commands./c (set_file_variables): Don't kill last char of $(^D) elts.
+
+Sat Jun 30 00:53:38 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * ar.c (ar_member_date): Don't free ARNAME since enter_file holds onto
+ the storage.
+
+ * arscan.c (ar_scan) [sun386 && PORTAR == 1]: Treat like USGr3.
+
+Wed Jun 27 14:38:49 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * main.c (main): Put a newline on the debugging message when deciding
+ not to remake a makefile to avoid a possible loop.
+ Only decide not to remake makefiles that have commands (as well as
+ being targets and having no deps).
+
+Fri Jun 22 12:35:37 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * default.c (default_variables): Define `LINK.s' and `LINK.S'.
+ (default_suffix_rules): Define .S.o rule.
+
+ * job.c (construct_command_argv): If we decide to go the slow route,
+ free all storage for the chopped args.
+ (start_job): Free the argument list's storage correctly.
+ construct_command_argv only allocates ARGV and ARGV[0].
+
+Tue Jun 19 18:27:43 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.58.4.
+
+Fri Jun 15 21:12:10 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * glob.c: New version from ai-lab which doesn't do [^abc].
+
+Thu Jun 7 00:30:46 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * dir.c: Copied dirent vs direct et al mess from glob.c.
+
+ * glob.c: Replaced with updated version from djm.
+ * glob.c: Check macro DIRENT instead of _POSIX_SOURCE for <dirent.h>.
+ __GNU_LIBRARY__ implies DIRENT and STDC_HEADERS.
+
+Thu May 31 22:19:49 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * vpath.c (vpath_search): Don't stop the loop if a pattern matches but
+ the search fails. All matching patterns have their paths searched
+ (like it says in the manual).
+
+ * make.texinfo (Rules: Directory Search: Selective Search): Say that
+ multiple `vpath' directives with the same pattern DO accumulate, not
+ supersede earlier ones.
+
+ * vpath.c (print_vpath_data_base): Increment the count of vpaths on
+ each loop iteration, rather than letting it stay zero.
+
+ * Version 3.58.3.
+
+ * job.c (block_children, unblock_children): Made static.
+ (push_children_blocked_p, pop_children_blocked_p): New functions to
+ push and pop whether children are blocked or not.
+ * job.h: Declare push_children_blocked_p, pop_children_blocked_p and
+ not block_children, unblock_children.
+ * commands.c (fatal_error_signal), job.c (wait_for_children, new_job,
+ child_execute_job), main.c (main, log_working_directory): Use sequences
+ of push_children_blocked_p (1) and pop_children_blocked_p () instead of
+ explicitly blocking and unblocking children.
+ * function.c (expand_function: `shell'): Don't unblock children. The
+ push-pop sequence in wait_for_children makes it unnecessary.
+
+Tue May 29 21:30:00 1990 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * read.c (do_define): Don't include the last newline in the definition.
+
+ * function.c (expand_function: `shell'): Call construct_command_argv
+ before forking and don't fork if it returns nil. Free the argument
+ list's storage before finishing.
+
+ * job.c (start_job): Free the storage for the child's argument list
+ in the parent side of the fork after the child has been spawned.
+
+ * job.c (start_job): If construct_command_argv returns nil, go to the
+ next command line.
+
+ * job.c (construct_command_argv): Use the shell if the command contains
+ an unterminated quote.
+
+Wed May 23 19:54:10 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.58.2.
+
+ * read.c (read_makefile): Parse "override define" correctly.
+
+Thu May 17 15:25:58 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * arscan.c [USG]: Don't declare memcpy and memcmp. <memory.h> should
+ do this anyway (and lack of declarations is harmless).
+
+ * remote-customs.c: Renamed to remote-cstms.c for System V.
+ * remote.c [CUSTOMS]: Changed accordingly.
+
+Sun May 13 14:38:39 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * GNUmakefile: Use same cmds for doc tar.Z as for dist tar.Z (so the
+ contents go in make-N.NN).
+
+Thu Apr 26 19:33:25 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.58.1.
+
+Wed Apr 25 20:27:52 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * job.c (init_siglist): Don't do SIGUSR1 and SIGUSR2 if they are the
+ same as SIGIO and SIGURG (true on Cray).
+
+Tue Apr 24 20:26:41 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * arscan.c (ar_scan): Do behavior for PORTAR == 1 and USGr3 also
+ #ifdef APOLLO.
+
+Wed Apr 11 10:00:39 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * job.c (exec_command): Set the effective GID to the real GID. Somehow
+ this code got lost.
+
+ * implicit.c (pattern_search): Use the right index variable when
+ seeing if we need to expand FILE->also_make.
+
+Sun Mar 4 09:18:58 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.58.0.
+
+ * remake.c (remake_file): Treat non-targets without commands under -t
+ the same as anything else without commands.
+
+Sat Feb 24 17:46:04 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * default.c (default_variables: PREPROCESS.S): Removed $< from defn.
+
+ * main.c (main): Ignore arguments that are the empty string, rather
+ than feeding them to enter_file and barfing therein.
+
+Wed Feb 14 16:28:37 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * main.c (main): Call construct_include_path after doing chdirs.
+
+Thu Feb 8 13:43:44 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.58.
+
+Sat Feb 3 22:06:55 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.57.7.
+
+ * make.texinfo (Implicit: Catalogue of Rules): For RCS, noted that
+ working files are never overwritten by the default rule.
+
+Thu Feb 1 17:27:54 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * rule.c (count_implicit_rule_limits): Redid loop control to not run
+ twice on freed rules.
+
+ * GNUmakefile: Made `.dep' files be architecture-specific too.
+
+ * main.c (main, log_working_directory) [USG]: Block children around
+ calls to `getwd' (actually `getcwd' on USG), because that function
+ sometimes spawns a child running /bin/pwd on USG.
+
+Tue Jan 30 14:02:50 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * function.c (subst_expand): Pay attention to SUFFIX_ONLY, putz.
+
+Wed Jan 24 21:03:29 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * make.man: Fixed repeated word.
+
+ * make.texinfo (Missing): Reworded a buggy sentence.
+
+Mon Jan 22 12:39:22 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * main.c (print_version): Added 1990 to copyright notice.
+
+ * Version 3.57.6.
+
+Sat Jan 20 11:52:01 1990 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * file.c (rename_file): Don't free the storage for the old name, since
+ it might not have been malloc'd.
+
+ * job.c (construct_command_argv): Call
+ allocated_variable_expand_for_file instead of variable_expand_for_file
+ to expand `$(SHELL)'.
+
+ * make.texinfo (Bugs): Change address from roland@wheaties.ai.mit.edu
+ to roland@prep.ai.mit.edu.
+
+Tue Jan 16 19:22:33 1990 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.57.5.
+
+Sun Jan 14 16:48:01 1990 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * job.c (start_job): Only call wait_to_start_job for the first command
+ line in each sequence.
+
+Thu Jan 4 14:27:20 1990 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * load.c [LDAV_BASED] (wait_to_start_job): Loop while job_slots_used >
+ 0, not > 1.
+
+ * job.c (search_path): Don't return a pointer to local storage.
+ Allocate data space for the pathname instead.
+
+ * function.c (expand_function: `shell'): Don't write garbage if the
+ child wrote no output.
+
+Wed Jan 3 15:28:30 1990 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.57.4.
+
+ * file.h (struct file): New member `renamed', a `struct file *' that is
+ the place this file has been renamed to (or nil).
+ (check_renamed): Macro to check for a file having been renamed.
+ Dereferences the renaming and sets the given variable.
+ * file.c (rename_file): Completely rewritten. Renames in place if
+ possible, or moves FILE to a different hash bucket if there is no
+ existing file with the new name. If there is an existing file with the
+ new name, FILE is merged into it and FILE->renamed is set to point to
+ it.
+ * variable.c (merge_variable_sets): New fn to merge two variable sets.
+ (merge_variable_set_lists): New fn to merge two variable set lists.
+ * variable.h: Declare merge_variable_set_lists.
+ * remake.c (update_file_1, check_dep): Run `check_renamed' after
+ calling file_mtime, check_dep.
+ (update_file): Same after update_file_1.
+ (update_goal_chain, update_file_1, check_dep): Same after update_file.
+
+ * read.c (uniquize_deps): New fn, broken out of record_files, to remove
+ duplicate deps from a chain.
+ (record_files): Use it.
+ * implicit.c (pattern_search): Use uniquize_deps.
+
+ * file.h (file_mtime_1): New macro, like file_mtime, but take second
+ arg, passed to f_mtime.
+ (file_mtime): Implement as file_mtime_1 (file, 1).
+ (file_mtime_no_search): New macro: file_mtime (file, 0).
+ * remake.c (f_mtime): Take new arg SEARCH. Only do VPATH and `-lNAME'
+ searching if it is nonzero.
+ * main.c (main): Use file_mtime_no_search for makefiles.
+ * remake.c (update_goal_chain): Use file_mtime_no_search if MAKEFILES.
+
+ * main.c (printed_version): New variable, init'd to zero.
+ (print_version): Set it to nonzero before returning.
+ (die): If -v and !printed_version, call print_version before clean up
+ and death.
+
+ * main.c (log_working_directory): Keep track of whether or not the
+ "Entering" message has been printed, and return without printing the
+ "Leaving" message if not.
+
+ * main.c (decode_switches): Don't complain about missing args before
+ checking for a noarg_value elt in the command_switch structure.
+
+Tue Jan 2 15:41:08 1990 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.texinfo (Commands: Recursion: Options/Recursion): Document
+ special case of -j.
+
+ * make.texinfo, main.c, job.c: Changed copyright notices to include
+ 1990.
+
+ * make.texinfo (Top): Fixed introductory paragraph, which said that
+ `make' itself (instead of the manual) has various chapters.
+ (Variables: Advanced: Substitution Refs): When pxref'ing about
+ `patsubst', use node `Text Functions', not `Functions'.
+ Add an xref about `patsubst' after description of $(var:a%b=c%d).
+ (Functions: Syntax of Functions): Explain why mixing delimiters in
+ function/var refs is unwise. Clarify fn arg evaluation order.
+ (Options): Reworded sentence about `-e'.
+ (Implicit: Implicit Variables): Don't say `RM' is unused.
+ Say the dflt values for the flag vars is empty unless otherwise noted,
+ since some have defaults.
+ (Implicit: Pattern Rules: Pattern Examples): Clarified use of $< and $@
+ in first example.
+ (Implicit: Last Resort): Don't say the .DEFAULT example creates files
+ "silently". It's automatic, but not silent.
+ (Implicit: Search Algorithm): Fixed confusing ungrammatical sentence
+ for item 5.1.
+ (Archives: Archive Update): Added missing `next' pointer.
+ (Archives: Archive Symbols): Note that GNU `ar' deals with this
+ automatically.
+
+ * job.c (search_path): New fn, to search for an executable file in a
+ search path (broken out of exec_command).
+ (exec_command): Take fourth arg, the shell program to use (if
+ necessary). Use search_path for the program, and the shell program.
+ Pass args "file args ..." to shell program (with no -c), where FILE is
+ the full pathname of the program (script) to be run.
+ (child_execute_job): Pass shell program to exec_command.
+ * main.c (main): Ditto.
+
+ * main.c (main): Don't write a message if exec_command returns, because
+ it will never return.
+
+Fri Dec 22 16:19:58 1989 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * default.c (default_variables: "LINK.cc"): Use $(C++FLAGS) instead of
+ $(CFLAGS).
+
+Wed Dec 20 09:58:48 1989 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * job.c (new_job): If start_job set the child's `command_state' to
+ `cs_finished', call notice_finished_file.
+
+Sun Dec 17 19:45:41 1989 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * Version 3.57.3.
+
+Wed Dec 13 17:57:12 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * rule.c (convert_to_pattern): Accept files with dependencies as
+ suffix rules.
+
+Thu Nov 30 15:47:13 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.57.2.
+
+ * function.c (expand_function: `shell'): Don't clobber BUFFER and then
+ try to free it.
+
+ * remake.c (update_file_1): Took code to force remake of nonexistent
+ deps out of #if 0, and changed the test to nonexistent non-intermediate
+ deps. In version 4, I think removing this test completely will
+ implement the new feature that if a: b and b: c and c is newer than a,
+ b need not be remade.
+
+Sun Nov 26 16:12:41 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * compatMakefile (load.o, remote.o): Use $*.c instead of explicit file
+ names so that using VPATH works.
+
+Tue Nov 21 14:57:18 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.57.1.
+
+Fri Nov 10 03:28:40 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remake.c (check_dep): Set *MUST_MAKE_PTR if FILE does not exist after
+ being updated. (The exact opposite test was here before; why???)
+ (update_file_1): Set a dep's `changed' member after updating it if it
+ is phony and has commands (because they will then always be executed).
+
+Thu Nov 9 13:47:12 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * load.c [UMAX]: #ifdef UMAX_43 include different headers for the
+ `inq_stats' call.
+ * compatMakefile (LOAD_AVG): Document UMAX_43.
+
+ * Version 3.57.0.
+
+ * commands.c (chop_commands): New function to chop commands into lines.
+ * job.c (new_job): Break that code out, and call chop_commands.
+ * remake.c (remake_file): Call chop_commands before looking at
+ FILE->cmds->any_recurse.
+
+ * make.texinfo (Running: Goals): Don't say that the default target
+ won't be taken from an included makefile.
+
+ * remake.c (update_file_1): #if 0 out setting MUST_MAKE if a dep
+ doesn't exist.
+
+Fri Nov 3 15:53:03 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.57.
+
+ * variable.c (try_variable_definition): Don't calculate useless value.
+
+ * main.c (define_makeflags): Fixed -j propagation.
+
+ * commands.c (execute_file_commands): Removed unused variable.
+
+Sun Oct 29 11:11:15 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (execute_file_commands): If the commands are empty, call
+ notice_finished_file before returning.
+
+Sat Oct 28 23:06:32 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remake.c (update_file_1): Don't always update a target that has no
+ deps. Only do this for double-colon targets.
+
+Wed Oct 25 16:36:16 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * main.c (main) [hpux]: hpux == HPUX.
+ * compatMakefile (defines): Document that HPUX should be defined.
+
+Tue Oct 24 19:19:48 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.8.
+
+ * job.c (exec_command): Fixed what mode bits are checked.
+
+ * remake.c (update_file_1): "No cmds and no deps actually changed"
+ loses if ! FILE->is_target.
+
+ * make.texinfo (Variables: Setting): Don't say that spaces after a
+ variable definition are ignored (since they aren't).
+
+Mon Oct 23 14:34:23 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.7.
+
+ * remake.c (update_file_1): If, after being updated, any dependency
+ does not exist, remake the target.
+
+ * remake.c (update_file_1): Always update if FILE has commands but no
+ deps.
+
+ * commands.c (execute_file_commands): If we return early because there
+ are no commands, set FILE->updated.
+
+Thu Oct 19 18:47:37 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * arscan.c (ar_scan) [M_XENIX]: Don't run atoi or atol on the
+ `struct ar_hdr' members that are int or long int on Xenix.
+
+Sat Oct 14 10:43:03 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * arscan.c (ar_scan): Cosmetic clean ups.
+ (ar_name_equal): New function to compare names, handling truncated
+ member names and special `.o' truncation.
+ (ar_member_pos): Use ar_name_equal.
+ * ar.c (ar_member_date_1): Use ar_name_equal.
+
+ * Version 3.56.6.
+
+ * file.h (struct file): Made `update_status' a `short int', and moved
+ it before `command_state' so the bitfields can be packed better.
+
+ * remake.c (files_remade): Made global.
+ (notice_finished_file): Don't increment files_remade.
+ * job.c (new_job): Do.
+
+ * job.c (start_job): Don't return a value. Always set
+ CHILD->file->command_state to either cs_running or cs_finished.
+ (new_job, child_handler): Don't expect start_job to return a value.
+ Instead, look at the file's command_state.
+
+ * commands.c (chop_commands): Merged into job.c (new_job).
+ * commands.h: Don't declare chop_commands.
+
+ * job.c (start_job): Made static.
+ (new_job): New function to create a `struct child' and call start_job.
+ (free_child): New function to free a `struct child'.
+ (child_handler, new_job): Call it.
+ * job.h: Don't declare start_job. Do declare new_job.
+ * commands.c (execute_file_commands): Call new_job.
+
+ * commands.c (execute_file_commands): Don't set FILE->update_status if
+ start_job fails.
+
+ * function.c (expand_function): Don't use `reading_filename' and
+ `reading_lineno_ptr' if they're nil.
+
+Fri Oct 13 18:16:00 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * read.c (find_semicolon): New function to look for an unquoted ; not
+ preceded by an unquoted # in a string.
+ (read_makefile): Call it before expanding the line. If it finds a ;,
+ cut the line short there before expanding it. If not, call it again
+ after expanding.
+
+ * commands.c (execute_file_commands): Don't check FILE->command_state.
+ We won't get called unless it's cs_not_started.
+
+ * read.c (read_makefile): Call collapse_line on the variable-expanded
+ rule line after checking for ; and #.
+
+ * job.c (start_job): When there are no more commands, always return 0.
+ * commands.c (execute_file_commands): Don't put the new child in the
+ `children' chain unless FILE->command_state is cs_running.
+
+ * read.c (read_makefile): Rewrote ;-handling to only do it once (why
+ did I do it twice??) and to check for a # before the ;.
+
+ * job.c (start_job): Set CHILD->file->update_status to 0 when we run
+ out of commands. Set it to 1 before returning failure.
+ (child_handler): Don't set C->file->update_status to 0 when start_job
+ returns success and commands are not running.
+
+ * read.c (read_makefile): If there is a # before the ; for commands,
+ forget the ; and commands.
+
+Thu Oct 12 15:48:16 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * job.c (child_execute_job): Pass -c to the shell.
+
+Wed Oct 11 18:41:10 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.5.
+
+ * main.c (define_makeflags): Cleaned up to keep better track of dashes
+ written, etc.
+
+ * function.c (expand_function: `shell'): When converting newlines to
+ spaces in output, search with `index' calls rather than a simple loop.
+
+ * main.c (main): Make sure stdout is line-buffered.
+
+ * main.c (decode_switches): Always check for missing switch arg.
+
+Mon Oct 9 17:17:23 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.4.
+
+Sat Oct 7 00:32:25 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (set_file_variables): #ifdef NO_ARCHIVES, still set $@ and
+ $%.
+
+ * commands.c (set_file_variables): Include a trailing slash in the
+ directory variables (@D, etc.).
+
+ * job.c (child_handler): Call notice_finished_file after changing a
+ child's state to `cs_finished'.
+ * remake.c (update_file_1): Don't call notice_finished_file if
+ FILE->command_state == cs_finished.
+
+Wed Oct 4 16:09:33 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.3.
+
+Tue Oct 3 21:09:51 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * read.c (read_all_makefiles): When setting elements of MAKEFILES from
+ the contents of read_makefiles, make sure we're using the right
+ element.
+
+ * dir.c, glob.c [USGr3 || DIRENT]: Don't define d_ino as d_fileno.
+
+ * Version 3.56.2.
+
+ * remake.c (update_file_1): Return zero after calling remake_file if
+ FILE->command_state != cs_finished. Test update_status thoroughly.
+
+ * commands.c (execute_file_commands): Don't call notice_finished_file.
+
+ * remake.c (remake_file): Return immediately after calling
+ execute_file_commands.
+
+Sat Sep 30 14:57:05 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.1 (alpha).
+
+ * file.h (struct file): Made `update_status' not be a bitfield, since
+ some broken compilers don't handle it right.
+
+ * function.c (expand_function: `join'): Don't clobber the pointers and
+ then try to free them.
+
+ * job.c (exec_command): Fixed & vs = precedence problem.
+
+Thu Sep 28 17:29:56 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remake.c (update_file_1): Fixed typo in debugging output.
+
+ * remake.c (library_file_mtime): Search for /usr/local/lib/libLIB.a
+ after /usr/lib/libLIB.a.
+
+Tue Sep 26 16:07:58 1989 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * read.c (conditional_line): For `ifeq (a, b)', swallow space after the
+ comma.
+
+Sun Sep 24 13:25:32 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * function.c (patsubst_function): If BY_WORD and the match is not a
+ full word, update the text pointer correctly.
+
+ * function.c (expand_function: `word'): Don't lose track of the second
+ arg's expansion and free something else instead.
+
+Fri Sep 22 16:15:29 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.56.
+
+Thu Sep 21 14:28:42 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * main.c (main): Make an array of the mtimes of the makefiles before
+ updating them, and compare their file_mtimes against this later. Don't
+ re-exec if a makefile was successfully updated but didn't change. If a
+ makefile failed to be remade and no longer exists, die. If a makefile
+ failed to be remade, but changed anyway, re-exec. If a makefile failed
+ to be remade, but is unchanged, continue on.
+
+Wed Sep 20 18:02:07 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.55.6.
+
+ * implicit.c (pattern_search): Maintain an array CHECK_LASTSLASH of the
+ CHECK_LASTSLASH flag values used to match each member of TRYRULES.
+ When making FILE->stem, if CHECKED_LASTSLASH[FOUNDRULE], prepend the
+ part of FILENAME before LASTSLASH.
+
+Tue Sep 19 17:44:08 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * dir.c (dir_file_exists_p): Check for FILENAME being nil before
+ checking for it being "".
+
+ * main.c (define_makeflags): Fixed test for whether a flag/flag_off
+ option was non-default. Also changed to generate a string that Unix
+ Make will grok (except for FP/int values and new flags).
+
+ * job.c (child_execute_job): Don't use the shell's -c option.
+ Also fixed an off-by-one bug in the ARGV -> shell arg list copying.
+
+Mon Sep 18 15:17:31 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.55.5.
+
+ * read.c (parse_file_seq): Check the beginning of the file name for a
+ `./', not the two chars after the end of the name (Q rather than P).
+
+ * job.c (child_execute_job): Include all of ARGV in the arg list for
+ the shell.
+
+ * main.c (define_makeflags): Don't include floating and positive_int
+ options in !PF.
+
+ * job.c (exec_command): Set the effective gid to the real gid before
+ execing.
+
+ * job.c (child_execute_job): Don't clobber the arg list when execing
+ the shell.
+
+Sun Sep 17 15:27:19 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * main.c (define_makeflags): Moved all the checking inside the switch.
+
+ * load.c [LDAV_BASED] (load_average): When we can't get the load
+ average, return zero instead of running off the end.
+
+ * file.c: Include variables.h.
+ * job.c: Declare dup2 and {block,unblock}_remote_children.
+ * file.h: Declare f_mtime.
+ * job.c: Don't declare construct_command_argv, since job.h does.
+ * function.c, main.c, load.c, remake.c: Include job.h.
+ * load.c [LDAV_BASED] (load_average): Declare nlist.
+ * variable.h: Declare print_file_variables.
+ * job.c [!USG]: Don't declare sigsetmask.
+ [!USG]: Declare getdtablesize.
+ Don't declare load_average. Do declare wait_to_start_job.
+ Declare vfork, gete[gu]id, execve.
+ * commands.c: Declare remote_kill, getpid.
+ * make.h: Declare kill, exit, sigblock, pipe, close, ctime, open,
+ lseek, read.
+ * make.h [not USG]: Declare sigsetmask.
+ * job.h: Declare wait_for_children and {block,unblock}_children.
+
+ * dir.c (dir_file_exists_p): If FILENAME is nil, read in the whole
+ directory.
+ (find_directory): When we want to read in the whole directory, call
+ dir_file_exists_p with nil instead of "".
+
+ * file.h (struct file), job.h (struct child),
+ variable.h (struct variable): Use bitfields for flags.
+ * make.h (ENUM_BITFIELD): If GCC or #ifdef ENUM_BITFIELDS, define as
+ :BITS, else empty.
+ * compatMakefile (defines): Document ENUM_BITFIELDS.
+
+Sat Sep 16 12:38:58 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.55.4 (alpha).
+
+ * GNUmakefile (dist): Depend on default and doc.
+
+ * load.c [LDAV_BASED]: Include <nlist.h> rather than <a.out.h>; #ifdef
+ NLIST_NAME_UNION, use n_un.n_name instead of n_name.
+ * compatMakefile (LOAD_AVG): Document NLIST_NAME_UNION.
+
+ * job.c [USG-ish]: Don't redefine WIF{SIGNALED,EXITED} if they're
+ already defined.
+
+Fri Sep 15 13:59:42 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * glob.c, dir.c [USGr3 or DIRENT]: If neither d_ino, nor d_fileno is
+ defined, define d_ino as d_fileno.
+
+Thu Sep 14 18:29:38 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * job.c: Don't declare exec_command static.
+
+ * make.texinfo (Name Index): Changed title to include directives.
+
+ * Version 3.55.3 (alpha).
+
+ * make.texinfo (Running: Options): Document -e.
+
+ * main.c (main): Always give imported environment variables origin
+ `o_env'.
+ * variable.c (define_variable_in_set): Under -e, if ORIGIN, or an
+ existing variable's origin, is `o_env', make it `o_env_override'.
+
+ * load.c: Use the symbol KERNEL_FILE_NAME instead of KERNEL_FILE.
+ * compatMakefile: Changed the comment for `LOAD_AVG' accordingly.
+
+Thu Sep 7 16:46:26 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.55.2 (alpha).
+
+ * variable.c (print_variable_set), rule.c (print_rule_data_base),
+ file.c (print_file_data_base): If NO_FLOAT is defined, don't use
+ floating-point for printing statistics.
+ * compatMakefile (defines): Document NO_FLOAT.
+
+ * make.h (HASH): New macro to add the hashing value of one char to a
+ variable.c.
+ * file.c (lookup_file, enter_file, rename_file): Use it.
+ * dir.c (find_directory, dir_file_exists_p, file_impossible_p): Ditto.
+ * variable.c (define_variable_in_set, lookup_variable): Same here.
+
+ * variable.c, file.c, dir.c: Don't define *_BUCKETS if they are already
+ defined.
+
+ * compatMakefile (defines): Added comment about defining NO_ARCHIVES.
+ (ARCHIVES, ARCHIVES_SRC): New variables for {ar,arscan}.[oc].
+ (objs, srcs): Use $(ARCHIVES) and $(ARCHIVES_SRC).
+ * commands.c (set_file_variables), dir.c (file_exists_p),
+ remake.c (touch_file, name_mtime), implicit.c (try_implicit_rule,
+ pattern_search), make.h: If NO_ARCHIVES is #defined, don't do any
+ archive stuff.
+
+ * commands.c (set_file_variables): Don't kill the last char of
+ directory names in $([@*<%?^]D).
+
+Wed Sep 6 15:23:11 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * default.c (default_terminal_rules {%:: %,v}, {%:: RCS/%,v}): Don't
+ run co if the target exists.
+
+ * glob.c (glob_match): [!xyz], rather than [^xyz], means none of [xyz].
+
+ * glob.c: Misc minor cosmetic changes.
+
+Tue Sep 5 14:49:56 1989 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * load.c [LDAV_BASED] (load_average): Check for == -1, rather than < 0
+ to see if lseek fails. On some systems, `avenrun' is at an offset >
+ (2**31)-1, and lseek succeeds, returning a negative value.
+
+Mon Sep 4 11:07:58 1989 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * rule.c (new_pattern_rule): Return `int' instead of `void': nonzero if
+ the passed rule was used, zero if not.
+ (install_pattern_rule): Pay attention to the return from
+ new_pattern_rule, and don't set the rule's `terminal' flag or give it
+ commands unless it's used.
+ (create_pattern_rule): Same idea.
+
+ * dir.c (find_directory): Removed unused variable.
+
+ * commands.c (execute_file_commands): Removed unused variable.
+
+ * read.c (record_files): Don't use NAME after freeing it.
+
+Sat Sep 2 00:33:19 1989 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * Version 3.55.1 (alpha).
+
+ * function.c (string_glob): Don't add spaces after file names that
+ aren't added. (Also means don't add spaces without checking the size
+ of the buffer.)
+
+ * remake.c (update_goal_chain): Don't remove makefiles with cmds and no
+ deps from the chain.
+ * main.c (main): Do it here, before calling update_goal_chain.
+
+ * remake.c (update_goal_chain): When updating fails, change STATUS even
+ if MAKEFILES is set. Also stop remaking when updating fails if not
+ under -k and MAKEFILES is not set.
+
+ * remake.c (remake_file, update_file_1, notice_finished_file),
+ commands.c (execute_file_commands), make.h, commands.h: The functions
+ remake_file, notice_finished_file, and execute_file_commands no longer
+ return values, and their callers no longer expect values returned.
+
+ * remake.c (notice_finished_file): Don't set FILE's modtime to now if
+ it is a non-target with no commands.
+
+Fri Sep 1 00:04:39 1989 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * read.c (read_all_makefiles): After freeing each element on MAKEFILES,
+ replace it with the name stored in read_makefiles by read_makefile.
+
+ * remake.c (update_file_1): Don't decide not to remake if FILE has no
+ cmds and no deps actually changed if FILE doesn't have any deps.
+
+ * file.c (remove_intermediate): Remove precious files that also have
+ the `dontcare' flag set.
+
+ * remake.c (update_file_1): Don't always remake if FILE has cmds but no
+ deps; only if FILE is double-colon. (I don't know why this should be
+ done for double-colon targets, but that's what Unix make does.)
+
+ * load.c [LDAV_BASED] (load_average): Write error messages if the
+ various system calls fail. Keep track of if we've failed before.
+ The first time we fail, write a message saying -l won't be enforced.
+ The first time we succeed after having failed, write a message saying
+ -l will be enforced again.
+
+ * remake.c [USG]: Don't #include <sys/file.h>
+
+ * load.c [generic Unix LDAV_BASED]: #include <fcntl.h> #ifdef USG,
+ else <sys/file.h> instead.
+
+ * job.c [USG && !USGr3 && !HAVE_DUP2]: Remove redundant
+ #include <errno.h> and declaration of `errno'.
+ [...] (dup2): Fixed so it won't always lose.
+
+ * default.c (default_suffix_rules: .texinfo.dvi): Copy, rather than
+ move, the aux and index files, so the TeX run can use them.
+
+ * compatMakefile: Remove redundant comment.
+
+ * load.c [generic Unix LDAV_BASED]: Include <a.out.h> instead of
+ <nlist.h>, since the `struct nlist' declaration in <nlist.h> varies
+ more than the one in <a.out.h>.
+ (load_average): Use the `n_un.n_name' field of the `struct nlist',
+ since the <a.out.h> declaration uses the union.
+
+ * main.c (main): For the temporary files made for stdin makefiles, set
+ the `intermediate' and `dontcare' flags.
+ * file.c (remove_intermediates): Don't print any messages for files
+ whose `dontcare' flag is set. (The only files that will be
+ intermediate and `dontcare' will be the temporary files made for stdin
+ makefiles.)
+
+ * job.c (exec_command): Made global.
+ * job.h: Declare it.
+ * main.c (main): Use exec_command when re-execing.
+
+ * make.h: Declare environ.
+ * make.c: Don't.
+
+ * job.c (child_execute_job): New function to perform everything done in
+ the child side of a fork (for a job or `shell' function).
+ (start_job): Call it.
+ * job.h: Declare construct_command_argv and child_execute_job.
+ * function.c (expand_function: `shell'): Use child_execute_job.
+
+Thu Aug 31 18:42:51 1989 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * function.c (expand_function: `shell'): Remove a trailing newline
+ instead of turning it into a space.
+
+ * main.c (main): Do init_siglist #ifdef HAVE_SIGLIST.
+
+ * job.c [WTERMSIG || (USG && !HAVE_SYS_WAIT)]: Test each W* macro
+ separately and define all those that aren't defined.
+
+Sat Aug 26 15:13:21 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * ar.c (ar_name): Return zero for `(foo)'.
+
+ * Version 3.55.
+
+ * make.texinfo (Rules: Multiple Targets): Make this node's `next'
+ pointer point to `Static Pattern'.
+ * make.texinfo (Makefiles: MAKEFILES Variable): Make this node's `prev'
+ pointer point to `Makefile Names'.
+
+ * make.1: Renamed to make.man.
+ * compatMakefile: Define `mandir' and `manext'.
+ (install): Depend on $(mandir)/make.$(manext).
+ ($(mandir)/make.$(manext)): Depend on make.man and copy it to $@.
+ ($(bindir)/make): Use `make' rather than $<; so Unix make can grok it.
+
+Thu Aug 24 03:35:48 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * variable.c (target_environment): Allow variables that start with
+ underscores.
+
+Wed Aug 23 22:50:32 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * variable.c (target_environment): Reject variables that don't start
+ with letters.
+
+Tue Aug 22 04:14:29 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * GNUmakefile (make-$(version).tar.Z): Put make.1 (the Unix manual
+ page) in the tar file.
+
+ * variable.c (target_environment): Don't write variables with origin
+ o_default (i.e., ones from default.c).
+ * make.texinfo (Commands: Recursion: Variables/Recursion): Document
+ that default variables are not put in the environment.
+
+ * remake.c (update_file_1): Remake all targets with commands but no
+ deps.
+
+Sat Aug 19 06:03:16 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (update_file_1): In the final loop, set the deps'
+ `changed' members if they are newer than FILE.
+
+ * remake.c (update_goal_chain): Under -d, print a message if we decide
+ not to remake a makefile so as to avoid a possible infinite loop.
+
+Fri Aug 18 20:30:14 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (remake_file): Cleaned up.
+
+ * commands.c (execute_file_commands): If the commands are empty, set
+ FILE->update_status to zero before returning.
+
+ * remake.c (notice_finished_file): Set `last_mtime' fields to zero
+ instead of calling name_mtime; file_mtime will do that later if anybody
+ cares.
+
+Thu Aug 17 10:01:11 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * make.texinfo (Rules: Wildcards: Wildcard Examples): Give this node a
+ `prev' pointer.
+
+ * Version 3.54.9 (alpha).
+
+ * make.texinfo: Fixed some @nodes.
+
+ * remake.c (check_dep): Don't set *MUST_MAKE_PTR if FILE doesn't exist
+ after running update_file.
+
+ * remake.c (notice_finished_file): If FILE has no commands, pretend its
+ modtime is now.
+
+ * remake.c (update_file_1): In the loops that call update_file on the
+ deps, compare modtimes before and after (unless deps are still being
+ made) and set the deps' `changed' members. Do not set the `changed'
+ members in the loop that prints the newer/older debugging messages.
+ * remake.c (update_file_1): If no deps changed and FILE has no
+ commands, decide it doesn't need remaking.
+
+ * remake.c (update_file_1): Print a debugging message if we take
+ commands from default_file.
+
+ * make.texinfo (Rules: Directory Search: Selective Search): Removed
+ note about warning for `vpath' with a constant pathname, since it isn't
+ warned about anymore.
+
+ * remake.c (update_goal_chain): If MAKEFILES, remove makefiles which
+ are targets and have no deps.
+ * make.texinfo (Makefiles: Remaking Makefiles): Document that makefiles
+ will not be remade if they are targets but have no dependencies.
+
+Tue Aug 15 00:00:08 1989 Roland McGrath (roland at apple-gunkies.ai.mit.edu)
+
+ * remake.c (notice_finished_file): Increment files_remade for non-phony
+ files if they didn't exist before (even if they still don't).
+
+ * job.c: Include <errno.h> and declare errno.
+
+ * job.c (exec_command): If the execve fails with ENOEXEC (Exec format
+ error), return instead of exiting the child process.
+
+ * job.c (start_job): In the child side, if exec_command fails, try
+ using the shell.
+
+ * job.c (start_job): In the child side, call unblock_children instead
+ of sigsetmask.
+
+ * remake.c (notice_finished_file): Under -n or -q, always increment
+ files_remade for non-phony files.
+
+ * rule.c (intall_pattern_rule): Use find_percent.
+
+ * vpath.c (vpath_search): Pass the `percent' members to
+ pattern_matches.
+
+Mon Aug 14 23:30:24 1989 Roland McGrath (roland at apple-gunkies.ai.mit.edu)
+
+ * vpath.c (struct vpath): New member `percent', to hold a pointer into
+ the pattern where the % is.
+ (construct_vpath_list): Call find_percent on the pattern and set the
+ new `percent' member.
+ * read.c (read_makefile): Don't run find_percent on `vpath' directive
+ patterns.
+
+ * function.c (pattern_matches): Take new arg PERCENT, a pointer into
+ PATTERN where the % is. If PERCENT is nil, copy PATTERN into local
+ space and run find_percent on it.
+ (expand_function: `filter', `filter-out'): Pass new arg to
+ pattern_matches.
+ * read.c (record_files): Pass PATTERN_PERCENT to pattern_matches for
+ static pattern rules. Save the percent pointer into implicit rule
+ targets, and pass them to create_pattern_rule.
+ * rule.c (convert_to_pattern): Pass new arg to create_pattern_rule.
+ (create_pattern_rule): Take new arg TARGET_PERCENTS, nil or an array of
+ pointers into the corresponding elements of TARGETS, where the %s are.
+
+Sun Aug 13 00:29:19 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.8.
+
+ * README.templatate, README-doc.template: New files, turned into README
+ and README-doc to go into the two distribution tar files.
+ * GNUmakefile: Added a rule to edit the version number in
+ README.template and README-doc.template, producing README and
+ README-doc.
+
+ * remake.c (update_goal_chain): If -n or -q is in effect for a
+ makefile, and it got updated, don't change STATUS, so we can still
+ return -1 (meaning nothing was done). This avoids an infinite loop on
+ "make -n Makefile".
+
+Sat Aug 12 23:14:24 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (notice_finished_file): Treat -q the same as -n.
+
+ * remake.c (update_goal_chain): Fixed handling of return from
+ update_file. If -n or -q is in effect, ignore it.
+
+ * job.c (start_job): Don't test for -t. We should never get called in
+ that case.
+
+Fri Aug 11 04:09:14 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * function.c (expand_function): Removed unused variables.
+ (handle_function): Removed unused variable.
+
+ * main.c (main): Removed unused variable.
+
+Wed Aug 9 09:37:10 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.7.
+
+ * remake.c (notice_finished_file): If FILE's modtime actually changed,
+ increment files_remade.
+ (remake_file): Don't increment files_remade.
+
+ * remake.c (update_file): Don't print "up to date" messages for
+ phony files.
+
+ * job.c (child_handler): Don't set C->file->update_status to 1 if
+ start_job returns nonzero under -n or -t.
+
+ * expand.c (variable_expand): Count parens in $(foo:a=b) refs.
+
+ * main.c: Removed old declaration of `glob_tilde' (which hasn't existed
+ for a few months).
+
+Tue Aug 8 23:53:43 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * job.c (exec_command): Fixed to not ignore the last path component and
+ to do the right thing with an empty path.
+
+Fri Aug 4 15:58:19 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (library_file_mtime): Look for libLIB.a, not /libLIB.a.
+ Do VPATH search on libLIB.a, not /usr/lib/libLIB.a
+
+Thu Aug 3 20:42:00 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * job.c [HAVE_SYS_WAIT or not USG]: If WIFSIGNALED is not defined by
+ <sys/wait.h>, define it as (WTERMSIG != 0).
+
+Tue Aug 1 19:25:34 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (remake_file): If FILE has no commands and is a target,
+ don't set its time to now. The time gets reset by notice_finished_file
+ anyway, and it isn't needed since check_dep checks for nonexistence.
+
+ * Version 3.54.6.
+
+ * read.c (read_makefile): Don't read off the end of the string after an
+ `include'.
+
+ * job.c (exec_command): New function to search the path for a file and
+ execute it.
+ (start_job): Use exec_command rather than execvp.
+
+ * read.c (read_makefile): Expand `include' directive args before
+ parsing them. Allow trailing whitespace after filename.
+
+ * variable.c (target_environment): Put makelevel + 1, rather than
+ makelevel, in the `MAKELEVEL' envariable.
+
+Sat Jul 29 10:27:04 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * remake.c (notice_finished_file): Don't get the new modtime of phony
+ files.
+
+ * remake.c (remake_file): Run commands instead of touching under -t if
+ FILE->cmds->any_recurse is set.
+
+ * commands.h (struct commands): Add new member `any_recurse', to be set
+ nonzero if any `lines_recurse' element is nonzero.
+ * commands.c (chop_commands): Set the `any_recurse' member.
+
+ * commands.c (execute_file_commands): Split chopping of commands into
+ lines into new function chop_commands.
+ * commands.h: Declare chop_commands.
+
+ * read.c (read_makefile): Test for a line beginning with a tab after
+ checking for conditional lines, but before all other checks.
+
+Fri Jul 28 18:10:29 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * read.c (read_makefile): Match directives against collapsed line
+ and use that for their args.
+
+ * read.c (read_makefile): Warn about extra text after `include'.
+
+Tue Jul 25 14:34:25 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * make.texinfo (Rules: Directory Search: Selective Search): Fixed
+ example to use correct `vpath' syntax.
+
+Mon Jul 24 12:10:58 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.5.
+
+ * job.c (start_job): In the child side, unblock SIGCHLD.
+
+Fri Jul 21 18:25:59 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * make.h: Don't include <sys/types.h> #ifdef sun.
+
+Mon Jul 17 14:29:10 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * implicit.c (pattern_search): If ar_name (FILENAME), don't check for
+ directory names.
+
+ * job.c (wait_for_children): Changed "waiting for children" message to
+ "waiting for unfinished jobs".
+
+Fri Jul 14 13:17:13 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * load.c (load_average): Use an unsigned offset into kmem.
+
+Thu Jul 13 18:44:49 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * variable.c (pop_variable_scope): Don't free the head of the chain of
+ variables in each bucket twice.
+
+Tue Jul 11 06:45:24 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * GNUmakefile: Include COPYING in the doc tar file.
+
+ * variable.c, read.c, misc.c, job.c, function.c: Replace some identical
+ "for" loops with next_token or end_of_token calls.
+
+Mon Jul 10 16:55:08 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.4.
+
+ * compatMakefile: Documented new conditionals.
+
+ * job.c: Don't define sys_siglist if HAVE_SIGLIST is defined.
+ Don't define dup2 if HAVE_DUP2 is defined.
+
+ * job.c (child_handler): Interpret the return from start_job correctly.
+
+ * remake.c (update_file_1): Don't write "target not remade because of
+ errors" message under -n or -q.
+
+ * read.c: Declare getpwnam.
+
+ * glob.c: Use <dirent.h> if DIRENT is defined.
+ [USG]: Don't declare memcpy, since <memory.h> does.
+
+Fri Jul 7 20:53:13 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * misc.c (collapse_line): Copy the line over in the right place.
+
+Fri Jul 7 18:33:24 1989 Roland McGrath (fsf at void.ai.mit.edu)
+
+ * remake.c: Conditionalize inclusion of <sys/file.h> on not
+ USG, since HP-UX defines a `struct file' there.
+
+Fri Jul 7 12:11:30 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * job.c: If WTERMSIG is defined by <sys/wait.h>, define WAIT_T as int,
+ and don't define other macros; this covers HP-UX.
+ If WTERMSIG is not defined, use int or union wait based on USG and
+ HAVE_SYS_WAIT; this covers BSD and SysV.
+
+ * Version 3.54.3 (alpha).
+
+ * job.c [USG and not USGr3]: Include <errno.h> and declare errno.
+
+ * job.c (unblock_children [USG]): Declare child_handler.
+
+ * job.c: Renamed WRETCODE to WEXITSTATUS.
+ [HAVE_SYS_WAIT or not USG]: Undefine WTERMSIG, WCOREDUMP, and
+ WEXITSTATUS before defining them. The HP-UX <sys/wait.h> defines them.
+
+ * main.c (main): If there are no goals, fatal AFTER printing the data
+ base under -p.
+
+Thu Jul 6 22:43:33 1989 Roland McGrath (roland at apple-gunkies.ai.mit.edu)
+
+ * glob.c [USG]: #define rindex as strrchr.
+
+ * job.c [USG]: Include <sys/param.h> and #define getdtablesize() as
+ NOFILE.
+
+Wed Jul 5 09:36:00 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.2 (alpha).
+
+ * expand.c (variable_expand): When expanding recursive variable
+ references (${${a}}), use the correct delimiters in the constructed
+ variable reference.
+
+Mon Jul 3 18:29:26 1989 Roland McGrath (roland at apple-gunkies.ai.mit.edu)
+
+ * compatMakefile: Clear out and redefine the .SUFFIXES list because
+ silly Sun 4 make defines .cps.h.
+
+ * compatMakefile: Fix comment about -DNO_MINUS_C_MINUS_O.
+
+ * remake.c: Include <sys/file.h> for O_* on 4.2.
+
+ * commands.c: Define sigmask if it's not defined.
+
+Fri Jun 30 07:33:08 1989 Roland McGrath (roland at apple-gunkies.ai.mit.edu)
+
+ * remake.c (remake_file): Don't always increment files_remade.
+
+ * variable.c (push_new_variable_scope): Zero the new variable hash
+ table.
+
+Thu Jun 29 17:14:32 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * expand.c (variable_expand): When terminating the variable expansion
+ buffer, use variable_buffer_output instead of a simply zero store,
+ because the buffer may need to be enlarged.
+
+Wed Jun 28 16:53:47 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.54.
+
+ * default.c (default_suffixes): Added `.ln'.
+ (default_suffix_rules): Changed lint rules to use -C.
+
+Thu Jun 22 20:49:35 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * job.c (start_job): Set `environ' to CHILD->environment before execing
+ in the child process!
+
+Tue Jun 20 17:23:13 1989 Roland McGrath (roland at spiff.ai.mit.edu)
+
+ * compatMakefile: Put job.h and rule.h in `srcs'.
+
+ * Version 3.53.
+
+Mon Jun 19 16:25:18 1989 Roland McGrath (roland at spiff.ai.mit.edu)
+
+ * job.c (start_job): If there are no more commands, return nonzero
+ under -n or -t.
+
+ * compatMakefile (make): Pass `-f' to mv.
+
+ * GNUmakefile: If `ARCH' or `machine' is defined, make $(ARCH)/*.o and
+ $(ARCH)/make instead of *.o and make.
+
+ * function.c (string_glob): Don't try to use freed storage!
+
+ * read.c (readline): If there is only one byte of space in the buffer,
+ enlarge the buffer before reading more.
+
+ * arscan.c [M_XENIX]: Miscellaneous minor changes for Xenix.
+
+Sun Jun 18 13:07:45 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * GNUmakefile (depend): Split commands into two lines so they won't be
+ so long when variable-expanded.
+
+ * compatMakefile: Documented MINUS_C_MINUS_O meaning. The line
+ describing it got removed when the USG/wait stuff was documented.
+
+Sat Jun 17 22:56:54 1989 Roland McGrath (roland at hobbes.ai.mit.edu)
+
+ * Version 3.52.
+
+Mon Jun 12 17:45:11 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remake.c (check_dep): Drop circular dependencies instead of fataling.
+ (update_file_1 already does this.)
+
+ * default.c (default_suffix_rules): For .s -> .o, put the -o flag to
+ the assembler before the source file name.
+
+Sun Jun 11 12:00:52 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.51.
+
+ * make.texinfo (Features): Noted 1003.2 requirement of `+' meaning.
+
+ * file.c (remove_intermediates): If !SIG, write a single "rm" command
+ line, listing all files.
+
+ * read.c (read_makefile): Don't free the storage for the passed
+ filename, since it might not be malloc'd. When doing an included
+ makefile, free the name's storage.
+ (read_all_makefiles): Use variable_expand to find the value of
+ `MAKEFILES'. Free the storage for the names of -f makefiles.
+ (read_makefile): Allocate storage for the makefile name in the
+ `struct file' in read_makefiles.
+
+ * make.texinfo (Running: Instead of Execution): Document the effect of
+ + and $(MAKE)/${MAKE}.
+
+ * make.texinfo (Functions: Foreach Function): Document that if the
+ iteration variable was undefined before the `foreach' call, it will be
+ undefined after the call.
+
+ * commands.c: Split into commands.c, job.h, and job.c.
+
+ * rule.c (try_implicit_rule, pattern_search): Moved to new file
+ implicit.c.
+
+ * rule.c: Split into rule.h, rule.c, and default.c.
+ * default.c (install_default_pattern_rules): Renamed to
+ install_default_implicit_rules.
+ * make.h, main.c (main): Renamed uses.
+
+ * make.c: Renamed to misc.c.
+
+ * make.c (main, log_working_directory, decode_switches,
+ decode_env_switches, define_makeflags, die, print_version,
+ print_data_base): Moved to new file main.c.
+
+ * commands.c (execute_file_commands): Don't collapse backslash-newlines
+ here. When chopping the commands up into lines, don't chop at
+ backslash-newlines.
+ (start_job): Collapse backslash-newlines after printing the line.
+
+ * commands.c (start_job): Don't collapse backslash-newlines here.
+ (execute_file_commands): Collapse backslash-newlines before chopping
+ the commands up into lines.
+
+ * commands.c (set_file_variables): Initialize the length counters for
+ $^ and $? to zero!
+
+ * commands.c (start_job): Use vfork instead of fork. Someone else says
+ the child and parent DO have separate file descriptors.
+
+ * variable.c: Split internals into variable.c, function expansion into
+ function.c, and variable expansion into expand.c.
+ * function.c (handle_function): New function to check for a function
+ invocation and expand it.
+ * expand.c (variable_expand): Use handle_function.
+ * variable.c (push_new_variable_scope): New function to push a new
+ empty variable set onto the current setlist.
+ (pop_variable_scope): New function to pop the topmost set from the
+ current setlist and free its storage.
+ * function.c (expand_function: `foreach'): Push a new variable scope
+ for the iteration variable and pop the scope when finished.
+ * variable.h: Declare new functions.
+ * variable.c (initialize_variable_output): New function to return a
+ pointer to the beginning of the output buffer.
+ (save_variable_output): New function to save the variable output state.
+ (restore_variable_output): New function to restore it.
+ * expand.c (variable_expand): Use initialize_variable_output.
+ (allocated_variable_expand): Use {save,restore}_variable_output.
+ * variable.c (current_setlist): Renamed to current_variable_set_list
+ and made global.
+
+Sat Jun 10 00:11:25 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remake.c (library_file_mtime): Check for libNAME.a in the current
+ directory before doing VPATH search.
+
+ * variable.c (print_variable_set): Don't write "# Variables", and write
+ fewer blank lines.
+ (print_variable_data_base): Precede the variables with "# Variables".
+
+ * make.c (main): Print the data base under -p after doing everything
+ else, just before exitting. This way it gets info determined in
+ updating the goal targets.
+
+ * variable.c (print_variable_data_base): Split into print_variable,
+ which prints one variable, and print_variable_set, which prints a set.
+ Replaced with a call to print_variable_set for the global set.
+ (print_file_variables): New function to print a given file's local
+ variables.
+
+ * file.c (print_file_data_base): Call print_file_variables to print
+ each file's local variables.
+
+ * commands.c (set_file_variables): Actually define the values for
+ the $^ and $? variables!!!
+
+ * make.texinfo (Implicit: Pattern Rules: Automatic): Document new D and
+ F versions of $^ and $?.
+
+ * commands.c (start_job): In the child fork, use getdtablesize and a
+ loop to close all file descriptors other than 0, 1, and 2. We need to
+ do this since not only the bad stdin pipe, but also some directories,
+ may be open.
+
+ * commands.c (start_job): Use fork instead of vfork, because a vfork
+ parent and child share file descriptors, and our child needs to diddle
+ with stdin.
+
+ * variable.c (initialize_file_variables): When created a new variable
+ set, zero out the hash table.
+
+ * variable.c (target_environment): Don't use variables whose names are
+ not made up of alphanumerics and underscores.
+
+ * remake.c (update_file_1): Set the `parent' member of each dependency
+ to FILE before updating it.
+
+ * file.h (struct file): Add `parent' member.
+
+ * variable.c (initialize_file_variables): Don't take second arg PARENT.
+ Use FILE->parent instead. If FILE->parent->variables is nil, recurse
+ to initialize it.
+
+ * variable.h: Declare {allocated_}variable_expand_for_file.
+
+ * variable.c (allocated_variable_expand): Now
+ allocated_variable_expand_for_file, calling variable_expand_for_file,
+ and taking second arg FILE.
+ (allocated_variable_expand): New function, a wrapper around
+ allocated_variable_expand_for_file, passing a nil second arg.
+
+Fri Jun 9 12:11:45 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (start_job): On the child side of the fork, always close
+ the bad stdin file descriptor.
+
+ * commands.c (struct child): New member `environment', to hold the
+ environment for the child.
+ (execute_file_commands): Set the new childs `environment' member to nil
+ before calling start_job.
+ (start_job): Set up CHILD->environment before running the commands if
+ it is nil.
+
+ * make.c (main): Don't call new_environ. `shell' functions will now be
+ run with the environment make was called with.
+
+ * commands.c (child_handler): Don't check C->command_ptr before calling
+ start_job since we now have to check C->file->cmds->command_lines and
+ it's easier to let start_job handle all that.
+
+ * commands.c (struct child): New member `command_line', to hold an
+ index into file->cmds->command_lines.
+ (execute_file_commands): Set the new child's `command_line' to 0 and
+ its `commands' and `commands_ptr' to nil.
+ (start_job): When CHILD->command_ptr runs out, increment
+ CHILD->command_line and run the corresponding line from
+ CHILD->file->cmds->command_lines. Run it even under -t, -q, or -n if
+ the CHILD->file->cmds->lines_recurse element for that line is set.
+
+ * commands.c (execute_file_commands): Chop CMDS up into lines, setting
+ its `command_lines' and `lines_recurse' members, if it wasn't already
+ chopped.
+
+ * commands.h (struct commands): New members `command_lines' and
+ `lines_recurse'. The first is an array of chopped-up lines; the second
+ is an array of flags, each nonzero if the corresponding line is
+ recursive.
+
+ * variable.c (variable_expand_for_file): If FILE is nil, just do a
+ vanilla variable_expand.
+ (expand_function: `shell'): Pass second arg (as nil) to
+ construct_command_argv.
+
+ * commands.c (construct_command_argv): Use variable_expand_for_file on
+ `$(SHELL)' and `$(IFS)' instead of lookup_variable to check those
+ variables. This handles file-local and recursive values correctly.
+ To support this, take an additional argument FILE.
+
+ * variable.c (initialize_file_variables): New function to initialize
+ FILE's variable set list from PARENT's setlist. PARENT is the
+ immediate dependent that caused FILE to be remade, or nil if FILE is a
+ goal. (When user-level per-file variables are implemented, PARENT
+ should be passed as nil when defining per-file variables.)
+
+ * variable.c (variable_expand_for_file): New function to expand a line
+ using the variable set of a given file, and reporting error messages
+ for the file and line number of that file's commands.
+
+ * variable.h: Don't declare lookup_variable_for_file.
+
+ * variable.c (lookup_variable_*): Turned back into lookup_variable. It
+ now uses current_setlist.
+ (global_setlist): New static `struct variable_set_list', a setlist
+ containing global_variable_set.
+ (current_setlist): New static `struct variable_set_list *', a pointer
+ to the current variable set list.
+ (define_variable): Define in the current top-level set, not the global
+ set.
+
+ * commands.c (set_file_variables): New function to set up the automatic
+ variables for a file in its own variable set.
+ (execute_file_commands): Use set_file_variables.
+
+ * variable.c (new_environ): Replaced with target_environment, taking an
+ argument FILE, and returning an environment for FILE's commands.
+
+ * variable.c, variable.h: Remove all global special variable pointers.
+
+ * variable.c (define_variable_for_file): New function like
+ define_variable, but takes additional arg FILE, and defines the
+ variable in the variable set at the top of FILE's chain.
+ (lookup_variable_for_file): New function like lookup_variable, but
+ takes additional arg FILE, and looks the variable up in all of FILE's
+ variable sets.
+
+ * file.h (struct file): New member `variables', a `struct
+ variable_set_list' containing the list of variable sets used in the
+ expansion of the file's commands.
+
+ * variable.c (variables): Replaced with static `struct variable_set'
+ global_variable_set.
+ (define_variable): Now define_variable_in_set, taking additional
+ argument SET, the `struct variable_set' to define it in.
+ (define_variable): Use define_variable_in_set with global_variable_set.
+ (lookup_variable): Now lookup_variable_in_set, taking additional
+ argument SET, the `struct variable_set' to look it up in.
+ (lookup_variable): Use lookup_variable_in_set with global_variable_set.
+ (lookup_variable_in_setlist): New function to look up a variable in a
+ `struct variable_set_list' using lookup_variable_in_set.
+
+ * variable.h (struct variable_set): New structure, containing a hash
+ table and the number of hash buckets.
+ (struct variable_set_list): New structure, containing a link for a
+ linked-list, and a `struct variable_set'.
+
+ * commands.c (start_job): Under -n, return what the recursive start_job
+ call returns, since it might actually start a child.
+
+ * make.texinfo (Rules: Wildcards): Document ~ and ~USER expansion.
+
+ * commands.c (execute_file_commands): If start_job returns
+ failure, but -t is set, set FILE->update_status to success.
+ (start_job): If -t is set, and the commands are not recursive, return
+ failure (is is done for -q).
+
+ * remake.c (touch_file): New function to touch FILE.
+ (remake_file): Use touch_file. When touching a file, still do
+ execute_file_commands.
+
+ * remake.c (remake_file): Don't check question_flag (-q), since we
+ can't know here if the commands are recursive.
+
+ * commands.c (start_job): Don't use the `recursive' member of
+ CHILD->file->cmds. Instead, check for leading +s and $(MAKE) or
+ ${MAKE} in the command line here.
+
+ * commands.h (struct commands): Remove `recursive' member.
+
+ * rule.c (install_default_pattern_rules): Remove use of `recursive'
+ member.
+
+ * read.c (record_files): Don't check commands from $(MAKE) and set
+ their `recursive' member.
+
+ * commands.c (fatal_error_signal): Treat SIGQUIT like SIGINT, SIGHUP,
+ and SIGTERM, but don't send it to ourselves because it will cause a
+ core dump.
+
+Thu Jun 8 20:30:04 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.50.
+
+ * variable.c (variable_expand): Use allocated_variable_expand instead
+ of expand_argument in a few places.
+
+ * variable.c (allocated_variable_expand): Do static variable shuffling
+ here instead of using expand_argument.
+ (expand_argument): Use allocated_variable_expand.
+
+ * variable.c (recursively_expand): New function to recursively expand
+ its argument (a `struct variable'), returning the malloc'd value.
+ (variable_expand): Use recursively_expand.
+
+Sun May 28 12:49:27 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (main): Fixed buggy fix in deciding to increase space for
+ command-line variable definitions. (First it never did it, then it
+ always did it; now it does it when necessary.)
+
+Sat May 27 14:01:54 1989 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * make.c (main): Fixed bug in deciding to increase space for
+ command-line variable definitions.
+
+Fri May 26 15:48:01 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * read.c (multi_glob): Use allocated_expand_variable for checking
+ `HOME' variable for ~ expansion, since this may be called from inside a
+ `wildcard' function expansion.
+
+ * variable.h: Declare allocated_expand_variable.
+
+ * variable.c (allocated_expand_variable): New function to do variable
+ expansion in an allocated buffer, rather than the static one.
+
+ * make.c (main): Don't set glob_tilde (it no longer exists).
+
+ * variable.c (string_glob): Use multi_glob and parse_file_seq.
+
+ * read.c (multi_glob): Do ~ expansion here.
+
+ * glob.c (glob_tilde, glob_filename): Removed ~ expansion.
+
+ * variable.c (define_variable, lookup_variable): Use a smarter hashing
+ algorithm (the same one used for files and directories).
+ (VARIABLE_BUCKETS): Increased to 523.
+
+ * file.c (enter_file, lookup_file, rename_file): Use a smarter hashing
+ algorithm, spreading the bits about somewhat.
+
+ * make.c (log_working_directory): Under `-p', precede the directory
+ message with a `#'.
+
+ * make.c (print_version): Under `-p', precede each line with a `#'.
+ (print_data_base): Precede the header line with a `#' and include the
+ date and time on it.
+
+ * vpath.c (print_vpath_data_base): Precede non-directive
+ lines with `#'s.
+
+ * commands.c (print_commands): Precede the non-command line with a `#'.
+
+ * rule.c (print_rule_data_base), file.c (print_file_data_base): Precede
+ non-rule lines with `#'s.
+
+ * dir.c (print_dir_data_base): Precede all lines with `#'s.
+
+ * variable.c (print_variable_data_base): Changed format so that it can
+ be makefile input. Lines that are not variable definitions are
+ preceded with `#'. Nonrecursive variable definitions are made with all
+ dollar signs doubled to reproduce the initial value. Recursive
+ variable definitions containing newlines are done with `define'
+ directives. Nonrecursive variable definitions containing newlines, and
+ variable names containing :, =, or newlines, will come out garbled.
+
+Wed May 24 00:20:04 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.49.
+
+Tue May 23 19:18:00 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * variable.c (expand_function: `filter'/`filter-out'): Use
+ find_percent instead of pattern_p.
+
+ * variable.c (expand_function: `patsubst'): Pass new args (both nil)
+ to patsubst_expand.
+ (variable_expand): For $(var:pat%=rep%) references, pass new args to
+ patsubst_expand so as to avoid find_percent and thus disallow
+ quoting the %s.
+
+ * read.c (record_files): Pass new args to patsubst_expand.
+
+ * variable.c (patsubst_expand): Take two new args: PATTERN_PERCENT
+ and REPLACE_PERCENT. Each of these, if non-nil, means that PATTERN
+ (or REPLACE) has already been run through find_percent, and
+ PATTERN_PERCENT (or REPLACE_PERCENT) is the result.
+
+ * make.h: Declare find_percent instead of pattern_p.
+
+ * read.c (pattern_p): Changed to find_percent, returning a pointer
+ to the %, or nil if there is none.
+ (record_files): Take another arg, PATTERN_PERCENT, a pointer to the
+ % in PATTERN.
+ (read_makefile): Pass PATTERN_PERCENT to record_files.
+
+ * make.texinfo (Rules: Static Pattern: Static Usage,
+ Rules: Directory Search: Selective Search,
+ Functions: Text Functions): Documented that `%' can be quoted.
+
+ * variable.c (expand_function: `filter'/`filter-out'): Use pattern_p
+ to allow quoted %s in patterns.
+
+ * variable.c (patsubst_expand): Use pattern_p on PATTERN and REPLACE
+ to allow quoted %s. Quoting backslashes are removed from REPLACE
+ even if PATTERN contains no unquoted %.
+
+ * read.c (pattern_p): Made global.
+ * make.h: Declare pattern_p.
+
+ * read.c (pattern_p): New function to search for an unquoted % in a
+ string. Backslashes quote %s and backslashes. Quoting backslashes
+ are removed from the string by compacting it into itself. Returns
+ nonzero if an unquoted % was found, zero if not.
+ (record_files): Use pattern_p to check for implicit rules.
+ (read_makefile): Use pattern_p to check for static pattern rules.
+ Also use it to allow quoted %s in `vpath' patterns; warn about
+ `vpath' patterns with no %s.
+
+Mon May 22 16:31:52 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * glob.c (glob_filename): Replace a `1' with the `l' that should
+ have been there. This incidentally stops it from dumping core.
+
+ * glob.c (glob_filename): If the path is just a directory, with no
+ file name pattern, return the directory alone.
+
+ * glob.c (glob_tilde): New global variable (int), defaults to zero.
+ (glob_filename): If glob_tilde is nonzero, expand ~ or ~USER.
+
+ * variable.c (string_glob): Keep a static allocated buffer for file
+ names taken from the list, instead of allocating and freeing one
+ every time.
+
+Fri May 19 18:06:26 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.c (decode_switches): Get floating numbers from the right string.
+
+Sun May 14 13:48:04 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * commands.c (delete_child_targets): When deleting `also_make'
+ files, include the target's name in the message:
+ make: *** [foo] Deleting file `bar'
+
+Sat May 13 17:34:26 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (max_load_average, default_load_average): Default to -1.
+
+ * load.c (wait_to_start_job): Return if max_load_average is < 0.0,
+ not equal.
+
+Fri May 12 16:08:05 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * variable.c (variable_buffer_output): Don't try to do pointer
+ arithmetic between objects not in the same array.
+
+Wed May 10 15:55:29 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * rule.c [M_XENIX] (default_suffix_rules, default_variables): Minor
+ changes to allow for strange compiler syntax.
+
+ * rule.c (default_variables): Don't include "> $@" in
+ $(PREPROCESS.S), since it's already in the .S.s rule.
+
+ * file.c (enter_file): Make a new double-colon file the `prev'
+ member of the bottom `prev' file (the one whose `prev' is nil).
+
+ * read.c (do_define): Append newlines after copying the lines into
+ the value buffer, so we end up with a trailing newline.
+
+ * make.c (print_version): If the global variable
+ `remote_description' is not nil or "", append "-%s" (its value) to
+ the version number.
+ * remote-*.c: Define remote_description appropriately.
+
+Sun May 7 15:15:53 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (error_status): Converted to new function child_error,
+ taking new arguments TARGET_NAME and IGNORED, and writing an error
+ message: "*** [target] Error 1" (or signal #, etc.), appending
+ " (ignored)" if IGNORED is nonzero.
+ (child_handler): Use child_error instead of error_status.
+
+ * compatMakefile (all): Don't depend on `doc'.
+
+ * compatMakefile (clean): Don't remove make-info*.
+ (realclean): New rule, depends on `clean', removes tags, TAGS,
+ and all Info and TeX files.
+
+Thu May 4 17:00:46 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * variable.c (print_variable_data_base), file.c
+ (print_file_data_base), rule.c (print_rule_data_base),
+ Use floating-point for averages and percentages.
+
+ * make.c (print_data_base): Print messages before and after the data
+ base information.
+
+ * commands.c (print_commands): Changed output format to separate
+ lines in commands and prefix them with tabs.
+
+ * dir.c (print_dir_data_base): Changed output format slightly.
+
+ * vpath.c (struct vpath, construct_vpath_list,
+ selective_vpath_search): Remove the `exists' member and its uses.
+
+ * vpath.c (print_vpath_data_base): New function to print all
+ selective and general VPATH search paths (for -p).
+
+ * make.c (print_data_base): Call print_vpath_data_base.
+
+ * file.c (print_file_data_base): Changed format to look more like a
+ makefile rule. Now reports all information in the `struct file'.
+
+ * rule.c (print_rule_data_base): Changed format of display from:
+ %: (terminal)
+ depends on: RCS/%,v
+ to:
+ %: RCS/%,v
+ is terminal.
+ references nonexistent subdirectory.
+ Also include number and percent that refer to nonexistent
+ subdirectories.
+
+Thu Apr 27 15:45:40 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.c (main): Figure out the level of recursion before writing
+ the `Entering directory' message.
+ * variable.c (define_automatic_variables): Don't figure out the
+ level of recursion from `MAKELEVEL'. It's now done in main.
+
+ * Version 3.48.
+
+Wed Apr 26 16:39:17 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (child_handler): Set `update_status' to zero when there
+ are no more commands.
+
+ * make.c (log_working_directory): If MAKELEVEL > 0, indicate the
+ recurson in the message (make[1]: ...).
+
+ * commands.c (child_handler): Change status to `cs_finished' when
+ commands fail.
+
+ * commands.c (start_job): Return 0 (success) if there were no more
+ commands for the child.
+ (child_handler): Change the status to `cs_finished' when start_job
+ fails to start the commands.
+
+ * make.c (main): Don't handle SIGEMT if it's not defined.
+ Do handle SIGDANGER if it is defined.
+
+ * commands.c (child_handler): Reorganized inner loop so that it
+ doesn't try to inspect the child before finding it.
+
+Tue Apr 25 16:28:24 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.c (end_of_token): Fixed bug wherein backslashes caused
+ immediate return.
+
+ * Version 3.47.
+
+ * make.texinfo (Implicit: Pattern Rules: Automatic): Document
+ setting of `$*' for explicit rules. Add note clarifying that
+ automatic variables, though referred to in the documentation as
+ `$<', etc. are no different than `$(<)', etc.
+
+Fri Apr 21 18:00:12 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * file.c (enter_file): Don't strip leading `./'s.
+
+ * read.c (parse_file_seq): Strip leading `./'s.
+
+Thu Apr 13 17:26:41 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.texinfo (Commands: Parallel, Running: Options): Document that
+ -l with no argument removes a previous load limit.
+
+ * make.c (struct command_switch): New member `default_value'.
+ (default_job_slots): Default value (of 1) for -j.
+ (default_load_average): Default value (of 0, unlimited) for -l.
+ (command_switches): Use default values for -j and -l.
+ Also, -l without an arg now means no load limit.
+ (define_makeflags): Don't write positive_int or floating options
+ whose values are their defaults.
+
+ * make.c (main): Under -w, write a `Leaving directory' message
+ before re-execing.
+
+Tue Apr 11 16:46:29 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.46.
+
+ * Makefile: Provide an easy place for system-specific definitions
+ (-DUSG, etc.) and extra object files (for whatever).
+
+ * make.texinfo: Miscellaneous fixes from RMS.
+
+Mon Apr 10 19:31:34 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * rule.c (pattern_search): Put rules with `subdir' flags set in
+ TRYRULES, since these might be valid with VPATHs. In the TRYRULES
+ loop, don't do lookup_file or file_exists_p calls for dependencies
+ of rules with `subdir' flags set, but still do vpath_search calls
+ and intermediate-file searches.
+
+Thu Apr 6 16:33:00 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.texinfo (Implicit: Pattern Rules: Automatic): Document the
+ new definition of $* for explicit rules.
+
+ * commands.c (execute_file_commands): If FILE->stem is nil, figure
+ out if FILE->name ends in a suffix in the .SUFFIXES list; if so,
+ store the name sans suffix in FILE->stem (and $*).
+
+Wed Apr 5 15:24:48 1989 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * file.c (remove_intermediates): Don't use `file_exists_p' to check
+ for the existence of intermediate files, because the hashed
+ directories will probably be out of date.
+
+ * commands.c (child_handler): Free the good stdin before running the
+ next command line.
+
+ * commands.c [USG] (init_siglist): Don't case SIGEMT if it's not
+ defined. Do case SIGDANGER (for IBM RT) if it is defined.
+
+ * commands.c: Changed `SYS_WAIT' to `HAVE_SYS_WAIT'.
+ (child_handler): Use `wait3' if HAVE_SYS_WAIT is #defined.
+
+ * file.c (enter_file): If any `./'s are stripped off, allocate a new
+ copy of the shortened name.
+
+ * rule.c (pattern_search): Allocate the right length strings for
+ `also_make' members.
+
+Sat Apr 1 13:28:38 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.45.
+
+ * GNUmakefile: Make a separate tarfile of the DVI and info files.
+
+ * make.c (define_makeflags): If a switch that takes an argument has
+ its default value, put the switch in MAKEFLAGS with no arguments.
+
+ * make.c (command_switches): Pass `-l' in MAKEFLAGS.
+
+Wed Mar 29 17:50:05 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * GNUmakefile: Don't include the DVI and info files in the dist.
+
+ * commands.c (child_handler): Don't call
+ check_changed_{directories,vpaths}.
+
+ * make.h: Don't declare check_changed_{directories,vpaths}.
+
+ * vpath.c (check_changed_vpaths): Removed this function.
+
+ * dir.c (struct directory): Remove `modtime' member.
+ (find_directory): Don't set `modtime' member.
+ (check_changed_directories): Removed this function.
+
+ * remake.c (update_file_1): Set FILE->command_state to cs_finished
+ if it didn't need to be remade.
+
+ * remake.c (update_file): Only write the "up to date" message if the
+ target went from `not_started' state to `finished' state without
+ incrementing the count of files remade.
+
+ * commands.c [USG] (init_siglist): If both SIGCHLD and SIGCLD are
+ defined, don't put them both in the `switch'.
+
+Tue Mar 28 15:37:02 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * file.c (rename_file): Change FILE's name!!!
+
+ * rule.c (create_pattern_rule): Set the `terminal' member of the new
+ rule after calling new_pattern_rule, which zeros it.
+
+ * rule.c (default_variables): Use $(C++) in $(COMPILE.cc)!
+
+Sun Mar 26 15:52:30 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Makefile: Added a `clean' target.
+
+Fri Mar 24 15:08:46 1989 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * Version 3.44.
+
+ * file.c (rename_file): If a `struct file' for the renamed-to name
+ exists, and it is a target or has deps or commands, barf.
+ If not just remove the old one for put in the new one.
+
+ * remake.c (update_file_1, check_dep): Changed it back so that equal
+ modtimes to NOT make dependencies be considered newer. RCS checks
+ out files with equal modtimes as the RCS files, so this screws it.
+
+ * make.h, glob.c: If __GNUC__ is defined, use __builtin_alloca.
+
+ * Makefile: Use variables `ALLOCA' and `ALLOCASRC' so systems
+ without a good standard alloca can get it from the Emacs
+ distribution (or somewhere).
+
+ * dir.c: Don't include <sys/stat.h>, since make.h does.
+
+ * make.c: Removed debugging version of getwd.
+
+Thu Mar 23 16:16:27 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.43.
+
+ * remake.c (update_file_1): If a dependency loop is found, don't
+ fatal. Emit an error message and remove the dependency.
+
+ * remake.c (library_file_mtime): Fixed to use the right names.
+ (update_file_1, check_dep): Consider a dependency "newer" than its
+ dependent if they have the same modification time.
+
+Wed Mar 22 19:31:35 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * file.c (remove_intermediates): Don't try to remove nonexistent files.
+
+Mon Mar 20 10:21:22 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.42.
+
+ * rule.c (default_variables): Set F77 to $(FC) and F77FLAGS to
+ $(FFLAGS) so explicit rules expecting these (which are in System V)
+ will work. However, there is no way to make setting these affect
+ the implicit rules, unless we trash FC and FFLAGS (which BSD uses).
+ [USG]: Set GET to `get' rather than `/usr/sccs/get'.
+
+Sun Mar 19 20:00:27 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * vpath.c (construct_vpath_list): Don't replace VPATH[ELEM] with
+ dir_name (V), because the latter may get freed.
+
+Sat Mar 18 15:01:39 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.41.
+
+ * make.texinfo: Cleaned-up edition 0.1 Beta from RMS and Bob Chassell.
+
+ * file.c (rename_file): If a file with the new name already existed,
+ use the same storage space, after freeing the old file's name, deps,
+ and `also_make' member, preserving the link in the chain.
+ Also write an error message telling the user to report the incident;
+ I don't think this should be able to happen, but I'm not sure.
+
+ * file.c (rename_file): Don't add the hash values of the old and new
+ names together! Reset HASHVAL before computing the second value.
+
+ * dir.c (check_changed_directories): Zero the new file hash table
+ after allocating it.
+
+ * dir.c (dir_file_exists_p): If FILENAME is "", return 1 if the
+ directory exists.
+
+ * vpath.c (check_changed_vpaths): New function to run through the
+ search paths of all VPATHs, making the `exists' members correspond
+ to reality.
+
+ * commands.c (child_handler): Call check_changed_vpaths.
+
+ * make.h: Declare check_changed_vpaths.
+
+ * vpath.c (struct vpath): New element `exists', an array of char
+ flags; exists[N] is nonzero if searchpath[N] exists.
+ (construct_vpath_list): Set the `exists' member.
+ (selective_vpath_search): Don't search directories whose `exists'
+ elements are zero.
+
+ * read.c (read_makefile): Set the `dontcare' flag of makefiles
+ from the MAKEFILES variable if they were not mentioned anywhere but
+ in the MAKEFILES variable.
+
+ * read.c (read_makefile): Don't write an error message if fopen
+ fails for a makefile from the MAKEFILES variable.
+
+ * dir.c (struct directory): Add `modtime' member to record the
+ modification time of the directory when it was opened.
+ (check_changed_directories): New function to check all known
+ directories; if their modtimes have changed since they were opened,
+ their file tables are cleared and they are reset to be read in.
+
+ * commands.c (child_handler): Call check_changed_directories before
+ returning.
+ make.h: Declare check_changed_directories.
+
+Tue Mar 14 20:07:13 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.40.
+
+ * make.c (print_version): Made the copyright say 1988, 1989.
+
+ * read.c (read_all_makefiles): Don't set *MAKEFILES to the name of
+ the end of the read_makefiles chain, since the latter may be from an
+ included makefile. (Why did I do this before?)
+
+ * make.c (main): Set argv[0] to "" if it was nil. Don't put the
+ command-line variable definitions into argv[0], only into the MAKE
+ variable!
+
+Sun Mar 5 20:44:08 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * ar.c (ar_member_date, ar_touch): Remove the trailing ) from the
+ member name.
+
+Fri Mar 3 18:15:15 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (construct_command_argv): Initialize NEW_ARGV to 0. At
+ `slow' label, if NEW_ARGV is not 0, free it; then allocate 4 strings.
+
+Tue Feb 28 14:29:39 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.39.
+
+ * COPYING, make.texinfo: New GNU General Public License, version 1.
+
+ * *.c, *.h, Makefile: New copyright notices for the new GNU General
+ Public License, version 1.
+
+ * commands.c [USG]: Define WRETCODE correctly (again).
+
+ * variable.c (expand_function: `shell'): Don't capture the standard
+ error output of the shell command.
+
+ * ar.c (ar_touch, ar_member_date): Allocate MEMNAME with the right
+ length.
+
+ * load.c [not UMAX] (load_average): Don't clobber the first nlist
+ member when trying to set the second!
+
+Thu Feb 23 13:13:53 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * commands.c (child_handler): Really ignore errors under -i and for
+ - lines, don't just print a different message.
+
+ * make.c (decode_switches): Fixed handling of arguments (or lack
+ thereof) to switches.
+
+Wed Feb 22 16:25:39 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * commands.c (construct_command_argv): Don't clobber LINE when
+ checking the IFS variable.
+
+Sun Feb 19 11:17:07 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * load.c [UMAX, not NO_LDAV] (load_average): Return 0.0 rather than
+ randomness when calls fail.
+
+ * Version 3.38.
+
+ * commands.c (fatal_error_signal): If handling a user kill signal
+ (TERM, INT, HUP), wait for the children without printing the
+ "Waiting for children" message, since they will die quickly.
+
+ * Version 3.37.
+
+ * remote-stub.c (remote_status): Take another arg, BLOCK. If this
+ is nonzero block waiting for remote children. If not, return 0 if
+ we would have to block.
+
+ * commands.c (child_handler) [not USG]: If called as a signal
+ handler, use wait3 and don't block.
+ [USG]: If called as a signal handler, return after handling one child.
+
+Sat Feb 18 13:37:04 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * file.c (snap_deps): Process all double-colon entries of each file,
+ not just the first one.
+
+ * Version 3.36.
+
+ * remote-stub.c: remote.c renamed.
+ remote.c: Just include remote-stub.c
+
+ * commands.c (child_handler): If we were called as a signal handler,
+ return after handling one child.
+
+ * commands.c [not USG]: Include <signal.h> and define `sigmask' if
+ <signal.h> doesn't.
+ (block_children, unblock_children): Use sigmask rather than
+ bitshifting explicitly (and incorrectly).
+
+ * remote.c (remote_kill): New function to send a signal to a
+ remote child.
+
+ * commands.c (fatal_error_signal): If we get a SIGTERM, send one to
+ each living child. If we get a SIGTERM, SIGINT, or SIGHUP, delete
+ all pending targets before waiting for children.
+ (struct child): Add new member `deleted'.
+ (start_job): Initialize `deleted' member to 0.
+ (delete_child_targets): New function to delete a given child's
+ targets, unless the `deleted' flag in the `struct child' says they
+ have already been deleted. Sets this flag before returning.
+
+Thu Feb 16 18:32:07 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c [USG]: Define `WRETCODE' correctly (X & 0xff00).
+
+Tue Feb 14 16:05:00 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (construct_command_argv): Don't make the 0th element of
+ the argument list be "sh" when executing /bin/sh, because start_job
+ uses the 0th element as the program name.
+
+Sun Feb 12 17:42:05 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.35.
+
+ * read.c (readline): Put a null in the beginning of the buffer
+ before starting the reading loop.
+
+ * read.c (read_makefile): Made main reading loop while
+ !feof (infile), and removed EOF check after calling readline.
+
+Sun Feb 5 19:52:38 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * remote.c (block_remote_children, unblock_remote_children): New
+ (stub) functions to block and restore asynchronous notification of
+ remote child death.
+
+ * commands.c (block_children): Call block_remote_children.
+ (unblock_children): Call unblock_remote_children.
+ (child_handler): If called as a signal handler, block remote
+ children on entry and unblock them before returning.
+
+ * commands.c (child_handler): For unknown children, if they are
+ remote, give their remote ID; if local, give their PID and make's.
+
+ * commands.c (execute_file_command): Don't put a new child in the
+ chain unless start_job succeeds. Block children before calling
+ start_job, and unblock them after putting the child in the chain and
+ incrementing `job_slots_used' (if start_job succeeded).
+
+ * commands.c (block_children, unblock_children): Make these globally
+ visible (not `static').
+ commands.h: Declare block_children and unblock_children.
+
+ * variable.c (expand_function: `shell'): Use
+ `shell_function_completed'. Block children before forking and
+ unblock after `shell_function_pid' is set properly and
+ `shell_function_completed' is reset to 0.
+
+ * commands.c (child_handler): When the child of the `shell' function
+ completes, set `shell_function_completed' to 1 if it actually ran,
+ or -1 if it didn't (due to fork or exec failure).
+
+ * commands.c (block_children, unblock_children): New functions to
+ block and unblock the child termination signal.
+ (wait_for_children): Use block_children and unblock_children.
+ (execute_file_commands): Block children around the critical section
+ wherein a new child is put on the chain.
+
+ * make.c (main): Change the environment to contain the correct
+ MAKELEVEL before re-execing.
+
+Sat Feb 4 18:28:48 1989 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.34.
+
+Fri Feb 3 16:36:49 1989 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * rule.c (default_variables): Fixed $(LINK.c).
+
+Wed Feb 1 18:05:07 1989 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Version 3.33.
+
+ * version.c: Removed copyright notice, since this is a one-line file.
+
+ * commands.c (error_status): Made it return BUF, rather than running
+ off the end (this apparently worked on Sun 3s for some reason).
+
+ * ar.c, commands.c, dep.h, load.c, make.c, make.h, read.c, remake.c,
+ rule.c, variable.c, Makefile: Changed copyrght notices to cover 1989.
+
+Mon Jan 30 15:51:28 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.32.
+
+Fri Jan 27 20:09:24 1989 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * remake.c (remake_file): Don't touch phony targets.
+
+ * rule.c (convert_to_pattern): Fixed an incorrect length passed to
+ savestring.
+
+ * variable.c (expand_function: `shell'): Close the read side of the
+ pipe on the parent side of the fork.
+
+ * commands.c (start_job): On the child of the fork, close the
+ BAD_STDIN fd if we're not using it.
+
+ * read.c (record_files): A file beginning with a dot can be a
+ default target if it also contains a slash (as in `../foo').
+
+ * commands.c (wait_for_children): For BSD, block SIGCHLD rather than
+ ignoring it to avoid a race condition when child_handler is returning.
+
+ * commands.c (child_handler): Do blocking waits.
+ (error_status): Return a string describing exit status. (Split out
+ of child_handler).
+
+ * read.c (multi_glob): Change VECTOR to VEC for Alliant.
+
+Thu Jan 5 00:06:51 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.31.
+
+ * make.texinfo (Features): Noted $(foo:PAT=SUB) from SunOS 4.0.
+
+ * make.texinfo (Options/Recursion): -d and -p go in the environment.
+
+ * load.c: Include "commands.h".
+
+Wed Jan 4 17:49:25 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * make.c (switches): -d and -p can come from the environment and are
+ put into it.
+
+ * read.c (record_files): Fixed the checking for duplicate deps so it
+ doesn't clobber the first one.
+
+ * make.texinfo: Documented default implicit rule changes.
+
+ * rule.c: Revamped default suffix rules. They now use Sun's style
+ of using variables `COMPILE.c', `LINK.c', etc. for each suffix, and
+ use `TARGET_ARCH' and `TARGET_MACH' variable where appropriate.
+ Also support Modula-2 compilation (suffixes .sym, .def, and .mod).
+ Ratfor Yacc support is gone, since nobody has yacc -r.
+ All EFL support is gone, since nobody uses EFL.
+
+ * ar.c, arscan.c: Don't assume `long int' and `int' are the same.
+
+ * commands.c [USG]: Fixed wait status bit encoding.
+ [USG and not USGr3] (dup2): Define this for SysVr2.
+
+ * make.h, dep.h, make.c [iAPX286]: Make allowances for this
+ brain-damaged compiler.
+
+ * make.texinfo (Variables: Flavors): Fixed a typo.
+
+Tue Jan 3 18:09:31 1989 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * ar.c (ar_member_date, ar_touch): Truncate member names to 15 chars.
+
+ * Version 3.30.
+
+ * commands.c [SYS_WAIT]: If this is defined, use BSD <sys/wait.h>
+ and wait3 even if USG.
+
+ * read.c (record_files): Defining .DEFAULT with no deps or commands
+ clears its commands.
+
+ * rule.c (default_suffixes): Added `.sh'.
+ (default_suffix_rules): Added single-suffix .sh rule, copies source
+ to target and makes target executable.
+ make.texinfo (Catalogue of Rules): Documented .sh rule and its use
+ in conjunction with SCCS.
+
+ * rule.c (set_default_suffixes): Define variable `SUFFIXES' to the
+ default list ("" under -r).
+ make.texinfo (Suffix Rules): Document `SUFFIXES' variable.
+
+ * rule.c (default_variables), make.texinfo (Implicit Variables):
+ Variable AR defaults to `ar', ARFLAGS to `rv', and RM to `rm -f'.
+
+ * rule.c (install_default_pattern_rules): Default variables are made
+ recursive.
+ (default_variables): Added "CPP", defined to "$(CC) -E".
+ (default_suffixes): Added `.S', before `.s'.
+ (default_suffix_rules): New rule for .S to .s, runs CPP.
+ All rules that use CPP now include "$(CPPFLAGS)".
+ make.texinfo (Catalogue of Implicit Rules, Implicit Variables):
+ Documented above changes.
+
+ * commands.c [USG] (sys_siglist): Don't define.
+ [USG] (init_siglist): New function to initialize sys_siglist.
+
+ * make.texinfo (Variables: Reference): Documented `$(foo:PAT=SUB)'
+ references.
+
+ * variable.c (variable_expand): A reference `$(foo:PAT=SUB)' is
+ equivalent to `$(patsubst PAT,SUB,$(foo))'.
+
+ * variable.c (variable_expand): Free the storage for the expansion
+ of a recursive variable when it is nod longer needed.
+
+ * variable.c (variable_expand): When checking for `$($(foo))', use
+ lindex so as not to search for the second `$' outside the parens.
+
+ * make.c (struct stringlist, main, decode_switches): Changed `index'
+ member to `idx'.
+
+Sat Dec 24 16:02:32 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (wait_for_children [USG]): Handle SIGCLD with SIG_DFL,
+ rather than SIG_IGN. Ignoring SIGCLD reportedly makes wait return -1.
+
+ * arscan.c [USGr3]: Define PORTAR to 1 (as with sun386).
+ (ar_scan [USGr3]): Remove trailing slashes from member names.
+
+Thu Dec 22 17:54:05 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * make.texinfo (Makefiles: Overriding Makefiles): New node
+ documenting use of .DEFAULT to have one makefile defer unmakeable
+ targets to another.
+
+ * make.texinfo (Implicit: Using Implicit, Implicit: Last Resort):
+ Mention empty commands and xref node `Empty Commands'.
+
+Wed Dec 21 20:12:40 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.29.
+
+ * make.c (struct command_switch, command_switches, et al): New
+ member `noarg_value', if not nil, ptr to value to use if no arg is
+ given to a switch that would otherwise require one. The -j option
+ can now be given w/o an arg, to mean infinite jobs.
+ * commands.c: If job_slots is zero, infinite jobs.
+
+ * read.c (read_all_makefiles, read_makefile): Make makefiles precious.
+
+ * make.c (decode_switches): For a positive_int or floating option,
+ if we moved to the next argument word, but found no argument for the
+ option, move back to the correct word.
+
+ * make.c (decode_switches): If we got any unknown options, die after
+ processing all arguments.
+
+ * GNUmakefile: Moved `include depend' to the end, so the default
+ goal will be set before then.
+
+ * load.c (wait_to_start_job [Unix, UMAX]): Merged into one version
+ under #ifdef LDAV_BASED. Only loop while we have jobs running.
+ Sleep for increasing amounts (increase one second per iteration)
+ before checking the load average (after the first check).
+ Get the load average from function load_average.
+ (wait_to_start_job [not LDAV_BASED]): Always return.
+ (load_average [UMAX]): Fetch load average for Encore UMAX.
+ (load_average [not NO_LDAV]): Fetch load average from /dev/kmem.
+ [not NO_LDAV]: Define LDAV_BASED.
+
+Tue Dec 20 18:54:50 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.28.
+
+ * commands.c (wait_for_children): Take second arg, ERROR. If
+ nonzero, and there are children, print a message on stderr.
+ (execute_file_commands, fatal_error_signal): Pass second arg.
+ * make.c (die), remake.c (update_goal_chain), variable.c
+ (expand_function: `shell'): Ditto.
+
+Sat Dec 17 01:05:38 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * commands.c (start_job): Call wait_to_start_job before forking.
+
+ * load.c (load_average): Converted to wait_to_start_job.
+
+ * remote.c: New file for remote execution functions.
+ (start_remote_job_p): Return nonzero if the next job should be run
+ remotely.
+ (start_remote_job): Start a remote job and return an ID for it.
+ (remote_status): Get status of dead remote children.
+
+Fri Dec 16 16:51:07 1988 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * commands.c (start_job): If start_remote_job_p () returns nonzero,
+ call start_remote_job to start the job rather than fork and exec.
+ (child_handler):
+
+ * commands.c (execute_file_commands): Moved load average checking to
+ start_job.
+
+ * commands.c (child_handler: USG): Record the pid wait returns.
+
+ * load.c (UMAX): Added some #include's needed for UMAX.
+
+ * read.c (multi_glob), variable.c (string_glob): Ignore a (char **)
+ -1 return from glob_filename.
+
+ * variable.c (variable_expand): Make sure we don't increment past
+ the end of the string we were passed.
+
+ * variable.c (variable_expand): Terminate the expansion.
+
+ * file.c (rename_file): If there is already a file under the new
+ name, set its contents equal to FILE's (ick).
+
+ * variable.c (define_automatic_variables): Pass all the args to
+ define_variable when defining MAKELEVEL!
+
+ * commands.c (execute_file_commands): If max_load_average > 0, and
+ we have children running, don't start up another child until the
+ load average goes below max_load_average.
+
+ * make.c: New variable `max_load_average'.
+ (struct command_switch, decode_switches, decode_env_switches):
+ Handle floating-point (double) args.
+ (command_switches): Added `-l' switch to set `max_load_average'.
+
+ * load.c (load_average): New file and function to return a double
+ that is the current load average (1.00 scale).
+ * GNUmakefile, oldMakefile: Pass flags in $(LOAD_AVG) for load.c.
+
+Thu Dec 15 15:22:08 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Makefile: Renamed to oldMakefile.
+ * GNUmakefile: Make Makefile from oldMakefile and depend.
+
+ * read.c (read_all_makefiles): When putting the default makefiles in
+ the read_makefiles chain so they will be remade, put them in the
+ right order.
+
+ * remake.c (update_goal_chain): If MAKEFILES is nonzero, always make
+ in serial, and return as soon as one goal whose `changed' member is
+ nonzero is successfully remade.
+
+ * commands.c: Don't include <sys/fcntl.h>.
+
+ * commands.c (construct_command_argv): Added ` to sh_chars.
+
+ * make.h: Don't declare construct_makeflags.
+
+ * make.c (main): Set up MAKEFLAGS and MFLAGS and make an environment
+ both before and after reading the makefiles, so the makefiles can
+ use them and possible change them, and later children will get the
+ right information.
+ (construct_makeflags): Replaced with define_makeflags (static void),
+ which defines the two variables.
+ * variable.c (define_automatic_variables): Don't define MAKEFLAGS
+ and MFLAGS.
+
+Mon Dec 12 14:40:31 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * Version 3.27.
+
+ * commands.c (child_handler): Reset the handler to ourselves when
+ called for USG, since it has no safe signals.
+
+ * commands.c: For USG, use an int rather than a `union wait' for
+ wait calls, and dissect it with bitmasks.
+ (child_handler): No wait3 system call in USG. Since we can't
+ protect from hanging, always return immediately if we have no
+ children we know about and we're not running a `shell' function.
+ (There is still the danger of hanging waiting for a child that died
+ without our being notified.)
+
+ * remake.c: Include <fcntl.h> instead of <sys/file.h>. What we need
+ is really in <fcntl.h>, and while BSD <sys/file.h> includes
+ <fcntl.h>, USG doesn't.
+
+ * make.c (main): Figure out the program name before doing anything
+ which might need it (in a call to error or fatal).
+
+ * dir.c, glob.c: Use `struct dirent' and <dirent.h> for USGr3.
+
+ * arscan.c (ar_scan): Added missing & before buf (which is an int)
+ if SARMAG is not defined (SysV).
+
+Fri Dec 9 18:44:13 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Version 3.26.
+
+ * dir.c (find_directory, dir_file_exists_p): Keep track of how many
+ directories we have open and don't let it be more than
+ MAX_OPEN_DIRECTORIES (currently 10).
+
+ * variable.c (expand_function: `foreach'): Use expand_argument
+ rather than variable_expand so each repetition doesn't clobber the
+ last!!!
+
+Mon Dec 5 15:58:46 1988 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * Version 3.25.
+
+ * Makefile: Define `install' target.
+
+ * GNUmakefile: Don't include GNUmakefile or depend in the
+ distribution file.
+
+Wed Nov 30 15:53:42 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * commands.c (execute_file_commands): Don't clobber a null into
+ random storage if there were no $^ and/or $? words.
+
+ * remake.c (check_dep): Set *MUST_MAKE_PTR nonzero if a dependency
+ doesn't exist.
+
+ * ar.c (ar_member_date, ar_touch): Make sure the modtime of the
+ archive file itself is known before we fetch or change the modtime
+ of one of its members.
+
+ * read.c (read_makefile): Expand variable and function references
+ before parsing rules so variable can contain special characters
+ (colons and semicolons).
+
+Sat Nov 26 11:36:31 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * variable.c (expand_function: `filter', `filter-out'): Fixed so
+ that filter-out works right.
+
+ * variable.c (expand_function: `filter', `filter-out'): Made these
+ functions use each word of their first argument as a pattern.
+
+Fri Nov 25 10:51:47 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.24.
+
+ * read.c (record_files): If a target is listed more than once in a
+ single rule (that defines commands), give a warning message rather
+ than the counter-intuitive message saying commands were already
+ defined (in the same place).
+
+ * make.c (fatal, error): Made them both take 6 args since there is
+ at least one error message that need that many. Too bad vfprintf is
+ not universal!
+
+ * Version 3.23.
+
+ * read.c (read_makefile): Moved the construction of the `struct
+ commands' into record_files. Call record_files before recursing for an
+ included makefile so the higher-up will determine the default goal.
+ (record_files): Take arguments COMMANDS, COMMANDS_IDX and
+ COMMANDS_STARTED and construct a `struct commands.
+
+Thu Nov 24 14:36:33 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.22.
+
+ * make.c (main): Made it a fatal error if we can't move back to the
+ directory we started in before re-execing.
+
+ * make.c (main): Get the current directory before doing anything
+ else, so we know it even if we don't need it for the value of
+ `MAKE', since we might want it when re-execing.
+
+Wed Nov 23 13:34:44 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.21.
+
+ * read.c (record_files): Eliminate duplicate deps in a chain.
+
+ * variable.c (expand_function: `sort'): Pass the right number to
+ qsort, not one less.
+
+ * remake.c (remake_file): Always call notice_finished_file if
+ FILE->command_state == cs_finished.
+
+ * commands.c (execute_file_commands): Call notice_finished_file to
+ set FILE's status correctly when start_job fails (because it's out
+ of commands or running under -n).
+
+Fri Nov 18 15:31:12 1988 Roland McGrath (mcgrath at saffron.Berkeley.EDU)
+
+ * Version 3.20.
+
+ * remake.c (update_file_1): Set the `update_status' of FILE to
+ nonzero and set FILE's `updated' bit if we have decided to give up
+ on remaking FILE because of errors in the dependencies.
+
+ * rule.c (pattern_search): Debugging messages use `dependency' (vs.
+ `dependent') properly.
+
+ * make.texinfo (Conditionals: Conditional Syntax): Function index
+ entries for `ifndef' and `ifneq'.
+
+ * variable.c (define_automatic_variables): Define `MAKELEVEL' to the
+ decimal number of the makelevel, since it may be malformed or blank.
+
+ * remake.c (remake_file): Call notice_finished_file after touching.
+
+Sat Nov 12 19:29:34 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.19.
+
+ * GNUmakefile (dist): Pass the `-f' flag to compress.
+
+ * vpath.c (build_vpath_lists): Check for VPATHS being nil after
+ constructing the general VPATH list from the `VPATH' variable.
+
+Fri Nov 11 08:02:26 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (fatal, error): Made error messages for recursive runs be
+ shorter.
+
+Thu Nov 10 16:51:36 1988 Roland McGrath (mcgrath at basil.Berkeley.EDU)
+
+ * Version 3.18.
+
+ * read.c (read_makefile): Made it eat leading spaces and formfeeds
+ (but not tabs), like it's documented to.
+
+ * read.c (read_makefile): Let included makefiles determine the
+ default goal, as is done by System V Make.
+
+Tue Nov 1 19:03:08 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * variable.c (new_environ): Don't increment VCNT when a variable is
+ rejected.
+
+Fri Oct 28 16:54:15 1988 Roland McGrath (mcgrath at basil.Berkeley.EDU)
+
+ * Version 3.17.
+
+ * rule.c (convert_to_pattern): Don't use the same storage for a name
+ in two rules since new_pattern_rule may free this storage when a
+ rule is discarded.
+
+ * rule.c (new_pattern_rule): Undid useless change I made Oct 25.
+
+Thu Oct 27 19:17:53 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.16.
+
+ * GNUmakefile, Makefile: Fixed a typo in a comment.
+ * Makefile: Removed malloc.o from object file list.
+
+ * variable.c: Removed old debugging #define's for xmalloc and
+ xrealloc so non-ANSI cpp's won't barf.
+
+ * make.c (main): Made local array for temp file name static so
+ compilers that don't do auto aggregate initialization won't barf.
+
+ * read.c: Removed static declaration of copy_dep_chain since it is
+ no longer static.
+
+Tue Oct 25 16:59:30 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * rule.c (new_pattern_rule): If we threw out the new rule because it
+ matched an old one and OVERRIDE was zero, don't put the freed
+ pointer in the chain!
+
+Wed Oct 19 15:07:43 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Version 3.15.
+
+ * variable.c (expand_function: `sort'): Don't do the sorting and
+ writing out if there were no words in the first place.
+
+ * remake.c (remake_file): Only fail with a "no way to make" message
+ for a dependency (non-target) file. If we don't know how to remake
+ a target file, pretend it was successfully remade and is very new.
+
+ * remake.c (remake_file): Don't increment `files_remade' for a
+ non-target file we don't know how to remake.
+
+ * read.c (record_files): Don't die with "both : and :: entries" for
+ a file whose `is_target' flag is not set.
+
+Tue Oct 18 17:24:11 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * variable.c (expand_function: `patsubst', `subst'): Free the right
+ things!
+
+ * variable.c (expand_function: `subst'): Don't clobber the
+ pointer to the end of the second arg and then try to use it!!!
+
+Mon Oct 17 16:44:45 1988 Roland McGrath (mcgrath at catnip.Berkeley.EDU)
+
+ * variable.c (expand_function: `patsubst'): Don't clobber the
+ pointer to the end of the second arg and then try to use it!!!
+
+ * variable.c (expand_function: `word' function): Made it parse its
+ second argument correctly.
+
+ * ar.c (ar_touch): Return 1 rather than -1 for on errors.
+
+Sat Oct 15 15:12:16 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.14.
+
+ * GNUmakefile: Removed explicit rule for make.dvi since the built-in
+ implicit rule now works.
+
+ * rule.c (default_suffix_rules): Fixed .texinfo.dvi rule yet again
+ so that it really works, now that parens are counted.
+
+ * remake.c (update_file_1): Set FILE's `updated' flag after calling
+ remake_file if it failed or finished immediately.
+
+ * remake.c (update_file): Use the `updated' flag rather than the
+ command state to decide if a file was fully considered, and
+ therefore might give an "up to date" message.
+
+ * variable.c (expand_function): Made all functions that take more
+ than one argument count parens of the appropriate flavor in their
+ args and ignore commands nested in parens.
+
+Fri Oct 14 18:35:00 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * read.c (read_all_makefiles): Pass second arg to read_makefile for
+ default makefiles.
+
+Thu Oct 13 16:40:08 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.13.
+
+ * GNUmakefile: Added an explicit rule for make.dvi since the
+ built-in .texinfo.dvi implicit rule is screwed up.
+
+ * rule.c (default_suffix_rules): Added a comment that the
+ .texinfo.dvi rule does not work because of an ahem, feature of Make
+ that at some point will be fixed--er, enhanced to alleviate this
+ difficulty.
+
+ * rule.c (default_suffix_rules): Fixed Texinfo -> DVI rule (again).
+
+ * make.texinfo (Commands: Execution): Documented new competing for
+ standard input among children.
+
+ * commands.c (struct child): Added `good_stdin' flag to tell if this
+ child has the stdin that doesn't point into nirvana.
+ (good_stdin_used): New variable to tell if any child has the good
+ standard input.
+ (child_handler): Reset `good_stdin_used' if a dead child's
+ `good_stdin' flag is set.
+ (start_job): Give the new child the good standard input if
+ `good_stdin_used' is no set, and set the child's `good_stdin' flag
+ appropriately.
+
+ * rule.c (default_suffix_rules): Changed Texinfo -> DVI rule to work
+ better (I hope).
+
+ * read.c (read_all_makefiles): Stop reading default makefiles after
+ one is found.
+
+ * read.c (read_makefile): Reset `reading_filename' and
+ `reading_lineno_ptr' after recursing for an included makefile.
+
+ * GNUmakefile: New GNU Make-specific makefile that does everything
+ Makefile does plus distribution stuff, and doesn't contain any hacks
+ to try to work with Unix make.
+
+ * Makefile: Removed distribution stuff.
+
+ * make.c (main): Use mktemp to construct the names of temporary
+ files used for standard input makefiles.
+
+ * make.c (main): Don't turn standard input into a broken pipe.
+
+ * commands.c (start_job): Keep two extra file descriptors around: a
+ good standard input, and a bad one that reads from a broken pipe.
+ On the child side of the fork, if there are other children, give
+ this one the broken pipe so they won't compete; if this is the only
+ one, give it the good standard input.
+
+ * make.h: Declare notice_finished_file.
+
+ * commands.c (execute_file_commands): Use noticed_finished_file
+ after waiting for the child when there is only one job slot.
+
+ * remake.c (notice_finished_file): New function to re-check mtime's
+ and such things to be done when commands finish.
+ (update_file_1): Use notice_finished_file.
+
+ * commands.c (child_handler, execute_file_commands): Use new
+ variable `job_slots_used' to record the number of jobs currently
+ running, rather than diddling with `job_slots'.
+ (execute_file_commands): Increment `job_slots_used' before calling
+ start_job and decrement it on failure to avoid race condition.
+ If there is only one job slot, wait for the child to finish and
+ return its status so commands are run in linear order, as if there
+ were no parallelism.
+
+Wed Oct 12 15:59:03 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * remake.c (remake_file): Don't print a "No way to make" message for
+ targets whose `dontcare' flags are set.
+
+ * read.c (read_all_makefiles): Set the `dontcare' flag of the
+ `struct file' each default makefile added to the chain.
+
+ * file.h (struct file): Add `dontcare' member.
+
+ * read.c (read_all_makefiles): When no default makefiles are found,
+ put the names of all those tried in the `read_makefiles' chain so
+ they will be updated if possible, giving their `struct dep's'
+ `changed' members the value of 0 so we won't care if they cannot be
+ found or remade.
+
+ * make.texinfo (Makefiles: Remaking Makefiles): Documented that
+ default makefiles will be remade if not found.
+
+ * read.c (read_all_makefiles): If no default makefiles can be found,
+ go through the list of default names, trying to make one, stopping
+ if one is made.
+
+ * remake.c (remake_file): Set STATUS to 0 after successfully touching.
+
+ * dir.c (file_impossible, file_impossible_p): Don't clobber FILENAME
+ to "" and then try to to a strcmp on it!!!
+
+Mon Oct 10 16:09:18 1988 Roland McGrath (mcgrath at cinnamon.Berkeley.EDU)
+
+ * make.c (main): Don't do `dir_load (".")'.
+
+ * rule.c (count_implicit_rule_limits), vpath.c
+ (construct_vpath_list): Test the existence of a given directory by
+ `dir_file_exists_p (DIR, ".")' and assume that if this returns zero,
+ it means the directory really does not exist.
+
+ * dir.c (struct dirdata): Replaced with `struct directory' for
+ directories, each containing a chain of `struct dirfiles', one for
+ each file (real or impossible).
+ (dir_load): Removed.
+ (find_directory): New function to find the `struct directory' for a
+ named directory and return it (possibly creating a new one).
+ (dir_file_exists_p): Read the directory on the fly if its stream is
+ still valid (and ever was) if the file we're looking for is not
+ already in the hash tables.
+ (file_impossible, file_impossible_p, dir_name, print_dir_data_base):
+ Use the new directory/file scheme.
+
+ * make.texinfo: Miscellaneous editorial changes and clarifiactions.
+
+ * commands.c (struct child): Remove `environ' member.
+ (child_handler, start_job, execute_file_commands): Remove use of
+ `environ' member and new_environ.
+
+ * make.c (main): Call new_environ after reading makefiles.
+
+ * variable.h: Declare `new_environ' to return void.
+
+ * variable.c (new_environ): Put the environment in `environ' and
+ return void.
+
+Fri Oct 7 15:48:39 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Version 3.12.
+
+ * Makefile: Don't make the uncompressed tar file.
+
+ * variable.c (expand_function: `shell' function): Made it not expect
+ read to null-terminate the buffer.
+
+ * Makefile: Made it use a temporary symlink to . rather than a
+ temporary directory to make the distribution tar file.
+
+Thu Oct 6 17:52:35 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.11.
+
+ * make.texinfo: Fixed a line that got garbaged somehow.
+
+Mon Oct 3 16:14:39 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * make.c (main): Try to move back to the directory we started in
+ before re-exec ourself.
+
+ * remake.c (update_file_1): A double-colon target with no deps
+ always needs to be remade.
+
+ * remake.c (remake_file): Changed "No way to make" message to say
+ `target' rather than `file'.
+
+Sun Oct 2 12:50:47 1988 Roland McGrath (mcgrath at catnip.Berkeley.EDU)
+
+ * remake.c (update_file_1): Set FILE->update_status to the return
+ value of remake_file.
+
+ * rule.c (convert_to_pattern): Fixed swapped lengths passed to
+ xmalloc for source/target suffixes.
+
+ * make.texinfo: Documented that MAKEFLAGS and MFLAGS are read in
+ from makefiles. Updated the `Features' section a bit.
+
+ * make.c (main): Read switches from MAKEFLAGS and MFLAGS variables
+ after reading in makefiles.
+
+ * make.c (main): Put a line "/tmp/foo:;" rather than ".PHONY:
+ /tmp/foo" in front of temp files made for stdin makefiles.
+
+ * remake.c (update_file): Test the state of the right `struct file'
+ for double-colon files.
+
+ * make.c (main): Put a ".PHONY: /tmp/foo" line in front of temp
+ files made for stdin makefiles so they won't be remade when we
+ re-exec. Kludge-o-matic!!
+
+ * remake.c (update_goal_chain): Judge files as being finished based
+ on their `updated' flag, not their state.
+
+ * read.c (read_makefile): Don't check for FILENAME being "-".
+ (read_all_makefiles): Set each element of MAKEFILES to the name put
+ in READ_MAKEFILES by read_makefile, since read_makefile may free the
+ storage for the name it is passed, and someone might want to look at
+ the elements of MAKEFILES again.
+
+ * make.c (main): For each `-f' flag with arg `-' (standard input),
+ read standard input into a temp file and pass the temp file's name
+ to read_all_makefiles, after making sure it will not be remade.
+
+ * make.c (construct_makeflags): Always put out `-j1'.
+
+Sat Oct 1 00:19:59 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * commands.c (execute_file_commands): If commands are nothing but
+ whitespace, set the state to `cs_finished' before returning 0.
+
+ * make.c (decode_switches): Allocate space for args in stringlists
+ so they can be freed later.
+
+ * make.h: Declare `makelevel'.
+
+ * variable.c (makelevel): Moved to make.c (and made global).
+
+ * make.c (fatal, error): Print the makelevel if it's > 0.
+ (perror_with_name): Use error rather than calling fprintf directly.
+ (pfatal_with_name): Use fatal rather than fprintf and die.
+
+ * variable.c (new_environ): Don't put default variables (origin
+ `o_default') into the environment; they just take up space.
+
+ * read.c (read_makefile): Don't add FILENAME to the chain of read
+ makefiles if it's "-" (standard input).
+
+ * remake.c (update_goal_chain): Set STATUS correctly when nothing
+ happens (as well as in all other situations).
+
+ * make.c (construct_makeflags): Put a `-' before each switch and
+ spaces between them.
+
+ * Version 3.10.
+
+ * commands.c (wait_for_children): Don't check if `children' is nil.
+ This is the case when waiting for the child of a `shell' function.
+
+ * dir.c (dir_load): Don't add a hash-table entry for directory
+ DIRNAME and filename "" if DIRNAME doesn't exist.
+
+ * commands.c (execute_file_commands): Return 0 after start_job
+ returns 1 (failure) under the -n flag.
+
+ * remake.c (remake_file): Set the state to `cs_finished' when not
+ calling execute_file_commands.
+
+ * remake.c (update_goal_chain): Second arg is now MAKEFILES, nonzero
+ meaning to disable -t, -q, and -n for each target unless the target
+ was also given on the command-line.
+
+ * read.c (read_makefile): Enter the `struct file's for the makefiles
+ added to the `read_makefiles' `struct dep' chain.
+
+ * remake.c (update_goal_chain): Made it not enter the files for the
+ goals in the chain. It will already have been done.
+
+ * rule.c (convert_to_pattern): Null-terminate the names of targets
+ and deps of the pattern rules properly.
+
+Fri Sep 30 18:56:20 1988 Roland McGrath (mcgrath at nutmeg.Berkeley.EDU)
+
+ * make.c (main): Call install_default_pattern_rules.
+
+ * make.h: Declare copy_dep_chain.
+
+ * read.c (copy_dep_chain): Moved to make.c (and made global).
+
+ * make.c (main): Call update_goal_chain to update goals.
+ Update read makefiles and re-exec self if they change.
+
+ * remake.c (update_file): Make this function static.
+ (update_goal_chain): New function to update a `struct dep' chain of
+ goals, waiting until they are all finished before returning.
+
+ * make.h: Don't declare update_file. Declare update_goal_chain.
+
+ * make.c (main): Call snap_deps, etc. that were in read_all_makefiles.
+
+ * read.c (find_makefile): Removed this function.
+ (read_all_makefiles): Don't update makefiles, don't diddle with
+ pattern rules, don't call snap_deps, etc. Return a `struct dep'
+ chain of all makefiles read.
+ (read_makefile): Now takes two args: FILENAME and TYPE, which is 0
+ for a normal makefile, 1 for MAKEFILES variable or 2 for an included
+ makefile. Add a `struct dep' containing the name of the makefile
+ (as it was found in the search path for type 2s), and TYPE in the
+ `changed' member to the global `read_makefiles' chain.
+
+ * make.h, rule.c (displace_pattern_rules,
+ add_displaced_pattern_rules): Removed these functions.
+
+ * read.c (read_makefile): Variable-expand the name of an `include'd
+ makefile before calling find_makefile on it.
+
+ * file.c (snap_deps): If the `struct file' for a `struct dep'
+ already exists, free the `struct dep's `name' member before setting
+ it to nil (since this info is in the `struct file').
+
+ * read.c (copy_dep_chain): Made it copy each name rather than
+ leaving multiple `struct dep's with the same pointers.
+
+Thu Sep 29 19:08:13 1988 Roland McGrath (mcgrath at catnip.Berkeley.EDU)
+
+ * make.c (decode_switches): Fixed second decode_env_switches call to
+ use correct length of "MFLAGS" (6, not 5).
+
+ * read.c (read_makefile): Don't stop reading when readline returns
+ zero lines read. Only stop when the stream reaches EOF. This makes
+ it recognize the last line of a makefile without a newline.
+
+ * remake.c (remake_file): If we don't know how to make FILE, set its
+ command state to `cs_finished'.
+
+ * remake.c (update_file): Don't write the "up to date" message if
+ update_file_1 returned a nonzero status.
+
+Wed Sep 28 16:30:07 1988 Roland McGrath (mcgrath at catnip.Berkeley.EDU)
+
+ * commands.c (child_handler): Set the `update_status' member
+ properly for ignored errors.
+
+ * rule.c (convert_to_pattern): Made it not care about if the target
+ suffix comes before the source suffix in the .SUFFIXES list.
+
+ * make.texinfo: Misc editorial changes.
+
+ * commands.c (wait_for_children): Return immediately if `children'
+ is nil (there are no children).
+
+Tue Sep 27 15:33:14 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Version 3.09.
+
+ * commands.c (struct child): New member `command_ptr' to hold the
+ current position in the commands. The `commands' member is never
+ changed.
+ (start_job, child_handler, execute_file_commands): Use new method
+ for `commands' and `command_ptr' members.
+
+ * make.c (decode_env_switches): Skip past an invalid letter (instead
+ of looping forever).
+
+ * commands.c (struct child): Add `environ' member to hold the
+ environment for this child.
+ (execute_file_commands): Get a new environment from new_environ and
+ put in the the new `struct child's `environ' member.
+ (child_handler): When freeing a child, free its `commands' member, the
+ elements of its `environ' array and its `environ' member itself.
+ (start_job): Set `environ' to the child's `environ' member before
+ exec'ing the command.
+
+ * variable.h, variable.c (new_environ): Made it return the new
+ environment, not putting it in `environ'.
+
+ * remake.c (update_file): Don't give a "is up to date" message
+ unless no files were remade and the state went from `cs_not_started'
+ to `cs_finished', so repeat calls to finish jobs won't get the message.
+
+Mon Sep 26 16:26:08 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * Version 3.08.
+
+ * make.texinfo (Commands: Execution): Documented that children will
+ be waited for rather than killed.
+
+ * commands.c (fatal_error_signal): Wait for children.
+ (kill_children): Removed this function.
+
+ * make.c (main, die): Wait for children to die, don't kill them.
+
+ * variable.c (expand_function): Use wait_for_children.
+
+ * make.c (main): Use wait_for_children rather than child_handler.
+
+ * commands.c (wait_for_children): New function to block waiting for
+ children, insuring that child_handler is not called recursively.
+ (execute_file_commands, kill_children): Use wait_for_children.
+
+ * commands.c (child_handler): Start up additional commands in a
+ sequence after an ignored error.
+
+ * remake.c (update_file): Don't print "`foo' is up to date" messages
+ when update_file_1 returns while commands are executing.
+
+ * remake.c (update_file_1): Pass the file name to name_mtime, not
+ the bloody `struct file', dammit!!
+
+ * commands.c (child_handler): Print out the "*** ..." error message
+ when not under -i. (I somehow forgot this.)
+
+ * remake.c (update_file_1): Use name_mtime rather than file_mtime to
+ re-get the mtime of a file whose commands have finished.
+
+ * make.c (command_switches, decode_switches, decode_env_switches):
+ Make all switches that take string args allow them right after the
+ switch letter.
+
+ * commands.c (child_handler): Check for a child being the `shell'
+ function's command returning and set the global variable for
+ expand_function to check.
+
+ * variable.c (expand_function): For the `shell' function, instead of
+ waiting for the child shell ourselves, let child_handler do it and
+ loop around waiting for something to happen.
+
+ * make.c (print_version): Made the copyright year static, not dynamic.
+
+ * make.h, make.c: Remove construct_argv function.
+
+ * make.c (main): Say "no goal target" instead of "no target".
+
+ * make.texinfo (Commands: Parallel): Don't send SIGKILL.
+
+ * commands.c (kill_children): Don't send SIGKILL to children that
+ aren't killed by the first signal.
+
+ * make.c (main), commands.c (kill_children): Decide between SIGCHLD
+ and SIGCLD based on whether or not SIGCHLD is defined, not on USG.
+
+ * Makefile: Link make with $(LOADLIBES).
+
+ * read.c (construct_include_path): Fixed another bad xrealloc call.
+
+ * make.c (decode_switches): Fixed an xrealloc call with no first arg.
+
+Sat Sep 24 01:16:21 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * Version 3.07.
+
+ * remake.c (update_file_1): If deps are running, set state to
+ `cs_deps_running' and return 0. If deps are done, run commands.
+
+ * commands.c (child_handler): Made it delete non-precious targets
+ killed by fatal signals.
+
+ * make.texinfo: Documented parallelism.
+
+Fri Sep 23 16:52:27 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * remake.c (update_file_1): Don't return if FILE's state is
+ `cs_deps_running'. In that case, we need to run through and check
+ the states of all our dependencies.
+
+ * commands.c (execute_file_commands): Decrement `job_slots' after
+ starting a new job to run file commands.
+
+ * commands.c (start_job): Made it set the state to `cs_running'.
+
+ * make.c (main): Fixed usage of `g', `lastgoal', and `goals' in the
+ goal-making loop.
+
+ * commands.c (child_handler): When commands finish, set the
+ corresponding file's `update_status' and `updated' flags as
+ appropriate, and reset the modtimes of the file and any `also_make'
+ files it has.
+
+ * remake.c (remake_file): Don't re-set `last_mtime' and set `updated'.
+
+ * commands.c (fatal_error_signal): Don't swallow all the children
+ with a loop around `wait ((union wait *) 0)'!!!
+
+ * make.c (struct command_switch): Added `positive_int' type.
+ (switches): Added -j (job_slots).
+ (construct_makeflags, decode_switches, decode_env_switches):
+ Handle`positive_int'-type switches.
+
+ * glob.c (glob_vector): Rename local variable `vector' to `VeCtOr'.
+ This is said to avoid a conflict with some system's global `vector'
+ variable.
+
+ * variable.c (expand_function): Made the `shell' function use
+ construct_command_argv and do its own child control and piping.
+
+ * make.c (main): Turn standard input into a broken pipe after
+ reading in all makefiles (the last time it will be needed).
+
+ * commands.c (struct child): Remove `pipe_fd' member. We don't use
+ pipes any more.
+ (start_job): Return 0 for success, 1 or failure (rather than void).
+ Don't use pipes. Don't turn the child's stdin into a broken pipe.
+ (child_handler): Print "*** Error" messages when necessary.
+ Die on failed commands when -k was not given.
+ (execute_file_commands): Check the return of start_job and remove
+ the child from the chain and return failure if it is nonzero.
+
+ * make.c (die): New function to clean up and exit.
+ (fatal, pfatal_with_name): Use die.
+
+Thu Sep 22 14:27:11 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * commands.c (struct child): Added `commands', `pipe_fd', and
+ `noerror' members to keep track of info about a command thread.
+ (start_job): New function to start a job and update the argument
+ `struct child' to reflect its status.
+ (execute_file_commands): Merged run_file_commands back in.
+ Made it use new start_job function.
+
+ * rule.c (freerule): Don't free the `struct commands' of the
+ discarded rule. It may be used in more than one place.
+
+ * commands.c (execute_command_line): Made it not try to delete the
+ possibly partly-made file. The child_handler function will do this.
+ (fatal_error_signal): Ditto + call kill_children.
+
+ * make.h: Declare job_slots.
+
+ * make.c (main): Collect goals in a dep chain and run through this
+ chain waiting for a child, eliminating finished goals, updating all
+ remaining goals, and quitting if they fail and not -k.
+
+ * commands.c (child_handler): If called with SIG < 0, - SIG is the
+ max number of children to bury.
+
+ * commands.c (child_handler): If called with SIG as zero,
+ block waiting for running children.
+ (kill_children): Call child_handler with zero rather than SIGCHLD.
+
+ * remake.c (update_file_1): Use the `command_state' member of FILE
+ and its dependencies to determine what commands are running, what to
+ do, etc. If commands or dep commands are running when we are
+ called, return success (0). If commands finished since the last
+ time we were called, return their status.
+
+ * commands.h: Declare kill_children.
+
+ * commands.c: Define `struct child' to keep track of child
+ processes, with the chain in `children'.
+ (child_handler): New function to catch child-termination signals
+ (SIGCHLD, or SIGCLD for USG), store the returned status in the
+ appropriate structure, take the now-obsolete `struct child' out of
+ the chain, and free its storage.
+ (execute_file_commands): Put all of the stuff invloving running the
+ commands into new function run_file_commands. Execute_file_commands
+ now does process management for the commands, while
+ run_file_commands (which is run in a subprocess) runs the commands.
+ (kill_children): New function to kill all running children by
+ sending them signal SIG. If there are any children still living
+ after they are all sent SIG, they are all sent SIGKILL.
+
+ * make.c (main): Catch SIGCHLD (SIGCLD for USG) with child_handler.
+
+ * commands.h: Declare child_handler function.
+
+ * commands.c (execute_file_commands): Check the `command_state'
+ member of FILE and return 0 if it is `cs_running' or
+ `cs_deps_running' and return the stored status if it is `cs_finished'.
+
+ * file.h (struct file): Added `command_state' member.
+
+ * commands.c (execute_command_line): Add `$' to the list of
+ characters special to the shell.
+
+Wed Sep 21 15:57:41 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * read.c (read_all_makefiles): Call convert_to_pattern before
+ recomputing the limits after adding the displaced rules.
+
+ * make.c (main): Move calls to snap_deps, convert_to_pattern, and
+ build_vpath_lists to read_all_makefiles.
+
+ * read.c (read_all_makefiles): Install the default pattern rules
+ before checking to remake the makefiles, displace these rules before
+ reading in the makefiles, and then add the displaced rules to the
+ chain after reading in all the makefiles.
+
+ * make.c (main): Don't call install_default_pattern_rules or
+ count_implicit_rule_limits.
+
+ * make.h: Declare displace_pattern_rules and
+ add_displaced_pattern_rules.
+
+ * rule.c (displace_pattern_rules, add_displaced_pattern_rules): New
+ functions to stow the chain and add the stowed chain on the end of
+ the current chain.
+
+ * make.texinfo (Implicit: Search Algorithm): Fixed PREV reference.
+
+ * make.c (main): Call construct_include_path right after decoding
+ the switches.
+
+ * read.c (find_makefile): Use rename_file.
+
+ * file.h: Declare rename_file.
+
+ * file.c (rename_file): New function to rename a `struct file' and
+ put it in the correct hash bucket.
+
+ * read.c (find_makefile): New function to find and update a makefile.
+ (read_all_makefilese): Use find_makefile.
+ (read_makefile): Don't do updating. Removed UPDATEIT arg.
+
+ * remake.c (update_file_1): Took out setting the `updated' member to
+ -1 rather than 1 sometimes.
+
+ * make.c (main): Made it print version info before doing anything else.
+
+ * remake.c (library_file_mtime, f_mtime): Removed use of last two
+ arguments to vpath_search.
+
+ * rule.c (pattern_search): Removed use of last two arguments
+ to vpath_search.
+
+ * vpath.c (vpath_search, selective_vpath_search): Removed unused
+ DIRPREFIX and DPLEN args.
+
+ * read.c (read_makefile): Also turn off -n when updating makefiles.
+
+Tue Sep 20 17:01:10 1988 Roland McGrath (mcgrath at pepper.Berkeley.EDU)
+
+ * Makefile: Put tags files in the tarfile.
+
+ * read.c (read_makefile): Get the modtime of the makefile via a stat
+ call so that a later file_mtime call won't do VPATH search for it.
+
+ * read.c (read_makefile): Don't turn off -t and -q if the makefile
+ was a command-line target.
+
+ * make.c (main): Enter command-line targets as files and set their
+ `cmd_target' members.
+
+ * file.h (struct file): Added `cmd_target' member.
+
+ * read.c (read_makefile): Temporarily turn off -t and -q while
+ updating makefiles.
+
+ * make.c (main): Don't use arg 0 from other_args (which is now
+ argv[0]; i.e., the program's name).
+
+ * read.c (read_makefile): Only return nonzero if commands were
+ actually run to remake the makefile.
+
+ * remake.c (update_file_1): Set FILE->updated to -1 if no commands
+ were actually run (because no update was done or -t was given).
+
+ * make.c (decode_switches): Fixed bug wherein xrealloc was passed
+ bad args if it tried to expand other_args->list.
+
+ * read.c (read_all_makefiles): Made it not look at the `MAKE'
+ variable, just use argv[0].
+
+Sun Sep 18 17:34:11 1988 Roland McGrath (mcgrath at paris.Berkeley.EDU)
+
+ * read.c (rerun_make): New function to re-exec make.
+
+ * make.c (construct_makeflags, construct_argv): New functions to
+ construct the `MAKEFLAGS' variable and to construct an arg list from
+ parsed info.
+
+ * read.c (read_makefile): New arg UPDATEIT, if nonzero, says to
+ update the makefile as a target before reading it in. When reading
+ included makefiles, pass this as zero. Now returns nonzero if the
+ makefile was updated, zero if not.
+ (read_all_makefiles): Pass a nonzero UPDATEIT arg to read_makefile
+ for all default and -f makefiles and all makefiles from the
+ `MAKEFILES' variable. If any of the makefiles has changed, re-exec
+ self to re-read them.
+
+ * remake.c (update_file): Print a "File `foo' up to date'" message
+ under -p.
+
+ * commands.c (execute_file_commands): Allocate one byte for each of
+ $^ and $< rather than zero if they are to be empty.
+
+Fri Sep 16 13:59:59 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Version 3.06.
+
+ * make.c (command_switches): Fixed entry for `-o' switch.
+
+ * make.texinfo: Renamed -c switch to -C.
+
+ * make.c: Renamed -c switch to -C.
+
+ * Miscellaneous de-linting.
+
+ * read.c (record_files): Made it not free the storage for the name
+ if it started with `./' and was therefore not quite the same as in
+ the `struct file'.
+
+ * read.c (record_files): If commands were specified twice, the error
+ message specifies in what files and at what line numbers.
+
+ * make.c (main): If any of the signals we usually fatal on were
+ ignored by the parent (probably a shell), ignore them.
+
+ * make.c (main): Print version info for -v, -p, or -d.
+ (print_data_base): Don't print version info. It will be done in main.
+
+ * variable.c: Increased number of hash buckets to 257.
+
+ * file.c: Increased number of hash buckets to 1007.
+
+ * rule.c (count_implicit_rule_limits): Moved comptation of
+ `maxsuffix' to convert_to_pattern, since that function uses
+ `maxsuffix', and must be called before count_implicit_rule_limits.
+
+ * rule.c (pattern_search): If an existent (non-intermediate)
+ dependency was found via a terminal rule, set its
+ `tried_implicit' flag, so it will never have implicit rule search done.
+
+ * glob.c: Bug fix to avoid alloca(0).
+
+ * arscan.c: USG and Sun386i fixes.
+
+Thu Sep 15 19:40:26 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * make.texinfo: Fixed some typos and spelling errors.
+
+Wed Sep 7 14:20:39 1988 Roland McGrath (mcgrath at helen.Berkeley.EDU)
+
+ * make.c (decode_switches): Fixed bug wherein a bad option would
+ give a useless error message and loop forever.
+
+Tue Sep 6 14:36:02 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.texinfo: Documented `shell' function.
+
+ * variable.c (expand_function): New function `shell', does
+ backquote-style command expansion of its arg.
+
+ * commands.c (execute_command_line): Second arg OUTBUF, if not nil,
+ gets filled in with a malloc'd buffer containing the piped stdout of
+ the command.
+ (execute_file_commands): Use above (pass nil).
+
+Mon Sep 5 17:03:49 1988 Roland McGrath (mcgrath at hecuba.Berkeley.EDU)
+
+ * Makefile: Added copyright notice.
+ Added a comment about defining `NO_MINUS_C_MINUS_O' if necessary.
+
+ * Version 3.05.
+
+ * rule.c (default_suffix_rules): Don't pass `-o' switches with `-c'
+ switches if `NO_MINUS_C_MINUS_O' is #define'd.
+
+ * make.texinfo: Documented `GNUmakefile'.
+
+ * read.c (read_all_makefiles): Made it try default makefile
+ `GNUmakefile' before others.
+
+ * make.texinfo: Added new-style Texinfo header thingies.
+
+Sat Sep 3 18:09:39 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * Version 3.04.
+
+ * make.texinfo (Chained Rules): Added a @cindex about using
+ .PRECIOUS to preserve intermediate files.
+
+ * remake.c (update_file_1): Made it not just return before executing
+ commands under -p.
+
+ * rule.c (default_pattern_rules, default_variables): Made it use
+ `$(AR)' for `ar r' (to put files in archives).
+
+ * vpath.c (build_vpath_lists): Made it recursively expand the
+ `VPATH' variable (by using variable_expand instead of lookup_variable).
+
+ * read.c (conditional_line): Made it not swallow whitespace after
+ the comma in an `ifeq' using the `(a,b)' syntax.
+
+ * rule.c (count_implicit_rule_limits): Made it not crash if a
+ pattern rule dep begins with `/'.
+
+Sun Aug 28 15:51:12 1988 Roland McGrath (mcgrath at homer.Berkeley.EDU)
+
+ * make.texinfo: Clarified that the arg to the `origin' function is a
+ variable *name*, not a reference.
+
+ * make.texinfo: Clarified that both -Idir and -I dir are allowed.
+
+Sat Aug 27 13:49:28 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * remake.c (remake_file): Made touching phonies work right.
+
+Wed Aug 24 20:40:48 1988 Roland McGrath (mcgrath at nutmeg.Berkeley.EDU)
+
+ * make.texinfo: Removed reference to `RANLIB' variable.
+
+ * Version 3.03.
+
+ * variables.c (expand_function): Added `origin' function.
+ * make.texinfo: Documented same.
+
+ * read.c (record_files): Made double-colon entries work.
+
+Sat Aug 20 21:09:39 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (collapse_continuations): Bug fix from RMS.
+
+ * rule.c (install_default_pattern_rules): Made it set the
+ `in_use' flag of the created rules to zero, rather than letting
+ it be random garbage.
+
+ * rule.c (pattern_search): Fixed putting `also make' targets into
+ file strucutres.
+
+ * read.c (record_files): Fixed bug which made double-colon entries
+ make it read off into space.
+
+ * make.c (decode_switches): Made it understand `ignored' switches
+ rather than dumping core.
+
+Sun Aug 14 16:49:00 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * read.c (read_makefile): Made `include' filenames be
+ variable-expanded.
+
+ * read.c (read_makefile): Fixed an error message.
+
+ * read.c (read_makefile): Made it accept ^L's as whitespace.
+ * make.c (next_token, end_of_token): Ditto.
+
+ * vpath.c (vpath_search): Fixed it so that the general VPATH (from
+ the variable) is always checked, even if a selective VPATH (from a
+ directive) matched the filename.
+
+Sat Aug 13 14:20:46 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (decode_switches, main): Made the command switches be
+ processed from a table of switches, variables, and types. No
+ functions are passed argc and argv any more. They are passed arrays
+ of strings they need to process.
+ * read.c (read_all_makefiles): Made it take an array rather than
+ argc and argv.
+ (construct_include_path): Ditto.
+
+ * make.c (collapse_continuations): Made it work right (I hope).
+
+ * make.texinfo: Minor editorial changes.
+
+ * read.c (read_makefile): Minor speed improvement by freeing and
+ then mallocing something rather than reallocing it to avoid the
+ unnecessary bcopy.
+
+Thu Aug 11 00:10:43 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.texinfo: Fixed some unquoted @'s.
+
+ * make.texinfo: Documented multiple-target pattern rules.
+ Miscellaneous minor editorial changes and corrections.
+
+ * make.texinfo (Implicit: Catalogue of Rules): Removed the list of
+ variables. That's what the next section is for.
+ (Implicit: Implicit Variables): Made it agree with reality.
+
+Wed Aug 10 00:55:39 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * variable.c (print_variable_data_base): Fixed bug which made -p
+ dump core. (This was a really idiotic bug.)
+
+ * rule.c (pattern_search): Fixed a bug which made it make the
+ `also_make' member of the file in question nil if the first of
+ the successful rule's targets was the matching one.
+ Made it use only as much storage as necessary in the `also_make'
+ member.
+ (create_pattern): Made it use only as much storage as necessary in
+ the `lens' and `suffixes' members of the created rule.
+
+ * remake.c (library_file_mtime): Made it `static'.
+
+ * file.c: Added a declaration for `errno', which is declared in some
+ <errno.h>'s, but not all.
+
+ * file.h (struct file): Added `also_make' member for multiple-target
+ implicit rules.
+ * rule.c (pattern_search): Made it put the names of files updated by
+ the given file's commands in its `also_make' member.
+ * remake.c (update_file_1): Made it mark the files in a file's
+ `also_make' member as updated when the file is updated.
+
+ * variable.c (try_variable_definition): Fixed a bug which made it
+ define a variable with the name of the whole definition when there
+ was no space before the = or :=.
+
+ * make.texinfo (Features): Made the changes which were made in RCS
+ revision 2.7 but somehow lost since then. Added -W.
+
+Tue Aug 9 10:04:50 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * variable.h: Added `o_default' to `enum variable_origin'.
+ * variable.c (print_variable_data_base): Made it print the origins of
+ the variables.
+ * rule.c (install_default_pattern_rules): Made it define the default
+ variables with origin `o_default'.
+
+ * make.texinfo: Documented -W.
+
+ * make.c (decode_switches, main): Added the -W flag to give files a
+ time-stamp of now, for a `what if' effect when used with -n.
+
+ * commands.c (print_commands): Made it say `(built-in)' for commands
+ that are built into the default ruleset.
+
+ * read.c (record_file): Made .SUFFIXES get its deps frontwards (again).
+ * rule.c (set_default_suffixes, convert_to_pattern): Made it read
+ .SUFFIXES's deps frontwards, so the converted rules will not be in
+ reverse order.
+
+ * rule.c (new_pattern_rule): Fixed a bug wherein it would keep
+ searching after it had removed a matching rule and ended up diddling
+ with freed storage.
+
+ * rule.c (freerule): Made it take the given rule off the chain.
+ (new_pattern_rule, count_implicit_rule_limits): Use freerule to
+ remove rules from the chain.
+
+ * vpath.c (construct_vpath_list): Made it return after cleaning out
+ all previous searchpaths when given a nil DIRPATH arg, so it won't
+ go into the construction code and dump core dereferencing a nil
+ pointer.
+
+ * variable.c (patsubst_expand): Fixed a bug which made it not match
+ correctly and sometimes dump core.
+
+Mon Aug 8 16:35:48 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * rule.c (default_suffix_rules): Made the .texinfo.dvi rule remove
+ the files used in the comparison to determine whether or not a
+ second TeX run is necessary.
+
+ * make.texinfo: Fixed some overfull TeX hboxes.
+
+ * make.texinfo (Implicit: Catalogue of Rules): Fixed a Texinfo error.
+
+ * rule.c (create_pattern_rule): Fixed bug wherein index was not
+ being passed its second arg.
+
+ * read.c (getline): Merged back into readline.
+
+ * rule.c (default_suffixes, default_suffix_rules,
+ default_variables): Added .texinfo.info rule.
+ * make.texinfo (Implicit: Catalogue of Rules): Documented
+ .texinfo.dvi and .texinfo.info rules.
+
+ * make.texinfo (Top): Changed `last updated' date to be correct (for
+ the last time it was updated, not today). Changed `for version
+ 3.00' since it's not going to be called that.
+
+Sat Aug 6 19:51:10 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * commands.c (print_commands): Added this function to print the
+ contents of a `struct commands' for -p.
+ * rule.c (print_rule_data_base): Use above.
+ * file.c (print_file_data_base): Ditto.
+
+ * rule.c (count_implicit_rule_limits, new_pattern_rule,
+ install_pattern_rule, print_rule_data_base): Made it understand the
+ changed `struct rule' and act accordingly.
+ (freerule): Added this function to free all the storage used by a rule.
+
+ * rule.c (pattern_search): Made it grok multiple targets of pattern
+ rules. The matching is done properly, but at present, only the
+ matching pattern's target is used to give deps and commands.
+
+Fri Aug 5 18:00:29 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * rule.c (struct rule): Changed name, namelen, and patsuffix members
+ to targets, lens, and suffixes, which are arrays, for multiple targets.
+ (create_pattern_rule): Now takes first arg TARGETS, a nil-terminated
+ array of targets, rather than a single target and patsuffix pointer.
+
+ * read.c (record_files): If it finds an implicit pattern rule, it
+ collects all the targets into an array and passes the whole thing to
+ create_pattern_rule. If there are non-pattern targets, it is a
+ fatal error.
+
+Tue Aug 2 15:06:38 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (readline): Split backslash-newline checking from reading
+ and buffer-expanding.
+ (getline): Created to do the reading and buffer-expanding formerly
+ done in readline.
+
+ * rule.c (pattern_search): Made it reject nonterminal match-anything
+ rules when a specific rule has matched, rather than rejecting
+ terminal match-anything rules in this case.
+
+ * rule.c (convert_to_pattern): Fixed a bug caused when the change to
+ make it only recognize two-suffix rules whose target suffixes
+ precede their dependency suffixes which made it work in the opposite
+ direction (even worse than it started out).
+
+ * rule.c (pattern_search): Made it reject nonterminal match-anything
+ rules as intermediate targets when searching for both real and
+ intermediate dependencies, rather than only when searching for
+ intermediate ones.
+
+Sun Jul 31 00:33:56 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * rule.c (convert_to_pattern): Made it only recognize two-suffix
+ rules whose target suffix comes before the dependency suffix in the
+ .SUFFIXES list.
+
+ * variable.c (define_automatic_variables): Made all automatic
+ variables be defined with origin `o_automatic'.
+
+ * variable.h: Added `o_automatic' to `enum variable_origin'
+
+ * file.c (remove_intermediates): Made it not print an error message
+ if the error was that the file does not exist.
+
+ * rule.c: Removed `recursive' member from `struct rule'.
+
+ * remake.c (library_file_mtime): Made it not use the directory hash
+ functions, as reading in and hashing /usr/lib and /lib is slow and
+ most likely unnecessary.
+
+ * remake.c (remake_file): Changed message from ``No specification
+ for making'' to ``No way to make'' so it will be short enough that
+ most filenames will fit on a line.
+ Made it look at the `recursive' member of the `struct commands',
+ rather than of the `struct file' (which no longer has one).
+
+ * commands.c (execute_file_commands): Made it look at the
+ `recursive' member of the `struct commands', rather than of the
+ `struct file' (which no longer has one).
+
+ * file.h: Removed `recursive' member from `struct file'.
+
+ * commands.h: Added `recursive' member to `struct commands'.
+
+ * dep.h: Removed unused `quotedparen' member from `struct nameseq'
+ and `struct dep'.
+
+ * read.c (dequote): Removed this function.
+ (multi_glob): Removed reference to `quotedparen' member of
+ a `struct nameseq' and calls to dequote.
+
+ * read.c (record_files): Made it set the stem for $* for all static
+ pattern rules, not just those with commands given at that time.
+ Removed check for recursive commands.
+ Made it check for pairs of .SUFFIXES dependencies to reject as
+ default goals as well as single ones (that don't start with dots).
+ (read_makefile): Added checks for recursive commands to set
+ the `recursive' flag in the `struct commands'.
+
+Sat Jul 30 15:47:23 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (find_next_token): Made the LENGTHPTR arg optionally nil.
+
+ * make.c: Removed `files_made' variable which is defined static in
+ remake.c and used only there.
+ (main): Cleaned up somewhat.
+ (decode_switches): Cleaned up a bit. Made an unknown option be a
+ non-fatal error.
+ (decode_env_switches): Made LEN arg unsigned. Cleaned up.
+ (print_version): Made it say ``see the source'' rather than ``see
+ the source file'', since there is more than one.
+
+ * file.h: Made `num_intermediates' declared unsigned.
+
+ * file.c: Made `num_intermediates' variable unsigned.
+ (remove_intermediates): Removed unused FORMAT arg.
+ (enter_file): Made it handle double-colon files properly, adding the
+ new entry as the old entry's prev pointer.
+
+ * dir.c: Re-indented the `struct dir' definition to be right.
+ (dir_load): Cleaned up slightly.
+ (file_exists_p): Removed comment saying we could use `access', since
+ that is a bad idea (except for setuid programs). Cleaned up slightly.
+
+ * commands.c: Changed some comments slightly.
+ (execute_file_commands): Cleaned up a bit. Changed some comments,
+ added others. Moved freeing of storage for $^ and $? to the same
+ place as for the other automatic variables.
+ (execute_command_line): Made `#' trigger a shell.
+ Added some comments. Cleaned up a bit. Put all the special chars
+ that trigger shells into an array easily changeable at the top.
+
+ * ar.c: Added comments explaining each function.
+ (ar_scan_1): Merged into ar_member_date.
+ (ar_member_date): Changed call to ar_scan_1 to the body of that
+ function.
+ (ar_member_date_1): Simplified to a ?: expression rather than an
+ if-else statement.
+ (ar_member_touch): Changed error handling around a bit.
+ None of these errors are fatal now.
+
+ * variable.c (subst_expand): Added a new arg BY_WORD, to do substs
+ only on full words.
+ (patsubst_expand): Fixed bug which made calls whose patterns
+ contained no `%' to not work correctly, by using above.
+ (variable_expand): Pass extra arg to `subst_expand'.
+
+ * variable.c (expand_function): Fixed bug which made `foreach' calls
+ with one-word lists run off into never-never land.
+
+Fri Jul 29 20:12:36 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * variable.c (expand_function): Made a very minor speed improvement
+ by avoiding an unnecessary strlen call.
+
+Wed Jul 27 16:01:47 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * rule.c (default_suffixes): Rearranged the list somewhat; added
+ `.el' and `.elc' to speed things up (especially when building
+ Emacs), for the same reason `.h' is there.
+
+ * read.c (record_files): Changed `lineno' from `long' to
+ `unsigned int'.
+
+Sun Jul 24 02:15:30 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * variable.c (expand_function): Eliminated use of `wstok'
+ because it is non-reentrant and unreliable.
+ Fixed a minor bug which would cause something not to be freed.
+ * make.c (wstok): Removed `wstok' because it is no longer used.
+
+ * variable.c (expand_function): Made `foreach' function put
+ spaces between output texts like it's supposed to.
+
+Sat Jul 23 17:32:55 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * rule.c (default_suffixes, default_suffix_rules): Added rule
+ to make %.dvi from %.texinfo.
+
+ * dir.c (print_dir_data_base): Made it say a bit more.
+
+Fri Jul 22 23:13:16 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * make.c (print_data_base): Split this function up into one
+ for each thing.
+ * variable.c (print_variable_data_base): One of the above.
+ * rule.c (print_rule_data_base): Ditto.
+ * file.c (print_file_data_base): Ditto.
+ * dir.c (print_dir_data_base): Ditto.
+
+ * rule.c (install_pattern_rule): Fixed a bug which caused the
+ terminal and recursive flags to always be zero for rules
+ entered by this function.
+
+ * make.texinfo (Rules: Double-colon): Added a paragraph
+ explaining the purpose of double-colon rules.
+
+ * make.texinfo (Implicit: Catalogue of Rules): Updated to
+ reflect new C++, TeX, Web, and Texinfo rules. Other slight
+ editorial changes.
+
+ * commands.c (execute_file_commands): Fixed a bug wherein
+ random memory could get written for files with no deps.
+
+Wed Jul 20 19:30:31 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * read.c (readline): Fix bug wherein it would not recognize a
+ backslash-newline if the buffer filled up and was enlarged
+ right before reading the newline.
+
+Tue Jul 19 19:55:02 1988 Roland McGrath (mcgrath at chilli.Berkeley.EDU)
+
+ * read.c: Added default suffix rules for .cc (using $(C++),
+ which defaults to `g++', and $(C++FLAGS)), .tex, .dvi, .web
+ and .cweb (using $(TEX), $(WEAVE), $(TANGLE), $(CWEAVE) and
+ $(CTANGLE)).
+
+Sat Jul 16 21:24:28 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Made error formats use %u rather than %ld for line numbers,
+ which are now unsigned int's rather than long's.
+
+ * read.c (conditional_line): Fixed some bugs caused by use of
+ unsigned int rather than int in one place.
+
+ * read.c (conditional_line): Put the info about active
+ conditionals in a struct.
+ (read_makefile): Make a new struct of info about conditionals
+ for included makefiles and restore the old one after the
+ included makefile has been read.
+
+ * read.c (read_makefile): Don't try to read a makefile with
+ name "" after giving an error message because an `include'
+ directive gave no filename.
+
+ * read.c (read_makefile): Give an error message for
+ non-whitespace text after the filename in an `include' directive.
+
+ * make.c (error): Take five args, like `fatal'. It managed to
+ lose with only two. Is there a better way to do this without vfprintf?
+
+ * read.c (read_makefile): Commands consisting of only
+ whitespace are not the same as no commands. I thought I'd
+ fixed this bug months ago; it seems to have come back.
+
+ * make.c (collapse_continuations): All whitespace around a
+ backslash-newline combination is turned into a single space.
+
+ * Added COPYING file and copyright notices to all files.
+
+ * make.texinfo (Running: Goals): Fix a typo.
+
+ * read.c (do_define): Take an arg for the origin of the
+ variable being defined.
+ (read_makefile): Grok `override define'.
+
+ * make.texinfo (Variables: Override Directive, Defining):
+ Document the `override define' combination directive.
+
+ * ar.c (ar_member_date): Make a 0 return from `ar_scan' return
+ (time_t) -1 (nonexistent file), rather than (time_t) 0, which,
+ when put in the `struct file', makes `file_mtime' try to get
+ the mtime over and over again.
+
+ * variable.c (pattern_matches): Fix a bug that made patterns
+ not beginning with `%' never match.
+
+Fri Jul 15 21:01:44 1988 Roland McGrath (mcgrath at tully.Berkeley.EDU)
+
+ * Took Make out of RCS.
+
+ * Split the monolithic `make.c' into several smaller files.
+
+
+
+Copyright (C) 1988-2009 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/ChangeLog.2 b/src/kmk/ChangeLog.2
new file mode 100644
index 0000000..0816454
--- /dev/null
+++ b/src/kmk/ChangeLog.2
@@ -0,0 +1,6653 @@
+2000-06-22 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): Increment commands_started before the
+ special check for ":" (empty command) to avoid spurious "is up to
+ date" messages. Also move the test for question_flag after we
+ expand arguments, and only stop if the expansion provided an
+ actual command to run, not just whitespace. This fixes PR/1780.
+
+2000-06-21 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): If we find a semicolon in the target
+ definition, remember where it was. If the line turns out to be a
+ target-specific variable, add back the semicolon and everything
+ after it. Fixes PR/1709.
+
+2000-06-19 Paul D. Smith <psmith@gnu.org>
+
+ * config.h-vms.template: #define uintmax_t for this system.
+ * config.ami.template: Ditto.
+ * config.h.W32.template: Ditto.
+
+ * configure.in: We don't use select(2) anymore, so don't bother
+ checking for it.
+ * acconfig.h: Ditto.
+ * acinclude.m4: Ditto.
+
+ * file.c (all_secondary): New static global; if 1 it means
+ .SECONDARY with no prerequisites was seen in the makefile.
+ (snap_deps): Set it appropriately.
+ (remove_intermediates): Check it.
+ (num_intermediates): Remove this global, it's not used anywhere.
+ (considered): Move this to remake.c and make it static.
+
+ * NEWS: Document the change to .SECONDARY.
+ * make.texinfo (Special Targets): Document the change to .SECONDARY.
+
+ * implicit.c (pattern_search): Remove the increment of
+ num_intermediates; it's not used.
+ * filedef.h: Remove num_intermediates and considered.
+
+ * function.c (handle_function): If the last argument was empty, we
+ were pretending it didn't exist rather than providing an empty
+ value. Keep looking until we're past the end, not just at the end.
+
+ * implicit.c (pattern_search): Multi-target implicit rules weren't
+ expanding the "also made" targets correctly if the pattern didn't
+ contain a slash but the target did; in that case the directory
+ part wasn't being added back to the stem on the "also made"
+ targets. Reported by Seth M LaForge <sethml@newtonlabs.com>, with
+ a patch.
+
+2000-06-17 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS.template (DESTDIR, bindir, datadir, libdir)
+ (infodir, mandir, includedir): Support installation under a
+ non-default DESTDIR.
+
+ * remake.c (f_mtime): Fix the spelling of __MSDOS__.
+
+ * configh.DOS.template (HAVE_FDOPEN, HAVE_MKSTEMP): Define.
+
+2000-06-14 Paul D. Smith <psmith@gnu.org>
+
+ * acinclude.m4 (pds_WITH_GETTEXT): rewrite fp_WITH_GETTEXT and
+ rename it to avoid confusion. This version is very specific: it
+ won't accept any gettext that isn't GNU. If the user doesn't
+ explicitly ask for the included gettext, we look to see if the
+ system gettext is GNU (testing both the actual libintl library,
+ and the libintl.h header file). Only if the system gettext is
+ really GNU gettext will we allow it to be used.
+ (pds_CHECK_SYSTEM_GETTEXT): A helper function.
+
+2000-06-13 Paul D. Smith <psmith@gnu.org>
+
+ * gettext.h: If we have libintl.h, use that instead of any of the
+ contents of gettext.h. We won't check for libintl.h unless we're
+ using the system gettext.
+
+ * function.c (func_word): Clarify error message.
+
+2000-06-10 Paul Eggert <eggert@twinsun.com>
+
+ Support nanosecond resolution on hosts with 64-bit time_t and
+ uintmax_t (e.g. 64-bit Sparc Solaris), by splitting
+ FILE_TIMESTAMP into a 30-bit part for nanoseconds, with the
+ rest for seconds, if FILE_TIMESTAMP is at least 64 bits wide.
+
+ * make.h: Always define FILE_TIMESTAMP to be uintmax_t, for
+ simplicity.
+
+ * filedef.h (FILE_TIMESTAMP_HI_RES, FILE_TIMESTAMP_LO_BITS)
+ (UNKNOWN_MTIME, NONEXISTENT_MTIME, OLD_MTIME)
+ (ORDINARY_MTIME_MIN, ORDINARY_MTIME_MAX): New macros.
+ (FILE_TIMESTAMP_STAT_MODTIME): Now takes fname arg. All uses changed.
+ (FILE_TIMESTAMP_DIV, FILE_TIMESTAMP_MOD)
+ (FILE_TIMESTAMP_FROM_S_AND_NS): Remove.
+ (FILE_TIMESTAMP_S, FILE_TIMESTAMP_NS): Use shifts instead of
+ multiplication and division. Offset the timestamps by
+ ORDINARY_MTIME_MIN.
+ (file_timestamp_cons): New decl.
+ (NEW_MTIME): Now just the maximal timestamp value, as we no longer use
+ -1 to refer to nonexistent files.
+
+ * file.c (snap_deps, print_file): Use NONEXISTENT_MTIME,
+ UNKNOWN_MTIME, and OLD_MTIME instead of magic constants.
+ * filedef.h (file_mtime_1): Likewise.
+ * main.c (main): Likewise.
+ * remake.c (update_file_1, notice_finished_file, check_dep)
+ (f_mtime, name_mtime, library_search): Likewise.
+ * vpath.c (selective_vpath_search): Likewise.
+
+ * remake.c (f_mtime): Do not assume that (time_t) -1 equals
+ NONEXISTENT_MTIME. When futzing with time stamps, adjust by
+ multiples of 2**30, not 10**9. Do not calculate timestamp
+ adjustments on DOS unless they are needed.
+
+ * commands.c (delete_target): Do not assume that
+ FILE_TIMESTAMP_S yields -1 for a nonexistent file, as that is
+ no longer true with the new representation.
+
+ * file.c (file_timestamp_cons): New function, replacing
+ FILE_TIMESTAMP_FROM_S_AND_NS. All uses changed.
+ (file_timestamp_now): Use FILE_TIMESTAMP_HI_RES instead of 1 <
+ FILE_TIMESTAMPS_PER_S to determine whether we're using hi-res
+ timestamps.
+ (print_file): Print OLD_MTIME values as "very old" instead of
+ as a timestamp.
+
+2000-05-31 Paul Eggert <eggert@twinsun.com>
+
+ * remake.c (name_mtime): Check for stat failures. Retry if EINTR.
+
+2000-05-24 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (decode_switches): The "positive_int" switch uses atoi()
+ which succeeds for any input, and doesn't notice if extra,
+ non-digit text is after the number. This causes make to mis-parse
+ command lines like "make -j 5foo" as "make -j5" (ignoring "foo"
+ completely) instead of "make -j0 5foo" (where "5foo" is a
+ target). Fix this by checking the value by hand. We could use
+ strtol() if we were sure of having it; this is the only
+ questionable use of atoi() I found so we'll just stick with that.
+ Fixes PR/1716.
+
+ * i18n/ja.po, i18n/nl.po, i18n/pt_BR.po: New translation files.
+ * configure.in (ALL_LINGUAS): Added pt_BR.
+
+2000-05-22 Paul Eggert <eggert@twinsun.com>
+
+ * remake.c (f_mtime): Fix bug when handling future odd
+ timestamps in the WINDOWS32 case. Do not bother initializing
+ static var to zero. Simplify code that works around WINDOWS32
+ and __MSDOS__ time skew brain damage.
+
+2000-05-22 Paul Eggert <eggert@twinsun.com>
+
+ * job.c: Don't include time.h, as make.h already does this.
+
+2000-05-22 Paul Eggert <eggert@twinsun.com>
+
+ * configure.in (AC_CHECK_HEADERS): Add sys/time.h.
+ (AC_HEADER_TIME): Add.
+ (clock_gettime): Prefer -lrt to -lposix4, for Solaris 7.
+ (gettimeofday): Add check for standard version of gettimeofday.
+ This merges changes written by Paul D. Smith.
+
+ * file.c (file_timestamp_now): Use gettimeofday if available
+ and if clock_gettime does not work. Don't bother with
+ high-resolution clocks if file timestamps have only one-second
+ resolution.
+
+ * make.h <sys/time.h>: Include, conditionally on the usual
+ TIME_WITH_SYS_TIME and HAVE_SYS_TIME_H macros. This is needed
+ for gettimeofday.
+
+2000-05-20 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): We weren't keeping makefile names around
+ unless there was a rule defined in them; but now we need to keep
+ them for variables as well. Forget trying to be fancy: just keep
+ every makefile name we successfully open.
+
+ * remote-cstms.c (start_remote_job_p): Change DB_EXTRA (?) to DB_JOBS.
+
+2000-05-17 Paul Eggert <eggert@twinsun.com>
+
+ * commands.c (chop_commands): Ensure ctype macro args are nonnegative.
+ * expand.c (variable_expand_string): Likewise.
+ * function.c (subst_expand, lookup_function, msdos_openpipe):
+ Likewise.
+ * job.c (vms_redirect, start_job_command, new_job, child_execute_job,
+ construct_command_argv_internal, construct_command_argv): Likewise.
+ * main.c (decode_env_switches, quote_for_env): Likewise.
+ * misc.c (collapse_continuations, end_of_token, end_of_token_w32,
+ next_token): Likewise.
+ * read.c (read_makefile, do_define, conditional_line,
+ find_char_unquote,get_next_mword): Likewise.
+ * variable.c (try_variable_definition): Likewise.
+ * vpath.c (construct_vpath_list): Likewise.
+ * w32/pathstuff.c (convert_vpath_to_windows32): Likewise.
+
+2000-05-10 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * main.c (main) [__MSDOS__]: Add SIGFPE to signals we block when
+ running child programs, to prevent Make from dying on Windows 9X
+ when the child triggers an FP exception.
+
+2000-05-08 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (find_directory) [WINDOWS32]: If we strip a trailing "\"
+ from the directory name, remember to add it back. The argument
+ might really be inside a longer string (e.g. %Path%) and if you
+ don't restore the "\" it'll be truncated permanently. Fixes PR/1722.
+ Reported by <steven@surfcast.com>
+
+2000-05-02 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal) [WINDOWS32]: Added "rd"
+ and "rmdir" to the list of command.com commands.
+ Reported by Elod Horvath <Elod_Horvath@lnotes5.bankofny.com>
+
+2000-04-24 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/ja.po: New translation file from the Japanese language team.
+
+2000-04-18 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): If ar_member_date() returns -1 (the member
+ doesn't exist), then return (FILE_TIMESTAMP)-1 rather than
+ returning the timestamp calculated from the value -1. Fixes PR/1696.
+ Reported by Gilles Bourhis <Gilles.Bourhis@univ-rennes1.fr>.
+
+2000-04-17 Paul D. Smith <psmith@gnu.org>
+
+ * config.h.W32.template: Add LOCALEDIR macro resolving to "".
+ * w32/subproc/sub_proc.c (process_begin): Remove reference to
+ debug_flag; change it to a DB() call. Fixes PR/1700.
+ Reported by Jim Smith <jwksmith@attglobal.net>
+
+2000-04-17 Bruno Haible <haible@clisp.cons.org>
+
+ * arscan.c [BeOS]: Add replacement for nonexistent <ar.h> from GNU
+ binutils.
+
+2000-04-11 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (expand_builtin_function): If no arguments were
+ provided, just quit early rather than changing each function to
+ test for this.
+ (function_table[]): Change the min # of arguments to 0 for all
+ those functions for which it makes sense (currently everything
+ that used to take a minimum of 1 argument, except $(call ...)).
+ Fixes PR/1689.
+
+2000-04-09 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * README.DOS: Add instructions to install a binary distro.
+ Mention latest versions of Windows.
+
+2000-04-07 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * main.c (main): Rename TMP_TEMPLATE into DEFAULT_TMPDIR, and use
+ it for the directory of the temporary file. If P_tmpdir is
+ defined, use it in preference to "/tmp/". Try $TMPDIR, $TEMP, and
+ $TMP in the environment before defaulting to DEFAULT_TMPDIR.
+ (print_version): Add year 2000 to the Copyright line.
+
+2000-04-04 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.79 released.
+
+ * make.texinfo: Update documentation with new features for 3.79.
+
+ * function.c (func_wordlist): Don't re-order arguments to
+ wordlist.
+
+2000-04-03 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): Archive member timestamps are stored as
+ time_t, without nanoseconds. But, f_mtime() wants to return
+ nanosecond info on those systems that support it. So, convert the
+ return value of ar_member_date() into a FILE_TIMESTAMP, using 0 as
+ the nanoseconds.
+
+2000-03-28 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.78.92 released.
+
+ * build.template: Updates for gettext support; some bugs fixed.
+
+2000-03-27 Paul D. Smith <psmith@gnu.org>
+
+ * config.guess, config.sub: Updated from config CVS archive at
+ :pserver:anoncvs@subversions.gnu.org:/home/cvs as of today.
+
+ * read.c (record_files): Check if expanding a static pattern
+ rule's prerequisite pattern leaves an empty string as the
+ prerequisite, and issue an error if so. Fixes PR/1670.
+ (read_makefile): Store the starting linenumber for a rule in
+ TGTS_STARTED.
+ (record_waiting_files): Use the TGTS_STARTED value for the file
+ location passed to record_file() instead of the current
+ linenumber, so error messages list the line where the target was
+ defined instead of the line after the end of the rule definition.
+
+ * remake.c (start_updating, finish_updating, is_updating): Fix
+ PR/1671; circular dependencies in double-colon rules are not
+ diagnosed. These macros set the updating flag in the root
+ double-colon file instead of the current one, if it's part of a
+ double-colon list. This solution provided by Tim Magill
+ <magill@gate.net>; I just changed the macro names :).
+ (update_file_1): Call them.
+ (check_dep): Call them.
+
+ The change to not automatically evaluate the $(call ...)
+ function's arguments breaks recursive use of call. Although using
+ $(if ...) and $(foreach ...) in $(call ...) macros is important,
+ the error conditions generated are simply to obscure for me to
+ feel comfortable with. If a method is devised to get both
+ working, we'll revisit. For now, remove this change.
+
+ * function.c (function_table): Turn on the expand bit for func_call.
+ (func_call): Don't expand arguments for builtin functions; that
+ will have already been done.
+
+2000-03-26 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (remove_intermediates): Never remove targets explicitly
+ requested on the command-line by checking the cmd_target flag.
+ Fixed PR/1669.
+
+2000-03-23 Paul Eggert <eggert@twinsun.com>
+
+ * filedef.h (FILE_TIMESTAMP_STAT_MODTIME): Use st_mtime instead of
+ st_mtim.tv_sec; the latter doesn't work on Unixware.
+
+2000-03-18 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (file_hash_enter): If we're trying to change a file into
+ itself, just return. We used to assert this wasn't true, but
+ someone came up with a weird case involving archives. After
+ playing with it for a while I decided it was OK to ignore it.
+
+ * default.c: Define COFLAGS to empty to avoid spurious warnings.
+
+ * filedef.h: Change #if ST_MTIM_NSEC to #ifdef; this is a macro
+ containing the name of the nsec field, not true/false.
+ * make.h: Ditto.
+ Reported by Marco Franzen <Marco.Franzen@Thyron.com>.
+
+2000-03-08 Tim Magill <magill@gate.net>
+
+ * remake.c (update_file): Return the exit status of the pruned
+ file when pruning, not just 0. Fixes PR/1634.
+
+2000-02-24 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Close a minor potential security hole; if you're
+ reading makefiles from stdin (who does that?) you could run into a
+ race condition with the temp file using mktemp() or tmpnam(). Add
+ a check for mkstemp() and fdopen().
+ * main.c (open_tmpfile): New function to open a temporary file.
+ If we have mkstemp() (and fdopen()), use that. If not use
+ mktemp() or tmpnam(). If we have fdopen(), use open() to open the
+ file O_CREAT|O_EXCL. If not, fall back to normal fopen() (insecure).
+ (main): Call it.
+ * job.c (child_execute_job) [VMS]: Call it.
+
+ * variable.c (lookup_variable): If we find a variable which is
+ being expanded, then note it but keep looking through the rest of
+ the set list to see if we can find one that isn't. If we do,
+ return that. If we don't, return the original. Fix for PR/1610.
+
+ While implementing this I realized that it also solves PR/1380 in
+ a much more elegant way. I don't know what I was smoking before.
+ So, remove the hackage surrounding the original fix for that (see
+ below). Change this function back to lookup_variable and remove
+ the extra setlist argument.
+ * variable.h (recursively_expand_setlist): Remove the macro,
+ rename the prototype, and remove the extra setlist argument.
+ (lookup_variable): Ditto.
+ * expand.c (recursively_expand): Rename and remove the extra
+ setlist argument.
+ (reference_variable): Use lookup_variable() again.
+ (allocated_variable_append): Remove the extra setlist argument.
+
+2000-02-21 Paul D. Smith <psmith@gnu.org>
+
+ * README.template: A few updates.
+
+ * i18n/de.po: New version from the German translation team.
+
+2000-02-09 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.78.91 released.
+
+2000-02-07 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Reset *p2 to ':', not *colonp. If any
+ filenames contained backslashes the resulting output (without
+ backslashes) will be shorter, so setting *colonp doesn't change
+ the right character. Fix for PR/1586.
+
+ For += target-specific variables we need to remember which
+ variable set we found the variable in, so we can start looking
+ from there in the next iteration (otherwise we might see it again
+ in recursively_expand and fail!). This is turning into a hack; if
+ it gets any worse we'll have to rethink this entire algorithm...
+ implementing expansion of these references separately from the
+ "normal" expansion, say, instead of using the same codepath.
+ Actually, it's already "worse enough" :-/.
+ Fix for PR/1380.
+
+ * variable.h (recursively_expand_setlist): Rename
+ recursively_expand to add a struct variable_set_list argument, and
+ make a macro for recursively_expand.
+ (lookup_variable_setlist): Rename lookup_variable to add a struct
+ variable_set_list argument, and make a macro for lookup_variable.
+
+ * expand.c (recursively_expand_setlist): Take an extra struct
+ variable_set_list argument and pass it to allocated_variable_append().
+ (reference_variable): Use lookup_variable_setlist() and pass the
+ returned variable_set_list to recursively_expand_setlist.
+ (allocated_variable_append): Take an extra setlist argument and
+ use this as the starting place when searching for the appended
+ expansion. If it's null, use current_variable_set_list as before.
+
+ * variable.c (lookup_variable_setlist): If the LISTP argument is
+ not nil, set it to the list containing the variable we found.
+
+2000-02-04 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (print_variable): Write out filename/linenumber
+ information for the variable definition if present.
+ (define_variable_in_set): Store filename information if provided.
+ (define_variable, define_variable_for_file): Removed.
+ (try_variable_definition): Use define_variable_loc() to keep
+ variable definition location information.
+ * read.c (read_makefile): Keep variable definition location info.
+ (do_define): Ditto.
+ (record_target_var): Ditto.
+ * variable.h (define_variable_in_set): New fileinfo argument.
+ (define_variable, define_variable_loc, define_variable_for_file):
+ Declare new macros.
+
+ Fix PR/1407:
+
+ * filedef.h (struct file): Rename patvar to pat_variables and make
+ it just a variable_set_list; we need our own copy of the pattern
+ variable's variable set list here to avoid overwriting the global
+ one.
+ * variable.c (initialize_file_variables): Move the instantiation
+ of the pat_variables pointer here. Only do the search after we're
+ done reading the makefiles so we don't search too early. If
+ there's a pat_variables value, set up the variables next ptr.
+ * expand.c (variable_expand_for_file): Remove the setup of the
+ pat_variables info; it's done earlier now to ensure the parent's
+ pattern variables are set up correctly as well.
+
+2000-02-03 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (sh_chars_dos) [WINDOWS32]: Add "&" as a shell
+ metacharacter for the W32 DOS shell.
+ Reported by Warren Jones <wjones@tc.fluke.com>.
+
+2000-02-02 Paul D. Smith <psmith@gnu.org>
+
+ Fixes for the OpenVMS port from Hartmut Becker <becker@rto.dec.com>
+
+ * config.h-vms [VMS]: Define LOCALEDIR to something; needed for
+ the expansion of bindtextdomain() even though it's a no-op.
+ * vmsfunctions.c (strcmpi): Remove duplicate definition of strcmpi().
+ (readdir): Use DB() instead of testing debug_flag.
+ * dir.c (file_impossible) [VMS]: Search "p" not "name".
+ * job.c [VMS]: Switch from debug_flag to the new DB macro. Add
+ some i18n _() macros (even though VMS doesn't yet support it).
+
+ * function.c (patsubst_expand): Change "len" to not be unsigned to
+ avoid type mismatches.
+
+ * main.c (main): Declare signame_init() if we're going to call it.
+
+2000-01-29 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS.template: Track changes in Makefile.in
+ (install-recursive, uninstall-recursive): Add missing targets.
+ (DESTDIR): Define.
+ (install-binPROGRAMS, uninstall-binPROGRAMS): Use $(DESTDIR).
+
+ * default.c (default_variables) [__MSDOS__]: Define CXX to gpp.
+
+2000-01-27 Paul D. Smith <psmith@gnu.org>
+
+ * gettext.c: Some warning cleanups, and a fix for systems which
+ don't define HAVE_ALLOCA (the workaround code was included
+ twice).
+
+2000-01-26 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.78.90 released.
+
+2000-01-25 Paul D. Smith <psmith@gnu.org>
+
+ Change gettext support to use the simplified version in libit 0.7.
+
+ * getopt.c, make.h: Use gettext.h instead of libintl.h.
+ * ABOUT-NLS, gettext.h, gettext.c: New files from libit 0.7.
+ Modified to remove some static declarations which aren't defined.
+ * acconfig.h: Use new gettext #defines.
+ * acinclude.m4: Add fp_WITH_GETTEXT; remove AM_GNU_GETTEXT.
+ * configure.in: Call fp_WITH_GETTEXT instead.
+ * Makefile.am: New gettext stuff. Also force inclusion of glob
+ files for systems which have LIBC glob.
+
+ * i18n/Makefile.am, i18n/.cvsignore: New dir for translation files.
+ * i18n/de.po, i18n/es.po, i18n/fr.po, i18n/ko.po, i18n/nl.po:
+ * i18n/pl.po, i18n/ru.po: Import translations already done for
+ earlier versions of GNU make. Thanks for that work!!
+
+ * po/Makefile.in.in, po/POTFILES.in: Removed.
+
+2000-01-23 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (decode_debug_flags): If debug_flag is set, enable all
+ debugging levels.
+ (debug_flag): Resurrect this flag variable.
+ (switches): Make -d give the old behavior of turning on all
+ debugging. Change --debug alone to emit basic debugging and take
+ optional arguments to expand debugging.
+ * NEWS: Document the new debugging options.
+
+ * remake.c (no_rule_error): Remove this function. This tries to
+ fix a real problem--see the description with the introduction of
+ this function below. However, the cure is worse than the disease
+ and this approach won't work.
+ (remake_file): Put the code from no_rule_error back here.
+ (update_file_1): Remove call to no_rule_error.
+
+ * filedef.h (struct file): Remove mfile_status field.
+
+2000-01-22 Paul D. Smith <psmith@gnu.org>
+
+ Integrate GNU gettext support.
+
+ * configure.in: Add AM_GNU_GETTEXT.
+ * Makefile.am: Add options for setting LOCALEDIR, -Iintl, etc.
+ * acinclude.m4: Add gettext autoconf macros.
+ * acconfig.h: Add new gettext #defines.
+ * make.h: Include libintl.h. Make sure _() and N_() macros are
+ declared. Make gettext() an empty macro is NLS is disabled.
+ * main.c (struct command_switch switches[]): Can't initialize
+ static data with _() (gettext calls), so use N_() there then use
+ gettext() directly when printing the strings.
+ * remake.c (no_rule_error): The string constants can't be static
+ when initializing _() macros.
+ * file.c (print_file): Reformat a few strings to work better for
+ translation.
+ * po/POTFILES.in, po/Makefile.in.in: New files. Take
+ Makefile.in.in from the latest GNU tar distribution, as that
+ version works better than the one that comes with gettext.
+ * NEWS: Mention i18n ability.
+
+2000-01-21 Paul D. Smith <psmith@gnu.org>
+
+ Installed patches for the VMS port.
+ Patches provided by: Hartmut Becker <Hartmut.Becker@compaq.com>
+
+ * readme.vms, arscan.c, config.h-vms, default.c, dir.c, file.c:
+ * implicit.c, job.c, make.h, makefile.com, makefile.vms, rule.c:
+ * variable.c, vmsdir.h, vmsfunctions.c, vmsify.c, glob/glob.c:
+ * glob/glob.h: Installed patches. See readme.vms for details.
+
+2000-01-14 Andreas Schwab <schwab@suse.de>
+
+ * dir.c (read_dirstream): Initialize d_type if it exists.
+
+2000-01-11 Paul D. Smith <psmith@gnu.org>
+
+ Resolve PR/xxxx: don't automatically evaluate the $(call ...)
+ function's arguments. While we're here, clean up argument passing
+ protocol to always use simple nul-terminated strings, instead of
+ sometimes using offset pointers to mark the end of arguments.
+ This change also fixes PR/1517.
+ Reported by Damien GIBOU <damien.gibou@st.com>.
+
+ * function.c (struct function_table_entry): Remove the negative
+ required_args hack; put in explicit min and max # of arguments.
+ (function_table): Add in the max value. Turn off the expand bit
+ for func_call.
+ (expand_builtin_function): Test against minimum_args instead of
+ the obsolete required_args.
+ (handle_function): Rewrite this. We don't try to be fancy and
+ pass one style of arguments to expanded functions and another
+ style to non-expanded functions: pass pointers to nul-terminated
+ strings to all functions.
+ (func_call): Rewrite this. If we are invoking a builtin function
+ and it's supposed to have its arguments expanded, do that (since
+ it's not done by handle_function for $(call ...) anymore). For
+ non-builtins, just add the variables as before but mark them as
+ recursive so they'll be expanded later, as needed.
+ (func_if): All arguments are vanilla nul-terminated strings:
+ remove trickery with "argv[1]-1".
+ (func_foreach): Ditto.
+
+ * expand.c (expand_argument): If the second arg is NULL, expand
+ the entire first argument.
+
+ * job.c (new_job): Zero the child struct. This change was just
+ made to keep some heap checking software happy, not because there
+ was an actual bug (the important memory was being cleared properly).
+
+1999-12-15 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (print_variable): Print the variable with += if the
+ append flag is set.
+
+ * implicit.c (pattern_search): Remove the extra check of the
+ implicit flag added on 8/24/1998. This causes problems and the
+ reason for the change was better resolved by the change made to
+ check_deps() on 1998-08-26. This fixes PR/1423.
+
+1999-12-08 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (dir_setup_glob): On 64 bit ReliantUNIX (5.44 and above)
+ in LFS mode, stat() is actually a macro for stat64(). Assignment
+ doesn't work in that case. So, stat is a macro, make a local
+ wrapper function to invoke it.
+ (local_stat): Wrapper function, if needed.
+ Reported by Andrej Borsenkow <Andrej.Borsenkow@mow.siemens.ru>.
+
+1999-12-02 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_file): Move the considered test outside the
+ double-colon loop, _but_ make sure we test the double_colon target
+ not the "current" target. If we stop early because one
+ double-colon target is running, mark all the rest considered and
+ try to start their prerequisites (so they're marked considered).
+ Fix for PR/1476 suggested by Tim Magill <tim.magill@telops.gte.com>.
+
+1999-11-22 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * function.c (windows32_openpipe, func_shell): Correct Windows32
+ problem where $(shell nosuchfile) would incorrectly exit make. The
+ fix is to print the error and let make continue.
+ Reported by David Masterson <David.Masterson@kla-tencor.com>.
+
+ * w32/subproc/misc.c (arr2envblk): Memory leak fix.
+
+1999-11-21 Paul D. Smith <psmith@gnu.org>
+
+ Rework GNU make debugging to provide different levels of output.
+
+ * NEWS: mention it.
+ * debug.h: New file. Define various debugging levels and macros.
+ * function.c, implicit.c, job.c, main.c, misc.c, read.c, remake.c
+ * remote-cstms.c, vmsfunctions.c: Replace all code depending on
+ debug_flag with invocations of debugging macros.
+ * make.h: Remove debug_flag and DEBUGPR, add db_level.
+
+1999-11-18 Paul Eggert <eggert@twinsun.com>
+
+ * acinclude.m4 (AC_SYS_LARGEFILE_FLAGS): Work around a problem
+ with the QNX 4.25 shell, which doesn't propagate exit status of
+ failed commands inside shell assignments.
+
+1999-11-17 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_if): Find the end of the arg list by testing
+ the next item for NULL; any other test is not correct.
+ Reported by Graham Reed <grahamr@algorithmics.com> (PR/1429).
+
+ Fix += when used in a target-specific variable context.
+
+ * variable.h: New bitfield APPEND set if we have a +=
+ target-specific variable.
+
+ * variable.c (try_variable_definition): Add an argument to specify
+ if we're trying a target-specific variable. If we are and it's an
+ append style, don't append it, record it as normal recursive, but
+ set the APPEND flag so it'll be expanded later.
+ * main.c (handle_non_switch_argument): Use new
+ try_variable_definition() signature.
+ * read.c (read_makefile,record_target_var): Ditto.
+
+ * expand.c (allocated_variable_append): New function: like
+ allocated_variable_expand(), but we expand the same variable name
+ in the context of the ``next'' variable set, then we append this
+ expanded value.
+ (recursively_expand): Invoke it, if the APPEND bit is set.
+
+1999-11-10 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (snap_deps): If the .NOTPARALLEL target is defined, turn
+ off parallel builds for this make only (still allow submakes to be
+ run in parallel).
+ * main.c: New variable, ``not_parallel''.
+ * make.h: Add an extern for it.
+ * job.c (new_job): Test NOT_PARALLEL as well as JOB_SLOTS.
+ * NEWS: Add info on .NOTPARALLEL.
+ * make.texinfo (Special Targets): Document it.
+
+ * configure.in (GLOBDIR): Set to "glob" if we need to build the
+ glob library.
+ * Makefile.am (SUBDIRS): Use the GLOBDIR variable instead of
+ "glob" so we don't try to build glob if we don't need to (if we
+ have GLIBC glob). Reported by Lars Hecking <lhecking@nmrc.ucc.ie>.
+
+ * main.c (main): Don't put "***" in the clock skew warning
+ message. Reported by karl@gnu.org.
+
+ * make.h: Remove unneeded signal setup.
+
+ * signame.c: Remove extraneous #includes; some versions of Ultrix
+ don't protect against multiple inclusions and it causes compile
+ errors. Reported by Simon Burge <simonb@thistledown.com.au>.
+
+1999-10-15 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (quote_for_env): Rename from quote_as_word().
+
+ * make.h, *.c: Prefer strchr() and strrchr() in the code
+ rather than index() and rindex(). Define strchr/strrchr in terms
+ of index/rindex if the former aren't supported.
+
+ * default.c (CHECKOUT,v): Replace the fancy, complicated
+ patsubst/filter expression with a simple $(if ...) expression.
+
+ * main.c (print_usage): Add the bug reporting mailing address to
+ the --help output, as per the GNU coding standards.
+ Reported by Paul Eggert <eggert@twinsun.com>.
+
+ * README.customs: Installed information on running Customs-ized
+ GNU make and setuid root, collected by Ted Stern <stern@tera.com>.
+
+ * read.c (read_all_makefiles): PR/1394: Mark the end of the next
+ token in the MAKEFILES value string _before_ we dup it.
+
+1999-10-13 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in (make_cv_sys_gnu_glob): We used to add the -Iglob
+ flag to CPPFLAGS, but that loses if the user specifies his own
+ CPPFLAGS; this one gets added _after_ his and if he happens to
+ have an old or broken glob.h--boom. Instead, put it in GLOBINC
+ and SUBST it.
+
+ * Makefile.am (INCLUDES): Add @GLOBINC@ to the INCLUDES macro;
+ these things get on the compile line well before the user's
+ CPPFLAGS.
+
+1999-10-12 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (notice_finished_file): If we get here and -n is set,
+ see if all the command lines are marked recursive. If so, then we
+ ran every command there is, so check the mtime on this file just
+ like we would normally. If not, we assume the command we didn't
+ run would updates the target and set mtime of the target to "very new".
+
+ * job.c (start_job_command): Update lines_flags in the file's cmds
+ structure with any per-line tokens we found (`@', `-', `+').
+
+1999-10-08 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (initialize_file_variables): Always recurse to
+ initialize the parent's file variables: the parent might not have
+ any rules to run so it might not have been initialized before
+ this--we need this to set up the chain properly for
+ target-specific variables.
+
+1999-09-29 Paul Eggert <eggert@twinsun.com>
+
+ * main.c (quote_as_word): Always quote for decode_env_switches
+ instead of for the shell, so that arguments with strange
+ characters are are passed to submakes correctly. Remove
+ double_dollars arg; we always double dollars now. All callers
+ changed.
+ (decode_env_switches): Don't run off the end of an environment
+ variable whose contents ends in a unescaped backslash.
+
+1999-09-23 Paul D. Smith <psmith@gnu.org>
+
+ * commands.c, function.c, job.c, read.c: Cast arguments to
+ ctype.h functions/macros to _unsigned_ char for portability.
+
+ * remake.c, function.c: Compiler warning fixes: the second
+ argument to find_next_token() should be an _unsigned_ int*.
+ Reported by Han-Wen Nienhuys <hanwen@cs.uu.nl>.
+
+1999-09-23 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.78.1 released.
+
+ * make.texinfo: Update version/date stamp.
+
+ * main.c (main): Argh. For some reason we were closing _all_ the
+ jobserver pipes before we re-exec'd due to changed makefiles.
+ This means that any re-exec got a "jobserver unavailable" error :-/.
+ I can't believe we didn't notice this before.
+
+1999-09-22 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.78 released.
+
+ * main.c (main): Only fail on multiple --jobserver-fds options if
+ they aren't all the same. Some makefiles use things like
+ $(MAKE) $(MFLAGS) which will cause multiple, identical copies of
+ --jobserver-fds to show up.
+
+1999-09-16 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (define_makeflags): Zero out FLAGSTRING to avoid
+ uninitialized memory reads when checking *p != '-' in the loop.
+
+1999-09-15 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.97 released.
+
+ * configure.in (MAKE_HOST): AC_SUBST this so it will go into the
+ makefile.
+ * Makefile.am (check-local): Print a success banner if the check
+ succeeds.
+ (check-regression): A bit of fine-tuning.
+
+1999-09-15 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * README.DOS.template: Document requirements for the test suite.
+ * Makefile.DOS.template: Updates to allow the test suite to run
+ from "make check".
+
+ * main.c (main): Handle it if argv[0] isn't an absolute path.
+
+1999-09-13 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.96 released.
+
+ * Makefile.am (loadavg): Use CPPFLAGS, etc. to make sure we get
+ all the right #defines to compile.
+ (check-regression): Look for the regression test suite in the make
+ package itself. If we're building remotely, use symlinks to make
+ a local copy.
+ (dist-hook): Put the test suite into the tar file.
+
+ * configure.in: Look for perl for the test suite.
+
+1999-09-10 Paul Eggert <eggert@twinsun.com>
+
+ * acinclude.m4 (AC_SYS_LARGEFILE_FLAGS): If on HP-UX 10.20 or
+ later, and using GCC, define __STDC_EXT__; this works around a
+ bug in GCC 2.95.1.
+
+1999-09-08 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (print_version): Ugh. GLIBC's configure tries to check
+ make version strings and is too aggressive with their matching
+ expressions. I've struck a deal with them to leave the version
+ output as-is for 3.78, and they'll change their configure checks
+ so that I can change this back in the future.
+
+1999-09-07 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * job.c (construct_command_argv_internal) [__MSDOS__]: Add "echo"
+ and "unset" to the list of builtin shell commands.
+
+ * configh.DOS.template (MAKE_HOST): Define to "i386-pc-msdosdjgpp"
+ which is the canonical name of the DJGPP host.
+
+1999-09-05 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.95 released.
+
+ * make.texinfo (Make Errors): Document some new jobserver error
+ messages.
+
+1999-09-04 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * make.texinfo (Make Errors): Document the hint about 8 spaces
+ instead of a TAB.
+ (Call Function, Quick Reference): Use @code{$(1)}, not @var.
+
+ * main.c (main) [__MSDOS__]: Say "on this platform" instead of "on
+ MS-DOS", since the MSDOS version could run on Windows.
+
+1999-09-03 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (notice_finished_file): Always set mtime_before_update
+ if it's not been set, not just if we ran some rules. Otherwise we
+ may have a situation where a target's prerequisite was rebuilt but
+ not changed, so this target's rules weren't run, then
+ update_goal_chain() sees mtime_before_update != last_mtime and
+ thinks that the top-level target changed when it really didn't.
+ This can cause an infinite loop when remaking makefiles.
+ (update_goal_chain): If we get back to the top and we don't know
+ what the goal's last_mtime was, find it now. We need to know so
+ we can compare it to mtime_before_update later (this is only
+ crucial when remaking makefiles--should we only do it then?)
+
+1999-09-02 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): If "override" appears as the first
+ prerequisite, look further to ensure this is really a
+ target-specific variable definition, and not just some
+ prerequisite named "override".
+
+1999-09-01 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (IS_PATHSEP) [WINDOWS32]: Allow backslash separators
+ for W32 platforms.
+ * read.c (record_files) [WINDOWS32]: Allow backslash separators
+ for W32 platforms.
+ * implicit.c (pattern_search) [WINDOWS32]: Allow backslash
+ separators for W32 platforms.
+
+ * configure.in (MAKE_HOST): Define it to be the canonical build
+ host info, now that we need AC_CANONICAL_HOST anyway (for large
+ file support).
+ * version.c (make_host): Define a variable to MAKE_HOST so we're
+ sure to get it from the local config.h.
+ * main.c (print_version): Use it in the version information.
+ * config.ami.template: Add MAKE_HOST.
+ * configh.dos.template: Ditto.
+ * config.h.W32.template: Ditto.
+ * config.h-vms.template: Ditto.
+
+ * main.c (main): Close the jobserver file descriptors if we need
+ to re-exec ourselves.
+ Also print more reasonable error if users force -jN for submakes.
+ This may be common for a while until people use the jobserver
+ feature. If it happens, we ignore the existing jobserver stuff
+ and use whatever they specified on the commandline.
+ (define_makeflags): Fixed a long-standing bug: if a long name
+ only option comes immediately after a single letter option with no
+ argument, then the option string is constructed incorrectly. For
+ example, with -w and --jobserver-fds you get "-w-jobserver-fds..."
+ instead of "-w --jobserver-fds..."; add in an extra " -".
+
+ * make.texinfo (Phony Targets): Add another example of using
+ .PHONY with subdirectories/recursive make.
+
+1999-08-30 Paul D. Smith <psmith@gnu.org>
+
+ * README.W32.template: Renamed from README.W32 so it's
+ autogenerated during the dist. A few minor modifications.
+
+ * configure.in: Check for kstat_open before AC_FUNC_GETLOADAVG
+ since the latter needs to know whether the former exists to give
+ an accurate result.
+
+1999-08-26 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * NMakefile [WINDOWS32]: Now more robust. If you change a file
+ under w32/subproc, the make.exe will be relinked. Also added some
+ tests to make sure erase commands won't fail when executed in a
+ pristine build environment.
+
+ * w32/subproc/sub_proc.c [WINDOWS32]: Added support for
+ HAVE_CYGWIN_SHELL. If you are using the Cygwin B20.1 release, it
+ is now possible to have have native support for this shell without
+ having to rely on klutzy BATCH_MODE_ONLY_SHELL.
+
+ * config.h.W32 [WINDOWS32]: Added HAVE_CYGWIN_SHELL macro which
+ users can define if they want to build make to use this shell.
+
+ * README.W32 [WINDOWS32]: Added informaton about
+ HAVE_CYGWIN_SHELL. Cleaned up text a bit to make it more current.
+
+1999-08-26 Paul Eggert <eggert@twinsun.com>
+
+ Support large files in AIX, HP-UX, and IRIX.
+
+ * acinclude.m4 (AC_LFS): Remove. Superseded by AC_SYS_LARGEFILE.
+ (AC_SYS_LARGEFILE_FLAGS, AC_SYS_LARGEFILE_SPACE_APPEND,
+ AC_SYS_LARGEFILE_MACRO_VALUE, AC_SYS_LARGEFILE): New macros.
+ (jm_AC_TYPE_UINTMAX_T): Check for busted compilers that can't
+ shift or divide unsigned long long.
+ (AM_PROG_CC_STDC): New macro; a temporary workaround of a bug in
+ automake 1.4.
+
+ * configure.in (AC_CANONICAL_HOST): Add; required by new
+ AC_SYS_LARGEFILE.
+ (AC_SYS_LARGEFILE): Renamed from AC_LFS.
+ (AM_PROG_CC_STDC): Add.
+
+ * config.guess, config.sub: New files, needed for AC_CANONICAL_HOST.
+
+1999-08-25 Paul Eggert <eggert@twinsun.com>
+
+ * make.h (CHAR_MAX): New macro.
+ * main.c (struct command_switch): c is now int,
+ so that it can store values greater than CHAR_MAX.
+ (switches): Replace small numbers N with CHAR_MAX+N-1,
+ to avoid problems with non-ASCII character sets.
+ (short_option): New macro.
+ (init_switches, print_usage, define_makeflags): Use it instead of
+ isalnum.
+
+1999-08-25 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.94 released.
+
+ * main.c (main) [__MSDOS__]: If the user uses -j, warn that it's
+ not supported and reset it.
+
+ * make.h (ISDIGIT): Obtained this from the textutils distribution.
+ * main.c (decode_switches): Use it.
+ * function.c (is_numeric): Use it.
+
+ * main.c (struct command_switch): Store the switch char in an
+ unsigned char to shut up GCC about using it with ctype.h macros.
+ Besides, it _is_ always unsigned.
+
+1999-08-24 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Change "dependency" to "prerequisite" and
+ "dependencies" to "prerequisites". Various other cleanups related
+ to the terminology change.
+ * file.c: Change debugging and error messages to use
+ "prerequisite" instead of "dependency".
+ * implicit.c: Ditto.
+ * remake.c: Ditto.
+ * NEWS: Document it.
+
+1999-08-23 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_file): Move the considered check into the
+ double-colon rule loop, so we consider double-colon rules
+ individually (otherwise after the first is pruned, the rest won't
+ get run).
+
+ * README.template: Minor changes.
+
+ Remove the debugging features of the jobserver, so it no longer
+ writes distinct tokens to the pipe. Thus, we don't need to store
+ the token we get. A side effect of this is to remove a potential
+ "unavailable token" situation: make-1 invokes make-2 with its
+ special token and make-3 with a normal token; make-2 completes.
+ Now we're waiting for make-3 but using 2 tokens; our special token
+ is idle. In the new version we don't have special tokens per se,
+ we merely decide if we already have a child or not. If we don't,
+ we don't need a token. If we do, we have to get one to run the
+ next child. Similar for putting tokens back: if we're cleaning up
+ the last child, we don't put a token back. Otherwise, we do.
+
+ * main.c: Add a new, internal flag --jobserver-fds instead of
+ overloading the meaning of -j. Remove job_slots_str and add the
+ stringlist jobserver_fds.
+ (struct command_switch): We don't need the int_string type.
+ (switches[]): Add a new option for --jobserver-fds and remove
+ conditions around -j. Make the description for the former 0 so it
+ doesn't print during "make --help".
+ (main): Rework jobserver parsing. If we got --jobserver-fds
+ make sure it's valid. We only get one and job_slots must be 0.
+ If we're the toplevel make (-jN without --jobserver-fds) create
+ the pipe and write generic tokens.
+ Create the stringlist struct for the submakes.
+ Clean up the stringlist where necessary.
+ (init_switches): Remove int_string handling.
+ (print_usage): Don't print internal flags (description ptr is 0).
+ (decode_switches): Remove int_string handling.
+ (define_makeflags): Remove int_string handling.
+
+ * job.c: Remove my_job_token flag and all references to the
+ child->job_token field.
+ (free_job_token): Remove this and merge it into free_child().
+ (reap_children): Rework the "reaped a child" logic slightly.
+ Don't call defunct free_job_token anymore. Always call
+ free_child, even if we're dying.
+ (free_child): If we're not freeing the only child, put a token
+ back in the pipe. Then, if we're dying, don't bother to free.
+ (new_job): If we are using the jobserver, loop checking to see if
+ a) there are no children or b) we get a token from the pipe.
+
+ * job.h (struct child): Remove the job_token field.
+
+1999-08-20 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (try_variable_definition): Allocate for variable
+ expansion in f_append with a simple variable: if we're looking at
+ target-specific variables we don't want to trash the buffer.
+ Noticed by Reiner Beninga <Reiner.Beninga@mchp.siemens.de>.
+
+1999-08-16 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * main.c (main) [__MSDOS__]: Mirror any backslashes in argv[0], to
+ avoid problems in shell commands that use backslashes as escape
+ characters.
+
+1999-08-16 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.93 released.
+
+1999-08-13 Paul D. Smith <psmith@gnu.org
+
+ * function.c (func_if): New function $(if ...) based on the
+ original by Han-Wen but reworked quite a bit.
+ (function_table): Add it.
+ * NEWS: Introduce it.
+ * make.texinfo (If Function): Document it.
+
+ * job.c (free_job_token): Check for EINTR when writing tokens to
+ the jobserver pipe.
+
+1999-08-12 Paul D. Smith <psmith@gnu.org>
+
+ Another jobserver algorithm change. We conveniently forgot that
+ the blocking bit is shared by all users of the pipe, it's not a
+ per-process setting. Since we have many make processes all
+ sharing the pipe we can't use the blocking bit as a signal handler
+ flag. Instead, we'll dup the pipe's read FD and have the SIGCHLD
+ handler close the dup'd FD. This will cause the read() to fail
+ with EBADF the next time we invoke it, so we know we need to reap
+ children. We then re-dup and reap.
+
+ * main.c (main): Define the job_rfd variable to hold the dup'd FD.
+ Actually dup the read side of the pipe. Don't bother setting the
+ blocking bit on the file descriptor.
+ * make.h: Declare the job_rfd variable.
+ * job.c (child_handler): If the dup'd jobserver pipe is open,
+ close it and assign -1 to job_rfd to notify the main program that
+ we got a SIGCHLD.
+ (start_job_command): Close the dup'd FD before exec'ing children.
+ Since we open and close this thing so often it doesn't seem
+ worth it to use the close-on-exec bit.
+ (new_job): Remove code for testing/setting the blocking bit.
+ Instead of EAGAIN, test for EBADF. If the dup'd FD has been
+ closed, re-dup it before we reap children.
+
+ * function.c (func_shell): Be a little more accurate about the
+ length of the error string to allocate.
+
+ * expand.c (variable_expand_for_file): If there's no filenm info
+ (say, from a builtin command) then reset reading_file to 0.
+
+1999-08-09 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile: Use g in sed (s///g) to replace >1 variable per
+ line.
+
+ * Makefile.DOS.template [__MSDOS__]: Fix mostlyclean-aminfo to
+ remove the right files.
+
+1999-08-01 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * function.c (msdos_openpipe) [__MSDOS__]: *Really* return a FILE
+ ptr.
+
+1999-08-01 Paul D. Smith <psmith@gnu.org>
+
+ New jobserver algorithm to avoid a possible hole where we could
+ miss SIGCHLDs and get into a deadlock. The original algorithm was
+ suggested by Roland McGrath with a nice refinement by Paul Eggert.
+ Many thanks as well to Tim Magill and Howard Chu, who also
+ provided many viable ideas and critiques. We all had a fun week
+ dreaming up interesting ways to use and abuse UNIX syscalls :).
+
+ Previously we could miss a SIGCHLD if it happened after we reaped
+ the children but before we re-entered the blocking read. If this
+ happened to all makes and/or all children, make would never wake
+ up.
+
+ We avoid this by having the SIGCHLD handler reset the blocking bit
+ on the jobserver pipe read FD (normally read does block in this
+ algorithm). Now if the handler is called between the time we reap
+ and the time we read(), and there are no tokens available, the
+ read will merely return with EAGAIN instead of blocking.
+
+ * main.c (main): Set the blocking bit explicitly here.
+ * job.c (child_handler): If we have a jobserver pipe, set the
+ non-blocking bit for it.
+ (start_waiting_job): Move the token stuff back to new_job; if we
+ do it here then we're not controlling the number of remote jobs
+ started!
+ (new_job): Move the check for job slots to _after_ we've created a
+ child structure. If the read returns without getting a token, set
+ the blocking bit then try to reap_children.
+
+ * make.h (EINTR_SET): Define to test errno if EINTR is available,
+ or 0 otherwise. Just some code cleanup.
+ * arscan.c (ar_member_touch): Use it.
+ * function.c (func_shell): Use it.
+ * job.c (reap_children): Use it.
+ * remake.c (touch_file): Use it.
+
+1999-07-28 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Define _() and N_() macros as passthrough to initiate
+ NLS support.
+ * <all>: Add _()/N_() around translatable strings.
+
+1999-07-27 Paul D. Smith <psmith@gnu.org>
+
+ * read.c: Make sure make.h comes before other headers.
+
+1999-07-26 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Quick Reference): Update with the new features.
+
+1999-07-25 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * remake.c [__MSDOS__]: Don't include variables.h, it's already
+ included.
+
+ * function.c (msdos_openpipe) [__MSDOS__]: Return FILE ptr.
+ (func_shell) [__MSDOS__]: Fix the argument list.
+
+ * Makefile.DOS.template: Update from Makefile.in.
+
+ * README.DOS.template: Configure command fixed.
+
+ * configh.dos.template: Update to provide definitions for
+ uintmax_t, fd_set_size_t, and HAVE_SELECT.
+
+1999-07-24 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.91 released.
+
+ * configure.in: Changes to the boostrapping code: if build.sh.in
+ doesn't exist configure spits an error and generates an empty
+ build.sh file which causes make to be confused.
+ * maintMakefile: Don't build README early.
+
+1999-07-23 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (my_job_token): This variable controls whether we've
+ handed our personal token to a subprocess or not. Note we could
+ probably infer this from the value of job_slots_used, but it's
+ clearer to just keep it separately. Job_slots_used isn't really
+ relevant when running the job server.
+ (free_job_token): New function: free a job token. If we don't
+ have one, no-op. If we have the personal token, reclaim it. If
+ we have another token, write it back to the pipe.
+ (reap_children): Call free_job_token.
+ (free_child): Call free_job_token.
+ (start_job_command): Remove duplicate test for '+' in the command.
+ If we don't appear to be running a recursive make, close the
+ jobserver filedescriptors.
+ (start_waiting_job): If our personal token is available, use that
+ instead of going to the server pipe.
+ (*): Add the token value to many debugging statements, and print
+ the child target name in addition to the ptr hex value.
+ Change the default "no token" value from '\0' to '-' so it looks
+ better in the output.
+
+ * main.c (main): Install the child_handler with sigaction()
+ instead of signal() if we have it. On SysV systems, signal() uses
+ SysV semantics which are a pain. But sigaction() always does what
+ we want.
+ (main): If we got job server FDs from the environment, test them
+ to see if they're open. If not, the parent make closed them
+ because it didn't think we were a submake. Print a warning and
+ suggestion to use "+" on the submake invocation, and hard-set to
+ -j1 for this instance of make.
+ (main): Change the algorithm for assigning slots to be more
+ robust. Previously make checked to see if it thought a subprocess
+ was a submake and if so, didn't give it a token. Since make's
+ don't consume tokens we could spawn many of makes fighting for a
+ small number of tokens. Plus this is unreliable because submakes
+ might not be recognized by the parent (see above) then all the
+ tokens could be used up by unrecognized makes, and no one could
+ run. Now every make consumes a token from its parent. However,
+ the make can also use this token to spawn a child. If the make
+ wants more than one, it goes to the jobserver pipe. Thus there
+ will never be more than N makes running for -jN, and N*2 processes
+ (N makes and their N children). Every make can always run at
+ least one job, and we'll never deadlock. (Note the closing of the
+ pipe for non-submakes also solves this, but this is still a better
+ algorithm.) So! Only put N-1 tokens into the pipe, since the
+ topmost make keeps one for itself.
+
+ * configure.in: Find sigaction. Disable job server support unless
+ the system provides it, in addition to either waitpid() or
+ wait3().
+
+1999-07-22 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * arscan.c (ar_member_touch) [WINDOWS32]: The ar_date field is a
+ string on Windows, not a timestamp.
+
+1999-07-21 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77.90 released.
+
+ * Makefile.am (AUTOMAKE_OPTIONS): Require automake 1.4.
+
+ * function.c: Rearrange so we don't need to predeclare the
+ function_table array; K&R C compilers don't like that.
+
+ * acinclude.m4 (AC_FUNC_SELECT): Ouch; this requires an ANSI C
+ compiler! Change to work with K&R compilers as well.
+
+ * configure.in (AC_OUTPUT): Put build.sh back. I don't know how I
+ thought it would work this way :-/. We'll have to think of
+ something else.
+ * Makefile.am: Remove rule to create build.sh.
+
+ * default.c (default_suffix_rules): Rearrange the default command
+ lines to conform to POSIX rules (put the filename argument $<
+ _after_ the OUTPUT_OPTION, not before it).
+
+ * various: Changed !strncmp() calls to strneq() macros.
+
+ * misc.c (sindex): Make slightly more efficient.
+
+ * dir.c (file_impossible): Change savestring(X,strlen(X)) to xstrdup().
+ * implicit.c (pattern_search): Ditto.
+ * main.c (enter_command_line_file): Ditto.
+ (main): Ditto.
+ * misc.c (copy_dep_chain): Ditto.
+ * read.c (read_makefile): Ditto.
+ (parse_file_seq): Ditto.
+ (tilde_expand): Ditto.
+ (multi_glob): Ditto.
+ * rule.c (install_pattern_rule): Ditto.
+ * variable.c (define_variable_in_set): Ditto.
+ (define_automatic_variables): Ditto.
+ * vpath.c (construct_vpath_list): Ditto.
+
+ * misc.c (xrealloc): Some reallocs are non-standard: work around
+ them in xrealloc by calling malloc if PTR is NULL.
+ * main.c (main): Call xrealloc() directly instead of testing for
+ NULL.
+
+ * function.c (func_sort): Don't try to free NULL; some older,
+ non-standard versions of free() don't like it.
+
+ * configure.in (--enable-dmalloc): Install some support for using
+ dmalloc (http://www.dmalloc.com/) with make. Use --enable-dmalloc
+ with configure to enable it.
+
+ * function.c (function_table_entry): Whoops! The function.c
+ rewrite breaks backward compatibility: all text to a function is
+ broken into arguments, and extras are ignored. So $(sort a,b,c)
+ returns "a"! Etc. Ouch. Fix it by making a positive value in
+ the REQUIRED_ARGS field mean exactly that many arguments to the
+ function; any "extras" are considered part of the last argument as
+ before. A negative value means at least that many, but may be
+ more: in this case all text is broken on commas.
+ (handle_function): Stop when we've seen REQUIRED_ARGS args, if >0.
+ (expand_builtin_function): Compare number of args to the absolute
+ value of REQUIRED_ARGS.
+
+1999-07-20 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): Ensure that the state of the target
+ is cs_running. It might not be if we skipped all the lines due to
+ -n (for example).
+
+ * commands.c (execute_file_commands): If we discover that the
+ command script is empty and succeed early, set cs_running so the
+ modtime of the target is still rechecked.
+
+ * rule.c (freerule): Free the dependency list for the rule.
+
+ * implicit.c (pattern_search): When turning an intermediate file
+ into a real target, keep the also_make list.
+ Free the dep->name if we didn't use it during enter_file().
+
+1999-07-16 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Don't allocate the commands buffer until
+ we're sure we found a makefile and won't return early (mem leak).
+
+ * job.c (start_job_command): Broken #ifdef test: look for F_SETFD,
+ not FD_SETFD. Close-on-exec isn't getting set on the bad_stdin
+ file descriptor and it's leaking :-/.
+ * getloadavg.c (getloadavg): Ditto.
+
+1999-07-15 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Fix some potential memory stomps parsing
+ `define' directives where no variable name is given.
+
+ * function.c (func_call): Rename from func_apply. Various code
+ cleanup and tightening.
+ (function_table): Add "call" as a valid builtin function.
+
+ * make.texinfo (Call Function): Document it.
+
+ * NEWS: Announce it.
+
+1999-07-09 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * variable.c (try_variable_definition) [__MSDOS__, WINDOWS32]:
+ Treat "override SHELL=" the same as just "SHELL=".
+
+1999-07-09 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_waiting_job): Don't get a second job token if we
+ already have one; if we're waiting on the load to go down
+ start_waiting_job() might get called twice on the same file.
+
+ * filedef.h (struct file): Add new field, mtime_before_update.
+ When notice_finished_file runs it assigns the cached last_mtime to
+ this field.
+ * remake.c (update_goal_chain): Notice that a file wasn't updated
+ by asking if it changed (g->changed) and comparing the current
+ cached time (last_mtime) with the previous one, stored in
+ mtime_before_update. The previous check ("did last_mtime changed
+ during the run of update_file?") fails for parallel builds because
+ last_mtime is set during reap_children, before update_file is run.
+ This causes update_goal_chain to always return -1 (nothing
+ rebuilt) when running parallel (-jN). This is OK during "normal"
+ builds since our caller (main) treats these cases identically in
+ that case, but if when rebuilding makefiles the difference is very
+ important, as it controls whether we re-exec or not.
+ * file.c (file_hash_enter): Copy the mtime_before_update field.
+ (snap_deps): Initialize mtime_before_update to -1.
+ * main.c (main): Initialize mtime_before_update on old (-o) and
+ new (-W) files.
+
+1999-07-08 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (switches): Define a new switch -R (or
+ --no-builtin-variables). This option disables the defining of all
+ the GNU make builtin variables.
+ (main): If -R was given, force -r as well.
+ * default.c (define_default_variables): Test the new flag.
+ * make.h: Declare global flag.
+ * make.texinfo (Options Summary): Document the new option.
+ (Implicit Variables): Ditto.
+
+1999-07-06 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Options Summary): Correct examples in
+ --print-data-base option summary (problem reported by David Morse
+ <morse@nichimen.com>).
+
+ * arscan.c: Add support for archives in Windows (VC++). Frank
+ Libbrecht <frankl@abzx.belgium.hp.com> provided info on how to do
+ this.
+ * NMakefile.template (CFLAGS_any): Remove NO_ARCHIVES from the
+ compile line.
+ * build_w32.bat: Ditto.
+
+ * remake.c (no_rule_error): Fix -include/sinclude so it doesn't
+ give errors if you try to -include the same file twice.
+ (updating_makefiles): New variable: we need to know this info in
+ no_rule_error() so we know whether to print an error or not.
+ (update_file_1): Unconditionally call no_rule_error(), don't try
+ to play games with the dontcare flag.
+
+1999-06-14 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Remaking Makefiles): Add a description of how to
+ prevent implicit rule searches for makefiles.
+
+ * make.1: Remove statement that make continues processing when -v
+ is given.
+
+1999-06-14 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Cast -1 arguments to
+ variable_expand_string() to long. Alexandre Sauve
+ <Alexandre.SAUVE@ifp.fr> reports that without casts, this breaks
+ on a NEC SUPER-UX SX-4 system (and it's wrong without a cast
+ anyway). Of course, (a) I'd really love to start using function
+ prototypes, and (b) there's a whole slew of issues related to int
+ vs. long and signed vs. unsigned in the length handling of
+ variable buffers, etc. Gross. Needs a complete mucking-out.
+ * expand.c (variable_expand): Ditto.
+
+ * acinclude.m4 (AC_FUNC_SELECT): Slight enhancement for AIX 3.2 by
+ Lars Hecking <lhecking@nmrc.ucc.ie>.
+
+ * read.c (get_next_mword): Allow colons to be escaped in target
+ names: fix for regression failure.
+
+1999-04-26 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Reset read_makefiles to empty after processing so
+ we get the right error message.
+
+1999-04-25 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Updates to @dircategory and @direntry suggested by
+ Karl Berry <karl@cs.umb.edu>.
+
+1999-04-23 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * job.c (start_job_command) [__MSDOS__]: Call unblock_sigs before
+ turning off dos_command_running, so child's signals produce the
+ right effect.
+
+ * commands.c (fatal_error_signal) [__MSDOS__]: Use EXIT_FAILURE
+ instead of 1.
+
+1999-04-18 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * configh.dos.template: Update to recognize that version 2.02 of
+ DJGPP contains sys_siglist stuff.
+
+1999-04-14 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Options/Recursion): Document the job server.
+ (Parallel): Tweaks.
+
+1999-04-13 Paul D. Smith <psmith@gnu.org>
+
+ Implement a new "job server" feature; the implementation was
+ suggested by Howard Chu <hyc@highlandsun.com>.
+
+ * configure.in (job-server): New disable option for job server
+ support--it's enabled by default. If it works well this will go
+ away.
+
+ * NEWS: Summarize the new feature.
+
+ * acconfig.h: New definition MAKE_JOBSERVER if job server support
+ is enabled.
+ * config.h-vms.template: Undef MAKE_JOBSERVER for this port.
+ * config.h.W32.template: Ditto.
+ * config.ami.template: Ditto.
+
+ * main.c (struct command_switch): Add a new type: int_string.
+ (switches[]) Use int_string for -j if MAKE_JOBSERVER.
+ (init_switches): Initialize the new int_string switch type.
+ (print_usage): New function, extracted from decode_switches().
+ (decode_switches): Call it. Decode the new int_string switch type.
+ (define_makeflags): Add new int_string switch data to MAKEFLAGS.
+ (job_fds[]) Array to contain the pipe file descriptors.
+ (main): Parse the job_slots_str option results. If necessary,
+ create the pipe and seed it with tokens. Set the non-blocking bit
+ for the read fd. Enable the signal handler for SIGCHLD even if we
+ have a non-hanging wait; it's needed to interrupt the select() in
+ job.c:start_waiting_job().
+
+ * make.h: Declare job_fds[].
+
+ * job.h (struct child): Add job_token field to store the token for
+ this job (if any).
+
+ * job.c (reap_children): When a child is fully reaped, release the
+ token back into the pipe.
+ (free_child): If the child to be freed still has a token, put it
+ back.
+ (new_job): Initialize the job_token member.
+ (start_waiting_job): For local jobs, if we're using the pipe, get
+ a token before we check the load, etc. We do this by performing a
+ non-blocking read in a loop. If the read fails, no token is
+ available. Do a select on the fd to wait for a token. We need to
+ re-enable the signal handler for SIGCHLD even if we have a
+ non-hanging waitpid() or wait3(), so that the signal will
+ interrupt the select() and we can wake up to reap children.
+ (child_handler): Re-enable the signal handler. The count is still
+ kept although it's not needed or used unless you don't have
+ waitpid() or wait3().
+
+1999-04-10 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Reset the considered bit on all the makefiles if
+ something failed to update; we need to examine them again if they
+ appear as normal targets in order to get the proper error message.
+
+1999-04-09 Paul D. Smith <psmith@gnu.org>
+
+ Performance enhancement from Tim Magill <tim.magill@telops.gte.com>.
+
+ * remake.c (update_file): If you have large numbers of
+ dependencies and you run in parallel, make can spend considerable
+ time each pass through the graph looking at branches it has
+ already seen. Since we only reap_children() when starting a pass,
+ not in the middle, if a branch has been seen already in that pass
+ nothing interesting can happen until the next pass. So, we toggle
+ a bit saying whether we've seen this target in this pass or not.
+ (update_goal_chain): Initially set the global considered toggle to
+ 1, since all targets initialize their boolean to 0. At the end of
+ each pass, toggle the global considered variable.
+ * filedef.h (struct file): Per-file considered toggle bit.
+ * file.c: New global toggle variable considered.
+
+1999-04-05 Paul D. Smith <psmith@gnu.org>
+
+ * arscan.c (ar_scan): Added support for ARFZMAG (compressed
+ archives?) for Digital UNIX C++. Information provided by
+ Patrick E. Krogel <pekrogel@mtu.edu>.
+ (ar_member_touch): Ditto.
+
+1999-04-03 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): If: a) we found a file and b) we didn't
+ create it and c) it's not marked as an implicit target and d) it
+ is marked as an intermediate target, then it was so marked due to
+ an .INTERMEDIATE special target, but it already existed in the
+ directory. In this case, unset the intermediate flag so we won't
+ delete it when make is done. It feels like it would be cleaner to
+ put this check in update_file_1() but I worry it'll get missed...
+
+1999-04-01 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): Use bcopy() to copy
+ overlapping strings, rather than strcpy(). ISO C says the latter
+ is undefined. Found this in a bug report from 1996! Ouch!
+
+1999-03-31 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (readline): Ignore carriage returns at the end of the
+ line, to allow Windows-y CRLF line terminators.
+
+1999-03-30 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Don't put build.sh here, since build.sh.in doesn't
+ exist initially. This cause autoreconf and automake to fail when
+ run on a clean CVS checkout. Instead, we create build.sh in the
+ Makefile (see below).
+
+ * Makefile.am: Remove BUILT_SOURCES; this is no longer relevant.
+ Put those files directly into EXTRA_DIST so they're distributed.
+ Create a local build rule to create build.sh.
+ Create a local maintainer-clean rule to delete all the funky
+ maintainers files.
+
+ * maintMakefile: Makefile.in depends on README, since automake
+ fails if it doesn't exist. Also don't remove glob/Makefile.in
+ here, as it causes problems.
+
+1999-03-26 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Substitute GLOBLIB if we need the link the
+ glob/libglob.a library.
+ * Makefile.am (make_LDADD): Use the subst variable GLOBLIB so we
+ don't link the local libglob.a at all if we don't need it.
+ * build.template: Don't compile glob/*.o unless we want globlib.
+ * maintMakefile (build.sh.in): Substitute the glob/*.o files
+ separately.
+
+1999-03-25 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Various typos and additions, pointed out by James
+ G. Sack <jsack@dornfeld.com>.
+
+1999-03-22 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Functions): Add a new section documenting the new
+ $(error ...) and $(warning ...) functions. Also updated copyright
+ dates.
+ * NEWS: Updated for the new functions.
+ * function.c (func_error): Implement the new $(error ...) and
+ $(warning ...) functions.
+ (function_table): Insert new functions into the table.
+ (func_firstword): Don't call find_next_token() with argv[0]
+ itself, since that function modifies the pointer.
+ * function.c: Cleanups and slight changes to the new method of
+ calling functions.
+
+1999-03-20 Han-Wen Nienhuys <hanwen@cs.uu.nl>
+
+ * function.c: Rewrite to use one C function per make function,
+ instead of a huge switch statement. Also allows some cleanup of
+ multi-architecture issues, and a cleaner API which makes things
+ like func_apply() simple.
+
+ * function.c (func_apply): Initial implementation. Expand either
+ a builtin function or a make variable in the context of some
+ arguments, provided as $1, $2, ... $N.
+
+1999-03-19 Eli Zaretskii <eliz@is.elta.co.il>
+1999-03-19 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * job.c (construct_command_argv_internal): Don't treat _all_
+ backslashes as escapes, only those which really escape a special
+ character. This allows most normal "\" directory separators to be
+ treated normally.
+
+1999-03-05 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Check for a system strdup().
+ * misc.c (xstrdup): Created. Suggestion by Han-Wen Nienhuys
+ <hanwen@cs.uu.nl>.
+ * make.h: Prototype xstrdup().
+ * remake.c (library_search): Use it.
+ * main.c (main): Use it.
+ (find_and_set_default_shell): Use it.
+ * job.c (construct_command_argv_internal): Use it.
+ * dir.c (find_directory): Use it.
+
+ * Makefile.am, configure.in: Use AC_SUBST_FILE to insert the
+ maintMakefile instead of "include", to avoid automake 1.4
+ incompatibility.
+
+1999-03-04 Paul D. Smith <psmith@gnu.org>
+
+ * amiga.c, amiga.h, ar.c, arscan.c, commands.c, commands.h,
+ * default.c, dep.h, dir.c, expand.c, file.c, filedef.h, function.c,
+ * implicit.c, job.c, job.h, main.c, make.h, misc.c, read.c, remake.c
+ * remote-cstms.c, remote-stub.c, rule.h, variable.c, variable.h,
+ * vpath.c, Makefile.ami, NMakefile.template, build.template,
+ * makefile.vms: Updated FSF address in the copyright notice.
+
+ * variable.c (try_variable_definition): If we see a conditional
+ variable and we decide to set it, re-type it as recursive so it
+ will be expanded properly later.
+
+1999-02-22 Paul D. Smith <psmith@gnu.org>
+
+ * NEWS: Mention new .LIBPATTERNS feature.
+
+ * make.texinfo (Libraries/Search): Describe the use and
+ ramifications of the new .LIBPATTERNS variable.
+
+ * remake.c (library_search): Instead of searching only for the
+ hardcoded expansion "libX.a" for a library reference "-lX", we
+ obtain a list of patterns from the .LIBPATTERNS variable and
+ search those in order.
+
+ * default.c: Added a new default variable .LIBPATTERNS. The
+ default for UNIX is "lib%.so lib%.a". Amiga and DOS values are
+ also provided.
+
+ * read.c: Remove bogus HAVE_GLOB_H references; always include
+ vanilla glob.h.
+
+1999-02-21 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (expand_function): Set value to 0 to avoid freeing it.
+ * variable.c (pop_variable_scope): Free the value of the variable.
+ (try_variable_definition): For simple variables, use
+ allocated_variable_expand() to avoid stomping on the variable
+ buffer when we still need it for other things.
+
+ * arscan.c: Modified to support AIX 4.3 big archives. The changes
+ are based on information provided by Phil Adams
+ <padams@austin.ibm.com>.
+
+1999-02-19 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Check to see if the GNU glob library is already
+ installed on the system. If so, _don't_ add -I./glob to the
+ compile line. Using the system glob code with the local headers
+ is very bad mojo!
+ Rewrite SCCS macros to use more autoconf facilities.
+
+ * Makefile.am: Move -Iglob out of INCLUDES; it'll get added to
+ CPPFLAGS by configure now.
+ Automake 1.4 introduced its own "include" feature which conflicts
+ with the maintMakefile stuff. A hack that seems to work is add a
+ space before the include :-/.
+
+ * build.template: Move -Iglob out of the compile line; it'll get
+ added to CPPFLAGS by configure now.
+
+1999-02-16 Glenn D. Wolf <Glenn_Wolf@email.sps.mot.com>
+
+ * arscan.c (ar_scan) [VMS]: Initialized VMS_member_date before
+ calling lbr$get_index since if the archive is empty,
+ VMS_get_member_info won't get called at all, and any leftover date
+ will be used. This bug shows up if any member of any archive is
+ made, followed by a dependency check on a different, empty
+ archive.
+
+1998-12-13 Martin Zinser <zinser@decus.decus.de>
+
+ * config.h-vms [VMS]: Set _POSIX_C_SOURCE. Redefine the getopt
+ functions so we don't use the broken VMS versions.
+ * makefile.com [VMS]: Allow debugging.
+ * dir.c (dir_setup_glob) [VMS]: Don't extern stat() on VMS.
+
+1998-11-30 Paul D. Smith <psmith@gnu.org>
+
+ * signame.c (init_sig): Check the sizes of signals being set up to
+ avoid array overwrites (if the system headers have problems).
+
+1998-11-17 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (record_files): Clean up some indentation.
+
+1998-11-08 Han-Wen Nienhuys <hanwen@cs.uu.nl>
+
+ * rule.c (print_rule_data_base): Fix arguments to fatal() call.
+
+1998-10-13 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): If the command list resolves to no
+ chars at all (e.g.: "foo:;$(empty)") then command_ptr is NULL;
+ quit early.
+
+1998-10-12 Andreas Schwab <schwab@issan.cs.uni-dortmund.de>
+
+ * rule.c (print_rule_data_base): Ignore num_pattern_rules if it is
+ zero.
+
+1998-10-09 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Allow non-empty lines to expand to the
+ empty string after variable, etc., expansion, and be ignored.
+
+1998-09-21 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): Only add COMMAND.COM
+ "@echo off" line for non-UNIXy shells.
+
+1998-09-09 Paul D. Smith <psmith@gnu.org>
+
+ * w32/subproc/sub_proc.c: Add in missing HAVE_MKS_SHELL tests.
+
+1998-09-04 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): If we hit the "missing separator" error,
+ check for the common case of 8 spaces instead of a TAB and give an
+ extra comment to help people out.
+
+1998-08-29 Paul Eggert <eggert@twinsun.com>
+
+ * configure.in (AC_STRUCT_ST_MTIM_NSEC):
+ Renamed from AC_STRUCT_ST_MTIM.
+
+ * acinclude.m4 (AC_STRUCT_ST_MTIM_NSEC): Likewise.
+ Port to UnixWare 2.1.2 and pedantic Solaris 2.6.
+
+ * acconfig.h (ST_MTIM_NSEC):
+ Renamed from HAVE_ST_MTIM, with a new meaning.
+
+ * filedef.h (FILE_TIMESTAMP_FROM_S_AND_NS):
+ Use new ST_MTIM_NSEC macro.
+
+1998-08-26 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (check_dep): For any intermediate file, not just
+ secondary ones, try implicit and default rules if no explicit
+ rules are given. I'm not sure why this was restricted to
+ secondary rules in the first place.
+
+1998-08-24 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Special Targets): Update documentation for
+ .INTERMEDIATE: if used with no dependencies, then it does nothing;
+ old docs said it marked all targets as intermediate, which it
+ didn't... and which would be silly :).
+
+ * implicit.c (pattern_search): If we find a dependency in our
+ internal tables, make sure it's not marked intermediate before
+ accepting it as a found_file[].
+
+1998-08-20 Paul D. Smith <psmith@gnu.org>
+
+ * ar.c (ar_glob): Use existing alpha_compare() with qsort.
+ (ar_glob_alphacompare): Remove it.
+
+ Modify Paul Eggert's patch so we don't abandon older systems:
+
+ * configure.in: Warn the user if neither waitpid() nor wait3() is
+ available.
+
+ * job.c (WAIT_NOHANG): Don't syntax error on ancient hosts.
+ (child_handler, dead_children): Define these if WAIT_NOHANG is not
+ available.
+ (reap_children): Only track the dead_children count if no
+ WAIT_NOHANG. Otherwise, it's a boolean.
+
+ * main.c (main): Add back signal handler if no WAIT_NOHANG is
+ available; only use default signal handler if it is.
+
+1998-08-20 Paul Eggert <eggert@twinsun.com>
+
+ Install a more robust signal handling mechanism for systems which
+ support it.
+
+ * job.c (WAIT_NOHANG): Define to a syntax error if our host
+ is truly ancient; this should never happen.
+ (child_handler, dead_children): Remove.
+ (reap_children): Don't try to keep separate track of how many
+ dead children we have, as this is too bug-prone.
+ Just ask the OS instead.
+ (vmsHandleChildTerm): Fix typo in error message; don't mention
+ child_handler.
+
+ * main.c (main): Make sure we're not ignoring SIGCHLD/SIGCLD;
+ do this early, before we could possibly create a subprocess.
+ Just use the default behavior; don't have our own handler.
+
+1998-08-18 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * read.c (read_makefile) [__MSDOS__, WINDOWS32]: Add code to
+ recognize library archive members when dealing with drive spec
+ mess. Discovery and initial fix by George Racz <gracz@mincom.com>.
+
+1998-08-18 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Check for stdlib.h explicitly (some hosts have it
+ but don't have STDC_HEADERS).
+ * make.h: Use HAVE_STDLIB_H. Clean up some #defines.
+ * config.ami: Re-compute based on new config.h.in contents.
+ * config.h-vms: Ditto.
+ * config.h.W32: Ditto.
+ * configh.dos: Ditto.
+
+ * dir.c (find_directory) [WINDOWS32]: Windows stat() fails if
+ directory names end with `\' so strip it.
+
+1998-08-17 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Added copyright year to the printed copy. Removed
+ the price from the manual. Change the top-level reference to
+ running make to be "Invoking make" instead of "make Invocation",
+ to comply with GNU doc standards.
+
+ * make.h (__format__, __printf__): Added support for these in
+ __attribute__ macro.
+ (message, error, fatal): Use ... prototype form under __STDC__.
+ Add __format__ attributes for printf-style functions.
+
+ * configure.in (AC_FUNC_VPRINTF): Check for vprintf()/_doprnt().
+
+ * misc.c (message, error, fatal): Add preprocessor stuff to enable
+ creation of variable-argument functions with appropriate
+ prototypes, that works with ANSI, pre-ANSI, varargs.h, stdarg.h,
+ v*printf(), _doprnt(), or none of the above. Culled from GNU
+ fileutils and slightly modified.
+ (makefile_error, makefile_error): Removed (merged into error() and
+ fatal(), respectively).
+ * amiga.c: Use them.
+ * ar.c: Use them.
+ * arscan.c: Use them.
+ * commands.c: Use them.
+ * expand.c: Use them.
+ * file.c: Use them.
+ * function.c: Use them.
+ * job.c: Use them.
+ * main.c: Use them.
+ * misc.c: Use them.
+ * read.c: Use them.
+ * remake.c: Use them.
+ * remote-cstms.c: Use them.
+ * rule.c: Use them.
+ * variable.c: Use them.
+
+ * make.h (struct floc): New structure to store file location
+ information.
+ * commands.h (struct commands): Use it.
+ * variable.c (try_variable_definition): Use it.
+ * commands.c: Use it.
+ * default.c: Use it.
+ * file.c: Use it.
+ * function.c: Use it.
+ * misc.c: Use it.
+ * read.c: Use it.
+ * rule.c: Use it.
+
+1998-08-16 Paul Eggert <eggert@twinsun.com>
+
+ * filedef.h (FILE_TIMESTAMP_PRINT_LEN_BOUND): Add 10, for nanoseconds.
+
+1998-08-16 Paul Eggert <eggert@twinsun.com>
+
+ * filedef.h (FLOOR_LOG2_SECONDS_PER_YEAR): New macro.
+ (FILE_TIMESTAMP_PRINT_LEN_BOUND): Tighten bound, and try to
+ make it easier to understand.
+
+1998-08-14 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): We've already unquoted any colon chars
+ by the time we're done reading the targets, so arrange for
+ parse_file_seq() on the target list to not do so again.
+
+1998-08-05 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Added glob/configure.in data. We'll have the glob
+ code include the regular make config.h, rather than creating its
+ own.
+
+ * getloadavg.c (main): Change return type to int.
+
+1998-08-01 Paul Eggert <eggert@twinsun.com>
+
+ * job.c (reap_children): Ignore unknown children.
+
+1998-07-31 Paul D. Smith <psmith@gnu.org>
+
+ * make.h, filedef.h, dep.h, rule.h, commands.h, remake.c:
+ Add prototypes for functions. Some prototypes needed to be moved
+ in order to get #include order reasonable.
+
+1998-07-30 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Added MIN/MAX.
+ * filedef.h: Use them; remove FILE_TIMESTAMP_MIN.
+
+1998-07-30 Paul Eggert <eggert@twinsun.com>
+
+ Add support for sub-second timestamp resolution on hosts that
+ support it (just Solaris 2.6, so far).
+
+ * acconfig.h (HAVE_ST_MTIM, uintmax_t): New undefs.
+ * acinclude.m4 (jm_AC_HEADER_INTTYPES_H, AC_STRUCT_ST_MTIM,
+ jm_AC_TYPE_UINTMAX_T): New defuns.
+ * commands.c (delete_target): Convert file timestamp to
+ seconds before comparing to archive timestamp. Extract mod
+ time from struct stat using FILE_TIMESTAMP_STAT_MODTIME.
+ * configure.in (C_STRUCT_ST_MTIM, jm_AC_TYPE_UINTMAX_T): Add.
+ (AC_CHECK_LIB, AC_CHECK_FUNCS): Add clock_gettime.
+ * file.c (snap_deps): Use FILE_TIMESTAMP, not time_t.
+ (file_timestamp_now, file_timestamp_sprintf): New functions.
+ (print_file): Print file timestamps as FILE_TIMESTAMP, not
+ time_t.
+ * filedef.h: Include <inttypes.h> if available and if HAVE_ST_MTIM.
+ (FILE_TIMESTAMP, FILE_TIMESTAMP_STAT_MODTIME, FILE_TIMESTAMP_MIN,
+ FILE_TIMESTAMPS_PER_S, FILE_TIMESTAMP_FROM_S_AND_NS,
+ FILE_TIMESTAMP_DIV, FILE_TIMESTAMP_MOD, FILE_TIMESTAMP_S,
+ FILE_TIMESTAMP_NS, FILE_TIMESTAMP_PRINT_LEN_BOUND): New macros.
+ (file_timestamp_now, file_timestamp_sprintf): New decls.
+ (struct file.last_mtime, f_mtime, file_mtime_1, NEW_MTIME):
+ time_t -> FILE_TIMESTAMP.
+ * implicit.c (pattern_search): Likewise.
+ * vpath.c (vpath_search, selective_vpath_search): Likewise.
+ * main.c (main): Likewise.
+ * remake.c (check_dep, name_mtime, library_search, f_mtime): Likewise.
+ (f_mtime): Use file_timestamp_now instead of `time'.
+ Print file timestamp with file_timestamp_sprintf.
+ * vpath.c (selective_vpath_search): Extract file time stamp from
+ struct stat with FILE_TIMESTAMP_STAT_MODTIME.
+
+1998-07-28 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.77 released.
+
+ * dosbuild.bat: Change to DOS CRLF line terminators.
+
+ * make-stds.texi: Update from latest version.
+
+ * make.texinfo (Options Summary): Clarify that the -r option
+ affects only rules, not builtin variables.
+
+1998-07-27 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Make __attribute__ resolve to empty for non-GCC _and_
+ for GCC pre-2.5.x.
+
+ * misc.c (log_access): Print UID/GID's as unsigned long int for
+ maximum portability.
+
+ * job.c (reap_children): Print PIDs as long int for maximum
+ portability.
+
+1998-07-24 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS (*_INSTALL, *_UNINSTALL): Replace `true' with `:'.
+
+1998-07-25 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.76.94 released.
+
+1998-07-23 Paul D. Smith <psmith@gnu.org>
+
+ * config.h.W32.template: Make sure all the #defines of macros here
+ have a value (e.g., use ``#define HAVE_STRING_H 1'' instead of
+ just ``#define HAVE_STRING_H''. Keeps the preprocessor happy in
+ some contexts.
+
+ * make.h: Remove __attribute__((format...)) stuff; using it with
+ un-prototyped functions causes older GCC's to fail.
+
+ * Version 3.76.93 released.
+
+1998-07-22 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (print_file_data_base): Fix average calculation.
+
+1998-07-20 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (die): Postpone the chdir() until after
+ remove_intermediates() so that intermediate targets with relative
+ pathnames are removed properly.
+
+1998-07-17 Paul D. Smith <psmith@gnu.org>
+
+ * filedef.h (struct file): New flag: did we print an error or not?
+
+ * remake.c (no_rule_error): New function to print error messages,
+ extraced from remake_file().
+
+ * remake.c (remake_file): Invoke the new error print function.
+ (update_file_1): Invoke the error print function if we see that we
+ already tried this target and it failed, but that an error wasn't
+ printed for it. This can happen if a file is included with
+ -include or sinclude and couldn't be built, then later is also
+ the dependency of another target. Without this change, make just
+ silently stops :-/.
+
+1998-07-16 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Removed "beta" version designator.
+ Updated ISBN for the next printing.
+
+1998-07-13 Paul Eggert <eggert@twinsun.com>
+
+ * acinclude.m4: New AC_LFS macro to determine if special compiler
+ flags are needed to allow access to large files (e.g., Solaris 2.6).
+ * configure.in: Invoke it.
+
+1998-07-08 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS: track changes in Makefile.in.
+
+1998-07-07 Paul D. Smith <psmith@gnu.org>
+
+ * remote-cstms.c (start_remote_job): Move gethostbyaddr() to the
+ top so host is initialized early enough.
+
+ * acinclude.m4: New file. Need some special autoconf macros to
+ check for network libraries (-lsocket, -lnsl, etc.) when
+ configuring Customs.
+
+ * configure.in (make_try_customs): Invoke new network libs macro.
+
+1998-07-06 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.76.92 released.
+
+ * README.customs: Added to the distribution.
+
+ * configure.in (make_try_customs): Rewrite to require an installed
+ Customs library, rather than looking at the build directory.
+
+ * Makefile.am (man_MANS): Install make.1.
+ * make.1: Renamed from make.man.
+
+ * make.texinfo (Bugs): New mailing list address for GNU make bug
+ reports.
+
+1998-07-02 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.76.91 released.
+
+ * default.c: Added default rule for new-style RCS master file
+ storage; ``% :: RCS/%''.
+ Added default rules for DOS-style C++ files with suffix ".cpp".
+ They use the new LINK.cpp and COMPILE.cpp macros, which are set by
+ default to be equal to LINK.cc and COMPILE.cc.
+
+1998-06-19 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * job.c (start_job_command): Reset execute_by_shell after an empty
+ command was skipped.
+
+1998-06-09 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Keep track of the temporary filename created when
+ reading a makefile from stdin (-f-) and attempt to remove it
+ as soon as we know we're not going to re-exec. If we are, add it
+ to the exec'd make's cmd line with "-o" so the exec'd make doesn't
+ try to rebuild it. We still have a hole: if make re-execs then
+ the temporary file will never be removed. To fix this we'd need
+ a brand new option that meant "really delete this".
+ * AUTHORS, getopt.c, getopt1.c, getopt.h, main.c (print_version):
+ Updated mailing addresses.
+
+1998-06-08 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Andreas Luik <luik@isa.de> points out that the
+ check for makefile :: rules with commands but no dependencies
+ causing a loop terminates incorrectly.
+
+ * maintMakefile: Make a template for README.DOS to update version
+ numbers.
+
+1998-05-30 Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+
+ * remake.c (update_file_1): Don't free the memory for the
+ dependency structure when dropping a circular dependency.
+
+1998-05-30 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * dir.c (file_exists_p, file_impossible_p, file_impossible)
+ [__MSDOS__, WINDOWS32]: Retain trailing slash in "d:/", and make
+ dirname of "d:foo" be "d:".
+
+1998-05-26 Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+
+ * read.c (read_makefile): Avoid running past EOS when scanning
+ file name after `include'.
+
+1998-05-26 Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+
+ * make.texinfo (Flavors): Correct description of conditional
+ assignment, which is not equivalent to ifndef.
+ (Setting): Likewise.
+
+1998-05-24 Paul D. Smith <psmith@gnu.org>
+
+ * arscan.c (ar_name_equal): strncmp() might be implemented as a
+ macro, so don't put preprocessor conditions inside the arguments
+ list.
+
+1998-05-23 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * read.c (read_makefile) [__MSDOS__, WINDOWS32]: Skip colons in
+ drive specs when parsing targets, target-specific variables and
+ static pattern rules. A colon can only be part of drive spec if
+ it is after the first letter in a token.
+
+1998-05-22 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * remake.c (f_mtime) [__MSDOS__]: Allow up to 3 sec of skew before
+ yelling bloody murder.
+
+ * dosbuild.bat: Use -DINCLUDEDIR= and -DLIBDIR= where appropriate.
+
+ * read.c (parse_file_seq): Combine the special file-handling code
+ for WINDOWS32 and __MSDOS__ into a single snippet.
+ (get_next_mword) [__MSDOS__, WINDOWS32]: Allow a word to include a
+ colon as part of a drive spec.
+
+ * job.c (batch_mode_shell) [__MSDOS__]: Declare.
+
+1998-05-20 Paul D. Smith <psmith@gnu.org>
+
+ * Version 3.76.90 released.
+
+1998-05-19 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Make Errors): Added a new appendix describing
+ common errors make might generate and how to resolve them (or at
+ least more information on what they mean).
+
+ * maintMakefile (NMAKEFILES): Use the new automake 1.3 feature
+ to create a dependency file to construct Makefile.DOS, SMakefile,
+ and NMakefile.
+ (.dep_segment): Generate the dependency fragment file.
+
+1998-05-14 Paul D. Smith <psmith@gnu.org>
+
+ * make.man: Minor changes.
+
+1998-05-13 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (pattern_matches,expand_function): Change variables
+ and types named "word" to something else, to avoid compilation
+ problems on Cray C90 Unicos.
+ * variable.h: Modify the function prototype.
+
+1998-05-11 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * job.c (construct_command_argv_internal) [WINDOWS32]: Turn off
+ echo when using a batch file, and make sure the command ends in a
+ newline.
+
+1998-05-03 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in (make_try_customs): Add some customs flags if the
+ user configures custom support.
+
+ * job.c, remote-cstms.c: Merge in changes for custom library.
+
+ * remote-stub.c: Add option to stub start_remote_job_p().
+
+1998-05-01 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): Install VPATH+ handling for archives; use
+ the hname field instead of the name field, and rehash when
+ appropriate.
+
+1998-04-30 Paul D. Smith <psmith@gnu.org>
+
+ * rule.c (print_rule_data_base): Print out any pattern-specific
+ variable values into the rules database.
+
+ * variable.c (print_variable_set): Make this variable extern, to
+ be called by print_rule_data_base() for pattern-specific variables.
+
+ * make.texinfo (Pattern-specific): Document pattern-specific
+ variables.
+
+1998-04-29 Paul D. Smith <psmith@gnu.org>
+
+ * expand.c (variable_expand_for_file): Make static; its only
+ called internally. Look up this target in the list of
+ pattern-specific variables and insert the variable set into the
+ queue to be searched.
+
+ * filedef.h (struct file): Add a new field to hold the
+ previously-found pattern-specific variable reference. Add a new
+ flag to remember whether we already searched for this file.
+
+ * rule.h (struct pattern_var): New structure for storing
+ pattern-specific variable values. Define new function prototypes.
+
+ * rule.c: New variables pattern_vars and last_pattern_var for
+ storage and handling of pattern-specific variable values.
+ (create_pattern_var): Create a new pattern-specific variable value
+ structure.
+ (lookup_pattern_var): Try to match a target to one of the
+ pattern-specific variable values.
+
+1998-04-22 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo (Target-specific): Document target-specific
+ variables.
+
+1998-04-21 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (define_variable_in_set): Made globally visible.
+ (lookup_variable_in_set): New function: like lookup_variable but
+ look only in a specific variable set.
+ (target_environment): Use lookup_variable_in_set() to get the
+ correct export rules for a target-specific variable.
+ (create_new_variable_set): Create a new variable set, and just
+ return it without installing it anywhere.
+ (push_new_variable_scope): Reimplement in terms of
+ create_new_variable_set.
+
+ * read.c (record_target_var): Like record_files, but instead of
+ files create a target-specific variable value for each of the
+ listed targets. Invoked from read_makefile() when the target line
+ turns out to be a target-specific variable assignment.
+
+1998-04-19 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (read_makefile): Rewrite the entire target parsing
+ section to implement target-specific variables. In particular, we
+ cannot expand the entire line as soon as it's read in, since we
+ may want to evaluate parts of it with different variable contexts
+ active. Instead, start expanding from the beginning until we find
+ the `:' (or `::'), then determine what kind of line this is and
+ continue appropriately.
+
+ * read.c (get_next_mword): New function to parse a makefile line
+ by "words", considering an entire variable or function as one
+ word. Return the type read in, along with its starting position
+ and length.
+ (enum make_word_type): The types of words that are recognized by
+ get_next_mword().
+
+ * variable.h (struct variable): Add a flag to specify a per-target
+ variable.
+
+ * expand.c: Make variable_buffer global. We need this during the
+ new parsing of the makefile.
+ (variable_expand_string): New function. Like variable_expand(),
+ but start at a specific point in the buffer, not the beginning.
+ (variable_expand): Rewrite to simply call variable_expand_string().
+
+1998-04-13 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_goal_chain): Allow the rebuilding makefiles
+ step to use parallel jobs. Not sure why this was disabled:
+ hopefully we won't find out :-/.
+
+1998-04-11 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Set the CURDIR makefile variable.
+ * make.texinfo (Recursion): Document it.
+
+1998-03-17 Paul D. Smith <psmith@gnu.org>
+
+ * misc.c (makefile_fatal): If FILE is nil, invoke plain fatal().
+ * variable.c (try_variable_definition): Use new feature.
+
+1998-03-10 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Don't pass included, rebuilt makefiles to
+ re-exec'd makes with -o. Reopens a possible loop, but it caused
+ too many problems.
+
+1998-03-02 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (try_variable_definition): Implement ?=.
+ * make.texinfo (Setting): Document it.
+
+1998-02-28 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * job.c (start_job_command): Reset execute_by_shell after an empty
+ command, like ":", has been seen.
+
+Tue Oct 07 15:00:00 1997 Phil Brooks <phillip_brooks@hp.com>
+
+ * make.h [WINDOWS32]: make case sensitivity configurable
+ * dir.c [WINDOWS32]: make case sensitivity configurable
+ * README.W32: Document case sensitivity
+ * config.ami: Share case warping code with Windows
+
+Mon Oct 6 18:48:45 CDT 1997 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * w32/subproc/sub_proc.c: Added support for MKS toolkit shell
+ (turn on HAVE_MKS_SHELL).
+ * read.c [WINDOWS32]: Fixed a problem with multiple target rules
+ reported by Gilbert Catipon (gcatipon@tibco.com). If multiple
+ path tokens in a rule did not have drive letters, make would
+ incorrectly concatenate the 2 tokens together.
+ * main.c/variable.c [WINDOWS32]: changed SHELL detection code to
+ follow what MSDOS did. In addition to watching for SHELL variable
+ updates, make's main will attempt to default the value of SHELL
+ before and after makefiles are parsed.
+ * job.c/job.h [WINDOWS32]: The latest changes made to enable use
+ of the GNUWIN32 shell from make could cause make to fail due to a
+ concurrency condition between parent and child processes. Make
+ now creates a batch file per job instead of trying to reuse the
+ same singleton batch file.
+ * job.c/job.h/function.c/config.h.W32 [WINDOWS32]: Renamed macro
+ from HAVE_CYGNUS_GNUWIN32_TOOLS to BATCH_MODE_ONLY_SHELL. Reworked
+ logic to reduce complexity. WINDOWS32 now uses the unixy_shell
+ variable to detect Bourne-shell compatible environments. There is
+ also a batch_mode_shell variable that determines whether not
+ command lines should be executed via script files. A WINDOWS32
+ system with no sh.exe installed would have unixy_shell set to
+ FALSE and batch_mode_shell set to TRUE. If you have a unixy shell
+ that does not behave well when invoking things via 'sh -c xxx',
+ you may want to turn on BATCH_MODE_ONLY_SHELL and see if things
+ improve.
+ * NMakefile: Added /D DEBUG to debug build flags so that unhandled
+ exceptions could be debugged.
+
+Mon Oct 6 00:04:25 1997 Rob Tulloh <rob_tulloh@dev.tivoli.com>
+
+ * main.c [WINDOWS32]: The function define_variable() does not
+ handle NULL. Test before calling it to set Path.
+ * main.c [WINDOWS32]: Search Path again after makefiles have been
+ parsed to detect sh.exe.
+ * job.c [WINDOWS32]: Added support for Cygnus GNU WIN32 tools.
+ To use, turn on HAVE_CYGNUS_GNUWIN32_TOOLS in config.h.W32.
+ * config.h.W32: Added HAVE_CYGNUS_GNUWIN32_TOOLS macro.
+
+Sun Oct 5 22:43:59 1997 John W. Eaton <jwe@bevo.che.wisc.edu>
+
+ * glob/glob.c (glob_in_dir) [VMS]: Globbing shouldn't be
+ case-sensitive.
+ * job.c (child_execute_job) [VMS]: Use a VMS .com file if the
+ command contains a newline (e.g. from a define/enddef block).
+ * vmsify.c (vmsify): Return relative pathnames wherever possible.
+ * vmsify.c (vmsify): An input string like "../.." returns "[--]".
+
+Wed Oct 1 15:45:09 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * NMakefile: Changed nmake to $(MAKE).
+ * subproc.bat: Take the make command name from the command
+ line. If no command name was given, default to nmake.
+ * job.c [MSDOS, WINDOWS32]: Fix memory stomp: temporary file names
+ are now always created in heap memory.
+ * w32/subproc/sub_proc.c: New implementation of make_command_line()
+ which is more compatible with different Bourne shell implementations.
+ Deleted the now obsolete fix_command_line() function.
+ * main.c [WINDOWS32]: Any arbitrary spelling of Path can be
+ detected. Make will ensure that the special spelling `Path' is
+ inserted into the environment when the path variable is propagated
+ within itself and to make's children.
+ * main.c [WINDOWS32]: Detection of sh.exe was occurring too
+ soon. The 2nd check for the existence of sh.exe must come after
+ the call to read_all_makefiles().
+
+Fri Sep 26 01:14:18 1997 <zinser@axp602.gsi.de>
+
+ * makefile.com [VMS]: Fixed definition of sys.
+ * readme.vms: Comments on what's changed lately.
+
+Fri Sep 26 01:14:18 1997 John W. Eaton <jwe@bevo.che.wisc.edu>
+
+ * read.c (read_all_makefiles): Allow make to find files named
+ "MAKEFILE" with no extension on VMS.
+ * file.c (lookup_file): Lowercase filenames on VMS.
+
+1997-09-29 Paul D. Smith <psmith@baynetworks.com>
+
+ * read.c (read_makefile): Reworked target detection again; the old
+ version had an obscure quirk.
+
+Fri Sep 19 09:20:49 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.76.1 released.
+
+ * Makefile.am: Add loadavg files to clean rules.
+
+ * configure.in (AC_OUTPUT): Remove stamp-config; no longer needed.
+ * Makefile.ami (distclean): Ditto.
+ * SMakefile (distclean): Ditto.
+
+ * main.c (main): Arg count should be int, not char! Major braino.
+
+Tue Sep 16 10:18:22 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.76 released.
+
+Tue Sep 2 10:07:39 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * function.c (expand_function): When processing $(shell...)
+ translate a CRLF (\r\n) sequence as well as a newline (\n) to a
+ space. Also remove an ending \r\n sequence.
+ * make.texinfo (Shell Function): Document it.
+
+Fri Aug 29 12:59:06 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * w32/pathstuff.c (convert_Path_to_windows32): Fix problem where
+ paths which contain single character entries like `.' are not
+ handled correctly.
+
+ * README.W32: Document path handling issues on Windows systems.
+
+Fri Aug 29 02:01:27 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.75.93.
+
+Thu Aug 28 19:39:06 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * job.c (exec_command) [WINDOWS32]: If exec_command() is invoked
+ from main() to re-exec make, the call to execvp() would
+ incorrectly return control to parent shell before the exec'ed
+ command could run to completion. I believe this is a feature of
+ the way that execvp() is implemented on top of WINDOWS32 APIs. To
+ alleviate the problem, use the supplied process launch function in
+ the sub_proc library and suspend the parent process until the
+ child process has run. When the child exits, exit the parent make
+ with the exit code of the child make.
+
+Thu Aug 28 17:04:47 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Makefile.DOS.template (distdir): Fix a line that got wrapped in
+ email.
+
+ * Makefile.am (loadavg): Give the necessary cmdline options when
+ linking loadavg.
+
+ * configure.in: Check for pstat_getdynamic for getloadvg on HP.
+
+ * job.c (start_job_command) [VMS, _AMIGA]: Don't perform empty
+ command optimization on these systems; it doesn't make sense.
+
+Wed Aug 27 17:09:32 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.75.92
+
+Tue Aug 26 11:59:15 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * main.c (print_version): Add '97 to copyright years.
+
+ * read.c (do_define): Check the length of the array before looking
+ at a particular offset.
+
+ * job.c (construct_command_argv_internal): Examine the last byte
+ of the previous arg, not the byte after that.
+
+Sat Aug 23 1997 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS.template: New file (converted to Makefile.DOS in
+ the distribution).
+
+ * configure.bat: Rewrite to use Makefile.DOS instead of editing
+ Makefile.in. Add support for building from outside of the source
+ directory. Fail if the environment block is too small.
+
+ * configh.dos: Use <sys/config.h>.
+
+ * README.DOS: Update instructions.
+
+Fri Aug 22 1997 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * job.c (start_job_command) [__MSDOS__]: Don't test for "/bin/sh"
+ literally, use value of unixy_shell instead.
+
+ * filedef.h (NEW_MTIME): Use 1 less than maximum possible value if
+ time_t is unsigned.
+
+Sat Aug 16 00:56:15 1997 John W. Eaton <jwe@bevo.che.wisc.edu>
+
+ * vmsify.c (vmsify, case 11): After translating `..' elements, set
+ nstate to N_OPEN if there are still more elements to process.
+ (vmsify, case 2): After translating `foo/bar' up to the slash,
+ set nstate to N_OPEN, not N_DOT.
+
+Fri Aug 8 15:18:09 1997 John W. Eaton <jwe@bevo.che.wisc.edu>
+
+ * dir.c (vmsstat_dir): Leave name unmodified on exit.
+ * make.h (PATH_SEPARATOR_CHAR): Set to comma for VMS.
+ * vpath.c: Fix comments to refer to path separator, not colon.
+ (selective_vpath_search): Avoid Unixy slash handling for VMS.
+
+Thu Aug 7 22:24:03 1997 John W. Eaton <jwe@bevo.che.wisc.edu>
+
+ * ar.c [VMS]: Don't declare ar_member_touch.
+ Delete VMS version of ar_member_date.
+ Enable non-VMS versions of ar_member_date and ar_member_date_1 for
+ VMS too.
+ * arscan.c (VMS_get_member_info): New function.
+ (ar_scan): Provide version for VMS systems.
+ (ar_name_equal): Simply compare name and mem on VMS systems.
+ Don't define ar_member_pos or ar_member_touch on VMS systems.
+
+ * config.h-vms (pid_t, uid_t): Don't define.
+
+ * remake.c: Delete declaration of vms_stat.
+ (name_mtime): Don't call vms_stat.
+ (f_mtime) [VMS]: Funky time value manipulation no longer necessary.
+
+ * file.c (print_file): [VMS] Use ctime, not cvt_time.
+
+ * make.h [VMS]: Don't define POSIX.
+
+ * makefile.com (filelist): Include ar and arscan.
+ Also include them in the link commands.
+ Don't define NO_ARCHIVES in cc command.
+
+ * makefile.vms (ARCHIVES, ARCHIVES_SRC): Uncomment.
+ (defines): Delete NO_ARCHIVES from list.
+
+ * remake.c (f_mtime): Only check to see if intermediate file is
+ out of date if it also exists (i.e., mtime != (time_t) -1).
+
+ * vmsdir.h (u_long, u_short): Skip typedefs if using DEC C.
+
+Fri Jun 20 23:02:07 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * w32/subproc/sub_proc.c: Get W32 sub_proc to handle shebang
+ (#!/bin/sh) in script files correctly.
+ Fixed a couple of memory leaks.
+ Fixed search order in find_file() (w32/subproc/sub_proc.c) so that
+ files with extensions are preferred over files without extensions.
+ Added search for files with .cmd extension too.
+ * w32/subproc/misc.c (arr2envblk): Fixed memory leak.
+
+Mon Aug 18 09:41:08 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.75.91
+
+Fri Aug 15 13:50:54 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * read.c (do_define): Remember to count the newline after the endef.
+
+Thu Aug 14 23:14:37 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * many: Rewrote builds to use Automake 1.2.
+
+ * AUTHORS: New file.
+ * maintMakefile: Contains maintainer-only make snippets.
+ * GNUmakefile: This now only runs the initial auto* tools.
+ * COPYING,texinfo.tex,mkinstalldirs,install-sh: Removed (obtained
+ automatically by automake).
+ * compatMakefile: Removed (not needed anymore).
+ * README,build.sh.in: Removed (built from templates).
+ * config.h.in,Makefile.in: Removed (built by tools).
+
+Wed Aug 13 02:22:08 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * make.texinfo: Updates for DOS/Windows information (Eli Zaretskii)
+ * README,README.DOS: Ditto.
+
+ * remake.c (update_file_1,f_mtime): Fix GPATH handling.
+ * vpath.c (gpath_search): Ditto.
+
+ * file.c (rename_file): New function: rehash, but also rename to
+ the hashname.
+ * filedef.h: Declare it.
+
+ * variable.c (merge_variable_set_lists): Remove free() of variable
+ set; since various files can share variable sets we don't want to
+ free them here.
+
+Tue Aug 12 10:51:54 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * configure.in: Require autoconf 2.12
+
+ * make.texinfo: Replace all "cd subdir; $(MAKE)" examples with a
+ more stylistically correct "cd subdir && $(MAKE)".
+
+ * main.c: Global variable `clock_skew_detected' defined.
+ (main): Print final warning if it's set.
+ * make.h: Declare it.
+ * remake.c (f_mtime): Test and set it.
+
+ * job.c (start_job_command): Add special optimizations for
+ "do-nothing" rules, containing just the shell no-op ":". This is
+ useful for timestamp files and can make a real difference if you
+ have a lot of them (requested by Fergus Henderson <fjh@cs.mu.oz.au>).
+
+ * configure.in,Makefile.in: Rewrote to use the new autoconf
+ program_transform_name macro.
+
+ * function.c (function_strip): Strip newlines as well as spaces
+ and TABs.
+
+Fri Jun 6 23:41:04 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * remake.c (f_mtime): Datestamps on FAT-based files are rounded to
+ even seconds when stored, so if the date check fails on WINDOWS32
+ systems, see if this "off-by-one" error is the problem.
+
+ * General: If your TZ environment variable is not set correctly
+ then all your timestamps will be off by hours. So, set it!
+
+Mon Apr 7 02:06:22 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * Version 3.75.1
+
+ * compatMakefile (objs): Define & use the $(GLOB) variable so
+ that it's removed correctly from build.sh.in when it's built.
+
+ * configure.in: On Solaris we can use the kstat_*() functions to
+ get load averages without needing special permissions. Add a
+ check for -lkstat to see if we have it.
+
+ * getloadavg.c (getloadavg): Use HAVE_LIBKSTAT instead of SUN5 as
+ the test to enable kstat_open(), etc. processing.
+
+Fri Apr 4 20:21:18 1997 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * <lots>: Fixes to work in the DJGPP DOS environment.
+
+Mon Mar 31 02:42:52 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * function.c (expand_function): Added new function $(wordlist).
+
+ * make.texinfo (Filename Functions): Document $(wordlist) function.
+
+ * vpath.c (build_vpath_lists): Construct the GPATH variable
+ information in the same manner we used to construct VPATH.
+ (gpath_search): New function to search GPATH.
+
+ * make.h: Declare the new function.
+
+ * remake.c (update_file_1): Call it, and keep VPATH if it's found.
+
+ * make.texinfo (Search Algorithm): Document GPATH variable.
+
+Sun Mar 30 20:57:16 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * main.c (handle_non_switch_argument): Defined the MAKECMDGOALS
+ variable to contain the user options passed in on the cmd line.
+
+ * make.texinfo (Goals): Document MAKECMDGOALS variable.
+
+ * remake.c (f_mtime): Print a warning if we detect a clock skew
+ error, rather than failing.
+
+ * main.c (main): If we rebuild any makefiles and need to re-exec,
+ add "-o<mkfile>" options for each makefile rebuilt to avoid
+ infinite looping.
+
+Fri Mar 28 15:26:05 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * job.c (construct_command_argv_internal): Track whether the last
+ arg in the cmd string was empty or not (Roland).
+ (construct_command_argv_internal): If the shell line is empty,
+ don't do anything (Roland).
+
+ * glob/glob.h,glob/glob.c,glob/fnmatch.c,glob/fnmatch.h: Install
+ the latest changes from the GLIBC version of glob (Ulrich Drepper).
+
+ * getloadavg.c,make-stds.texi: New version (Roland).
+
+ * (ALL): Changed WIN32 to W32 or WINDOWS32 (RMS).
+
+Mon Mar 24 15:33:34 1997 Rob Tulloh <rob_tulloh@tivoli.com>
+
+ * README.W32: Describe preliminary FAT support.
+
+ * build_w32.bat: Use a variable for the final exe name.
+
+ * dir.c (find_directory): W32: Find the filesystem type.
+ (dir_contents_file_exists_p): W32: for FAT filesystems, always
+ rehash since FAT doesn't change directory mtime on change.
+
+ * main.c (handle_runtime_exceptions): W32: Add an
+ UnhandledExceptionFilter so that when make bombs due to ^C or a
+ bug, it won't cause a GUI requestor to pop up unless debug is
+ turned on.
+ (main): Call it.
+
+Mon Mar 24 00:57:34 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * configure.in, config.h.in, config.ami, config.h-vms, config.h.w32:
+ Check for memmove() function.
+
+ * make.h (bcopy): If memmove() available, define bcopy() to use it.
+ Otherwise just use bcopy(). Don't use memcpy(); it's not guaranteed
+ to handle overlapping moves.
+
+ * read.c (read_makefile): Fix some uninitialized memory reads
+ (reported by Purify).
+
+ * job.c (construct_command_argv_internal): Use bcopy() not
+ strcpy(); strcpy() isn't guaranteed to handle overlapping moves.
+
+ * Makefile.in: Change install-info option ``--infodir'' to
+ ``--info-dir'' for use with new texinfo.
+
+ * function.c (expand_function): $(basename) and $(suffix) should
+ only search for suffixes as far back as the last directory (e.g.,
+ only the final filename in the path).
+
+Sun Mar 23 00:13:05 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * make.texinfo: Add @dircategory/@direntry information.
+ (Top): Remove previous reference to (dir) (from RMS).
+ (Static Usage): Add "all:" rule to example.
+ (Automatic Dependencies): fix .d file creation example.
+
+ * Install VPATH+ patch:
+
+ * filedef.h (struct file): Add in hname field to store the hashed
+ filename, and a flag to remember if we're using the vpath filename
+ or not. Renamed a few functions for more clarity.
+
+ * file.c (lookup_file,enter_file,file_hash_enter): Store filenames
+ in the hash table based on their "hash name". We can change this
+ while keeping the original target in "name".
+ (rehash_file): Renamed from "rename_file" to be more accurate.
+ Changes the hash name, but not the target name.
+
+ * remake.c (update_file_1): Modify -d output for more detailed
+ VPATH info. If we don't need to rebuild, use the VPATH name.
+ (f_mtime): Don't search for vpath if we're ignoring it. Call
+ renamed function rehash_file. Call name_mtime instead of
+ file_mtime, to avoid infinite recursion since the file wasn't
+ actually renamed.
+
+ * implicit.c (pattern_search): if we find an implicit file in
+ VPATH, save the original name not the VPATH name.
+
+ * make.texinfo (Directory Search): Add a section on the new VPATH
+ functionality.
+
+Sun Dec 1 18:36:04 1996 Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+
+ * dir.c (file_exists_p, file_impossible, file_impossible_p): If
+ dirname is empty replace it by the name of the root directory.
+ Note that this doesn't work (yet) for W32, Amiga, or VMS.
+
+Tue Oct 08 13:57:03 1996 Rob Tulloh <tulloh@tivoli.com>
+
+ * main.c (main): W32 bug fix for PATH vars.
+
+Tue Sep 17 1996 Paul Eggert <eggert@twinsun.com>
+
+ * filedef.h (NEW_MTIME): Don't assume that time_t is a signed
+ 32-bit quantity.
+
+ * make.h: (CHAR_BIT, INTEGER_TYPE_SIGNED, INTEGER_TYPE_MAXIMUM,
+ INTEGER_TYPE_MINIMUM): New macros.
+
+Tue Aug 27 01:06:34 1996 Roland McGrath <roland@baalperazim.frob.com>
+
+ * Version 3.75 released.
+
+ * main.c (print_version): Print out bug-reporting address.
+
+Mon Aug 26 19:55:47 1996 Roland McGrath <roland@baalperazim.frob.com>
+
+ * main.c (print_data_base): Don't declare ctime; headers do it for us
+ already.
+
+Sun Jul 28 15:37:09 1996 Rob Tulloh (tulloh@tivoli.com)
+
+ * w32/pathstuff.c: Turned convert_vpath_to_w32() into a
+ real function. This was done so that VPATH could contain
+ white space separated pathnames. Please note that directory
+ paths (in VPATH/vpath context) containing white space are not
+ supported (just as they are not under Unix). See README.W32
+ for suggestions.
+
+ * w32/include/pathstuff.h: Added prototype for the new
+ function convert_vpath_to_w32. Deleted macro for same.
+
+ * README.W32: Added some notes about why I chose not to try
+ and support pathnames which contain white space and some
+ workaround suggestions.
+
+Thu Jul 25 19:53:31 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * GNUmakefile (mkdep-nolib): Use -MM option unconditionally.
+
+ * Version 3.74.7.
+
+ * main.c (define_makeflags): Back up P to point at null terminator
+ when killing final space and dash before setting MFLAGS.
+
+ From Robert Hoehne <robert.hoehne@Mathematik.TU-Chemnitz.DE>:
+ * dir.c [__MSDOS__ && DJGPP > 1]: Include <libc/dosio.h> and defin
+ `__opendir_flags' initialized to 0.
+ (dosify) [__MSDOS__ && DJGPP > 1]: Return name unchanged if _USE_LFN.
+ (find_directory) [__MSDOS__ && DJGPP > 1]: If _USE_LGN, set
+ __opendir_flags to __OPENDIR_PRESERVE_CASE.
+
+ * vmsfunctions.c (vms_stat): `sys$dassgn (DevChan);' added by kkaempf.
+
+ * GNUmakefile (w32files): Add NMakefile.
+
+ * NMakefile (LDFLAGS_debug): Value fixed by tulloh.
+
+Sat Jul 20 12:32:10 1996 Klaus Kämpf (kkaempf@progis.de)
+
+ * remake.c (f_mtime) [VMS]: Add missing `if' conditional for future
+ modtime check.
+ * config.h-vms, makefile.vms, readme.vms, vmsify.c: Update address.
+
+Sat Jul 20 05:29:43 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * configure.in: Require autoconf 2.10 or later.
+
+Fri Jul 19 16:57:27 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * Version 3.74.6.
+
+ * GNUmakefile (w32files): New variable.
+ (distfiles): Add it.
+ * w32: Updated by Rob Tulloh.
+
+ * makefile.vms (LOADLIBES): Fix typo.
+
+Sun Jul 14 12:59:27 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * job.c (construct_command_argv_internal): Fix up #else, #endifs.
+
+ * configh.dos: Define HAVE_DIRENT_H instead of DIRENT.
+
+ * remake.c (f_mtime): Don't compare MTIME to NOW if MTIME == -1.
+
+ * Version 3.74.5.
+
+ * main.c (main): Exit with status 2 when update_goal_chain returns 2.
+
+Sat Jun 22 14:56:05 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * configure.in: Don't check for _sys_siglist.
+ * make.h [HAVE__SYS_SIGLIST]: Don't test this; just punt if there is
+ no strsignal or sys_siglist.
+
+ * read.c (conditional_line): Strip ws in `ifeq (a , b)' so it is the
+ same as `ifeq (a, b)'.
+
+ * job.c (reap_children): Don't call die if handling_fatal_signal.
+
+ * file.c (file_hash_enter): Allow renaming :: to : when latter is
+ non-target, or : to :: when former is non-target.
+
+ * job.c (start_job_command): Call block_sigs.
+ (block_sigs): New function, broken out of start_job_command.
+ (reap_children): Block fatal signals around removing dead child from
+ chain and adjusting job_slots_used.
+ * job.h: Declare block_sigs.
+
+ * remote-stub.c (remote_setup, remote_cleanup): New (empty) functions.
+ * main.c (main): Call remote_setup.
+ (die): Call remote_cleanup.
+
+ * job.c (reap_children): Quiescent value of shell_function_pid is
+ zero, not -1.
+
+ * main.c (print_version): Add 96 to copyright years.
+
+Sat Jun 15 20:30:01 1996 Andreas Schwab <schwab@issan.informatik.uni-dortmund.de>
+
+ * read.c (find_char_unquote): Avoid calling strlen on every call
+ just to throw away the value most of the time.
+
+Sun Jun 2 12:24:01 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * main.c (decode_env_switches): Prepend '-' to ARGV[1] if it contains
+ no '=', regardless of ARGC.
+ (define_makeflags): Elide leading '-' from MAKEFLAGS value if first
+ word is short option, regardless of WORDS.
+
+Wed May 22 17:24:51 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * makefile.vms: Set LOADLIBES.
+ * makefile.com (link_using_library): Fix typo.
+
+Wed May 15 17:37:26 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * dir.c (print_dir_data_base): Use %ld dev and ino and cast them to
+ long.
+
+Wed May 15 10:14:14 CDT 1996 Rob Tulloh <tulloh@tivoli.com>
+
+ * dir.c: W32 does not support inode. For now, fully qualified
+ pathname along with st_mtime will be keys for files.
+ Fixed problem where vpath can be confused when files
+ are added to a directory after the directory has already been
+ read in. The code now attempts to reread the directory if it
+ discovers that the datestamp on the directory has changed since
+ it was cached by make. This problem only seems to occur on W32
+ right now so it is lumped under port #ifdef WINDOWS32.
+
+ * function.c: W32: call subproc library (CreateProcess()) instead of
+ fork/exec.
+
+ * job.c: W32: Added the code to do fork/exec/waitpid style processing
+ on W32 systems via calls to subproc library.
+
+ * main.c: W32: Several things added here. First, there is code
+ for dealing with PATH and SHELL defaults. Make tries to figure
+ out if the user has %PATH% set in the environment and sets it to
+ %Path% if it is not set already. Make also looks to see if sh.exe
+ is anywhere to be found. Code path through job.c will change
+ based on existence of a working Bourne shell. The checking for
+ default shell is done twice: once before makefiles are read in
+ and again after. Fall back to MSDOS style execution mode if no sh.exe
+ is found. Also added some debug support that allows user to pause make
+ with -D switch and attach a debugger. This is especially useful for
+ debugging recursive calls to make where problems appear only in the
+ sub-make.
+
+ * make.h: W32: A few macros and header files for W32 support.
+
+ * misc.c: W32: Added a function end_of_token_w32() to assist
+ in parsing code in read.c.
+
+ * read.c: W32: Fixes similar to MSDOS which allow colon to
+ appear in filenames. Use of colon in filenames would otherwise
+ confuse make.
+
+ * remake.c: W32: Added include of io.h to eliminate compiler
+ warnings. Added some code to default LIBDIR if it is not set
+ on W32.
+
+ * variable.c: W32: Added support for detecting Path/PATH
+ and converting them to semicolon separated lists for make's
+ internal use. New function sync_Path_environment()
+ which is called in job.c and function.c before creating a new
+ process. Caller must set Path in environment since we don't
+ have fork() to do this for us.
+
+ * vpath.c: W32: Added detection for filenames containing
+ forward or backward slashes.
+
+ * NMakefile: W32: Visual C compatible makefile for use with nmake.
+ Use this to build GNU make the first time on Windows NT or Windows 95.
+
+ * README.W32: W32: Contains some helpful notes.
+
+ * build_w32.bat: W32: If you don't like nmake, use this the first
+ time you build GNU make on Windows NT or Windows 95.
+
+ * config.h.W32: W32 version of config.h
+
+ * subproc.bat: W32: A bat file used to build the
+ subproc library from the top-level NMakefile. Needed because
+ WIndows 95 (nmake) doesn't allow you to cd in a make rule.
+
+ * w32/include/dirent.h
+ * w32/compat/dirent.c: W32: opendir, readdir, closedir, etc.
+
+ * w32/include/pathstuff.h: W32: used by files needed functions
+ defined in pathstuff.c (prototypes).
+
+ * w32/include/sub_proc.h: W32: prototypes for subproc.lib functions.
+
+ * w32/include/w32err.h: W32: prototypes for w32err.c.
+
+ * w32/pathstuff.c: W32: File and Path/Path conversion functions.
+
+ * w32/subproc/build.bat: W32: build script for subproc library
+ if you don't wish to use nmake.
+
+ * w32/subproc/NMakefile: W32: Visual C compatible makefile for use
+ with nmake. Used to build subproc library.
+
+ * w32/subproc/misc.c: W32: subproc library support code
+ * w32/subproc/proc.h: W32: subproc library support code
+ * w32/subproc/sub_proc.c: W32: subproc library source code
+ * w32/subproc/w32err.c: W32: subproc library support code
+
+Mon May 13 14:37:42 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * Version 3.74.4.
+
+ * GNUmakefile (vmsfiles): Fix typo.
+
+ * GNUmakefile (amigafiles): Add amiga.h.
+
+Sun May 12 19:19:43 1996 Aaron Digulla <digulla@fh-konstanz.de>
+
+ * dir.c: New function: amigafy() to fold filenames
+ Changes HASH() to HASHI() to fold filenames on Amiga.
+ Stringcompares use strieq() instead of streq()
+ The current directory on Amiga is "" instead of "."
+ * file.c: Likewise.
+
+ * amiga.c: New function wildcard_expansion(). Allows to use
+ Amiga wildcards with $(wildcard )
+
+ * amiga.h: New file. Prototypes for amiga.c
+
+ * function.c: Use special function wildcard_expansion() for
+ $(wildcard ) to allow Amiga wildcards
+ The current directory on Amiga is "" instead of "."
+
+ * job.c: No Pipes on Amiga, too
+ (load_too_high) Neither on Amiga
+ ENV variable on Amiga are in a special directory and are not
+ passed as third argument to main().
+
+ * job.h: No envp on Amiga
+
+ * make.h: Added HASHI(). This is the same as HASH() but converts
+ it's second parameter to lowercase on Amiga to fold filenames.
+
+ * main.c: (main), variable.c Changed handling of ENV-vars. Make
+ stores now the names of the variables only and reads their contents
+ when they are accessed to reflect that these variables are really
+ global (i.e., they CAN change WHILE make runs !) This handling is
+ made in lookup_variable()
+
+ * Makefile.ami: renamed file.h to filedep.h
+ Updated dependencies
+
+ * read.c: "find_semicolon" is declared as static but never defined.
+ No difference between Makefile and makefile on Amiga; added
+ SMakefile to *default_makefiles[].
+ (read_makefile) SAS/C want's two_colon and pattern_percent be set
+ before use.
+ The current directory on Amiga is "" instead of "."
+ Strange #endif moved.
+
+ * README.Amiga: updated feature list
+
+ * SMakefile: Updated dependencies
+
+ * variable.c: Handling of ENV variable happens inside lookup_variable()
+
+Sat May 11 17:58:32 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * variable.c (try_variable_definition): Count parens in lhs variable
+ refs to avoid seeing =/:=/+= inside a ref.
+
+Thu May 9 13:54:49 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * commands.c (fatal_error_signal) [SIGQUIT]: Make SIGQUIT check
+ conditional.
+
+ * main.c (main): Use unsigned for fread return.
+
+ * read.c (parse_file_seq): Use `int' for char arg to avoid widening
+ conflict issues.
+ * dep.h: Fix prototype.
+
+ * function.c (expand_function) [_AMIGA]: Fix some typos.
+ (patsubst_expand): Make len vars unsigned.
+
+ * GNUmakefile (globfiles): Add AmigaDOS support files.
+ (distfiles): Add $(amigafiles).
+ (amigafiles): New variable.
+
+Thu Nov 7 10:18:16 1995 Aaron Digulla <digulla@fh-konstanz.de>
+
+ * Added Amiga support in commands.c, dir.c, function.c,
+ job.c, main.c, make.h, read.c, remake.c
+ * commands.c: Amiga has neither SIGHUP nor SIGQUIT
+ * dir.c: Amiga has filenames with Upper- and Lowercase,
+ but "FileName" is the same as "filename". Added strieq()
+ which is use to compare filenames. This is like streq()
+ on all other systems. Also there is no such thing as
+ "." under AmigaDOS.
+ * function.c: On Amiga, the environment is not passed as envp,
+ there are no pipes and Amiga can't fork. Use my own function
+ to create a new child.
+ * job.c: default_shell is "" (The system automatically chooses
+ a shell for me). Have to use the same workaround as MSDOS for
+ running batch commands. Added HAVE_SYS_PARAM_H. NOFILE isn't
+ known on Amiga. Cloned code to run children from MSDOS. Own
+ version of sh_chars[] and sh_cmds[]. No dup2() or dup() on Amiga.
+ * main.c: Force stack to 20000 bytes. Read environment from ENV:
+ device. On Amiga, exec_command() does return, so I exit()
+ afterwards.
+ * make.h: Added strieq() to compare filenames.
+ * read.c: Amiga needs special extension to have passwd. Only
+ one include-dir. "Makefile" and "makefile" are the same.
+ Added "SMakefile". Added special code to handle device names (xxx:)
+ and "./" in rules.
+ * remake.c: Only one lib-dir. Amiga link-libs are named "%s.lib"
+ instead of "lib%s.a".
+ * main.c, rule.c, variable.c: Avoid floats at all costs.
+ * vpath.c: Get rid of as many alloca()s as possible.
+
+Thu May 9 13:20:43 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * read.c (read_makefile): Grok `sinclude' as alias for `-include'.
+
+Wed Mar 20 09:52:27 1996 Roland McGrath <roland@charlie-brown.gnu.ai.mit.edu>
+
+ * GNUmakefile (vmsfiles): New variable.
+ (distfiles): Include $(vmsfiles).
+
+Tue Mar 19 20:21:34 1996 Roland McGrath <roland@charlie-brown.gnu.ai.mit.edu>
+
+ Merged VMS port from Klaus Kaempf <kkaempf@didymus.rmi.de>.
+ * make.h (PARAMS): New macro.
+ * config.h-vms: New file.
+ * makefile.com: New file.
+ * makefile.vms: New file.
+ * readme.vms: New file.
+ * vmsdir.h: New file.
+ * vmsfunctions.c: New file.
+ * vmsify.c: New file.
+ * file.h: Renamed to filedef.h to avoid conflict with VMS system hdr.
+ * ar.c: Added prototypes and changes for VMS.
+ * commands.c: Likewise.
+ * commands.h: Likewise.
+ * default.c: Likewise.
+ * dep.h: Likewise.
+ * dir.c: Likewise.
+ * expand.c: Likewise.
+ * file.c: Likewise.
+ * function.c: Likewise.
+ * implicit.c: Likewise.
+ * job.c: Likewise.
+ * job.h: Likewise.
+ * main.c: Likewise.
+ * make.h: Likewise.
+ * misc.c: Likewise.
+ * read.c: Likewise.
+ * remake.c: Likewise.
+ * remote-stub.c: Likewise.
+ * rule.c: Likewise.
+ * rule.h: Likewise.
+ * variable.c: Likewise.
+ * variable.h: Likewise.
+ * vpath.c: Likewise.
+ * compatMakefile (srcs): Rename file.h to filedef.h.
+
+Sat Aug 19 23:11:00 1995 Richard Stallman <rms@mole.gnu.ai.mit.edu>
+
+ * remake.c (check_dep): For a secondary file, try implicit and
+ default rules if appropriate.
+
+Wed Aug 2 04:29:42 1995 Richard Stallman <rms@mole.gnu.ai.mit.edu>
+
+ * remake.c (check_dep): If an intermediate file exists,
+ do consider its actual date.
+
+Sun Jul 30 00:49:53 1995 Richard Stallman <rms@mole.gnu.ai.mit.edu>
+
+ * file.h (struct file): New field `secondary'.
+ * file.c (snap_deps): Check for .INTERMEDIATE and .SECONDARY.
+ (remove_intermediates): Don't delete .SECONDARY files.
+
+Sat Mar 2 16:26:52 1996 Roland McGrath <roland@charlie-brown.gnu.ai.mit.edu>
+
+ * compatMakefile (srcs): Add getopt.h; prepend $(srcdir)/ to getopt*.
+
+Fri Mar 1 12:04:47 1996 Roland McGrath <roland@charlie-brown.gnu.ai.mit.edu>
+
+ * Version 3.74.3.
+
+ * remake.c (f_mtime): Move future modtime check before FILE is
+ clobbered by :: loop.
+
+ * dir.c: Use canonical code from autoconf manual for dirent include.
+ [_D_NAMLEN]: Redefine NAMLEN using this.
+ (dir_contents_file_exists_p): Use NAMLEN macro.
+ (read_dirstream) [_DIRENT_HAVE_D_NAMLEN]: Only set d_namlen #if this.
+
+ * compatMakefile (objs): Add missing backslash.
+
+Wed Feb 28 03:56:20 1996 Roland McGrath <roland@charlie-brown.gnu.ai.mit.edu>
+
+ * default.c (default_terminal_rules): Remove + prefix from RCS cmds.
+ (default_variables): Put + prefix in $(CHECKOUT,v) value instead.
+
+ * remake.c (f_mtime): Check for future timestamps; give error and mark
+ file as "failed to update".
+
+Fri Jan 12 18:09:36 1996 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c: Don't declare unblock_sigs; job.h already does.
+
+Sat Jan 6 16:24:44 1996 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * acconfig.h (HAVE_SYSCONF_OPEN_MAX): #undef removed.
+
+ * job.c (NGROUPS_MAX): Don't try to define this macro.
+
+Fri Dec 22 18:44:44 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * compatMakefile (GETOPT, GETOPT_SRC, GLOB): Variables removed.
+ (objs, srcs): Include their values here instead of references.
+
+Thu Dec 14 06:21:29 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.74.2.
+
+ * job.c (reap_children): Call unblock_sigs after start_job_command.
+
+Thu Dec 14 07:22:03 1995 Roland McGrath <roland@duality.gnu.ai.mit.edu>
+
+ * dir.c (dir_setup_glob): Don't use lstat; glob never calls it anyway.
+ Avoid & before function names to silence bogus sunos4 compiler.
+
+ * configure.in: Remove check for `sysconf (_SC_OPEN_MAX)'.
+
+Tue Dec 12 00:48:42 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.74.1.
+
+ * dir.c (read_dirstream): Fix braino: fill in the buffer when not
+ reallocating it!
+
+Mon Dec 11 22:26:15 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * misc.c (collapse_continuations): Fix skipping of trailing \s so
+ it can never dereference before the beginning of the array.
+
+ * read.c (find_semicolon): Function removed.
+ (read_makefile): Don't use find_semicolon or remove_comments for
+ rule lines. Use find_char_unquote directly and handle quoted comments
+ properly.
+
+ * default.c: Remove all [M_XENIX] code.
+
+ * dir.c [HAVE_D_NAMLEN]: Define this for __GNU_LIBRARY__ > 1.
+ (D_NAMLEN): Macro removed.
+ (FAKE_DIR_ENTRY): New macro.
+ (dir_contents_file_exists_p): Test HAVE_D_NAMLEN instead of using
+ D_NAMLEN.
+ (read_dirstream): Return a struct dirent * for new glob interface.
+ (init_dir): Function removed.
+ (dir_setup_glob): New function.
+ * main.c (main): Don't call init_dir.
+ * read.c (multi_glob): Call dir_setup_glob on our glob_t and use
+ GLOB_ALTDIRFUNC flag.
+
+ * misc.c (safe_stat): Function removed.
+ * read.c, commands.c, remake.c, vpath.c: Use plain stat instead of
+ safe_stat.
+
+Sat Nov 25 20:35:18 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c [HAVE_UNION_WAIT]: Include sys/wait.h.
+
+ * main.c (log_working_directory): Made global.
+ Print entering msg only once.
+ * make.h (log_working_directory): Declare it.
+ * misc.c (message): Take new arg PREFIX. Print "make: " only if
+ nonzero. Call log_working_directory.
+ * remake.c: Pass new arg in `message' calls.
+ * job.c (start_job_command): Pass new arg to `message'; fix
+ inverted test in that call.
+
+Tue Nov 21 19:01:12 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c (start_job_command): Use `message' to print the command,
+ and call it with null if the command is silent.
+ * remake.c (touch_file): Use message instead of printf.
+
+Tue Oct 10 14:59:30 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (enter_command_line_file): Barf if NAME is "".
+
+Sat Sep 9 06:33:20 1995 Roland McGrath <roland@whiz-bang.gnu.ai.mit.edu>
+
+ * commands.c (delete_target): Ignore unlink failure if it is ENOENT.
+
+Thu Aug 17 15:08:57 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * configure.in: Don't check for getdtablesize.
+ * job.c (getdtablesize): Remove decls and macros.
+
+Thu Aug 10 19:10:03 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (define_makeflags): Omit command line variable
+ definitions from MFLAGS value.
+
+ * arscan.c (ar_scan) [AIAMAG]: Check for zero MEMBER_OFFSET,
+ indicating a valid, but empty, archive.
+
+Mon Aug 7 15:40:03 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * dir.c (file_impossible_p): Correctly reset FILENAME to name
+ within directory before hash search.
+
+ * job.c (child_error): Do nothing if IGNORED under -s.
+
+ * job.c (exec_command): Correctly use ARGV[0] for script name when
+ running shell directly.
+
+Tue Aug 1 14:39:14 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c (child_execute_job): Close STDIN_FD and STDOUT_FD after
+ dup'ing from them. Don't try to close all excess descriptors;
+ getdtablesize might return a huge value. Any open descriptors in
+ the parent should have FD_CLOEXEC set.
+ (start_job_command): Set FD_CLOEXEC flag on BAD_STDIN descriptor.
+
+Tue Jun 20 03:47:15 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * read.c (read_all_makefiles): Properly append default makefiles
+ to the end of the `read_makefiles' chain.
+
+Fri May 19 16:36:32 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.74 released.
+
+Wed May 10 17:43:34 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.73.3.
+
+Tue May 9 17:15:23 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * compatMakefile ($(infodir)/make.info): Make sure $$dir is set in
+ install-info cmd.
+
+Wed May 3 15:56:06 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * file.c (print_file): Grok update_status of 1 for -q.
+
+Thu Apr 27 12:39:35 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.73.2.
+
+Wed Apr 26 17:15:57 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * file.c (remove_intermediates): Fix inverted test to bail under
+ -n for signal case. Bail under -q or -t.
+ Skip files with update_status==-1.
+
+ * job.c (job_next_command): Skip empty lines.
+ (new_job): Don't test the return of job_next_command.
+ Just let start_waiting_job handle the case of empty commands.
+
+Wed Apr 19 03:25:54 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * function.c [__MSDOS__]: Include <fcntl.h>. From DJ Delorie.
+
+ * Version 3.73.1.
+
+Sat Apr 8 14:53:24 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * remake.c (notice_finished_file): Set FILE->update_status to zero
+ if it's -1.
+
+Wed Apr 5 00:20:24 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.73 released.
+
+Tue Mar 28 13:25:46 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (main): Fixed braino in assert.
+
+ * Version 3.72.13.
+
+Mon Mar 27 05:29:12 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c: Avoid string in assert expression. Some systems are broken.
+
+Fri Mar 24 00:32:32 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (main): Handle 1 and 2 returns from update_goal_chain
+ makefile run properly.
+
+ * Version 3.72.12.
+
+ * main.c (handle_non_switch_argument): New function, broken out of
+ decode_switches.
+ (decode_switches): Set optind to 0 to reinitialize getopt, not to 1.
+ When getopt_long returns EOF, break the loop and handle remaining args
+ with a simple second loop.
+
+ * remake.c (remake_file): Set update_status to 2 instead of 1 for
+ no rule to make. Mention parent (dependent) in error message.
+ (update_file_1): Handle FILE->update_status == 2 in -d printout.
+ * job.c (start_job_command, reap_children): Set update_status to 2
+ instead of 1 for failed commands.
+
+Tue Mar 21 16:23:38 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c (search_path): Function removed (was already #if 0'd out).
+ * configure.in: Remove AC_TYPE_GETGROUPS; nothing needs it any more.
+
+Fri Mar 17 15:57:40 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * configure.bat: Write @CPPFLAGS@ translation.
+
+Mon Mar 13 00:45:59 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * read.c (parse_file_seq): Rearranged `l(a b)' -> `l(a) l(b)' loop
+ to not skip the elt immediately preceding `l(...'.
+
+Fri Mar 10 13:56:49 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.11.
+
+ * read.c (find_char_unquote): Make second arg a string of stop
+ chars instead of a single stop char. Stop when any char in the
+ string is hit. All callers changed.
+ (find_semicolon): Pass stop chars "#;" to one find_char_unquote call,
+ instead of using two calls. If the match is not a ; but a #,
+ return zero.
+ * misc.c: Changed find_char_unquote callers here too.
+
+ * Version 3.72.10.
+
+ * read.c (read_makefile, parse_file_seq): Fix typo __MS_DOS__ ->
+ __MSDOS__.
+
+ * GNUmakefile (globfiles): Add glob/configure.bat.
+ (distfiles): Add configh.dos, configure.bat.
+
+Wed Mar 8 13:10:57 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ Fixes for MS-DOS from DJ Delorie.
+ * read.c (read_makefile, parse_file_seq) [__MS_DOS__]: Don't see :
+ as separator in "C:\...".
+ * configh.dos (STDC_HEADERS): Define only if undefined.
+ (HAVE_SYS_PARAM_H): Don't define this.
+ (HAVE_STRERROR): Define this.
+ * job.c (construct_command_argv_internal) [__MSDOS__]: Fix typos.
+
+ * Version 3.72.9.
+
+ * main.c (decode_switches): Reset optind to 1 instead of 0.
+
+Tue Mar 7 17:31:06 1995 Roland McGrath <roland@geech.gnu.ai.mit.edu>
+
+ * main.c (decode_switches): If non-option arg is "-", ignore it.
+
+Mon Mar 6 23:57:38 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.8.
+
+Wed Feb 22 21:26:36 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.7.
+
+Tue Feb 21 22:10:43 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (main): Pass missing arg to tmpnam.
+
+ * configure.in: Check for strsignal.
+ * job.c (child_error): Use strsignal.
+ * main.c (main): Don't call signame_init #ifdef HAVE_STRSIGNAL.
+
+ * misc.c (strerror): Fix swapped args in sprintf.
+
+Mon Feb 13 11:50:08 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * configure.in (CFLAGS, LDFLAGS): Don't set these variables.
+
+Fri Feb 10 18:44:12 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (print_version): Add 95 to copyright years.
+
+ * Version 3.72.6.
+
+ * job.c (start_job_command): Remember to call notice_finished_file
+ under -n when not recursing. To do this, consolidate that code
+ under the empty command case and goto there for the -n case.
+
+Tue Feb 7 13:36:03 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * make.h [! STDC_HEADERS]: Don't declare qsort. Sun headers
+ declare it int.
+
+Mon Feb 6 17:37:01 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * read.c (read_makefile): For bogus line starting with tab, ignore
+ it if blank after removing comments.
+
+ * main.c: Cast results of `alloca' to `char *'.
+ * expand.c: Likewise.
+
+Sun Feb 5 18:35:46 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.5.
+
+ * configure.in: Check for mktemp.
+ * main.c (main) [! HAVE_MKTEMP]: Use tmpnam instead of mktemp.
+
+ * configure.in (make_cv_sysconf_open_max): New check for `sysconf
+ (_SC_OPEN_MAX)'.
+ * acconfig.h: Added #undef HAVE_SYSCONF_OPEN_MAX.
+ * job.c [HAVE_SYSCONF_OPEN_MAX] (getdtablesize): Define as macro
+ using sysconf.
+
+Fri Jan 27 04:42:09 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * remake.c (update_file_1): When !MUST_MAKE, don't set
+ FILE->update_status to zero before calling notice_finished_file.
+ (notice_finished_file): Touch only when FILE->update_status is zero.
+ (remake_file): Set FILE->update_status to zero after not calling
+ execute_file_command and deciding to touch instead.
+
+Thu Jan 26 01:29:32 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (debug_signal_handler): New function; toggles debug_flag.
+ (main): Handle SIGUSR1 with that.
+
+Mon Jan 16 15:46:56 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * compatMakefile (realclean): Remove Info files.
+
+Sun Jan 15 08:23:09 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.4.
+
+ * job.c (start_job_command): Save and restore environ around vfork
+ call.
+ (search_path): Function #if 0'd out.
+ (exec_command): Use execvp instead of search_path.
+
+ * expand.c (variable_expand): Rewrote computed variable name and
+ substitution reference handling to be simpler. First expand the
+ entire text between the parens if it contains any $s, then examine
+ the result of that for subtitution references and do no further
+ expansion while parsing them.
+
+ * job.c (construct_command_argv_internal): Handle " quoting too,
+ when no backslash, $ or ` characters appear inside the quotes.
+
+ * configure.in (union wait check): If WEXITSTATUS and WTERMSIG are
+ defined, just use int.
+
+Tue Jan 10 06:27:27 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * default.c (default_variables) [__hpux]: Remove special
+ definition of ARFLAGS. Existence of the `f' flag is not
+ consistent across HPUX versions; and one might be using GNU ar
+ anyway.
+
+ * compatMakefile (clean): Don't remove Info files.
+
+ * compatMakefile (check): Remove gratuitous target declaration.
+
+Sat Jan 7 11:38:23 1995 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * compatMakefile (ETAGS, CTAGS): Don't use -t.
+
+ * arscan.c (ar_name_equal) [cray]: Subtract 1 like [__hpux].
+
+ * main.c (decode_switches): For --help, print usage to stdout.
+
+Mon Dec 5 12:42:18 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.3.
+
+ * remake.c (update_file_1): Do set_command_state (FILE,
+ cs_not_started) only if old state was deps_running.
+
+Mon Nov 28 14:24:03 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c (start_waiting_job): Use set_command_state.
+
+ * build.template (CPPFLAGS): New variable.
+ (prefix, exec_prefix): Set from @...@.
+ (compilation loop): Pass $CPPFLAGS to compiler.
+
+ * GNUmakefile (build.sh.in): Make it executable.
+
+ * GNUmakefile (globfiles): Add configure.in, configure.
+
+ * Version 3.72.2.
+
+ * configure.in (AC_OUTPUT): Don't write glob/Makefile.
+
+ * configure.in (AC_CHECK_SYMBOL): Use AC_DEFINE_UNQUOTED.
+
+ * configure.in: Don't check for ranlib.
+
+Tue Nov 22 22:42:40 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * remake.c (notice_finished_file): Only mark also_make's as
+ updated if really ran cmds.
+
+Tue Nov 15 06:32:46 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * configure.in: Put dnls before random whitespace.
+
+Sun Nov 13 05:02:25 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * compatMakefile (CPPFLAGS): New variable, set from @CPPFLAGS@.
+ (RANLIB): Variable removed.
+ (prefix, exec_prefix): Set these from @...@.
+ (.c.o): Use $(CPPFLAGS).
+ (glob/libglob.a): Don't pass down variables to sub-make.
+ glob/Makefile should be configured properly by configure.
+ (distclean): Remove config.log and config.cache (autoconf stuff).
+
+Mon Nov 7 13:58:06 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * acconfig.h: Add #undef HAVE_UNION_WAIT.
+ * configure.in: Converted to Autoconf v2.
+ * dir.c: Test HAVE_DIRENT_H, HAVE_SYS_DIR_H, HAVE_NDIR_H instead
+ of DIRENT, SYSDIR, NDIR.
+ * build.sh.in (prefix, exec_prefix): Set these from @...@.
+ (CPPFLAGS): New variable, set from @CPPFLAGS@.
+ (compiling loop): Pass $CPPFLAGS before $CFLAGS.
+ * install.sh: File renamed to install-sh.
+
+ * main.c (define_makeflags): When no flags, set WORDS to zero.
+
+Sun Nov 6 18:34:01 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.1.
+
+ * main.c (define_makeflags): Terminate properly when FLAGSTRING is
+ empty.
+
+Fri Nov 4 16:02:51 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.72.
+
+Tue Nov 1 01:18:10 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.71.5.
+
+ * job.c (start_job_command): When ARGV is nil, only set
+ update_state and call notice_finished_file if job_next_command
+ returns zero.
+
+ * job.c (start_job_command): Call notice_finished_file for empty
+ command line.
+
+Thu Oct 27 02:02:45 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * file.c (snap_deps): Set COMMANDS_SILENT for .SILENT, not
+ COMMANDS_NOERROR.
+
+Wed Oct 26 02:14:10 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.71.4.
+
+Tue Oct 25 22:49:24 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * file.c (snap_deps): Set command_flags bits in all :: entries.
+
+Mon Oct 24 18:47:50 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * make.h (posix_pedantic): Declare it.
+ * main.c (main): Move checks .IGNORE, .SILENT, .POSIX to
+ snap_deps.
+ * file.c (snap_deps): Check .IGNORE, .SILENT, .POSIX here instead
+ of in main. If .IGNORE has deps, OR COMMANDS_NOERROR into their
+ command_flags and don't set -i. Likewise .SILENT.
+ * job.c (start_job_command): In FLAGS initialization, OR in
+ CHILD->file->command_flags.
+ * file.h (struct file): New member `command_flags'.
+
+Sun Oct 16 01:01:51 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (switches): Bump flag values for --no-print-directory and
+ --warn-undefined-variables, so neither is 1 (which indicates a
+ nonoption argument).
+
+Sat Oct 15 23:39:48 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (main): Add missing code in .IGNORE test.
+
+Mon Oct 10 04:09:03 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * variable.c (define_automatic_variables): Define +D and +F.
+
+Sat Oct 1 04:07:48 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (main): Define hidden automatic variable with command
+ vars, and MAKEOVERRIDES to a reference to that.
+ (define_makeflags): If posix_pedantic, write a reference to that
+ instead.
+
+Thu Sep 29 00:14:26 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * main.c (posix_pedantic): New variable.
+ (main): Set posix_pedantic if .POSIX is a target.
+ Fix .IGNORE and .SILENT checks to require is_target.
+
+ * commands.c (set_file_variables): Define new automatic variable
+ $+, like $^ but before calling uniquize_deps.
+
+ * job.c (reap_children): Call delete_child_targets for non-signal
+ error if .DELETE_ON_ERROR is a target.
+
+Tue Sep 27 01:57:14 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.71.3.
+
+Mon Sep 26 18:16:55 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * job.c (reap_children): Don't change C->file->command_state when
+ dying. Test it only after calling start_job_command for a new
+ command line. When no more cmds, just set C->file->update_status.
+ (start_job_command): When the last line is empty or under -n, set
+ C->file->update_status.
+ (start_waiting_job): Grok cs_not_started after start_job_command
+ as success.
+ (new_job): Set C->file->update_status when there are no cmds.
+ (job_next_command): When out of lines, don't set
+ CHILD->file->update_status or CHILD->file->command_state.
+
+ * main.c (quote_as_word): Renamed from shell_quote. Take new arg;
+ if nonzero, also double $s.
+ (main): Define MAKEOVERRIDES from command_variables here.
+ (define_makeflags): Don't use command_variables here; instead write a
+ reference $(MAKEOVERRIDES) in MAKEFLAGS. Make vars recursive.
+
+ * dir.c [__MSDOS__]: Fixed typo.
+
+ * vpath.c (selective_vpath_search): Reset EXISTS when stat fails.
+
+Sat Sep 10 03:01:35 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * remake.c: Include <assert.h> and use assert instead of printfs
+ and abort.
+
+ * main.c (decode_switches): Loop until optind hits ARGC, not just
+ until getopt_long returns EOF. Initialize C to zero before loop;
+ in loop if C is EOF, set optarg from ARGV[optind++], else call
+ getopt_long.
+ (decode_env_switches): Use variable_expand instead of
+ allocated_variable_expand. Allocate a fresh buffer to copy split
+ words into; scan characters by hand to break words and
+ debackslashify.
+ (shell_quote): New function.
+ (define_makeflags): Allocate doubled space for switch args, and command
+ variable names and values; use shell_quote to quote those things.
+
+Fri Sep 9 01:37:47 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * Version 3.71.2.
+
+ * acconfig.h: Add HAVE_SYS_SIGLIST and HAVE__SYS_SIGLIST.
+
+ * main.c (decode_switches): The non-option return from getopt is
+ 1, not 0.
+ (command_variables): New type and variable.
+ (decode_switches, decode_env_switches): After making a variable
+ definition, record the struct variable pointer in the
+ command_variables chain.
+ (define_makeflags): If ALL, write variable definitions for
+ command_variables.
+
+ * main.c (other_args): Variable removed.
+ (goals, lastgoal): New static variables (moved from auto in main).
+ (main): Don't process OTHER_ARGS at all.
+ Don't set variable MAKEOVERRIDES at all; define MAKE to just
+ $(MAKE_COMMAND).
+ (init_switches): Prepend a - {return in order} instead of a +
+ {require order}.
+ (decode_switches): Don't set OTHER_ARGS at all.
+ Grok '\0' return from getopt_long as non-option argument; try
+ variable definition and (if !ENV) enter goal targets here.
+ (decode_env_switches): Use allocated_variable_expand to store value.
+ Use find_next_token to simplify word-splitting loop. Don't
+ prepend a dash to uninterpreted value. Instead, if split into
+ only one word, try variable definition and failing that prepend a
+ dash to the word and pass it to decode_switches as a single arg.
+
+Wed Sep 7 03:02:46 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ * remake.c (notice_finished_file): Only recheck modtimes if
+ FILE->command_state was cs_running on entry (meaning the commands
+ actually just ran).
+ (update_file_1): Whenever we set FILE->update_status, call
+ notice_finished_file instead of just set_command_state.
+ * job.c (start_job_command): Whenever we set
+ CHILD->file->update_status, call notice_finished_file instead of
+ just set_command_state.
+
+Tue Sep 6 19:13:54 1994 Roland McGrath <roland@geech.gnu.ai.mit.edu>
+
+ * default.c: Add missing ".
+
+ * job.c: Changed all assignments of command_state members to calls
+ to set_command_state.
+ * remake.c: Likewise.
+ * file.c (set_command_state): New function.
+ * file.h: Declare set_command_state.
+
+ * main.c (init_switches): Put a + first in options.
+
+Mon Jul 25 18:07:46 1994 Roland McGrath <roland@churchy.gnu.ai.mit.edu>
+
+ Merge MSDOS/GO32 port from DJ Delorie <dj@ctron.com>.
+ * vpath.c: Changed all uses of ':' to PATH_SEPARATOR_CHAR.
+ * main.c (directory_before_chdir): New variable, moved out of main
+ (was local).
+ (main) [__MSDOS__]: Look for \ or : to delimit last component of
+ PROGRAM. Don't frob ARGV[0] before setting MAKE_COMMAND variable.
+ (die): Change back to `directory_before_chdir' before dying.
+ * make.h (PATH_SEPARATOR_CHAR): New macro; differing defns for
+ [__MSDOS__] and not.
+ * job.c [__MSDOS__]: Include <process.h>.
+ [__MSDOS__] (dos_pid, dos_status, dos_bname, dos_bename,
+ dos_batch_file): New variables.
+ (reap_children) [__MSDOS__]: Don't call wait; just examine those vars.
+ (unblock_sigs) [__MSDOS__]: Do nothing.
+ (start_job_command) [__MSDOS__]: Use spawnvpe instead of vfork & exec.
+ (load_too_high) [__MSDOS__]: Always return true.
+ (search_path) [__MSDOS__]: Check for : or / in FILE to punt.
+ Use PATH_SEPARATOR_CHAR instead of ':'.
+ (construct_command_argv_internal) [__MSDOS__]: Wholly different
+ values for sh_chars and sh_cmds. Wholly new code to handle shell
+ scripts.
+ * function.c (expand_function: `shell') [__MSDOS__]: Wholly new
+ implementation.
+ * dir.c [__MSDOS__] (dosify): New function.
+ (dir_contents_file_exists_p) [__MSDOS__]: Call it on FILENAME and
+ process the result instead of FILENAME itself.
+ (file_impossible_p) [__MSDOS__]: Likewise.
+ * default.c [__MSDOS__]: Define GCC_IS_NATIVE.
+ (default_suffix_rules) [__MSDOS__]: Use `y_tab.c' instead of `y.tab.c'.
+ (default_variables) [GCC_IS_NATIVE]: Set CC and CXX to `gcc', YACC to
+ `bison -y', and LEX to `flex'.
+ * configure.bat, configh.dos: New files.
+ * commands.c (fatal_error_signal) [__MSDOS__]: Just remove
+ intermediates and exit.
+
+ * commands.c (set_file_variables): Add parens in length
+ computation in .SUFFIXES dep loop to quiet compiler warning. From
+ Jim Meyering.
+
+ * read.c (read_makefile): Free FILENAME if we allocated it. From
+ Jim Meyering.
+
+Mon Jul 4 17:47:08 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * misc.c (safe_stat): New function, EINTR-safe wrapper around stat.
+ * vpath.c (selective_vpath_search): Use safe_stat in place of stat.
+ * read.c (construct_include_path): Use safe_stat in place of stat.
+ * job.c (search_path): Use safe_stat in place of stat.
+ * dir.c (find_directory): Use safe_stat in place of stat.
+ * commands.c (delete_target): Use safe_stat in place of stat.
+ * arscan.c (ar_member_touch) [EINTR]: Do EINTR looping around fstat.
+ * remake.c (name_mtime): Use safe_stat in place of stat.
+ (touch_file) [EINTR]: Do EINTR looping around fstat.
+
+Fri Jun 24 05:40:24 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Check for a shell command first, and
+ then strip leading tabs before further checking if it's not a
+ shell command line.
+
+ * make.h [__arm]: Undefine POSIX.
+ [!__GNU_LIBRARY__ && !POSIX && !_POSIX_VERSION]: Don't declare system
+ functions that return int.
+
+ * job.c (construct_command_argv_internal): After swallowing a
+ backslash-newline combination, if INSTRING is set goto string_char
+ (new label) for normal INSTRING handling code.
+
+Sat Jun 4 01:11:20 1994 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * configure.in: Don't check for sys_siglist and _sys_siglist with
+ AC_HAVE_FUNCS. Instead use two AC_COMPILE_CHECKs.
+
+Mon May 23 18:20:38 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.71.1 released.
+
+ * make.h [!__GNU_LIBRARY__ && !POSIX]: Also test #ifndef
+ _POSIX_VERSION for these declarations.
+
+ * misc.c [GETLOADAVG_PRIVILEGED] [POSIX]: Remove bogus #ifndefs
+ around #undefs of HAVE_SETREUID and HAVE_SETREGID.
+
+Sat May 21 16:26:38 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.71 released.
+
+ * misc.c [GETLOADAVG_PRIVILEGED] [POSIX]: Don't test [HAVE_SETUID]
+ and [HAVE_SETGID]. Every system has those, and configure doesn't
+ check for them.
+
+ * make.h [_POSIX_VERSION]: Don't #define POSIX #ifdef ultrix.
+
+ * compatMakefile (loadavg): Depend on and use loadavg.c instead of
+ getloadavg.c.
+ (loadavg.c): Link or copy it from getloadavg.c.
+ (distclean): Remove loadavg.c.
+
+Mon May 16 22:59:04 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.70.4.
+
+ * misc.c [GETLOADAVG_PRIVILEGED] [! POSIX]: Undefine HAVE_SETEUID
+ and HAVE_SETEGID.
+
+ * default.c (default_terminal_rules): In SCCS rules, put
+ $(SCCS_OUTPUT_OPTION) before $<. On some systems -G is grokked
+ only before the file name.
+ * configure.in (SCCS_GET_MINUS_G check): Put -G flag before file name.
+
+Tue May 10 16:27:38 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Swallow
+ backslash-newline combinations inside '' strings too.
+
+Thu May 5 04:15:10 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (do_define): Call collapse_continuations on each line
+ before all else.
+
+Mon Apr 25 19:32:02 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Notice newline inside
+ '' string when RESTP is non-null.
+
+Fri Apr 22 17:33:30 1994 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.70.3.
+
+ * remake.c (update_goal_chain): Reset FILE to G->file after the
+ double-colon loop so it is never null for following code.
+
+ * read.c (read_makefile): Fix `override define' parsing to skip
+ whitespace after `define' properly.
+
+ * compatMakefile (srcdir): Define as @srcdir@; don't reference
+ $(VPATH).
+ (glob/Makefile): New target.
+
+Thu Apr 21 16:16:55 1994 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.70.2.
+
+ * misc.c (remove_comments): Use find_char_unquote.
+ * make.h (find_char_unquote): Declare it.
+ * read.c (find_char_unquote): New function, generalized from
+ find_percent.
+ (find_percent, find_semicolon, parse_file_seq): Use that.
+
+Wed Apr 20 18:42:39 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * implicit.c (pattern_search): Always allocate new storage for
+ FILE->stem. It is not safe to store STEM's address because it
+ might be auto storage.
+
+ * configure.in: Check for seteuid and setegid.
+ * misc.c [HAVE_SETEUID]: Declare seteuid.
+ [HAVE_SETEGID]: Declare setegid.
+ (make_access, user_access) [HAVE_SETEUID]: Use seteuid.
+ [HAVE_SETEGID]: Use setegid.
+
+ * remake.c (update_goal_chain): Set STATUS to FILE->update_status,
+ to preserve whether it's 2 for error or 1 for -q trigger. When
+ STATUS gets nonzero and -q is set, always stop immediately.
+ * main.c (main, decode_switches): Die with 2 for errors.
+ (main): Accept 2 return from update_goal_chain and die with that.
+ * misc.c (fatal, makefile_fatal): Die with 2; 1 is reserved for -q
+ answer.
+ * job.c (reap_children): Die with 2 for error.
+ (start_job_command): Set update_status to 2 for error. Set it to
+ 1 when we would run a command and question_flag is set.
+
+ * read.c (read_makefile): Don't mark makefiles as precious. Just
+ like other targets, they can be left inconsistent and in need of
+ remaking by aborted commands.
+
+ * read.c (read_makefile): Write no error msg for -include file.
+
+Tue Apr 5 05:22:19 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * commands.c (fatal_error_signal): Don't unblock signals.
+
+ * file.h (struct file): Change member `double_colon' from flag to
+ `struct file *'.
+ * read.c (record_files): Set double_colon pointer instead of flag.
+ * main.c (main): When disqualifying makefiles for updating, use
+ double_colon pointer to find all entries for a file.
+ * file.c (enter_file): If there is already a double-colon entry
+ for the file, set NEW->double_colon to that pointer.
+ (file_hash_enter): Use FILE->double_colon to find all entries to
+ set name.
+ * remake.c (update_goal_chain): Do inner loop on double-colon entries.
+ (update_file): Use FILE->double_colon pointer to find all entries.
+ (f_mtime): Likewise.
+ (notice_finished_file): Propagate mtime change to all entries.
+
+ * variable.c (try_variable_definition): Return after abort.
+
+Fri Apr 1 18:44:15 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Remove unused variable.
+ (parse_file_seq): When removing an elt that is just `)', properly
+ fix up the previous elt's next pointer.
+
+Mon Mar 28 18:31:49 1994 Roland McGrath (roland@mole.gnu.ai.mit.edu)
+
+ * configure.in: Do AC_SET_MAKE.
+ * GNUmakefile (Makefile.in): Edit MAKE assignment into @SET_MAKE@.
+
+Fri Mar 4 00:02:32 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * function.c (subst_expand): If BY_WORD or SUFFIX_ONLY is set and
+ the search string is the empty string, find a match at the end of
+ each word (using end_of_token in place of sindex).
+
+ * misc.c (end_of_token): Don't treat backslashes specially; you
+ can no longer escape blanks with backslashes in export, unexport,
+ and vpath. This was never documented anyway.
+
+Thu Mar 3 23:53:46 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Variable name for `define' is not just
+ first token; use whole rest of line and strip trailing blanks.
+
+Wed Feb 16 16:03:45 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.70.1.
+
+ * read.c (read_makefile): Add -d msg stating args.
+
+ * read.c (read_makefile): Use isspace to skip over leading
+ whitespace, and explicitly avoid skipping over tabs. Don't want
+ to skip just spaces though; formfeeds et al should be skipped.
+
+ * default.c (default_variables) [__hpux]: Add f in ARFLAGS.
+
+ * arscan.c (ar_name_equal) [__hpux]: Subtract 2 instead of 1 from
+ sizeof ar_name for max length to compare.
+
+ * misc.c [GETLOADAVG_PRIVILEGED] [POSIX]: Undefine HAVE_SETREUID
+ #ifdef HAVE_SETUID; likewise HAVE_SETREGID and HAVE_SETGID.
+
+ * main.c (main): Call user_access after setting `program', in case
+ it needs to use it in an error message.
+
+ * read.c (read_makefile): Ignore an empty line starting with a tab.
+
+Thu Feb 10 21:45:31 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in (AC_SYS_SIGLIST_DECLARED): Use this instead of
+ AC_COMPILE_CHECK that is now its contents.
+
+Fri Feb 4 16:28:54 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h: #undef strerror after #include <string.h>.
+ [! ANSI_STRING]: Declare strerror.
+
+Thu Feb 3 02:21:22 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * misc.c (strerror): #undef any macro before function definition.
+
+Mon Jan 31 19:07:23 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (try_variable_definition): Calculate BEG before loop
+ to strip blanks by decrementing END. Don't decr END to before BEG.
+
+ * read.c (read_makefile): Skip over leading space characters, but
+ not tabs, after removing continuations and comments (it used to
+ use isspace).
+
+Tue Jan 25 16:45:05 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (define_automatic_variables): In $(@D) et al, use
+ patsubst to remove trailing slash.
+
+ * commands.c (delete_target): New function, broken out of
+ delete_child_targets. Check for archive members and give special msg.
+ (delete_child_targets): Use delete_target.
+
+Mon Jan 17 17:03:22 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (default_suffix_rules): Use $(TEXI2DVI_FLAGS) in
+ texi2dvi rules. Use $(MAKEINFO_FLAGS) in makeinfo rules.
+
+Tue Jan 11 19:29:55 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * GNUmakefile (tarfiles): Omit make-doc.
+ (make-$(version).tar): Include make.info*.
+
+Fri Jan 7 16:27:00 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (configure, config.h.in): Comment out rules.
+
+Thu Jan 6 18:08:08 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (binprefix, manprefix): New variables.
+ (instname): Variable removed.
+ (install): Use $({bin,man}prefix)make in place of $(instname).
+ File targets likewised renamed.
+
+Mon Jan 3 17:50:25 1994 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.70 released.
+
+Thu Dec 23 14:46:54 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.69.3.
+
+ * read.c (parse_file_seq): Inside multi-word archive ref
+ translation loop, check NEW1==0 at end and break out of the loop.
+
+ * GNUmakefile (make-$(version).tar): Distribute install.sh.
+ * install.sh: New file.
+
+ * configure.in (SCCS_GET_MINUS_G check): Put redirection for admin
+ cmds outside subshell parens, to avoid "command not found" msgs
+ from the shell.
+
+Wed Dec 22 17:00:43 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in (SCCS_GET_MINUS_G check): Put -G flag last in get cmd.
+ Redirect output & error from get to /dev/null.
+ Fix reversed sense of test.
+
+Fri Dec 17 15:31:36 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in (SCCS_GET_MINUS_G check): Use parens instead of
+ braces inside if condition command; some shells lose.
+
+Thu Dec 16 15:10:23 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.69.2.
+
+ * arscan.c [M_UNIX]: Move #undef M_XENIX for PORTAR stuff.
+ (PORTAR) [M_XENIX]: Define to 0 instead of 1.
+
+ * main.c (define_makeflags): Only export MAKEFLAGS if !ALL.
+
+Wed Dec 15 17:47:48 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (main): Cast result of pointer arith to unsigned int
+ before passing to define_variable for envars. Matters when
+ sizeof(unsigned)!=sizeof(ptrdiff_t).
+
+Tue Dec 14 14:21:16 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in: Add new check for SCCS_GET_MINUS_G.
+ * config.h.in: Add #undef SCCS_GET_MINUS_G.
+ * default.c (default_terminal_rules): Use `$(SCCS_OUTPUT_OPTION)' in
+ place of `-G $@' in SCCS commands.
+ (default_variables) [SCCS_GET_MINUS_G]: Define SCCS_OUTPUT_OPTION
+ to "-G$@".
+
+ * configure.in (AC_OUTPUT): Put touch stamp-config in second arg
+ (so it goes in config.status), rather than afterward.
+
+ * ar.c (ar_member_date): Don't call enter_file on the archive file
+ if it doesn't exist (by file_exists_p).
+
+ * compatMakefile ($(infodir)/make.info): Replace `$$d/foo.info'
+ with `$$dir/make.info' in install-info invocation (oops).
+
+ * vpath.c (construct_vpath_list): Only set LASTPATH set PATH when
+ we do not unlink and free PATH.
+
+ * file.c (print_file_data_base): Fix inverted calculation for
+ average files per hash bucket.
+
+ * read.c (readline): When we see a NUL, give only a warning and
+ synthesize a newline to terminate the building line (used to
+ fatal). Move fgets call into the loop condition, and after the
+ loop test ferror (used to test !feof in the loop).
+
+Fri Dec 3 16:40:31 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in: Check for strerror in AC_HAVE_FUNCS.
+
+Thu Dec 2 15:37:50 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ Differentiate different flavors of missing makefile error msgs,
+ removing gratuitous `fopen: ' and giving caller for included makefiles.
+ * misc.c [! HAVE_STRERROR]: Define our own strerror here.
+ (perror_with_name, pfatal_with_name): Use strerror instead of
+ replicating its functionality.
+ * read.c (read_makefile): Return int instead of void.
+ (read_all_makefiles, read_makefile): Change callers to notice zero
+ return and give error msg.
+
+Thu Nov 11 11:47:36 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.69.1.
+
+ * default.c: Put `-G $@' before $< in SCCS cmds.
+
+Wed Nov 10 06:06:14 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): After trying a variable defn, notice if
+ the line begins with a tab, and diagnose an error.
+
+Sun Nov 7 08:07:37 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.69.
+
+Wed Nov 3 06:54:33 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.10.
+
+ * implicit.c (try_implicit_rule): Look for a normal rule before an
+ archive rule.
+
+Fri Oct 29 16:45:28 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * function.c (expand_function: `sort'): Double NWORDS when it
+ overflows, instead of adding five.
+
+ * compatMakefile (clean): Remove loadavg.
+
+Wed Oct 27 17:58:33 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.9.
+
+ * file.h (NEW_MTIME): Define new macro.
+ * main.c (main): Set time of NEW_FILES to NEW_MTIME, not to
+ current time returned from system. Removed variable NOW.
+ * remake.c (notice_finished_file): Use NEW_MTIME in place of
+ current time here too.
+
+Tue Oct 26 19:45:35 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.8.
+
+ * remake.c (update_file_1): Don't clear MUST_MAKE when FILE has no
+ cmds and !DEPS_CHANGED unless also !NOEXIST.
+
+Mon Oct 25 15:25:21 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (parse_file_seq): When converting multi-word archive
+ refs, ignore a word beginning with a '('.
+
+Fri Oct 22 02:53:38 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in: Check for sys/timeb.h.
+ * make.h [HAVE_SYS_TIMEB_H]: Test this before including it.
+
+Thu Oct 21 16:48:17 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.7.
+
+ * rule.c (convert_suffix_rule): New local TARGPERCENT. Set it to
+ TARGNAME+1 for "(%.o)", to TARGNAME for "%.?". Use it in place of
+ TARGNAME to initialize PERCENTS[0].
+
+Mon Oct 18 06:49:35 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in: Use AC_HAVE_HEADERS(unistd.h) instead of AC_UNISTD_H.
+ Remove AC_USG; it is no longer used.
+
+ * file.c (print_file): New function, broken out of
+ print_file_data_base.
+ (print_file_data_base): Call it.
+ * rule.c (print_rule): New function, broken out of
+ print_rule_data_base.
+ (print_rule_data_base): Call it.
+
+Thu Oct 14 14:54:03 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (install_default_suffix_rules): New function, broken
+ out of install_default_implicit_rules.
+ (install_default_implicit_rules): Move suffix rule code there.
+ * make.h: Declare install_default_suffix_rules.
+ * main.c (main): Call install_default_suffix_rules before reading
+ makefiles. Move convert_to_pattern call before
+ install_default_implicit_rules.
+
+ * job.h (struct child): Make `pid' member type `pid_t' instead of
+ `int'.
+
+ * compatMakefile (RANLIB): New variable, set by configure.
+ (glob/libglob.a): Pass RANLIB value down to submake.
+
+ Fixes for SCO 3.2 "devsys 4.2" from pss@tfn.com (Peter Salvitti).
+ * make.h: Include <sys/timeb.h> before <time.h> for SCO lossage.
+ * job.c [! getdtablesize] [! HAVE_GETDTABLESIZE]: If NOFILE is not
+ defined but NOFILES_MAX is, define it to be that.
+
+Mon Oct 11 19:47:33 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * GNUmakefile (make-$(version).tar): Depend on acconfig.h, so it
+ is distributed.
+
+Sun Oct 3 15:15:33 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (default_terminal_rules): Add `-G $@' to SCCS get cmds.
+
+Tue Sep 28 14:18:20 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Add ^ to SH_CHARS; it
+ is another symbol for | in some shells.
+ * main.c (main): Add it to CMD_DEFS quoting list as well.
+
+Mon Sep 20 18:05:24 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Remove '=' from
+ SH_CHARS. Only punt on '=' if it is unquoted in a word before the
+ first word without an unquoted '='.
+
+ * main.c (define_makeflags): Set v_export for MAKEFLAGS.
+
+Fri Sep 17 00:37:18 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (update_file_1): Use .DEFAULT cmds for phony targets.
+
+ * make.h [_AIX && _POSIX_SOURCE]: Define POSIX.
+
+ * commands.c (delete_child_targets): Don't delete phony files.
+
+ * job.c (start_job_command): Set COMMANDS_RECURSE in FLAGS if we
+ see a `+' at the beginning of the command line.
+
+Thu Sep 9 17:57:14 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.6.
+
+Wed Sep 8 20:14:21 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (define_makeflags): Define MAKEFLAGS with o_file, not o_env.
+
+Mon Aug 30 12:31:58 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * expand.c (variable_expand): Fatal on an unterminated reference.
+
+Thu Aug 19 16:27:53 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.5.
+
+ * variable.c (define_automatic_variables): Define new o_default
+ variable `MAKE_VERSION' from version_string and remote_description.
+
+ * make.h (version_string, remote_description): Declare these here.
+ * main.c: Don't declare version_string.
+ (print_version): Don't declare remote_description.
+
+Wed Aug 18 15:01:24 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Free space pointed to by CONDITIONALS
+ before restoring the old pointer.
+
+Mon Aug 16 17:33:36 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile ($(objs)): Depend on config.h.
+
+ * GNUmakefile (build.sh.in): Depend on compatMakefile.
+
+ * configure.in: Touch stamp-config after AC_OUTPUT.
+
+Fri Aug 13 16:04:22 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.4.
+
+Thu Aug 12 17:18:57 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h: Include <config.h> instead of "config.h".
+
+Wed Aug 11 02:35:25 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (main): Make all variables interned from ENVP be v_export.
+ * variable.c (try_variable_definition): In v_default case, don't
+ check for an o_file variable that `getenv' finds.
+
+ * job.c (reap_children): New local variable ANY_LOCAL; set it
+ while setting ANY_REMOTE. If !ANY_LOCAL, don't wait for local kids.
+
+ * main.c (main): Don't call decode_env_switches on MFLAGS. DOC THIS.
+
+ * function.c (expand_function): #if 0 out freeing of ENVP since it
+ is environ.
+
+Mon Aug 9 17:37:20 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.3.
+
+ * remote-stub.c (remote_status): Set errno to ECHILD before return.
+ * job.c (reap_children): Scan the chain for remote children and
+ never call remote_status if there are none.
+
+ * function.c (expand_function: `shell'): #if 0 out calling
+ target_environment; just set ENVP to environ instead.
+
+ * job.c (reap_children): Check for negative return from
+ remote_status and fatal for it.
+ When blocking local child wait returns 0, then try a blocking call
+ to remote_status.
+
+Tue Aug 3 00:19:00 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (clean): Delete make.info* and make.dvi here.
+ (distclean): Not here.
+
+ * dep.h (RM_*): Use #defines instead of enum to avoid lossage from
+ compilers that don't like enum values used as ints.
+
+Mon Aug 2 16:46:34 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (loadavg): Add $(LOADLIBES).
+
+Sun Aug 1 16:01:15 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.2.
+
+ * compatMakefile (loadavg, check-loadavg): New targets.
+ (check): Depend on check-loadavg.
+
+ * compatMakefile (glob/libglob.a): Depend on config.h.
+
+ * misc.c (log_access): Write to stderr instead of stdout.
+
+Fri Jul 30 00:07:02 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.1.
+
+Thu Jul 29 23:26:40 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in (SYS_SIGLIST_DECLARED): In test program include
+ <unistd.h> #ifdef HAVE_UNISTD_H.
+
+ * compatMakefile (.PHONY): Put after `all' et al.
+
+ * configure.in: Add AC_IRIX_SUN.
+
+Wed Jul 28 17:41:12 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.68.
+
+Mon Jul 26 14:36:49 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.8.
+
+Sun Jul 25 22:09:08 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.7.
+
+ * compatMakefile ($(infodir)/make.info): Don't use $(instname).
+ Run install-info script if present.
+
+Fri Jul 23 16:03:50 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h [STAT_MACROS_BROKEN]: Test this instead of [uts].
+
+ * configure.in: Add AC_STAT_MACROS_BROKEN.
+
+Wed Jul 14 18:48:11 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.6.
+
+ * read.c (read_makefile): Recognize directive `-include', like
+ `include' but sets RM_DONTCARE flag.
+
+ * variable.c (target_environment): If FILE is nil, use
+ current_variable_set_list in place of FILE->variables.
+ * function.c (expand_function: `shell'): Get an environment for
+ the child from target_environment instead of using environ.
+
+ * dep.h: Declare read_all_makefiles here.
+ (RM_*): Define new enum constants.
+ * read.c (read_makefile): Second arg is FLAGS instead of TYPE.
+ Treat it as a bit mask containing RM_*.
+ (read_all_makefiles): For default makefiles, set D->changed to
+ RM_DONTCARE instead of 1.
+ * main.c: Don't declare read_all_makefiles here.
+ (main): Check `changed' member of read_makefiles elts for RM_*
+ flags instead of specific integer values.
+
+Mon Jul 12 22:42:17 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h [sequent && i386]: #undef POSIX. From trost@cse.ogi.edu.
+
+Thu Jul 8 19:51:23 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * vpath.c (construct_vpath_list): If ELEM is zero 0, free PATTERN
+ as well as VPATH.
+ (build_vpath_lists): Empty `vpaths' around construct_vpath_list
+ call for $(VPATH). Expand $(strip $(VPATH)), not just $(VPATH).
+
+ * rule.c (convert_suffix_rule): Use alloca instead of xmalloc for
+ PERCENTS, whose storage is not consumed by create_pattern_rule.
+
+ * make.h [__mips && _SYSTYPE_SVR3]: #undef POSIX.
+
+Wed Jun 30 18:11:40 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.5.
+
+ * rule.c (max_pattern_targets): New variable.
+ (count_implicit_rule_limits): Compute its value.
+ * rule.h: Declare it.
+ * implicit.c (pattern_search): Make TRYRULES max_target_patterns
+ times bigger. Move adding new TRYRULES elt inside the inner
+ targets loop, so each matching target gets its own elt in MATCHES
+ and CHECKED_LASTSLASH.
+
+ * file.c (remove_intermediates): If SIG!=0 say `intermediate file'
+ instead of just `file' in error msg.
+
+Fri Jun 25 14:55:15 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv): Turn off
+ --warn-undefined-variables around expansion of SHELL and IFS.
+ * read.c (tilde_expand): Likewise for HOME.
+ (read_all_makefiles): Likewise for MAKEFILES.
+ * vpath.c (build_vpath_lists): Likewise for VPATH.
+
+ * main.c (warn_undefined_variables_flag): New flag variable.
+ (switches): Add --warn-undefined-variables.
+ * make.h (warn_undefined_variables_flag): Declare it.
+ * expand.c (warn_undefined): New function.
+ (reference_variable): Call it if the variable is undefined.
+ (variable_expand): In substitution ref, call warn_undefined if the
+ variable is undefined.
+
+ * default.c (default_pattern_rules): Add `%.c: %.w %.ch' and
+ `%.tex: %.w %.ch' rules.
+ (default_suffix_rules: .w.c, .w.tex): Pass three args: $< - $@.
+ (default_suffixes): Add `.ch'.
+
+Mon Jun 21 17:55:39 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (default_suffixes): Replace `.cweb' with `.w'.
+ (default_suffix_rules): Rename `.cweb.c' and `.cweb.tex' to `.w.c'
+ and `.w.tex'.
+
+Fri Jun 11 14:42:09 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile ($(bindir)/$(instname)): Add missing backslash.
+
+Thu Jun 10 18:14:08 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.4.
+
+ * read.c (multi_glob): Don't free OLD and OLD->name in the
+ FOUND!=0 fork. Use new block-local variable F instead of
+ clobbering OLD.
+
+ * ar.c (glob_pattern_p): New function, snarfed from glob/glob.c.
+ (ar_glob): Call it; return nil immediately if MEMBER_PATTERN
+ contains no metacharacters.
+
+Wed Jun 9 16:25:35 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * ar.c (ar_glob{_match,_alphacompare}): New function.
+
+ * dep.h [! NO_ARCHIVES]: Declare it.
+ * read.c (multi_glob) [! NO_ARCHIVES]: Use it on archive member elts.
+
+ * read.c (read_makefile): Pass flag (1) to parse_file_seq, not to
+ multi_glob (which doesn't take a 3rd arg).
+ * rule.c (install_pattern_rule): Likewise.
+ * default.c (set_default_suffixes): Here too.
+ * function.c (string_glob): Don't pass gratuitous arg to multi_glob.
+
+ * read.c (parse_file_seq) [! NO_ARCHIVES]: Add post-processing
+ loop to translate archive refs "lib(a b)" into "lib(a) lib(b)".
+
+Mon Jun 7 19:26:51 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (installdirs): Actually pass directory names.
+ ($(bindir)/$(instname)): Test chgrp&&chmod exit status with `if';
+ if it fails, echo a warning msg, but don't make the rule fail.
+
+ * read.c (tilde_expand): New function, broken out of tilde_expand.
+ (multi_glob): Call it.
+ (construct_include_path): Expand ~ in directory names.
+ * dep.h: Declare tilde_expand.
+ * main.c (enter_command_line_file): Expand ~ at the start of NAME.
+ (main): Expand ~ in -C args.
+ * read.c (read_makefile): Expand ~ in FILENAME unless TYPE==2.
+
+Fri Jun 4 13:34:47 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (decode_env_switches): Use xmalloc instead of alloca for ARGS.
+
+ * main.c (main): Put result of alloca in temporary variable with
+ simple assignment, to make SGI compiler happy.
+
+Thu Jun 3 20:15:46 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.3.
+
+ * main.c (main): Before re-execing, remove intermediate files, and
+ print the data base under -p. Sexier debugging message.
+
+ * implicit.c (pattern_search): Allocate an extra copy of the name
+ of a winning intermediate file when putting it in FOUND_FILES.
+
+Wed Jun 2 16:38:08 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Pass flag (1) to parse_file_seq, not to
+ multi_glob (which doesn't take a 3rd arg).
+
+ * dir.c (dir_contents_file_exists_p): When reading dirents, ignore
+ chars within D_NAMLEN that are NULs.
+
+ * main.c (decode_switches): Don't savestring ARGV[0] to put it
+ into `other_args'.
+ For string switch, don't savestring `optarg'.
+ (main): Don't free elts of makefiles->list that are "-".
+ Use alloca'd rather than savestring'd storage for elts of
+ makefiles->list that are temporary file names.
+ * read.c (read_all_makefiles): Don't free *MAKEFILES.
+ * file.c (enter_file): Don't strip `./'s.
+ * main.c (enter_command_line_file): New function.
+ (main): Use it in place of enter_file for command-line goals from
+ other_files, and for old_files and new_files.
+
+Mon May 31 18:41:40 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.2.
+
+ * compatMakefile (.SUFFIXES): Add .info.
+ ($(infodir)/$(instname).info): Find make.info* in cwd if there,
+ else in $srcdir. Use basename to remove dir name from installed name.
+
+Thu May 27 17:35:02 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * implicit.c (pattern_search): When interning FOUND_FILES, try
+ lookup_file first; if found, free the storage for our copy of the name.
+
+Wed May 26 14:31:20 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.1.
+
+ * main.c (decode_switches): In usage msg, write `--switch=ARG' or
+ `--switch[=OPTARG]' rather than `--switch ARG' or `--switch [ARG]'.
+
+Mon May 24 16:17:31 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * rule.c (convert_suffix_rule): New function.
+ (convert_to_pattern): Use it instead of doing all the work here
+ several times.
+ For target suffix `.a', generate both the archive magic rule and
+ the normal rule.
+
+ * compatMakefile (distclean): Remove stamp-config.
+
+Sat May 22 16:15:18 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.67.
+
+ * file.c (remove_intermediates): Don't write extra space after `rm'.
+
+ * main.c (struct command_switch.type): Remove `usage_and_exit'.
+ (print_usage_flag): New variable.
+ (switches: --help): Make type `flag', to set print_usage_flag.
+ (init_switches): Remove `usage_and_exit' case.
+ (decode_switches): Likewise.
+ (decode_switches): Print usage if print_usage_flag is set.
+ When printing usage, die with status of BAD.
+ (main): Die with 0 if print_version_flag.
+
+Fri May 21 16:09:28 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.66.
+
+Wed May 19 21:30:44 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (installdirs): New target.
+ (install): Depend on it.
+
+Sun May 16 20:15:07 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.65.2.
+
+Fri May 14 16:40:09 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * vpath.c (construct_vpath_list): In removal loop for DIRPATH==0,
+ set LASTPATH to PATH, not NEXT.
+
+ * dir.c (read_dirstream): Break out of loop after incrementing
+ DS->buckets such that it reaches DIRFILE_BUCKETS; avoid trying to
+ dereference DS->contents->files[DIRFILE_BUCKETS].
+
+ * read.c (read_makefile): Clear no_targets after reading a
+ targetful rule line.
+
+ * main.c (main): If print_version_flag is set, exit after printing
+ the version.
+ (switches): Change --version docstring to say it exits.
+
+ * make.h [butterfly]: #undef POSIX.
+
+Wed May 12 15:20:21 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.65.1.
+
+ * arscan.c (ar_scan) [! AIAMAG]: Don't declare LONG_NAME.
+ [AIAMAG]: Pass TRUNCATE flag arg to (*FUNCTION), always zero.
+
+ * function.c (handle_function): Use fatal instead of
+ makefile_fatal when reading_filename is nil.
+
+ * configure.in: Add AC_GETGROUPS_T.
+ * job.c (search_path): Use GETGROUPS_T in place of gid_t.
+
+Sun May 9 15:41:25 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.65.
+
+Fri May 7 18:34:56 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * function.c (handle_function): Fatal for unmatched paren.
+
+Thu May 6 16:13:41 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.64.3.
+
+ * commands.c (handling_fatal_signal): New variable.
+ (fatal_error_signal): Set it.
+ * job.c (reap_children): Avoid nonreentrant operations if that is set.
+ * make.h: Declare handling_fatal_signal.
+
+ * expand.c (reference_variable): New function, snippet of code
+ broken out of simple-reference case of variable_expand.
+ (variable_expand): Use it for simple refs.
+ (variable_expand): When checking for a computed variable name,
+ notice a colon that comes before the final CLOSEPAREN. Expand
+ only up to the colon, and then replace the pending text with a
+ copy containing the expanded name and fall through to subst ref
+ handling.
+ (variable_expand): Don't bother expanding the name if a colon
+ appears before the first $.
+ (expand_argument): Use alloca instead of savestring.
+ (variable_expand): For subst ref, expand both sides of = before
+ passing to [pat]subst_expand. Use find_percent instead of lindex
+ to check the lhs for a %.
+
+Wed May 5 14:45:52 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.64.2.
+
+Mon May 3 17:00:32 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * arscan.c (ar_name_equal) [AIAMAG]: Abort if TRUNCATED is nonzero.
+
+ * read.c (read_makefile): Pass extra arg of 1 to parse_file_seq,
+ not to multi_glob.
+
+Thu Apr 29 19:47:33 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.64.1.
+
+ * arscan.c (ar_scan): New local flag var LONG_NAME. Set it when
+ we read the member name in any of the fashions that allow it to be
+ arbitrarily long. Pass its negation to FUNCTION.
+ (describe_member): Take TRUNCATED from ar_scan and print it.
+ (ar_name_equal): Take new arg TRUNCATED; if nonzero, compare only
+ the first sizeof (struct ar_hdr.ar_name) chars.
+ (ar_member_pos): Take TRUNCATED from ar_scan, pass to ar_name_equal.
+ * ar.c (ar_member_date_1): Likewise.
+
+Wed Apr 28 21:18:22 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (reap_children): Before calling start_job_command to start
+ the next command line, reset C->remote by calling start_remote_job_p.
+
+Mon Apr 26 15:56:15 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * arscan.c (ar_scan): New local var NAMEMAP.
+ In loop, rename NAME to NAMEBUF; new var NAME is a pointer; new
+ flag IS_NAMEMAP. When extracting the member name, always put a
+ null at its end first. If the name is "//" or "/ARFILENAMES", set
+ IS_NAMEMAP. If we have already read in NAMEMAP, and NAME looks
+ like " /N", get full name from NAMEMAP+N.
+ Else if NAME looks like "#1/N", read N chars from the
+ elt data to be the full name. At end of loop, if IS_NAMEMAP, read
+ the elt's data into alloca'd NAMEMAP.
+ (ar_name_equal): #if 0 truncating code.
+
+ * make.h: Don't declare vfork at all. It returns int anyway,
+ unless <unistd.h> declared it; and we conflicted with some systems.
+
+ * main.c (define_makeflags): If FLAGSTRING[1] is '-', define
+ MAKEFLAGS to all of FLAGSTRING, not &FLAGSTRING[1]. Don't want to
+ define it to something like "-no-print-directory".
+ Use %g format instead of %f for floating-valued things.
+
+Thu Apr 22 18:40:58 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * GNUmakefile (Makefile.in): Use a substitution ref on nolib-deps
+ to change remote-%.dep to remote-stub.dep.
+
+Wed Apr 21 15:17:54 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.64.
+
+Fri Apr 16 14:22:22 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (install): Remove - prefix from chgrp+chmod.
+
+ * Version 3.63.8.
+
+Thu Apr 15 18:24:07 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * acconfig.h: New file; contains "#undef SCCS_GET" for autoheader.
+ * configure.in: If /usr/sccs/get exists, define SCCS_GET to that,
+ else to "get".
+ * default.c (default_variables): Set GET to macro SCCS_GET.
+
+ * read.c (parse_file_seq): Take extra arg STRIP; strip `./' only
+ if nonzero. I hope this is the last time this argument is added
+ or removed.
+ (read_makefile): Pass it 1 when parsing include file names.
+ Pass it 1 when parsing target file names.
+ Pass it 1 when parsing static pattern target pattern names.
+ * rule.c (install_pattern_rule): Pass it 1 when parsing rule deps.
+ * default.c (set_default_suffixes): Pass it 1 when parsing
+ default_suffixes.
+ * function.c (string_glob): Pass it 0 here.
+
+Wed Apr 14 11:32:05 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * misc.c (log_access): New function.
+ ({init,user,make,child}_access): Call it.
+ (child_access): Abort if !access_inited.
+
+ * main.c (switches: --no-print-directory): Use 1 instead of -1 for
+ single-letter option.
+ (init_switches, decode_switches, define_makeflags): An option with
+ no single-letter version is no longer indicated by a value of -1;
+ instead a value that is !isalnum.
+ (init_switches): Don't put such switches into the string, only
+ into the long_option table.
+
+ * make.h [!NSIG] [!_NSIG]: #define NSIG 32.
+
+ * job.c [HAVE_WAITPID]: Remove #undef HAVE_UNION_WAIT. AIX's
+ bsdcc defined WIF* to use union wait.
+
+ * main.c (struct command_switch): Change member `c' to type int.
+ (switches): Make const.
+ (decode_switches): Use `const struct command_switch *'.
+ (define_makeflags): Likewise.
+
+ * default.c (default_suffix_rules): Add `-o $@' to makeinfo rules.
+
+Mon Apr 12 12:30:04 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.63.7.
+
+ * configure.in (AC_HAVE_HEADERS): Check for string.h and memory.h.
+ Removed AC_MEMORY_H.
+ * make.h [USG, NeXT]: Don't test these.
+ [HAVE_STRING_H]: Test this to include string.h and define ANSI_STRING.
+ [HAVE_MEMORY_H]: Test this instead of NEED_MEMORY_H.
+ [! ANSI_STRING]: Put decls of bcopy et al here.
+ [sparc]: Don't test this for alloca.h; HAVE_ALLOCA_H is sufficient.
+ [HAVE_SIGSETMASK]: Test this rather than USG.
+ [__GNU_LIBRARY__ || POSIX]: Don't #include <unistd.h> again.
+ * main.c (main): Handle SIGCHLD if defined, and SIGCLD if defined.
+ It doesn't hurt to do both if they are both defined, and testing
+ USG is useless.
+ * dir.c: Rationalize directory header conditionals.
+ * arscan.c [HAVE_FCNTL_H]: Test this rather than USG || POSIX.
+
+ * default.c (default_suffixes): Add `.txinfo'.
+ (default_suffix_rules): Add `.txinfo.info' and `.txinfo.dvi' rules.
+
+ * variable.c (try_variable_definition): Replace RECURSIVE flag
+ with enum FLAVOR, which can be simple, recursive, or append.
+ Recognize += as append flavor. Set new variable VALUE in a switch
+ on FLAVOR. For append flavor, prepend the variable's old value.
+ If the variable was previously defined recursive, set FLAVOR to
+ recursive; if it was defined simple, expand the new value before
+ appending it to the old value. Pass RECURSIVE flag to
+ define_variable iff FLAVOR == recursive.
+
+ * variable.c (try_variable_definition): Use alloca and bcopy for
+ NAME, instead of savestring. Might as well use stack storage
+ since we free it immediately anyway.
+
+Thu Apr 8 18:04:43 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (start_waiting_jobs): Move decl of JOB outside of loop.
+
+ * main.c (define_makeflags): Rename `struct flag' member `switch'
+ to `cs', which is not a reserved word.
+
+Wed Apr 7 15:30:51 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (new_job): Call start_waiting_jobs first thing.
+ (start_waiting_job): Changed return type from void to int.
+ Return 0 when putting the child on the waiting_jobs chain.
+ (start_waiting_jobs): Don't check load and job_slots here.
+ Always take a job off the chain and call start_waiting_job on it;
+ give up and return when start_waiting_job returns zero.
+
+ * main.c (define_makeflags: struct flag): Change member `char c' to
+ `struct command_switch *switch'.
+ (ADD_FLAG): Set that to CS instead of CS->c.
+ If CS->c is -1, increment FLAGSLEN for the long name.
+ When writing out FLAGS, handle FLAGS->switch->c == -1 and write
+ the long name instead.
+
+ * compatMakefile (stamp-config): New target of old config.h rule.
+ Touch stamp-config after running config.status.
+ (config.h): Just depend on stamp-config, and have empty commands.
+
+Mon Apr 5 20:14:02 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c [HAVE_WAITPID]: #undef HAVE_UNION_WAIT.
+
+ * configure.in (AC_HAVE_FUNCS): Check for psignal.
+
+Fri Apr 2 17:15:46 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (long_option_aliases): Remove "new"; it is already an
+ unambiguous prefix of "new-file".
+
+Sun Mar 28 16:57:17 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.63.6.
+
+Wed Mar 24 14:26:19 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * vpath.c (selective_vpath_search): When adding the
+ name-within-directory at the end of NAME, and we don't add a
+ slash, don't copy FILENAME in one char too far into NAME.
+
+ * variable.c (define_automatic_variables): Find default_shell's
+ length with strlen, not numerology.
+
+Wed Mar 17 20:02:27 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (define_makeflags): Add the elts of a string option in
+ reverse order, so they come out right when reversed again.
+
+Fri Mar 12 15:38:45 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (make.info): Use `-o make.info'.
+
+Thu Mar 11 14:13:00 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (REMOTE): Set to @REMOTE@; change comments to
+ reflect new use.
+ (objs): Replace remote.o with remote-$(REMOTE).o.
+ (srcs): Replace remote.c with remote-$(REMOTE).c.
+ (remote.o): Rule removed.
+
+ * configure.in (REMOTE): Subst this in Makefile et al; default "stub".
+ Use AC_WITH to grok --with-customs arg to set REMOTE=cstms.
+ * GNUmakefile (build.sh.in): Filter out remote-% from objs list.
+ * build.template (REMOTE): New var; set to @REMOTE@.
+ (objs): Add remote-${REMOTE}.o.
+
+Wed Mar 10 15:12:24 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.63.5.
+
+ * implicit.c (pattern_search): Fix "dependent"->"dependency" in
+ "Rejecting impossible" -d msg.
+
+ * file.c (file_hash_enter): New local vars {OLD,NEW}BUCKET. Store
+ mod'd values there; never mod {OLD,NEW}HASH.
+
+Mon Mar 8 13:32:48 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * remake.c [eta10]: Include <fcntl.h> instead of <sys/file.h>.
+
+ * compatMakefile (VPATH): Set this to @srcdir@.
+ (srcdir): Set this to $(VPATH).
+
+ * main.c (main): New local var DIRECTORY_BEFORE_CHDIR. Save in it
+ a copy of CURRENT_DIRECTORY after the first getcwd. Use it
+ instead of CURRENT_DIRECTORY when chdir'ing back before re-execing.
+
+ * remake.c (notice_finished_file): Pass missing SEARCH arg to f_mtime.
+
+ * read.c (read_makefile): Remove extraneous arg to parse_file_seq.
+
+Mon Feb 22 14:19:38 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile ($(infodir)/$(instname).info): Use , instead of /
+ as the sed delimiter char.
+
+Sun Feb 21 14:11:04 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.63.4.
+
+ * rule.h (struct rule): Removed `subdir' member.
+ * rule.c (new_pattern_rule): No need to clear it.
+ (count_implicit_rule_limits): Set the `changed' flag in each dep
+ that refers to a nonexistent directory. No longer set rule-global
+ `subdir' flag with that information.
+ (print_rule_data_base): Don't record info on `subdir' flags.
+
+ * implicit.c (pattern_search): Check the DEP->changed flag rather
+ than the (now gone) RULE->subdir flag. Also test CHECK_LASTSLASH;
+ if it is set, the file might exist even though the DEP->changed
+ flag is set.
+
+ * rule.c (count_implicit_rule_limits): Pass "", not ".", as file
+ name arg to dir_file_exists_p to check for existence of directory.
+
+ * implicit.c (pattern_search): Inside dep-finding loop, set
+ CHECK_LASTSLASH from the value recorded in CHECKED_LASTSLASH[I],
+ rather than computing it anew.
+
+ * commands.c (set_file_variables): Must alloca space for PERCENT
+ and copy it, to avoid leaving the trailing `)' in the value.
+
+ * misc.c (remove_comments): Fixed backslash-checking loop
+ condition to allow it to look at the first char on the line.
+ P2 >= LINE, not P2 > LINE.
+
+ * compatMakefile ($(bindir)/$(instname)): Before moving $@.new to
+ $@, rm $@.old and mv $@ to $@.old.
+
+ * variable.c (try_variable_definition): Take new args FILENAME and
+ LINENO. Fatal if the variable name is empty.
+ * read.c (read_makefile): Change callers.
+ * main.c (main): Likewise.
+
+ * compatMakefile (group): Define to @KMEM_GROUP@, autoconf magic
+ that configure will replace with the group owning /dev/kmem.
+
+Mon Feb 8 14:26:43 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * vpath.c (vpath_search): Take second arg MTIME_PTR, pass thru to
+ selective_vpath_search.
+ (selective_vpath_search): Take second arg MTIME_PTR.
+ If the dir cache thinks a file exists, stat it to make sure, and
+ put the modtime in *MTIME_PTR.
+ * remake.c (library_search): Take second arg MTIME_PTR.
+ When we find a match, record its mtime there.
+ Pass MTIME_PTR through to vpath_search to do same.
+ (f_mtime): Pass &MTIME as new 2nd arg to {vpath,library}_search;
+ store it in FILE->last_mtime if set nonzero.
+ * implicit.c (pattern_search): Pass nil 2nd arg to vpath_search.
+
+ * compatMakefile (remote.o): Prepend `$(srcdir)/' to `remote-*.c',
+ so globbing looks somewhere it will find things.
+
+ * compatMakefile ($(infodir)/$(instname).info): Install `make.info*'
+ not `$(srcdir)/make.info*'; no need to use basename.
+
+Fri Feb 5 12:52:43 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.63.3.
+
+ * compatMakefile (install): Add missing ;\s.
+
+ Make -, @, and + prefixes on a pre-expanded command line affect
+ all lines in the expansion, not just the first.
+ * commands.h (struct commands): Replace `lines_recurse' member
+ with `lines_flags'.
+ (COMMANDS_{RECURSE,SILENT,NOERROR}): New macros, bits to set in
+ that flag byte.
+ * commands.c (chop_commands): Set `lines_flags' instead of
+ `lines_recurse'. Record not only + but also @ and - prefixes.
+ * remake.c (notice_finished_file): Check the COMMANDS_RECURSE bit
+ in FILE->cmds->lines_flags, rather than FILE->cmds->lines_recurse.
+ * job.c (start_job_command): Replaced RECURSIVE and NOPRINT local
+ var with FLAGS; initialize it to the appropriate `lines_flags' byte.
+ Set CHILD->noerror if the COMMANDS_NOERROR bit is set in FLAGS.
+ Set the COMMANDS_SILENT bit in FLAGS for a @ prefix.
+
+ * remake.c (update_goal_chain): Set G->file to its prev after
+ checking for G being finished, since that check needs to examine
+ G->file.
+
+ * configure.in (union wait check) [HAVE_WAITPID]: Try using
+ waitpid with a `union wait' STATUS arg. If waitpid and union wait
+ don't work together, we should not use union wait.
+
+ * Version 3.63.2.
+
+ * remake.c (update_goal_chain): When G->file->updated, move
+ G->file to its prev. We aren't finished until G->file is nil.
+
+Thu Feb 4 12:53:04 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (starting_directory): New global variable.
+ (main): Set it to cwd after doing -Cs.
+ (log_working_directory): Use it, rather than computing each time.
+ * make.h: Declare it.
+
+ * compatMakefile (SHELL): Define to /bin/sh for losing Unix makes.
+
+ * main.c (decode_env_switches): Allocate (1 + LEN + 1) words for
+ ARGV, rather than LEN words plus one byte.
+
+Wed Feb 3 18:13:52 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile ($(bindir)/$(instname)): Put - before
+ install_setgid command line, so its failure won't be an error.
+ (infodir): New variable.
+ (install): Depend on $(infodir)/$(instname).info.
+ ($(infodir)/$(instname).info): New target.
+
+ * read.c (read_makefile): If FILENAMES is nil when we see a line
+ starting with a tab, don't treat it as a command. Just fall
+ through, rather than giving an error.
+
+ * read.c (read_makefile): If the NO_TARGETS flag is set when we see a
+ command line, don't clear it before continuing. We want
+ subsequent command lines to be ignored as well.
+
+ * job.c (new_job): Before expanding each command line, collapse
+ backslash-newline combinations that are inside var or fn references.
+
+Mon Feb 1 16:00:13 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (exec_prefix): Default to $(prefix), not /usr/local.
+
+ * compatMakefile (make.info): Pass -I$(srcdir) to makeinfo.
+
+ * job.c [POSIX] (unblock_sigs): Made global.
+ [!POSIX] (unblock_sigs): Move defns to job.h.
+ * job.h [POSIX] (unblock_sigs): Declare.
+
+Sun Jan 31 19:11:05 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): In vpath parsing, after finding the
+ pattern token, take entire rest of line as the search path, not
+ just the next token.
+
+ * compatMakefile (remote.o): Depend on remote-*.c.
+
+Thu Jan 28 16:40:29 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * commands.c (set_file_variables): Don't define any F or D versions.
+ * variable.c (define_automatic_variables): Define them here as
+ recursively-expanded variables that use the dir and notdir funcs.
+
+ * variable.c (target_environment): In v_default case, don't export
+ o_default or o_automatic variables.
+
+ * configure.in (union wait check): Remove ` and ' inside C code;
+ they confuse the shell script.
+
+Mon Jan 25 13:10:42 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.63.1.
+
+ * vpath.c (construct_vpath_list): When skipping further processing
+ of an elt that is ".", don't also skip the code that pushes P past
+ the next separator.
+
+ * compatMakefile (distclean): Don't remove make-*.
+
+ * configure.in (HAVE_UNION_WAIT): Try to use WEXITSTATUS if it's
+ defined. If one cannot use WEXITSTATUS with a `union wait'
+ argument, we don't want to believe the system has `union wait' at all.
+
+ * remake.c (update_file): Do nothing to print "up to date" msgs.
+ (update_goal_chain): Do it here instead.
+ Use the `changed' flag of each goal's `struct dep' to keep track
+ of whether files_remade (now commands_started) changed around a
+ call to update_file for that goal.
+ When a goal is finished, and its file's update_status is zero (i.e.,
+ success or nothing done), test the `changed' flag and give an "up
+ to date" msg iff it is clear.
+ * make.h (files_remade): Renamed to commands_started.
+ * remake.c: Changed defn.
+ (update_goal_chain): Changed uses.
+ * job.c (start_job_command): Increment commands_started here.
+ (reap_children): Not here.
+
+ * remake.c (update_goal_chain): Don't do anything with files'
+ `prev' members. update_file now completely handles this.
+
+ * variable.c (target_environment): Don't expand recursive
+ variables if they came from the environment.
+
+ * main.c (define_makeflags): For flags with omitted optional args,
+ store {"", 0} with ADD_FLAG. When constructing FLAGSTRING, a flag
+ so stored cannot have more flags appended to the same word.
+
+Fri Jan 22 14:46:16 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (print_variable_set): In vars/bucket calculation,
+ don't spuriously multiply by 100.
+
+ * Version 3.63.
+
+ * job.c [!HAVE_UNION_WAIT] (WTERMSIG, WCOREDUMP, WEXITSTATUS):
+ Don't define if already defined.
+
+ * remake.c (update_file): Don't keep track of the command_state before
+ calling update_file_1. Remove local variable COMMANDS_FINISHED,
+ and don't test it to decide to print the "is up to date" msg.
+ Testing for files_remade having changed should always be sufficient.
+ The old method lost when we are called in the goal chain run on a
+ makefile, because the makefile's command_state is already
+ `cs_finished' from the makefile chain run.
+
+ * misc.c [HAVE_SETRE[GU]ID]: Test these to decl setre[gu]id.
+
+ * configure.in: Rewrote wait checking.
+ Use AC_HAVE_HEADERS to check for <sys/wait.h>.
+ Use AC_HAVE_FUNCS to check for waitpid and wait3.
+ Use a compile check to test just for `union wait'.
+ * job.c: Rewrote conditionals accordingly.
+ [HAVE_WAITPID]: Test this only to define WAIT_NOHANG.
+ [HAVE_WAIT3]: Likewise.
+ [HAVE_UNION_WAIT]: Test this to define WAIT_T and W*.
+
+ * configure.in: Set CFLAGS and LDFLAGS before all checks.
+
+ * dir.c: Add static forward decls of {open,read}_dirstream.
+
+Thu Jan 21 17:18:00 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.31.
+
+ * job.c [NGROUPS_MAX && NGROUPS_MAX==0]: #undef NGROUPS_MAX.
+
+ * compatMakefile (CFLAGS, LDFLAGS): Set to @CFLAGS@/@LDFLAGS@.
+ * build.template (CFLAGS, LDFLAGS): Same here.
+ * configure.in: AC_SUBST(CFLAGS) and LDFLAGS.
+ Set them to -g if not defined in the environment.
+
+ * remake.c (library_search): Use LIBNAME consistently, setting it
+ only once, to be the passed name sans `-l'.
+ Pass new var FILE to be modified by vpath_search.
+
+Mon Jan 18 14:53:54 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.30.
+
+ * job.c (start_waiting_jobs): Return when job_slots_used is equal to
+ job_slots.
+
+ * configure.in: Add AC_CONST for the sake of getopt.
+
+ * read.c (read_makefile): Continue after parsing `override'
+ directive, rather than falling through to lossage.
+ Check for EOL or blank after "override define".
+
+ * compatMakefile (.c.o, remote.o): Put $(CFLAGS) after other switches.
+
+Fri Jan 15 12:52:52 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.29.
+
+ * main.c (define_makeflags): After writing everything into
+ FLAGSTRING, only back up two chars if [-1] is a dash, meaning we
+ just wrote " -". Always terminate the string at *P.
+
+ * remake.c (library_search): When constructing names in std dirs,
+ use &(*LIB)[2] for the stem, not LIBNAME (which points at the
+ buffer we are writing into!).
+
+Thu Jan 14 13:50:06 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Set IN_IGNORED_DEFINE for "override
+ define" when IGNORING is true.
+
+ * compatMakefile (distclean): Remove config.status and build.sh.
+
+Wed Jan 13 16:01:12 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.28.
+
+ * misc.c (xmalloc, xrealloc): Cast result of malloc/realloc to
+ (char *).
+
+ * arscan.c (ar_scan) [AIAMAG]: Cast read arg to (char *).
+
+ * variable.c (define_automatic_variables): Override SHELL value for
+ origin o_env_override as well as o_env.
+
+ * GNUmakefile (build.sh.in): Don't replace %globobjs%. Instead,
+ add the names of the glob objects (w/subdir) to %objs%.
+ * build.template (globobjs): Removed.
+ Take basename of $objs before linking.
+
+Tue Jan 12 12:31:06 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.27.
+
+ * configure.in (AC_OUTPUT): Also edit build.sh.
+ * build.template: New file.
+ * GNUmakefile (build.sh.in): New rule to create it from build.template.
+ (make-$(version).tar.Z): Depend on build.sh.in.
+
+ * main.c (die): Call print_data_base if -p.
+ (main): Don't call it here.
+
+ * compatMakefile (defines): Add @DEFS@. configure should turn this
+ into -DHAVE_CONFIG_H.
+
+Mon Jan 11 14:39:23 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.26.
+
+ * misc.c (init_access): Surround with #ifdef GETLOADAVG_PRIVILEGED.
+ ({make,user,child}_access) [! GETLOADAVG_PRIVILEGED]: Make no-op.
+ * compatMakefile (install_setgid): New var, set by configure.
+ (install): Install setgid $(group) only if $(install_setgid) is true.
+
+Fri Jan 8 15:31:55 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (load_too_high): If getloadavg fails with errno==0, give a
+ message saying that load limits are not supported.
+
+ * vpath.c (construct_vpath_list): Rewrote path deletion code to
+ not try to use PATH's next link after freeing PATH.
+
+ * main.c (define_makeflags): Rewritten; now handles string-valued
+ option, and has no arbitrary limits.
+ (switches): Set `toenv' flag for -I and -v.
+
+ * main.c (decode_env_switches): Cast return value of alloca to char *.
+
+ * misc.c (child_access) [HAVE_SETREUID, HAVE_SETREGID]: Use
+ setre[gu]id in place of set[gu]id.
+
+Wed Jan 6 15:06:12 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (main): Define MAKEOVERRIDES, MAKE, and MAKE_COMMAND with
+ origin o_default.
+
+ * make.h [POSIX]: Don't test this to use ANSI_STRING.
+ Testing STDC_HEADERS should be sufficient.
+
+ * job.h: Declare start_waiting_jobs.
+
+ * read.c (read_makefile): Add missing parens in if stmt that find
+ conditional directives.
+
+ * main.c (main): Declare init_dir.
+ * implicit.c (pattern_search): Always use two % specs in a
+ DEBUGP2, and always pass two non-nil args.
+ Cast field width args to int.
+ Add missing parens in !RULE->subdir if stmt.
+ * function.c (expand_function, patsubst_expand): Add parens around
+ assignments inside `while' stmts.
+ * commands.c (print_commands): Cast field width args to int.
+
+ * read.c (do_define): Cast return value of alloca to (char *).
+
+ * main.c (init_switches): New function, broken out of decode_switches.
+ (decode_switches): Take new arg ENV. If set, ignore non-option
+ args; print no error msgs; ignore options with clear `env' flags.
+ (decode_env_switches): Rewritten to chop envar value into words
+ and pass them to decode_switches.
+ (switches): Set `env' flag for -I and -v.
+
+ * dir.c (init_dir): Cast free to __glob_closedir_hook's type.
+
+Tue Jan 5 14:52:15 1993 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.25.
+
+ * job.c [HAVE_SYS_WAIT || !USG]: Don't #include <sys/time.h> and
+ <sys/resource.h>. <sys/time.h> interacts badly with <time.h>, and
+ we don't need these anyway.
+
+ * configure.in (AC_HAVE_FUNCS): Check for setre[gu]id.
+ * misc.c ({user,make}_access): Test #ifndef HAVE_SETRE[GU]ID, not
+ #ifdef POSIX || USG. SunOS 4.1 is supposedly POSIX.1 compliant,
+ but its set[gu]id functions aren't; its setre[gu]id functions work.
+
+ * misc.c ({user,make,child}_access): Give name of caller in error msgs.
+
+ * job.c (load_too_high): Say "cannot enforce load limit" in error msg.
+
+ * configure.in: Call AC_PROG_CC.
+ * compatMakefile (CC): Define to @CC@ (autoconf magic).
+
+ * compatMakefile: Add .NOEXPORT magic target.
+
+Mon Jan 4 17:00:03 1993 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (print_version): Updated copyright to include 93.
+
+Thu Dec 31 12:26:15 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * make.h [_AIX]: Don't declare alloca.
+
+Tue Dec 29 13:45:13 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.24.
+
+ * compatMakefile (objs): Add signame.o.
+ (srcs): Add signame.[ch].
+
+ * compatMakefile (srcs): Add config.h.in.
+ (remote.o): Add -I. before -I$(srcdir).
+
+Mon Dec 28 15:51:26 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.23.
+
+ * read.c (readline): Fatal when LEN==0, indicating a line starting
+ with a NUL.
+ (readline): Take new arg LINENO, for use in error msg.
+ (read_makefile, do_define): Pass it.
+
+ * compatMakefile (glob/libglob.a): Pass -DHAVE_CONFIG_H in CPPFLAGS.
+ (.c.o): Add -I. before -I$(srcdir).
+
+Wed Dec 23 12:12:04 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Accept and ignore a rule with no targets.
+
+ * compatMakefile (ALLOCA_SRC): New variable.
+ (srcs): Include its value.
+
+ * read.c (struct conditional): Renamed member `max_ignoring' to
+ `allocated'; added new member `seen_else'.
+ (conditional_line): Initialize seen_else flag when starting an `if...';
+ set it when we see an `else'; fatal if set when we see `else'.
+ (read_makefile): Fatal "missing `endif'" if there are any pending
+ conditionals, not just if we are still ignoring.
+
+Tue Dec 22 15:36:28 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (manext): Set to 1, not l.
+ ($(mandir)/$(instname).$(manext)): Use $(srcdir) for make.man in cmds.
+
+ * file.c (file_hash_enter): Don't call uniquize_deps here.
+ * read.c (record_files): Likewise.
+ * implicit.c (pattern_search): Likewise.
+ * commands.c (set_file_variables): Call it only here.
+
+ * default.c (default_variables) [__convex__]: FC=fc.
+
+ * variable.c (target_environment): Expand the values of recursively
+ expanded variables when putting them into the environment.
+ * expand.c (recursively_expand): Made global.
+ * make.h (recursively_expand): Declare it.
+
+ * remake.c (check_dep): Set FILE->command_state to cs_deps_running
+ when a dep's command_state is cs_running or cs_deps_running.
+
+ * read.c (read_makefile): Changed error msg for spurious cmds to
+ not say "first target".
+
+Sun Dec 20 17:56:09 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * configure.in: Do AC_CONFIG_HEADER right after AC_INIT.
+ * make.h (HAVE_CONFIG_H): #include "config.h", then #define this.
+ * compatMakefile (config.h, configure, config.h.in): New rules.
+ (defines): Removed @DEFS@.
+
+Thu Dec 17 16:11:40 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (realclean): Just depend on distclean; no cmds.
+ (distclean): Do what realclean did before; also remove Makefile and
+ config.h; don't remove configure.
+ (info, dvi): New targets; depend on make.{info,dvi}.
+ (doc): Removed target.
+ (MAKEINFO, TEXI2DVI): New vars.
+ (make.info, make.dvi): Use them instead of explicit cmds.
+
+Wed Dec 16 16:25:24 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * configure.in: Added fcntl.h to AC_HAVE_HEADERS. getloadavg cares.
+
+Wed Dec 9 15:21:01 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (long_option_aliases): Add --new-file alias for -W.
+
+ * default.c (default_variables): Change all C++ to CXX and C++FLAGS
+ to CXXFLAGS.
+
+ * read.c (do_define): Expand the variable name before using it.
+
+ * main.c (main): Define variable "MAKE_COMMAND" to argv[0];
+ define "MAKE=$(MAKE_COMMAND) $(MAKEOVERRIDES)" always.
+
+ * remake.c (library_search): Search for libNAME.a in cwd; look in
+ vpath before looking in standard dirs, not after.
+ Changed order of std dirs to: /lib, /usr/lib, ${prefix}/lib.
+
+Mon Nov 23 14:57:34 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * default.c (default_pattern_rules, default_terminal_rules): Added
+ brackets around initializers.
+
+ * variable.c (try_variable_definition): Don't check for LINE[0]=='\t'.
+ (try_variable_definition): Expand the name before defining the var.
+
+ * job.c (init_siglist): Removed function.
+ Removed decl of `sys_siglist'.
+ * make.h [! HAVE_SYS_SIGLIST]: #include "signame.h".
+ [HAVE_SYS_SIGLIST && !SYS_SIGLIST_DECLARED]: Declare sys_siglist
+ only under these conditions.
+ * main.c (main): Don't declare init_siglist.
+ (main) [! HAVE_SYS_SIGLIST]: Call signame_init instead of init_siglist.
+
+Wed Nov 18 14:52:51 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (record_files): Don't try to append to FIRSTDEPS if it's
+ nil; instead just set it to MOREDEPS.
+
+Mon Nov 16 17:49:17 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * vpath.c (construct_vpath_list): Initialize P to DIRPATH before
+ loop that sets MAXELEM.
+
+Fri Nov 13 18:23:18 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.22.
+
+Thu Nov 12 15:45:31 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (start_job_command): Under -n, increment files_remade after
+ processing (i.e., printing) all command lines.
+
+Tue Nov 10 15:33:53 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * read.c (record_files): Append new deps if this rule has no
+ commands; prepend them to existing deps if this rule has no commands.
+
+ * dir.c (open_dirstream): Return nil if DIR->contents->files is nil.
+
+ * read.c (parse_file_seq): Removed last arg STRIP. Always strip `./'s.
+ (read_makefile): Changed callers.
+ * function.c (string_glob): Likewise.
+ * rule.c (install_pattern_rule): Likewise.
+
+Mon Nov 9 17:50:16 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (files_remade): Made global.
+ (notice_finished_file): Don't increment files_remade here; this
+ function gets called in many situations where no remaking was in
+ fact done.
+ * job.c (reap_children): Do it here instead, when we know that
+ actual commands have been run for the file.
+ * make.h (files_remade): Declare it.
+
+Thu Nov 5 18:26:10 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * vpath.c (construct_vpath_list): Allow blanks as well as colons to
+ separate elts in the search path.
+
+ * read.c (read_makefile): Don't fatal on extra tokens in `vpath'.
+ The search path can contain spaces now.
+
+Tue Nov 3 20:44:32 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (check): New target; no-op.
+
+ * file.c (file_hash_enter): Mod OLDHASH by FILE_BUCKETS after
+ testing for OLDHASH==0 but before using the value.
+ (rename_file): Don't mod OLDHASH by FILE_BUCKETS before passing it
+ to file_hash_enter.
+
+ * file.c (rename_file): Notice when OLDFILE->cmds came from
+ default.c, and don't try to print ->filename in that case.
+
+Sun Oct 25 01:48:23 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (update_file): Don't process F->also_make here.
+ (notice_finished_file): Don't process FILE->also_make if no attempt
+ to update FILE was actually made.
+ Fixed to call f_mtime directly to refresh their modtimes.
+
+Sat Oct 24 22:08:59 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (find_percent): Don't increment P again after skipping
+ an escaped %.
+
+ * expand.c (variable_expand): In call to patsubst_expand, don't
+ find `%'s ourselves; let that function do it.
+
+ * read.c (read_makefile: record_waiting_files): Don't call
+ record_files if FILENAMES is nil.
+ (read_makefile): All alternatives in the parsing, except for rule
+ lines, fall through to the end of the loop. At the end of the
+ loop, do record_waiting_files so we notice later spurious cmds.
+
+Fri Oct 23 15:57:37 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (define_automatic_variables): Free old value of SHELL
+ before replacing it.
+
+Thu Oct 15 18:57:56 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (.c.o): Add -I$(srcdir)/glob to flags.
+
+ * dir.c (open_dirstream): Cast return value to __ptr_t.
+
+ * default.c (default_variables: "GET") [_IBMR2]: Use USG defn.
+
+ * make.h (MAXPATHLEN): Moved out of #ifndef POSIX.
+ (GET_PATH_MAX): Moved from #ifdef POSIX to #ifdef PATH_MAX #else.
+ Define as (get_path_max ()).
+ [! PATH_MAX] (NEED_GET_PATH_MAX): Define.
+ [! PATH_MAX] (get_path_max): Declare fn.
+ * misc.c [NEED_GET_PATH_MAX] (get_path_max): New function.
+
+Mon Oct 12 13:34:45 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.21.
+
+ * job.c (sys_siglist): Only declare #ifndef SYS_SIGLIST_DECLARED.
+ * make.h [! HAVE_SYS_SIGLIST && HAVE__SYS_SIGLIST]: #define
+ SYS_SIGLIST_DECLARED.
+
+ * dir.c (file_impossible): When initializing DIR->contents, set
+ DIR->contents->dirstream to nil.
+
+ * compatMakefile (GLOB): Define new variable.
+ (objs): Use it, rather than glob/libglob.a explicitly.
+
+ * read.c (parse_file_seq): When stripping "./", handle cases like
+ ".///foo" and "./////".
+ * file.c (lookup_file, enter_file): Likewise.
+
+Sun Oct 11 17:00:35 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * dir.c (struct dirstream, {open,read}_dirstream): New
+ data type and functions to read a directory sequentially.
+ (init_dir): New function to hook it into glob.
+ * main.c (main): Call init_dir.
+
+ * compatMakefile (objs): Added glob/libglob.a.
+ * configure.in: Remove code to test for glob.
+
+Fri Oct 9 12:08:30 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (record_files): Generalized test for NAME pointing
+ somewhere into F->name.
+
+ * variable.c (define_variable_in_set): Free old value when replacing.
+
+ * read.c (do_define): Free the linebuffer before returning.
+ (record_files): When clearing .SUFFIXES deps, free their data.
+ (multi_glob): Free OLD and its data when replacing it with results
+ of glob run.
+
+ * commands.c (set_file_variables): Use alloca in place of xmalloc
+ for temp space for $^, $?, et al.
+
+ * dir.c (struct directory): New member `contents' replaces `files'
+ and `dirstream'.
+ (struct directory_contents): New type.
+ (directories_contents): New hash table.
+ (dir_struct_file_exists_p): Take a struct directory_contents.
+ (dir_file_exists_p): Pass it the `contents' member of the dir found.
+ (dir_struct_file_exists_p): Renamed to dir_contents_file_exists_p;
+ made static. Return 0 if DIR is nil (meaning it couldn't be stat'd).
+ (dir_file_exists_p, find_directory): Change all callers.
+ (file_impossible): Use DIR->contents, initializing it if nil.
+ (print_dir_data_base): Use DIR->contents, and print out device and
+ inode numbers with each directory.
+
+ * Changes for performance win from John Gilmore <gnu@cygnus.com>:
+ * dir.c (DIRECTORY_BUCKETS): Increase to 199.
+ (DIRFILE_BUCKETS): Decrease to 107.
+ (find_directory): Allocate and zero a multiple of
+ sizeof (struct dirfile *), not of sizeof (struct dirfile).
+ (dir_struct_file_exists_p): New function, nearly all code from
+ dir_file_exists_p.
+ (dir_file_exists_p): Just call find_directory+dir_struct_file_exists_p.
+ * vpath.c (selective_vpath_search): Remove redundant
+ dir_file_exists_p call.
+
+ * configure.in: Comment out glob check; always use our code.
+
+Fri Oct 2 19:41:20 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * make.h [! HAVE_SYS_SIGLIST && HAVE__SYS_SIGLIST]: #define
+ HAVE_SYS_SIGLIST; after doing #define sys_siglist _sys_siglist, we
+ do have it.
+
+Wed Sep 30 19:21:01 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (main): Don't do -w automatically if -s.
+
+Tue Sep 29 21:07:55 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (printed_version): Move variable inside print_version.
+ (print_version): Return immediately if printed_version is set.
+ (die): Don't test printed_version here.
+ (decode_switches): Under -v, do print_version before giving usage.
+ (DESCRIPTION_COLUMN): New macro.
+ (decode_switches): Use it when printing the usage message.
+ Leave at least two spaces between options and their descriptions.
+
+Fri Sep 25 13:12:42 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.20.
+
+Wed Sep 16 16:15:22 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Save errno value from trying to open
+ FILENAME, and restore it before erring; otherwise we get the errno
+ value from the last elt of the search path.
+
+Tue Sep 15 15:12:47 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (long_option_aliases): Add --stop for -S.
+
+ * read.c (word1eq): Do strncmp before dereferencing someplace that
+ may be out in space.
+
+Wed Sep 9 15:50:41 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (notice_finished_file): If all the command lines were
+ recursive, don't do the touching.
+
+ * job.c (start_job_command): Don't check for + here.
+ * commands.c (chop_commands): Do it here instead.
+
+ * default.c (default_terminal_rules): Prepend + to cmds for RCS.
+
+Wed Sep 2 17:53:08 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (objs): Include $(ALLOCA).
+
+ * make.h [CRAY]: Move #define signal bsdsignal to before #includes.
+
+Thu Aug 27 17:45:43 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * read.c (default_include_directories): Add INCLUDEDIR first.
+ * compatMakefile (includedir): Define.
+ (defines): Add -D for INCLUDEDIR="$(includedir)".
+
+ * read.c (read_makefile): Grok multiple files in `include';
+ globbing too.
+
+ * remake.c (library_search): New function.
+ (library_file_mtime): Remove function.
+ (f_mtime): Use library_search instead of library_file_mtime.
+ * compatMakefile (libdir): Define.
+ (defines): Add -D for LIBDIR="$(libdir)".
+ * make.texinfo (Libraries/Search): Document change.
+
+ * file.c (rename_file): Fix file_hash_enter call with missing arg.
+
+Wed Aug 26 17:10:46 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.19.
+
+ * main.c (main): Set command_state to cs_finished for temp files
+ made for stdin makefiles.
+
+ * main.c (decode_switches): Don't tell getopt to return non-option
+ args in order.
+ Ignore an argument of `-'.
+
+Thu Aug 20 13:36:04 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c (start_job_command): If (touch_flag && !RECURSIVE), ignore
+ the command line and go to the next.
+ (notice_finished_file): Under -t, touch FILE.
+ * remake.c (remake_file): Don't touch it here.
+
+Wed Aug 19 16:06:09 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * function.c (pattern_matches): Use temporary for strlen (WORD)
+ instead of two function calls.
+
+ * compatMakefile (LOAD_AVG): Remove variable and comments.
+
+Tue Aug 18 14:58:58 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * make.texinfo (Running): Node renamed to `make Invocation'.
+
+Fri Aug 14 12:27:10 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * arscan.c (ar_name_equal): Don't compare [MAX-3..MAX] if
+ NAMELEN != MEMLEN.
+
+Thu Aug 13 17:50:09 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.18.
+
+ * main.c: Don't #include <time.h>; make.h already does.
+
+Mon Aug 10 17:03:01 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * implicit.c (pattern_search): Fixed copying of suffix when building
+ also_make elts.
+
+ * function.c (expand_function: `shell'): Make sure BUFFER is
+ null-terminated before replacing newlines.
+
+ * compatMakefile (mandir): Use man$(manext), not always manl.
+
+Sun Aug 2 01:42:50 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * rule.c (new_pattern_rule): Not static.
+ * rule.h: Declare it.
+
+ * file.c (file_hash_enter): New function, most code from rename_file.
+ (rename_file): Call it.
+ * file.h (file_hash_enter): Declare it.
+
+ * dep.h: Doc fix.
+
+Thu Jul 30 15:40:48 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (decode_switches): Handle usage_and_exit when building
+ long options vector.
+
+ * default.c (default_terminal_rules): Make RCS rules use $(CHECKOUT,v).
+ (default_variables): Define CHECKOUT,v (hairy).
+
+ * make.h [!HAVE_SYS_SIGLIST && HAVE__SYS_SIGLIST]: #define
+ sys_siglist to _sys_siglist.
+
+Sun Jul 26 16:56:32 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * NEWS: Add header and tail copyright info like Emacs NEWS.
+
+ * make.h [ANSI_STRING]: Don't #define index, rindex, bcmp, bzero,
+ bcopy if already #define'd.
+ [STDC_HEADERS] (qsort, abort, exit): Declare here.
+ [! __GNU_LIBRARY__ && !POSIX]: Not here.
+
+ * make.h [_AIX]: #pragma alloca first thing.
+
+ * job.c (start_waiting_job): Set the command_state to cs_running
+ when we queue a job on waiting_jobs.
+
+Fri Jul 24 02:16:28 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.c (define_automatic_variables): Use "" instead of nil
+ for empty value.
+
+Thu Jul 23 22:31:18 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.17.
+
+ * main.c (struct command_switch.type): Add alternative usage_and_exit.
+ (command_switches): Add -h/--help.
+
+Thu Jul 16 14:27:50 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * GNUmakefile (make-$(version).tar.Z): Include NEWS, not CHANGES.
+ * README.template: Mention NEWS.
+ * CHANGES: Renamed to NEWS.
+
+ * main.c [! STDC_HEADERS] [sun]: Don't declare exit.
+
+Tue Jul 14 18:48:41 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (main): Set -o files' command_states to cs_finished.
+
+ * rule.c (count_implicit_rule_limits): Decrement num_pattern_rules
+ when tossing a rule.
+
+ * main.c (main): Use alloca only in simple local var assignment,
+ for braindead SGI compiler.
+
+ * rule.c (print_rule_data_base): Barf if num_pattern_rules is
+ inconsistent with the number computed when listing them.
+
+Mon Jul 13 17:51:53 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * commands.c (set_file_variables): For $? and $^ elts that are archive
+ member refs, use member name only.
+
+Fri Jul 10 00:05:04 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * variable.h (struct variable.export): Add new alternative v_ifset.
+ * variable.c (target_environment): Check for it.
+ (define_automatic_variables): Set it for MAKEFILES.
+
+Thu Jul 9 21:24:28 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (objs): Remove getloadavg.o; $(extras) gets it.
+ (remote.o): Use $(srcdir)/remote.c, not $remote.c<.
+ (distclean, mostlyclean): New targets.
+
+Tue Jul 7 19:12:49 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.16.
+
+ * compatMakefile (config.status): Remove rule.
+
+ * job.c (start_waiting_job): Free C after using C->file, not before.
+
+Sat Jul 4 20:51:49 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * commands.c, job.c, main.c, make.h, remote-cstms.c: Use #ifdef
+ HAVE_* instead of #ifndef *_MISSING.
+ * configure.in: Use AC_HAVE_FUNCS instead of AC_MISSING_FUNCS (gone).
+
+Thu Jul 2 18:47:52 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * main.c (main): makelevel>0 or -C implies -w.
+
+Tue Jun 30 20:50:17 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * file.c, job.c, function.c: Don't #include <errno.h>.
+ make.h: Do it here instead.
+ * arscan.c (ar_member_touch): Don't declare errno.
+
+Thu Jun 25 17:06:55 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * GNUmakefile (make-$(version).tar.Z): Depend on INSTALL, configure.in.
+
+ * remake.c (update_file): If commands or deps are running after
+ update_file_1 returns, break out of the :: rule (->prev) loop and
+ just return.
+
+ * job.c (job_next_command): New function; code from start_job.
+ (start_job_command): Renamed from start_job. Call job_next_command
+ and recurse for empty command lines and -n.
+ (start_waiting_job): Call start_job_command, not start_job.
+ (new_job): Call job_next_command to prime the child structure, and
+ then call start_waiting_job.
+ (reap_children): Use job_next_command and start_job_command.
+ (start_waiting_job): Call start_remote_job_p here, and store its
+ result in C->remote. If zero, check the load average and
+ maybe put C on waiting_jobs.
+ (start_job_command): Test CHILD->remote rather than calling
+ start_remote_job_p. Don't do load avg checking at all here.
+
+ * main.c (main): Don't handle SIGILL, SIGIOT, SIGEMT, SIGBUS,
+ SIGSEGV, SIGFPE or SIGTRAP.
+
+ * compatMakefile (glob/libglob.a): Don't pass srcdir to sub-make.
+ configure will set it in glob/Makefile.
+
+Wed Jun 24 19:40:34 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * dir.c [DIRENT] (direct): Don't define to dirent.
+ [! DIRENT] (direct): Define to dirent.
+ (dir_file_exists_p): Use struct dirent instead of struct direct.
+
+ * make.h (getcwd): No space between macro and ( for args!
+
+ * job.c (start_job): Don't put the job on waiting_jobs if
+ job_slots_used==0.
+
+ * make.texinfo (Missing): Shortened title.
+
+Tue Jun 23 18:42:21 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * file.c (remove_intermediates): Print "rm" commands under -n.
+
+Mon Jun 22 16:20:02 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.15.
+
+Fri Jun 19 16:20:26 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * arscan.c [M_UNIX]: #undef M_XENIX.
+
+Wed Jun 17 17:59:28 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * default.c (default_terminal_rules): Put @ prefix on RCS cmds.
+
+Tue Jun 16 19:24:17 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile (getloadavg.o): Removed special rule.
+ (CFLAGS): Don't include $(defines).
+ (.c.o): Define suffix rule.
+ (glob/libglob.a): Pass CPPFLAGS=$(defines) to submake.
+ (GETOPT_SRC, srcs, tagsrcs): Prefix files with $(srcdir)/.
+
+ * arscan.c (ar_name_equal): Moved local vars inside #if'd block.
+
+ * make.h (max): Removed.
+ * expand.c (variable_buffer_output): Don't use it.
+
+ * compatMakefile (INSTALL): Define.
+ (Makefile): New rule to make from Makefile.in.
+ (srcdir): Define.
+ (VPATH): Define.
+ (getloadavg.o, remote.o): Use autoconf $foo< hack.
+
+ * commands.c (fatal_error_signal): Removed return.
+
+Mon Jun 15 17:42:51 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.14.
+
+ * make.texinfo (Summary): New node.
+ (Special Targets): Mention .EXPORT_ALL_VARIABLES here.
+
+ * variable.c (max): Moved to make.h.
+
+ * compatMakefile (objs, srcs): Added ar & arscan.
+
+ * job.c (start_waiting_job): New function, 2nd half of new_job.
+ (new_job): Call it.
+ (start_waiting_jobs): New function.
+ * remake.c (update_goal_chain): Call start_waiting_jobs at the top
+ of the main loop.
+ * compatMakefile (objs, srcs): Removed load, added getloadavg.
+
+Fri Jun 12 19:33:16 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c (load_too_high): New function. Uses getloadavg.
+ (waiting_jobs): New variable.
+ (start_job): Don't call wait_to_start_job. Instead, if
+ load_too_high returns nonzero, add the child to the
+ `waiting_jobs' chain and return without starting the job.
+
+Thu Jun 11 00:05:28 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * expand.c (variable_buffer_output): Made global again.
+ * variable.h: And declare it.
+
+ * arscan.c (PORTAR): Define for all systems if PORT5AR is not defined.
+ (AR_NAMELEN, AR_TRAILING_SLASH): Removed.
+ (ar_scan): Don't use it. Don't #ifdef AR_TRAILING_SLASH; just look
+ for a slash in the archive at run time.
+ (ar_name_equal): Rewrote .o hacking to not use AR_NAMELEN, and to
+ cope with trailing-slash and non-trailing-slash archives.
+
+ * main.c (main) [! SETVBUF_REVERSED]: Test this instead of USGr3 et al.
+ [SETVBUF_REVERSED]: Always allocate a buffer ourselves.
+
+ * load.c (load_average) [sgi]: Use sysmp call.
+
+ * compatMakefile (INSTALL_DATA, INSTALL_PROGRAM): Define.
+ ($(bindir)/$(instname), $(mandir)/make.$(manext)): Use them.
+
+ * make.h [HAVE_VFORK_H]: #include <vfork.h>.
+ (vfork, VFORK_NAME): Don't define.
+ * job.c (start_job): Use "vfork" in place of VFORK_NAME.
+
+ * make.h [HAVE_LIMITS_H, HAVE_SYS_PARAM_H]: If #define'd, #include
+ the each file. Rearranged PATH_MAX hacking.
+ * job.c: Rearranged NGROUPS_MAX hacking.
+
+ * remake.c (fstat, time): Don't declare.
+
+ * compatMakefile (defines): Value is @DEFS@.
+ (LOADLIBES): Value is @LIBS@.
+ (extras): Value is @LIBOBJS@.
+ (ARCHIVES, ARCHIVES_SRC, ALLOCASRC): Removed.
+ * arscan.c, ar.c: Surround body with #ifndef NO_ARCHIVES.
+
+ * misc.c [! HAVE_UNISTD_H]: Test instead of !POSIX to decl get*id.
+
+ * make.h [GETCWD_MISSING]: Test instead of !USG && !POSIX et al.
+ (getcwd): Just declare if present. If not, declare as a macro
+ using getwd, and declare getwd.
+ [PATH_MAX] (GET_PATH_MAX): #define to PATH_MAX.
+ * main.c (main, log_working_directory): Use getcwd instead of getwd.
+
+ * main.c (main) [SETLINEBUF_MISSING]: Test this instead of USG.
+
+ * make.h (SIGHANDLER, SIGNAL): Removed.
+ (RETSIGTYPE): Define if not #define'd.
+ * main.c (main): Use signal in place of SIGNAL.
+
+ * main.c [SYS_SIGLIST_MISSING]: Test instead of USG.
+
+ * job.c (search_path) [GETGROUPS_MISSING]: Test instead of USG.
+ [HAVE_UNISTD_H]: Test instead of POSIX to not decl getgroups.
+
+ * main.c [! HAVE_UNISTD_H]: Test instead of !POSIX to decl chdir.
+ [! STDC_HEADERS]: Test instead of !POSIX to decl exit & atof.
+
+ * job.c (child_handler), commands.c (fatal_error_signal): Return
+ RETSIGTYPE instead of int.
+ * main.c (main): Declare fatal_error_signal and child_handler here
+ to return RETSIGTYPE; removed top-level decl of former.
+
+ * commands.c (fatal_error_signal), job.c (unblock_sigs, start_job),
+ main.c [SIGSETMASK_MISSING]: Test this instead of USG.
+
+Wed Jun 10 22:06:13 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c [HAVE_WAITPID]: Test this instead of USG.
+ [! HAVE_UNISTD_H]: Test this instead of !POSIX to declare misc fns.
+ (GID_T): Don't #define.
+ (search_path): Use gid_t instead of GID_T.
+ [GETDTABLESIZE_MISSING, SYS_SIGLIST_MISSING, DUP2_MISSING]: Test
+ these individually instead of USG for all.
+ * make.h (ctime): Don't declare. #include time.h instead.
+ [HAVE_UNISTD_H]: #include <unistd.h> and #define POSIX #ifdef
+ _POSIX_VERSION.
+ * dir.c [__GNU_LIBRARY__] (D_NAMLEN): Define to use d_namlen member.
+ * make.h [NEED_MEMORY_H]: Only include memory.h #ifdef this.
+
+ * arscan.c: Removed #ifdef mess about string.h et al.
+ Just #include make.h instead.
+ * make.h (fstat, atol): Declare.
+
+ * commands.c (fatal_error_signal): Don't use sigmask to check for
+ propagated signals; use ||s instead.
+ (PROPAGATED_SIGNAL_MASK): Removed.
+ (fatal_error_signal) [POSIX]: Use sigprocmask in place of sigsetmask.
+
+ * variable.c (variable_buffer, variable_buffer_length,
+ initialize_variable_output, variable_output): Moved to expand.c;
+ made all static.
+ (struct output_state, save_variable_output,
+ restore_variable_output): Removed.
+ * expand.c (initialize_variable_output): Put a NUL at the beginning
+ of the new buffer after allocating it.
+ (allocated_variable_expand_for_file): Don't use
+ {save,restore}_variable_output. Do it by hand instead, keeping
+ state on the stack instead of malloc'ing it.
+ (allocated_variable_expand): Removed.
+ * variable.h (allocated_variable_expand): Define here as macro.
+ (variable_buffer_output, initialize_variable_output,
+ save_variable_output, restore_variable_output): Removed decls.
+
+ * read.c (conditional_line): For an if cmd, if any elt of the
+ conditionals stack is ignoring, just push a new level that ignores
+ and return 1; don't evaluate the condition.
+
+Thu Jun 4 21:01:20 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (main): Put #ifdef's around frobbing SIGSYS and SIGBUS.
+
+ * job.c (getdtablesize): Don't declare or #define if already #define'd.
+
+Wed Jun 3 23:42:36 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * file.c (snap_deps): If `.EXPORT_ALL_VARIABLES' is a target, set
+ export_all_variables.
+ * make.texinfo (Variables/Recursion): Document .EXPORT_ALL_VARIABLES.
+
+Tue Jun 2 21:08:35 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.13.
+
+ * commands.c (set_file_variables): Calculate length for ^D and ?D
+ individually, making sure to give them at least enough space for "./".
+
+ * make.h [CRAY]: #define signal to bsdsignal.
+
+ * default.c (default_variables) [CRAY]: Define PC, SEGLDR,
+ CF77PPFLAGS, CF77PP, CFT, CF, and FC.
+
+ * arscan.c (AR_HDR_SIZE): Define to sizeof (struct ar_hdr), if it
+ wasn't defined by <ar.h>.
+
+Thu May 28 00:56:53 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.12.
+
+Tue May 26 01:26:30 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * rule.c (new_pattern_rule): Initialize LASTRULE to nil, not
+ pattern_rules.
+
+Mon May 25 19:02:15 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (decode_switches): Initialize all the long_option elt members.
+
+Thu May 21 16:34:24 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * make.texinfo (Text Functions): Correct filter-out description.
+
+Tue May 19 20:50:01 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * compatMakefile (realclean): Don't remove backup files.
+
+ * main.c (decode_switches): Allocate ARGC+1 elts in `other_args'.
+
+Sun May 17 16:38:48 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.62.11.
+
+Thu May 14 16:42:33 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * job.c (reap_children): Don't die if wait returns EINTR.
+
+Wed May 13 18:28:25 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c (reap_children): Always run the next command for a
+ successful target. If we are going to die, we don't want to leave
+ the target partially made.
+
+Tue May 12 00:39:19 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): After loop, if we only
+ have one word, check it for being a shell command.
+
+ * main.c (decode_switches): Allocate ARGC slots in other_args to
+ begin with, so we never need to worry about growing it.
+ If we get a non-option arg and POSIXLY_CORRECT is in the
+ environment, break out of the loop. After the loop, add all remaining
+ args to other_args list.
+
+ * main.c (decode_switches): For positive_int and floating switches
+ when optarg is nil, use next arg if it looks right (start with a
+ digit, or maybe decimal point for floating).
+
+ * variable.c (define_automatic_variables): Always set SHELL to
+ default if it comes from the environment. Set its export bit.
+ * make.texinfo (Environment): Document change.
+
+Mon May 11 00:32:46 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.62.10.
+
+ * compatMakefile (tags, TAGS): Use vars for cmds.
+ (ETAGS, CTAGS): Define.
+
+ * main.c (decode_switches): If a switches elt has a nil long_name,
+ make the long option name elt be "".
+ Fixed loop to not ignore all the options.
+
+ * make.texinfo (Option Summary): Added long options.
+
+ * main.c (switches): Changed -m's description to "-b".
+ (decode_switches): When printing the usage message, don't print
+ switches whose descriptions start with -.
+ When constructing the list of names for switch -C, search the
+ switches vector for switches whose descriptions are "-C".
+
+ * main.c (switches): Call -S --no-keep-going, not --dont-keep-going.
+ Call -I --include-dir, not --include-path.
+ (long_option_aliases): Added --new == -W, --assume-new == -W,
+ --assume-old == -o, --max-load == -l, --dry-run == -n, --recon == -n,
+ --makefile == -f.
+
+ * main.c (switches): Removed bogus "silent" elt.
+ (long_option_aliases): Define new var.
+ (decode_switches): Add long_option_aliases onto the end of the long
+ options vector created for getopt_long.
+ Look through long_option_aliases for extra names to list
+ in usage message.
+
+Sat May 9 00:21:05 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (log_working_directory): Fixed to properly not print the
+ leaving message when we haven't printed the entering message.
+
+Fri May 8 21:55:35 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * main.c (struct command_switch): Added elts `long_name',
+ `description', and `argdesc'.
+ (switches): Added initializers for new members.
+ (decode_switches): Rewritten to use getopt_long.
+ * compatMakefile (GETOPT, GETOPT_SRC): Define.
+ (objs, srcs): Include them.
+
+ * job.c (child_died): Renamed to dead_children; made static.
+ (child_handler): Increment dead_children instead of setting child_died.
+ (reap_children): Decrement dead_children instead of clearing
+ child_died. The point of all this is to avoid printing "waiting
+ for unfinished jobs" when we don't actually need to block.
+ This happened when multiple SIGCHLDs before reap_children was called.
+
+ * job.c (reap_children): If ERR is set, so we don't call start_job
+ on the child being reaped, instead set its command_state to
+ cs_finished.
+ (reap_children, child_handler, new_job): I added several
+ debugging printf's while fixing this. I left them in if (debug_flag)
+ because they may be useful for debugging this stuff again.
+
+Wed May 6 22:02:37 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): v_export is not 1.
+
+Mon May 4 17:27:37 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.9.
+
+ * variable.c (export_all_variables): New variable.
+ (target_environment): Export variables whose `export' member is
+ v_default if export_all_variables is set and their names are benign.
+ * variable.h: Declare export_all_variables.
+ * read.c (read_makefile): If export or unexport is given with no
+ args, set or clear export_all_variables, respectively.
+
+ * variable.c (target_environment): Exclude MAKELEVEL in the loop,
+ so it isn't duplicated when we add it at the end.
+
+Sun May 3 17:44:48 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.8.
+
+ * variable.h (struct variable): Added new member `export'.
+ * variable.c (define_variable_in_set): Initialize it to v_default.
+ (target_environment): Don't check for .NOEXPORT.
+ Export variables whose `export' member is v_default and that would
+ have been exported under .NOEXPORT, and variables whose `export'
+ member is v_export.
+ (try_variable_definition): Return the variable defined.
+ * variable.h (try_variable_definition): Changed decl.
+ * read.c (read_makefile): Recognize `export' and `unexport' directives.
+
+Fri May 1 11:39:38 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * main.c (main) [POSIX]: Reversed args to sigaddset.
+
+Thu Apr 30 17:33:32 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c [POSIX || !USG] (unblock_sigs): New fn.
+ (start_job): Block signals before forking.
+ (new_job): Unblock signals after putting the new child on the chain.
+ * main.c (main) [POSIX]: Use sigset_t fatal_signal_set instead of
+ int fatal_signal_mask.
+
+ * load.c [sgi] (LDAV_CVT): Define.
+
+Wed Apr 29 17:15:59 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.62.7.
+
+ * load.c (load_average) [sgi]: Clear the high bit of the address
+ from the symbol table before looking it up in kmem.
+
+ * misc.c (fatal, makefile_fatal): Put *** in fatal error messages.
+ (remake_file): No longer needed in message here.
+
+ * main.c (die): Call reap_children with BLOCK==1.
+
+Tue Apr 28 20:44:35 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * rule.c (freerule): Don't set LASTRULE->next if LASTRULE is nil.
+
+Sun Apr 26 15:09:51 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * rule.c (count_implicit_rule_limits): Initialize LASTRULE to nil,
+ not to head of chain. Extract next ptr before we might do
+ freerule, and use that for next iteration.
+ (freerule): Still do next ptr frobbing if LASTRULE is nil.
+
+Tue Apr 21 03:16:29 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * job.c (child_error): Removed extra %s from error msg format.
+
+ * Version 3.62.6.
+
+ * job.c (reap_children): Don't start later commands in a sequence
+ if ERR is nonzero.
+
+ * job.c (new_job): Always call reap_children with BLOCK==0 first thing.
+
+ * job.c (reap_children): New function; work that used to be done in
+ child_handler.
+ (child_died): New global var.
+ (child_handler): Now just sets child_died.
+ (wait_for_children): Removed.
+ (unknown_children_possible, block_signals, unblock_signals,
+ push_signals_blocked_p, pop_signals_blocked_p): Removed.
+ (child_execute_job): Removed call to unblock_signals.
+ (new_job): Removed calls to push_signals_blocked_p and
+ pop_signals_blocked_p.
+ * job.h: Declare reap_children, not wait_for_children.
+ * commands.c (fatal_error_signal), job.c (new_job),
+ load.c [LDAV_BASED] (wait_to_start_job), main.c (die),
+ remake.c (update_goal_chain), function.c (expand_function: `shell'):
+ Changed wait_for_children calls to reap_children.
+ Some needed to be loops to wait for all children to die.
+ * commands.c (fatal_error_signal), main.c (main,
+ log_working_directory), function.c (expand_function): Removed calls
+ to push_signals_blocked_p and pop_signals_blocked_p.
+ * job.h: Removed decls.
+
+ * job.h: Added copyright notice.
+
+Wed Apr 15 02:02:40 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (child_error): No *** for ignored error.
+
+Tue Apr 14 18:31:21 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * implicit.c (DEBUGP2): Use do ... while (0) instead of if ... else to
+ avoid compiler warnings.
+
+ * read.c (parse_file_seq): Don't remove ./ when it is followed by a
+ blank.
+
+Mon Apr 13 21:56:15 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h (DEBUGPR): Use do ... while (0) instead of if ... else to
+ avoid compiler warnings.
+
+ * remake.c (notice_finished_file): Run file_mtime on the also_make
+ files, so vpath_search can happen.
+
+ * GNUmakefile (tests): Use perl test suite from csa@sw.stratus.com.
+ (alpha-files): Include test suite tar file.
+
+Fri Apr 3 00:50:13 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * Version 3.62.5.
+
+Wed Apr 1 05:31:18 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * remake.c (update_file, update_file_1): Do check_renamed on elts
+ of dep chains when traversing them. Something unrelated might have
+ renamed one of the files the dep chain points to.
+
+ * file.c (rename_file): If FILE has been renamed, follow its
+ `renamed' ptr, so we get to the final real FILE. Using the renamed
+ ones loses because they are not in the hash table, so the removal
+ code loops infinitely.
+
+ * read.c (read_all_makefiles): Clobber null terminator into
+ MAKEFILES expansion, so string passed to read_makefile is properly
+ terminated.
+
+Mon Mar 30 20:18:02 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * commands.c (set_file_variables): $* for archive member with
+ explicit cmds is stem of member, not of whole `lib(member)'.
+
+Thu Mar 26 15:24:38 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.62.4.
+
+Tue Mar 24 05:20:51 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * rule.c (new_pattern_rule): Rules are identical only if all their
+ targets match (regardless of order).
+
+Wed Mar 11 13:49:54 1992 Roland McGrath (roland@geech.gnu.ai.mit.edu)
+
+ * remake.c (remake_file): Changed error "no way to make" to "no
+ rule to make". Fiat Hugh.
+
+ * make.texinfo (Last Resort): Describe %:: rules and new .DEFAULT
+ behavior.
+
+ * remake.c (update_file_1): Only use .DEFAULT cmds if FILE is not a
+ target.
+
+Tue Mar 10 18:13:13 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * remote-stub.c, remote-cstms.c (start_remote_job): Take new arg,
+ environment to pass to child.
+ * job.c (start_job): Pass it.
+
+Mon Mar 9 19:00:11 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * file.c (enter_file): Also strip ./s here, to get command-line
+ target names.
+
+ * remote-cstms.c: Add comment telling people to leave me alone.
+
+ * compatMakefile (manpage install): Remove target before copying.
+
+Tue Mar 3 18:43:21 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.texinfo (Missing): Renamed to "Incompatibilities and ...".
+ Added paragraph describing $? incompatibility with Unix and POSIX.2.
+
+Sun Mar 1 15:50:54 1992 Roland McGrath (roland@nutrimat.gnu.ai.mit.edu)
+
+ * function.c (expand_function: `shell'): Don't declare fork or pipe.
+ Use vfork instead of fork.
+
+Tue Feb 25 22:05:32 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * make.texinfo (Chained Rules): Clarify .PRECIOUS to save
+ intermediate files.
+
+ * load.c [sun] (LDAV_CVT): Define to divide by FSCALE.
+
+Sun Feb 16 02:05:16 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * Version 3.62.3.
+
+Sat Feb 15 17:12:20 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * compatMakefile (makeinfo): Use emacs batch-texinfo-format fn.
+
+Fri Feb 14 00:11:55 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Correctly handle define & endef in ifdefs.
+
+ * read.c (record_files): Pass arg for %s in error msg.
+
+ * main.c (main) [__IBMR2, POSIX]: Use correct (a la USGr3) setvbuf
+ call.
+
+Wed Feb 12 12:07:39 1992 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * make.texinfo (Libraries/Search): Say it does /usr/local/lib too.
+
+Sun Feb 9 23:06:24 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * read.c (read_makefile): Check for extraneous `endef' when ignoring.
+
+Thu Feb 6 16:15:48 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * Version 3.62.2.
+
+Tue Feb 4 20:04:46 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Correctly ignore
+ whitespace after backslash-NL.
+
+Fri Jan 31 18:30:05 1992 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * compatMakefile: Ignore errors from chgrp and chmod when installing.
+
+Wed Jan 29 18:13:30 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * main.c (main): When setting MAKELEVEL in the env to re-exec,
+ allocate space so as not to clobber past the end of the old string.
+
+ * make.h [HAVE_ALLOCA_H]: Include <alloca.h>
+ * compatMakefile (defines): Document HAVE_ALLOCA_H.
+
+Mon Jan 20 13:40:05 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * make.h [VFORK_MISSING]: Use fork instead.
+ * compatMakefile (defines): Document same.
+
+ * job.c (construct_command_argv_internal): Don't create an empty
+ arg if backslash-NL is at beginning of word.
+
+Sun Jan 19 16:26:53 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * main.c [DGUX]: Call setvbuf as for USGr3.
+
+ * job.c (construct_command_argv_internal): Notice correctly that
+ backslash-NL is the end of the arg (because it is replaced with a
+ space).
+
+Thu Jan 16 18:42:38 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): If SHELL is nil, set it
+ to default_shell before proceeding.
+
+ * make.h [sgi]: No alloca.h, after all.
+
+Wed Jan 15 12:30:04 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * read.c (multi_glob): Cons up the chain of the results of glob
+ from back to front, so it comes out in forward order.
+
+ * job.c (construct_command_argv_internal): Don't eat char following
+ backslash-NL.
+
+Mon Jan 13 19:16:56 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * Version 3.62.1.
+
+ * default.c (default_variables) [ultrix]: GET=get, like USG.
+
+ * job.c (construct_command_argv_internal): Remove tabs following
+ backslash-NL combos in the input line, so they don't show up when
+ that line is printed.
+
+ * read.c (read_makefile): Don't collapse_continuations the line on
+ input; do it on the copy we do remove_comments on.
+ For rule lines, collapse_continuations the line after chopping
+ ";cmds" off the end, so we don't eat conts in the cmds.
+ Give error for ";cmds" with no rule.
+ * job.c (construct_command_argv_internal): Eat backslash-NL combos
+ when constructing the line to recurse on for slow, too.
+
+Sat Jan 11 02:20:27 1992 Roland McGrath (roland@albert.gnu.ai.mit.edu)
+
+ * file.c (enter_file): Don't strip leading `./'s.
+ * read.c (parse_file_seq): Take new arg STRIP; if nonzero, do it here.
+ * default.c (set_default_suffixes), function.c (string_glob),
+ read.c (read_makefile), rule.c (install_pattern_rule): Change callers.
+
+ * default.c (default_variables) [_IBMR2]: FC=xlf
+
+ * job.c (construct_command_argv_internal): Turn backslash-NL and
+ following whitespace into a single space, rather than just eating
+ the backslash.
+
+ * make.texinfo (Copying): @include gpl.texinfo, rather than
+ duplicating its contents.
+
+Fri Nov 8 20:06:03 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Make sure not to bother
+ processing an empty line.
+
+ * Version 3.62.0.
+
+ * job.c (construct_command_argv_internal): Always recurse for slow;
+ simple case didn't handle finding newlines.
+
+Tue Nov 5 18:51:10 1991 Roland McGrath (roland@wookumz.gnu.ai.mit.edu)
+
+ * job.c (construct_command_argv_internal): Set RESTP properly when
+ slow; don't \ify past a newline.
+
+Fri Nov 1 19:34:28 1991 Roland McGrath (roland@churchy.gnu.ai.mit.edu)
+
+ * make.h [sgi]: #include <alloca.h>.
+
+
+
+See ChangeLog.1, available in the Git repository at:
+
+ http://git.savannah.gnu.org/cgit/make.git/tree/
+
+for earlier changes.
+
+
+Copyright (C) 1991-2007 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/ChangeLog.3 b/src/kmk/ChangeLog.3
new file mode 100644
index 0000000..5fcf273
--- /dev/null
+++ b/src/kmk/ChangeLog.3
@@ -0,0 +1,5633 @@
+2013-10-09 Paul Smith <psmith@gnu.org>
+
+ Version 4.0 released.
+
+ * configure.ac: Updated for the release.
+ * NEWS: Updated for the release.
+
+ * maintMakefile (tag-release): New target to add a Git tag.
+ * read.c (eval): Typo fix.
+ * ChangeLog.1: Typo fixes.
+ * w32/subproc/sub_proc.c (process_cleanup): Typo fix.
+
+2013-10-07 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/compat/posixfcn.c (tmpfile): Move declaration of h before
+ the first executable statement. Reported by Gisle Vanem
+ <gvanem@yahoo.no>.
+
+2013-10-05 Paul Smith <psmith@gnu.org>
+
+ * makeint.h (MAP_USERFUNC): A new map type for function names.
+ * main.c (initialize_stopchar_map): Set up the function name map.
+
+ * gnumake.h (gmk_func_ptr): Define a type for function pointers.
+ (gmk_add_function): Convert the last argument to FLAGS.
+ (GMK_FUNC_*): Define flags for the function. Change the default
+ behavior to "expand" since that's the most common one.
+
+ * function.c (function_table_entry): Use new function pointer type.
+ (lookup_function): Accept any valid function name character based
+ on the MAP_USERFUNC values.
+ (define_new_function): Use the new calling signature. Verify that
+ registered functions have valid names.
+
+ * guile.c (guile_gmake_setup): Use new calling signatures.
+ * loadapi.c (gmk_add_function): Ditto.
+ * variable.h (define_new_function): Ditto.
+
+ * doc/make.texi (Loaded Object API): Make the registered function
+ API documentation more clear.
+
+2013-10-03 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (abspath): Reset root_len to one for Cygwin only when
+ HAVE_DOS_PATHS is defined. Suggested by Christopher Faylor.
+
+2013-10-02 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/compat/posixfcn.c (tmpfile): New function, a replacement for
+ the Windows libc version.
+
+ Fix $abspath on Cygwin when HAVE_DOS_PATHS is in effect.
+ * function.c (IS_ABSOLUTE) [__CYGWIN__]: Special definition for
+ Cygwin.
+ (abspath) [__CYGWIN__]: Reset root_len to 1 if the absolute file
+ name has the Posix /foo/bar form.
+ [HAVE_DOS_PATHS]: Use root_len instead of hard-coded 2.
+
+2013-10-01 Paul Smith <psmith@gnu.org>
+
+ * configure.ac: Update version to 3.99.93.
+ * NEWS: Ditto.
+
+2013-09-30 Paul Smith <psmith@gnu.org>
+
+ * guile.c: Portability fixes for Guile 1.8.
+
+2013-09-29 Paul Smith <psmith@gnu.org>
+
+ * output.c (output_dump): Always write Enter/Leave messages to stdio.
+ (log_working_directory): This now always writes to stdio, so we
+ don't need the struct output parameter anymore.
+ (output_start): Show the working directory when output_sync is not
+ set or is recursive.
+ * main.c (main): Ensure the special "already shown Enter message"
+ token is removed from MAKE_RESTARTS before the user can see it.
+ * function.c (func_shell_base): If the output_context stderr
+ exists but is invalid, write to the real stderr.
+ Fixes suggested by Frank Heckenbach <f.heckenbach@fh-soft.de>.
+
+ * output.c: Guard unistd.h inclusion, add io.h.
+ * gnumake.h: Move GMK_EXPORT before the declarations.
+ * make_msvc_net2003.vcproj: Add missing files.
+ Changes for MSVC suggested by Gerte Hoogewerf <g.hoogewerf@gmail.com>
+
+ * function.c (func_shell_base) [EMX]: Fix EMX support for output-sync.
+ * job.c (child_execute_job) [EMX]: Ditto.
+ * job.h (child_execute_job) [EMX]: Ditto.
+ * w32/compat/posixfcn.c: Invert the test for NO_OUTPUT_SYNC.
+
+ * guile.c (GSUBR_TYPE): Pre-2.0 Guile doesn't provide a typedef
+ for gsubr pointers. Create one.
+ (guile_define_module): Use it.
+ (internal_guile_eval): Force UTF-8 encoding for Guile strings.
+
+ * main.c (main): Clear GNUMAKEFLAGS after parsing, to avoid
+ proliferation of options.
+ * NEWS: Document it.
+ * doc/make.texi (Options/Recursion): Ditto.
+
+2013-09-23 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/compat/posixfcn.c: Fix the forgotten OUTPUT_SYNC conditional.
+
+ * job.h: Ditto, but in a comment.
+
+2013-09-22 Paul Smith <psmith@gnu.org>
+
+ * configure.ac: Update version to 3.99.92.
+ * NEWS: Ditto.
+
+ * implicit.c (pattern_search): After second expansion be sure to
+ handle order-only markers inside the expansion properly.
+ Fixes Savannah bug #31155.
+
+ * guile.c (guile_define_module): Technically a void* cannot
+ contain a pointer-to-function and some compilers warn about this.
+ Cast the function pointers.
+ * load.c (load_object): Ditto.
+
+ * read.c (eval): If load_file() returns -1, don't add this to the
+ "to be rebuilt" list.
+ * doc/make.texi (load Directive): Document it.
+
+ * guile.c (guile_gmake_setup): Don't initialize Guile so early.
+ (func_guile): Lazily initialize Guile the first time the $(guile ..)
+ function is invoked. Guile can steal file descriptors which
+ confuses our jobserver FD checking, so we don't want to initialize
+ it before we have to.
+
+ VMS port updates by Hartmut Becker <becker.ismaning@freenet.de>
+
+ * makefile.com: Add output to the filelist.
+ * output.c (va_copy): Add an implementation of this macro for VMS.
+ * commands.c: Ensure filedef.h is #included before dep.h.
+ * dir.c: Ditto.
+ * file.c: Ditto.
+ * guile.c: Ditto.
+ * main.c: Ditto.
+ * misc.c: Ditto.
+ * read.c: Ditto.
+ * rule.c: Ditto.
+ * variable.c: Ditto.
+ * readme.vms: Renamed to README.VMS and updates for this release.
+ * Makefile.am: Ditto.
+ * NEWS: Ditto.
+ * README.template: Ditto.
+ * Makefile.DOS.template: Ditto.
+
+2013-09-21 Paul Smith <psmith@gnu.org>
+
+ * maintMakefile (check-alt-config): Create a target to test
+ alternative configurations. Each one will build make with a
+ different configuration then run the test suite.
+
+ Invert the output-sync #define to NO_OUTPUT_SYNC
+
+ * configure.ac: Don't set OUTPUT_SYNC.
+ * makeint.h: Ditto.
+ * main.c: Use NO_OUTPUT_SYNC instead of OUTPUT_SYNC.
+ * output.c: Ditto.
+ * output.h: Ditto.
+ * job.h: Ditto.
+ * job.c: Ditto.
+ * config.ami.template: Set NO_OUTPUT_SYNC.
+ * config.h-vms.template: Ditto.
+ * config.h.W32.template: Ditto.
+ * configh.dos.template: Ditto.
+
+ Output generated while reading makefiles should be synced.
+
+ * main.c (make_sync): Define a context for syncing while reading
+ makefiles and other top-level operations.
+ (main): If we request syncing, enable it while we are parsing
+ options, reading makefiles, etc. to capture that output. Just
+ before we start to run rules, dump the output if any.
+ (die): Dump any output we've been syncing before we die
+ * output.h (OUTPUT_SET): Disable output_context if not syncout.
+
+ Stderr generated from shell functions in recipes should be synced.
+
+ * job.h (FD_STDIN, FD_STDOUT, FD_STDERR): Create new macros to
+ avoid magic numbers.
+ (child_execute_job): Take a FD for stderr.
+ * job.c (child_execute_job): Handle STDERR FD's in addition to
+ stdin and stdout.
+ (start_job_command): Call child_execute_job() with the new STDERR
+ parameter. Instead of performing the dup() here, send it to
+ child_execute_job() where it's already being done.
+ * function.c (func_shell_base): Pass the OUTPUT_CONTEXT stderr to
+ child_execute_job() if it's set, otherwise FD_STDERR.
+ * main.c (main): Pass FD_STDERR to child_execute_job().
+
+2013-09-19 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Set MAKE_RESTARTS to negative before re-exec if
+ we've already generated an "Entering" message. If we are started
+ and notice that MAKE_RESTARTS is negative, assume we already wrote
+ "Entering" and don't write it again.
+
+2013-09-18 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Set starting_directory before we write any
+ errors. Fixes Savannah bug #40043.
+
+2013-09-16 Eli Zaretskii <eliz@gnu.org>
+
+ * output.c [WINDOWS32]: Include windows.h and sub_proc.h, to avoid
+ compiler warnings for CLOSE_ON_EXEC.
+
+2013-09-16 Paul Smith <psmith@gnu.org>
+
+ * configure.ac: Update version to 3.99.91.
+ * NEWS: Ditto.
+
+2013-09-15 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Error Messages): Add a bit more info to the
+ section on static pattern errors, since they're common.
+ Fixes Savannah bug #31326.
+
+ * read.c (eval_makefile): If the file open fails with an
+ unrecoverable error, stop now rather than trying to make it.
+ Fixes Savannah bug #27374.
+
+ * main.c (main): Perform the validation of the jobserver FDs
+ early, before we read makefiles, to ensure that something hasn't
+ opened and used those FDs for some other reason.
+ Fixes Savannah bug #39934.
+
+ * main.c (main): Don't set MAKEFLAGS in the environment when we
+ restart. We have the original command line flags so keep the
+ original MAKEFLAGS settings as well.
+ Fixes Savannah bug #39203.
+
+2013-09-14 Paul Smith <psmith@gnu.org>
+
+ * main.c (decode_debug_flags): Add support for the "n" flag to
+ disable all debugging.
+ * make.1: Document the "n" (none) flag.
+ * doc/make.texi (Options Summary): Ditto.
+ * NEWS: Ditto.
+ Fixes Savannah bug #35248.
+
+ * misc.c (close_stdout): Move to output.c.
+ * main.c (main): Move atexit call to output_init().
+ * makeint.h: Remove close_stdout() declaration.
+ * output.c (output_init): Add close_stdout at exit only if it's open.
+ Fixes Savannah bug #33134. Suggested by David Boyce <dsb@boyski.com>.
+
+2013-09-14 Paul Smith <psmith@gnu.org>
+
+ * misc.c (set_append_mode, open_tmpfd, open_tmpfile): Move to output.c.
+ * misc.h: Ditto.
+ * output.h: Ditto.
+ * main.c (main): Move stdio init into output.c:output_init().
+ Change open_tmpfile() to output_tmpfile().
+ * output.c: Rename open_*() to output_*(). set_append_mode() and
+ open_tmpfd() are static.
+ (_outputs, log_working_directory): Accept a struct output and
+ print to that rather than the global context.
+ (output_dump): In recurse mode print enter/leave once for the
+ whole makefile.
+ (output_init): Initialize this processes stdio as well as child's.
+
+ * vmsjobs.c: Reformat to be closer to convention.
+
+2013-09-12 Paul Smith <psmith@gnu.org>
+
+ Rework output to handle synchronization and directory logging more
+ reliably.
+
+ * output.c: New file. Implement lazy synchronization and
+ directory logging so that we manage them "just in time", and the
+ destination of the output is set via a global state variable.
+ * output.h: New file.
+ * function.c (func_shell_base): Ensure the output is set up before
+ running a shell command, in case it writes to stderr.
+ (func_error): Use outputs() to generate output.
+ * job.h (struct child): Add struct output to track the child's output.
+ * job.c: Use struct output in the child structure to track output.
+ (child_out, sync_init, assign_child_tempfiles, pump_from_tmp)
+ (acquire_semaphore, release_semaphore, sync_output): Move most of
+ the output_sync handling to output.c.
+ (child_error): Set output, then use simple message() and error()
+ not _s versions.
+ * main.c (log_working_directory): Moved to output.c
+ (trace_option, decode_trace_flags) Remove. Remove support for
+ different trace modes; we don't use it anymore.
+ (die) Invoke output_close() before we exit.
+ * misc.c (message_s, error_s): Removed; no longer needed.
+ (message, error, fatal, perror_with_name, pfatal_with_name): Moved
+ to output.c.
+ * makeint.h: Remove message_s(), error_s(), and
+ log_working_directory(). Remove the TRACE_* macros.
+ * doc/make.texi: Enhance documentation for output sync, and remove
+ MODE assignment for --trace.
+ * make.1: Remove MODE assignment for --trace.
+ * Makefile.am: Add new files.
+ * NMakefile.template: Ditto.
+ * SMakefile.template: Ditto.
+ * build_w32.bat: Ditto.
+ * dosbuild.bat: Ditto.
+ * make.lnk: Ditto.
+ * make_nsvc_net2003.vcproj: Ditto.
+ * makefile.vms: Ditto.
+ * po/POTFILES.in: Ditto.
+
+2013-08-22 Petr Machata <pmachata@redhat.com>
+
+ * function.c (func_shell_base): Get rid of any avoidable limit on
+ stack size for processes spawned via $(shell).
+
+2013-07-22 Paul Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Use PARSE_SIMPLE_SEQ() even for
+ non-second expansion prerequisites, to handle globbing in patterns.
+ Fixes Savannah bug #39310.
+
+ * dep.h (PARSE_SIMPLE_SEQ): Macro for simple file sequence parsing.
+ * default.c (set_default_suffixes): Use it.
+ * file.c (split_prereqs): Ditto.
+ * main.c (main): Ditto.
+ * read.c (eval): Ditto.
+ * rule.c (install_pattern_rule): Ditto.
+ * file.c (split_prereqs): Use PARSEFS_NONE instead of 0.
+
+2013-07-21 Paul Smith <psmith@gnu.org>
+
+ Cleanups detected by cppcheck. Fixes Savannah bug #39158.
+ * arscan.c (ar_scan): Reduce the scope of local variables.
+ * dir.c (vms_hash): Ditto.
+ (find_directory): Ditto.
+ (file_impossible_p): Ditto.
+ * expand.c (variable_expand_string): Ditto.
+ * function.c (func_sort): Ditto.
+ (func_and): Ditto.
+ * job.c (reap_children): Ditto.
+ (exec_command): Ditto.
+ * main.c (main): Ditto.
+ * misc.c (collapse_continuations): Ditto.
+ * read.c (eval): Ditto.
+ (parse_file_seq): Ditto.
+ * vpath.c (gpath_search): Ditto.
+ (selective_vpath_search): Ditto.
+ * job.c (is_bourne_compatible_shell): Simplify for non-Windows systems.
+ * remake.c (f_mtime): Remove duplicate test.
+ * signame.c (strsignal): Fix bogus conditional.
+
+ * job.c (assign_child_tempfiles): Assign OUTFD to -1 for safety.
+ (start_job_command): Don't test output_sync and sync_cmd: redundant.
+ Changes suggested by Frank Heckenbach <f.heckenbach@fh-soft.de>.
+
+2013-07-14 Paul Smith <psmith@gnu.org>
+
+ * filedef.h (update_status): Convert UPDATE_STATUS from a char to
+ an enumeration. Some systems declare "char" to be "unsigned"
+ which broke the code (which expected to be able to use -1 as a
+ flag). Using magic values was unpleasant, so rather than just
+ force "signed char" I reworked it to use an enum.
+
+ * dep.h (update_goal_chain): Return an update_status value not int.
+ * remake.c (touch_file): Ditto.
+ (update_goal_chain): Track the update_status enum.
+
+ * file.c (enter_file): Use new enumeration values with update_status.
+ (remove_intermediates): Ditto.
+ (print_file): Ditto.
+ * commands.c (execute_file_commands): Ditto.
+ * job.c (reap_children): Ditto.
+ (start_job_command): Ditto.
+ (start_waiting_job): Ditto.
+ * main.c (main): Ditto.
+ * remake.c (update_file): Ditto.
+ (complain): Ditto.
+ (update_file_1): Ditto.
+ (notice_finished_file): Ditto.
+ (remake_file): Ditto.
+ * vmsjobs.c (vmsHandleChildTerm): Ditto.
+
+2013-07-09 Paul Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Keep a local copy of the number of
+ deps in deplist: the global max might change due to recursion.
+ Fixes a bug reported by Martin d'Anjou <martin.danjou14@gmail.com>.
+
+2013-06-28 Paul Smith <psmith@gnu.org>
+
+ * misc.c (set_append_mode): Set the O_APPEND flag on a file descriptor.
+ (open_tmpfd): Set append mode on the temporary file descriptor.
+ * main.c (main): Set append mode on stdout and stderr.
+ * makeint.h (set_append_mode): Declare it.
+
+2013-06-22 Eli Zaretskii <eliz@gnu.org>
+
+ * build_w32.bat (LinkGCC): Prevent a comment from being displayed
+ at build time.
+
+ * job.c (construct_command_argv_internal) [WINDOWS32]: Use
+ case-insensitive comparison with internal commands of non-Unix
+ shells.
+
+ * main.c (find_and_set_default_shell): Don't use file_exists_p or
+ dir_file_exists_p, as those call readdir, which can fail if PATH
+ includes directories with non-ASCII characters, and that would
+ cause Make to fail at startup with confusing diagnostics. See
+ https://sourceforge.net/mailarchive/message.php?msg_id=30846737
+ for the details.
+
+2013-06-22 Paul Smith <psmith@gnu.org>
+
+ Improve performance by using a character map to determine where we
+ want to stop searching strings, rather than discrete comparisons.
+
+ * read.c (find_char_unquote): Pass a stop map instead of various
+ flags and use that to check when to stop parsing the string.
+ (eval): Use the new find_char_unquote() calling signature.
+ (remove_comments): Ditto.
+ (unescape_char): Ditto.
+ (find_percent_cached): Ditto.
+ (parse_file_seq): Use a stop-map flag.
+ * main.c (stopchar_map): Character map definition.
+ (initialize_stopchar_map): Initialize the map definition.
+ (main): Invoke the map initialization function.
+ * misc.c (end_of_token_w32): Remove unused function.
+ * dir.c (dosify): Use STOP_SET to check for stop chars.
+ * main.c (main): Ditto.
+ * misc.c (end_of_token): Ditto.
+ * function.c (subst_expand): Ditto.
+ (func_notdir_suffix): Ditto.
+ (func_basename_dir): Ditto.
+ (abspath): Ditto.
+ * job.c (is_bourne_compatible_shell): Ditto.
+ * variable.c (parse_variable_definition): Ditto.
+ * read.c (eval): Ditto.
+ (conditional_line): Ditto.
+ (find_percent_cached): Ditto.
+ * dep.h (PARSE_FILE_SEQ): Update function declaration.
+ * default.c (set_default_suffixes): Update PARSE_FILE_SEQ() call.
+ * file.c (split_prereqs): Ditto.
+ * function.c (string_glob): Ditto.
+ * implicit.c (pattern_search): Ditto.
+ * rule.c (install_pattern_rule): Ditto.
+ * main.c (main): Ditto.
+
+2013-06-21 Paul Smith <psmith@gnu.org>
+
+ * main.c (verify_flag): Global variable to determine whether to
+ verify the database or not.
+ (decode_debug_flags): If debug mode, enable verify_flag.
+ (main): If MAKE_MAINTAINER_MODE, enable verify_flag, otherwise not.
+ (die): Only verify the database if verify_flag is set.
+ * file.c (enter_file): Don't check caching unless verify_flag.
+ * makeint.h: Export verify_flag.
+
+2013-05-27 Paul Smith <psmith@gnu.org>
+
+ * variable.c (define_automatic_variables): Create a new variable
+ MAKE_HOST.
+
+2013-05-27 Hartmut Becker <becker.ismaning@freenet.de>
+
+ * function.c (func_shell_base) [VMS]: Support VMS.
+ * makefile.com [VMS]: Ditto.
+ * makefile.vms [VMS]: Ditto.
+ * makeint.h [VMS]: Ditto.
+ * vmsjobs.c [VMS]: Ditto.
+ * job.h: Define RECORD_SYNC_MUTEX() when OUTPUT_SYNC is not set.
+ * load.c (unload_file): Fix signature if MAKE_LOAD is not set.
+
+2013-05-26 Paul Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): Ensure that archive file names are in the
+ string cache. Fixes Savannah bug #38442.
+
+ * read.c (readline): To be safe, move the entire buffer if we
+ detect a CR. Fixes Savannah bug #38945.
+
+ * job.c (new_job): Compare OUT to the beginning of the OUT
+ var/function, not IN. Fixes Savannah bug #39035.
+
+2013-05-22 Paul Smith <psmith@gnu.org>
+
+ * main.c (switches[]): Order switches so simple flags all come first.
+ (define_makeflags): Rework to make option passing more
+ reliable and the code less tricksy. Ensure simple flags are kept
+ in the initial batch of flags. Do not allow any flags with
+ options in that batch. If there are only non-simple flags MAKEFLAGS
+ begins with ' '.
+ (print_data_base): Print the version. Fixes part of Savannah #35336.
+
+ * read.c (eval_buffer): Initialize lineno.
+
+2013-05-18 Alexey Pavlov <alexpux@gmail.com> (tiny change)
+
+ * w32/Makefile.am (libw32_a_SOURCES): Add compat/posixfcn.c.
+
+ * configure.ac (OUTPUT_SYNC): Define for mingw32 target.
+
+ * job.c (construct_command_argv_internal) <sh_cmds_dos>
+ [WINDOWS32]: Add "move". Fixes Savannah bug #30714.
+
+ * guile.c: Move inclusion of makeint.h before gnumake.h. This
+ order must be observed when building Make, because gnumake.h must
+ be included with GMK_BUILDING_MAKE defined, which makeint.h
+ already does. Otherwise, the linker will look for, and fail to
+ find, gmk_* functions in some external dynamic library.
+
+2013-05-17 Benno Schulenberg <bensberg@justemail.net>
+
+ * main.c (decode_output_sync_flags): Fix output message.
+ * read.c (EXTRANEOUS): Ditto.
+ (record_files): Ditto.
+ * remake.c (update_file_1): Ditto.
+
+2013-05-17 Eli Zaretskii <eliz@gnu.org>
+
+ * main.c (prepare_mutex_handle_string): Define conditioned on
+ OUTPUT_SYNC.
+
+ * build_w32.bat: Copy config.h.W32 to config.h regardless of
+ whether or not we are building from SCM.
+
+2013-05-17 Paul Smith <psmith@gnu.org>
+
+ * configure.ac: Update version to 3.99.90.
+ * NEWS: Ditto.
+
+ * Source (*.[ch]): Remove TABs, use GNU coding styles.
+
+ * ALL: Update copyright.
+
+ * hash.c (CALLOC): Use xcalloc() to handle out of memory errors.
+
+ * makeint.h: Prototype new unload_file() function.
+ * load.c (unload_file): Create a function to unload a file.
+ (struct load_list): Type to remember loaded objects.
+ (loaded_syms): Global variable of remembered loaded objects so we
+ can unload them later. We don't have to remove from the list
+ because the only time we unload is if we're about to re-exec.
+ (load_object): Remove unneeded extra DLP argument.
+ (load_file): Remove unneeded extra DLP argument.
+ * filedef.h (struct file): Remove the DLP pointer and add the
+ LOADED bit flag. Saves 32/64 bytes per file, as this pointer is
+ almost never needed.
+ * read.c (eval): Set the new LOADED bit flag on the file.
+ * file.c (rehash_file): Merge the loaded bitfield.
+ * commands.c (execute_file_commands): Call unload_file() instead
+ of dlclose() directly.
+
+2013-05-14 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Loaded Object API): Document the requirement for
+ the plugin_is_GPL_compatible symbol.
+ * load.c (load_object): Check for plugin_is_GPL_compatible symbol.
+
+2013-05-13 Paul Smith <psmith@gnu.org>
+
+ * filedef.h (struct file): Add a builtin flag.
+ * file.c (enter_file): Unset the builtin flag.
+ (rehash_file): Ditto.
+ (print_file): Don't print builtin files if we've omitted them.
+ * default.c (undefine_default_variables): New function: go through
+ the default variables and undefine them.
+ (set_default_suffixes): Mark these suffix rules as builtin.
+ * makeint.h: Prototype.
+ * main.c (main): Handle addition of -r and -R to MAKEFLAGS in the
+ makefile. Fixes Savannah bug #20501.
+
+ * main.c (define_makeflags): Assign o_env_override level to
+ MAKEFLAGS to ensure it's set even in the presence of -e.
+ Fixes Savannah bug #2216.
+
+ * makeint.h (TRACE_NONE, TRACE_RULE, TRACE_DIRECTORY): Define
+ constants for the trace mode.
+ * main.c: Add new --trace mode parsing.
+ (decode_trace_flags): New function.
+ (decode_switches): Call it.
+ (define_makeflags): Fix a bug with long-name options.
+ * misc.c (fatal): Remove special output-sync handling.
+ * make.1: Document new --trace mode flags.
+ * doc/make.texi (Options Summary): Ditto.
+
+2013-05-11 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (child_out): Output the newline following the message
+ before fllush-ing the stream. Avoids displaying the following
+ failure message, which goes to stderr, on the same line.
+
+2013-05-06 Eli Zaretskii <eliz@gnu.org>
+
+ * gnumake.h (GMK_EXPORT) [_WIN32]: Move the dllexport declaration
+ here from makeint.h.
+
+ * makeint.h (GMK_BUILDING_MAKE) [WINDOWS32]: Define before
+ including gnumake.h.
+
+ * doc/make.texi (Loaded Object Example): Add a note about building
+ shared objects on MS-Windows.
+
+2013-05-05 Paul Smith <psmith@gnu.org>
+
+ * makeint.h (OUTPUT_SYNC_LINE, OUTPUT_SYNC_RECURSE): Rename
+ output-sync options "job" to "line" and "make" to "recurse".
+ * main.c (decode_output_sync_flags): Ditto.
+ * job.c (reap_children): Ditto.
+ (start_job_command): Ditto.
+ * make.1: Ditto.
+ * doc/make.texi (Parallel Output): Ditto.
+
+ * job.c (child_out): Write newlines explicitly, and don't do
+ anything if the message is empty.
+ (sync_output): Put working dir messages around stdout AND stderr.
+ (start_job_command): Move the tmp file assignment earlier. After
+ we do it, write the command line to the temp file to get the order
+ correct.
+
+ * misc.c (message): Remove special handling for output_sync.
+ (error): Ditto.
+
+2013-05-04 Paul Smith <psmith@gnu.org>
+
+ * loadapi.c (gmk_alloc): New function.
+ * gnumake.h: Add gmk_alloc(). Clean GMK_EXPORT a bit to avoid MAIN.
+ * makeint.h (GMK_EXPORT): New handling, vs. MAIN.
+ * doc/make.texi (Loaded Object API): Add information on the memory
+ handling functions.
+ (Loaded Object Example): Create an example.
+
+ * job.c (pump_from_tmp): (Rename) Write to stdout/stderr using
+ FILE* rather than fd. It's not a good idea to mix and match.
+
+2013-05-04 Eli Zaretskii <eliz@gnu.org>
+
+ * makeint.h (ftruncate) [_MSC_VER]: Redirect to _chsize.
+ (_S_ISDIR): If not defined (MinGW64), define to S_ISDIR.
+
+2013-05-04 Paul Smith <psmith@gnu.org>
+
+ * job.c (child_out): Handle EINTR and incomplete write scenarios.
+ (sync_init): New function: separate the initialization code.
+ (assign_child_tempfiles): Remove truncation from this function,
+ (sync_output): and add it here after output is generated.
+ (reap_children): Always call sync_output() in case output_sync was
+ reset after the child started, due to error.
+ (start_job_command): Create new sync_cmd variable. Use new method
+ for initializing the handle.
+ If we're not syncing the output be sure any output we've saved is
+ dumped immediately before starting the child.
+
+2013-05-04 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (start_job_command): Make the condition for creating a
+ temporary output file be identical to the Posix code branch.
+ Suggested by Frank Heckenbach <f.heckenbach@fh-soft.de>.
+
+2013-05-03 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/sub_proc.c: Include makeint.h. Remove a private
+ incompatible prototype of xmalloc.
+ (batch_file_with_spaces): New function, detects Windows batch
+ files whose names include whitespace characters.
+ (process_begin): If exec_name is a batch file with whitespace
+ characters in its name, pass NULL as the first argument to
+ CreateProcess. This avoids weird failures due to buggy quoting by
+ CreateProcess. For the details, see the discussion starting at
+ http://lists.gnu.org/archive/html/make-w32/2013-04/msg00008.html.
+
+ * load.c (load_object, load_file): Accept an additional argument
+ DLP and return in it a pointer that can be used to unload the
+ dynamic object.
+
+ * read.c (eval): Call load_file with an additional argument, and
+ record the pointer returned there in the 'struct file' object of
+ dynamic objects in that object's 'struct file'.
+
+ * commands.c (execute_file_commands): Unload dynamic objects
+ before remaking them, to avoid failure to remake if the OS doesn't
+ allow overwriting objects that are in use.
+
+ * filedef.h (struct file): New member dlopen_ptr.
+
+ * gnumake.h (GMK_EXPORT): Define to dllexport/dllimport
+ decorations for Windows and to nothing on other platforms.
+ (gmk_eval, gmk_expand, gmk_add_function): Add GMK_EXPORT qualifier
+ to prototypes.
+
+ * makeint.h (MAIN): Define before including gnumake.h, to give
+ correct dllexport decorations to exported functions.
+ (load_file): Adjust prototype.
+
+ * loadapi.c: Don't include gnumake.h, since makeint.h already
+ includes it, and takes care of defining MAIN before doing so.
+
+ * build_w32.bat (LinkGCC): Produce an import library for functions
+ exported by Make for loadable dynamic objects.
+
+ * w32/compat/posixfcn.c (dlclose): New function.
+
+ * w32/include/dlfcn.h (dlclose): Add prototype.
+
+2013-05-01 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (start_job_command) [WINDOWS32]: Make the same fix for
+ MS-Windows as the previous commit did for Posix platforms.
+ (construct_command_argv_internal): Don't treat a backslash as an
+ escape character before whitespace, if the shell is not a Posix
+ shell. For the description of the problem, see
+ http://lists.gnu.org/archive/html/make-w32/2013-04/msg00014.html.
+
+2013-05-01 Paul Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): Don't redirect output for recursive
+ make jobs, unless we're in makefile synchronization mode.
+
+2013-04-30 Stefano Lattarini <stefano.lattarini@gmail.com> (tiny change)
+
+ build: enable the 'silent-rules' automake options
+
+ * configure.ac (AM_INIT_AUTOMAKE): Here. The future major Automake
+ version 2.0 (ETA about one, one and half year from now) will enable
+ it by default, so better prepare ourselves.
+
+2013-04-30 Stefano Lattarini <stefano.lattarini@gmail.com> (tiny change)
+
+ build: require Autoconf >= 2.62 and Automake >= 1.11.1
+
+ Older versions of those tools should be considered fully obsolete.
+ Also, GNU make already requires Gettext >= 0.18.1, which has been
+ released six months after Automake 1.11.1 and two years after
+ Autoconf 2.62; so the new requirement shouldn't be problematic
+ for people already bootstrapping GNU make from the Git repository.
+
+ * configure.ac (AC_PREREQ): Require Autoconf 2.62 or later.
+ (AM_INIT_AUTOMAKE): Require Automake 1.11.1 or later (1.11 had
+ some serious bugs, and should not be used).
+
+2013-04-30 Stefano Lattarini <stefano.lattarini@gmail.com> (tiny change)
+
+ build: get rid of 'HAVE_ANSI_COMPILER' C preprocessor conditional
+
+ GNU make already assume C89 or later throughout the codebase, and
+ that preprocessor conditional was no longer used anyway.
+
+ * configure.ac: Remove AC_DEFINE of HAVE_ANSI_COMPILER.
+ * config.ami.template: Remove #define of HAVE_ANSI_COMPILER.
+ * config.h-vms.template: Likewise.
+ * config.h.W32.template: Likewise.
+ * configh.dos.template: Likewise.
+
+2013-04-30 Stefano Lattarini <stefano.lattarini@gmail.com> (tiny change)
+
+ cosmetics: fix few innocuous typos
+
+ Most of these were found using Lucas De Marchi's 'codespell' tool.
+
+ * ChangeLog: Fix minor typos.
+ * ChangeLog.2: Likewise.
+ * README.Amiga: Likewise.
+ * TODO.private: Likewise.
+ * function.c: Likewise.
+ * glob/glob.h: Likewise.
+ * job.c: Likewise.
+ * main.c: Likewise.
+ * readme.vms: Likewise.
+ * remake.c: Likewise.
+ * tests/ChangeLog: Likewise.
+ * tests/NEWS: Likewise.
+ * tests/README: Likewise.
+ * tests/scripts/variables/private: Likewise.
+ * vmsdir.h: Likewise.
+ * signame.c: Likewise. While at it, improve line wrapping in the
+ touched comment.
+
+2013-04-29 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/include/dlfcn.h: New file.
+
+ * w32/compat/posixfcn.c: Include dlfcn.h.
+ (dlopen, dlerror, dlsym) [MAKE_LOAD]: New functions, in support of
+ dynamic loading.
+
+ * config.h.W32.template (MAKE_LOAD): Define.
+
+ * load.c (load_object) [HAVE_DOS_PATHS]: Support backslashes and
+ drive letters in file names of dynamic objects.
+
+ * job.c (construct_command_argv_internal) [WINDOWS32]: Return
+ right after generating new_argv for one_shell case. This fixes
+ the Windows build for both Unixy shell and stock Windows shells.
+
+2013-04-28 Eli Zaretskii <eliz@gnu.org>
+
+ * dir.c (local_stat) [WINDOWS32]: Use the wrapper on MS-Windows.
+ If the argument ends in "dir/.", make sure the parent dir exists
+ and is indeed a directory. Fixes Savannah bug #37065.
+
+2013-04-28 Paul Smith <psmith@gnu.org>
+
+ * makeint.h (message_s, error_s): Functions that print to strings
+ rather than directly to files.
+ * misc.c (message_s, error_s): Create them.
+ * job.c (child_error): Print error messages to the output sync
+ logs, if one exists, rather then directly to the terminal.
+ (reap_children): Move the per-line sync after child_error().
+
+ * configure.ac: Remove support for pre-ANSI variadic function calls.
+ * makeint.h: Ditto.
+ * misc.c: Ditto.
+ * config.ami.template: Ditto.
+ * config.h-vms.template: Ditto.
+ * config.h.W32.template: Ditto.
+ * configh.dos.template: Ditto.
+
+ Implement a "per-job" output synchronization option.
+
+ * main.c (decode_output_sync_flags): Recognize the new option.
+ * makeint.h (OUTPUT_SYNC_JOB): Add new values for "job"
+ * job.c (assign_child_tempfiles): In per-job mode, truncate the
+ temp file for re-use by the next job.
+ (sync_output): Don't close the temp files as we may still use them.
+ (free_child): Close the temp files here as we definitely don't
+ need them.
+ (new_job): In per-job output mode, sync_output() after each job.
+ * job.h (struct child): Avoid ifdefs.
+ * make.1: Add new options to the man page.
+ * doc/make.texi (Parallel Output): Break documentation on input
+ and output into separate sections for readability. Document the
+ new "job" and "none" modes.
+
+2013-04-27 Paul Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): Fix oneshell support
+ for non-POSIX-sh shells.
+
+ * load.c (load_object): Extract all POSIX-isms into a separate
+ function for portability.
+ (load_file): Check the .LOADED variable first and don't invoke
+ load_object() if it's already been loaded.
+
+2013-04-27 Eli Zaretskii <eliz@gnu.org>
+
+ * read.c (record_files): Pay attention to .ONESHELL in MS-Windows.
+
+ * job.c (construct_command_argv_internal): Support .ONESHELL on
+ MS-Windows, when the shell is not a Unixy shell.
+
+2013-04-27 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c: Fix compilation error on GNU/Linux due to "label at end
+ of compound statement".
+
+2013-04-27 Frank Heckenbach <f.heckenbach@fh-soft.de> (tiny change)
+
+ * job.c (sync_output): Don't discard the output if
+ acquire_semaphore fails; instead, dump the output unsynchronized.
+
+2013-04-27 Eli Zaretskii <eliz@gnu.org>
+
+ Support --output-sync on MS-Windows.
+ * w32/compat/posixfcn.c: New file, with emulations of Posix
+ functions and Posix functionality for MS-Windows.
+
+ * w32/subproc/sub_proc.c: Include io.h.
+ (process_noinherit): New function, forces a file descriptor to not
+ be inherited by child processes.
+ (process_easy): Accept two additional arguments, and use them to
+ set up the standard output and standard error handles of the child
+ process.
+
+ * w32/include/sub_proc.h (process_easy): Adjust prototype.
+ (process_noinherit): Add prototype.
+
+ * read.c [WINDOWS32]: Include windows.h and sub_proc.h.
+
+ * makeint.h (LOCALEDIR) [WINDOWS32}: Define to NULL if not
+ defined. This is needed because the MS-Windows build doesn't have
+ a canonical place for LOCALEDIR.
+ (WIN32_LEAN_AND_MEAN) [WINDOWS32]: Define, to avoid getting from
+ windows.h header too much stuff that could conflict with the code.
+
+ * main.c <sync_mutex>: New static variable.
+ <switches>: Add support for "--sync-mutex" switch.
+ (decode_output_sync_flags): Decode the --sync-mutex= switch.
+ (prepare_mutex_handle_string) [WINDOWS32]: New function.
+ (main): Add "output-sync" to .FEATURES.
+
+ * job.h (CLOSE_ON_EXEC) [WINDOWS32]: Define to call
+ process_noinherit.
+ (F_GETFD, F_SETLKW, F_WRLCK, F_UNLCK, struct flock) [WINDOWS32]:
+ New macros.
+ (RECORD_SYNC_MUTEX): New macro, a no-op for Posix platforms.
+ (sync_handle_t): New typedef.
+
+ * job.c <sync_handle>: Change type to sync_handle_t.
+ (FD_NOT_EMPTY): Seek to the file's end. Suggested by Frank
+ Heckenbach <f.heckenbach@fh-soft.de>.
+ (pump_from_tmp_fd) [WINDOWS32]: Switch to_fd to binary mode for
+ the duration of this function, and then change back before
+ returning.
+ (start_job_command) [WINDOWS32]: Support output_sync mode on
+ MS-Windows. Use a system-wide mutex instead of locking
+ stdout/stderr. Call process_easy with two additional arguments:
+ child->outfd and child->errfd.
+ (exec_command) [WINDOWS32]: Pass two additional arguments, both
+ -1, to process_easy, to adjust for the changed function signature.
+
+ * function.c (windows32_openpipe) [WINDOWS32]: This function now
+ returns an int, which is -1 if it fails and zero otherwise. It
+ also calls 'error' instead of 'fatal', to avoid exiting
+ prematurely.
+ (func_shell_base) [WINDOWS32]: Call perror_with_name if
+ windows32_openpipe fails, now that it always returns. This avoids
+ a compiler warning that error_prefix is not used in the MS-Windows
+ build.
+
+ * config.h.W32.template (OUTPUT_SYNC): Define.
+
+ * build_w32.bat: Add w32/compat/posixfcn.c to compilation and
+ linking commands.
+
+2013-04-20 Stefano Lattarini <stefano.lattarini@gmail.com> (tiny change)
+
+ * README.git: Our autoconf input file is 'configure.ac', not
+ 'configure.in'. Adjust accordingly.
+ * build_w32.bat: Likewise.
+ * config.h-vms.template: Likewise.
+ * Makefile.DOS.template: Likewise.
+
+2013-04-16 Paul Smith <psmith@gnu.org>
+
+ * misc.c (open_tmpfd): Add a new function that returns a temporary
+ file by file descriptor.
+ (open_tmpfile): Move here from main.c.
+ * job.c (assign_child_tempfiles): Use the new open_tmpfd().
+
+2013-04-15 Paul Smith <psmith@gnu.org>
+
+ * makeint.h (OUTPUT_SYNC_TARGET, OUTPUT_SYNC_MAKE): Rename.
+ * job.c (start_job_command): Use new constants.
+ * main.c: New -O argument format.
+
+ * doc/make.texi (Options Summary): Document the argument to -O.
+ * make.1: Ditto.
+
+ * main.c (define_makeflags): Don't add space between a single-char
+ option and its argument.
+
+2013-04-06 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Implicit Variables): Clarify LDFLAGS vs. LDLIBS.
+ Fixes Savannah bug #37970.
+
+ * remake.c (check_dep): Reconsider files waiting on prerequisites,
+ as they may have finished. Fixes Savannah bug #37703.
+
+2013-02-28 Paul Smith <psmith@gnu.org>
+
+ * function.c (func_realpath): On Solaris (at least) realpath() can
+ fail due to EINTR, so loop it. Fixes Savannah bug #38420.
+
+2013-02-25 Paul Smith <psmith@gnu.org>
+
+ Add a proposed supported API for GNU make loaded objects.
+
+ * doc/make.texi (Loaded Object API): Document it.
+ * Makefile.am (make_SOURCES): Add new loadapi.c.
+ * dep.h: Remove eval_buffer(); moved to loadapi.c:gmk_eval().
+ * read.c (eval_buffer): Change eval_buffer() signature.
+ * main.c (main): Change eval_buffer() signature.
+ * variable.h (define_new_function): Change func_ptr signature.
+ * load.c (SYMBOL_EXTENSION): Change the extension.
+ * loadapi.c: Implement the new API.
+ * gnumake.h (gmk_eval): New function prototype.
+ (gmk_expand) Ditto.
+ (gmk_add_function) Ditto.
+ * gmk-default.scm (gmk-eval): Remove: now implemented in guile.c.
+ * guile.c (guile_expand_wrapper): Use gmk_expand()
+ (guile_eval_wrapper): Implement eval here to avoid double-expansion.
+ (guile_define_module): Define gmk-eval.
+ (func_guile): Use new func_ptr calling model.
+ (guile_gmake_setup): Use gmk_add_function() to declare $(guile ...)
+ * function.c (function_table_entry): Provide alternative func_ptr.
+ (func_eval): New signature for eval_buffer();
+ (function_table_init): New initialization for function_table_entry.
+ (expand_builtin_function): Support alternative invocation signature.
+ (define_new_function): Ditto.
+
+2013-01-20 Paul Smith <psmith@gnu.org>
+
+ * gnumake.h: New file to contain externally-visible content.
+ * makeint.h: Include gnumake.h. Move gmk_floc type to gnumake.h.
+ * Makefile.am (include_HEADERS): Install the gnumake.h header.
+
+ * makeint.h: Change struct floc to gmk_floc typedef.
+ * Many: Use the new typedef.
+
+ * make.h: Rename to makeint.h.
+ * Many: Use the new name makeint.h.
+
+2013-01-19 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (load Directive): Update to discuss location of
+ loaded object file.
+ (Remaking Loaded Objects): Document remaking of loaded objects.
+
+ * main.c (main): Rename READ_MAKEFILES to READ_FILES.
+ * read.c: Change READ_MAKEFILES to READ_FILES since it now
+ contains loaded object files as well.
+ (read_all_makefiles): Ditto.
+ (eval_makefile): Ditto.
+ (eval): Add any loaded file to the READ_FILES list, so that it
+ will be considered for re-build.
+
+ * load.c (load_file): Return the simple filename (no symbol) in
+ the LDNAME argument (now a const char **).
+ This filename should no longer have "./" prepended: modify the
+ function to always check the current directory if the name has no
+ "/", before using the normal methods.
+ * make.h: Change the load_file() prototype.
+
+ * README.git: Add a bit more documentation on Git workflow & rules.
+
+2013-01-13 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Restore all make flags after re-exec is complete.
+ Fixes Savannah bug #38051.
+
+2013-01-12 Paul Smith <psmith@gnu.org>
+
+ Convert CVS archive to Git.
+
+ * configure.in: Rename to configure.ac.
+ * README.cvs: Rename to README.git and rework for Git.
+ * maintMakefile: Use git clean for cleanup.
+ * ChangeLog: Use new Git repository URL.
+ * ChangeLog.2: Ditto.
+ * Makefile.am: Change documentation for Git
+ * Makefile.DOS.template: Ditto.
+ * README.template: Ditto.
+ * build_w32.bat: Ditto.
+ * prepare_w32.bat: Ditto.
+ * .cvsignore: Rename to .gitignore, and change to Git format.
+
+2012-12-08 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (create_batch_file): Fix last change: always increment the
+ counter of batch files before trying to use it.
+
+2012-12-07 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (construct_command_argv_internal): Remove " from
+ sh_chars_dos[]. Ignore an escaped backslash inside a string
+ quoted with "..". This lifts the 4KB or 8KB command-line length
+ limitation imposed by the Windows shells when a command uses quoted
+ strings, because we now don't call the shell in that case.
+
+ * job.c (create_batch_file): Declare the counter of batch files
+ static, to avoid having 2 jobs using the same file name and
+ stepping on each other's toes. When all 64K names are used up,
+ make one more loop looking for slots that became vacant. This
+ avoids leaving behind temporary batch files in the temporary
+ directory, which happens frequently on a fast machine when using
+ parallel builds.
+ (reap_children): Add debug message for when removal of a temporary
+ batch file fails.
+
+2012-10-29 Paul Smith <psmith@gnu.org>
+
+ New feature: "load" directive for dynamically-loaded objects.
+
+ * NEWS: Document new "load" directive.
+ * doc/make.texi (Extending make): New chapter on extensions to make.
+ * configure.in: Check for dlopen/dlsym/dlerror and -ldl.
+ * Makefile.am (make_SOURCES): Add new file load.c.
+ * make.h: Prototype for load_file().
+ * main.c (main): Add "load" to .FEATURES if it's available.
+ * read.c (eval): Parse "load" and "-load" directives.
+
+2012-09-29 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Require a new version of gettext (1.18.1).
+ Fixes Savannah bug #37307.
+
+2012-09-09 Paul Smith <psmith@gnu.org>
+
+ * configure.in (bsd_signal): Define _GNU_SOURCE, a la make.h.
+ Fixes Savannah bug #32247.
+
+ * remake.c (update_file_1): Force intermediate files to be
+ considered, not pruned, if their non-intermediate parent needs to
+ be remade. Fixes Savannah bug #30653.
+
+ * job.c (construct_command_argv_internal): Keep the command line
+ on the heap for very long lines. Fixes Savannah bug #36451.
+
+ * function.c (func_realpath): BSD realpath(3) doesn't fail if the
+ file does not exist: use stat. Fixes Savannah bug #35919.
+
+ * file.c (expand_deps): Duplicate the current variable buffer, not
+ the old pointer. Fixes Savannah bug #36925.
+
+ * read.c (eval): If we detect an initial UTF-8 BOM, skip it.
+ Fixes Savannah bug #36529.
+ (record_target_var): Remove unused variable "fname".
+ (eval): Use the correct pointer when adding to the variable buffer.
+ Fixes Savannah bug #36106.
+
+2012-09-09 Eli Zaretskii <eliz@gnu.org>
+
+ * read.c (unescape_char): Fix a thinko in the last change.
+
+2012-09-09 Paul Smith <psmith@gnu.org>
+
+ * default.c (default_variables): Use a correct default LIBPPATERNS
+ for MacOS. Fixes Savannah bug #37197.
+
+ * read.c (record_files): Reset the default macro values if .POSIX
+ is set. Fixes Savannah bug #37069.
+ (parse_file_seq): Break out of an infinite loop if we're not
+ making progress when parsing archive references.
+
+2012-09-01 Eli Zaretskii <eliz@gnu.org>
+
+ * README.W32.template: Update for job-server and Guile support.
+
+ * read.c (unescape_char): Advance 'p' after copying the unescaped
+ characters. Otherwise the backslashes are incorrectly erased from
+ the original string.
+
+2012-03-05 Paul Smith <psmith@gnu.org>
+
+ Update copyright notices to use year ranges, as allowed by
+ clarifications in the GNU Maintainer's Manual.
+
+2012-03-04 Paul Smith <psmith@gnu.org>
+
+ * read.c (unescape_char): New function to remove escapes from a char.
+ (record_files): Call it on the dependency string to unescape ":".
+ Fixes Savannah bug #12126 and bug #16545.
+
+ * make.h (CSTRLEN): Determine the length of a constant string.
+ * main.c: Use the new macro.
+ * read.c: Ditto.
+ * variable.h: Ditto.
+ * function.c: Simplify checks for function alternatives.
+
+ * expand.c (variable_append): If the current set is local and the
+ next one is not a parent, then treat the next set as
+ local as well. Fixes Savannah bug #35468.
+
+2012-03-03 Paul Smith <psmith@gnu.org>
+
+ * acinclude.m4 (AC_STRUCT_ST_MTIM_NSEC): Add support for AIX 5.2+
+ nanosecond timestamps. Fixes Savannah bug #32485.
+
+ Convert uses of `foo' for quoting to 'foo' to reflect changes in
+ the GNU Coding Standards. Fixes Savannah bug #34530.
+
+ * job.c (construct_command_argv_internal): In oneshell we need to
+ break the SHELLFLAGS up for argv. Fixes Savannah bug #35397.
+
+ * function.c (func_filter_filterout): Recompute the length of each
+ filter word in case it was compressed due to escape chars. Don't
+ reset the string as it's freed. Fixes Savannah bug #35410.
+
+ * misc.c (collapse_continuations): Only use POSIX-style
+ backslash/newline handling if the .POSIX target is set.
+ Addresses Savannah bug #16670 without backward-incompatibility.
+ * NEWS: Document behavior change.
+ * doc/make.texi (Splitting Lines): New section describing how to
+ use backslash/newline to split long lines.
+
+2012-02-26 Paul Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Check the stem length to avoid
+ stack overflows in stem_str. Fixes Savannah bug #35525.
+
+2012-02-03 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/sub_proc.c (proc_stdin_thread, proc_stdout_thread)
+ (proc_stderr_thread, process_pipe_io): Ifdef away unused
+ functions.
+
+ * w32/subproc/w32err.c (map_windows32_error_to_string) [_MSC_VER]:
+ Don't use TLS storage for szMessageBuffer. Ifdef away special
+ code for handling Winsock error codes. Make the function return a
+ `const char *'. Suggested by Ozkan Sezer. Fixes Savannah bug #34832.
+
+2012-01-29 Paul Smith <psmith@gnu.org>
+
+ * gmk-default.scm (to-string-maybe): Variables map to empty strings.
+ In Guile 2.0, (define ...) results in a variable object so make
+ sure that maps to an empty string in make.
+
+ * variable.c (parse_variable_definition): New POSIX assignment ::=
+ Take a struct variable to return more information after parsing.
+ (assign_variable_definition): New parse_variable_definition() call.
+ * variable.h: New declaration of parse_variable_definition().
+ * read.c (do_define): New parse_variable_definition() call.
+ (parse_var_assignment): Ditto.
+ (get_next_mword): Parse ::= as a variable assignment.
+ * doc/make.texi (Flavors): Describe the new ::= syntax.
+ * NEWS: Mention the ::= operator.
+
+ * variable.h (struct variable): Rearrange elts to reduce struct size.
+
+ * function.c (func_file): Create a new function, $(file ...)
+ * doc/make.texi (File Function): Document the $(file ..) function.
+ * NEWS: Announce it.
+
+ * gmk-default.scm (to-string-maybe): Use a more portable way to
+ test for unprintable characters.
+ * configure.in [GUILE]: Guile 1.6 doesn't have pkg-config
+ * build_w32.bat: Ditto.
+
+2012-01-28 Eli Zaretskii <eliz@gnu.org>
+
+ * config.h.W32.template: Update from config.h.in.
+
+ Support a Windows build with Guile.
+
+ * README.W32.template: Update with instructions for building with
+ Guile.
+
+ * build_w32.bat: Support building with Guile.
+
+ * make.h [HAVE_STDINT_H]: Include stdint.h.
+
+ * main.c (main, clean_jobserver): Move declarations of variables
+ not used in the WINDOWS32 build to the #else branch, to avoid
+ compiler warnings.
+
+ Fix failures on MS-Windows when Make's standard handles are invalid.
+ This can happen when Make is invoked from a GUI application.
+
+ * w32/subproc/sub_proc.c (process_init_fd): Don't dereference
+ pproc if it is a NULL pointer.
+ (process_begin, process_cleanup): Don't try to close pipe handles
+ whose value is INVALID_HANDLE_VALUE.
+ (process_easy): Initialize hIn, hOut, and hErr to
+ INVALID_HANDLE_VALUE. If DuplicateHandle fails with
+ ERROR_INVALID_HANDLE, duplicate a handle for the null device
+ instead of STD_INPUT_HANDLE, STD_OUTPUT_HANDLE or
+ STD_ERROR_HANDLE. Don't try to close pipe handles whose value is
+ INVALID_HANDLE_VALUE.
+
+ * function.c (windows32_openpipe): Initialize hIn and hErr to
+ INVALID_HANDLE_VALUE. If DuplicateHandle fails with
+ ERROR_INVALID_HANDLE, duplicate a handle for the null device
+ instead of STD_INPUT_HANDLE or STD_ERROR_HANDLE. Fix indentation.
+ Don't try to close handles whose value is INVALID_HANDLE_VALUE.
+
+2012-01-25 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (define_new_function): Fix format strings in calls to
+ `fatal'.
+
+2012-01-17 Paul Smith <psmith@gnu.org>
+
+ * guile.c (func_guile): Handle NULL returns from Guile.
+
+2012-01-16 Paul Smith <psmith@gnu.org>
+
+ * make.h (PATH_SEPARATOR_CHAR): Allow resetting for crosscompiling
+ for Windows. Patch by Chris Sutcliffe <ir0nh34d@gmail.com>
+ Fixes Savannah bug #34818.
+
+2012-01-15 Paul Smith <psmith@gnu.org>
+
+ * variable.h: Prototype an interface for defining new make functions.
+ * function.c (define_new_function): Define it.
+ (func_guile): Remove the "guile" function.
+ (function_table_init): Ditto.
+ * guile.c (func_guile): Add the "guile" function here.
+ (setup_guile): Call define_new_function() to define it.
+ (guile_eval_string): Obsolete.
+
+ * all: Update copyright notices.
+
+2012-01-12 Paul Smith <psmith@gnu.org>
+
+ Support GNU Guile as an embedded extension language for GNU make.
+
+ * NEWS: Note the new Guile capability.
+ * Makefile.am (EXTRA_DIST, make_SOURCES): Add new guile source files.
+ (AM_CFLAGS): Add Guile compiler flags.
+ (guile): Add a rule for converting default SCM into a C header.
+ * configure.in: Add support for --with-guile.
+ Also, convert the entire file to properly escaped autoconf m4, and
+ utilize newer features such as AS_IF() and AS_CASE().
+ * doc/make.texi (Guile Function): Document the GNU guile integration.
+ * make.h (guile_eval_string, guile_boot): Prototypes for Guile.
+ * main.c (main): Run guile_boot() to handle main().
+ (real_main): All the previous content of main() is here.
+ (real_main): Add "guile" to the .FEATURES variable.
+ * function.c (func_guile): Call Guile.
+ * guile.c: New file implementing GNU make integration with GNU Guile.
+ * gmk-default.scm: The integration of GNU make with Guile uses
+ Guile itself for much of the parsing and conversion of return
+ types, etc. This implementation is embedded into GNU make.
+ * config.h-vms.template: Disable Guile support.
+ * config.h.W32.template: Ditto.
+ * configh.dos.template: Ditto.
+ * config.ami.template: Ditto.
+ * makefile.vms: Add new Guile files.
+ * Makefile.DOS.template: Ditto.
+ * Makefile.ami: Ditto.
+ * NMakefile.template: Ditto.
+ * SMakefile.template: Ditto.
+ * build_w32.bat: Ditto.
+ * dosbuild.bat: Ditto.
+ * make_msvc_net2001.vcproj: Ditto.
+
+2011-11-15 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Use %ld when printing DWORD values.
+ * job.c (new_job): Ditto.
+ * w32/include/sub_proc.h: Use const.
+ * w32/subproc/sub_proc.c (open_jobserver_semaphore): Use const.
+ Fixes Savannah bug #34830. Changes suggested by Ozkan Sezer.
+
+ * configure.in (MAKE_JOBSERVER): Enable jobserver on W32 systems.
+ * config.h.W32.template (MAKE_JOBSERVER): Ditto.
+
+2011-11-14 Paul Smith <psmith@gnu.org>
+
+ * read.c (eval): parse_file_seq() might shorten the string due to
+ backslash removal. Start parsing again at the colon.
+ Fixes Savannah bug #33399.
+
+2011-11-13 Paul Smith <psmith@gnu.org>
+
+ * file.c (file_timestamp_cons): Match up data types to avoid warnings.
+ * filedef.h: Ditto.
+ * misc.c (concat): Ditto.
+ * read.c (eval): Assign value to avoid warnings.
+ * function.c (func_shell_base): Use fork() instead of vfork() to
+ avoid warnings.
+ * make.h (INTEGER_TYPE_SIGNED): Use <=0 to avoid warnings.
+ Fixes Savannah bug #34608.
+
+ * job.c (construct_command_argv): Remove _p.
+ (construct_command_argv_internal): Remove _ptr.
+ Fixes Savannah bug #32567.
+
+ * main.c (clean_jobserver): Don't write the free token to the pipe.
+ Change suggested by Tim Newsome <tnewsome@aristanetworks.com>
+
+ * acinclude.m4 (AC_STRUCT_ST_MTIM_NSEC): Add support for Darwin.
+ * filedef.h (FILE_TIMESTAMP_STAT_MODTIME): Ditto.
+ Patch provided by Troy Runkel <Troy.Runkel@mathworks.com>
+
+2011-10-11 Troy Runkel <Troy.Runkel@mathworks.com>
+
+ * config.h.W32: Enable job server support for Windows.
+ * main.c [WINDOWS32]: Include sub_proc.h
+ (main): Create a named semaphore to implement the job server.
+ (clean_jobserver): Free the job server semaphore when make is finished.
+ * job.c [WINDOWS32]: Define WAIT_NOHANG
+ (reap_children): Support non-blocking wait for child processes.
+ (free_child): Release job server semaphore when child process finished.
+ (job_noop): Don't define function on Windows.
+ (set_child_handler_action_flags): Don't define function on Windows.
+ (new_job): Wait for job server semaphore or child process termination.
+ (exec_command): Pass new parameters to process_wait_for_any.
+ * w32/include/sub_proc.h [WINDOWS32]: New/updated EXTERN_DECL entries.
+ * w32/subproc/sub_proc.c [WINDOWS32]: Added job server implementation.
+ (open_jobserver_semaphore): Open existing job server semaphore by name.
+ (create_jobserver_semaphore): Create new job server named semaphore.
+ (free_jobserver_semaphore): Close existing job server semaphore.
+ (acquire_jobserver_semaphore): Decrement job server semaphore count.
+ (release_jobserver_semaphore): Increment job server semaphore count.
+ (has_jobserver_semaphore): Returns whether job server semaphore exists.
+ (get_jobserver_semaphore_name): Returns name of job server semaphore.
+ (wait_for_semaphore_or_child_process): Wait for either the job server
+ semaphore to become signalled or a child process to terminate.
+ (process_wait_for_any_private): Support for non-blocking wait for child.
+ (process_wait_for_any): Added support for non-blocking wait for child.
+ (process_file_io): Pass new parameters to process_wait_for_any_private.
+
+2011-09-18 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): If we're re-exec'ing and we're the master make,
+ then restore the job_slots value so it goes back into MAKEFLAGS
+ properly. See Savannah bug #33873.
+
+ * remake.c (library_search): STD_DIRS is computed when other
+ static vars like buflen etc. are computed, so it must be static
+ as well. See Savannah bug #32511.
+
+2011-09-16 Paul Smith <psmith@gnu.org>
+
+ * maintMakefile (do-po-update): Apparently we have to avoid
+ certificate checks on the http://translationproject.org site now.
+
+2011-09-12 Paul Smith <psmith@gnu.org>
+
+ * read.c (eval): Ensure exported variables are defined in the
+ global scope. Fixes Savannah bug #32498.
+
+2011-09-11 Paul Smith <psmith@gnu.org>
+
+ * Makefile.am (dist-hook): Remove w32/Makefile and .deps/ from the
+ dist file. Fixes Savannah bug #31489.
+
+ * doc/make.texi (Complex Makefile): Add a hint about using
+ #!/usr/bin/make (for Savannah support request #106459)
+
+2011-09-02 Paul Smith <psmith@gnu.org>
+
+ * remake.c (touch_file): If we have both -n and -t, -n takes
+ precedence. Patch from Michael Witten <mfwitten@gmail.com>
+
+2011-08-29 Paul Smith <psmith@gnu.org>
+
+ * expand.c (variable_expand_string): Always allocate a new buffer
+ for a string we're expanding. The string we're working on can get
+ freed while we work on it (for example if it's the value of a
+ variable which modifies itself using an eval operation).
+ See Savannah patch #7534 for the original report by Lubomir Rintel.
+
+2011-06-12 Paul Smith <psmith@gnu.org>
+
+ * read.c (parse_file_seq): Move the check for empty members out of
+ the loop so we can go to the next member properly.
+ Another fix for Savannah bug #30612.
+
+ * config.h-vms.template: Newer versions of VMS have strncasecmp()
+ Patch provided by: Hartmut Becker <becker.ismaning@freenet.de>
+
+2011-05-07 Paul Smith <psmith@gnu.org>
+
+ * expand.c (variable_append): Add a LOCAL argument to track
+ whether this is the first invocation or not. If it's not and
+ private_var is set, then skip this variable and try the next one.
+ Fixes Savannah bug #32872.
+
+ * read.c (parse_file_seq): Ensure existence checks use glob().
+
+2011-05-07 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (construct_command_argv_internal): Don't assume shellflags
+ is always non-NULL. Escape-protect characters special to the
+ shell when copying the value of SHELL into new_line. Fixes
+ Savannah bug #23922.
+
+2011-05-02 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Special Variables): Add documentation for the new
+ words in .FEATURES. Fixes Savannah bug #32058.
+ (Flavor Function): Rewrite the section on the flavor function.
+ Fixes Savannah bug #31582.
+
+ * function.c (func_sort): Use the same algorithm to count the
+ number of words we will get after the split, as we use to split.
+ Based on a patch from Matthias Hopf. Fixes Savannah bug #33125.
+
+ * make.h: Make global variable stack_limit extern.
+ Fixes Savannah bug #32753.
+
+2011-05-01 Paul Smith <psmith@gnu.org>
+
+ * read.c (parse_file_seq): Don't try to invoke glob() unless there
+ are potential wildcard characters in the filename. Performance
+ enhancement suggested by Michael Meeks <michael.meeks@novell.com>
+
+2011-04-29 Boris Kolpackov <boris@codesynthesis.com>
+
+ * read.c (eval_makefile): Delay caching of the file name until after
+ all the expansions and searches.
+
+2011-04-17 David A. Wheeler <dwheeler@dwheeler.com>
+
+ * doc/make.texi (Reading Makefiles): Document "!=".
+ (Setting): Ditto.
+ (Features): Ditto.
+ * variable.h (enum variable_flavor): New type "f_shell".
+ * variable.c (shell_result): Send a string to the shell and store
+ the output.
+ (do_variable_definition): Handle f_shell variables: expand the
+ value, then send it to the shell and store the result.
+ (parse_variable_definition): Parse "!=" shell assignments.
+ * read.c (get_next_mword): Treat "!=" as a varassign word.
+ * function.c (fold_newlines): If trim_newlines is set remove all
+ trailing newlines; otherwise remove only the last newline.
+ (func_shell_base): Move the guts of the shell function here.
+ (func_shell): Call func_shell_base().
+
+2011-02-21 Paul Smith <psmith@gnu.org>
+
+ * strcache.c (various): Increase performance based on comments
+ from Ralf Wildenhues <Ralf.Wildenhues@gmx.de>. Stop looking for
+ a buffer when we find the first one that fits, not the best fit.
+ If there is not enough free space in a buffer move it to a
+ separate list so we don't have to walk it again.
+ * make.h (NDEBUG): Turn off asserts unless maintainer mode is set.
+ (strcache_add_len, strcache_setbufsize): Use unsigned length/size.
+ * maintMakefile (AM_CPPFLAGS): Enable MAKE_MAINTAINER_MODE.
+
+ * remake.c (complain): Move translation lookups closer to use.
+
+2011-02-13 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi: Clean up references to "static" variables and
+ semicolon errors. Patch from Michael Witten <mfwitten@gmail.com>.
+
+2010-12-27 Paul Smith <psmith@gnu.org>
+
+ * make.1: Update the header/footer info in the man page.
+
+2010-11-28 Paul Smith <psmith@gnu.org>
+
+ * read.c (record_target_var): Don't reset v if it's the same as
+ the global version. Fixes Savannah bug #31743.
+
+2010-11-06 Paul Smith <psmith@gnu.org>
+
+ * variable.c (print_auto_variable): Print auto variables; ignore others.
+ (print_noauto_variable): Print non-auto variables; ignore others.
+ (print_variable_set): Allow the caller to select which type to print.
+ (print_target_variables): Show all the non-auto variables for a target.
+
+ * default.c (install_default_suffix_rules): Initialize recipe_prefix.
+ * rule.c (install_pattern_rule): Ditto.
+ * read.c (record_files): Pass in the current recipe prefix. Remember
+ it in the struct command for these targets.
+ (eval): Remember the value of RECIPEPREFIX when we start parsing.
+ Do not remove recipe prefixes from the recipe here: we'll do it later.
+ * job.c (start_job_command): Remove recipe prefix characters early,
+ before we print the output or chop it up.
+ * file.c (print_file): If recipe_prefix is not standard, reset it
+ in -p output. Assign target variables in -p output as well.
+
+ * commands.c (chop_commands): Max command lines is USHRT_MAX.
+ Set any_recurse as a bitfield.
+ * make.h (USHRT_MAX): Define if not set.
+
+2010-10-27 Paul Smith <psmith@gnu.org>
+
+ * commands.h (struct commands): Rearrange to make better use of
+ memory. Add new recipe_prefix value.
+
+2010-10-26 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Setting): Document the treatment of
+ backslash-newline in variable values.
+ * misc.c (collapse_continuations): Do not collapse multiple
+ backslash-newlines into a single space. Fixes Savannah bug #16670.
+
+2010-08-29 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Implicit Variables): Document LDLIBS and LOADLIBES.
+ Fixes Savannah bug #30807.
+ (Instead of Execution): Mention that included makefiles are still
+ rebuilt even with -n. Fixes Savannah bug #30762.
+
+ * configure.in: Bump to 3.82.90.
+
+ * make.h: Add trace_flag variable.
+ * main.c (switches): Add --trace option.
+ (trace_flag): Declare variable.
+ * job.c (start_job_command): Show recipe if trace_flag is set.
+ (new_job): Show trace messages if trace_flag is set.
+ * doc/make.texi (Options Summary): Document the new --trace option.
+ * make.1: Add --trace documentation.
+ * NEWS: Mention --trace.
+
+ * job.c (child_error): Show recipe filename/linenumber on error.
+ Also show "(ignored)" when appropriate even for signals/coredumps.
+ * NEWS: Mention file/linenumber change.
+
+ * main.c (main): Print version info when DB_BASIC is set.
+
+ * job.c (construct_command_argv_internal): If shellflags is not
+ set, choose an appropriate default value. Fixes Savannah bug #30748.
+
+2010-08-27 Eli Zaretskii <eliz@gnu.org>
+
+ * variable.c (define_automatic_variables) [__MSDOS__ || WINDOWS32]:
+ Remove trailing backslashes in $(@D), $(<D), etc., for consistency
+ with forward slashes. Fixes Savannah bug #30795.
+
+2010-08-13 Paul Smith <psmith@gnu.org>
+
+ * NEWS: Accidentally forgot to back out the sorted wildcard
+ enhancement in 3.82, so update NEWS.
+ Also add NEWS about the error check for explicit and pattern
+ targets in the same rule, added to 3.82.
+
+ * main.c (main): Add "oneshell" to $(.FEATURES) (forgot to add
+ this in 3.82!)
+
+ * read.c (parse_file_seq): Fix various errors parsing archives
+ with multiple objects in the parenthesis, as well as wildcards.
+ Fixes Savannah bug #30612.
+
+2010-08-10 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Expand MAKEFLAGS before adding it to the
+ environment when re-exec'ing. Fixes Savannah bug #30723.
+
+2010-08-07 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/build.bat: Make all 3 cl.exe compile command lines
+ use the same /I switches. Fixes Savannah bug #30662.
+
+ * function.c (func_shell) [WINDOWS32]: Reset just_print_flag
+ around the call to construct_command_argv, so that a temporary
+ batch file _is_ created when needed for $(shell).
+ Fixes Savannah bug #16362.
+
+2010-08-07 Juan Manuel Guerrero <juan.guerrero@gmx.de>
+
+ * configh.dos.template (HAVE_STRNCASECMP): Define.
+
+2010-07-28 Paul Smith <psmith@gnu.org>
+
+ Version 3.82 released.
+
+ * configure.in: Change release version.
+ * NEWS: Change the date.
+
+ * read.c (parse_file_seq): Remove GLOB_NOSORT for
+ backward-compatibility. We'll add it back in next release.
+ * NEWS: Note it.
+
+2010-07-24 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (pid2str) [WINDOWS32]: Fix CPP conditionals for using %Id
+ format.
+
+2010-07-18 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Switch bsd_signal to AC_CHECK_DECLS() to make sure
+ we have a declaration. Fixes Savannah bug #25713 (maybe?)
+ * doc/make.texi (Complex Makefile): Cleanup variable assignments.
+ (One Shell): New subsection for the .ONESHELL special target.
+
+ Patches by Ozkan Sezer <sezeroz@gmail.com>:
+
+ * misc.c (strncasecmp): Local implementation for systems without.
+ * config.h.W32.template (HAVE_STRNICMP): Define on Windows.
+ * configure.in: Check for strncasecmp/strncmpi/strnicmp.
+ * job.c [WINDOWS32]: Don't define dup2 on Windows.
+ (pid2str): Use "%Id" even with MSVC
+ (exec_command): Cast to pid_t when calling pid2str().
+ * w32/subproc/sub_proc.c [WINDOWS32]: Include config.h first.
+ Use stddef.h on MSVC to get intptr_t.
+ * w32/subproc/misc.c [WINDOWS32]: Include config.h first.
+ * w32/compat/dirent.c [WINDOWS32]: Include config.h first.
+ (readdir): Cast -1 to correct type for d_ino.
+ * w32/pathstuff.c [WINDOWS32]: Ensure make.h is included first.
+ * make.h [WINDOWS32]: Don't prototype alloca() on Windows.
+ Add configuration for strncasecmp().
+ * main.c (ADD_SIG) [WINDOWS32]: Avoid warnings in MSVC.
+ * config.h.W32.template [WINDOWS32]: Don't warn on unsafe
+ functions or variables.
+ * NMakefile.template [WINDOWS32]: Remove /MACHINE:I386.
+ * main.c (clean_jobserver): Cast due to MSVC brokenness.
+ (decode_switches): Ditto.
+ * vpath.c (construct_vpath_list): Ditto.
+ * rule.c (freerule): Ditto.
+ * ar.c (ar_glob): Ditto.
+
+2010-07-16 Boris Kolpackov <boris@codesynthesis.com>
+
+ * misc.c (concat): Fix buffer overrun.
+
+2010-07-12 Paul Smith <psmith@gnu.org>
+
+ Update copyrights to add 2010.
+
+ * build_w32.bat: Support for MSVC Windows x86_64 builds.
+ * job.c: Don't define execve() on MSVC/64bit.
+ Patch by Viktor Szakats. Fixes Savannah bug #27590.
+
+2010-07-12 Eli Zaretskii <eliz@gnu.org>
+
+ * make.h (alloca) [!__GNUC__]: Don't define prototype.
+ (int w32_kill): Use pid_t for process ID argument.
+ Fixes Savannah bug #27809.
+
+2010-07-12 Paul Smith <psmith@gnu.org>
+
+ Integrated new .ONESHELL feature.
+ Patch by David Boyce <dsb@boyski.com>. Modified by me.
+
+ * NEWS: Add a note about the new feature.
+ * job.c (is_bourne_compatible_shell): Determine whether we're
+ using a standard POSIX shell or not.
+ (start_job_command): Accept '-ec' as POSIX shell flags.
+ (construct_command_argv_internal): If one_shell is set and we are
+ using a POSIX shell, remove "interior" prefix characters such as
+ "@", "+", "-". Also treat "\n" as a special character when
+ choosing the slow path, if ONESHELL is set.
+ * job.h (is_bourne_compatible_argv): Define the new function.
+
+ * make.h (one_shell): New global variable to remember setting.
+ * main.c: Declare it.
+ * read.c (record_files): Set it.
+ * commands.c (chop_commands): If one_shell is set, don't chop
+ commands into multiple lines; just keep one line.
+
+2010-07-09 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/sub_proc.c: Include stdint.h.
+ (sub_process_t): Use intptr_t for file handles and pid_t for
+ process ID.
+ (process_pipes, process_init_fd, process_begin): Use intptr_t for
+ file handles and pid_t for process ID. Fixes Savannah bug #27809.
+ Patch by Ozkan Sezer <sezeroz@gmail.com>
+
+ * function.c (abspath): Support absolute file names in UNC format.
+ Fixes Savannah bug #30312.
+
+ * job.c (pid2str) [WINDOWS32]: Don't use %Id with GCC < 4.x.
+ (exec_command) [WINDOWS32]: Use pid2str instead of non-portable
+ %Id.
+
+ * main.c (handle_runtime_exceptions): Use %p to print addresses,
+ to DTRT on both 32-bit and 64-bit hosts. Savannah bug #27809.
+
+ * job.c (w32_kill, start_job_command, create_batch_file): Use
+ pid_t for process IDs and intptr_t for the 1st arg of
+ _open_osfhandle.
+ * function.c (windows32_openpipe): Use pid_t for process IDs and
+ intptr_t for the 1st arg of _open_osfhandle.
+ (func_shell): Use pid_t for process IDs.
+ * main.c (main) [WINDOWS32]: Pacify the compiler.
+ * config.h.W32.template (pid_t): Add a definition for 64-bit
+ Windows builds that don't use GCC. Fixes Savannah bug #27809.
+ Patch by Ozkan Sezer <sezeroz@gmail.com>
+
+2010-07-07 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Bump to a new prerelease version 3.81.91.
+
+2010-07-06 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Set a default value of "-c" for .SHELLFLAGS.
+ * NEWS: Mention the new behavior of .POSIX and the new .SHELLFLAGS
+ variable.
+ * job.c (construct_command_argv): Retrieve the .SHELLFLAGS value
+ and pass it to construct_command_argv_internal().
+ (construct_command_argv_internal): If .SHELLFLAGS is non-standard
+ use the slow path. Use that value instead of hard-coded "-c".
+
+2010-07-05 Paul Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): lastslash can be const.
+ * dir.c (downcase): Remove unused variable.
+ * hash.c (hash_init): Cast sizeof for error message.
+ * arscan.c (ar_scan): Cast to char* for WINDOWS32.
+ (ar_member_touch): Ditto.
+ * ar.c (glob_pattern_p): Avoid symbol collision: open -> opened
+ * signame.c (strsignal): Ditto: signal -> sig
+ * job.c (create_batch_file): Ditto: error -> error_string
+ (pid2str): Portably convert a pid_t into a string
+ (reap_children): Use it.
+ (start_waiting_job): Use it.
+ Savannah bug #27809. Patch by Ozkan Sezer <sezeroz@gmail.com>
+
+2010-07-03 Paul Smith <psmith@gnu.org>
+
+ * read.c (parse_file_seq): All archive groups must end with ')' as
+ the LAST character in a word. If there is no word ending in ')'
+ then it's not an archive group. Fixes Savannah bug #28525.
+
+2010-07-01 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Append optional features using separate calls.
+ Not as efficient but not all compilers allow conditionals inside
+ macro calls. Fixes Savannah bug #29244.
+
+2010-01-10 Paul Smith <psmith@gnu.org>
+
+ * make.h (patheq): Rename strieq() to patheq() for clarity.
+ * dir.c (dir_contents_file_exists_p): Use it.
+
+ * dir.c (file_impossible): Convert xmalloc/memset to xcalloc.
+ * file.c (enter_file): Ditto.
+ * job.c (new_job): Ditto.
+
+2009-12-11 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (construct_command_argv_internal) <sh_cmds_dos>
+ [WINDOWS32]: Add "echo." and a few more commands that are built
+ into cmd.exe. Fixes Savannah bug #28126.
+
+ * file.c (lookup_file) [HAVE_DOS_PATHS]: Treat '\\' like we do
+ with '/'.
+
+2009-11-15 Paul Smith <psmith@gnu.org>
+
+ Patches for VMS provided by Hartmut Becker <Hartmut.Becker@hp.com>
+
+ * vmsjobs.c (ctrlYPressed) [VMS]: Deal with CTRL-Y.
+ (vmsHandleChildTerm) [VMS]: Ditto.
+ (astYHandler) [VMS]: Ditto.
+ (tryToSetupYAst) [VMS]: Ditto.
+ (child_execute_job) [VMS]: Ditto.
+
+ * vmsify.c (trnlog) [VMS]: Fix const errors.
+ (vmsify) [VMS]: Ditto.
+
+ * readme.vms [VMS]: Update with notes for 3.82.
+
+ * job.h (comname) [VMS]: Remember the temporary command filename
+
+ * dir.c (vmsify) [VMS]: Fix const errors.
+ (vms_hash) [VMS]: Ditto.
+ (vmsstat_dir) [VMS]: Ditto.
+ (find_directory) [VMS]: Fix case-insensitive option for VMS
+ (dir_contents_file_exists_p) [VMS]: Ditto.
+ (file_impossible) [VMS]: Ditto.
+
+ * config.h-vms.template (HAVE_FDOPEN) [VMS]: Have it.
+ (HAVE_STRCASECMP) [VMS]: Ditto.
+
+ * arscan.c (VMS_get_member_info) [VMS]: Fix timezone computation.
+ (ar_scan) [VMS]: Fix const error.
+
+2009-11-12 Boris Kolpackov <boris@codesynthesis.com>
+
+ * vpath.c (vpath_search, selective_vpath_search): Add index arguments
+ which allows the caller to get the index of the matching directory.
+
+ * make.h (vpath_search): Update prototype.
+
+ * remake.c (library_search): Implement linker-compatible library
+ search. Use the new VPATH_SEARCH index functionality to keep track
+ of the directory index for each match. Select the match with the
+ lowest directory index.
+
+ * implicit.c (pattern_search): Pass NULL for the index arguments in
+ the VPATH_SEARCH call.
+
+ * doc/make.texi (Directory Search for Link Libraries): Describe the
+ new search behavior.
+
+ * NEWS: Add a note about the new behavior.
+
+2009-10-25 Paul Smith <psmith@gnu.org>
+
+ * AUTHORS, et.al.: Update copyright years.
+
+ * implicit.c (stemlen_compare): Fix qsort() compare bug that
+ caused implicit rules with equal stem lengths to be sorted
+ indeterminately.
+
+2009-10-24 Paul Smith <psmith@gnu.org>
+
+ * main.c (usage): Add --eval to the usage string.
+ (switches): Add the --eval switch.
+ (main): If --eval is given, add them to the simply-expanded variable
+ -*-eval-flags-*- (necessary to allow recursion to work properly).
+ (define_makeflags): Add -*-eval-flags-*- to MAKEFLAGS.
+
+ * NEWS: Describe the new --eval command line argument.
+ * doc/make.texi (Options Summary): Document --eval.
+
+ * dep.h: eval_buffer() returns void.
+ * read.c (eval_buffer): Ditto.
+ (eval): Ditto.
+
+ * variable.h (define_variable_cname): New macro for constant
+ variable names.
+ * default.c (set_default_suffixes): Use it.
+ * main.c (main): Ditto.
+ (handle_non_switch_argument): Ditto.
+ (define_makeflags): Ditto.
+ * read.c (read_all_makefiles): Ditto.
+ * variable.c (define_automatic_variables): Ditto.
+
+ * commands.c (dep_hash_cmp): Avoid casts.
+ (dep_hash_1): Ditto.
+ (dep_hash_2): Ditto.
+
+2009-10-22 Boris Kolpackov <boris@codesynthesis.com>
+
+ * read.c (read_all_makefiles): Mark the default makefile dependency
+ dontcare.
+
+2009-10-07 Boris Kolpackov <boris@codesynthesis.com>
+
+ * read.c (do_undefine): Free the expanded variable name.
+
+ * commands.c (dep_hash_cmp, set_file_variables): Move the order-only
+ to normal upgrade logic from dep_hash_cmp to set_file_variables.
+
+2009-10-06 Boris Kolpackov <boris@codesynthesis.com>
+
+ * dep.h (uniquize_deps): Remove.
+
+ * read.c (uniquize_deps): Merge into set_file_variables in
+ commands.c.
+ (dep_hash_1, dep_hash_2, dep_hash_cmp): Move to commands.c.
+
+ * commands.c (set_file_variables): Avoid modifying the dep
+ chain to achieve uniqueness. Fixes savannah bug 25780.
+
+ * implicit.c (pattern_search): Instead of re-setting all automatic
+ variables for each rule we try, just update $*.
+
+2009-10-06 Boris Kolpackov <boris@codesynthesis.com>
+
+ * variable.h (undefine_variable_in_set): New function declaration.
+ (undefine_variable_global): New macro.
+
+ * variable.c (undefine_variable_in_set): New function implementation.
+
+ * read.c (vmodifiers): Add undefine_v modifier.
+ (parse_var_assignment): Parse undefine.
+ (do_undefine): Handle the undefine directive.
+ (eval): Call do_undefine if undefine_v is set.
+
+ * main.c (.FEATURES): Add a keyword to indicate the new feature.
+
+ * doc/make.texi (Undefine Directive): Describe the new directive.
+
+ * NEWS: Add a note about the new directive.
+
+2009-10-05 Boris Kolpackov <boris@codesynthesis.com>
+
+ * implicit.c (pattern_search): Initialize file variables only
+ if we need to parse a rule that requires the second expansion.
+
+2009-10-03 Paul Smith <psmith@gnu.org>
+
+ * make.h: Include <alloca.h> even on systems where __GNUC__ is
+ defined. Not sure why it was done the other way.
+ Requested by David Boyce <dsb@boyski.com>.
+
+2009-09-30 Boris Kolpackov <boris@codesynthesis.com>
+
+ * dep.h (dep): Add the DONTCARE bitfield.
+
+ * filedef.h (file):Add the NO_DIAG bitfield.
+
+ * read.c (eval_makefile): Set the DONTCARE flag in struct dep,
+ not struct file (a file can be a dependency of many targets,
+ some don't care, some do).
+
+ * remake.c (update_goal_chain): Propagate DONTCARE from struct
+ dep to struct file before updating the goal and restore it
+ afterwards.
+ (update_file): Don't prune the dependency graph if this target
+ has failed but the diagnostics hasn't been issued.
+ (complain): Scan the file's dependency graph to find the file
+ that caused the failure.
+ (update_file_1): Use NO_DIAG instead of DONTCARE to decide
+ whether to print diagnostics.
+
+ Fixes Savannah bugs #15110, #25493, #12686, and #17740.
+
+2009-09-28 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Pattern Intro): Move the match algorithm
+ discussion into the "Pattern Match" node.
+ (Pattern Match): Expand on the pattern rule matching algorithm.
+
+2009-09-28 Andreas Buening <andreas.buening@nexgo.de>
+
+ * job.c (construct_command_argv_internal) [OS2]: Don't eat too
+ much of the command line on a single pass.
+
+2009-09-28 Boris Kolpackov <boris@codesynthesis.com>
+
+ * varible.c (create_pattern_var): Insert variables into the
+ PATTERN_VARS list in the shortest patterns first order.
+
+ * implicit.c (tryrule): Add STEMLEN and ORDER members. These are
+ used to sort the rules.
+ (stemlen_compare): Compare two tryrule elements.
+ (pattern_search): Sort the rules so that they are in the shortest
+ stem first order.
+
+ * main.c (.FEATURES): Add a keyword to indicate the new behavior.
+
+ * doc/make.texi (Pattern-specific Variable Values): Describe the
+ new pattern-specific variables application order.
+ (Introduction to Pattern Rules): Describe the new pattern rules
+ search order.
+
+ * NEWS: Add a note about the new behavior.
+
+2009-09-27 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Double-Colon): Mention that pattern rules with
+ double-colons have a different meaning. Savannah bug #27497.
+
+2009-09-27 Juan Manuel Guerrero <juan.guerrero@gmx.de>
+
+ * configh.dos.template: Remove unconditional definition of
+ SYS_SIGLIST_DECLARED.
+ Include <sys/version.h> because ports of GCC 4.3.0 and later no
+ longer include it, so macros like __DJGPP_MINOR__ are no longer
+ defined automatically.
+
+ * Makefile.DOS.template (INCLUDES): Use $(prefix) and the
+ corresponding variables to define LIBDIR, INCLUDEDIR and LOCALEDIR
+ instead of using the hardcoded ones.
+ (SUBDIRS): doc subdir added.
+ (INFO_DEPS, DVIS): Values changed to 'make.info' and 'make.dvi'.
+ (TEXI2HTML, TEXI2HTML_FLAGS): Removed. Use makeinfo --html to
+ create html formated docs. texi2html may not be ported to DOS.
+ (make.info, make.dvi, make.ps, make.html): Make targets depend on
+ 'make.texi'.
+ (.texi.info, .texi, .texi.dvi): Now invoked recursively. Change
+ -I switch to look in ./ instead of ./doc.
+ (html): Target depend on html-recursive instead of make_1.html.
+ (make_1.html): Removed.
+ (mostlyclean-aminfo): Use $(srcdir)/doc instead of ./ as prefix.
+ (all-recursive): Allow for more than one subdir in the build
+ process.
+ (mostlyclean-recursive, clean-recursive, distclean-recursive)
+ (maintainer-clean-recursive, check-recursive): Enter in doc/ too.
+ (tags-recursive): Allow for more than one subdir in the build
+ process.
+ (info-recursive, dvi-recursive, ps-recursive, html-recursive): New
+ targets. Enter into doc/ to produce the targets.
+ (all-am): $(INFO_DEPS) replaced by info.
+
+2009-09-26 Paul Smith <psmith@gnu.org>
+
+ * read.c (record_files): Use free_ns() to free struct nameseq.
+ (eval): Ditto.
+
+ * rule.c (freerule): Use free_dep_chain().
+
+ * read.c (record_files): Free FILENAMES chain for implicit rules.
+ (eval): Static pattern targets go into the string cache.
+
+ * function.c (string_glob): Free NAME in the nameseq chain.
+
+2009-09-25 Boris Kolpackov <boris@codesynthesis.com>
+
+ * implicit.c (pattern_search): Terminate early if we haven't
+ found any rules to try (performance improvement).
+
+2009-09-25 Boris Kolpackov <boris@codesynthesis.com>
+
+ * implicit.c (pattern_search): Merge three parallel arrays,
+ TRYRULES, MATCHES, and CHECKED_LASTSLASH, into one array
+ of struct TRYRULE. In the old version the latter two arrays
+ had insufficient length.
+
+2009-09-24 Paul Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Add back support for order-only
+ prerequisites for secondary expansion implicit rules, that were
+ accidentally dropped. If we find a "|", enable order-only mode
+ and set IGNORE_MTIME on all deps that are seen afterward.
+ (pattern_search): Fix memory leaks: for intermediate files where
+ we've already set the file variable and pattern variable sets, be
+ sure to either save or free them as appropriate.
+
+2009-09-23 Paul Smith <psmith@gnu.org>
+
+ Rework the way secondary expansion is stored, for efficiency.
+ This changes secondary expansion so that ONLY WHEN we know we have
+ a possibility of needing secondary expansion, do we defer the
+ secondary expansion. This means more parsing the deps but we use
+ a lot less memory (due to the strcache). Also, this fixes
+ Savannah bug #18622.
+
+ * read.c (eval): Don't parse the dep string here anymore.
+ (record_files): Take the dep argument as an unparsed string. If
+ secondary expansion is enabled AND the prereq string has a '$' in
+ it, then set NEED_2ND_EXPANSION and keep the entire string.
+ Otherwise, parse the dep string here to construct the dep list
+ with the names in the strcache.
+
+ * misc.c (copy_dep_chain): For NEED_2ND_EXPANSION, we need to
+ duplicate the name string (others are in the strcache).
+
+ * implicit.c: Remove struct idep and free_idep_chain(): unused.
+ (struct patdeps): New structure to store prereq information.
+ (pattern_search): Use the NEED_2ND_EXPANSION flag to determine
+ which prerequisites need expansion, and expand only those.
+
+ * file.c (split_prereqs): Break parse_prereqs() into two parts: this
+ and enter_prereqs(). split_prereqs() takes a fully-expanded string
+ and splits it into a DEP list, handling order-only prereqs.
+ (enter_prereqs): This function enters a list of DEPs into the file
+ database. If there's a stem defined, expand any pattern chars.
+ (expand_deps): Only try to expand DEPs which have NEED_2ND_EXPANSION
+ set. Use the above functions.
+ (snap_deps): Only perform second expansion on prereqs that need it,
+ as defined by the NEED_2ND_EXPANSION flag.
+ (print_prereqs): New function to print the prereqs
+ (print_file): Call print_prereqs() rather than print inline.
+
+ * hash.h (STRING_COMPARE): Take advantage of strcache() by
+ comparing pointers.
+ (STRING_N_COMPARE): Ditto.
+ (ISTRING_COMPARE): Ditto.
+
+ * dep.h (PARSE_FILE_SEQ): New macro to reduce casts.
+ (parse_file_seq): Return void*
+ * read.c (parse_file_seq): Return void*.
+ (eval): Invoke macroized version of parse_file_seq()
+ * default.c (set_default_suffixes): Ditto.
+ * file.c (split_prereqs): Ditto.
+ * function.c (string_glob): Ditto.
+ * main.c (main): Ditto.
+ * rule.c (install_pattern_rule): Ditto.
+
+ * filedef.h: Add split_prereqs(), enter_prereqs(), etc.
+
+2009-09-16 Paul Smith <psmith@gnu.org>
+
+ * misc.c (alloc_dep, free_dep): Now that we have xcalloc(),
+ convert to macros.
+ * dep.h: Create alloc_dep() / free_dep() macros.
+
+ * implicit.c (pattern_search): Take advantage of the new
+ parse_file_seq() to add the directory prefix to each prereq.
+
+ * dep.h: Remove multi_glob() and enhance parse_file_seq() to do it
+ all. Avoid reversing chains. Support adding prefixes.
+ * read.c (parse_file_seq): Rewrite to support globbing. Allow for
+ cached/non-cached results.
+ (eval): Remove multi_glob() & invoke new parse_file_seq().
+ * rule.c (install_pattern_rule): Ditto.
+ * main.c (main): Ditto.
+ * implicit.c (pattern_search): Ditto.
+ * function.c (string_glob): Ditto.
+ * file.c (parse_prereqs): Ditto.
+ * default.c (set_default_suffixes): Ditto.
+
+ * variable.c (parse_variable_definition): Don't run off the end of
+ the string if it ends in whitespace (found with valgrind).
+
+ * commands.c (set_file_variables): Keep space for all targets in
+ $? if -B is given (found with valgrind).
+
+2009-09-15 Paul Smith <psmith@gnu.org>
+
+ * misc.c (concat): Make concat() variadic so it takes >3 arguments.
+ (xcalloc): Add new function.
+ * make.h: New declarations.
+
+ * ar.c (ar_glob_match): New calling method for concat().
+ * main.c (main): Ditto.
+ (decode_env_switches): Ditto.
+ * read.c (eval_makefile): Ditto.
+ (tilde_expand): Ditto.
+ (parse_file_seq): Ditto.
+ * variable.c (target_environment): Ditto.
+ (sync_Path_environment): Ditto.
+
+ * ar.c (ar_glob_match): Use xcalloc().
+ * dir.c (file_impossible): Ditto.
+ * file.c (enter_file): Ditto.
+ * job.c (new_job): Ditto.
+ * read.c (parse_file_seq): Ditto.
+ * vmsfunctions.c (opendir): Ditto.
+
+2009-09-14 Rafi Einstein <rafi.einstein@gmail.com> (tiny patch)
+
+ * w32/subproc/sub_proc.c (process_begin): Check *ep non-NULL
+ inside the loop that looks up environment for PATH.
+
+2009-08-31 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (windows32_openpipe): Update envp after calling
+ sync_Path_environment.
+
+2009-08-02 Paul Smith <psmith@gnu.org>
+
+ * remake.c (notice_finished_file): Ensure file->cmds is not null
+ before looping through them. Fixes Savannah bug #21824.
+
+ * doc/make.texi (Wildcard Examples): Clarify when objects is
+ wildcard-expanded. Fixes Savannah bug #24509. Patch by Martin Dorey.
+ (Include): Clarify the behavior of -include.
+ Fixes Savannah bug #18963.
+
+2009-08-01 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Catalogue of Rules): Clarify where -c, -F,
+ etc. come on the command line. Fixes Savannah bug #27093.
+
+ * expand.c (expand_argument): If the argument is large enough use
+ xmalloc() instead of alloca(). Fixes Savannah bug #27143.
+
+ * variable.c (do_variable_definition): Avoid using alloca() to
+ hold values, which can be large. Fixes Savannah bug #23960.
+
+ * job.c (new_job): Use memmove() instead of strcpy() since both
+ pointers are in the same memory block. Fixes Savannah bug #27148.
+ Patch by Petr Machata.
+
+2009-07-29 Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+
+ * job.c (construct_command_argv_internal): Add "ulimit" and
+ "unset" to the sh_cmds for Unixy shells.
+
+2009-07-29 Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+
+ * configure.in: Move side-effects outside AC_CACHE_VAL arguments
+ that set make_cv_sys_gnu_glob, so they are also correctly set
+ when the cache has been populated before.
+
+2009-07-04 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (func_realpath) [!HAVE_REALPATH]: Require the file to
+ exist, as realpath(3) does where it's supported.
+
+2006-07-04 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (IS_ABSOLUTE, ROOT_LEN): New macros.
+ (abspath): Support systems that define HAVE_DOS_PATHS (have
+ drive letters in their file names). Use IS_PATHSEP instead of a
+ literal '/' comparison. Fixes Savannah bug #26886.
+
+2009-06-14 Paul Smith <psmith@gnu.org>
+
+ * remake.c (update_file_1): Remember the original file we marked
+ as updating, so we can clear that flag again. If we find a target
+ via vpath, FILE might change.
+ (check_dep): Ditto. Fixes Savannah bug #13529.
+ Patch by Reid Madsen <reid.madsen@tek.com>.
+
+2009-06-13 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (MAKEFILES Variable): Be explicit that files
+ included by MAKEFILES cannot give default goals.
+ * read.c (eval): If set_default is not set, pass the no-default-goal
+ value when we read included makefiles. Fixes Savannah bug #13401.
+
+ * ar.c (ar_name): Ensure that targets with empty parens aren't
+ considered archive member references: archive members must have a
+ non-empty "member" string. Fixes Savannah bug #18435.
+
+ * function.c (string_glob): Rely on multi_glob() to determine
+ whether files exist or not. Remove call to file_exists_p() which
+ is not always correct. Fixes Savannah bug #21231.
+ * read.c (multi_glob): Add a new argument EXISTS_ONLY; if true
+ then only files that really exist will be returned.
+ * dep.h: Add new argument to multi_glob().
+ * rule.c (install_pattern_rule): Ditto.
+ * read.c (eval): Ditto.
+ * main.c (main): Ditto.
+ * implicit.c (pattern_search): Ditto.
+ * file.c (parse_prereqs): Ditto.
+ * default.c (set_default_suffixes): Ditto.
+
+2009-06-09 Paul Smith <psmith@gnu.org>
+
+ * commands.c (set_file_variables): If always_make_flag is set,
+ always add the prereq to $?. Fixes Savannah bug #17825.
+
+ * remake.c (update_file_1): When rebuilding deps of FILE, also try
+ to rebuild the deps of all the also_make targets for that file.
+ Fixes Savannah bug #19108.
+
+ * implicit.c (pattern_search): Undo test for is_target, added by
+ BorisK on 21 Sep 2004. This goes against step 5c in the "Implicit
+ Rule Search Algorithm". Fixes Savannah bug #17752.
+
+ * main.c (clean_jobserver): Clear the jobserver_fds options and
+ set job_slots to the default when we clean up.
+ (define_makeflags): Return the new MAKEFLAGS value.
+ (main): Reset MAKEFLAGS in the environment when we re-exec.
+ Fixes Savannah bug #18124.
+
+2009-06-08 Paul Smith <psmith@gnu.org>
+
+ * read.c (eval): Collapse continuations post-semicolon on target-
+ specific variables. Fixes Savannah bug #17521.
+
+2009-06-07 Paul Smith <psmith@gnu.org>
+
+ * job.c (reap_children): For older systems without waitpid() (are
+ there any of these left?) run wait(2) inside EINTRLOOP to handle
+ EINTR errors. Fixes Savannah bug #16401.
+
+ * (various): Debug message cleanup. Fixes Savannah bug #16469.
+
+ * main.c: Fix bsd_signal() typedef. Fixes Savannah bug #16473.
+
+ * file.c (snap_deps): Set SNAPPED_DEPS at the start of snapping,
+ not the end, to catch second expansion $(eval ...) defining new
+ target/prereq relationships during snap_deps.
+ Fixes Savannah bug #24622.
+
+ * read.c (record_files): The second-expansion "f->updating" hack
+ was not completely correct: if assumed that the target with
+ commands always had prerequisites; if one didn't then the ordering
+ was messed up. Fixed for now to use f->updating to decide whether
+ to preserve the last element in the deps list... but this whole
+ area of constructing and reversing the deps list is too confusing
+ and needs to be reworked. Fixes Savannah bug #21198.
+
+2009-06-06 Paul Smith <psmith@gnu.org>
+
+ * hash.c (hash_insert): Remove useless test for NULL.
+ Fixes Savannah bug #21823.
+
+ * make.h: Move SET_STACK_SIZE determination to make.h.
+ * main.c (main): New global variable, STACK_LIMIT, holds the
+ original stack limit when make was started.
+ * job.c (start_job_command): Reset the stack limit, if we changed it.
+ Fixes Savannah bug #22010.
+
+ * remake.c (check_dep): Only set the target's state to not-started
+ if it's not already running. Found this while testing -j10 builds
+ of glibc: various targets were being rebuilt multiple times.
+ Fix from Knut St. Osmundsen; fixes a problem reported in Savannah
+ bug #15919.
+
+ * read.c (multi_glob): Don't pass GLOB_NOCHECK to glob(3); instead
+ handle the GLOB_NOMATCH error. This is to work around Sourceware.org
+ Bugzilla bug 10246.
+
+2009-06-04 Paul Smith <psmith@gnu.org>
+
+ * read.c (eval): Skip initial whitespace (ffeed, vtab, etc.)
+
+ * maintMakefile: Modify access of config and gnulib Savannah
+ modules to use GIT instead of CVS.
+
+ * main.c (main): Initialize the LENGTH field in SHELL_VAR.
+ Fixes Savannah bug #24655.
+
+ * read.c (eval_buffer): Don't dereference reading_file if it's NULL;
+ this can happen during some invocations of $(eval ...) for example.
+ Fixes Savannah bug #24588. Patch by Lars Jessen <ljessen@ljessen.dk>
+
+2009-06-02 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Check for fileno()
+ * read.c (eval_makefile): If fileno() is available, set CLOSE_ON_EXEC
+ for the makefile file so invocations of $(shell ...) don't inherit it.
+ Fixes Savannah bug #24277.
+
+2009-06-01 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): The previous fix for .DEFAULT_GOAL had issues;
+ expansion was handled incorrectly. Rework the default goal
+ handling to save the variable only. Remove default_goal_file and
+ default_goal_name.
+ * read.c (eval): Check default_goal_var, not default_goal_name.
+ * read.c (record_target_var): Don't check default_goal_file here.
+
+2009-05-31 Paul Smith <psmith@gnu.org>
+
+ * main.c (main): Expand the .DEFAULT_GOAL variable before using
+ it, and if the multi_glob() returns nothing (say it expanded to
+ nothing but spaces) then don't crash. Fixes Savannah bug #25697.
+
+ * doc/make.texi (Quick Reference): Add $(if ..), $(or ..), and
+ $(and ..) to the reference. Fixes Savannah bug #25694.
+
+ * make.1: Be clear that some recipes will be executed even with -n.
+ * doc/make.texi: Ditto. Fixes Savannah bug #25460.
+
+ * doc/make.texi (Override Directive): Make more clear how
+ overrides and appends interact.
+ Elucidates part of Savannah bug #26207.
+
+ * read.c (record_target_var): Don't reset the origin on
+ target-specific variables; try_variable_definition() will handle
+ this correctly. Fixes Savannah bug #26207.
+
+ * maintMakefile (do-po-update): Copy PO files into $(top_srcdir).
+ Fixes Savannah bug #25712.
+
+ * implicit.c (pattern_search): Keep a pointer to the beginning of
+ the filename and save that instead of the constructed pointer.
+ Fixes Savannah bug #26593.
+ Patch by Mark Seaborn <mrs@mythic-beasts.com>
+
+2009-05-30 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Multi-Line): Add a description of the new abilities
+ of define/endef. Rename "Sequences" to "Multi-Line" and fix some
+ "command sequence" vs. "recipe" syntax.
+ * read.c (do_define): Modify to allow assignment tokens (=, :=, etc.)
+ after a define, to create variables with those flavors.
+
+2009-05-25 Paul Smith <psmith@gnu.org>
+
+ Reworked the parser for variable assignments to allow multiple
+ modifiers, and in any order. Also allows variable and
+ prerequisites to be modifier names ('export', 'private', etc.)
+
+ * NEWS: Add notes about user-visible changes.
+
+ * read.c (struct vmodifiers): Remember what modifiers were seen.
+ (parse_var_assignment): New function to parse variable assignments.
+ (eval): Call the new function. Handle variable assignments earlier.
+
+ * variable.c (parse_variable_definition): Only parse; don't create var.
+ (assign_variable_definition): Call parse, then create the var.
+
+2009-05-24 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi: Fix the ISBN for the GNU make manual. Incorrect
+ value noticed by Hans Stol <hans.stol@nc3a.nato.int>.
+
+2009-03-14 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/pathstuff.c (convert_Path_to_windows32): Fix last change.
+ Fixes Savannah bug #25412.
+
+ * w32/subproc/sub_proc.c <top level>: Update Copyright years. Add
+ prototype for xmalloc.
+ (find_file): Accept 3 arguments PATH_VAR, FULL_FNAME, and FULL_LEN
+ instead of an LPOFSTRUCT pointer. Use xmalloc instead of malloc.
+ Loop over an array of extensions, instead of duplicating the same
+ code inline. Use SearchPath followed by CreateFile, instead of
+ the obsolete OpenFile. Fixes Savannah bug #17277.
+ (process_begin): Find $(PATH) in `envp', and pass a pointer to it
+ to `find_file'. Fixes Savannah bug #25662.
+
+2009-03-07 Eli Zaretskii <eliz@gnu.org>
+
+ * function.c (func_shell): Don't close pipedes[1] if it is -1.
+ Fixes Savannah bug #20495.
+
+2009-02-28 Ralf Wildenhues <address@hidden>
+
+ * doc/make.texi (Instead of Execution): Document interaction of
+ -t with phony targets.
+
+2009-02-23 Ramon Garcia <ramon.garcia.f@gmail.com>
+
+ Introduce a new keyword "private" which applies to target-specific
+ variables and prevents their values from being inherited.
+
+ * variable.h (struct variable): Add private_var flag to each variable.
+ Add a flag to specify which list entry switches to the parent target.
+ * variable.c (define_variable_in_set): Initialize private_var flag.
+ (lookup_variable): Skip private variables in parent contexts.
+ (initialize_file_variables): Set next_is_parent appropriately.
+ (print_variable): Show the private_var flag.
+ * read.c (eval): Recognize the private keyword.
+ (record_target_var): Set private_var.
+ * doc/make.texi (Suppressing Inheritance): Add documentation.
+
+2008-10-26 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Check for strndup().
+ * misc.c (xstrndup): Rename savestring to xstrndup. Use strndup
+ if it's available.
+ * make.h: Rename savestring to xstrndup.
+ * commands.c (chop_commands): Ditto.
+ * function.c (func_foreach): Ditto.
+ * read.c (eval, record_files): Ditto.
+ * variable.c (define_variable_in_set): Ditto.
+
+2008-09-30 Eli Zaretskii <eliz@gnu.org>
+
+ * build_w32.bat (GCCBuild): Use "-gdwarf-2 -g3" instead of
+ "-gstabs+ -ggdb3".
+
+ * w32/subproc/build.bat (GCCBuild): Likewise.
+
+2008-09-30 David Russo <d-russo@ti.com> (tiny change)
+
+ * job.c (construct_command_argv_internal): Avoid extra backslash
+ in batch-mode Unixy shells. Under DB_JOBS, display the contents
+ of the batch file.
+
+2008-05-31 Eli Zaretskii <eliz@gnu.org>
+
+ * README.W32.template: Remove obsolete text about non-support for
+ -jN without Unixy shell. Remove obsolete text about not supplying
+ Visual Studio project files (we do supply them). Modify text to
+ prefer GCC builds to MSC builds.
+
+2008-04-02 Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+
+ * doc/make.texi (Empty Targets): Fix typo.
+
+2008-03-27 Paul Smith <psmith@gnu.org>
+
+ Fix Savannah bug #22379:
+ * ar.c (ar_glob_match): Zero the allocated structure.
+ * read.c (parse_file_seq): Ditto.
+
+2008-03-08 Brian Dessent <brian@dessent.net>
+
+ * maintMakefile: Update Translation Project location.
+
+2008-01-26 Eli Zaretskii <eliz@gnu.org>
+
+ * variable.c (target_environment): Don't use shell_var if its
+ `value' field is NULL.
+
+2007-12-22 Eli Zaretskii <eliz@gnu.org>
+
+ Suggested by Juan Manuel Guerrero <juan.guerrero@gmx.de>:
+
+ * Makefile.DOS.template (info_TEXINFOS): Remove unused variable.
+ (TEXINFOS): Value changed to `doc/make.texi'.
+ (.SUFFIXES): Use .texi instead of .texinfo.
+ (make.info, make.dvi): Depend on doc/make.texi.
+ (.texi.info): New target, instead of ".texinfo.info". Change -I
+ switch to $(MAKEINFO) to look in doc/. Use --no-split.
+ (.texi): New target, instead of ".texinfo". Change -I switch to
+ $(MAKEINFO) to look in doc/. Use --no-split.
+ (.texi.dvi): New target, instead of ".texinfo.dvi". Change -I
+ switch to $(MAKEINFO) to look in doc/.
+ (install-info-am, uninstall-info): Don't look for "*.i[0-9]" and
+ "*.i[0-9][0-9]" (due to --no-split above).
+ (noinst_TEXINFOS, TEXI2HTML, TEXI2HTML_FLAGS): New variables.
+ (html, make_1.html): New targets.
+ (.PHONY): Add "html".
+ (.SUFFIXES): Add .html.
+
+2007-12-22 Juan Manuel Guerrero <juan.guerrero@gmx.de> (tiny change)
+
+ * configh.dos.template [__DJGPP__]: Replace HAVE_SYS_SIGLIST with
+ HAVE_DECL_SYS_SIGLIST.
+
+ * job.c (child_execute_job): Remove __MSDOS__ because MSDOS/DJGPP
+ build does not use child_execute_job.
+
+ * variable.c (define_automatic_variables) [__MSDOS__]: Always
+ export the SHELL environment variable to the child.
+
+2007-12-22 Eli Zaretskii <eliz@gnu.org>
+
+ * config.h.W32: Include sys/types.h.
+ [!_PID_T_] (pid_t): Define only if not already defined by sys/types.h.
+
+ * vpath.c (construct_vpath_list) [HAVE_DOS_PATHS]: Support VPATH
+ values that use `:' in drive letters, when PATH_SEPARATOR_CHAR is
+ also `:'.
+
+2007-11-04 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi: Convert references to "commands", "command
+ lines", and "command script" to "recipe".
+ * NEWS: Ditto.
+ * commands.c, file.c, job.c, remake.c, read.c, variable.c, main.c:
+ Ditto.
+
+2007-10-27 Bruno Haible <bruno@clisp.org>
+
+ * remake.c (f_mtime): Print time difference values between 100 and
+ ULONG_MAX in fixed-point notation rather than in exponention notation.
+
+2007-10-12 Eli Zaretskii <eliz@gnu.org>
+
+ * variable.c (do_variable_definition): Allow $(SHELL) to expand to
+ a more complex value than a simple shell: if it's not a default
+ shell now then expand it and see if is a default shell then.
+
+2007-10-10 Eli Zaretskii <eliz@gnu.org>
+
+ * dir.c (find_directory) [WINDOWS32]: Remove trailing slashes from
+ pathnames, with const strings.
+ * build_w32.bat [WINDOWS32]: If no config.h.W32 exists, create one
+ from the template (used for building from CVS, not a dist).
+
+2007-10-10 Paul Smith <psmith@gnu.org>
+
+ * make.h: Add a prototype for w32_kill() (change suggested by
+ Yongwei Wu <wuyongwei@gmail.com>).
+
+2007-09-21 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/pathstuff.c (convert_Path_to_windows32): Handle quoted
+ directories in Path.
+
+2007-09-12 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi: Applied wording cleanups from Savannah patch #6195.
+ Provided by Diego Biurrun <diego@biurrun.de>
+ (Complex Makefile): Remove .PHONY setting for tar: patch #6196.
+ Provided by Diego Biurrun <diego@biurrun.de>
+
+2007-09-11 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Special Variables): Moved this into the "How to
+ Use Variables" chapter. Added a table entry for .RECIPEPREFIX.
+ (MAKEFILE_LIST) No longer a section; this was added into the
+ "Special Variables" section.
+ (Rule Introduction): Reference .RECIPEPREFIX.
+ (Simple Makefile): Ditto.
+ (Rule Syntax): Ditto.
+ (Command Syntax): Ditto.
+ (Error Messages): Ditto.
+
+2007-09-10 Paul Smith <psmith@gnu.org>
+
+ * commands.c (print_commands): Don't print an extra line in the
+ command scripts. Prefix the command scripts with cmd_prefix, not \t.
+
+ * read.c (construct_include_path): Add the full string to the cache; we
+ were chopping the last char.
+
+ * NEWS: Announce the .RECIPEPREFIX special variable.
+ * variable.c (lookup_special_var): Rename from handle_special_var().
+ (lookup_variable): Call the new name.
+ (set_special_var): New function: handle setting of special variables.
+ When setting .RECIPEPREFIX, reset the cmd_prefix global variable.
+ (do_variable_definition): Call it.
+ * make.h (RECIPEPREFIX_DEFAULT): Define the default command prefix char.
+ (RECIPEPREFIX_NAME): Define the command prefix special variable name.
+ * main.c (main): Create the .RECIPEPREFIX special variable.
+ * read.c (eval): Remove the cmd_prefix characters from the command
+ scripts here, so they're not stored in the commands array at all,
+ rather than waiting and stripping them out during command construction.
+ * job.c (construct_command_argv_internal): Don't skip cmd_prefix here.
+
+2007-08-15 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (GNU Free Documentation License): The fdl.texi
+ file has had the section info removed, so add some to make.texi
+ before we include it.
+
+2007-08-15 Icarus Sparry <savannah@icarus.freeuk.com>
+
+ * remake.c (check_dep): Reset the target state for intermediate
+ files. They might have been considered before but not updated
+ then (order-only for example) but they will be this time.
+ Fixes Savannah bug #'s 3330 and 15919.
+
+2007-07-21 Eli Zaretskii <eliz@gnu.org>
+
+ Fix Savannah bug #20549:
+ * function.c (func_shell): Call construct_command_argv with zero
+ value of FLAGS.
+ * job.c (construct_command_argv_internal): New argument FLAGS; all
+ callers changed.
+ [WINDOWS32]: If FLAGS has the COMMANDS_RECURSE bit set, ignore
+ just_print_flag.
+ * job.h (construct_command_argv_internal): Update prototype.
+
+2007-07-13 Paul Smith <psmith@gnu.org>
+
+ * file.c (expand_deps): Use variable_buffer as the start of the
+ buffer, not the original pointer (in case it was reallocated).
+ Fix suggested by Rafi Einstein <rafi.einstein@formalism-labs.com>.
+ Fixes Savannah bug #20452.
+
+2007-07-04 Paul Smith <psmith@gnu.org>
+
+ * (ALL FILES): Update to GPLv3.
+ * (ALL FILES): Update copyright for 2007.
+
+ * main.c (print_version): Move the host type info to the second line.
+
+2007-06-29 Thiemo Seufer <ths@mips.com>
+
+ * maintMakefile: Update Translation Project location.
+
+2007-06-13 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (Reading Makefiles): "Expansion of deferred" ->
+ "Expansion of a deferred"
+ Fixes Savannah bug #20018.
+
+ * expand.c (variable_expand_for_file): Preserve the value of
+ reading_file rather than setting it to 0 at the end.
+ Fixes Savannah bug #20033.
+
+2007-05-11 Paul Smith <psmith@gnu.org>
+
+ * job.c (new_job): Add debug info to specify where make found the
+ command script it is running to build a target.
+ Fixes Savannah bug #18617.
+
+ * default.c (default_suffixes,default_suffix_rules,default_variables):
+ Add support for Objective C. Fixes Savannah bug #16389.
+ Based on a patch provided by Peter O'Gorman <peter@pogma.com>.
+
+ * function.c (func_lastword): Initialize p.
+
+ * doc/make.texi (Eval Function, Implicit Variables, Special Targets):
+ Doc fixes noticed by Bob <twobanjobob@sbcglobal.net>. Patch from
+ Dave Korn <dave.korn@artimi.com>
+
+2007-05-08 Paul Smith <psmith@gnu.org>
+
+ Fix Savannah bug #19656:
+
+ * configure.in: Check for strcasecmp(), strcmpi(), and stricmp().
+
+ * make.h: Change all case-insensitive string compares to use
+ strcasecmp() (from POSIX). If we don't have that but do have one
+ of the others, define strcasecmp to be one of those instead. If
+ we don't have any, declare a prototype for our own version.
+
+ * misc.c (strcasecmp): Use this if we can't find any native
+ case-insensitive string comparison function.
+ * vmsfunctions.c: Remove strcmpi(); we'll use misc.c:strcasecmp().
+ * main.c (find_and_set_default_shell): Use strcasecmp() instead of
+ strcmpi().
+ * job.c (_is_unixy_shell, construct_command_argv_internal): Use
+ strcasecmp() instead of stricmp().
+ * hash.h (ISTRING_COMPARE, return_ISTRING_COMPARE): Use strcasecmp()
+ instead of strcmpi().
+ * acinclude.m4: Remove the strcasecmp() check from here.
+
+2007-03-21 Paul Smith <psmith@gnu.org>
+
+ * configure.in: Don't turn on case-insensitive file system support
+ if --disable-... is given. Fixes Savannah bug #19348.
+
+2007-03-19 Paul Smith <psmith@gnu.org>
+
+ * ALL: Use the strcache for all file name strings, or other
+ strings which we will never free. The goal is to save memory by
+ avoiding duplicate copies of strings. However, at the moment this
+ doesn't save much memory in most situations: due to secondary
+ expansion we actually save prerequisite lists twice (once before
+ the secondary expansion, and then again after it's been parsed
+ into individual file names in the dep list). We will resolve this
+ in a future change, by doing the parsing up-front for targets
+ where secondary expansion is not set.
+
+ Moving things into the strcache also allows us to use const
+ pointers in many more places.
+
+2007-01-03 Paul Smith <psmith@gnu.org>
+
+ * make.h (ENULLLOOP): Reset errno after each failed invocation of
+ the function, not just the first. Fixes Savannah bug #18680.
+
+2006-11-18 Paul Smith <psmith@gnu.org>
+
+ * strcache.c (strcache_add_len): Don't allocate a new buffer
+ unless the string is not already nil-terminated. Technically this
+ is a violation of the standard, since we may be passed an array
+ that is not long enough to test one past. However, in make this
+ is never true since we only use nil-terminated strings or
+ sub-strings thereof.
+
+ * read.c (eval, do_define): Use cmd_prefix instead of '\t'.
+
+ * main.c: New global cmd_prefix, defaults to '\t'.
+ * job.c (construct_command_argv_internal): Use cmd_prefix instead
+ of '\t'.
+
+ * dir.c: Constified.
+ (dir_contents_file_exists_p): Check for an error return from
+ readdir(), just in case.
+
+ * commands.c: Constified.
+ * default.c: Constified.
+ * expand.c: Constified.
+ * function.c: Partial constification.
+ * variable.c: Partial constification.
+ * vmsify.c: Constification. Hard to test this but I hope I didn't
+ screw it up!
+ * vpath.c: Partial constification.
+ * w32/pathstuff.c: Partial constification.
+
+2006-11-16 Eli Zaretskii <eliz@gnu.org>
+
+ * main.c (main) [HAVE_DOS_PATHS]: Treat DOS style argv[0] with
+ backslashes and drive letters as absolute.
+
+2006-10-22 Paul Smith <psmith@gnu.org>
+
+ * main.c (struct command_switch): Use const and void*.
+
+2006-10-21 Paul Smith <psmith@gnu.org>
+
+ * ar.c: Constified.
+ * arscan.c: Constified.
+
+2006-09-30 Paul Smith <psmith@gnu.org>
+
+ * doc/make.texi (MAKEFILE_LIST Variable): Modify reference to
+ point to lastword since the example was updated.
+ Fixes Savannah bug #16304.
+ (Secondary Expansion): Correct example description.
+ Fixes Savannah bug #16468.
+ (Makefile Contents): Clarify that comments cannot appear within
+ variable references or function calls.
+ Fixes Savannah bug #16577.
+ (Special Targets): Clarify how .NOTPARALLEL works in recursion.
+ Fixes Savannah bug #17701.
+ Reported by Ralf Wildenhues <Ralf.Wildenhues@gmx.de>:
+ (Prerequisite Types): Added an example of using order-only
+ prerequisites. Fixes Savannah bug #17880.
+ (Rule Syntax): "lise" -> "list"
+ (Multiple Rules): ... -> @dots{}
+ (Splitting Lines): ditto.
+
+ * remake.c (update_file_1): Prereqs that don't exist should be
+ considered changed, for the purposes of $?.
+ Fixes Savannah bug #16051.
+
+ * make.1: Remove extraneous "+".
+ Fixes Savannah bug #16652.
+
+2006-09-06 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Include sys/types.h when checking for sys/wait.h.
+
+2006-08-18 Eli Zaretskii <eliz@gnu.org>
+
+ * configure.in (PATH_SEPARATOR_CHAR): Define to the value of
+ $PATH_SEPARATOR.
+
+ * make.h (PATH_SEPARATOR_CHAR): Define only if still undefined.
+ Normally, it is defined in config.h.
+
+ * config/dospaths.m4 <ac_cv_dos_paths>: Define to yes on Cygwin as
+ well.
+
+ * job.c (construct_command_argv_internal) [HAVE_DOS_PATHS]: Define
+ sh_chars_sh for Windows platforms that emulate Unix.
+
+2006-05-07 Paul D. Smith <psmith@gnu.org>
+
+ * README.OS2.template: Updates provided by Andreas Buening
+ <andreas.buening@nexgo.de>.
+
+2006-04-30 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Include <direct.h> if HAVE_DIRECT_H.
+ * config.h.W32.template (HAVE_DIRECT_H): Set it if it's available.
+
+2006-04-26 Paul D. Smith <psmith@gnu.org>
+
+ * README.cvs: Add a reminder to notify the GNU translation robot.
+
+ * doc/make.texi: Change @direcategory (requested by Karl Berry).
+
+2006-04-20 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile (po-check): Use Perl instead of grep -E, for systems
+ that don't have extended grep.
+ (cvsclean): Use $(PERL) instead of perl.
+
+2006-04-09 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile: Add some extra warning options (GCC 4.1 only?)
+
+ * expand.c, implicit.c, main.c, read.c: Rename variables so that
+ inner-scope variables don't mask outer-scope variables.
+
+ * ar.c, arscan.c, commands.c, default.c, dir.c, expand.c, file.c:
+ * function.c, getloadavg.c, implicit.c, job.c, main.c, misc.c, read.c:
+ * remake.c, remote-cstms.c, rule.c, strcache.c, variable.c:
+ * vmsfunctions.c, vmsify.c, vpath.c: Remove all casts of returned
+ values from memory allocation functions: they return void* and so
+ don't need to be cast. Also remove (char *) casts of arguments to
+ xrealloc().
+
+ * configure.in: Remove checks for memcpy/memmove/strchr.
+
+ * make.h: Remove bcmp/bcopy/bzero/strchr/strrchr macros.
+
+ * ar.c, arscan.c, commands.c, dir.c: Convert all bzero/bcopy/bcmp
+ calls to memset/memcpy/memmove/memcmp calls.
+ * expand.c, file.c, function.c, getloadavg.c, implicit.c: Ditto.
+ * job.c, main.c, misc.c, read.c, remake.c, rule.c: Ditto.
+ * variable.c, vpath.c: Ditto.
+
+ * make.h (EXIT_FAILURE): Should be 1, not 0.
+
+2006-04-06 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Removed AM_C_PROTOTYPES. Starting now on we
+ require an ISO C 1989 standard compiler and runtime library.
+
+ * Makefile.am: Remove the ansi2knr feature.
+
+ * make.h: Remove the PARAMS() macro definition and all uses of it.
+
+ * amiga.h, ar.c, arscan.c: Remove all uses of the PARAMS() macro.
+ * commands.c, commands.h, config.h-vms.template: Ditto.
+ * dep.h, dir.c, expand.c, filedef.h, function.c: Ditto.
+ * implicit.c, job.c, job.h, main.c, read.c, remake.c: Ditto.
+ * rule.c, rule.h, variable.h, vmsdir.h, vmsjobs.c, vpath.c: Ditto.
+
+ * NEWS: Update.
+
+2006-04-01 Paul D. Smith <psmith@gnu.org>
+
+ Version 3.81 released.
+
+ * NEWS: Updated for 3.81.
+
+ * README.cvs: Mention that vpath builds are not supported out of
+ CVS. Fixes Savannah bug #16236.
+ Remove update of make.texi from the list of things to do; we use
+ version.texi now.
+
+2006-03-26 Paul D. Smith <psmith@gnu.org>
+
+ * doc/make.texi: Clean up licensing. Use @copying and version.texi
+ support from automake, as described in the Texinfo manual.
+
+2006-03-25 Eli Zaretskii <eliz@gnu.org>
+
+ * implicit.c (pattern_search) [HAVE_DOS_PATHS]: Don't compare b
+ with lastslash, since the latter points to filename, not to
+ target.
+ * job.c (construct_command_argv_internal) [HAVE_DOS_PATHS]:
+ Declare and define sh_chars_sh[].
+
+2006-03-23 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Look for build.sh.in in $srcdir so it will be
+ built for remote configurations as well.
+
+ * Makefile.am: Make sure to clean up build.sh during distclean.
+ Fixes Savannah bug #16166.
+
+ * misc.c (log_access): Takes a const char *.
+ * function.c (fold_newlines): Takes an unsigned int *.
+ Both fixes for Savannah bug #16170.
+
+2006-03-22 Boris Kolpackov <boris@kolpackov.net>
+
+ * implicit.c (pattern_search): Call set_file_variables only
+ if we have prerequisites that need second expansion. Fixes
+ Savannah bug #16140.
+
+2006-03-19 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_file): Add alloca(0) to clean up alloca'd
+ memory on hosts that don't support it directly.
+
+ * README.cvs: Add information on steps for making a release (to
+ make sure I don't forget any).
+
+ * main.c (clean_jobserver): Move jobserver cleanup code into a new
+ function.
+ (die): Cleanup code was removed from here; call the new function.
+ (main): If we are re-execing, clean up the jobserver first so we
+ don't leak file descriptors.
+ Reported by Craig Fithian <craig.fithian@citigroup.com>
+
+2006-03-17 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile (do-po-update): Rewrite this rule to clean up and
+ allow multiple concurrent runs.
+ Patch from Joseph Myers <joseph@codesourcery.com>
+
+2006-03-17 Boris Kolpackov <boris@kolpackov.net>
+
+ * dep.h (struct dep): Add the stem field.
+ * misc.c (alloc_dep, free_dep): New functions.
+ (copy_dep_chain): Copy stem.
+ (free_dep_chain): Use free_dep.
+ * read.c (record_files): Store stem in the dependency line.
+ * file.c (expand_deps): Use stem stored in the dependency line. Use
+ free_dep_chain instead of free_ns_chain.
+ * implicit.c (pattern_search): Use alloc_dep and free_dep.
+ * read.c (read_all_makefiles, eval_makefile, eval): Ditto.
+ * main.c (main, handle_non_switch_argument): Ditto.
+ * remake.c (check_dep): Ditto.
+ * rule.c (convert_suffix_rule, freerule): Ditto.
+
+2006-03-14 Paul D. Smith <psmith@gnu.org>
+
+ * expand.c (variable_append): Instead of appending everything then
+ expanding the result, we expand (or not, if it's simple) each part
+ as we add it.
+ (allocated_variable_append): Don't expand the final result.
+ Fixes Savannah bug #15913.
+
+2006-03-09 Paul Smith <psmith@gnu.org>
+
+ * remake.c (update_file_1): Revert the change of 3 Jan 2006 which
+ listed non-existent files as changed. Turns out there's a bug in
+ the Linux kernel builds which means that this change causes
+ everything to rebuild every time. We will re-introduce this fix
+ in the next release, to give them time to fix their build system.
+ Fixes Savannah bug #16002.
+ Introduces Savannah bug #16051.
+
+ * implicit.c (pattern_search) [DOS_PATHS]: Look for DOS paths if
+ we *don't* find UNIX "/".
+ Reported by David Ergo <david.ergo@alterface.com>
+
+2006-03-04 Eli Zaretskii <eliz@gnu.org>
+
+ * variable.c (do_variable_definition) [WINDOWS32]: Call the shell
+ locator function find_and_set_default_shell if SHELL came from the
+ command line.
+
+2006-02-20 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (merge_variable_set_lists): It's legal for *setlist0
+ to be null; don't core in that case.
+
+2006-02-19 Paul D. Smith <psmith@gnu.org>
+
+ * commands.c (set_file_variables): Realloc, not malloc, the static
+ string values to avoid memory leaks.
+
+ * expand.c (recursively_expand_for_file): Only set reading_file to
+ an initialized value.
+
+ * implicit.c (pattern_search): We need to make a copy of the stem
+ if we get it from an intermediate dep, since those get freed.
+
+ * file.c (lookup_file) [VMS]: Don't lowercase special targets that
+ begin with ".".
+ (enter_file) [VMS]: Ditto.
+ Patch provided by Hartmut Becker <Hartmut.Becker@hp.com>.
+
+2006-02-24 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (construct_command_argv_internal): Fix last change.
+
+ * w32/subproc/sub_proc.c (process_pipe_io): Make dwStdin,
+ dwStdout, and dwStderr unsigned int: avoids compiler warnings in
+ the calls to _beginthreadex.
+
+ * expand.c (recursively_expand_for_file): Initialize `save' to
+ prevent compiler warnings.
+
+2006-02-18 Eli Zaretskii <eliz@gnu.org>
+
+ * job.c (construct_command_argv_internal): Don't create a temporary
+ script/batch file if we are under -n. Call _setmode to switch the
+ script file stream to text mode.
+
+2006-02-17 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (merge_variable_set_lists): Don't try to merge the
+ global_setlist. Not only is this useless, but it can lead to
+ circularities in the linked list, if global_setlist->next in one
+ list gets set to point to another list which also ends in
+ global_setlist.
+ Fixes Savannah bug #15757.
+
+2006-02-15 Paul D. Smith <psmith@gnu.org>
+
+ Fix for Savannah bug #106.
+
+ * expand.c (expanding_var): Keep track of which variable we're
+ expanding. If no variable is being expanded, it's the same as
+ reading_file.
+ * make.h (expanding_var): Declare it.
+ * expand.c (recursively_expand_for_file): Set expanding_var to the
+ current variable we're expanding, unless there's no file info in
+ it (could happen if it comes from the command line or a default
+ variable). Restore it before we exit.
+ * expand.c (variable_expand_string): Use the expanding_var file
+ info instead of the reading_file info.
+ * function.c (check_numeric): Ditto.
+ (func_word): Ditto.
+ (func_wordlist): Ditto.
+ (func_error): Ditto.
+ (expand_builtin_function): Ditto.
+ (handle_function): Ditto.
+
+2006-02-14 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval): Even if the included filenames expands to the
+ empty string we still need to free the allocated buffer.
+
+ * implicit.c (pattern_search): If we allocated a variable set for
+ an impossible file, free it.
+ * variable.c (free_variable_set): New function.
+ * variable.h: Declare it.
+
+ * read.c (read_all_makefiles): Makefile names are kept in the
+ strcache, so there's never any need to alloc/free them.
+ (eval): Ditto.
+
+ * main.c (main): Add "archives" to the .FEATURES variable if
+ archive support is enabled.
+ * doc/make.texi (Special Variables): Document it.
+
+2006-02-13 Paul D. Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Add checking for DOS pathnames to
+ the pattern rule target LASTSLASH manipulation.
+ Fixes Savannah bug #11183.
+
+2006-02-11 Paul D. Smith <psmith@gnu.org>
+
+ * (ALL FILES): Updated copyright and license notices.
+
+2006-02-10 Paul D. Smith <psmith@gnu.org>
+
+ A new internal capability: the string cache is a read-only cache
+ of strings, with a hash table interface for fast lookup. Nothing
+ in the cache will ever be freed, so there's no need for reference
+ counting, etc. This is the beginning of a full solution for
+ Savannah bug #15182, but for now we only store makefile names here.
+
+ * strcache.c: New file. Implement a read-only string cache.
+ * make.h: Add prototypes for new functions.
+ * main.c (initialize_global_hash_tables): Initialize the string cache.
+ (print_data_base): Print string cache stats.
+ * read.c (eval_makefile): Use the string cache to store makefile
+ names. Rewrite the string allocation to be sure we free everything.
+
+2006-02-10 Eli Zaretskii <eliz@gnu.org>
+
+ * dir.c (dir_contents_file_exists_p): Don't opendir if the
+ directory time stamp didn't change, except on FAT filesystems.
+ Suggested by J. David Bryan <jdbryan@acm.org>.
+
+2006-02-09 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_or): Implement a short-circuiting OR function.
+ (func_and): Implement a short-circuiting AND function.
+ (function_table_init): Update the table with the new functions.
+ * doc/make.texi (Conditional Functions): Changed the "if" section
+ to one on general conditional functions. Added documentation for
+ $(and ...) and $(or ...) functions.
+ * NEWS: Note new $(and ...) and $(or ...) functions.
+
+2006-02-08 Boris Kolpackov <boris@kolpackov.net>
+
+ * job.h (struct child): Add the dontcare bitfield.
+ * job.c (new_job): Cache dontcare flag.
+ * job.c (reap_children): Use cached dontcare flag instead of the
+ one in struct file. Fixes Savannah bug #15641.
+
+2006-02-06 Paul D. Smith <psmith@gnu.org>
+
+ * vpath.c (selective_vpath_search): If the file we find has a
+ timestamp from -o or -W, use that instead of the real time.
+ * remake.c (f_mtime): If the mtime is a special token from -o or
+ -W, don't overwrite it with the real mtime.
+ Fixes Savannah bug #15341.
+
+ Updates from Markus Mauhart <qwe123@chello.at>:
+
+ * w32/subproc/sub_proc.c (process_begin): Remove no-op tests.
+ (process_signal, process_last_err, process_exit_code): Manage
+ invalid handle values.
+ (process_{outbuf,errbuf,outcnt,errcnt,pipes}): Unused and don't
+ manage invalid handles; remove them.
+ * job.c (start_job_command) [WINDOWS32]: Jump out on error.
+ * config.h.W32.template [WINDOWS32]: Set flags for Windows builds.
+ * README.cvs: Updates for building from CVS.
+
+2006-02-05 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (enter_file): Keep track of the last double_colon entry,
+ to avoid walking the list every time we want to add a new one.
+ Fixes Savannah bug #15533.
+ * filedef.h (struct file): Add a new LAST pointer.
+
+ * dir.c (directory_contents_hash_cmp): Don't use subtraction to do
+ the comparison. For 64-bits systems the result of the subtraction
+ might not fit into an int. Use comparison instead.
+ Fixes Savannah bug #15534.
+
+ * doc/make.texi: Update the chapter on writing commands to reflect
+ the changes made in 3.81 for backslash/newline and SHELL handling.
+
+2006-02-01 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (dir_contents_file_exists_p) [WINDOWS32]: Make sure
+ variable st is not used when it's not initialized.
+ Patch from Eli Zaretskii <eliz@gnu.org>.
+
+2006-01-31 Paul D. Smith <psmith@gnu.org>
+
+ * README.W32.template: Applied patch #4785 from
+ Markus Mauhart <qwe123@chello.at>.
+ * README.cvs: Applied patch #4786 from
+ Markus Mauhart <qwe123@chello.at>.
+ * make_msvc_net2003.vcproj [WINDOWS32]: New version from
+ J. Grant <jg@jguk.org>.
+
+ * main.c: Update the copyright year in the version output.
+ * prepare_w32.bat: Remove this file from the distribution.
+
+2006-01-21 Eli Zaretskii <eliz@gnu.org>
+
+ * remake.c (update_goal_chain): Set g->changed instead of
+ incrementing it, as it is only 8-bit wide, and could overflow if
+ many commands got started in update_file.
+
+ * w32/include/sub_proc.h: Add a prototype for process_used_slots.
+
+ * w32/subproc/sub_proc.c: Change dimension of proc_array[] to
+ MAXIMUM_WAIT_OBJECTS.
+ (process_wait_for_any_private): Change dimension of handles[]
+ array to MAXIMUM_WAIT_OBJECTS.
+ (process_used_slots): New function.
+ (process_register): Don't register more processes than the
+ available number of slots.
+ (process_easy): Don't start new processes if all slots are used up.
+
+ * job.c (load_too_high, start_waiting_jobs) [WINDOWS32]: If there
+ are already more children than sub_proc.c can handle, behave as if
+ the load were too high.
+ (start_job_command): Fix a typo in error message when process_easy
+ fails.
+
+2006-01-14 Eli Zaretskii <eliz@gnu.org>
+
+ * main.c (main) [WINDOWS32]: Don't refuse to run with -jN, even if
+ the shell is not sh.exe.
+
+ * job.c (create_batch_file): Renamed from create_batch_filename;
+ all callers changed. Don't close the temporary file; return its
+ file descriptor instead. New arg FD allows to return the file
+ descriptor.
+ (construct_command_argv_internal): Use _fdopen instead of fopen to
+ open the batch file.
+
+2006-01-04 Paul D. Smith <psmith@gnu.org>
+
+ * readme.vms: Updates for case-insensitive VMS file systems from
+ Hartmut Becker <Hartmut.Becker@hp.com>.
+ * dir.c (vms_hash): Ditto.
+ * vmsify.c (copyto): Ditto.
+ * vmsfunctions.c (readdir): Ditto.
+
+ * make.1: Add a section on the exit codes for make.
+
+ * doc/make.texi: A number of minor updates to the documentation.
+
+2006-01-03 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_file_1): Mark a prerequisite changed if it
+ doesn't exist.
+
+ * read.c (eval): Be sure to strip off trailing whitespace from the
+ prerequisites list properly. Also, initialize all fields in
+ struct dep when creating a new one.
+
+2005-12-28 Paul D. Smith <psmith@gnu.org>
+
+ * config.h.W32.template [WINDOWS32]: Add in some pragmas to
+ disable warnings for MSC.
+ Patch by Rob Tulloh <rtulloh@yahoo.com>.
+
+2005-12-17 Eli Zaretskii <eliz@gnu.org>
+
+ * doc/make.texi (Execution): Add a footnote about changes in
+ handling of backslash-newline sequences. Mention the differences
+ on MS-DOS and MS-Windows.
+
+ * NEWS: More details about building the MinGW port and a pointer
+ to README.W32. Fix the section name that describes the new
+ backward-incompatible processing of backslash-newline sequences.
+ The special processing of SHELL set to "cmd" is only relevant to
+ MS-Windows, not MS-DOS.
+
+2005-12-17 Eli Zaretskii <eliz@gnu.org>
+
+ * main.c (handle_runtime_exceptions): Cast exrec->ExceptionAddress
+ to DWORD, to avoid compiler warnings.
+ * job.c (exec_command): Cast hWaitPID and hPID to DWORD, and
+ use %ld in format, to avoid compiler warnings.
+
+ * doc/make.texi (Special Targets): Fix a typo.
+ (Appending): Fix cross-reference to Setting.
+ (Special Variables, Secondary Expansion, File Name Functions)
+ (Flavor Function, Pattern Match, Quick Reference): Ensure two
+ periods after a sentence.
+ (Execution): Add @: after "e.g.".
+ (Environment): Fix punctuation.
+ (Target-specific, Call Function, Quick Reference): Add @: after "etc."
+ (Shell Function, Target-specific): Add @: after "vs."
+
+2005-12-14 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (record_target_var): Initialize variable's export field
+ with v_default instead of leaving it "initialized" by whatever
+ garbage happened to be on the heap.
+
+2005-12-12 Paul D. Smith <psmith@gnu.org>
+
+ * make.1: Fix some display errors and document all existing options.
+ Patch by Mike Frysinger <vapier@gentoo.org>.
+
+2005-12-11 Paul D. Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): If 2nd expansion is not set for
+ this implicit rule, replace the pattern with the stem directly,
+ and don't re-expand the variable list. Along with the other
+ .SECONDEXPANSION changes below, fixes bug #13781.
+
+2005-12-09 Boris Kolpackov <boris@kolpackov.net>
+
+ * implicit.c (pattern_search): Mark other files that this rule
+ builds as targets so that they are not treated as intermediates
+ by the pattern rule search algorithm. Fixes bug #13022.
+
+2005-12-07 Boris Kolpackov <boris@kolpackov.net>
+
+ * remake.c (notice_finished_file): Propagate the change of
+ modification time to all the double-colon entries only if
+ it is the last one to be updated. Fixes bug #14334.
+
+2005-11-17 Boris Kolpackov <boris@kolpackov.net>
+
+ * function.c (func_flavor): Implement the flavor function which
+ returns the flavor of a variable.
+ * doc/make.texi (Functions for Transforming Text): Document it.
+ * NEWS: Add it to the list of new functions.
+
+2005-11-14 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (construct_include_path): Set the .INCLUDE_DIRS special
+ variable.
+ * doc/make.texi (Special Variables): Document .INCLUDE_DIRS.
+ * NEWS: Add .INCLUDE_DIRS to the list of new special variables.
+
+2005-10-26 Paul Smith <psmith@gnu.org>
+
+ * read.c (record_files): Don't set deps flags if there are no deps.
+ * maintMakefile: We only need to build the templates when we are
+ creating a distribution, so don't do it for "all".
+
+2005-10-24 Paul D. Smith <psmith@gnu.org>
+
+ Make secondary expansion optional: its enabled by declaring the
+ special target .SECONDEXPANSION.
+
+ * NEWS: Update information on second expansion capabilities.
+ * doc/make.texi (Secondary Expansion): Document the
+ .SECONDEXPANSION special target and its behavior.
+ * dep.h (struct dep): Add a flag STATICPATTERN, set to true if the
+ prerequisite list was found in a static pattern rule.
+ (free_dep_chain): Declare a prototype.
+ * file.c (parse_prereqs): New function: break out some complexity
+ from expand_deps().
+ (expand_deps): If we aren't doing second expansion, replace % with
+ the stem for static pattern rules. Call the new function.
+ * filedef.h (parse_prereqs): Declare a prototype.
+ * implicit.c (pattern_search): Initialize the new staticpattern
+ field.
+ * main.c (second_expansion): Declare a global variable to remember
+ if the special target has been seen. Initialize the new
+ staticpattern field for prerequisites.
+ * make.h: Extern for second_expansion.
+ * misc.c (free_dep_chain): New function: frees a struct dep list.
+ * read.c (read_all_makefiles): Initialize the staticpattern field.
+ (eval_makefile): Ditto.
+ (record_files): Check for the .SECONDEXPANSION target and set
+ second_expansion global if it's found.
+ Use the new free_dep_chain() instead of doing it by hand.
+ Set the staticpattern field for prereqs of static pattern targets.
+
+2005-10-16 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Set CURDIR to be a file variable instead of a
+ default, so that values of CURDIR inherited from the environment
+ won't override the make value.
+
+2005-09-26 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): If the line is empty
+ remember to free the temporary argv strings.
+ Fixes bug # 14527.
+
+2005-09-16 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): The noerror flag is a boolean (single
+ bit); set it appropriately.
+ Reported by Mark Eichin <eichin@metacarta.com>
+
+2005-08-29 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_error): On Windows, output from $(info ...)
+ seems to come in the wrong order. Try to force it with fflush().
+
+2005-08-10 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (record_files): Move code that sets stem for static
+ pattern rules out of the if (!two_colon) condition so it is
+ also executed for two-colon rules. Fixes Savannah bug #13881.
+
+2005-08-08 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Don't test that __STDC__ is non-0. Some compilers
+ (Windows for example) set it to 0 to denote "ISO C + extensions".
+ Fixes bug # 13594.
+
+2005-08-07 Paul D. Smith <psmith@gnu.org>
+
+ * w32/pathstuff.c (getcwd_fs): Fix warning about assignment in a
+ conditional (slightly different version of a fix from Eli).
+
+ Fix a bug reported by Michael Matz <matz@suse.de>: patch included.
+ If make is running in parallel without -k and two jobs die in a
+ row, but not too close to each other, then make will quit without
+ waiting for the rest of the jobs to die.
+
+ * main.c (die): Don't reset err before calling reap_children() the
+ second time: we still want it to be in the error condition.
+ * job.c (reap_children): Use a static variable, rather than err,
+ to control whether or not the error message should be printed.
+
+2005-08-06 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/sub_proc.c: Include signal.h.
+ (process_pipe_io, process_file_io): Pass a pointer to a local
+ DWORD variable to GetExitCodeProcess. If the exit code is
+ CONTROL_C_EXIT, put SIGINT into pproc->signal.
+
+ * job.c [WINDOWS32]: Include windows.h.
+ (main_thread) [WINDOWS32]: New global variable.
+ (reap_children) [WINDOWS32]: Get the handle for the main thread
+ and store it in main_thread.
+
+ * commands.c [WINDOWS32]: Include windows.h and w32err.h.
+ (fatal_error_signal) [WINDOWS32]: Suspend the main thread before
+ doing anything else. When we are done, close the main thread
+ handle and exit with status 130.
+
+2005-07-30 Eli Zaretskii <eliz@gnu.org>
+
+ * w32/subproc/sub_proc.c (process_begin): Don't pass a NULL
+ pointer to fprintf.
+
+ * main.c (find_and_set_default_shell): If found a DOSish shell,
+ set sh_found and the value of default_shell, and report the
+ findings in debug mode.
+
+ * job.c (construct_command_argv_internal): Check unixy_shell, not
+ no_default_sh_exe, to decide whether to use Unixy or DOSish
+ builtin commands.
+
+ * README.W32: Update with info about the MinGW build.
+
+ * build_w32.bat: Support MinGW.
+
+ * w32/subproc/build.bat: Likewise.
+
+ * w32/subproc/sub_proc.c (process_easy): Fix format strings for
+ printing DWORD args.
+
+ * function.c (windows32_openpipe): Fix format strings for printing
+ DWORD args.
+
+ * job.c (reap_children) [WINDOWS32]: Don't declare 'status' and
+ 'reap_mode'.
+ (start_job_command): Fix format string for printing the result of
+ process_easy.
+ (start_job_command) [WINDOWS32]: Do not define.
+ (exec_command): Fix format string for printing HANDLE args.
+
+ * main.c (handle_runtime_exceptions): Fix sprintf format strings
+ to avoid compiler warnings.
+ (open_tmpfile): Declare fd only if HAVE_FDOPEN is defined.
+ (Note: some of these fixes were submitted independently by J. Grant)
+
+2005-07-30 J. Grant <jg@jguk.org>
+
+ * prepare_w32.bat: Copy config.h.w32 to config.h if not exist.
+ * make_msvc_net2003.vcproj, make_msvc_net2003.sln: MSVC Project files.
+ * Makefile.am (EXTRA_DIST): Add MSVC Project files.
+
+2005-07-15 Paul Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal) [DOS,WINDOWS32,OS/2]: If
+ we don't have a POSIX shell, then revert to the old
+ backslash-newline behavior (where they are stripped).
+ Fixes bug #13665.
+
+2005-07-08 Paul D. Smith <psmith@gnu.org>
+
+ * config.h.W32.template: Reorder to match the standard config.h,
+ for easier comparisons.
+ From J. Grant <jg@jguk.org>
+
+ * maintMakefile: Remove .dep_segment before overwriting it, in
+ case it's not writable or noclobber is set.
+ * expand.c (variable_expand_string): Cast result of pointer
+ arithmetic to avoid a warning.
+ * main.c (switches): Add full-fledged final initializer.
+
+2005-07-06 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: IRIX has _sys_siglist. Tru64 UNIX has __sys_siglist.
+ * signame.c (strsignal): If we found _sys_siglist[] or
+ __sys_siglist[] use those instead of sys_siglist[].
+ From Albert Chin <china@thewrittenword.com>
+
+2005-07-04 Paul D. Smith <psmith@gnu.org>
+
+ * config.h-vms.template [VMS]: Latest VMS has its own glob() and
+ globfree(); set up to use the GNU versions.
+ From Martin Zinser <zinser@zinser.no-ip.info>
+
+2005-07-03 Paul D. Smith <psmith@gnu.org>
+
+ From J. Grant <jg@jguk.org>:
+
+ * README.W32.template: Update the Windows and tested MSVC versions.
+ * NMakefile.template (CFLAGS_any): Change warning level from W3 to W4.
+ * w32/subproc/NMakefile (CFLAGS_any): Ditto.
+ * build_w32.bat: Ditto.
+ * w32/subproc/build.bat: Ditto.
+
+2005-06-28 Paul D. Smith <psmith@gnu.org>
+
+ * signame.c: HAVE_DECL_* macros are set to 0, not undef, if the
+ declaration was checked but not present.
+
+2005-06-27 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (find_directory): Change type of fs_serno/fs_flags/fs_len
+ to unsigned long. Fixes Savannah bug #13550.
+
+ * w32/subproc/sub_proc.c: Remove (HANDLE) casts on lvalues.
+ (process_pipe_io): Initialize tStdin/tStdout/tStderr variables.
+ Fixes Savannah bug #13551.
+
+2005-06-26 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Fix bug in ANSI_STRING/strerror() handling; only define
+ it if ANSI_STRING is not set.
+
+2005-06-25 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval): If no filenames are passed to any of the
+ "include" variants, don't print an error.
+ * doc/make.texi (Include): Document this.
+ Fixes Savannah bug #1761.
+
+ * job.c (construct_command_argv_internal): Sanitize handling of
+ backslash/newline pairs according to POSIX: that is, keep the
+ backslash-newline in the command script, but remove a following
+ TAB character, if present. In the fast path, make sure that the
+ behavior matches what the shell would do both inside and outside
+ of quotes. In the slow path, quote the backslash and put a
+ literal newline in the string.
+ Fixes Savannah bug #1332.
+ * doc/make.texi (Execution): Document the new behavior and give
+ some examples.
+ * NEWS: Make a note of the new behavior.
+
+ * make.h [WINDOWS32]: #include <direct.h>.
+ Fixes Savannah bug #13478.
+
+ * remake.c (name_mtime): If the stat() of a file fails and the -L
+ option was given and the file is a symlink, take the best mtime of
+ the symlink we can get as the mtime of the file and don't fail.
+ Fixes Savannah bug #13280.
+
+ * read.c (find_char_unquote): Accept a new argument IGNOREVARS.
+ If it's set, then don't stop on STOPCHARs or BLANKs if they're
+ inside a variable reference. Make this function static as it's
+ only used here.
+ (eval): Call find_char_unquote() with IGNOREVARS set when we're
+ parsing an unexpanded line looking for semicolons.
+ Fixes Savannah bug #1454.
+ * misc.c (remove_comments): Move this to read.c and make it static
+ as it's only used there. Call find_char_unquote() with new arg.
+ * make.h: Remove prototypes for find_char_unquote() and
+ remove_comments() since they're static now.
+
+ * main.c (main): If we see MAKE_RESTARTS in the environment, unset
+ its export flag and obtain its value. When we need to re-exec,
+ increment the value and add it into the environment.
+ * doc/make.texi (Special Variables): Document MAKE_RESTARTS.
+ * NEWS: Mention MAKE_RESTARTS.
+ * main.c (always_make_set): New variable. Change the -B option to
+ set this one instead.
+ (main): When checking makefiles, only set always_make_flag if
+ always_make_set is set AND the restarts flag is 0. When building
+ normal targets, set it IFF always_make_set is set.
+ (main): Avoid infinite recursion with -W, too: only set what-if
+ files to NEW before we check makefiles if we've never restarted
+ before. If we have restarted, set what-if files to NEW _after_ we
+ check makefiles.
+ Fixes Savannah bug #7566:
+
+2005-06-17 Paul D. Smith <psmith@gnu.org>
+
+ * default.c: Change VMS implicit rules to use $$$$ instead of $$
+ in the prerequisites list.
+
+2005-06-12 Paul D. Smith <psmith@gnu.org>
+
+ Fix Savannah bug # 1328.
+
+ * configure.in: Check for atexit().
+ * misc.c (close_stdout): Test stdout to see if writes to it have
+ failed. If so, be sure to exit with a non-0 error code. Based on
+ code found in gnulib.
+ * make.h: Prototype.
+ * main.c (main): Install close_stdout() with atexit().
+
+2005-06-10 Paul D. Smith <psmith@gnu.org>
+
+ VMS build updates from Hartmut Becker <Hartmut.Becker@hp.com>:
+
+ * vmsjobs.c [VMS]: Updates to compile on VMS: add some missing
+ headers; make vmsWaitForChildren() static; extern vmsify().
+ * job.c [VMS]: Move vmsWaitForChildren() prototype to be global.
+ Don't create child_execute_job() here (it's in vmsjobs.c).
+ * makefile.vms (job.obj) [VMS]: Add vmsjobs.c as a prerequisite.
+
+2005-06-09 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (push_new_variable_scope): File variables point
+ directly to the global_setlist variable. So, inserting a new
+ scope in front of that has no effect on those variables: they
+ don't go through current_variable_set_list. If we're pushing a
+ scope and the current scope is global, push it "the other way" so
+ that the new setlist is in the global_setlist variable, and
+ next points to a new setlist with the global variable set.
+ (pop_variable_scope): Properly undo a push with the new
+ semantics.
+ Fixes Savannah bug #11913.
+
+2005-05-31 Boris Kolpackov <boris@kolpackov.net>
+
+ * job.c (reap_children): Don't die of the command failed but
+ the dontcare flag is set. Fixes Savannah bug #13216.
+
+ * implicit.c (pattern_search): When creating a target from
+ an implicit rule match, lookup pattern target and set precious
+ flag in a newly created target. Fixes Savannah bug #13218.
+
+2005-05-13 Paul D. Smith <psmith@gnu.org>
+
+ Implement "if... else if... endif" syntax.
+
+ * read.c (eval): Push all checks for conditional words ("ifeq",
+ "else", etc.) down into the conditional_line() function.
+ (conditional_line): Rework to allow "else if..." clause. New
+ return value -2 for lines which are not conditionals. The
+ ignoring flag can now also be 2, which means "already parsed a
+ true branch". If that value is seen no other branch of this
+ conditional can be considered true. In the else parsing if there
+ is extra text after the else, invoke conditional_line()
+ recursively to see if it's another conditional. If not, it's an
+ error. If so, raise the conditional value to this level instead
+ of creating a new conditional nesting level. Special check for
+ "else" and "endif", which aren't allowed on the "else" line.
+ * doc/make.texi (Conditional Syntax): Document the new syntax.
+
+2005-05-09 Paul D. Smith <psmith@gnu.org>
+
+ * Makefile.am (EXTRA_make_SOURCES): Add vmsjobs.c
+ (MAYBE_W32): Rework how SUBDIRS are handled so that "make dist"
+ recurses to the w32 directory, even on non-Windows systems. Use
+ the method suggested in the automake manual.
+ * configure.in: Add w32/Makefile to AC_CONFIG_FILES.
+ * maintMakefile (gnulib-url): They moved the texinfo.tex files.
+
+2005-05-07 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (die): If we're dying with a fatal error (not that a
+ command has failed), write back any leftover tokens before we go.
+
+ * job.c (set_child_handler_action_flags): If there are jobs
+ waiting for the load to go down, set an alarm to go off in 1
+ second. This allows us to wake up from a potentially long-lasting
+ read() and start a new job if the load has gone down. Turn it off
+ after the read.
+ (job_noop): Dummy signal handler function.
+ (new_job): Invoke it with the new semantics.
+
+ * docs/make.texi: Document secondary expansion. Various cleanups
+ and random work.
+
+2005-05-03 Paul D. Smith <psmith@gnu.org>
+
+ Rename .DEFAULT_TARGET to .DEFAULT_GOAL: in GNU make terminology
+ the targets which are to ultimately be made are called "goals";
+ see the GNU make manual. Also, MAKECMDGOALS, etc.
+
+ * filedef.h, read.c, main.c: Change .DEFAULT_TARGET to
+ .DEFAULT_GOAL, and default_target_name to default_goal_name.
+ * doc/make.texi (Special Variables): Document .DEFAULT_GOAL.
+
+2005-05-02 Paul D. Smith <psmith@gnu.org>
+
+ * job.c, vmsjobs.c (vmsWaitForChildren, vms_redirect,
+ vms_handle_apos, vmsHandleChildTerm, reEnableAst, astHandler,
+ tryToSetupYAst, child_execute_job) [VMS]: Move VMS-specific
+ functions to vmsjobs.c. #include it into jobs.c.
+
+ Grant Taylor <gtaylor@picante.com> reports that -j# can lose
+ jobserver tokens. I found that this happens when an exported
+ recursive variable contains a $(shell ...) function reference: in
+ this situation we could "forget" to write back a token.
+
+ * job.c, job.h: Add variable jobserver_tokens: counts the tokens
+ we have. It's not reliable to depend on the number of children in
+ our linked list so keep a separate count.
+ (new_job): Check jobserver_tokens rather than children &&
+ waiting_jobs. Increment jobserver_tokens when we get one.
+ (free_child): If jobserver_tokens is 0, internal error. If it's
+ >1, write a token back to the jobserver pipe (we don't write a
+ token for the "free" job). Decrement jobserver_tokens.
+
+ * main.c: Add variable master_job_slots.
+ (main): Set it to hold the number of jobs requested if we're the
+ master process, when using the jobserver.
+ (die): Sanity checks: first test jobserver_tokens to make sure
+ this process isn't holding any tokens we didn't write back.
+ Second, if master_job_slots is set count the tokens left in the
+ jobserver pipe and ensure it's the same as master_job_slots (- 1).
+
+2005-04-24 Paul D. Smith <psmith@gnu.org>
+
+ Grant Taylor <gtaylor@picante.com> reports that -j# in conjunction
+ with -l# can lose jobserver tokens, because waiting jobs are not
+ consulted properly when checking for the "free" token.
+
+ * job.c (free_child): Count waiting_jobs as having tokens.
+ * job.c (new_job): Ditto. Plus, call start_waiting_jobs() here to
+ handle jobs waiting for the load to drop.
+
+2005-04-23 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Be careful to not core if a variable setting in
+ the environment doesn't contain an '='. This is illegal but can
+ happen in broken setups.
+ Reported by Joerg Schilling <schilling@fokus.fraunhofer.de>.
+
+2005-04-12 Paul D. Smith <psmith@gnu.org>
+
+ The second expansion feature causes significant slowdown. Timing
+ a complex makefile (GCC 4.1) shows a slowdown from .25s to just
+ read the makefile before the feature, to 11+s to do the same
+ operations after the feature. Additionally, memory usage
+ increased drastically. To fix this I added some intelligence that
+ avoids the overhead of the second expansion unless it's required.
+
+ * dep.h: Add a new boolean field, need_2nd_expansion.
+
+ * read.c (eval): When creating the struct dep for the target,
+ check if the name contains a "$"; if so set need_2nd_expansion to 1.
+ (record_files): If there's a "%" in a static pattern rule, it gets
+ converted to "$*" so set need_2nd_expansion to 1.
+
+ * file.c (expand_deps): Rework to be more efficient. Only perform
+ initialize_file_variables(), set_file_variables(), and
+ variable_expand_for_file() if the need_2nd_expansion is set.
+
+ * implicit.c (pattern_search): Default need_2nd_expansion to 0.
+ (pattern_search): Ditto.
+ * main.c (handle_non_switch_argument): Ditto.
+ (main): Ditto.
+ * read.c (read_all_makefiles): Ditto.
+ (eval_makefile): Ditto.
+
+2005-04-07 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main) [WINDOWS32]: Export PATH to sub-shells, not Path.
+ * variable.c (sync_Path_environment): Ditto.
+ Patch by Alessandro Vesely. Fixes Savannah bug #12209.
+
+ * main.c (main): Define the .FEATURES variable.
+ * NEWS: Announce .FEATURES.
+ * doc/make.texi (Special Variables): Document .FEATURES.
+
+ * remake.c (check_dep): If a file is .PHONY, update it even if
+ it's marked intermediate. Fixes Savannah bug #12331.
+
+2005-03-15 Boris Kolpackov <boris@kolpackov.net>
+
+ * file.c (expand_deps): Factor out the second expansion and
+ prerequisite line parsing logic from snap_deps().
+
+ * file.c (snap_deps): Use expand_deps(). Expand and parse
+ prerequisites of the .SUFFIXES special target first. Fixes
+ Savannah bug #12320.
+
+2005-03-13 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main) [MSDOS]: Export SHELL in MSDOS. Requested by Eli
+ Zaretskii.
+
+2005-03-11 Paul D. Smith <psmith@gnu.org>
+
+ * signame.c (strsignal): HAVE_DECL_SYS_SIGLIST is 0 when not
+ available, not undefined (from Earnie Boyd).
+
+2005-03-10 Boris Kolpackov <boris@kolpackov.net>
+
+ * implicit.c (pattern_search): Mark an intermediate target as
+ precious if it happened to be a prerequisite of some (other)
+ target. Fixes Savannah bug #12267.
+
+2005-03-09 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval_makefile): Add alloca(0).
+ (eval_buffer): Ditto.
+
+2005-03-09 Boris Kolpackov <boris@kolpackov.net>
+
+ * main.c (main): Use o_file instead of o_default when defining
+ the .DEFAULT_TARGET special variable.
+ * read.c (eval): Use define_variable_global() instead of
+ define_variable() when setting new value for the .DEFAULT_TARGET
+ special variable. Fixes Savannah bug #12266.
+
+2005-03-04 Boris Kolpackov <boris@kolpackov.net>
+
+ * imlicit.c (pattern_search): Mark files for which an implicit
+ rule has been found as targets. Fixes Savannah bug #12202.
+
+2005-03-04 Paul D. Smith <psmith@gnu.org>
+
+ * AUTHORS: Update.
+ * doc/make.texi (Automatic Variables): Document $|.
+
+2005-03-03 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (record_files): Instead of substituting % with
+ actual stem value in dependency list replace it with $*.
+ This fixes stem triple expansion bug.
+
+ * implicit.c (pattern_search): Copy stem to a separate
+ buffer and make it a properly terminated string. Assign
+ this buffer instead of STEM (which is not terminated) to
+ f->stem. Instead of substituting % with actual stem value
+ in dependency list replace it with $*. This fixes stem
+ triple expansion bug.
+
+2005-03-01 Paul D. Smith <psmith@gnu.org>
+
+ * commands.c (fatal_error_signal) [WINDOWS32]: Don't call kill()
+ on Windows, as it takes a handle not a pid. Just exit.
+ Fix from patch #3679, provided by Alessandro Vesely.
+
+ * configure.in: Update check for sys_siglist[] from autoconf manual.
+ * signame.c (strsignal): Update to use the new autoconf macro.
+
+2005-03-01 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (record_files): Add a check for the list of prerequisites
+ of a static pattern rule being empty. Fixes Savannah bug #12180.
+
+2005-02-28 Paul D. Smith <psmith@gnu.org>
+
+ * doc/make.texi (Text Functions): Update docs to allow the end
+ ordinal for $(wordlist ...) to be 0.
+ * function.c (func_wordlist): Fail if the start ordinal for
+ $(wordlist ...) is <1. Matches documentation.
+ Resolves Savannah support request #103195.
+
+ * remake.c (update_goal_chain): Fix logic for stopping in -q:
+ previously we were stopping when !-q, exactly the opposite. This
+ has been wrong since version 1.34, in 1994!
+ (update_file): If we got an error don't break out to run more
+ double-colon rules: just return immediately.
+ Fixes Savannah bug #7144.
+
+2005-02-27 Paul D. Smith <psmith@gnu.org>
+
+ * misc.c (end_of_token): Make argument const.
+ * make.h: Update prototype.
+
+ * function.c (abspath, func_realpath, func_abspath): Use
+ PATH_VAR() and GET_PATH_MAX instead of PATH_MAX.
+ * dir.c (downcase): Use PATH_VAR() instead of PATH_MAX.
+ * read.c (record_files): Ditto.
+ * variable.c (do_variable_definition): Ditto.
+
+ * function.c (func_error): Create a new function $(info ...) that
+ simply prints the message to stdout with no extras.
+ (function_table_init): Add new function to the table.
+ * NEWS: Add $(info ...) reference.
+ * doc/make.texi (Make Control Functions): Document it.
+
+ New feature: if the system supports symbolic links, and the user
+ provides the -L/--check-symlink-time flag, then use the latest
+ mtime between the symlink(s) and the target file.
+
+ * configure.in (MAKE_SYMLINKS): Check for lstat() and
+ readlink(). If both are available, define MAKE_SYMLINKS.
+ * main.c: New variable: check_symlink_flag.
+ (usage): Add a line for -L/--check-symlink-times to the help string.
+ (switches): Add -L/--check-symlink-times command line argument.
+ (main): If MAKE_SYMLINKS is not defined but the user specified -L,
+ print a warning and disable it again.
+ * make.h: Declare check_symlink_flag.
+ * remake.c (name_mtime): If MAKE_SYMLINKS and check_symlink_flag,
+ if the file is a symlink then check each link in the chain and
+ choose the NEWEST mtime we find as the mtime for the file. The
+ newest mtime might be the file itself!
+ * NEWS: Add information about this new feature.
+ * doc/make.texi (Options Summary): Add -L/--check-symlink-times docs.
+
+ Avoid core dumps described in Savannah bug # 12124:
+
+ * file.c: New variable snapped_deps remember whether we've run
+ snap_deps().
+ (snap_deps): Set it.
+ * filedef.h: Extern it.
+ * read.c (record_files): Check snapped_deps; if it's set then
+ we're trying to eval a new target/prerequisite relationship from
+ within a command script, which we don't support. Fatal.
+
+2005-02-28 Boris Kolpackov <boris@kolpackov.net>
+
+ Implementation of the .DEFAULT_TARGET special variable.
+
+ * read.c (eval): If necessary, update default_target_name when
+ reading rules.
+ * read.c (record_files): Update default_target_file if
+ default_target_name has changed.
+ * main.c (default_target_name): Define.
+ * main.c (main): Enter .DEFAULT_TARGET as make variable. If
+ default_target_name is set use default_target_file as a root
+ target to make.
+ * filedef.h (default_target_name): Declare.
+ * dep.h (free_dep_chain):
+ * misc.c (free_dep_chain): Change to operate on struct nameseq
+ and change name to free_ns_chain.
+ * file.c (snap_deps): Update to use free_ns_chain.
+
+2005-02-27 Boris Kolpackov <boris@kolpackov.net>
+
+ Implementation of the second expansion in explicit rules,
+ static pattern rules and implicit rules.
+
+ * read.c (eval): Refrain from chopping up rule's dependencies.
+ Store them in a struct dep as a single dependency line. Remove
+ the code that implements SySV-style automatic variables.
+
+ * read.c (record_files): Adjust the code that handles static
+ pattern rules to expand all percents instead of only the first
+ one. Reverse the order in which dependencies are stored so that
+ when the second expansion reverses them again they appear in
+ the makefile order (with some exceptions, see comments in
+ the code). Remove the code that implements SySV-style automatic
+ variables.
+
+ * file.c (snap_deps): Implement the second expansion and chopping
+ of dependency lines for explicit rules.
+
+ * implicit.c (struct idep): Define an auxiliary data type to hold
+ implicit rule's dependencies after stem substitution and
+ expansion.
+
+ * implicit.c (free_idep_chain): Implement.
+
+ * implicit.c (get_next_word): Implement helper function for
+ parsing implicit rule's dependency lines into words taking
+ into account variable expansion requests. Used in the stem
+ splitting code.
+
+ * implicit.c (pattern_search): Implement the second expansion
+ for implicit rules. Also fixes bug #12091.
+
+ * commands.h (set_file_variables): Declare.
+ * commands.c (set_file_variables): Remove static specifier.
+
+ * dep.h (free_dep_chain): Declare.
+ * misc.c (free_dep_chain): Implement.
+
+ * variable.h (variable_expand_for_file): Declare.
+ * expand.c (variable_expand_for_file): Remove static specifier.
+
+ * make.h (strip_whitespace): Declare.
+ * function.c (strip_whitespace): Remove static specifier.
+
+2005-02-26 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Check for ferror() when reading makefiles from stdin.
+ Apparently some shells in Windows don't close pipes properly and
+ require this check.
+
+2005-02-24 Jonathan Grant <jg@jguk.org>
+
+ * configure.in: Add MinGW configuration options, and extra w32 code
+ directory.
+ * Makefile.am: Add MinGW configuration options, and extra w32 code
+ directory.
+ * main.c: Determine correct program string (after last \ without .exe).
+ * subproc/sub_proc.c: `GetExitCodeProcess' from incompatible pointer
+ type fix x2
+ * w32/Makefile.am: Import to build win32 lib of sub_proc etc.
+ * subproc/w32err.c: MSVC thread directive not applied to MinGW builds.
+ * tests/run_make_tests.pl, tests/test_driver.pl: MSYS testing
+ environment support.
+
+2004-04-16 Dmitry V. Levin <ldv@altlinux.org>
+
+ * function.c (func_shell): When initializing error_prefix, check
+ that reading file name is not null. This fixes long-standing
+ segfault in cases like "make 'a1=$(shell :)' 'a2:=$(a1)'".
+
+2005-02-09 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile: Update the CVS download URL to simplify them.
+ Also, the ftp://ftp.gnu.org/GNUinfo site was removed so I'm
+ downloading the .texi files from Savannah now.
+
+ Fixed these issues reported by Markus Mauhart <qwe123@chello.at>:
+
+ * main.c (handle_non_switch_argument): Only add variables to
+ command_variables if they're not already there: duplicate settings
+ waste space and can be confusing to read.
+
+ * w32/include/sub_proc.h: Remove WINDOWS32. It's not needed since
+ this header is never included by non-WINDOWS32 code, and it
+ requires <config.h> to define which isn't always included first.
+
+ * dir.c (read_dirstream) [MINGW]: Use proper macro names when
+ testing MINGW32 versions.
+
+ * main.c (log_working_directory): flush stdout to be sure the WD
+ change is printed before any stderr messages show up.
+
+2005-02-01 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile (po_repo): Update the GNU translation site URL.
+
+2004-12-01 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Change char* env_shell to struct variable shell_var.
+ * variable.c (target_environment): Use new shell_var.
+
+2004-11-30 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: The old way we avoided creating build.sh from
+ build.sh.in before build.sh.in exists doesn't work anymore; we
+ have to use raw M4 (thanks to Andreas Schwab <schwab@suse.de> for
+ the help!). This also keeps automake from complaining.
+ * Makefile.am (README): Add a dummy target so automake won't
+ complain that this file doesn't exist when we checkout from CVS.
+ * maintMakefile (.dep_segment): Rewrite this rule since newer
+ versions of automake don't provide DEP_FILES.
+
+2004-11-30 Boris Kolpackov <boris@kolpackov.net>
+
+ Implementation of `realpath' and `abspath' built-in functions.
+
+ * configure.in: Check for realpath.
+ * function.c (abspath): Return an absolute file name that does
+ not contain any `.' or `..' components, nor repeated `/'.
+ * function.c (func_abspath): For each name call abspath.
+ * function.c (func_realpath): For each name call realpath
+ from libc or delegate to abspath if realpath is not available.
+ * doc/make.texi (Functions for File Names): Document new functions.
+ * doc/make.texi (Quick Reference): Ditto.
+
+2004-11-28 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main) [WINDOWS32]: Remove any trailing slashes from -C
+ arguments. Fixes bug #10252.
+
+ Fix for bug #1276: Handle SHELL according to POSIX requirements.
+
+ * main.c (main): Set SHELL to v_noexport by default. Remember the
+ original environment setting of SHELL in the env_shell variable.
+ * main.h: Export new env_shell variable.
+ * variable.c (target_environment): If we find a v_noexport
+ variable for SHELL, add a SHELL variable with the env_shell value.
+ * doc/make.texi (Quick Reference): Document the POSIX behavior.
+ * doc/make.texi (Variables/Recursion): Ditto.
+
+2004-11-28 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (find_and_set_default_shell) [WINDOWS32]: check for
+ equality of "cmd"/"cmd.exe", not inequality. Fixes bug #11155.
+ Patch by Alessandro Vesely.
+
+2004-11-12 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (child_execute_job) [VMS]: Don't treat "#" as a comment on
+ the command line if it's inside a string.
+ Patch by: Hartmut Becker <Hartmut.Becker@hp.com>
+
+2004-10-21 Boris Kolpackov <boris@kolpackov.net>
+
+ * function.c (func_lastword): New function: return last word
+ from the list of words.
+ * doc/make.texi: Document $(lastword ). Fix broken links in
+ Quick Reference section.
+
+2004-10-06 Paul D. Smith <psmith@gnu.org>
+
+ Apply patch from Alessandro Vesely, provided with bug # 9748.
+ Fix use of tmpnam() to work with Borland C.
+
+ * job.c (construct_command_argv_internal) [WINDOWS32]: Remove
+ construction of a temporary filename, and call new function
+ create_batch_filename().
+ (create_batch_filename) [WINDOWS32]: New function to create a
+ temporary filename.
+
+2004-10-05 Boris Kolpackov <boris@kolpackov.net>
+
+ * read.c (record_target_var): Expand simple pattern-specific
+ variable.
+ * variable.c (initialize_file_variables): Do not expand simple
+ pattern-specific variable.
+
+2004-09-28 Boris Kolpackov <boris@kolpackov.net>
+
+ * remake.c (update_file_1): When rebuilding makefiles inherit
+ dontcare flag from a target that triggered update.
+
+2004-09-27 Boris Kolpackov <boris@kolpackov.net>
+
+ * variable.c (initialize_file_variables): Mark pattern-specific
+ variable as a per-target and copy export status.
+
+2004-09-21 Boris Kolpackov <boris@kolpackov.net>
+
+ * file.c (snap_deps): Mark .PHONY prerequisites as targets.
+
+ * implicit.c (pattern_search): When considering an implicit rule's
+ prerequisite check that it is actually a target rather then
+ just an entry in the file hashtable.
+
+2004-09-21 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (readstring): Fix some logic errors in backslash handling.
+ (eval): Remove some unnecessary processing in buffer handling.
+ (record_target_var): Assert that parse_variable_definition() succeeded.
+ Reported by: Markus Mauhart <qwe123@chello.at>.
+
+ * misc.c: Removed the sindex() function. All instances of this
+ function were trivially replaceable by the standard strstr()
+ function, and that function will always have better (or certainly
+ no worse) performance than the very simple-minded algorithm
+ sindex() used. This can matter with complex makefiles.
+ * make.h: Remove the prototype for sindex().
+ * function.c (subst_expand): Convert sindex() call to strstr().
+ This means we no longer need to track the TLEN value so remove that.
+ (func_findstring): Convert sindex() to strstr().
+ * commands.c (chop_commands): Convert sindex() calls to strstr().
+ Suggested by: Markus Mauhart <qwe123@chello.at>.
+
+ * main.c (find_and_set_default_shell) [WINDOWS32]: Implement the
+ idea behind Savannah Patch #3144 from david.baird@homemail.com.
+ If SHELL is set to CMD.EXE then assume it's batch-mode and
+ non-unixy. I wrote the code differently from the patch, though,
+ to make it safer. This also resolves bug #9174.
+
+2004-09-20 Paul D. Smith <psmith@gnu.org>
+
+ * expand.c (variable_expand_string): Modify to invoke
+ patsubst_expand() instead of subst_expand(); the latter didn't
+ handle suffix patterns correctly.
+ * function.c (subst_expand): Remove the SUFFIX_ONLY parameter; it
+ was used only from variable_expand_string() and is no longer used
+ there.
+ (func_subst): Ditto, on call to subst_expand().
+ (patsubst_expand): Require the percent pointers to point to the
+ character after the %, not to the % itself.
+ * read.c (record_files): New call criteria for patsubst_expand().
+ * variable.h: Remove SUFFIX_ONLY from subst_expand() prototype.
+ This is to fix a bug reported by Markus Mauhart <qwe123@chello.at>.
+
+2004-09-19 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (subst_expand): Fix a check in by_word: look for a
+ previous blank if we're beyond the beginning of the string, not
+ the beginning of the word.
+ Bugs reported by Markus Mauhart <qwe123@chello.at>.
+
+2004-05-16 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_goal_chain): Change the argument specifying
+ whether we're rebuilding makefiles to be a global variable,
+ REBUILDING_MAKEFILES.
+ (complain): Extract the code that complains about no rules to make
+ a target into a separate function.
+ (update_file_1): If we tried to rebuild a file during the makefile
+ rebuild phase and it was dontcare, then no message was printed.
+ If we then try to build the same file during the normal build,
+ print a message this time.
+ (remake_file): Don't complain about un-remake-able files when
+ we're rebuilding makefiles.
+
+2004-05-11 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): OS/2 patches from
+ Andreas Buening <andreas.buening@nexgo.de>.
+
+2004-05-10 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (update_file): Don't walk the double-colon chain unless
+ this is a double-colon rule. Fix suggested by Boris Kolpackov
+ <boris@kolpackov.net>.
+
+ * makefile.vms (CFLAGS): Remove glob/globfree (see readme.vms docs)
+ * readme.vms: New section describing OpenVMS support and issues.
+ * default.c (default_variables): Add support for IA64.
+ * job.c (tryToSetupYAst) [VMS]: On VMS running make in batch mode
+ without some privilege aborts make with the error
+ %SYSTEM-F-NOPRIV. It happens when setting up a handler for
+ pressing Ctrl+Y and the input device is no terminal. The change
+ catches this error and just continues.
+
+ Patches by Hartmut Becker <Hartmut.Becker@hp.com>
+
+2004-04-25 Paul D. Smith <psmith@gnu.org>
+
+ * commands.c (set_file_variables): Set $< properly in the face of
+ order-only prerequisites.
+ Patch from Boris Kolpackov <boris@kolpackov.net>
+
+2004-04-21 Bob Byrnes <byrnes@curl.com>
+
+ * main.c (main): Notice failures to remake makefiles.
+
+2004-03-28 Paul D. Smith <psmith@gnu.org>
+
+ Patches for Acorn RISC OS by Peter Naulls <peter@chocky.org>
+
+ * job.c: No default shell for RISC OS.
+ (load_too_high): Hard-code the return to 1.
+ (construct_command_argv_internal): No sh_chars or sh_cmds.
+ * getloadavg.c: Don't set LOAD_AVE_TYPE on RISC OS.
+
+2004-03-20 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (do_variable_definition): Don't append from the
+ global set if a previous non-appending target-specific variable
+ definition exists. Reported by Oliver Schmidt <oschmidt@gmx.net>
+ (with fix).
+
+ * expand.c (reference_variable): Don't give up on variables with
+ no value that have the target-specific append flag set: they might
+ have a value after all. Reported by Oliver Schmidt
+ <oschmidt@gmx.net> (with fix) and also by Maksim A. Nikulin
+ <nikulin@dx1cmd.inp.nsk.su>.
+
+ * rule.c (count_implicit_rule_limits): Don't delete patterns which
+ refer to absolute pathnames in directories that don't exist: some
+ portion of the makefile could create those directories before we
+ match the pattern. Fixes bugs #775 and #108.
+
+ Fixes from Jonathan R. Grant <jg-make@jguk.org>:
+
+ * main.c (main): Free makefile_mtimes if we have any.
+ * README.W32.template: Update documentation for the current status
+ of the MS-Windows port.
+ * NMakefile.template (MAKE): Add "MAKE = nmake". A conflicting
+ environment variable is sometimes already defined which causes the
+ build to fail.
+ * main.c (debug_signal_handler): Only define this function if
+ SIGUSR1 is available.
+
+ Fixes for OS/2 from Andreas Beuning <andreas.buening@nexgo.de>:
+
+ * configure.in [OS/2]: Relocate setting of HAVE_SA_RESTART for OS/2.
+ * README.OS2.template: Documentation updates.
+ * build.template: Add LIBINTL into LOADLIBES. Add $CFLAGS to the
+ link line for safety.
+ * maintMakefile (build.sh.in): Remove an extraneous ")".
+ * job.c (child_execute_job): Close saved FDs.
+ * job.c (exec_command) [OS/2]: exec_command(): If the command
+ can't be exec'ed and if the shell is not Unix-sh, then try again
+ with argv = { "cmd", "/c", ... }. Normally, this code is never
+ reached for the cmd shell unless the command really doesn't exist.
+ (construct_command_argv_internal) [OS/2]: The code for cmd
+ handling now uses new_argv = { "cmd", "/c", "original line", NULL}.
+ The CMD builtin commands are case insensitive so use strcasecmp().
+
+2004-03-19 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (do_define): Re-order line counter increment so the count
+ is accurate (we were losing one line per define). Reported by
+ Dave Yost <Dave@Yost.com>.
+
+2004-03-06 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in (HAVE_ANSI_COMPILER): Define if we have an ANSI/ISO
+ compiler.
+ * make.h: Convert uses of __STDC__ to HAVE_ANSI_COMPILER.
+ * misc.c (message,error,fatal): Ditto.
+ * configh.dos.template: Define HAVE_ANSI_COMPILER.
+ * config.h.W32.template: Ditto.
+ * config.h-vms.template: Ditto.
+ * config.ami.template: Ditto.
+
+2004-03-04 Paul D. Smith <psmith@gnu.org>
+
+ * README.template: Add a note about broken /bin/sh on SunOS
+ 4.1.3_U1 & 4.1.4. Fix up Savannah links.
+
+ * misc.c (message, error, fatal): Don't use "..." if we're using
+ varargs. ansi2knr should handle this but it doesn't work: it
+ translates "..." to va_dcl etc. but _AFTER_ the preprocessor is
+ done. On many systems (SunOS for example) va_dcl is a #define.
+ So, force the use of the non-"..." version on pre-ANSI compilers.
+
+ * maintMakefile (sign-dist): Create some rules to help automate
+ the new GNU ftp upload method.
+
+2004-02-24 Paul D. Smith <psmith@gnu.org>
+
+ * config.h.W32.template: Add HAVE_STDARG_H
+ * config.h-vms.template: Ditto.
+ * config.ami.template: Ditto.
+
+2004-02-23 Jonathan Grant <jg-make@jguk.org>
+
+ * README.W32.template: Add a notation about -j with BATCH_MODE_ONLY.
+ * build_w32.bat: Remove extra "+".
+
+2004-02-23 Paul D. Smith <psmith@gnu.org>
+
+ * make.h: Create an UNUSED macro to mark unused parameters.
+ * (many): Clean up warnings by applying UNUSED, fixing
+ signed/unsigned incompatibilities, etc.
+
+ * acinclude.m4 (AC_STRUCT_ST_MTIM_NSEC): Add quoting to silence
+ autoconf warnings.
+ * filedef.h: Name the command_state enumeration.
+ * file.c (set_command_state): Use the enumeration in the function
+ argument.
+
+ * configure.in: Explicitly set SET_MAKE to empty, to disable
+ MAKE=make even when no make already exists. Fix bug #3823.
+
+2004-02-22 Paul D. Smith <psmith@gnu.org>
+
+ * maintMakefile: Perl script to clean up all non-CVS files. Use
+ it on all the subdirectories for the cvs-clean target.
+
+ * main.c (decode_switches): Require non-empty strings for all our
+ string command-line options. Fixes Debian bug # 164165.
+
+ * configure.in: Check for stdarg.h and varargs.h.
+ * make.h (USE_VARIADIC): Set this if we can use variadic functions
+ for printing messages.
+ * misc.c: Check USE_VARIADIC instead of (obsolete) HAVE_STDVARARGS.
+ (message): Ditto.
+ (error): Ditto.
+ (fatal): Ditto.
+
+ A number of patches for OS/2 support from Andreas Buening
+ <andreas.buening@nexgo.de>:
+
+ * job.c (child_handler) [OS/2]: Allow this on OS/2 but we have to
+ disable the SIGCHLD handler.
+ (reap_children) [OS/2]: Remove special handling of job_rfd.
+ (set_child_handler_action_flags) [OS/2]: Use this function in OS/2.
+ (new_job) [OS/2]: Disable the SIGCHLD handler on OS/2.
+ * main.c (main) [OS/2]: Special handling for paths in OS/2.
+ * configure.in [OS/2]: Force SA_RESTART for OS/2.
+ * Makefile.am (check-regression): Use $(EXEEXT) for Windows-type
+ systems.
+
+2004-02-21 Paul D. Smith <psmith@gnu.org>
+
+ * w32/subproc/sub_proc.c (process_easy) [W32]: Christoph Schulz
+ <mail@kristov.de> reports that if process_begin() fails we don't
+ handle the error condition correctly in all cases.
+ * w32/subproc/w32err.c (map_windows32_error_to_string): Make sure
+ to have a newline on the message.
+
+ * job.c (construct_command_argv_internal): Add "test" to UNIX
+ sh_cmds[]. Fixes Savannah bug # 7606.
+
+2004-02-04 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (vms_handle_apos) [VMS]: Fix various string handling
+ situations in VMS DCL. Fixes Savannah bug #5533. Fix provided by
+ Hartmut Becker <Hartmut.Becker@hp.com>.
+
+2004-01-21 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (load_too_high): Implement an algorithm to control the
+ "thundering herd" problem when using -l to control job creation
+ via the load average. The system only recomputes the load once a
+ second but we can start many jobs in a second. To solve this we
+ keep track of the number of jobs started in the last second and
+ apply a weight to try to guess what a correct load would be.
+ The algorithm was provided by Thomas Riedl <thomas.riedl@siemens.com>.
+ Also fixes bug #4693.
+ (reap_children): Decrease the job count for this second.
+ (start_job_command): Increase the job count for this second.
+
+ * read.c (conditional_line): Expand the text after ifn?def before
+ checking to see if it's a single word. Fixes bug #7257.
+
+2004-01-09 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (print_file): Recurse to print all targets in
+ double-colon rules. Fixes bug #4518, reported (with patch) by
+ Andrew Chatham <chatham@google.com>.
+
+2004-01-07 Paul D. Smith <psmith@gnu.org>
+
+ * acinclude.m4: Remove make_FUNC_SETVBUF_REVERSED.
+ * configure.in: Change make_FUNC_SETVBUF_REVERSED to
+ AC_FUNC_SETVBUF_REVERSED.
+
+ * doc/make.texi (Target-specific): Fix Savannah bug #1772.
+ (MAKE Variable): Fix Savannah bug #4898.
+
+ * job.c (construct_command_argv_internal): Add "!" to the list of
+ shell escape chars. POSIX sh allows it to appear before a
+ command, to negate the exit code. Fixes bug #6404.
+
+ * implicit.c (pattern_search): When matching an implicit rule,
+ remember which dependencies have the ignore_mtime flag set.
+ Original fix provided in Savannah patch #2349, by Benoit
+ Poulot-Cazajous <Benoit.Poulot-Cazajous@jaluna.com>.
+
+2003-11-22 Paul D. Smith <psmith@gnu.org>
+
+ * README.W32.template (Outputs): Clarification on -j with
+ BATCH_MODE_ONLY_SEHLL suggested by Jonathan R. Grant
+ <jg-make@jguk.org>.
+
+2003-11-02 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_if): Strip all the trailing whitespace from the
+ condition, then don't expand it. Fixed bug # 5798.
+
+ * expand.c (recursively_expand_for_file): If we're expanding a
+ variable with no file context, then use the variable's context.
+ Fixes bug # 6195.
+
+2003-10-21 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (log_working_directory): Add newlines to printf()s.
+
+ * README.cvs: Add a note to ignore warnings during autoreconf.
+
+ * maintMakefile (po_repo): Set a new URL for PO file updates.
+ (get-config/config.guess get-config/config.sub): Get these files
+ from the Savannah config project instead of ftp.gnu.org.
+
+2003-10-05 Paul Eggert <eggert@twinsun.com>
+
+ * main.c (main): Avoid potential subscript error if environ has
+ short strings.
+
+2003-08-22 Paul D. Smith <psmith@gnu.org>
+
+ * misc.c (xmalloc, xrealloc): Add one to 0 sizes, to cater to
+ systems which don't yet implement the C89 standard :-/.
+
+2003-07-18 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (directory_contents_hash_1, directory_contents_hash_1)
+ [WINDOWS32]: Initialize hash.
+
+2003-06-19 Earnie Boyd <earnie@uses.sf.net>
+
+ * dir.c (read_dirstream): Provide a workaround for broken versions of
+ the MinGW dirent structure.
+
+2003-05-30 Earnie Boyd <earnie@users.sf.net>
+
+ * w32/include/dirent.h: Add __MINGW32__ filter.
+
+2003-05-30 Earnie Boyd <earnie@users.sf.net>
+
+ * make.h: Add global declaration of *make_host.
+ * main.c (print_usage): Remove local declaration of *make_host.
+ (print_version): Display "This program built for ..." after Copyright
+ notice.
+
+2003-05-30 Earnie Boyd <earnie@users.sf.net>
+
+ * doc/make.texi: Change "ifinfo" to "ifnottex" as suggested by the
+ execution of "makeinfo --html make.texi".
+
+2003-04-30 Paul D. Smith <psmith@gnu.org>
+
+ * build.template: Make some changes to maybe allow this script to
+ work on DOS/Windows/OS2 systems. Suggested by Andreas Buening.
+
+ * README.OS2.template: New file for OS/2 support. Original
+ contributed by Andreas Buening.
+ * configure.in: Invoke new pds_AC_DOS_PATHS macro to test for
+ DOS-style paths.
+
+2003-04-19 Paul D. Smith <psmith@gnu.org>
+
+ Fix bug #1405: allow a target to match multiple pattern-specific
+ variables.
+
+ * rule.c (create_pattern_var, lookup_pattern_var): Move these to
+ variable.c, where they've always belonged.
+ * rule.h: Move the prototypes and struct pattern_var as well.
+ * variable.c (initialize_file_variables): Invoke
+ lookup_pattern_var() in a loop, until no more matches are found.
+ If a match is found, create a new variable set for the target's
+ pattern variables. Then merge the contents of each matching
+ pattern variable set into the target's pattern variable set.
+ (lookup_pattern_var): Change this function to be usable
+ in a loop. It takes a starting position: if NULL, start at the
+ beginning; if non-NULL, start with the pattern variable after that
+ position, and return the next matching pattern.
+ (create_pattern_var): Create a unique instance of
+ pattern-specific variables for every definition in the makefile.
+ Don't combine the same pattern together. This allows us to
+ process the variable handling properly even when the same pattern
+ is used multiple times.
+ (parse_variable_definition): New function: break out the parsing
+ of a variable definition line from try_variable_definition.
+ (try_variable_definition): Call parse_variable_definition to
+ parse.
+ (print_variable_data_base): Print out pattern-specific variables.
+ * variable.h (struct variable): Remember when a variable is
+ conditional. Also remember its flavor.
+ (struct pattern_var): Instead of keeping a variable set, we just
+ keep a single variable for each pattern.
+ * read.c (record_target_var): Each pattern variable contains only a
+ single variable, not a set, so create it properly.
+ * doc/make.texi (Pattern-specific): Document the new behavior.
+
+2003-04-17 Paul D. Smith <psmith@gnu.org>
+
+ * dir.c (file_exists_p) [VMS]: Patch provided with Bug #3018 by
+ Jean-Pierre Portier <portierjp2@free.fr>. I don't understand the
+ file/directory naming rules for VMS so I can't tell whether this
+ is correct or not.
+
+2003-04-09 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in (HAVE_DOS_PATHS): Define this on systems that need
+ DOS-style pathnames: backslash separators and drive specifiers.
+
+2003-03-28 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (snap_deps): If .SECONDARY with no targets is given, set
+ the intermediate flag on all targets. Fixes bug #2515.
+
+2003-03-24 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in, Makefile.am, glob/Makefile.am, doc/Makefile.am:
+ Upgrade to autoconf 2.57 and automake 1.7.3.
+
+ * job.c: More OS/2 changes from Andreas Buening.
+
+ * file.c (print_file): Fix variable initialization.
+ Fixes bug #2892.
+
+ * remake.c (notice_finished_file):
+
+ * make.h (ENULLLOOP): Set errno = 0 before invoking the command;
+ some calls (like readdir()) return NULL in valid situations
+ without resetting errno. Fixes bug #2846.
+
+2003-02-25 Paul D. Smith <psmith@gnu.org>
+
+ Port to OS/2 (__EMX__) by Andreas Buening <andreas.buening@nexgo.de>.
+
+ * job.c (_is_unixy_shell) [OS/2]: New function.
+ Set default shell to /bin/sh.
+ (reap_children): Close the job_rfd pipe here since we don't use a
+ SIGCHLD handler.
+ (set_child_handler_action_flags): define this to empty on OS/2.
+ (start_job_command): Close the jobserver pipe and use
+ child_execute_job() instead of fork/exec.
+ (child_execute_job): Rewrite to handle stdin/stdout FDs and spawn
+ rather than exec'ing, then reconfigure stdin/stdout.
+ (exec_command): Rewrite to use spawn instead of exec. Return the
+ PID of the child.
+
+ * main.c (main) [OS/2]: Call initialize_main(). Handle argv[0] as
+ in DOS. Handle the TEMP environment variable as in DOS. Don't
+ use a SIGCHLD handler on OS/2. Choose a shell as in DOS. Don't
+ use -j in DOS mode. Use child_execute_job() instead of
+ exec_command().
+
+ * function.c (func_shell) [OS/2]: Can't use fork/exec on OS/2: use
+ spawn() instead.
+
+ * job.h [OS/2]: Move CLOSE_ON_EXEC here from job.c. Add
+ prototypes that return values.
+
+ * remake.c (f_mtime) [OS/2]: Handle FAT timestamp offsets for OS/2.
+
+ * read.c (readline) [OS/2]: Don't handle CRLF specially on OS/2.
+ * default.c (default_suffixes) [OS/2]: Set proper default suffixes
+ for OS/2.
+ * vpath.c (construct_vpath_list) [OS/2]: Handle OS/2 paths like
+ DOS paths.
+
+2003-02-24 Paul D. Smith <psmith@gnu.org>
+
+ * default.c [VMS]: New default rules for .cxx -> .obj compiles.
+ * job.c (child_execute_job) [VMS]: New code for handling spawn().
+ (child_execute_job) [VMS]: Handle error status properly.
+ Patches provided by Hartmut Becker <Hartmut.Becker@compaq.com>.
+
+ * function.c (func_shell): Use EINTRLOOP() while reading from the
+ subshell pipe (Fixes bug #2502).
+ * job.c (free_child): Use EINTRLOOP() while writing tokens to the
+ jobserver pipe.
+ * main.c (main): Ditto.
+
+2003-01-30 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval): eval() was not fully reentrant, because the
+ collapsed buffer was static. Change it to be an automatic
+ variable so that eval() can be invoked recursively.
+ Fixes bug # 2238.
+ (eval): Apply patch # 1022: fix memory reference error on long
+ target-specific variable lines.
+ Patch provided by Steve Brown <Steve.Brown@macquarie.com>.
+
+ * function.c (check_numeric): Combine the is_numeric() function
+ into this function, since it's only called from one place.
+ Constify this function. Have it print the incorrect string in the
+ error message. Fixes bug #2407.
+ (strip_whitespace): Constify.
+ (func_if): Constify.
+ * expand.c (expand_argument): Constify.
+
+2003-01-29 Paul D. Smith <psmith@gnu.org>
+
+ Fix bug # 2169, also reported by other people on various systems.
+
+ * make.h: Some systems, such as Solaris and PTX, do not fully
+ implement POSIX-compliant SA_RESTART functionality; important
+ system calls like stat() and readdir() can still fail with EINTR
+ even if SA_RESTART has been set on the signal handler. So,
+ introduce macros EINTRLOOP() and ENULLLOOP() which can loop on
+ EINTR for system calls which return -1 or 0 (NULL), respectively,
+ on error.
+ Also, remove the old atomic_stat()/atomic_readdir() and
+ HAVE_BROKEN_RESTART handling.
+
+ * configure.in: Remove setting of HAVE_BROKEN_RESTART.
+
+ * arscan.c (ar_member_touch): Use EINTRLOOP() to wrap fstat().
+ * remake.c (touch_file): Ditto.
+
+ * commands.c (delete_target): Use EINTRLOOP() to wrap stat().
+ * read.c (construct_include_path): Ditto.
+ * remake.c (name_mtime): Ditto.
+ * vpath.c (selective_vpath_search): Ditto.
+ * dir.c (find_directory): Ditto.
+ (local_stat): Ditto.
+ (find_directory): Use ENULLLOOP() to wrap opendir().
+ (dir_contents_file_exists_p): Use ENULLLOOP() to wrap readdir().
+
+ * misc.c: Remove HAVE_BROKEN_RESTART, atomic_stat(), and
+ atomic_readdir() handling.
+
+2003-01-22 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_call): Fix Bug #1744. If we're inside a
+ recursive invocation of $(call ...), mask any of the outer
+ invocation's arguments that aren't used by this one, so that this
+ invocation doesn't "inherit" them accidentally.
+
+2002-12-05 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (subst_expand): Valery Khamenia reported a
+ pathological performance hit when doing substitutions on very
+ large values with lots of words: turns out we were invoking
+ strlen() a ridiculous number of times. Instead of having each
+ call to sindex() call strlen() again, keep track of how much of
+ the text we've seen and pass the length to sindex().
+
+2002-11-19 Paul D. Smith <psmith@gnu.org>
+
+ * README.cvs, configure.in: Upgrade to require autoconf 2.56.
+
+
+2002-11-16 Paul D. Smith <psmith@gnu.org>
+
+ * NMakefile.template (OBJS): Add hash.c object file.
+ * SMakefile.template (srcs): Ditto.
+ * Makefile.ami (objs): Ditto.
+ * build_w32.bat: Ditto.
+
+ * Makefile.DOS.template: Remove extra dependencies.
+
+2002-10-25 Paul D. Smith <psmith@gnu.org>
+
+ * expand.c (install_variable_buffer): New function. Install a new
+ variable_buffer context and return the previous one.
+ (restore_variable_buffer): New function. Free the current
+ variable_buffer context and put a previously saved one back.
+ * variable.h: Prototypes for {install,restore}_variable_buffer.
+ * function.c (func_eval): Push a new variable_buffer context
+ before we eval, then restore the old one when we're done.
+ Fixes Bug #1517.
+
+ * read.c (install_conditionals): New function. Install a new
+ conditional context and return the previous one.
+ (restore_conditionals): New function. Free the current
+ conditional context and put a previously saved one back.
+ (eval): Use the {install,restore}_conditionals for "include"
+ handling.
+ (eval_buffer): Use {install,restore}_conditionals to preserve the
+ present conditional state before we evaluate the buffer.
+ Fixes Bug #1516.
+
+ * doc/make.texi (Quick Reference): Add references to $(eval ...)
+ and $(value ...).
+ (Recursion): Add a variable index entry for CURDIR.
+
+ * README.cvs: Update to appropriate versions.
+ * Makefile.am (nodist_loadavg_SOURCES): automake gurus point out I
+ don't need to copy loadavg.c: automake is smart enough to create
+ it for me. Still have a bug in automake related to ansi2knr tho.
+
+2002-10-14 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (notice_finished_file): Only touch targets if they have
+ at least one command (as per POSIX). Resolve Bug #1418.
+
+ * *.c: Convert to using ANSI C-style function definitions.
+ * Makefile.am: Enable the ansi2knr feature of automake.
+ * configure.in: ditto.
+
+2002-10-13 Paul D. Smith <psmith@gnu.org>
+
+ * commands.c (set_file_variables): Bug #1379: Don't use alloca()
+ for automatic variable values like $^, etc. In the case of very
+ large lists of prerequisites this causes problems. Instead reuse
+ a static buffer (resizeable) for each variable.
+
+ * read.c (eval): Fix Bug #1391: allow "export" keyword in
+ target-specific variable definitions. Check for it and set an
+ "exported" flag.
+ (record_target_var): Set the export field to v_export if the
+ "exported" flag is set.
+ * doc/make.texi (Target-specific): Document the ability to use
+ "export".
+
+ * doc/make.texi: Change the name of the section on automatic
+ variables from "Automatic" to "Automatic Variables". Added text
+ clarifying the scope of automatic variables.
+
+2002-10-04 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval): Allow SysV $$@ variables to use {} braces as well
+ as () braces.
+ (record_files): Ditto.
+
+ * expand.c (variable_expand_string): In $(A:x=y) expansion limit
+ the search for the '=' to only within the enclosing parens.
+
+2002-10-03 Paul D. Smith <psmith@gnu.org>
+
+ Version 3.80 released.
+
+ * dir.c: Change hash functions to use K&R function definition style.
+ * function.c: Ditto.
+ * read.c: Ditto.
+ * variable.c: Ditto.
+
+ Update to automake 1.7.
+
+ * Makefile.am (AUTOMAKE_OPTIONS): Update to require 1.7.
+ (pdf): Remove this target as automake now provides one.
+
+ * configure.in: Change AM_CONFIG_HEADER to AC_CONFIG_HEADERS.
+
+2002-09-30 Martin P.J. Zinser <zinser@decus.de>
+
+ * makefile.com: Updates for GNU make 3.80.
+ * makefile.vms: Ditto.
+
+2002-09-23 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (enum make_word_type): Remove w_comment.
+ (get_next_mword): Don't treat comment characters as special; where
+ this function is used we will never see a comment (it's stripped
+ before we get here) and treating comments specially means that
+ targets like "foo\#bar" aren't handled properly.
+
+2002-09-18 Paul D. Smith <psmith@gnu.org>
+
+ * doc/make.texi (Bugs): Update with some info on Savannah, etc.
+
+ * read.c (eval): Expansion of arguments to export/unexport was
+ ignoring all arguments after the first one. Change the algorithm
+ to expand the whole line once, then parse the results.
+
+2002-09-17 Paul D. Smith <psmith@gnu.org>
+
+ Fix Bug #940 (plus another bug I found while looking at this):
+
+ * read.c (record_target_var): enter_file() will add a new entry if
+ it's a double-colon target: we don't want to do that in this
+ situation. Invoke lookup_file() and only enter_file() if it does
+ not already exist. If the file we get back is a double-colon then
+ add this variable to the "root" double-colon target.
+
+ * variable.c (initialize_file_variables): If this file is a
+ double-colon target but is not the "root" target, then initialize
+ the root and make the root's variable list the parent of our
+ variable list.
+
+2002-09-13 Paul D. Smith <psmith@gnu.org>
+
+ * doc/make.texi (MAKE Variable): Add some indexing for "+".
+
+ * hash.c (round_up_2): Get rid of a warning.
+
+2002-09-12 Paul D. Smith <psmith@gnu.org>
+
+ * Makefile.am (loadavg_SOURCES, loadavg.c): Tiptoe around automake
+ so it doesn't complain about getloadavg.c.
+
+ * commands.c (set_file_variables): Make sure we always alloca() at
+ least 1 character for the value of $? (for '\0').
+
+2002-09-11 Paul D. Smith <psmith@gnu.org>
+
+ * hash.h (STRING_COMPARE, ISTRING_COMPARE, STRING_N_COMPARE): Fix
+ macro to use RESULT instead of the incorrect _RESULT_.
+
+ * make.h (HAVE_BROKEN_RESTART): Add prototypes for atomic_stat()
+ and atomic_readdir(). We need to #include dirent.h to get this to
+ work.
+ * misc.c (atomic_readdir): Fix typos.
+
+2002-09-10 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (eval): Expand variable lists given to export and
+ unexport, so that "export $(LIST_OF_VARIABLES)" (etc.) works.
+ (conditional_line): Ditto for "ifdef". Fixes bug #103.
+
+ * doc/make.texi (Variables/Recursion): Document this.
+ (Conditional Syntax): And here.
+
+2002-09-09 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Check for memmove().
+
+2002-09-07 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in (HAVE_BROKEN_RESTART): Define this on PTX systems;
+ Michael Sterrett <msterret@coat.com> reports that while it has
+ SA_RESTART, it does not work properly.
+
+ * misc.c (atomic_stat): If HAVE_BROKEN_RESTART, create a function
+ that invokes stat() and loops to do it again if it returns EINTR.
+ (atomic_readdir): Ditto, with readdir().
+
+ * make.h (stat, readdir): If HAVE_BROKEN_RESTART, alias stat()
+ and readdir() to atomic_stat() and atomic_readdir().
+
+2002-09-04 Paul D. Smith <psmith@gnu.org>
+
+ * implicit.c (pattern_search): Daniel <barkalow@reputation.com>
+ reports that GNU make sometimes doesn't recognize that targets can
+ be made, when directories can be created as prerequisites. He
+ reports that changing the order of predicates in the DEP->changed
+ flag test so that lookup_file() is always performed, solves this
+ problem.
+
+2002-08-08 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Require a newer version of gettext.
+
+ * misc.c (perror_with_name): Translate the format string (for
+ right-to-left language support).
+ (pfatal_with_name): Ditto.
+
+ * main.c: Create a static array of strings to store the usage
+ text. This is done to facilitate translations.
+ (struct command_switch): Remove argdesc and description fields.
+ (switches): Remove values for obsolete fields.
+ (print_usage): Print each element of the usage array.
+
+ * hash.c: Change function definitions to be K&R style.
+
+2002-08-02 Paul D. Smith <psmith@gnu.org>
+
+ * NEWS: Remove the mention of .TARGETS; we aren't going to publish
+ this one because it's too hard to get right. We'll look at it for
+ a future release.
+ * main.c (main): Don't create the .TARGETS variable.
+ * variable.c (handle_special_var): Don't handle .TARGETS.
+
+2002-08-01 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (switches): Add a new option, -B (--always-make). If
+ specified, make will rebuild all targets that it encounters even
+ if they don't appear to be out of date.
+ (always_make_flag): New flag.
+ * make.h: Extern always_make_flag.
+ * remake.c (update_file_1): Check always_make_flag; if it's set we
+ will always rebuild any target we can, even if none of its
+ prerequisites are newer.
+ * NEWS: Mention it.
+
+ * doc/make.texi (Shell Function): Make it clear that make
+ variables marked as "export" are not passed to instances of the
+ shell function.
+
+ Add new introspection variable .VARIABLES and .TARGETS.
+
+ * variable.c (handle_special_var): New function. If the variable
+ reference passed in is "special" (.VARIABLES or .TARGETS),
+ calculate the new value if necessary. .VARIABLES is handled here:
+ walk through the hash of defined variables and construct a value
+ which is a list of the names. .TARGETS is handled by
+ build_target_list().
+ (lookup_variable): Invoke handle_special_var().
+ * file.c (build_target_list): Walk through the hask of known files
+ and construct a list of the names of all the ones marked as
+ targets.
+ * main.c (main): Initialize them to empty (and as simple variables).
+ * doc/make.texi (Special Variables): Document them.
+ * NEWS: Mention them.
+
+ * variable.h (struct variable): Add a new flag "exportable" which
+ is true if the variable name is valid for export.
+ * variable.c (define_variable_in_set): Set "exportable" when a new
+ variable is defined.
+ (target_environment): Use the "exportable" flag instead of
+ re-checking the name here... an efficiency improvement.
+
+2002-07-31 Paul D. Smith <psmith@gnu.org>
+
+ * config.h-vms.template: Updates to build on VMS. Thanks to
+ Brian_Benning@aksteel.com for helping verify the build.
+ * makefile.com: Build the new hash.c file.
+ * hash.h: Use strcpmi(), not stricmp(), in the
+ HAVE_CASE_INSENSITIVE_FS case.
+
+2002-07-30 Paul D. Smith <psmith@gnu.org>
+
+ * hash.h (ISTRING_COMPARE, return_ISTRING_COMPARE): Add missing
+ backslashes to the HAVE_CASE_INSENSITIVE_FS case.
+ Reported by <Brian_Benning@aksteel.com>.
+
+2002-07-10 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (pop_variable_scope): Remove variable made unused by
+ new hash infrastructure.
+ * read.c (dep_hash_cmp): Rewrite this to handle ignore_mtime
+ comparisons as well as name comparisons.
+ * variable.h: Add a prototype for new hash_init_function_table().
+ * file.c (lookup_file): Remove variables made unused by new hash
+ infrastructure.
+ * dir.c (directory_contents_hash_2): Missing return of hash value.
+ (dir_contents_file_exists_p): Remove variables made unused by new
+ hash infrastructure.
+
+
+ Installed Greg McGary's integration of the hash functions from the
+ GNU id-utils package:
+
+2002-07-10 Greg McGary <greg@mcgary.org>
+
+ * scripts/functions/filter-out: Add literals to to the
+ pattern space in order to add complexity, and trigger
+ use of an internal hash table. Fix documentation strings.
+ * scripts/targets/INTERMEDIATE: Reverse order of files
+ passed to expected `rm' command.
+
+2002-07-10 Greg McGary <greg@mcgary.org>
+
+ * Makefile.am (SRCS): Add hash.c (noinst_HEADERS): Add hash.h
+ * hash.c: New file, taken from id-utils.
+ * hash.h: New file, taken from id-utils.
+
+ * make.h (HASH, HASHI): Remove macros.
+ (find_char_unquote): Change arglist in decl.
+ (hash_init_directories): New function decl.
+ * variable.h (hash.h): New #include.
+ (MAKELEVEL_NAME, MAKELEVEL_LENGTH): New constants.
+ * filedef.h (hash.h): New #include.
+ (struct file) [next]: Remove member.
+ (file_hash_enter): Remove function decl.
+ (init_hash_files): New function decl.
+
+ * ar.c (ar_name): Delay call to strlen until needed.
+ * main.c (initialize_global_hash_tables): New function.
+ (main): Call it. Use MAKELEVEL_NAME & MAKELEVEL_LENGTH.
+ * misc.c (remove_comments): Pass char constants to find_char_unquote.
+ * remake.c (notice_finished_file): Update last_mtime on `prev' chain.
+
+ * dir.c (hash.h): New #include.
+ (struct directory_contents) [next, files]: Remove members.
+ [ctime]: Add member for VMS. [dirfiles]: Add hash-table member.
+ (directory_contents_hash_1, directory_contents_hash_2,
+ directory_contents_hash_cmp): New functions.
+ (directories_contents): Change type to `struct hash_table'.
+ (struct directory) [next]: Remove member.
+ (directory_hash_1, directory_hash_2, directory_hash_cmp): New funcs.
+ (directory): Change type to `struct hash_table'.
+ (struct dirfile) [next]: Remove member.
+ [length]: Add member. [impossible]: widen type to fill alignment gap.
+ (dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp): New functions.
+ (find_directory): Use new hash table package.
+ (dir_contents_file_exists_p): Likewise.
+ (file_impossible): Likewise.
+ (file_impossible_p): Likewise.
+ (print_dir_data_base): Likewise.
+ (open_dirstream): Likewise.
+ (read_dirstream): Likewise.
+ (hash_init_directories): New function.
+
+ * file.c (hash.h): New #include.
+ (file_hash_1, file_hash_2, file_hash_cmp): New functions.
+ (files): Change type to `struct hash_table'.
+ (lookup_file): Use new hash table package.
+ (enter_file): Likewise.
+ (remove_intermediates): Likewise.
+ (snap_deps): Likewise.
+ (print_file_data_base): Likewise.
+
+ * function.c
+ (function_table_entry_hash_1, function_table_entry_hash_2,
+ function_table_entry_hash_cmp): New functions.
+ (lookup_function): Remove `table' argument.
+ Use new hash table package.
+ (struct a_word) [chain, length]: New members.
+ (a_word_hash_1, a_word_hash_2, a_word_hash_cmp): New functions.
+ (struct a_pattern): New struct.
+ (func_filter_filterout): Pass through patterns noting boundaries
+ and '%', if present. Note a_word length. Use a hash table if
+ arglists are large enough to justify cost.
+ (function_table_init): Renamed from function_table.
+ (function_table): Declare as `struct hash_table'.
+ (FUNCTION_TABLE_ENTRIES): New constant.
+ (hash_init_function_table): New function.
+
+ * read.c (hash.h): New #include.
+ (read_makefile): Pass char constants to find_char_unquote.
+ (dep_hash_1, dep_hash_2, dep_hash_cmp): New functions.
+ (uniquize_deps): Use hash table to efficiently identify duplicates.
+ (find_char_unquote): Accept two char-constant stop chars, rather
+ than a string constant, avoiding zillions of calls to strchr.
+ Tighten inner search loops to test only for desired delimiters.
+
+ * variable.c (variable_hash_1, variable_hash_2,
+ variable_hash_cmp): New functions.
+ (variable_table): Declare as `struct hash_table'.
+ (global_variable_set): Remove initialization.
+ (init_hash_global_variable_set): New function.
+ (define_variable_in_set): Use new hash table package.
+ (lookup_variable): Likewise.
+ (lookup_variable_in_set): Likewise.
+ (initialize_file_variables): Likewise.
+ (pop_variable_scope): Likewise.
+ (create_new_variable_set): Likewise.
+ (merge_variable_sets): Likewise.
+ (define_automatic_variables): Likewise.
+ (target_environment): Likewise.
+ (print_variable_set): Likewise.
+
+2002-07-10 Paul D. Smith <psmith@gnu.org>
+
+ Implement the SysV make syntax $$@, $$(@D), and $$(@F) in the
+ prerequisite list. A real SysV make will expand the entire
+ prerequisites list _twice_: we don't do that as it's a big
+ backward-compatibility problem. We only replace those specific
+ variables.
+
+ * read.c (record_files): Replace any $@, $(@D), and $(@F) variable
+ references left in the list of prerequisites. Check for .POSIX as
+ we record targets, so we can disable non-POSIX behavior while
+ reading makefiles as well as running them.
+ (eval): Check the prerequisite list to see if we have anything
+ that looks like a SysV prerequisite variable reference.
+
+2002-07-09 Paul D. Smith <psmith@gnu.org>
+
+ * doc/make.texi (Prerequisite Types): Add a new section describing
+ order-only prerequisites.
+
+ * read.c (uniquize_deps): If we have the same file as both a
+ normal and order-only prereq, get rid of the order-only prereq,
+ since the normal one supersedes it.
+
+2002-07-08 Paul D. Smith <psmith@gnu.org>
+
+ * AUTHORS: Added Greg McGary to the AUTHORS file.
+ * NEWS: Blurbed order-only prerequisites.
+ * file.c (print_file): Show order-only deps properly when printing
+ the database.
+
+ * maintMakefile: Add "update" targets for wget'ing the latest
+ versions of various external files. Taken from Makefile.maint in
+ autoconf, etc.
+
+ * dosbuild.bat: Somehow we got _double_ ^M's. Remove them.
+ Reported by Eli Zaretskii <eliz@is.elta.co.il>.
+
+2002-07-07 Paul D. Smith <psmith@gnu.org>
+
+ * po/*.po: Remove. We'll use wget to retrieve them at release
+ time.
+
+ * variable.c (do_variable_definition) [W32]: On W32 using cmd
+ rather than a shell you get an exception. Make sure we look up
+ the variable. Patch provided by Eli Zaretskii <eliz@is.elta.co.il>.
+
+ * remake.c (notice_finished_file): Fix handling of -t flag.
+ Patch provided by Henning Makholm <henning@makholm.net>.
+
+ * implicit.c (pattern_search): Some systems apparently run short
+ of stack space, and using alloca() in this function caused an
+ overrun. I modified it to use xmalloc() on the two variables
+ which seemed like they might get large. Fixes Bug #476.
+
+ * main.c (print_version): Update copyright notice to conform with
+ GNU standards.
+ (print_usage): Update help output.
+
+ * function.c (func_eval): Create a new make function, $(eval
+ ...). Expand the arguments, put them into a buffer, then invoke
+ eval_buffer() on the resulting string.
+ (func_quote): Create a new function, $(quote VARNAME). Inserts
+ the value of the variable VARNAME without expanding it any
+ further.
+
+ * read.c (struct ebuffer): Change the linebuffer structure to an
+ "eval buffer", which can be either a file or a buffer.
+ (eval_makefile): Move the code in the old read_makefile() which
+ located a makefile into here: create a struct ebuffer with that
+ information. Have it invoke the new function eval() with that
+ ebuffer.
+ (eval_buffer): Create a new function that creates a struct ebuffer
+ that holds a string buffer instead of a file. Have it invoke
+ eval() with that ebuffer.
+ (eval): New function that contains the guts of the old
+ read_makefile() function: this function parses makefiles. Obtains
+ data to parse from the provided ebuffer. Some modifications to
+ make the flow of the function cleaner and clearer. Still could
+ use some work here...
+ (do_define): Takes a struct ebuffer instead of a FILE*. Read the
+ contents of the define/endef variable from the ebuffer.
+ (readstring): Read the next line from a string-style ebuffer.
+ (readline): Read the next line from an ebuffer. If it's a string
+ ebuffer, invoke readstring(). If it's a FILE* ebuffer, read it
+ from the file.
+
+ * dep.h (eval_buffer): Prototype eval_buffer();
+
+ * variable.c (do_variable_definition): Make sure that all
+ non-target-specific variables are registered in the global set.
+ If we're invoked from an $(eval ...) we might be inside a $(call
+ ...) or other function which has pushed a variable scope; we still
+ want to define our variables from evaluated makefile code in the
+ global scope.
+
+2002-07-03 Greg McGary <greg@mcgary.org>
+
+ * dep.h (struct dep) [ignore_mtime]: New member.
+ [changed]: convert to a bitfield.
+ * implicit.c (pattern_search): Zero ignore_mtime.
+ * main.c (main, handle_non_switch_argument): Likewise.
+ * rule.c (convert_suffix_rule): Likewise.
+ * read.c (read_all_makefiles, read_makefile, multi_glob): Likewise.
+ (read_makefile): Parse '|' in prerequisite list.
+ (uniquize_deps): Consider ignore_mtime when comparing deps.
+ * remake.c (update_file_1, check_dep): Don't force remake for
+ dependencies that have d->ignore_mtime.
+ * commands.c (FILE_LIST_SEPARATOR): New constant.
+ (set_file_variables): Don't include a
+ prerequisite in $+, $^ or $? if d->ignore_mtime.
+ Define $|.
+
+2002-06-18 Paul D. Smith <psmith@gnu.org>
+
+ * make.texinfo: Updates for next revision. New date/rev/etc.
+ Recreate all Info menus. Change license on the manual to the GNU
+ Free Documentation License. A number of typos.
+ (Variables Simplify): Don't use "-" before it's defined.
+ (Automatic Prerequisites): Rewrite the target example to work
+ properly if the compile fails. Remove incorrect comments about
+ how "set -e" behaves.
+ (Text Functions): Move the "word", "wordlist", "words", and
+ "firstword" functions here, from "File Name Functions".
+ * make-stds.texi: Update from latest GNU version.
+ * fdl.texi: (created) Import the latest GNU version.
+
+2002-06-06 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (do_variable_definition): New function: extract the
+ part of try_variable_definition() that actually sets the value
+ into a separate function.
+ (try_variable_definition): Call do_variable_definition() after
+ parsing the variable definition string.
+ (define_variable_in_set): Make the name argument const.
+
+ * variable.h (enum variable_flavor): Make public.
+ (do_variable_definition): Create prototype.
+
+ * read.c (read_all_makefiles): Create a new built-in variable,
+ MAKEFILE_LIST.
+ (read_makefile): Add each makefile read in to this variable value.
+
+2002-05-18 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * Makefile.DOS.template: Tweak according to changes in the
+ distribution. Add back the dependencies of *.o files.
+
+ * configh.dos.template: Synchronize with config.h.in.
+
+2002-05-09 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (file_timestamp_now): Use K&R function declaration.
+
+ * getloadavg.c (getloadavg): Merge setlocale() fix from sh-utils
+ getloadavg.c. Autoconf thinks QNX is SVR4-like, but it isn't, so
+ #undef it. Remove predefined setup of NLIST_STRUCT. Decide
+ whether to include nlist.h based on HAVE_NLIST_H. Change obsolete
+ NLIST_NAME_UNION to new HAVE_STRUCT_NLIST_N_UN_N_NAME.
+ * configure.in (NLIST_STRUCT): Define this if we have nlist.h and
+ nlist.n_name is a pointer rather than an array.
+
+ * acinclude.m4 (make_FUNC_SETVBUF_REVERSED): Grab the latest
+ version of AC_FUNC_SETVBUF_REVERSED from autoconf CVS.
+ * configure.in: Use it instead of the old version.
+
+ * main.c (main): Prefer setvbuf() to setlinebuf().
+
+2002-05-08 Paul D. Smith <psmith@gnu.org>
+
+ * Makefile.am (make_LDADD): Add GETLOADAVG_LIBS.
+ (loadavg_LDADD): Ditto.
+
+2002-04-29 Paul D. Smith <psmith@gnu.org>
+
+ * expand.c (recursively_expand_for_file): Rename
+ recursively_expand() to recursively_expand_for_file() and provide
+ an extra argument, struct file. If the argument is provided, set
+ the variable scope to that of the file before expanding.
+ * variable.h (recursively_expand): Make this a macro that invokes
+ recursively_expand_for_file() with a NULL file pointer.
+ * variable.c (target_environment): Call the renamed function and
+ provide the current file context.
+ Fixes Debian bug #144306.
+
+2002-04-28 Paul D. Smith <psmith@gnu.org>
+
+ Allow $(call ...) user-defined variables to be self-referencing
+ without throwing an error. Allows implementation of transitive
+ closures, among other possibly useful things.
+ Requested by: Philip Guenther <guenther@sendmail.com>
+
+ * variable.h (struct variable): Add a new field: exp_count, and
+ new macros to hold its size and maximum value.
+ (warn_undefined): Make this a macro.
+ * variable.c (define_variable_in_set): Initialize it.
+ * expand.c (recursively_expand): If we detect recursive expansion
+ of a variable, check the exp_count field. If it's greater than 0
+ allow the recursion and decrement the count.
+ (warn_undefined): Remove this (now a macro in variable.h).
+ * function.c (func_call): Before we expand the user-defined
+ function, modify its exp_count field to contain the maximum
+ number of recursive calls we'll allow. After the call, reset it
+ to 0.
+
+2002-04-21 Paul D. Smith <psmith@gnu.org>
+
+ Modified to use latest autoconf (2.53), automake (1.6.1), and
+ gettext (0.11.1). We're using gettext's new "external" support,
+ to avoid including libintl source with GNU make.
+
+ * README.cvs: New file. Explain how to build GNU make from CVS.
+
+ * configure.in: Modify checking for the system glob library.
+ Use AC_EGREP_CPP instead of AC_TRY_CPP. Remove the setting of
+ GLOBDIR (we will always put "glob" in SUBDIRS, so automake
+ etc. will manage it correctly). Set an automake conditional
+ USE_LOCAL_GLOB to decide whether to compile the glob library.
+
+ * getloadavg.c (main): Include make.h in the "TEST" program to
+ avoid warnings.
+
+ * Makefile.am: Remove special rules for loadavg. Replace them
+ with Automake capabilities for building extra programs.
+
+ * signame.c: This file does nothing if the system provide
+ strsignal(). If not, it implements strsignal(). If the system
+ doesn't define sys_siglist, then we make our own; otherwise we use
+ the system version.
+ * signame.h: Removed.
+
+ * main.c (main): No need to invoke signame_init(). Update copyright.
+
+ * ABOUT-NLS: Removed.
+ * gettext.c: Removed.
+ * gettext.h: Get a simplified copy from the gettext package.
+ * po/*: Created.
+ * i18n/*.po: Moved to po/.
+ * i18n/: Removed.
+
+ * config/*: Created. Contains package configuration helper files.
+ * config.guess, config.sub: Moved to config directory.
+
+ * configure.in (AC_CONFIG_FILES): Add po/Makefile.in, config/Makefile.
+ Rework to use new-style autoconf features. Use the "external"
+ mode for gettext. Make the build.sh config file conditional on
+ whether build.sh.in exists, to avoid autoconf errors.
+ * acinclude.m4: Removed almost all macros as being obsolete.
+ Rewrote remaining macros to use AC_DEFINE.
+ * acconfig.h: Removed.
+
+ * Makefile.am (EXTRA_DIST): Add config/config.rpath. Use a
+ conditional to handle customs support. Remove special handling
+ for i18n features.
+
+2002-04-20 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_call): Don't mark the argument variables $1,
+ etc. as recursive. They've already been fully expanded so
+ there's no need to do it again, and doing so strips escaped $'s.
+ Reported by Sebastian Glita <glseba@yahoo.com>.
+
+ * remake.c (notice_finished_file): Walk through double-colon
+ entries via the prev field, not the next field!
+ Reported by Greg McGary <greg@mcgary.org>.
+
+ * main.c (main): If the user specifies -q and asks for a specific
+ target which is a makefile, we got an assert. In that case it
+ turns out we should continue normally instead.
+
+ * i18n/de.po, i18n/fr.po: Installed an updated translation.
+
+ * i18n/he.po: Installed a new translation.
+
+2002-01-07 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/es.po, i18n/ru.po: Installed an updated translation.
+
+2001-12-04 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/ja.po: Installed an updated translation.
+
+2001-09-06 Paul Eggert <eggert@twinsun.com>
+
+ * configure.in (AC_CHECK_HEADERS): Add sys/resource.h.
+ (AC_CHECK_FUNCS): Add getrlimit, setrlimit.
+
+ * main.c: Include <sys/resource.h> if it, getrlimit, and setrlimit
+ are available.
+ (main): Get rid of any avoidable limit on stack size.
+
+2001-09-04 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/da.po: Installed an updated translation.
+
+2001-08-03 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/fr.po: Installed an updated translation.
+ Resolves Debian bug #106720.
+
+2001-06-13 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/da.po, configure.in (ALL_LINGUAS): Installed a new
+ translation.
+
+2001-06-11 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/ko.po: Installed a new translation.
+
+2001-05-06 Paul D. Smith <psmith@gnu.org>
+
+ Modify the EINTR handling.
+
+ * job.c (new_job): Reorganize the jobserver algorithm. Reorder
+ the way in which we manage the file descriptor/signal handler race
+ trap to be more efficient.
+
+2001-05-06 Paul Eggert <eggert@twinsun.com>
+
+ Restart almost all system calls that are interrupted, instead
+ of worrying about EINTR. The lone exception is the read() for
+ job tokens.
+
+ * configure.in (HAVE_SA_RESTART): New macro.
+ (MAKE_JOBSERVER): Define to 1 only if HAVE_SA_RESTART.
+ * main.c (main): Use SA_RESTART instead of the old,
+ nonstandard SA_INTERRUPT.
+
+ * configure.in (AC_CHECK_FUNCS): Add bsd_signal.
+ * main.c (bsd_signal): New function or macro,
+ if the implementation doesn't supply it.
+ (The bsd_signal function will be in POSIX 1003.1-200x.)
+ (HANDLESIG): Remove.
+ (main, FATAL_SIG): Use bsd_signal instead of signal or HANDLESIG.
+
+ * make.h (EINTR_SET): Remove.
+ (SA_RESTART): New macro.
+
+ * arscan.c (ar_member_touch): Don't worry about EINTR.
+ * function.c (func_shell): Likewise.
+ * job.c (reap_children, free_child, new_job): Likewise.
+ * main.c (main): Likewise.
+ * remake.c (touch_file, name_mtime): Likewise.
+
+ * arscan.c (ar_member_touch): Fix bug uncovered by EINTR removal;
+ if fstat failed with errno!=EINTR, the error was ignored.
+
+ * job.c (set_child_handler_action_flags): New function.
+ (new_job): Use it to temporarily clear the SIGCHLD action flags
+ while reading the token.
+
+2001-05-02 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (start_job_command): Don't add define/endef per-line flags
+ to the top-level flags setting.
+
+2001-04-03 Paul D. Smith <psmith@gnu.org>
+
+ * arscan.c (VMS_get_member_info,ar_scan) [VMS]: VMS sets the low
+ bit on error, so check for odd return values, not non-0 return
+ values.
+ (VMS_get_member_info): Calculate the timezone differences correctly.
+ Reported by John Fowler <jfowler@nyx.net>.
+
+
+2001-03-14 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (lookup_variable) [VMS]: Null-terminate the variable
+ value before invoking define_variable().
+ Reported by John Fowler <jfowler@nyx.net>.
+
+2001-02-07 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (record_target_var): If we reset the variable due to a
+ command-line variable setting overriding it, turn off the "append"
+ flag.
+
+2001-01-17 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (lookup_variable) [VMS]: When getting values from the
+ environment, allocate enough space for the _value_ plus escapes,
+ not enough space for the name plus escapes :-/.
+ Reported by John Fowler <jfowler@nyx.net>.
+
+ * remake.c (f_mtime): Removed the "***" prefix from the mod time
+ warnings that make generates, so it doesn't look like an error.
+ Reported by Karl Berry <karl@gnu.org>.
+
+
+ Fix for PR/2020: Rework appended target-specific variables. I'm
+ fairly confident this algorithm is finally correct.
+
+ * expand.c (allocated_variable_append): Rewrite. Instead of
+ expanding each appended variable then adding all the expanded
+ strings together, we append all the unexpanded values going up
+ through the variable set contexts, then expand the final result.
+ This behaves just like non-target-specific appended variable
+ values, while the old way didn't in various corner cases.
+ (variable_append): New function: recursively append the unexpanded
+ value of a variable, walking from the outermost variable scope to
+ the innermost.
+ * variable.c (lookup_variable): Remove the code that looked up the
+ variable set list if the found variable was "append". We don't
+ need this anymore.
+ (lookup_variable_in_set): Make this non-static so we can use it
+ elsewhere.
+ (try_variable_definition): Use lookup_variable_in_set() rather
+ than faking out current_variable_set_list by hand (cleanup).
+ * variable.h: Add a prototype for the now non-static
+ lookup_variable_in_set().
+
+2000-11-17 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime) [WINDOWS32]: On various advice, I changed the
+ WINDOWS32 port to assume timestamps can be up to 3 seconds away
+ before throwing a fit.
+
+2000-11-17 Paul D. Smith <psmith@gnu.org>
+
+ * read.c (readline): CRLF calculations had a hole, if you hit the
+ buffer grow scenario just right. Reworked the algorithm to avoid
+ the need for len or lastlen at all. Problem description with
+ sample code chages provided by Chris Faylor <cgf@redhat.com>.
+
+2000-10-24 Paul D. Smith <psmith@gnu.org>
+
+ * gettext.c (SWAP): Declare this with the prototype, otherwise
+ some systems don't work (non-32-bit? Reported for Cray T3E).
+ Reported by Thorstein Thorsteinsson <thor@signe.teokem.lu.se>.
+
+2000-10-05 Paul D. Smith <psmith@gnu.org>
+
+ * acinclude.m4 (AM_LC_MESSAGES): Remove undefined macro
+ AM_LC_MESSAGES; it doesn't seem to do anything anyway??
+
+ * i18n/gl.po, configure.in (ALL_LINGUAS): New Galician translation.
+
+2000-09-22 Paul D. Smith <psmith@gnu.org>
+
+ * gettext.c: Don't #define _GETTEXT_H here; we only include some
+ parts of the real gettext.h here, and we expect to really include
+ the real gettext.h later. If we keep this #define, it's ignored.
+
+2000-09-21 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (log_working_directory): Rework the text to use complete
+ sentences, to make life simpler for the translators.
+
+2000-08-29 Paul D. Smith <psmith@gnu.org>
+
+ * file.c (remove_intermediates): Print a debug message before we
+ remove intermediate files, so the user (if she uses -d) knows
+ what's going on.
+
+2000-08-21 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (try_variable_definition): Change how we handle
+ target-specific append variable defns: instead of just setting the
+ value, expand it as an append _but_ only within the current
+ target's context. Otherwise we lose all but the last value if the
+ variable is appended more than once within the current target
+ context. Fixes PR/1831.
+
+2000-08-16 Paul D. Smith <psmith@gnu.org>
+
+ * function.c (func_shell): Nul-terminate the buffer before
+ printing an exec error message (just in case it's not!).
+ Fixes PR/1860, reported by Joey Hess <joey@valinux.com>.
+
+2000-07-25 Paul D. Smith <psmith@gnu.org>
+
+ * job.c (construct_command_argv_internal): Add "~" to the list of
+ sh_chars[] which disallow optimizing out the shell call.
+
+2000-07-23 Paul Eggert <eggert@twinsun.com>
+
+ * NEWS, make.texinfo: Document .LOW_RESOLUTION_TIME, which
+ supersedes --disable-nsec-timestamps.
+ * make.texinfo: Consistently use "time stamp" instead of "timestamp".
+ * README: Remove --disable-nsec-timestamps.
+
+ * filedef.h (struct file.low_resolution_time): New member.
+ * file.c (snap_deps): Add support for .LOW_RESOLUTION_TIME.
+ * remake.c (update_file_1):
+ Avoid spurious rebuilds due to low resolution time stamps,
+ generalizing the earlier code that applied only to archive members.
+ (f_mtime): Archive members always have low resolution time stamps.
+
+ * configure.in: Remove --disable-nsec-timestamps, as this has
+ been superseded by .LOW_RESOLUTION_TIME.
+
+2000-07-23 Paul Eggert <eggert@twinsun.com>
+
+ * configure.in (enable_nsec_timestamps): Renamed from
+ make_cv_nsec_timestamps, since enable/disable options
+ shouldn't be cached.
+
+2000-07-23 Bruno Haible <haible@clisp.cons.org>
+ and Paul Eggert <eggert@twinsun.com>
+
+ * file.c (file_timestamp_now):
+ Use preprocessor-time check for FILE_TIMESTAMP_HI_RES
+ so that clock_gettime is not linked unless needed.
+
+ * filedef.h (FILE_TIMESTAMP_HI_RES):
+ Remove definition; "configure" now does this.
+
+ * configure.in (jm_AC_TYPE_UINTMAX_T): Move up,
+ to before high resolution file timestamp check,
+ since that check now uses uintmax_t.
+ (FILE_TIMESTAMP_HI_RES): Define to nonzero if the code should use
+ high resolution file timestamps.
+ (HAVE_CLOCK_GETTIME): Do not define if !FILE_TIMESTAMP_HI_RES,
+ so that we don't link in clock_gettime unnecessarily.
+
+2000-07-17 Paul D. Smith <psmith@gnu.org>
+
+ * i18n/ja.po: New version of the translation file.
+
+2000-07-07 Paul D. Smith <psmith@gnu.org>
+
+ * remake.c (f_mtime): If NO_FLOAT is defined, don't bother with
+ the offset calculation.
+ (name_mtime): Replace EINTR test with EINTR_SET macro.
+
+2000-07-07 Paul Eggert <eggert@twinsun.com>
+
+ Fix for PR/1811:
+
+ * remake.c (update_file_1):
+ Avoid spurious rebuilds of archive members due to their
+ timestamp resolution being only one second.
+ (f_mtime): Avoid spurious warnings of timestamps in the future due to
+ the clock's resolution being lower than file timestamps'.
+ When warning about future timestamps, report only the discrepancy,
+ not the absolute value of the timestamp and the current time.
+
+ * file.c (file_timestamp_now): New arg RESOLUTION.
+ * filedef.h (file_timestamp_now): Likewise.
+ (FILE_TIMESTAMP_NS): Now returns int. All uses changed.
+
+2000-07-05 Paul D. Smith <psmith@gnu.org>
+
+ * variable.c (lookup_variable) [VMS]: Remove vestigial references
+ to listp. Fixes PR/1793.
+
+2000-06-26 Paul Eggert <eggert@twinsun.com>
+
+ * Makefile.am (MAINTAINERCLEANFILES): New macro, with stamp-pot in it.
+
+ * dir.c (vms_hash): Ensure ctype macro args are nonnegative.
+
+ * remake.c (f_mtime): Remove unused var memtime.
+
+2000-06-25 Martin Buchholz <martin@xemacs.org>
+
+ * make.texinfo, NEWS, TODO.private: Minor spelling corrections.
+ Ran spell-check on make.texinfo.
+
+2000-06-23 Paul D. Smith <psmith@gnu.org>
+
+ * main.c (main): Replace EXIT_SUCCESS, EXIT_FAILURE, and
+ EXIT_TROUBLE with MAKE_SUCCESS, MAKE_FAILURE, and MAKE_TROUBLE.
+ * make.h: Define these macros.
+
+ * Version 3.79.1 released.
+
+ * configure.in: Add a new option, --disable-nsec-timestamps, to
+ avoid using sub-second timestamps on systems that support it. It
+ can lead to problems, e.g. if your makefile relies on "cp -p".
+ * README.template: Document the issue with "cp -p".
+
+ * config.guess, config.sub: Updated.
+
+
+
+See ChangeLog.2, available in the Git repository at:
+
+ http://git.savannah.gnu.org/cgit/make.git/tree/
+
+for earlier changes.
+
+
+Copyright (C) 2000-2013 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/INSTALL b/src/kmk/INSTALL
new file mode 100644
index 0000000..095b1eb
--- /dev/null
+++ b/src/kmk/INSTALL
@@ -0,0 +1,231 @@
+Installation Instructions
+*************************
+
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004 Free
+Software Foundation, Inc.
+
+This file is free documentation; the Free Software Foundation gives
+unlimited permission to copy, distribute and modify it.
+
+Basic Installation
+==================
+
+These are generic installation instructions.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation. It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions. Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, and a
+file `config.log' containing compiler output (useful mainly for
+debugging `configure').
+
+ It can also use an optional file (typically called `config.cache'
+and enabled with `--cache-file=config.cache' or simply `-C') that saves
+the results of its tests to speed up reconfiguring. (Caching is
+disabled by default to prevent problems with accidental use of stale
+cache files.)
+
+ If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release. If you are using the cache, and at
+some point `config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+ The file `configure.ac' (or `configure.in') is used to create
+`configure' by a program called `autoconf'. You only need
+`configure.ac' if you want to change it or regenerate `configure' using
+a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+ 1. `cd' to the directory containing the package's source code and type
+ `./configure' to configure the package for your system. If you're
+ using `csh' on an old version of System V, you might need to type
+ `sh ./configure' instead to prevent `csh' from trying to execute
+ `configure' itself.
+
+ Running `configure' takes awhile. While running, it prints some
+ messages telling which features it is checking for.
+
+ 2. Type `make' to compile the package.
+
+ 3. Optionally, type `make check' to run any self-tests that come with
+ the package.
+
+ 4. Type `make install' to install the programs and any data files and
+ documentation.
+
+ 5. You can remove the program binaries and object files from the
+ source code directory by typing `make clean'. To also remove the
+ files that `configure' created (so you can compile the package for
+ a different kind of computer), type `make distclean'. There is
+ also a `make maintainer-clean' target, but that is intended mainly
+ for the package's developers. If you use it, you may have to get
+ all sorts of other programs in order to regenerate files that came
+ with the distribution.
+
+Compilers and Options
+=====================
+
+Some systems require unusual options for compilation or linking that the
+`configure' script does not know about. Run `./configure --help' for
+details on some of the pertinent environment variables.
+
+ You can give `configure' initial values for configuration parameters
+by setting variables in the command line or in the environment. Here
+is an example:
+
+ ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
+
+ *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory. To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+ If you have to use a `make' that does not support the `VPATH'
+variable, you have to compile the package for one architecture at a
+time in the source code directory. After you have installed the
+package for one architecture, use `make distclean' before reconfiguring
+for another architecture.
+
+Installation Names
+==================
+
+By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PREFIX'.
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PREFIX', the package will
+use PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+ In addition, if you use an unusual directory layout you can give
+options like `--bindir=DIR' to specify different values for particular
+kinds of files. Run `configure --help' for a list of the directories
+you can set and what kinds of files go in them.
+
+ If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System). The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+ For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+There may be some features `configure' cannot figure out automatically,
+but needs to determine by the type of machine the package will run on.
+Usually, assuming the package is built to be run on the _same_
+architectures, `configure' can figure that out, but if it prints a
+message saying it cannot guess the machine type, give it the
+`--build=TYPE' option. TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name which has the form:
+
+ CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+ OS KERNEL-OS
+
+ See the file `config.sub' for the possible values of each field. If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+ If you are _building_ compiler tools for cross-compiling, you should
+use the `--target=TYPE' option to select the type of system they will
+produce code for.
+
+ If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with `--host=TYPE'.
+
+Sharing Defaults
+================
+
+If you want to set default values for `configure' scripts to share, you
+can create a site shell script called `config.site' that gives default
+values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists. Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+Variables not defined in a site shell script can be set in the
+environment passed to `configure'. However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost. In order to avoid this problem, you should set
+them in the `configure' command line, using `VAR=value'. For example:
+
+ ./configure CC=/usr/local2/bin/gcc
+
+will cause the specified gcc to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+`configure' Invocation
+======================
+
+`configure' recognizes the following options to control how it operates.
+
+`--help'
+`-h'
+ Print a summary of the options to `configure', and exit.
+
+`--version'
+`-V'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--cache-file=FILE'
+ Enable the cache: use and save the results of the tests in FILE,
+ traditionally `config.cache'. FILE defaults to `/dev/null' to
+ disable caching.
+
+`--config-cache'
+`-C'
+ Alias for `--cache-file=config.cache'.
+
+`--quiet'
+`--silent'
+`-q'
+ Do not print messages saying which checks are being made. To
+ suppress all normal output, redirect it to `/dev/null' (any error
+ messages will still be shown).
+
+`--srcdir=DIR'
+ Look for the package's source code in directory DIR. Usually
+ `configure' can determine that directory automatically.
+
+`configure' also accepts some other, not widely useful, options. Run
+`configure --help' for more details.
+
diff --git a/src/kmk/Makefile.DOS.template b/src/kmk/Makefile.DOS.template
new file mode 100644
index 0000000..47d83eb
--- /dev/null
+++ b/src/kmk/Makefile.DOS.template
@@ -0,0 +1,587 @@
+# -*-Makefile-*- template for DJGPP
+# Makefile.in generated automatically by automake 1.2 from Makefile.am
+#
+# Copyright (C) 1994-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+SHELL = /bin/sh
+
+srcdir = .
+VPATH = $(srcdir)
+# $DJDIR is defined automatically by DJGPP to point
+# to the root of the DJGPP installation tree.
+prefix = /dev/env/DJDIR
+exec_prefix = ${prefix}
+
+bindir = /bin
+datadir = /share
+libdir = /lib
+infodir = /info
+mandir = /man
+includedir = /include
+oldincludedir = c:/djgpp/include
+
+DESTDIR = /dev/env/DJDIR
+
+pkgdatadir = $(datadir)/make
+pkglibdir = $(libdir)/make
+pkgincludedir = $(includedir)/make
+localedir = $(datadir)/locale
+
+INSTALL = ${exec_prefix}/bin/ginstall -c
+INSTALL_PROGRAM = ${exec_prefix}/bin/ginstall -c
+INSTALL_DATA = ${exec_prefix}/bin/ginstall -c -m 644
+INSTALL_SCRIPT = ${exec_prefix}/bin/ginstall -c
+transform = s,x,x,
+
+# This will fail even if they don't have a Unix-like shell (stock DOS
+# shell doesn't know about `false'). The only difference is that they
+# get "Error -1" instead of "Error 1".
+EXIT_FAIL = false
+
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+EXEEXT = .exe
+OBJEXT = o
+
+AR = ar
+AWK = gawk
+CC = gcc
+CPP = gcc -E
+LIBOBJS =
+MAKEINFO = ${exec_prefix}/bin/makeinfo
+PACKAGE = make
+PERL = perl
+RANLIB = ranlib
+REMOTE = stub
+VERSION = %VERSION%
+
+AUTOMAKE_OPTIONS = 1.2
+
+bin_PROGRAMS = %PROGRAMS%$(EXEEXT)
+
+make_SOURCES = %SOURCES%
+# This should include the glob/ prefix
+libglob_a_SOURCES = %GLOB_SOURCES%
+make_LDADD = glob/libglob.a
+
+man_MANS = make.1
+
+INCLUDES = -I$(srcdir)/glob -DLIBDIR=\"$(prefix)$(libdir)\" -DINCLUDEDIR=\"$(prefix)$(includedir)\" -DLOCALEDIR=\"$(prefix)$(localedir)\"
+
+BUILT_SOURCES = README build.sh-in
+
+EXTRA_DIST = $(BUILT_SOURCES) $(man_MANS) README.customs remote-cstms.c make-stds.texi texinfo.tex SCOPTIONS SMakefile Makefile.ami README.Amiga config.ami amiga.c amiga.h NMakefile README.DOS configh.dos configure.bat makefile.com README.W32 build_w32.bat config.h-W32 subproc.bat make.lnk config.h-vms makefile.vms README.VMS vmsdir.h vmsfunctions.c vmsify.c gmk-default.scm gmk-default.h
+
+SUBDIRS = glob doc
+mkinstalldirs = ${exec_prefix}/bin/gmkdir -p
+CONFIG_HEADER = config.h
+CONFIG_CLEAN_FILES = build.sh
+PROGRAMS = $(bin_PROGRAMS)
+
+MAKE_HOST = i386-pc-msdosdjgpp
+
+
+DEFS = -I. -I$(srcdir) -I.
+CPPFLAGS = -DHAVE_CONFIG_H
+LDFLAGS =
+LIBS =
+make_OBJECTS = %OBJECTS%
+make_DEPENDENCIES = glob/libglob.a
+make_LDFLAGS =
+libglob_a_LIBADD =
+libglob_a_OBJECTS = %GLOB_OBJECTS%
+noinst_LIBRARIES = glob/libglob.a
+CFLAGS = -O2 -g
+COMPILE = $(CC) $(DEFS) $(INCLUDES) $(CPPFLAGS) $(CFLAGS)
+LINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@
+TEXI2DVI = texi2dvi
+TEXINFO_TEX = $(srcdir)/config/texinfo.tex
+INFO_DEPS = doc/make.info
+DVIS = doc/make.dvi
+TEXINFOS = doc/make.texi
+noinst_TEXINFOS = doc/fdl.texi doc/make-stds.texi
+man1dir = $(mandir)/man1
+MANS = $(man_MANS)
+
+NROFF = nroff
+DIST_COMMON = README ABOUT-NLS AUTHORS COPYING ChangeLog INSTALL Makefile.am Makefile.in NEWS acconfig.h aclocal.m4 alloca.c build.sh-in config.h-in configure configure.ac getloadavg.c
+
+DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
+
+TAR = gtar
+GZIP = --best
+SOURCES = $(make_SOURCES)
+OBJECTS = $(make_OBJECTS)
+HEADERS = $(wildcard $(srcdir)/*.h)
+
+default: all
+
+.SUFFIXES:
+.SUFFIXES: .c .dvi .info .o .obj .ps .texi .tex .html
+
+mostlyclean-hdr:
+
+clean-hdr:
+
+distclean-hdr:
+ -rm -f config.h
+
+maintainer-clean-hdr:
+
+mostlyclean-binPROGRAMS:
+
+clean-binPROGRAMS:
+ -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS)
+
+distclean-binPROGRAMS:
+
+maintainer-clean-binPROGRAMS:
+
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(bindir)
+ @list='$(bin_PROGRAMS)'; for p in $$list; do if test -f $$p; then echo " $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p | sed '$(transform)'`"; $(INSTALL_PROGRAM) $$p $(DESTDIR)$(bindir)/`echo $$p | sed '$(transform)'`; else :; fi; done
+
+uninstall-binPROGRAMS:
+ $(NORMAL_UNINSTALL)
+ list='$(bin_PROGRAMS)'; for p in $$list; do rm -f $(DESTDIR)$(bindir)/`echo $$p|sed '$(transform)'`.exe; done
+
+.c.o:
+ $(COMPILE) -c $<
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT) *$(EXEEXT) make.new core
+
+clean-compile:
+
+distclean-compile:
+ -rm -f *.tab.c *_tab.c
+
+maintainer-clean-compile:
+
+make$(EXEEXT): $(make_OBJECTS) $(make_DEPENDENCIES)
+ @command.com /c if exist make del make
+ @command.com /c if exist make.exe del make.exe
+ $(LINK) $(make_LDFLAGS) $(make_OBJECTS) $(make_LDADD) $(LIBS)
+
+# Documentation
+
+make.info: make.texi
+make.dvi: make.texi
+make.ps: make.dvi make.texi
+make.html: make.texi
+
+
+DVIPS = dvips
+
+.texi.info:
+ @command.com /c if exist make.info* del make.info*
+ @command.com /c if exist make.i* del make.i*
+ $(MAKEINFO) -I$(srcdir) --no-split $< -o ./$@
+
+.texi:
+ @command.com /c if exist make.info* del make.info*
+ @command.com /c if exist make.i* del make.i*
+ $(MAKEINFO) -I$(srcdir) --no-split $< -o ./$@
+
+.texi.dvi:
+ TEXINPUTS="$(srcdir);$$TEXINPUTS" MAKEINFO='$(MAKEINFO) -I $(srcdir)' $(TEXI2DVI) $<
+
+.dvi.ps:
+ $(DVIPS) $< -o $@
+
+# Other documentation formats
+
+html: html-recursive
+
+.texi.html:
+ @command.com /c if exist make.html* del make.html*
+ $(MAKEINFO) --html -I$(srcdir) --no-split $< -o ./$@
+
+install-info-am: $(INFO_DEPS)
+ @$(NORMAL_INSTALL)
+ $(mkinstalldirs) $(DESTDIR)$(infodir)
+ @for file in $(INFO_DEPS); do iifile=`echo $$file | sed "s|doc/||"`; d=$(srcdir); for ifile in `cd $$d && echo $$file`; do if test -f $$d/$$ifile; then echo " $(INSTALL_DATA) $$d/$$ifile $(DESTDIR)$(infodir)/$$iifile"; $(INSTALL_DATA) $$d/$$ifile $(DESTDIR)$(infodir)/$$iifile; else : ; fi; done; done
+ @$(POST_INSTALL)
+ @if $(SHELL) -c 'install-info --version | sed 1q | fgrep -s -v -i debian' >/dev/null 2>&1; then for file in $(INFO_DEPS); do iifile=`echo $$file | sed "s|doc/||"`; echo " install-info --info-dir=$(DESTDIR)$(infodir) $(DESTDIR)$(infodir)/$$iifile"; install-info --info-dir=$(DESTDIR)$(infodir) $(DESTDIR)$(infodir)/$$iifile || :; done; else : ; fi
+
+uninstall-info:
+ $(PRE_UNINSTALL)
+ @if $(SHELL) -c 'install-info --version | sed 1q | fgrep -s -v -i debian' >/dev/null 2>&1; then ii=yes; else ii=; fi; for file in $(INFO_DEPS); do test -z $ii || install-info --info-dir=$(DESTDIR)$(infodir) --remove $$file; done
+ $(NORMAL_UNINSTALL)
+ for file in $(INFO_DEPS); do (cd $(DESTDIR)$(infodir) && rm -f $$file); done
+
+dist-info: $(INFO_DEPS)
+ for base in $(INFO_DEPS); do d=$(srcdir); for file in `cd $$d && eval echo $$base*`; do test -f $(distdir)/$$file || ln $$d/$$file $(distdir)/$$file 2> /dev/null || cp -p $$d/$$file $(distdir)/$$file; done; done
+
+mostlyclean-aminfo:
+ -rm -f $(srcdir)/doc/make.aux $(srcdir)/doc/make.cp $(srcdir)/doc/make.cps $(srcdir)/doc/make.dvi \
+ $(srcdir)/doc/make.fn $(srcdir)/doc/make.fns $(srcdir)/doc/make.ky $(srcdir)/doc/make.kys \
+ $(srcdir)/doc/make.ps $(srcdir)/doc/make.log $(srcdir)/doc/make.pg $(srcdir)/doc/make.toc \
+ $(srcdir)/doc/make.tp $(srcdir)/doc/make.tps $(srcdir)/doc/make.vr $(srcdir)/doc/make.vrs \
+ $(srcdir)/doc/make.op $(srcdir)/doc/make.tr $(srcdir)/doc/make.cv $(srcdir)/doc/make.cn \
+ $(srcdir)/doc/make.html
+
+clean-aminfo:
+
+distclean-aminfo:
+
+maintainer-clean-aminfo:
+ for i in $(INFO_DEPS); do rm -f $$i*; done
+
+install-man1:
+ $(mkinstalldirs) $(DESTDIR)$(man1dir)
+ @list='$(man1_MANS)'; \
+ l2='$(man_MANS)'; for i in $$l2; do \
+ case "$$i" in \
+ *.1*) list="$$list $$i" ;; \
+ esac; \
+ done; \
+ for i in $$list; do \
+ if test -f $(srcdir)/$$i; then file=$(srcdir)/$$i; \
+ else file=$$i; fi; \
+ ext=`echo $$i | sed -e 's/^.*\\.//'`; \
+ inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \
+ inst=`echo $$inst | sed '$(transform)'`.$$ext; \
+ echo " $(INSTALL_DATA) $$file $(DESTDIR)$(man1dir)/$$inst"; \
+ $(INSTALL_DATA) $$file $(DESTDIR)$(man1dir)/$$inst; \
+ done
+
+uninstall-man1:
+ @list='$(man1_MANS)'; \
+ l2='$(man_MANS)'; for i in $$l2; do \
+ case "$$i" in \
+ *.1*) list="$$list $$i" ;; \
+ esac; \
+ done; \
+ for i in $$list; do \
+ ext=`echo $$i | sed -e 's/^.*\\.//'`; \
+ inst=`echo $$i | sed -e 's/\\.[0-9a-z]*$$//'`; \
+ inst=`echo $$inst | sed '$(transform)'`.$$ext; \
+ echo " rm -f $(DESTDIR)$(man1dir)/$$inst"; \
+ rm -f $(DESTDIR)$(man1dir)/$$inst; \
+ done
+install-man: $(MANS)
+ @$(NORMAL_INSTALL)
+ $(MAKE) install-man1
+uninstall-man:
+ @$(NORMAL_UNINSTALL)
+ $(MAKE) uninstall-man1
+
+# Assume that the only thing to do in glob is to build libglob.a,
+# but do a sanity check: if $SUBDIRS will ever have more than
+# a single directory, yell bloody murder.
+all-recursive:
+ifeq ($(findstring glob, $(SUBDIRS)), glob)
+ @command.com /c if not exist glob\\nul md glob
+ @echo Making all in glob
+ $(MAKE) -C glob -f ../Makefile INCLUDES='-I$(srcdir) -I$(srcdir)/glob' DEFS='-I.. -I$(srcdir)' VPATH=$(srcdir)/glob libglob.a
+endif
+
+$(SUBDIRS):
+ command.com /c md $@
+
+libglob.a: $(libglob_a_OBJECTS)
+ command.com /c if exist libglob.a del libglob.a
+ $(AR) cru libglob.a $(libglob_a_OBJECTS) $(libglob_a_LIBADD)
+ $(RANLIB) libglob.a
+
+mostlyclean-recursive clean-recursive distclean-recursive \
+maintainer-clean-recursive check-recursive:
+ifeq ($(words $(SUBDIRS)), 2)
+ @echo Making $(shell echo $@ | sed s/-recursive//) in glob
+ $(MAKE) -C glob -f ../Makefile $(shell echo $@ | sed s/-recursive//)-am
+ @echo Making $(shell echo $@ | sed s/-recursive//) in doc
+ $(MAKE) -C doc -f ../Makefile $(shell echo $@ | sed s/-recursive//)-am
+else
+ @echo FATAL: There is more than two directory in "($(SUBDIRS))"
+ @$(EXIT_FAIL)
+endif
+
+tags-in-glob: $(libglob_a_SOURCES)
+ etags $(addprefix $(srcdir)/,$^) -o ./glob/TAGS
+
+tags-recursive:
+ifeq ($(words $(SUBDIRS)), 2)
+ $(MAKE) tags-in-glob
+else
+ @echo FATAL: There is more than two directory in "($(SUBDIRS))"
+ @$(EXIT_FAIL)
+endif
+
+tags: TAGS
+
+ID: $(HEADERS) $(SOURCES)
+ mkid $(srcdir)/$(SOURCES) $(srcdir)/$(libglob_a_SOURCES) ./config.h $(HEADERS)
+
+TAGS: tags-recursive $(HEADERS) $(srcdir)/$(SOURCES) config.h $(TAGS_DEPENDENCIES)
+ etags -i ./glob/TAGS $(ETAGS_ARGS) $(srcdir)/$(SOURCES) ./config.h $(HEADERS)
+
+mostlyclean-tags:
+
+clean-tags:
+
+distclean-tags:
+ -rm -f TAGS ID
+
+maintainer-clean-tags:
+
+distdir = $(PACKAGE)-$(VERSION)
+top_distdir = $(distdir)
+
+# This target untars the dist file and tries a VPATH configuration. Then
+# it guarantees that the distribution is self-contained by making another
+# tarfile.
+distcheck: dist
+ rm -rf $(distdir)
+ GZIP=$(GZIP) $(TAR) zxf $(distdir).tar.gz
+ mkdir $(distdir)/=build
+ mkdir $(distdir)/=inst
+ dc_install_base=`cd $(distdir)/=inst && pwd`; cd $(distdir)/=build && ../configure --srcdir=.. --prefix=$$dc_install_base && $(MAKE) && $(MAKE) dvi && $(MAKE) check && $(MAKE) install && $(MAKE) installcheck && $(MAKE) dist
+ rm -rf $(distdir)
+ @echo "========================"; echo "$(distdir).tar.gz is ready for distribution"; echo "========================"
+dist: distdir
+ -chmod -R a+r $(distdir)
+ GZIP=$(GZIP) $(TAR) chozf $(distdir).tar.gz $(distdir)
+ rm -rf $(distdir)
+dist-all: distdir
+ -chmod -R a+r $(distdir)
+ GZIP=$(GZIP) $(TAR) chozf $(distdir).tar.gz $(distdir)
+ rm -rf $(distdir)
+distdir: $(DISTFILES)
+ rm -rf $(distdir)
+ mkdir $(distdir)
+ -chmod 777 $(distdir)
+ @for file in $(DISTFILES); do d=$(srcdir); test -f $(distdir)/$$file || ln $$d/$$file $(distdir)/$$file 2> /dev/null || cp -p $$d/$$file $(distdir)/$$file; done; for subdir in $(SUBDIRS); do test -d $(distdir)/$$subdir || mkdir $(distdir)/$$subdir || exit 1; chmod 777 $(distdir)/$$subdir; (cd $$subdir && $(MAKE) top_distdir=../$(top_distdir)/$$subdir distdir=../$(distdir)/$$subdir distdir) || exit 1; done
+ $(MAKE) top_distdir="$(top_distdir)" distdir="$(distdir)" dist-info
+ $(MAKE) top_distdir="$(top_distdir)" distdir="$(distdir)" dist-hook
+
+info: info-recursive
+info-recursive:
+ifeq ($(findstring doc, $(SUBDIRS)), doc)
+ @command.com /c if not exist doc\\nul md doc
+ @echo Making all in doc
+ $(MAKE) -C doc -f ../Makefile VPATH=$(srcdir)/doc make.info
+endif
+
+dvi: dvi-recursive
+dvi-recursive:
+ifeq ($(findstring doc, $(SUBDIRS)), doc)
+ @command.com /c if not exist doc\\nul md doc
+ @echo Making all in doc
+ $(MAKE) -C doc -f ../Makefile VPATH=$(srcdir)/doc make.dvi
+endif
+
+ps: ps-recursive
+ps-recursive:
+ifeq ($(findstring doc, $(SUBDIRS)), doc)
+ @command.com /c if not exist doc\\nul md doc
+ @echo Making all in doc
+ $(MAKE) -C doc -f ../Makefile VPATH=$(srcdir)/doc make.ps
+endif
+
+html-recursive:
+ifeq ($(findstring doc, $(SUBDIRS)), doc)
+ @command.com /c if not exist doc\\nul md doc
+ @echo Making all in doc
+ $(MAKE) -C doc -f ../Makefile VPATH=$(srcdir)/doc make.html
+endif
+
+check: all-am check-recursive check-local
+ @:
+installcheck: installcheck-recursive
+all-recursive-am: config.h
+ $(MAKE) all-recursive
+
+all-am: Makefile $(PROGRAMS) config.h info
+
+install-exec-am: install-binPROGRAMS
+
+install-data-am: install-info-am
+
+uninstall-am: uninstall-binPROGRAMS uninstall-info
+
+install-exec: install-exec-recursive install-exec-am
+ @$(NORMAL_INSTALL)
+
+install-data: install-data-recursive install-data-am
+ @$(NORMAL_INSTALL)
+
+install-recursive uninstall-recursive:
+ @:
+
+install: install-recursive install-exec-am install-data-am
+ @:
+
+uninstall: uninstall-recursive uninstall-am
+
+all: all-recursive-am all-am
+
+install-strip:
+ $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' INSTALL_SCRIPT='$(INSTALL_PROGRAM)' install
+installdirs: installdirs-recursive
+ $(mkinstalldirs) $(bindir) $(infodir)
+
+
+mostlyclean-generic:
+ -test -z "$(MOSTLYCLEANFILES)" || rm -f $(MOSTLYCLEANFILES)
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -rm -f Makefile $(DISTCLEANFILES)
+ -rm -f config.cache config.log stamp-h stamp-h[0-9]*
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+
+maintainer-clean-generic:
+ -test -z "$(MAINTAINERCLEANFILES)" || rm -f $(MAINTAINERCLEANFILES)
+ -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES)
+mostlyclean-am: mostlyclean-hdr mostlyclean-binPROGRAMS mostlyclean-compile mostlyclean-aminfo mostlyclean-tags mostlyclean-generic
+
+clean-am: clean-hdr clean-binPROGRAMS clean-compile clean-aminfo clean-tags clean-generic mostlyclean-am
+
+distclean-am: distclean-hdr distclean-binPROGRAMS distclean-compile distclean-aminfo distclean-tags distclean-generic clean-am
+
+maintainer-clean-am: maintainer-clean-hdr maintainer-clean-binPROGRAMS maintainer-clean-compile maintainer-clean-aminfo maintainer-clean-tags maintainer-clean-generic distclean-am
+
+mostlyclean: mostlyclean-recursive mostlyclean-am
+
+clean: clean-noinstLIBRARIES clean-recursive clean-am
+
+distclean: distclean-recursive distclean-am
+ rm -f config.status
+
+maintainer-clean: maintainer-clean-recursive maintainer-clean-am
+ @echo "This command is intended for maintainers to use;"
+ @echo "it deletes files that may require special tools to rebuild."
+ rm -f config.status
+
+.PHONY: default mostlyclean-hdr distclean-hdr clean-hdr \
+maintainer-clean-hdr mostlyclean-binPROGRAMS distclean-binPROGRAMS \
+clean-binPROGRAMS maintainer-clean-binPROGRAMS uninstall-binPROGRAMS \
+install-binPROGRAMS mostlyclean-compile distclean-compile clean-compile \
+maintainer-clean-compile install-info-am uninstall-info \
+mostlyclean-aminfo distclean-aminfo clean-aminfo \
+maintainer-clean-aminfo install-data-recursive uninstall-data-recursive \
+install-exec-recursive uninstall-exec-recursive installdirs-recursive \
+uninstalldirs-recursive all-recursive check-recursive check-am \
+installcheck-recursive info-recursive dvi-recursive \
+mostlyclean-recursive distclean-recursive clean-recursive \
+maintainer-clean-recursive tags tags-recursive mostlyclean-tags \
+distclean-tags clean-tags maintainer-clean-tags distdir \
+mostlyclean-depend distclean-depend clean-depend \
+maintainer-clean-depend info dvi check-local installcheck \
+all-recursive-am all-am install-exec-am install-data-am uninstall-am \
+install-exec install-data install uninstall all installdirs \
+mostlyclean-generic distclean-generic clean-generic \
+maintainer-clean-generic clean mostlyclean distclean maintainer-clean \
+html
+
+
+# --------------- Local DIST Section
+
+# Install the w32 subdirectory
+#
+dist-hook:
+ (cd $(srcdir); \
+ w32=`find w32 -follow \( -name .git -prune \) -o -type f -print`; \
+ tar chf - $$w32) \
+ | (cd $(distdir); tar xfBp -)
+
+# --------------- Local CHECK Section
+
+# Note: check-loadavg is NOT a prerequisite of check-local, since
+# there's no uptime utility, and the test it does doesn't make sense
+# on MSDOS anyway.
+check-local: check-shell check-regression
+ @banner=" Regression PASSED: GNU Make $(VERSION) ($(MAKE_HOST)) built with $(CC) "; \
+ dashes=`echo "$$banner" | sed s/./=/g`; \
+ echo; \
+ echo "$$dashes"; \
+ echo "$$banner"; \
+ echo "$$dashes"; \
+ echo
+
+.PHONY: check-loadavg check-shell check-regression
+
+# > check-shell
+#
+# check-shell is designed to fail if they don't have a Unixy shell
+# installed. The test suite requires such a shell.
+check-shell:
+ @echo If Make says Error -1, you do not have Unix-style shell installed
+ @foo=bar.exe :
+
+# > check-loadavg
+#
+loadavg: loadavg.c config.h
+ @rm -f loadavg
+ $(LINK) -DTEST $(make_LDFLAGS) loadavg.c $(LIBS)
+# We copy getloadavg.c into a different file rather than compiling it
+# directly because some compilers clobber getloadavg.o in the process.
+loadavg.c: getloadavg.c
+ ln $(srcdir)/getloadavg.c loadavg.c || \
+ cp $(srcdir)/getloadavg.c loadavg.c
+check-loadavg: loadavg
+ @echo The system uptime program believes the load average to be:
+ -uptime
+ @echo The GNU load average checking code believes:
+ -./loadavg
+
+# > check-regression
+#
+# Look for the make test suite, and run it if found. Look in MAKE_TEST if
+# specified, or else in the srcdir or the distdir, their parents, and _their_
+# parents.
+#
+check-regression:
+ @if test -f "$(srcdir)/tests/run_make_tests"; then \
+ if $(PERL) -v >/dev/null 2>&1; then \
+ case `cd $(srcdir); pwd` in `pwd`) : ;; \
+ *) test -d tests || mkdir tests; \
+ for f in run_make_tests run_make_tests.pl test_driver.pl scripts; do \
+ rm -rf tests/$$f; cp -pr $(srcdir)/tests/$$f tests; \
+ done ;; \
+ esac; \
+ echo "cd tests && $(PERL) ./run_make_tests.pl -make ../make.exe $(MAKETESTFLAGS)"; \
+ cd tests && $(PERL) ./run_make_tests.pl -make ../make.exe $(MAKETESTFLAGS); \
+ else \
+ echo "Can't find a working Perl ($(PERL)); the test suite requires Perl."; \
+ fi; \
+ else \
+ echo "Can't find the GNU Make test suite ($(srcdir)/tests)."; \
+ fi
+
+# --------------- Maintainer's Section
+
+# Note this requires GNU make. Not to worry, since it will only be included
+# in the Makefile if we're in the maintainer's environment.
+#include $(srcdir)/maintMakefile
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
+
+# --------------- DEPENDENCIES
diff --git a/src/kmk/Makefile.am b/src/kmk/Makefile.am
new file mode 100644
index 0000000..12cc103
--- /dev/null
+++ b/src/kmk/Makefile.am
@@ -0,0 +1,333 @@
+# This is a -*-Makefile-*-, or close enough
+#
+# Copyright (C) 1997-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+AUTOMAKE_OPTIONS = dist-bzip2 silent-rules std-options
+ACLOCAL_AMFLAGS = -I config
+
+MAKE_HOST = @MAKE_HOST@
+
+# Only process if target is MS-Windows
+if WINDOWSENV
+ MAYBE_W32 = w32
+ W32INC = -I $(top_srcdir)/w32/include
+ W32LIB = -Lw32 -lw32
+ ossrc =
+else
+ ossrc = posixos.c
+endif
+
+# we can safely drop doc and po when bootstrapping kmk.
+# SUBDIRS = glob config po doc $(MAYBE_W32)
+SUBDIRS = glob config $(MAYBE_W32)
+
+bin_PROGRAMS = kmk kmk_redirect
+include_HEADERS = gnumake.h
+
+if USE_CUSTOMS
+ remote = remote-cstms.c
+else
+ remote = remote-stub.c
+endif
+
+kmk_SOURCES = ar.c arscan.c commands.c default.c dir.c expand.c file.c \
+ function.c getopt.c getopt1.c guile.c implicit.c job.c load.c \
+ loadapi.c main.c misc.c $(ossrc) output.c read.c remake.c \
+ rule.c signame.c strcache.c variable.c version.c vpath.c \
+ hash.c $(remote) \
+ \
+ expreval.c \
+ incdep.c \
+ strcache2.c \
+ alloccache.c \
+ kbuild.c \
+ kbuild-object.c \
+ electric.c \
+ ../lib/md5.c \
+ ../lib/kDep.c \
+ ../lib/kbuild_version.c \
+ ../lib/dos2unix.c \
+ ../lib/maybe_con_fwrite.c \
+ ../lib/version_compare.c \
+ \
+ kmkbuiltin.c \
+ kmkbuiltin/append.c \
+ kmkbuiltin/cat.c \
+ kmkbuiltin/chmod.c \
+ kmkbuiltin/cmp.c \
+ kmkbuiltin/cmp_util.c \
+ kmkbuiltin/cp.c \
+ kmkbuiltin/cp_utils.c \
+ kmkbuiltin/echo.c \
+ kmkbuiltin/expr.c \
+ kmkbuiltin/install.c \
+ kmkbuiltin/kDepIDB.c \
+ kmkbuiltin/kDepObj.c \
+ kmkbuiltin/ln.c \
+ kmkbuiltin/md5sum.c \
+ kmkbuiltin/mkdir.c \
+ kmkbuiltin/mv.c \
+ kmkbuiltin/printf.c \
+ kmkbuiltin/redirect.c \
+ kmkbuiltin/rm.c \
+ kmkbuiltin/rmdir.c \
+ kmkbuiltin/sleep.c \
+ kmkbuiltin/test.c \
+ kmkbuiltin/touch.c \
+ \
+ kmkbuiltin/err.c \
+ kmkbuiltin/getopt_r.c \
+ kmkbuiltin/getopt1_r.c \
+ kmkbuiltin/fts.c \
+ kmkbuiltin/setmode.c \
+ kmkbuiltin/strmode.c \
+ kmkbuiltin/strlcpy.c \
+ kmkbuiltin/osdep.c \
+ kmkbuiltin/kbuild_protection.c \
+ kmkbuiltin/common-env-and-cwd-opt.c
+
+kmk_redirect_SOURCES = kmkbuiltin/redirect.c \
+ kmkbuiltin/common-env-and-cwd-opt.c \
+ kmkbuiltin/err.c \
+ ../lib/kbuild_version.c
+kmk_redirect_CFLAGS = -UKMK -DKMK_BUILTIN_STANDALONE
+
+EXTRA_kmk_SOURCES = vmsjobs.c remote-stub.c remote-cstms.c
+
+noinst_HEADERS = commands.h dep.h filedef.h job.h makeint.h rule.h variable.h \
+ debug.h getopt.h gettext.h hash.h output.h os.h
+
+#kmk_LDADD = @LIBOBJS@ @ALLOCA@ $(GLOBLIB) @GETLOADAVG_LIBS@ @LIBINTL@
+kmk_LDADD = @LIBOBJS@ @ALLOCA@ $(GLOBLIB) @GETLOADAVG_LIBS@ \
+ $(GUILE_LIBS)
+# Only process if target is MS-Windows
+if WINDOWSENV
+ kmk_LDADD += $(W32LIB)
+endif
+
+man_MANS = make.1
+
+# org - DEFS = -DLOCALEDIR=\"$(localedir)\" -DLIBDIR=\"$(libdir)\" -DINCLUDEDIR=\"$(includedir)\" @DEFS@
+DEFS = \
+ -DNO_ARCHIVES \
+ -DEXPERIMENTAL \
+ -DCONFIG_WITH_TOUPPER_TOLOWER \
+ -DCONFIG_WITH_DEFINED \
+ -DCONFIG_WITH_EXPLICIT_MULTITARGET \
+ -DCONFIG_WITH_DOT_MUST_MAKE \
+ -DCONFIG_WITH_PREPEND_ASSIGNMENT \
+ -DCONFIG_WITH_LOCAL_VARIABLES \
+ -DCONFIG_WITH_2ND_TARGET_EXPANSION \
+ -DCONFIG_WITH_ALLOC_CACHES \
+ -DCONFIG_WITH_STRCACHE2 \
+ \
+ -DKMK \
+ -DKMK_HELPERS \
+ -DCONFIG_NO_DEFAULT_SUFFIXES \
+ -DCONFIG_NO_DEFAULT_PATTERN_RULES \
+ -DCONFIG_NO_DEFAULT_TERMINAL_RULES \
+ -DCONFIG_NO_DEFAULT_SUFFIX_RULES \
+ -DCONFIG_NO_DEFAULT_VARIABLES \
+ -DCONFIG_WITH_EXTENDED_NOTPARALLEL \
+ -DCONFIG_WITH_INCLUDEDEP \
+ -DCONFIG_WITHOUT_THREADS \
+ -DCONFIG_WITH_VALUE_LENGTH \
+ \
+ -DCONFIG_WITH_ABSPATHEX \
+ -DCONFIG_WITH_COMMANDS_FUNC \
+ -DCONFIG_WITH_DATE \
+ -DCONFIG_WITH_DEFINED_FUNCTIONS \
+ -DCONFIG_WITH_EVALPLUS \
+ -DCONFIG_WITH_FILE_SIZE \
+ -DCONFIG_WITH_LOOP_FUNCTIONS \
+ -DCONFIG_WITH_MATH \
+ -DCONFIG_WITH_NANOTS \
+ -DCONFIG_WITH_ROOT_FUNC \
+ -DCONFIG_WITH_RSORT \
+ -DCONFIG_WITH_STACK \
+ -DCONFIG_WITH_STRING_FUNCTIONS \
+ -DCONFIG_WITH_WHERE_FUNCTION \
+ -DCONFIG_WITH_WHICH \
+ -DCONFIG_WITH_XARGS \
+ \
+ -DCONFIG_WITH_COMPARE \
+ -DCONFIG_WITH_SET_CONDITIONALS \
+ -DCONFIG_WITH_IF_CONDITIONALS \
+ -DCONFIG_WITH_PRINTF \
+ -DCONFIG_WITH_MINIMAL_STATS \
+ -DCONFIG_PRETTY_COMMAND_PRINTING \
+ -DCONFIG_WITH_PRINT_STATS_SWITCH \
+ -DCONFIG_WITH_PRINT_TIME_SWITCH \
+ -DCONFIG_WITH_RDONLY_VARIABLE_VALUE \
+ -DCONFIG_WITH_LAZY_DEPS_VARS \
+ \
+ -DKBUILD_TYPE=\"$(KBUILD_TYPE)\" \
+ -DKBUILD_HOST=\"$(KBUILD_TARGET)\" \
+ -DKBUILD_HOST_ARCH=\"$(KBUILD_TARGET_ARCH)\" \
+ -DKBUILD_HOST_CPU=\"$(KBUILD_TARGET_CPU)\" \
+ \
+ -DKBUILD_SVN_REV=1 \
+ -DKBUILD_VERSION_MAJOR=0 \
+ -DKBUILD_VERSION_MINOR=1 \
+ -DKBUILD_VERSION_PATCH=9998 \
+ \
+ -DCONFIG_WITH_KMK_BUILTIN \
+ @DEFS@
+
+AM_CPPFLAGS = $(GLOBINC) -I$(srcdir)/../lib -I$(srcdir)/../lib/kStuff/include
+AM_CFLAGS = $(GUILE_CFLAGS)
+# Only process if target is MS-Windows
+if WINDOWSENV
+ AM_CPPFLAGS += $(W32INC)
+endif
+
+
+# Extra stuff to include in the distribution.
+
+EXTRA_DIST = ChangeLog README build.sh.in $(man_MANS) \
+ README.customs README.OS2 \
+ SCOPTIONS SMakefile \
+ README.Amiga Makefile.ami config.ami make.lnk amiga.c amiga.h \
+ README.DOS Makefile.DOS configure.bat dosbuild.bat configh.dos\
+ README.W32 NMakefile config.h.W32 build_w32.bat subproc.bat \
+ make_msvc_net2003.sln make_msvc_net2003.vcproj \
+ README.VMS makefile.vms makefile.com config.h-vms \
+ vmsdir.h vmsfunctions.c vmsify.c vms_exit.c vms_progname.c \
+ vms_export_symbol.c vms_export_symbol_test.com \
+ gmk-default.scm gmk-default.h
+
+# This is built during configure, but behind configure's back
+
+DISTCLEANFILES = build.sh
+
+# --------------- Internationalization Section
+
+localedir = $(datadir)/locale
+
+# --------------- Local INSTALL Section
+
+# If necessary, change the gid of the app and turn on the setgid flag.
+#
+
+# Whether or not make needs to be installed setgid.
+# The value should be either 'true' or 'false'.
+# On many systems, the getloadavg function (used to implement the '-l'
+# switch) will not work unless make is installed setgid kmem.
+#
+inst_setgid = @NEED_SETGID@
+
+# Install make setgid to this group so it can get the load average.
+#
+inst_group = @KMEM_GROUP@
+
+install-exec-local:
+ @if $(inst_setgid); then \
+ app=$(DESTDIR)$(bindir)/`echo $(bin_PROGRAMS)|sed '$(transform)'`; \
+ if chgrp $(inst_group) $$app && chmod g+s $$app; then \
+ echo "chgrp $(inst_group) $$app && chmod g+s $$app"; \
+ else \
+ echo "$$app needs to be owned by group $(inst_group) and setgid;"; \
+ echo "otherwise the '-l' option will probably not work."; \
+ echo "You may need special privileges to complete the installation"; \
+ echo "of $$app."; \
+ fi; \
+ else true; fi
+
+# --------------- Generate the Guile default module content
+
+guile.$(OBJEXT): gmk-default.h
+gmk-default.h: $(srcdir)/gmk-default.scm
+ (echo 'static const char *const GUILE_module_defn = " '\\ \
+ && sed -e 's/;.*//' -e '/^[ \t]*$$/d' -e 's/"/\\"/g' -e 's/$$/ \\/' \
+ $(srcdir)/gmk-default.scm \
+ && echo '";') > $@
+
+# --------------- Local DIST Section
+
+# Install the w32 and tests subdirectories
+#
+dist-hook:
+ (cd $(srcdir); \
+ sub=`find w32 tests -follow \( -name .git -o -name .deps -o -name work -o -name .gitignore -o -name \*.orig -o -name \*.rej -o -name \*~ -o -name Makefile \) -prune -o -type f -print`; \
+ tar chf - $$sub) \
+ | (cd $(distdir); tar xfBp -)
+
+
+# --------------- Local CHECK Section
+
+check-local: check-regression check-loadavg
+ @banner=" Regression PASSED: GNU Make $(VERSION) ($(MAKE_HOST)) built with $(CC) "; \
+ dashes=`echo "$$banner" | sed s/./=/g`; \
+ echo; \
+ echo "$$dashes"; \
+ echo "$$banner"; \
+ echo "$$dashes"; \
+ echo
+
+.PHONY: check-loadavg check-regression
+
+check-loadavg: loadavg$(EXEEXT)
+ @echo The system uptime program believes the load average to be:
+ -uptime
+ @echo The GNU load average checking code thinks:
+ -./loadavg$(EXEEXT)
+
+# The loadavg function is invoked during "make check" to test getloadavg.
+check_PROGRAMS = loadavg
+nodist_loadavg_SOURCES = getloadavg.c
+loadavg_CPPFLAGS = -DTEST
+loadavg_LDADD = @GETLOADAVG_LIBS@
+
+# > check-regression
+#
+# Look for the make test suite, and run it if found and we can find perl.
+# If we're building outside the tree, we use symlinks to make a local copy of
+# the test suite. Unfortunately the test suite itself isn't localizable yet.
+#
+MAKETESTFLAGS =
+
+check-regression: tests/config-flags.pm
+ @if test -f '$(srcdir)/tests/run_make_tests'; then \
+ ulimit -n 128; \
+ if $(PERL) -v >/dev/null 2>&1; then \
+ case `cd '$(srcdir)'; pwd` in `pwd`) : ;; \
+ *) test -d tests || mkdir tests; \
+ rm -f srctests; \
+ if ln -s '$(srcdir)/tests' srctests; then \
+ for f in run_make_tests run_make_tests.pl test_driver.pl scripts; do \
+ rm -f tests/$$f; ln -s ../srctests/$$f tests; \
+ done; fi ;; \
+ esac; \
+ echo "cd tests && $(PERL) ./run_make_tests.pl -srcdir $(abs_srcdir) -make ../make$(EXEEXT) $(MAKETESTFLAGS)"; \
+ cd tests && $(PERL) ./run_make_tests.pl -srcdir '$(abs_srcdir)' -make '../make$(EXEEXT)' $(MAKETESTFLAGS); \
+ else \
+ echo "Can't find a working Perl ($(PERL)); the test suite requires Perl."; \
+ fi; \
+ else \
+ echo "Can't find the GNU Make test suite ($(srcdir)/tests)."; \
+ fi
+
+
+# --------------- Maintainer's Section
+
+# Tell automake that I haven't forgotten about this file and it will be
+# created before we build a distribution (see maintMakefile in the Git
+# distribution).
+
+README:
+
+@MAINT_MAKEFILE@
diff --git a/src/kmk/Makefile.ami b/src/kmk/Makefile.ami
new file mode 100644
index 0000000..39a9788
--- /dev/null
+++ b/src/kmk/Makefile.ami
@@ -0,0 +1,308 @@
+# -*-Makefile-*- for GNU make on Amiga
+#
+# NOTE: If you have no 'make' program at all to process this makefile, run
+# 'build.sh' instead.
+#
+# Copyright (C) 1995-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Makefile for GNU Make
+#
+
+CC = sc
+RM = delete
+
+CFLAGS =
+CPPFLAGS =
+LDFLAGS =
+
+# Define these for your system as follows:
+# -DNO_ARCHIVES To disable 'ar' archive support.
+# -DNO_FLOAT To avoid using floating-point numbers.
+# -DENUM_BITFIELDS If the compiler isn't GCC but groks enum foo:2.
+# Some compilers apparently accept this
+# without complaint but produce losing code,
+# so beware.
+# NeXT 1.0a uses an old version of GCC, which required -D__inline=inline.
+# See also 'config.h'.
+defines =
+
+# Which flavor of remote job execution support to use.
+# The code is found in 'remote-$(REMOTE).c'.
+REMOTE = stub
+
+# If you are using the GNU C library, or have the GNU getopt functions in
+# your C library, you can comment these out.
+GETOPT = getopt.o getopt1.o
+GETOPT_SRC = $(srcdir)getopt.c $(srcdir)getopt1.c $(srcdir)getopt.h
+
+# If you are using the GNU C library, or have the GNU glob functions in
+# your C library, you can comment this out. GNU make uses special hooks
+# into the glob functions to be more efficient (by using make's directory
+# cache for globbing), so you must use the GNU functions even if your
+# system's C library has the 1003.2 glob functions already. Also, the glob
+# functions in the AIX and HPUX C libraries are said to be buggy.
+GLOB = glob/glob.lib
+
+# If your system doesn't have alloca, or the one provided is bad, define this.
+ALLOCA = alloca.o
+ALLOCA_SRC = $(srcdir)alloca.c
+
+# If your system needs extra libraries loaded in, define them here.
+# System V probably need -lPW for alloca. HP-UX 7.0's alloca in
+# libPW.a is broken on HP9000s300 and HP9000s400 machines. Use
+# alloca.c instead on those machines.
+LOADLIBES =
+
+# Any extra object files your system needs.
+extras = amiga.o
+
+# Common prefix for machine-independent installed files.
+prefix =
+# Common prefix for machine-dependent installed files.
+exec_prefix =
+
+# Directory to install 'make' in.
+bindir = sc:c
+# Directory to find libraries in for '-lXXX'.
+libdir = lib:
+# Directory to search by default for included makefiles.
+includedir = include:
+# Directory to install the Info files in.
+infodir = doc:
+# Directory to install the man page in.
+mandir = t:
+# Number to put on the man page filename.
+manext = 1
+# Prefix to put on installed 'make' binary file name.
+binprefix =
+# Prefix to put on installed 'make' man page file name.
+manprefix = $(binprefix)
+
+# Whether or not make needs to be installed setgid.
+# The value should be either 'true' or 'false'.
+# On many systems, the getloadavg function (used to implement the '-l'
+# switch) will not work unless make is installed setgid kmem.
+install_setgid = false
+# Install make setgid to this group so it can read /dev/kmem.
+group = sys
+
+# Program to install 'make'.
+INSTALL_PROGRAM = copy
+# Program to install the man page.
+INSTALL_DATA = copy
+# Generic install program.
+INSTALL = copy
+
+# Program to format Texinfo source into Info files.
+MAKEINFO = makeinfo
+# Program to format Texinfo source into DVI files.
+TEXI2DVI = texi2dvi
+
+# Programs to make tags files.
+ETAGS = etags -w
+CTAGS = ctags -w
+
+#guile = guile.o
+
+objs = commands.o job.o dir.o file.o misc.o main.o read.o remake.o \
+ rule.o implicit.o default.o variable.o expand.o function.o \
+ vpath.o version.o ar.o arscan.o signame.o strcache.o hash.o \
+ remote-$(REMOTE).o $(GETOPT) $(ALLOCA) $(extras) $(guile)
+
+srcs = $(srcdir)commands.c $(srcdir)job.c $(srcdir)dir.c \
+ $(srcdir)file.c $(srcdir)getloadavg.c $(srcdir)misc.c \
+ $(srcdir)main.c $(srcdir)read.c $(srcdir)remake.c \
+ $(srcdir)rule.c $(srcdir)implicit.c $(srcdir)default.c \
+ $(srcdir)variable.c $(srcdir)expand.c $(srcdir)function.c \
+ $(srcdir)vpath.c $(srcdir)version.c $(srcdir)hash.c \
+ $(srcdir)guile.c $(srcdir)remote-$(REMOTE).c \
+ $(srcdir)ar.c $(srcdir)arscan.c $(srcdir)strcache.c \
+ $(srcdir)signame.c $(srcdir)signame.h $(GETOPT_SRC) \
+ $(srcdir)commands.h $(srcdir)dep.h $(srcdir)filedep.h \
+ $(srcdir)job.h $(srcdir)makeint.h $(srcdir)rule.h \
+ $(srcdir)variable.h $(ALLOCA_SRC) $(srcdir)config.h.in
+
+
+.SUFFIXES:
+.SUFFIXES: .o .c .h .ps .dvi .info .texinfo
+
+all: make
+info: make.info
+dvi: make.dvi
+# Some makes apparently use .PHONY as the default goal if it is before 'all'.
+.PHONY: all check info dvi
+
+make.info: make.texinfo
+ $(MAKEINFO) -I$(srcdir) $(srcdir)make.texinfo -o make.info
+
+make.dvi: make.texinfo
+ $(TEXI2DVI) $(srcdir)make.texinfo
+
+make.ps: make.dvi
+ dvi2ps make.dvi > make.ps
+
+make: $(objs) $(GLOB)
+ $(CC) Link $(LDFLAGS) $(objs) Lib $(GLOB) $(LOADLIBES) To make.new
+ -delete make
+ rename make.new make
+
+TMPFILE = t:Make$$
+
+$(GLOB):
+ cd glob @@\
+ $(MAKE) -$(MAKEFLAGS) -f Makefile
+
+# -I. is needed to find config.h in the build directory.
+OUTPUT_OPTION =
+.c.o:
+ $(CC) $(defines) IDir "" IDir glob \
+ $(CPPFLAGS) $(CFLAGS) $< $(OUTPUT_OPTION)
+
+# For some losing Unix makes.
+SHELL = /bin/sh
+#@SET_MAKE@
+
+glob/libglob.a: FORCE config.h
+ cd glob; $(MAKE) libglob.a
+FORCE:
+
+.PHONY: install installdirs
+install: installdirs \
+ $(bindir)$(binprefix)make $(infodir)make.info \
+ $(mandir)$(manprefix)make.$(manext)
+
+installdirs:
+ $(SHELL) ${srcdir}/mkinstalldirs $(bindir) $(infodir) $(mandir)
+
+$(bindir)$(binprefix)make: make
+ $(INSTALL_PROGRAM) make $@.new
+ @if $(install_setgid); then \
+ if chgrp $(group) $@.new && chmod g+s $@.new; then \
+ echo "chgrp $(group) $@.new && chmod g+s $@.new"; \
+ else \
+ echo "$@ needs to be owned by group $(group) and setgid;"; \
+ echo "otherwise the '-l' option will probably not work."; \
+ echo "You may need special privileges to install $@."; \
+ fi; \
+ else true; fi
+# Some systems can't deal with renaming onto a running binary.
+ -$(RM) $@.old
+ -mv $@ $@.old
+ mv $@.new $@
+
+$(infodir)make.info: make.info
+ if [ -r ./make.info ]; then dir=.; else dir=$(srcdir); fi; \
+ for file in $${dir}/make.info*; do \
+ name="`basename $$file`"; \
+ $(INSTALL_DATA) $$file \
+ `echo $@ | sed "s,make.info\$$,$$name,"`; \
+ done
+# Run install-info only if it exists.
+# Use 'if' instead of just prepending '-' to the
+# line so we notice real errors from install-info.
+# We use '$(SHELL) -c' because some shells do not
+# fail gracefully when there is an unknown command.
+ if $(SHELL) -c 'install-info --version' >/dev/null 2>&1; then \
+ if [ -r ./make.info ]; then dir=.; else dir=$(srcdir); fi; \
+ install-info --infodir=$(infodir) $$dir/make.info; \
+ else true; fi
+
+$(mandir)$(manprefix)make.$(manext): make.man
+ $(INSTALL_DATA) $(srcdir)make.man $@
+
+
+loadavg: loadavg.c config.h
+ $(CC) $(defines) -DTEST -I. -I$(srcdir) $(CFLAGS) $(LDFLAGS) \
+ loadavg.c $(LOADLIBES) -o $@
+# We copy getloadavg.c into a different file rather than compiling it
+# directly because some compilers clobber getloadavg.o in the process.
+loadavg.c: getloadavg.c
+ ln $(srcdir)getloadavg.c loadavg.c || \
+ cp $(srcdir)getloadavg.c loadavg.c
+check-loadavg: loadavg
+ @echo The system uptime program believes the load average to be:
+ -uptime
+ @echo The GNU load average checking code believes:
+ ./loadavg
+check: check-loadavg
+
+
+.PHONY: clean realclean distclean mostlyclean
+clean: glob-clean
+ -$(RM) make loadavg "#?.o" core make.dvi
+
+distclean: clean glob-realclean
+ -$(RM) Makefile config.h config.status build.sh
+ -$(RM) config.log config.cache
+ -$(RM) TAGS tags
+ -$(RM) make.?? make.??s make.log make.toc make.*aux
+ -$(RM) loadavg.c
+
+realclean: distclean
+ -$(RM) make.info*
+mostlyclean: clean
+
+.PHONY: glob-clean glob-realclean
+glob-clean glob-realclean:
+ cd glob @@\
+ $(MAKE) $@
+
+# This tells versions [3.59,3.63) of GNU make not to export all variables.
+.NOEXPORT:
+
+# The automatically generated dependencies below may omit config.h
+# because it is included with '#include <config.h>' rather than
+# '#include "config.h"'. So we add the explicit dependency to make sure.
+$(objs): config.h
+
+# Automatically generated dependencies will be put at the end of the file.
+
+# Automatically generated dependencies.
+commands.o: commands.c makeint.h dep.h filedef.h variable.h job.h \
+ commands.h
+job.o: job.c makeint.h job.h filedef.h commands.h variable.h
+dir.o: dir.c makeint.h
+file.o: file.c makeint.h dep.h filedef.h job.h commands.h variable.h
+misc.o: misc.c makeint.h dep.h
+main.o: main.c makeint.h dep.h filedef.h variable.h job.h commands.h \
+ getopt.h
+guile.o: guile.c makeint.h dep.h debug.h variable.h gmk-default.h
+read.o: read.c makeint.h dep.h filedef.h job.h commands.h variable.h \
+ glob/glob.h
+remake.o: remake.c makeint.h filedef.h job.h commands.h dep.h
+rule.o: rule.c makeint.h dep.h filedef.h job.h commands.h variable.h \
+ rule.h
+implicit.o: implicit.c makeint.h rule.h dep.h filedef.h
+default.o: default.c makeint.h rule.h dep.h filedef.h job.h commands.h \
+ variable.h
+variable.o: variable.c makeint.h dep.h filedef.h job.h commands.h \
+ variable.h
+expand.o: expand.c makeint.h filedef.h job.h commands.h variable.h
+function.o: function.c makeint.h filedef.h variable.h dep.h job.h \
+ commands.h amiga.h
+vpath.o: vpath.c makeint.h filedef.h variable.h
+strcache.o: strcache.c makeint.h hash.h
+version.o: version.c
+ar.o: ar.c makeint.h filedef.h dep.h
+arscan.o: arscan.c makeint.h
+signame.o: signame.c signame.h
+remote-stub.o: remote-stub.c makeint.h filedef.h job.h commands.h
+getopt.o: getopt.c
+getopt1.o : getopt1.c getopt.h
+getloadavg.o: getloadavg.c
+amiga.o: amiga.c makeint.h variable.h amiga.h
diff --git a/src/kmk/Makefile.kmk b/src/kmk/Makefile.kmk
new file mode 100644
index 0000000..ff93ae5
--- /dev/null
+++ b/src/kmk/Makefile.kmk
@@ -0,0 +1,770 @@
+# $Id: Makefile.kmk 3572 2022-10-24 08:36:35Z bird $
+## @file
+# Sub-makefile for kmk / GNU Make.
+#
+
+#
+# Copyright (c) 2004-2011 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+SUB_DEPTH = ../..
+include $(KBUILD_PATH)/subheader.kmk
+
+# Enable new children handling for windows.
+CONFIG_NEW_WIN_CHILDREN = 1
+
+#
+# Template for kmk and the kmk_* binaries in this makefile.
+#
+TEMPLATE_BIN-KMK = Template for src/gmake binaries
+TEMPLATE_BIN-KMK_EXTENDS = BIN-THREADED
+TEMPLATE_BIN-KMK_DEFS = \
+ HAVE_CONFIG_H \
+ $(TEMPLATE_BIN_DEFS) \
+ KBUILD_SVN_REV=$(KBUILD_SVN_REV) \
+ KBUILD_TYPE=$(TMP_QUOTE_SLASH)"$(KBUILD_TYPE)$(TMP_QUOTE_SLASH)"
+TEMPLATE_BIN-KMK_DEPS = \
+ $(kmk_0_OUTDIR)/config.h \
+ $(kmk_0_OUTDIR)/fts.h
+TEMPLATE_BIN-KMK_CLEAN = $(TEMPLATE_BIN-KMK_DEPS)
+TEMPLATE_BIN-KMK_DEPS.solaris = \
+ $(kmk_0_OUTDIR)/paths.h
+TEMPLATE_BIN-KMK_CLEAN.solaris = $(TEMPLATE_BIN-KMK_DEPS.solaris)
+TEMPLATE_BIN-KMK_DEPS.win = \
+ $(kmk_0_OUTDIR)/sysexits.h \
+ $(kmk_0_OUTDIR)/unistd.h \
+ $(kmk_0_OUTDIR)/paths.h \
+ $(kmk_0_OUTDIR)/grp.h \
+ $(kmk_0_OUTDIR)/pwd.h \
+ $(kmk_0_OUTDIR)/inttypes.h
+TEMPLATE_BIN-KMK_CFLAGS.win.amd64 = $(TEMPLATE_BIN-THREADED_CFLAGS.win.amd64) -wd4244 -wd4267
+TEMPLATE_BIN-KMK_CLEAN.win = $(TEMPLATE_BIN-KMK_DEPS.win)
+TEMPLATE_BIN-KMK_DEFS.debug = $(TEMPLATE_BIN_DEFS.debug) MAKE_MAINTAINER_MODE
+TEMPLATE_BIN-KMK_INCS = $(kmk_0_OUTDIR) . $(TEMPLATE_BIN-THREADED_INCS)
+ifneq ($(KBUILD_TARGET),os2)
+ TEMPLATE_BIN-KMK_INCS += glob
+endif
+TEMPLATE_BIN-KMK_LIBS = $(LIB_KUTIL) $(TEMPLATE_BIN-THREADED_LIBS) $(kmkmissing_1_TARGET) $(LIB_KUTIL)
+TEMPLATE_BIN-KMK_LIBS.x86 = $(LIB_KUTIL) $(TEMPLATE_BIN-THREADED_LIBS.x86)
+TEMPLATE_BIN-KMK_LIBS.amd64 = $(LIB_KUTIL) $(TEMPLATE_BIN-THREADED_LIBS.amd64)
+ifdef ELECTRIC_HEAP # for electric heap (see electric.c).
+ifeq ($(KBUILD_TARGET),win)
+ TEMPLATE_BIN-KMK_CFLAGS = $(TEMPLATE_BIN-THREADED_CFLAGS) /FI$(kmk_DEFPATH)/electric.h -DELECTRIC_HEAP=1
+else
+ TEMPLATE_BIN-KMK_CFLAGS = $(TEMPLATE_BIN-THREADED_CFLAGS) -include $(kmk_DEFPATH)/electric.h -DELECTRIC_HEAP=1
+endif
+endif
+ifdef CONFIG_WITH_ALLOCCACHE_DEBUG
+ TEMPLATE_BIN-KMK_DEFS += CONFIG_WITH_ALLOCCACHE_DEBUG
+endif
+ifdef CONFIG_NEW_WIN_CHILDREN
+ TEMPLATE_BIN-KMK_DEFS.win = $(TEMPLATE_BIN_DEFS.win) CONFIG_NEW_WIN_CHILDREN
+endif
+# GCC sanitizers.
+ifdef GCC_SANITIZERS
+ TEMPLATE_BIN-KMK_CFLAGS ?= $(TEMPLATE_BIN-THREADED_CFLAGS)
+# TEMPLATE_BIN-KMK_CFLAGS += -fsanitize=address -fsanitize=undefined -static-libubsan -D GCC_ADDRESS_SANITIZER
+ TEMPLATE_BIN-KMK_CFLAGS += -fsanitize=address -fsanitize=undefined -D GCC_ADDRESS_SANITIZER
+ TEMPLATE_BIN-KMK_LDFLAGS ?= $(TEMPLATE_BIN-THREADED_LDFLAGS)
+ TEMPLATE_BIN-KMK_LDFLAGS += -fsanitize=address -fsanitize=undefined
+endif
+
+#
+# Library version of the above.
+#
+TEMPLATE_LIB-KMK = Template for src/gmake libraries
+TEMPLATE_LIB-KMK_EXTENDS = BIN-KMK
+TEMPLATE_LIB-KMK_POST_CMDS.win = $(NO_SUCH_VARIABLE)
+
+#
+# Template for building standalone built-in utilities.
+#
+TEMPLATE_BIN-KMK-BUILTIN = Template for standalone built-in utilies.
+TEMPLATE_BIN-KMK-BUILTIN_EXTENDS = BIN-KMK
+TEMPLATE_BIN-KMK-BUILTIN_EXTENDS_BY = appending
+TEMPLATE_BIN-KMK-BUILTIN_DEFS += KMK_BUILTIN_STANDALONE
+TEMPLATE_BIN-KMK-BUILTIN_SOURCES += kmkbuiltin/err.c
+
+#
+# A library containing the missing features needed by kmk and the
+# kmk_* binaries. Saves a bit of work later on.
+#
+LIBRARIES += kmkmissing
+kmkmissing_TEMPLATE = LIB-KMK
+kmkmissing_DEFS = KMK
+kmkmissing_NOINST = 1
+kmkmissing_SOURCES = \
+ kmkbuiltin/fts.c \
+ kmkbuiltin/setmode.c \
+ kmkbuiltin/strmode.c \
+ kmkbuiltin/kbuild_protection.c \
+ kmkbuiltin/common-env-and-cwd-opt.c \
+ kmkbuiltin/getopt_r.c \
+ kmkbuiltin/getopt1_r.c \
+ getopt.c \
+ getopt1.c \
+ electric.c
+ifneq ($(KBUILD_TARGET),os2)
+kmkmissing_SOURCES += \
+ glob/glob.c
+endif
+
+kmkmissing_SOURCES.darwin = \
+ kmkbuiltin/darwin.c \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.dragonfly = \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.freebsd = \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.gnuhurd += \
+ kmkbuiltin/strlcpy.c
+
+kmkmissing_SOURCES.gnukfbsd += \
+ kmkbuiltin/strlcpy.c
+
+kmkmissing_SOURCES.gnuknbsd += \
+ kmkbuiltin/strlcpy.c
+
+kmkmissing_SOURCES.haiku = \
+ kmkbuiltin/haikufakes.c \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.linux += \
+ kmkbuiltin/strlcpy.c
+
+kmkmissing_SOURCES.netbsd = \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.openbsd = \
+ kmkbuiltin/openbsd.c
+
+kmkmissing_SOURCES.solaris = \
+ kmkbuiltin/strlcpy.c \
+ kmkbuiltin/solfakes.c \
+ glob/fnmatch.c
+
+kmkmissing_SOURCES.win += \
+ kmkbuiltin/strlcpy.c \
+ kmkbuiltin/mscfakes.c \
+ glob/fnmatch.c \
+ getloadavg.c \
+ w32/subproc/misc.c \
+ w32/subproc/w32err.c \
+ w32/pathstuff.c \
+ w32/imagecache.c \
+ w32/compat/posixfcn.c
+
+#
+# kmk
+#
+PROGRAMS += kmk
+
+kmk_TEMPLATE = BIN-KMK
+
+kmk_DEFS = \
+ NO_ARCHIVES \
+ EXPERIMENTAL \
+ CONFIG_WITH_TOUPPER_TOLOWER \
+ CONFIG_WITH_DEFINED \
+ CONFIG_WITH_EXPLICIT_MULTITARGET \
+ CONFIG_WITH_DOT_MUST_MAKE \
+ CONFIG_WITH_PREPEND_ASSIGNMENT \
+ CONFIG_WITH_LOCAL_VARIABLES \
+ CONFIG_WITH_2ND_TARGET_EXPANSION \
+ CONFIG_WITH_ALLOC_CACHES \
+ CONFIG_WITH_STRCACHE2 \
+ \
+ KMK \
+ KMK_HELPERS \
+ CONFIG_NO_DEFAULT_SUFFIXES \
+ CONFIG_NO_DEFAULT_PATTERN_RULES \
+ CONFIG_NO_DEFAULT_TERMINAL_RULES \
+ CONFIG_NO_DEFAULT_SUFFIX_RULES \
+ CONFIG_NO_DEFAULT_VARIABLES \
+ \
+ CONFIG_WITH_ABSPATHEX \
+ CONFIG_WITH_COMMANDS_FUNC \
+ CONFIG_WITH_DATE \
+ CONFIG_WITH_DEFINED_FUNCTIONS \
+ CONFIG_WITH_EVALPLUS \
+ CONFIG_WITH_FILE_SIZE \
+ CONFIG_WITH_LOOP_FUNCTIONS \
+ CONFIG_WITH_MATH \
+ CONFIG_WITH_NANOTS \
+ CONFIG_WITH_ROOT_FUNC \
+ CONFIG_WITH_RSORT \
+ CONFIG_WITH_STACK \
+ CONFIG_WITH_STRING_FUNCTIONS \
+ CONFIG_WITH_WHERE_FUNCTION \
+ CONFIG_WITH_WHICH \
+ CONFIG_WITH_XARGS \
+ \
+ CONFIG_WITH_EXTENDED_NOTPARALLEL \
+ CONFIG_WITH_INCLUDEDEP \
+ CONFIG_WITH_VALUE_LENGTH \
+ CONFIG_WITH_COMPARE \
+ CONFIG_WITH_SET_CONDITIONALS \
+ CONFIG_WITH_IF_CONDITIONALS \
+ CONFIG_WITH_PRINTF \
+ CONFIG_WITH_MINIMAL_STATS \
+ \
+ CONFIG_PRETTY_COMMAND_PRINTING \
+ CONFIG_WITH_PRINT_STATS_SWITCH \
+ CONFIG_WITH_PRINT_TIME_SWITCH \
+ CONFIG_WITH_RDONLY_VARIABLE_VALUE \
+ CONFIG_WITH_LAZY_DEPS_VARS \
+ CONFIG_WITH_MEMORY_OPTIMIZATIONS \
+ \
+ KBUILD_HOST=$(TMP_QUOTE_SLASH)"$(KBUILD_TARGET)$(TMP_QUOTE_SLASH)" \
+ KBUILD_HOST_ARCH=$(TMP_QUOTE_SLASH)"$(KBUILD_TARGET_ARCH)$(TMP_QUOTE_SLASH)" \
+ KBUILD_HOST_CPU=$(TMP_QUOTE_SLASH)"$(KBUILD_TARGET_CPU)$(TMP_QUOTE_SLASH)"
+# kmk_DEFS += CONFIG_WITH_COMPILER # experimental, doesn't work 101% right it seems.
+kmk_DEFS.x86 = CONFIG_WITH_OPTIMIZATION_HACKS
+kmk_DEFS.amd64 = CONFIG_WITH_OPTIMIZATION_HACKS
+kmk_DEFS.win = CONFIG_NEW_WIN32_CTRL_EVENT CONFIG_WITH_OUTPUT_IN_MEMORY
+kmk_DEFS.debug = CONFIG_WITH_MAKE_STATS
+ifdef CONFIG_WITH_MAKE_STATS
+ kmk_DEFS += CONFIG_WITH_MAKE_STATS
+endif
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+ kmk_DEFS += CONFIG_WITH_KMK_BUILTIN_STATS
+#endif
+ifdef CONFIG_WITH_EVAL_COMPILER
+ kmk_DEFS += CONFIG_WITH_EVAL_COMPILER
+endif
+ifdef CONFIG_WITH_COMPILE_EVERYTHING
+ kmk_DEFS += CONFIG_WITH_COMPILE_EVERYTHING
+endif
+
+#ifeq ($(KBUILD_TYPE).$(USERNAME),debug.bird)
+# kmk_DEFS += CONFIG_WITH_COMPILER CONFIG_WITH_EVAL_COMPILER CONFIG_WITH_COMPILE_EVERYTHING
+#endif
+
+kmk_SOURCES = \
+ main.c \
+ read.c \
+ hash.c \
+ strcache.c \
+ variable.c \
+ ar.c \
+ arscan.c \
+ commands.c \
+ default.c \
+ expand.c \
+ file.c \
+ function.c \
+ implicit.c \
+ job.c \
+ misc.c \
+ output.c \
+ remake.c \
+ rule.c \
+ signame.c \
+ version.c \
+ vpath.c \
+ remote-stub.c \
+ guile.c \
+ load.c \
+ \
+ alloccache.c \
+ expreval.c \
+ incdep.c \
+ strcache2.c \
+ kmk_cc_exec.c \
+ kbuild.c \
+ kbuild-object.c
+ifeq ($(KBUILD_TARGET),win)
+ kmk_SOURCES += \
+ dir-nt-bird.c \
+ w32/w32os.c
+else
+ kmk_SOURCES += \
+ dir.c \
+ posixos.c
+endif
+
+ifndef CONFIG_NEW_WIN_CHILDREN
+kmk_SOURCES.win = \
+ w32/subproc/sub_proc.c
+else
+kmk_SOURCES.win = \
+ w32/winchildren.c
+endif
+
+kmk_DEFS.freebsd.x86 = CONFIG_WITHOUT_THREADS
+
+#kmk_LIBS.solaris = malloc
+#kmk_DEFS.solaris += HAVE_MALLINFO
+
+#
+# kmkbuiltin commands
+#
+kmk_DEFS += CONFIG_WITH_KMK_BUILTIN
+kmk_LIBS += $(LIB_KUTIL) #$(LIB_KDEP)
+kmk_SOURCES += \
+ kmkbuiltin.c \
+ kmkbuiltin/append.c \
+ kmkbuiltin/cat.c \
+ kmkbuiltin/chmod.c \
+ kmkbuiltin/cmp.c \
+ kmkbuiltin/cmp_util.c \
+ kmkbuiltin/cp.c \
+ kmkbuiltin/cp_utils.c \
+ kmkbuiltin/echo.c \
+ kmkbuiltin/expr.c \
+ kmkbuiltin/install.c \
+ kmkbuiltin/kDepIDB.c \
+ kmkbuiltin/kDepObj.c \
+ ../lib/kDep.c \
+ kmkbuiltin/md5sum.c \
+ kmkbuiltin/mkdir.c \
+ kmkbuiltin/mv.c \
+ kmkbuiltin/ln.c \
+ kmkbuiltin/printf.c \
+ kmkbuiltin/redirect.c \
+ kmkbuiltin/rm.c \
+ kmkbuiltin/rmdir.c \
+ $(if-expr $(KBUILD_TARGET) == win,kmkbuiltin/kSubmit.c) \
+ kmkbuiltin/sleep.c \
+ kmkbuiltin/test.c \
+ kmkbuiltin/touch.c \
+ \
+ kmkbuiltin/err.c
+kmk_SOURCES.win += \
+ kmkbuiltin/kill.c
+
+
+## @todo kmkbuiltin/redirect.c
+
+## Some profiling
+#kmk_SOURCES += kbuildprf.c
+#kmk_DEFS += open=prf_open read=prf_read lseek=prf_lseek close=prf_close
+##kmk_DEFS += KMK_PRF=1
+##kmkmissing_DEFS += KMK_PRF=1
+
+#
+# Standalone kmkbuiltin commands.
+#
+PROGRAMS += \
+ kmk_append \
+ kmk_cat \
+ kmk_chmod \
+ kmk_cp \
+ kmk_cmp \
+ kmk_echo \
+ kmk_expr \
+ kmk_md5sum \
+ kmk_mkdir \
+ kmk_mv \
+ kmk_install \
+ kmk_ln \
+ kmk_printf \
+ kmk_redirect \
+ kmk_rm \
+ kmk_rmdir \
+ kmk_sleep \
+ kmk_test \
+ kmk_touch \
+ kDepIDB \
+ kDepObj
+PROGRAMS.win += \
+ kmk_kill
+
+kmk_append_TEMPLATE = BIN-KMK-BUILTIN
+kmk_append_INCS = .
+kmk_append_SOURCES = \
+ kmkbuiltin/append.c
+
+kmk_cat_TEMPLATE = BIN-KMK-BUILTIN
+kmk_cat_SOURCES = \
+ kmkbuiltin/cat.c
+
+kmk_chmod_TEMPLATE = BIN-KMK-BUILTIN
+kmk_chmod_SOURCES = \
+ kmkbuiltin/chmod.c
+
+kmk_cmp_TEMPLATE = BIN-KMK-BUILTIN
+kmk_cmp_SOURCES = \
+ kmkbuiltin/cmp.c \
+ kmkbuiltin/cmp_util.c
+
+kmk_cp_TEMPLATE = BIN-KMK-BUILTIN
+kmk_cp_SOURCES = \
+ kmkbuiltin/cp.c \
+ kmkbuiltin/cp_utils.c \
+ kmkbuiltin/cmp_util.c
+
+kmk_echo_TEMPLATE = BIN-KMK-BUILTIN
+kmk_echo_SOURCES = \
+ kmkbuiltin/echo.c
+
+kmk_expr_TEMPLATE = BIN-KMK-BUILTIN
+kmk_expr_SOURCES = \
+ kmkbuiltin/expr.c
+
+kmk_install_TEMPLATE = BIN-KMK-BUILTIN
+kmk_install_SOURCES = \
+ kmkbuiltin/install.c
+
+kmk_kill_TEMPLATE = BIN-KMK-BUILTIN
+kmk_kill_SOURCES = \
+ kmkbuiltin/kill.c
+
+kmk_ln_TEMPLATE = BIN-KMK-BUILTIN
+kmk_ln_SOURCES = \
+ kmkbuiltin/ln.c
+
+kmk_mkdir_TEMPLATE = BIN-KMK-BUILTIN
+kmk_mkdir_SOURCES = \
+ kmkbuiltin/mkdir.c
+
+kmk_md5sum_TEMPLATE = BIN-KMK-BUILTIN
+kmk_md5sum_SOURCES = \
+ kmkbuiltin/md5sum.c
+kmk_md5sum_LIBS = $(LIB_KUTIL)
+
+kmk_mv_TEMPLATE = BIN-KMK-BUILTIN
+kmk_mv_SOURCES = \
+ kmkbuiltin/mv.c
+
+kmk_printf_TEMPLATE = BIN-KMK-BUILTIN
+kmk_printf_SOURCES = \
+ kmkbuiltin/printf.c
+
+kmk_rm_TEMPLATE = BIN-KMK-BUILTIN
+kmk_rm_SOURCES = \
+ kmkbuiltin/rm.c
+
+kmk_redirect_TEMPLATE = BIN-KMK-BUILTIN
+kmk_redirect_SOURCES = \
+ kmkbuiltin/redirect.c
+kmk_redirect_SOURCES.win = \
+ ../lib/startuphacks-win.c
+
+kmk_rmdir_TEMPLATE = BIN-KMK-BUILTIN
+kmk_rmdir_SOURCES = \
+ kmkbuiltin/rmdir.c
+
+kmk_sleep_TEMPLATE = BIN-KMK-BUILTIN
+kmk_sleep_SOURCES = \
+ kmkbuiltin/sleep.c
+
+kmk_test_TEMPLATE = BIN-KMK-BUILTIN
+kmk_test_SOURCES = \
+ kmkbuiltin/test.c
+
+kmk_touch_TEMPLATE = BIN-KMK-BUILTIN
+kmk_touch_SOURCES = \
+ kmkbuiltin/touch.c
+
+kDepIDB_TEMPLATE = BIN-KMK-BUILTIN
+kDepIDB_INCS = .
+kDepIDB_LIBS = $(LIB_KDEP) $(LIB_KUTIL)
+kDepIDB_SOURCES = \
+ kmkbuiltin/kDepIDB.c
+
+kDepObj_TEMPLATE = BIN-KMK-BUILTIN
+kDepObj_INCS = .
+kDepObj_LIBS = $(LIB_KDEP) $(LIB_KUTIL)
+kDepObj_SOURCES = \
+ kmkbuiltin/kDepObj.c
+
+
+#
+# kmk_gmake - almost plain GNU Make.
+#
+PROGRAMS += kmk_gmake
+
+kmk_gmake_TEMPLATE = BIN-KMK
+kmk_gmake_DEFS = \
+ HAVE_CONFIG_H \
+ CONFIG_WITH_TOUPPER_TOLOWER \
+ EXPERIMENTAL
+# NO_ARCHIVES
+
+kmk_gmake_SOURCES = \
+ main.c \
+ read.c \
+ hash.c \
+ strcache.c \
+ variable.c \
+ ar.c \
+ arscan.c \
+ commands.c \
+ default.c \
+ dir.c \
+ expand.c \
+ file.c \
+ function.c \
+ implicit.c \
+ job.c \
+ misc.c \
+ output.c \
+ remake.c \
+ rule.c \
+ signame.c \
+ version.c \
+ vpath.c \
+ remote-stub.c \
+ guile.c \
+ load.c
+ifeq ($(KBUILD_TARGET),win)
+ kmk_gmake_SOURCES += \
+ w32/w32os.c
+else
+ kmk_gmake_SOURCES += \
+ posixos.c
+endif
+
+ifndef CONFIG_NEW_WIN_CHILDREN
+kmk_gmake_SOURCES.win = \
+ w32/subproc/sub_proc.c
+else
+kmk_gmake_SOURCES.win = \
+ w32/winchildren.c
+endif
+
+
+#
+# kmk_fmake - Faster GNU Make.
+#
+ifeq ($(USER),bird) # for experimental purposes only.
+PROGRAMS += kmk_fgmake
+endif
+
+kmk_fgmake_TEMPLATE = BIN-KMK
+kmk_fgmake_DEFS = \
+ HAVE_CONFIG_H \
+ NO_ARCHIVES \
+ CONFIG_WITH_TOUPPER_TOLOWER \
+ EXPERIMENTAL \
+ \
+ CONFIG_WITH_ALLOC_CACHES \
+ CONFIG_WITH_LAZY_DEPS_VARS \
+ CONFIG_WITH_STRCACHE2 \
+ CONFIG_WITH_VALUE_LENGTH \
+ CONFIG_WITH_RDONLY_VARIABLE_VALUE
+# TODO ?
+# CONFIG_WITH_PRINT_STATS_SWITCH \
+# CONFIG_WITH_EXTENDED_NOTPARALLEL \
+
+kmk_fgmake_SOURCES = \
+ main.c \
+ read.c \
+ hash.c \
+ strcache.c \
+ strcache2.c \
+ variable.c \
+ ar.c \
+ arscan.c \
+ commands.c \
+ default.c \
+ dir.c \
+ expand.c \
+ file.c \
+ function.c \
+ implicit.c \
+ job.c \
+ misc.c \
+ output.c \
+ alloccache.c \
+ remake.c \
+ rule.c \
+ signame.c \
+ version.c \
+ vpath.c \
+ remote-stub.c \
+ guile.c \
+ load.c
+ifeq ($(KBUILD_TARGET),win)
+ kmk_fgmake_SOURCES += \
+ w32/w32os.c
+# @todo: dir-nt-bird.c for fgmake
+else
+ kmk_fgmake_SOURCES += \
+ posixos.c
+endif
+
+kmk_fgmake_SOURCES.win = \
+ w32/subproc/sub_proc.c
+
+
+#
+# tstFileInfo
+#
+PROGRAMS.win += tstFileInfo
+tstFileInfo_TEMPLATE = BIN
+tstFileInfo_SOURCES = w32/tstFileInfo.c
+
+#
+# tstFileInfo
+#
+PROGRAMS.win += tstFtsFake
+tstFtsFake_TEMPLATE = BIN-KMK
+tstFtsFake_NOINST = 1
+tstFtsFake_DEFS = USE_OLD_FTS
+tstFtsFake_SOURCES = ../lib/nt/tstNtFts.c
+
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
+#
+# Use checked in config.h instead of running ./Configure for it.
+#
+kmk_config.h.$(KBUILD_TARGET) := $(kmk_DEFPATH)/config.h.$(KBUILD_TARGET)
+$(kmk_0_OUTDIR)/config.h: $(kmk_config.h.$(KBUILD_TARGET))
+ $(MKDIR) -p $(dir $@)
+ $(CP) $^ $@
+
+#
+# Some missing headers.
+#
+if1of ($(KBUILD_TARGET), win nt)
+$(kmk_0_OUTDIR)/fts.h: $(MAKEFILE) | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(APPEND) -t "$@" "#include <nt/fts-nt.h>"
+else
+$(kmk_0_OUTDIR)/fts.h: $(kmk_DEFPATH)/kmkbuiltin/ftsfake.h | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(CP) $^ $@
+endif
+
+$(kmk_0_OUTDIR)/unistd.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+$(kmk_0_OUTDIR)/sysexits.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+$(kmk_0_OUTDIR)/inttypes.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+$(kmk_0_OUTDIR)/paths.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+$(kmk_0_OUTDIR)/pwd.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+$(kmk_0_OUTDIR)/grp.h: | $(call DIRDEP,$(kmk_0_OUTDIR))
+ $(ECHO_EXT) > $@
+
+
+#
+# Some tests.
+#
+parallel: parallel_1 parallel_2 parallel_3 parallel_4 parallel_5
+parallel_1 parallel_2 parallel_3 parallel_4 parallel_5:
+ echo $@_start ; sleep 1; echo $@_done
+
+my_test:
+ echo "1"
+ echo "2"
+ echo "3"
+ echo "4"
+
+
+#
+# Shell execution tests.
+#
+test_shell: test_shell_quoting test_shell_double_quoting test_shell_newline
+
+# shell double and single quoting check (was busted on windows in 3.81).
+test_shell_quoting:
+ $(ECHO_EXT) "double quoted sTrInG"
+ $(ECHO_EXT) "double quoted sTrInG" | $(SED_EXT) -e "s/sTrInG/string/g"
+ $(ECHO_EXT) 'single quoted sTrInG' | $(SED_EXT) -e 's/sTrInG/string/g'
+ $(ECHO) "This string should not be printed with double quotes."
+ $(ECHO) 'This string should not be printed with single quotes.'
+ ( echo " #define PWD \"`pwd`\""; )
+
+test_shell_double_quoting:
+ $(ECHO_EXT) "foo foo foo" | $(SED_EXT) -e \
+ "s/foo/$@/" -e \
+ "s/foo/works/" \
+ -e "s/foo/\!/"
+
+test_shell_double_quoting2:
+ $(ECHO_EXT) "foo foo foo" | $(SED_EXT) -e \
+ "s/foo/$@/" -e \
+ "s/foo/works/" \
+ -e\
+ "s/foo/\!/"
+
+# when using batch mode shell, the newline got escaped twice and spoiling everything.
+test_shell_newline:
+ $(ECHO_EXT) "foo foo foo" | $(SED_EXT) -e \
+ 's/foo/$@/' -e \
+ 's/foo/works/' \
+ -e 's/foo/\!/'
+
+
+test_stack:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-stack.kmk
+
+test_math:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-math.kmk
+
+test_if1of:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-if1of.kmk
+
+test_local:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-local.kmk
+
+test_includedep:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-includedep.kmk
+
+test_root:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-root.kmk
+
+test_2ndtargetexp:
+ $(MAKE) -f $(kmk_DEFPATH)/testcase-2ndtargetexp.kmk
+
+test_30_continued_on_failure_worker:
+ this_executable_does_not_exist.exe
+ echo "We shouldn't see this..."
+
+test_30_continued_on_failure:
+ $(MAKE) -f $(MAKEFILE) test_30_continued_on_failure_worker; \
+ RC=$$?; \
+ if test $${RC} -ne 2; then \
+ echo "$@: FAILED - exit code $${RC} instead of 2."; \
+ exit 1; \
+ else \
+ echo "$@: SUCCESS"; \
+ fi
+
+test_lazy_deps_vars:
+ $(MAKE) -C $(kmk_DEFPATH) -f testcase-lazy-deps-vars.kmk
+
+
+test_all: \
+ test_math \
+ test_stack \
+ test_shell \
+ test_if1of \
+ test_local \
+ test_root \
+ test_includedep \
+ test_2ndtargetexp \
+ test_30_continued_on_failure \
+ test_lazy_deps_vars
+
+
diff --git a/src/kmk/Makefile.os2 b/src/kmk/Makefile.os2
new file mode 100644
index 0000000..5f61dde
--- /dev/null
+++ b/src/kmk/Makefile.os2
@@ -0,0 +1,47 @@
+# $Id: Makefile.os2 200 2004-12-17 14:05:38Z bird $
+
+OBJDIR = objdir/OS2.libc
+#OBJDIR = .
+SRC = ar.c arscan.c commands.c default.c dir.c expand.c file.c \
+ function.c implicit.c job.c main.c \
+ misc.c read.c remake.c rule.c signame.c \
+ variable.c version.c vpath.c hash.c \
+ getopt.c getopt1.c remote-stub.c
+OBJS = $(addprefix $(OBJDIR)/, $(SRC:.c=.obj))
+CFLAGS = -Zomf -g -Wall -I$(OBJDIR) -I. -DCONFIG_NO_DEFAULT_SUFFIXES \
+ -DCONFIG_NO_DEFAULT_PATTERN_RULES -DCONFIG_NO_DEFAULT_TERMINAL_RULES \
+ -DCONFIG_NO_DEFAULT_SUFFIX_RULES -DCONFIG_NO_DEFAULT_VARIABLES
+ifndef DEBUG
+CFLAGS += -O3
+endif
+#-DMAKE_DLLSHELL
+
+all: $(OBJDIR) $(OBJDIR)/make-new.exe
+
+clean:
+ rm -f $(OBJS) $(OBJDIR)/make-new.exe $(OBJDIR)/config.h
+
+$(OBJDIR)/make-new.exe: $(OBJDIR)/config.h $(OBJS)
+ gcc -g $(CFLAGS) -Zhigh-mem -Zstack 1024 -o $@ $(OBJS)
+
+$(OBJDIR)/%.obj : %.c
+ gcc -c $(CFLAGS) -o $@ -DHAVE_CONFIG_H $<
+
+$(OBJDIR)/config.h: config.h.os2
+ cp $< $@
+
+$(OBJDIR):
+ mkdir.exe -p $@
+
+test:
+ echo "1"
+ echo "2"
+ echo "3"
+ echo "4"
+
+parallel: parallel_1 parallel_2 parallel_3 parallel_4 parallel_5
+
+parallel_1 parallel_2 parallel_3 parallel_4 parallel_5:
+ echo $@_start ; sleep 1; echo $@_done
+
+
diff --git a/src/kmk/NEWS b/src/kmk/NEWS
new file mode 100644
index 0000000..0b04245
--- /dev/null
+++ b/src/kmk/NEWS
@@ -0,0 +1,1540 @@
+GNU make NEWS -*-indented-text-*-
+ History of user-visible changes.
+ 10 June 2016
+
+See the end of this file for copyrights and conditions.
+
+All changes mentioned here are more fully described in the GNU make
+manual, which is contained in this distribution as the file doc/make.texi.
+See the README file and the GNU make manual for instructions for
+reporting bugs.
+
+Version 4.2.1 (10 Jun 2016)
+
+A complete list of bugs fixed in this version is available here:
+h
+ttp://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=107&set=custom
+
+This release is a bug-fix release.
+
+
+Version 4.2 (22 May 2016)
+
+A complete list of bugs fixed in this version is available here:
+
+http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=106&set=custom
+
+* New variable: $(.SHELLSTATUS) is set to the exit status of the last != or
+ $(shell ...) function invoked in this instance of make. This will be "0" if
+ successful or not "0" if not successful. The variable value is unset if no
+ != or $(shell ...) function has been invoked.
+
+* The $(file ...) function can now read from a file with $(file <FILE).
+ The function is expanded to the contents of the file. The contents are
+ expanded verbatim except that the final newline, if any, is stripped.
+
+* The makefile line numbers shown by GNU make now point directly to the
+ specific line in the recipe where the failure or warning occurred.
+ Sample changes suggested by Brian Vandenberg <phantall@gmail.com>
+
+* The interface to GNU make's "jobserver" is stable as documented in the
+ manual, for tools which may want to access it.
+
+ WARNING: Backward-incompatibility! The internal-only command line option
+ --jobserver-fds has been renamed for publishing, to --jobserver-auth.
+
+* The amount of parallelism can be determined by querying MAKEFLAGS, even when
+ the job server is enabled (previously MAKEFLAGS would always contain only
+ "-j", with no number, when job server was enabled).
+
+* VMS-specific changes:
+
+ * Perl test harness now works.
+
+ * Full support for converting Unix exit status codes to VMS exit status
+ codes. BACKWARD INCOMPATIBILITY Notice: On a child failure the VMS exit
+ code is now the encoded Unix exit status that Make usually generates, not
+ the VMS exit status of the child.
+
+
+Version 4.1 (05 Oct 2014)
+
+A complete list of bugs fixed in this version is available here:
+
+http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=105&set=custom
+
+* New variables: $(MAKE_TERMOUT) and $(MAKE_TERMERR) are set to non-empty
+ values if stdout or stderr, respectively, are believed to be writing to a
+ terminal. These variables are exported by default.
+
+* Allow a no-text-argument form of the $(file ...) function. Without a text
+ argument nothing is written to the file: it is simply opened in the
+ requested mode, then closed again.
+
+* Change the fatal error for mixed explicit and implicit rules, that was
+ introduced in GNU make 3.82, to a non-fatal error. However, this syntax is
+ still deprecated and may return to being illegal in a future version of GNU
+ make. Makefiles that rely on this syntax should be fixed.
+ See https://savannah.gnu.org/bugs/?33034
+
+* VMS-specific changes:
+
+ * Support for library files added, including support for using the GNV ar
+ utility.
+
+ * Partial support for properly encoding Unix exit status codes into VMS exit
+ status codes.
+
+ WARNING: Backward-incompatibility! These are different exit status codes
+ than Make exited with in the past.
+
+ * Macros to hold the current make command are set up to translate the
+ argv[0] string to a VMS format path name and prefix it with "MCR " so that
+ the macro has a space in it.
+
+ WARNING: Backward-incompatibility! This may break complex makefiles that
+ do processing on those macros. This is unlikely because so much in that
+ area was not and is still not currently working on VMS, it is unlikely to
+ find such a complex makefile, so this is more likely to impact
+ construction of a future makefile.
+
+ * A command file is always used to run the commands for a recipe.
+
+ WARNING: Backward-incompatibility! Running the make self tests has
+ exposed that there are significant differences in behavior when running
+ with the command file mode. It is unknown if this will be noticed by most
+ existing VMS makefiles.
+
+Version 4.0 (09 Oct 2013)
+
+A complete list of bugs fixed in this version is available here:
+
+http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=101&set=custom
+
+* WARNING: Backward-incompatibility!
+ If .POSIX is specified, then make adheres to the POSIX backslash/newline
+ handling requirements, which introduces the following changes to the
+ standard backslash/newline handling in non-recipe lines:
+ * Any trailing space before the backslash is preserved
+ * Each backslash/newline (plus subsequent whitespace) is converted to a
+ single space
+
+* New feature: GNU Guile integration
+ This version of GNU make can be compiled with GNU Guile integration.
+ GNU Guile serves as an embedded extension language for make.
+ See the "Guile Function" section in the GNU Make manual for details.
+ Currently GNU Guile 1.8 and 2.0+ are supported. In Guile 1.8 there is no
+ support for internationalized character sets. In Guile 2.0+, scripts can be
+ encoded in UTF-8.
+
+* New command line option: --output-sync (-O) enables grouping of output by
+ target or by recursive make. This is useful during parallel builds to avoid
+ mixing output from different jobs together giving hard-to-understand
+ results. Original implementation by David Boyce <dsb@boyski.com>.
+ Reworked and enhanced by Frank Heckenbach <f.heckenbach@fh-soft.de>.
+ Windows support by Eli Zaretskii <eliz@gnu.org>.
+
+* New command line option: --trace enables tracing of targets. When enabled
+ the recipe to be invoked is printed even if it would otherwise be suppressed
+ by .SILENT or a "@" prefix character. Also before each recipe is run the
+ makefile name and linenumber where it was defined are shown as well as the
+ prerequisites that caused the target to be considered out of date.
+
+* New command line option argument: --debug now accepts a "n" (none) flag
+ which disables all debugging settings that are currently enabled.
+
+* New feature: The "job server" capability is now supported on Windows.
+ Implementation contributed by Troy Runkel <Troy.Runkel@mathworks.com>
+
+* New feature: The .ONESHELL capability is now supported on Windows. Support
+ added by Eli Zaretskii <eliz@gnu.org>.
+
+* New feature: "!=" shell assignment operator as an alternative to the
+ $(shell ...) function. Implemented for compatibility with BSD makefiles.
+ Note there are subtle differences between "!=" and $(shell ...). See the
+ description in the GNU make manual.
+ WARNING: Backward-incompatibility!
+ Variables ending in "!" previously defined as "variable!= value" will now be
+ interpreted as shell assignment. Change your assignment to add whitespace
+ between the "!" and "=": "variable! = value"
+
+* New feature: "::=" simple assignment operator as defined by POSIX in 2012.
+ This operator has identical functionality to ":=" in GNU make, but will be
+ portable to any implementation of make conforming to a sufficiently new
+ version of POSIX (see http://austingroupbugs.net/view.php?id=330). It is
+ not necessary to define the .POSIX target to access this operator.
+
+* New feature: Loadable objects
+ This version of GNU make contains a "technology preview": the ability to
+ load dynamic objects into the make runtime. These objects can be created by
+ the user and can add extended functionality, usable by makefiles.
+
+* New function: $(file ...) writes to a file.
+
+* New variable: $(GNUMAKEFLAGS) will be parsed for make flags, just like
+ MAKEFLAGS is. It can be set in the environment or the makefile, containing
+ GNU make-specific flags to allow your makefile to be portable to other
+ versions of make. Once this variable is parsed, GNU make will set it to the
+ empty string so that flags will not be duplicated on recursion.
+
+* New variable: `MAKE_HOST' gives the name of the host architecture
+ make was compiled for. This is the same value you see after 'Built for'
+ when running 'make --version'.
+
+* Behavior of MAKEFLAGS and MFLAGS is more rigorously defined. All simple
+ flags are grouped together in the first word of MAKEFLAGS. No options that
+ accept arguments appear in the first word. If no simple flags are present
+ MAKEFLAGS begins with a space. Flags with both short and long versions
+ always use the short versions in MAKEFLAGS. Flags are listed in
+ alphabetical order using ASCII ordering. MFLAGS never begins with "- ".
+
+* Setting the -r and -R options in MAKEFLAGS inside a makefile now works as
+ expected, removing all built-in rules and variables, respectively.
+
+* If a recipe fails, the makefile name and linenumber of the recipe are shown.
+
+* A .RECIPEPREFIX setting is remembered per-recipe and variables expanded
+ in that recipe also use that recipe prefix setting.
+
+* In -p output, .RECIPEPREFIX settings are shown and all target-specific
+ variables are output as if in a makefile, instead of as comments.
+
+* On MS-Windows, recipes that use ".." quoting will no longer force
+ invocation of commands via temporary batch files and stock Windows
+ shells, they will be short-circuited and invoked directly. (In
+ other words, " is no longer a special character for stock Windows
+ shells.) This avoids hitting shell limits for command length when
+ quotes are used, but nothing else in the command requires the shell.
+ This change could potentially mean some minor incompatibilities in
+ behavior when the recipe uses quoted string on shell command lines.
+
+
+Version 3.82 (28 Jul 2010)
+
+A complete list of bugs fixed in this version is available here:
+
+http://sv.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=104&set=custom
+
+* Compiling GNU make now requires a conforming ISO C 1989 compiler and
+ standard runtime library.
+
+* WARNING: Backward-incompatibility!
+ The POSIX standard for make was changed in the 2008 version in a
+ fundamentally incompatible way: make is required to invoke the shell as if
+ the '-e' flag were provided. Because this would break many makefiles that
+ have been written to conform to the original text of the standard, the
+ default behavior of GNU make remains to invoke the shell with simply '-c'.
+ However, any makefile specifying the .POSIX special target will follow the
+ new POSIX standard and pass '-e' to the shell. See also .SHELLFLAGS
+ below.
+
+* WARNING: Backward-incompatibility!
+ The '$?' variable now contains all prerequisites that caused the target to
+ be considered out of date, even if they do not exist (previously only
+ existing targets were provided in $?).
+
+* WARNING: Backward-incompatibility!
+ Wildcards were not documented as returning sorted values, but the results
+ have been sorted up until this release.. If your makefiles require sorted
+ results from wildcard expansions, use the $(sort ...) function to request
+ it explicitly.
+
+* WARNING: Backward-incompatibility!
+ As a result of parser enhancements, three backward-compatibility issues
+ exist: first, a prerequisite containing an "=" cannot be escaped with a
+ backslash any longer. You must create a variable containing an "=" and
+ use that variable in the prerequisite. Second, variable names can no
+ longer contain whitespace, unless you put the whitespace in a variable and
+ use the variable. Third, in previous versions of make it was sometimes
+ not flagged as an error for explicit and pattern targets to appear in the
+ same rule. Now this is always reported as an error.
+
+* WARNING: Backward-incompatibility!
+ The pattern-specific variables and pattern rules are now applied in the
+ shortest stem first order instead of the definition order (variables
+ and rules with the same stem length are still applied in the definition
+ order). This produces the usually-desired behavior where more specific
+ patterns are preferred. To detect this feature search for 'shortest-stem'
+ in the .FEATURES special variable.
+
+* WARNING: Backward-incompatibility!
+ The library search behavior has changed to be compatible with the standard
+ linker behavior. Prior to this version for prerequisites specified using
+ the -lfoo syntax make first searched for libfoo.so in the current
+ directory, vpath directories, and system directories. If that didn't yield
+ a match, make then searched for libfoo.a in these directories. Starting
+ with this version make searches first for libfoo.so and then for libfoo.a
+ in each of these directories in order.
+
+* New command line option: --eval=STRING causes STRING to be evaluated as
+ makefile syntax (akin to using the $(eval ...) function). The evaluation
+ is performed after all default rules and variables are defined, but before
+ any makefiles are read.
+
+* New special variable: .RECIPEPREFIX allows you to reset the recipe
+ introduction character from the default (TAB) to something else. The
+ first character of this variable value is the new recipe introduction
+ character. If the variable is set to the empty string, TAB is used again.
+ It can be set and reset at will; recipes will use the value active when
+ they were first parsed. To detect this feature check the value of
+ $(.RECIPEPREFIX).
+
+* New special variable: .SHELLFLAGS allows you to change the options passed
+ to the shell when it invokes recipes. By default the value will be "-c"
+ (or "-ec" if .POSIX is set).
+
+* New special target: .ONESHELL instructs make to invoke a single instance
+ of the shell and provide it with the entire recipe, regardless of how many
+ lines it contains. As a special feature to allow more straightforward
+ conversion of makefiles to use .ONESHELL, any recipe line control
+ characters ('@', '+', or '-') will be removed from the second and
+ subsequent recipe lines. This happens _only_ if the SHELL value is deemed
+ to be a standard POSIX-style shell. If not, then no interior line control
+ characters are removed (as they may be part of the scripting language used
+ with the alternate SHELL).
+
+* New variable modifier 'private': prefixing a variable assignment with the
+ modifier 'private' suppresses inheritance of that variable by
+ prerequisites. This is most useful for target- and pattern-specific
+ variables.
+
+* New make directive: 'undefine' allows you to undefine a variable so that
+ it appears as if it was never set. Both $(flavor) and $(origin) functions
+ will return 'undefined' for such a variable. To detect this feature search
+ for 'undefine' in the .FEATURES special variable.
+
+* The parser for variable assignments has been enhanced to allow multiple
+ modifiers ('export', 'override', 'private') on the same line as variables,
+ including define/endef variables, and in any order. Also, it is possible
+ to create variables and targets named as these modifiers.
+
+* The 'define' make directive now allows a variable assignment operator
+ after the variable name, to allow for simple, conditional, or appending
+ multi-line variable assignment.
+
+* VMS-specific changes:
+
+ * Michael Gehre (at VISTEC-SEMI dot COM) supplied a fix for a problem with
+ timestamps of object modules in OLBs. The timestamps were not correctly
+ adjusted to GMT based time, if the local VMS time was using a daylight
+ saving algorithm and if daylight saving was switched off.
+
+ * John Eisenbraun (at HP dot COM) supplied fixes and and an enhancement to
+ append output redirection in action lines.
+
+ * Rework of ctrl+c and ctrl+y handling.
+
+ * Fix a problem with cached strings, which showed on case-insensitive file
+ systems.
+
+ * Build fixes for const-ified code in VMS specific sources.
+
+ * A note on appending the redirected output. With this change, a simple
+ mechanism is implemented to make ">>" work in action lines. In VMS
+ there is no simple feature like ">>" to have DCL command or program
+ output redirected and appended to a file. GNU make for VMS already
+ implements the redirection of output. If such a redirection is detected,
+ an ">" on the action line, GNU make creates a DCL command procedure to
+ execute the action and to redirect its output. Based on that, now ">>"
+ is also recognized and a similar but different command procedure is
+ created to implement the append. The main idea here is to create a
+ temporary file which collects the output and which is appended to the
+ wanted output file. Then the temporary file is deleted. This is all done
+ in the command procedure to keep changes in make small and simple. This
+ obviously has some limitations but it seems good enough compared with
+ the current ">" implementation. (And in my opinion, redirection is not
+ really what GNU make has to do.) With this approach, it may happen that
+ the temporary file is not yet appended and is left in SYS$SCRATCH.
+ The temporary file names look like "CMDxxxxx.". Any time the created
+ command procedure can not complete, this happens. Pressing Ctrl+Y to
+ abort make is one case. In case of Ctrl+Y the associated command
+ procedure is left in SYS$SCRATCH as well. Its name is CMDxxxxx.COM.
+
+ * Change in the Ctrl+Y handling. The CtrlY handler now uses $delprc to
+ delete all children. This way also actions with DCL commands will be
+ stopped. As before the CtrlY handler then sends SIGQUIT to itself,
+ which is handled in common code.
+
+ * Change in deleteing temporary command files. Temporary command files
+ are now deleted in the vms child termination handler. That deletes
+ them even if a Ctrl+C was pressed.
+
+ * The behavior of pressing Ctrl+C is not changed. It still has only an
+ effect, after the current action is terminated. If that doesn't happen
+ or takes too long, Ctrl+Y should be used instead.
+
+
+Version 3.81 (01 Apr 2006)
+
+* GNU make is ported to OS/2.
+
+* GNU make is ported to MinGW. The MinGW build is only supported by
+ the build_w32.bat batch file; see the file README.W32 for more
+ details.
+
+* WARNING: Future backward-incompatibility!
+ Up to and including this release, the '$?' variable does not contain
+ any prerequisite that does not exist, even though that prerequisite
+ might have caused the target to rebuild. Starting with the _next_
+ release of GNU make, '$?' will contain all prerequisites that caused
+ the target to be considered out of date.
+ See http://savannah.gnu.org/bugs/?16051
+
+* WARNING: Backward-incompatibility!
+ GNU make now implements a generic "second expansion" feature on the
+ prerequisites of both explicit and implicit (pattern) rules. In order
+ to enable this feature, the special target '.SECONDEXPANSION' must be
+ defined before the first target which takes advantage of it. If this
+ feature is enabled then after all rules have been parsed the
+ prerequisites are expanded again, this time with all the automatic
+ variables in scope. This means that in addition to using standard
+ SysV $$@ in prerequisites lists, you can also use complex functions
+ such as $$(notdir $$@) etc. This behavior applies to implicit rules,
+ as well, where the second expansion occurs when the rule is matched.
+ However, this means that when '.SECONDEXPANSION' is enabled you must
+ double-quote any "$" in your filenames; instead of "foo: boo$$bar" you
+ now must write "foo: foo$$$$bar". Note that the SysV $$@ etc. feature,
+ which used to be available by default, is now ONLY available when the
+ .SECONDEXPANSION target is defined. If your makefiles take advantage
+ of this SysV feature you will need to update them.
+
+* WARNING: Backward-incompatibility!
+ In order to comply with POSIX, the way in which GNU make processes
+ backslash-newline sequences in recipes has changed. If your makefiles
+ use backslash-newline sequences inside of single-quoted strings in
+ recipes you will be impacted by this change. See the GNU make manual
+ subsection "Splitting Recipe Lines" (node "Splitting Lines"), in
+ section "Recipe Syntax", chapter "Writing Recipe in Rules", for
+ details.
+
+* WARNING: Backward-incompatibility!
+ Some previous versions of GNU make had a bug where "#" in a function
+ invocation such as $(shell ...) was treated as a make comment. A
+ workaround was to escape these with backslashes. This bug has been
+ fixed: if your makefile uses "\#" in a function invocation the
+ backslash is now preserved, so you'll need to remove it.
+
+* New command line option: -L (--check-symlink-times). On systems that
+ support symbolic links, if this option is given then GNU make will
+ use the most recent modification time of any symbolic links that are
+ used to resolve target files. The default behavior remains as it
+ always has: use the modification time of the actual target file only.
+
+* The "else" conditional line can now be followed by any other valid
+ conditional on the same line: this does not increase the depth of the
+ conditional nesting, so only one "endif" is required to close the
+ conditional.
+
+* All pattern-specific variables that match a given target are now used
+ (previously only the first match was used).
+
+* Target-specific variables can be marked as exportable using the
+ "export" keyword.
+
+* In a recursive $(call ...) context, any extra arguments from the outer
+ call are now masked in the context of the inner call.
+
+* Implemented a solution for the "thundering herd" problem with "-j -l".
+ This version of GNU make uses an algorithm suggested by Thomas Riedl
+ <thomas.riedl@siemens.com> to track the number of jobs started in the
+ last second and artificially adjust GNU make's view of the system's
+ load average accordingly.
+
+* New special variables available in this release:
+ - .INCLUDE_DIRS: Expands to a list of directories that make searches
+ for included makefiles.
+ - .FEATURES: Contains a list of special features available in this
+ version of GNU make.
+ - .DEFAULT_GOAL: Set the name of the default goal make will
+ use if no goals are provided on the command line.
+ - MAKE_RESTARTS: If set, then this is the number of times this
+ instance of make has been restarted (see "How Makefiles Are Remade"
+ in the manual).
+ - New automatic variable: $| (added in 3.80, actually): contains all
+ the order-only prerequisites defined for the target.
+
+* New functions available in this release:
+ - $(lastword ...) returns the last word in the list. This gives
+ identical results as $(word $(words ...) ...), but is much faster.
+ - $(abspath ...) returns the absolute path (all "." and ".."
+ directories resolved, and any duplicate "/" characters removed) for
+ each path provided.
+ - $(realpath ...) returns the canonical pathname for each path
+ provided. The canonical pathname is the absolute pathname, with
+ all symbolic links resolved as well.
+ - $(info ...) prints its arguments to stdout. No makefile name or
+ line number info, etc. is printed.
+ - $(flavor ...) returns the flavor of a variable.
+ - $(or ...) provides a short-circuiting OR conditional: each argument
+ is expanded. The first true (non-empty) argument is returned; no
+ further arguments are expanded. Expands to empty if there are no
+ true arguments.
+ - $(and ...) provides a short-circuiting AND conditional: each
+ argument is expanded. The first false (empty) argument is
+ returned; no further arguments are expanded. Expands to the last
+ argument if all arguments are true.
+
+* Changes made for POSIX compatibility:
+ - Only touch targets (under -t) if they have a recipe.
+ - Setting the SHELL make variable does NOT change the value of the
+ SHELL environment variable given to programs invoked by make. As
+ an enhancement to POSIX, if you export the make variable SHELL then
+ it will be set in the environment, just as before.
+
+* On MS Windows systems, explicitly setting SHELL to a pathname ending
+ in "cmd" or "cmd.exe" (case-insensitive) will force GNU make to use
+ the DOS command interpreter in batch mode even if a UNIX-like shell
+ could be found on the system.
+
+* On VMS there is now support for case-sensitive filesystems such as ODS5.
+ See the README.VMS file for information.
+
+* Parallel builds (-jN) no longer require a working Bourne shell on
+ Windows platforms. They work even with the stock Windows shells, such
+ as cmd.exe and command.com.
+
+* Updated to autoconf 2.59, automake 1.9.5, and gettext 0.14.1. Users
+ should not be impacted.
+
+* New translations for Swedish, Chinese (simplified), Ukrainian,
+ Belarusian, Finnish, Kinyarwandan, and Irish. Many updated
+ translations.
+
+A complete list of bugs fixed in this version is available here:
+
+ http://savannah.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=103
+
+
+Version 3.80 (03 Oct 2002)
+
+* A new feature exists: order-only prerequisites. These prerequisites
+ affect the order in which targets are built, but they do not impact
+ the rebuild/no-rebuild decision of their dependents. That is to say,
+ they allow you to require target B be built before target A, without
+ requiring that target A will always be rebuilt if target B is updated.
+ Patch for this feature provided by Greg McGary <greg@mcgary.org>.
+
+* For compatibility with SysV make, GNU make now supports the peculiar
+ syntax $$@, $$(@D), and $$(@F) in the prerequisites list of a rule.
+ This syntax is only valid within explicit and static pattern rules: it
+ cannot be used in implicit (suffix or pattern) rules. Edouard G. Parmelan
+ <egp@free.fr> provided a patch implementing this feature; however, I
+ decided to implement it in a different way.
+
+* The argument to the "ifdef" conditional is now expanded before it's
+ tested, so it can be a constructed variable name.
+
+ Similarly, the arguments to "export" (when not used in a variable
+ definition context) and "unexport" are also now expanded.
+
+* A new function is defined: $(value ...). The argument to this
+ function is the _name_ of a variable. The result of the function is
+ the value of the variable, without having been expanded.
+
+* A new function is defined: $(eval ...). The arguments to this
+ function should expand to makefile commands, which will then be
+ evaluated as if they had appeared in the makefile. In combination
+ with define/endef multiline variable definitions this is an extremely
+ powerful capability. The $(value ...) function is also sometimes
+ useful here.
+
+* A new built-in variable is defined, $(MAKEFILE_LIST). It contains a
+ list of each makefile GNU make has read, or started to read, in the
+ order in which they were encountered. So, the last filename in the
+ list when a makefile is just being read (before any includes) is the
+ name of the current makefile.
+
+* A new built-in variable is defined: $(.VARIABLES). When it is
+ expanded it returns a complete list of variable names defined by all
+ makefiles at that moment.
+
+* A new command line option is defined, -B or --always-make. If
+ specified GNU make will consider all targets out-of-date even if they
+ would otherwise not be.
+
+* The arguments to $(call ...) functions were being stored in $1, $2,
+ etc. as recursive variables, even though they are fully expanded
+ before assignment. This means that escaped dollar signs ($$ etc.)
+ were not behaving properly. Now the arguments are stored as simple
+ variables. This may mean that if you added extra escaping to your
+ $(call ...) function arguments you will need to undo it now.
+
+* The variable invoked by $(call ...) can now be recursive: unlike other
+ variables it can reference itself and this will not produce an error
+ when it is used as the first argument to $(call ...) (but only then).
+
+* New pseudo-target .LOW_RESOLUTION_TIME, superseding the configure
+ option --disable-nsec-timestamps. You might need this if your build
+ process depends on tools like "cp -p" preserving time stamps, since
+ "cp -p" (right now) doesn't preserve the subsecond portion of a time
+ stamp.
+
+* Updated translations for French, Galician, German, Japanese, Korean,
+ and Russian. New translations for Croatian, Danish, Hebrew, and
+ Turkish.
+
+* Updated internationalization support to Gettext 0.11.5.
+ GNU make now uses Gettext's "external" feature, and does not include
+ any internationalization code itself. Configure will search your
+ system for an existing implementation of GNU Gettext (only GNU Gettext
+ is acceptable) and use it if it exists. If not, NLS will be disabled.
+ See ABOUT-NLS for more information.
+
+* Updated to autoconf 2.54 and automake 1.7. Users should not be impacted.
+
+* VMS-specific changes:
+
+ * In default.c define variable ARCH as IA64 for VMS on Itanium systems.
+
+ * In makefile.vms avoid name collision for glob and globfree.
+
+ * This is the VMS port of GNU Make done by Hartmut.Becker@compaq.com.
+
+ It is based on the specific version 3.77k and on 3.78.1. 3.77k was done
+ by Klaus Kämpf <kkaempf@rmi.de>, the code was based on the VMS port of
+ GNU Make 3.60 by Mike Moretti.
+
+ It was ported on OpenVMS/Alpha V7.1, DECC V5.7-006. It was re-build and
+ tested on OpenVMS/Alpha V7.2, OpenVMS/VAX 7.1 and 5.5-2. Different
+ versions of DECC were used. VAXC was tried: it fails; but it doesn't
+ seem worth to get it working. There are still some PTRMISMATCH warnings
+ during the compile. Although perl is working on VMS the test scripts
+ don't work. The function $shell is still missing.
+
+ There is a known bug in some of the VMS CRTLs. It is in the shipped
+ versions of VMS V7.2 and V7.2-1 and in the currently (October 1999)
+ available ECOs for VMS V7.1 and newer versions. It is fixed in versions
+ shipped with newer VMS versions and all ECO kits after October 1999. It
+ only shows up during the daylight saving time period (DST): stat()
+ returns a modification time 1 hour ahead. This results in GNU make
+ warning messages. For a just created source you will see:
+
+ $ gmake x.exe
+ gmake.exe;1: *** Warning: File 'x.c' has modification time in the future
+ (940582863 > 940579269)
+ cc /obj=x.obj x.c
+ link x.obj /exe=x.exe
+ gmake.exe;1: *** Warning: Clock skew detected. Your build may be
+ incomplete.
+
+
+A complete list of bugs fixed in this version is available here:
+
+ http://savannah.gnu.org/bugs/index.php?group=make&report_id=111&fix_release_id=102
+
+
+Version 3.79.1 (23 Jun 2000)
+
+* .SECONDARY with no prerequisites now prevents any target from being
+ removed because make thinks it's an intermediate file, not just those
+ listed in the makefile.
+
+* New configure option --disable-nsec-timestamps, but this was
+ superseded in later versions by the .LOW_RESOLUTION_TIME pseudo-target.
+
+Version 3.79 (04 Apr 2000)
+
+* GNU make optionally supports internationalization and locales via the
+ GNU gettext (or local gettext if suitable) package. See the ABOUT-NLS
+ file for more information on configuring GNU make for NLS.
+
+* Previously, GNU make quoted variables such as MAKEFLAGS and
+ MAKEOVERRIDES for proper parsing by the shell. This allowed them to
+ be used within make build scripts. However, using them there is not
+ proper behavior: they are meant to be passed to subshells via the
+ environment. Unfortunately the values were not quoted properly to be
+ passed through the environment. This meant that make didn't properly
+ pass some types of command line values to submakes.
+
+ With this version we change that behavior: now these variables are
+ quoted properly for passing through the environment, which is the
+ correct way to do it. If you previously used these variables
+ explicitly within a make rule you may need to re-examine your use for
+ correctness given this change.
+
+* A new pseudo-target .NOTPARALLEL is available. If defined, the
+ current makefile is run serially regardless of the value of -j.
+ However, submakes are still eligible for parallel execution.
+
+* The --debug option has changed: it now allows optional flags
+ controlling the amount and type of debugging output. By default only
+ a minimal amount information is generated, displaying the names of
+ "normal" targets (not makefiles) that were deemed out of date and in
+ need of being rebuilt.
+
+ Note that the -d option behaves as before: it takes no arguments and
+ all debugging information is generated.
+
+* The `-p' (print database) output now includes filename and linenumber
+ information for variable definitions, to aid debugging.
+
+* The wordlist function no longer reverses its arguments if the "start"
+ value is greater than the "end" value. If that's true, nothing is
+ returned.
+
+* Hartmut Becker provided many updates for the VMS port of GNU make.
+ See the README.VMS file for more details.
+
+* VMS-specific changes:
+
+ * Fix a problem with automatically remaking makefiles. GNU make uses an
+ execve to restart itself after a successful remake of the makefile. On
+ UNIX systems execve replaces the running program with a new one and
+ resets all signal handling to the default. On VMS execve creates a child
+ process, signal and exit handlers of the parent are still active, and,
+ unfortunately, corrupt the exit code from the child. Fix in job.c:
+ ignore SIGCHLD.
+
+ * Added some switches to reflect latest features of DECC. Modifications in
+ makefile.vms.
+
+ * Set some definitions to reflect latest features of DECC. Modifications in
+ config.h-vms (which is copied to config.h).
+
+ * Added extern strcmpi declaration to avoid 'implicitly declared' messages.
+ Modification in make.h.
+
+ * Default rule for C++, conditionals for gcc (GCC_IS_NATIVE) or DEC/Digital/
+ Compaq c/c++ compilers. Modifications in default.c.
+
+ * Usage of opendir() and friends, suppress file version. Modifications in
+ dir.c.
+
+ * Added VMS specific code to handle ctrl+c and ctrl+y to abort make.
+ Modifications in job.c.
+
+ * Added support to have case sensitive targets and dependencies but to
+ still use case blind file names. This is especially useful for Java
+ makefiles on VMS:
+
+ .SUFFIXES :
+ .SUFFIXES : .class .java
+ .java.class :
+ javac "$<
+ HelloWorld.class : HelloWorld.java
+
+ * A new macro WANT_CASE_SENSITIVE_TARGETS in config.h-vms was introduced.
+ It needs to be enabled to get this feature; default is disabled. The
+ macro HAVE_CASE_INSENSITIVE_FS must not be touched: it is still enabled.
+ Modifications in file.c and config.h-vms.
+
+ * Bootstrap make to start building make is still makefile.com, but make
+ needs to be re-made with a make to make a correct version: ignore all
+ possible warnings, delete all objects, rename make.exe to a different
+ name and run it.
+
+ * Made some minor modifications to the bootstrap build makefile.com.
+
+Version 3.78 (22 Sep 1999)
+
+* Two new functions, $(error ...) and $(warning ...) are available. The
+ former will cause make to fail and exit immediately upon expansion of
+ the function, with the text provided as the error message. The latter
+ causes the text provided to be printed as a warning message, but make
+ proceeds normally.
+
+* A new function $(call ...) is available. This allows users to create
+ their own parameterized macros and invoke them later. Original
+ implementation of this function was provided by Han-Wen Nienhuys
+ <hanwen@cs.uu.nl>.
+
+* A new function $(if ...) is available. It provides if-then-else
+ capabilities in a builtin function. Original implementation of this
+ function was provided by Han-Wen Nienhuys <hanwen@cs.uu.nl>.
+
+* Make defines a new variable, .LIBPATTERNS. This variable controls how
+ library dependency expansion (dependencies like ``-lfoo'') is performed.
+
+* Make accepts CRLF sequences as well as traditional LF, for
+ compatibility with makefiles created on other operating systems.
+
+* Make accepts a new option: -R, or --no-builtin-variables. This option
+ disables the definition of the rule-specific builtin variables (CC,
+ LD, AR, etc.). Specifying this option forces -r (--no-builtin-rules)
+ as well.
+
+* A "job server" feature, suggested by Howard Chu <hyc@highlandsun.com>.
+
+ On systems that support POSIX pipe(2) semantics, GNU make can now pass
+ -jN options to submakes rather than forcing them all to use -j1. The
+ top make and all its sub-make processes use a pipe to communicate with
+ each other to ensure that no more than N jobs are started across all
+ makes. To get the old behavior of -j back, you can configure make
+ with the --disable-job-server option.
+
+* The confusing term "dependency" has been replaced by the more accurate
+ and standard term "prerequisite", both in the manual and in all GNU make
+ output.
+
+* GNU make supports the "big archive" library format introduced in AIX 4.3.
+
+* GNU make supports large files on AIX, HP-UX, and IRIX. These changes
+ were provided by Paul Eggert <eggert@twinsun.com>. (Large file
+ support for Solaris and Linux was introduced in 3.77, but the
+ configuration had issues: these have also been resolved).
+
+* The Windows 95/98/NT (W32) version of GNU make now has native support
+ for the Cygnus Cygwin release B20.1 shell (bash).
+
+* The GNU make regression test suite, long available separately "under
+ the table", has been integrated into the release. You can invoke it
+ by running "make check" in the distribution. Note that it requires
+ Perl (either Perl 4 or Perl 5) to run.
+
+Version 3.77 (28 Jul 1998)
+
+* Implement BSD make's "?=" variable assignment operator. The variable
+ is assigned the specified value only if that variable is not already
+ defined.
+
+* Make defines a new variable, "CURDIR", to contain the current working
+ directory (after the -C option, if any, has been processed).
+ Modifying this variable has no effect on the operation of make.
+
+* Make defines a new default RCS rule, for new-style master file
+ storage: ``% :: RCS/%'' (note no ``,v'' suffix).
+
+ Make defines new default rules for DOS-style C++ file naming
+ conventions, with ``.cpp'' suffixes. All the same rules as for
+ ``.cc'' and ``.C'' suffixes are provided, along with LINK.cpp and
+ COMPILE.cpp macros (which default to the same value as LINK.cc and
+ COMPILE.cc). Note CPPFLAGS is still C preprocessor flags! You should
+ use CXXFLAGS to change C++ compiler flags.
+
+* A new feature, "target-specific variable values", has been added.
+ This is a large change so please see the appropriate sections of the
+ manual for full details. Briefly, syntax like this:
+
+ TARGET: VARIABLE = VALUE
+
+ defines VARIABLE as VALUE within the context of TARGET. This is
+ similar to SunOS make's "TARGET := VARIABLE = VALUE" feature. Note
+ that the assignment may be of any type, not just recursive, and that
+ the override keyword is available.
+
+ COMPATIBILITY: This new syntax means that if you have any rules where
+ the first or second dependency has an equal sign (=) in its name,
+ you'll have to escape them with a backslash: "foo : bar\=baz".
+ Further, if you have any dependencies which already contain "\=",
+ you'll have to escape both of them: "foo : bar\\\=baz".
+
+* A new appendix listing the most common error and warning messages
+ generated by GNU make, with some explanation, has been added to the
+ GNU make User's Manual.
+
+* Updates to the GNU make Customs library support (see README.customs).
+
+* Updates to the Windows 95/NT port from Rob Tulloh (see README.W32),
+ and to the DOS port from Eli Zaretski (see README.DOS).
+
+* VMS-specific changes:
+
+ * This is the VMS port of GNU Make.
+ It is based on the VMS port of GNU Make 3.60 by Mike Moretti.
+ This port was done by Klaus Kämpf <kkaempf@rmi.de>
+
+ * There is first-level support available from proGIS Software, Germany.
+ Visit their web-site at http://www.progis.de to get information
+ about other vms software and forthcoming updates to gnu make.
+
+ * /bin/sh style I/O redirection is supported. You can now write lines like
+ mcr sys$disk:[]program.exe < input.txt > output.txt &> error.txt
+
+ * Makefile variables are looked up in the current environment. You can set
+ symbols or logicals in DCL and evaluate them in the Makefile via
+ $(<name-of-symbol-or-logical>). Variables defined in the Makefile
+ override VMS symbols/logicals !
+
+ * Functions for file names are working now. See the GNU Make manual for
+ $(dir ...) and $(wildcard ...). Unix-style and VMS-style names are
+ supported as arguments.
+
+ * The default rules are set up for GNU C. Building an executable from a
+ single source file is as easy as 'make file.exe'.
+
+ * The variable $(ARCH) is predefined as ALPHA or VAX resp. Makefiles for
+ different VMS systems can now be written by checking $(ARCH) as in
+ ifeq ($(ARCH),ALPHA)
+ $(ECHO) "On the Alpha"
+ else
+ $(ECHO) "On the VAX"
+ endif
+
+ * Command lines of excessive length are correctly broken and written to a
+ batch file in sys$scratch for later execution. There's no limit to the
+ lengths of commands (and no need for .opt files :-) any more.
+
+ * Empty commands are handled correctly and don't end in a new DCL process.
+
+Version 3.76.1 (19 Sep 1997)
+
+* Small (but serious) bug fix. Quick rollout to get into the GNU source CD.
+
+Version 3.76 (16 Sep 1997)
+
+* GNU make now uses automake to control Makefile.in generation. This
+ should make it more consistent with the GNU standards.
+
+* VPATH functionality has been changed to incorporate the VPATH+ patch,
+ previously maintained by Paul Smith <psmith@baynetworks.com>. See the
+ manual.
+
+* Make defines a new variable, `MAKECMDGOALS', to contain the goals that
+ were specified on the command line, if any. Modifying this variable
+ has no effect on the operation of make.
+
+* A new function, `$(wordlist S,E,TEXT)', is available: it returns a
+ list of words from number S to number E (inclusive) of TEXT.
+
+* Instead of an error, detection of future modification times gives a
+ warning and continues. The warning is repeated just before GNU make
+ exits, so it is less likely to be lost.
+
+* Fix the $(basename) and $(suffix) functions so they only operate on
+ the last filename, not the entire string:
+
+ Command Old Result New Result
+ ------- ---------- ----------
+ $(basename a.b) a a
+ $(basename a.b/c) a a.b/c
+ $(suffix a.b) b b
+ $(suffix a.b/c) b/c <empty>
+
+* The $(strip) function now removes newlines as well as TABs and spaces.
+
+* The $(shell) function now changes CRLF (\r\n) pairs to a space as well
+ as newlines (\n).
+
+* Updates to the Windows 95/NT port from Rob Tulloh (see README.W32).
+
+* Eli Zaretskii has updated the port to 32-bit protected mode on MSDOS
+ and MS-Windows, building with the DJGPP v2 port of GNU C/C++ compiler
+ and utilities. See README.DOS for details, and direct all questions
+ concerning this port to Eli Zaretskii <eliz@is.elta.co.il> or DJ
+ Delorie <dj@delorie.com>.
+
+* VMS-specific changes:
+
+ * John W. Eaton has updated the VMS port to support libraries and VPATH.
+
+ * The cd command is supported if it's called as $(CD). This invokes
+ the 'builtin_cd' command which changes the directory.
+ Calling 'set def' doesn't do the trick, since a sub-shell is
+ spawned for this command, the directory is changed *in this sub-shell*
+ and the sub-shell ends.
+
+ * Libraries are not supported. They were in GNU Make 3.60 but somehow I
+ didn't care porting the code. If there is enough interest, I'll do it at
+ some later time.
+
+ * The variable $^ separates files with commas instead of spaces (It's the
+ natural thing to do for VMS).
+
+ * See defaults.c for VMS default suffixes and my definitions for default
+ rules and variables.
+
+ * The shell function is not implemented yet.
+
+ * Load average routines haven't been implemented for VMS yet.
+
+ * The default include directory for including other makefiles is
+ SYS$SYSROOT:[SYSLIB] (I don't remember why I didn't just use
+ SYS$LIBRARY: instead; maybe it wouldn't work that way).
+
+ * The default makefiles make looks for are: makefile.vms, gnumakefile,
+ makefile., and gnumakefile. .
+
+ * The stat() function and handling of time stamps in VMS is broken, so I
+ replaced it with a hack in vmsfunctions.c. I will provide a full rewrite
+ somewhere in the future. Be warned, the time resolution inside make is
+ less than what vms provides. This might be a problem on the faster Alphas.
+
+ * You can use a : in a filename only if you precede it with a backslash ('\').
+ E.g.- hobbes\:[bogas.files]
+
+ * Make ignores success, informational, or warning errors (-S-, -I-, or -W-).
+ But it will stop on -E- and -F- errors. (unless you do something
+ to override this in your makefile, or whatever).
+
+ * Remote stuff isn't implemented yet.
+
+ * Multiple line DCL commands, such as "if" statements, must be put inside
+ command files. You can run a command file by using \@.
+
+Version 3.75 (27 Aug 1996)
+
+* The directory messages printed by `-w' and implicitly in sub-makes,
+ are now omitted if Make runs no commands and has no other messages to print.
+
+* Make now detects files that for whatever reason have modification times
+ in the future and gives an error. Files with such impossible timestamps
+ can result from unsynchronized clocks, or archived distributions
+ containing bogus timestamps; they confuse Make's dependency engine
+ thoroughly.
+
+* The new directive `sinclude' is now recognized as another name for
+ `-include', for compatibility with some other Makes.
+
+* Aaron Digulla has contributed a port to AmigaDOS. See README.Amiga for
+ details, and direct all Amiga-related questions to <digulla@fh-konstanz.de>.
+
+* Rob Tulloh of Tivoli Systems has contributed a port to Windows NT or 95.
+ See README.W32 for details, and direct all Windows-related questions to
+ <rob_tulloh@tivoli.com>.
+
+* VMS-specific changes:
+
+ * Lots of default settings are adapted for VMS. See default.c.
+
+ * Long command lines are now converted to command files.
+
+ * Comma (',') as a separator is now allowed. See makefile.vms for an example.
+
+Version 3.73 (05 Apr 1995)
+
+* Converted to use Autoconf version 2, so `configure' has some new options.
+ See INSTALL for details.
+
+* You can now send a SIGUSR1 signal to Make to toggle printing of debugging
+ output enabled by -d, at any time during the run.
+
+Version 3.72 (04 Nov 1994)
+
+* DJ Delorie has ported Make to MS-DOS using the GO32 extender.
+ He is maintaining the DOS port, not the GNU Make maintainer;
+ please direct bugs and questions for DOS to <djgpp@sun.soe.clarkson.edu>.
+ MS-DOS binaries are available for FTP from ftp.simtel.net in
+ /pub/simtelnet/gnu/djgpp/.
+
+* The `MAKEFLAGS' variable (in the environment or in a makefile) can now
+ contain variable definitions itself; these are treated just like
+ command line variable definitions. Make will automatically insert any
+ variable definitions from the environment value of `MAKEFLAGS' or from
+ the command line, into the `MAKEFLAGS' value exported to children. The
+ `MAKEOVERRIDES' variable previously included in the value of `$(MAKE)'
+ for sub-makes is now included in `MAKEFLAGS' instead. As before, you can
+ reset `MAKEOVERRIDES' in your makefile to avoid putting all the variables
+ in the environment when its size is limited.
+
+* If `.DELETE_ON_ERROR' appears as a target, Make will delete the target of
+ a rule if it has changed when its recipe exits with a nonzero status,
+ just as when the recipe gets a signal.
+
+* The automatic variable `$+' is new. It lists all the dependencies like
+ `$^', but preserves duplicates listed in the makefile. This is useful
+ for linking rules, where library files sometimes need to be listed twice
+ in the link order.
+
+* You can now specify the `.IGNORE' and `.SILENT' special targets with
+ dependencies to limit their effects to those files. If a file appears as
+ a dependency of `.IGNORE', then errors will be ignored while running the
+ recipe to update that file. Likewise if a file appears as a dependency
+ of `.SILENT', then the recipe to update that file will not be printed
+ before it is run. (This change was made to conform to POSIX.2.)
+
+Version 3.71 (21 May 1994)
+
+* The automatic variables `$(@D)', `$(%D)', `$(*D)', `$(<D)', `$(?D)', and
+ `$(^D)' now omit the trailing slash from the directory name. (This change
+ was made to comply with POSIX.2.)
+
+* The source distribution now includes the Info files for the Make manual.
+ There is no longer a separate distribution containing Info and DVI files.
+
+* You can now set the variables `binprefix' and/or `manprefix' in
+ Makefile.in (or on the command line when installing) to install GNU make
+ under a name other than `make' (i.e., ``make binprefix=g install''
+ installs GNU make as `gmake').
+
+* The built-in Texinfo rules use the new variables `TEXI2DVI_FLAGS' for
+ flags to the `texi2dvi' script, and `MAKEINFO_FLAGS' for flags to the
+ Makeinfo program.
+
+* The exit status of Make when it runs into errors is now 2 instead of 1.
+ The exit status is 1 only when using -q and some target is not up to date.
+ (This change was made to comply with POSIX.2.)
+
+Version 3.70 (03 Jan 1994)
+
+* It is no longer a fatal error to have a NUL character in a makefile.
+ You should never put a NUL in a makefile because it can have strange
+ results, but otherwise empty lines full of NULs (such as produced by
+ the `xmkmf' program) will always work fine.
+
+* The error messages for nonexistent included makefiles now refer to the
+ makefile name and line number where the `include' appeared, so Emacs's
+ C-x ` command takes you there (in case it's a typo you need to fix).
+
+Version 3.69 (07 Nov 1993)
+
+* Implicit rule search for archive member references is now done in the
+ opposite order from previous versions: the whole target name `LIB(MEM)'
+ first, and just the member name and parentheses `(MEM)' second.
+
+* Make now gives an error for an unterminated variable or function reference.
+ For example, `$(foo' with no matching `)' or `${bar' with no matching `}'.
+
+* The new default variable `MAKE_VERSION' gives the version number of
+ Make, and a string describing the remote job support compiled in (if any).
+ Thus the value (in this release) is something like `3.69' or `3.69-Customs'.
+
+* Commands in an invocation of the `shell' function are no longer run
+ with a modified environment like recipes are. As in versions before
+ 3.68, they now run with the environment that `make' started with. We
+ have reversed the change made in version 3.68 because it turned out to
+ cause a paradoxical situation in cases like:
+
+ export variable = $(shell echo value)
+
+ When Make attempted to put this variable in the environment for a
+ recipe, it would try expand the value by running the shell command
+ `echo value'. In version 3.68, because it constructed an environment
+ for that shell command in the same way, Make would begin to go into an
+ infinite loop and then get a fatal error when it detected the loop.
+
+* The recipe given for `.DEFAULT' is now used for phony targets with no
+ recipe.
+
+Version 3.68 (28 Jul 1993)
+
+* You can list several archive member names inside parenthesis:
+ `lib(mem1 mem2 mem3)' is equivalent to `lib(mem1) lib(mem2) lib(mem3)'.
+
+* You can use wildcards inside archive member references. For example,
+ `lib(*.o)' expands to all existing members of `lib' whose names end in
+ `.o' (e.g. `lib(a.o) lib(b.o)'); `*.a(*.o)' expands to all such members
+ of all existing files whose names end in `.a' (e.g. `foo.a(a.o)
+ foo.a(b.o) bar.a(c.o) bar.a(d.o)'.
+
+* A suffix rule `.X.a' now produces two pattern rules:
+ (%.o): %.X # Previous versions produced only this.
+ %.a: %.X # Now produces this as well, just like other suffixes.
+
+* The new flag `--warn-undefined-variables' says to issue a warning message
+ whenever Make expands a reference to an undefined variable.
+
+* The new `-include' directive is just like `include' except that there is
+ no error (not even a warning) for a nonexistent makefile.
+
+* Commands in an invocation of the `shell' function are now run with a
+ modified environment like recipes are, so you can use `export' et al
+ to set up variables for them. They used to run with the environment
+ that `make' started with.
+
+Version 3.66 (21 May 1993)
+
+* `make --version' (or `make -v') now exits immediately after printing
+ the version number.
+
+Version 3.65 (09 May 1993)
+
+* Make now supports long-named members in `ar' archive files.
+
+Version 3.64 (21 Apr 1993)
+
+* Make now supports the `+=' syntax for a variable definition which appends
+ to the variable's previous value. See the section `Appending More Text
+ to Variables' in the manual for full details.
+
+* The new option `--no-print-directory' inhibits the `-w' or
+ `--print-directory' feature. Make turns on `--print-directory'
+ automatically if you use `-C' or `--directory', and in sub-makes; some
+ users have found this behavior undesirable.
+
+* The built-in implicit rules now support the alternative extension
+ `.txinfo' for Texinfo files, just like `.texinfo' and `.texi'.
+
+Version 3.63 (22 Jan 1993)
+
+* Make now uses a standard GNU `configure' script. See the new file
+ INSTALL for the new (and much simpler) installation procedure.
+
+* There is now a shell script to build Make the first time, if you have no
+ other `make' program. `build.sh' is created by `configure'; see README.
+
+* GNU Make now completely conforms to the POSIX.2 specification for `make'.
+
+* Elements of the `$^' and `$?' automatic variables that are archive
+ member references now list only the member name, as in Unix and POSIX.2.
+
+* You should no longer ever need to specify the `-w' switch, which prints
+ the current directory before and after Make runs. The `-C' switch to
+ change directory, and recursive use of Make, now set `-w' automatically.
+
+* Multiple double-colon rules for the same target will no longer have their
+ recipes run simultaneously under -j, as this could result in the two
+ recipes trying to change the file at the same time and interfering with
+ one another.
+
+* The `SHELL' variable is now never taken from the environment.
+ Each makefile that wants a shell other than the default (/bin/sh) must
+ set SHELL itself. SHELL is always exported to child processes.
+ This change was made for compatibility with POSIX.2.
+
+* Make now accepts long options. There is now an informative usage message
+ that tells you what all the options are and what they do. Try `make --help'.
+
+* There are two new directives: `export' and `unexport'. All variables are
+ no longer automatically put into the environments of the recipe lines that
+ Make runs. Instead, only variables specified on the command line or in
+ the environment are exported by default. To export others, use:
+ export VARIABLE
+ or you can define variables with:
+ export VARIABLE = VALUE
+ or:
+ export VARIABLE := VALUE
+ You can use just:
+ export
+ or:
+ .EXPORT_ALL_VARIABLES:
+ to get the old behavior. See the node `Variables/Recursion' in the manual
+ for a full description.
+
+* The recipe from the `.DEFAULT' special target is only applied to
+ targets which have no rules at all, not all targets with no recipe.
+ This change was made for compatibility with Unix make.
+
+* All fatal error messages now contain `***', so they are easy to find in
+ compilation logs.
+
+* Dependency file names like `-lNAME' are now replaced with the actual file
+ name found, as with files found by normal directory search (VPATH).
+ The library file `libNAME.a' may now be found in the current directory,
+ which is checked before VPATH; the standard set of directories (/lib,
+ /usr/lib, /usr/local/lib) is now checked last.
+ See the node `Libraries/Search' in the manual for full details.
+
+* A single `include' directive can now specify more than one makefile to
+ include, like this:
+ include file1 file2
+ You can also use shell file name patterns in an `include' directive:
+ include *.mk
+
+* The default directories to search for included makefiles, and for
+ libraries specified with `-lNAME', are now set by configuration.
+
+* You can now use blanks as well as colons to separate the directories in a
+ search path for the `vpath' directive or the `VPATH' variable.
+
+* You can now use variables and functions in the left hand side of a
+ variable assignment, as in "$(foo)bar = value".
+
+* The `MAKE' variable is always defined as `$(MAKE_COMMAND) $(MAKEOVERRIDES)'.
+ The `MAKE_COMMAND' variable is now defined to the name with which make
+ was invoked.
+
+* The built-in rules for C++ compilation now use the variables `$(CXX)' and
+ `$(CXXFLAGS)' instead of `$(C++)' and `$(C++FLAGS)'. The old names had
+ problems with shells that cannot have `+' in environment variable names.
+
+* The value of a recursively expanded variable is now expanded when putting
+ it into the environment for child processes. This change was made for
+ compatibility with Unix make.
+
+* A rule with no targets before the `:' is now accepted and ignored.
+ This change was made for compatibility with SunOS 4 make.
+ We do not recommend that you write your makefiles to take advantage of this.
+
+* The `-I' switch can now be used in MAKEFLAGS, and are put there
+ automatically just like other switches.
+
+Version 3.61
+
+* Built-in rules for C++ source files with the `.C' suffix.
+ We still recommend that you use `.cc' instead.
+
+* If a recipe is given too many times for a single target, the last one
+ given is used, and a warning message is printed.
+
+* Error messages about makefiles are in standard GNU error format,
+ so C-x ` in Emacs works on them.
+
+* Dependencies of pattern rules which contain no % need not actually exist
+ if they can be created (just like dependencies which do have a %).
+
+Version 3.60
+
+* A message is always printed when Make decides there is nothing to be done.
+ It used to be that no message was printed for top-level phony targets
+ (because "`phony' is up to date" isn't quite right). Now a different
+ message "Nothing to be done for `phony'" is printed in that case.
+
+* Archives on AIX now supposedly work.
+
+* When the recipes specified for .DEFAULT are used to update a target,
+ the $< automatic variable is given the same value as $@ for that target.
+ This is how Unix make behaves, and this behavior is mandated by POSIX.2.
+
+Version 3.59
+
+* The -n, -q, and -t options are not put in the `MAKEFLAGS' and `MFLAG'
+ variables while remaking makefiles, so recursive makes done while remaking
+ makefiles will behave properly.
+
+* If the special target `.NOEXPORT' is specified in a makefile,
+ only variables that came from the environment and variables
+ defined on the command line are exported.
+
+Version 3.58
+
+* Suffix rules may have dependencies (which are ignored).
+
+Version 3.57
+
+* Dependencies of the form `-lLIB' are searched for as /usr/local/lib/libLIB.a
+ as well as libLIB.a in /usr/lib, /lib, the current directory, and VPATH.
+
+Version 3.55
+
+* There is now a Unix man page for GNU Make. It is certainly not a
+ replacement for the Texinfo manual, but it documents the basic
+ functionality and the switches. For full documentation, you should
+ still read the Texinfo manual. Thanks to Dennis Morse of Stanford
+ University for contributing the initial version of this.
+
+* Variables which are defined by default (e.g., `CC') will no longer be
+ put into the environment for child processes. (If these variables are
+ reset by the environment, makefiles, or the command line, they will
+ still go into the environment.)
+
+* Makefiles which have recipes but no dependencies (and thus are always
+ considered out of date and in need of remaking), will not be remade (if they
+ were being remade only because they were makefiles). This means that GNU
+ Make will no longer go into an infinite loop when fed the makefiles that
+ `imake' (necessary to build X Windows) produces.
+
+* There is no longer a warning for using the `vpath' directive with an explicit
+pathname (instead of a `%' pattern).
+
+Version 3.51
+
+* When removing intermediate files, only one `rm' command line is printed,
+ listing all file names.
+
+* There are now automatic variables `$(^D)', `$(^F)', `$(?D)', and `$(?F)'.
+ These are the directory-only and file-only versions of `$^' and `$?'.
+
+* Library dependencies given as `-lNAME' will use "libNAME.a" in the current
+ directory if it exists.
+
+* The automatic variable `$($/)' is no longer defined.
+
+* Leading `+' characters on a recipe line make that line be executed even
+ under -n, -t, or -q (as if the line contained `$(MAKE)').
+
+* For recipe lines containing `$(MAKE)', `${MAKE}', or leading `+' characters,
+ only those lines are executed, not the entire recipe.
+ (This is how Unix make behaves for lines containing `$(MAKE)' or `${MAKE}'.)
+
+Version 3.50
+
+* Filenames in rules will now have ~ and ~USER expanded.
+
+* The `-p' output has been changed so it can be used as a makefile.
+ (All information that isn't specified by makefiles is prefaced with comment
+ characters.)
+
+Version 3.49
+
+* The % character can be quoted with backslash in implicit pattern rules,
+ static pattern rules, `vpath' directives, and `patsubst', `filter', and
+ `filter-out' functions. A warning is issued if a `vpath' directive's
+ pattern contains no %.
+
+* The `wildcard' variable expansion function now expands ~ and ~USER.
+
+* Messages indicating failed recipe lines now contain the target name:
+ make: *** [target] Error 1
+
+* The `-p' output format has been changed somewhat to look more like
+ makefile rules and to give all information that Make has about files.
+
+Version 3.48
+
+Version 3.47
+
+* The `-l' switch with no argument removes any previous load-average limit.
+
+* When the `-w' switch is in effect, and Make has updated makefiles,
+ it will write a `Leaving directory' message before re-executing itself.
+ This makes the `directory change tracking' changes to Emacs's compilation
+ commands work properly.
+
+Version 3.46
+
+* The automatic variable `$*' is now defined for explicit rules,
+ as it is in Unix make.
+
+Version 3.45
+
+* The `-j' switch is now put in the MAKEFLAGS and MFLAGS variables when
+ specified without an argument (indicating infinite jobs).
+ The `-l' switch is not always put in the MAKEFLAGS and MFLAGS variables.
+
+* Make no longer checks hashed directories after running recipes.
+ The behavior implemented in 3.41 caused too much slowdown.
+
+Version 3.44
+
+* A dependency is NOT considered newer than its dependent if
+ they have the same modification time. The behavior implemented
+ in 3.43 conflicts with RCS.
+
+Version 3.43
+
+* Dependency loops are no longer fatal errors.
+
+* A dependency is considered newer than its dependent if
+ they have the same modification time.
+
+Version 3.42
+
+* The variables F77 and F77FLAGS are now set by default to $(FC) and
+ $(FFLAGS). Makefiles designed for System V make may use these variables in
+ explicit rules and expect them to be set. Unfortunately, there is no way to
+ make setting these affect the Fortran implicit rules unless FC and FFLAGS
+ are not used (and these are used by BSD make).
+
+Version 3.41
+
+* Make now checks to see if its hashed directories are changed by recipes.
+ Other makes that hash directories (Sun, 4.3 BSD) don't do this.
+
+Version 3.39
+
+* The `shell' function no longer captures standard error output.
+
+Version 3.32
+
+* A file beginning with a dot can be the default target if it also contains
+ a slash (e.g., `../bin/foo'). (Unix make allows this as well.)
+
+Version 3.31
+
+* Archive member names are truncated to 15 characters.
+
+* Yet more USG stuff.
+
+* Minimal support for Microport System V (a 16-bit machine and a
+ brain-damaged compiler). This has even lower priority than other USG
+ support, so if it gets beyond trivial, I will take it out completely.
+
+* Revamped default implicit rules (not much visible change).
+
+* The -d and -p options can come from the environment.
+
+Version 3.30
+
+* Improved support for USG and HPUX (hopefully).
+
+* A variable reference like `$(foo:a=b)', if `a' contains a `%', is
+ equivalent to `$(patsubst a,b,$(foo))'.
+
+* Defining .DEFAULT with no deps or recipe clears its recipe.
+
+* New default implicit rules for .S (cpp, then as), and .sh (copy and
+ make executable). All default implicit rules that use cpp (even
+ indirectly), use $(CPPFLAGS).
+
+Version 3.29
+
+* Giving the -j option with no arguments gives you infinite jobs.
+
+Version 3.28
+
+* New option: "-l LOAD" says not to start any new jobs while others are
+ running if the load average is not below LOAD (a floating-point number).
+
+* There is support in place for implementations of remote command execution
+ in Make. See the file remote.c.
+
+Version 3.26
+
+* No more than 10 directories will be kept open at once.
+ (This number can be changed by redefining MAX_OPEN_DIRECTORIES in dir.c.)
+
+Version 3.25
+
+* Archive files will have their modification times recorded before doing
+ anything that might change their modification times by updating an archive
+ member.
+
+Version 3.20
+
+* The `MAKELEVEL' variable is defined for use by makefiles.
+
+Version 3.19
+
+* The recursion level indications in error messages are much shorter than
+ they were in version 3.14.
+
+Version 3.18
+
+* Leading spaces before directives are ignored (as documented).
+
+* Included makefiles can determine the default goal target.
+ (System V Make does it this way, so we are being compatible).
+
+Version 3.14.
+
+* Variables that are defaults built into Make will not be put in the
+ environment for children. This just saves some environment space and,
+ except under -e, will be transparent to sub-makes.
+
+* Error messages from sub-makes will indicate the level of recursion.
+
+* Hopefully some speed-up for large directories due to a change in the
+ directory hashing scheme.
+
+* One child will always get a standard input that is usable.
+
+* Default makefiles that don't exist will be remade and read in.
+
+Version 3.13.
+
+* Count parentheses inside expansion function calls so you can
+ have nested calls: `$(sort $(foreach x,a b,$(x)))'.
+
+Version 3.12.
+
+* Several bug fixes, including USG and Sun386i support.
+
+* `shell' function to expand shell commands a la `
+
+* If the `-d' flag is given, version information will be printed.
+
+* The `-c' option has been renamed to `-C' for compatibility with tar.
+
+* The `-p' option no longer inhibits other normal operation.
+
+* Makefiles will be updated and re-read if necessary.
+
+* Can now run several recipes at once (parallelism), -j option.
+
+* Error messages will contain the level of Make recursion, if any.
+
+* The `MAKEFLAGS' and `MFLAGS' variables will be scanned for options after
+ makefiles are read.
+
+* A double-colon rule with no dependencies will always have its recipe run.
+ (This is how both the BSD and System V versions of Make do it.)
+
+Version 3.05
+
+(Changes from versions 1 through 3.05 were never recorded. Sorry.)
+
+-------------------------------------------------------------------------------
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/NMakefile.template b/src/kmk/NMakefile.template
new file mode 100644
index 0000000..2007a3b
--- /dev/null
+++ b/src/kmk/NMakefile.template
@@ -0,0 +1,132 @@
+# -*-Makefile-*- to build GNU make with nmake
+#
+# NOTE: If you have no 'make' program at all to process this makefile,
+# run 'build_w32.bat' instead.
+#
+# Copyright (C) 1996-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+LINK = link
+CC = cl
+MAKE = nmake
+
+OUTDIR=.
+MAKEFILE=NMakefile
+SUBPROC_MAKEFILE=NMakefile
+
+CFLAGS_any = /nologo /MT /W4 /GX /Zi /YX /I . /I glob /I w32/include /D WIN32 /D WINDOWS32 /D _CONSOLE /D HAVE_CONFIG_H
+CFLAGS_debug = $(CFLAGS_any) /Od /D DEBUG /D _DEBUG /FR.\WinDebug/ /Fp.\WinDebug/make.pch /Fo.\WinDebug/ /Fd.\WinDebug/make.pdb
+CFLAGS_release = $(CFLAGS_any) /O2 /D NDEBUG /FR.\WinRel/ /Fp.\WinRel/make.pch /Fo.\WinRel/
+
+LDFLAGS_debug = w32\subproc\WinDebug\subproc.lib /NOLOGO /SUBSYSTEM:console\
+ /STACK:0x400000 /INCREMENTAL:no /PDB:WinDebug/make.pdb /OUT:WinDebug/make.exe /DEBUG
+LDFLAGS_release = w32\subproc\WinRel\subproc.lib /NOLOGO /SUBSYSTEM:console\
+ /STACK:0x400000 /INCREMENTAL:no /OUT:WinRel/make.exe
+
+all: config.h subproc Release Debug
+
+#
+# Make sure we build the subproc library first. It has it's own
+# makefile. To be portable to Windows 95, we put the instructions
+# on how to build the library into a batch file. On NT, we could
+# simply have done foo && bar && dog, but this doesn't port.
+#
+subproc: w32/subproc/WinDebug/subproc.lib w32/subproc/WinRel/subproc.lib
+
+w32/subproc/WinDebug/subproc.lib w32/subproc/WinRel/subproc.lib: w32/subproc/misc.c w32/subproc/sub_proc.c w32/subproc/w32err.c
+ subproc.bat $(SUBPROC_MAKEFILE) $(MAKE)
+ if exist WinDebug\make.exe erase WinDebug\make.exe
+ if exist WinRel\make.exe erase WinRel\make.exe
+
+config.h: config.h.W32
+ copy $? $@
+
+Release:
+ $(MAKE) /f $(MAKEFILE) LDFLAGS="$(LDFLAGS_release)" CFLAGS="$(CFLAGS_release)" OUTDIR=WinRel WinRel/make.exe
+Debug:
+ $(MAKE) /f $(MAKEFILE) LDFLAGS="$(LDFLAGS_debug)" CFLAGS="$(CFLAGS_debug)" OUTDIR=WinDebug WinDebug/make.exe
+
+clean:
+ if exist WinDebug\nul rmdir /s /q WinDebug
+ if exist WinRel\nul rmdir /s /q WinRel
+ if exist w32\subproc\WinDebug\nul rmdir /s /q w32\subproc\WinDebug
+ if exist w32\subproc\WinRel\nul rmdir /s /q w32\subproc\WinRel
+ if exist config.h erase config.h
+ erase *.pdb
+
+$(OUTDIR):
+ if not exist .\$@\nul mkdir .\$@
+
+LIBS = kernel32.lib user32.lib advapi32.lib
+
+guile = $(OUTDIR)/guile.obj
+
+OBJS = \
+ $(OUTDIR)/ar.obj \
+ $(OUTDIR)/arscan.obj \
+ $(OUTDIR)/commands.obj \
+ $(OUTDIR)/default.obj \
+ $(OUTDIR)/dir.obj \
+ $(OUTDIR)/expand.obj \
+ $(OUTDIR)/file.obj \
+ $(OUTDIR)/function.obj \
+ $(OUTDIR)/getloadavg.obj \
+ $(OUTDIR)/getopt.obj \
+ $(OUTDIR)/getopt1.obj \
+ $(OUTDIR)/hash.obj \
+ $(OUTDIR)/implicit.obj \
+ $(OUTDIR)/job.obj \
+ $(OUTDIR)/load.obj \
+ $(OUTDIR)/main.obj \
+ $(OUTDIR)/misc.obj \
+ $(OUTDIR)/output.obj \
+ $(OUTDIR)/read.obj \
+ $(OUTDIR)/remake.obj \
+ $(OUTDIR)/remote-stub.obj \
+ $(OUTDIR)/rule.obj \
+ $(OUTDIR)/signame.obj \
+ $(OUTDIR)/strcache.obj \
+ $(OUTDIR)/variable.obj \
+ $(OUTDIR)/version.obj \
+ $(OUTDIR)/vpath.obj \
+ $(OUTDIR)/glob.obj \
+ $(OUTDIR)/fnmatch.obj \
+ $(OUTDIR)/dirent.obj \
+ $(OUTDIR)/pathstuff.obj \
+ $(OUTDIR)/posixfcn.obj \
+ $(OUTDIR)/w32os.obj \
+ $(guile)
+
+$(OUTDIR)/make.exe: $(OUTDIR) $(OBJS)
+ $(LINK) @<<
+ $(LDFLAGS) $(LIBS) $(OBJS)
+<<
+
+.c{$(OUTDIR)}.obj:
+ $(CC) $(CFLAGS) /c $<
+
+$(OUTDIR)/glob.obj : glob/glob.c
+ $(CC) $(CFLAGS) /c $?
+$(OUTDIR)/fnmatch.obj : glob/fnmatch.c
+ $(CC) $(CFLAGS) /c $?
+$(OUTDIR)/dirent.obj : w32/compat/dirent.c
+ $(CC) $(CFLAGS) /c $?
+$(OUTDIR)/posixfcn.obj : w32/compat/posixfcn.c
+ $(CC) $(CFLAGS) /c $?
+$(OUTDIR)/pathstuff.obj : w32/pathstuff.c
+ $(CC) $(CFLAGS) /c $?
+$(OUTDIR)/w32os.obj : w32/w32os.c
+ $(CC) $(CFLAGS) /c $?
diff --git a/src/kmk/README.Amiga b/src/kmk/README.Amiga
new file mode 100644
index 0000000..68d3ea7
--- /dev/null
+++ b/src/kmk/README.Amiga
@@ -0,0 +1,77 @@
+Short: Port of GNU make with SAS/C (no ixemul.library required)
+Author: GNU, Amiga port by Aaron "Optimizer" Digulla
+Uploader: Aaron "Optimizer" Digulla (digulla@fh-konstanz.de)
+Type: dev/c
+
+This is a pure Amiga port of GNU make. It needs no extra libraries or
+anything. It has the following features (in addition to any features of
+GNU make):
+
+- Runs Amiga-Commands with SystemTags() (Execute)
+- Can run multi-line statements
+- Allows to use Device-Names in targets:
+
+ c:make : make.o
+
+ is ok. To distinguish between device-names and target : or ::, MAKE
+ looks for spaces. If there are any around :, it's taken as a target
+ delimiter, if there are none, it's taken as the name of a device. Note
+ that "make:make.o" tries to create "make.o" on the device "make:".
+- Replaces @@ by a newline in any command line:
+
+ if exists make @@\
+ delete make.bak quiet @@\
+ rename make make.bak @@\
+ endif @@\
+ $(CC) Link Make.o To make
+
+ works. Note that the @@ must stand alone (i.e., "make@@\" is illegal).
+ Also be careful that there is a space after the "\" (i.e., at the
+ beginning of the next line).
+- Can be made resident to save space and time
+- Amiga specific wildcards can be used in $(wildcard ...)
+
+BUGS:
+- The line
+
+ dummy.h : src/*.c
+
+tries to make dummy.h from "src/*.c" (i.e., no wildcard-expansion takes
+place). You have to use "$(wildcard src/*.c)" instead.
+
+COMPILING FROM SCRATCH
+----------------------
+
+To recompile, you need SAS/C 6.51. make itself is not necessary, there
+is an smakefile.
+
+1. Copy config.ami to config.h
+2. If you use make to compile, copy Makefile.ami to Makefile and
+ glob/Makefile.ami to glob/Makefile. Copy make into the current
+ directory.
+
+3. Run smake/make
+
+INSTALLATION
+
+Copy make somewhere in your search path (e.g., sc:c or sc:bin).
+If you plan to use recursive makes, install make resident:
+
+ Resident make Add
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/README.DOS.template b/src/kmk/README.DOS.template
new file mode 100644
index 0000000..bc31adb
--- /dev/null
+++ b/src/kmk/README.DOS.template
@@ -0,0 +1,340 @@
+Port of GNU Make to 32-bit protected mode on MSDOS and MS-Windows.
+
+Builds with DJGPP v2 port of GNU C/C++ compiler and utilities.
+
+
+New (since 3.74) DOS-specific features:
+
+ 1. Supports long filenames when run from DOS box on Windows 9x.
+
+ 2. Supports both stock DOS COMMAND.COM and Unix-style shells
+ (details in 'Notes' below).
+
+ 3. Supports DOS drive letters in dependencies and pattern rules.
+
+ 4. Better support for DOS-style backslashes in pathnames (but see
+ 'Notes' below).
+
+ 5. The $(shell) built-in can run arbitrary complex commands,
+ including pipes and redirection, even when COMMAND.COM is your
+ shell.
+
+ 6. Can be built without floating-point code (see below).
+
+ 7. Supports signals in child programs and restores the original
+ directory if the child was interrupted.
+
+ 8. Can be built without (a previous version of) Make.
+
+ 9. The build process requires only standard tools. (Optional
+ targets like "install:" and "clean:" still need additional
+ programs, though, see below.)
+
+ 10. Beginning with v3.78, the test suite works in the DJGPP
+ environment (requires Perl and auxiliary tools; see below).
+
+
+To install a binary distribution:
+
+ Simply unzip the makNNNb.zip file (where NNN is the version number)
+ preserving the directory structure (-d switch if you use PKUNZIP).
+ If you are installing Make on Windows 9X or Windows 2000, use an
+ unzip program that supports long filenames in zip files. After
+ unzipping, make sure the directory with make.exe is on your PATH,
+ and that's all you need to use Make.
+
+
+To build from sources:
+
+ 1. Unzip the archive, preserving the directory structure (-d switch
+ if you use PKUNZIP). If you build Make on Windows 9X or Windows
+ 2000, use an unzip program that supports long filenames in zip
+ files.
+
+ If you are unpacking an official GNU source distribution, use
+ either DJTAR (which is part of the DJGPP development
+ environment), or the DJGPP port of GNU Tar.
+
+ 2. Invoke the 'configure.bat' batch file.
+
+ If you are building Make in-place, i.e. in the same directory
+ where its sources are kept, just type "configure.bat" and press
+ [Enter]. Otherwise, you need to supply the path to the source
+ directory as an argument to the batch file, like this:
+
+ c:\djgpp\gnu\make-%VERSION%\configure.bat c:/djgpp/gnu/make-%VERSION%
+
+ Note the forward slashes in the source path argument: you MUST
+ use them here.
+
+ 3. If configure.bat doesn't find a working Make, it will suggest to
+ use the 'dosbuild.bat' batch file to build Make. Either do as it
+ suggests or install another Make program (a pre-compiled binary
+ should be available from the usual DJGPP sites) and rerun
+ configure.bat.
+
+ 4. If you will need to run Make on machines without an FPU, you
+ might consider building a version of Make which doesn't issue
+ floating-point instructions (they don't help much on MSDOS
+ anyway). To this end, edit the Makefile created by
+ configure.bat and add -DNO_FLOAT to the value of CPPFLAGS.
+
+ 5. Invoke Make.
+
+ If you are building from outside of the source directory, you
+ need to tell Make where the sources are, like this:
+
+ make srcdir=c:/djgpp/gnu/make-%VERSION%
+
+ (configure.bat will tell you this when it finishes). You MUST
+ use a full, not relative, name of the source directory here, or
+ else Make might fail.
+
+ 6. After Make finishes, if you have a Unix-style shell installed,
+ you can use the 'install' target to install the package. You
+ will also need GNU Fileutils and GNU Sed for this (they should
+ be available from the DJGPP sites).
+
+ By default, GNU make will install into your DJGPP installation
+ area. If you wish to use a different directory, override the
+ DESTDIR variable when invoking "make install", like this:
+
+ make install DESTDIR=c:/other/dir
+
+ This causes the make executable to be placed in c:/other/dir/bin,
+ the man pages in c:/other/dir/man, etc.
+
+ Without a Unix-style shell, you will have to install programs
+ and the docs manually. Copy make.exe to a directory on your
+ PATH, make.i* info files to your Info directory, and update the
+ file 'dir' in your Info directory by adding the following item
+ to the main menu:
+
+ * Make: (make.info). The GNU make utility.
+
+ If you have the 'install-info' program (from the GNU Texinfo
+ package), it will do that for you if you invoke it like this:
+
+ install-info --info-dir=c:/djgpp/info c:/djgpp/info/make.info
+
+ (If your Info directory is other than C:\DJGPP\INFO, change this
+ command accordingly.)
+
+ 7. The 'clean' targets also require Unix-style shell, and GNU Sed
+ and 'rm' programs (the latter from Fileutils).
+
+ 8. To run the test suite, type "make check". This requires a Unix
+ shell (I used the DJGPP port of Bash 2.03), Perl, Sed, Fileutils
+ and Sh-utils.
+
+
+Notes:
+-----
+
+ 1. The shell issue.
+
+ This is probably the most significant improvement, first
+ introduced in the port of GNU Make 3.75.
+
+ The original behavior of GNU Make is to invoke commands
+ directly, as long as they don't include characters special to
+ the shell or internal shell commands, because that is faster.
+ When shell features like redirection or filename wildcards are
+ involved, Make calls the shell.
+
+ This port supports both DOS shells (the stock COMMAND.COM and its
+ 4DOS/NDOS replacements), and Unix-style shells (tested with the
+ venerable Stewartson's 'ms_sh' 2.3 and the DJGPP port of 'bash' by
+ Daisuke Aoyama <jack@st.rim.or.jp>).
+
+ When the $SHELL variable points to a Unix-style shell, Make
+ works just like you'd expect on Unix, calling the shell for any
+ command that involves characters special to the shell or
+ internal shell commands. The only difference is that, since
+ there is no standard way to pass command lines longer than the
+ infamous DOS 126-character limit, this port of Make writes the
+ command line to a temporary disk file and then invokes the shell
+ on that file.
+
+ If $SHELL points to a DOS-style shell, however, Make will not
+ call it automatically, as it does with Unix shells. Stock
+ COMMAND.COM is too dumb and would unnecessarily limit the
+ functionality of Make. For example, you would not be able to
+ use long command lines in commands that use redirection or
+ pipes. Therefore, when presented with a DOS shell, this port of
+ Make will emulate most of the shell functionality, like
+ redirection and pipes, and shall only call the shell when a
+ batch file or a command internal to the shell is invoked. (Even
+ when a command is an internal shell command, Make will first
+ search the $PATH for it, so that if a Makefile calls 'mkdir',
+ you can install, say, a port of GNU 'mkdir' and have it called
+ in that case.)
+
+ The key to all this is the extended functionality of 'spawn' and
+ 'system' functions from the DJGPP library; this port just calls
+ 'system' where it would invoke the shell on Unix. The most
+ important aspect of these functions is that they use a special
+ mechanism to pass long (up to 16KB) command lines to DJGPP
+ programs. In addition, 'system' emulates some internal
+ commands, like 'cd' (so that you can now use forward slashes
+ with it, and can also change the drive if the directory is on
+ another drive). Another aspect worth mentioning is that you can
+ call Unix shell scripts directly, provided that the shell whose
+ name is mentioned on the first line of the script is installed
+ anywhere along the $PATH. It is impossible to tell here
+ everything about these functions; refer to the DJGPP library
+ reference for more details.
+
+ The $(shell) built-in is implemented in this port by calling
+ 'popen'. Since 'popen' calls 'system', the above considerations
+ are valid for $(shell) as well. In particular, you can put
+ arbitrary complex commands, including pipes and redirection,
+ inside $(shell), which is in many cases a valid substitute for
+ the Unix-style command substitution (`command`) feature.
+
+
+ 2. "SHELL=/bin/sh" -- or is it?
+
+ Many Unix Makefiles include a line which sets the SHELL, for
+ those versions of Make which don't have this as the default.
+ Since many DOS systems don't have 'sh' installed (in fact, most
+ of them don't even have a '/bin' directory), this port takes
+ such directives with a grain of salt. It will only honor such a
+ directive if the basename of the shell name (like 'sh' in the
+ above example) can indeed be found in the directory that is
+ mentioned in the SHELL= line ('/bin' in the above example), or
+ in the current working directory, or anywhere on the $PATH (in
+ that order). If the basename doesn't include a filename
+ extension, Make will look for any known extension that indicates
+ an executable file (.exe, .com, .bat, .btm, .sh, and even .sed
+ and .pl). If any such file is found, then $SHELL will be
+ defined to the exact pathname of that file, and that shell will
+ hence be used for the rest of processing. But if the named
+ shell is *not* found, the line which sets it will be effectively
+ ignored, leaving the value of $SHELL as it was before. Since a
+ lot of decisions that this port makes depend on the gender of
+ the shell, I feel it doesn't make any sense to tailor Make's
+ behavior to a shell which is nowhere to be found.
+
+ Note that the above special handling of "SHELL=" only happens
+ for Makefiles; if you set $SHELL in the environment or on the
+ Make command line, you are expected to give the complete
+ pathname of the shell, including the filename extension.
+
+ The default value of $SHELL is computed as on Unix (see the Make
+ manual for details), except that if $SHELL is not defined in the
+ environment, $COMSPEC is used. Also, if an environment variable
+ named $MAKESHELL is defined, it takes precedence over both
+ $COMSPEC and $SHELL. Note that, unlike Unix, $SHELL in the
+ environment *is* used to set the shell (since on MSDOS, it's
+ unlikely that the interactive shell will not be suitable for
+ Makefile processing).
+
+ The bottom line is that you can now write Makefiles where some
+ of the targets require a real (i.e. Unix-like) shell, which will
+ nevertheless work when such shell is not available (provided, of
+ course, that the commands which should always work, don't
+ require such a shell). More important, you can convert Unix
+ Makefiles to MSDOS and leave the line which sets the shell
+ intact, so that people who do have Unixy shell could use it for
+ targets which aren't converted to DOS (like 'install' and
+ 'uninstall', for example).
+
+
+ 3. Default directories.
+
+ GNU Make knows about standard directories where it searches for
+ library and include files mentioned in the Makefile. Since
+ MSDOS machines don't have standard places for these, this port
+ will search ${DJDIR}/lib and ${DJDIR}/include respectively.
+ $DJDIR is defined automatically by the DJGPP startup code as the
+ root of the DJGPP installation tree (unless you've tampered with
+ the DJGPP.ENV file). This should provide reasonable default
+ values, unless you moved parts of DJGPP to other directories.
+
+
+ 4. Letter-case in filenames.
+
+ If you run Make on Windows 9x, you should be aware of the
+ letter-case issue. Make is internally case-sensitive, but all
+ file operations are case-insensitive on Windows 9x, so
+ e.g. files 'FAQ', 'faq' and 'Faq' all refer to the same file, as
+ far as Windows is concerned. The underlying DJGPP C library
+ functions honor the letter-case of the filenames they get from
+ the OS, except that by default, they down-case 8+3 DOS filenames
+ which are stored in upper case in the directory and would break
+ many Makefiles otherwise. (The details of which filenames are
+ converted to lower case are explained in the DJGPP libc docs,
+ under the '_preserve_fncase' and '_lfn_gen_short_fname'
+ functions, but as a thumb rule, any filename that is stored in
+ upper case in the directory, is a valid DOS 8+3 filename and
+ doesn't include characters invalid on MSDOS FAT filesystems,
+ will be automatically down-cased.) User reports that I have
+ indicate that this default behavior is generally what you'd
+ expect; however, your input is most welcome.
+
+ In any case, if you hit a situation where you must force Make to
+ get the 8+3 DOS filenames in upper case, set FNCASE=y in the
+ environment or in the Makefile.
+
+
+ 5. DOS-style pathnames.
+
+ There are a lot of places throughout the program sources which
+ make implicit assumptions about the pathname syntax. In
+ particular, the directories are assumed to be separated by '/',
+ and any pathname which doesn't begin with a '/' is assumed to be
+ relative to the current directory. This port attempts to
+ support DOS-style pathnames which might include the drive letter
+ and use backslashes instead of forward slashes. However, this
+ support is not complete; I feel that pursuing this support too
+ far might break some more important features, particularly if
+ you use a Unix-style shell (where a backslash is a quote
+ character). I only consider support of backslashes desirable
+ because some Makefiles invoke non-DJGPP programs which don't
+ understand forward slashes. A notable example of such programs
+ is the standard programs which come with MSDOS. Otherwise, you
+ are advised to stay away from backslashes whenever possible. In
+ particular, filename globbing won't work on pathnames with
+ backslashes, because the GNU 'glob' library doesn't support them
+ (backslash is special in filename wildcards, and I didn't want
+ to break that).
+
+ One feature which *does* work with backslashes is the filename-
+ related built-in functions such as $(dir), $(notdir), etc.
+ Drive letters in pathnames are also fully supported.
+
+
+
+Bug reports:
+-----------
+
+ Bugs that are clearly related to the MSDOS/DJGPP port should be
+ reported first on the comp.os.msdos.djgpp news group (if you cannot
+ post to Usenet groups, write to the DJGPP mailing list,
+ <djgpp@delorie.com>, which is an email gateway into the above news
+ group). For other bugs, please follow the procedure explained in
+ the "Bugs" chapter of the Info docs. If you don't have an Info
+ reader, look up that chapter in the 'make.i1' file with any text
+ browser/editor.
+
+
+ Enjoy,
+ Eli Zaretskii <eliz@is.elta.co.il>
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/README.OS2.template b/src/kmk/README.OS2.template
new file mode 100644
index 0000000..0ce81b4
--- /dev/null
+++ b/src/kmk/README.OS2.template
@@ -0,0 +1,176 @@
+Port of GNU make to OS/2.
+
+Features of GNU make that do not work under OS/2:
+ - remote job execution
+ - dynamic load balancing
+
+
+Special features of the OS/2 version:
+
+Due to the fact that some people might want to use sh syntax in
+Makefiles while others might want to use OS/2's native shell cmd.exe,
+GNU make supports both shell types. The following list defines the order
+that is used to determine the shell:
+
+ 1. The shell specified by the environment variable MAKESHELL.
+ 2. The shell specified by the SHELL variable within a Makefile. Like
+ Unix, SHELL is NOT taken from the environment.
+ 3. The shell specified by the COMSPEC environment variable.
+ 4. The shell specified by the OS2_SHELL environment variable.
+ 5. If none of the above is defined /bin/sh is used as default. This
+ happens e.g. in the make testsuite.
+
+Note: - Points 3 and 4 can be turned off at compile time by adding
+ -DNO_CMD_DEFAULT to the CPPFLAGS.
+ - DOS support is not tested for EMX and therefore might not work.
+ - The UNIXROOT environment variable is supported to find /bin/sh
+ if it is not on the current drive.
+
+
+COMPILATION OF GNU MAKE FOR OS/2:
+
+I. ***** SPECIAL OPTIONS *****
+
+ - At compile time you can turn off that cmd is used as default shell
+ (but only /bin/sh). Simply set CPPFLAGS="-DNO_CMD_DEFAULT" and make
+ will not use cmd unless you cause it to do so by setting MAKESHELL to
+ cmd or by specifying SHELL=cmd in your Makefile.
+
+ - At compile time you can set CPPFLAGS="-DNO_CHDIR2" to turn off that
+ GNU make prints drive letters. This is necessary if you want to run
+ the testsuite.
+
+
+II. ***** REQUIREMENTS FOR THE COMPILATION *****
+
+A standard Unix like build environment:
+
+ - sh compatible shell (ksh, bash, ash, but tested only with pdksh 5.2.14
+ release 2)
+ If you use pdksh it is recommended to update to 5.2.14 release 2. Older
+ versions may not work! You can get this version at
+ http://www.math.ohio-state.edu/~ilya/software/os2/pdksh-5.2.14-bin-2.zip
+ - GNU file utilities (make sure that install.exe from the file utilities
+ is in front of your PATH before X:\OS2\INSTALL\INSTALL.EXE. I recommend
+ also to change the filename to ginstall.exe instead of install.exe
+ to avoid confusion with X:\OS2\INSTALL\INSTALL.EXE)
+ - GNU shell utilities
+ - GNU text utilities
+ - gawk
+ - grep
+ - sed
+ - GNU make 3.79.1 (special OS/2 patched version) or higher
+ - perl 5.005 or higher
+ - GNU texinfo (you can use 3.1 (gnuinfo.zip), but I recommend 4.0)
+
+If you want to recreate the configuration files (developers only!)
+you need also: GNU m4 1.4, autoconf 2.59, automake 1.9.6 (or compatible)
+
+
+III. ***** COMPILATION AND INSTALLATION *****
+
+ a) ** Developers only - Everyone else should skip this section **
+ To recreate the configuration files use:
+
+ export EMXSHELL=ksh
+ aclocal -I config
+ automake
+ autoconf
+ autoheader
+
+
+b) Installation into x:/usr
+
+ Note: Although it is possible to compile make using "./configure",
+ "make", "make install" this is not recommended. In particular,
+ you must ALWAYS use LDFLAGS="-Zstack 0x6000" because the default
+ stack size is far to small and make will not work properly!
+
+Recommended environment variables and installation options:
+
+ export ac_executable_extensions=".exe"
+ export CPPFLAGS="-D__ST_MT_ERRNO__"
+ export CFLAGS="-O2 -Zomf -Zmt"
+ export LDFLAGS="-Zcrtdll -Zlinker /exepack:2 -Zlinker /pm:vio -Zstack 0x6000"
+ export RANLIB="echo"
+ ./configure --prefix=x:/usr --infodir=x:/usr/share/info --mandir=x:/usr/share/man --without-included-gettext
+ make AR=emxomfar
+ make install
+
+Note: If you use gcc 2.9.x I recommend to set also LIBS="-lgcc"
+
+Note: You can add -DNO_CMD_DEFAULT and -DNO_CHDIR2 to CPPFLAGS.
+ See section I. for details.
+
+
+IV. ***** NLS support *****
+
+GNU make has NLS (National Language Support), with the following
+caveats:
+
+ a) It will only work with GNU gettext, and
+ b) GNU gettext support is not included in the GNU make package.
+
+Therefore, if you wish to enable the internationalization features of
+GNU make you must install GNU gettext on your system before configuring
+GNU make.
+
+You can choose the languages to be installed. To install support for
+English, German and French only enter:
+
+ export LINGUAS="en de fr"
+
+If you don't specify LINGUAS all languages are installed.
+
+If you don't want NLS support (English only) use the option
+--disable-nls for the configure script. Note if GNU gettext is not
+installed then NLS will not be enabled regardless of this flag.
+
+
+V. ***** Running the make test suite *****
+
+To run the included make test suite you have to set
+
+ CPPFLAGS="-D__ST_MT_ERRNO__ -DNO_CMD_DEFAULT -DNO_CHDIR2"
+
+before you compile make. This is due to some restrictions of the
+testsuite itself. -DNO_CMD_DEFAULT causes make to use /bin/sh as default
+shell in every case. Normally you could simply set MAKESHELL="/bin/sh"
+to do this but the testsuite ignores the environment. -DNO_CHDIR2 causes
+make not to use drive letters for directory names (i.e. _chdir2() and
+_getcwd2() are NOT used). The testsuite interpretes the whole output of
+make, especially statements like make[1]: Entering directory
+'C:/somewhere/make-3.79.1/tests' where the testsuite does not expect the
+drive letter. This would be interpreted as an error even if there is
+none.
+
+To run the testsuite do the following:
+
+ export CPPFLAGS="-D__ST_MT_ERRNO__ -DNO_CMD_DEFAULT -DNO_CHDIR2"
+ export CFLAGS="-Zomf -O2 -Zmt"
+ export LDFLAGS="-Zcrtdll -s -Zlinker /exepack:2 -Zlinker /pm:vio -Zstack 0x6000"
+ export RANLIB="echo"
+ ./configure --prefix=x:/usr --disable-nls
+ make AR=emxomfar
+ make check
+
+All tests should work fine with the exception of one of the "INCLUDE_DIRS"
+tests which will fail if your /usr/include directory is on a drive different
+from the make source tree.
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 2003-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/README.VMS b/src/kmk/README.VMS
new file mode 100644
index 0000000..5532b01
--- /dev/null
+++ b/src/kmk/README.VMS
@@ -0,0 +1,515 @@
+Overview: -*-text-mode-*-
+---------
+
+ This version of GNU make has been tested on:
+ OpenVMS V8.3/V8.4 (Alpha) and V8.4 (Integrity) AND V7.3 (VAX)
+
+ This version of GNU Make is intended to be run from DCL to run
+ make scripts with a special syntax that is described below. It
+ likely will not be able to run unmodified Unix makefiles.
+
+ There is an older implementation of GNU Make that was ported to GNV.
+ Work is now in progress to merge that port to get a single version
+ of GNU Make available. When that merge is done, GNU Make will auto
+ detect that it is running under a Posix shell and then operate as close to
+ GNU Make on Unix as possible.
+
+ The descriptions below are for running GNU make from DCL or equivalent.
+
+Recipe differences:
+-------------------
+
+ GNU Make for OpenVMS can not currently run native Unix make files because of
+ differences in the implementation.
+
+ I am trying to document the current behavior in this section. This is based
+ on the information in the file NEWS. and running the test suite.
+ TODO: More tests are needed to validate and demonstrate the OpenVMS
+ expected behavior.
+
+ In some cases the older behavior of GNU Make when run from DCL is not
+ compatible with standard makefile behavior.
+
+ This behavior can be changed when running GNU Make from DCL by setting
+ either DCL symbols or logical names of the format GNV$. The settings
+ are enabled with a string starting with one of '1', 'T', or 'E' for "1",
+ "TRUE", or "ENABLE". They are disabled with a '0', 'F', or 'D' for "1",
+ "FALSE", or "DISABLE". If they are not explicitly set to one of these
+ values, then they will be set to their default values.
+
+ The value of the setting DECC$FILENAME_UNIX_REPORT or
+ DECC$FILENAME_UNIX_ONLY will now cause the $(dir x) function to return
+ './' or '[]' as appropriate.
+
+ The name GNV$MAKE_OLD_VMS when enabled will cause GNU Make to behave as
+ much as the older method as can be done with out disabling VMS features.
+ When it is disabled GNU Make have the new behavior which more closely
+ matches Unix Make behavior.
+
+ The default is currently the old behavior when running GNU Make from DCL.
+ In the future this may change. When running make from GNV Bash the new
+ behavior is the default.
+
+ This is a global setting that sets the default behavior for several other
+ options that can be individually changed. Many of the individual settings
+ are to make it so that the self tests for GNU Make need less VMS specific
+ modifications.
+
+ The name GNV$MAKE_COMMA when enabled will cause GNU Make to expect a comma
+ for a path separator and use a comma for the separator for a list of files.
+ When disabled, it will cause GNU Make to use a colon for a path separator
+ and a space for the separator for a list of files. The default is to be
+ enabled if the GNU Make is set to the older behavior.
+
+ The name GNV$MAKE_SHELL_SIM when enabled will cause GNU Make to try to
+ simulate a Posix shell more closely. The following behaviors occur:
+
+ * Single quotes are converted to double quotes and any double
+ quotes inside of them are doubled. No environment variable expansion
+ is simulated.
+ * A exit command status will be converted to a Posix Exit
+ where 0 is success and non-zero is failure.
+ * The $ character will cause environment variable expansion.
+ * Environent variables can be set on the command line before a command.
+
+ VMS generally uses logical name search lists instead of path variables
+ where the resolution is handled by VMS independent of the program. Which
+ means that it is likely that nothing will notice if the default path
+ specifier is changed in the future.
+
+ Currently the built in VMS specific macros and recipes depend on the comma
+ being used as a file list separator.
+ TODO: Remove this dependency as other functions in GNU Make depend on a
+ space being used as a separator.
+
+ The format for recipes are a combination of Unix macros, a subset of
+ simulated UNIX commands, some shell emulation, and OpenVMS commands.
+ This makes the resulting makefiles unique to the OpenVMS port of GNU make.
+
+ If you are creating a OpenVMS specific makefile from scratch, you should also
+ look at MMK (Madgoat Make) available at https://github.com/endlesssoftware/mmk
+ MMK uses full OpenVMS syntax and a persistent subprocess is used for the
+ recipe lines, allowing multiple line rules.
+
+ The default makefile search order is "makefile.vms", "gnumakefile",
+ "makefile". TODO: See if that lookup is case sensitive.
+
+ When Make is invoked from DCL, it will create a foreign command
+ using the name of executable image, with any facility prefix removed,
+ for the duration of the make program, so it can be used internally
+ to recursively run make(). The macro MAKE_COMMAND will be set to
+ this foreign command.
+
+ When make is launched from an exec*() command from a C program,
+ the foreign command is not created. The macro MAKE_COMMAND will be
+ set to the actual command passed as argv[0] to the exec*() function.
+
+ If the DCL symbol or logical name GNV$MAKE_USE_MCR exists, then
+ the macro MAKE_COMMAND will be set to be an "MCR" command with the
+ absolute path used by DCL to launch make. The foreign command
+ will not be created.
+
+ The macro MAKE is set to be the same value as the macro MAKE_COMMAND
+ on all platforms.
+
+ Each recipe command is normally run as a separate spawned processes,
+ except for the cases documented below where a temporary DCL command
+ file may be used.
+
+ BUG: Testing has shown that the commands in the temporary command files
+ are not always created properly. This issue is still under investigation.
+
+ Any macros marked as exported are temporarily created as DCL symbols
+ for child images to use. DCL symbol substitution is not done with these
+ commands.
+ Untested: Symbol substitution.
+
+ When a temporary DCL command file is used, DCL symbol substitution
+ will work.
+
+ For VMS 7.3-1 and earlier, command lines are limited to 255 characters
+ or 1024 characters in a command file.
+ For VMS 7.3-2 and later, command lines are limited to 4059 characters
+ or 8192 characters in a command file.
+
+ VMS limits each token of a command line to 256 characters, and limits
+ a command line to 127 tokens.
+
+ Command lines above the limit length are written to a command file
+ in sys$scratch:.
+
+ In order to handle Unix style extensions to VMS DCL, GNU Make has
+ parsed the recipe commands and them modified them as needed. The
+ parser has been re-written to resolve numerous bugs in handling
+ valid VMS syntax and potential buffer overruns.
+
+ The new parser may need whitespace characters where DCL does not require
+ it, and also may require that quotes are matched were DCL forgives if
+ they are not. There is a small chance that existing VMS specific makefiles
+ will be affected.
+
+ The '<', '>' was previously implemented using command files. Now
+ GNU Make will check to see if the is already a VMS "PIPE" command and
+ if it is not, will convert the command to a VMS "PIPE" command.
+
+ The '>>' redirection has been implemented by using a temporary command file.
+ This will be described later.
+
+ The DCL symbol or logical name GNV$MAKE_USE_CMD_FILE when set to a
+ string starting with one of '1','T', or 'E' for "1", "TRUE", or "ENABLE",
+ then temporary DCL command files are always used for running commands.
+
+ Some recipe strings with embedded new lines will not be handled correctly
+ when a command file is used.
+
+ GNU Make generally does text comparisons for the targets and sources. The
+ make program itself can handle either Unix or OpenVMS format filenames, but
+ normally does not do any conversions from one format to another.
+ TODO: The OpenVMS format syntax handling is incomplete.
+ TODO: ODS-5 EFS support is missing.
+ BUG: The internal routines to convert filenames to and from OpenVMS format
+ do not work correctly.
+
+ Note: In the examples below, line continuations such as a backslash may have
+ been added to make the examples easier to read in this format.
+ BUG: That feature does not completely work at this time.
+
+ Since the OpenVMS utilities generally expect OpenVMS format paths, you will
+ usually have to use OpenVMS format paths for rules and targets.
+ BUG: Relative OpenVMS paths may not work in targets, especially combined
+ with vpaths. This is because GNU make will just concatenate the directories
+ as it does on Unix.
+
+ The variables $^ and $@ separate files with commas instead of spaces.
+ This is controlled by the name GNV$MAKE_COMMA as documented in the
+ previous section.
+
+ While this may seem the natural thing to do with OpenVMS, it actually
+ causes problems when trying to use other make functions that expect the
+ files to be separated by spaces. If you run into this, you need the
+ following workaround to convert the output.
+ TODO: Look at have the $^ and $@ use spaces like on Unix and have
+ and easy to use function to do the conversions and have the built
+ in OpenVMS specific recipes and macros use it.
+
+ Example:
+
+comma := ,
+empty :=
+space := $(empty) $(empty)
+
+foo: $(addsuffix .3,$(subs $(comma),$(space),$^)
+
+
+ Makefile variables are looked up in the current environment. You can set
+ symbols or logicals in DCL and evaluate them in the Makefile via
+ $(<name-of-symbol-or-logical>). Variables defined in the Makefile
+ override OpenVMS symbols/logicals.
+
+ OpenVMS logical and symbols names show up as "environment" using the
+ origin function. when the "-e" option is specified, the origion function
+ shows them as "environment override". On Posix the test scripts indicate
+ that they should show up just as "environment".
+
+ When GNU make reads in a symbol or logical name into the environment, it
+ converts any dollar signs found to double dollar signs for convenience in
+ using DCL symbols and logical names in recipes. When GNU make exports a
+ DCL symbol for a child process, if the first dollar sign found is followed
+ by second dollar sign, then all double dollar signs will be convirted to
+ single dollar signs.
+
+ The variable $(ARCH) is predefined as IA64, ALPHA or VAX respectively.
+ Makefiles for different OpenVMS systems can now be written by checking
+ $(ARCH). Since IA64 and ALPHA are similar, usually just a check for
+ VAX or not VAX is sufficient.
+
+ You may have to update makefiles that assume VAX if not ALPHA.
+
+ifeq ($(ARCH),VAX)
+ $(ECHO) "On the VAX"
+else
+ $(ECHO) "On the ALPHA or IA64"
+endif
+
+ Empty commands are handled correctly and don't end in a new DCL process.
+
+ The exit command needs to have OpenVMS exit codes. To pass a Posix code
+ back to the make script, you need to encode it by multiplying it by 8
+ and then adding %x1035a002 for a failure code and %x1035a001 for a
+ success. Make will interpret any posix code other than 0 as a failure.
+ TODO: Add an option have simulate Posix exit commands in recipes.
+
+ Lexical functions can be used in pipes to simulate shell file test rules.
+
+ Example:
+
+ Posix:
+b : c ; [ -f $@ ] || echo >> $@
+
+ OpenVMS:
+b : c ; if f$$search("$@") then pipe open/append xx $@ ; write xx "" ; close xx
+
+
+ You can also use pipes and turning messages off to silently test for a
+ failure.
+
+x = %x1035a00a
+
+%.b : %.c
+<tab>pipe set mess/nofac/noiden/nosev/notext ; type $^/output=$@ || exit $(x)
+
+
+Runtime issues:
+
+ The OpenVMS C Runtime has a convention for encoding a Posix exit status into
+ to OpenVMS exit codes. These status codes will have the hex value of
+ 0x35a000. OpenVMS exit code may also have a hex value of %x10000000 set on
+ them. This is a flag to tell DCL not to write out the exit code.
+
+ To convert an OpenVMS encoded Posix exit status code to the original code
+ You subtract %x35a000 and any flags from the OpenVMS code and divide it by 8.
+
+ WARNING: Backward-incompatibility!
+ The make program exit now returns the same encoded Posix exit code as on
+ Unix. Previous versions returned the OpenVMS exit status code if that is what
+ caused the recipe to fail.
+ TODO: Provide a way for scripts calling make to obtain that OpenVMS status
+ code.
+
+ Make internally has two error codes, MAKE_FAILURE and MAKE_TROUBLE. These
+ will have the error "-E-" severity set on exit.
+
+ MAKE_TROUBLE is returned only if the option "-q" or "--question" is used and
+ has a Posix value of 1 and an OpenVMS status of %x1035a00a.
+
+ MAKE_FAILURE has a Posix value of 2 and an OpenVMS status of %x1035a012.
+
+ Output from GNU make may have single quotes around some values where on
+ other platforms it does not. Also output that would be in double quotes
+ on some platforms may show up as single quotes on VMS.
+
+ There may be extra blank lines in the output on VMS.
+ https://savannah.gnu.org/bugs/?func=detailitem&item_id=41760
+
+ There may be a "Waiting for unfinished jobs..." show up in the output.
+
+ Error messages generated by Make or Unix utilities may slightly vary from
+ Posix platforms. Typically the case may be different.
+
+ When make deletes files, on posix platforms it writes out 'rm' and the list
+ of files. On VMS, only the files are writen out, one per line.
+ TODO: VMS
+
+ There may be extra leading white space or additional or missing whitespace
+ in the output of recipes.
+
+ GNU Make uses sys$scratch: for the tempfiles that it creates.
+
+ The OpenVMS CRTL library maps /tmp to sys$scratch if the TMP: logical name
+ does not exist. As the CRTL may use both sys$scratch: and /tmp internally,
+ if you define the TMP logical name to be different than SYS$SCRATCH:,
+ you may end up with only some temporary files in TMP: and some in SYS$SCRATCH:
+
+ The default include directory for including other makefiles is
+ SYS$SYSROOT:[SYSLIB] (I don't remember why I didn't just use
+ SYS$LIBRARY: instead; maybe it wouldn't work that way).
+ TODO: A better default may be desired.
+
+ If the device for a file in a recipe does not exist, on OpenVMS an error
+ message of "stat: <file>: no such device or address" will be output.
+
+ Make ignores success, informational, or warning errors (-S-, -I-, or
+ -W-). But it will stop on -E- and -F- errors. (unless you do something
+ to override this in your makefile, or whatever).
+
+
+Unix compatibilty features:
+---------------------------
+
+ If the command 'echo' is seen, any single quotes on the line will be
+ converted to double quotes.
+
+ The variable $(CD) is implemented as a built in Change Directory
+ command. This invokes the 'builtin_cd' Executing a 'set default'
+ recipe doesn't do the trick, since it only affects the subprocess
+ spawned for that command.
+
+ The 'builtin_cd' is generally expected to be on its own line.
+ The 'builtin_cd' either from the expansion of $(CD) or directly
+ put in a recipe line will be executed before any other commands in
+ that recipe line. DCL parameter substitution will not work for the
+ 'builtin_cd' command.
+
+ Putting a 'builtin_cd' in a pipeline or an IF-THEN line should not be
+ done because the 'builtin_cd' is always executed
+ and executed first. The directory change is persistent.
+
+ Unix shell style I/O redirection is supported. You can now write lines like:
+ "<tab>mcr sys$disk:[]program.exe < input.txt > output.txt &> error.txt"
+
+ Posix shells have ":" as a null command. These are now handled.
+ https://savannah.gnu.org/bugs/index.php?41761
+
+ A note on appending the redirected output. A simple mechanism is
+ implemented to make ">>" work in action lines. In OpenVMS there is no simple
+ feature like ">>" to have DCL command or program output redirected and
+ appended to a file. GNU make for OpenVMS implements the redirection
+ of ">>" by using a command procedure.
+
+ The current algorithm creates the output file if it does not exist and
+ then uses the DCL open/append to extend it. SYS$OUTPUT is then directed
+ to that file.
+
+ The implementation supports only one redirected append output to a file
+ and that redirection is done before any other commands in that line
+ are executed, so it redirects all output for that command.
+
+ The older implementation wrote the output to a temporary file in
+ in sys$scratch: and then attempted to append the file to the existing file.
+ The temporary file names looked like "CMDxxxxx.". Any time the created
+ command procedure can not complete, this happens. Pressing Ctrl+Y to
+ abort make is one case.
+
+ In case of Ctrl+Y the associated command procedure is left in SYS$SCRATCH:.
+ The command procedures will be named gnv$make_cmd*.com.
+
+ The CtrlY handler now uses $delprc to delete all children. This way also
+ actions with DCL commands will be stopped. As before the CtrlY handler
+ then sends SIGQUIT to itself, which is handled in common code.
+
+ Temporary command files are now deleted in the OpenVMS child termination
+ handler. That deletes them even if a Ctrl+C was pressed.
+ TODO: Does the previous section about >> leaving files still apply?
+
+ The behavior of pressing Ctrl+C is not changed. It still has only an effect,
+ after the current action is terminated. If that doesn't happen or takes too
+ long, Ctrl+Y should be used instead.
+
+
+Build Options:
+
+ Added support to have case sensitive targets and dependencies but to
+ still use case blind file names. This is especially useful for Java
+ makefiles on VMS:
+
+<TAB>.SUFFIXES :
+<TAB>.SUFFIXES : .class .java
+<TAB>.java.class :
+<TAB><TAB>javac "$<"
+<TAB>HelloWorld.class : HelloWorld.java
+
+ A new macro WANT_CASE_SENSITIVE_TARGETS in config.h-vms was introduced.
+ It needs to be enabled to get this feature; default is disabled.
+ TODO: This should be a run-time setting based on if the process
+ has been set to case sensitive.
+
+
+Unimplemented functionality:
+
+ The new feature "Loadable objects" is not yet supported. If you need it,
+ please send a change request or submit a bug report.
+
+ The new option --output-sync (-O) is accepted but has no effect: GNU make
+ for OpenVMS does not support running multiple commands simultaneously.
+
+
+Self test failures and todos:
+-----------------------------
+
+ The test harness can not handle testing some of the VMS specific modes
+ because of the features needed for to be set for the Perl to run.
+ Need to find a way to set the VMS features before running make as a
+ child.
+
+ GNU make was not currently translating the OpenVMS encoded POSIX values
+ returned to it back to the Posix values. I have temporarily modified the
+ Perl test script to compensate for it. This should be being handled
+ internally to Make.
+ TODO: Verify and update the Perl test script.
+
+ The features/parallelism test was failing. OpenVMS is executing the rules
+ in sequence not in parallel as this feature was not implemented.
+ GNU Make on VMS no longer claims it is implemented.
+ TODO: Implement it.
+
+ Symlink support is not present. Symlinks are supported by OpenVMS 8.3 and
+ later.
+
+ Error messages should be supressed with the "-" at the beginning of a line.
+ On openVMS they were showing up. TODO: Is this still an issue?
+
+ The internal vmsify and unixify OpenVMS to/from UNIX are not handling logical
+ names correctly.
+
+
+Build instructions:
+------------------
+
+ Don't use the HP C V7.2-001 compiler, which has an incompatible change
+ how __STDC__ is defined. This results at least in compile time warnings.
+
+Make a 1st version
+ $ @makefile.com ! ignore any compiler and/or linker warning
+ $ copy make.exe 1st-make.exe
+
+ Use the 1st version to generate a 2nd version as a test.
+ $ mc sys$disk:[]1st-make clean ! ignore any file not found messages
+ $ mc sys$disk:[]1st-make
+
+ Verify your 2nd version by building Make again.
+ $ copy make.exe 2nd-make.exe
+ $ mc sys$disk:[]2nd-make clean
+ $ mc sys$disk:[]2nd-make
+
+
+Running the tests:
+------------------
+
+ Running the tests on OpenVMS requires the following software to be installed
+ as most of the tests are Unix oriented.
+
+ * Perl 5.18 or later.
+ https://sourceforge.net/projects/vmsperlkit/files/
+ * GNV 2.1.3 + Updates including a minimum of:
+ * Bash 4.3.30
+ * ld_tools 3.0.2
+ * coreutils 8.21
+ https://sourceforge.net/p/gnv/wiki/InstallingGNVPackages/
+ https://sourceforge.net/projects/gnv/files/
+
+ As the test scripts need to create some foreign commands that persist
+ after the test is run, it is recommend that either you use a subprocess or
+ a dedicated login to run the tests.
+
+ To get detailed information for running the tests:
+
+ $ set default [.tests]
+ $ @run_make_tests help
+
+ Running the script with no parameters will run all the tests.
+
+ After the the test script has been run once in a session, assuming
+ that you built make in sys$disk:[make], you can redefined the
+ "bin" logical name as follows:
+
+ $ define bin sys$disk:[make],gnv$gnu:[bin]
+
+ Then you can use Perl to run the scripts.
+
+ $ perl run_make_tests.pl
+
+
+Acknowlegements:
+----------------
+
+See NEWS. for details of past changes.
+
+ These are the currently known contributers to this port.
+
+ Hartmut Becker
+ John Malmberg
+ Michael Gehre
+ John Eisenbraun
+ Klaus Kaempf
+ Mike Moretti
+ John W. Eaton
diff --git a/src/kmk/README.W32.template b/src/kmk/README.W32.template
new file mode 100644
index 0000000..3ac3354
--- /dev/null
+++ b/src/kmk/README.W32.template
@@ -0,0 +1,314 @@
+This version of GNU make has been tested on:
+ Microsoft Windows 2000/XP/2003/Vista/7/8/10
+It has also been used on Windows 95/98/NT, and on OS/2.
+
+It builds with the MinGW port of GCC (tested with GCC 3.4.2, 4.8.1,
+and 4.9.3).
+
+It also builds with MSVC 2.x, 4.x, 5.x, 6.x, 2003, and 14 (2015) as
+well as with .NET 7.x and .NET 2003.
+
+As of version 4.0, a build with Guile is supported (tested with Guile
+2.0.3). To build with Guile, you will need, in addition to Guile
+itself, its dependency libraries and the pkg-config program. The
+latter is used to figure out which compilation and link switches and
+libraries need to be mentioned on the compiler command lines to
+correctly link with Guile. A Windows port of pkg-config can be found
+on ezwinports site:
+
+ http://sourceforge.net/projects/ezwinports/
+
+The libraries on which Guile depends can vary depending on your
+version and build of Guile. At the very least, the Boehm's GC library
+will be needed, and typically also GNU MP, libffi, libunistring, and
+libtool's libltdl. Whoever built the port of Guile you have should
+also provide you with these dependencies or a URL where to download
+them. A precompiled 32-bit Windows build of Guile is available from
+the ezwinports site mentioned above.
+
+The Windows port of GNU make is maintained jointly by various people.
+It was originally made by Rob Tulloh.
+It is currently maintained by Eli Zaretskii.
+
+
+Do this first, regardless of the build method you choose:
+---------------------------------------------------------
+
+ 1. Edit config.h.W32 to your liking (especially the few shell-related
+ defines near the end, or HAVE_CASE_INSENSITIVE_FS which corresponds
+ to './configure --enable-case-insensitive-file-system'). (We don't
+ recommend to define HAVE_CASE_INSENSITIVE_FS, but you may wish to
+ consider that if you have a lot of files whose names are in upper
+ case, while Makefile rules are written for lower-case versions.)
+
+
+Using make_msvc_net2003.vcproj
+------------------------------
+
+ 2. Open make_msvc_net2003.vcproj in MSVS71 or MSVC71 or any compatible IDE,
+ then build this project as usual. There's also a solution file for
+ Studio 2003.
+
+
+Building with (MinGW-)GCC using build_w32.bat
+---------------------------------------------
+
+ 2. Open a W32 command prompt for your installed (MinGW-)GCC, setup a
+ correct PATH and other environment variables for it, then execute ...
+
+ build_w32.bat gcc
+
+ This produces gnumake.exe in the GccRel directory.
+ If you want a version of GNU make built with debugging enabled,
+ add the --debug option.
+
+ The batch file will probe for Guile installation, and will build
+ gnumake.exe with Guile if it finds it. If you have Guile
+ installed, but want to build Make without Guile support, type
+
+ build_w32.bat --without-guile gcc
+
+
+Building with (MSVC++-)cl using build_w32.bat or NMakefile
+----------------------------------------------------------
+
+ 2. Open a W32 command prompt for your installed (MSVC++-)cl, setup a
+ correct PATH and other environment variables for it (usually via
+ executing vcvars32.bat or vsvars32.bat from the cl-installation,
+ e.g. "%VS71COMNTOOLS%vsvars32.bat"; or using a corresponding start
+ menue entry from the cl-installation), then execute EITHER ...
+
+ build_w32.bat
+
+ This produces gnumake.exe in the WinRel directory.
+ If you want a version of GNU make built with debugging enabled,
+ add the --debug option.
+
+ ... OR ...
+
+ nmake /f NMakefile
+
+ (this produces WinDebug/make.exe and WinRel/make.exe).
+
+ The batch file will probe for Guile installation, and will build
+ gnumake.exe with Guile if it finds it. If you have Guile
+ installed, but want to build Make without Guile support, type
+
+ build_w32.bat --without-guile
+
+-------------------
+-- Notes/Caveats --
+-------------------
+
+GNU make on Windows 32-bit platforms:
+
+ This version of make is ported natively to Windows32 platforms
+ (Windows NT 3.51, Windows NT 4.0, Windows 2000, Windows XP,
+ Windows 95, and Windows 98). It does not rely on any 3rd party
+ software or add-on packages for building. The only thing
+ needed is a Windows compiler. Two compilers supported
+ officially are the MinGW port of GNU GCC, and the various
+ versions of the Microsoft C compiler.
+
+ Do not confuse this port of GNU make with other Windows32 projects
+ which provide a GNU make binary. These are separate projects
+ and are not connected to this port effort.
+
+GNU make and sh.exe:
+
+ This port prefers if you have a working sh.exe somewhere on
+ your system. If you don't have sh.exe, the port falls back to
+ MSDOS mode for launching programs (via a batch file). The
+ MSDOS mode style execution has not been tested that carefully
+ though (The author uses GNU bash as sh.exe).
+
+ There are very few true ports of Bourne shell for NT right now.
+ There is a version of GNU bash available from Cygnus "Cygwin"
+ porting effort (http://www.cygwin.com/).
+ Other possibilities are the MKS version of sh.exe, or building
+ your own with a package like NutCracker (DataFocus) or Portage
+ (Consensys). Also MinGW includes sh (http://mingw.org/).
+
+GNU make and brain-dead shells (BATCH_MODE_ONLY_SHELL):
+
+ Some versions of Bourne shell do not behave well when invoked
+ as 'sh -c' from CreateProcess(). The main problem is they seem
+ to have a hard time handling quoted strings correctly. This can
+ be circumvented by writing commands to be executed to a batch
+ file and then executing the command by calling 'sh file'.
+
+ To work around this difficulty, this version of make supports
+ a batch mode. When BATCH_MODE_ONLY_SHELL is defined at compile
+ time, make forces all command lines to be executed via script
+ files instead of by command line. In this mode you must have a
+ working sh.exe in order to use parallel builds (-j).
+
+ A native Windows32 system with no Bourne shell will also run
+ in batch mode. All command lines will be put into batch files
+ and executed via $(COMSPEC) (%COMSPEC%). However, parallel
+ builds ARE supported with Windows shells (cmd.exe and
+ command.com). See the next section about some peculiarities
+ of parallel builds on Windows.
+
+Support for parallel builds
+
+ Parallel builds (-jN) are supported in this port, with 1
+ limitation: The number of concurrent processes has a hard
+ limit of 64, due to the way this port implements waiting for
+ its subprocesses.
+
+GNU make and Cygnus GNU Windows32 tools:
+
+ Good news! Make now has native support for Cygwin sh. To enable,
+ define the HAVE_CYGWIN_SHELL in config.h and rebuild make
+ from scratch. This version of make tested with B20.1 of Cygwin.
+ Do not define BATCH_MODE_ONLY_SHELL if you use HAVE_CYGWIN_SHELL.
+
+GNU make and the MKS shell:
+
+ There is now semi-official support for the MKS shell. To turn this
+ support on, define HAVE_MKS_SHELL in the config.h.W32 before you
+ build make. Do not define BATCH_MODE_ONLY_SHELL if you turn
+ on HAVE_MKS_SHELL.
+
+GNU make handling of drive letters in pathnames (PATH, vpath, VPATH):
+
+ There is a caveat that should be noted with respect to handling
+ single character pathnames on Windows systems. When colon is
+ used in PATH variables, make tries to be smart about knowing when
+ you are using colon as a separator versus colon as a drive
+ letter. Unfortunately, something as simple as the string 'x:/'
+ could be interpreted 2 ways: (x and /) or (x:/).
+
+ Make chooses to interpret a letter plus colon (e.g. x:/) as a
+ drive letter pathname. If it is necessary to use single
+ character directories in paths (VPATH, vpath, Path, PATH), the
+ user must do one of two things:
+
+ a. Use semicolon as the separator to disambiguate colon. For
+ example use 'x;/' if you want to say 'x' and '/' are
+ separate components.
+
+ b. Qualify the directory name so that there is more than
+ one character in the path(s) used. For example, none
+ of these settings are ambiguous:
+
+ ./x:./y
+ /some/path/x:/some/path/y
+ x:/some/path/x:x:/some/path/y
+
+ Please note that you are free to mix colon and semi-colon in the
+ specification of paths. Make is able to figure out the intended
+ result and convert the paths internally to the format needed
+ when interacting with the operating system, providing the path
+ is not within quotes, e.g. "x:/test/test.c".
+
+ You are encouraged to use colon as the separator character.
+ This should ease the pain of deciding how to handle various path
+ problems which exist between platforms. If colon is used on
+ both Unix and Windows systems, then no ifdef'ing will be
+ necessary in the makefile source.
+
+GNU make test suite:
+
+ I verified all functionality with a slightly modified version
+ of make-test-%VERSION% (modifications to get test suite to run
+ on Windows NT). All tests pass in an environment that includes
+ sh.exe. Tests were performed on both Windows NT and Windows 95.
+
+Pathnames and white space:
+
+ Unlike Unix, Windows 95/NT systems encourage pathnames which
+ contain white space (e.g. C:\Program Files\). These sorts of
+ pathnames are valid on Unix too, but are never encouraged.
+ There is at least one place in make (VPATH/vpath handling) where
+ paths containing white space will simply not work. There may be
+ others too. I chose to not try and port make in such a way so
+ that these sorts of paths could be handled. I offer these
+ suggestions as workarounds:
+
+ 1. Use 8.3 notation. i.e. "x:/long~1/", which is actually
+ "x:\longpathtest". Type "dir /x" to view these filenames
+ within the cmd.exe shell.
+ 2. Rename the directory so it does not contain white space.
+
+ If you are unhappy with this choice, this is free software
+ and you are free to take a crack at making this work. The code
+ in w32/pathstuff.c and vpath.c would be the places to start.
+
+Pathnames and Case insensitivity:
+
+ Unlike Unix, Windows 95/NT systems are case insensitive but case
+ preserving. For example if you tell the file system to create a
+ file named "Target", it will preserve the case. Subsequent access to
+ the file with other case permutations will succeed (i.e. opening a
+ file named "target" or "TARGET" will open the file "Target").
+
+ By default, GNU make retains its case sensitivity when comparing
+ target names and existing files or directories. It can be
+ configured, however, into a case preserving and case insensitive
+ mode by adding a define for HAVE_CASE_INSENSITIVE_FS to
+ config.h.W32.
+
+ For example, the following makefile will create a file named
+ Target in the directory subdir which will subsequently be used
+ to satisfy the dependency of SUBDIR/DepTarget on SubDir/TARGET.
+ Without HAVE_CASE_INSENSITIVE_FS configured, the dependency link
+ will not be made:
+
+ subdir/Target:
+ touch $@
+
+ SUBDIR/DepTarget: SubDir/TARGET
+ cp $^ $@
+
+ Reliance on this behavior also eliminates the ability of GNU make
+ to use case in comparison of matching rules. For example, it is
+ not possible to set up a C++ rule using %.C that is different
+ than a C rule using %.c. GNU make will consider these to be the
+ same rule and will issue a warning.
+
+SAMBA/NTFS/VFAT:
+
+ I have not had any success building the debug version of this
+ package using SAMBA as my file server. The reason seems to be
+ related to the way VC++ 4.0 changes the case name of the pdb
+ filename it is passed on the command line. It seems to change
+ the name always to to lower case. I contend that the VC++
+ compiler should not change the casename of files that are passed
+ as arguments on the command line. I don't think this was a
+ problem in MSVC 2.x, but I know it is a problem in MSVC 4.x.
+
+ The package builds fine on VFAT and NTFS filesystems.
+
+ Most all of the development I have done to date has been using
+ NTFS and long file names. I have not done any considerable work
+ under VFAT. VFAT users may wish to be aware that this port of
+ make does respect case sensitivity.
+
+FAT:
+
+ Version 3.76 added support for FAT filesystems. Make works
+ around some difficulties with stat'ing of files and caching of
+ filenames and directories internally.
+
+Bug reports:
+
+ Please submit bugs via the normal bug reporting mechanism which
+ is described in the GNU make manual and the base README.
+
+-------------------------------------------------------------------------------
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/README.customs b/src/kmk/README.customs
new file mode 100644
index 0000000..67e1252
--- /dev/null
+++ b/src/kmk/README.customs
@@ -0,0 +1,112 @@
+ -*-indented-text-*-
+
+GNU make can utilize the Customs library, distributed with Pmake, to
+provide builds distributed across multiple hosts.
+
+In order to utilize this capability, you must first download and build
+the Customs library. It is contained in the Pmake distribution, which
+can be obtained at:
+
+ ftp://ftp.icsi.berkeley.edu/pub/ai/stolcke/software/
+
+This integration was tested (superficially) with Pmake 2.1.33.
+
+
+BUILDING CUSTOMS
+----------------
+
+First, build pmake and Customs. You need to build pmake first, because
+Customs require pmake to build. Unfortunately, this is not trivial;
+please see the pmake and Customs documentation for details. The best
+place to look for instructions is in the pmake-2.1.33/INSTALL file.
+
+Note that the 2.1.33 Pmake distribution comes with a set of patches to
+GNU make, distributed in the pmake-2.1.33/etc/gnumake/ directory. These
+patches are based on GNU make 3.75 (there are patches for earlier
+versions of GNU make, also). The parts of this patchfile which relate
+directly to Customs support have already been incorporated into this
+version of GNU make, so you should _NOT_ apply the patch file.
+
+However, there are a few non-Customs specific (as far as I could tell)
+changes here which are not incorporated (for example, the modification
+to try expanding -lfoo to libfoo.so). If you rely on these changes
+you'll need to re-apply them by hand.
+
+Install the Customs library and header files according to the
+documentation. You should also install the man pages (contrary to
+comments in the documentation, they weren't installed automatically for
+me; I had to cd to the 'pmake-2.1.33/doc' directory and run 'pmake
+install' there directly).
+
+
+BUILDING GNU MAKE
+-----------------
+
+Once you've installed Customs, you can build GNU make to use it. When
+configuring GNU make, merely use the '--with-customs=DIR' option.
+Provide the directory containing the 'lib' and 'include/customs'
+subdirectories as DIR. For example, if you installed the customs
+library in /usr/local/lib and the headers in /usr/local/include/customs,
+then you'd pass '--with-customs=/usr/local' as an option to configure.
+
+Run make (or use build.sh) normally to build GNU make as described in
+the INSTALL file.
+
+See the documentation for Customs for information on starting and
+configuring Customs.
+
+
+INVOKING CUSTOMS-IZED GNU MAKE
+-----------------------------
+
+One thing you should be aware of is that the default build environment
+for Customs requires root permissions. Practically, this means that GNU
+make must be installed setuid root to use Customs.
+
+If you don't want to do this, you can build Customs such that root
+permissions are not necessary. Andreas Stolcke <stolcke@speech.sri.com>
+writes:
+
+ > pmake, gnumake or any other customs client program is not required to
+ > be suid root if customs was compiled WITHOUT the USE_RESERVED_PORTS
+ > option in customs/config.h. Make sure the "customs" service in
+ > /etc/services is defined accordingly (port 8231 instead of 1001).
+
+ > Not using USE_RESERVED_PORTS means that a user with programming
+ > skills could impersonate another user by writing a fake customs
+ > client that pretends to be someone other than himself. See the
+ > discussion in etc/SECURITY.
+
+
+PROBLEMS
+--------
+
+SunOS 4.1.x:
+ The customs/sprite.h header file #includes the <malloc.h> header
+ files; this conflicts with GNU make's configuration so you'll get a
+ compile error if you use GCC (or any other ANSI-capable C compiler).
+
+ I commented out the #include in sprite.h:107:
+
+ #if defined(sun) || defined(ultrix) || defined(hpux) || defined(sgi)
+ /* #include <malloc.h> */
+ #else
+
+ YMMV.
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1998-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/README.git b/src/kmk/README.git
new file mode 100644
index 0000000..41a7675
--- /dev/null
+++ b/src/kmk/README.git
@@ -0,0 +1,292 @@
+ -*-text-*-
+
+-------------------------------------------------------------------------------
+Copyright (C) 2002-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+-------------------------------------------------------------------------------
+
+Obtaining Git Code
+------------------
+
+This seems redundant, since if you're reading this you most likely have
+already performed this step; however, for completeness, you can obtain the GNU
+make source code via Git from the FSF's Savannah project
+<http://savannah.gnu.org/projects/make/>:
+
+ $ git clone git://git.savannah.gnu.org/make.git
+
+
+Changes using Git
+-----------------
+
+For non-developers, you can continue to provide patches as before, or if you
+make a public repository I can pull from that if you prefer.
+
+Starting with GNU make 4.0 we no longer keep a separate ChangeLog file in
+source control. We use the Gnulib git-to-changelog conversion script to
+convert the Git comments into ChangeLog-style entries for release. As a
+result, please format your Git comments carefully so they will look clean
+after conversion. In particular, each line of your comment will have a TAB
+added before it so be sure your comment lines are not longer than 72
+characters; prefer 70 or less. Please use standard ChangeLog formats for
+your commit messages (sans the leading TAB of course).
+
+Rule #1: Don't rewrite pushed history (don't use "git push --force").
+
+Typical simple workflow might be:
+
+ * Edit files
+ * Use "git status" and "git diff" to verify your changes
+ * Use "git add" to stage the changes you want to make
+ * Use "git commit" to commit the staged changes to your local repository
+ * Use "git pull" to accept & merge new changes from the Savannah repository
+ * Use "git push" to push your commits back to the Savannah repository
+
+For Emacs users, there are many options for Git integration but I strongly
+recommend the Magit package: http://www.emacswiki.org/emacs/Magit
+It makes the workflow much clearer, and has advanced features such as
+constructing multiple commits from various files and even from different
+diff chunks in the same file. There is a video available which helps a lot.
+
+
+Coding Standards
+----------------
+
+GNU make code adheres to the GNU Coding Standards. Please use only spaces and
+no TAB characters in source code.
+
+Additionally, GNU make is a foundational bootstrap package for the GNU
+project; as such it is very conservative about language features it expects.
+It should build with any C compiler conforming to the ANSI C89 / ISO C90
+standard.
+
+
+Building From Git
+-----------------
+
+To build GNU make from Git, you will need Autoconf, Automake, and
+Gettext, and any tools that those utilities require (GNU m4, Perl,
+etc.). See the configure.ac file to find the minimum versions of each
+of these tools. You will also need a copy of wget and gnulib.
+
+When building from Git you must build in the source directory: "VPATH
+builds" from remote directories are not supported. Once you've created
+a distribution, of course, you can unpack it and do a VPATH build from
+there.
+
+After checking out the code, you will need to perform these steps to get
+to the point where you can run "make".
+
+ 1) $ autoreconf -i
+
+ This rebuilds all the things that need rebuilding, installing
+ missing files as symbolic links.
+
+ You may get warnings here about missing files like README, etc.
+ Ignore them, they are harmless.
+
+ 2) $ ./configure
+
+ Generate a Makefile
+
+ 3) $ make update
+
+ Use wget to retrieve various other files that GNU make relies on,
+ but does not keep in its own source tree.
+
+ NB: You may need GNU make to correctly perform this step; if you use
+ a platform-local make you may get problems with missing files in doc/.
+
+At this point you have successfully brought your Git copy of the GNU
+make source directory up to the point where it can be treated
+more-or-less like the official package you would get from ftp.gnu.org.
+That is, you can just run:
+
+ $ make && make check && make install
+
+to build and install GNU make.
+
+
+Windows builds from Git
+-----------------------
+
+If you have a UNIX emulation like CYGWIN you can opt to run the general
+build procedure above; it will work. Be sure to read
+README.W32.template for information on options you might want to use
+when running ./configure.
+
+If you can't or don't want to do that, then rename the file
+README.W32.template to README.W32 and follow those instructions.
+
+
+Creating a Package
+------------------
+
+Once you have performed the above steps (including the configuration and
+build) you can create a GNU make package. This is very simple, just
+run:
+
+ $ make dist-gzip
+
+and, if you like:
+
+ $ make dist-bzip2
+
+Even better, you should run this:
+
+ $ make distcheck
+
+Which will build both .gz and .bz2 package files, then unpack them into
+a temporary location, try to build them, and repack them, verifying that
+everything works, you get the same results, _and_ no extraneous files
+are left over after the "distclean" rule--whew!! Now, _that_ is why
+converting to Automake is worth the trouble! A big "huzzah!" to Tom
+T. and the AutoToolers!
+
+
+Steps to Release
+----------------
+
+Here are the things that need to be done (in more or less this order)
+before making an official release. If something breaks such that you need to
+change code, be sure to start over again sufficiently that everything is
+consistent (that's why we don't finalize the Git tag, etc. until the end).
+
+ * Update the configure.ac file with the new release number.
+ * Update the EDITION value in the doc/make.texi file.
+ * Update the NEWS file with the release number and date.
+ * Ensure the Savannah bug list URL in the NEWS file uses the correct
+ "Fixed Release" ID number.
+ * Run "make distcheck" to be sure it all works.
+ * Run "make check-alt-config" to be sure alternative configurations work
+ * Run "make update-makeweb" to get a copy of the GNU make web pages
+ * Run "make update-gnuweb" to get a copy of the GNU website boilerplate pages
+ * Update the web page boilerplate if necessary:
+ ../gnu-www/www/server/standards/patch-from-parent ../make-web/make.html \
+ ../gnu-www/www/server/standards/boilerplate.html
+ * Run "make gendocs" (requires gnulib) to generate the manual files for
+ the GNU make web pages.
+ * Follow the directions from gendocs for the web page repository
+ * run "make tag-release" to create a Git tag for the release
+ * Push everything:
+ git push --tags origin master
+
+Manage the Savannah project for GNU make:
+
+ >>> This is only for real releases, not release candidate builds <<<
+
+ * In Savannah modify the "Value", "Rank", and "Description" values for the
+ current "SCM" entry in both "Component Version" and "Fix Release" fields
+ to refer to the new release. The "Rank" field should be 10 less than the
+ previous release so it orders properly.
+ * In Savannah create a new entry for the "Component Version" and "Fix
+ Release" fields:
+ - Value: SCM
+ - Rank: 20
+ - Descr: Issues found in code retrieved from Source Code Management (Git), rather than a distributed version. Please include the SHA you are working with.
+
+ - Descr: Fixed in Source Code Management (Git). The fix will be included in the next release of GNU make.
+
+Start the next release:
+
+ * Update configure.ac and add a ".90" to the release number.
+ * Update the NEWS file with a new section for the release / date.
+ * Update the Savannah URL for the bugs fixed in the NEWS section.
+
+
+Publishing a Package
+--------------------
+
+In order to publish a package on the FSF FTP site, either the release
+site ftp://ftp.gnu.org, or the prerelease site ftp://alpha.gnu.org, you
+first need to have my GPG private key and my passphrase to unlock it.
+And, you can't have them! So there! But, just so I remember here's
+what to do:
+
+ Make sure the "Steps to Release" are complete and committed and tagged.
+
+ git clone git://git.savannah.gnu.org/make.git make-release
+
+ cd make-release
+
+ <run the commands above to build the release>
+
+ make upload-alpha # for alpha.gnu.org (pre-releases)
+ -OR-
+ make upload-ftp # for ftp.gnu.org (official releases)
+
+Depending on your distribution (whether GnuPG is integrated with your keyring
+etc.) it will either pop up a window asking for your GPG key passphrase one
+time, or else it will use the CLI to ask for the GPG passphrase _THREE_ times.
+Sigh.
+
+
+For both final releases and pre-releases, send an email with the URL of
+the package to the GNU translation robot to allow the translators to
+work on it:
+
+ <coordinator@translationproject.org>
+
+
+Where to Announce
+-----------------
+
+Create the announcement in a text file, using 'git shortlog',
+then sign it with GPG:
+
+ gpg --clearsign <announcement.txt>
+
+Or, use your mail client's PGP/GPG signing capabilities.
+
+Announce the release:
+
+ * For release candidate builds:
+ To: bug-make@gnu.org
+ CC: coordinator@translationproject.org
+ BCC: help-make@gnu.org, make-w32@gnu.org, make-alpha@gnu.org
+
+ * For release builds
+ To: info-gnu@gnu.org, bug-make@gnu.org
+ CC: coordinator@translationproject.org
+ BCC: help-make@gnu.org, make-w32@gnu.org, make-alpha@gnu.org
+
+ * Add a news item to the Savannah project site.
+ * Add an update to freecode.com (nee freshmeat.net)
+
+
+Appendix A - For The Brave
+--------------------------
+
+For those of you who trust me implicitly, or are just brave (or
+foolhardy), here is a canned sequence of commands to build a GNU make
+distribution package from a virgin Git source checkout (assuming all the
+prerequisites are available of course).
+
+This list is eminently suitable for a quick swipe o' the mouse and a
+swift click o' mouse-2 into an xterm. Go for it!
+
+autoreconf -i
+./configure
+make update
+make
+make check
+
+Or, for a debugging version:
+
+autoreconf -i && ./configure CFLAGS=-g && make update && make && make check
+
+Or, all-in-one:
+
+autoreconf -i && ./configure && make update && make && make check
diff --git a/src/kmk/README.template b/src/kmk/README.template
new file mode 100644
index 0000000..7739faa
--- /dev/null
+++ b/src/kmk/README.template
@@ -0,0 +1,178 @@
+This directory contains the %VERSION% release of GNU Make.
+
+See the file NEWS for the user-visible changes from previous releases.
+In addition, there have been bugs fixed.
+
+Please check the system-specific notes below for any caveats related to
+your operating system.
+
+For general building and installation instructions, see the file INSTALL.
+
+If you need to build GNU Make and have no other 'make' program to use,
+you can use the shell script 'build.sh' instead. To do this, first run
+'configure' as described in INSTALL. Then, instead of typing 'make' to
+build the program, type 'sh build.sh'. This should compile the program
+in the current directory. Then you will have a Make program that you can
+use for './make install', or whatever else.
+
+Some systems' Make programs are broken and cannot process the Makefile for
+GNU Make. If you get errors from your system's Make when building GNU
+Make, try using 'build.sh' instead.
+
+
+GNU Make is free software. See the file COPYING for copying conditions.
+GNU Make is copyright by the Free Software Foundation. Copyright notices
+condense sequential years into a range; e.g. "1987-1994" means all years
+from 1987 to 1994 inclusive.
+
+Downloading
+-----------
+
+GNU Make can be obtained in many different ways. See a description here:
+
+ http://www.gnu.org/software/software.html
+
+
+Documentation
+-------------
+
+GNU make is fully documented in the GNU Make manual, which is contained
+in this distribution as the file make.texinfo. You can also find
+on-line and preformatted (PostScript and DVI) versions at the FSF's web
+site. There is information there about ordering hardcopy documentation.
+
+ http://www.gnu.org/
+ http://www.gnu.org/doc/doc.html
+ http://www.gnu.org/manual/manual.html
+
+
+Development
+-----------
+
+GNU Make development is hosted by Savannah, the FSF's online development
+management tool. Savannah is here:
+
+ http://savannah.gnu.org
+
+And the GNU Make development page is here:
+
+ http://savannah.gnu.org/projects/make/
+
+You can find most information concerning the development of GNU Make at
+this site.
+
+
+Bug Reporting
+-------------
+
+You can send GNU make bug reports to <bug-make@gnu.org>. Please see the
+section of the GNU make manual entitled 'Problems and Bugs' for
+information on submitting useful and complete bug reports.
+
+You can also use the online bug tracking system in the Savannah GNU Make
+project to submit new problem reports or search for existing ones:
+
+ http://savannah.gnu.org/bugs/?group=make
+
+If you need help using GNU make, try these forums:
+
+ help-make@gnu.org
+ help-utils@gnu.org
+ news:gnu.utils.help
+ news:gnu.utils.bug
+
+
+Git Access
+----------
+
+The GNU make source repository is available via Git from the
+GNU Savannah Git server; look here for details:
+
+ http://savannah.gnu.org/git/?group=make
+
+Please note: you won't be able to build GNU make from Git without
+installing appropriate maintainer's tools, such as GNU m4, automake,
+autoconf, Perl, GNU make, and GCC. See the README.git file for hints on
+how to build GNU make once these tools are available. We make no
+guarantees about the contents or quality of the latest code in the Git
+repository: it is not unheard of for code that is known to be broken to
+be checked in. Use at your own risk.
+
+
+System-specific Notes
+---------------------
+
+It has been reported that the XLC 1.2 compiler on AIX 3.2 is buggy such
+that if you compile make with 'cc -O' on AIX 3.2, it will not work
+correctly. It is said that using 'cc' without '-O' does work.
+
+The standard /bin/sh on SunOS 4.1.3_U1 and 4.1.4 is broken and cannot be
+used to configure GNU make. Please install a different shell such as
+bash or pdksh in order to run "configure". See this message for more
+information:
+ http://mail.gnu.org/archive/html/bug-autoconf/2003-10/msg00190.html
+
+One area that is often a problem in configuration and porting is the code
+to check the system's current load average. To make it easier to test and
+debug this code, you can do 'make check-loadavg' to see if it works
+properly on your system. (You must run 'configure' beforehand, but you
+need not build Make itself to run this test.)
+
+Another potential source of porting problems is the support for large
+files (LFS) in configure for those operating systems that provide it.
+Please report any bugs that you find in this area. If you run into
+difficulties, then as a workaround you should be able to disable LFS by
+adding the '--disable-largefile' option to the 'configure' script.
+
+On systems that support micro- and nano-second timestamp values and
+where stat(2) provides this information, GNU make will use it when
+comparing timestamps to get the most accurate possible result. However,
+note that many current implementations of tools that *set* timestamps do
+not preserve micro- or nano-second granularity. This means that "cp -p"
+and other similar tools (tar, etc.) may not exactly duplicate timestamps
+with micro- and nano-second granularity on some systems. If your build
+system contains rules that depend on proper behavior of tools like "cp
+-p", you should consider using the .LOW_RESOLUTION_TIME pseudo-target to
+force make to treat them properly. See the manual for details.
+
+
+Ports
+-----
+
+ - See README.customs for details on integrating GNU make with the
+ Customs distributed build environment from the Pmake distribution.
+
+ - See README.VMS for details about GNU Make on OpenVMS.
+
+ - See README.Amiga for details about GNU Make on AmigaDOS.
+
+ - See README.W32 for details about GNU Make on Windows NT, 95, or 98.
+
+ - See README.DOS for compilation instructions on MS-DOS and MS-Windows
+ using DJGPP tools.
+
+ A precompiled binary of the MSDOS port of GNU Make is available as part
+ of DJGPP; see the WWW page http://www.delorie.com/djgpp/ for more
+ information.
+
+Please note there are two _separate_ ports of GNU make for Microsoft
+systems: a native Windows tool built with (for example) MSVC or Cygwin,
+and a DOS-based tool built with DJGPP. Please be sure you are looking
+at the right README!
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/SCOPTIONS b/src/kmk/SCOPTIONS
new file mode 100644
index 0000000..f89daae
--- /dev/null
+++ b/src/kmk/SCOPTIONS
@@ -0,0 +1,13 @@
+ERRORREXX
+OPTIMIZE
+NOVERSION
+OPTIMIZERTIME
+OPTIMIZERALIAS
+DEFINE INCLUDEDIR="include:"
+DEFINE LIBDIR="lib:"
+DEFINE NO_ALLOCA
+DEFINE NO_FLOAT
+DEFINE NO_ARCHIVES
+IGNORE=161
+IGNORE=100
+STARTUP=cres
diff --git a/src/kmk/SMakefile.template b/src/kmk/SMakefile.template
new file mode 100644
index 0000000..1b60d85
--- /dev/null
+++ b/src/kmk/SMakefile.template
@@ -0,0 +1,218 @@
+# -*-Makefile-*- for building GNU make with smake
+#
+# NOTE: If you have no 'make' program at all to process this makefile,
+# run 'build.sh' instead.
+#
+# Copyright (C) 1995-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Makefile for GNU Make
+#
+
+# Ultrix 2.2 make doesn't expand the value of VPATH.
+VPATH = /make-%VERSION%/
+# This must repeat the value, because configure will remove 'VPATH = .'.
+srcdir = /make-%VERSION%/
+
+CC = sc
+RM = delete
+MAKE = smake
+
+CFLAGS =
+CPPFLAGS =
+LDFLAGS =
+
+# Define these for your system as follows:
+# -DNO_ARCHIVES To disable 'ar' archive support.
+# -DNO_FLOAT To avoid using floating-point numbers.
+# -DENUM_BITFIELDS If the compiler isn't GCC but groks enum foo:2.
+# Some compilers apparently accept this
+# without complaint but produce losing code,
+# so beware.
+# NeXT 1.0a uses an old version of GCC, which required -D__inline=inline.
+# See also 'config.h'.
+defines =
+
+# Which flavor of remote job execution support to use.
+# The code is found in 'remote-$(REMOTE).c'.
+REMOTE = stub
+
+# If you are using the GNU C library, or have the GNU getopt functions in
+# your C library, you can comment these out.
+GETOPT = getopt.o getopt1.o
+GETOPT_SRC = $(srcdir)getopt.c $(srcdir)getopt1.c $(srcdir)getopt.h
+
+# If you are using the GNU C library, or have the GNU glob functions in
+# your C library, you can comment this out. GNU make uses special hooks
+# into the glob functions to be more efficient (by using make's directory
+# cache for globbing), so you must use the GNU functions even if your
+# system's C library has the 1003.2 glob functions already. Also, the glob
+# functions in the AIX and HPUX C libraries are said to be buggy.
+GLOB = Lib glob/glob.lib
+
+# If your system doesn't have alloca, or the one provided is bad, define this.
+ALLOCA = alloca.o
+ALLOCA_SRC = $(srcdir)alloca.c
+
+# If your system needs extra libraries loaded in, define them here.
+# System V probably need -lPW for alloca. HP-UX 7.0's alloca in
+# libPW.a is broken on HP9000s300 and HP9000s400 machines. Use
+# alloca.c instead on those machines.
+LOADLIBES =
+
+# Any extra object files your system needs.
+extras = amiga.o
+
+# Common prefix for machine-independent installed files.
+prefix =
+# Common prefix for machine-dependent installed files.
+exec_prefix =
+
+# Directory to install 'make' in.
+bindir = sc:c
+# Directory to find libraries in for '-lXXX'.
+libdir = lib:
+# Directory to search by default for included makefiles.
+includedir = include:
+# Directory to install the Info files in.
+infodir = doc:
+# Directory to install the man page in.
+mandir = t:
+# Number to put on the man page filename.
+manext = 1
+# Prefix to put on installed 'make' binary file name.
+binprefix =
+# Prefix to put on installed 'make' man page file name.
+manprefix = $(binprefix)
+
+# Whether or not make needs to be installed setgid.
+# The value should be either 'true' or 'false'.
+# On many systems, the getloadavg function (used to implement the '-l'
+# switch) will not work unless make is installed setgid kmem.
+install_setgid = false
+# Install make setgid to this group so it can read /dev/kmem.
+group = sys
+
+# Program to install 'make'.
+INSTALL_PROGRAM = copy
+# Program to install the man page.
+INSTALL_DATA = copy
+# Generic install program.
+INSTALL = copy
+
+# Program to format Texinfo source into Info files.
+MAKEINFO = makeinfo
+# Program to format Texinfo source into DVI files.
+TEXI2DVI = texi2dvi
+
+# Programs to make tags files.
+ETAGS = etags -w
+CTAGS = ctags -w
+
+#guile = guile.o
+
+objs = commands.o job.o dir.o file.o misc.o main.o read.o remake.o \
+ rule.o implicit.o default.o variable.o expand.o function.o \
+ vpath.o version.o ar.o arscan.o signame.o strcache.o hash.o \
+ output.o remote-$(REMOTE).o $(GLOB) $(GETOPT) $(ALLOCA) \
+ $(extras) $(guile)
+
+srcs = $(srcdir)commands.c $(srcdir)job.c $(srcdir)dir.c \
+ $(srcdir)file.c $(srcdir)getloadavg.c $(srcdir)misc.c \
+ $(srcdir)main.c $(srcdir)read.c $(srcdir)remake.c \
+ $(srcdir)rule.c $(srcdir)implicit.c $(srcdir)default.c \
+ $(srcdir)variable.c $(srcdir)expand.c $(srcdir)function.c \
+ $(srcdir)vpath.c $(srcdir)version.c $(srcdir)hash.c \
+ $(srcdir)guile.c $(srcdir)remote-$(REMOTE).c \
+ $(srcdir)ar.c $(srcdir)arscan.c $(srcdir)strcache.c \
+ $(srcdir)signame.c $(srcdir)signame.h $(GETOPT_SRC) \
+ $(srcdir)commands.h $(srcdir)dep.h $(srcdir)file.h \
+ $(srcdir)job.h $(srcdir)makeint.h $(srcdir)rule.h \
+ $(srcdir)output.c $(srcdir)output.h \
+ $(srcdir)variable.h $(ALLOCA_SRC) $(srcdir)config.h.in
+
+
+.SUFFIXES:
+.SUFFIXES: .o .c .h .ps .dvi .info .texinfo
+
+all: make
+info: make.info
+dvi: make.dvi
+# Some makes apparently use .PHONY as the default goal if it is before 'all'.
+.PHONY: all check info dvi
+
+make.info: make.texinfo
+ $(MAKEINFO) -I$(srcdir) $(srcdir)make.texinfo -o make.info
+
+make.dvi: make.texinfo
+ $(TEXI2DVI) $(srcdir)make.texinfo
+
+make.ps: make.dvi
+ dvi2ps make.dvi > make.ps
+
+make: $(objs) glob/glob.lib
+ $(CC) Link $(LDFLAGS) $(objs) $(LOADLIBES) To make.new
+ -delete quiet make
+ rename make.new make
+
+# -I. is needed to find config.h in the build directory.
+.c.o:
+ $(CC) $(defines) IDir "" IDir $(srcdir)glob \
+ $(CPPFLAGS) $(CFLAGS) $< $(OUTPUT_OPTION)
+
+glob/glob.lib:
+ execute <<
+ cd glob
+ smake
+<
+
+tagsrcs = $(srcs) $(srcdir)remote-*.c
+TAGS: $(tagsrcs)
+ $(ETAGS) $(tagsrcs)
+tags: $(tagsrcs)
+ $(CTAGS) $(tagsrcs)
+
+.PHONY: install installdirs
+install:
+ copy make sc:c
+
+loadavg: loadavg.c config.h
+ $(CC) $(defines) -DTEST -I. -I$(srcdir) $(CFLAGS) $(LDFLAGS) \
+ loadavg.c $(LOADLIBES) -o $@
+
+clean: glob-clean
+ -$(RM) -f make loadavg *.o core make.dvi
+
+distclean: clean glob-realclean
+ -$(RM) -f Makefile config.h config.status build.sh
+ -$(RM) -f config.log config.cache
+ -$(RM) -f TAGS tags
+ -$(RM) -f make.?? make.??s make.log make.toc make.*aux
+ -$(RM) -f loadavg.c
+
+realclean: distclean
+ -$(RM) -f make.info*
+
+mostlyclean: clean
+
+.PHONY: glob-clean glob-realclean
+
+glob-clean glob-realclean:
+ execute <<
+ cd glob
+ smake $@
+<
diff --git a/src/kmk/TODO.private b/src/kmk/TODO.private
new file mode 100644
index 0000000..a56827a
--- /dev/null
+++ b/src/kmk/TODO.private
@@ -0,0 +1,117 @@
+ -*-Indented-Text-*-
+GNU Make TODO List
+------------------
+
+This list comes both from the authors and from users of GNU make.
+
+They are listed in no particular order!
+
+Also, I don't guarantee that all of them will be ultimately deemed "good
+ideas" and implemented. These are just the ones that, at first blush,
+seem to have some merit (and that I can remember).
+
+However, if you see something here you really, really want, speak up.
+All other things being equal, I will tend to implement things that seem
+to maximize user satisfaction.
+
+If you want to implement some of them yourself, barring the ones I've
+marked below, have at it! Please contact me first to let me know you're
+working on it, and give me some info about the design--and, critically,
+information about any user-visible syntax change, etc.
+
+
+The Top Item
+------------
+
+If you know perl (or want to learn DejaGNU or similar), the number one
+priority on my list of things I don't have time to do right now is
+fixing up the GNU make test suite. Most importantly it needs to be made
+"parallelizable", so more than one regression can run at the same time
+(essentially, make the "work" directory local). Also, the CWD during
+the test should be in the work directory or, better, a test-specific
+temporary directory so each test gets a new directory; right now
+sometimes tests leak files into the main directory which causes
+subsequent tests to fail (some tests may need to be tweaked). Beyond
+that, any cleanup done to make writing, reading, or handling tests
+simpler would be great! Please feel free to make whatever changes you
+like to the current tests, given some high-level goals, and that you'll
+port the current tests to whatever you do :).
+
+
+The Rest of the List
+--------------------
+
+ 1) Option to check more than timestamps to determine if targets have
+ changed. This is also a very big one. It's _close_ to my plate :),
+ and I have very definite ideas about how I would like it done.
+ Please pick something else unless you must have this feature. If
+ you try it, please work _extremely_ closely with me on it.
+
+ 1a) Possibly a special case of this is the .KEEP_STATE feature of Sun's
+ make. Some great folks at W U. in Canada did an implementation of
+ this for a class project. Their approach is reasonable and
+ workable, but doesn't really fit into my ideas for #2. Maybe
+ that's OK. I have paperwork for their work so if you want to do
+ this one talk to me to get what they've already done.
+
+ [K R Praveen <praveen@cair.res.in>]
+
+ 2) Currently you can use "%.foo %.bar : %.baz" to mean that one
+ invocation of the rule builds both targets. GNU make needs a way to
+ do that for explicit rules, too. I heard a rumor that some versions
+ of make all you to say "a.foo + a.bar : a.baz" to do this (i.e., a
+ "+" means one invocation builds both). Don't know if this is the
+ best syntax or not... what if you say "a.foo + a.bar a.bam : a.baz";
+ what does that mean?
+
+ 3) Multi-token pattern rule matching (allow %1/%2.c : %1/obj/%2.o,
+ etc., or something like that). Maybe using regex?
+
+ 4) Provide a .TARGETS variable, containing the names of the targets
+ defined in the makefile.
+
+ Actually, I now think a $(targets ...) function, at least, might be
+ better than a MAKETARGETS variable. The argument would be types of
+ targets to list: "phony" is the most useful one. I suppose
+ "default" might also be useful. Maybe some others; check the
+ bitfields to see what might be handy.
+
+ 5) Some sort of operating-system independent way of handling paths
+ would be outstanding, so makefiles can be written for UNIX, VMS,
+ DOS, MS-Windows, Amiga, etc. with a minimum of specialization.
+
+ Or, perhaps related/instead of, some sort of meta-quoting syntax so
+ make can deal with filenames containing spaces, colons, etc. I
+ dunno, maybe something like $[...]? This may well not be worth
+ doing until #1 is done.
+
+ 6) Right now the .PRECIOUS, .INTERMEDIATE, and .SECONDARY
+ pseudo-targets have different capabilities. For example, .PRECIOUS
+ can take a "%", the others can't. Etc. These should all work the
+ same, insofar as that makes sense.
+
+ 7) Improved debugging/logging/etc. capabilities. Part of this is done:
+ I introduced a number of debugging enhancements. Tim Magill is (I
+ think) looking into options to control output more selectively.
+ One thing I want to do in debugging is add a flag to allow debugging
+ of variables as they're expanded (!). This would be incredibly
+ verbose, but could be invaluable when nothing else seems to work and
+ you just can't figure it out. The way variables are expanded now
+ means this isn't 100% trivial, but it probably won't be hard.
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1997-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/acinclude.m4 b/src/kmk/acinclude.m4
new file mode 100644
index 0000000..a80bcb3
--- /dev/null
+++ b/src/kmk/acinclude.m4
@@ -0,0 +1,163 @@
+dnl acinclude.m4 -- Extra macros needed for GNU make.
+dnl
+dnl Automake will incorporate this into its generated aclocal.m4.
+dnl Copyright (C) 1998-2016 Free Software Foundation, Inc.
+dnl This file is part of GNU Make.
+dnl
+dnl GNU Make is free software; you can redistribute it and/or modify it under
+dnl the terms of the GNU General Public License as published by the Free
+dnl Software Foundation; either version 3 of the License, or (at your option)
+dnl any later version.
+dnl
+dnl GNU Make is distributed in the hope that it will be useful, but WITHOUT
+dnl ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+dnl FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+dnl more details.
+dnl
+dnl You should have received a copy of the GNU General Public License along
+dnl with this program. If not, see <http://www.gnu.org/licenses/>.
+
+dnl ---------------------------------------------------------------------------
+dnl Got this from the lynx 2.8 distribution.
+dnl by T.E.Dickey <dickey@clark.net>
+dnl and Jim Spath <jspath@mail.bcpl.lib.md.us>
+dnl and Philippe De Muyter <phdm@macqel.be>
+dnl
+dnl Created: 1997/1/28
+dnl Updated: 1997/12/23
+dnl ---------------------------------------------------------------------------
+dnl After checking for functions in the default $LIBS, make a further check
+dnl for the functions that are netlib-related (these aren't always in the
+dnl libc, etc., and have to be handled specially because there are conflicting
+dnl and broken implementations.
+dnl Common library requirements (in order):
+dnl -lresolv -lsocket -lnsl
+dnl -lnsl -lsocket
+dnl -lsocket
+dnl -lbsd
+AC_DEFUN([CF_NETLIBS],[
+cf_test_netlibs=no
+AC_MSG_CHECKING(for network libraries)
+AC_CACHE_VAL(cf_cv_netlibs,[
+AC_MSG_RESULT(working...)
+cf_cv_netlibs=""
+cf_test_netlibs=yes
+AC_CHECK_FUNCS(gethostname,,[
+ CF_RECHECK_FUNC(gethostname,nsl,cf_cv_netlibs,[
+ CF_RECHECK_FUNC(gethostname,socket,cf_cv_netlibs)])])
+#
+# FIXME: sequent needs this library (i.e., -lsocket -linet -lnsl), but
+# I don't know the entrypoints - 97/7/22 TD
+AC_CHECK_LIB(inet,main,cf_cv_netlibs="-linet $cf_cv_netlibs")
+#
+if test "$ac_cv_func_lsocket" != no ; then
+AC_CHECK_FUNCS(socket,,[
+ CF_RECHECK_FUNC(socket,socket,cf_cv_netlibs,[
+ CF_RECHECK_FUNC(socket,bsd,cf_cv_netlibs)])])
+fi
+#
+AC_CHECK_FUNCS(gethostbyname,,[
+ CF_RECHECK_FUNC(gethostbyname,nsl,cf_cv_netlibs)])
+])
+LIBS="$LIBS $cf_cv_netlibs"
+test $cf_test_netlibs = no && echo "$cf_cv_netlibs" >&AC_FD_MSG
+])dnl
+dnl ---------------------------------------------------------------------------
+dnl Re-check on a function to see if we can pick it up by adding a library.
+dnl $1 = function to check
+dnl $2 = library to check in
+dnl $3 = environment to update (e.g., $LIBS)
+dnl $4 = what to do if this fails
+dnl
+dnl This uses 'unset' if the shell happens to support it, but leaves the
+dnl configuration variable set to 'unknown' if not. This is a little better
+dnl than the normal autoconf test, which gives misleading results if a test
+dnl for the function is made (e.g., with AC_CHECK_FUNC) after this macro is
+dnl used (autoconf does not distinguish between a null token and one that is
+dnl set to 'no').
+AC_DEFUN([CF_RECHECK_FUNC],[
+AC_CHECK_LIB($2,$1,[
+ CF_UPPER(cf_tr_func,$1)
+ AC_DEFINE_UNQUOTED(HAVE_$cf_tr_func,1,[Define if you have function $1])
+ ac_cv_func_$1=yes
+ $3="-l$2 [$]$3"],[
+ ac_cv_func_$1=unknown
+ unset ac_cv_func_$1 2>/dev/null
+ $4],
+ [[$]$3])
+])dnl
+dnl ---------------------------------------------------------------------------
+dnl Make an uppercase version of a variable
+dnl $1=uppercase($2)
+AC_DEFUN([CF_UPPER],
+[
+changequote(,)dnl
+$1=`echo $2 | tr '[a-z]' '[A-Z]'`
+changequote([,])dnl
+])dnl
+
+
+dnl ---------------------------------------------------------------------------
+dnl From Paul Eggert <eggert@twinsun.com>
+dnl Update for Darwin by Troy Runkel <Troy.Runkel@mathworks.com>
+dnl Update for AIX by Olexiy Buyanskyy (Savannah bug 32485)
+
+AC_DEFUN([AC_STRUCT_ST_MTIM_NSEC],
+ [AC_CACHE_CHECK([for nanoseconds field of struct stat],
+ ac_cv_struct_st_mtim_nsec,
+ [ac_save_CPPFLAGS="$CPPFLAGS"
+ ac_cv_struct_st_mtim_nsec=no
+ # st_mtim.tv_nsec -- the usual case
+ # st_mtim._tv_nsec -- Solaris 2.6, if
+ # (defined _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED == 1
+ # && !defined __EXTENSIONS__)
+ # st_mtim.st__tim.tv_nsec -- UnixWare 2.1.2
+ # st_mtime_n -- AIX 5.2 and above
+ # st_mtimespec.tv_nsec -- Darwin (Mac OSX)
+ for ac_val in st_mtim.tv_nsec st_mtim._tv_nsec st_mtim.st__tim.tv_nsec st_mtime_n st_mtimespec.tv_nsec; do
+ CPPFLAGS="$ac_save_CPPFLAGS -DST_MTIM_NSEC=$ac_val"
+ AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/stat.h>
+ ], [struct stat s; s.ST_MTIM_NSEC;],
+ [ac_cv_struct_st_mtim_nsec=$ac_val; break])
+ done
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ ])
+
+ if test $ac_cv_struct_st_mtim_nsec != no; then
+ AC_DEFINE_UNQUOTED([ST_MTIM_NSEC], [$ac_cv_struct_st_mtim_nsec],
+ [Define if struct stat contains a nanoseconds field])
+ fi
+ ]
+)
+
+dnl bird: Copy of above for atime
+AC_DEFUN([AC_STRUCT_ST_ATIM_NSEC],
+ [AC_CACHE_CHECK([for nanoseconds access time field of struct stat],
+ ac_cv_struct_st_atim_nsec,
+ [ac_save_CPPFLAGS="$CPPFLAGS"
+ ac_cv_struct_st_atim_nsec=no
+ # st_atim.tv_nsec -- the usual case
+ # st_atim._tv_nsec -- Solaris 2.6, if
+ # (defined _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED == 1
+ # && !defined __EXTENSIONS__)
+ # st_atim.st__tim.tv_nsec -- UnixWare 2.1.2
+ # st_atime_n -- AIX 5.2 and above
+ # st_atimespec.tv_nsec -- Darwin (Mac OSX)
+ for ac_val in st_atim.tv_nsec st_atim._tv_nsec st_atim.st__tim.tv_nsec st_atime_n st_atimespec.tv_nsec; do
+ CPPFLAGS="$ac_save_CPPFLAGS -DST_ATIM_NSEC=$ac_val"
+ AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/stat.h>
+ ], [struct stat s; s.ST_ATIM_NSEC;],
+ [ac_cv_struct_st_atim_nsec=$ac_val; break])
+ done
+ CPPFLAGS="$ac_save_CPPFLAGS"
+ ])
+
+ if test $ac_cv_struct_st_atim_nsec != no; then
+ AC_DEFINE_UNQUOTED([ST_ATIM_NSEC], [$ac_cv_struct_st_atim_nsec],
+ [Define if struct stat contains a nanoseconds field])
+ fi
+ ]
+)
+
diff --git a/src/kmk/alloca.c b/src/kmk/alloca.c
new file mode 100644
index 0000000..02ac921
--- /dev/null
+++ b/src/kmk/alloca.c
@@ -0,0 +1,503 @@
+/* alloca.c -- allocate automatically reclaimed memory
+ (Mostly) portable public-domain implementation -- D A Gwyn
+
+ This implementation of the PWB library alloca function,
+ which is used to allocate space off the run-time stack so
+ that it is automatically reclaimed upon procedure exit,
+ was inspired by discussions with J. Q. Johnson of Cornell.
+ J.Otto Tennant <jot@cray.com> contributed the Cray support.
+
+ There are some preprocessor constants that can
+ be defined when compiling for your specific system, for
+ improved efficiency; however, the defaults should be okay.
+
+ The general concept of this implementation is to keep
+ track of all alloca-allocated blocks, and reclaim any
+ that are found to be deeper in the stack than the current
+ invocation. This heuristic does not reclaim storage as
+ soon as it becomes invalid, but it will do so eventually.
+
+ As a special case, alloca(0) reclaims storage without
+ allocating any. It is a good idea to use alloca(0) in
+ your main control loop, etc. to force garbage collection. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef emacs
+#include "blockinput.h"
+#endif
+
+/* If compiling with GCC 2, this file's not needed. */
+#if !defined (__GNUC__) || __GNUC__ < 2
+
+/* If someone has defined alloca as a macro,
+ there must be some other way alloca is supposed to work. */
+#ifndef alloca
+
+#ifdef emacs
+#ifdef static
+/* actually, only want this if static is defined as ""
+ -- this is for usg, in which emacs must undefine static
+ in order to make unexec workable
+ */
+#ifndef STACK_DIRECTION
+you
+lose
+-- must know STACK_DIRECTION at compile-time
+#endif /* STACK_DIRECTION undefined */
+#endif /* static */
+#endif /* emacs */
+
+/* If your stack is a linked list of frames, you have to
+ provide an "address metric" ADDRESS_FUNCTION macro. */
+
+#if defined (CRAY) && defined (CRAY_STACKSEG_END)
+long i00afunc ();
+#define ADDRESS_FUNCTION(arg) (char *) i00afunc (&(arg))
+#else
+#define ADDRESS_FUNCTION(arg) &(arg)
+#endif
+
+#if __STDC__
+typedef void *pointer;
+#else
+typedef char *pointer;
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* Different portions of Emacs need to call different versions of
+ malloc. The Emacs executable needs alloca to call xmalloc, because
+ ordinary malloc isn't protected from input signals. On the other
+ hand, the utilities in lib-src need alloca to call malloc; some of
+ them are very simple, and don't have an xmalloc routine.
+
+ Non-Emacs programs expect this to call use xmalloc.
+
+ Callers below should use malloc. */
+
+#ifndef emacs
+#define malloc xmalloc
+#endif
+extern pointer malloc ();
+
+/* Define STACK_DIRECTION if you know the direction of stack
+ growth for your system; otherwise it will be automatically
+ deduced at run-time.
+
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+
+#ifndef STACK_DIRECTION
+#define STACK_DIRECTION 0 /* Direction unknown. */
+#endif
+
+#if STACK_DIRECTION != 0
+
+#define STACK_DIR STACK_DIRECTION /* Known at compile-time. */
+
+#else /* STACK_DIRECTION == 0; need run-time code. */
+
+static int stack_dir; /* 1 or -1 once known. */
+#define STACK_DIR stack_dir
+
+static void
+find_stack_direction (void)
+{
+ static char *addr = NULL; /* Address of first 'dummy', once known. */
+ auto char dummy; /* To get stack address. */
+
+ if (addr == NULL)
+ { /* Initial entry. */
+ addr = ADDRESS_FUNCTION (dummy);
+
+ find_stack_direction (); /* Recurse once. */
+ }
+ else
+ {
+ /* Second entry. */
+ if (ADDRESS_FUNCTION (dummy) > addr)
+ stack_dir = 1; /* Stack grew upward. */
+ else
+ stack_dir = -1; /* Stack grew downward. */
+ }
+}
+
+#endif /* STACK_DIRECTION == 0 */
+
+/* An "alloca header" is used to:
+ (a) chain together all alloca'ed blocks;
+ (b) keep track of stack depth.
+
+ It is very important that sizeof(header) agree with malloc
+ alignment chunk size. The following default should work okay. */
+
+#ifndef ALIGN_SIZE
+#define ALIGN_SIZE sizeof(double)
+#endif
+
+typedef union hdr
+{
+ char align[ALIGN_SIZE]; /* To force sizeof(header). */
+ struct
+ {
+ union hdr *next; /* For chaining headers. */
+ char *deep; /* For stack depth measure. */
+ } h;
+} header;
+
+static header *last_alloca_header = NULL; /* -> last alloca header. */
+
+/* Return a pointer to at least SIZE bytes of storage,
+ which will be automatically reclaimed upon exit from
+ the procedure that called alloca. Originally, this space
+ was supposed to be taken from the current stack frame of the
+ caller, but that method cannot be made to work for some
+ implementations of C, for example under Gould's UTX/32. */
+
+pointer
+alloca (unsigned size)
+{
+ auto char probe; /* Probes stack depth: */
+ register char *depth = ADDRESS_FUNCTION (probe);
+
+#if STACK_DIRECTION == 0
+ if (STACK_DIR == 0) /* Unknown growth direction. */
+ find_stack_direction ();
+#endif
+
+ /* Reclaim garbage, defined as all alloca'd storage that
+ was allocated from deeper in the stack than currently. */
+
+ {
+ register header *hp; /* Traverses linked list. */
+
+#ifdef emacs
+ BLOCK_INPUT;
+#endif
+
+ for (hp = last_alloca_header; hp != NULL;)
+ if ((STACK_DIR > 0 && hp->h.deep > depth)
+ || (STACK_DIR < 0 && hp->h.deep < depth))
+ {
+ register header *np = hp->h.next;
+
+ free ((pointer) hp); /* Collect garbage. */
+
+ hp = np; /* -> next header. */
+ }
+ else
+ break; /* Rest are not deeper. */
+
+ last_alloca_header = hp; /* -> last valid storage. */
+
+#ifdef emacs
+ UNBLOCK_INPUT;
+#endif
+ }
+
+ if (size == 0)
+ return NULL; /* No allocation required. */
+
+ /* Allocate combined header + user data storage. */
+
+ {
+ register pointer new = malloc (sizeof (header) + size);
+ /* Address of header. */
+
+ if (new == 0)
+ abort();
+
+ ((header *) new)->h.next = last_alloca_header;
+ ((header *) new)->h.deep = depth;
+
+ last_alloca_header = (header *) new;
+
+ /* User storage begins just after header. */
+
+ return (pointer) ((char *) new + sizeof (header));
+ }
+}
+
+#if defined (CRAY) && defined (CRAY_STACKSEG_END)
+
+#ifdef DEBUG_I00AFUNC
+#include <stdio.h>
+#endif
+
+#ifndef CRAY_STACK
+#define CRAY_STACK
+#ifndef CRAY2
+/* Stack structures for CRAY-1, CRAY X-MP, and CRAY Y-MP */
+struct stack_control_header
+ {
+ long shgrow:32; /* Number of times stack has grown. */
+ long shaseg:32; /* Size of increments to stack. */
+ long shhwm:32; /* High water mark of stack. */
+ long shsize:32; /* Current size of stack (all segments). */
+ };
+
+/* The stack segment linkage control information occurs at
+ the high-address end of a stack segment. (The stack
+ grows from low addresses to high addresses.) The initial
+ part of the stack segment linkage control information is
+ 0200 (octal) words. This provides for register storage
+ for the routine which overflows the stack. */
+
+struct stack_segment_linkage
+ {
+ long ss[0200]; /* 0200 overflow words. */
+ long sssize:32; /* Number of words in this segment. */
+ long ssbase:32; /* Offset to stack base. */
+ long:32;
+ long sspseg:32; /* Offset to linkage control of previous
+ segment of stack. */
+ long:32;
+ long sstcpt:32; /* Pointer to task common address block. */
+ long sscsnm; /* Private control structure number for
+ microtasking. */
+ long ssusr1; /* Reserved for user. */
+ long ssusr2; /* Reserved for user. */
+ long sstpid; /* Process ID for pid based multi-tasking. */
+ long ssgvup; /* Pointer to multitasking thread giveup. */
+ long sscray[7]; /* Reserved for Cray Research. */
+ long ssa0;
+ long ssa1;
+ long ssa2;
+ long ssa3;
+ long ssa4;
+ long ssa5;
+ long ssa6;
+ long ssa7;
+ long sss0;
+ long sss1;
+ long sss2;
+ long sss3;
+ long sss4;
+ long sss5;
+ long sss6;
+ long sss7;
+ };
+
+#else /* CRAY2 */
+/* The following structure defines the vector of words
+ returned by the STKSTAT library routine. */
+struct stk_stat
+ {
+ long now; /* Current total stack size. */
+ long maxc; /* Amount of contiguous space which would
+ be required to satisfy the maximum
+ stack demand to date. */
+ long high_water; /* Stack high-water mark. */
+ long overflows; /* Number of stack overflow ($STKOFEN) calls. */
+ long hits; /* Number of internal buffer hits. */
+ long extends; /* Number of block extensions. */
+ long stko_mallocs; /* Block allocations by $STKOFEN. */
+ long underflows; /* Number of stack underflow calls ($STKRETN). */
+ long stko_free; /* Number of deallocations by $STKRETN. */
+ long stkm_free; /* Number of deallocations by $STKMRET. */
+ long segments; /* Current number of stack segments. */
+ long maxs; /* Maximum number of stack segments so far. */
+ long pad_size; /* Stack pad size. */
+ long current_address; /* Current stack segment address. */
+ long current_size; /* Current stack segment size. This
+ number is actually corrupted by STKSTAT to
+ include the fifteen word trailer area. */
+ long initial_address; /* Address of initial segment. */
+ long initial_size; /* Size of initial segment. */
+ };
+
+/* The following structure describes the data structure which trails
+ any stack segment. I think that the description in 'asdef' is
+ out of date. I only describe the parts that I am sure about. */
+
+struct stk_trailer
+ {
+ long this_address; /* Address of this block. */
+ long this_size; /* Size of this block (does not include
+ this trailer). */
+ long unknown2;
+ long unknown3;
+ long link; /* Address of trailer block of previous
+ segment. */
+ long unknown5;
+ long unknown6;
+ long unknown7;
+ long unknown8;
+ long unknown9;
+ long unknown10;
+ long unknown11;
+ long unknown12;
+ long unknown13;
+ long unknown14;
+ };
+
+#endif /* CRAY2 */
+#endif /* not CRAY_STACK */
+
+#ifdef CRAY2
+/* Determine a "stack measure" for an arbitrary ADDRESS.
+ I doubt that "lint" will like this much. */
+
+static long
+i00afunc (long *address)
+{
+ struct stk_stat status;
+ struct stk_trailer *trailer;
+ long *block, size;
+ long result = 0;
+
+ /* We want to iterate through all of the segments. The first
+ step is to get the stack status structure. We could do this
+ more quickly and more directly, perhaps, by referencing the
+ $LM00 common block, but I know that this works. */
+
+ STKSTAT (&status);
+
+ /* Set up the iteration. */
+
+ trailer = (struct stk_trailer *) (status.current_address
+ + status.current_size
+ - 15);
+
+ /* There must be at least one stack segment. Therefore it is
+ a fatal error if "trailer" is null. */
+
+ if (trailer == 0)
+ abort ();
+
+ /* Discard segments that do not contain our argument address. */
+
+ while (trailer != 0)
+ {
+ block = (long *) trailer->this_address;
+ size = trailer->this_size;
+ if (block == 0 || size == 0)
+ abort ();
+ trailer = (struct stk_trailer *) trailer->link;
+ if ((block <= address) && (address < (block + size)))
+ break;
+ }
+
+ /* Set the result to the offset in this segment and add the sizes
+ of all predecessor segments. */
+
+ result = address - block;
+
+ if (trailer == 0)
+ {
+ return result;
+ }
+
+ do
+ {
+ if (trailer->this_size <= 0)
+ abort ();
+ result += trailer->this_size;
+ trailer = (struct stk_trailer *) trailer->link;
+ }
+ while (trailer != 0);
+
+ /* We are done. Note that if you present a bogus address (one
+ not in any segment), you will get a different number back, formed
+ from subtracting the address of the first block. This is probably
+ not what you want. */
+
+ return (result);
+}
+
+#else /* not CRAY2 */
+/* Stack address function for a CRAY-1, CRAY X-MP, or CRAY Y-MP.
+ Determine the number of the cell within the stack,
+ given the address of the cell. The purpose of this
+ routine is to linearize, in some sense, stack addresses
+ for alloca. */
+
+static long
+i00afunc (long address)
+{
+ long stkl = 0;
+
+ long size, pseg, this_segment, stack;
+ long result = 0;
+
+ struct stack_segment_linkage *ssptr;
+
+ /* Register B67 contains the address of the end of the
+ current stack segment. If you (as a subprogram) store
+ your registers on the stack and find that you are past
+ the contents of B67, you have overflowed the segment.
+
+ B67 also points to the stack segment linkage control
+ area, which is what we are really interested in. */
+
+ stkl = CRAY_STACKSEG_END ();
+ ssptr = (struct stack_segment_linkage *) stkl;
+
+ /* If one subtracts 'size' from the end of the segment,
+ one has the address of the first word of the segment.
+
+ If this is not the first segment, 'pseg' will be
+ nonzero. */
+
+ pseg = ssptr->sspseg;
+ size = ssptr->sssize;
+
+ this_segment = stkl - size;
+
+ /* It is possible that calling this routine itself caused
+ a stack overflow. Discard stack segments which do not
+ contain the target address. */
+
+ while (!(this_segment <= address && address <= stkl))
+ {
+#ifdef DEBUG_I00AFUNC
+ fprintf (stderr, "%011o %011o %011o\n", this_segment, address, stkl);
+#endif
+ if (pseg == 0)
+ break;
+ stkl = stkl - pseg;
+ ssptr = (struct stack_segment_linkage *) stkl;
+ size = ssptr->sssize;
+ pseg = ssptr->sspseg;
+ this_segment = stkl - size;
+ }
+
+ result = address - this_segment;
+
+ /* If you subtract pseg from the current end of the stack,
+ you get the address of the previous stack segment's end.
+ This seems a little convoluted to me, but I'll bet you save
+ a cycle somewhere. */
+
+ while (pseg != 0)
+ {
+#ifdef DEBUG_I00AFUNC
+ fprintf (stderr, "%011o %011o\n", pseg, size);
+#endif
+ stkl = stkl - pseg;
+ ssptr = (struct stack_segment_linkage *) stkl;
+ size = ssptr->sssize;
+ pseg = ssptr->sspseg;
+ result += size;
+ }
+ return (result);
+}
+
+#endif /* not CRAY2 */
+#endif /* CRAY */
+
+#endif /* no alloca */
+#endif /* not GCC version 2 */
diff --git a/src/kmk/alloccache.c b/src/kmk/alloccache.c
new file mode 100644
index 0000000..54c28d5
--- /dev/null
+++ b/src/kmk/alloccache.c
@@ -0,0 +1,259 @@
+/* $Id: alloccache.c 3141 2018-03-14 21:58:32Z bird $ */
+/** @file
+ * alloccache - Fixed sized allocation cache.
+ *
+ * The rational for using an allocation cache, is that it is way faster
+ * than malloc+free on most systems. It may be more efficient as well,
+ * depending on the way the heap implementes small allocations. Also,
+ * with the incdep.c code being threaded, all heaps (except for MSC)
+ * ran into severe lock contention issues since both the main thread
+ * and the incdep worker thread was allocating a crazy amount of tiny
+ * allocations (struct dep, struct nameseq, ++).
+ *
+ * Darwin also showed a significant amount of time spent just executing
+ * free(), which is kind of silly. The alloccache helps a bit here too.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include "filedef.h"
+#include "dep.h"
+#include "debug.h"
+#include <assert.h>
+
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+
+/* Free am item.
+ This was not inlined because of aliasing issues arrising with GCC.
+ It is also in a separate file for this reason (it used to be in misc.c
+ but since free_dep_chain() was using it there, we ran the risk of it
+ being inlined and gcc screwing up). */
+void
+alloccache_free (struct alloccache *cache, void *item)
+{
+#ifndef CONFIG_WITH_ALLOCCACHE_DEBUG
+ struct alloccache_free_ent *f = (struct alloccache_free_ent *)item;
+# if 0 /*ndef NDEBUG*/
+ struct alloccache_free_ent *c;
+ unsigned int i = 0;
+ for (c = cache->free_head; c != NULL; c = c->next, i++)
+ MY_ASSERT_MSG (c != f && i < 0x10000000,
+ ("i=%u total_count=%u\n", i, cache->total_count));
+# endif
+
+ f->next = cache->free_head;
+ cache->free_head = f;
+ MAKE_STATS(cache->free_count++;);
+#else /* CONFIG_WITH_ALLOCCACHE_DEBUG */
+
+ struct alloccache **ppcache = (struct alloccache **)item - 1;
+ MY_ASSERT_MSG (*ppcache == cache, ("*ppcache=%p cache=%p item=%p\n", *ppcache, cache, item));
+ *ppcache = NULL;
+ free(ppcache);
+#endif /* CONFIG_WITH_ALLOCCACHE_DEBUG */
+}
+
+/* Default allocator. */
+static void *
+alloccache_default_grow_alloc(void *ignore, unsigned int size)
+{
+ return xmalloc (size);
+}
+
+/* Worker for growing the cache. */
+struct alloccache_free_ent *
+alloccache_alloc_grow (struct alloccache *cache)
+{
+#ifndef CONFIG_WITH_ALLOCCACHE_DEBUG
+ void *item;
+ unsigned int items = (64*1024 - 32) / cache->size;
+ cache->free_start = cache->grow_alloc (cache->grow_arg, items * cache->size);
+ cache->free_end = cache->free_start + items * cache->size;
+ cache->total_count+= items;
+
+# ifndef NDEBUG /* skip the first item so the heap can detect free(). */
+ cache->total_count--;
+ cache->free_start += cache->size;
+# endif
+
+ item = cache->free_start;
+ cache->free_start += cache->size;
+ /* caller counts */
+ return (struct alloccache_free_ent *)item;
+#else /* CONFIG_WITH_ALLOCCACHE_DEBUG */
+
+ /* Prefix the allocation with a cache pointer so alloccahce_free can better
+ catch incorrect calls. */
+ struct alloccache **ppcache = (struct alloccache **)xmalloc(sizeof(*ppcache) + cache->size);
+ *ppcache = cache;
+ return (struct alloccache_free_ent *)(ppcache + 1);
+#endif /* CONFIG_WITH_ALLOCCACHE_DEBUG */
+}
+
+/* List of alloc caches, for printing. */
+static struct alloccache *alloccache_head = NULL;
+
+/* Initializes an alloc cache */
+void
+alloccache_init (struct alloccache *cache, unsigned int size, const char *name,
+ void *(*grow_alloc)(void *grow_arg, unsigned int size), void *grow_arg)
+{
+ unsigned act_size;
+
+ /* ensure OK alignment and min sizeof (struct alloccache_free_ent). */
+ if (size <= sizeof (struct alloccache_free_ent))
+ act_size = sizeof (struct alloccache_free_ent);
+ else if (size <= 32)
+ {
+ act_size = 4;
+ while (act_size < size)
+ act_size <<= 1;
+ }
+ else
+ act_size = (size + 31U) & ~(size_t)31;
+
+ /* align the structure. */
+ cache->free_start = NULL;
+ cache->free_end = NULL;
+ cache->free_head = NULL;
+ cache->size = act_size;
+ cache->total_count = 0;
+ cache->alloc_count = 0;
+ cache->free_count = 0;
+ cache->name = name;
+ cache->grow_arg = grow_arg;
+ cache->grow_alloc = grow_alloc ? grow_alloc : alloccache_default_grow_alloc;
+
+ /* link it. */
+ cache->next = alloccache_head;
+ alloccache_head = cache;
+}
+
+/* Terminate an alloc cache, free all the memory it contains. */
+void
+alloccache_term (struct alloccache *cache,
+ void (*term_free)(void *term_arg, void *ptr, unsigned int size), void *term_arg)
+{
+ /*cache->size = 0;*/
+ (void)cache;
+ (void)term_free;
+ (void)term_arg;
+ /* FIXME: Implement memory segment tracking and cleanup. */
+}
+
+/* Joins to caches, unlinking the 2nd one. */
+void
+alloccache_join (struct alloccache *cache, struct alloccache *eat)
+{
+ assert (cache->size == eat->size);
+
+#if 0 /* probably a waste of time */ /* FIXME: Optimize joining, avoid all list walking. */
+ /* add the free list... */
+ if (eat->free_head)
+ {
+ unsigned int eat_in_use = eat->alloc_count - eat->free_count;
+ unsigned int dst_in_use = cache->alloc_count - cache->free_count;
+ if (!cache->free_head)
+ cache->free_head = eat->free_head;
+ else if (eat->total_count - eat_in_use < cache->total_count - dst_ins_use)
+ {
+ struct alloccache_free_ent *last = eat->free_head;
+ while (last->next)
+ last = last->next;
+ last->next = cache->free_head;
+ cache->free_head = eat->free_head;
+ }
+ else
+ {
+ struct alloccache_free_ent *last = cache->free_head;
+ while (last->next)
+ last = last->next;
+ last->next = eat->free_head;
+ }
+ }
+
+ /* ... and the free space. */
+ while (eat->free_start != eat->free_end)
+ {
+ struct alloccache_free_ent *f = (struct alloccache_free_ent *)eat->free_start;
+ eat->free_start += eat->size;
+ f->next = cache->free_head;
+ cache->free_head = f;
+ }
+
+ /* and statistics */
+ cache->alloc_count += eat->alloc_count;
+ cache->free_count += eat->free_count;
+#else
+ /* and statistics */
+ cache->alloc_count += eat->alloc_count;
+ cache->free_count += eat->free_count;
+#endif
+ cache->total_count += eat->total_count;
+
+ /* unlink and disable the eat cache */
+ if (alloccache_head == eat)
+ alloccache_head = eat->next;
+ else
+ {
+ struct alloccache *cur = alloccache_head;
+ while (cur->next != eat)
+ cur = cur->next;
+ assert (cur && cur->next == eat);
+ cur->next = eat->next;
+ }
+
+ eat->size = 0;
+ eat->free_end = eat->free_start = NULL;
+ eat->free_head = NULL;
+}
+
+/* Print one alloc cache. */
+void
+alloccache_print (struct alloccache *cache)
+{
+ printf (_("\n# Alloc Cache: %s\n"
+ "# Items: size = %-3u total = %-6u"),
+ cache->name, cache->size, cache->total_count);
+ MAKE_STATS(printf (_(" in-use = %-6lu"),
+ cache->alloc_count - cache->free_count););
+ MAKE_STATS(printf (_("\n# alloc calls = %-7lu free calls = %-7lu"),
+ cache->alloc_count, cache->free_count););
+ printf ("\n");
+}
+
+/* Print all alloc caches. */
+void
+alloccache_print_all (void)
+{
+ struct alloccache *cur;
+ puts ("");
+ for (cur = alloccache_head; cur; cur = cur->next)
+ alloccache_print (cur);
+}
+
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
diff --git a/src/kmk/amiga.c b/src/kmk/amiga.c
new file mode 100644
index 0000000..cfd0d08
--- /dev/null
+++ b/src/kmk/amiga.c
@@ -0,0 +1,117 @@
+/* Running commands on Amiga
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "variable.h"
+#include "amiga.h"
+#include <assert.h>
+#include <exec/memory.h>
+#include <dos/dostags.h>
+#include <proto/exec.h>
+#include <proto/dos.h>
+
+static const char Amiga_version[] = "$VER: Make 3.74.3 (12.05.96) \n"
+ "Amiga Port by A. Digulla (digulla@home.lake.de)";
+
+int
+MyExecute (char **argv)
+{
+ char * buffer, * ptr;
+ char ** aptr;
+ int len = 0;
+ int status;
+
+ for (aptr=argv; *aptr; aptr++)
+ {
+ len += strlen (*aptr) + 4;
+ }
+
+ buffer = AllocMem (len, MEMF_ANY);
+
+ if (!buffer)
+ O (fatal, NILF, "MyExecute: Cannot allocate space for calling a command\n");
+
+ ptr = buffer;
+
+ for (aptr=argv; *aptr; aptr++)
+ {
+ if (((*aptr)[0] == ';' && !(*aptr)[1]))
+ {
+ *ptr ++ = '"';
+ strcpy (ptr, *aptr);
+ ptr += strlen (ptr);
+ *ptr ++ = '"';
+ }
+ else if ((*aptr)[0] == '@' && (*aptr)[1] == '@' && !(*aptr)[2])
+ {
+ *ptr ++ = '\n';
+ continue;
+ }
+ else
+ {
+ strcpy (ptr, *aptr);
+ ptr += strlen (ptr);
+ }
+ *ptr ++ = ' ';
+ *ptr = 0;
+ }
+
+ ptr[-1] = '\n';
+
+ status = SystemTags (buffer,
+ SYS_UserShell, TRUE,
+ TAG_END);
+
+ FreeMem (buffer, len);
+
+ if (SetSignal (0L,0L) & SIGBREAKF_CTRL_C)
+ status = 20;
+
+ /* Warnings don't count */
+ if (status == 5)
+ status = 0;
+
+ return status;
+}
+
+char *
+wildcard_expansion (char *wc, char *o)
+{
+# define PATH_SIZE 1024
+ struct AnchorPath * apath;
+
+ if ( (apath = AllocMem (sizeof (struct AnchorPath) + PATH_SIZE,
+ MEMF_CLEAR))
+ )
+ {
+ apath->ap_Strlen = PATH_SIZE;
+
+ if (MatchFirst (wc, apath) == 0)
+ {
+ do
+ {
+ o = variable_buffer_output (o, apath->ap_Buf,
+ strlen (apath->ap_Buf));
+ o = variable_buffer_output (o, " ",1);
+ } while (MatchNext (apath) == 0);
+ }
+
+ MatchEnd (apath);
+ FreeMem (apath, sizeof (struct AnchorPath) + PATH_SIZE);
+ }
+
+ return o;
+}
diff --git a/src/kmk/amiga.h b/src/kmk/amiga.h
new file mode 100644
index 0000000..afc910a
--- /dev/null
+++ b/src/kmk/amiga.h
@@ -0,0 +1,18 @@
+/* Definitions for amiga specific things
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+int MyExecute (char ** argv);
+char * wildcard_expansion (char * wc, char * o);
diff --git a/src/kmk/ar.c b/src/kmk/ar.c
new file mode 100644
index 0000000..b9c1cf7
--- /dev/null
+++ b/src/kmk/ar.c
@@ -0,0 +1,328 @@
+/* Interface to 'ar' archives for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#ifndef NO_ARCHIVES
+
+#include "filedef.h"
+#include "dep.h"
+#include <fnmatch.h>
+
+/* Return nonzero if NAME is an archive-member reference, zero if not. An
+ archive-member reference is a name like 'lib(member)' where member is a
+ non-empty string.
+ If a name like 'lib((entry))' is used, a fatal error is signaled at
+ the attempt to use this unsupported feature. */
+
+int
+ar_name (const char *name)
+{
+ const char *p = strchr (name, '(');
+ const char *end;
+
+ if (p == 0 || p == name)
+ return 0;
+
+ end = p + strlen (p) - 1;
+ if (*end != ')' || end == p + 1)
+ return 0;
+
+ if (p[1] == '(' && end[-1] == ')')
+ OS (fatal, NILF, _("attempt to use unsupported feature: '%s'"), name);
+
+ return 1;
+}
+
+
+/* Parse the archive-member reference NAME into the archive and member names.
+ Creates one allocated string containing both names, pointed to by ARNAME_P.
+ MEMNAME_P points to the member. */
+
+void
+ar_parse_name (const char *name, char **arname_p, char **memname_p)
+{
+ char *p;
+
+ *arname_p = xstrdup (name);
+ p = strchr (*arname_p, '(');
+ *(p++) = '\0';
+ p[strlen (p) - 1] = '\0';
+ *memname_p = p;
+}
+
+
+/* This function is called by 'ar_scan' to find which member to look at. */
+
+/* ARGSUSED */
+static long int
+ar_member_date_1 (int desc UNUSED, const char *mem, int truncated,
+ long int hdrpos UNUSED, long int datapos UNUSED,
+ long int size UNUSED, long int date,
+ int uid UNUSED, int gid UNUSED, unsigned int mode UNUSED,
+ const void *name)
+{
+ return ar_name_equal (name, mem, truncated) ? date : 0;
+}
+
+/* Return the modtime of NAME. */
+
+time_t
+ar_member_date (const char *name)
+{
+ char *arname;
+ char *memname;
+ long int val;
+
+ ar_parse_name (name, &arname, &memname);
+
+ /* Make sure we know the modtime of the archive itself because we are
+ likely to be called just before commands to remake a member are run,
+ and they will change the archive itself.
+
+ But we must be careful not to enter_file the archive itself if it does
+ not exist, because pattern_search assumes that files found in the data
+ base exist or can be made. */
+ {
+ struct file *arfile;
+ arfile = lookup_file (arname);
+ if (arfile == 0 && file_exists_p (arname))
+ arfile = enter_file (strcache_add (arname));
+
+ if (arfile != 0)
+ (void) f_mtime (arfile, 0);
+ }
+
+ val = ar_scan (arname, ar_member_date_1, memname);
+
+ free (arname);
+
+ return (val <= 0 ? (time_t) -1 : (time_t) val);
+}
+
+/* Set the archive-member NAME's modtime to now. */
+
+#ifdef VMS
+int
+ar_touch (const char *name)
+{
+ O (error, NILF, _("touch archive member is not available on VMS"));
+ return -1;
+}
+#else
+int
+ar_touch (const char *name)
+{
+ char *arname, *memname;
+ int val;
+
+ ar_parse_name (name, &arname, &memname);
+
+ /* Make sure we know the modtime of the archive itself before we
+ touch the member, since this will change the archive modtime. */
+ {
+ struct file *arfile;
+ arfile = enter_file (strcache_add (arname));
+ f_mtime (arfile, 0);
+ }
+
+ val = 1;
+ switch (ar_member_touch (arname, memname))
+ {
+ case -1:
+ OS (error, NILF, _("touch: Archive '%s' does not exist"), arname);
+ break;
+ case -2:
+ OS (error, NILF, _("touch: '%s' is not a valid archive"), arname);
+ break;
+ case -3:
+ perror_with_name ("touch: ", arname);
+ break;
+ case 1:
+ OSS (error, NILF,
+ _("touch: Member '%s' does not exist in '%s'"), memname, arname);
+ break;
+ case 0:
+ val = 0;
+ break;
+ default:
+ OS (error, NILF,
+ _("touch: Bad return code from ar_member_touch on '%s'"), name);
+ }
+
+ free (arname);
+
+ return val;
+}
+#endif /* !VMS */
+
+/* State of an 'ar_glob' run, passed to 'ar_glob_match'. */
+
+/* On VMS, (object) modules in libraries do not have suffixes. That is, to
+ find a match for a pattern, the pattern must not have any suffix. So the
+ suffix of the pattern is saved and the pattern is stripped (ar_glob).
+ If there is a match and the match, which is a module name, is added to
+ the chain, the saved suffix is added back to construct a source filename
+ (ar_glob_match). */
+
+struct ar_glob_state
+ {
+ const char *arname;
+ const char *pattern;
+#ifdef VMS
+ char *suffix;
+#endif
+ unsigned int size;
+ struct nameseq *chain;
+ unsigned int n;
+ };
+
+/* This function is called by 'ar_scan' to match one archive
+ element against the pattern in STATE. */
+
+static long int
+ar_glob_match (int desc UNUSED, const char *mem, int truncated UNUSED,
+ long int hdrpos UNUSED, long int datapos UNUSED,
+ long int size UNUSED, long int date UNUSED, int uid UNUSED,
+ int gid UNUSED, unsigned int mode UNUSED, const void *arg)
+{
+ struct ar_glob_state *state = (struct ar_glob_state *)arg;
+
+ if (fnmatch (state->pattern, mem, FNM_PATHNAME|FNM_PERIOD) == 0)
+ {
+ /* We have a match. Add it to the chain. */
+ struct nameseq *new = xcalloc (state->size);
+#ifdef VMS
+ if (state->suffix)
+ new->name = strcache_add(
+ concat(5, state->arname, "(", mem, state->suffix, ")"));
+ else
+#endif
+ new->name = strcache_add(concat(4, state->arname, "(", mem, ")"));
+ new->next = state->chain;
+ state->chain = new;
+ ++state->n;
+ }
+
+ return 0L;
+}
+
+/* Return nonzero if PATTERN contains any metacharacters.
+ Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
+static int
+ar_glob_pattern_p (const char *pattern, int quote)
+{
+ const char *p;
+ int opened = 0;
+
+ for (p = pattern; *p != '\0'; ++p)
+ switch (*p)
+ {
+ case '?':
+ case '*':
+ return 1;
+
+ case '\\':
+ if (quote)
+ ++p;
+ break;
+
+ case '[':
+ opened = 1;
+ break;
+
+ case ']':
+ if (opened)
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+
+/* Glob for MEMBER_PATTERN in archive ARNAME.
+ Return a malloc'd chain of matching elements (or nil if none). */
+
+struct nameseq *
+ar_glob (const char *arname, const char *member_pattern, unsigned int size)
+{
+ struct ar_glob_state state;
+ struct nameseq *n;
+ const char **names;
+ unsigned int i;
+#ifdef VMS
+ char *vms_member_pattern;
+#endif
+ if (! ar_glob_pattern_p (member_pattern, 1))
+ return 0;
+
+ /* Scan the archive for matches.
+ ar_glob_match will accumulate them in STATE.chain. */
+ state.arname = arname;
+ state.pattern = member_pattern;
+#ifdef VMS
+ {
+ /* In a copy of the pattern, find the suffix, save it and remove it from
+ the pattern */
+ char *lastdot;
+ vms_member_pattern = xstrdup(member_pattern);
+ lastdot = strrchr(vms_member_pattern, '.');
+ state.suffix = lastdot;
+ if (lastdot)
+ {
+ state.suffix = xstrdup(lastdot);
+ *lastdot = 0;
+ }
+ state.pattern = vms_member_pattern;
+ }
+#endif
+ state.size = size;
+ state.chain = 0;
+ state.n = 0;
+ ar_scan (arname, ar_glob_match, &state);
+
+#ifdef VMS
+ /* Deallocate any duplicated string */
+ free(vms_member_pattern);
+ if (state.suffix)
+ {
+ free(state.suffix);
+ }
+#endif
+
+ if (state.chain == 0)
+ return 0;
+
+ /* Now put the names into a vector for sorting. */
+ names = alloca (state.n * sizeof (const char *));
+ i = 0;
+ for (n = state.chain; n != 0; n = n->next)
+ names[i++] = n->name;
+
+ /* Sort them alphabetically. */
+ /* MSVC erroneously warns without a cast here. */
+ qsort ((void *)names, i, sizeof (*names), alpha_compare);
+
+ /* Put them back into the chain in the sorted order. */
+ i = 0;
+ for (n = state.chain; n != 0; n = n->next)
+ n->name = names[i++];
+
+ return state.chain;
+}
+
+#endif /* Not NO_ARCHIVES. */
diff --git a/src/kmk/arscan.c b/src/kmk/arscan.c
new file mode 100644
index 0000000..2baa8c8
--- /dev/null
+++ b/src/kmk/arscan.c
@@ -0,0 +1,982 @@
+/* Library function for scanning an archive file.
+Copyright (C) 1987-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#ifdef TEST
+/* Hack, the real error() routine eventually pulls in die from main.c */
+#define error(a, b, c, d)
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+
+#ifndef NO_ARCHIVES
+
+#ifdef VMS
+#include <lbrdef.h>
+#include <mhddef.h>
+#include <credef.h>
+#include <descrip.h>
+#include <ctype.h>
+#include <ssdef.h>
+#include <stsdef.h>
+#include <rmsdef.h>
+
+/* This symbol should be present in lbrdef.h. */
+#ifndef LBR$_HDRTRUNC
+#pragma extern_model save
+#pragma extern_model globalvalue
+extern unsigned int LBR$_HDRTRUNC;
+#pragma extern_model restore
+#endif
+
+#include <unixlib.h>
+#include <lbr$routines.h>
+
+const char *
+vmsify (const char *name, int type);
+
+/* Time conversion from VMS to Unix
+ Conversion from local time (stored in library) to GMT (needed for gmake)
+ Note: The tm_gmtoff element is a VMS extension to the ANSI standard. */
+static time_t
+vms_time_to_unix(void *vms_time)
+{
+ struct tm *tmp;
+ time_t unix_time;
+
+ unix_time = decc$fix_time(vms_time);
+ tmp = localtime(&unix_time);
+ unix_time -= tmp->tm_gmtoff;
+
+ return unix_time;
+}
+
+
+/* VMS library routines need static variables for callback */
+static void *VMS_lib_idx;
+
+static const void *VMS_saved_arg;
+
+static long int (*VMS_function) ();
+
+static long int VMS_function_ret;
+
+
+/* This is a callback procedure for lib$get_index */
+static int
+VMS_get_member_info(struct dsc$descriptor_s *module, unsigned long *rfa)
+{
+ int status, i;
+ const int truncated = 0; /* Member name may be truncated */
+ time_t member_date; /* Member date */
+ char *filename;
+ unsigned int buffer_length; /* Actual buffer length */
+
+ /* Unused constants - Make does not actually use most of these */
+ const int file_desc = -1; /* archive file descriptor for reading the data */
+ const int header_position = 0; /* Header position */
+ const int data_position = 0; /* Data position in file */
+ const int data_size = 0; /* Data size */
+ const int uid = 0; /* member gid */
+ const int gid = 0; /* member gid */
+ const int mode = 0; /* member protection mode */
+ /* End of unused constants */
+
+ static struct dsc$descriptor_s bufdesc =
+ { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
+
+ /* Only need the module definition */
+ struct mhddef *mhd;
+
+ /* If a previous callback is non-zero, just return that status */
+ if (VMS_function_ret)
+ {
+ return SS$_NORMAL;
+ }
+
+ /* lbr_set_module returns more than just the module header. So allocate
+ a buffer which is big enough: the maximum LBR$C_MAXHDRSIZ. That's at
+ least bigger than the size of struct mhddef.
+ If the request is too small, a buffer truncated warning is issued so
+ it can be reissued with a larger buffer.
+ We do not care if the buffer is truncated, so that is still a success. */
+ mhd = xmalloc(LBR$C_MAXHDRSIZ);
+ bufdesc.dsc$a_pointer = (char *) mhd;
+ bufdesc.dsc$w_length = LBR$C_MAXHDRSIZ;
+
+ status = lbr$set_module(&VMS_lib_idx, rfa, &bufdesc, &buffer_length, 0);
+
+ if ((status != LBR$_HDRTRUNC) && !$VMS_STATUS_SUCCESS(status))
+ {
+ ON(error, NILF,
+ _("lbr$set_module() failed to extract module info, status = %d"),
+ status);
+
+ lbr$close(&VMS_lib_idx);
+
+ return status;
+ }
+
+#ifdef TEST
+ /* When testing this code, it is useful to know the length returned */
+ printf("Input length = %d, actual = %d\n",
+ bufdesc.dsc$w_length, buffer_length);
+#endif
+
+ /* Conversion from VMS time to C time.
+ VMS defectlet - mhddef is sub-optimal, for the time, it has a 32 bit
+ longword, mhd$l_datim, and a 32 bit fill instead of two longwords, or
+ equivalent. */
+ member_date = vms_time_to_unix(&mhd->mhd$l_datim);
+ free(mhd);
+
+ /* Here we have a problem. The module name on VMS does not have
+ a file type, but the filename pattern in the "VMS_saved_arg"
+ may have one.
+ But only the method being called knows how to interpret the
+ filename pattern.
+ There are currently two different formats being used.
+ This means that we need a VMS specific code in those methods
+ to handle it. */
+ filename = xmalloc(module->dsc$w_length + 1);
+
+ /* TODO: We may need an option to preserve the case of the module
+ For now force the module name to lower case */
+ for (i = 0; i < module->dsc$w_length; i++)
+ filename[i] = _tolower((unsigned char )module->dsc$a_pointer[i]);
+
+ filename[i] = '\0';
+
+ VMS_function_ret = (*VMS_function)(file_desc, filename, truncated,
+ header_position, data_position, data_size, member_date, uid, gid, mode,
+ VMS_saved_arg);
+
+ free(filename);
+ return SS$_NORMAL;
+}
+
+
+/* Takes three arguments ARCHIVE, FUNCTION and ARG.
+
+ Open the archive named ARCHIVE, find its members one by one,
+ and for each one call FUNCTION with the following arguments:
+ archive file descriptor for reading the data,
+ member name,
+ member name might be truncated flag,
+ member header position in file,
+ member data position in file,
+ member data size,
+ member date,
+ member uid,
+ member gid,
+ member protection mode,
+ ARG.
+
+ NOTE: on VMS systems, only name, date, and arg are meaningful!
+
+ The descriptor is poised to read the data of the member
+ when FUNCTION is called. It does not matter how much
+ data FUNCTION reads.
+
+ If FUNCTION returns nonzero, we immediately return
+ what FUNCTION returned.
+
+ Returns -1 if archive does not exist,
+ Returns -2 if archive has invalid format.
+ Returns 0 if have scanned successfully. */
+
+long int
+ar_scan (const char *archive, ar_member_func_t function, const void *varg)
+{
+ char *vms_archive;
+
+ static struct dsc$descriptor_s libdesc =
+ { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL };
+
+ const unsigned long func = LBR$C_READ;
+ const unsigned long type = LBR$C_TYP_UNK;
+ const unsigned long index = 1;
+ unsigned long lib_idx;
+ int status;
+
+ VMS_saved_arg = varg;
+
+ /* Null archive string can show up in test and cause an access violation */
+ if (archive == NULL)
+ {
+ /* Null filenames do not exist */
+ return -1;
+ }
+
+ /* archive path name must be in VMS format */
+ vms_archive = (char *) vmsify(archive, 0);
+
+ status = lbr$ini_control(&VMS_lib_idx, &func, &type, 0);
+
+ if (!$VMS_STATUS_SUCCESS(status))
+ {
+ ON(error, NILF, _("lbr$ini_control() failed with status = %d"), status);
+ return -2;
+ }
+
+ libdesc.dsc$a_pointer = vms_archive;
+ libdesc.dsc$w_length = strlen(vms_archive);
+
+ status = lbr$open(&VMS_lib_idx, &libdesc, 0, NULL, 0, NULL, 0);
+
+ if (!$VMS_STATUS_SUCCESS(status))
+ {
+
+ /* TODO: A library format failure could mean that this is a file
+ generated by the GNU AR utility and in that case, we need to
+ take the UNIX codepath. This will also take a change to the
+ GNV AR wrapper program. */
+
+ switch (status)
+ {
+ case RMS$_FNF:
+ /* Archive does not exist */
+ return -1;
+ default:
+#ifndef TEST
+ OSN(error, NILF,
+ _("unable to open library '%s' to lookup member status %d"),
+ archive, status);
+#endif
+ /* For library format errors, specification says to return -2 */
+ return -2;
+ }
+ }
+
+ VMS_function = function;
+
+ /* Clear the return status, as we are supposed to stop calling the
+ callback function if it becomes non-zero, and this is a static
+ variable. */
+ VMS_function_ret = 0;
+
+ status = lbr$get_index(&VMS_lib_idx, &index, VMS_get_member_info, NULL, 0);
+
+ lbr$close(&VMS_lib_idx);
+
+ /* Unless a failure occurred in the lbr$ routines, return the
+ the status from the 'function' routine. */
+ if ($VMS_STATUS_SUCCESS(status))
+ {
+ return VMS_function_ret;
+ }
+
+ /* This must be something wrong with the library and an error
+ message should already have been printed. */
+ return -2;
+}
+
+#else /* !VMS */
+
+/* SCO Unix's compiler defines both of these. */
+#ifdef M_UNIX
+#undef M_XENIX
+#endif
+
+/* On the sun386i and in System V rel 3, ar.h defines two different archive
+ formats depending upon whether you have defined PORTAR (normal) or PORT5AR
+ (System V Release 1). There is no default, one or the other must be defined
+ to have a nonzero value. */
+
+#if (!defined (PORTAR) || PORTAR == 0) && (!defined (PORT5AR) || PORT5AR == 0)
+#undef PORTAR
+#ifdef M_XENIX
+/* According to Jim Sievert <jas1@rsvl.unisys.com>, for SCO XENIX defining
+ PORTAR to 1 gets the wrong archive format, and defining it to 0 gets the
+ right one. */
+#define PORTAR 0
+#else
+#define PORTAR 1
+#endif
+#endif
+
+/* On AIX, define these symbols to be sure to get both archive formats.
+ AIX 4.3 introduced the "big" archive format to support 64-bit object
+ files, so on AIX 4.3 systems we need to support both the "normal" and
+ "big" archive formats. An archive's format is indicated in the
+ "fl_magic" field of the "FL_HDR" structure. For a normal archive,
+ this field will be the string defined by the AIAMAG symbol. For a
+ "big" archive, it will be the string defined by the AIAMAGBIG symbol
+ (at least on AIX it works this way).
+
+ Note: we'll define these symbols regardless of which AIX version
+ we're compiling on, but this is okay since we'll use the new symbols
+ only if they're present. */
+#ifdef _AIX
+# define __AR_SMALL__
+# define __AR_BIG__
+#endif
+
+#ifndef WINDOWS32
+# if !defined (__ANDROID__) && !defined (__BEOS__) && !defined (__HAIKU__) /* bird: exclude haiku */
+# include <ar.h>
+# else
+ /* These platforms don't have <ar.h> but have archives in the same format
+ * as many other Unices. This was taken from GNU binutils for BeOS.
+ */
+# define ARMAG "!<arch>\n" /* String that begins an archive file. */
+# define SARMAG 8 /* Size of that string. */
+# define ARFMAG "`\n" /* String in ar_fmag at end of each header. */
+struct ar_hdr
+ {
+ char ar_name[16]; /* Member file name, sometimes / terminated. */
+ char ar_date[12]; /* File date, decimal seconds since Epoch. */
+ char ar_uid[6], ar_gid[6]; /* User and group IDs, in ASCII decimal. */
+ char ar_mode[8]; /* File mode, in ASCII octal. */
+ char ar_size[10]; /* File size, in ASCII decimal. */
+ char ar_fmag[2]; /* Always contains ARFMAG. */
+ };
+# endif
+# define TOCHAR(_m) (_m)
+#else
+/* These should allow us to read Windows (VC++) libraries (according to Frank
+ * Libbrecht <frankl@abzx.belgium.hp.com>)
+ */
+# include <windows.h>
+# include <windef.h>
+# include <io.h>
+# define ARMAG IMAGE_ARCHIVE_START
+# define SARMAG IMAGE_ARCHIVE_START_SIZE
+# define ar_hdr _IMAGE_ARCHIVE_MEMBER_HEADER
+# define ar_name Name
+# define ar_mode Mode
+# define ar_size Size
+# define ar_date Date
+# define ar_uid UserID
+# define ar_gid GroupID
+/* In Windows the member names have type BYTE so we must cast them. */
+# define TOCHAR(_m) ((char *)(_m))
+#endif
+
+/* Cray's <ar.h> apparently defines this. */
+#ifndef AR_HDR_SIZE
+# define AR_HDR_SIZE (sizeof (struct ar_hdr))
+#endif
+
+/* Takes three arguments ARCHIVE, FUNCTION and ARG.
+
+ Open the archive named ARCHIVE, find its members one by one,
+ and for each one call FUNCTION with the following arguments:
+ archive file descriptor for reading the data,
+ member name,
+ member name might be truncated flag,
+ member header position in file,
+ member data position in file,
+ member data size,
+ member date,
+ member uid,
+ member gid,
+ member protection mode,
+ ARG.
+
+ The descriptor is poised to read the data of the member
+ when FUNCTION is called. It does not matter how much
+ data FUNCTION reads.
+
+ If FUNCTION returns nonzero, we immediately return
+ what FUNCTION returned.
+
+ Returns -1 if archive does not exist,
+ Returns -2 if archive has invalid format.
+ Returns 0 if have scanned successfully. */
+
+long int
+ar_scan (const char *archive, ar_member_func_t function, const void *arg)
+{
+#ifdef AIAMAG
+ FL_HDR fl_header;
+# ifdef AIAMAGBIG
+ int big_archive = 0;
+ FL_HDR_BIG fl_header_big;
+# endif
+#endif
+ char *namemap = 0;
+ int desc = open (archive, O_RDONLY, 0);
+ if (desc < 0)
+ return -1;
+#ifdef SARMAG
+ {
+ char buf[SARMAG];
+ int nread;
+ EINTRLOOP (nread, read (desc, buf, SARMAG));
+ if (nread != SARMAG || memcmp (buf, ARMAG, SARMAG))
+ {
+ (void) close (desc);
+ return -2;
+ }
+ }
+#else
+#ifdef AIAMAG
+ {
+ int nread;
+ EINTRLOOP (nread, read (desc, &fl_header, FL_HSZ));
+ if (nread != FL_HSZ)
+ {
+ (void) close (desc);
+ return -2;
+ }
+#ifdef AIAMAGBIG
+ /* If this is a "big" archive, then set the flag and
+ re-read the header into the "big" structure. */
+ if (!memcmp (fl_header.fl_magic, AIAMAGBIG, SAIAMAG))
+ {
+ off_t o;
+
+ big_archive = 1;
+
+ /* seek back to beginning of archive */
+ EINTRLOOP (o, lseek (desc, 0, 0));
+ if (o < 0)
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ /* re-read the header into the "big" structure */
+ EINTRLOOP (nread, read (desc, &fl_header_big, FL_HSZ_BIG));
+ if (nread != FL_HSZ_BIG)
+ {
+ (void) close (desc);
+ return -2;
+ }
+ }
+ else
+#endif
+ /* Check to make sure this is a "normal" archive. */
+ if (memcmp (fl_header.fl_magic, AIAMAG, SAIAMAG))
+ {
+ (void) close (desc);
+ return -2;
+ }
+ }
+#else
+ {
+#ifndef M_XENIX
+ int buf;
+#else
+ unsigned short int buf;
+#endif
+ int nread;
+ EINTRLOOP (nread, read (desc, &buf, sizeof (buf)));
+ if (nread != sizeof (buf) || buf != ARMAG)
+ {
+ (void) close (desc);
+ return -2;
+ }
+ }
+#endif
+#endif
+
+ /* Now find the members one by one. */
+ {
+#ifdef SARMAG
+ register long int member_offset = SARMAG;
+#else
+#ifdef AIAMAG
+ long int member_offset;
+ long int last_member_offset;
+
+#ifdef AIAMAGBIG
+ if ( big_archive )
+ {
+ sscanf (fl_header_big.fl_fstmoff, "%20ld", &member_offset);
+ sscanf (fl_header_big.fl_lstmoff, "%20ld", &last_member_offset);
+ }
+ else
+#endif
+ {
+ sscanf (fl_header.fl_fstmoff, "%12ld", &member_offset);
+ sscanf (fl_header.fl_lstmoff, "%12ld", &last_member_offset);
+ }
+
+ if (member_offset == 0)
+ {
+ /* Empty archive. */
+ close (desc);
+ return 0;
+ }
+#else
+#ifndef M_XENIX
+ register long int member_offset = sizeof (int);
+#else /* Xenix. */
+ register long int member_offset = sizeof (unsigned short int);
+#endif /* Not Xenix. */
+#endif
+#endif
+
+ while (1)
+ {
+ register int nread;
+ struct ar_hdr member_header;
+#ifdef AIAMAGBIG
+ struct ar_hdr_big member_header_big;
+#endif
+#ifdef AIAMAG
+ char name[256];
+ int name_len;
+ long int dateval;
+ int uidval, gidval;
+ long int data_offset;
+#else
+ char namebuf[sizeof member_header.ar_name + 1];
+ char *name;
+ int is_namemap; /* Nonzero if this entry maps long names. */
+ int long_name = 0;
+#endif
+ long int eltsize;
+ unsigned int eltmode;
+ long int fnval;
+ off_t o;
+
+ EINTRLOOP (o, lseek (desc, member_offset, 0));
+ if (o < 0)
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+#ifdef AIAMAG
+#define AR_MEMHDR_SZ(x) (sizeof(x) - sizeof (x._ar_name))
+
+#ifdef AIAMAGBIG
+ if (big_archive)
+ {
+ EINTRLOOP (nread, read (desc, &member_header_big,
+ AR_MEMHDR_SZ(member_header_big)));
+
+ if (nread != AR_MEMHDR_SZ(member_header_big))
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ sscanf (member_header_big.ar_namlen, "%4d", &name_len);
+ EINTRLOOP (nread, read (desc, name, name_len));
+
+ if (nread != name_len)
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ name[name_len] = 0;
+
+ sscanf (member_header_big.ar_date, "%12ld", &dateval);
+ sscanf (member_header_big.ar_uid, "%12d", &uidval);
+ sscanf (member_header_big.ar_gid, "%12d", &gidval);
+ sscanf (member_header_big.ar_mode, "%12o", &eltmode);
+ sscanf (member_header_big.ar_size, "%20ld", &eltsize);
+
+ data_offset = (member_offset + AR_MEMHDR_SZ(member_header_big)
+ + name_len + 2);
+ }
+ else
+#endif
+ {
+ EINTRLOOP (nread, read (desc, &member_header,
+ AR_MEMHDR_SZ(member_header)));
+
+ if (nread != AR_MEMHDR_SZ(member_header))
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ sscanf (member_header.ar_namlen, "%4d", &name_len);
+ EINTRLOOP (nread, read (desc, name, name_len));
+
+ if (nread != name_len)
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ name[name_len] = 0;
+
+ sscanf (member_header.ar_date, "%12ld", &dateval);
+ sscanf (member_header.ar_uid, "%12d", &uidval);
+ sscanf (member_header.ar_gid, "%12d", &gidval);
+ sscanf (member_header.ar_mode, "%12o", &eltmode);
+ sscanf (member_header.ar_size, "%12ld", &eltsize);
+
+ data_offset = (member_offset + AR_MEMHDR_SZ(member_header)
+ + name_len + 2);
+ }
+ data_offset += data_offset % 2;
+
+ fnval =
+ (*function) (desc, name, 0,
+ member_offset, data_offset, eltsize,
+ dateval, uidval, gidval,
+ eltmode, arg);
+
+#else /* Not AIAMAG. */
+ EINTRLOOP (nread, read (desc, &member_header, AR_HDR_SIZE));
+ if (nread == 0)
+ /* No data left means end of file; that is OK. */
+ break;
+
+ if (nread != AR_HDR_SIZE
+#if defined(ARFMAG) || defined(ARFZMAG)
+ || (
+# ifdef ARFMAG
+ memcmp (member_header.ar_fmag, ARFMAG, 2)
+# else
+ 1
+# endif
+ &&
+# ifdef ARFZMAG
+ memcmp (member_header.ar_fmag, ARFZMAG, 2)
+# else
+ 1
+# endif
+ )
+#endif
+ )
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ name = namebuf;
+ memcpy (name, member_header.ar_name, sizeof member_header.ar_name);
+ {
+ register char *p = name + sizeof member_header.ar_name;
+ do
+ *p = '\0';
+ while (p > name && *--p == ' ');
+
+#ifndef AIAMAG
+ /* If the member name is "//" or "ARFILENAMES/" this may be
+ a list of file name mappings. The maximum file name
+ length supported by the standard archive format is 14
+ characters. This member will actually always be the
+ first or second entry in the archive, but we don't check
+ that. */
+ is_namemap = (!strcmp (name, "//")
+ || !strcmp (name, "ARFILENAMES/"));
+#endif /* Not AIAMAG. */
+ /* On some systems, there is a slash after each member name. */
+ if (*p == '/')
+ *p = '\0';
+
+#ifndef AIAMAG
+ /* If the member name starts with a space or a slash, this
+ is an index into the file name mappings (used by GNU ar).
+ Otherwise if the member name looks like #1/NUMBER the
+ real member name appears in the element data (used by
+ 4.4BSD). */
+ if (! is_namemap
+ && (name[0] == ' ' || name[0] == '/')
+ && namemap != 0)
+ {
+ name = namemap + atoi (name + 1);
+ long_name = 1;
+ }
+ else if (name[0] == '#'
+ && name[1] == '1'
+ && name[2] == '/')
+ {
+ int namesize = atoi (name + 3);
+
+ name = alloca (namesize + 1);
+ EINTRLOOP (nread, read (desc, name, namesize));
+ if (nread != namesize)
+ {
+ close (desc);
+ return -2;
+ }
+ name[namesize] = '\0';
+
+ long_name = 1;
+ }
+#endif /* Not AIAMAG. */
+ }
+
+#ifndef M_XENIX
+ sscanf (TOCHAR (member_header.ar_mode), "%o", &eltmode);
+ eltsize = atol (TOCHAR (member_header.ar_size));
+#else /* Xenix. */
+ eltmode = (unsigned short int) member_header.ar_mode;
+ eltsize = member_header.ar_size;
+#endif /* Not Xenix. */
+
+ fnval =
+ (*function) (desc, name, ! long_name, member_offset,
+ member_offset + AR_HDR_SIZE, eltsize,
+#ifndef M_XENIX
+ atol (TOCHAR (member_header.ar_date)),
+ atoi (TOCHAR (member_header.ar_uid)),
+ atoi (TOCHAR (member_header.ar_gid)),
+#else /* Xenix. */
+ member_header.ar_date,
+ member_header.ar_uid,
+ member_header.ar_gid,
+#endif /* Not Xenix. */
+ eltmode, arg);
+
+#endif /* AIAMAG. */
+
+ if (fnval)
+ {
+ (void) close (desc);
+ return fnval;
+ }
+
+#ifdef AIAMAG
+ if (member_offset == last_member_offset)
+ /* End of the chain. */
+ break;
+
+#ifdef AIAMAGBIG
+ if (big_archive)
+ sscanf (member_header_big.ar_nxtmem, "%20ld", &member_offset);
+ else
+#endif
+ sscanf (member_header.ar_nxtmem, "%12ld", &member_offset);
+
+ if (lseek (desc, member_offset, 0) != member_offset)
+ {
+ (void) close (desc);
+ return -2;
+ }
+#else
+
+ /* If this member maps archive names, we must read it in. The
+ name map will always precede any members whose names must
+ be mapped. */
+ if (is_namemap)
+ {
+ char *clear;
+ char *limit;
+
+ namemap = alloca (eltsize);
+ EINTRLOOP (nread, read (desc, namemap, eltsize));
+ if (nread != eltsize)
+ {
+ (void) close (desc);
+ return -2;
+ }
+
+ /* The names are separated by newlines. Some formats have
+ a trailing slash. Null terminate the strings for
+ convenience. */
+ limit = namemap + eltsize;
+ for (clear = namemap; clear < limit; clear++)
+ {
+ if (*clear == '\n')
+ {
+ *clear = '\0';
+ if (clear[-1] == '/')
+ clear[-1] = '\0';
+ }
+ }
+
+ is_namemap = 0;
+ }
+
+ member_offset += AR_HDR_SIZE + eltsize;
+ if (member_offset % 2 != 0)
+ member_offset++;
+#endif
+ }
+ }
+
+ close (desc);
+ return 0;
+}
+#endif /* !VMS */
+
+/* Return nonzero iff NAME matches MEM.
+ If TRUNCATED is nonzero, MEM may be truncated to
+ sizeof (struct ar_hdr.ar_name) - 1. */
+
+int
+ar_name_equal (const char *name, const char *mem, int truncated)
+{
+ const char *p;
+
+ p = strrchr (name, '/');
+ if (p != 0)
+ name = p + 1;
+
+#ifndef VMS
+ if (truncated)
+ {
+#ifdef AIAMAG
+ /* TRUNCATED should never be set on this system. */
+ abort ();
+#else
+ struct ar_hdr hdr;
+#if !defined (__hpux) && !defined (cray)
+ return strneq (name, mem, sizeof (hdr.ar_name) - 1);
+#else
+ return strneq (name, mem, sizeof (hdr.ar_name) - 2);
+#endif /* !__hpux && !cray */
+#endif /* !AIAMAG */
+ }
+
+ return !strcmp (name, mem);
+#else
+ /* VMS members do not have suffixes, but the filenames usually
+ have.
+ Do we need to strip VMS disk/directory format paths?
+
+ Most VMS compilers etc. by default are case insensitive
+ but produce uppercase external names, incl. module names.
+ However the VMS librarian (ar) and the linker by default
+ are case sensitive: they take what they get, usually
+ uppercase names. So for the non-default settings of the
+ compilers etc. there is a need to have a case sensitive
+ mode. */
+ {
+ int len;
+ len = strlen(mem);
+ int match;
+ char *dot;
+ if ((dot=strrchr(name,'.')))
+ match = (len == dot - name) && !strncasecmp(name, mem, len);
+ else
+ match = !strcasecmp (name, mem);
+ return match;
+ }
+#endif /* !VMS */
+}
+
+#ifndef VMS
+/* ARGSUSED */
+static long int
+ar_member_pos (int desc UNUSED, const char *mem, int truncated,
+ long int hdrpos, long int datapos UNUSED, long int size UNUSED,
+ long int date UNUSED, int uid UNUSED, int gid UNUSED,
+ unsigned int mode UNUSED, const void *name)
+{
+ if (!ar_name_equal (name, mem, truncated))
+ return 0;
+ return hdrpos;
+}
+
+/* Set date of member MEMNAME in archive ARNAME to current time.
+ Returns 0 if successful,
+ -1 if file ARNAME does not exist,
+ -2 if not a valid archive,
+ -3 if other random system call error (including file read-only),
+ 1 if valid but member MEMNAME does not exist. */
+
+int
+ar_member_touch (const char *arname, const char *memname)
+{
+ long int pos = ar_scan (arname, ar_member_pos, memname);
+ int fd;
+ struct ar_hdr ar_hdr;
+ off_t o;
+ int r;
+ unsigned int ui;
+ struct stat statbuf;
+
+ if (pos < 0)
+ return (int) pos;
+ if (!pos)
+ return 1;
+
+ EINTRLOOP (fd, open (arname, O_RDWR, 0666));
+ if (fd < 0)
+ return -3;
+ /* Read in this member's header */
+ EINTRLOOP (o, lseek (fd, pos, 0));
+ if (o < 0)
+ goto lose;
+ EINTRLOOP (r, read (fd, &ar_hdr, AR_HDR_SIZE));
+ if (r != AR_HDR_SIZE)
+ goto lose;
+ /* Write back the header, thus touching the archive file. */
+ EINTRLOOP (o, lseek (fd, pos, 0));
+ if (o < 0)
+ goto lose;
+ EINTRLOOP (r, write (fd, &ar_hdr, AR_HDR_SIZE));
+ if (r != AR_HDR_SIZE)
+ goto lose;
+ /* The file's mtime is the time we we want. */
+ EINTRLOOP (r, fstat (fd, &statbuf));
+ if (r < 0)
+ goto lose;
+#if defined(ARFMAG) || defined(ARFZMAG) || defined(AIAMAG) || defined(WINDOWS32)
+ /* Advance member's time to that time */
+ for (ui = 0; ui < sizeof ar_hdr.ar_date; ui++)
+ ar_hdr.ar_date[ui] = ' ';
+ sprintf (TOCHAR (ar_hdr.ar_date), "%lu", (long unsigned) statbuf.st_mtime);
+#ifdef AIAMAG
+ ar_hdr.ar_date[strlen (ar_hdr.ar_date)] = ' ';
+#endif
+#else
+ ar_hdr.ar_date = statbuf.st_mtime;
+#endif
+ /* Write back this member's header */
+ EINTRLOOP (o, lseek (fd, pos, 0));
+ if (o < 0)
+ goto lose;
+ EINTRLOOP (r, write (fd, &ar_hdr, AR_HDR_SIZE));
+ if (r != AR_HDR_SIZE)
+ goto lose;
+ close (fd);
+ return 0;
+
+ lose:
+ r = errno;
+ close (fd);
+ errno = r;
+ return -3;
+}
+#endif
+
+#ifdef TEST
+
+long int
+describe_member (int desc, const char *name, int truncated,
+ long int hdrpos, long int datapos, long int size,
+ long int date, int uid, int gid, unsigned int mode,
+ const void *arg)
+{
+ extern char *ctime ();
+
+ printf (_("Member '%s'%s: %ld bytes at %ld (%ld).\n"),
+ name, truncated ? _(" (name might be truncated)") : "",
+ size, hdrpos, datapos);
+ printf (_(" Date %s"), ctime (&date));
+ printf (_(" uid = %d, gid = %d, mode = 0%o.\n"), uid, gid, mode);
+
+ return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+ ar_scan (argv[1], describe_member, NULL);
+ return 0;
+}
+
+#endif /* TEST. */
+#endif /* NO_ARCHIVES. */
diff --git a/src/kmk/build.template b/src/kmk/build.template
new file mode 100644
index 0000000..4b01b46
--- /dev/null
+++ b/src/kmk/build.template
@@ -0,0 +1,81 @@
+#!/bin/sh
+# Shell script to build GNU Make in the absence of any 'make' program.
+# @configure_input@
+
+# Copyright (C) 1993-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See Makefile.in for comments describing these variables.
+
+srcdir='@srcdir@'
+CC='@CC@'
+CFLAGS='@CFLAGS@ @GUILE_CFLAGS@'
+CPPFLAGS='@CPPFLAGS@'
+LDFLAGS='@AM_LDFLAGS@ @LDFLAGS@'
+ALLOCA='@ALLOCA@'
+LOADLIBES='@LIBS@ @GUILE_LIBS@ @LIBINTL@'
+eval extras=\'@LIBOBJS@\'
+REMOTE='@REMOTE@'
+GLOBLIB='@GLOBLIB@'
+PATH_SEPARATOR='@PATH_SEPARATOR@'
+OBJEXT='@OBJEXT@'
+EXEEXT='@EXEEXT@'
+
+# Common prefix for machine-independent installed files.
+prefix='@prefix@'
+# Common prefix for machine-dependent installed files.
+exec_prefix=`eval echo @exec_prefix@`
+# Directory to find libraries in for '-lXXX'.
+libdir=${exec_prefix}/lib
+# Directory to search by default for included makefiles.
+includedir=${prefix}/include
+
+localedir=${prefix}/share/locale
+aliaspath=${localedir}${PATH_SEPARATOR}.
+
+defines="-DLOCALEDIR=\"${localedir}\" -DLIBDIR=\"${libdir}\" -DINCLUDEDIR=\"${includedir}\""' @DEFS@'
+
+# Exit as soon as any command fails.
+set -e
+
+# These are all the objects we need to link together.
+objs="%objs% remote-${REMOTE}.${OBJEXT} ${extras} ${ALLOCA}"
+
+if [ x"$GLOBLIB" != x ]; then
+ objs="$objs %globobjs%"
+ globinc=-I${srcdir}/glob
+fi
+
+# Compile the source files into those objects.
+for file in `echo ${objs} | sed 's/\.'${OBJEXT}'/.c/g'`; do
+ echo compiling ${file}...
+ $CC $defines $CPPFLAGS $CFLAGS \
+ -c -I. -I${srcdir} ${globinc} ${srcdir}/$file
+done
+
+# The object files were actually all put in the current directory.
+# Remove the source directory names from the list.
+srcobjs="$objs"
+objs=
+for obj in $srcobjs; do
+ objs="$objs `basename $obj`"
+done
+
+# Link all the objects together.
+echo linking make...
+$CC $CFLAGS $LDFLAGS $objs $LOADLIBES -o makenew${EXEEXT}
+echo done
+mv -f makenew${EXEEXT} make${EXEEXT}
diff --git a/src/kmk/build_w32.bat b/src/kmk/build_w32.bat
new file mode 100644
index 0000000..59e068b
--- /dev/null
+++ b/src/kmk/build_w32.bat
@@ -0,0 +1,250 @@
+@echo off
+rem Copyright (C) 1996-2016 Free Software Foundation, Inc.
+rem This file is part of GNU Make.
+rem
+rem GNU Make is free software; you can redistribute it and/or modify it under
+rem the terms of the GNU General Public License as published by the Free
+rem Software Foundation; either version 3 of the License, or (at your option)
+rem any later version.
+rem
+rem GNU Make is distributed in the hope that it will be useful, but WITHOUT
+rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+rem more details.
+rem
+rem You should have received a copy of the GNU General Public License along
+rem with this program. If not, see <http://www.gnu.org/licenses/>.
+
+call :Reset
+
+if "%1" == "-h" goto Usage
+if "%1" == "--help" goto Usage
+
+set MAKE=gnumake
+set GUILE=Y
+set COMPILER=msvc
+
+:ParseSW
+if "%1" == "--debug" goto SetDebug
+if "%1" == "--without-guile" goto NoGuile
+if "%1" == "gcc" goto SetCC
+if "%1" == "" goto DoneSW
+
+:SetDebug
+set DEBUG=Y
+shift
+goto ParseSW
+
+:NoGuile
+set GUILE=N
+echo Building without Guile
+shift
+goto ParseSW
+
+:SetCC
+set COMPILER=gcc
+echo Building with GCC
+shift
+goto ParseSW
+
+rem Build with Guile is supported only on NT and later versions
+:DoneSW
+echo.
+echo Creating GNU Make for Windows 9X/NT/2K/XP/Vista/7/8
+if "%DEBUG%" == "Y" echo Building without compiler optimizations
+
+if "%COMPILER%" == "gcc" goto GccBuild
+
+set OUTDIR=.\WinRel
+set "OPTS=/O2 /D NDEBUG"
+set LINKOPTS=
+if "%DEBUG%" == "Y" set OUTDIR=.\WinDebug
+if "%DEBUG%" == "Y" set "OPTS=/Zi /Od /D _DEBUG"
+if "%DEBUG%" == "Y" set LINKOPTS=/DEBUG
+call :Build
+goto Done
+
+:GccBuild
+set OUTDIR=.\GccRel
+set OPTS=-O2
+if "%DEBUG%" == "Y" set OPTS=-O0
+if "%DEBUG%" == "Y" set OUTDIR=.\GccDebug
+call :Build
+goto Done
+
+:Done
+call :Reset
+goto :EOF
+
+:Build
+:: Clean the directory if it exists
+if exist %OUTDIR%\nul rmdir /S /Q %OUTDIR%
+
+:: Recreate it
+mkdir %OUTDIR%
+mkdir %OUTDIR%\glob
+mkdir %OUTDIR%\w32
+mkdir %OUTDIR%\w32\compat
+mkdir %OUTDIR%\w32\subproc
+
+if "%GUILE%" == "Y" call :ChkGuile
+
+echo.
+echo Compiling %OUTDIR% version
+
+if exist config.h.W32.template call :ConfigSCM
+copy config.h.W32 %OUTDIR%\config.h
+
+call :Compile ar
+call :Compile arscan
+call :Compile commands
+call :Compile default
+call :Compile dir
+call :Compile expand
+call :Compile file
+call :Compile function
+call :Compile getloadavg
+call :Compile getopt
+call :Compile getopt1
+call :Compile glob\fnmatch
+call :Compile glob\glob
+call :Compile guile GUILE
+call :Compile hash
+call :Compile implicit
+call :Compile job
+call :Compile load
+call :Compile loadapi
+call :Compile main GUILE
+call :Compile misc
+call :Compile output
+call :Compile read
+call :Compile remake
+call :Compile remote-stub
+call :Compile rule
+call :Compile signame
+call :Compile strcache
+call :Compile variable
+call :Compile version
+call :Compile vpath
+call :Compile w32\compat\posixfcn
+call :Compile w32\pathstuff
+call :Compile w32\subproc\misc
+call :Compile w32\subproc\sub_proc
+call :Compile w32\subproc\w32err
+call :Compile w32\w32os
+
+if not "%COMPILER%" == "gcc" call :Compile w32\compat\dirent
+
+call :Link
+
+echo.
+if not exist %OUTDIR%\%MAKE%.exe echo %OUTDIR% build FAILED!
+if exist %OUTDIR%\%MAKE%.exe echo %OUTDIR% build succeeded.
+goto :EOF
+
+:Compile
+set EXTRAS=
+if "%2" == "GUILE" set "EXTRAS=%GUILECFLAGS%"
+if "%COMPILER%" == "gcc" goto GccCompile
+
+:: MSVC Compile
+echo on
+cl.exe /nologo /MT /W4 /EHsc %OPTS% /I %OUTDIR% /I . /I glob /I w32/include /D WINDOWS32 /D WIN32 /D _CONSOLE /D HAVE_CONFIG_H /FR%OUTDIR% /Fp%OUTDIR%\%MAKE%.pch /Fo%OUTDIR%\%1.obj /Fd%OUTDIR%\%MAKE%.pdb %EXTRAS% /c %1.c
+@echo off
+echo %OUTDIR%\%1.obj >>%OUTDIR%\link.sc
+goto :EOF
+
+:GccCompile
+:: GCC Compile
+echo on
+gcc -mthreads -Wall -std=gnu99 -gdwarf-2 -g3 %OPTS% -I%OUTDIR% -I. -I./glob -I./w32/include -DWINDOWS32 -DHAVE_CONFIG_H %EXTRAS% -o %OUTDIR%\%1.o -c %1.c
+@echo off
+goto :EOF
+
+:Link
+echo Linking %OUTDIR%/%MAKE%.exe
+if "%COMPILER%" == "gcc" goto GccLink
+
+:: MSVC Link
+echo %GUILELIBS% kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib >>%OUTDIR%\link.sc
+echo on
+link.exe /NOLOGO /SUBSYSTEM:console /PDB:%OUTDIR%\%MAKE%.pdb %LINKOPTS% /OUT:%OUTDIR%\%MAKE%.exe @%OUTDIR%\link.sc
+@echo off
+goto :EOF
+
+:GccLink
+:: GCC Link
+echo on
+gcc -mthreads -gdwarf-2 -g3 -o %OUTDIR%\%MAKE%.exe %OUTDIR%\variable.o %OUTDIR%\rule.o %OUTDIR%\remote-stub.o %OUTDIR%\commands.o %OUTDIR%\file.o %OUTDIR%\getloadavg.o %OUTDIR%\default.o %OUTDIR%\signame.o %OUTDIR%\expand.o %OUTDIR%\dir.o %OUTDIR%\main.o %OUTDIR%\getopt1.o %OUTDIR%\guile.o %OUTDIR%\job.o %OUTDIR%\output.o %OUTDIR%\read.o %OUTDIR%\version.o %OUTDIR%\getopt.o %OUTDIR%\arscan.o %OUTDIR%\remake.o %OUTDIR%\misc.o %OUTDIR%\hash.o %OUTDIR%\strcache.o %OUTDIR%\ar.o %OUTDIR%\function.o %OUTDIR%\vpath.o %OUTDIR%\implicit.o %OUTDIR%\loadapi.o %OUTDIR%\load.o %OUTDIR%\glob\glob.o %OUTDIR%\glob\fnmatch.o %OUTDIR%\w32\pathstuff.o %OUTDIR%\w32\compat\posixfcn.o %OUTDIR%\w32\w32os.o %OUTDIR%\w32\subproc\misc.o %OUTDIR%\w32\subproc\sub_proc.o %OUTDIR%\w32\subproc\w32err.o %GUILELIBS% -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -luuid -lodbc32 -lodbccp32 -Wl,--out-implib=%OUTDIR%\libgnumake-1.dll.a
+@echo off
+goto :EOF
+
+:ConfigSCM
+echo Generating config from SCM templates
+sed -n "s/^AC_INIT(\[GNU make\],\[\([^]]\+\)\].*/s,%%VERSION%%,\1,g/p" configure.ac > %OUTDIR%\config.h.W32.sed
+echo s,%%PACKAGE%%,make,g >> %OUTDIR%\config.h.W32.sed
+sed -f %OUTDIR%\config.h.W32.sed config.h.W32.template > config.h.W32
+echo static const char *const GUILE_module_defn = ^" \> gmk-default.h
+sed -e "s/;.*//" -e "/^[ \t]*$/d" -e "s/\"/\\\\\"/g" -e "s/$/ \\\/" gmk-default.scm >> gmk-default.h
+echo ^";>> gmk-default.h
+goto :EOF
+
+:ChkGuile
+if not "%OS%" == "Windows_NT" goto NoGuile
+pkg-config --help > %OUTDIR%\guile.tmp 2> NUL
+if ERRORLEVEL 1 goto NoPkgCfg
+
+echo Checking for Guile 2.0
+if not "%COMPILER%" == "gcc" set PKGMSC=--msvc-syntax
+pkg-config --cflags --short-errors "guile-2.0" > %OUTDIR%\guile.tmp
+if not ERRORLEVEL 1 set /P GUILECFLAGS= < %OUTDIR%\guile.tmp
+
+pkg-config --libs --static --short-errors %PKGMSC% "guile-2.0" > %OUTDIR%\guile.tmp
+if not ERRORLEVEL 1 set /P GUILELIBS= < %OUTDIR%\guile.tmp
+
+if not "%GUILECFLAGS%" == "" goto GuileDone
+
+echo Checking for Guile 1.8
+pkg-config --cflags --short-errors "guile-1.8" > %OUTDIR%\guile.tmp
+if not ERRORLEVEL 1 set /P GUILECFLAGS= < %OUTDIR%\guile.tmp
+
+pkg-config --libs --static --short-errors %PKGMSC% "guile-1.8" > %OUTDIR%\guile.tmp
+if not ERRORLEVEL 1 set /P GUILELIBS= < %OUTDIR%\guile.tmp
+
+if not "%GUILECFLAGS%" == "" goto GuileDone
+
+echo No Guile found, building without Guile
+goto GuileDone
+
+:NoPkgCfg
+echo pkg-config not found, building without Guile
+
+:GuileDone
+if "%GUILECFLAGS%" == "" goto :EOF
+
+echo Guile found, building with Guile
+set "GUILECFLAGS=%GUILECFLAGS% -DHAVE_GUILE"
+goto :EOF
+
+:Usage
+echo Usage: %0 [options] [gcc]
+echo Options:
+echo. --debug For GCC only, make a debug build
+echo. (MSVC build always makes both debug and release)
+echo. --without-guile Do not compile Guile support even if found
+echo. --help Display these instructions and exit
+goto :EOF
+
+:Reset
+set COMPILER=
+set DEBUG=
+set GUILE=
+set GUILECFLAGS=
+set GUILELIBS=
+set LINKOPTS=
+set MAKE=
+set NOGUILE=
+set OPTS=
+set OUTDIR=
+set PKGMSC=
+goto :EOF
diff --git a/src/kmk/commands.c b/src/kmk/commands.c
new file mode 100644
index 0000000..619839c
--- /dev/null
+++ b/src/kmk/commands.c
@@ -0,0 +1,954 @@
+/* Command processing for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "dep.h"
+#include "variable.h"
+#include "job.h"
+#include "commands.h"
+#ifdef WINDOWS32
+#include <windows.h>
+#include "w32err.h"
+# ifdef CONFIG_NEW_WIN_CHILDREN
+# include "w32/winchildren.h"
+# endif
+#endif
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+# include <assert.h>
+#endif
+
+#if VMS
+# define FILE_LIST_SEPARATOR (vms_comma_separator ? ',' : ' ')
+#else
+# define FILE_LIST_SEPARATOR ' '
+#endif
+
+#ifndef HAVE_UNISTD_H
+int getpid ();
+#endif
+
+#ifndef CONFIG_WITH_STRCACHE2
+
+static unsigned long
+dep_hash_1 (const void *key)
+{
+ const struct dep *d = key;
+ return_STRING_HASH_1 (dep_name (d));
+}
+
+static unsigned long
+dep_hash_2 (const void *key)
+{
+ const struct dep *d = key;
+ return_STRING_HASH_2 (dep_name (d));
+}
+
+static int
+dep_hash_cmp (const void *x, const void *y)
+{
+ const struct dep *dx = x;
+ const struct dep *dy = y;
+ return strcmp (dep_name (dx), dep_name (dy));
+}
+
+
+#else /* CONFIG_WITH_STRCACHE2 */
+
+/* Exploit the fact that all names are in the string cache. This means equal
+ names shall have the same storage and there is no need for hashing or
+ comparing. Use the address as the first hash, avoiding any touching of
+ the name, and the length as the second. */
+
+static unsigned long
+dep_hash_1 (const void *key)
+{
+ const char *name = dep_name ((struct dep const *) key);
+ assert (strcache2_is_cached (&file_strcache, name));
+ return (size_t) name / sizeof(void *);
+}
+
+static unsigned long
+dep_hash_2 (const void *key)
+{
+ const char *name = dep_name ((struct dep const *) key);
+ return strcache2_get_len (&file_strcache, name);
+}
+
+static int
+dep_hash_cmp (const void *x, const void *y)
+{
+ struct dep *dx = (struct dep *) x;
+ struct dep *dy = (struct dep *) y;
+ const char *dxname = dep_name (dx);
+ const char *dyname = dep_name (dy);
+ int cmp = dxname == dyname ? 0 : 1;
+
+ /* check preconds: both cached and the cache contains no duplicates. */
+ assert (strcache2_is_cached (&file_strcache, dxname));
+ assert (strcache2_is_cached (&file_strcache, dyname));
+ assert (cmp == 0 || strcmp (dxname, dyname) != 0);
+
+ /* If the names are the same but ignore_mtimes are not equal, one of these
+ is an order-only prerequisite and one isn't. That means that we should
+ remove the one that isn't and keep the one that is. */
+
+ if (!cmp && dx->ignore_mtime != dy->ignore_mtime)
+ dx->ignore_mtime = dy->ignore_mtime = 0;
+
+ return cmp;
+}
+
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+/* Create as copy of DEPS without duplicates, similar to what
+ set_file_variables does. Used by func_deps. */
+
+struct dep *create_uniqute_deps_chain (struct dep *deps)
+{
+ struct dep *d;
+ struct dep *head = NULL;
+ struct dep **ppnext= &head;
+ struct hash_table dep_hash;
+ void **slot;
+
+ hash_init (&dep_hash, 500, dep_hash_1, dep_hash_2, dep_hash_cmp);
+
+ for (d = deps; d != 0; d = d->next)
+ {
+ if (d->need_2nd_expansion)
+ continue;
+
+ slot = hash_find_slot (&dep_hash, d);
+ if (HASH_VACANT (*slot))
+ {
+ struct dep *n = alloc_dep();
+ *n = *d;
+ n->next = NULL;
+ *ppnext = n;
+ ppnext = &n->next;
+ hash_insert_at (&dep_hash, n, slot);
+ }
+ else
+ {
+ /* Upgrade order only if a normal dep exists.
+ Note! Elected not to upgrade the original, only the sanitized
+ list, need to check that out later. FIXME TODO */
+ struct dep *d2 = (struct dep *)*slot;
+ if (d->ignore_mtime != d2->ignore_mtime)
+ d->ignore_mtime = d2->ignore_mtime = 0;
+ }
+ }
+
+ return head;
+}
+#endif /* CONFIG_WITH_LAZY_DEPS_VARS */
+
+/* Set FILE's automatic variables up. */
+
+void
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+set_file_variables (struct file *file, int called_early)
+#else
+set_file_variables (struct file *file)
+#endif
+{
+ struct dep *d;
+ const char *at, *percent, *star, *less;
+#ifdef CONFIG_WITH_STRCACHE2
+ const char *org_stem = file->stem;
+#endif
+
+#ifndef NO_ARCHIVES
+ /* If the target is an archive member 'lib(member)',
+ then $@ is 'lib' and $% is 'member'. */
+
+ if (ar_name (file->name))
+ {
+ unsigned int len;
+ const char *cp;
+ char *p;
+
+ cp = strchr (file->name, '(');
+ p = alloca (cp - file->name + 1);
+ memcpy (p, file->name, cp - file->name);
+ p[cp - file->name] = '\0';
+ at = p;
+ len = strlen (cp + 1);
+ p = alloca (len);
+ memcpy (p, cp + 1, len - 1);
+ p[len - 1] = '\0';
+ percent = p;
+ }
+ else
+#endif /* NO_ARCHIVES. */
+ {
+ at = file->name;
+ percent = "";
+ }
+
+ /* $* is the stem from an implicit or static pattern rule. */
+ if (file->stem == 0)
+ {
+ /* In Unix make, $* is set to the target name with
+ any suffix in the .SUFFIXES list stripped off for
+ explicit rules. We store this in the 'stem' member. */
+ const char *name;
+ unsigned int len;
+
+#ifndef NO_ARCHIVES
+ if (ar_name (file->name))
+ {
+ name = strchr (file->name, '(') + 1;
+ len = strlen (name) - 1;
+ }
+ else
+#endif
+ {
+ name = file->name;
+#ifndef CONFIG_WITH_STRCACHE2
+ len = strlen (name);
+#else
+ len = strcache2_get_len (&file_strcache, name);
+#endif
+ }
+
+#ifndef CONFIG_WITH_STRCACHE2
+ for (d = enter_file (strcache_add (".SUFFIXES"))->deps; d ; d = d->next)
+ {
+ unsigned int slen = strlen (dep_name (d));
+#else
+ for (d = enter_file (suffixes_strcached)->deps; d ; d = d->next)
+ {
+ unsigned int slen = strcache2_get_len (&file_strcache, dep_name (d));
+#endif
+ if (len > slen && strneq (dep_name (d), name + (len - slen), slen))
+ {
+ file->stem = strcache_add_len (name, len - slen);
+ break;
+ }
+ }
+ if (d == 0)
+ file->stem = "";
+ }
+ star = file->stem;
+
+ /* $< is the first not order-only dependency. */
+ less = "";
+ for (d = file->deps; d != 0; d = d->next)
+ if (!d->ignore_mtime)
+ {
+ if (!d->need_2nd_expansion)
+ less = dep_name (d);
+ break;
+ }
+
+ if (file->cmds == default_file->cmds)
+ /* This file got its commands from .DEFAULT.
+ In this case $< is the same as $@. */
+ less = at;
+
+#define DEFINE_VARIABLE(name, len, value) \
+ (void) define_variable_for_file (name,len,value,o_automatic,0,file)
+
+ /* Define the variables. */
+
+#ifndef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ DEFINE_VARIABLE ("<", 1, less);
+ DEFINE_VARIABLE ("*", 1, star);
+ DEFINE_VARIABLE ("@", 1, at);
+ DEFINE_VARIABLE ("%", 1, percent);
+#else /* CONFIG_WITH_RDONLY_VARIABLE_VALUE */
+# define DEFINE_VARIABLE_RO_VAL(name, len, value, value_len) \
+ define_variable_in_set((name), (len), (value), (value_len), -1, \
+ (o_automatic), 0, (file)->variables->set, NILF)
+
+ if (*less == '\0')
+ DEFINE_VARIABLE_RO_VAL ("<", 1, "", 0);
+ else if (less != at || at == file->name)
+ DEFINE_VARIABLE_RO_VAL ("<", 1, less, strcache_get_len (less));
+ else
+ DEFINE_VARIABLE ("<", 1, less);
+
+ if (*star == '\0')
+ DEFINE_VARIABLE_RO_VAL ("*", 1, "", 0);
+ else if (file->stem != org_stem)
+ DEFINE_VARIABLE_RO_VAL ("*", 1, star, strcache_get_len (star));
+ else
+ DEFINE_VARIABLE ("*", 1, star);
+
+ if (at == file->name)
+ DEFINE_VARIABLE_RO_VAL ("@", 1, at, strcache_get_len (at));
+ else
+ DEFINE_VARIABLE ("@", 1, at);
+
+ if (*percent == '\0')
+ DEFINE_VARIABLE_RO_VAL ("%", 1, "", 0);
+ else
+ DEFINE_VARIABLE ("%", 1, percent);
+#endif /* CONFIG_WITH_RDONLY_VARIABLE_VALUE */
+
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+ /* The $^, $+, $? and $| variables should not be set if we're called
+ early by a .MUST_MAKE invocation or $(commands ). */
+ if (called_early)
+ return;
+#endif
+
+ /* Compute the values for $^, $+, $?, and $|. */
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+ /* Lazy doesn't work for double colon rules with multiple files with
+ commands, nor for files that has been thru rehash_file() (vpath). */
+ if ( ( file->double_colon
+ && ( file->double_colon != file
+ || file->last != file))
+ || file->name != file->hname) /* XXX: Rehashed files should be fixable! */
+#endif
+ {
+ static char *plus_value=0, *bar_value=0, *qmark_value=0;
+ static unsigned int plus_max=0, bar_max=0, qmark_max=0;
+
+ unsigned int qmark_len, plus_len, bar_len;
+ char *cp;
+ char *caret_value;
+ char *qp;
+ char *bp;
+ unsigned int len;
+
+ struct hash_table dep_hash;
+ void **slot;
+
+ /* Compute first the value for $+, which is supposed to contain
+ duplicate dependencies as they were listed in the makefile. */
+
+ plus_len = 0;
+ bar_len = 0;
+ for (d = file->deps; d != 0; d = d->next)
+ {
+ if (!d->need_2nd_expansion)
+ {
+ if (d->ignore_mtime)
+#ifndef CONFIG_WITH_STRCACHE2
+ bar_len += strlen (dep_name (d)) + 1;
+#else
+ bar_len += strcache2_get_len (&file_strcache, dep_name (d)) + 1;
+#endif
+ else
+#ifndef CONFIG_WITH_STRCACHE2
+ plus_len += strlen (dep_name (d)) + 1;
+#else
+ plus_len += strcache2_get_len (&file_strcache, dep_name (d)) + 1;
+#endif
+ }
+ }
+
+ if (bar_len == 0)
+ bar_len++;
+ if (plus_len == 0)
+ plus_len++;
+
+ if (plus_len > plus_max)
+ plus_value = xrealloc (plus_value, plus_max = plus_len);
+
+ cp = plus_value;
+
+ qmark_len = plus_len + 1; /* Will be this or less. */
+ for (d = file->deps; d != 0; d = d->next)
+ if (! d->ignore_mtime && ! d->need_2nd_expansion)
+ {
+ const char *c = dep_name (d);
+
+#ifndef NO_ARCHIVES
+ if (ar_name (c))
+ {
+ c = strchr (c, '(') + 1;
+ len = strlen (c) - 1;
+ }
+ else
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ len = strlen (c);
+#else
+ len = strcache2_get_len (&file_strcache, c);
+#endif
+
+ memcpy (cp, c, len);
+ cp += len;
+ *cp++ = FILE_LIST_SEPARATOR;
+ if (! (d->changed || always_make_flag))
+ qmark_len -= len + 1; /* Don't space in $? for this one. */
+ }
+
+ /* Kill the last space and define the variable. */
+
+ cp[cp > plus_value ? -1 : 0] = '\0';
+ DEFINE_VARIABLE ("+", 1, plus_value);
+
+ /* Compute the values for $^, $?, and $|. */
+
+ cp = caret_value = plus_value; /* Reuse the buffer; it's big enough. */
+
+ if (qmark_len > qmark_max)
+ qmark_value = xrealloc (qmark_value, qmark_max = qmark_len);
+ qp = qmark_value;
+
+ if (bar_len > bar_max)
+ bar_value = xrealloc (bar_value, bar_max = bar_len);
+ bp = bar_value;
+
+ /* Make sure that no dependencies are repeated in $^, $?, and $|. It
+ would be natural to combine the next two loops but we can't do it
+ because of a situation where we have two dep entries, the first
+ is order-only and the second is normal (see below). */
+
+ hash_init (&dep_hash, 500, dep_hash_1, dep_hash_2, dep_hash_cmp);
+
+ for (d = file->deps; d != 0; d = d->next)
+ {
+ if (d->need_2nd_expansion)
+ continue;
+
+ slot = hash_find_slot (&dep_hash, d);
+ if (HASH_VACANT (*slot))
+ hash_insert_at (&dep_hash, d, slot);
+ else
+ {
+ /* Check if the two prerequisites have different ignore_mtime.
+ If so then we need to "upgrade" one that is order-only. */
+
+ struct dep* hd = (struct dep*) *slot;
+
+ if (d->ignore_mtime != hd->ignore_mtime)
+ d->ignore_mtime = hd->ignore_mtime = 0;
+ }
+ }
+
+ for (d = file->deps; d != 0; d = d->next)
+ {
+ const char *c;
+
+ if (d->need_2nd_expansion || hash_find_item (&dep_hash, d) != d)
+ continue;
+
+ c = dep_name (d);
+#ifndef NO_ARCHIVES
+ if (ar_name (c))
+ {
+ c = strchr (c, '(') + 1;
+ len = strlen (c) - 1;
+ }
+ else
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ len = strlen (c);
+#else
+ len = strcache2_get_len (&file_strcache, c);
+#endif
+
+ if (d->ignore_mtime)
+ {
+ memcpy (bp, c, len);
+ bp += len;
+ *bp++ = FILE_LIST_SEPARATOR;
+ }
+ else
+ {
+ memcpy (cp, c, len);
+ cp += len;
+ *cp++ = FILE_LIST_SEPARATOR;
+ if (d->changed || always_make_flag)
+ {
+ memcpy (qp, c, len);
+ qp += len;
+ *qp++ = FILE_LIST_SEPARATOR;
+ }
+ }
+ }
+
+ hash_free (&dep_hash, 0);
+
+ /* Kill the last spaces and define the variables. */
+
+ cp[cp > caret_value ? -1 : 0] = '\0';
+ DEFINE_VARIABLE ("^", 1, caret_value);
+
+ qp[qp > qmark_value ? -1 : 0] = '\0';
+ DEFINE_VARIABLE ("?", 1, qmark_value);
+
+ bp[bp > bar_value ? -1 : 0] = '\0';
+ DEFINE_VARIABLE ("|", 1, bar_value);
+ }
+#undef DEFINE_VARIABLE
+}
+
+/* Chop CMDS up into individual command lines if necessary.
+ Also set the 'lines_flags' and 'any_recurse' members. */
+
+void
+chop_commands (struct commands *cmds)
+{
+ unsigned int nlines, idx;
+ char **lines;
+
+ /* If we don't have any commands,
+ or we already parsed them, never mind. */
+
+ if (!cmds || cmds->command_lines != 0)
+ return;
+
+ /* Chop CMDS->commands up into lines in CMDS->command_lines. */
+
+ if (one_shell)
+ {
+ int l = strlen (cmds->commands);
+
+ nlines = 1;
+ lines = xmalloc (nlines * sizeof (char *));
+ lines[0] = xstrdup (cmds->commands);
+
+ /* Strip the trailing newline. */
+ if (l > 0 && lines[0][l-1] == '\n')
+ lines[0][l-1] = '\0';
+ }
+ else
+ {
+ const char *p;
+
+ nlines = 5;
+ lines = xmalloc (nlines * sizeof (char *));
+ idx = 0;
+ p = cmds->commands;
+ while (*p != '\0')
+ {
+ const char *end = p;
+ find_end:;
+ end = strchr (end, '\n');
+ if (end == 0)
+ end = p + strlen (p);
+ else if (end > p && end[-1] == '\\')
+ {
+ int backslash = 1;
+ const char *b;
+ for (b = end - 2; b >= p && *b == '\\'; --b)
+ backslash = !backslash;
+ if (backslash)
+ {
+ ++end;
+ goto find_end;
+ }
+ }
+
+ if (idx == nlines)
+ {
+ nlines += 2;
+ lines = xrealloc (lines, nlines * sizeof (char *));
+ }
+ lines[idx++] = xstrndup (p, end - p);
+ p = end;
+ if (*p != '\0')
+ ++p;
+ }
+
+ if (idx != nlines)
+ {
+ nlines = idx;
+ lines = xrealloc (lines, nlines * sizeof (char *));
+ }
+ }
+
+ /* Finally, set the corresponding CMDS->lines_flags elements and the
+ CMDS->any_recurse flag. */
+
+ if (nlines > USHRT_MAX)
+ ON (fatal, &cmds->fileinfo, _("Recipe has too many lines (%ud)"), nlines);
+
+ cmds->ncommand_lines = nlines;
+ cmds->command_lines = lines;
+
+ cmds->any_recurse = 0;
+#ifndef CONFIG_WITH_COMMANDS_FUNC
+ cmds->lines_flags = xmalloc (nlines);
+#else
+ cmds->lines_flags = xmalloc (nlines * sizeof (cmds->lines_flags[0]));
+#endif
+
+ for (idx = 0; idx < nlines; ++idx)
+ {
+ unsigned char flags = 0;
+ const char *p = lines[idx];
+
+ while (ISBLANK (*p) || *p == '-' || *p == '@' || *p == '+' IF_WITH_COMMANDS_FUNC(|| *p == '%'))
+ switch (*(p++))
+ {
+ case '+':
+ flags |= COMMANDS_RECURSE;
+ break;
+ case '@':
+ flags |= COMMANDS_SILENT;
+ break;
+ case '-':
+ flags |= COMMANDS_NOERROR;
+ break;
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+ case '%':
+ flags |= COMMAND_GETTER_SKIP_IT;
+ break;
+#endif
+ }
+
+ /* If no explicit '+' was given, look for MAKE variable references. */
+ if (!(flags & COMMANDS_RECURSE)
+#ifndef KMK
+ && (strstr (p, "$(MAKE)") != 0 || strstr (p, "${MAKE}") != 0))
+#else
+ && (strstr (p, "$(KMK)") != 0 || strstr (p, "${KMK}") != 0 ||
+ strstr (p, "$(MAKE)") != 0 || strstr (p, "${MAKE}") != 0))
+#endif
+ flags |= COMMANDS_RECURSE;
+
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ /* check if kmk builtin command */
+ if (!strncmp(p, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
+ flags |= COMMANDS_KMK_BUILTIN;
+#endif
+
+ cmds->lines_flags[idx] = flags;
+ cmds->any_recurse |= flags & COMMANDS_RECURSE ? 1 : 0;
+ }
+}
+
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+/* This is for saving memory in func_commands. */
+void
+free_chopped_commands (struct commands *cmds)
+{
+ if ( cmds
+ && cmds->command_lines != 0
+ && cmds->refs == 0)
+ {
+ unsigned idx = cmds->ncommand_lines;
+ while (idx-- > 0)
+ free (cmds->command_lines[idx]);
+ free (cmds->command_lines);
+ free (cmds->lines_flags);
+ cmds->command_lines = 0;
+ cmds->lines_flags = 0;
+ cmds->ncommand_lines = 0;
+ }
+}
+
+#endif /* CONFIG_WITH_MEMORY_OPTIMIZATIONS */
+/* Execute the commands to remake FILE. If they are currently executing,
+ return or have already finished executing, just return. Otherwise,
+ fork off a child process to run the first command line in the sequence. */
+
+void
+execute_file_commands (struct file *file)
+{
+ const char *p;
+
+ /* Don't go through all the preparations if
+ the commands are nothing but whitespace. */
+
+ for (p = file->cmds->commands; *p != '\0'; ++p)
+ if (!ISSPACE (*p) && *p != '-' && *p != '@' && *p != '+')
+ break;
+ if (*p == '\0')
+ {
+ /* If there are no commands, assume everything worked. */
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ file->command_flags |= COMMANDS_NO_COMMANDS;
+#endif
+ set_command_state (file, cs_running);
+ file->update_status = us_success;
+ notice_finished_file (file);
+ return;
+ }
+
+ /* First set the automatic variables according to this file. */
+
+ initialize_file_variables (file, 0);
+
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+ set_file_variables (file, 0 /* final call */);
+#else
+ set_file_variables (file);
+#endif
+
+ /* If this is a loaded dynamic object, unload it before remaking.
+ Some systems don't support overwriting a loaded object. */
+ if (file->loaded)
+ unload_file (file->name);
+
+ /* Start the commands running. */
+ new_job (file);
+}
+
+/* This is set while we are inside fatal_error_signal,
+ so things can avoid nonreentrant operations. */
+
+int handling_fatal_signal = 0;
+
+/* Handle fatal signals. */
+
+RETSIGTYPE
+fatal_error_signal (int sig)
+{
+#ifdef __MSDOS__
+ extern int dos_status, dos_command_running;
+
+ if (dos_command_running)
+ {
+ /* That was the child who got the signal, not us. */
+ dos_status |= (sig << 8);
+ return;
+ }
+ remove_intermediates (1);
+ exit (EXIT_FAILURE);
+#else /* not __MSDOS__ */
+#ifdef _AMIGA
+ remove_intermediates (1);
+ if (sig == SIGINT)
+ fputs (_("*** Break.\n"), stderr);
+
+ exit (10);
+#else /* not Amiga */
+#if defined (WINDOWS32) && !defined (CONFIG_NEW_WIN32_CTRL_EVENT)
+ extern HANDLE main_thread;
+
+ /* Windows creates a sperate thread for handling Ctrl+C, so we need
+ to suspend the main thread, or else we will have race conditions
+ when both threads call reap_children. */
+ if (main_thread)
+ {
+ DWORD susp_count = SuspendThread (main_thread);
+
+ if (susp_count != 0)
+ fprintf (stderr, "SuspendThread: suspend count = %ld\n", susp_count);
+ else if (susp_count == (DWORD)-1)
+ {
+ DWORD ierr = GetLastError ();
+
+ fprintf (stderr, "SuspendThread: error %ld: %s\n",
+ ierr, map_windows32_error_to_string (ierr));
+ }
+ }
+#endif
+ handling_fatal_signal = 1;
+
+ /* Set the handling for this signal to the default.
+ It is blocked now while we run this handler. */
+ signal (sig, SIG_DFL);
+
+ /* A termination signal won't be sent to the entire
+ process group, but it means we want to kill the children. */
+
+ if (sig == SIGTERM)
+ {
+ struct child *c;
+ for (c = children; c != 0; c = c->next)
+ if (!c->remote)
+# if defined (CONFIG_NEW_WIN_CHILDREN) && defined (WINDOWS32)
+ MkWinChildKill (c->pid, SIGTERM, c);
+# else
+ (void) kill (c->pid, SIGTERM);
+# endif
+ }
+
+ /* If we got a signal that means the user
+ wanted to kill make, remove pending targets. */
+
+ if (sig == SIGTERM || sig == SIGINT
+#ifdef SIGHUP
+ || sig == SIGHUP
+#endif
+#ifdef SIGQUIT
+ || sig == SIGQUIT
+#endif
+ )
+ {
+ struct child *c;
+
+ /* Remote children won't automatically get signals sent
+ to the process group, so we must send them. */
+ for (c = children; c != 0; c = c->next)
+ if (c->remote)
+ (void) remote_kill (c->pid, sig);
+
+ for (c = children; c != 0; c = c->next)
+ delete_child_targets (c);
+
+ /* Clean up the children. We don't just use the call below because
+ we don't want to print the "Waiting for children" message. */
+ while (job_slots_used > 0)
+ reap_children (1, 0);
+ }
+ else
+ /* Wait for our children to die. */
+ while (job_slots_used > 0)
+ reap_children (1, 1);
+
+ /* Delete any non-precious intermediate files that were made. */
+
+ remove_intermediates (1);
+#ifdef SIGQUIT
+ if (sig == SIGQUIT)
+ /* We don't want to send ourselves SIGQUIT, because it will
+ cause a core dump. Just exit instead. */
+ exit (MAKE_TROUBLE);
+#endif
+
+#ifdef WINDOWS32
+# ifndef CONFIG_NEW_WIN32_CTRL_EVENT
+ if (main_thread)
+ CloseHandle (main_thread);
+# endif /* !CONFIG_NEW_WIN32_CTRL_EVENT */
+ /* Cannot call W32_kill with a pid (it needs a handle). The exit
+ status of 130 emulates what happens in Bash. */
+ exit (130);
+#else
+ /* Signal the same code; this time it will really be fatal. The signal
+ will be unblocked when we return and arrive then to kill us. */
+ if (kill (getpid (), sig) < 0)
+ pfatal_with_name ("kill");
+#endif /* not WINDOWS32 */
+#endif /* not Amiga */
+#endif /* not __MSDOS__ */
+}
+
+/* Delete FILE unless it's precious or not actually a file (phony),
+ and it has changed on disk since we last stat'd it. */
+
+static void
+delete_target (struct file *file, const char *on_behalf_of)
+{
+ struct stat st;
+ int e;
+
+ if (file->precious || file->phony)
+ return;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ assert (!file->multi_maybe);
+#endif
+
+#ifndef NO_ARCHIVES
+ if (ar_name (file->name))
+ {
+ time_t file_date = (file->last_mtime == NONEXISTENT_MTIME
+ ? (time_t) -1
+ : (time_t) FILE_TIMESTAMP_S (file->last_mtime));
+ if (ar_member_date (file->name) != file_date)
+ {
+ if (on_behalf_of)
+ OSS (error, NILF,
+ _("*** [%s] Archive member '%s' may be bogus; not deleted"),
+ on_behalf_of, file->name);
+ else
+ OS (error, NILF,
+ _("*** Archive member '%s' may be bogus; not deleted"),
+ file->name);
+ }
+ return;
+ }
+#endif
+
+ EINTRLOOP (e, stat (file->name, &st));
+ if (e == 0
+ && S_ISREG (st.st_mode)
+ && FILE_TIMESTAMP_STAT_MODTIME (file->name, st) != file->last_mtime)
+ {
+ if (on_behalf_of)
+ OSS (error, NILF,
+ _("*** [%s] Deleting file '%s'"), on_behalf_of, file->name);
+ else
+ OS (error, NILF, _("*** Deleting file '%s'"), file->name);
+ if (unlink (file->name) < 0
+ && errno != ENOENT) /* It disappeared; so what. */
+ perror_with_name ("unlink: ", file->name);
+ }
+}
+
+
+/* Delete all non-precious targets of CHILD unless they were already deleted.
+ Set the flag in CHILD to say they've been deleted. */
+
+void
+delete_child_targets (struct child *child)
+{
+ struct dep *d;
+
+ if (child->deleted)
+ return;
+
+ /* Delete the target file if it changed. */
+ delete_target (child->file, NULL);
+
+ /* Also remove any non-precious targets listed in the 'also_make' member. */
+ for (d = child->file->also_make; d != 0; d = d->next)
+ delete_target (d->file, child->file->name);
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* Also remove any multi target siblings, except for the 'maybe' ones (we
+ handle that here) and precious ones (delete_target deals with that).
+ Note that CHILD is always the multi target head (see remake.c). */
+ if (child->file == child->file->multi_head)
+ {
+ struct file *f2;
+ for (f2 = child->file->multi_next; f2; f2 = f2->multi_next)
+ if (!f2->multi_maybe)
+ delete_target (f2, child->file->name);
+ }
+#endif
+
+ child->deleted = 1;
+}
+
+/* Print out the commands in CMDS. */
+
+void
+print_commands (const struct commands *cmds)
+{
+ const char *s;
+
+ fputs (_("# recipe to execute"), stdout);
+
+ if (cmds->fileinfo.filenm == 0)
+ puts (_(" (built-in):"));
+ else
+ printf (_(" (from '%s', line %lu):\n"),
+ cmds->fileinfo.filenm, cmds->fileinfo.lineno);
+
+ s = cmds->commands;
+ while (*s != '\0')
+ {
+ const char *end;
+ int bs;
+
+ /* Print one full logical recipe line: find a non-escaped newline. */
+ for (end = s, bs = 0; *end != '\0'; ++end)
+ {
+ if (*end == '\n' && !bs)
+ break;
+
+ bs = *end == '\\' ? !bs : 0;
+ }
+
+ printf ("%c%.*s\n", cmd_prefix, (int) (end - s), s);
+
+ s = end + (end[0] == '\n');
+ }
+}
diff --git a/src/kmk/commands.h b/src/kmk/commands.h
new file mode 100644
index 0000000..f06367b
--- /dev/null
+++ b/src/kmk/commands.h
@@ -0,0 +1,70 @@
+/* Definition of data structures describing shell commands for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Structure that gives the commands to make a file
+ and information about where these commands came from. */
+
+struct commands
+ {
+ floc fileinfo; /* Where commands were defined. */
+ char *commands; /* Commands text. */
+ char **command_lines; /* Commands chopped up into lines. */
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+ unsigned short *lines_flags;/* One set of flag bits for each line. */
+#else
+ unsigned char *lines_flags; /* One set of flag bits for each line. */
+#endif
+ unsigned short ncommand_lines;/* Number of command lines. */
+ char recipe_prefix; /* Recipe prefix for this command set. */
+ unsigned int any_recurse:1; /* Nonzero if any 'lines_flags' elt has */
+ /* the COMMANDS_RECURSE bit set. */
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ int refs; /* References. */
+#endif
+ };
+
+/* Bits in 'lines_flags'. */
+#define COMMANDS_RECURSE 1 /* Recurses: + or $(MAKE). */
+#define COMMANDS_SILENT 2 /* Silent: @. */
+#define COMMANDS_NOERROR 4 /* No errors: -. */
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+# define COMMANDS_NOTPARALLEL 32 /* kmk: The commands must be executed alone. */
+# define COMMANDS_NO_COMMANDS 64 /* kmk: No commands. */
+#endif
+#ifdef CONFIG_WITH_KMK_BUILTIN
+# define COMMANDS_KMK_BUILTIN 128 /* kmk: kmk builtin command. */
+#endif
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+# define COMMAND_GETTER_SKIP_IT 256 /* $(commands target) skips this: % */
+#endif
+
+RETSIGTYPE fatal_error_signal (int sig);
+void execute_file_commands (struct file *file);
+void print_commands (const struct commands *cmds);
+void delete_child_targets (struct child *child);
+void chop_commands (struct commands *cmds);
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+void free_chopped_commands (struct commands *cmd);
+#endif
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+void set_file_variables (struct file *file, int called_early);
+#else
+void set_file_variables (struct file *file);
+#endif
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+struct dep *create_uniqute_deps_chain (struct dep *deps);
+#endif
+
diff --git a/src/kmk/config.ami.template b/src/kmk/config.ami.template
new file mode 100644
index 0000000..4c5bb78
--- /dev/null
+++ b/src/kmk/config.ami.template
@@ -0,0 +1,337 @@
+/* config.h -- hand-massaged for Amiga -*-C-*-
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Define if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* #undef _ALL_SOURCE */
+#endif
+
+/* Define if using alloca.c. */
+#define C_ALLOCA
+
+/* Define if the closedir function returns void instead of int. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to empty if the keyword does not work. */
+/* #undef const */
+
+/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems.
+ This function is required for alloca.c support on those systems. */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Define if the 'getloadavg' function needs to be run setuid or setgid. */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 'unsigned long' or 'unsigned long long'
+ if <inttypes.h> doesn't define. */
+#define uintmax_t unsigned long
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define gid_t int
+
+/* Define if you have alloca, as a function or macro. */
+/* #undef HAVE_ALLOCA */
+
+/* Define if you have <alloca.h> and it should be used (not on Ultrix). */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define if your system has a working fnmatch function. */
+/* #undef HAVE_FNMATCH */
+
+/* Define if your system has its own 'getloadavg' function. */
+/* #undef HAVE_GETLOADAVG */
+
+/* Define if you have the getmntent function. */
+/* #undef HAVE_GETMNTENT */
+
+/* Embed GNU Guile support */
+/* #undef HAVE_GUILE */
+
+/* Define if the 'long double' type works. */
+/* #undef HAVE_LONG_DOUBLE */
+
+/* Define if you support file names longer than 14 characters. */
+#define HAVE_LONG_FILE_NAMES 1
+
+/* Define if you have a working 'mmap' system call. */
+/* #undef HAVE_MMAP */
+
+/* Define if system calls automatically restart after interruption
+ by a signal. */
+/* #undef HAVE_RESTARTABLE_SYSCALLS */
+
+/* Define if your struct stat has st_blksize. */
+/* #undef HAVE_ST_BLKSIZE */
+
+/* Define if your struct stat has st_blocks. */
+/* #undef HAVE_ST_BLOCKS */
+
+/* Define if you have the strcoll function and it is properly defined. */
+#define HAVE_STRCOLL 1
+
+/* Define if your struct stat has st_rdev. */
+#define HAVE_ST_RDEV 1
+
+/* Define if you have the strftime function. */
+#define HAVE_STRFTIME 1
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define if your struct tm has tm_zone. */
+/* #undef HAVE_TM_ZONE */
+
+/* Define if you don't have tm_zone but do have the external array
+ tzname. */
+#define HAVE_TZNAME 1
+
+/* Define if you have <unistd.h>. */
+#define HAVE_UNISTD_H 1
+
+/* Define if utime(file, NULL) sets file's timestamp to the present. */
+/* #undef HAVE_UTIME_NULL */
+
+/* Define if you have the wait3 system call. */
+/* #undef HAVE_WAIT3 */
+
+/* Define if on MINIX. */
+/* #undef _MINIX */
+
+/* Define if your struct nlist has an n_un member. */
+/* #undef NLIST_NAME_UNION */
+
+/* Define if you have <nlist.h>. */
+/* #undef NLIST_STRUCT */
+
+/* Define if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define pid_t int
+
+/* Define if the system does not provide POSIX.1 features except
+ with this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define if you need to in order for stat and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define as the return type of signal handlers (int or void). */
+#define RETSIGTYPE void
+
+/* Define if the setvbuf function takes the buffering type as its second
+ argument and the buffer pointer as the third, as on System V
+ before release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown
+ */
+#define STACK_DIRECTION -1
+
+/* Define if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define if you have the ANSI C header files. */
+#define STDC_HEADERS
+
+/* Define on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define if 'sys_siglist' is declared by <signal.h>. */
+/* #undef SYS_SIGLIST_DECLARED */
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define uid_t int
+
+/* Define for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define for Encore UMAX 4.3 that has <inq_status/cpustats.h>
+ instead of <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Name of this package (needed by automake) */
+#define PACKAGE "%PACKAGE%"
+
+/* Version of this package (needed by automake) */
+#define VERSION "%VERSION%"
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define this if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define this to enable job server support in GNU make. */
+/* #undef MAKE_JOBSERVER */
+
+/* Define to be the nanoseconds member of struct stat's st_mtim,
+ if it exists. */
+/* #undef ST_MTIM_NSEC */
+
+/* Define this if the C library defines the variable 'sys_siglist'. */
+/* #undef HAVE_SYS_SIGLIST */
+
+/* Define this if the C library defines the variable '_sys_siglist'. */
+/* #undef HAVE__SYS_SIGLIST */
+
+/* Define this if you have the 'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define if you have the dup2 function. */
+/* #undef HAVE_DUP2 */
+
+/* Define if you have the getcwd function. */
+#define HAVE_GETCWD 1
+
+/* Define if you have the getgroups function. */
+/* #undef HAVE_GETGROUPS */
+
+/* Define if you have the gethostbyname function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define if you have the gethostname function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define if you have the memmove function. */
+#define HAVE_MEMMOVE 1
+
+/* Define if you have the mktemp function. */
+#define HAVE_MKTEMP 1
+
+/* Define if you have the psignal function. */
+/* #undef HAVE_PSIGNAL */
+
+/* Define if you have the pstat_getdynamic function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define if you have the setegid function. */
+/* #undef HAVE_SETEGID */
+
+/* Define if you have the seteuid function. */
+/* #undef HAVE_SETEUID */
+
+/* Define if you have the setlinebuf function. */
+/* #undef HAVE_SETLINEBUF */
+
+/* Define if you have the setregid function. */
+/* #undef HAVE_SETREGID */
+
+/* Define if you have the setreuid function. */
+/* #undef HAVE_SETREUID */
+
+/* Define if you have the sigsetmask function. */
+/* #undef HAVE_SIGSETMASK */
+
+/* Define if you have the socket function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the strcasecmp function. */
+/* #undef HAVE_STRCASECMP */
+
+/* Define to 1 if you have the strcmpi function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the stricmp function. */
+/* #undef HAVE_STRICMP */
+
+/* Define if you have the strerror function. */
+#define HAVE_STRERROR 1
+
+/* Define if you have the strsignal function. */
+/* #undef HAVE_STRSIGNAL */
+
+/* Define if you have the wait3 function. */
+/* #undef HAVE_WAIT3 */
+
+/* Define if you have the waitpid function. */
+/* #undef HAVE_WAITPID */
+
+/* Define if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define if you have the <memory.h> header file. */
+/* #undef HAVE_MEMORY_H */
+
+/* Define if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define if you have the <stdlib.h> header file. */
+/* #undef HAVE_STDLIB_H */
+
+/* Define if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define if you have the <sys/dir.h> header file. */
+#define HAVE_SYS_DIR_H 1
+
+/* Define if you have the <sys/ndir.h> header file. */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define if you have the <sys/param.h> header file. */
+/* #undef HAVE_SYS_PARAM_H */
+
+/* Define if you have the <sys/timeb.h> header file. */
+/* #undef HAVE_SYS_TIMEB_H */
+
+/* Define if you have the <sys/wait.h> header file. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define if you have the dgc library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define if you have the kstat library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the `isatty' function. */
+/* #undef HAVE_ISATTY */
+
+/* Define to 1 if you have the `ttyname' function. */
+/* #undef HAVE_TTYNAME */
+
+/* Define if you have the sun library (-lsun). */
+/* #undef HAVE_LIBSUN */
+
+/* Output sync sypport */
+#define NO_OUTPUT_SYNC
+
+/* Define for Case Insensitve behavior */
+#define HAVE_CASE_INSENSITIVE_FS
+
+/* Build host information. */
+#define MAKE_HOST "Amiga"
diff --git a/src/kmk/config.h-vms.template b/src/kmk/config.h-vms.template
new file mode 100644
index 0000000..2a4a943
--- /dev/null
+++ b/src/kmk/config.h-vms.template
@@ -0,0 +1,432 @@
+/* config.h-vms. Generated by hand by Klaus Kämpf <kkaempf@rmi.de> -*-C-*-
+
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* config.h. Generated automatically by configure. */
+/* config.h.in. Generated automatically from configure.ac by autoheader. */
+
+/* Pull in types.h here to get __CRTL_VER defined for old versions of the
+ compiler which don't define it. */
+#ifdef __DECC
+# include <types.h>
+#endif
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* #undef _ALL_SOURCE */
+#endif
+
+/* Define to 1 if NLS is requested. */
+/* #undef ENABLE_NLS */
+
+/* Define as 1 if you have dcgettext. */
+/* #undef HAVE_DCGETTEXT */
+
+/* Define as 1 if you have gettext and don't want to use GNU gettext. */
+/* #undef HAVE_GETTEXT */
+
+/* Embed GNU Guile support */
+/* #undef HAVE_GUILE */
+
+/* Define to 1 if your locale.h file contains LC_MESSAGES. */
+/* #undef HAVE_LC_MESSAGES */
+
+/* Define to the installation directory for locales. */
+#define LOCALEDIR ""
+
+/* Define as 1 if you have the stpcpy function. */
+/* #undef HAVE_STPCPY */
+
+/* Define to 1 if the closedir function returns void instead of int. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to empty if the keyword does not work. */
+/* #undef const */
+
+/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems.
+ This function is required for alloca.c support on those systems. */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Define to 1 if the 'getloadavg' function needs to be run setuid or setgid. */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 'unsigned long' or 'unsigned long long'
+ if <inttypes.h> doesn't define. */
+#define uintmax_t unsigned long
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to 1 if you have alloca, as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix). */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if you have the fdopen function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if your system has a working fnmatch function. */
+/* #undef HAVE_FNMATCH */
+
+/* Define to 1 if your system has its own 'getloadavg' function. */
+/* #undef HAVE_GETLOADAVG */
+
+/* Define to 1 if you have the getmntent function. */
+/* #undef HAVE_GETMNTENT */
+
+/* Define to 1 if the 'long double' type works. */
+/* #undef HAVE_LONG_DOUBLE */
+
+/* Define to 1 if you support file names longer than 14 characters. */
+#define HAVE_LONG_FILE_NAMES 1
+
+/* Define to 1 if you have a working 'mmap' system call. */
+/* #undef HAVE_MMAP */
+
+/* Define to 1 if system calls automatically restart after interruption
+ by a signal. */
+/* #undef HAVE_RESTARTABLE_SYSCALLS */
+
+/* Define to 1 if your struct stat has st_blksize. */
+/* #undef HAVE_ST_BLKSIZE */
+
+/* Define to 1 if your struct stat has st_blocks. */
+/* #undef HAVE_ST_BLOCKS */
+
+/* Define to 1 if you have the strcoll function and it is properly defined. */
+/* #undef HAVE_STRCOLL */
+
+/* Define to 1 if you have the strncasecmp' function. */
+#if __CRTL_VER >= 70000000
+#define HAVE_STRNCASECMP 1
+#endif
+
+/* Define to 1 if your struct stat has st_rdev. */
+/* #undef HAVE_ST_RDEV */
+
+/* Define to 1 if you have the strftime function. */
+/* #undef HAVE_STRFTIME */
+
+/* Define to 1 if you have <sys/wait.h> that is POSIX.1 compatible. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to 1 if your struct tm has tm_zone. */
+/* #undef HAVE_TM_ZONE */
+
+/* Define to 1 if you don't have tm_zone but do have the external array
+ tzname. */
+/* #undef HAVE_TZNAME */
+
+/* Define to 1 if you have <unistd.h>. */
+#ifdef __DECC
+#define HAVE_UNISTD_H 1
+#endif
+
+/* Define to 1 if utime(file, NULL) sets file's timestamp to the present. */
+/* #undef HAVE_UTIME_NULL */
+
+/* Define to 1 if you have the wait3 system call. */
+/* #undef HAVE_WAIT3 */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 1 if your struct nlist has an n_un member. */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if you have <nlist.h>. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+/* I assume types.h is available for all 5.0 cc/cxx compilers */
+#if __DECC_VER < 50090000
+#define pid_t int
+#endif
+
+/* Define to 1 if the system does not provide POSIX.1 features except
+ with this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for stat and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define as the return type of signal handlers (int or void). */
+#define RETSIGTYPE void
+
+/* Define to 1 if the setvbuf function takes the buffering type as its second
+ argument and the buffer pointer as the third, as on System V
+ before release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown
+ */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+/* #undef STDC_HEADERS */
+
+/* Define on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if 'sys_siglist' is declared by <signal.h>. */
+/* #undef SYS_SIGLIST_DECLARED */
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#if __DECC_VER < 50090000
+#define uid_t int
+#endif
+
+/* Define for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define for Encore UMAX 4.3 that has <inq_status/cpustats.h>
+ instead of <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Name of this package (needed by automake) */
+#define PACKAGE "%PACKAGE%"
+
+/* Version of this package (needed by automake) */
+#define VERSION "%VERSION%"
+
+/* Define to the name of the SCCS 'get' command. */
+/* #undef SCCS_GET */
+
+/* Define this if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define this to enable job server support in GNU make. */
+/* #undef MAKE_JOBSERVER */
+
+/* Define to be the nanoseconds member of struct stat's st_mtim,
+ if it exists. */
+/* #undef ST_MTIM_NSEC */
+
+/* Define to 1 if the C library defines the variable 'sys_siglist'. */
+/* #undefine HAVE_SYS_SIGLIST */
+
+/* Define to 1 if the C library defines the variable '_sys_siglist'. */
+/* #undef HAVE__SYS_SIGLIST */
+
+/* Define to 1 if you have the 'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the dup2 function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the getcwd function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the getgroups function. */
+/* #undef HAVE_GETGROUPS */
+
+/* Define to 1 if you have the gethostbyname function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the gethostname function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the memmove function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the mktemp function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the psignal function. */
+/* #undef HAVE_PSIGNAL */
+
+/* Define to 1 if you have the pstat_getdynamic function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the setegid function. */
+/* #undef HAVE_SETEGID */
+
+/* Define to 1 if you have the seteuid function. */
+/* #undef HAVE_SETEUID */
+
+/* Define to 1 if you have the setlinebuf function. */
+/* #undef HAVE_SETLINEBUF */
+
+/* Define to 1 if you have the setregid function. */
+/* #undefine HAVE_SETREGID */
+
+/* Define to 1 if you have the setreuid function. */
+/* #define HAVE_SETREUID */
+
+/* Define to 1 if you have the sigsetmask function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the socket function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the strcasecmp function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the strcmpi function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the stricmp function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the strerror function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the strsignal function. */
+/* #undef HAVE_STRSIGNAL */
+
+/* Define to 1 if you have the wait3 function. */
+/* #undef HAVE_WAIT3 */
+
+/* Define to 1 if you have the waitpid function. */
+/* #undef HAVE_WAITPID */
+
+/* Define to 1 if you have the <dirent.h> header file. */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#ifdef __DECC
+#define HAVE_FCNTL_H 1
+#endif
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+/* #undef HAVE_MEMORY_H */
+
+/* Define to 1 if you have the <ndir.h> header file. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the <sys/dir.h> header file. */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file. */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+/* #undef HAVE_SYS_PARAM_H */
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#ifndef __GNUC__
+#define HAVE_SYS_TIMEB_H 1
+#endif
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to 1 if you have the dgc library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the kstat library (-lkstat). */
+/* #undef HAVE_LIBKSTAT *
+
+/* Define to 1 if you have the sun library (-lsun). */
+/* #undef HAVE_LIBSUN */
+
+/* Define to 1 if you have the `isatty' function. */
+/* #undef HAVE_ISATTY */
+
+/* Define to 1 if you have the `ttyname' function. */
+/* #undef HAVE_TTYNAME */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define for case insensitve filenames */
+#define HAVE_CASE_INSENSITIVE_FS 1
+
+/* VMS specific, define it if you want to use case sensitive targets */
+/* #undef WANT_CASE_SENSITIVE_TARGETS */
+
+/* VMS specific, V7.0 has opendir() and friends, so it's undefined */
+/* If you want to use non-VMS code for opendir() etc. on V7.0 and greater
+ define the first or both macros AND change the compile command to get the
+ non-VMS versions linked: (prefix=(all,except=(opendir,... */
+/* #undef HAVE_VMSDIR_H */
+/* #undef _DIRENT_HAVE_D_NAMLEN */
+
+/* On older systems without 7.0 backport of CRTL use non-VMS code for opendir() etc. */
+#if __CRTL_VER < 70000000
+# define HAVE_VMSDIR_H 1
+#endif
+
+#if defined(HAVE_VMSDIR_H) && defined(HAVE_DIRENT_H)
+#undef HAVE_DIRENT_H
+#endif
+
+#define HAVE_STDLIB_H 1
+#define INCLUDEDIR "sys$sysroot:[syslib]"
+#define LIBDIR "sys$sysroot:[syslib]"
+
+/* Don't use RTL functions of OpenVMS */
+#ifdef __DECC
+#include <stdio.h>
+#include <unistd.h>
+#define getopt gnu_getopt
+#define optarg gnu_optarg
+#define optopt gnu_optopt
+#define optind gnu_optind
+#define opterr gnu_opterr
+#define globfree gnu_globfree
+#define glob gnu_glob
+#endif
+
+/* Define if using alloca.c. */
+/* #undef C_ALLOCA */
+/* maybe this should be placed into makeint.h */
+#if defined(__VAX) && defined(__DECC)
+#define alloca(n) __ALLOCA(n)
+#endif
+
+/* Output sync sypport */
+#define NO_OUTPUT_SYNC
+
+/* Define to 1 to write even short single-line actions into a VMS/DCL command
+ file; this also enables exporting make environment variables into the
+ (sub-)process, which executes the action.
+ The usual make rules apply whether a shell variable - here a DCL symbol or
+ VMS logical [see CRTL getenv()] - is added to the make environment and
+ is exported. */
+#define USE_DCL_COM_FILE 1
+
+/* Build host information. */
+#define MAKE_HOST "VMS"
diff --git a/src/kmk/config.h.W32.template b/src/kmk/config.h.W32.template
new file mode 100644
index 0000000..73446e0
--- /dev/null
+++ b/src/kmk/config.h.W32.template
@@ -0,0 +1,532 @@
+/* config.h.W32 -- hand-massaged config.h file for Windows builds -*-C-*-
+
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Suppress some Visual C++ warnings.
+ Maybe after the code cleanup for ISO C we can remove some/all of these. */
+#if _MSC_VER > 1000
+# pragma warning(disable:4100) /* unreferenced formal parameter */
+# pragma warning(disable:4102) /* unreferenced label */
+# pragma warning(disable:4127) /* conditional expression is constant */
+# pragma warning(disable:4131) /* uses old-style declarator */
+# pragma warning(disable:4702) /* unreachable code */
+# define _CRT_SECURE_NO_WARNINGS /* function or variable may be unsafe */
+# define _CRT_NONSTDC_NO_WARNINGS /* functions w/o a leading underscore */
+#endif
+
+/* Define to 1 if the 'closedir' function returns void instead of 'int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of '_getb67', 'GETB67', 'getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for 'alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using 'alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using 'getloadavg.c'. */
+#define C_GETLOADAVG 1
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Define to 1 if translation of program messages to the user's native
+ language is requested. */
+/* #undef ENABLE_NLS */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if the 'getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have 'alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if you have the 'atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+/* #undef HAVE_CLOCK_GETTIME */
+
+/* Embed GNU Guile support. Windows build sets this on the
+ compilation command line. */
+/* #undef HAVE_GUILE */
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+ */
+/* #undef HAVE_DCGETTEXT */
+
+/* Define to 1 if you have the declaration of 'bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 0
+
+/* Define to 1 if you have the declaration of 'sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of '_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of '__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines 'DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <direct.h> header file, and it defines getcwd()
+ and chdir().
+ */
+#if (defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(__INTERIX)
+# define HAVE_DIRECT_H 1
+#endif
+
+/* Use platform specific coding */
+#define HAVE_DOS_PATHS 1
+
+/* Define to 1 if you have the 'dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the 'fdopen' function. */
+#ifdef __MINGW32__
+#define HAVE_FDOPEN 1
+#endif
+
+/* Define to 1 if you have the 'fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the 'getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the 'getgroups' function. */
+/* #undef HAVE_GETGROUPS */
+
+/* Define to 1 if you have the 'gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the 'gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the 'getloadavg' function. */
+/* #undef HAVE_GETLOADAVG */
+
+/* Define to 1 if you have the 'getrlimit' function. */
+/* #undef HAVE_GETRLIMIT */
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+/* #undef HAVE_GETTEXT */
+
+/* Define to 1 if you have a standard gettimeofday function */
+#ifdef __MINGW32__
+#define HAVE_GETTIMEOFDAY 1
+#endif
+
+/* Define if you have the iconv() function. */
+/* #undef HAVE_ICONV */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_INTTYPES_H 1
+#endif
+
+/* Define to 1 if you have the 'dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the 'kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+/*#define HAVE_LOCALE_H 1*/
+
+/* Define to 1 if you have the 'lstat' function. */
+/* #undef HAVE_LSTAT */
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the 'mkstemp' function. */
+/* #undef HAVE_MKSTEMP */
+
+/* Define to 1 if you have the 'mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines 'DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the 'pipe' function. */
+/* #undef HAVE_PIPE */
+
+/* Define to 1 if you have the 'pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the 'readlink' function. */
+/* #undef HAVE_READLINK */
+
+/* Define to 1 if you have the 'realpath' function. */
+/* #undef HAVE_REALPATH */
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+/* #undef HAVE_SA_RESTART */
+
+/* Define to 1 if you have the 'setegid' function. */
+/* #undef HAVE_SETEGID */
+
+/* Define to 1 if you have the 'seteuid' function. */
+/* #undef HAVE_SETEUID */
+
+/* Define to 1 if you have the 'setlinebuf' function. */
+/* #undef HAVE_SETLINEBUF */
+
+/* Define to 1 if you have the 'setlocale' function. */
+/*#define HAVE_SETLOCALE 1*/
+
+/* Define to 1 if you have the 'setregid' function. */
+/* #undef HAVE_SETREGID */
+
+/* Define to 1 if you have the 'setreuid' function. */
+/* #undef HAVE_SETREUID */
+
+/* Define to 1 if you have the 'setrlimit' function. */
+/* #undef HAVE_SETRLIMIT */
+
+/* Define to 1 if you have the 'setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the 'sigaction' function. */
+/* #undef HAVE_SIGACTION */
+
+/* Define to 1 if you have the 'sigsetmask' function. */
+/* #undef HAVE_SIGSETMASK */
+
+/* Define to 1 if you have the 'socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_STDINT_H 1
+#endif
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the 'strcasecmp' function. */
+#ifdef __MINGW32__
+#define HAVE_STRCASECMP 1
+#endif
+
+/* Define to 1 if you have the 'strcmpi' function. */
+#define HAVE_STRCMPI 1
+
+/* Define to 1 if you have the 'strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the 'strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the 'strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the 'stricmp' function. */
+#define HAVE_STRICMP 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+/* #define HAVE_STRINGS_H 1 */
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the 'strncasecmp' function. */
+#ifdef __MINGW32__
+#define HAVE_STRNCASECMP 1
+#endif
+
+/* Define to 1 if you have the 'strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the 'strndup' function. */
+/* #undef HAVE_STRNDUP */
+
+/* Define to 1 if you have the 'strnicmp' function. */
+#ifdef __MINGW32__
+#define HAVE_STRNICMP 1
+#endif
+
+/* Define to 1 if you have the 'strsignal' function. */
+/* #undef HAVE_STRSIGNAL */
+
+/* Define to 1 if you have the `isatty' function. */
+#define HAVE_ISATTY 1
+
+/* Define to 1 if you have the `ttyname' function. */
+#define HAVE_TTYNAME 1
+char *ttyname (int);
+
+/* Define to 1 if 'n_un.n_name' is a member of 'struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines 'DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines 'DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_SYS_PARAM_H 1
+#endif
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+/* #undef HAVE_SYS_RESOURCE_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_SYS_TIME_H 1
+#endif
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to 1 if you have the \'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_UNISTD_H 1
+#endif
+
+/* Define to 1 if you have the 'wait3' function. */
+/* #undef HAVE_WAIT3 */
+
+/* Define to 1 if you have the 'waitpid' function. */
+/* #undef HAVE_WAITPID */
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "Windows32"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable 'load' support in GNU make. */
+#define MAKE_LOAD 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+/* #undef MAKE_SYMLINKS */
+
+/* Define to 1 if your 'struct nlist' has an 'n_un' member. Obsolete, depend
+ on 'HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of this package (needed by automake) */
+#define PACKAGE "%PACKAGE%"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.gnu.org/software/make/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "%VERSION%"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ';'
+
+/* Define as the return type of signal handlers ('int' or 'void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "echo no sccs get"
+
+/* Define this if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define to 1 if the 'setvbuf' function takes the buffering type as its
+ second argument and the buffer pointer as the third, as on System V before
+ release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+/* #undef ST_MTIM_NSEC */
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#ifdef __MINGW32__
+#define TIME_WITH_SYS_TIME 1
+#endif
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Version number of package */
+#define VERSION "%VERSION%"
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for 'stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if 'const' does not conform to ANSI C. */
+/* #undef const */
+
+#include <sys/types.h>
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define gid_t int
+
+/* Define to 'int' if <sys/types.h> does not define. */
+/* GCC 4.x reportedly defines pid_t. */
+#ifndef _PID_T_
+#ifdef _WIN64
+#define pid_t __int64
+#else
+#define pid_t int
+#endif
+#endif
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define uid_t int
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+#if !HAVE_STDINT_H && !HAVE_INTTYPES_H
+#define uintmax_t unsigned long
+#endif
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to the installation directory for locales. */
+#define LOCALEDIR ""
+
+/*
+ * Refer to README.W32 for info on the following settings
+ */
+
+
+/*
+ * If you have a shell that does not grok 'sh -c quoted-command-line'
+ * correctly, you need this setting. Please see below for specific
+ * shell support.
+ */
+/*#define BATCH_MODE_ONLY_SHELL 1 */
+
+/*
+ * Define if you have the Cygnus "Cygwin" GNU Windows32 tool set.
+ * Do NOT define BATCH_MODE_ONLY_SHELL if you define HAVE_CYGWIN_SHELL
+ */
+/*#define HAVE_CYGWIN_SHELL 1 */
+
+/*
+ * Define if you have the MKS tool set or shell. Do NOT define
+ * BATCH_MODE_ONLY_SHELL if you define HAVE_MKS_SHELL
+ */
+/*#define HAVE_MKS_SHELL 1 */
+
+/*
+ * Enforce the mutual exclusivity restriction.
+ */
+#ifdef HAVE_MKS_SHELL
+#undef BATCH_MODE_ONLY_SHELL
+#endif
+
+#ifdef HAVE_CYGWIN_SHELL
+#undef BATCH_MODE_ONLY_SHELL
+#endif
diff --git a/src/kmk/config.h.darwin b/src/kmk/config.h.darwin
new file mode 100644
index 0000000..18e2818
--- /dev/null
+++ b/src/kmk/config.h.darwin
@@ -0,0 +1,491 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 1
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the declaration of `bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 1
+
+/* Define to 1 if you have the declaration of `dlerror', and to 0 if you
+ don't. */
+#define HAVE_DECL_DLERROR 1
+
+/* Define to 1 if you have the declaration of `dlopen', and to 0 if you don't.
+ */
+#define HAVE_DECL_DLOPEN 1
+
+/* Define to 1 if you have the declaration of `dlsym', and to 0 if you don't.
+ */
+#define HAVE_DECL_DLSYM 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup' function. */
+#define HAVE_DUP 1
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `isatty' function. */
+#define HAVE_ISATTY 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+#if 0 /* before 10.12.0 pselect is a useland implementation, which is insufficient for our purposes. */
+/* Define to 1 if you have the `pselect' function. */
+# define HAVE_PSELECT 1
+#else
+# undef HAVE_PSELECT
+#endif
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strndup' function. */
+/* #define HAVE_STRNDUP 1 - not in snow leopard */
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is a member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the `ttyname' function. */
+#define HAVE_TTYNAME 1
+
+/* Define to 1 if the system has the type `uintmax_t'. */
+#define HAVE_UINTMAX_T 1
+
+/* Define to 1 if you have the 'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if the system has the type `unsigned long long int'. */
+#define HAVE_UNSIGNED_LONG_LONG_INT 1
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. */
+#define MAKE_HOST "x86_64-apple-darwin16.7.0"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable 'load' support in GNU make. */
+/* #undef MAKE_LOAD */
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 4.2.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.gnu.org/software/make/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "4.2.1"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+#define ST_ATIM_NSEC st_atimespec.tv_nsec
+
+/* Define if struct stat contains a nanoseconds field */
+#define ST_MTIM_NSEC st_mtimespec.tv_nsec
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "4.2.1"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef ssize_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define to the widest unsigned integer type if <stdint.h> and <inttypes.h>
+ do not define. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
diff --git a/src/kmk/config.h.freebsd b/src/kmk/config.h.freebsd
new file mode 100644
index 0000000..8fa99cc
--- /dev/null
+++ b/src/kmk/config.h.freebsd
@@ -0,0 +1,433 @@
+/* config.h. Generated by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Define to 1 if you have the `bsd_signal' function. */
+/* #undef HAVE_BSD_SIGNAL */
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+/* #undef HAVE_CLOCK_GETTIME */
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the \`union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. (not used by kmk) */
+#if defined(__X86__) || defined(__i386__) || defined(__I386__)
+#define MAKE_HOST "i386-unknown-freebsd6.2"
+#else
+#define MAKE_HOST "amd64-unknown-freebsd6.2"
+#endif
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 3.82"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.82"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define to 1 if the C compiler supports function prototypes. */
+#define PROTOTYPES 1
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define to 1 if the `setvbuf' function takes the buffering type as its
+ second argument and the buffer pointer as the third, as on System V before
+ release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+/* #undef ST_MTIM_NSEC */
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Version number of package */
+#define VERSION "3.82"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define like PROTOTYPES; this can be used by system headers. */
+#define __PROTOTYPES 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
+
diff --git a/src/kmk/config.h.haiku b/src/kmk/config.h.haiku
new file mode 100644
index 0000000..3580374
--- /dev/null
+++ b/src/kmk/config.h.haiku
@@ -0,0 +1,459 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+#define C_GETLOADAVG 1
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 1
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the declaration of `bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 0
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+/* #undef HAVE_GETLOADAVG */
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+#define HAVE_SETLOCALE 1
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+/* #undef HAVE_SIGSETMASK */
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strndup' function. */
+#define HAVE_STRNDUP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is a member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the \`union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+/* #undef HAVE_WAIT3 */
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "i586-pc-haiku"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 3.82"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.gnu.org/software/make/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.82"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+#define ST_MTIM_NSEC tv_nsec
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "3.82"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
diff --git a/src/kmk/config.h.linux b/src/kmk/config.h.linux
new file mode 100644
index 0000000..99c5fa4
--- /dev/null
+++ b/src/kmk/config.h.linux
@@ -0,0 +1,498 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/*
+ * bird: Move up this bunch so we can include features.h early.
+ */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+#include <features.h> /* bird */
+
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 1
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the declaration of `bsd_signal', and to 0 if you
+ don't. */
+/* #define HAVE_DECL_BSD_SIGNAL 1 */
+
+/* Define to 1 if you have the declaration of `dlerror', and to 0 if you
+ don't. */
+#define HAVE_DECL_DLERROR 1
+
+/* Define to 1 if you have the declaration of `dlopen', and to 0 if you don't.
+ */
+#define HAVE_DECL_DLOPEN 1
+
+/* Define to 1 if you have the declaration of `dlsym', and to 0 if you don't.
+ */
+#define HAVE_DECL_DLSYM 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup' function. */
+#define HAVE_DUP 1
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `isatty' function. */
+#define HAVE_ISATTY 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pselect' function. */
+#define HAVE_PSELECT 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strndup' function. */
+#define HAVE_STRNDUP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is a member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/select.h> header file. */
+#define HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the `ttyname' function. */
+#define HAVE_TTYNAME 1
+
+/* Define to 1 if the system has the type `uintmax_t'. */
+#define HAVE_UINTMAX_T 1
+
+/* Define to 1 if you have the 'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if the system has the type `unsigned long long int'. */
+#define HAVE_UNSIGNED_LONG_LONG_INT 1
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "i686-pc-linux-gnu"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable 'load' support in GNU make. */
+/* #undef MAKE_LOAD */
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 4.2.1"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://kbuild.org/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "4.2.1"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+#if defined(__USE_XOPEN2K8) || defined(__USE_MISC)
+# define ST_MTIM_NSEC st_mtim.tv_nsec
+# define ST_ATIM_NSEC st_atim.tv_nsec
+#else
+# define ST_MTIM_NSEC st_mtimensec
+# define ST_ATIM_NSEC st_atimensec
+#endif
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+
+/* Version number of package */
+#define VERSION "4.2.1"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+#define _FILE_OFFSET_BITS 64
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `long int' if <sys/types.h> does not define. */
+/* #undef off_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef ssize_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define to the widest unsigned integer type if <stdint.h> and <inttypes.h>
+ do not define. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
+
diff --git a/src/kmk/config.h.netbsd b/src/kmk/config.h.netbsd
new file mode 100755
index 0000000..77c43d1
--- /dev/null
+++ b/src/kmk/config.h.netbsd
@@ -0,0 +1,459 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+/* #undef HAVE_CLOCK_GETTIME */
+
+/* Define to 1 if you have the declaration of `bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 0
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strndup' function. */
+#define HAVE_STRNDUP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is a member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the \`union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "i386-unknown-netbsdelf5.1."
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 3.82"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://www.gnu.org/software/make/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.82"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+/* #undef ST_MTIM_NSEC */
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# define _ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# define _POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# define _TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# define __EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define VERSION "3.82"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef _DARWIN_USE_64_BIT_INODE
+# define _DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+/* #undef size_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
diff --git a/src/kmk/config.h.os2 b/src/kmk/config.h.os2
new file mode 100644
index 0000000..32b919c
--- /dev/null
+++ b/src/kmk/config.h.os2
@@ -0,0 +1,476 @@
+/* config.h. Generated by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Define to 1 if translation of program messages to the user's native
+ language is requested. */
+#define ENABLE_NLS 1
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the declaration of `bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 1
+
+/* Define if you have the clock_gettime function. */
+/* #undef HAVE_CLOCK_GETTIME */
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+ */
+#define HAVE_DCGETTEXT 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+/* #undef HAVE_DOPRNT */
+
+/* Use platform specific coding */
+#define HAVE_DOS_PATHS 1
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+#define HAVE_GETTEXT 1
+
+/* Define if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define if you have the iconv() function. */
+/* #undef HAVE_ICONV */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the `memcpy' function. */
+#define HAVE_MEMCPY 1
+
+/* Define to 1 if you have the `memmove' function. */
+#define HAVE_MEMMOVE 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+#define HAVE_SIGSETMASK 1
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+/* #undef HAVE_STRCASECMP */
+
+/* Define to 1 if you have the `strchr' function. */
+#define HAVE_STRCHR 1
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if you have the `strndup' function. */
+#define HAVE_STRNDUP 1
+
+/* Define to 1 if you have the `strnicmp' function. */
+#define HAVE_STRNICMP 1
+
+/* Define to 1 if `n_un.n_name' is member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define this if you have the \`union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+/* #undef HAVE_VFORK */
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+/* #undef HAVE_WORKING_VFORK */
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "i386-pc-os2-emx"
+
+/* Define this to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define this to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 3.82"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.82"
+
+/* Define to 1 if the C compiler supports function prototypes. */
+#define PROTOTYPES 1
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define this if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define to 1 if the `setvbuf' function takes the buffering type as its
+ second argument and the buffer pointer as the third, as on System V before
+ release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run-time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+/* #undef ST_MTIM_NSEC */
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Version number of package */
+#define VERSION "3.81"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define like PROTOTYPES; this can be used by system headers. */
+#define __PROTOTYPES 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+#define vfork fork
+
+/*
+ * Modifications:
+ */
+
+/* Define to the installation directory for locales. */
+#define LOCALEDIR ""
+
+/*
+ * If you have a shell that does not grok 'sh -c quoted-command-line'
+ * correctly, you need this setting. Please see below for specific
+ * shell support.
+ */
+#define BATCH_MODE_ONLY_SHELL 1
+
+#define _DIRENT_HAVE_D_NAMLEN 1
+#define _DIRENT_HAVE_D_TYPE 1
+
+#define INCLUDEDIR "."
+#define LIBDIR "."
+
+#include "inlined_memchr.h"
+
diff --git a/src/kmk/config.h.solaris b/src/kmk/config.h.solaris
new file mode 100644
index 0000000..adeaca3
--- /dev/null
+++ b/src/kmk/config.h.solaris
@@ -0,0 +1,443 @@
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.in by autoheader. */
+
+/* Define to 1 if the `closedir' function returns void instead of `int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of `_getb67', `GETB67', `getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for `alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using `alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using `getloadavg.c'. */
+/* #undef C_GETLOADAVG */
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 1
+
+/* Define to 1 if the `getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have `alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+#define HAVE_ALLOCA_H 1
+
+/* Define to 1 if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Define to 1 if you have the `atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Define to 1 if you have the `bsd_signal' function. */
+#define HAVE_BSD_SIGNAL 1
+#define HAVE_DECL_BSD_SIGNAL 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+#define HAVE_CLOCK_GETTIME 1
+
+/* Define to 1 if you have the declaration of `sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of `_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 1
+
+/* Define to 1 if you have the declaration of `__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you don't have `vprintf' but do have `_doprnt.' */
+#define HAVE_DOPRNT 1
+
+/* Use platform specific coding */
+/* #undef HAVE_DOS_PATHS */
+
+/* Define to 1 if you have the `dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the `fdopen' function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the `fork' function. */
+#define HAVE_FORK 1
+
+/* Define to 1 if you have the `getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the `getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the `gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the `gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the `getloadavg' function. */
+#define HAVE_GETLOADAVG 1
+
+/* Define to 1 if you have the `getrlimit' function. */
+#define HAVE_GETRLIMIT 1
+
+/* Define to 1 if you have a standard gettimeofday function */
+#define HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#define HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the `kstat' library (-lkstat). */
+#define HAVE_LIBKSTAT 1
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1
+
+/* Define to 1 if you have the `lstat' function. */
+#define HAVE_LSTAT 1
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mkstemp' function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the `mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the `pipe' function. */
+#define HAVE_PIPE 1
+
+/* Define to 1 if you have the `pselect' function. */
+#define HAVE_PSELECT 1
+
+/* Define to 1 if you have the `pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the `readlink' function. */
+#define HAVE_READLINK 1
+
+/* Define to 1 if you have the `realpath' function. */
+#define HAVE_REALPATH 1
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+#define HAVE_SA_RESTART 1
+
+/* Define to 1 if you have the `setegid' function. */
+#define HAVE_SETEGID 1
+
+/* Define to 1 if you have the `seteuid' function. */
+#define HAVE_SETEUID 1
+
+/* Define to 1 if you have the `setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the `setlocale' function. */
+/* #undef HAVE_SETLOCALE */
+
+/* Define to 1 if you have the `setregid' function. */
+#define HAVE_SETREGID 1
+
+/* Define to 1 if you have the `setreuid' function. */
+#define HAVE_SETREUID 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `sigsetmask' function. */
+/* #undef HAVE_SIGSETMASK */
+
+/* Define to 1 if you have the `socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdarg.h> header file. */
+#define HAVE_STDARG_H 1
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#define HAVE_STDINT_H 1
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the `strcasecmp' function. */
+#define HAVE_STRCASECMP 1
+
+/* Define to 1 if you have the `strcmpi' function. */
+/* #undef HAVE_STRCMPI */
+
+/* Define to 1 if you have the `strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Define to 1 if you have the `strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the `strncmp' function. */
+/* #undef HAVE_STRNICMP */
+
+/* Define to 1 if you have the `strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the `strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the `strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the `stricmp' function. */
+/* #undef HAVE_STRICMP */
+
+/* Define to 1 if you have the <strings.h> header file. */
+#define HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strsignal' function. */
+#define HAVE_STRSIGNAL 1
+
+/* Define to 1 if `n_un.n_name' is member of `struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#define HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+#define HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#define HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#define HAVE_SYS_WAIT_H 1
+
+/* Define to 1 if you have the \`union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#define HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the <varargs.h> header file. */
+/* #undef HAVE_VARARGS_H */
+
+/* Define to 1 if you have the `vfork' function. */
+#define HAVE_VFORK 1
+
+/* Define to 1 if you have the <vfork.h> header file. */
+/* #undef HAVE_VFORK_H */
+
+/* Define to 1 if you have the `vprintf' function. */
+#define HAVE_VPRINTF 1
+
+/* Define to 1 if you have the `wait3' function. */
+#define HAVE_WAIT3 1
+
+/* Define to 1 if you have the `waitpid' function. */
+#define HAVE_WAITPID 1
+
+/* Define to 1 if `fork' works. */
+#define HAVE_WORKING_FORK 1
+
+/* Define to 1 if `vfork' works. */
+#define HAVE_WORKING_VFORK 1
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "i386-pc-solaris2.11"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+#define MAKE_SYMLINKS 1
+
+/* Define to 1 if your `struct nlist' has an `n_un' member. Obsolete, depend
+ on `HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of package */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU make"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU make 3.82"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "make"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "3.82"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ':'
+
+/* Define to 1 if the C compiler supports function prototypes. */
+#define PROTOTYPES 1
+
+/* Define as the return type of signal handlers (`int' or `void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "get"
+
+/* Define to 1 if the SCCS 'get' command understands the '-G<file>' option. */
+#define SCCS_GET_MINUS_G 1
+
+/* Define to 1 if the `setvbuf' function takes the buffering type as its
+ second argument and the buffer pointer as the third, as on System V before
+ release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at runtime.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the `S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+#define ST_MTIM_NSEC st_mtim.tv_nsec
+#define ST_ATIM_NSEC st_atim.tv_nsec
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#define TIME_WITH_SYS_TIME 1
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Version number of package */
+#define VERSION "3.82"
+
+/* Use platform specific coding */
+/* #undef WINDOWS32 */
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+#define _FILE_OFFSET_BITS 64
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define like PROTOTYPES; this can be used by system headers. */
+#define __PROTOTYPES 1
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef gid_t */
+
+/* Define to `int' if <sys/types.h> does not define. */
+/* #undef pid_t */
+
+/* Define to `int' if <sys/types.h> doesn't define. */
+/* #undef uid_t */
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+/* #undef uintmax_t */
+
+/* Define as `fork' if `vfork' does not work. */
+/* #undef vfork */
+
+#include "inlined_memchr.h"
+
diff --git a/src/kmk/config.h.win b/src/kmk/config.h.win
new file mode 100644
index 0000000..eccf0b2
--- /dev/null
+++ b/src/kmk/config.h.win
@@ -0,0 +1,575 @@
+/* config.h.W32 -- hand-massaged config.h file for Windows builds -*-C-*-
+
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef ___config_h_win
+#define ___config_h_win
+
+/* Suppress some Visual C++ warnings.
+ Maybe after the code cleanup for ISO C we can remove some/all of these. */
+#if _MSC_VER > 1000
+# pragma warning(disable:4100) /* unreferenced formal parameter */
+# pragma warning(disable:4102) /* unreferenced label */
+# pragma warning(disable:4127) /* conditional expression is constant */
+# pragma warning(disable:4131) /* uses old-style declarator */
+# pragma warning(disable:4702) /* unreachable code */
+# ifndef _CRT_SECURE_NO_WARNINGS
+# define _CRT_SECURE_NO_WARNINGS /* function or variable may be unsafe */
+# endif
+# ifndef _CRT_NONSTDC_NO_WARNINGS
+# define _CRT_NONSTDC_NO_WARNINGS /* functions w/o a leading underscore */
+# endif
+#endif
+
+/* Define to 1 if the 'closedir' function returns void instead of 'int'. */
+/* #undef CLOSEDIR_VOID */
+
+/* Define to one of '_getb67', 'GETB67', 'getb67' for Cray-2 and Cray-YMP
+ systems. This function is required for 'alloca.c' support on those systems.
+ */
+/* #undef CRAY_STACKSEG_END */
+
+/* Define to 1 if using 'alloca.c'. */
+/* #undef C_ALLOCA */
+
+/* Define to 1 if using 'getloadavg.c'. */
+#define C_GETLOADAVG 1
+
+/* Define to 1 for DGUX with <sys/dg_sys_info.h>. */
+/* #undef DGUX */
+
+/* Define to 1 if translation of program messages to the user's native
+ language is requested. */
+/* #undef ENABLE_NLS */
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if the 'getloadavg' function needs to be run setuid or setgid.
+ */
+/* #undef GETLOADAVG_PRIVILEGED */
+
+/* Define to 1 if you have 'alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have <alloca.h> and it should be used (not on Ultrix).
+ */
+/* #undef HAVE_ALLOCA_H */
+
+/* Define to 1 if you have the 'atexit' function. */
+#define HAVE_ATEXIT 1
+
+/* Define if your compiler conforms to the ANSI C standard. */
+#define HAVE_ANSI_COMPILER 1
+
+/* Use case insensitive file names */
+/* #undef HAVE_CASE_INSENSITIVE_FS */
+
+/* Define to 1 if you have the clock_gettime function. */
+/* #undef HAVE_CLOCK_GETTIME */
+
+/* Embed GNU Guile support. Windows build sets this on the
+ compilation command line. */
+/* #undef HAVE_GUILE */
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+ */
+/* #undef HAVE_DCGETTEXT */
+
+/* Define to 1 if you have the declaration of 'bsd_signal', and to 0 if you
+ don't. */
+#define HAVE_DECL_BSD_SIGNAL 0
+
+/* Define to 1 if you have the declaration of 'sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL_SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of '_sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL__SYS_SIGLIST 0
+
+/* Define to 1 if you have the declaration of '__sys_siglist', and to 0 if you
+ don't. */
+#define HAVE_DECL___SYS_SIGLIST 0
+
+/* Define to 1 if you have the <dirent.h> header file, and it defines 'DIR'.
+ */
+#define HAVE_DIRENT_H 1
+
+/* Define to 1 if you have the <direct.h> header file, and it defines getcwd()
+ and chdir().
+ */
+#if (defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(__INTERIX)
+# define HAVE_DIRECT_H 1
+#endif
+
+/* Use platform specific coding */
+#define HAVE_DOS_PATHS 1
+
+/* Define to 1 if you have the 'dup2' function. */
+#define HAVE_DUP2 1
+
+/* Define to 1 if you have the <fcntl.h> header file. */
+#define HAVE_FCNTL_H 1
+
+/* Define to 1 if you have the 'fdopen' function. */
+#ifdef __MINGW32__
+#define HAVE_FDOPEN 1
+#endif
+
+/* Define to 1 if you have the 'fileno' function. */
+#define HAVE_FILENO 1
+
+/* Define to 1 if you have the 'getcwd' function. */
+#define HAVE_GETCWD 1
+
+/* Define to 1 if you have the 'getgroups' function. */
+/* #undef HAVE_GETGROUPS */
+
+/* Define to 1 if you have the 'gethostbyname' function. */
+/* #undef HAVE_GETHOSTBYNAME */
+
+/* Define to 1 if you have the 'gethostname' function. */
+/* #undef HAVE_GETHOSTNAME */
+
+/* Define to 1 if you have the 'getloadavg' function. */
+/* #undef HAVE_GETLOADAVG */
+
+/* Define to 1 if you have the 'getrlimit' function. */
+/* #undef HAVE_GETRLIMIT */
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+/* #undef HAVE_GETTEXT */
+
+/* Define to 1 if you have a standard gettimeofday function */
+#ifdef __MINGW32__
+#define HAVE_GETTIMEOFDAY 1
+#endif
+
+/* Define if you have the iconv() function. */
+/* #undef HAVE_ICONV */
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_INTTYPES_H 1
+#endif
+
+/* Define to 1 if you have the 'dgc' library (-ldgc). */
+/* #undef HAVE_LIBDGC */
+
+/* Define to 1 if you have the 'kstat' library (-lkstat). */
+/* #undef HAVE_LIBKSTAT */
+
+/* Define to 1 if you have the <limits.h> header file. */
+#define HAVE_LIMITS_H 1
+
+/* Define to 1 if you have the <locale.h> header file. */
+#define HAVE_LOCALE_H 1 /* bird: differs */
+
+/* Define to 1 if you have the 'lstat' function. */
+/* #undef HAVE_LSTAT */
+
+/* Define to 1 if you have the <mach/mach.h> header file. */
+/* #undef HAVE_MACH_MACH_H */
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the 'mkstemp' function. */
+/* #undef HAVE_MKSTEMP */
+
+/* Define to 1 if you have the 'mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the <ndir.h> header file, and it defines 'DIR'. */
+/* #undef HAVE_NDIR_H */
+
+/* Define to 1 if you have the <nlist.h> header file. */
+/* #undef HAVE_NLIST_H */
+
+/* Define to 1 if you have the 'pipe' function. */
+/* #undef HAVE_PIPE */
+
+/* Define to 1 if you have the 'pstat_getdynamic' function. */
+/* #undef HAVE_PSTAT_GETDYNAMIC */
+
+/* Define to 1 if you have the 'readlink' function. */
+/* #undef HAVE_READLINK */
+
+/* Define to 1 if you have the 'realpath' function. */
+/* #undef HAVE_REALPATH */
+
+/* Define to 1 if <signal.h> defines the SA_RESTART constant. */
+/* #undef HAVE_SA_RESTART */
+
+/* Define to 1 if you have the 'setegid' function. */
+/* #undef HAVE_SETEGID */
+
+/* Define to 1 if you have the 'seteuid' function. */
+/* #undef HAVE_SETEUID */
+
+/* Define to 1 if you have the 'setlinebuf' function. */
+/* #undef HAVE_SETLINEBUF */
+
+/* Define to 1 if you have the 'setlocale' function. */
+/*#define HAVE_SETLOCALE 1*/
+
+/* Define to 1 if you have the 'setregid' function. */
+/* #undef HAVE_SETREGID */
+
+/* Define to 1 if you have the 'setreuid' function. */
+/* #undef HAVE_SETREUID */
+
+/* Define to 1 if you have the 'setrlimit' function. */
+/* #undef HAVE_SETRLIMIT */
+
+/* Define to 1 if you have the 'setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+/* Define to 1 if you have the 'sigaction' function. */
+/* #undef HAVE_SIGACTION */
+
+/* Define to 1 if you have the 'sigsetmask' function. */
+/* #undef HAVE_SIGSETMASK */
+
+/* Define to 1 if you have the 'socket' function. */
+/* #undef HAVE_SOCKET */
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#if defined(__MINGW32__) || _MSC_VER >= 1600 /* bird: added latter */
+# define HAVE_STDINT_H 1
+#endif
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#define HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the 'strcasecmp' function. */
+#ifdef __MINGW32__
+#define HAVE_STRCASECMP 1
+#endif
+
+/* Define to 1 if you have the 'strcmpi' function. */
+#define HAVE_STRCMPI 1
+
+/* Define to 1 if you have the 'strcoll' function and it is properly defined.
+ */
+#define HAVE_STRCOLL 1
+
+/* Define to 1 if you have the 'strdup' function. */
+#define HAVE_STRDUP 1
+
+/* Define to 1 if you have the 'strerror' function. */
+#define HAVE_STRERROR 1
+
+/* Define to 1 if you have the 'stricmp' function. */
+#define HAVE_STRICMP 1
+
+/* Define to 1 if you have the <strings.h> header file. */
+/* #define HAVE_STRINGS_H 1 */
+
+/* Define to 1 if you have the <string.h> header file. */
+#define HAVE_STRING_H 1
+
+/* Define to 1 if you have the 'strncasecmp' function. */
+#ifdef __MINGW32__
+#define HAVE_STRNCASECMP 1
+#endif
+
+/* Define to 1 if you have the 'strncmpi' function. */
+/* #undef HAVE_STRNCMPI */
+
+/* Define to 1 if you have the 'strndup' function. */
+/* #undef HAVE_STRNDUP */
+
+/* Define to 1 if you have the 'strnicmp' function. */
+/*#ifdef __MINGW32__ - bird */
+#define HAVE_STRNICMP 1
+/* #endif - bird */
+
+/* Define to 1 if you have the 'strsignal' function. */
+/* #undef HAVE_STRSIGNAL */
+
+/* Define to 1 if you have the `isatty' function. */
+#define HAVE_ISATTY 1
+
+/* Define to 1 if you have the `ttyname' function. */
+#define HAVE_TTYNAME 1
+char *ttyname (int);
+
+/* Define to 1 if 'n_un.n_name' is a member of 'struct nlist'. */
+/* #undef HAVE_STRUCT_NLIST_N_UN_N_NAME */
+
+/* Define to 1 if you have the <sys/dir.h> header file, and it defines 'DIR'.
+ */
+/* #undef HAVE_SYS_DIR_H */
+
+/* Define to 1 if you have the <sys/ndir.h> header file, and it defines 'DIR'.
+ */
+/* #undef HAVE_SYS_NDIR_H */
+
+/* Define to 1 if you have the <sys/param.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_SYS_PARAM_H 1
+#endif
+
+/* Define to 1 if you have the <sys/resource.h> header file. */
+/* #undef HAVE_SYS_RESOURCE_H */
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#define HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the <sys/timeb.h> header file. */
+#define HAVE_SYS_TIMEB_H 1
+
+/* Define to 1 if you have the <sys/time.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_SYS_TIME_H 1
+#endif
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#define HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the <sys/wait.h> header file. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to 1 if you have the \'union wait' type in <sys/wait.h>. */
+/* #undef HAVE_UNION_WAIT */
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#ifdef __MINGW32__
+#define HAVE_UNISTD_H 1
+#endif
+
+/* Define to 1 if you have the 'wait3' function. */
+/* #undef HAVE_WAIT3 */
+
+/* Define to 1 if you have the 'waitpid' function. */
+/* #undef HAVE_WAITPID */
+
+/* Build host information. (not used by kmk) */
+#define MAKE_HOST "Windows32"
+
+/* Define to 1 to enable job server support in GNU make. */
+#define MAKE_JOBSERVER 1
+
+/* Define to 1 to enable 'load' support in GNU make. */
+#define MAKE_LOAD 1
+
+/* Define to 1 to enable symbolic link timestamp checking. */
+/* #undef MAKE_SYMLINKS */
+
+/* Define to 1 if your 'struct nlist' has an 'n_un' member. Obsolete, depend
+ on 'HAVE_STRUCT_NLIST_N_UN_N_NAME */
+/* #undef NLIST_NAME_UNION */
+
+/* Define to 1 if struct nlist.n_name is a pointer rather than an array. */
+/* #undef NLIST_STRUCT */
+
+/* Define to 1 if your C compiler doesn't accept -c and -o together. */
+/* #undef NO_MINUS_C_MINUS_O */
+
+/* Name of this package (needed by automake) */
+#define PACKAGE "make"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-make@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "kmk"
+
+/* Define to the home page for this package. */
+#define PACKAGE_URL "http://kbuild.org/"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "4.2.1"
+
+/* Define to the character that separates directories in PATH. */
+#define PATH_SEPARATOR_CHAR ';'
+
+/* Define as the return type of signal handlers ('int' or 'void'). */
+#define RETSIGTYPE void
+
+/* Define to the name of the SCCS 'get' command. */
+#define SCCS_GET "echo no sccs get"
+
+/* Define this if the SCCS 'get' command understands the '-G<file>' option. */
+/* #undef SCCS_GET_MINUS_G */
+
+/* Define to 1 if the 'setvbuf' function takes the buffering type as its
+ second argument and the buffer pointer as the third, as on System V before
+ release 3. */
+/* #undef SETVBUF_REVERSED */
+
+/* If using the C implementation of alloca, define if you know the
+ direction of stack growth for your system; otherwise it will be
+ automatically deduced at run time.
+ STACK_DIRECTION > 0 => grows toward higher addresses
+ STACK_DIRECTION < 0 => grows toward lower addresses
+ STACK_DIRECTION = 0 => direction of growth unknown */
+/* #undef STACK_DIRECTION */
+
+/* Define to 1 if the 'S_IS*' macros in <sys/stat.h> do not work properly. */
+/* #undef STAT_MACROS_BROKEN */
+
+/* Define to 1 if you have the ANSI C header files. */
+#define STDC_HEADERS 1
+
+/* Define if struct stat contains a nanoseconds field */
+#define ST_MTIM_NSEC st_mtim.tv_nsec /* bird */
+#define ST_ATIM_NSEC st_atim.tv_nsec /* bird */
+
+/* Define to 1 on System V Release 4. */
+/* #undef SVR4 */
+
+/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */
+#ifdef __MINGW32__
+#define TIME_WITH_SYS_TIME 1
+#endif
+
+/* Define to 1 for Encore UMAX. */
+/* #undef UMAX */
+
+/* Define to 1 for Encore UMAX 4.3 that has <inq_status/cpustats.h> instead of
+ <sys/cpustats.h>. */
+/* #undef UMAX4_3 */
+
+/* Version number of package */
+#define VERSION "4.2.1"
+
+/* Define if using the dmalloc debugging malloc package */
+/* #undef WITH_DMALLOC */
+
+/* Define to 1 if on AIX 3.
+ System headers sometimes define this.
+ We just want to avoid a redefinition error message. */
+#ifndef _ALL_SOURCE
+/* # undef _ALL_SOURCE */
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef _FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef _LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef _MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef _POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for 'stat' and other things to work. */
+/* #undef _POSIX_SOURCE */
+
+/* Define to empty if 'const' does not conform to ANSI C. */
+/* #undef const */
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define gid_t int
+
+/* Define to 'int' if <sys/types.h> does not define. */
+/* Note (bird)! sub_proc.c needs this to be pointer sized. */
+#define pid_t intptr_t
+
+/* Define to 'int' if <sys/types.h> doesn't define. */
+#define uid_t int
+
+/* Define uintmax_t if not defined in <stdint.h> or <inttypes.h>. */
+#if !HAVE_STDINT_H && !HAVE_INTTYPES_H
+# if 0
+# define uintmax_t unsigned long
+# else
+# define uintmax_t unsigned __int64
+# endif
+#endif
+
+/* Define if you have <sys/wait.h> that is POSIX.1 compatible. */
+/* #undef HAVE_SYS_WAIT_H */
+
+/* Define to the installation directory for locales. */
+#define LOCALEDIR ""
+
+/*
+ * Refer to README.W32 for info on the following settings
+ */
+
+
+/*
+ * If you have a shell that does not grok 'sh -c quoted-command-line'
+ * correctly, you need this setting. Please see below for specific
+ * shell support.
+ */
+/*#define BATCH_MODE_ONLY_SHELL 1 */
+
+/*
+ * Define if you have the Cygnus "Cygwin" GNU Windows32 tool set.
+ * Do NOT define BATCH_MODE_ONLY_SHELL if you define HAVE_CYGWIN_SHELL
+ */
+/*#define HAVE_CYGWIN_SHELL 1 */
+
+/*
+ * Define if you have the MKS tool set or shell. Do NOT define
+ * BATCH_MODE_ONLY_SHELL if you define HAVE_MKS_SHELL
+ */
+/*#define HAVE_MKS_SHELL 1 */
+
+/*
+ * Enforce the mutual exclusivity restriction.
+ */
+#ifdef HAVE_MKS_SHELL
+#undef BATCH_MODE_ONLY_SHELL
+#endif
+
+#ifdef HAVE_CYGWIN_SHELL
+#undef BATCH_MODE_ONLY_SHELL
+#endif
+
+/* bird stat hacks. */
+#include <io.h>
+#include <direct.h>
+#include "nt/ntstat.h"
+
+/* bird dirent hack. */
+#define _DIRENT_H /* see w32/dirent.h */
+#include "nt/ntdir.h"
+#define _DIRENT_HAVE_D_NAMLEN 1
+#define _DIRENT_HAVE_D_TYPE 1
+
+/* bird: Not sure if this is necessary any more... */
+#define BATCH_MODE_ONLY_SHELL
+
+#include "inlined_memchr.h"
+
+/* bird: Include mscfakes.h to make sure we have all it's tricks applied. */
+#ifndef ___mscfakes_h
+# include "kmkbuiltin/mscfakes.h"
+#endif
+
+/*
+ * Map posixfcn.c stuff to non-conflicting names.
+ */
+#include <stdio.h>
+#include <io.h>
+
+#define tmpfile posixfcn_tmpfile
+FILE *posixfcn_tmpfile(void);
+
+#define isatty posixfcn_isatty
+int posixfcn_isatty(int fd);
+
+#endif /* bird */
+
diff --git a/src/kmk/config/.gitignore b/src/kmk/config/.gitignore
new file mode 100644
index 0000000..d11a488
--- /dev/null
+++ b/src/kmk/config/.gitignore
@@ -0,0 +1,12 @@
+ar-lib
+compile
+config.guess
+config.rpath
+config.sub
+depcomp
+install-sh
+mdate-sh
+missing
+texinfo.tex
+*.m4
+!dospaths.m4
diff --git a/src/kmk/config/ChangeLog.1 b/src/kmk/config/ChangeLog.1
new file mode 100644
index 0000000..8549501
--- /dev/null
+++ b/src/kmk/config/ChangeLog.1
@@ -0,0 +1,49 @@
+2012-01-15 Paul Smith <psmith@gnu.org>
+
+ * dospaths.m4: Use AC_LANG_PROGRAM to encapsulate the test code.
+ Fixes Savannah bug #35256. Patch from Sebastian Pipping.
+
+2006-03-09 Paul Smith <psmith@gnu.org>
+
+ * dospaths.m4: Add MSYS to the list of targets allowing DOS-style
+ pathnames. Reported by David Ergo <david.ergo@alterface.com>.
+
+2005-07-01 Paul D. Smith <psmith@gnu.org>
+
+ * Makefile.am (EXTRA_DIST): Added more M4 files to EXTRA_DIST, so
+ users can re-run aclocal.
+
+2003-04-30 Paul D. Smith <psmith@gnu.org>
+
+ * dospaths.m4: New macro to test for DOS-style pathnames, based on
+ coreutils 5.0 "dos.m4" by Jim Meyering.
+
+2002-04-21 gettextize <bug-gnu-gettext@gnu.org>
+
+ * codeset.m4: New file, from gettext-0.11.1.
+ * gettext.m4: New file, from gettext-0.11.1.
+ * glibc21.m4: New file, from gettext-0.11.1.
+ * iconv.m4: New file, from gettext-0.11.1.
+ * isc-posix.m4: New file, from gettext-0.11.1.
+ * lcmessage.m4: New file, from gettext-0.11.1.
+ * lib-ld.m4: New file, from gettext-0.11.1.
+ * lib-link.m4: New file, from gettext-0.11.1.
+ * lib-prefix.m4: New file, from gettext-0.11.1.
+ * progtest.m4: New file, from gettext-0.11.1.
+ * Makefile.am: New file.
+
+
+Copyright (C) 2002-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/config/Makefile.am b/src/kmk/config/Makefile.am
new file mode 100644
index 0000000..7bce036
--- /dev/null
+++ b/src/kmk/config/Makefile.am
@@ -0,0 +1,18 @@
+# -*-Makefile-*-, or close enough
+# Copyright (C) 2002-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Autoconf / automake know how to handle this directory.
diff --git a/src/kmk/config/dospaths.m4 b/src/kmk/config/dospaths.m4
new file mode 100644
index 0000000..9aa9814
--- /dev/null
+++ b/src/kmk/config/dospaths.m4
@@ -0,0 +1,33 @@
+# Test if the system uses DOS-style pathnames (drive specs and backslashes)
+# By Paul Smith <psmith@gnu.org>. Based on dos.m4 by Jim Meyering.
+#
+# Copyright (C) 1993-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+AC_DEFUN([pds_AC_DOS_PATHS], [
+ AC_CACHE_CHECK([whether system uses MSDOS-style paths], [ac_cv_dos_paths], [
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#if !defined _WIN32 && !defined __WIN32__ && !defined __MSDOS__ && !defined __EMX__ && !defined __MSYS__ && !defined __CYGWIN__
+neither MSDOS nor Windows nor OS2
+#endif
+]])],
+ [ac_cv_dos_paths=yes],
+ [ac_cv_dos_paths=no])])
+
+ AS_IF([test x"$ac_cv_dos_paths" = xyes],
+ [ AC_DEFINE_UNQUOTED([HAVE_DOS_PATHS], 1,
+ [Define if the system uses DOS-style pathnames.])])
+])
diff --git a/src/kmk/configh.dos.template b/src/kmk/configh.dos.template
new file mode 100644
index 0000000..c43e644
--- /dev/null
+++ b/src/kmk/configh.dos.template
@@ -0,0 +1,113 @@
+/* configh.dos -- hand-massaged config.h file for MS-DOS builds -*-C-*-
+
+Copyright (C) 1994-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Include this header to make __DJGPP_MINOR__ available because DJGPP ports
+ of GCC 4.3.0 and later no longer do it automatically. */
+#include <sys/version.h>
+
+/* Many things are defined already by a system header. */
+#include <sys/config.h>
+
+#if __DJGPP__ > 2 || __DJGPP_MINOR__ > 1
+
+/* Define to 1 if 'sys_siglist' is declared by <signal.h> or <unistd.h>. */
+# define SYS_SIGLIST_DECLARED 1
+
+/* Define to 1 if the C library defines the variable '_sys_siglist'. */
+# define HAVE_DECL_SYS_SIGLIST 1
+
+#else
+
+/* Define NSIG. */
+# define NSIG SIGMAX
+
+#endif
+
+/* Use high resolution file timestamps if nonzero. */
+#define FILE_TIMESTAMP_HI_RES 0
+
+/* Define to 1 if you have 'alloca', as a function or macro. */
+#define HAVE_ALLOCA 1
+
+/* Define to 1 if you have the fdopen function. */
+#define HAVE_FDOPEN 1
+
+/* Define to 1 if you have the 'getgroups' function. */
+#define HAVE_GETGROUPS 1
+
+/* Define to 1 if you have the <memory.h> header file. */
+#define HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the mkstemp function. */
+#define HAVE_MKSTEMP 1
+
+/* Define to 1 if you have the 'mktemp' function. */
+#define HAVE_MKTEMP 1
+
+/* Define to 1 if you have the 'setlinebuf' function. */
+#define HAVE_SETLINEBUF 1
+
+/* Define to 1 if you have the 'setvbuf' function. */
+#define HAVE_SETVBUF 1
+
+#define SCCS_GET "get"
+
+/* Define to 'unsigned long' or 'unsigned long long'
+ if <inttypes.h> doesn't define. */
+#define uintmax_t unsigned long long
+
+/* Define the type of the first arg to select(). */
+#define fd_set_size_t int
+
+/* Define to 1 if you have the select function. */
+#define HAVE_SELECT 1
+
+/* Define to 1 if you have the stricmp function. */
+#define HAVE_STRICMP 1
+
+/* Define to 1 if you have the 'strncasecmp' function. */
+#define HAVE_STRNCASECMP 1
+
+/* Name of the package */
+#define PACKAGE "%PACKAGE%"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define PACKAGE_BUGREPORT "bug-%PACKAGE%@gnu.org"
+
+/* Define to the full name of this package. */
+#define PACKAGE_NAME "GNU %PACKAGE%"
+
+/* Define to the full name and version of this package. */
+#define PACKAGE_STRING "GNU %PACKAGE% %VERSION%"
+
+/* Define to the one symbol short name of this package. */
+#define PACKAGE_TARNAME "%PACKAGE%"
+
+/* Define to the version of this package. */
+#define PACKAGE_VERSION "%VERSION%"
+
+/* Output sync sypport */
+#define NO_OUTPUT_SYNC
+
+/* Version number of package */
+#define VERSION "%VERSION%"
+
+/* Build host information. */
+#define MAKE_HOST "i386-pc-msdosdjgpp"
+
+/* Grok DOS paths (drive specs and backslash path element separators) */
+#define HAVE_DOS_PATHS
diff --git a/src/kmk/configure.ac b/src/kmk/configure.ac
new file mode 100644
index 0000000..d950182
--- /dev/null
+++ b/src/kmk/configure.ac
@@ -0,0 +1,534 @@
+# Process this file with autoconf to produce a configure script.
+#
+# Copyright (C) 1993-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+AC_INIT([GNU make],[4.2.1],[bug-make@gnu.org])
+
+AC_PREREQ([2.69])
+
+# Autoconf setup
+AC_CONFIG_AUX_DIR([config])
+AC_CONFIG_SRCDIR([vpath.c])
+AC_CONFIG_HEADERS([config.h])
+
+# Automake setup
+# We have to enable "foreign" because ChangeLog is auto-generated
+# We cannot enable -Werror because gettext 0.18.1 has invalid content
+# When we update gettext to 0.18.3 or better we can add it again.
+# bird: Added subdir-objects
+AM_INIT_AUTOMAKE([1.15 foreign -Werror -Wall subdir-objects])
+
+# Checks for programs.
+AC_USE_SYSTEM_EXTENSIONS
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_RANLIB
+AC_PROG_CPP
+AC_CHECK_PROG([AR], [ar], [ar], [ar])
+# Perl is needed for the test suite (only)
+AC_CHECK_PROG([PERL], [perl], [perl], [perl])
+
+# Needed for w32/Makefile.am
+AM_PROG_AR
+
+# Specialized system macros
+AC_CANONICAL_HOST
+AC_AIX
+AC_ISC_POSIX
+AC_MINIX
+
+# Enable gettext, in "external" mode.
+# bird: causes trouble when it doesn't find 'po' in SUBDIRS, so skip it.
+#AM_GNU_GETTEXT_VERSION([0.19.4])
+#AM_GNU_GETTEXT([external])
+
+# This test must come as early as possible after the compiler configuration
+# tests, because the choice of the file model can (in principle) affect
+# whether functions and headers are available, whether they work, etc.
+AC_SYS_LARGEFILE
+
+# Checks for libraries.
+AC_SEARCH_LIBS([getpwnam], [sun])
+
+# Checks for header files.
+AC_HEADER_STDC
+AC_HEADER_DIRENT
+AC_HEADER_STAT
+AC_HEADER_TIME
+AC_CHECK_HEADERS([stdlib.h locale.h unistd.h limits.h fcntl.h string.h \
+ memory.h sys/param.h sys/resource.h sys/time.h sys/timeb.h \
+ sys/select.h])
+
+AM_PROG_CC_C_O
+AC_C_CONST
+AC_TYPE_SIGNAL
+AC_TYPE_UID_T
+AC_TYPE_PID_T
+AC_TYPE_OFF_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UINTMAX_T
+
+# Find out whether our struct stat returns nanosecond resolution timestamps.
+
+AC_STRUCT_ST_MTIM_NSEC
+AC_STRUCT_ST_ATIM_NSEC
+AC_CACHE_CHECK([whether to use high resolution file timestamps],
+ [make_cv_file_timestamp_hi_res],
+[ make_cv_file_timestamp_hi_res=no
+ AS_IF([test "$ac_cv_struct_st_mtim_nsec" != no],
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif]],
+ [[char a[0x7fffffff < (uintmax_t)-1 >> 30 ? 1 : -1];]])],
+ [make_cv_file_timestamp_hi_res=yes])
+ ])])
+AS_IF([test "$make_cv_file_timestamp_hi_res" = yes], [val=1], [val=0])
+AC_DEFINE_UNQUOTED([FILE_TIMESTAMP_HI_RES], [$val],
+ [Use high resolution file timestamps if nonzero.])
+
+AS_IF([test "$make_cv_file_timestamp_hi_res" = yes],
+[ # Solaris 2.5.1 needs -lposix4 to get the clock_gettime function.
+ # Solaris 7 prefers the library name -lrt to the obsolescent name -lposix4.
+ AC_SEARCH_LIBS([clock_gettime], [rt posix4])
+ AS_IF([test "$ac_cv_search_clock_gettime" != no],
+ [ AC_DEFINE([HAVE_CLOCK_GETTIME], [1],
+ [Define to 1 if you have the clock_gettime function.])
+ ])
+])
+
+# Check for DOS-style pathnames.
+pds_AC_DOS_PATHS
+
+# See if we have a standard version of gettimeofday(). Since actual
+# implementations can differ, just make sure we have the most common
+# one.
+AC_CACHE_CHECK([for standard gettimeofday], [ac_cv_func_gettimeofday],
+ [ac_cv_func_gettimeofday=no
+ AC_RUN_IFELSE([AC_LANG_SOURCE([[#include <sys/time.h>
+ #include <stdlib.h> /* kmk/darwin: for exit */
+ int main ()
+ {
+ struct timeval t; t.tv_sec = -1; t.tv_usec = -1;
+ exit (gettimeofday (&t, 0) != 0
+ || t.tv_sec < 0 || t.tv_usec < 0);
+ }]])],
+ [ac_cv_func_gettimeofday=yes],
+ [ac_cv_func_gettimeofday=no],
+ [ac_cv_func_gettimeofday="no (cross-compiling)"])])
+AS_IF([test "$ac_cv_func_gettimeofday" = yes],
+[ AC_DEFINE([HAVE_GETTIMEOFDAY], [1],
+ [Define to 1 if you have a standard gettimeofday function])
+])
+
+AC_CHECK_FUNCS([strdup strndup mkstemp mktemp fdopen fileno \
+ dup dup2 getcwd realpath sigsetmask sigaction \
+ getgroups seteuid setegid setlinebuf setreuid setregid \
+ getrlimit setrlimit setvbuf pipe strerror strsignal \
+ lstat readlink atexit isatty ttyname pselect])
+
+# We need to check declarations, not just existence, because on Tru64 this
+# function is not declared without special flags, which themselves cause
+# other problems. We'll just use our own.
+AC_CHECK_DECLS([bsd_signal], [], [], [[#define _GNU_SOURCE 1
+#include <signal.h>]])
+
+AC_FUNC_FORK
+
+AC_FUNC_SETVBUF_REVERSED
+
+# Rumor has it that strcasecmp lives in -lresolv on some odd systems.
+# It doesn't hurt much to use our own if we can't find it so I don't
+# make the effort here.
+AC_CHECK_FUNCS([strcasecmp strncasecmp strcmpi strncmpi stricmp strnicmp])
+
+# strcoll() is used by the GNU glob library
+AC_FUNC_STRCOLL
+
+AC_FUNC_ALLOCA
+AC_FUNC_CLOSEDIR_VOID
+
+# bird: This guile detection does not work on rhel4. Disabled it all since we doesn't care about guile.
+## See if the user wants to add (or not) GNU Guile support
+#PKG_PROG_PKG_CONFIG
+#AC_ARG_WITH([guile], [AS_HELP_STRING([--with-guile],
+# [Support GNU Guile for embedded scripting])])
+#
+## For some strange reason, at least on Ubuntu, each version of Guile
+## comes with it's own PC file so we have to specify them as individual
+## packages. Ugh.
+#AS_IF([test "x$with_guile" != xno],
+#[ PKG_CHECK_MODULES([GUILE], [guile-2.0], [have_guile=yes],
+# [PKG_CHECK_MODULES([GUILE], [guile-1.8], [have_guile=yes],
+# [have_guile=no])])
+#])
+#
+#AS_IF([test "$have_guile" = yes],
+# [AC_DEFINE([HAVE_GUILE], [1], [Embed GNU Guile support])])
+#
+#AM_CONDITIONAL([HAVE_GUILE], [test "$have_guile" = yes])
+
+AC_FUNC_GETLOADAVG
+
+# AC_FUNC_GETLOADAVG is documented to set the NLIST_STRUCT value, but it
+# doesn't. So, we will.
+
+AS_IF([test "$ac_cv_header_nlist_h" = yes],
+[ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <nlist.h>]],
+ [[struct nlist nl;
+ nl.n_name = "string";
+ return 0;]])],
+ [make_cv_nlist_struct=yes],
+ [make_cv_nlist_struct=no])
+ AS_IF([test "$make_cv_nlist_struct" = yes],
+ [ AC_DEFINE([NLIST_STRUCT], [1],
+ [Define to 1 if struct nlist.n_name is a pointer rather than an array.])
+ ])
+])
+
+AC_CHECK_DECLS([sys_siglist, _sys_siglist, __sys_siglist], , ,
+ [AC_INCLUDES_DEFAULT
+#include <signal.h>
+/* NetBSD declares sys_siglist in unistd.h. */
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+])
+
+
+# Check out the wait reality.
+AC_CHECK_HEADERS([sys/wait.h],[],[],[[#include <sys/types.h>]])
+AC_CHECK_FUNCS([waitpid wait3])
+AC_CACHE_CHECK([for union wait], [make_cv_union_wait],
+[ AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
+#include <sys/wait.h>]],
+ [[union wait status; int pid; pid = wait (&status);
+#ifdef WEXITSTATUS
+/* Some POSIXoid systems have both the new-style macros and the old
+ union wait type, and they do not work together. If union wait
+ conflicts with WEXITSTATUS et al, we don't want to use it at all. */
+ if (WEXITSTATUS (status) != 0) pid = -1;
+#ifdef WTERMSIG
+ /* If we have WEXITSTATUS and WTERMSIG, just use them on ints. */
+ -- blow chunks here --
+#endif
+#endif
+#ifdef HAVE_WAITPID
+ /* Make sure union wait works with waitpid. */
+ pid = waitpid (-1, &status, 0);
+#endif
+ ]])],
+ [make_cv_union_wait=yes],
+ [make_cv_union_wait=no])
+])
+AS_IF([test "$make_cv_union_wait" = yes],
+[ AC_DEFINE([HAVE_UNION_WAIT], [1],
+ [Define to 1 if you have the 'union wait' type in <sys/wait.h>.])
+])
+
+
+# If we're building on Windows/DOS/OS/2, add some support for DOS drive specs.
+AS_IF([test "$PATH_SEPARATOR" = ';'],
+[ AC_DEFINE([HAVE_DOS_PATHS], [1],
+ [Define to 1 if your system requires backslashes or drive specs in pathnames.])
+])
+
+# See if the user wants to use pmake's "customs" distributed build capability
+AC_SUBST([REMOTE]) REMOTE=stub
+use_customs=false
+AC_ARG_WITH([customs],
+[AC_HELP_STRING([--with-customs=DIR],
+ [enable remote jobs via Customs--see README.customs])],
+[ AS_CASE([$withval], [n|no], [:],
+ [make_cppflags="$CPPFLAGS"
+ AS_CASE([$withval],
+ [y|ye|yes], [:],
+ [CPPFLAGS="$CPPFLAGS -I$with_customs/include/customs"
+ make_ldflags="$LDFLAGS -L$with_customs/lib"])
+ CF_NETLIBS
+ AC_CHECK_HEADER([customs.h],
+ [use_customs=true
+ REMOTE=cstms
+ LIBS="$LIBS -lcustoms" LDFLAGS="$make_ldflags"],
+ [with_customs=no
+ CPPFLAGS="$make_cppflags" make_badcust=yes])
+ ])
+])
+
+# Tell automake about this, so it can include the right .c files.
+AM_CONDITIONAL([USE_CUSTOMS], [test "$use_customs" = true])
+
+# See if the user asked to handle case insensitive file systems.
+AH_TEMPLATE([HAVE_CASE_INSENSITIVE_FS], [Use case insensitive file names])
+AC_ARG_ENABLE([case-insensitive-file-system],
+ AC_HELP_STRING([--enable-case-insensitive-file-system],
+ [assume file systems are case insensitive]),
+ [AS_IF([test "$enableval" = yes], [AC_DEFINE([HAVE_CASE_INSENSITIVE_FS])])])
+
+# See if we can handle the job server feature, and if the user wants it.
+AC_ARG_ENABLE([job-server],
+ AC_HELP_STRING([--disable-job-server],
+ [disallow recursive make communication during -jN]),
+ [make_cv_job_server="$enableval" user_job_server="$enableval"],
+ [make_cv_job_server="yes"])
+
+AS_IF([test "$ac_cv_func_waitpid" = no && test "$ac_cv_func_wait3" = no],
+ [has_wait_nohang=no],
+ [has_wait_nohang=yes])
+
+AC_CACHE_CHECK([for SA_RESTART], [make_cv_sa_restart], [
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <signal.h>]],
+ [[return SA_RESTART;]])],
+ [make_cv_sa_restart=yes],
+ [make_cv_sa_restart=no])])
+
+AS_IF([test "$make_cv_sa_restart" != no],
+[ AC_DEFINE([HAVE_SA_RESTART], [1],
+ [Define to 1 if <signal.h> defines the SA_RESTART constant.])
+])
+
+# Only allow jobserver on systems that support it
+AS_CASE([/$ac_cv_func_pipe/$ac_cv_func_sigaction/$make_cv_sa_restart/$has_wait_nohang/],
+ [*/no/*], [make_cv_job_server=no])
+
+# Also supported on OS2 and MinGW
+AS_CASE([$host_os], [os2*|mingw*], [make_cv_job_server=yes])
+
+# If we support it and the user didn't disable it, build with jobserver
+AS_CASE([/$make_cv_job_server/$user_job_server/],
+ [*/no/*], [: no jobserver],
+ [AC_DEFINE(MAKE_JOBSERVER, 1,
+ [Define to 1 to enable job server support in GNU make.])
+ ])
+
+# If dl*() functions are supported we can enable the load operation
+AC_CHECK_DECLS([dlopen, dlsym, dlerror], [], [],
+ [[#include <dlfcn.h>]])
+
+AC_ARG_ENABLE([load],
+ AC_HELP_STRING([--disable-load],
+ [disable support for the 'load' operation]),
+ [make_cv_load="$enableval" user_load="$enableval"],
+ [make_cv_load="yes"])
+
+AS_CASE([/$ac_cv_have_decl_dlopen/$ac_cv_have_decl_dlsym/$ac_cv_have_decl_dlerror/],
+ [*/no/*], [make_cv_load=no])
+
+# We might need -ldl
+AS_IF([test "$make_cv_load" = yes], [
+ AC_SEARCH_LIBS([dlopen], [dl], [], [make_cv_load=])
+ ])
+
+AS_CASE([/$make_cv_load/$user_load/],
+ [*/no/*], [make_cv_load=no],
+ [AC_DEFINE(MAKE_LOAD, 1,
+ [Define to 1 to enable 'load' support in GNU make.])
+ ])
+
+# If we want load support, we might need to link with export-dynamic.
+# See if we can figure it out. Unfortunately this is very difficult.
+# For example passing -rdynamic to the SunPRO linker gives a warning
+# but succeeds and creates a shared object, not an executable!
+AS_IF([test "$make_cv_load" = yes], [
+ AC_MSG_CHECKING([If the linker accepts -Wl,--export-dynamic])
+ old_LDFLAGS="$LDFLAGS"
+ LDFLAGS="$LDFLAGS -Wl,--export-dynamic"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([int main(){}])],
+ [AC_MSG_RESULT([yes])
+ AC_SUBST([AM_LDFLAGS], [-Wl,--export-dynamic])],
+ [AC_MSG_RESULT([no])
+ AC_MSG_CHECKING([If the linker accepts -rdynamic])
+ LDFLAGS="$old_LDFLAGS -rdynamic"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([int main(){}])],
+ [AC_MSG_RESULT([yes])
+ AC_SUBST([AM_LDFLAGS], [-rdynamic])],
+ [AC_MSG_RESULT([no])])
+ ])
+ LDFLAGS="$old_LDFLAGS"
+])
+
+# if we have both lstat() and readlink() then we can support symlink
+# timechecks.
+AS_IF([test "$ac_cv_func_lstat" = yes && test "$ac_cv_func_readlink" = yes],
+ [ AC_DEFINE([MAKE_SYMLINKS], [1],
+ [Define to 1 to enable symbolic link timestamp checking.])
+])
+
+# Find the SCCS commands, so we can include them in our default rules.
+
+AC_CACHE_CHECK([for location of SCCS get command], [make_cv_path_sccs_get], [
+ AS_IF([test -f /usr/sccs/get],
+ [make_cv_path_sccs_get=/usr/sccs/get],
+ [make_cv_path_sccs_get=get])
+])
+AC_DEFINE_UNQUOTED([SCCS_GET], ["$make_cv_path_sccs_get"],
+ [Define to the name of the SCCS 'get' command.])
+
+ac_clean_files="$ac_clean_files s.conftest conftoast" # Remove these later.
+AS_IF([(/usr/sccs/admin -n s.conftest || admin -n s.conftest) >/dev/null 2>&1 &&
+ test -f s.conftest],
+[ # We successfully created an SCCS file.
+ AC_CACHE_CHECK([if SCCS get command understands -G], [make_cv_sys_get_minus_G],
+ [AS_IF([$make_cv_path_sccs_get -Gconftoast s.conftest >/dev/null 2>&1 &&
+ test -f conftoast],
+ [make_cv_sys_get_minus_G=yes],
+ [make_cv_sys_get_minus_G=no])
+ ])
+ AS_IF([test "$make_cv_sys_get_minus_G" = yes],
+ [AC_DEFINE([SCCS_GET_MINUS_G], [1],
+ [Define to 1 if the SCCS 'get' command understands the '-G<file>' option.])
+ ])
+])
+rm -f s.conftest conftoast
+
+## bird: always use our glob impl. Avoids trouble with newish glibc.
+# Check the system to see if it provides GNU glob. If not, use our
+# local version.
+#x# AC_CACHE_CHECK([if system libc has GNU glob], [make_cv_sys_gnu_glob],
+#x# [ AC_EGREP_CPP([gnu glob],[
+#x# #include <features.h>
+#x# #include <glob.h>
+#x# #include <fnmatch.h>
+#x#
+#x# #define GLOB_INTERFACE_VERSION 1
+#x# #if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1
+#x# # include <gnu-versions.h>
+#x# # if _GNU_GLOB_INTERFACE_VERSION == GLOB_INTERFACE_VERSION
+#x# gnu glob
+#x# # endif
+#x# #endif],
+#x# [make_cv_sys_gnu_glob=yes],
+#x# [make_cv_sys_gnu_glob=no])])
+#x# AS_IF([test "$make_cv_sys_gnu_glob" = no],
+AS_IF([test yes = yes],
+[ GLOBINC='-I$(srcdir)/glob'
+ GLOBLIB=glob/libglob.a
+ make_cv_sys_gnu_glob=no
+])
+AC_SUBST([GLOBINC])
+AC_SUBST([GLOBLIB])
+
+# Tell automake about this, so it can build the right .c files.
+AM_CONDITIONAL([USE_LOCAL_GLOB], [test "$make_cv_sys_gnu_glob" = no])
+
+# Let the makefile know what our build host is
+
+AC_DEFINE_UNQUOTED([MAKE_HOST],["$host"],[Build host information.])
+MAKE_HOST="$host"
+AC_SUBST([MAKE_HOST])
+
+w32_target_env=no
+AM_CONDITIONAL([WINDOWSENV], [false])
+
+AS_CASE([$host],
+ [*-*-mingw32],
+ [AM_CONDITIONAL([WINDOWSENV], [true])
+ w32_target_env=yes
+ AC_DEFINE([WINDOWS32], [1], [Use platform specific coding])
+ AC_DEFINE([HAVE_DOS_PATHS], [1], [Use platform specific coding])
+ ])
+
+AC_DEFINE_UNQUOTED([PATH_SEPARATOR_CHAR],['$PATH_SEPARATOR'],
+ [Define to the character that separates directories in PATH.])
+
+# Include the Maintainer's Makefile section, if it's here.
+
+MAINT_MAKEFILE=/dev/null
+AS_IF([test -r "$srcdir/maintMakefile"],
+[ MAINT_MAKEFILE="$srcdir/maintMakefile"
+])
+AC_SUBST_FILE([MAINT_MAKEFILE])
+
+# Allow building with dmalloc
+AM_WITH_DMALLOC
+
+# Forcibly disable SET_MAKE. If it's set it breaks things like the test
+# scripts, etc.
+SET_MAKE=
+
+# Sanity check and inform the user of what we found
+
+AS_IF([test "x$make_badcust" = xyes], [
+echo
+echo "WARNING: --with-customs specified but no customs.h could be found;"
+echo " disabling Customs support."
+echo
+])
+
+AS_CASE([$with_customs],
+[""|n|no|y|ye|yes], [:],
+[AS_IF([test -f "$with_customs/lib/libcustoms.a"], [:],
+[ echo
+ echo "WARNING: '$with_customs/lib' does not appear to contain the"
+ echo " Customs library. You must build and install Customs"
+ echo " before compiling GNU make."
+ echo
+])])
+
+AS_IF([test "x$has_wait_nohang" = xno],
+[ echo
+ echo "WARNING: Your system has neither waitpid() nor wait3()."
+ echo " Without one of these, signal handling is unreliable."
+ echo " You should be aware that running GNU make with -j"
+ echo " could result in erratic behavior."
+ echo
+])
+
+AS_IF([test "x$make_cv_job_server" = xno && test "x$user_job_server" = xyes],
+[ echo
+ echo "WARNING: Make job server requires a POSIX-ish system that"
+ echo " supports the pipe(), sigaction(), and either"
+ echo " waitpid() or wait3() functions. Your system doesn't"
+ echo " appear to provide one or more of those."
+ echo " Disabling job server support."
+ echo
+])
+
+AS_IF([test "x$make_cv_load" = xno && test "x$user_load" = xyes],
+[ echo
+ echo "WARNING: 'load' support requires a POSIX-ish system that"
+ echo " supports the dlopen(), dlsym(), and dlerror() functions."
+ echo " Your system doesn't appear to provide one or more of these."
+ echo " Disabling 'load' support."
+ echo
+])
+
+# Specify what files are to be created.
+# bird: Removed po/Makefile.in.
+#AC_CONFIG_FILES([Makefile glob/Makefile po/Makefile.in config/Makefile \
+# doc/Makefile w32/Makefile tests/config-flags.pm])
+AC_CONFIG_FILES([Makefile glob/Makefile config/Makefile \
+ doc/Makefile w32/Makefile tests/config-flags.pm])
+
+# OK, do it!
+
+AC_OUTPUT
+
+# We only generate the build.sh if we have a build.sh.in; we won't have
+# one before we've created a distribution.
+AS_IF([test -f "$srcdir/build.sh.in"],
+[ ./config.status --file build.sh
+ chmod +x build.sh
+])
+
+dnl Local Variables:
+dnl comment-start: "dnl "
+dnl comment-end: ""
+dnl comment-start-skip: "\\bdnl\\b\\s *"
+dnl compile-command: "make configure config.h.in"
+dnl End:
diff --git a/src/kmk/configure.bat b/src/kmk/configure.bat
new file mode 100644
index 0000000..3c41f38
--- /dev/null
+++ b/src/kmk/configure.bat
@@ -0,0 +1,60 @@
+@echo off
+rem Copyright (C) 1994-2016 Free Software Foundation, Inc.
+rem This file is part of GNU Make.
+rem
+rem GNU Make is free software; you can redistribute it and/or modify it under
+rem the terms of the GNU General Public License as published by the Free
+rem Software Foundation; either version 3 of the License, or (at your option)
+rem any later version.
+rem
+rem GNU Make is distributed in the hope that it will be useful, but WITHOUT
+rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+rem more details.
+rem
+rem You should have received a copy of the GNU General Public License along
+rem with this program. If not, see <http://www.gnu.org/licenses/>.
+
+echo Configuring MAKE for DJGPP
+
+rem The SmallEnv trick protects against too small environment block,
+rem in which case the values will be truncated and the whole thing
+rem goes awry. COMMAND.COM will say "Out of environment space", but
+rem many people don't care, so we force them to care by refusing to go.
+
+rem Where is the srcdir?
+set XSRC=.
+if not "%XSRC%"=="." goto SmallEnv
+if "%1%"=="" goto SrcDone
+set XSRC=%1
+if not "%XSRC%"=="%1" goto SmallEnv
+
+:SrcDone
+
+update %XSRC%/configh.dos ./config.h
+
+rem Do they have Make?
+redir -o junk.$$$ -eo make -n -f NUL
+rem REDIR will return 1 if it cannot run Make.
+rem If it can run Make, it will usually return 2,
+rem but 0 is also OK with us.
+if errorlevel 2 goto MakeOk
+if not errorlevel 1 goto MakeOk
+if exist junk.$$$ del junk.$$$
+echo No Make program found--use DOSBUILD.BAT to build Make.
+goto End
+
+rem They do have Make. Generate the Makefile.
+
+:MakeOk
+del junk.$$$
+update %XSRC%/Makefile.DOS ./Makefile
+echo Done.
+if not "%XSRC%"=="." echo Invoke Make thus: "make srcdir=%XSRC%"
+goto End
+
+:SmallEnv
+echo Your environment is too small. Please enlarge it and run me again.
+
+:End
+set XRSC=
diff --git a/src/kmk/debug.h b/src/kmk/debug.h
new file mode 100644
index 0000000..e423d90
--- /dev/null
+++ b/src/kmk/debug.h
@@ -0,0 +1,66 @@
+/* Debugging macros and interface.
+Copyright (C) 1999-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#define DB_NONE (0x000)
+#define DB_BASIC (0x001)
+#define DB_VERBOSE (0x002)
+#define DB_JOBS (0x004)
+#define DB_IMPLICIT (0x008)
+#define DB_MAKEFILES (0x100)
+#ifdef KMK
+# define DB_KMK (0x800)
+#endif
+
+#define DB_ALL (0xfff)
+
+extern int db_level;
+
+#ifdef KMK
+
+/* Some extended info for -j and .NOTPARALLEL tracking. */
+extern unsigned int makelevel;
+extern unsigned int job_slots;
+extern unsigned int job_slots_used;
+
+#define DB_HDR() do { printf ("[%u:%u/%u]", makelevel, job_slots_used, job_slots); } while (0)
+
+#define ISDB(_l) ((_l)&db_level)
+
+#define DBS(_l,_x) do{ if(ISDB(_l)) {DB_HDR(); \
+ print_spaces (depth); \
+ printf _x; fflush (stdout);} }while(0)
+
+#define DBF(_l,_x) do{ if(ISDB(_l)) {DB_HDR(); \
+ print_spaces (depth); \
+ printf (_x, file->name); \
+ fflush (stdout);} }while(0)
+
+#define DB(_l,_x) do{ if(ISDB(_l)) {DB_HDR(); printf _x; fflush (stdout);} }while(0)
+
+#else /* !KMK */
+
+#define ISDB(_l) ((_l)&db_level)
+
+#define DBS(_l,_x) do{ if(ISDB(_l)) {print_spaces (depth); \
+ printf _x; fflush (stdout);} }while(0)
+
+#define DBF(_l,_x) do{ if(ISDB(_l)) {print_spaces (depth); \
+ printf (_x, file->name); \
+ fflush (stdout);} }while(0)
+
+#define DB(_l,_x) do{ if(ISDB(_l)) {printf _x; fflush (stdout);} }while(0)
+
+#endif /* !KMK */
diff --git a/src/kmk/default.c b/src/kmk/default.c
new file mode 100644
index 0000000..74b0cff
--- /dev/null
+++ b/src/kmk/default.c
@@ -0,0 +1,774 @@
+/* Data base of default implicit rules for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "variable.h"
+#include "rule.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+
+/* Define GCC_IS_NATIVE if gcc is the native development environment on
+ your system (gcc/bison/flex vs cc/yacc/lex). */
+#if defined(__MSDOS__) || defined(__EMX__)
+# define GCC_IS_NATIVE
+#endif
+
+
+/* This is the default list of suffixes for suffix rules.
+ '.s' must come last, so that a '.o' file will be made from
+ a '.c' or '.p' or ... file rather than from a .s file. */
+
+static char default_suffixes[]
+#ifndef CONFIG_NO_DEFAULT_SUFFIXES
+#ifdef VMS
+ /* VMS should include all UNIX/POSIX + some VMS extensions */
+ = ".out .exe .a .olb .hlb .tlb .mlb .ln .o .obj .c .cxx .cc .cpp .pas .p \
+.for .f .r .y .l .ym .yl .mar .s .ss .i .ii .mod .sym .def .h .info .dvi \
+.tex .texinfo .texi .txinfo .mem .hlp .brn .rnh .rno .rnt .rnx .w .ch .cweb \
+.web .com .sh .elc .el";
+#elif defined(__EMX__)
+ = ".out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S \
+.mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo \
+.w .ch .web .sh .elc .el .obj .exe .dll .lib";
+#else
+ = ".out .a .ln .o .c .cc .C .cpp .p .f .F .m .r .y .l .ym .yl .s .S \
+.mod .sym .def .h .info .dvi .tex .texinfo .texi .txinfo \
+.w .ch .web .sh .elc .el";
+#endif
+#else /* CONFIG_NO_DEFAULT_SUFFIXES */
+ = "";
+#endif /* CONFIG_NO_DEFAULT_SUFFIXES */
+
+static struct pspec default_pattern_rules[] =
+ {
+#ifndef CONFIG_NO_DEFAULT_PATTERN_RULES
+#ifdef VMS
+ { "(%)", "%",
+ "@if f$$search(\"$@\") .eqs. \"\" then $(LIBRARY)/CREATE/"
+ "$(or "
+ "$(patsubst %,TEXT,$(filter %.tlb %.TLB,$@)),"
+ "$(patsubst %,HELP,$(filter %.hlb %.HLB,$@)),"
+ "$(patsubst %,MACRO,$(filter %.mlb %.MLB,$@)),"
+ "$(and "
+ "$(patsubst %,SHARE,$(filter %.olb %.OLB,$@)),"
+ "$(patsubst %,SHARE,$(filter %.exe %.EXE,$<))),"
+ "OBJECT)"
+ " $@\n"
+ "$(AR) $(ARFLAGS) $@ $<" },
+
+#else
+ { "(%)", "%",
+ "$(AR) $(ARFLAGS) $@ $<" },
+#endif
+ /* The X.out rules are only in BSD's default set because
+ BSD Make has no null-suffix rules, so 'foo.out' and
+ 'foo' are the same thing. */
+#ifdef VMS
+ { "%.exe", "%",
+ "$(CP) $< $@" },
+
+#endif
+ { "%.out", "%",
+ "@rm -f $@ \n cp $< $@" },
+
+ /* Syntax is "ctangle foo.w foo.ch foo.c". */
+ { "%.c", "%.w %.ch",
+ "$(CTANGLE) $^ $@" },
+ { "%.tex", "%.w %.ch",
+ "$(CWEAVE) $^ $@" },
+#endif /* !CONFIG_NO_DEFAULT_PATTERN_RULES */
+ { 0, 0, 0 }
+ };
+
+static struct pspec default_terminal_rules[] =
+ {
+#ifndef CONFIG_NO_DEFAULT_TERMINAL_RULES
+#ifdef VMS
+
+ /* RCS. */
+ { "%", "%$$5lv", /* Multinet style */
+ "if f$$search(\"$@\") .nes. \"\" then +$(CHECKOUT,v)" },
+ { "%", "[.$$rcs]%$$5lv", /* Multinet style */
+ "if f$$search(\"$@\") .nes. \"\" then +$(CHECKOUT,v)" },
+ { "%", "%_v", /* Normal style */
+ "if f$$search(\"$@\") .nes. \"\" then +$(CHECKOUT,v)" },
+ { "%", "[.rcs]%_v", /* Normal style */
+ "if f$$search(\"$@\") .nes. \"\" then +$(CHECKOUT,v)" },
+
+ /* SCCS. */
+ /* ain't no SCCS on vms */
+
+#else
+ /* RCS. */
+ { "%", "%,v",
+ "$(CHECKOUT,v)" },
+ { "%", "RCS/%,v",
+ "$(CHECKOUT,v)" },
+ { "%", "RCS/%",
+ "$(CHECKOUT,v)" },
+
+ /* SCCS. */
+ { "%", "s.%",
+ "$(GET) $(GFLAGS) $(SCCS_OUTPUT_OPTION) $<" },
+ { "%", "SCCS/s.%",
+ "$(GET) $(GFLAGS) $(SCCS_OUTPUT_OPTION) $<" },
+#endif /* !VMS */
+#endif /* !CONFIG_NO_DEFAULT_TERMINAL_RULES */
+ { 0, 0, 0 }
+ };
+
+static const char *default_suffix_rules[] =
+ {
+#ifndef CONFIG_NO_DEFAULT_SUFFIX_RULES
+#ifdef VMS
+ ".o",
+ "$(LINK.obj) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".obj",
+ "$(LINK.obj) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".s",
+ "$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".S",
+ "$(LINK.S) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".c",
+ "$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".cc",
+ "$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".C",
+ "$(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".cpp",
+ "$(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".f",
+ "$(LINK.f) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".m",
+ "$(LINK.m) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".p",
+ "$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".F",
+ "$(LINK.F) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".r",
+ "$(LINK.r) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".mod",
+ "$(COMPILE.mod) -o $@ -e $@ $^",
+
+ ".def.sym",
+ "$(COMPILE.def) -o $@ $<",
+
+ ".sh",
+ "copy $< >$@",
+
+ ".obj.exe",
+ "$(LINK.obj) $^ $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@",
+ ".mar.exe",
+ "$(COMPILE.mar) $^ \n $(LINK.obj) $(subst .mar,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@",
+ ".s.o",
+ "$(COMPILE.s) -o $@ $<",
+ ".s.exe",
+ "$(COMPILE.s) $^ \n $(LINK.obj) $(subst .s,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@",
+ ".c.exe",
+ "$(COMPILE.c) $^ \n $(LINK.obj) $(subst .c,.obj,$^) $(LOADLIBES) $(LDLIBS) $(CRT0) /exe=$@",
+ ".cc.exe",
+#ifdef GCC_IS_NATIVE
+ "$(COMPILE.cc) $^ \n $(LINK.obj) $(CXXSTARTUP),sys$$disk:[]$(subst .cc,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@",
+#else
+ "$(COMPILE.cc) $^ \n $(CXXLINK.obj) $(subst .cc,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@",
+ ".cxx.exe",
+ "$(COMPILE.cxx) $^ \n $(CXXLINK.obj) $(subst .cxx,.obj,$^) $(LOADLIBES) $(LXLIBS) $(LDLIBS) $(CXXRT0) /exe=$@",
+#endif
+ ".for.exe",
+ "$(COMPILE.for) $^ \n $(LINK.obj) $(subst .for,.obj,$^) $(LOADLIBES) $(LDLIBS) /exe=$@",
+ ".pas.exe",
+ "$(COMPILE.pas) $^ \n $(LINK.obj) $(subst .pas,.obj,$^) $(LOADLIBES) $(LDLIBS) /exe=$@",
+
+ ".com",
+ "copy $< >$@",
+
+ ".mar.obj",
+ "$(COMPILE.mar) /obj=$@ $<",
+ ".s.obj",
+ "$(COMPILE.s) /obj=$@ $<",
+ ".ss.obj",
+ "$(COMPILE.s) /obj=$@ $<",
+ ".c.i",
+ "$(COMPILE.c)/prep /list=$@ $<",
+ ".c.s",
+ "$(COMPILE.c)/noobj/machine /list=$@ $<",
+ ".i.s",
+ "$(COMPILE.c)/noprep/noobj/machine /list=$@ $<",
+ ".c.obj",
+ "$(COMPILE.c) /obj=$@ $<",
+ ".c.o",
+ "$(COMPILE.c) /obj=$@ $<",
+ ".cc.ii",
+ "$(COMPILE.cc)/prep /list=$@ $<",
+ ".cc.ss",
+ "$(COMPILE.cc)/noobj/machine /list=$@ $<",
+ ".ii.ss",
+ "$(COMPILE.cc)/noprep/noobj/machine /list=$@ $<",
+ ".cc.obj",
+ "$(COMPILE.cc) /obj=$@ $<",
+ ".cc.o",
+ "$(COMPILE.cc) /obj=$@ $<",
+ ".cxx.obj",
+ "$(COMPILE.cxx) /obj=$@ $<",
+ ".cxx.o",
+ "$(COMPILE.cxx) /obj=$@ $<",
+ ".for.obj",
+ "$(COMPILE.for) /obj=$@ $<",
+ ".for.o",
+ "$(COMPILE.for) /obj=$@ $<",
+ ".pas.obj",
+ "$(COMPILE.pas) /obj=$@ $<",
+ ".pas.o",
+ "$(COMPILE.pas) /obj=$@ $<",
+
+ ".y.c",
+ "$(YACC.y) $< \n rename y_tab.c $@",
+ ".l.c",
+ "$(LEX.l) $< \n rename lexyy.c $@",
+
+ ".texinfo.info",
+ "$(MAKEINFO) $<",
+
+ ".tex.dvi",
+ "$(TEX) $<",
+
+ ".cpp.o",
+ "$(COMPILE.cpp) $(OUTPUT_OPTION) $<",
+ ".f.o",
+ "$(COMPILE.f) $(OUTPUT_OPTION) $<",
+ ".m.o",
+ "$(COMPILE.m) $(OUTPUT_OPTION) $<",
+ ".p.o",
+ "$(COMPILE.p) $(OUTPUT_OPTION) $<",
+ ".r.o",
+ "$(COMPILE.r) $(OUTPUT_OPTION) $<",
+ ".mod.o",
+ "$(COMPILE.mod) -o $@ $<",
+
+ ".c.ln",
+ "$(LINT.c) -C$* $<",
+ ".y.ln",
+ "$(YACC.y) $< \n rename y_tab.c $@",
+
+ ".l.ln",
+ "@$(RM) $*.c\n $(LEX.l) $< > $*.c\n$(LINT.c) -i $*.c -o $@\n $(RM) $*.c",
+
+#else /* ! VMS */
+
+ ".o",
+ "$(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".s",
+ "$(LINK.s) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".S",
+ "$(LINK.S) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".c",
+ "$(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".cc",
+ "$(LINK.cc) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".C",
+ "$(LINK.C) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".cpp",
+ "$(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".f",
+ "$(LINK.f) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".m",
+ "$(LINK.m) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".p",
+ "$(LINK.p) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".F",
+ "$(LINK.F) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".r",
+ "$(LINK.r) $^ $(LOADLIBES) $(LDLIBS) -o $@",
+ ".mod",
+ "$(COMPILE.mod) -o $@ -e $@ $^",
+
+ ".def.sym",
+ "$(COMPILE.def) -o $@ $<",
+
+ ".sh",
+ "cat $< >$@ \n chmod a+x $@",
+
+ ".s.o",
+ "$(COMPILE.s) -o $@ $<",
+ ".S.o",
+ "$(COMPILE.S) -o $@ $<",
+ ".c.o",
+ "$(COMPILE.c) $(OUTPUT_OPTION) $<",
+ ".cc.o",
+ "$(COMPILE.cc) $(OUTPUT_OPTION) $<",
+ ".C.o",
+ "$(COMPILE.C) $(OUTPUT_OPTION) $<",
+ ".cpp.o",
+ "$(COMPILE.cpp) $(OUTPUT_OPTION) $<",
+ ".f.o",
+ "$(COMPILE.f) $(OUTPUT_OPTION) $<",
+ ".m.o",
+ "$(COMPILE.m) $(OUTPUT_OPTION) $<",
+ ".p.o",
+ "$(COMPILE.p) $(OUTPUT_OPTION) $<",
+ ".F.o",
+ "$(COMPILE.F) $(OUTPUT_OPTION) $<",
+ ".r.o",
+ "$(COMPILE.r) $(OUTPUT_OPTION) $<",
+ ".mod.o",
+ "$(COMPILE.mod) -o $@ $<",
+
+ ".c.ln",
+ "$(LINT.c) -C$* $<",
+ ".y.ln",
+#ifndef __MSDOS__
+ "$(YACC.y) $< \n $(LINT.c) -C$* y.tab.c \n $(RM) y.tab.c",
+#else
+ "$(YACC.y) $< \n $(LINT.c) -C$* y_tab.c \n $(RM) y_tab.c",
+#endif
+ ".l.ln",
+ "@$(RM) $*.c\n $(LEX.l) $< > $*.c\n$(LINT.c) -i $*.c -o $@\n $(RM) $*.c",
+
+ ".y.c",
+#ifndef __MSDOS__
+ "$(YACC.y) $< \n mv -f y.tab.c $@",
+#else
+ "$(YACC.y) $< \n mv -f y_tab.c $@",
+#endif
+ ".l.c",
+ "@$(RM) $@ \n $(LEX.l) $< > $@",
+ ".ym.m",
+ "$(YACC.m) $< \n mv -f y.tab.c $@",
+ ".lm.m",
+ "@$(RM) $@ \n $(LEX.m) $< > $@",
+
+ ".F.f",
+ "$(PREPROCESS.F) $(OUTPUT_OPTION) $<",
+ ".r.f",
+ "$(PREPROCESS.r) $(OUTPUT_OPTION) $<",
+
+ /* This might actually make lex.yy.c if there's no %R% directive in $*.l,
+ but in that case why were you trying to make $*.r anyway? */
+ ".l.r",
+ "$(LEX.l) $< > $@ \n mv -f lex.yy.r $@",
+
+ ".S.s",
+ "$(PREPROCESS.S) $< > $@",
+
+ ".texinfo.info",
+ "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@",
+
+ ".texi.info",
+ "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@",
+
+ ".txinfo.info",
+ "$(MAKEINFO) $(MAKEINFO_FLAGS) $< -o $@",
+
+ ".tex.dvi",
+ "$(TEX) $<",
+
+ ".texinfo.dvi",
+ "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<",
+
+ ".texi.dvi",
+ "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<",
+
+ ".txinfo.dvi",
+ "$(TEXI2DVI) $(TEXI2DVI_FLAGS) $<",
+
+ ".w.c",
+ "$(CTANGLE) $< - $@", /* The '-' says there is no '.ch' file. */
+
+ ".web.p",
+ "$(TANGLE) $<",
+
+ ".w.tex",
+ "$(CWEAVE) $< - $@", /* The '-' says there is no '.ch' file. */
+
+ ".web.tex",
+ "$(WEAVE) $<",
+
+#endif /* !VMS */
+#endif /* !CONFIG_NO_DEFAULT_SUFFIX_RULES */
+ 0, 0,
+ };
+
+static const char *default_variables[] =
+ {
+#ifndef CONFIG_NO_DEFAULT_VARIABLES
+#ifdef VMS
+#ifdef __ALPHA
+ "ARCH", "ALPHA",
+#endif
+#ifdef __ia64
+ "ARCH", "IA64",
+#endif
+#ifdef __VAX
+ "ARCH", "VAX",
+#endif
+ "AR", "library",
+ "LIBRARY", "library",
+ "ARFLAGS", "/replace",
+ "AS", "macro",
+ "MACRO", "macro",
+#ifdef GCC_IS_NATIVE
+ "CC", "gcc",
+#else
+ "CC", "cc",
+#endif
+ "CD", "builtin_cd",
+ "ECHO", "builtin_echo",
+#ifdef GCC_IS_NATIVE
+ "C++", "gcc/plus",
+ "CXX", "gcc/plus",
+#else
+ "C++", "cxx",
+ "CXX", "cxx",
+#ifndef __ia64
+ "CXXLD", "cxxlink",
+ "CXXLINK", "cxxlink",
+#else
+ /* CXXLINK is not used on VMS/IA64 */
+ "CXXLD", "link",
+ "CXXLINK", "link",
+#endif
+#endif
+ "CO", "co",
+ "CPP", "$(CC) /preprocess_only",
+ "FC", "fortran",
+ /* System V uses these, so explicit rules using them should work.
+ However, there is no way to make implicit rules use them and FC. */
+ "F77", "$(FC)",
+ "F77FLAGS", "$(FFLAGS)",
+ "LD", "link",
+ "LEX", "lex",
+ "PC", "pascal",
+ "YACC", "bison/yacc",
+ "YFLAGS", "/Define/Verbose",
+ "BISON", "bison",
+ "MAKEINFO", "makeinfo",
+ "TEX", "tex",
+ "TEXINDEX", "texindex",
+
+ "RM", "delete/nolog",
+
+ "CSTARTUP", "",
+#ifdef GCC_IS_NATIVE
+ "CRT0", ",sys$$library:vaxcrtl.olb/lib,gnu_cc_library:crt0.obj",
+ "CXXSTARTUP", "gnu_cc_library:crtbegin.obj",
+ "CXXRT0", ",sys$$library:vaxcrtl.olb/lib,gnu_cc_library:crtend.obj,gnu_cc_library:gxx_main.obj",
+ "LXLIBS", ",gnu_cc_library:libstdcxx.olb/lib,gnu_cc_library:libgccplus.olb/lib",
+ "LDLIBS", ",gnu_cc_library:libgcc.olb/lib",
+#else
+ "CRT0", "",
+ "CXXSTARTUP", "",
+ "CXXRT0", "",
+ "LXLIBS", "",
+ "LDLIBS", "",
+#endif
+
+ "LINK.o", "$(LD) $(LDFLAGS)",
+ "LINK.obj", "$(LD) $(LDFLAGS)",
+#ifndef GCC_IS_NATIVE
+ "CXXLINK.obj", "$(CXXLD) $(LDFLAGS)",
+ "COMPILE.cxx", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+#endif
+ "COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+ "LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+ "COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+ "COMPILE.C", "$(COMPILE.cc)",
+ "COMPILE.cpp", "$(COMPILE.cc)",
+ "LINK.C", "$(LINK.cc)",
+ "LINK.cpp", "$(LINK.cc)",
+ "YACC.y", "$(YACC) $(YFLAGS)",
+ "LEX.l", "$(LEX) $(LFLAGS)",
+ "YACC.m", "$(YACC) $(YFLAGS)",
+ "LEX.m", "$(LEX) $(LFLAGS) -t",
+ "COMPILE.for", "$(FC) $(FFLAGS) $(TARGET_ARCH)",
+ "COMPILE.f", "$(FC) $(FFLAGS) $(TARGET_ARCH) -c",
+ "LINK.f", "$(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c",
+ "LINK.r", "$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.pas", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+ "COMPILE.def", "$(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH)",
+ "COMPILE.mod", "$(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH)",
+ "COMPILE.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.mar", "$(MACRO) $(MACROFLAGS)",
+ "COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)",
+ "LINK.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)",
+ "COMPILE.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c",
+ "PREPROCESS.S", "$(CC) -E $(CPPFLAGS)",
+ "PREPROCESS.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F",
+ "PREPROCESS.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F",
+ "LINT.c", "$(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+
+ "MV", "rename/new_version",
+ "CP", "copy",
+ ".LIBPATTERNS", "%.olb lib%.a",
+
+#else /* !VMS */
+
+ "AR", "ar",
+ "ARFLAGS", "rv",
+ "AS", "as",
+#ifdef GCC_IS_NATIVE
+ "CC", "gcc",
+# ifdef __MSDOS__
+ "CXX", "gpp", /* g++ is an invalid name on MSDOS */
+# else
+ "CXX", "gcc",
+# endif /* __MSDOS__ */
+ "OBJC", "gcc",
+#else
+ "CC", "cc",
+ "CXX", "g++",
+ "OBJC", "cc",
+#endif
+
+ /* This expands to $(CO) $(COFLAGS) $< $@ if $@ does not exist,
+ and to the empty string if $@ does exist. */
+ "CHECKOUT,v", "+$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@)",
+ "CO", "co",
+ "COFLAGS", "",
+
+ "CPP", "$(CC) -E",
+#ifdef CRAY
+ "CF77PPFLAGS", "-P",
+ "CF77PP", "/lib/cpp",
+ "CFT", "cft77",
+ "CF", "cf77",
+ "FC", "$(CF)",
+#else /* Not CRAY. */
+#ifdef _IBMR2
+ "FC", "xlf",
+#else
+#ifdef __convex__
+ "FC", "fc",
+#else
+ "FC", "f77",
+#endif /* __convex__ */
+#endif /* _IBMR2 */
+ /* System V uses these, so explicit rules using them should work.
+ However, there is no way to make implicit rules use them and FC. */
+ "F77", "$(FC)",
+ "F77FLAGS", "$(FFLAGS)",
+#endif /* Cray. */
+ "GET", SCCS_GET,
+ "LD", "ld",
+#ifdef GCC_IS_NATIVE
+ "LEX", "flex",
+#else
+ "LEX", "lex",
+#endif
+ "LINT", "lint",
+ "M2C", "m2c",
+#ifdef pyr
+ "PC", "pascal",
+#else
+#ifdef CRAY
+ "PC", "PASCAL",
+ "SEGLDR", "segldr",
+#else
+ "PC", "pc",
+#endif /* CRAY. */
+#endif /* pyr. */
+#ifdef GCC_IS_NATIVE
+ "YACC", "bison -y",
+#else
+ "YACC", "yacc", /* Or "bison -y" */
+#endif
+ "MAKEINFO", "makeinfo",
+ "TEX", "tex",
+ "TEXI2DVI", "texi2dvi",
+ "WEAVE", "weave",
+ "CWEAVE", "cweave",
+ "TANGLE", "tangle",
+ "CTANGLE", "ctangle",
+
+ "RM", "rm -f",
+
+ "LINK.o", "$(CC) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.c", "$(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.m", "$(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+#ifndef HAVE_CASE_INSENSITIVE_FS
+ /* On case-insensitive filesystems, treat *.C files as *.c files,
+ to avoid erroneously compiling C sources as C++, which will
+ probably fail. */
+ "COMPILE.C", "$(COMPILE.cc)",
+#else
+ "COMPILE.C", "$(COMPILE.c)",
+#endif
+ "COMPILE.cpp", "$(COMPILE.cc)",
+ "LINK.cc", "$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+#ifndef HAVE_CASE_INSENSITIVE_FS
+ "LINK.C", "$(LINK.cc)",
+#else
+ "LINK.C", "$(LINK.c)",
+#endif
+ "LINK.cpp", "$(LINK.cc)",
+ "YACC.y", "$(YACC) $(YFLAGS)",
+ "LEX.l", "$(LEX) $(LFLAGS) -t",
+ "YACC.m", "$(YACC) $(YFLAGS)",
+ "LEX.m", "$(LEX) $(LFLAGS) -t",
+ "COMPILE.f", "$(FC) $(FFLAGS) $(TARGET_ARCH) -c",
+ "LINK.f", "$(FC) $(FFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -c",
+ "LINK.r", "$(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "COMPILE.def", "$(M2C) $(M2FLAGS) $(DEFFLAGS) $(TARGET_ARCH)",
+ "COMPILE.mod", "$(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH)",
+ "COMPILE.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c",
+ "LINK.p", "$(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)",
+ "LINK.s", "$(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)",
+ "COMPILE.s", "$(AS) $(ASFLAGS) $(TARGET_MACH)",
+ "LINK.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)",
+ "COMPILE.S", "$(CC) $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c",
+ "PREPROCESS.S", "$(CC) -E $(CPPFLAGS)",
+ "PREPROCESS.F", "$(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F",
+ "PREPROCESS.r", "$(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F",
+ "LINT.c", "$(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)",
+
+#ifndef NO_MINUS_C_MINUS_O
+ "OUTPUT_OPTION", "-o $@",
+#endif
+
+#ifdef SCCS_GET_MINUS_G
+ "SCCS_OUTPUT_OPTION", "-G$@",
+#endif
+
+#if defined(_AMIGA)
+ ".LIBPATTERNS", "%.lib",
+#elif defined(__MSDOS__)
+ ".LIBPATTERNS", "lib%.a $(DJDIR)/lib/lib%.a",
+#elif defined(__APPLE__)
+ ".LIBPATTERNS", "lib%.dylib lib%.a",
+#elif defined(__CYGWIN__) || defined(WINDOWS32)
+ ".LIBPATTERNS", "lib%.dll.a %.dll.a lib%.a %.lib lib%.dll %.dll",
+#else
+ ".LIBPATTERNS", "lib%.so lib%.a",
+#endif
+
+#endif /* !VMS */
+#endif /* !CONFIG_NO_DEFAULT_VARIABLES */
+ /* Make this assignment to avoid undefined variable warnings. */
+ "GNUMAKEFLAGS", "",
+ 0, 0
+ };
+
+/* Set up the default .SUFFIXES list. */
+
+void
+set_default_suffixes (void)
+{
+ suffix_file = enter_file (strcache_add (".SUFFIXES"));
+ suffix_file->builtin = 1;
+
+ if (no_builtin_rules_flag)
+ define_variable_cname ("SUFFIXES", "", o_default, 0);
+ else
+ {
+ struct dep *d;
+ const char *p = default_suffixes;
+ suffix_file->deps = enter_prereqs (PARSE_SIMPLE_SEQ ((char **)&p, struct dep),
+ NULL);
+ for (d = suffix_file->deps; d; d = d->next)
+ d->file->builtin = 1;
+
+ define_variable_cname ("SUFFIXES", default_suffixes, o_default, 0);
+ }
+}
+
+/* Enter the default suffix rules as file rules. This used to be done in
+ install_default_implicit_rules, but that loses because we want the
+ suffix rules installed before reading makefiles, and the pattern rules
+ installed after. */
+
+void
+install_default_suffix_rules (void)
+{
+ const char **s;
+
+ if (no_builtin_rules_flag)
+ return;
+
+ for (s = default_suffix_rules; *s != 0; s += 2)
+ {
+ struct file *f = enter_file (strcache_add (s[0]));
+ /* This function should run before any makefile is parsed. */
+ assert (f->cmds == 0);
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ f->cmds = xmalloc (sizeof (struct commands));
+#else
+ f->cmds = alloccache_alloc (&commands_cache);
+#endif
+ f->cmds->fileinfo.filenm = 0;
+ f->cmds->commands = xstrdup (s[1]);
+ f->cmds->command_lines = 0;
+ f->cmds->recipe_prefix = RECIPEPREFIX_DEFAULT;
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ f->cmds->refs = 1000;
+#endif
+ f->builtin = 1;
+ }
+}
+
+
+/* Install the default pattern rules. */
+
+void
+install_default_implicit_rules (void)
+{
+ struct pspec *p;
+
+ if (no_builtin_rules_flag)
+ return;
+
+ for (p = default_pattern_rules; p->target != 0; ++p)
+ install_pattern_rule (p, 0);
+
+ for (p = default_terminal_rules; p->target != 0; ++p)
+ install_pattern_rule (p, 1);
+}
+
+void
+define_default_variables (void)
+{
+ const char **s;
+
+ if (no_builtin_variables_flag)
+ return;
+
+ for (s = default_variables; *s != 0; s += 2)
+ define_variable (s[0], strlen (s[0]), s[1], o_default, 1);
+}
+
+void
+undefine_default_variables (void)
+{
+ const char **s;
+
+ for (s = default_variables; *s != 0; s += 2)
+ undefine_variable_global (s[0], strlen (s[0]), o_default);
+}
diff --git a/src/kmk/dep.h b/src/kmk/dep.h
new file mode 100644
index 0000000..d53e8ef
--- /dev/null
+++ b/src/kmk/dep.h
@@ -0,0 +1,184 @@
+/* Definitions of dependency data structures for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Structure used in chains of names, for parsing and globbing. */
+
+#define NAMESEQ(_t) \
+ _t *next; \
+ const char *name
+
+struct nameseq
+ {
+ NAMESEQ (struct nameseq);
+ };
+
+/* Flag bits for the second argument to 'read_makefile'.
+ These flags are saved in the 'flags' field of each
+ 'struct goaldep' in the chain returned by 'read_all_makefiles'. */
+
+#define RM_NO_DEFAULT_GOAL (1 << 0) /* Do not set default goal. */
+#define RM_INCLUDED (1 << 1) /* Search makefile search path. */
+#define RM_DONTCARE (1 << 2) /* No error if it doesn't exist. */
+#define RM_NO_TILDE (1 << 3) /* Don't expand ~ in file name. */
+#define RM_NOFLAG 0
+
+/* Structure representing one dependency of a file.
+ Each struct file's 'deps' points to a chain of these, through 'next'.
+ 'stem' is the stem for this dep line of static pattern rule or NULL. */
+
+#ifndef CONFIG_WITH_INCLUDEDEP
+#define DEP(_t) \
+ NAMESEQ (_t); \
+ struct file *file; \
+ const char *stem; \
+ unsigned short flags : 8; \
+ unsigned short changed : 1; \
+ unsigned short ignore_mtime : 1; \
+ unsigned short staticpattern : 1; \
+ unsigned short need_2nd_expansion : 1
+#else
+# define DEP(_t) \
+ NAMESEQ (_t); \
+ struct file *file; \
+ const char *stem; \
+ unsigned short flags : 8; \
+ unsigned short changed : 1; \
+ unsigned short ignore_mtime : 1; \
+ unsigned short staticpattern : 1; \
+ unsigned short need_2nd_expansion : 1; \
+ unsigned short includedep : 1
+#endif
+
+struct dep
+ {
+ DEP (struct dep);
+ };
+
+/* Structure representing one goal.
+ The goals to be built constitute a chain of these, chained through 'next'.
+ 'stem' is not used, but it's simpler to include and ignore it. */
+
+struct goaldep
+ {
+ DEP (struct goaldep);
+ unsigned short error;
+ floc floc;
+ };
+
+/* Options for parsing lists of filenames. */
+
+#define PARSEFS_NONE 0x0000
+#define PARSEFS_NOSTRIP 0x0001
+#define PARSEFS_NOAR 0x0002
+#define PARSEFS_NOGLOB 0x0004
+#define PARSEFS_EXISTS 0x0008
+#define PARSEFS_NOCACHE 0x0010
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+#define PARSE_FILE_SEQ(_s,_t,_c,_p,_f) \
+ (_t *)parse_file_seq ((_s),sizeof (_t),(_c),(_p),(_f))
+#define PARSE_SIMPLE_SEQ(_s,_t) \
+ (_t *)parse_file_seq ((_s),sizeof (_t),MAP_NUL,NULL,PARSEFS_NONE)
+#else
+# define PARSE_FILE_SEQ(_s,_t,_c,_p,_f) \
+ (_t *)parse_file_seq ((_s),sizeof (_t),(_c),(_p),(_f), \
+ &PARSE_FILE_SEQ_IGNORE_ ## _t ## _cache)
+# define PARSE_SIMPLE_SEQ(_s,_t) \
+ (_t *)parse_file_seq ((_s),sizeof (_t),MAP_NUL,NULL,PARSEFS_NONE, \
+ &PARSE_FILE_SEQ_IGNORE_ ## _t ## _cache)
+# define PARSE_FILE_SEQ_IGNORE_struct
+#endif
+
+
+#ifdef VMS
+void *parse_file_seq ();
+#else
+void *parse_file_seq (char **stringp, unsigned int size,
+ int stopmap, const char *prefix, int flags
+ IF_WITH_ALLOC_CACHES_PARAM(struct alloccache *cache));
+#endif
+
+char *tilde_expand (const char *name);
+
+#ifndef NO_ARCHIVES
+struct nameseq *ar_glob (const char *arname, const char *member_pattern, unsigned int size);
+#endif
+
+#define dep_name(d) ((d)->name ? (d)->name : (d)->file->name)
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+
+#define alloc_seq_elt(_t) xcalloc (sizeof (_t))
+void free_ns_chain (struct nameseq *n);
+
+#if defined(MAKE_MAINTAINER_MODE) && defined(__GNUC__)
+/* Use inline to get real type-checking. */
+#define SI static inline
+SI struct nameseq *alloc_ns() { return alloc_seq_elt (struct nameseq); }
+SI struct dep *alloc_dep() { return alloc_seq_elt (struct dep); }
+SI struct goaldep *alloc_goaldep() { return alloc_seq_elt (struct goaldep); }
+
+SI void free_ns(struct nameseq *n) { free (n); }
+SI void free_dep(struct dep *d) { free_ns ((struct nameseq *)d); }
+SI void free_goaldep(struct goaldep *g) { free_dep ((struct dep *)g); }
+
+SI void free_dep_chain(struct dep *d) { free_ns_chain((struct nameseq *)d); }
+SI void free_goal_chain(struct goaldep *g) { free_dep_chain((struct dep *)g); }
+#else
+# define alloc_ns() alloc_seq_elt (struct nameseq)
+# define alloc_dep() alloc_seq_elt (struct dep)
+# define alloc_goaldep() alloc_seq_elt (struct goaldep)
+
+# define free_ns(_n) free (_n)
+# define free_dep(_d) free_ns (_d)
+# define free_goaldep(_g) free_dep (_g)
+
+# define free_dep_chain(_d) free_ns_chain ((struct nameseq *)(_d))
+# define free_goal_chain(_g) free_ns_chain ((struct nameseq *)(_g))
+#endif
+
+#else /* CONFIG_WITH_ALLOC_CACHES */
+
+# include <k/kDefs.h>
+
+K_INLINE struct nameseq *alloc_ns (void) { return (struct nameseq *)alloccache_calloc (&nameseq_cache); }
+K_INLINE void free_ns (struct nameseq *n) { alloccache_free (&nameseq_cache, n); }
+void free_ns_chain (struct nameseq *n);
+
+K_INLINE struct dep *alloc_dep (void) { return (struct dep *)alloccache_calloc (&dep_cache); }
+K_INLINE void free_dep (struct dep *d) { alloccache_free (&dep_cache, d); }
+void free_dep_chain (struct dep *d);
+
+K_INLINE struct goaldep *alloc_goaldep (void) { return (struct goaldep *)alloccache_calloc (&goaldep_cache); }
+K_INLINE void free_goaldep (struct goaldep *g) { alloccache_free (&goaldep_cache, g); }
+void free_goal_chain (struct goaldep *g);
+
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
+struct dep *copy_dep_chain (const struct dep *d);
+
+struct goaldep *read_all_makefiles (const char **makefiles);
+void eval_buffer (char *buffer, const floc *floc IF_WITH_VALUE_LENGTH(COMMA char *eos));
+enum update_status update_goal_chain (struct goaldep *goals);
+
+#ifdef CONFIG_WITH_INCLUDEDEP
+/* incdep.c */
+enum incdep_op { incdep_read_it, incdep_queue, incdep_flush };
+void eval_include_dep (const char *name, floc *f, enum incdep_op op);
+void incdep_flush_and_term (void);
+#endif
+
diff --git a/src/kmk/dir-nt-bird.c b/src/kmk/dir-nt-bird.c
new file mode 100644
index 0000000..d690751
--- /dev/null
+++ b/src/kmk/dir-nt-bird.c
@@ -0,0 +1,794 @@
+/* $Id: dir-nt-bird.c 3359 2020-06-05 16:17:17Z bird $ */
+/** @file
+ * Reimplementation of dir.c for NT using kFsCache.
+ *
+ * This should perform better on NT, especially on machines "infected"
+ * by antivirus programs.
+ */
+
+/*
+ * Copyright (c) 2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Windows.h> /* locking */
+#include "nt/kFsCache.h"
+#include "makeint.h"
+#if defined(KMK) && !defined(__OS2__)
+# include "glob/glob.h"
+#else
+# include <glob.h>
+#endif
+#include <assert.h>
+#include "kmkbuiltin.h"
+#include "kmkbuiltin/err.h"
+
+#include "nt_fullpath.h" /* for the time being - will be implemented here later on. */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** User data key indicating that it's an impossible file to make.
+ * See file_impossible() and file_impossible_p(). */
+#define KMK_DIR_NT_IMPOSSIBLE_KEY (~(KUPTR)7)
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * glob directory stream.
+ */
+typedef struct KMKNTOPENDIR
+{
+ /** Reference to the directory. */
+ PKFSDIR pDir;
+ /** Index of the next directory entry (child) to return. */
+ KU32 idxNext;
+ /** The structure in which to return the directory entry. */
+ struct dirent DirEnt;
+} KMKNTOPENDIR;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** The cache.*/
+PKFSCACHE g_pFsCache = NULL;
+/** Number of times dir_cache_invalid_missing was called. */
+static KU32 volatile g_cInvalidates = 0;
+/** Set by dir_cache_volatile_dir to indicate that the user has marked the
+ * volatile parts of the file system with custom revisioning and we only need to
+ * flush these. This is very handy when using a separate output directory
+ * from the sources. */
+static KBOOL g_fFsCacheIsUsingCustomRevision = K_FALSE;
+/** The ID of the main thread. We currently only let it access the cache. */
+static DWORD g_idMainThread = 0;
+
+
+void hash_init_directories(void)
+{
+ g_idMainThread = GetCurrentThreadId();
+ g_pFsCache = kFsCacheCreate(0);
+ if (g_pFsCache)
+ return;
+ fputs("kFsCacheCreate failed!", stderr);
+ exit(9);
+}
+
+
+/**
+ * Checks if @a pszName exists in directory @a pszDir.
+ *
+ * @returns 1 if it does, 0 if it doesn't.
+ *
+ * @param pszDir The directory.
+ * @param pszName The name.
+ *
+ * If empty string, just check if the directory exists.
+ *
+ * If NULL, just read the whole cache the directory into
+ * the cache (we always do that).
+ */
+int dir_file_exists_p(const char *pszDir, const char *pszName)
+{
+ int fRc = 0;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+
+ if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
+ {
+ if (pszName != 0)
+ {
+ /* Empty filename is just checking out the directory. */
+ if (*pszName == '\0')
+ fRc = 1;
+ else
+ {
+ PKFSOBJ pNameObj = kFsCacheLookupRelativeToDirA(g_pFsCache, (PKFSDIR)pDirObj, pszName, strlen(pszName),
+ 0/*fFlags*/, &enmError, NULL);
+ if (pNameObj)
+ {
+ fRc = pNameObj->bObjType == KFSOBJ_TYPE_MISSING;
+ kFsCacheObjRelease(g_pFsCache, pNameObj);
+ }
+ }
+ }
+ }
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ return fRc;
+}
+
+
+/**
+ * Checks if a file exists.
+ *
+ * @returns 1 if it does exist, 0 if it doesn't.
+ * @param pszPath The path to check out.
+ * @note Multi-thread safe.
+ */
+int file_exists_p(const char *pszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ fRc = pPathObj->bObjType != KFSOBJ_TYPE_MISSING;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * Checks if a file exists and is a regular file, given a UTF-16 string.
+ *
+ * @returns 1 if it regular file, 0 if doesn't exist or isn't a file
+ * @param pwszPath The UTF-16 path to check out.
+ * @note Multi-thread safe.
+ */
+int utf16_regular_file_p(const wchar_t *pwszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupW(g_pFsCache, pwszPath, &enmError);
+ if (pPathObj)
+ {
+ fRc = pPathObj->bObjType == KFSOBJ_TYPE_FILE;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * Just a way for vpath.c to get a correctly cased path, I think.
+ *
+ * @returns Directory path in string cache.
+ * @param pszDir The directory.
+ */
+const char *dir_name(const char *pszDir)
+{
+ char szTmp[MAX_PATH];
+ assert(GetCurrentThreadId() == g_idMainThread);
+ nt_fullpath(pszDir, szTmp, sizeof(szTmp));
+ return strcache_add(szTmp);
+}
+
+
+/**
+ * Makes future file_impossible_p calls return 1 for pszPath.
+ */
+void file_impossible(const char *pszPath)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ kFsCacheObjAddUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY, sizeof(KFSUSERDATA));
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+}
+
+/**
+ * Makes future file_impossible_p calls return 1 for pszPath.
+ */
+int file_impossible_p(const char *pszPath)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ fRc = kFsCacheObjGetUserData(g_pFsCache, pPathObj, KMK_DIR_NT_IMPOSSIBLE_KEY) != NULL;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+}
+
+
+/**
+ * opendir for glob.
+ *
+ * @returns Pointer to DIR like handle, NULL if directory not found.
+ * @param pszDir The directory to enumerate.
+ */
+static __ptr_t dir_glob_opendir(const char *pszDir)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+ if (pDirObj->bObjType == KFSOBJ_TYPE_DIR)
+ {
+ if (kFsCacheDirEnsurePopuplated(g_pFsCache, (PKFSDIR)pDirObj, NULL))
+ {
+ KMKNTOPENDIR *pDir = xmalloc(sizeof(*pDir));
+ pDir->pDir = (PKFSDIR)pDirObj;
+ pDir->idxNext = 0;
+ return pDir;
+ }
+ }
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ return NULL;
+}
+
+
+/**
+ * readdir for glob.
+ *
+ * @returns Pointer to DIR like handle, NULL if directory not found.
+ * @param pDir Directory enum handle by dir_glob_opendir.
+ */
+static struct dirent *dir_glob_readdir(__ptr_t pvDir)
+{
+ KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
+ KU32 const cChildren = pDir->pDir->cChildren;
+ assert(GetCurrentThreadId() == g_idMainThread);
+ while (pDir->idxNext < cChildren)
+ {
+ PKFSOBJ pEntry = pDir->pDir->papChildren[pDir->idxNext++];
+
+ /* Don't return missing objects. */
+ if (pEntry->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ /* Copy the name that fits, trying to avoid names with spaces.
+ If neither fits, skip the name. */
+ if ( pEntry->cchName < sizeof(pDir->DirEnt.d_name)
+ && ( pEntry->pszShortName == pEntry->pszName
+ || memchr(pEntry->pszName, ' ', pEntry->cchName) == NULL))
+ {
+ pDir->DirEnt.d_namlen = pEntry->cchName;
+ memcpy(pDir->DirEnt.d_name, pEntry->pszName, pEntry->cchName + 1);
+ }
+ else if (pEntry->cchShortName < sizeof(pDir->DirEnt.d_name))
+ {
+ pDir->DirEnt.d_namlen = pEntry->cchShortName;
+ memcpy(pDir->DirEnt.d_name, pEntry->pszShortName, pEntry->cchShortName + 1);
+ }
+ else
+ continue;
+
+ pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
+ if (pEntry->bObjType == KFSOBJ_TYPE_DIR)
+ pDir->DirEnt.d_type = DT_DIR;
+ else if (pEntry->bObjType == KFSOBJ_TYPE_FILE)
+ pDir->DirEnt.d_type = DT_REG;
+ else
+ pDir->DirEnt.d_type = DT_UNKNOWN;
+
+ return &pDir->DirEnt;
+ }
+ }
+
+ /*
+ * Fake the '.' and '..' directories because they're not part of papChildren above.
+ */
+ if (pDir->idxNext < cChildren + 2)
+ {
+ pDir->idxNext++;
+ pDir->DirEnt.d_type = DT_DIR;
+ pDir->DirEnt.d_namlen = pDir->idxNext - cChildren;
+ pDir->DirEnt.d_reclen = offsetof(struct dirent, d_name) + pDir->DirEnt.d_namlen;
+ pDir->DirEnt.d_name[0] = '.';
+ pDir->DirEnt.d_name[1] = '.';
+ pDir->DirEnt.d_name[pDir->DirEnt.d_namlen] = '\0';
+ return &pDir->DirEnt;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * closedir for glob.
+ *
+ * @param pDir Directory enum handle by dir_glob_opendir.
+ */
+static void dir_glob_closedir(__ptr_t pvDir)
+{
+ KMKNTOPENDIR *pDir = (KMKNTOPENDIR *)pvDir;
+ assert(GetCurrentThreadId() == g_idMainThread);
+ kFsCacheObjRelease(g_pFsCache, &pDir->pDir->Obj);
+ pDir->pDir = NULL;
+ free(pDir);
+}
+
+
+/**
+ * stat for glob.
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the info.
+ */
+static int dir_glob_stat(const char *pszPath, struct stat *pStat)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+/** @todo follow symlinks vs. on symlink! */
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ *pStat = pPathObj->Stats;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ errno = ENOENT;
+ return -1;
+}
+
+
+/**
+ * lstat for glob.
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the info.
+ */
+static int dir_glob_lstat(const char *pszPath, struct stat *pStat)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ *pStat = pPathObj->Stats;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ errno = ENOENT;
+ }
+ else
+ errno = enmError == KFSLOOKUPERROR_NOT_DIR
+ || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
+ ? ENOTDIR : ENOENT;
+
+ return -1;
+}
+
+
+/**
+ * Checks if @a pszDir exists and is a directory.
+ *
+ * @returns 1 if is directory, 0 if isn't or doesn't exists.
+ * @param pszDir The alleged directory.
+ */
+static int dir_globl_dir_exists_p(const char *pszDir)
+{
+ int fRc;
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pDirObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pDirObj)
+ {
+ fRc = pDirObj->bObjType == KFSOBJ_TYPE_DIR;
+ kFsCacheObjRelease(g_pFsCache, pDirObj);
+ }
+ else
+ fRc = 0;
+ return fRc;
+
+}
+
+
+/**
+ * Sets up pGlob with the necessary callbacks.
+ *
+ * @param pGlob Structure to populate.
+ */
+void dir_setup_glob(glob_t *pGlob)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ pGlob->gl_opendir = dir_glob_opendir;
+ pGlob->gl_readdir = dir_glob_readdir;
+ pGlob->gl_closedir = dir_glob_closedir;
+ pGlob->gl_stat = dir_glob_stat;
+#ifdef __EMX__ /* The FreeBSD implementation actually uses gl_lstat!! */
+ pGlob->gl_lstat = dir_glob_lstat;
+#else
+ pGlob->gl_exists = file_exists_p;
+ pGlob->gl_isdir = dir_globl_dir_exists_p;
+#endif
+}
+
+
+/**
+ * Print statitstics.
+ */
+void print_dir_stats(void)
+{
+ FILE *pOut = stdout;
+ KU32 cMisses;
+
+ fputs("\n"
+ "# NT dir cache stats:\n", pOut);
+ fprintf(pOut, "# %u objects, taking up %u (%#x) bytes, avg %u bytes\n",
+ g_pFsCache->cObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects, g_pFsCache->cbObjects / g_pFsCache->cObjects);
+ fprintf(pOut, "# %u A path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collision\n",
+ g_pFsCache->cAnsiPaths, g_pFsCache->cbAnsiPaths, g_pFsCache->cbAnsiPaths,
+ g_pFsCache->cbAnsiPaths / K_MAX(g_pFsCache->cAnsiPaths, 1), g_pFsCache->cAnsiPathCollisions);
+#ifdef KFSCACHE_CFG_UTF16
+ fprintf(pOut, "# %u W path hashes, taking up %u (%#x) bytes, avg %u bytes, %u collisions\n",
+ g_pFsCache->cUtf16Paths, g_pFsCache->cbUtf16Paths, g_pFsCache->cbUtf16Paths,
+ g_pFsCache->cbUtf16Paths / K_MAX(g_pFsCache->cUtf16Paths, 1), g_pFsCache->cUtf16PathCollisions);
+#endif
+ fprintf(pOut, "# %u child hash tables, total of %u entries, %u children inserted, %u collisions\n",
+ g_pFsCache->cChildHashTabs, g_pFsCache->cChildHashEntriesTotal,
+ g_pFsCache->cChildHashed, g_pFsCache->cChildHashCollisions);
+
+ cMisses = g_pFsCache->cLookups - g_pFsCache->cPathHashHits - g_pFsCache->cWalkHits;
+ fprintf(pOut, "# %u lookups: %u (%" KU64_PRI " %%) path hash hits, %u (%" KU64_PRI "%%) walks hits, %u (%" KU64_PRI "%%) misses\n",
+ g_pFsCache->cLookups,
+ g_pFsCache->cPathHashHits, g_pFsCache->cPathHashHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
+ g_pFsCache->cWalkHits, g_pFsCache->cWalkHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1),
+ cMisses, cMisses * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1));
+ fprintf(pOut, "# %u child searches, %u (%" KU64_PRI "%%) hash hits\n",
+ g_pFsCache->cChildSearches,
+ g_pFsCache->cChildHashHits, g_pFsCache->cChildHashHits * (KU64)100 / K_MAX(g_pFsCache->cChildSearches, 1));
+}
+
+
+void print_dir_data_base(void)
+{
+ /** @todo. */
+
+}
+
+
+/* duplicated in kWorker.c
+ * Note! Tries avoid to produce a result with spaces since they aren't supported by makefiles. */
+void nt_fullpath_cached(const char *pszPath, char *pszFull, size_t cbFull)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj;
+
+ KFSCACHE_LOCK(g_pFsCache); /* let's start out being careful. */
+
+ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ KSIZE off = pPathObj->cchParent;
+ if (off > 0)
+ {
+ KSIZE offEnd = off + pPathObj->cchName;
+ if (offEnd < cbFull)
+ {
+ PKFSDIR pAncestor;
+
+ pszFull[offEnd] = '\0';
+ memcpy(&pszFull[off], pPathObj->pszName, pPathObj->cchName);
+
+ for (pAncestor = pPathObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent)
+ {
+ kHlpAssert(off > 1);
+ kHlpAssert(pAncestor != NULL);
+ kHlpAssert(pAncestor->Obj.cchName > 0);
+ pszFull[--off] = '/';
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+ if ( pAncestor->Obj.pszName == pAncestor->Obj.pszShortName
+ || memchr(pAncestor->Obj.pszName, ' ', pAncestor->Obj.cchName) == NULL)
+#endif
+ {
+ off -= pAncestor->Obj.cchName;
+ kHlpAssert(pAncestor->Obj.cchParent == off);
+ memcpy(&pszFull[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName);
+ }
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+ else
+ {
+ /*
+ * The long name constains a space, so use the alternative name instead.
+ * Most likely the alternative name differs in length, usually it's shorter,
+ * so we have to shift the part of the path we've already assembled
+ * accordingly.
+ */
+ KSSIZE cchDelta = (KSSIZE)pAncestor->Obj.cchShortName - (KSSIZE)pAncestor->Obj.cchName;
+ if (cchDelta != 0)
+ {
+ if ((KSIZE)(offEnd + cchDelta) >= cbFull)
+ goto l_fallback;
+ memmove(&pszFull[off + cchDelta], &pszFull[off], offEnd + 1 - off);
+ off += cchDelta;
+ offEnd += cchDelta;
+ }
+ off -= pAncestor->Obj.cchShortName;
+ kHlpAssert(pAncestor->Obj.cchParent == off);
+ memcpy(&pszFull[off], pAncestor->Obj.pszShortName, pAncestor->Obj.cchShortName);
+ }
+#endif
+ }
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ KFSCACHE_UNLOCK(g_pFsCache);
+ return;
+ }
+ }
+ else
+ {
+ if ((size_t)pPathObj->cchName + 1 < cbFull)
+ {
+ /* Assume no spaces here. */
+ memcpy(pszFull, pPathObj->pszName, pPathObj->cchName);
+ pszFull[pPathObj->cchName] = '/';
+ pszFull[pPathObj->cchName + 1] = '\0';
+
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ KFSCACHE_UNLOCK(g_pFsCache);
+ return;
+ }
+ }
+
+ /* do fallback. */
+#ifdef KFSCACHE_CFG_SHORT_NAMES
+l_fallback:
+#endif
+ kHlpAssertFailed();
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ }
+ KFSCACHE_UNLOCK(g_pFsCache);
+
+ nt_fullpath(pszPath, pszFull, cbFull);
+}
+
+
+/**
+ * Special stat call used by remake.c
+ *
+ * @returns 0 on success, -1 + errno on failure.
+ * @param pszPath The path to stat.
+ * @param pStat Where to return the mtime field only.
+ */
+int stat_only_mtime(const char *pszPath, struct stat *pStat)
+{
+ /* Currently a little expensive, so just hit the file system once the
+ jobs starts comming in. */
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (g_cInvalidates == 0)
+ {
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError);
+ if (pPathObj)
+ {
+ if (pPathObj->bObjType != KFSOBJ_TYPE_MISSING)
+ {
+ kHlpAssert(pPathObj->fHaveStats); /* currently always true. */
+ pStat->st_mtime = pPathObj->Stats.st_mtime;
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ return 0;
+ }
+
+ kFsCacheObjRelease(g_pFsCache, pPathObj);
+ errno = ENOENT;
+ }
+ else
+ errno = enmError == KFSLOOKUPERROR_NOT_DIR
+ || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR
+ ? ENOTDIR : ENOENT;
+ return -1;
+ }
+ return birdStatModTimeOnly(pszPath, &pStat->st_mtim, 1 /*fFollowLink*/);
+}
+
+/**
+ * Do cache invalidation after a job completes.
+ */
+void dir_cache_invalid_after_job(void)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ g_cInvalidates++;
+ if (g_fFsCacheIsUsingCustomRevision)
+ kFsCacheInvalidateCustomBoth(g_pFsCache);
+ else
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the whole directory cache
+ *
+ * Used by $(dircache-ctl invalidate)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_all(void)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the whole directory cache and closes all open handles.
+ *
+ * Used by $(dircache-ctl invalidate-and-close-dirs)
+ * @param including_root Also close the root directory.
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_all_and_close_dirs(int including_root)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAllAndCloseDirs(g_pFsCache, !!including_root);
+}
+
+/**
+ * Invalidate missing bits of the directory cache.
+ *
+ * Used by $(dircache-ctl invalidate-missing)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_missing(void)
+{
+ g_cInvalidates++;
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Invalidate the volatile bits of the directory cache.
+ *
+ * Used by $(dircache-ctl invalidate-missing)
+ * @note Multi-thread safe.
+ */
+void dir_cache_invalid_volatile(void)
+{
+ g_cInvalidates++;
+ if (g_fFsCacheIsUsingCustomRevision)
+ kFsCacheInvalidateCustomBoth(g_pFsCache);
+ else
+ kFsCacheInvalidateAll(g_pFsCache);
+}
+
+/**
+ * Used by $(dircache-ctl ) to mark a directory subtree or file as volatile.
+ *
+ * The first call changes the rest of the cache to be considered non-volatile.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pszDir The directory (or file for what that is worth).
+ */
+int dir_cache_volatile_dir(const char *pszDir)
+{
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pObj = kFsCacheLookupA(g_pFsCache, pszDir, &enmError);
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (pObj)
+ {
+ KBOOL fRc = kFsCacheSetupCustomRevisionForTree(g_pFsCache, pObj);
+ kFsCacheObjRelease(g_pFsCache, pObj);
+ if (fRc)
+ {
+ g_fFsCacheIsUsingCustomRevision = K_TRUE;
+ return 0;
+ }
+ OS(error, reading_file, "failed to mark '%s' as volatile", pszDir);
+ }
+ else
+ OS(error, reading_file, "failed to mark '%s' as volatile (not found)", pszDir);
+ return -1;
+}
+
+/**
+ * Invalidates a deleted directory so the cache can close handles to it.
+ *
+ * Used by kmk_builtin_rm and kmk_builtin_rmdir.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pszDir The directory to invalidate as deleted.
+ */
+int dir_cache_deleted_directory(const char *pszDir)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (kFsCacheInvalidateDeletedDirectoryA(g_pFsCache, pszDir))
+ return 0;
+ return -1;
+}
+
+
+int kmk_builtin_dircache(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ assert(GetCurrentThreadId() == g_idMainThread);
+ if (argc >= 2)
+ {
+ const char *pszCmd = argv[1];
+ if (strcmp(pszCmd, "invalidate") == 0)
+ {
+ if (argc == 2)
+ {
+ dir_cache_invalid_all();
+ return 0;
+ }
+ errx(pCtx, 2, "the 'invalidate' command takes no arguments!\n");
+ }
+ else if (strcmp(pszCmd, "invalidate-missing") == 0)
+ {
+ if (argc == 2)
+ {
+ dir_cache_invalid_missing ();
+ return 0;
+ }
+ errx(pCtx, 2, "the 'invalidate-missing' command takes no arguments!\n");
+ }
+ else if (strcmp(pszCmd, "volatile") == 0)
+ {
+ int i;
+ for (i = 2; i < argc; i++)
+ dir_cache_volatile_dir(argv[i]);
+ return 0;
+ }
+ else if (strcmp(pszCmd, "deleted") == 0)
+ {
+ int i;
+ for (i = 2; i < argc; i++)
+ dir_cache_deleted_directory(argv[i]);
+ return 0;
+ }
+ else
+ errx(pCtx, 2, "Invalid command '%s'!\n", pszCmd);
+ }
+ else
+ errx(pCtx, 2, "No command given!\n");
+
+ K_NOREF(envp);
+ return 2;
+}
+
diff --git a/src/kmk/dir.c b/src/kmk/dir.c
new file mode 100644
index 0000000..1709479
--- /dev/null
+++ b/src/kmk/dir.c
@@ -0,0 +1,1602 @@
+/* Directory hashing for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "hash.h"
+#include "filedef.h"
+#include "dep.h"
+
+#ifdef HAVE_DIRENT_H
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+# ifdef VMS
+/* its prototype is in vmsdir.h, which is not needed for HAVE_DIRENT_H */
+const char *vmsify (const char *name, int type);
+# endif
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+# include <ndir.h>
+# endif
+# ifdef HAVE_VMSDIR_H
+# include "vmsdir.h"
+# endif /* HAVE_VMSDIR_H */
+#endif
+/* bird: FreeBSD + smbfs -> readdir() + EBADF */
+#ifdef __FreeBSD__
+# include <sys/mount.h>
+#endif
+/* bird: end */
+
+#ifdef CONFIG_WITH_STRCACHE2
+# include <stddef.h>
+#endif
+
+/* In GNU systems, <dirent.h> defines this macro for us. */
+#ifdef _D_NAMLEN
+# undef NAMLEN
+# define NAMLEN(d) _D_NAMLEN(d)
+#endif
+
+#if (defined (POSIX) || defined (VMS) || defined (WINDOWS32)) && !defined (__GNU_LIBRARY__)
+/* Posix does not require that the d_ino field be present, and some
+ systems do not provide it. */
+# define REAL_DIR_ENTRY(dp) 1
+# define FAKE_DIR_ENTRY(dp)
+#else
+# define REAL_DIR_ENTRY(dp) (dp->d_ino != 0)
+# define FAKE_DIR_ENTRY(dp) (dp->d_ino = 1)
+#endif /* POSIX */
+
+#ifdef __MSDOS__
+#include <ctype.h>
+#include <fcntl.h>
+
+/* If it's MSDOS that doesn't have _USE_LFN, disable LFN support. */
+#ifndef _USE_LFN
+#define _USE_LFN 0
+#endif
+
+static const char *
+dosify (const char *filename)
+{
+ static char dos_filename[14];
+ char *df;
+ int i;
+
+ if (filename == 0 || _USE_LFN)
+ return filename;
+
+ /* FIXME: what about filenames which violate
+ 8+3 constraints, like "config.h.in", or ".emacs"? */
+ if (strpbrk (filename, "\"*+,;<=>?[\\]|") != 0)
+ return filename;
+
+ df = dos_filename;
+
+ /* First, transform the name part. */
+ for (i = 0; i < 8 && ! STOP_SET (*filename, MAP_DOT|MAP_NUL); ++i)
+ *df++ = tolower ((unsigned char)*filename++);
+
+ /* Now skip to the next dot. */
+ while (! STOP_SET (*filename, MAP_DOT|MAP_NUL))
+ ++filename;
+ if (*filename != '\0')
+ {
+ *df++ = *filename++;
+ for (i = 0; i < 3 && ! STOP_SET (*filename, MAP_DOT|MAP_NUL); ++i)
+ *df++ = tolower ((unsigned char)*filename++);
+ }
+
+ /* Look for more dots. */
+ while (! STOP_SET (*filename, MAP_DOT|MAP_NUL))
+ ++filename;
+ if (*filename == '.')
+ return filename;
+ *df = 0;
+ return dos_filename;
+}
+#endif /* __MSDOS__ */
+
+#ifdef WINDOWS32
+#include <Windows.h>
+#include "pathstuff.h"
+#endif
+
+#ifdef _AMIGA
+#include <ctype.h>
+#endif
+
+#ifdef HAVE_CASE_INSENSITIVE_FS
+static const char *
+downcase (const char *filename)
+{
+ static PATH_VAR (new_filename);
+ char *df;
+
+ if (filename == 0)
+ return 0;
+
+ df = new_filename;
+ while (*filename != '\0')
+ {
+ *df++ = tolower ((unsigned char)*filename);
+ ++filename;
+ }
+
+ *df = 0;
+
+ return new_filename;
+}
+#endif /* HAVE_CASE_INSENSITIVE_FS */
+
+#ifdef VMS
+
+static char *
+downcase_inplace(char *filename)
+{
+ char *name;
+ name = filename;
+ while (*name != '\0')
+ {
+ *name = tolower ((unsigned char)*name);
+ ++name;
+ }
+ return filename;
+}
+
+#ifndef _USE_STD_STAT
+/* VMS 8.2 fixed the VMS stat output to have unique st_dev and st_ino
+ when _USE_STD_STAT is used on the compile line.
+
+ Prior to _USE_STD_STAT support, the st_dev is a pointer to thread
+ static memory containing the device of the last filename looked up.
+
+ Todo: find out if the ino_t still needs to be faked on a directory.
+ */
+
+/* Define this if the older VMS_INO_T is needed */
+#define VMS_INO_T 1
+
+static int
+vms_hash (const char *name)
+{
+ int h = 0;
+
+ while (*name)
+ {
+ unsigned char uc = *name;
+ int g;
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ h = (h << 4) + (isupper (uc) ? tolower (uc) : uc);
+#else
+ h = (h << 4) + uc;
+#endif
+ name++;
+ g = h & 0xf0000000;
+ if (g)
+ {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ return h;
+}
+
+/* fake stat entry for a directory */
+static int
+vmsstat_dir (const char *name, struct stat *st)
+{
+ char *s;
+ int h;
+ DIR *dir;
+
+ dir = opendir (name);
+ if (dir == 0)
+ return -1;
+ closedir (dir);
+ s = strchr (name, ':'); /* find device */
+ if (s)
+ {
+ /* to keep the compiler happy we said "const char *name", now we cheat */
+ *s++ = 0;
+ st->st_dev = (char *)vms_hash (name);
+ h = vms_hash (s);
+ *(s-1) = ':';
+ }
+ else
+ {
+ st->st_dev = 0;
+ h = vms_hash (name);
+ }
+
+ st->st_ino[0] = h & 0xff;
+ st->st_ino[1] = h & 0xff00;
+ st->st_ino[2] = h >> 16;
+
+ return 0;
+}
+
+# define stat(__path, __sbuf) vmsstat_dir (__path, __sbuf)
+
+#endif /* _USE_STD_STAT */
+#endif /* VMS */
+
+/* Hash table of directories. */
+
+#ifndef DIRECTORY_BUCKETS
+#ifdef KMK
+# define DIRECTORY_BUCKETS 4096
+# else
+# define DIRECTORY_BUCKETS 199
+# endif
+#endif
+
+struct directory_contents
+ {
+ dev_t dev; /* Device and inode numbers of this dir. */
+#ifdef WINDOWS32
+ /* Inode means nothing on WINDOWS32. Even file key information is
+ * unreliable because it is random per file open and undefined for remote
+ * filesystems. The most unique attribute I can come up with is the fully
+ * qualified name of the directory. Beware though, this is also
+ * unreliable. I'm open to suggestion on a better way to emulate inode. */
+# ifndef CONFIG_WITH_STRCACHE2
+ char *path_key;
+# else
+ char const *path_key; /* strcache'ed */
+# endif
+ time_t ctime;
+ time_t mtime; /* controls check for stale directory cache */
+ int fs_flags; /* FS_FAT, FS_NTFS, ... */
+# define FS_FAT 0x1
+# define FS_NTFS 0x2
+# define FS_UNKNOWN 0x4
+# ifdef KMK
+ time_t last_updated; /**< The last time the directory was re-read. */
+# endif
+#else
+# ifdef VMS_INO_T
+ ino_t ino[3];
+# else
+ ino_t ino;
+# endif
+#endif /* WINDOWS32 */
+ struct hash_table dirfiles; /* Files in this directory. */
+ DIR *dirstream; /* Stream reading this directory. */
+ };
+
+static unsigned long
+directory_contents_hash_1 (const void *key_0)
+{
+ const struct directory_contents *key = key_0;
+ unsigned long hash;
+
+#ifdef WINDOWS32
+# ifndef CONFIG_WITH_STRCACHE2
+ hash = 0;
+ ISTRING_HASH_1 (key->path_key, hash);
+# else /* CONFIG_WITH_STRCACHE2 */
+ hash = strcache2_calc_ptr_hash (&file_strcache, key->path_key);
+# endif /* CONFIG_WITH_STRCACHE2 */
+ hash ^= ((unsigned int) key->dev << 4) ^ (unsigned int) key->ctime;
+#else
+# ifdef VMS_INO_T
+ hash = (((unsigned int) key->dev << 4)
+ ^ ((unsigned int) key->ino[0]
+ + (unsigned int) key->ino[1]
+ + (unsigned int) key->ino[2]));
+# else
+ hash = ((unsigned int) key->dev << 4) ^ (unsigned int) key->ino;
+# endif
+#endif /* WINDOWS32 */
+ return hash;
+}
+
+static unsigned long
+directory_contents_hash_2 (const void *key_0)
+{
+ const struct directory_contents *key = key_0;
+ unsigned long hash;
+
+#ifdef WINDOWS32
+# ifndef CONFIG_WITH_STRCACHE2
+ hash = 0;
+ ISTRING_HASH_2 (key->path_key, hash);
+# else /* CONFIG_WITH_STRCACHE2 */
+ hash = strcache2_get_hash (&file_strcache, key->path_key);
+# endif /* CONFIG_WITH_STRCACHE2 */
+ hash ^= ((unsigned int) key->dev << 4) ^ (unsigned int) ~key->ctime;
+#else
+# ifdef VMS_INO_T
+ hash = (((unsigned int) key->dev << 4)
+ ^ ~((unsigned int) key->ino[0]
+ + (unsigned int) key->ino[1]
+ + (unsigned int) key->ino[2]));
+# else
+ hash = ((unsigned int) key->dev << 4) ^ (unsigned int) ~key->ino;
+# endif
+#endif /* WINDOWS32 */
+
+ return hash;
+}
+
+/* Sometimes it's OK to use subtraction to get this value:
+ result = X - Y;
+ But, if we're not sure of the type of X and Y they may be too large for an
+ int (on a 64-bit system for example). So, use ?: instead.
+ See Savannah bug #15534.
+
+ NOTE! This macro has side-effects!
+*/
+
+#define MAKECMP(_x,_y) ((_x)<(_y)?-1:((_x)==(_y)?0:1))
+
+static int
+directory_contents_hash_cmp (const void *xv, const void *yv)
+{
+ const struct directory_contents *x = xv;
+ const struct directory_contents *y = yv;
+ int result;
+
+#ifdef WINDOWS32
+# ifndef CONFIG_WITH_STRCACHE2
+ ISTRING_COMPARE (x->path_key, y->path_key, result);
+ if (result)
+ return result;
+# else /* CONFIG_WITH_STRCACHE2 */
+ if (x->path_key != y->path_key)
+ return -1;
+# endif /* CONFIG_WITH_STRCACHE2 */
+ result = MAKECMP(x->ctime, y->ctime);
+ if (result)
+ return result;
+#else
+# ifdef VMS_INO_T
+ result = MAKECMP(x->ino[0], y->ino[0]);
+ if (result)
+ return result;
+ result = MAKECMP(x->ino[1], y->ino[1]);
+ if (result)
+ return result;
+ result = MAKECMP(x->ino[2], y->ino[2]);
+ if (result)
+ return result;
+# else
+ result = MAKECMP(x->ino, y->ino);
+ if (result)
+ return result;
+# endif
+#endif /* WINDOWS32 */
+
+ return MAKECMP(x->dev, y->dev);
+}
+
+/* Table of directory contents hashed by device and inode number. */
+static struct hash_table directory_contents;
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+/* Allocation cache for directory contents. */
+struct alloccache directory_contents_cache;
+#endif
+
+struct directory
+ {
+ const char *name; /* Name of the directory. */
+
+ /* The directory's contents. This data may be shared by several
+ entries in the hash table, which refer to the same directory
+ (identified uniquely by 'dev' and 'ino') under different names. */
+ struct directory_contents *contents;
+ };
+
+#ifndef CONFIG_WITH_STRCACHE2
+static unsigned long
+directory_hash_1 (const void *key)
+{
+ return_ISTRING_HASH_1 (((const struct directory *) key)->name);
+}
+
+static unsigned long
+directory_hash_2 (const void *key)
+{
+ return_ISTRING_HASH_2 (((const struct directory *) key)->name);
+}
+
+static int
+directory_hash_cmp (const void *x, const void *y)
+{
+ return_ISTRING_COMPARE (((const struct directory *) x)->name,
+ ((const struct directory *) y)->name);
+}
+#endif /* !CONFIG_WITH_STRCACHE2 */
+
+/* Table of directories hashed by name. */
+static struct hash_table directories;
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+/* Allocation cache for directories. */
+struct alloccache directories_cache;
+#endif
+
+/* Never have more than this many directories open at once. */
+
+#define MAX_OPEN_DIRECTORIES 10
+
+static unsigned int open_directories = 0;
+
+
+/* Hash table of files in each directory. */
+
+struct dirfile
+ {
+ const char *name; /* Name of the file. */
+ size_t length;
+ short impossible; /* This file is impossible. */
+ };
+
+#ifndef CONFIG_WITH_STRCACHE2
+static unsigned long
+dirfile_hash_1 (const void *key)
+{
+ return_ISTRING_HASH_1 (((struct dirfile const *) key)->name);
+}
+
+static unsigned long
+dirfile_hash_2 (const void *key)
+{
+ return_ISTRING_HASH_2 (((struct dirfile const *) key)->name);
+}
+
+static int
+dirfile_hash_cmp (const void *xv, const void *yv)
+{
+ const struct dirfile *x = xv;
+ const struct dirfile *y = yv;
+ int result = x->length - y->length;
+ if (result)
+ return result;
+ return_ISTRING_COMPARE (x->name, y->name);
+}
+#endif /* !CONFIG_WITH_STRCACHE2 */
+
+#ifndef DIRFILE_BUCKETS
+#define DIRFILE_BUCKETS 107
+#endif
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+/* Allocation cache for dirfiles. */
+struct alloccache dirfile_cache;
+#endif
+
+
+static int dir_contents_file_exists_p (struct directory_contents *dir,
+ const char *filename);
+static struct directory *find_directory (const char *name);
+
+/* Find the directory named NAME and return its 'struct directory'. */
+
+static struct directory *
+find_directory (const char *name)
+{
+ struct directory *dir;
+ struct directory **dir_slot;
+ struct directory dir_key;
+
+#ifndef CONFIG_WITH_STRCACHE2
+ dir_key.name = name;
+ dir_slot = (struct directory **) hash_find_slot (&directories, &dir_key);
+#else
+ const char *p = name + strlen (name);
+# if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
+ dir_key.name = strcache_add_len (downcase(name), p - name);
+# else
+ dir_key.name = strcache_add_len (name, p - name);
+# endif
+ dir_slot = (struct directory **) hash_find_slot_strcached (&directories, &dir_key);
+#endif
+ dir = *dir_slot;
+
+ if (HASH_VACANT (dir))
+ {
+ /* The directory was not found. Create a new entry for it. */
+#ifndef CONFIG_WITH_STRCACHE2
+ const char *p = name + strlen (name);
+#endif
+ struct stat st;
+ int r;
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ dir = xmalloc (sizeof (struct directory));
+#else
+ dir = alloccache_alloc (&directories_cache);
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+#if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
+ /* Todo: Why is this only needed on VMS? */
+ {
+ char *lname = downcase_inplace (xstrdup (name));
+ dir->name = strcache_add_len (lname, p - name);
+ free (lname);
+ }
+#else
+ dir->name = strcache_add_len (name, p - name);
+#endif
+#else /* CONFIG_WITH_STRCACHE2 */
+ dir->name = dir_key.name;
+#endif /* CONFIG_WITH_STRCACHE2 */
+ hash_insert_at (&directories, dir, dir_slot);
+ /* The directory is not in the name hash table.
+ Find its device and inode numbers, and look it up by them. */
+
+#if defined(WINDOWS32)
+ {
+ char tem[MAXPATHLEN], *tstart, *tend;
+
+ /* Remove any trailing slashes. Windows32 stat fails even on
+ valid directories if they end in a slash. */
+ memcpy (tem, name, p - name + 1);
+ tstart = tem;
+ if (tstart[1] == ':')
+ tstart += 2;
+ for (tend = tem + (p - name - 1);
+ tend > tstart && (*tend == '/' || *tend == '\\');
+ tend--)
+ *tend = '\0';
+
+ r = stat (tem, &st);
+ }
+#else
+ EINTRLOOP (r, stat (name, &st));
+#endif
+
+ if (r < 0)
+ {
+ /* Couldn't stat the directory. Mark this by
+ setting the 'contents' member to a nil pointer. */
+ dir->contents = 0;
+ }
+ else
+ {
+ /* Search the contents hash table; device and inode are the key. */
+
+#ifdef WINDOWS32
+ PATH_VAR (w32_fullpath);
+ char *w32_path;
+#endif
+ struct directory_contents *dc;
+ struct directory_contents **dc_slot;
+ struct directory_contents dc_key;
+
+ dc_key.dev = st.st_dev;
+#ifdef WINDOWS32
+ w32_path = unix_slashes_resolved (name, w32_fullpath, GET_PATH_MAX);
+# ifndef CONFIG_WITH_STRCACHE2
+ dc_key.path_key = w32_path; /* = w32ify (name, 1); - bird */
+# else /* CONFIG_WITH_STRCACHE2 */
+ dc_key.path_key = strcache_add (w32_path);
+# endif /* CONFIG_WITH_STRCACHE2 */
+ dc_key.ctime = st.st_ctime;
+#else
+# ifdef VMS_INO_T
+ dc_key.ino[0] = st.st_ino[0];
+ dc_key.ino[1] = st.st_ino[1];
+ dc_key.ino[2] = st.st_ino[2];
+# else
+ dc_key.ino = st.st_ino;
+# endif
+#endif
+ dc_slot = (struct directory_contents **) hash_find_slot (&directory_contents, &dc_key);
+ dc = *dc_slot;
+
+ if (HASH_VACANT (dc))
+ {
+ /* Nope; this really is a directory we haven't seen before. */
+#ifdef WINDOWS32
+ char fs_label[BUFSIZ];
+ char fs_type[BUFSIZ];
+ unsigned long fs_serno;
+ unsigned long fs_flags;
+ unsigned long fs_len;
+#endif
+#if defined(WINDOWS32) && defined(KMK)
+ static char s_last_volume[4];
+ static int s_last_flags;
+#endif
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ dc = (struct directory_contents *)
+ xmalloc (sizeof (struct directory_contents));
+#else
+ dc = (struct directory_contents *)
+ alloccache_alloc (&directory_contents_cache);
+#endif
+
+ /* Enter it in the contents hash table. */
+ dc->dev = st.st_dev;
+#ifdef WINDOWS32
+# ifndef CONFIG_WITH_STRCACHE2
+ dc->path_key = xstrdup (w32_path);
+# else /* CONFIG_WITH_STRCACHE2 */
+ dc->path_key = dc_key.path_key;
+# endif /* CONFIG_WITH_STRCACHE2 */
+
+ dc->ctime = st.st_ctime;
+ dc->mtime = st.st_mtime;
+# ifdef KMK
+ dc->last_updated = time(NULL);
+# endif
+
+ /* NTFS is the only WINDOWS32 filesystem that bumps mtime on a
+ directory when files are added/deleted from a directory. */
+ w32_path[3] = '\0';
+
+# ifdef KMK /* Need for speed: Cache the GetVolumeInformation result. */
+ if ( s_last_volume[0] == w32_path[0]
+ && s_last_volume[1] == w32_path[1]
+ && s_last_volume[2] == w32_path[2]
+ && s_last_volume[3] == w32_path[3])
+ dc->fs_flags = s_last_flags;
+ else
+ {
+# endif
+ if (GetVolumeInformation (w32_path, fs_label, sizeof (fs_label),
+ &fs_serno, &fs_len, &fs_flags, fs_type,
+ sizeof (fs_type)) == FALSE)
+ dc->fs_flags = FS_UNKNOWN;
+ else if (!strcmp (fs_type, "FAT"))
+ dc->fs_flags = FS_FAT;
+ else if (!strcmp (fs_type, "NTFS"))
+ dc->fs_flags = FS_NTFS;
+ else
+ dc->fs_flags = FS_UNKNOWN;
+# ifdef KMK
+ s_last_volume[0] = w32_path[0];
+ s_last_volume[1] = w32_path[1];
+ s_last_volume[2] = w32_path[2];
+ s_last_volume[3] = w32_path[3];
+ s_last_flags = dc->fs_flags;
+# endif
+#else
+# ifdef VMS_INO_T
+ dc->ino[0] = st.st_ino[0];
+ dc->ino[1] = st.st_ino[1];
+ dc->ino[2] = st.st_ino[2];
+# else
+ dc->ino = st.st_ino;
+# endif
+#endif /* WINDOWS32 */
+ hash_insert_at (&directory_contents, dc, dc_slot);
+ ENULLLOOP (dc->dirstream, opendir (name));
+ if (dc->dirstream == 0)
+ /* Couldn't open the directory. Mark this by setting the
+ 'files' member to a nil pointer. */
+ dc->dirfiles.ht_vec = 0;
+ else
+ {
+#ifdef KMK
+ int buckets = st.st_nlink * 2;
+ if (buckets < DIRFILE_BUCKETS)
+ buckets = DIRFILE_BUCKETS;
+ hash_init_strcached (&dc->dirfiles, buckets, &file_strcache,
+ offsetof (struct dirfile, name));
+#else
+# ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&dc->dirfiles, DIRFILE_BUCKETS,
+ dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp);
+# else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&dc->dirfiles, DIRFILE_BUCKETS,
+ &file_strcache,
+ offsetof (struct dirfile, name));
+# endif /* CONFIG_WITH_STRCACHE2 */
+#endif
+ ++open_directories;
+ if (open_directories == MAX_OPEN_DIRECTORIES)
+ /* We have too many directories open already.
+ Read the entire directory and then close it. */
+ dir_contents_file_exists_p (dc, 0);
+ }
+ }
+
+ /* Point the name-hashed entry for DIR at its contents data. */
+ dir->contents = dc;
+ }
+ }
+
+ return dir;
+}
+
+/* Return 1 if the name FILENAME is entered in DIR's hash table.
+ FILENAME must contain no slashes. */
+
+static int
+dir_contents_file_exists_p (struct directory_contents *dir,
+ const char *filename)
+{
+ struct dirfile *df;
+ struct dirent *d;
+#ifdef WINDOWS32
+# ifndef KMK
+ struct stat st;
+# endif
+ int rehash = 0;
+#endif
+#ifdef KMK
+ int ret = 0;
+#endif
+
+ if (dir == 0 || dir->dirfiles.ht_vec == 0)
+ /* The directory could not be stat'd or opened. */
+ return 0;
+
+#ifdef __MSDOS__
+ filename = dosify (filename);
+#endif
+
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ filename = downcase (filename);
+#endif
+
+#ifdef __EMX__
+ if (filename != 0)
+ _fnlwr (filename); /* lower case for FAT drives */
+#endif
+ if (filename != 0)
+ {
+ struct dirfile dirfile_key;
+
+ if (*filename == '\0')
+ {
+ /* Checking if the directory exists. */
+ return 1;
+ }
+#ifndef CONFIG_WITH_STRCACHE2
+ dirfile_key.name = filename;
+ dirfile_key.length = strlen (filename);
+ df = hash_find_item (&dir->dirfiles, &dirfile_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ dirfile_key.length = strlen (filename);
+ dirfile_key.name = filename
+ = strcache_add_len (filename, dirfile_key.length);
+ df = hash_find_item_strcached (&dir->dirfiles, &dirfile_key);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ if (df)
+ return !df->impossible;
+ }
+
+ /* The file was not found in the hashed list.
+ Try to read the directory further. */
+
+ if (dir->dirstream == 0)
+ {
+#if defined(WINDOWS32) && !defined(KMK)
+ /*
+ * Check to see if directory has changed since last read. FAT
+ * filesystems force a rehash always as mtime does not change
+ * on directories (ugh!).
+ */
+# ifdef KMK
+ if (dir->path_key && time(NULL) > dc->last_updated + 2) /* KMK: Only recheck every 2 seconds. */
+# else
+ if (dir->path_key)
+# endif
+ {
+ if ((dir->fs_flags & FS_FAT) != 0)
+ {
+ dir->mtime = time ((time_t *) 0);
+ rehash = 1;
+ }
+# ifdef KMK
+ else if ( birdStatModTimeOnly (dir->path_key, &st.st_mtim, 1) == 0
+ && st.st_mtime > dir->mtime)
+# else
+ else if (stat (dir->path_key, &st) == 0 && st.st_mtime > dir->mtime)
+# endif
+ {
+ /* reset date stamp to show most recent re-process. */
+ dir->mtime = st.st_mtime;
+ rehash = 1;
+ }
+
+
+ /* If it has been already read in, all done. */
+ if (!rehash)
+ return 0;
+
+ /* make sure directory can still be opened; if not return. */
+ dir->dirstream = opendir (dir->path_key);
+ if (!dir->dirstream)
+ return 0;
+# ifdef KMK
+ dc->last_updated = time(NULL);
+# endif
+ }
+ else
+#endif
+ /* The directory has been all read in. */
+ return 0;
+ }
+
+ while (1)
+ {
+ /* Enter the file in the hash table. */
+ unsigned int len;
+ struct dirfile dirfile_key;
+ struct dirfile **dirfile_slot;
+
+ ENULLLOOP (d, readdir (dir->dirstream));
+ if (d == 0)
+ {
+/* bird: Workaround for smbfs mounts returning EBADF at the end of the search.
+ To exactly determin the cause here, I should probably do some smbfs
+ tracing, but for now just ignoring the EBADF on seems to work.
+ (The smb server is 64-bit vista, btw.) */
+#if defined (__FreeBSD__)
+ struct statfs stfs;
+ int saved_errno = errno;
+ errno = 0;
+ if (saved_errno == EBADF
+ && !fstatfs (dirfd (dir->dirstream), &stfs)
+ && !(stfs.f_flags & MNT_LOCAL)
+ && !strcmp(stfs.f_fstypename, "smbfs"))
+ {
+ /*fprintf (stderr, "EBADF on remote fs! dirfd=%d errno=%d\n",
+ dirfd (dir->dirstream), errno);*/
+ saved_errno = 0;
+ }
+ errno = saved_errno;
+#endif
+/* bird: end */
+ if (errno)
+ pfatal_with_name ("INTERNAL: readdir");
+ break;
+ }
+
+#if defined(VMS) && defined(HAVE_DIRENT_H)
+ /* In VMS we get file versions too, which have to be stripped off.
+ Some versions of VMS return versions on Unix files even when
+ the feature option to strip them is set. */
+ {
+ char *p = strrchr (d->d_name, ';');
+ if (p)
+ *p = '\0';
+ }
+#endif
+ if (!REAL_DIR_ENTRY (d))
+ continue;
+
+ len = NAMLEN (d);
+#ifndef CONFIG_WITH_STRCACHE2
+ dirfile_key.name = d->d_name;
+ dirfile_key.length = len;
+ dirfile_slot = (struct dirfile **) hash_find_slot (&dir->dirfiles, &dirfile_key);
+#else
+# if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
+ dirfile_key.name = strcache_add_len (downcase(d->d_name), len);
+# else
+ dirfile_key.name = strcache_add_len (d->d_name, len);
+# endif
+ dirfile_key.length = len;
+ dirfile_slot = (struct dirfile **) hash_find_slot_strcached (&dir->dirfiles, &dirfile_key);
+#endif
+#ifdef WINDOWS32
+ /*
+ * If re-reading a directory, don't cache files that have
+ * already been discovered.
+ */
+ if (! rehash || HASH_VACANT (*dirfile_slot))
+#endif
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ df = xmalloc (sizeof (struct dirfile));
+#else
+ df = alloccache_alloc (&dirfile_cache);
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+#if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
+ /* TODO: Why is this only needed on VMS? */
+ df->name = strcache_add_len (downcase_inplace (d->d_name), len);
+#else
+ df->name = strcache_add_len (d->d_name, len);
+#endif
+#else /* CONFIG_WITH_STRCACHE2 */
+ df->name = dirfile_key.name;
+#endif /* CONFIG_WITH_STRCACHE2 */
+ df->length = len;
+ df->impossible = 0;
+ hash_insert_at (&dir->dirfiles, df, dirfile_slot);
+ }
+ /* Check if the name matches the one we're searching for. */
+#ifndef CONFIG_WITH_STRCACHE2
+ if (filename != 0 && patheq (d->d_name, filename))
+#else
+ if (filename != 0 && dirfile_key.name == filename)
+#endif
+#ifdef KMK
+ ret = 1; /* Cache the whole dir. Prevents trouble on windows and os2 during 'rebuild'. */
+#else
+ return 1;
+#endif
+ }
+
+ /* If the directory has been completely read in,
+ close the stream and reset the pointer to nil. */
+ if (d == 0)
+ {
+ --open_directories;
+ closedir (dir->dirstream);
+ dir->dirstream = 0;
+ }
+#ifdef KMK
+ return ret;
+#else
+ return 0;
+#endif
+}
+
+/* Return 1 if the name FILENAME in directory DIRNAME
+ is entered in the dir hash table.
+ FILENAME must contain no slashes. */
+
+int
+dir_file_exists_p (const char *dirname, const char *filename)
+{
+#ifdef VMS
+ if ((filename != NULL) && (dirname != NULL))
+ {
+ int want_vmsify;
+ want_vmsify = (strpbrk (dirname, ":<[") != NULL);
+ if (want_vmsify)
+ filename = vmsify (filename, 0);
+ }
+#endif
+ return dir_contents_file_exists_p (find_directory (dirname)->contents,
+ filename);
+}
+
+/* Return 1 if the file named NAME exists. */
+
+int
+file_exists_p (const char *name)
+{
+ const char *dirend;
+ const char *dirname;
+ const char *slash;
+
+#ifndef NO_ARCHIVES
+ if (ar_name (name))
+ return ar_member_date (name) != (time_t) -1;
+#endif
+
+ dirend = strrchr (name, '/');
+#ifdef VMS
+ if (dirend == 0)
+ {
+ dirend = strrchr (name, ']');
+ dirend == NULL ? dirend : dirend++;
+ }
+ if (dirend == 0)
+ {
+ dirend = strrchr (name, '>');
+ dirend == NULL ? dirend : dirend++;
+ }
+ if (dirend == 0)
+ {
+ dirend = strrchr (name, ':');
+ dirend == NULL ? dirend : dirend++;
+ }
+#endif /* VMS */
+#ifdef HAVE_DOS_PATHS
+ /* Forward and backslashes might be mixed. We need the rightmost one. */
+ {
+ const char *bslash = strrchr (name, '\\');
+ if (!dirend || bslash > dirend)
+ dirend = bslash;
+ /* The case of "d:file". */
+ if (!dirend && name[0] && name[1] == ':')
+ dirend = name + 1;
+ }
+#endif /* HAVE_DOS_PATHS */
+ if (dirend == 0)
+#ifndef _AMIGA
+ return dir_file_exists_p (".", name);
+#else /* !AMIGA */
+ return dir_file_exists_p ("", name);
+#endif /* AMIGA */
+
+ slash = dirend;
+ if (dirend == name)
+ dirname = "/";
+ else
+ {
+ char *p;
+#ifdef HAVE_DOS_PATHS
+ /* d:/ and d: are *very* different... */
+ if (dirend < name + 3 && name[1] == ':' &&
+ (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+ dirend++;
+#endif
+ p = alloca (dirend - name + 1);
+ memcpy (p, name, dirend - name);
+ p[dirend - name] = '\0';
+ dirname = p;
+ }
+#ifdef VMS
+ if (*slash == '/')
+ slash++;
+#else
+ slash++;
+#endif
+ return dir_file_exists_p (dirname, slash);
+}
+
+/* Mark FILENAME as 'impossible' for 'file_impossible_p'.
+ This means an attempt has been made to search for FILENAME
+ as an intermediate file, and it has failed. */
+
+void
+file_impossible (const char *filename)
+{
+ const char *dirend;
+ const char *p = filename;
+ struct directory *dir;
+ struct dirfile *new;
+
+ dirend = strrchr (p, '/');
+#ifdef VMS
+ if (dirend == NULL)
+ {
+ dirend = strrchr (p, ']');
+ dirend == NULL ? dirend : dirend++;
+ }
+ if (dirend == NULL)
+ {
+ dirend = strrchr (p, '>');
+ dirend == NULL ? dirend : dirend++;
+ }
+ if (dirend == NULL)
+ {
+ dirend = strrchr (p, ':');
+ dirend == NULL ? dirend : dirend++;
+ }
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* Forward and backslashes might be mixed. We need the rightmost one. */
+ {
+ const char *bslash = strrchr (p, '\\');
+ if (!dirend || bslash > dirend)
+ dirend = bslash;
+ /* The case of "d:file". */
+ if (!dirend && p[0] && p[1] == ':')
+ dirend = p + 1;
+ }
+#endif /* HAVE_DOS_PATHS */
+ if (dirend == 0)
+#ifdef _AMIGA
+ dir = find_directory ("");
+#else /* !AMIGA */
+ dir = find_directory (".");
+#endif /* AMIGA */
+ else
+ {
+ const char *dirname;
+ const char *slash = dirend;
+ if (dirend == p)
+ dirname = "/";
+ else
+ {
+ char *cp;
+#ifdef HAVE_DOS_PATHS
+ /* d:/ and d: are *very* different... */
+ if (dirend < p + 3 && p[1] == ':' &&
+ (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+ dirend++;
+#endif
+ cp = alloca (dirend - p + 1);
+ memcpy (cp, p, dirend - p);
+ cp[dirend - p] = '\0';
+ dirname = cp;
+ }
+ dir = find_directory (dirname);
+#ifdef VMS
+ if (*slash == '/')
+ filename = p = slash + 1;
+ else
+ filename = p = slash;
+#else
+ filename = p = slash + 1;
+#endif
+ }
+
+ if (dir->contents == 0)
+ /* The directory could not be stat'd. We allocate a contents
+ structure for it, but leave it out of the contents hash table. */
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ dir->contents = xcalloc (sizeof (struct directory_contents));
+#else
+ dir->contents = alloccache_calloc (&directory_contents_cache);
+#endif
+
+ if (dir->contents->dirfiles.ht_vec == 0)
+ {
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&dir->contents->dirfiles, DIRFILE_BUCKETS,
+ dirfile_hash_1, dirfile_hash_2, dirfile_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&dir->contents->dirfiles, DIRFILE_BUCKETS,
+ &file_strcache, offsetof (struct dirfile, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+ }
+
+ /* Make a new entry and put it in the table. */
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ new = xmalloc (sizeof (struct dirfile));
+#else
+ new = alloccache_alloc (&dirfile_cache);
+#endif
+ new->length = strlen (filename);
+#if defined(HAVE_CASE_INSENSITIVE_FS) && defined(VMS)
+ /* todo: Why is this only needed on VMS? */
+ new->name = strcache_add_len (downcase (filename), new->length);
+#else
+ new->name = strcache_add_len (filename, new->length);
+#endif
+ new->impossible = 1;
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_insert (&dir->contents->dirfiles, new);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_insert_strcached (&dir->contents->dirfiles, new);
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+/* Return nonzero if FILENAME has been marked impossible. */
+
+int
+file_impossible_p (const char *filename)
+{
+ const char *dirend;
+ struct directory_contents *dir;
+ struct dirfile *dirfile;
+ struct dirfile dirfile_key;
+#ifdef VMS
+ int want_vmsify = 0;
+#endif
+
+ dirend = strrchr (filename, '/');
+#ifdef VMS
+ if (dirend == NULL)
+ {
+ want_vmsify = (strpbrk (filename, "]>:^") != NULL);
+ dirend = strrchr (filename, ']');
+ }
+ if (dirend == NULL && want_vmsify)
+ dirend = strrchr (filename, '>');
+ if (dirend == NULL && want_vmsify)
+ dirend = strrchr (filename, ':');
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* Forward and backslashes might be mixed. We need the rightmost one. */
+ {
+ const char *bslash = strrchr (filename, '\\');
+ if (!dirend || bslash > dirend)
+ dirend = bslash;
+ /* The case of "d:file". */
+ if (!dirend && filename[0] && filename[1] == ':')
+ dirend = filename + 1;
+ }
+#endif /* HAVE_DOS_PATHS */
+ if (dirend == 0)
+#ifdef _AMIGA
+ dir = find_directory ("")->contents;
+#else /* !AMIGA */
+ dir = find_directory (".")->contents;
+#endif /* AMIGA */
+ else
+ {
+ const char *dirname;
+ const char *slash = dirend;
+ if (dirend == filename)
+ dirname = "/";
+ else
+ {
+ char *cp;
+#ifdef HAVE_DOS_PATHS
+ /* d:/ and d: are *very* different... */
+ if (dirend < filename + 3 && filename[1] == ':' &&
+ (*dirend == '/' || *dirend == '\\' || *dirend == ':'))
+ dirend++;
+#endif
+ cp = alloca (dirend - filename + 1);
+ memcpy (cp, filename, dirend - filename);
+ cp[dirend - filename] = '\0';
+ dirname = cp;
+ }
+ dir = find_directory (dirname)->contents;
+#ifdef VMS
+ if (*slash == '/')
+ filename = slash + 1;
+ else
+ filename = slash;
+#else
+ filename = slash + 1;
+#endif
+ }
+
+ if (dir == 0 || dir->dirfiles.ht_vec == 0)
+ /* There are no files entered for this directory. */
+ return 0;
+
+#ifdef __MSDOS__
+ filename = dosify (filename);
+#endif
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ filename = downcase (filename);
+#endif
+#ifdef VMS
+ if (want_vmsify)
+ filename = vmsify (filename, 1);
+#endif
+
+#ifndef CONFIG_WITH_STRCACHE2
+ dirfile_key.name = filename;
+ dirfile_key.length = strlen (filename);
+ dirfile = hash_find_item (&dir->dirfiles, &dirfile_key);
+#else
+ dirfile_key.length = strlen (filename);
+ dirfile_key.name = strcache_add_len (filename, dirfile_key.length);
+ dirfile = hash_find_item_strcached (&dir->dirfiles, &dirfile_key);
+#endif
+ if (dirfile)
+ return dirfile->impossible;
+
+ return 0;
+}
+
+/* Return the already allocated name in the
+ directory hash table that matches DIR. */
+
+const char *
+dir_name (const char *dir)
+{
+ return find_directory (dir)->name;
+}
+
+/* Print the data base of directories. */
+
+void
+print_dir_data_base (void)
+{
+ unsigned int files;
+ unsigned int impossible;
+ struct directory **dir_slot;
+ struct directory **dir_end;
+
+ puts (_("\n# Directories\n"));
+
+ files = impossible = 0;
+
+ dir_slot = (struct directory **) directories.ht_vec;
+ dir_end = dir_slot + directories.ht_size;
+ for ( ; dir_slot < dir_end; dir_slot++)
+ {
+ struct directory *dir = *dir_slot;
+ if (! HASH_VACANT (dir))
+ {
+ if (dir->contents == 0)
+ printf (_("# %s: could not be stat'd.\n"), dir->name);
+ else if (dir->contents->dirfiles.ht_vec == 0)
+ {
+#ifdef WINDOWS32
+ printf (_("# %s (key %s, mtime %I64u): could not be opened.\n"),
+ dir->name, dir->contents->path_key,
+ (unsigned long long)dir->contents->mtime);
+#else /* WINDOWS32 */
+#ifdef VMS_INO_T
+ printf (_("# %s (device %d, inode [%d,%d,%d]): could not be opened.\n"),
+ dir->name, dir->contents->dev,
+ dir->contents->ino[0], dir->contents->ino[1],
+ dir->contents->ino[2]);
+#else
+ printf (_("# %s (device %ld, inode %ld): could not be opened.\n"),
+ dir->name, (long int) dir->contents->dev,
+ (long int) dir->contents->ino);
+#endif
+#endif /* WINDOWS32 */
+ }
+ else
+ {
+ unsigned int f = 0;
+ unsigned int im = 0;
+ struct dirfile **files_slot;
+ struct dirfile **files_end;
+
+ files_slot = (struct dirfile **) dir->contents->dirfiles.ht_vec;
+ files_end = files_slot + dir->contents->dirfiles.ht_size;
+ for ( ; files_slot < files_end; files_slot++)
+ {
+ struct dirfile *df = *files_slot;
+ if (! HASH_VACANT (df))
+ {
+ if (df->impossible)
+ ++im;
+ else
+ ++f;
+ }
+ }
+#ifdef WINDOWS32
+ printf (_("# %s (key %s, mtime %I64u): "),
+ dir->name, dir->contents->path_key,
+ (unsigned long long)dir->contents->mtime);
+#else /* WINDOWS32 */
+#ifdef VMS_INO_T
+ printf (_("# %s (device %d, inode [%d,%d,%d]): "),
+ dir->name, dir->contents->dev,
+ dir->contents->ino[0], dir->contents->ino[1],
+ dir->contents->ino[2]);
+#else
+ printf (_("# %s (device %ld, inode %ld): "),
+ dir->name,
+ (long)dir->contents->dev, (long)dir->contents->ino);
+#endif
+#endif /* WINDOWS32 */
+ if (f == 0)
+ fputs (_("No"), stdout);
+ else
+ printf ("%u", f);
+ fputs (_(" files, "), stdout);
+ if (im == 0)
+ fputs (_("no"), stdout);
+ else
+ printf ("%u", im);
+ fputs (_(" impossibilities"), stdout);
+ if (dir->contents->dirstream == 0)
+ puts (".");
+ else
+ puts (_(" so far."));
+ files += f;
+ impossible += im;
+#ifdef KMK
+ fputs ("# ", stdout);
+ hash_print_stats (&dir->contents->dirfiles, stdout);
+ fputs ("\n", stdout);
+#endif
+ }
+ }
+ }
+
+ fputs ("\n# ", stdout);
+ if (files == 0)
+ fputs (_("No"), stdout);
+ else
+ printf ("%u", files);
+ fputs (_(" files, "), stdout);
+ if (impossible == 0)
+ fputs (_("no"), stdout);
+ else
+ printf ("%u", impossible);
+ printf (_(" impossibilities in %lu directories.\n"), directories.ht_fill);
+#ifdef KMK
+ fputs ("# directories: ", stdout);
+ hash_print_stats (&directories, stdout);
+ fputs ("\n# directory_contents: ", stdout);
+ hash_print_stats (&directory_contents, stdout);
+ fputs ("\n", stdout);
+#endif
+}
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+/* Print stats */
+
+void print_dir_stats (void)
+{
+ /** @todo normal dir stats. */
+}
+#endif
+
+/* Hooks for globbing. */
+
+/* Structure describing state of iterating through a directory hash table. */
+
+struct dirstream
+ {
+ struct directory_contents *contents; /* The directory being read. */
+ struct dirfile **dirfile_slot; /* Current slot in table. */
+ };
+
+/* Forward declarations. */
+static __ptr_t open_dirstream (const char *);
+static struct dirent *read_dirstream (__ptr_t);
+
+static __ptr_t
+open_dirstream (const char *directory)
+{
+ struct dirstream *new;
+ struct directory *dir = find_directory (directory);
+
+ if (dir->contents == 0 || dir->contents->dirfiles.ht_vec == 0)
+ /* DIR->contents is nil if the directory could not be stat'd.
+ DIR->contents->dirfiles is nil if it could not be opened. */
+ return 0;
+
+ /* Read all the contents of the directory now. There is no benefit
+ in being lazy, since glob will want to see every file anyway. */
+
+ dir_contents_file_exists_p (dir->contents, 0);
+
+ new = xmalloc (sizeof (struct dirstream));
+ new->contents = dir->contents;
+ new->dirfile_slot = (struct dirfile **) new->contents->dirfiles.ht_vec;
+
+ return (__ptr_t) new;
+}
+
+static struct dirent *
+read_dirstream (__ptr_t stream)
+{
+ static char *buf;
+ static unsigned int bufsz;
+
+ struct dirstream *const ds = (struct dirstream *) stream;
+ struct directory_contents *dc = ds->contents;
+ struct dirfile **dirfile_end = (struct dirfile **) dc->dirfiles.ht_vec + dc->dirfiles.ht_size;
+
+ while (ds->dirfile_slot < dirfile_end)
+ {
+ struct dirfile *df = *ds->dirfile_slot++;
+ if (! HASH_VACANT (df) && !df->impossible)
+ {
+ /* The glob interface wants a 'struct dirent', so mock one up. */
+ struct dirent *d;
+ unsigned int len = df->length + 1;
+ unsigned int sz = sizeof (*d) - sizeof (d->d_name) + len;
+ if (sz > bufsz)
+ {
+ bufsz *= 2;
+ if (sz > bufsz)
+ bufsz = sz;
+ buf = xrealloc (buf, bufsz);
+ }
+ d = (struct dirent *) buf;
+#ifdef __MINGW32__
+# if __MINGW32_MAJOR_VERSION < 3 || (__MINGW32_MAJOR_VERSION == 3 && \
+ __MINGW32_MINOR_VERSION == 0)
+ d->d_name = xmalloc (len);
+# endif
+#endif
+ FAKE_DIR_ENTRY (d);
+#ifdef _DIRENT_HAVE_D_NAMLEN
+ d->d_namlen = len - 1;
+#endif
+#ifdef _DIRENT_HAVE_D_TYPE
+ d->d_type = DT_UNKNOWN;
+#endif
+ memcpy (d->d_name, df->name, len);
+ return d;
+ }
+ }
+
+ return 0;
+}
+
+/* On 64 bit ReliantUNIX (5.44 and above) in LFS mode, stat() is actually a
+ * macro for stat64(). If stat is a macro, make a local wrapper function to
+ * invoke it.
+ *
+ * On MS-Windows, stat() "succeeds" for foo/bar/. where foo/bar is a
+ * regular file; fix that here.
+ */
+#if !defined(stat) && !defined(WINDOWS32) || defined(VMS)
+# ifndef VMS
+# ifndef HAVE_SYS_STAT_H
+int stat (const char *path, struct stat *sbuf);
+# endif
+# else
+ /* We are done with the fake stat. Go back to the real stat */
+# ifdef stat
+# undef stat
+# endif
+# endif
+# define local_stat stat
+#else
+static int
+local_stat (const char *path, struct stat *buf)
+{
+ int e;
+#ifdef WINDOWS32
+ size_t plen = strlen (path);
+
+ /* Make sure the parent of "." exists and is a directory, not a
+ file. This is because 'stat' on Windows normalizes the argument
+ foo/. => foo without checking first that foo is a directory. */
+ if (plen > 1 && path[plen - 1] == '.'
+ && (path[plen - 2] == '/' || path[plen - 2] == '\\'))
+ {
+ char parent[MAXPATHLEN];
+
+ strncpy (parent, path, plen - 2);
+ parent[plen - 2] = '\0';
+ if (stat (parent, buf) < 0 || !_S_ISDIR (buf->st_mode))
+ return -1;
+ }
+#endif
+
+ EINTRLOOP (e, stat (path, buf));
+ return e;
+}
+#endif
+
+#ifdef KMK
+static int dir_exists_p (const char *dirname)
+{
+ if (file_exists_p (dirname))
+ {
+ struct directory *dir = find_directory (dirname);
+ if (dir != NULL && dir->contents && dir->contents->dirfiles.ht_vec != NULL)
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+void
+dir_setup_glob (glob_t *gl)
+{
+ gl->gl_opendir = open_dirstream;
+ gl->gl_readdir = read_dirstream;
+ gl->gl_closedir = free;
+ gl->gl_stat = local_stat;
+#ifdef __EMX__ /* The FreeBSD implementation actually uses gl_lstat!! */
+ gl->gl_lstat = local_stat;
+#endif
+#ifdef GLOB_WITH_EXTENDED_KMK_MEMBERS
+ gl->gl_exists = file_exists_p;
+ gl->gl_isdir = dir_exists_p;
+#endif
+ /* We don't bother setting gl_lstat, since glob never calls it.
+ The slot is only there for compatibility with 4.4 BSD. */
+}
+
+void
+hash_init_directories (void)
+{
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&directories, DIRECTORY_BUCKETS,
+ directory_hash_1, directory_hash_2, directory_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&directories, DIRECTORY_BUCKETS, &file_strcache,
+ offsetof (struct directory, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+ hash_init (&directory_contents, DIRECTORY_BUCKETS,
+ directory_contents_hash_1, directory_contents_hash_2,
+ directory_contents_hash_cmp);
+#ifdef CONFIG_WITH_ALLOC_CACHES
+ alloccache_init (&directories_cache, sizeof (struct directory),
+ "directories", NULL, NULL);
+ alloccache_init (&directory_contents_cache, sizeof (struct directory_contents),
+ "directory_contents", NULL, NULL);
+ alloccache_init (&dirfile_cache, sizeof (struct dirfile),
+ "dirfile", NULL, NULL);
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+}
+
diff --git a/src/kmk/doc/.gitignore b/src/kmk/doc/.gitignore
new file mode 100644
index 0000000..ca68d2d
--- /dev/null
+++ b/src/kmk/doc/.gitignore
@@ -0,0 +1,22 @@
+manual/
+gendocs_template
+fdl.texi
+make-stds.texi
+stamp-vti
+version.texi
+make.info*
+make*.html
+make.aux
+make.cp
+make.cps
+make.dvi
+make.fn
+make.fns
+make.ky
+make.log
+make.pdf
+make.pg
+make.ps
+make.toc
+make.tp
+make.vr
diff --git a/src/kmk/doc/Makefile.am b/src/kmk/doc/Makefile.am
new file mode 100644
index 0000000..11aa4d4
--- /dev/null
+++ b/src/kmk/doc/Makefile.am
@@ -0,0 +1,24 @@
+# -*-Makefile-*-, or close enough
+# Copyright (C) 2000-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+TEXI2HTML = texi2html
+TEXI2HTML_FLAGS = -split_chapter
+
+info_TEXINFOS = make.texi
+make_TEXINFOS = fdl.texi make-stds.texi
+
+CLEANFILES = make*.html
diff --git a/src/kmk/dosbuild.bat b/src/kmk/dosbuild.bat
new file mode 100644
index 0000000..71e71e1
--- /dev/null
+++ b/src/kmk/dosbuild.bat
@@ -0,0 +1,65 @@
+@echo off
+rem Copyright (C) 1998-2016 Free Software Foundation, Inc.
+rem This file is part of GNU Make.
+rem
+rem GNU Make is free software; you can redistribute it and/or modify it under
+rem the terms of the GNU General Public License as published by the Free
+rem Software Foundation; either version 3 of the License, or (at your option)
+rem any later version.
+rem
+rem GNU Make is distributed in the hope that it will be useful, but WITHOUT
+rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+rem more details.
+rem
+rem You should have received a copy of the GNU General Public License along
+rem with this program. If not, see <http://www.gnu.org/licenses/>.
+
+echo Building Make for MSDOS
+
+rem Echo ON so they will see what is going on.
+@echo on
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g commands.c -o commands.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g output.c -o output.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g job.c -o job.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g dir.c -o dir.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g file.c -o file.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g misc.c -o misc.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g main.c -o main.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -DINCLUDEDIR=\"c:/djgpp/include\" -O2 -g read.c -o read.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -DLIBDIR=\"c:/djgpp/lib\" -O2 -g remake.c -o remake.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g rule.c -o rule.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g implicit.c -o implicit.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g default.c -o default.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g variable.c -o variable.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g expand.c -o expand.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g function.c -o function.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g vpath.c -o vpath.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g hash.c -o hash.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g strcache.c -o strcache.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g version.c -o version.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g ar.c -o ar.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g arscan.c -o arscan.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g signame.c -o signame.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g remote-stub.c -o remote-stub.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g getopt.c -o getopt.o
+gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g getopt1.c -o getopt1.o
+@cd glob
+@if exist libglob.a del libglob.a
+gcc -I. -c -DHAVE_CONFIG_H -I.. -O2 -g glob.c -o glob.o
+gcc -I. -c -DHAVE_CONFIG_H -I.. -O2 -g fnmatch.c -o fnmatch.o
+ar rv libglob.a glob.o fnmatch.o
+@echo off
+cd ..
+echo commands.o > respf.$$$
+for %%f in (job output dir file misc main read remake rule implicit default variable) do echo %%f.o >> respf.$$$
+for %%f in (expand function vpath hash strcache version ar arscan signame remote-stub getopt getopt1) do echo %%f.o >> respf.$$$
+echo glob/libglob.a >> respf.$$$
+rem gcc -c -I. -I./glob -DHAVE_CONFIG_H -O2 -g guile.c -o guile.o
+rem echo guile.o >> respf.$$$
+@echo Linking...
+@echo on
+gcc -o make.new @respf.$$$
+@if exist make.exe echo Make.exe is now built!
+@if not exist make.exe echo Make.exe build failed...
+@if exist make.exe del respf.$$$
diff --git a/src/kmk/electric.c b/src/kmk/electric.c
new file mode 100644
index 0000000..b033073
--- /dev/null
+++ b/src/kmk/electric.c
@@ -0,0 +1,220 @@
+/* $Id: electric.c 2798 2015-09-19 20:35:03Z bird $ */
+/** @file
+ * A simple electric heap implementation.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef ELECTRIC_HEAP
+
+# ifdef WINDOWS32
+# include <windows.h>
+# else
+# include <sys/mman.h>
+# include <errno.h>
+# include <stdint.h>
+# endif
+# include <string.h>
+# include <stdlib.h>
+# include <stdio.h>
+
+
+# define FREED_ENTRIES 512
+static struct
+{
+ void *ptr;
+ unsigned aligned;
+} freed[FREED_ENTRIES];
+static unsigned freed_head = 0;
+static unsigned freed_tail = 0;
+
+
+static void fatal_error (const char *msg)
+{
+#ifdef _MSC_VER
+ fprintf (stderr, "electric heap error: %s\n", msg);
+ __debugbreak ();
+#else
+ fprintf (stderr, "electric heap error: %s (errno=%d)\n", msg, errno);
+ __asm__ ("int3"); /* not portable... */
+#endif
+ abort ();
+ exit (1);
+}
+
+static void free_it (void *ptr, unsigned aligned)
+{
+# ifdef WINDOWS32
+ if (!VirtualFree (ptr, 0, MEM_RELEASE))
+ fatal_error ("VirtualFree failed");
+# else
+ if (munmap(ptr, aligned))
+ fatal_error ("munmap failed");
+# endif
+}
+
+/* Return 1 if something was freed, 0 otherwise. */
+static int free_up_some (void)
+{
+ if (freed_tail == freed_head)
+ return 0;
+ free_it (freed[freed_tail].ptr, freed[freed_tail].aligned);
+ freed[freed_tail].ptr = NULL;
+ freed[freed_tail].aligned = 0;
+ freed_tail = (freed_tail + 1) % FREED_ENTRIES;
+ return 1;
+}
+
+static unsigned *get_hdr (void *ptr)
+{
+ if (((uintptr_t)ptr & 0xfff) < sizeof(unsigned))
+ return (unsigned *)(((uintptr_t)ptr - 0x1000) & ~0xfff);
+ return (unsigned *)((uintptr_t)ptr & ~0xfff);
+}
+
+void xfree (void *ptr)
+{
+ unsigned int size, aligned;
+ unsigned *hdr;
+# ifdef WINDOWS32
+ DWORD fFlags = PAGE_NOACCESS;
+# endif
+
+ if (!ptr)
+ return;
+
+ hdr = get_hdr (ptr);
+ size = *hdr;
+ aligned = (size + 0x1fff + sizeof(unsigned)) & ~0xfff;
+# ifdef WINDOWS32
+ if (!VirtualProtect (hdr, aligned - 0x1000, fFlags, &fFlags))
+ fatal_error ("failed to protect freed memory");
+# else
+ if (mprotect(hdr, aligned - 0x1000, PROT_NONE))
+ fatal_error ("failed to protect freed memory");
+# endif
+
+ freed[freed_head].ptr = hdr;
+ freed[freed_head].aligned = aligned;
+ if (((freed_head + 1) % FREED_ENTRIES) == freed_tail)
+ free_up_some();
+ freed_head = (freed_head + 1) % FREED_ENTRIES;
+}
+
+void *
+xmalloc (unsigned int size)
+{
+ /* Make sure we don't allocate 0, for pre-ANSI libraries. */
+ unsigned int aligned = (size + 0x1fff + sizeof(unsigned)) & ~0xfff;
+ unsigned *hdr;
+ unsigned i;
+ for (i = 0; i < FREED_ENTRIES; i++)
+ {
+# ifdef WINDOWS32
+ DWORD fFlags = PAGE_NOACCESS;
+ hdr = VirtualAlloc(NULL, aligned, MEM_COMMIT, PAGE_READWRITE);
+ if (hdr
+ && !VirtualProtect((char *)hdr + aligned - 0x1000, 0x1000, fFlags, &fFlags))
+ fatal_error ("failed to set guard page protection");
+# else
+ hdr = mmap(NULL, aligned, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
+ if (hdr == MAP_FAILED)
+ hdr = 0;
+ if (hdr
+ && mprotect((char *)hdr + aligned - 0x1000, 0x1000, PROT_NONE))
+ fatal_error ("failed to set guard page protection");
+# endif
+ if (hdr)
+ break;
+ if (!free_up_some ())
+ break;
+ }
+ if (hdr == 0)
+ fatal_error ("virtual memory exhausted");
+
+ *hdr = size;
+# if 0
+ return hdr + 1;
+# else
+ return (char *)hdr + aligned - 0x1000 - size;
+# endif
+}
+
+
+void *
+xcalloc (unsigned size)
+{
+ void *result;
+ result = xmalloc (size);
+ return memset (result, 0, size);
+}
+
+void *
+xrealloc (void *ptr, unsigned int size)
+{
+ void *result;
+ result = xmalloc (size);
+ if (ptr)
+ {
+ unsigned *hdr = get_hdr (ptr);
+ unsigned int oldsize = *hdr;
+ memcpy (result, ptr, oldsize >= size ? size : oldsize);
+ xfree (ptr);
+ }
+ return result;
+}
+
+char *
+xstrdup (const char *ptr)
+{
+ if (ptr)
+ {
+ size_t size = strlen (ptr) + 1;
+ char *result = xmalloc (size);
+ return memcpy (result, ptr, size);
+ }
+ return NULL;
+}
+
+# ifdef __GNUC__
+void *
+xmalloc_size_t (size_t size)
+{
+ return xmalloc(size);
+}
+
+void *
+xcalloc_size_t (size_t size, size_t items)
+{
+ return xcalloc(size * items);
+}
+
+void *
+xrealloc_size_t (void *ptr, size_t size)
+{
+ return xrealloc(ptr, size);
+}
+# endif /* __GNUC__ */
+
+#else /* !ELECTRIC_HEAP */
+extern void electric_heap_keep_ansi_c_quiet (void);
+#endif /* !ELECTRIC_HEAP */
+
diff --git a/src/kmk/electric.h b/src/kmk/electric.h
new file mode 100644
index 0000000..b655e7e
--- /dev/null
+++ b/src/kmk/electric.h
@@ -0,0 +1,66 @@
+/* $Id: electric.h 3150 2018-03-15 18:18:03Z bird $ */
+/** @file
+ * A simple electric heap implementation, wrapper header.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifdef ELECTRIC_HEAP
+
+#include <stdlib.h>
+#ifdef WINDOWS32
+# include <malloc.h>
+#endif
+#include <string.h> /* strdup */
+
+void xfree (void *);
+void *xcalloc (unsigned int);
+void *xmalloc (unsigned int);
+void *xrealloc (void *, unsigned int);
+char *xstrdup (const char *);
+#ifdef __GNUC__
+void *xmalloc_size_t (size_t size);
+void *xcalloc_size_t (size_t size, size_t items);
+void *xrealloc_size_t (void *ptr, size_t size);
+#endif
+
+
+#undef free
+//#define free(a) xfree(a)
+#define free xfree
+#undef strdup
+#define strdup(a) xstrdup(a)
+
+#undef calloc
+#undef malloc
+#undef realloc
+#ifdef __GNUC__
+# define calloc(a,b) xcalloc_size_t(a,b)
+# define malloc(a) xmalloc_size_t(a)
+# define realloc(a,b) xrealloc_size_t(a,b)
+#else
+# define calloc(a,b) xcalloc((a) * (b))
+# define malloc(a) xmalloc(a)
+# define realloc(a,b) xrealloc((a),(b))
+#endif
+
+#endif
+
diff --git a/src/kmk/example-spaces.kmk b/src/kmk/example-spaces.kmk
new file mode 100644
index 0000000..7533002
--- /dev/null
+++ b/src/kmk/example-spaces.kmk
@@ -0,0 +1,175 @@
+# $Id: example-spaces.kmk 3316 2020-03-31 01:13:22Z bird $
+## @file
+# kBuild - examples of GNU make filename quoting (escaping).
+#
+
+#
+# Copyright (c) 2020 knut st. osmundsen <bird-kBuild-spamxx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+
+all: \
+ phoney\ space\ \ 1 \
+ phoney\ colon\:\ 2 \
+ phoney\ hash\#\ 3 \
+ phoney\ dollar$$\ 4 \
+ phoney\ slash-space\\\ 5 \
+ phoney\ slash-hash\\\#\ 6 \
+ phoney\ slash-slash-hash\\\\\#\ 7 \
+ phoney\ percent%\ 10 \
+ phoney\ pipe\|\ 11 \
+ phoney\ plus+\ 12 \
+ all-trailing-slashes1 \
+ all-trailing-slashes2 \
+ all-trailing-slashes3 \
+ all-trailing-spaces1 \
+ all-trailing-spaces2 \
+ all-trailing-spaces3 \
+ all-trailing-spaces4 \
+ phoney\ 19-target-trailing-space-with-padding\ ignore \
+ phoney\ 20-target-trailing-space-with-newline-padding\ ignore \
+ phoney\ 21-target-trailing-space-with-newline-padding-and-tail\ my-tail-21
+
+
+ignore:
+
+#
+# Trailing slashes are complicated in dependency lists:
+#
+
+# Variant #1: Must have a line-continutation to work if last in the list, but no extra escaping.
+# This doesn't work: all-trailing-slashes1: phoney\ trailing-slash13\
+# This doesn't work: all-trailing-slashes1: phoney\ trailing-slash13\\
+all-trailing-slashes1: phoney\ trailing-slash13\ \
+
+all-trailing-slashes2: phoney\ trailing-slash13b\ \
+ \
+ \
+ \
+
+# Variant #2: If there are more dependencies following it, we must escape the trailing slash
+all-trailing-slashes3: \
+ phoney\ trailing-slash14\\ \
+ phoney\ space\ \ 1 # whatever
+
+#
+# Trailing spaces only works if there is a target following on the same line.
+#
+all-trailing-spaces1: phoney\ 15-trailing-space\ phoney_simple
+
+# Note! No stripping spaces! Trailing space here that gets stripped instead of escaped.
+all-trailing-spaces2: phoney\ 16-no-trailing-space\
+
+all-trailing-spaces3: phoney\ 17-3x-escaped-newlines\ \
+\
+\
+ becomes-single-space
+
+# Note! Must have a trailing space or comment.
+all-trailing-spaces4: phoney\ 18-3x-escaped-trailing-spaces-no-newline\ \ \ #
+
+
+#
+# TODO
+#
+
+#busted: phoney\ equal\=\ 8 \
+#impossible: phoney\ semi\;\ 9 \
+
+
+#
+# The rules.
+#
+
+phoney_simple:
+
+phoney\ space\ \ 1:
+ echo "#1: '$@'"
+
+phoney\ colon\:\ 2:
+ echo "#2: '$@'"
+
+phoney\ hash\#\ 3 :
+ echo "#3: '$@'"
+
+phoney\ dollar$$\ 4 :
+ echo "#4: '$@'"
+
+phoney\ slash-space\\\ 5:
+ echo "#5: '$@'"
+
+phoney\ slash-hash\\\#\ 6:
+ echo "#6: '$@'"
+
+phoney\ slash-slash-hash\\\\\#\ 7:
+ echo "#7: '$@'"
+
+## This is busted:
+#phoney\ equal=\ 8:
+# echo "#8: '$@'"
+
+## This seems impossible:
+#phoney\ semi\;:
+# echo "#9: '$@'"
+
+phoney\ percent\%\ 10: # Note! The percent is only escaped on the target side!
+ echo "#10: '$@'"
+
+phoney\ pipe|\ 11: # Note! The pipe is only escaped on the dependency list side!
+ echo "#11: '$@'"
+
+phoney\ plus+\ 12:
+ echo "#12: '$@'"
+
+phoney\ trailing-slash13\\:
+ echo "#13: '$@'"
+
+phoney\ trailing-slash13b\\:
+ echo "#13b: '$@'"
+
+phoney\ trailing-slash14\\:
+ echo "#14: '$@'"
+
+phoney\ 15-trailing-space\ :
+ echo "#15: '$@'"
+
+phoney\ 16-no-trailing-space\\:
+ echo "#16: '$@'"
+
+phoney\ 17-3x-escaped-newlines\ becomes-single-space:
+ echo "#17: '$@'"
+
+phoney\ 18-3x-escaped-trailing-spaces-no-newline\ \ \\:
+ echo "#18: '$@'"
+
+phoney\ 19-target-trailing-space-with-padding\ :
+ echo "#19: '$@'"
+
+phoney\ 20-target-trailing-space-with-newline-padding\ \
+\
+:
+ echo "#20: '$@'"
+
+phoney\ 21-target-trailing-space-with-newline-padding-and-tail\ \
+\
+ \
+ \
+my-tail-21:
+ echo "#21: '$@'"
+
diff --git a/src/kmk/expand.c b/src/kmk/expand.c
new file mode 100644
index 0000000..e11a35c
--- /dev/null
+++ b/src/kmk/expand.c
@@ -0,0 +1,1286 @@
+/* Variable expansion functions for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#ifdef CONFIG_WITH_COMPILER
+# include "kmk_cc_exec.h"
+#endif
+
+/* Initially, any errors reported when expanding strings will be reported
+ against the file where the error appears. */
+const floc **expanding_var = &reading_file;
+
+/* The next two describe the variable output buffer.
+ This buffer is used to hold the variable-expansion of a line of the
+ makefile. It is made bigger with realloc whenever it is too small.
+ variable_buffer_length is the size currently allocated.
+ variable_buffer is the address of the buffer.
+
+ For efficiency, it's guaranteed that the buffer will always have
+ VARIABLE_BUFFER_ZONE extra bytes allocated. This allows you to add a few
+ extra chars without having to call a function. Note you should never use
+ these bytes unless you're _sure_ you have room (you know when the buffer
+ length was last checked. */
+
+#define VARIABLE_BUFFER_ZONE 5
+
+#ifndef KMK
+static unsigned int variable_buffer_length;
+#else
+unsigned int variable_buffer_length;
+#endif
+char *variable_buffer;
+
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+struct recycled_buffer
+{
+ struct recycled_buffer *next;
+ unsigned int length;
+};
+struct recycled_buffer *recycled_head;
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+
+
+#ifndef KMK
+/* Subroutine of variable_expand and friends:
+ The text to add is LENGTH chars starting at STRING to the variable_buffer.
+ The text is added to the buffer at PTR, and the updated pointer into
+ the buffer is returned as the value. Thus, the value returned by
+ each call to variable_buffer_output should be the first argument to
+ the following call. */
+
+char *
+variable_buffer_output (char *ptr, const char *string, unsigned int length)
+{
+ register unsigned int newlen = length + (ptr - variable_buffer);
+
+ if ((newlen + VARIABLE_BUFFER_ZONE) > variable_buffer_length)
+ {
+ unsigned int offset = ptr - variable_buffer;
+ variable_buffer_length = (newlen + 100 > 2 * variable_buffer_length
+ ? newlen + 100
+ : 2 * variable_buffer_length);
+ variable_buffer = xrealloc (variable_buffer, variable_buffer_length);
+ ptr = variable_buffer + offset;
+ }
+
+ memcpy (ptr, string, length);
+ return ptr + length;
+}
+#endif
+
+/* Return a pointer to the beginning of the variable buffer. */
+
+static char *
+initialize_variable_output (void)
+{
+ /* If we don't have a variable output buffer yet, get one. */
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ if (variable_buffer == 0)
+ {
+ struct recycled_buffer *recycled = recycled_head;
+ if (recycled)
+ {
+ recycled_head = recycled->next;
+ variable_buffer_length = recycled->length;
+ variable_buffer = (char *)recycled;
+ }
+ else
+ {
+ variable_buffer_length = 384;
+ variable_buffer = xmalloc (variable_buffer_length);
+ }
+ variable_buffer[0] = '\0';
+ }
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ if (variable_buffer == 0)
+ {
+ variable_buffer_length = 200;
+ variable_buffer = xmalloc (variable_buffer_length);
+ variable_buffer[0] = '\0';
+ }
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+ return variable_buffer;
+}
+
+/* Recursively expand V. The returned string is malloc'd. */
+
+static char *allocated_variable_append (const struct variable *v);
+
+char *
+#ifndef CONFIG_WITH_VALUE_LENGTH
+recursively_expand_for_file (struct variable *v, struct file *file)
+#else
+recursively_expand_for_file (struct variable *v, struct file *file,
+ unsigned int *value_lenp)
+#endif
+{
+ char *value;
+ const floc *this_var;
+ const floc **saved_varp;
+ struct variable_set_list *save = 0;
+ int set_reading = 0;
+
+ /* Don't install a new location if this location is empty.
+ This can happen for command-line variables, builtin variables, etc. */
+ saved_varp = expanding_var;
+ if (v->fileinfo.filenm)
+ {
+ this_var = &v->fileinfo;
+ expanding_var = &this_var;
+ }
+
+ /* If we have no other file-reading context, use the variable's context. */
+ if (!reading_file)
+ {
+ set_reading = 1;
+ reading_file = &v->fileinfo;
+ }
+
+ if (v->expanding)
+ {
+ if (!v->exp_count)
+ /* Expanding V causes infinite recursion. Lose. */
+ OS (fatal, *expanding_var,
+ _("Recursive variable '%s' references itself (eventually)"),
+ v->name);
+ --v->exp_count;
+ }
+
+ if (file)
+ {
+ save = current_variable_set_list;
+ current_variable_set_list = file->variables;
+ }
+
+ v->expanding = 1;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (v->append)
+ value = allocated_variable_append (v);
+ else
+ value = allocated_variable_expand (v->value);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ if (!v->append)
+ {
+ if (!IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v))
+ value = allocated_variable_expand_2 (v->value, v->value_length, value_lenp);
+ else
+ {
+ unsigned int len = v->value_length;
+ value = xmalloc (len + 2);
+ memcpy (value, v->value, len + 1);
+ value[len + 1] = '\0'; /* Extra terminator like allocated_variable_expand_2 returns. Why? */
+ if (value_lenp)
+ *value_lenp = len;
+ }
+ }
+ else
+ {
+ value = allocated_variable_append (v);
+ if (value_lenp)
+ *value_lenp = strlen (value);
+ }
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+ v->expanding = 0;
+
+ if (set_reading)
+ reading_file = 0;
+
+ if (file)
+ current_variable_set_list = save;
+
+ expanding_var = saved_varp;
+
+ return value;
+}
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+/* Worker for reference_variable() and kmk_exec_* that expands the recursive
+ variable V. The main difference between this and
+ recursively_expand[_for_file] is that this worker avoids the temporary
+ buffer and outputs directly into the current variable buffer (O). */
+char *
+reference_recursive_variable (char *o, struct variable *v)
+{
+ const floc *this_var;
+ const floc **saved_varp;
+ int set_reading = 0;
+
+ /* Don't install a new location if this location is empty.
+ This can happen for command-line variables, builtin variables, etc. */
+ saved_varp = expanding_var;
+ if (v->fileinfo.filenm)
+ {
+ this_var = &v->fileinfo;
+ expanding_var = &this_var;
+ }
+
+ /* If we have no other file-reading context, use the variable's context. */
+ if (!reading_file)
+ {
+ set_reading = 1;
+ reading_file = &v->fileinfo;
+ }
+
+ if (v->expanding)
+ {
+ if (!v->exp_count)
+ /* Expanding V causes infinite recursion. Lose. */
+ OS (fatal, *expanding_var,
+ _("Recursive variable `%s' references itself (eventually)"),
+ v->name);
+ --v->exp_count;
+ }
+
+ v->expanding = 1;
+ if (!v->append)
+ {
+ /* Expand directly into the variable buffer. */
+# ifdef CONFIG_WITH_COMPILER
+ v->expand_count++;
+ if ( v->expandprog
+ || (v->expand_count == 3 && kmk_cc_compile_variable_for_expand (v)) )
+ o = kmk_exec_expand_to_var_buf (v, o);
+ else
+ variable_expand_string_2 (o, v->value, v->value_length, &o);
+# else
+ MAKE_STATS_2 (v->expand_count++);
+ variable_expand_string_2 (o, v->value, v->value_length, &o);
+# endif
+ }
+ else
+ {
+ /* XXX: Feel free to optimize appending target variables as well. */
+ char *value = allocated_variable_append (v);
+ unsigned int value_len = strlen (value);
+ o = variable_buffer_output (o, value, value_len);
+ free (value);
+ }
+ v->expanding = 0;
+
+ if (set_reading)
+ reading_file = 0;
+
+ expanding_var = saved_varp;
+
+ return o;
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+/* Expand a simple reference to variable NAME, which is LENGTH chars long. */
+
+#ifdef MY_INLINE /* bird */
+MY_INLINE char *
+#else
+#if defined(__GNUC__)
+__inline
+#endif
+static char *
+#endif
+reference_variable (char *o, const char *name, unsigned int length)
+{
+ struct variable *v;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *value;
+#endif
+
+ v = lookup_variable (name, length);
+
+ if (v == 0)
+ warn_undefined (name, length);
+
+ /* If there's no variable by that name or it has no value, stop now. */
+ if (v == 0 || (*v->value == '\0' && !v->append))
+ return o;
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ assert (v->value_length == strlen (v->value));
+ if (!v->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v))
+ o = variable_buffer_output (o, v->value, v->value_length);
+ else
+ o = reference_recursive_variable (o, v);
+#else /* !CONFIG_WITH_VALUE_LENGTH */
+ value = (v->recursive ? recursively_expand (v) : v->value);
+
+ o = variable_buffer_output (o, value, strlen (value));
+
+ if (v->recursive)
+ free (value);
+#endif /* !CONFIG_WITH_VALUE_LENGTH */
+
+ return o;
+}
+
+#ifndef CONFIG_WITH_VALUE_LENGTH /* Only using variable_expand_string_2! */
+/* Scan STRING for variable references and expansion-function calls. Only
+ LENGTH bytes of STRING are actually scanned. If LENGTH is -1, scan until
+ a null byte is found.
+
+ Write the results to LINE, which must point into 'variable_buffer'. If
+ LINE is NULL, start at the beginning of the buffer.
+ Return a pointer to LINE, or to the beginning of the buffer if LINE is
+ NULL.
+ */
+char *
+variable_expand_string (char *line, const char *string, long length)
+{
+ struct variable *v;
+ const char *p, *p1;
+ char *save;
+ char *o;
+ unsigned int line_offset;
+
+ if (!line)
+ line = initialize_variable_output ();
+ o = line;
+ line_offset = line - variable_buffer;
+
+ if (length == 0)
+ {
+ variable_buffer_output (o, "", 1);
+ return (variable_buffer);
+ }
+
+ /* We need a copy of STRING: due to eval, it's possible that it will get
+ freed as we process it (it might be the value of a variable that's reset
+ for example). Also having a nil-terminated string is handy. */
+ save = length < 0 ? xstrdup (string) : xstrndup (string, length);
+ p = save;
+
+ while (1)
+ {
+ /* Copy all following uninteresting chars all at once to the
+ variable output buffer, and skip them. Uninteresting chars end
+ at the next $ or the end of the input. */
+
+ p1 = strchr (p, '$');
+
+ o = variable_buffer_output (o, p, p1 != 0 ? (unsigned int)(p1 - p) : strlen (p) + 1);
+
+ if (p1 == 0)
+ break;
+ p = p1 + 1;
+
+ /* Dispatch on the char that follows the $. */
+
+ switch (*p)
+ {
+ case '$':
+ case '\0':
+ /* $$ or $ at the end of the string means output one $ to the
+ variable output buffer. */
+ o = variable_buffer_output (o, p1, 1);
+ break;
+
+ case '(':
+ case '{':
+ /* $(...) or ${...} is the general case of substitution. */
+ {
+ char openparen = *p;
+ char closeparen = (openparen == '(') ? ')' : '}';
+ const char *begp;
+ const char *beg = p + 1;
+ char *op;
+ char *abeg = NULL;
+ const char *end, *colon;
+
+ op = o;
+ begp = p;
+ if (handle_function (&op, &begp))
+ {
+ o = op;
+ p = begp;
+ break;
+ }
+
+ /* Is there a variable reference inside the parens or braces?
+ If so, expand it before expanding the entire reference. */
+
+ end = strchr (beg, closeparen);
+ if (end == 0)
+ /* Unterminated variable reference. */
+ O (fatal, *expanding_var, _("unterminated variable reference"));
+ p1 = lindex (beg, end, '$');
+ if (p1 != 0)
+ {
+ /* BEG now points past the opening paren or brace.
+ Count parens or braces until it is matched. */
+ int count = 0;
+ for (p = beg; *p != '\0'; ++p)
+ {
+ if (*p == openparen)
+ ++count;
+ else if (*p == closeparen && --count < 0)
+ break;
+ }
+ /* If COUNT is >= 0, there were unmatched opening parens
+ or braces, so we go to the simple case of a variable name
+ such as '$($(a)'. */
+ if (count < 0)
+ {
+ abeg = expand_argument (beg, p); /* Expand the name. */
+ beg = abeg;
+ end = strchr (beg, '\0');
+ }
+ }
+ else
+ /* Advance P to the end of this reference. After we are
+ finished expanding this one, P will be incremented to
+ continue the scan. */
+ p = end;
+
+ /* This is not a reference to a built-in function and
+ any variable references inside are now expanded.
+ Is the resultant text a substitution reference? */
+
+ colon = lindex (beg, end, ':');
+ if (colon)
+ {
+ /* This looks like a substitution reference: $(FOO:A=B). */
+ const char *subst_beg = colon + 1;
+ const char *subst_end = lindex (subst_beg, end, '=');
+ if (subst_end == 0)
+ /* There is no = in sight. Punt on the substitution
+ reference and treat this as a variable name containing
+ a colon, in the code below. */
+ colon = 0;
+ else
+ {
+ const char *replace_beg = subst_end + 1;
+ const char *replace_end = end;
+
+ /* Extract the variable name before the colon
+ and look up that variable. */
+ v = lookup_variable (beg, colon - beg);
+ if (v == 0)
+ warn_undefined (beg, colon - beg);
+
+ /* If the variable is not empty, perform the
+ substitution. */
+ if (v != 0 && *v->value != '\0')
+ {
+ char *pattern, *replace, *ppercent, *rpercent;
+ char *value = (v->recursive
+ ? recursively_expand (v)
+ : v->value);
+
+ /* Copy the pattern and the replacement. Add in an
+ extra % at the beginning to use in case there
+ isn't one in the pattern. */
+ pattern = alloca (subst_end - subst_beg + 2);
+ *(pattern++) = '%';
+ memcpy (pattern, subst_beg, subst_end - subst_beg);
+ pattern[subst_end - subst_beg] = '\0';
+
+ replace = alloca (replace_end - replace_beg + 2);
+ *(replace++) = '%';
+ memcpy (replace, replace_beg,
+ replace_end - replace_beg);
+ replace[replace_end - replace_beg] = '\0';
+
+ /* Look for %. Set the percent pointers properly
+ based on whether we find one or not. */
+ ppercent = find_percent (pattern);
+ if (ppercent)
+ {
+ ++ppercent;
+ rpercent = find_percent (replace);
+ if (rpercent)
+ ++rpercent;
+ }
+ else
+ {
+ ppercent = pattern;
+ rpercent = replace;
+ --pattern;
+ --replace;
+ }
+
+ o = patsubst_expand_pat (o, value, pattern, replace,
+ ppercent, rpercent);
+
+ if (v->recursive)
+ free (value);
+ }
+ }
+ }
+
+ if (colon == 0)
+ /* This is an ordinary variable reference.
+ Look up the value of the variable. */
+ o = reference_variable (o, beg, end - beg);
+
+ free (abeg);
+ }
+ break;
+
+ default:
+ if (ISSPACE (p[-1]))
+ break;
+
+ /* A $ followed by a random char is a variable reference:
+ $a is equivalent to $(a). */
+ o = reference_variable (o, p, 1);
+
+ break;
+ }
+
+ if (*p == '\0')
+ break;
+
+ ++p;
+ }
+
+ free (save);
+
+ variable_buffer_output (o, "", 1);
+ return (variable_buffer + line_offset);
+}
+
+#else /* CONFIG_WITH_VALUE_LENGTH */
+/* Scan STRING for variable references and expansion-function calls. Only
+ LENGTH bytes of STRING are actually scanned. If LENGTH is -1, scan until
+ a null byte is found.
+
+ Write the results to LINE, which must point into `variable_buffer'. If
+ LINE is NULL, start at the beginning of the buffer.
+ Return a pointer to LINE, or to the beginning of the buffer if LINE is
+ NULL. Set EOLP to point to the string terminator.
+ */
+char *
+variable_expand_string_2 (char *line, const char *string, long length, char **eolp)
+{
+ struct variable *v;
+ const char *p, *p1, *eos;
+ char *o;
+ unsigned int line_offset;
+
+ if (!line)
+ line = initialize_variable_output();
+ o = line;
+ line_offset = line - variable_buffer;
+
+ if (length < 0)
+ length = strlen (string);
+ else
+ MY_ASSERT_MSG (string + length == (p1 = memchr (string, '\0', length)) || !p1, ("len=%ld p1=%p %s\n", length, p1, line));
+
+ /* Simple 1: Emptry string. */
+
+ if (length == 0)
+ {
+ o = variable_buffer_output (o, "\0", 2);
+ *eolp = o - 2;
+ return (variable_buffer + line_offset);
+ }
+
+ /* Simple 2: Nothing to expand. ~50% if the kBuild calls. */
+
+ p1 = (const char *)memchr (string, '$', length);
+ if (p1 == 0)
+ {
+ o = variable_buffer_output (o, string, length);
+ o = variable_buffer_output (o, "\0", 2);
+ *eolp = o - 2;
+ assert (strchr (variable_buffer + line_offset, '\0') == *eolp);
+ return (variable_buffer + line_offset);
+ }
+
+ p = string;
+ eos = p + length;
+
+ while (1)
+ {
+ /* Copy all following uninteresting chars all at once to the
+ variable output buffer, and skip them. Uninteresting chars end
+ at the next $ or the end of the input. */
+
+ o = variable_buffer_output (o, p, p1 != 0 ? (p1 - p) : (eos - p));
+
+ if (p1 == 0)
+ break;
+ p = p1 + 1;
+
+ /* Dispatch on the char that follows the $. */
+
+ switch (*p)
+ {
+ case '$':
+ /* $$ seen means output one $ to the variable output buffer. */
+ o = variable_buffer_output (o, p, 1);
+ break;
+
+ case '(':
+ case '{':
+ /* $(...) or ${...} is the general case of substitution. */
+ {
+ char openparen = *p;
+ char closeparen = (openparen == '(') ? ')' : '}';
+ const char *begp;
+ const char *beg = p + 1;
+ char *op;
+ char *abeg = NULL;
+ unsigned int alen = 0;
+ const char *end, *colon;
+
+ op = o;
+ begp = p;
+ end = may_be_function_name (p + 1, eos);
+ if ( end
+ && handle_function (&op, &begp, end, eos))
+ {
+ o = op;
+ p = begp;
+ MY_ASSERT_MSG (!(p1 = memchr (variable_buffer + line_offset, '\0', o - (variable_buffer + line_offset))),
+ ("line=%p o/exp_end=%p act_end=%p\n", variable_buffer + line_offset, o, p1));
+ break;
+ }
+
+ /* Is there a variable reference inside the parens or braces?
+ If so, expand it before expanding the entire reference. */
+
+ end = memchr (beg, closeparen, eos - beg);
+ if (end == 0)
+ /* Unterminated variable reference. */
+ O (fatal, *expanding_var, _("unterminated variable reference"));
+ p1 = lindex (beg, end, '$');
+ if (p1 != 0)
+ {
+ /* BEG now points past the opening paren or brace.
+ Count parens or braces until it is matched. */
+ int count = 0;
+ for (p = beg; p < eos; ++p)
+ {
+ if (*p == openparen)
+ ++count;
+ else if (*p == closeparen && --count < 0)
+ break;
+ }
+ /* If COUNT is >= 0, there were unmatched opening parens
+ or braces, so we go to the simple case of a variable name
+ such as `$($(a)'. */
+ if (count < 0)
+ {
+ unsigned int len;
+ char saved;
+
+ /* Expand the name. */
+ saved = *p;
+ *(char *)p = '\0'; /* XXX: proove that this is safe! XXX2: shouldn't be necessary any longer! */
+ abeg = allocated_variable_expand_3 (beg, p - beg, &len, &alen);
+ beg = abeg;
+ end = beg + len;
+ *(char *)p = saved;
+ }
+ }
+ else
+ /* Advance P to the end of this reference. After we are
+ finished expanding this one, P will be incremented to
+ continue the scan. */
+ p = end;
+
+ /* This is not a reference to a built-in function and
+ any variable references inside are now expanded.
+ Is the resultant text a substitution reference? */
+
+ colon = lindex (beg, end, ':');
+ if (colon)
+ {
+ /* This looks like a substitution reference: $(FOO:A=B). */
+ const char *subst_beg, *subst_end, *replace_beg, *replace_end;
+
+ subst_beg = colon + 1;
+ subst_end = lindex (subst_beg, end, '=');
+ if (subst_end == 0)
+ /* There is no = in sight. Punt on the substitution
+ reference and treat this as a variable name containing
+ a colon, in the code below. */
+ colon = 0;
+ else
+ {
+ replace_beg = subst_end + 1;
+ replace_end = end;
+
+ /* Extract the variable name before the colon
+ and look up that variable. */
+ v = lookup_variable (beg, colon - beg);
+ if (v == 0)
+ warn_undefined (beg, colon - beg);
+
+ /* If the variable is not empty, perform the
+ substitution. */
+ if (v != 0 && *v->value != '\0')
+ {
+ char *pattern, *replace, *ppercent, *rpercent;
+ char *value = (v->recursive
+ ? recursively_expand (v)
+ : v->value);
+
+ /* Copy the pattern and the replacement. Add in an
+ extra % at the beginning to use in case there
+ isn't one in the pattern. */
+ pattern = alloca (subst_end - subst_beg + 2);
+ *(pattern++) = '%';
+ memcpy (pattern, subst_beg, subst_end - subst_beg);
+ pattern[subst_end - subst_beg] = '\0';
+
+ replace = alloca (replace_end - replace_beg + 2);
+ *(replace++) = '%';
+ memcpy (replace, replace_beg,
+ replace_end - replace_beg);
+ replace[replace_end - replace_beg] = '\0';
+
+ /* Look for %. Set the percent pointers properly
+ based on whether we find one or not. */
+ ppercent = find_percent (pattern);
+ if (ppercent)
+ {
+ ++ppercent;
+ rpercent = find_percent (replace);
+ if (rpercent)
+ ++rpercent;
+ }
+ else
+ {
+ ppercent = pattern;
+ rpercent = replace;
+ --pattern;
+ --replace;
+ }
+
+ o = patsubst_expand_pat (o, value, pattern, replace,
+ ppercent, rpercent);
+
+ if (v->recursive)
+ free (value);
+ }
+ }
+ }
+
+ if (colon == 0)
+ /* This is an ordinary variable reference.
+ Look up the value of the variable. */
+ o = reference_variable (o, beg, end - beg);
+
+ if (abeg)
+ recycle_variable_buffer (abeg, alen);
+ }
+ break;
+
+ case '\0':
+ assert (p == eos);
+ break;
+
+ default:
+ if (ISBLANK (p[-1])) /* XXX: This looks incorrect, previous is '$' */
+ break;
+
+ /* A $ followed by a random char is a variable reference:
+ $a is equivalent to $(a). */
+ o = reference_variable (o, p, 1);
+
+ break;
+ }
+
+ if (++p >= eos)
+ break;
+ p1 = memchr (p, '$', eos - p);
+ }
+
+ o = variable_buffer_output (o, "\0", 2); /* KMK: compensate for the strlen + 1 that was removed above. */
+ *eolp = o - 2;
+ MY_ASSERT_MSG (strchr (variable_buffer + line_offset, '\0') == *eolp,
+ ("expected=%d actual=%d\nlength=%ld string=%.*s\n",
+ (int)(*eolp - variable_buffer + line_offset), (int)strlen(variable_buffer + line_offset),
+ length, (int)length, string));
+ return (variable_buffer + line_offset);
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+/* Scan LINE for variable references and expansion-function calls.
+ Build in 'variable_buffer' the result of expanding the references and calls.
+ Return the address of the resulting string, which is null-terminated
+ and is valid only until the next time this function is called. */
+
+char *
+variable_expand (const char *line)
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ return variable_expand_string (NULL, line, (long)-1);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ char *s;
+
+ /* this function is abused a lot like this: variable_expand(""). */
+ if (!*line)
+ {
+ s = variable_buffer_output (initialize_variable_output (), "\0", 2);
+ return s - 2;
+ }
+ return variable_expand_string_2 (NULL, line, (long)-1, &s);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+}
+
+/* Expand an argument for an expansion function.
+ The text starting at STR and ending at END is variable-expanded
+ into a null-terminated string that is returned as the value.
+ This is done without clobbering 'variable_buffer' or the current
+ variable-expansion that is in progress. */
+
+char *
+expand_argument (const char *str, const char *end)
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *tmp, *alloc = NULL;
+ char *r;
+#endif
+
+ if (str == end)
+ return xstrdup ("");
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (!end || *end == '\0')
+ return allocated_variable_expand (str);
+
+ if (end - str + 1 > 1000)
+ tmp = alloc = xmalloc (end - str + 1);
+ else
+ tmp = alloca (end - str + 1);
+
+ memcpy (tmp, str, end - str);
+ tmp[end - str] = '\0';
+
+ r = allocated_variable_expand (tmp);
+
+ free (alloc);
+
+ return r;
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ if (!end)
+ return allocated_variable_expand_2 (str, ~0U, NULL);
+ return allocated_variable_expand_2 (str, end - str, NULL);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+}
+
+/* Expand LINE for FILE. Error messages refer to the file and line where
+ FILE's commands were found. Expansion uses FILE's variable set list. */
+
+char *
+variable_expand_for_file (const char *line, struct file *file)
+{
+ char *result;
+ struct variable_set_list *savev;
+ const floc *savef;
+
+ if (file == 0)
+ return variable_expand (line);
+
+ savev = current_variable_set_list;
+ current_variable_set_list = file->variables;
+
+ savef = reading_file;
+ if (file->cmds && file->cmds->fileinfo.filenm)
+ reading_file = &file->cmds->fileinfo;
+ else
+ reading_file = 0;
+
+ result = variable_expand (line);
+
+ current_variable_set_list = savev;
+ reading_file = savef;
+
+ return result;
+}
+
+#if defined (CONFIG_WITH_VALUE_LENGTH) || defined (CONFIG_WITH_COMMANDS_FUNC)
+/* Expand LINE for FILE. Error messages refer to the file and line where
+ FILE's commands were found. Expansion uses FILE's variable set list.
+
+ Differs from variable_expand_for_file in that it takes a pointer to
+ where in the variable buffer to start outputting the expanded string,
+ and that it can returned the length of the string if you wish. */
+
+char *
+variable_expand_for_file_2 (char *o, const char *line, unsigned int length,
+ struct file *file, unsigned int *value_lenp)
+{
+ char *result;
+ struct variable_set_list *savev;
+ const floc *savef;
+ long len = length == ~0U ? (long)-1 : (long)length;
+ char *eol;
+
+ if (!o)
+ o = initialize_variable_output();
+
+ if (file == 0)
+ result = variable_expand_string_2 (o, line, len, &eol);
+ else
+ {
+ savev = current_variable_set_list;
+ current_variable_set_list = file->variables;
+
+ savef = reading_file;
+ if (file->cmds && file->cmds->fileinfo.filenm)
+ reading_file = &file->cmds->fileinfo;
+ else
+ reading_file = 0;
+
+ result = variable_expand_string_2 (o, line, len, &eol);
+
+ current_variable_set_list = savev;
+ reading_file = savef;
+ }
+
+ if (value_lenp)
+ *value_lenp = eol - result;
+
+ return result;
+}
+
+#endif /* CONFIG_WITH_VALUE_LENGTH || CONFIG_WITH_COMMANDS_FUNC */
+/* Like allocated_variable_expand, but for += target-specific variables.
+ First recursively construct the variable value from its appended parts in
+ any upper variable sets. Then expand the resulting value. */
+
+static char *
+variable_append (const char *name, unsigned int length,
+ const struct variable_set_list *set, int local)
+{
+ const struct variable *v;
+ char *buf = 0;
+ /* If this set is local and the next is not a parent, then next is local. */
+ int nextlocal = local && set->next_is_parent == 0;
+
+ /* If there's nothing left to check, return the empty buffer. */
+ if (!set)
+ return initialize_variable_output ();
+
+ /* Try to find the variable in this variable set. */
+ v = lookup_variable_in_set (name, length, set->set);
+
+ /* If there isn't one, or this one is private, try the set above us. */
+ if (!v || (!local && v->private_var))
+ return variable_append (name, length, set->next, nextlocal);
+
+ /* If this variable type is append, first get any upper values.
+ If not, initialize the buffer. */
+ if (v->append)
+ buf = variable_append (name, length, set->next, nextlocal);
+ else
+ buf = initialize_variable_output ();
+
+ /* Append this value to the buffer, and return it.
+ If we already have a value, first add a space. */
+ if (buf > variable_buffer)
+ buf = variable_buffer_output (buf, " ", 1);
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ assert (v->value_length == strlen (v->value));
+#endif
+
+ /* Either expand it or copy it, depending. */
+ if (! v->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v))
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ return variable_buffer_output (buf, v->value, v->value_length);
+#else
+ return variable_buffer_output (buf, v->value, strlen (v->value));
+#endif
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ variable_expand_string_2 (buf, v->value, v->value_length, &buf);
+ return buf;
+#else
+ buf = variable_expand_string (buf, v->value, strlen (v->value));
+ return (buf + strlen (buf));
+#endif
+}
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+/* Expands the specified string, appending it to the specified
+ variable value. */
+void
+append_expanded_string_to_variable (struct variable *v, const char *value,
+ unsigned int value_len, int append)
+{
+ char *p = (char *) memchr (value, '$', value_len);
+ if (!p)
+ /* fast path */
+ append_string_to_variable (v,value, value_len, append);
+ else if (value_len)
+ {
+ unsigned int off_dollar = p - (char *)value;
+
+ /* Install a fresh variable buffer. */
+ char *saved_buffer;
+ unsigned int saved_buffer_length;
+ install_variable_buffer (&saved_buffer, &saved_buffer_length);
+
+ p = variable_buffer;
+ if (append || !v->value_length)
+ {
+ /* Copy the current value into it and append a space. */
+ if (v->value_length)
+ {
+ p = variable_buffer_output (p, v->value, v->value_length);
+ p = variable_buffer_output (p, " ", 1);
+ }
+
+ /* Append the assignment value. */
+ p = variable_buffer_output (p, value, off_dollar);
+ variable_expand_string_2 (p, value + off_dollar, value_len - off_dollar, &p);
+ }
+ else
+ {
+ /* Expand the assignemnt value. */
+ p = variable_buffer_output (p, value, off_dollar);
+ variable_expand_string_2 (p, value + off_dollar, value_len - off_dollar, &p);
+
+ /* Append a space followed by the old value. */
+ p = variable_buffer_output (p, " ", 1);
+ p = variable_buffer_output (p, v->value, v->value_length + 1) - 1;
+ }
+
+ /* Replace the variable with the variable buffer. */
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v->rdonly_val)
+ v->rdonly_val = 0;
+ else
+#endif
+ free (v->value);
+ v->value = variable_buffer;
+ v->value_length = p - v->value;
+ v->value_alloc_len = variable_buffer_length;
+ VARIABLE_CHANGED(v);
+
+ /* Restore the variable buffer, but without freeing the current. */
+ variable_buffer = NULL;
+ restore_variable_buffer (saved_buffer, saved_buffer_length);
+ }
+ /* else: Drop empty strings. Use $(NO_SUCH_VARIABLE) if a space is wanted. */
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+static char *
+allocated_variable_append (const struct variable *v)
+{
+ char *val;
+
+ /* Construct the appended variable value. */
+
+ char *obuf = variable_buffer;
+ unsigned int olen = variable_buffer_length;
+
+ variable_buffer = 0;
+
+ assert ((unsigned int)v->length == strlen (v->name)); /* bird */
+ val = variable_append (v->name, strlen (v->name), /** @todo optimize by using v->length! */
+ current_variable_set_list, 1);
+ variable_buffer_output (val, "", 1);
+ val = variable_buffer;
+
+ variable_buffer = obuf;
+ variable_buffer_length = olen;
+
+ return val;
+}
+
+/* Like variable_expand_for_file, but the returned string is malloc'd.
+ This function is called a lot. It wants to be efficient. */
+
+char *
+allocated_variable_expand_for_file (const char *line, struct file *file)
+{
+ char *value;
+
+ char *obuf = variable_buffer;
+ unsigned int olen = variable_buffer_length;
+
+ variable_buffer = 0;
+
+ value = variable_expand_for_file (line, file);
+
+ variable_buffer = obuf;
+ variable_buffer_length = olen;
+
+ return value;
+}
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+/* Handle the most common case in allocated_variable_expand_for_file
+ specially and provide some additional string length features. */
+
+char *
+allocated_variable_expand_2 (const char *line, unsigned int length,
+ unsigned int *value_lenp)
+{
+ char *value;
+ char *obuf = variable_buffer;
+ unsigned int olen = variable_buffer_length;
+ long len = length == ~0U ? -1L : (long)length;
+ char *eol;
+
+ variable_buffer = 0;
+
+ value = variable_expand_string_2 (NULL, line, len, &eol);
+ if (value_lenp)
+ *value_lenp = eol - value;
+
+ variable_buffer = obuf;
+ variable_buffer_length = olen;
+
+ return value;
+}
+
+/* Initially created for handling a special case for variable_expand_string2
+ where the variable name is expanded and freed right afterwards. This
+ variant allows the variable_buffer to be recycled and thus avoid bothering
+ with a slow free implementation. (Darwin is horrible slow.) */
+
+char *
+allocated_variable_expand_3 (const char *line, unsigned int length,
+ unsigned int *value_lenp,
+ unsigned int *buffer_lengthp)
+{
+ char *obuf = variable_buffer;
+ unsigned int olen = variable_buffer_length;
+ long len = (long)length;
+ char *value;
+ char *eol;
+
+ variable_buffer = 0;
+
+ value = variable_expand_string_2 (NULL, line, len, &eol);
+ if (value_lenp)
+ *value_lenp = eol - value;
+ *buffer_lengthp = variable_buffer_length;
+
+ variable_buffer = obuf;
+ variable_buffer_length = olen;
+
+ return value;
+}
+
+/* recycle a buffer. */
+
+void
+recycle_variable_buffer (char *buffer, unsigned int length)
+{
+ struct recycled_buffer *recycled = (struct recycled_buffer *)buffer;
+
+ assert (!(length & 31));
+ assert (length >= 384);
+ recycled->length = length;
+ recycled->next = recycled_head;
+ recycled_head = recycled;
+}
+
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+/* Install a new variable_buffer context, returning the current one for
+ safe-keeping. */
+
+void
+install_variable_buffer (char **bufp, unsigned int *lenp)
+{
+ *bufp = variable_buffer;
+ *lenp = variable_buffer_length;
+
+ variable_buffer = 0;
+ initialize_variable_output ();
+}
+
+#ifdef CONFIG_WITH_COMPILER
+/* Same as install_variable_buffer, except we supply a size hint. */
+
+char *
+install_variable_buffer_with_hint (char **bufp, unsigned int *lenp, unsigned int size_hint)
+{
+ struct recycled_buffer *recycled;
+ char *buf;
+
+ *bufp = variable_buffer;
+ *lenp = variable_buffer_length;
+
+ recycled = recycled_head;
+ if (recycled)
+ {
+ recycled_head = recycled->next;
+ variable_buffer_length = recycled->length;
+ variable_buffer = buf = (char *)recycled;
+ }
+ else
+ {
+ if (size_hint < 512)
+ variable_buffer_length = (size_hint + 1 + 63) & ~(unsigned int)63;
+ else if (size_hint < 4096)
+ variable_buffer_length = (size_hint + 1 + 1023) & ~(unsigned int)1023;
+ else
+ variable_buffer_length = (size_hint + 1 + 4095) & ~(unsigned int)4095;
+ variable_buffer = buf = xmalloc (variable_buffer_length);
+ }
+ buf[0] = '\0';
+ return buf;
+}
+#endif /* CONFIG_WITH_COMPILER */
+
+/* Restore a previously-saved variable_buffer setting (free the
+ current one). */
+
+void
+restore_variable_buffer (char *buf, unsigned int len)
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (variable_buffer);
+#else
+ if (variable_buffer)
+ recycle_variable_buffer (variable_buffer, variable_buffer_length);
+#endif
+
+ variable_buffer = buf;
+ variable_buffer_length = len;
+}
+
+
+/* Used to make sure there is at least SIZE bytes of buffer space
+ available starting at PTR. */
+char *
+ensure_variable_buffer_space(char *ptr, unsigned int size)
+{
+ unsigned int offset = (unsigned int)(ptr - variable_buffer);
+ assert(offset <= variable_buffer_length);
+ if (variable_buffer_length - offset < size)
+ {
+ unsigned minlen = size + offset;
+ variable_buffer_length *= 2;
+ if (variable_buffer_length < minlen + 100)
+ variable_buffer_length = (minlen + 100 + 63) & ~(unsigned int)63;
+ variable_buffer = xrealloc (variable_buffer, variable_buffer_length);
+ ptr = variable_buffer + offset;
+ }
+ return ptr;
+}
+
diff --git a/src/kmk/expreval.c b/src/kmk/expreval.c
new file mode 100644
index 0000000..61f7c7b
--- /dev/null
+++ b/src/kmk/expreval.c
@@ -0,0 +1,2387 @@
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+/* $Id: expreval.c 3544 2022-01-29 02:22:03Z bird $ */
+/** @file
+ * expreval - Expressions evaluator, C / BSD make / nmake style.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include <assert.h>
+
+#include <glob.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#include "debug.h"
+#include "hash.h"
+#include "version_compare.h"
+#include <ctype.h>
+#ifndef _MSC_VER
+# include <stdint.h>
+#endif
+#include <stdarg.h>
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** The max length of a string representation of a number. */
+#define EXPR_NUM_LEN ((sizeof("-9223372036854775802") + 4) & ~3)
+
+/** The max operator stack depth. */
+#define EXPR_MAX_OPERATORS 72
+/** The max operand depth. */
+#define EXPR_MAX_OPERANDS 128
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary
+ * operator, omitting isspace. */
+#define EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch) \
+ (ispunct((a_ch)) && (a_ch) != '@' && (a_ch) != '_')
+
+/** Check if @a a_ch is a valid separator for a alphabetical binary operator. */
+#define EXPR_IS_OP_SEPARATOR(a_ch) \
+ (isspace((a_ch)) || EXPR_IS_OP_SEPARATOR_NO_SPACE(a_ch))
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/** The 64-bit signed integer type we're using. */
+#ifdef _MSC_VER
+typedef __int64 EXPRINT64;
+#else
+# include <stdint.h>
+typedef int64_t EXPRINT64;
+#endif
+
+/** Pointer to a evaluator instance. */
+typedef struct EXPR *PEXPR;
+
+
+/**
+ * Operand variable type.
+ */
+typedef enum
+{
+ /** Invalid zero entry. */
+ kExprVar_Invalid = 0,
+ /** A number. */
+ kExprVar_Num,
+ /** A string in need of expanding (perhaps). */
+ kExprVar_String,
+ /** A simple string that doesn't need expanding. */
+ kExprVar_SimpleString,
+ /** A quoted string in need of expanding (perhaps). */
+ kExprVar_QuotedString,
+ /** A simple quoted string that doesn't need expanding. */
+ kExprVar_QuotedSimpleString,
+ /** The end of the valid variable types. */
+ kExprVar_End
+} EXPRVARTYPE;
+
+/**
+ * Operand variable.
+ */
+typedef struct
+{
+ /** The variable type. */
+ EXPRVARTYPE enmType;
+ /** The variable. */
+ union
+ {
+ /** Pointer to the string. */
+ char *psz;
+ /** The variable. */
+ EXPRINT64 i;
+ } uVal;
+} EXPRVAR;
+/** Pointer to a operand variable. */
+typedef EXPRVAR *PEXPRVAR;
+/** Pointer to a const operand variable. */
+typedef EXPRVAR const *PCEXPRVAR;
+
+/**
+ * Operator return statuses.
+ */
+typedef enum
+{
+ kExprRet_Error = -1,
+ kExprRet_Ok = 0,
+ kExprRet_Operator,
+ kExprRet_Operand,
+ kExprRet_EndOfExpr,
+ kExprRet_End
+} EXPRRET;
+
+/**
+ * Operator.
+ */
+typedef struct
+{
+ /** The operator. */
+ char szOp[11];
+ /** The length of the operator string. */
+ char cchOp;
+ /** The pair operator.
+ * This is used with '(' and '?'. */
+ char chPair;
+ /** The precedence. Higher means higher. */
+ char iPrecedence;
+ /** The number of arguments it takes. */
+ signed char cArgs;
+ /** Pointer to the method implementing the operator. */
+ EXPRRET (*pfn)(PEXPR pThis);
+} EXPROP;
+/** Pointer to a const operator. */
+typedef EXPROP const *PCEXPROP;
+
+/**
+ * Expression evaluator instance.
+ */
+typedef struct EXPR
+{
+ /** The full expression. */
+ const char *pszExpr;
+ /** The current location. */
+ const char *psz;
+ /** The current file location, used for errors. */
+ const floc *pFileLoc;
+ /** Pending binary operator. */
+ PCEXPROP pPending;
+ /** Top of the operator stack. */
+ int iOp;
+ /** Top of the operand stack. */
+ int iVar;
+ /** The operator stack. */
+ PCEXPROP apOps[EXPR_MAX_OPERATORS];
+ /** The operand stack. */
+ EXPRVAR aVars[EXPR_MAX_OPERANDS];
+} EXPR;
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/** Operator start character map.
+ * This indicates which characters that are starting operators and which aren't.
+ *
+ * Bit 0: Indicates that this char is used in operators.
+ * Bit 1: When bit 0 is clear, this indicates whitespace.
+ * When bit 1 is set, this indicates whether the operator can be used
+ * immediately next to an operand without any clear separation.
+ * Bits 2 thru 7: Index into g_aExprOps of the first operator starting with
+ * this character.
+ */
+static unsigned char g_auchOpStartCharMap[256];
+/** Whether we've initialized the map. */
+static int g_fExprInitializedMap = 0;
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static void expr_unget_op(PEXPR pThis);
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis);
+
+
+
+
+/**
+ * Displays an error message.
+ *
+ * The total string length must not exceed 256 bytes.
+ *
+ * @param pThis The evaluator instance.
+ * @param pszError The message format string.
+ * @param ... The message format args.
+ */
+static void expr_error(PEXPR pThis, const char *pszError, ...)
+{
+ char szTmp[256];
+ va_list va;
+
+ va_start(va, pszError);
+ vsprintf(szTmp, pszError, va);
+ va_end(va);
+
+ OS(fatal,pThis->pFileLoc, "%s", szTmp);
+}
+
+
+/**
+ * Converts a number to a string.
+ *
+ * @returns pszDst.
+ * @param pszDst The string buffer to write into. Assumes length of EXPR_NUM_LEN.
+ * @param iSrc The number to convert.
+ */
+static char *expr_num_to_string(char *pszDst, EXPRINT64 iSrc)
+{
+ static const char s_szDigits[17] = "0123456789abcdef";
+ char szTmp[EXPR_NUM_LEN];
+ char *psz = &szTmp[EXPR_NUM_LEN - 1];
+ int fNegative;
+
+ fNegative = iSrc < 0;
+ if (fNegative)
+ {
+ /** @todo this isn't right for INT64_MIN. */
+ iSrc = -iSrc;
+ }
+
+ *psz = '\0';
+ do
+ {
+#if 0
+ *--psz = s_szDigits[iSrc & 0xf];
+ iSrc >>= 4;
+#else
+ *--psz = s_szDigits[iSrc % 10];
+ iSrc /= 10;
+#endif
+ } while (iSrc);
+
+#if 0
+ *--psz = 'x';
+ *--psz = '0';
+#endif
+
+ if (fNegative)
+ *--psz = '-';
+
+ /* copy it into the output buffer. */
+ return (char *)memcpy(pszDst, psz, &szTmp[EXPR_NUM_LEN] - psz);
+}
+
+
+/**
+ * Attempts to convert a (simple) string into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance. This is optional when fQuiet is true.
+ * @param piSrc Where to store the numeric value on success.
+ * @param pszSrc The string to try convert.
+ * @param fQuiet Whether we should be quiet or grumpy on failure.
+ */
+static EXPRRET expr_string_to_num(PEXPR pThis, EXPRINT64 *piDst, const char *pszSrc, int fQuiet)
+{
+ EXPRRET rc = kExprRet_Ok;
+ char const *psz = pszSrc;
+ EXPRINT64 i;
+ unsigned uBase;
+ int fNegative;
+
+
+ /*
+ * Skip blanks.
+ */
+ while (ISBLANK(*psz))
+ psz++;
+
+ /*
+ * Check for '-'.
+ *
+ * At this point we will not need to deal with operators, this is
+ * just an indicator of negative numbers. If some operator ends up
+ * here it's because it came from a string expansion and thus shall
+ * not be interpreted. If this turns out to be an stupid restriction
+ * it can be fixed, but for now it stays like this.
+ */
+ fNegative = *psz == '-';
+ if (fNegative)
+ psz++;
+
+ /*
+ * Determin base.
+ *
+ * Recognize some exsotic prefixes here in addition to the two standard ones.
+ */
+ if (*psz != '0')
+ uBase = 10;
+ else if (psz[1] == 'x' || psz[1] == 'X')
+ {
+ uBase = 16;
+ psz += 2;
+ }
+ else if (psz[1] == 'b' || psz[1] == 'B')
+ {
+ uBase = 2;
+ psz += 2;
+ }
+ else if (psz[1] == 'd' || psz[1] == 'D')
+ {
+ uBase = 10;
+ psz += 2;
+ }
+ else if (psz[1] == 'o' || psz[1] == 'O')
+ {
+ uBase = 8;
+ psz += 2;
+ }
+ else if (isdigit(psz[1]) && psz[1] != '9' && psz[1] != '8')
+ {
+ uBase = 8;
+ psz++;
+ }
+ else
+ uBase = 10;
+
+ /*
+ * Convert until we hit a non-digit.
+ */
+ i = 0;
+ for (;;)
+ {
+ unsigned iDigit;
+ int ch = *psz;
+ switch (ch)
+ {
+ case '0': iDigit = 0; break;
+ case '1': iDigit = 1; break;
+ case '2': iDigit = 2; break;
+ case '3': iDigit = 3; break;
+ case '4': iDigit = 4; break;
+ case '5': iDigit = 5; break;
+ case '6': iDigit = 6; break;
+ case '7': iDigit = 7; break;
+ case '8': iDigit = 8; break;
+ case '9': iDigit = 9; break;
+ case 'a':
+ case 'A': iDigit = 10; break;
+ case 'b':
+ case 'B': iDigit = 11; break;
+ case 'c':
+ case 'C': iDigit = 12; break;
+ case 'd':
+ case 'D': iDigit = 13; break;
+ case 'e':
+ case 'E': iDigit = 14; break;
+ case 'f':
+ case 'F': iDigit = 15; break;
+
+ default:
+ /* is the rest white space? */
+ while (ISSPACE(*psz))
+ psz++;
+ if (*psz != '\0')
+ {
+ iDigit = uBase;
+ break;
+ }
+ /* fall thru */
+
+ case '\0':
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ return rc;
+ }
+ if (iDigit >= uBase)
+ {
+ if (fNegative)
+ i = -i;
+ *piDst = i;
+ if (!fQuiet)
+ expr_error(pThis, "Invalid number \"%.80s\"", pszSrc);
+ return kExprRet_Error;
+ }
+
+ /* add the digit and advance */
+ i *= uBase;
+ i += iDigit;
+ psz++;
+ }
+ /* not reached */
+}
+
+
+/**
+ * Checks if the variable is a string or not.
+ *
+ * @returns 1 if it's a string, 0 otherwise.
+ * @param pVar The variable.
+ */
+static int expr_var_is_string(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_String;
+}
+
+
+/**
+ * Checks if the variable contains a string that was quoted
+ * in the expression.
+ *
+ * @returns 1 if if was a quoted string, otherwise 0.
+ * @param pVar The variable.
+ */
+static int expr_var_was_quoted(PCEXPRVAR pVar)
+{
+ return pVar->enmType >= kExprVar_QuotedString;
+}
+
+
+/**
+ * Deletes a variable.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_delete(PEXPRVAR pVar)
+{
+ if (expr_var_is_string(pVar))
+ {
+ free(pVar->uVal.psz);
+ pVar->uVal.psz = NULL;
+ }
+ pVar->enmType = kExprVar_Invalid;
+}
+
+
+/**
+ * Initializes a new variables with a sub-string value.
+ *
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static void expr_var_init_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ /* convert string needing expanding into simple ones if possible. */
+ if ( enmType == kExprVar_String
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_SimpleString;
+ else if ( enmType == kExprVar_QuotedString
+ && !memchr(psz, '$', cch))
+ enmType = kExprVar_QuotedSimpleString;
+
+ pVar->enmType = enmType;
+ pVar->uVal.psz = xmalloc(cch + 1);
+ memcpy(pVar->uVal.psz, psz, cch);
+ pVar->uVal.psz[cch] = '\0';
+}
+
+
+#if 0 /* unused */
+/**
+ * Initializes a new variables with a string value.
+ *
+ * @param pVar The new variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static void expr_var_init_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ expr_var_init_substring(pVar, psz, strlen(psz), enmType);
+}
+
+
+/**
+ * Assigns a sub-string value to a variable.
+ *
+ * @param pVar The new variable.
+ * @param psz The start of the string value.
+ * @param cch The number of chars to copy.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_substring(PEXPRVAR pVar, const char *psz, size_t cch, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ expr_var_init_substring(pVar, psz, cch, enmType);
+}
+
+
+/**
+ * Assignes a string value to a variable.
+ *
+ * @param pVar The variable.
+ * @param psz The string value.
+ * @param enmType The string type.
+ */
+static void expr_var_assign_string(PEXPRVAR pVar, const char *psz, EXPRVARTYPE enmType)
+{
+ expr_var_delete(pVar);
+ expr_var_init_string(pVar, psz, enmType);
+}
+#endif /* unused */
+
+
+/**
+ * Simplifies a string variable.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_make_simple_string(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ {
+ char *psz = (char *)xmalloc(EXPR_NUM_LEN);
+ expr_num_to_string(psz, pVar->uVal.i);
+ pVar->uVal.psz = psz;
+ pVar->enmType = kExprVar_SimpleString;
+ break;
+ }
+
+ case kExprVar_String:
+ case kExprVar_QuotedString:
+ {
+ char *psz;
+ assert(strchr(pVar->uVal.psz, '$'));
+
+ psz = allocated_variable_expand(pVar->uVal.psz);
+ free(pVar->uVal.psz);
+ pVar->uVal.psz = psz;
+
+ pVar->enmType = pVar->enmType == kExprVar_String
+ ? kExprVar_SimpleString
+ : kExprVar_QuotedSimpleString;
+ break;
+ }
+
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ assert(0);
+ }
+}
+
+
+#if 0 /* unused */
+/**
+ * Turns a variable into a string value.
+ *
+ * @param pVar The variable.
+ */
+static void expr_var_make_string(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ expr_var_make_simple_string(pVar);
+ break;
+
+ case kExprVar_String:
+ case kExprVar_SimpleString:
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* nothing to do. */
+ break;
+
+ default:
+ assert(0);
+ }
+}
+#endif /* unused */
+
+
+/**
+ * Initializes a new variables with a integer value.
+ *
+ * @param pVar The new variable.
+ * @param i The integer value.
+ */
+static void expr_var_init_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = i;
+}
+
+
+/**
+ * Assigns a integer value to a variable.
+ *
+ * @param pVar The variable.
+ * @param i The integer value.
+ */
+static void expr_var_assign_num(PEXPRVAR pVar, EXPRINT64 i)
+{
+ expr_var_delete(pVar);
+ expr_var_init_num(pVar, i);
+}
+
+
+/**
+ * Turns the variable into a number.
+ *
+ * @returns status code.
+ * @param pThis The evaluator instance.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_make_num(PEXPR pThis, PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ expr_var_make_simple_string(pVar);
+ /* fall thru */
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ EXPRRET rc = expr_string_to_num(pThis, &i, pVar->uVal.psz, 0 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ expr_error(pThis, "Cannot convert a quoted string to a number");
+ return kExprRet_Error;
+
+ default:
+ assert(0);
+ return kExprRet_Error;
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Try to turn the variable into a number.
+ *
+ * @returns status code.
+ * @param pVar The variable.
+ */
+static EXPRRET expr_var_try_make_num(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ /* nothing to do. */
+ break;
+
+ case kExprVar_String:
+ expr_var_make_simple_string(pVar);
+ /* fall thru */
+ case kExprVar_SimpleString:
+ {
+ EXPRINT64 i;
+ EXPRRET rc = expr_string_to_num(NULL, &i, pVar->uVal.psz, 1 /* fQuiet */);
+ if (rc < kExprRet_Ok)
+ return rc;
+ expr_var_assign_num(pVar, i);
+ break;
+ }
+
+ default:
+ assert(0);
+ case kExprVar_QuotedString:
+ case kExprVar_QuotedSimpleString:
+ /* can't do this */
+ return kExprRet_Error;
+ }
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Initializes a new variables with a boolean value.
+ *
+ * @param pVar The new variable.
+ * @param f The boolean value.
+ */
+static void expr_var_init_bool(PEXPRVAR pVar, int f)
+{
+ pVar->enmType = kExprVar_Num;
+ pVar->uVal.i = !!f;
+}
+
+
+/**
+ * Assigns a boolean value to a variable.
+ *
+ * @param pVar The variable.
+ * @param f The boolean value.
+ */
+static void expr_var_assign_bool(PEXPRVAR pVar, int f)
+{
+ expr_var_delete(pVar);
+ expr_var_init_bool(pVar, f);
+}
+
+
+/**
+ * Turns the variable into an boolean.
+ *
+ * @returns the boolean interpretation.
+ * @param pVar The variable.
+ */
+static int expr_var_make_bool(PEXPRVAR pVar)
+{
+ switch (pVar->enmType)
+ {
+ case kExprVar_Num:
+ pVar->uVal.i = !!pVar->uVal.i;
+ break;
+
+ case kExprVar_String:
+ expr_var_make_simple_string(pVar);
+ /* fall thru */
+ case kExprVar_SimpleString:
+ {
+ /*
+ * Try convert it to a number. If that fails, use the
+ * GNU make boolean logic - not empty string means true.
+ */
+ EXPRINT64 iVal;
+ char const *psz = pVar->uVal.psz;
+ while (ISBLANK(*psz))
+ psz++;
+ if ( *psz
+ && expr_string_to_num(NULL, &iVal, psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar, iVal != 0);
+ else
+ expr_var_assign_bool(pVar, *psz != '\0');
+ break;
+ }
+
+ case kExprVar_QuotedString:
+ expr_var_make_simple_string(pVar);
+ /* fall thru */
+ case kExprVar_QuotedSimpleString:
+ /*
+ * Use GNU make boolean logic (not empty string means true).
+ * No stripping here, the string is quoted.
+ */
+ expr_var_assign_bool(pVar, *pVar->uVal.psz != '\0');
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ return pVar->uVal.i;
+}
+
+
+/**
+ * Pops a varable off the stack and deletes it.
+ * @param pThis The evaluator instance.
+ */
+static void expr_pop_and_delete_var(PEXPR pThis)
+{
+ expr_var_delete(&pThis->aVars[pThis->iVar]);
+ pThis->iVar--;
+}
+
+
+
+/**
+ * Tries to make the variables the same type.
+ *
+ * This will not convert numbers to strings, unless one of them
+ * is a quoted string.
+ *
+ * this will try convert both to numbers if neither is quoted. Both
+ * conversions will have to suceed for this to be commited.
+ *
+ * All strings will be simplified.
+ *
+ * @returns status code. Done complaining on failure.
+ *
+ * @param pThis The evaluator instance.
+ * @param pVar1 The first variable.
+ * @param pVar2 The second variable.
+ */
+static EXPRRET expr_var_unify_types(PEXPR pThis, PEXPRVAR pVar1, PEXPRVAR pVar2, const char *pszOp)
+{
+ /*
+ * Try make the variables the same type before comparing.
+ */
+ if ( !expr_var_was_quoted(pVar1)
+ && !expr_var_was_quoted(pVar2))
+ {
+ if ( expr_var_is_string(pVar1)
+ || expr_var_is_string(pVar2))
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_try_make_num(pVar2);
+ else if (!expr_var_is_string(pVar2))
+ expr_var_try_make_num(pVar1);
+ else
+ {
+ /*
+ * Both are strings, simplify them then see if both can be made into numbers.
+ */
+ EXPRINT64 iVar1;
+ EXPRINT64 iVar2;
+
+ expr_var_make_simple_string(pVar1);
+ expr_var_make_simple_string(pVar2);
+
+ if ( expr_string_to_num(NULL, &iVar1, pVar1->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok
+ && expr_string_to_num(NULL, &iVar2, pVar2->uVal.psz, 1 /* fQuiet */) >= kExprRet_Ok)
+ {
+ expr_var_assign_num(pVar1, iVar1);
+ expr_var_assign_num(pVar2, iVar2);
+ }
+ }
+ }
+ }
+ else
+ {
+ expr_var_make_simple_string(pVar1);
+ expr_var_make_simple_string(pVar2);
+ }
+
+ /*
+ * Complain if they aren't the same type now.
+ */
+ if (expr_var_is_string(pVar1) != expr_var_is_string(pVar2))
+ {
+ expr_error(pThis, "Unable to unify types for \"%s\"", pszOp);
+ return kExprRet_Error;
+ }
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Is variable defined, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_defined(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ struct variable *pMakeVar;
+
+ expr_var_make_simple_string(pVar);
+ pMakeVar = lookup_variable(pVar->uVal.psz, strlen(pVar->uVal.psz));
+ expr_var_assign_bool(pVar, pMakeVar && *pMakeVar->value != '\0');
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Does file(/dir/whatever) exist, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_exists(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ struct stat st;
+
+ expr_var_make_simple_string(pVar);
+ expr_var_assign_bool(pVar, stat(pVar->uVal.psz, &st) == 0);
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Is target defined, unary.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_target(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+ struct file *pFile = NULL;
+
+ /*
+ * Because of secondary target expansion, lookup the unexpanded
+ * name first.
+ */
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+ if ( pVar->enmType == kExprVar_String
+ || pVar->enmType == kExprVar_QuotedString)
+ {
+ pFile = lookup_file(pVar->uVal.psz);
+ if ( pFile
+ && !pFile->need_2nd_target_expansion)
+ pFile = NULL;
+ }
+ if (!pFile)
+#endif
+ {
+ expr_var_make_simple_string(pVar);
+ pFile = lookup_file(pVar->uVal.psz);
+ }
+
+ /*
+ * Always inspect the head of a multiple target rule
+ * and look for a file with commands.
+ */
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (pFile && pFile->multi_head)
+ pFile = pFile->multi_head;
+#endif
+
+ while (pFile && !pFile->cmds)
+ pFile = pFile->prev;
+
+ expr_var_assign_bool(pVar, pFile != NULL && pFile->is_target);
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Convert to boolean.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bool(PEXPR pThis)
+{
+ expr_var_make_bool(&pThis->aVars[pThis->iVar]);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Convert to number, works on quoted strings too.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_num(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ /* unquote the string */
+ if (pVar->enmType == kExprVar_QuotedSimpleString)
+ pVar->enmType = kExprVar_SimpleString;
+ else if (pVar->enmType == kExprVar_QuotedString)
+ pVar->enmType = kExprVar_String;
+
+ return expr_var_make_num(pThis, pVar);
+}
+
+
+/**
+ * Performs a strlen() on the simplified/converted string argument.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_strlen(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ expr_var_make_simple_string(pVar);
+ expr_var_assign_num(pVar, strlen(pVar->uVal.psz));
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Convert to string (simplified and quoted)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_str(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ expr_var_make_simple_string(pVar);
+ pVar->enmType = kExprVar_QuotedSimpleString;
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Pluss (dummy / make_integer)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_pluss(PEXPR pThis)
+{
+ return expr_var_make_num(pThis, &pThis->aVars[pThis->iVar]);
+}
+
+
+/**
+ * Minus (negate)
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_minus(PEXPR pThis)
+{
+ EXPRRET rc;
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = -pVar->uVal.i;
+
+ return rc;
+}
+
+
+
+/**
+ * Bitwise NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_not(PEXPR pThis)
+{
+ EXPRRET rc;
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar);
+ if (rc >= kExprRet_Ok)
+ pVar->uVal.i = ~pVar->uVal.i;
+
+ return rc;
+}
+
+
+/**
+ * Logical NOT.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_not(PEXPR pThis)
+{
+ PEXPRVAR pVar = &pThis->aVars[pThis->iVar];
+
+ expr_var_make_bool(pVar);
+ pVar->uVal.i = !pVar->uVal.i;
+
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Multiplication.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_multiply(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i *= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Division.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_divide(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i /= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Modulus.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_modulus(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i %= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+
+/**
+ * Addition (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_add(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i += pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Subtract (numeric).
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_sub(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i -= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+/**
+ * Bitwise left shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_left(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i <<= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise right shift.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_shift_right(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i >>= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_or_equal_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "vle");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than or equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_or_equal_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "<=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i <= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) <= 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_less_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "vlt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Less than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_less_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "<");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i < pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) < 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_or_equal_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "vge");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater or equal than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_or_equal_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, ">=");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i >= pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) >= 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_greater_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, "vgt");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Greater than.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_greater_than(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ rc = expr_var_unify_types(pThis, pVar1, pVar2, ">");
+ if (rc >= kExprRet_Ok)
+ {
+ if (!expr_var_is_string(pVar1))
+ expr_var_assign_bool(pVar1, pVar1->uVal.i > pVar2->uVal.i);
+ else
+ expr_var_assign_bool(pVar1, strcmp(pVar1->uVal.psz, pVar2->uVal.psz) > 0);
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal, version strings.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ expr_var_make_simple_string(pVar1);
+ expr_var_make_simple_string(pVar2);
+ if (!version_compare(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Try convert the string to a valid number and compare the numbers.
+ * 2. Convert the non-string to a number and compare the strings.
+ */
+ if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ expr_var_make_simple_string(pVar1);
+ expr_var_make_simple_string(pVar2);
+ expr_var_assign_bool(pVar1, version_compare(pVar1->uVal.psz, pVar2->uVal.psz) == 0);
+ }
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal, version string.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_ver_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_ver_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_equal(PEXPR pThis)
+{
+ EXPRRET rc = kExprRet_Ok;
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ int const fIsString1 = expr_var_is_string(pVar1);
+
+ /*
+ * The same type?
+ */
+ if (fIsString1 == expr_var_is_string(pVar2))
+ {
+ if (!fIsString1)
+ /* numbers are simple */
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ /* try a normal string compare. */
+ expr_var_make_simple_string(pVar1);
+ expr_var_make_simple_string(pVar2);
+ if (!strcmp(pVar1->uVal.psz, pVar2->uVal.psz))
+ expr_var_assign_bool(pVar1, 1);
+ /* try convert and compare as number instead. */
+ else if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ /* ok, they really aren't equal. */
+ else
+ expr_var_assign_bool(pVar1, 0);
+ }
+ }
+ else
+ {
+ /*
+ * If the type differs, there are now two options:
+ * 1. Convert the string to a valid number and compare the numbers.
+ * 2. Convert an empty string to a 'false' boolean value and compare
+ * numerically. This one is a bit questionable, so we don't try this.
+ */
+ if ( expr_var_try_make_num(pVar1) >= kExprRet_Ok
+ && expr_var_try_make_num(pVar2) >= kExprRet_Ok)
+ expr_var_assign_bool(pVar1, pVar1->uVal.i == pVar2->uVal.i);
+ else
+ {
+ expr_error(pThis, "Cannot compare strings and numbers");
+ rc = kExprRet_Error;
+ }
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return rc;
+}
+
+
+/**
+ * Not equal.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_not_equal(PEXPR pThis)
+{
+ EXPRRET rc = expr_op_equal(pThis);
+ if (rc >= kExprRet_Ok)
+ rc = expr_op_logical_not(pThis);
+ return rc;
+}
+
+
+/**
+ * Bitwise AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_and(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc;
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i &= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Bitwise XOR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_xor(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc;
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i ^= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Bitwise OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_bitwise_or(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+ EXPRRET rc;
+
+ rc = expr_var_make_num(pThis, pVar1);
+ if (rc >= kExprRet_Ok)
+ {
+ rc = expr_var_make_num(pThis, pVar2);
+ if (rc >= kExprRet_Ok)
+ pVar1->uVal.i |= pVar2->uVal.i;
+ }
+
+ expr_pop_and_delete_var(pThis);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Logical AND.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_and(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ if ( expr_var_make_bool(pVar1)
+ && expr_var_make_bool(pVar2))
+ expr_var_assign_bool(pVar1, 1);
+ else
+ expr_var_assign_bool(pVar1, 0);
+
+ expr_pop_and_delete_var(pThis);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Logical OR.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_logical_or(PEXPR pThis)
+{
+ PEXPRVAR pVar1 = &pThis->aVars[pThis->iVar - 1];
+ PEXPRVAR pVar2 = &pThis->aVars[pThis->iVar];
+
+ if ( expr_var_make_bool(pVar1)
+ || expr_var_make_bool(pVar2))
+ expr_var_assign_bool(pVar1, 1);
+ else
+ expr_var_assign_bool(pVar1, 0);
+
+ expr_pop_and_delete_var(pThis);
+ return kExprRet_Ok;
+}
+
+
+/**
+ * Left parenthesis.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_left_parenthesis(PEXPR pThis)
+{
+ /*
+ * There should be a right parenthesis operator lined up for us now,
+ * eat it. If not found there is an inbalance.
+ */
+ EXPRRET rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if ( rc == kExprRet_Operator
+ && pThis->apOps[pThis->iOp]->szOp[0] == ')')
+ {
+ /* pop it and get another one which we can leave pending. */
+ pThis->iOp--;
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc >= kExprRet_Ok)
+ expr_unget_op(pThis);
+ }
+ else
+ {
+ expr_error(pThis, "Missing ')'");
+ rc = kExprRet_Error;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Right parenthesis, dummy that's never actually called.
+ *
+ * @returns Status code.
+ * @param pThis The instance.
+ */
+static EXPRRET expr_op_right_parenthesis(PEXPR pThis)
+{
+ assert(0);
+ (void)pThis;
+ return kExprRet_Ok;
+}
+
+
+
+
+
+/**
+ * The operator table.
+ *
+ * This table is NOT ordered by precedence, but for linear search
+ * allowing for first match to return the correct operator. This
+ * means that || must come before |, or else | will match all.
+ */
+static const EXPROP g_aExprOps[] =
+{
+#define EXPR_OP(szOp, iPrecedence, cArgs, pfn) { szOp, sizeof(szOp) - 1, '\0', iPrecedence, cArgs, pfn }
+ /* Name, iPrecedence, cArgs, pfn */
+ EXPR_OP("defined", 90, 1, expr_op_defined),
+ EXPR_OP("exists", 90, 1, expr_op_exists),
+ EXPR_OP("target", 90, 1, expr_op_target),
+ EXPR_OP("bool", 90, 1, expr_op_bool),
+ EXPR_OP("num", 90, 1, expr_op_num),
+ EXPR_OP("strlen", 90, 1, expr_op_strlen),
+ EXPR_OP("str", 90, 1, expr_op_str),
+ EXPR_OP("+", 80, 1, expr_op_pluss),
+ EXPR_OP("-", 80, 1, expr_op_minus),
+ EXPR_OP("~", 80, 1, expr_op_bitwise_not),
+ EXPR_OP("*", 75, 2, expr_op_multiply),
+ EXPR_OP("/", 75, 2, expr_op_divide),
+ EXPR_OP("%", 75, 2, expr_op_modulus),
+ EXPR_OP("+", 70, 2, expr_op_add),
+ EXPR_OP("-", 70, 2, expr_op_sub),
+ EXPR_OP("<<", 65, 2, expr_op_shift_left),
+ EXPR_OP(">>", 65, 2, expr_op_shift_right),
+ EXPR_OP("<=", 60, 2, expr_op_less_or_equal_than),
+ EXPR_OP("<", 60, 2, expr_op_less_than),
+ EXPR_OP(">=", 60, 2, expr_op_greater_or_equal_than),
+ EXPR_OP(">", 60, 2, expr_op_greater_than),
+ EXPR_OP("vle", 60, 2, expr_op_ver_less_or_equal_than),
+ EXPR_OP("vlt", 60, 2, expr_op_ver_less_than),
+ EXPR_OP("vge", 60, 2, expr_op_ver_greater_or_equal_than),
+ EXPR_OP("vgt", 60, 2, expr_op_ver_greater_than),
+ EXPR_OP("==", 55, 2, expr_op_equal),
+ EXPR_OP("veq", 55, 2, expr_op_ver_equal),
+ EXPR_OP("!=", 55, 2, expr_op_not_equal),
+ EXPR_OP("vne", 55, 2, expr_op_ver_not_equal),
+ EXPR_OP("!", 80, 1, expr_op_logical_not),
+ EXPR_OP("^", 45, 2, expr_op_bitwise_xor),
+ EXPR_OP("&&", 35, 2, expr_op_logical_and),
+ EXPR_OP("&", 50, 2, expr_op_bitwise_and),
+ EXPR_OP("||", 30, 2, expr_op_logical_or),
+ EXPR_OP("|", 40, 2, expr_op_bitwise_or),
+ { "(", 1, ')', 10, 1, expr_op_left_parenthesis },
+ { ")", 1, '(', 10, 0, expr_op_right_parenthesis },
+ /* { "?", 1, ':', 5, 2, expr_op_question },
+ { ":", 1, '?', 5, 2, expr_op_colon }, -- too weird for now. */
+#undef EXPR_OP
+};
+
+/** Dummy end of expression fake. */
+static const EXPROP g_ExprEndOfExpOp =
+{
+ "", 0, '\0', 0, 0, NULL
+};
+
+
+/**
+ * Initializes the opcode character map if necessary.
+ */
+static void expr_map_init(void)
+{
+ unsigned i;
+ if (g_fExprInitializedMap)
+ return;
+
+ /*
+ * Initialize it.
+ */
+ memset(&g_auchOpStartCharMap, 0, sizeof(g_auchOpStartCharMap));
+ for (i = 0; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ unsigned int ch = (unsigned int)g_aExprOps[i].szOp[0];
+ if (!g_auchOpStartCharMap[ch])
+ {
+ g_auchOpStartCharMap[ch] = (i << 2) | 1;
+ if (!isalpha(ch))
+ g_auchOpStartCharMap[ch] |= 2; /* Need no clear separation from operands. */
+ }
+ }
+
+ /* whitespace (assumes C-like locale because I'm lazy): */
+#define SET_WHITESPACE(a_ch) do { \
+ assert(g_auchOpStartCharMap[(unsigned char)(a_ch)] == 0); \
+ g_auchOpStartCharMap[(unsigned char)(a_ch)] |= 2; \
+ } while (0)
+ SET_WHITESPACE(' ');
+ SET_WHITESPACE('\t');
+ SET_WHITESPACE('\n');
+ SET_WHITESPACE('\r');
+ SET_WHITESPACE('\v');
+ SET_WHITESPACE('\f');
+
+ g_fExprInitializedMap = 1;
+}
+
+
+/**
+ * Looks up a character in the map.
+ *
+ * @returns the value for that char, see g_auchOpStartCharMap for details.
+ * @param ch The character.
+ */
+static unsigned char expr_map_get(char ch)
+{
+ return g_auchOpStartCharMap[(unsigned char)ch];
+}
+
+
+/**
+ * Searches the operator table given a potential operator start char.
+ *
+ * @returns Pointer to the matching operator. NULL if not found.
+ * @param psz Pointer to what can be an operator.
+ * @param uchVal The expr_map_get value.
+ * @param fUnary Whether it must be an unary operator or not.
+ */
+static PCEXPROP expr_lookup_op(char const *psz, unsigned char uchVal, int fUnary)
+{
+ char ch = *psz;
+ unsigned i;
+ assert((uchVal & 2) == (isalpha(ch) ? 0 : 2));
+
+ for (i = uchVal >> 2; i < sizeof(g_aExprOps) / sizeof(g_aExprOps[0]); i++)
+ {
+ /* compare the string... */
+ if (g_aExprOps[i].szOp[0] != ch)
+ continue;
+ switch (g_aExprOps[i].cchOp)
+ {
+ case 1:
+ break;
+ case 2:
+ if (g_aExprOps[i].szOp[1] != psz[1])
+ continue;
+ break;
+ default:
+ if (strncmp(&g_aExprOps[i].szOp[1], psz + 1, g_aExprOps[i].cchOp - 1))
+ continue;
+ break;
+ }
+
+ /* ... and the operator type. */
+ if (fUnary == (g_aExprOps[i].cArgs == 1))
+ {
+ /* Check if we've got the needed operand separation: */
+ if ( (uchVal & 2)
+ || EXPR_IS_OP_SEPARATOR(psz[g_aExprOps[i].cchOp]))
+ {
+ /* got a match! */
+ return &g_aExprOps[i];
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Ungets a binary operator.
+ *
+ * The operator is poped from the stack and put in the pending position.
+ *
+ * @param pThis The evaluator instance.
+ */
+static void expr_unget_op(PEXPR pThis)
+{
+ assert(pThis->pPending == NULL);
+ assert(pThis->iOp >= 0);
+
+ pThis->pPending = pThis->apOps[pThis->iOp];
+ pThis->apOps[pThis->iOp] = NULL;
+ pThis->iOp--;
+}
+
+
+
+/**
+ * Get the next token, it should be a binary operator, or the end of
+ * the expression, or a right parenthesis.
+ *
+ * The operator is pushed onto the stack and the status code indicates
+ * which of the two we found.
+ *
+ * @returns status code. Will grumble on failure.
+ * @retval kExprRet_EndOfExpr if we encountered the end of the expression.
+ * @retval kExprRet_Operator if we encountered a binary operator or right
+ * parenthesis. It's on the operator stack.
+ *
+ * @param pThis The evaluator instance.
+ */
+static EXPRRET expr_get_binary_or_eoe_or_rparen(PEXPR pThis)
+{
+ /*
+ * See if there is anything pending first.
+ */
+ PCEXPROP pOp = pThis->pPending;
+ if (pOp)
+ pThis->pPending = NULL;
+ else
+ {
+ /*
+ * Eat more of the expression.
+ */
+ char const *psz = pThis->psz;
+
+ /* spaces */
+ unsigned char uchVal;
+ char ch;
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+
+ /* see what we've got. */
+ if (ch)
+ {
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (!pOp)
+ {
+ expr_error(pThis, "Expected binary operator, found \"%.42s\"...", psz);
+ return kExprRet_Error;
+ }
+ psz += pOp->cchOp;
+ }
+ else
+ pOp = &g_ExprEndOfExpOp;
+ pThis->psz = psz;
+ }
+
+ /*
+ * Push it.
+ */
+ if (pThis->iOp >= EXPR_MAX_OPERATORS - 1)
+ {
+ expr_error(pThis, "Operator stack overflow");
+ return kExprRet_Error;
+ }
+ pThis->apOps[++pThis->iOp] = pOp;
+
+ return pOp->iPrecedence
+ ? kExprRet_Operator
+ : kExprRet_EndOfExpr;
+}
+
+
+
+/**
+ * Get the next token, it should be an unary operator or an operand.
+ *
+ * This will fail if encountering the end of the expression since
+ * it is implied that there should be something more.
+ *
+ * The token is pushed onto the respective stack and the status code
+ * indicates which it is.
+ *
+ * @returns status code. On failure we'll be done bitching already.
+ * @retval kExprRet_Operator if we encountered an unary operator.
+ * It's on the operator stack.
+ * @retval kExprRet_Operand if we encountered an operand operator.
+ * It's on the operand stack.
+ *
+ * @param This The evaluator instance.
+ */
+static EXPRRET expr_get_unary_or_operand(PEXPR pThis)
+{
+ EXPRRET rc;
+ unsigned char uchVal;
+ PCEXPROP pOp;
+ char const *psz = pThis->psz;
+ char ch;
+
+ /*
+ * Eat white space and make sure there is something after it.
+ */
+ while (((uchVal = expr_map_get((ch = *psz))) & 3) == 2)
+ psz++;
+ if (ch == '\0')
+ {
+ expr_error(pThis, "Unexpected end of expression");
+ return kExprRet_Error;
+ }
+
+ /*
+ * Is it an operator?
+ */
+ pOp = NULL;
+ if (uchVal & 1)
+ pOp = expr_lookup_op(psz, uchVal, 1 /* fUnary */);
+ if (pOp)
+ {
+ /*
+ * Push the operator onto the stack.
+ */
+ if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ pThis->apOps[++pThis->iOp] = pOp;
+ rc = kExprRet_Operator;
+ }
+ else
+ {
+ expr_error(pThis, "Operator stack overflow");
+ rc = kExprRet_Error;
+ }
+ psz += pOp->cchOp;
+ }
+ else if (pThis->iVar < EXPR_MAX_OPERANDS - 1)
+ {
+ /*
+ * It's an operand. Figure out where it ends and
+ * push it onto the stack.
+ */
+ const char *pszStart;
+
+ rc = kExprRet_Ok;
+ if (ch == '"')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '"')
+ psz++;
+ expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedString);
+ if (ch != '\0')
+ psz++;
+ }
+ else if (ch == '\'')
+ {
+ pszStart = ++psz;
+ while ((ch = *psz) != '\0' && ch != '\'')
+ psz++;
+ expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_QuotedSimpleString);
+ if (ch != '\0')
+ psz++;
+ }
+ else
+ {
+ char achPars[20];
+ int iPar = -1;
+ char chEndPar = '\0';
+
+ pszStart = psz;
+ while ((ch = *psz) != '\0')
+ {
+ char ch2;
+
+ /* $(adsf) or ${asdf} needs special handling. */
+ if ( ch == '$'
+ && ( (ch2 = psz[1]) == '('
+ || ch2 == '{'))
+ {
+ psz++;
+ if (iPar > (int)(sizeof(achPars) / sizeof(achPars[0])))
+ {
+ expr_error(pThis, "Too deep nesting of variable expansions");
+ rc = kExprRet_Error;
+ break;
+ }
+ achPars[++iPar] = chEndPar = ch2 == '(' ? ')' : '}';
+ }
+ else if (ch == chEndPar)
+ {
+ iPar--;
+ chEndPar = iPar >= 0 ? achPars[iPar] : '\0';
+ }
+ else if (!chEndPar)
+ {
+ uchVal = expr_map_get(ch);
+ if (uchVal == 0)
+ { /*likely*/ }
+ else if ((uchVal & 3) == 2 /*isspace*/)
+ break;
+ else if ( (uchVal & 1)
+ && psz != pszStart /* not at the start */
+ && ( (uchVal & 2) /* operator without separator needs */
+ || EXPR_IS_OP_SEPARATOR_NO_SPACE(psz[-1])))
+ {
+ pOp = expr_lookup_op(psz, uchVal, 0 /* fUnary */);
+ if (pOp)
+ break;
+ }
+ }
+
+ /* next */
+ psz++;
+ }
+
+ if (rc == kExprRet_Ok)
+ expr_var_init_substring(&pThis->aVars[++pThis->iVar], pszStart, psz - pszStart, kExprVar_String);
+ }
+ }
+ else
+ {
+ expr_error(pThis, "Operand stack overflow");
+ rc = kExprRet_Error;
+ }
+ pThis->psz = psz;
+
+ return rc;
+}
+
+
+/**
+ * Evaluates the current expression.
+ *
+ * @returns status code.
+ *
+ * @param pThis The instance.
+ */
+static EXPRRET expr_eval(PEXPR pThis)
+{
+ EXPRRET rc;
+ PCEXPROP pOp;
+
+ /*
+ * The main loop.
+ */
+ for (;;)
+ {
+ /*
+ * Eat unary operators until we hit an operand.
+ */
+ do rc = expr_get_unary_or_operand(pThis);
+ while (rc == kExprRet_Operator);
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Look for a binary operator, right parenthesis or end of expression.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ expr_unget_op(pThis);
+
+ /*
+ * Pop operators and apply them.
+ *
+ * Parenthesis will be handed via precedence, where the left parenthesis
+ * will go pop the right one and make another operator pending.
+ */
+ while ( pThis->iOp >= 0
+ && pThis->apOps[pThis->iOp]->iPrecedence >= pThis->pPending->iPrecedence)
+ {
+ pOp = pThis->apOps[pThis->iOp--];
+ assert(pThis->iVar + 1 >= pOp->cArgs);
+ rc = pOp->pfn(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ }
+ if (rc < kExprRet_Ok)
+ break;
+
+ /*
+ * Get the next binary operator or end of expression.
+ * There should be no right parenthesis here.
+ */
+ rc = expr_get_binary_or_eoe_or_rparen(pThis);
+ if (rc < kExprRet_Ok)
+ break;
+ pOp = pThis->apOps[pThis->iOp];
+ if (!pOp->iPrecedence)
+ break; /* end of expression */
+ if (!pOp->cArgs)
+ {
+ expr_error(pThis, "Unexpected \"%s\"", pOp->szOp);
+ rc = kExprRet_Error;
+ break;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Destroys the given instance.
+ *
+ * @param pThis The instance to destroy.
+ */
+static void expr_destroy(PEXPR pThis)
+{
+ while (pThis->iVar >= 0)
+ {
+ expr_var_delete(pThis->aVars);
+ pThis->iVar--;
+ }
+ free(pThis);
+}
+
+
+/**
+ * Instantiates an expression evaluator.
+ *
+ * @returns The instance.
+ *
+ * @param pszExpr What to parse.
+ * This must stick around until expr_destroy.
+ */
+static PEXPR expr_create(char const *pszExpr)
+{
+ PEXPR pThis = (PEXPR)xmalloc(sizeof(*pThis));
+ pThis->pszExpr = pszExpr;
+ pThis->psz = pszExpr;
+ pThis->pFileLoc = NULL;
+ pThis->pPending = NULL;
+ pThis->iVar = -1;
+ pThis->iOp = -1;
+
+ expr_map_init();
+ return pThis;
+}
+
+
+/**
+ * Evaluates the given if expression.
+ *
+ * @returns -1, 0 or 1. (GNU make conditional check convention, see read.c.)
+ * @retval -1 if the expression is invalid.
+ * @retval 0 if the expression is true
+ * @retval 1 if the expression is false.
+ *
+ * @param line The expression.
+ * @param flocp The file location, used for errors.
+ */
+int expr_eval_if_conditionals(const char *line, const floc *flocp)
+{
+ /*
+ * Instantiate the expression evaluator and let
+ * it have a go at it.
+ */
+ int rc = -1;
+ PEXPR pExpr = expr_create(line);
+ pExpr->pFileLoc = flocp;
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to boolean and
+ * set our return value accordingly.
+ */
+ if (expr_var_make_bool(&pExpr->aVars[0]))
+ rc = 0;
+ else
+ rc = 1;
+ }
+ expr_destroy(pExpr);
+
+ return rc;
+}
+
+
+/**
+ * Evaluates the given expression and returns the result as a string.
+ *
+ * @returns variable buffer position.
+ *
+ * @param o The current variable buffer position.
+ * @param expr The expression.
+ */
+char *expr_eval_to_string(char *o, const char *expr)
+{
+ /*
+ * Instantiate the expression evaluator and let
+ * it have a go at it.
+ */
+ PEXPR pExpr = expr_create(expr);
+ if (expr_eval(pExpr) >= kExprRet_Ok)
+ {
+ /*
+ * Convert the result (on top of the stack) to a string
+ * and copy it out the variable buffer.
+ */
+ PEXPRVAR pVar = &pExpr->aVars[0];
+ expr_var_make_simple_string(pVar);
+ o = variable_buffer_output(o, pVar->uVal.psz, strlen(pVar->uVal.psz));
+ }
+ else
+ o = variable_buffer_output(o, "<expression evaluation failed>", sizeof("<expression evaluation failed>") - 1);
+ expr_destroy(pExpr);
+
+ return o;
+}
+
+
+#endif /* CONFIG_WITH_IF_CONDITIONALS */
+
diff --git a/src/kmk/file.c b/src/kmk/file.c
new file mode 100644
index 0000000..888d639
--- /dev/null
+++ b/src/kmk/file.c
@@ -0,0 +1,1458 @@
+/* Target file management for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "debug.h"
+#include "hash.h"
+#ifdef CONFIG_WITH_STRCACHE2
+# include <stddef.h>
+#endif
+
+
+/* Remember whether snap_deps has been invoked: we need this to be sure we
+ don't add new rules (via $(eval ...)) afterwards. In the future it would
+ be nice to support this, but it means we'd need to re-run snap_deps() or
+ at least its functionality... it might mean changing snap_deps() to be run
+ per-file, so we can invoke it after the eval... or remembering which files
+ in the hash have been snapped (a new boolean flag?) and having snap_deps()
+ only work on files which have not yet been snapped. */
+int snapped_deps = 0;
+
+/* Hash table of files the makefile knows how to make. */
+
+#ifndef CONFIG_WITH_STRCACHE2
+static unsigned long
+file_hash_1 (const void *key)
+{
+ return_ISTRING_HASH_1 (((struct file const *) key)->hname);
+}
+
+static unsigned long
+file_hash_2 (const void *key)
+{
+ return_ISTRING_HASH_2 (((struct file const *) key)->hname);
+}
+#endif /* !CONFIG_WITH_STRCACHE2 */
+
+static int
+file_hash_cmp (const void *x, const void *y)
+{
+#ifndef CONFIG_WITH_STRCACHE2
+ return_ISTRING_COMPARE (((struct file const *) x)->hname,
+ ((struct file const *) y)->hname);
+#else /* CONFIG_WITH_STRCACHE2 */
+ return ((struct file const *) x)->hname
+ == ((struct file const *) y)->hname ? 0 : -1;
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+static struct hash_table files;
+
+/* Whether or not .SECONDARY with no prerequisites was given. */
+static int all_secondary = 0;
+
+/* Access the hash table of all file records.
+ lookup_file given a name, return the struct file * for that name,
+ or nil if there is none.
+*/
+
+#ifndef CONFIG_WITH_STRCACHE2
+struct file *
+lookup_file (const char *name)
+#else /* CONFIG_WITH_STRCACHE2 */
+MY_INLINE struct file *
+lookup_file_common (const char *name, int cached)
+#endif /* CONFIG_WITH_STRCACHE2 */
+{
+ struct file *f;
+ struct file file_key;
+#ifdef VMS
+ int want_vmsify;
+#ifndef WANT_CASE_SENSITIVE_TARGETS
+ char *lname;
+#endif
+#endif
+
+ assert (*name != '\0');
+
+ /* This is also done in parse_file_seq, so this is redundant
+ for names read from makefiles. It is here for names passed
+ on the command line. */
+#ifdef VMS
+ want_vmsify = (strpbrk (name, "]>:^") != NULL);
+# ifndef WANT_CASE_SENSITIVE_TARGETS
+ if (*name != '.')
+ {
+ const char *n;
+ char *ln;
+ lname = xstrdup (name);
+ for (n = name, ln = lname; *n != '\0'; ++n, ++ln)
+ *ln = isupper ((unsigned char)*n) ? tolower ((unsigned char)*n) : *n;
+ *ln = '\0';
+ name = lname;
+ }
+# endif
+
+ while (name[0] == '[' && name[1] == ']' && name[2] != '\0')
+ name += 2;
+ while (name[0] == '<' && name[1] == '>' && name[2] != '\0')
+ name += 2;
+#endif
+ while (name[0] == '.'
+#ifdef HAVE_DOS_PATHS
+ && (name[1] == '/' || name[1] == '\\')
+#else
+ && name[1] == '/'
+#endif
+ && name[2] != '\0')
+ {
+ name += 2;
+ while (*name == '/'
+#ifdef HAVE_DOS_PATHS
+ || *name == '\\'
+#endif
+ )
+ /* Skip following slashes: ".//foo" is "foo", not "/foo". */
+ ++name;
+ }
+
+ if (*name == '\0')
+ {
+ /* It was all slashes after a dot. */
+#if defined(_AMIGA)
+ name = "";
+#else
+ name = "./";
+#endif
+#if defined(VMS)
+ /* TODO - This section is probably not needed. */
+ if (want_vmsify)
+ name = "[]";
+#endif
+ }
+#ifndef CONFIG_WITH_STRCACHE2
+ file_key.hname = name;
+ f = hash_find_item (&files, &file_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ if (!cached)
+ {
+ file_key.hname = strcache2_lookup_file (&file_strcache, name, strlen (name));
+ if (file_key.hname)
+ f = hash_find_item_strcached (&files, &file_key);
+ else
+ f = NULL;
+ }
+ else
+ {
+ file_key.hname = name;
+ f = hash_find_item_strcached (&files, &file_key);
+ }
+
+#endif /* CONFIG_WITH_STRCACHE2 */
+#if defined(VMS) && !defined(WANT_CASE_SENSITIVE_TARGETS)
+ if (*name != '.')
+ free (lname);
+#endif
+
+ return f;
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+/* Given a name, return the struct file * for that name,
+ or nil if there is none. */
+
+struct file *
+lookup_file (const char *name)
+{
+ return lookup_file_common (name, 0 /* cached */);
+}
+
+/* Given a name in the strcache, return the struct file * for that name,
+ or nil if there is none. */
+struct file *
+lookup_file_cached (const char *name)
+{
+ assert (strcache_iscached (name));
+ return lookup_file_common (name, 1 /* cached */);
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+
+/* Look up a file record for file NAME and return it.
+ Create a new record if one doesn't exist. NAME will be stored in the
+ new record so it should be constant or in the strcache etc.
+ */
+
+struct file *
+enter_file (const char *name)
+{
+ struct file *f;
+ struct file *new;
+ struct file **file_slot;
+ struct file file_key;
+
+ assert (*name != '\0');
+ assert (! verify_flag || strcache_iscached (name));
+
+#if defined(VMS) && !defined(WANT_CASE_SENSITIVE_TARGETS)
+ if (*name != '.')
+ {
+ const char *n;
+ char *lname, *ln;
+ lname = xstrdup (name);
+ for (n = name, ln = lname; *n != '\0'; ++n, ++ln)
+ if (isupper ((unsigned char)*n))
+ *ln = tolower ((unsigned char)*n);
+ else
+ *ln = *n;
+
+ *ln = '\0';
+ name = strcache_add (lname);
+ free (lname);
+ }
+#endif
+
+ file_key.hname = name;
+#ifndef CONFIG_WITH_STRCACHE2
+ file_slot = (struct file **) hash_find_slot (&files, &file_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ file_slot = (struct file **) hash_find_slot_strcached (&files, &file_key);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ f = *file_slot;
+ if (! HASH_VACANT (f) && !f->double_colon)
+ {
+ f->builtin = 0;
+ return f;
+ }
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ new = xcalloc (sizeof (struct file));
+#else
+ new = alloccache_calloc (&file_cache);
+#endif
+ new->name = new->hname = name;
+ new->update_status = us_none;
+
+ if (HASH_VACANT (f))
+ {
+ new->last = new;
+ hash_insert_at (&files, new, file_slot);
+ }
+ else
+ {
+ /* There is already a double-colon entry for this file. */
+ new->double_colon = f;
+ f->last->prev = new;
+ f->last = new;
+ }
+
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+ /* Check if the name needs 2nd expansion or not. */
+ if (second_target_expansion && strchr (name, '$') != NULL)
+ new->need_2nd_target_expansion = 1;
+#endif
+
+ return new;
+}
+
+/* Rehash FILE to NAME. This is not as simple as resetting
+ the 'hname' member, since it must be put in a new hash bucket,
+ and possibly merged with an existing file called NAME. */
+
+void
+rehash_file (struct file *from_file, const char *to_hname)
+{
+ struct file file_key;
+ struct file **file_slot;
+ struct file *to_file;
+ struct file *deleted_file;
+ struct file *f;
+
+#ifdef CONFIG_WITH_STRCACHE2
+ assert (strcache_iscached (to_hname));
+ assert (strcache_iscached (from_file->hname));
+#endif
+
+ /* If it's already that name, we're done. */
+ from_file->builtin = 0;
+ file_key.hname = to_hname;
+ if (! file_hash_cmp (from_file, &file_key))
+ return;
+
+ /* Find the end of the renamed list for the "from" file. */
+ file_key.hname = from_file->hname;
+ while (from_file->renamed != 0)
+ from_file = from_file->renamed;
+ if (file_hash_cmp (from_file, &file_key))
+ /* hname changed unexpectedly!! */
+ abort ();
+
+ /* Remove the "from" file from the hash. */
+#ifndef CONFIG_WITH_STRCACHE2
+ deleted_file = hash_delete (&files, from_file);
+#else
+ deleted_file = hash_delete_strcached (&files, from_file);
+#endif
+ if (deleted_file != from_file)
+ /* from_file isn't the one stored in files */
+ abort ();
+
+ /* Find where the newly renamed file will go in the hash. */
+ file_key.hname = to_hname;
+#ifndef CONFIG_WITH_STRCACHE2
+ file_slot = (struct file **) hash_find_slot (&files, &file_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ file_slot = (struct file **) hash_find_slot_strcached (&files, &file_key);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ to_file = *file_slot;
+
+ /* Change the hash name for this file. */
+ from_file->hname = to_hname;
+ for (f = from_file->double_colon; f != 0; f = f->prev)
+ f->hname = to_hname;
+
+ /* If the new name doesn't exist yet just set it to the renamed file. */
+ if (HASH_VACANT (to_file))
+ {
+ hash_insert_at (&files, from_file, file_slot);
+ return;
+ }
+
+ /* TO_FILE already exists under TO_HNAME.
+ We must retain TO_FILE and merge FROM_FILE into it. */
+
+ if (from_file->cmds != 0)
+ {
+ if (to_file->cmds == 0)
+ to_file->cmds = from_file->cmds;
+ else if (from_file->cmds != to_file->cmds)
+ {
+ size_t l = strlen (from_file->name);
+ /* We have two sets of commands. We will go with the
+ one given in the rule explicitly mentioning this name,
+ but give a message to let the user know what's going on. */
+ if (to_file->cmds->fileinfo.filenm != 0)
+ error (&from_file->cmds->fileinfo,
+ l + strlen (to_file->cmds->fileinfo.filenm) + INTSTR_LENGTH,
+ _("Recipe was specified for file '%s' at %s:%lu,"),
+ from_file->name, to_file->cmds->fileinfo.filenm,
+ to_file->cmds->fileinfo.lineno);
+ else
+ error (&from_file->cmds->fileinfo, l,
+ _("Recipe for file '%s' was found by implicit rule search,"),
+ from_file->name);
+ l += strlen (to_hname);
+ error (&from_file->cmds->fileinfo, l,
+ _("but '%s' is now considered the same file as '%s'."),
+ from_file->name, to_hname);
+ error (&from_file->cmds->fileinfo, l,
+ _("Recipe for '%s' will be ignored in favor of the one for '%s'."),
+ to_hname, from_file->name);
+ }
+ }
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* Merge multi target attributes and considerations. */
+ if (from_file->multi_head)
+ {
+ if (to_file->multi_head)
+ OSS (fatal, NILF, _("can't rename/merge multi target '%s' with multi target '%s'"),
+ from_file->name, to_hname);
+
+ to_file->multi_maybe = from_file->multi_maybe;
+ to_file->multi_next = from_file->multi_next;
+ to_file->multi_head = f = from_file->multi_head;
+ if (f == from_file)
+ {
+ for (; f != 0; f = f->multi_next)
+ f->multi_head = to_file;
+ to_file->multi_head = to_file;
+ }
+ else
+ {
+ while (f->multi_next != from_file)
+ f = f->multi_next;
+ assert(f->multi_next == from_file);
+ f->multi_next = to_file;
+ }
+# ifdef NDEBUG
+ from_file->multi_head = to_file->multi_head;
+ from_file->multi_next = NULL;
+# else
+ from_file->multi_head = (struct file *)0x2; /* poison */
+ from_file->multi_next = (struct file *)0x8;
+# endif
+ }
+#endif
+
+ /* Merge the dependencies of the two files. */
+
+ if (to_file->deps == 0)
+ to_file->deps = from_file->deps;
+ else
+ {
+ struct dep *deps = to_file->deps;
+ while (deps->next != 0)
+ deps = deps->next;
+ deps->next = from_file->deps;
+ }
+
+ merge_variable_set_lists (&to_file->variables, from_file->variables);
+
+ if (to_file->double_colon && from_file->is_target && !from_file->double_colon)
+ OSS (fatal, NILF, _("can't rename single-colon '%s' to double-colon '%s'"),
+ from_file->name, to_hname);
+ if (!to_file->double_colon && from_file->double_colon)
+ {
+ if (to_file->is_target)
+ OSS (fatal, NILF,
+ _("can't rename double-colon '%s' to single-colon '%s'"),
+ from_file->name, to_hname);
+ else
+ to_file->double_colon = from_file->double_colon;
+ }
+
+ if (from_file->last_mtime > to_file->last_mtime)
+ /* %%% Kludge so -W wins on a file that gets vpathized. */
+ to_file->last_mtime = from_file->last_mtime;
+
+ to_file->mtime_before_update = from_file->mtime_before_update;
+
+#define MERGE(field) to_file->field |= from_file->field
+ MERGE (precious);
+ MERGE (tried_implicit);
+ MERGE (updating);
+ MERGE (updated);
+ MERGE (is_target);
+ MERGE (cmd_target);
+ MERGE (phony);
+ MERGE (loaded);
+ MERGE (ignore_vpath);
+#undef MERGE
+
+ to_file->builtin = 0;
+ from_file->renamed = to_file;
+}
+
+/* Rename FILE to NAME. This is not as simple as resetting
+ the 'name' member, since it must be put in a new hash bucket,
+ and possibly merged with an existing file called NAME. */
+
+void
+rename_file (struct file *from_file, const char *to_hname)
+{
+ rehash_file (from_file, to_hname);
+ while (from_file)
+ {
+ from_file->name = from_file->hname;
+ from_file = from_file->prev;
+ }
+}
+
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+/* Performs secondary target name expansion and then renames
+ the file using rename_file. */
+static void
+do_2nd_target_expansion (struct file *f)
+{
+ unsigned int len;
+ char *tmp_name = allocated_variable_expand_2 (
+ f->name, strcache2_get_len (&file_strcache, f->name), &len);
+ const char *name = strcache_add_len (tmp_name, len);
+ free (tmp_name);
+ rename_file (f, name);
+}
+#endif /* CONFIG_WITH_2ND_TARGET_EXPANSION */
+
+/* Remove all nonprecious intermediate files.
+ If SIG is nonzero, this was caused by a fatal signal,
+ meaning that a different message will be printed, and
+ the message will go to stderr rather than stdout. */
+
+void
+remove_intermediates (int sig)
+{
+ struct file **file_slot;
+ struct file **file_end;
+ int doneany = 0;
+
+ /* If there's no way we will ever remove anything anyway, punt early. */
+ if (question_flag || touch_flag || all_secondary)
+ return;
+
+ if (sig && just_print_flag)
+ return;
+
+ file_slot = (struct file **) files.ht_vec;
+ file_end = file_slot + files.ht_size;
+ for ( ; file_slot < file_end; file_slot++)
+ if (! HASH_VACANT (*file_slot))
+ {
+ struct file *f = *file_slot;
+ /* Is this file eligible for automatic deletion?
+ Yes, IFF: it's marked intermediate, it's not secondary, it wasn't
+ given on the command line, and it's either a -include makefile or
+ it's not precious. */
+ if (f->intermediate && (f->dontcare || !f->precious)
+ && !f->secondary && !f->cmd_target)
+ {
+ int status;
+ if (f->update_status == us_none)
+ /* If nothing would have created this file yet,
+ don't print an "rm" command for it. */
+ continue;
+ if (just_print_flag)
+ status = 0;
+ else
+ {
+ status = unlink (f->name);
+ if (status < 0 && errno == ENOENT)
+ continue;
+ }
+ if (!f->dontcare)
+ {
+ if (sig)
+ OS (error, NILF,
+ _("*** Deleting intermediate file '%s'"), f->name);
+ else
+ {
+ if (! doneany)
+ DB (DB_BASIC, (_("Removing intermediate files...\n")));
+ if (!silent_flag)
+ {
+ if (! doneany)
+ {
+ fputs ("rm ", stdout);
+ doneany = 1;
+ }
+ else
+ putchar (' ');
+ fputs (f->name, stdout);
+ fflush (stdout);
+ }
+ }
+ if (status < 0)
+ perror_with_name ("unlink: ", f->name);
+ }
+ }
+ }
+
+ if (doneany && !sig)
+ {
+ putchar ('\n');
+ fflush (stdout);
+ }
+}
+
+/* Given a string containing prerequisites (fully expanded), break it up into
+ a struct dep list. Enter each of these prereqs into the file database.
+ */
+struct dep *
+split_prereqs (char *p)
+{
+ struct dep *new = PARSE_FILE_SEQ (&p, struct dep, MAP_PIPE, NULL,
+ PARSEFS_NONE);
+
+ if (*p)
+ {
+ /* Files that follow '|' are "order-only" prerequisites that satisfy the
+ dependency by existing: their modification times are irrelevant. */
+ struct dep *ood;
+
+ ++p;
+ ood = PARSE_SIMPLE_SEQ (&p, struct dep);
+
+ if (! new)
+ new = ood;
+ else
+ {
+ struct dep *dp;
+ for (dp = new; dp->next != NULL; dp = dp->next)
+ ;
+ dp->next = ood;
+ }
+
+ for (; ood != NULL; ood = ood->next)
+ ood->ignore_mtime = 1;
+ }
+
+ return new;
+}
+
+/* Given a list of prerequisites, enter them into the file database.
+ If STEM is set then first expand patterns using STEM. */
+struct dep *
+enter_prereqs (struct dep *deps, const char *stem)
+{
+ struct dep *d1;
+
+ if (deps == 0)
+ return 0;
+
+ /* If we have a stem, expand the %'s. We use patsubst_expand to translate
+ the prerequisites' patterns into plain prerequisite names. */
+ if (stem)
+ {
+ const char *pattern = "%";
+ char *buffer = variable_expand ("");
+ struct dep *dp = deps, *dl = 0;
+
+ while (dp != 0)
+ {
+ char *percent;
+ int nl = strlen (dp->name) + 1;
+ char *nm = alloca (nl);
+ memcpy (nm, dp->name, nl);
+ percent = find_percent (nm);
+ if (percent)
+ {
+ char *o;
+
+ /* We have to handle empty stems specially, because that
+ would be equivalent to $(patsubst %,dp->name,) which
+ will always be empty. */
+ if (stem[0] == '\0')
+ {
+ memmove (percent, percent+1, strlen (percent));
+ o = variable_buffer_output (buffer, nm, strlen (nm) + 1);
+ }
+ else
+ o = patsubst_expand_pat (buffer, stem, pattern, nm,
+ pattern+1, percent+1);
+
+ /* If the name expanded to the empty string, ignore it. */
+ if (buffer[0] == '\0')
+ {
+ struct dep *df = dp;
+ if (dp == deps)
+ dp = deps = deps->next;
+ else
+ dp = dl->next = dp->next;
+ free_dep (df);
+ continue;
+ }
+
+ /* Save the name. */
+ dp->name = strcache_add_len (buffer, o - buffer);
+ }
+ dp->stem = stem;
+ dp->staticpattern = 1;
+ dl = dp;
+ dp = dp->next;
+ }
+ }
+
+ /* Enter them as files, unless they need a 2nd expansion. */
+ for (d1 = deps; d1 != 0; d1 = d1->next)
+ {
+ if (d1->need_2nd_expansion)
+ continue;
+
+ d1->file = lookup_file (d1->name);
+ if (d1->file == 0)
+ d1->file = enter_file (d1->name);
+ d1->staticpattern = 0;
+ d1->name = 0;
+ }
+
+ return deps;
+}
+
+/* Set the intermediate flag. */
+
+static void
+set_intermediate (const void *item)
+{
+ struct file *f = (struct file *) item;
+ f->intermediate = 1;
+}
+
+/* Expand and parse each dependency line. */
+static void
+expand_deps (struct file *f)
+{
+ struct dep *d;
+ struct dep **dp;
+ const char *file_stem = f->stem;
+ int initialized = 0;
+
+ f->updating = 0;
+
+ /* Walk through the dependencies. For any dependency that needs 2nd
+ expansion, expand it then insert the result into the list. */
+ dp = &f->deps;
+ d = f->deps;
+ while (d != 0)
+ {
+ char *p;
+ struct dep *new, *next;
+ char *name = (char *)d->name;
+
+ if (! d->name || ! d->need_2nd_expansion)
+ {
+ /* This one is all set already. */
+ dp = &d->next;
+ d = d->next;
+ continue;
+ }
+
+#ifdef CONFIG_WITH_INCLUDEDEP
+ /* Dependencies loaded by includedep are ready for use and we skip
+ the expensive parsing and globbing for them. */
+
+ if (d->includedep)
+ {
+ d->need_2nd_expansion = 0;
+ d->file = lookup_file (name);
+ if (d->file == 0)
+ d->file = enter_file (name);
+ d->name = 0;
+ free(name);
+
+ dp = &d->next;
+ d = d->next;
+ continue;
+ }
+#endif /* CONFIG_WITH_INCLUDEDEP */
+
+ /* If it's from a static pattern rule, convert the patterns into
+ "$*" so they'll expand properly. */
+ if (d->staticpattern)
+ {
+ char *o = variable_expand ("");
+ o = subst_expand (o, name, "%", "$*", 1, 2, 0);
+ *o = '\0';
+#ifndef CONFIG_WITH_STRCACHE2
+ free (name);
+ d->name = name = xstrdup (variable_buffer); /* bird not d->name, can be reallocated */
+#else
+ d->name = strcache2_add (&file_strcache, variable_buffer, o - variable_buffer);
+#endif
+ d->staticpattern = 0;
+ }
+
+ /* We're going to do second expansion so initialize file variables for
+ the file. Since the stem for static pattern rules comes from
+ individual dep lines, we will temporarily set f->stem to d->stem. */
+ if (!initialized)
+ {
+ initialize_file_variables (f, 0);
+ initialized = 1;
+ }
+
+ if (d->stem != 0)
+ f->stem = d->stem;
+
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+ set_file_variables (f, 0 /* real call, f->deps == 0 so we're ok. */);
+#else
+ set_file_variables (f);
+#endif
+
+ p = variable_expand_for_file (d->name, f);
+
+ if (d->stem != 0)
+ f->stem = file_stem;
+
+ /* At this point we don't need the name anymore: free it. */
+ free (name);
+
+ /* Parse the prerequisites and enter them into the file database. */
+ new = enter_prereqs (split_prereqs (p), d->stem);
+
+ /* If there were no prereqs here (blank!) then throw this one out. */
+ if (new == 0)
+ {
+ *dp = d->next;
+ free_dep (d);
+ d = *dp;
+ continue;
+ }
+
+ /* Add newly parsed prerequisites. */
+ next = d->next;
+#ifdef KMK /* bird: memory leak */
+ assert(new != d);
+ free_dep (d);
+#endif
+ *dp = new;
+ for (dp = &new->next, d = new->next; d != 0; dp = &d->next, d = d->next)
+ ;
+ *dp = next;
+ d = *dp;
+ }
+}
+
+/* Reset the updating flag. */
+
+static void
+reset_updating (const void *item)
+{
+ struct file *f = (struct file *) item;
+ f->updating = 0;
+}
+
+/* For each dependency of each file, make the 'struct dep' point
+ at the appropriate 'struct file' (which may have to be created).
+
+ Also mark the files depended on by .PRECIOUS, .PHONY, .SILENT,
+ and various other special targets. */
+
+void
+snap_deps (void)
+{
+ struct file *f;
+ struct file *f2;
+ struct dep *d;
+
+ /* Remember that we've done this. Once we start snapping deps we can no
+ longer define new targets. */
+ snapped_deps = 1;
+
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+ /* Perform 2nd target expansion on files which requires this. This will
+ be re-inserting (delete+insert) hash table entries so we have to use
+ hash_dump(). */
+ if (second_target_expansion)
+ {
+ struct file **file_slot_0, **file_end, **file_slot;
+# ifdef KMK /* turn on warnings here. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 1;
+# endif
+
+ file_slot_0 = (struct file **) hash_dump (&files, 0, 0);
+ file_end = file_slot_0 + files.ht_fill;
+ for (file_slot = file_slot_0; file_slot < file_end; file_slot++)
+ for (f = *file_slot; f != 0; f = f->prev)
+ if (f->need_2nd_target_expansion)
+ do_2nd_target_expansion (f);
+ free (file_slot_0);
+
+# ifdef KMK
+ warn_undefined_variables_flag = save;
+# endif
+
+ /* Disable second target expansion now since we won't expand files
+ entered after this point. (Saves CPU cycles in enter_file()). */
+ second_target_expansion = 0;
+ }
+#endif /* CONFIG_WITH_2ND_TARGET_EXPANSION */
+
+#ifdef CONFIG_WITH_INCLUDEDEP
+ /* Process any queued includedep files. Since includedep is supposed
+ to be *simple* stuff, we can do this after the second target expansion
+ and thereby save a little time. */
+ incdep_flush_and_term ();
+#endif /* CONFIG_WITH_INCLUDEDEP */
+
+ /* Perform second expansion and enter each dependency name as a file. We
+ must use hash_dump() here because within these loops we likely add new
+ files to the table, possibly causing an in-situ table expansion.
+
+ We only need to do this if second_expansion has been defined; if it
+ hasn't then all deps were expanded as the makefile was read in. If we
+ ever change make to be able to unset .SECONDARY_EXPANSION this will have
+ to change. */
+
+ if (second_expansion)
+ {
+ struct file **file_slot_0 = (struct file **) hash_dump (&files, 0, 0);
+ struct file **file_end = file_slot_0 + files.ht_fill;
+ struct file **file_slot;
+ const char *suffixes;
+
+ /* Expand .SUFFIXES: its prerequisites are used for $$* calc. */
+ f = lookup_file (".SUFFIXES");
+ suffixes = f ? f->name : 0;
+ for (; f != 0; f = f->prev)
+ expand_deps (f);
+
+#if 0 /* def KMK - not-parallel is a performance kill, but since double-colon is werid anyway, skip this hack. */
+ /* This is a HACK to work around the still broken test #9 in
+ features/double_colon. It produces the wrong result if the build is
+ parallel because of changed evaluation order. Just make these
+ problematic rules execute in single field till a proper fix is
+ forthcomming... */
+
+ for (file_slot = file_slot_0; file_slot < file_end; file_slot++)
+ if ( (f = *file_slot) != 0
+ && f->double_colon
+ && ( f->double_colon != f
+ || f->last != f))
+ for (f2 = f->double_colon; f2 != 0; f2 = f2->prev)
+ f2->command_flags |= COMMANDS_NOTPARALLEL;
+#endif /* KMK */
+
+ /* For every target that's not .SUFFIXES, expand its prerequisites. */
+
+ for (file_slot = file_slot_0; file_slot < file_end; file_slot++)
+ for (f = *file_slot; f != 0; f = f->prev)
+ if (f->name != suffixes)
+ expand_deps (f);
+ free (file_slot_0);
+ }
+ else
+ /* We're not doing second expansion, so reset updating. */
+ hash_map (&files, reset_updating);
+
+ /* Now manage all the special targets. */
+
+ for (f = lookup_file (".PRECIOUS"); f != 0; f = f->prev)
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->precious = 1;
+
+ for (f = lookup_file (".LOW_RESOLUTION_TIME"); f != 0; f = f->prev)
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->low_resolution_time = 1;
+
+ for (f = lookup_file (".PHONY"); f != 0; f = f->prev)
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ {
+ /* Mark this file as phony nonexistent target. */
+ f2->phony = 1;
+ f2->is_target = 1;
+ f2->last_mtime = NONEXISTENT_MTIME;
+ f2->mtime_before_update = NONEXISTENT_MTIME;
+ }
+
+ for (f = lookup_file (".INTERMEDIATE"); f != 0; f = f->prev)
+ /* Mark .INTERMEDIATE deps as intermediate files. */
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->intermediate = 1;
+ /* .INTERMEDIATE with no deps does nothing.
+ Marking all files as intermediates is useless since the goal targets
+ would be deleted after they are built. */
+
+ for (f = lookup_file (".SECONDARY"); f != 0; f = f->prev)
+ /* Mark .SECONDARY deps as both intermediate and secondary. */
+ if (f->deps)
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->intermediate = f2->secondary = 1;
+ /* .SECONDARY with no deps listed marks *all* files that way. */
+ else
+ {
+ all_secondary = 1;
+ hash_map (&files, set_intermediate);
+ }
+
+ f = lookup_file (".EXPORT_ALL_VARIABLES");
+ if (f != 0 && f->is_target)
+ export_all_variables = 1;
+
+ f = lookup_file (".IGNORE");
+ if (f != 0 && f->is_target)
+ {
+ if (f->deps == 0)
+ ignore_errors_flag = 1;
+ else
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->command_flags |= COMMANDS_NOERROR;
+ }
+
+ f = lookup_file (".SILENT");
+ if (f != 0 && f->is_target)
+ {
+ if (f->deps == 0)
+ silent_flag = 1;
+ else
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->command_flags |= COMMANDS_SILENT;
+ }
+
+ f = lookup_file (".NOTPARALLEL");
+ if (f != 0 && f->is_target)
+#ifndef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ not_parallel = 1;
+#else /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+ {
+ if (f->deps == 0)
+ {
+ DB (DB_KMK, (_("not_parallel -1\n")));
+ not_parallel = -1;
+ }
+ else
+ for (d = f->deps; d != 0; d = d->next)
+ for (f2 = d->file; f2 != 0; f2 = f2->prev)
+ f2->command_flags |= COMMANDS_NOTPARALLEL;
+ }
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+#ifndef NO_MINUS_C_MINUS_O
+ /* If .POSIX was defined, remove OUTPUT_OPTION to comply. */
+ /* This needs more work: what if the user sets this in the makefile?
+ if (posix_pedantic)
+ define_variable_cname ("OUTPUT_OPTION", "", o_default, 1);
+ */
+#endif
+}
+
+/* Set the 'command_state' member of FILE and all its 'also_make's. */
+
+void
+set_command_state (struct file *file, enum cmd_state state)
+{
+ struct dep *d;
+
+ file->command_state = state;
+
+ for (d = file->also_make; d != 0; d = d->next)
+ d->file->command_state = state;
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (file->multi_head)
+ for (file = file->multi_head; file != 0; file = file->multi_next)
+ file->command_state = state;
+#endif
+}
+
+/* Convert an external file timestamp to internal form. */
+
+FILE_TIMESTAMP
+file_timestamp_cons (const char *fname, time_t stamp, long int ns)
+{
+ int offset = ORDINARY_MTIME_MIN + (FILE_TIMESTAMP_HI_RES ? ns : 0);
+ FILE_TIMESTAMP s = stamp;
+ FILE_TIMESTAMP product = (FILE_TIMESTAMP) s << FILE_TIMESTAMP_LO_BITS;
+ FILE_TIMESTAMP ts = product + offset;
+
+ if (! (s <= FILE_TIMESTAMP_S (ORDINARY_MTIME_MAX)
+ && product <= ts && ts <= ORDINARY_MTIME_MAX))
+ {
+ char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1];
+ const char *f = fname ? fname : _("Current time");
+ ts = s <= OLD_MTIME ? ORDINARY_MTIME_MIN : ORDINARY_MTIME_MAX;
+ file_timestamp_sprintf (buf, ts);
+ OSS (error, NILF,
+ _("%s: Timestamp out of range; substituting %s"), f, buf);
+ }
+
+ return ts;
+}
+
+/* Return the current time as a file timestamp, setting *RESOLUTION to
+ its resolution. */
+FILE_TIMESTAMP
+file_timestamp_now (int *resolution)
+{
+ int r;
+ time_t s;
+ int ns;
+
+ /* Don't bother with high-resolution clocks if file timestamps have
+ only one-second resolution. The code below should work, but it's
+ not worth the hassle of debugging it on hosts where it fails. */
+#if FILE_TIMESTAMP_HI_RES
+# if HAVE_CLOCK_GETTIME && defined CLOCK_REALTIME
+ {
+ struct timespec timespec;
+ if (clock_gettime (CLOCK_REALTIME, &timespec) == 0)
+ {
+ r = 1;
+ s = timespec.tv_sec;
+ ns = timespec.tv_nsec;
+ goto got_time;
+ }
+ }
+# endif
+# if HAVE_GETTIMEOFDAY
+ {
+ struct timeval timeval;
+ if (gettimeofday (&timeval, 0) == 0)
+ {
+ r = 1000;
+ s = timeval.tv_sec;
+ ns = timeval.tv_usec * 1000;
+ goto got_time;
+ }
+ }
+# endif
+#endif
+
+ r = 1000000000;
+ s = time ((time_t *) 0);
+ ns = 0;
+
+#if FILE_TIMESTAMP_HI_RES
+ got_time:
+#endif
+ *resolution = r;
+ return file_timestamp_cons (0, s, ns);
+}
+
+/* Place into the buffer P a printable representation of the file
+ timestamp TS. */
+void
+file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts)
+{
+ time_t t = FILE_TIMESTAMP_S (ts);
+ struct tm *tm = localtime (&t);
+
+ if (tm)
+ sprintf (p, "%04d-%02d-%02d %02d:%02d:%02d",
+ tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ else if (t < 0)
+ sprintf (p, "%ld", (long) t);
+ else
+ sprintf (p, "%lu", (unsigned long) t);
+ p += strlen (p);
+
+ /* Append nanoseconds as a fraction, but remove trailing zeros. We don't
+ know the actual timestamp resolution, since clock_getres applies only to
+ local times, whereas this timestamp might come from a remote filesystem.
+ So removing trailing zeros is the best guess that we can do. */
+ sprintf (p, ".%09d", FILE_TIMESTAMP_NS (ts));
+ p += strlen (p) - 1;
+ while (*p == '0')
+ p--;
+ p += *p != '.';
+
+ *p = '\0';
+}
+
+/* Print the data base of files. */
+
+void
+print_prereqs (const struct dep *deps)
+{
+ const struct dep *ood = 0;
+
+ /* Print all normal dependencies; note any order-only deps. */
+ for (; deps != 0; deps = deps->next)
+ if (! deps->ignore_mtime)
+ printf (" %s", dep_name (deps));
+ else if (! ood)
+ ood = deps;
+
+ /* Print order-only deps, if we have any. */
+ if (ood)
+ {
+ printf (" | %s", dep_name (ood));
+ for (ood = ood->next; ood != 0; ood = ood->next)
+ if (ood->ignore_mtime)
+ printf (" %s", dep_name (ood));
+ }
+
+ putchar ('\n');
+}
+
+static void
+print_file (const void *item)
+{
+ const struct file *f = item;
+
+ /* If we're not using builtin targets, don't show them.
+
+ Ideally we'd be able to delete them altogether but currently there's no
+ facility to ever delete a file once it's been added. */
+ if (no_builtin_rules_flag && f->builtin)
+ return;
+
+ putchar ('\n');
+
+ if (f->cmds && f->cmds->recipe_prefix != cmd_prefix)
+ {
+ fputs (".RECIPEPREFIX = ", stdout);
+ cmd_prefix = f->cmds->recipe_prefix;
+ if (cmd_prefix != RECIPEPREFIX_DEFAULT)
+ putchar (cmd_prefix);
+ putchar ('\n');
+ }
+
+ if (f->variables != 0)
+ print_target_variables (f);
+
+ if (!f->is_target)
+ puts (_("# Not a target:"));
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (f->multi_head)
+ {
+ const struct file *f2;
+ if (f->multi_head == f)
+ {
+ int multi_maybe = -1;
+ assert (!f->multi_maybe);
+ assert (!f->double_colon);
+
+ printf ("%s", f->name);
+ for (f2 = f->multi_next; f2 != 0; f2 = f2->multi_next)
+ {
+ printf (" %s%s", f2->multi_maybe != multi_maybe
+ ? f2->multi_maybe ? "+| " : "+ " : "",
+ f2->name);
+ multi_maybe = f2->multi_maybe;
+ }
+ if (f->deps)
+ printf (": \\\n\t");
+ else
+ putchar (':');
+ }
+ else
+ printf ("%s:%s", f->name, f->double_colon ? ":" : "");
+ }
+ else
+#endif
+ printf ("%s:%s", f->name, f->double_colon ? ":" : "");
+
+ print_prereqs (f->deps);
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (f->multi_head && f->multi_head != f)
+ {
+ const struct file *f2;
+ fputs (_("# In multi target list:"), stdout);
+ for (f2 = f->multi_head; f2 != 0; f2 = f2->multi_next)
+ printf (" %s%s", f2->name, f == f2 ? "(*)" : "");
+ putchar ('\n');
+ if (f->multi_maybe)
+ puts (_("# File is an optional multi target member."));
+ }
+#endif
+
+ if (f->precious)
+ puts (_("# Precious file (prerequisite of .PRECIOUS)."));
+ if (f->phony)
+ puts (_("# Phony target (prerequisite of .PHONY)."));
+ if (f->cmd_target)
+ puts (_("# Command line target."));
+ if (f->dontcare)
+ puts (_("# A default, MAKEFILES, or -include/sinclude makefile."));
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ if (f->eval_count > 0)
+ {
+# ifdef CONFIG_WITH_COMPILER
+ if (f->evalprog)
+ printf (_("# Makefile evaluated %u times - compiled\n"), f->eval_count);
+ else
+# endif
+ printf (_("# Makefile evaluated %u times\n"), f->eval_count);
+ }
+#endif
+ if (f->builtin)
+ puts (_("# Builtin rule"));
+ puts (f->tried_implicit
+ ? _("# Implicit rule search has been done.")
+ : _("# Implicit rule search has not been done."));
+ if (f->stem != 0)
+ printf (_("# Implicit/static pattern stem: '%s'\n"), f->stem);
+ if (f->intermediate)
+ puts (_("# File is an intermediate prerequisite."));
+ if (f->also_make != 0)
+ {
+ const struct dep *d;
+ fputs (_("# Also makes:"), stdout);
+ for (d = f->also_make; d != 0; d = d->next)
+ printf (" %s", dep_name (d));
+ putchar ('\n');
+ }
+ if (f->last_mtime == UNKNOWN_MTIME)
+ puts (_("# Modification time never checked."));
+ else if (f->last_mtime == NONEXISTENT_MTIME)
+ puts (_("# File does not exist."));
+ else if (f->last_mtime == OLD_MTIME)
+ puts (_("# File is very old."));
+ else
+ {
+ char buf[FILE_TIMESTAMP_PRINT_LEN_BOUND + 1];
+ file_timestamp_sprintf (buf, f->last_mtime);
+ printf (_("# Last modified %s\n"), buf);
+ }
+ puts (f->updated
+ ? _("# File has been updated.") : _("# File has not been updated."));
+ switch (f->command_state)
+ {
+ case cs_running:
+ puts (_("# Recipe currently running (THIS IS A BUG)."));
+ break;
+ case cs_deps_running:
+ puts (_("# Dependencies recipe running (THIS IS A BUG)."));
+ break;
+ case cs_not_started:
+ case cs_finished:
+ switch (f->update_status)
+ {
+ case us_none:
+ break;
+ case us_success:
+ puts (_("# Successfully updated."));
+ break;
+ case us_question:
+ assert (question_flag);
+ puts (_("# Needs to be updated (-q is set)."));
+ break;
+ case us_failed:
+ puts (_("# Failed to be updated."));
+ break;
+ }
+ break;
+ default:
+ puts (_("# Invalid value in 'command_state' member!"));
+ fflush (stdout);
+ fflush (stderr);
+ abort ();
+ }
+
+ if (f->variables != 0)
+ print_file_variables (f);
+
+ if (f->cmds != 0)
+ print_commands (f->cmds);
+
+ if (f->prev)
+ print_file ((const void *) f->prev);
+}
+
+void
+print_file_data_base (void)
+{
+ puts (_("\n# Files"));
+
+ hash_map (&files, print_file);
+
+ fputs (_("\n# files hash-table stats:\n# "), stdout);
+ hash_print_stats (&files, stdout);
+}
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+void
+print_file_stats (void)
+{
+ fputs (_("\n# files hash-table stats:\n# "), stdout);
+ hash_print_stats (&files, stdout);
+ fputs ("\n", stdout);
+}
+#endif
+
+/* Verify the integrity of the data base of files. */
+
+#define VERIFY_CACHED(_p,_n) \
+ do{ \
+ if (_p->_n && _p->_n[0] && !strcache_iscached (_p->_n)) \
+ error (NULL, strlen (_p->name) + CSTRLEN (# _n) + strlen (_p->_n), \
+ _("%s: Field '%s' not cached: %s"), _p->name, # _n, _p->_n); \
+ }while(0)
+
+static void
+verify_file (const void *item)
+{
+ const struct file *f = item;
+ const struct dep *d;
+
+ VERIFY_CACHED (f, name);
+ VERIFY_CACHED (f, hname);
+ VERIFY_CACHED (f, vpath);
+ VERIFY_CACHED (f, stem);
+
+ /* Check the deps. */
+ for (d = f->deps; d != 0; d = d->next)
+ {
+ if (! d->need_2nd_expansion)
+ VERIFY_CACHED (d, name);
+ VERIFY_CACHED (d, stem);
+ }
+}
+
+void
+verify_file_data_base (void)
+{
+ hash_map (&files, verify_file);
+}
+
+#define EXPANSION_INCREMENT(_l) ((((_l) / 500) + 1) * 500)
+
+char *
+build_target_list (char *value)
+{
+ static unsigned long last_targ_count = 0;
+
+ if (files.ht_fill != last_targ_count)
+ {
+ unsigned long max = EXPANSION_INCREMENT (strlen (value));
+ unsigned long len;
+ char *p;
+ struct file **fp = (struct file **) files.ht_vec;
+ struct file **end = &fp[files.ht_size];
+
+ /* Make sure we have at least MAX bytes in the allocated buffer. */
+ value = xrealloc (value, max);
+
+ p = value;
+ len = 0;
+ for (; fp < end; ++fp)
+ if (!HASH_VACANT (*fp) && (*fp)->is_target)
+ {
+ struct file *f = *fp;
+ int l = strlen (f->name);
+
+ len += l + 1;
+ if (len > max)
+ {
+ unsigned long off = p - value;
+
+ max += EXPANSION_INCREMENT (l + 1);
+ value = xrealloc (value, max);
+ p = &value[off];
+ }
+
+ memcpy (p, f->name, l);
+ p += l;
+ *(p++) = ' ';
+ }
+ *(p-1) = '\0';
+
+ last_targ_count = files.ht_fill;
+ }
+
+ return value;
+}
+
+void
+init_hash_files (void)
+{
+#ifndef CONFIG_WITH_STRCACHE2
+# ifdef KMK
+ hash_init (&files, /*65535*/ 32755, file_hash_1, file_hash_2, file_hash_cmp);
+# else
+ hash_init (&files, 1000, file_hash_1, file_hash_2, file_hash_cmp);
+# endif
+#else /* CONFIG_WITH_STRCACHE2 */
+# ifdef KMK
+ hash_init_strcached (&files, 32755, &file_strcache,
+ offsetof (struct file, hname));
+# else
+ hash_init_strcached (&files, 1000, &file_strcache,
+ offsetof (struct file, hname));
+# endif
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+/* EOF */
diff --git a/src/kmk/filedef.h b/src/kmk/filedef.h
new file mode 100644
index 0000000..cf606ce
--- /dev/null
+++ b/src/kmk/filedef.h
@@ -0,0 +1,254 @@
+/* Definition of target file data structures for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Structure that represents the info on one file
+ that the makefile says how to make.
+ All of these are chained together through 'next'. */
+
+#include "hash.h"
+
+struct file
+ {
+ const char *name;
+ const char *hname; /* Hashed filename */
+ const char *vpath; /* VPATH/vpath pathname */
+ struct dep *deps; /* all dependencies, including duplicates */
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+ struct dep *deps_no_dupes; /* dependencies without duplicates, created on
+ demaned by func_deps. */
+#endif
+ struct commands *cmds; /* Commands to execute for this target. */
+ const char *stem; /* Implicit stem, if an implicit
+ rule has been used */
+ struct dep *also_make; /* Targets that are made by making this. */
+ struct file *prev; /* Previous entry for same file name;
+ used when there are multiple double-colon
+ entries for the same file. */
+ struct file *last; /* Last entry for the same file name. */
+
+ /* File that this file was renamed to. After any time that a
+ file could be renamed, call 'check_renamed' (below). */
+ struct file *renamed;
+
+ /* List of variable sets used for this file. */
+ struct variable_set_list *variables;
+
+ /* Pattern-specific variable reference for this target, or null if there
+ isn't one. Also see the pat_searched flag, below. */
+ struct variable_set_list *pat_variables;
+
+ /* Immediate dependent that caused this target to be remade,
+ or nil if there isn't one. */
+ struct file *parent;
+
+ /* For a double-colon entry, this is the first double-colon entry for
+ the same file. Otherwise this is null. */
+ struct file *double_colon;
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* For a target of an explicit multi target rule, this points to the
+ primary target. Otherwise this is null. */
+ struct file *multi_head;
+ /* Pointer to next target of an explicit multi target rule. */
+ struct file *multi_next;
+#endif
+
+#ifdef CONFIG_WITH_COMPILER
+ struct kmk_cc_evalprog *evalprog; /* Pointer to evalval/evalctx "program". */
+#endif
+
+ FILE_TIMESTAMP last_mtime; /* File's modtime, if already known. */
+ FILE_TIMESTAMP mtime_before_update; /* File's modtime before any updating
+ has been performed. */
+ unsigned int considered; /* equal to 'considered' if file has been
+ considered on current scan of goal chain */
+ int command_flags; /* Flags OR'd in for cmds; see commands.h. */
+ enum update_status /* Status of the last attempt to update. */
+ {
+ us_success = 0, /* Successfully updated. Must be 0! */
+ us_none, /* No attempt to update has been made. */
+ us_question, /* Needs to be updated (-q is is set). */
+ us_failed /* Update failed. */
+ } update_status ENUM_BITFIELD (2);
+ enum cmd_state /* State of the commands. */
+ {
+ cs_not_started = 0, /* Not yet started. Must be 0! */
+ cs_deps_running, /* Dep commands running. */
+ cs_running, /* Commands running. */
+ cs_finished /* Commands finished. */
+ } command_state ENUM_BITFIELD (2);
+
+ unsigned int builtin:1; /* True if the file is a builtin rule. */
+ unsigned int precious:1; /* Non-0 means don't delete file on quit */
+ unsigned int loaded:1; /* True if the file is a loaded object. */
+ unsigned int low_resolution_time:1; /* Nonzero if this file's time stamp
+ has only one-second resolution. */
+ unsigned int tried_implicit:1; /* Nonzero if have searched
+ for implicit rule for making
+ this file; don't search again. */
+ unsigned int updating:1; /* Nonzero while updating deps of this file */
+ unsigned int updated:1; /* Nonzero if this file has been remade. */
+ unsigned int is_target:1; /* Nonzero if file is described as target. */
+ unsigned int cmd_target:1; /* Nonzero if file was given on cmd line. */
+ unsigned int phony:1; /* Nonzero if this is a phony file
+ i.e., a prerequisite of .PHONY. */
+ unsigned int intermediate:1;/* Nonzero if this is an intermediate file. */
+ unsigned int secondary:1; /* Nonzero means remove_intermediates should
+ not delete it. */
+ unsigned int dontcare:1; /* Nonzero if no complaint is to be made if
+ this target cannot be remade. */
+ unsigned int ignore_vpath:1;/* Nonzero if we threw out VPATH name. */
+ unsigned int pat_searched:1;/* Nonzero if we already searched for
+ pattern-specific variables. */
+ unsigned int no_diag:1; /* True if the file failed to update and no
+ diagnostics has been issued (dontcare). */
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ unsigned int multi_maybe:1; /* Nonzero if this file isn't always updated
+ by the explicit multi target rule. */
+#endif
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+ unsigned int need_2nd_target_expansion:1; /* Nonzero if this file needs
+ second expansion of its name. Whether it
+ can receive this is decided at parse time,
+ and the expanding done in snap_deps. */
+#endif
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ unsigned int eval_count:14; /* Times evaluated as a makefile. */
+#endif
+ };
+
+
+extern struct file *default_file;
+
+
+struct file *lookup_file (const char *name);
+#ifdef CONFIG_WITH_STRCACHE2
+struct file *lookup_file_cached (const char *name);
+#endif
+struct file *enter_file (const char *name);
+struct dep *split_prereqs (char *prereqstr);
+struct dep *enter_prereqs (struct dep *prereqs, const char *stem);
+void remove_intermediates (int sig);
+void snap_deps (void);
+void rename_file (struct file *file, const char *name);
+void rehash_file (struct file *file, const char *name);
+void set_command_state (struct file *file, enum cmd_state state);
+void notice_finished_file (struct file *file);
+void init_hash_files (void);
+void verify_file_data_base (void);
+char *build_target_list (char *old_list);
+void print_prereqs (const struct dep *deps);
+void print_file_data_base (void);
+int try_implicit_rule (struct file *file, unsigned int depth);
+int stemlen_compare (const void *v1, const void *v2);
+
+#if FILE_TIMESTAMP_HI_RES
+# define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \
+ file_timestamp_cons (fname, (st).st_mtime, (st).ST_MTIM_NSEC)
+#else
+# define FILE_TIMESTAMP_STAT_MODTIME(fname, st) \
+ file_timestamp_cons (fname, (st).st_mtime, 0)
+#endif
+
+/* If FILE_TIMESTAMP is 64 bits (or more), use nanosecond resolution.
+ (Multiply by 2**30 instead of by 10**9 to save time at the cost of
+ slightly decreasing the number of available timestamps.) With
+ 64-bit FILE_TIMESTAMP, this stops working on 2514-05-30 01:53:04
+ UTC, but by then uintmax_t should be larger than 64 bits. */
+#define FILE_TIMESTAMPS_PER_S (FILE_TIMESTAMP_HI_RES ? 1000000000 : 1)
+#define FILE_TIMESTAMP_LO_BITS (FILE_TIMESTAMP_HI_RES ? 30 : 0)
+
+#define FILE_TIMESTAMP_S(ts) (((ts) - ORDINARY_MTIME_MIN) \
+ >> FILE_TIMESTAMP_LO_BITS)
+#define FILE_TIMESTAMP_NS(ts) ((int) (((ts) - ORDINARY_MTIME_MIN) \
+ & ((1 << FILE_TIMESTAMP_LO_BITS) - 1)))
+
+/* Upper bound on length of string "YYYY-MM-DD HH:MM:SS.NNNNNNNNN"
+ representing a file timestamp. The upper bound is not necessarily 29,
+ since the year might be less than -999 or greater than 9999.
+
+ Subtract one for the sign bit if in case file timestamps can be negative;
+ subtract FLOOR_LOG2_SECONDS_PER_YEAR to yield an upper bound on how many
+ file timestamp bits might affect the year;
+ 302 / 1000 is log10 (2) rounded up;
+ add one for integer division truncation;
+ add one more for a minus sign if file timestamps can be negative;
+ add 4 to allow for any 4-digit epoch year (e.g. 1970);
+ add 25 to allow for "-MM-DD HH:MM:SS.NNNNNNNNN". */
+#define FLOOR_LOG2_SECONDS_PER_YEAR 24
+#define FILE_TIMESTAMP_PRINT_LEN_BOUND \
+ (((sizeof (FILE_TIMESTAMP) * CHAR_BIT - 1 - FLOOR_LOG2_SECONDS_PER_YEAR) \
+ * 302 / 1000) \
+ + 1 + 1 + 4 + 25)
+
+FILE_TIMESTAMP file_timestamp_cons (char const *, time_t, long int);
+FILE_TIMESTAMP file_timestamp_now (int *);
+void file_timestamp_sprintf (char *p, FILE_TIMESTAMP ts);
+
+/* Return the mtime of file F (a struct file *), caching it.
+ The value is NONEXISTENT_MTIME if the file does not exist. */
+#define file_mtime(f) file_mtime_1 ((f), 1)
+/* Return the mtime of file F (a struct file *), caching it.
+ Don't search using vpath for the file--if it doesn't actually exist,
+ we don't find it.
+ The value is NONEXISTENT_MTIME if the file does not exist. */
+#define file_mtime_no_search(f) file_mtime_1 ((f), 0)
+FILE_TIMESTAMP f_mtime (struct file *file, int search);
+#define file_mtime_1(f, v) \
+ ((f)->last_mtime == UNKNOWN_MTIME ? f_mtime ((f), v) : (f)->last_mtime)
+
+/* Special timestamp values. */
+
+/* The file's timestamp is not yet known. */
+#define UNKNOWN_MTIME 0
+
+/* The file does not exist. */
+#define NONEXISTENT_MTIME 1
+
+/* The file does not exist, and we assume that it is older than any
+ actual file. */
+#define OLD_MTIME 2
+
+/* The smallest and largest ordinary timestamps. */
+#define ORDINARY_MTIME_MIN (OLD_MTIME + 1)
+#if FILE_TIMESTAMP_HI_RES == 0 /* bird: shut up annoying warnings!
+ ASSUMES: unsigned FILE_TIMESTAMP ++. */
+# define ORDINARY_MTIME_MAX ( ~ (FILE_TIMESTAMP) 0 )
+#else
+#define ORDINARY_MTIME_MAX ((FILE_TIMESTAMP_S (NEW_MTIME) \
+ << FILE_TIMESTAMP_LO_BITS) \
+ + ORDINARY_MTIME_MIN + FILE_TIMESTAMPS_PER_S - 1)
+#endif
+
+/* Modtime value to use for 'infinitely new'. We used to get the current time
+ from the system and use that whenever we wanted 'new'. But that causes
+ trouble when the machine running make and the machine holding a file have
+ different ideas about what time it is; and can also lose for 'force'
+ targets, which need to be considered newer than anything that depends on
+ them, even if said dependents' modtimes are in the future. */
+#if 1 /* bird: ASSUME the type is unsigned and the wrath of a pedantic gcc. */
+# define NEW_MTIME ( ~ (FILE_TIMESTAMP) 0 )
+#else
+#define NEW_MTIME INTEGER_TYPE_MAXIMUM (FILE_TIMESTAMP)
+#endif
+
+#define check_renamed(file) \
+ while ((file)->renamed != 0) (file) = (file)->renamed /* No ; here. */
+
+/* Have we snapped deps yet? */
+extern int snapped_deps;
diff --git a/src/kmk/function.c b/src/kmk/function.c
new file mode 100644
index 0000000..5ad8586
--- /dev/null
+++ b/src/kmk/function.c
@@ -0,0 +1,8250 @@
+/* Builtin function expansion for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "variable.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "debug.h"
+
+#ifdef _AMIGA
+#include "amiga.h"
+#endif
+
+#ifdef WINDOWS32 /* bird */
+# include "pathstuff.h"
+# ifdef CONFIG_NEW_WIN_CHILDREN
+# include "w32/winchildren.h"
+# endif
+#endif
+
+#ifdef KMK_HELPERS
+# include "kbuild.h"
+#endif
+#ifdef CONFIG_WITH_PRINTF
+# include "kmkbuiltin.h"
+#endif
+#ifdef CONFIG_WITH_XARGS /* bird */
+# ifdef HAVE_LIMITS_H
+# include <limits.h>
+# endif
+#endif
+#ifdef CONFIG_WITH_COMPILER
+# include "kmk_cc_exec.h"
+#endif
+#include <assert.h> /* bird */
+
+#if defined (CONFIG_WITH_MATH) || defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_FILE_SIZE) /* bird */
+# include <ctype.h>
+typedef big_int math_int;
+static char *math_int_to_variable_buffer (char *, math_int);
+static math_int math_int_from_string (const char *str);
+#endif
+
+#ifdef CONFIG_WITH_NANOTS /* bird */
+# ifdef WINDOWS32
+# include <Windows.h>
+# endif
+#endif
+
+#ifdef __OS2__
+# define CONFIG_WITH_OS2_LIBPATH 1
+#endif
+#ifdef CONFIG_WITH_OS2_LIBPATH
+# define INCL_BASE
+# define INCL_ERRROS
+# include <os2.h>
+
+# define QHINF_EXEINFO 1 /* NE exeinfo. */
+# define QHINF_READRSRCTBL 2 /* Reads from the resource table. */
+# define QHINF_READFILE 3 /* Reads from the executable file. */
+# define QHINF_LIBPATHLENGTH 4 /* Gets the libpath length. */
+# define QHINF_LIBPATH 5 /* Gets the entire libpath. */
+# define QHINF_FIXENTRY 6 /* NE only */
+# define QHINF_STE 7 /* NE only */
+# define QHINF_MAPSEL 8 /* NE only */
+ extern APIRET APIENTRY DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction);
+#endif /* CONFIG_WITH_OS2_LIBPATH */
+
+#if defined(KMK) || defined(CONFIG_WITH_LAZY_DEPS_VARS)
+/** Checks if the @a_cch characters (bytes) in @a a_psz equals @a a_szConst. */
+# define STR_N_EQUALS(a_psz, a_cch, a_szConst) \
+ ( (a_cch) == sizeof (a_szConst) - 1 && !strncmp ((a_psz), (a_szConst), sizeof (a_szConst) - 1) )
+#endif
+
+#ifdef KMK
+# ifdef _MSC_VER
+# include "kmkbuiltin/mscfakes.h"
+# endif
+# include "version_compare.h"
+#endif
+
+
+struct function_table_entry
+ {
+ union {
+ char *(*func_ptr) (char *output, char **argv, const char *fname);
+ gmk_func_ptr alloc_func_ptr;
+ } fptr;
+ const char *name;
+ unsigned char len;
+ unsigned char minimum_args;
+ unsigned char maximum_args;
+ unsigned char expand_args:1;
+ unsigned char alloc_fn:1;
+ };
+
+static unsigned long
+function_table_entry_hash_1 (const void *keyv)
+{
+ const struct function_table_entry *key = keyv;
+ return_STRING_N_HASH_1 (key->name, key->len);
+}
+
+static unsigned long
+function_table_entry_hash_2 (const void *keyv)
+{
+ const struct function_table_entry *key = keyv;
+ return_STRING_N_HASH_2 (key->name, key->len);
+}
+
+static int
+function_table_entry_hash_cmp (const void *xv, const void *yv)
+{
+ const struct function_table_entry *x = xv;
+ const struct function_table_entry *y = yv;
+ int result = x->len - y->len;
+ if (result)
+ return result;
+ return_STRING_N_COMPARE (x->name, y->name, x->len);
+}
+
+static struct hash_table function_table;
+
+#ifdef CONFIG_WITH_MAKE_STATS
+long make_stats_allocations = 0;
+long make_stats_reallocations = 0;
+unsigned long make_stats_allocated = 0;
+unsigned long make_stats_ht_lookups = 0;
+unsigned long make_stats_ht_collisions = 0;
+#endif
+
+
+/* Store into VARIABLE_BUFFER at O the result of scanning TEXT and replacing
+ each occurrence of SUBST with REPLACE. TEXT is null-terminated. SLEN is
+ the length of SUBST and RLEN is the length of REPLACE. If BY_WORD is
+ nonzero, substitutions are done only on matches which are complete
+ whitespace-delimited words. */
+
+char *
+subst_expand (char *o, const char *text, const char *subst, const char *replace,
+ unsigned int slen, unsigned int rlen, int by_word)
+{
+ const char *t = text;
+ const char *p;
+
+ if (slen == 0 && !by_word)
+ {
+ /* The first occurrence of "" in any string is its end. */
+ o = variable_buffer_output (o, t, strlen (t));
+ if (rlen > 0)
+ o = variable_buffer_output (o, replace, rlen);
+ return o;
+ }
+
+ do
+ {
+ if (by_word && slen == 0)
+ /* When matching by words, the empty string should match
+ the end of each word, rather than the end of the whole text. */
+ p = end_of_token (next_token (t));
+ else
+ {
+ p = strstr (t, subst);
+ if (p == 0)
+ {
+ /* No more matches. Output everything left on the end. */
+ o = variable_buffer_output (o, t, strlen (t));
+ return o;
+ }
+ }
+
+ /* Output everything before this occurrence of the string to replace. */
+ if (p > t)
+ o = variable_buffer_output (o, t, p - t);
+
+ /* If we're substituting only by fully matched words,
+ or only at the ends of words, check that this case qualifies. */
+ if (by_word
+ && ((p > text && !ISSPACE (p[-1]))
+ || ! STOP_SET (p[slen], MAP_SPACE|MAP_NUL)))
+ /* Struck out. Output the rest of the string that is
+ no longer to be replaced. */
+ o = variable_buffer_output (o, subst, slen);
+ else if (rlen > 0)
+ /* Output the replacement string. */
+ o = variable_buffer_output (o, replace, rlen);
+
+ /* Advance T past the string to be replaced. */
+ t = p + slen;
+ } while (*t != '\0');
+
+ return o;
+}
+
+
+/* Store into VARIABLE_BUFFER at O the result of scanning TEXT
+ and replacing strings matching PATTERN with REPLACE.
+ If PATTERN_PERCENT is not nil, PATTERN has already been
+ run through find_percent, and PATTERN_PERCENT is the result.
+ If REPLACE_PERCENT is not nil, REPLACE has already been
+ run through find_percent, and REPLACE_PERCENT is the result.
+ Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the
+ character _AFTER_ the %, not to the % itself.
+*/
+
+char *
+patsubst_expand_pat (char *o, const char *text,
+ const char *pattern, const char *replace,
+ const char *pattern_percent, const char *replace_percent)
+{
+ unsigned int pattern_prepercent_len, pattern_postpercent_len;
+ unsigned int replace_prepercent_len, replace_postpercent_len;
+ const char *t;
+ unsigned int len;
+ int doneany = 0;
+
+ /* Record the length of REPLACE before and after the % so we don't have to
+ compute these lengths more than once. */
+ if (replace_percent)
+ {
+ replace_prepercent_len = replace_percent - replace - 1;
+ replace_postpercent_len = strlen (replace_percent);
+ }
+ else
+ {
+ replace_prepercent_len = strlen (replace);
+ replace_postpercent_len = 0;
+ }
+
+ if (!pattern_percent)
+ /* With no % in the pattern, this is just a simple substitution. */
+ return subst_expand (o, text, pattern, replace,
+ strlen (pattern), strlen (replace), 1);
+
+ /* Record the length of PATTERN before and after the %
+ so we don't have to compute it more than once. */
+ pattern_prepercent_len = pattern_percent - pattern - 1;
+ pattern_postpercent_len = strlen (pattern_percent);
+
+ while ((t = find_next_token (&text, &len)) != 0)
+ {
+ int fail = 0;
+
+ /* Is it big enough to match? */
+ if (len < pattern_prepercent_len + pattern_postpercent_len)
+ fail = 1;
+
+ /* Does the prefix match? */
+ if (!fail && pattern_prepercent_len > 0
+ && (*t != *pattern
+ || t[pattern_prepercent_len - 1] != pattern_percent[-2]
+ || !strneq (t + 1, pattern + 1, pattern_prepercent_len - 1)))
+ fail = 1;
+
+ /* Does the suffix match? */
+ if (!fail && pattern_postpercent_len > 0
+ && (t[len - 1] != pattern_percent[pattern_postpercent_len - 1]
+ || t[len - pattern_postpercent_len] != *pattern_percent
+ || !strneq (&t[len - pattern_postpercent_len],
+ pattern_percent, pattern_postpercent_len - 1)))
+ fail = 1;
+
+ if (fail)
+ /* It didn't match. Output the string. */
+ o = variable_buffer_output (o, t, len);
+ else
+ {
+ /* It matched. Output the replacement. */
+
+ /* Output the part of the replacement before the %. */
+ o = variable_buffer_output (o, replace, replace_prepercent_len);
+
+ if (replace_percent != 0)
+ {
+ /* Output the part of the matched string that
+ matched the % in the pattern. */
+ o = variable_buffer_output (o, t + pattern_prepercent_len,
+ len - (pattern_prepercent_len
+ + pattern_postpercent_len));
+ /* Output the part of the replacement after the %. */
+ o = variable_buffer_output (o, replace_percent,
+ replace_postpercent_len);
+ }
+ }
+
+ /* Output a space, but not if the replacement is "". */
+ if (fail || replace_prepercent_len > 0
+ || (replace_percent != 0 && len + replace_postpercent_len > 0))
+ {
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (doneany)
+ /* Kill the last space. */
+ --o;
+#else
+ /* Kill the last space and make sure there is a terminator there
+ so that strcache_add_len doesn't have to do a lot of exacty work
+ when expand_deps sends the output its way. */
+ if (doneany)
+ *--o = '\0';
+ else
+ o = variable_buffer_output (o, "\0", 1) - 1;
+#endif
+
+ return o;
+}
+
+/* Store into VARIABLE_BUFFER at O the result of scanning TEXT
+ and replacing strings matching PATTERN with REPLACE.
+ If PATTERN_PERCENT is not nil, PATTERN has already been
+ run through find_percent, and PATTERN_PERCENT is the result.
+ If REPLACE_PERCENT is not nil, REPLACE has already been
+ run through find_percent, and REPLACE_PERCENT is the result.
+ Note that we expect PATTERN_PERCENT and REPLACE_PERCENT to point to the
+ character _AFTER_ the %, not to the % itself.
+*/
+
+char *
+patsubst_expand (char *o, const char *text, char *pattern, char *replace)
+{
+ const char *pattern_percent = find_percent (pattern);
+ const char *replace_percent = find_percent (replace);
+
+ /* If there's a percent in the pattern or replacement skip it. */
+ if (replace_percent)
+ ++replace_percent;
+ if (pattern_percent)
+ ++pattern_percent;
+
+ return patsubst_expand_pat (o, text, pattern, replace,
+ pattern_percent, replace_percent);
+}
+
+#if defined (CONFIG_WITH_OPTIMIZATION_HACKS) || defined (CONFIG_WITH_VALUE_LENGTH)
+
+/* Char map containing the valid function name characters. */
+char func_char_map[256];
+
+/* Do the hash table lookup. */
+
+MY_INLINE const struct function_table_entry *
+lookup_function_in_hash_tab (const char *s, unsigned char len)
+{
+ struct function_table_entry function_table_entry_key;
+ function_table_entry_key.name = s;
+ function_table_entry_key.len = len;
+
+ return hash_find_item (&function_table, &function_table_entry_key);
+}
+
+/* Look up a function by name. */
+
+MY_INLINE const struct function_table_entry *
+lookup_function (const char *s, unsigned int len)
+{
+ unsigned char ch;
+# if 0 /* insane loop unroll */
+
+ if (len > MAX_FUNCTION_LENGTH)
+ len = MAX_FUNCTION_LENGTH + 1;
+
+# define X(idx) \
+ if (!func_char_map[ch = s[idx]]) \
+ { \
+ if (ISBLANK (ch)) \
+ return lookup_function_in_hash_tab (s, idx); \
+ return 0; \
+ }
+# define Z(idx) \
+ return lookup_function_in_hash_tab (s, idx);
+
+ switch (len)
+ {
+ default:
+ assert (0);
+ case 0: return 0;
+ case 1: return 0;
+ case 2: X(0); X(1); Z(2);
+ case 3: X(0); X(1); X(2); Z(3);
+ case 4: X(0); X(1); X(2); X(3); Z(4);
+ case 5: X(0); X(1); X(2); X(3); X(4); Z(5);
+ case 6: X(0); X(1); X(2); X(3); X(4); X(5); Z(6);
+ case 7: X(0); X(1); X(2); X(3); X(4); X(5); X(6); Z(7);
+ case 8: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); Z(8);
+ case 9: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); Z(9);
+ case 10: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); Z(10);
+ case 11: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); Z(11);
+ case 12: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); X(11); Z(12);
+ case 13: X(0); X(1); X(2); X(3); X(4); X(5); X(6); X(7); X(8); X(9); X(10); X(11); X(12);
+ if ((ch = s[12]) == '\0' || ISBLANK (ch))
+ return lookup_function_in_hash_tab (s, 12);
+ return 0;
+ }
+# undef Z
+# undef X
+
+# else /* normal loop */
+ const char *e = s;
+ if (len > MAX_FUNCTION_LENGTH)
+ len = MAX_FUNCTION_LENGTH;
+ while (func_char_map[ch = *e])
+ {
+ if (!len--)
+ return 0;
+ e++;
+ }
+ if (ch == '\0' || ISBLANK (ch))
+ return lookup_function_in_hash_tab (s, e - s);
+ return 0;
+# endif /* normal loop */
+}
+
+#else /* original code */
+/* Look up a function by name. */
+
+static const struct function_table_entry *
+lookup_function (const char *s)
+{
+ struct function_table_entry function_table_entry_key;
+ const char *e = s;
+ while (STOP_SET (*e, MAP_USERFUNC))
+ e++;
+
+ if (e == s || !STOP_SET(*e, MAP_NUL|MAP_SPACE))
+ return NULL;
+
+ function_table_entry_key.name = s;
+ function_table_entry_key.len = e - s;
+
+ return hash_find_item (&function_table, &function_table_entry_key);
+}
+#endif /* original code */
+
+
+/* Return 1 if PATTERN matches STR, 0 if not. */
+
+int
+pattern_matches (const char *pattern, const char *percent, const char *str)
+{
+ unsigned int sfxlen, strlength;
+
+ if (percent == 0)
+ {
+ unsigned int len = strlen (pattern) + 1;
+ char *new_chars = alloca (len);
+ memcpy (new_chars, pattern, len);
+ percent = find_percent (new_chars);
+ if (percent == 0)
+ return streq (new_chars, str);
+ pattern = new_chars;
+ }
+
+ sfxlen = strlen (percent + 1);
+ strlength = strlen (str);
+
+ if (strlength < (percent - pattern) + sfxlen
+ || !strneq (pattern, str, percent - pattern))
+ return 0;
+
+ return !strcmp (percent + 1, str + (strlength - sfxlen));
+}
+
+#ifdef KMK
+/* Return 1 if PATTERN matches STR, 0 if not.
+
+ PATTERN is the pattern to match against. PERCENT points to the '%' wildcard
+ inside PATTERN. SFXLEN is the length of pattern following PERCENT. */
+
+static int
+pattern_matches_ex (const char *pattern, const char *percent,
+ unsigned int sfxlen,
+ const char *str, unsigned int strlength)
+{
+ if (strlength < (percent - pattern) + sfxlen
+ || !strneq (pattern, str, percent - pattern)
+ || strcmp (percent + 1, str + (strlength - sfxlen)))
+ return 0;
+ return 1;
+}
+#endif
+
+
+/* Find the next comma or ENDPAREN (counting nested STARTPAREN and
+ ENDPARENtheses), starting at PTR before END. Return a pointer to
+ next character.
+
+ If no next argument is found, return NULL.
+*/
+
+static char *
+find_next_argument (char startparen, char endparen,
+ const char *ptr, const char *end)
+{
+ int count = 0;
+
+ for (; ptr < end; ++ptr)
+ if (*ptr == startparen)
+ ++count;
+
+ else if (*ptr == endparen)
+ {
+ --count;
+ if (count < 0)
+ return NULL;
+ }
+
+ else if (*ptr == ',' && !count)
+ return (char *)ptr;
+
+ /* We didn't find anything. */
+ return NULL;
+}
+
+
+/* Glob-expand LINE. The returned pointer is
+ only good until the next call to string_glob. */
+
+static char *
+string_glob (char *line)
+{
+ static char *result = 0;
+ static unsigned int length;
+ struct nameseq *chain;
+ unsigned int idx;
+
+ chain = PARSE_FILE_SEQ (&line, struct nameseq, MAP_NUL, NULL,
+ /* We do not want parse_file_seq to strip './'s.
+ That would break examples like:
+ $(patsubst ./%.c,obj/%.o,$(wildcard ./?*.c)). */
+ PARSEFS_NOSTRIP|PARSEFS_NOCACHE|PARSEFS_EXISTS);
+
+ if (result == 0)
+ {
+ length = 100;
+ result = xmalloc (100);
+ }
+
+ idx = 0;
+ while (chain != 0)
+ {
+ struct nameseq *next = chain->next;
+ unsigned int len = strlen (chain->name);
+
+ if (idx + len + 1 > length)
+ {
+ length += (len + 1) * 2;
+ result = xrealloc (result, length);
+ }
+ memcpy (&result[idx], chain->name, len);
+ idx += len;
+ result[idx++] = ' ';
+
+ /* Because we used PARSEFS_NOCACHE above, we have to free() NAME. */
+ free ((char *)chain->name);
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ free (chain);
+#else
+ alloccache_free (&nameseq_cache, chain);
+#endif
+ chain = next;
+ }
+
+ /* Kill the last space and terminate the string. */
+ if (idx == 0)
+ result[0] = '\0';
+ else
+ result[idx - 1] = '\0';
+
+ return result;
+}
+
+/*
+ Builtin functions
+ */
+
+static char *
+func_patsubst (char *o, char **argv, const char *funcname UNUSED)
+{
+ o = patsubst_expand (o, argv[2], argv[0], argv[1]);
+ return o;
+}
+
+
+static char *
+func_join (char *o, char **argv, const char *funcname UNUSED)
+{
+ int doneany = 0;
+
+ /* Write each word of the first argument directly followed
+ by the corresponding word of the second argument.
+ If the two arguments have a different number of words,
+ the excess words are just output separated by blanks. */
+ const char *tp;
+ const char *pp;
+ const char *list1_iterator = argv[0];
+ const char *list2_iterator = argv[1];
+ do
+ {
+ unsigned int len1, len2;
+
+ tp = find_next_token (&list1_iterator, &len1);
+ if (tp != 0)
+ o = variable_buffer_output (o, tp, len1);
+
+ pp = find_next_token (&list2_iterator, &len2);
+ if (pp != 0)
+ o = variable_buffer_output (o, pp, len2);
+
+ if (tp != 0 || pp != 0)
+ {
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+ while (tp != 0 || pp != 0);
+ if (doneany)
+ /* Kill the last blank. */
+ --o;
+
+ return o;
+}
+
+
+static char *
+func_origin (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* Expand the argument. */
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+ if (v == 0)
+ o = variable_buffer_output (o, "undefined", 9);
+ else
+ switch (v->origin)
+ {
+ default:
+ case o_invalid:
+ abort ();
+ break;
+ case o_default:
+ o = variable_buffer_output (o, "default", 7);
+ break;
+ case o_env:
+ o = variable_buffer_output (o, "environment", 11);
+ break;
+ case o_file:
+ o = variable_buffer_output (o, "file", 4);
+ break;
+ case o_env_override:
+ o = variable_buffer_output (o, "environment override", 20);
+ break;
+ case o_command:
+ o = variable_buffer_output (o, "command line", 12);
+ break;
+ case o_override:
+ o = variable_buffer_output (o, "override", 8);
+ break;
+ case o_automatic:
+ o = variable_buffer_output (o, "automatic", 9);
+ break;
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ case o_local:
+ o = variable_buffer_output (o, "local", 5);
+ break;
+#endif
+ }
+
+ return o;
+}
+
+static char *
+func_flavor (char *o, char **argv, const char *funcname UNUSED)
+{
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+
+ if (v == 0)
+ o = variable_buffer_output (o, "undefined", 9);
+ else
+ if (v->recursive)
+ o = variable_buffer_output (o, "recursive", 9);
+ else
+ o = variable_buffer_output (o, "simple", 6);
+
+ return o;
+}
+
+#ifdef CONFIG_WITH_WHERE_FUNCTION
+static char *
+func_where (char *o, char **argv, const char *funcname UNUSED)
+{
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+ char buf[64];
+
+ if (v == 0)
+ o = variable_buffer_output (o, "undefined", 9);
+ else
+ if (v->fileinfo.filenm)
+ {
+ o = variable_buffer_output (o, v->fileinfo.filenm, strlen(v->fileinfo.filenm));
+ sprintf (buf, ":%lu", v->fileinfo.lineno);
+ o = variable_buffer_output (o, buf, strlen(buf));
+ }
+ else
+ o = variable_buffer_output (o, "no-location", 11);
+
+ return o;
+}
+#endif /* CONFIG_WITH_WHERE_FUNCTION */
+
+static char *
+func_notdir_suffix (char *o, char **argv, const char *funcname)
+{
+ /* Expand the argument. */
+ const char *list_iterator = argv[0];
+ const char *p2;
+ int doneany =0;
+ unsigned int len=0;
+
+ int is_suffix = funcname[0] == 's';
+ int is_notdir = !is_suffix;
+ int stop = MAP_DIRSEP | (is_suffix ? MAP_DOT : 0);
+#ifdef VMS
+ /* For VMS list_iterator points to a comma separated list. To use the common
+ [find_]next_token, create a local copy and replace the commas with
+ spaces. Obviously, there is a problem if there is a ',' in the VMS filename
+ (can only happen on ODS5), the same problem as with spaces in filenames,
+ which seems to be present in make on all platforms. */
+ char *vms_list_iterator = alloca(strlen(list_iterator) + 1);
+ int i;
+ for (i = 0; list_iterator[i]; i++)
+ if (list_iterator[i] == ',')
+ vms_list_iterator[i] = ' ';
+ else
+ vms_list_iterator[i] = list_iterator[i];
+ vms_list_iterator[i] = list_iterator[i];
+ while ((p2 = find_next_token((const char**) &vms_list_iterator, &len)) != 0)
+#else
+ while ((p2 = find_next_token (&list_iterator, &len)) != 0)
+#endif
+ {
+ const char *p = p2 + len - 1;
+
+ while (p >= p2 && ! STOP_SET (*p, stop))
+ --p;
+
+ if (p >= p2)
+ {
+ if (is_notdir)
+ ++p;
+ else if (*p != '.')
+ continue;
+ o = variable_buffer_output (o, p, len - (p - p2));
+ }
+#ifdef HAVE_DOS_PATHS
+ /* Handle the case of "d:foo/bar". */
+ else if (is_notdir && p2[0] && p2[1] == ':')
+ {
+ p = p2 + 2;
+ o = variable_buffer_output (o, p, len - (p - p2));
+ }
+#endif
+ else if (is_notdir)
+ o = variable_buffer_output (o, p2, len);
+
+ if (is_notdir || p >= p2)
+ {
+#ifdef VMS
+ if (vms_comma_separator)
+ o = variable_buffer_output (o, ",", 1);
+ else
+#endif
+ o = variable_buffer_output (o, " ", 1);
+
+ doneany = 1;
+ }
+ }
+
+ if (doneany)
+ /* Kill last space. */
+ --o;
+
+ return o;
+}
+
+
+static char *
+func_basename_dir (char *o, char **argv, const char *funcname)
+{
+ /* Expand the argument. */
+ const char *p3 = argv[0];
+ const char *p2;
+ int doneany = 0;
+ unsigned int len = 0;
+
+ int is_basename = funcname[0] == 'b';
+ int is_dir = !is_basename;
+ int stop = MAP_DIRSEP | (is_basename ? MAP_DOT : 0) | MAP_NUL;
+#ifdef VMS
+ /* As in func_notdir_suffix ... */
+ char *vms_p3 = alloca (strlen(p3) + 1);
+ int i;
+ for (i = 0; p3[i]; i++)
+ if (p3[i] == ',')
+ vms_p3[i] = ' ';
+ else
+ vms_p3[i] = p3[i];
+ vms_p3[i] = p3[i];
+ while ((p2 = find_next_token((const char**) &vms_p3, &len)) != 0)
+#else
+ while ((p2 = find_next_token (&p3, &len)) != 0)
+#endif
+ {
+ const char *p = p2 + len - 1;
+ while (p >= p2 && ! STOP_SET (*p, stop))
+ --p;
+
+ if (p >= p2 && (is_dir))
+ o = variable_buffer_output (o, p2, ++p - p2);
+ else if (p >= p2 && (*p == '.'))
+ o = variable_buffer_output (o, p2, p - p2);
+#ifdef HAVE_DOS_PATHS
+ /* Handle the "d:foobar" case */
+ else if (p2[0] && p2[1] == ':' && is_dir)
+ o = variable_buffer_output (o, p2, 2);
+#endif
+ else if (is_dir)
+#ifdef VMS
+ {
+ extern int vms_report_unix_paths;
+ if (vms_report_unix_paths)
+ o = variable_buffer_output (o, "./", 2);
+ else
+ o = variable_buffer_output (o, "[]", 2);
+ }
+#else
+#ifndef _AMIGA
+ o = variable_buffer_output (o, "./", 2);
+#else
+ ; /* Just a nop... */
+#endif /* AMIGA */
+#endif /* !VMS */
+ else
+ /* The entire name is the basename. */
+ o = variable_buffer_output (o, p2, len);
+
+#ifdef VMS
+ if (vms_comma_separator)
+ o = variable_buffer_output (o, ",", 1);
+ else
+#endif
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+
+ if (doneany)
+ /* Kill last space. */
+ --o;
+
+ return o;
+}
+
+#if 1 /* rewrite to new MAP stuff? */
+# ifdef VMS
+# define IS_PATHSEP(c) ((c) == ']')
+# else
+# ifdef HAVE_DOS_PATHS
+# define IS_PATHSEP(c) ((c) == '/' || (c) == '\\')
+# else
+# define IS_PATHSEP(c) ((c) == '/')
+# endif
+# endif
+#endif
+
+#ifdef CONFIG_WITH_ROOT_FUNC
+
+/*
+ $(root path)
+
+ This is mainly for dealing with drive letters and UNC paths on Windows
+ and OS/2.
+ */
+static char *
+func_root (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *paths = argv[0] ? argv[0] : "";
+ int doneany = 0;
+ const char *p;
+ unsigned int len;
+
+ while ((p = find_next_token (&paths, &len)) != 0)
+ {
+ const char *p2 = p;
+
+# ifdef HAVE_DOS_PATHS
+ if ( len >= 2
+ && p2[1] == ':'
+ && ( (p2[0] >= 'A' && p2[0] <= 'Z')
+ || (p2[0] >= 'a' && p2[0] <= 'z')))
+ {
+ p2 += 2;
+ len -= 2;
+ }
+ else if (len >= 4 && IS_PATHSEP(p2[0]) && IS_PATHSEP(p2[1])
+ && !IS_PATHSEP(p2[2]))
+ {
+ /* Min recognized UNC: "//./" - find the next slash
+ Typical root: "//srv/shr/" */
+ /* XXX: Check if //./ needs special handling. */
+
+ p2 += 3;
+ len -= 3;
+ while (len > 0 && !IS_PATHSEP(*p2))
+ p2++, len--;
+
+ if (len && IS_PATHSEP(p2[0]) && (len == 1 || !IS_PATHSEP(p2[1])))
+ {
+ p2++;
+ len--;
+
+ if (len) /* optional share */
+ while (len > 0 && !IS_PATHSEP(*p2))
+ p2++, len--;
+ }
+ else
+ p2 = NULL;
+ }
+ else if (IS_PATHSEP(*p2))
+ {
+ p2++;
+ len--;
+ }
+ else
+ p2 = NULL;
+
+# elif defined (VMS) || defined (AMGIA)
+ /* XXX: VMS and AMGIA */
+ O (fatal, NILF, _("$(root ) is not implemented on this platform"));
+# else
+ if (IS_PATHSEP(*p2))
+ {
+ p2++;
+ len--;
+ }
+ else
+ p2 = NULL;
+# endif
+ if (p2 != NULL)
+ {
+ /* Include all subsequent path separators. */
+
+ while (len > 0 && IS_PATHSEP(*p2))
+ p2++, len--;
+ o = variable_buffer_output (o, p, p2 - p);
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+
+ if (doneany)
+ /* Kill last space. */
+ --o;
+
+ return o;
+}
+
+/*
+ $(notroot path)
+
+ This is mainly for dealing with drive letters and UNC paths on Windows
+ and OS/2.
+ */
+static char *
+func_notroot (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *paths = argv[0] ? argv[0] : "";
+ int doneany = 0;
+ const char *p;
+ unsigned int len;
+
+ while ((p = find_next_token (&paths, &len)) != 0)
+ {
+ const char *p2 = p;
+
+# ifdef HAVE_DOS_PATHS
+ if ( len >= 2
+ && p2[1] == ':'
+ && ( (p2[0] >= 'A' && p2[0] <= 'Z')
+ || (p2[0] >= 'a' && p2[0] <= 'z')))
+ {
+ p2 += 2;
+ len -= 2;
+ }
+ else if (len >= 4 && IS_PATHSEP(p2[0]) && IS_PATHSEP(p2[1])
+ && !IS_PATHSEP(p2[2]))
+ {
+ /* Min recognized UNC: "//./" - find the next slash
+ Typical root: "//srv/shr/" */
+ /* XXX: Check if //./ needs special handling. */
+ unsigned int saved_len = len;
+
+ p2 += 3;
+ len -= 3;
+ while (len > 0 && !IS_PATHSEP(*p2))
+ p2++, len--;
+
+ if (len && IS_PATHSEP(p2[0]) && (len == 1 || !IS_PATHSEP(p2[1])))
+ {
+ p2++;
+ len--;
+
+ if (len) /* optional share */
+ while (len > 0 && !IS_PATHSEP(*p2))
+ p2++, len--;
+ }
+ else
+ {
+ p2 = p;
+ len = saved_len;
+ }
+ }
+
+# elif defined (VMS) || defined (AMGIA)
+ /* XXX: VMS and AMGIA */
+ O (fatal, NILF, _("$(root ) is not implemented on this platform"));
+# endif
+
+ /* Exclude all subsequent / leading path separators. */
+
+ while (len > 0 && IS_PATHSEP(*p2))
+ p2++, len--;
+ if (len > 0)
+ o = variable_buffer_output (o, p2, len);
+ else
+ o = variable_buffer_output (o, ".", 1);
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+
+ if (doneany)
+ /* Kill last space. */
+ --o;
+
+ return o;
+}
+
+#endif /* CONFIG_WITH_ROOT_FUNC */
+
+static char *
+func_addsuffix_addprefix (char *o, char **argv, const char *funcname)
+{
+ int fixlen = strlen (argv[0]);
+ const char *list_iterator = argv[1];
+ int is_addprefix = funcname[3] == 'p';
+ int is_addsuffix = !is_addprefix;
+
+ int doneany = 0;
+ const char *p;
+ unsigned int len;
+
+ while ((p = find_next_token (&list_iterator, &len)) != 0)
+ {
+ if (is_addprefix)
+ o = variable_buffer_output (o, argv[0], fixlen);
+ o = variable_buffer_output (o, p, len);
+ if (is_addsuffix)
+ o = variable_buffer_output (o, argv[0], fixlen);
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+
+ if (doneany)
+ /* Kill last space. */
+ --o;
+
+ return o;
+}
+
+static char *
+func_subst (char *o, char **argv, const char *funcname UNUSED)
+{
+ o = subst_expand (o, argv[2], argv[0], argv[1], strlen (argv[0]),
+ strlen (argv[1]), 0);
+
+ return o;
+}
+
+#ifdef CONFIG_WITH_DEFINED_FUNCTIONS
+
+/* Used by func_firstdefined and func_lastdefined to parse the optional last
+ argument. Returns 0 if the variable name is to be returned and 1 if it's
+ the variable value value. */
+static int
+parse_value_name_argument (const char *arg1, const char *funcname)
+{
+ const char *end;
+ int rc;
+
+ if (arg1 == NULL)
+ return 0;
+
+ end = strchr (arg1, '\0');
+ strip_whitespace (&arg1, &end);
+
+ if (!strncmp (arg1, "name", end - arg1))
+ rc = 0;
+ else if (!strncmp (arg1, "value", end - arg1))
+ rc = 1;
+ else
+# if 1 /* FIXME: later */
+ OSS (fatal, *expanding_var,
+ _("second argument to `%s' function must be `name' or `value', not `%s'"),
+ funcname, arg1);
+# else
+ {
+ /* check the expanded form */
+ char *exp = expand_argument (arg1, strchr (arg1, '\0'));
+ arg1 = exp;
+ end = strchr (arg1, '\0');
+ strip_whitespace (&arg1, &end);
+
+ if (!strncmp (arg1, "name", end - arg1))
+ rc = 0;
+ else if (!strncmp (arg1, "value", end - arg1))
+ rc = 1;
+ else
+ OSS (fatal, *expanding_var,
+ _("second argument to `%s' function must be `name' or `value', not `%s'"),
+ funcname, exp);
+ free (exp);
+ }
+# endif
+
+ return rc;
+}
+
+/* Given a list of variable names (ARGV[0]), returned the first variable which
+ is defined (i.e. value is not empty). ARGV[1] indicates whether to return
+ the variable name or its value. */
+static char *
+func_firstdefined (char *o, char **argv, const char *funcname)
+{
+ unsigned int i;
+ const char *words = argv[0]; /* Use a temp variable for find_next_token */
+ const char *p;
+ int ret_value = parse_value_name_argument (argv[1], funcname);
+
+ /* FIXME: Optimize by not expanding the arguments, but instead expand them
+ one by one here. This will require a find_next_token variant which
+ takes `$(' and `)' into account. */
+ while ((p = find_next_token (&words, &i)) != NULL)
+ {
+ struct variable *v = lookup_variable (p, i);
+ if (v && v->value_length)
+ {
+ if (ret_value)
+ variable_expand_string_2 (o, v->value, v->value_length, &o);
+ else
+ o = variable_buffer_output (o, p, i);
+ break;
+ }
+ }
+
+ return o;
+}
+
+/* Given a list of variable names (ARGV[0]), returned the last variable which
+ is defined (i.e. value is not empty). ARGV[1] indicates whether to return
+ the variable name or its value. */
+static char *
+func_lastdefined (char *o, char **argv, const char *funcname)
+{
+ struct variable *last_v = NULL;
+ unsigned int i;
+ const char *words = argv[0]; /* Use a temp variable for find_next_token */
+ const char *p;
+ int ret_value = parse_value_name_argument (argv[1], funcname);
+
+ /* FIXME: Optimize this. Walk from the end on unexpanded arguments. */
+ while ((p = find_next_token (&words, &i)) != NULL)
+ {
+ struct variable *v = lookup_variable (p, i);
+ if (v && v->value_length)
+ {
+ last_v = v;
+ break;
+ }
+ }
+
+ if (last_v != NULL)
+ {
+ if (ret_value)
+ variable_expand_string_2 (o, last_v->value, last_v->value_length, &o);
+ else
+ o = variable_buffer_output (o, last_v->name, last_v->length);
+ }
+ return o;
+}
+
+#endif /* CONFIG_WITH_DEFINED_FUNCTIONS */
+
+static char *
+func_firstword (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int i;
+ const char *words = argv[0]; /* Use a temp variable for find_next_token */
+ const char *p = find_next_token (&words, &i);
+
+ if (p != 0)
+ o = variable_buffer_output (o, p, i);
+
+ return o;
+}
+
+static char *
+func_lastword (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int i;
+ const char *words = argv[0]; /* Use a temp variable for find_next_token */
+ const char *p = NULL;
+ const char *t;
+
+ while ((t = find_next_token (&words, &i)))
+ p = t;
+
+ if (p != 0)
+ o = variable_buffer_output (o, p, i);
+
+ return o;
+}
+
+static char *
+func_words (char *o, char **argv, const char *funcname UNUSED)
+{
+ int i = 0;
+ const char *word_iterator = argv[0];
+ char buf[20];
+
+ while (find_next_token (&word_iterator, NULL) != 0)
+ ++i;
+
+ sprintf (buf, "%d", i);
+ o = variable_buffer_output (o, buf, strlen (buf));
+
+ return o;
+}
+
+/* Set begpp to point to the first non-whitespace character of the string,
+ * and endpp to point to the last non-whitespace character of the string.
+ * If the string is empty or contains nothing but whitespace, endpp will be
+ * begpp-1.
+ */
+char *
+strip_whitespace (const char **begpp, const char **endpp)
+{
+ while (*begpp <= *endpp && ISSPACE (**begpp))
+ (*begpp) ++;
+ while (*endpp >= *begpp && ISSPACE (**endpp))
+ (*endpp) --;
+ return (char *)*begpp;
+}
+
+static void
+check_numeric (const char *s, const char *msg)
+{
+ const char *end = s + strlen (s) - 1;
+ const char *beg = s;
+ strip_whitespace (&s, &end);
+
+ for (; s <= end; ++s)
+ if (!ISDIGIT (*s)) /* ISDIGIT only evals its arg once: see makeint.h. */
+ break;
+
+ if (s <= end || end - beg < 0)
+ OSS (fatal, *expanding_var, "%s: '%s'", msg, beg);
+}
+
+
+
+static char *
+func_word (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *end_p;
+ const char *p;
+ int i;
+
+ /* Check the first argument. */
+ check_numeric (argv[0], _("non-numeric first argument to 'word' function"));
+ i = atoi (argv[0]);
+
+ if (i == 0)
+ O (fatal, *expanding_var,
+ _("first argument to 'word' function must be greater than 0"));
+
+ end_p = argv[1];
+ while ((p = find_next_token (&end_p, 0)) != 0)
+ if (--i == 0)
+ break;
+
+ if (i == 0)
+ o = variable_buffer_output (o, p, end_p - p);
+
+ return o;
+}
+
+static char *
+func_wordlist (char *o, char **argv, const char *funcname UNUSED)
+{
+ int start, count;
+
+ /* Check the arguments. */
+ check_numeric (argv[0],
+ _("non-numeric first argument to 'wordlist' function"));
+ check_numeric (argv[1],
+ _("non-numeric second argument to 'wordlist' function"));
+
+ start = atoi (argv[0]);
+ if (start < 1)
+ ON (fatal, *expanding_var,
+ "invalid first argument to 'wordlist' function: '%d'", start);
+
+ count = atoi (argv[1]) - start + 1;
+
+ if (count > 0)
+ {
+ const char *p;
+ const char *end_p = argv[2];
+
+ /* Find the beginning of the "start"th word. */
+ while (((p = find_next_token (&end_p, 0)) != 0) && --start)
+ ;
+
+ if (p)
+ {
+ /* Find the end of the "count"th word from start. */
+ while (--count && (find_next_token (&end_p, 0) != 0))
+ ;
+
+ /* Return the stuff in the middle. */
+ o = variable_buffer_output (o, p, end_p - p);
+ }
+ }
+
+ return o;
+}
+
+static char *
+func_findstring (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* Find the first occurrence of the first string in the second. */
+ if (strstr (argv[1], argv[0]) != 0)
+ o = variable_buffer_output (o, argv[0], strlen (argv[0]));
+
+ return o;
+}
+
+static char *
+func_foreach (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* expand only the first two. */
+ char *varname = expand_argument (argv[0], NULL);
+ char *list = expand_argument (argv[1], NULL);
+ const char *body = argv[2];
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ long body_len = strlen (body);
+#endif
+
+ int doneany = 0;
+ const char *list_iterator = list;
+ const char *p;
+ unsigned int len;
+ struct variable *var;
+
+ /* Clean up the variable name by removing whitespace. */
+ char *vp = next_token (varname);
+ end_of_token (vp)[0] = '\0';
+
+ push_new_variable_scope ();
+ var = define_variable (vp, strlen (vp), "", o_automatic, 0);
+
+ /* loop through LIST, put the value in VAR and expand BODY */
+ while ((p = find_next_token (&list_iterator, &len)) != 0)
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *result = 0;
+
+ free (var->value);
+ var->value = xstrndup (p, len);
+
+ result = allocated_variable_expand (body);
+
+ o = variable_buffer_output (o, result, strlen (result));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ free (result);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ if (len >= var->value_alloc_len)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (var->rdonly_val)
+ var->rdonly_val = 0;
+ else
+# endif
+ free (var->value);
+ var->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (len + 1);
+ var->value = xmalloc (var->value_alloc_len);
+ }
+ memcpy (var->value, p, len);
+ var->value[len] = '\0';
+ var->value_length = len;
+ VARIABLE_CHANGED (var);
+
+ variable_expand_string_2 (o, body, body_len, &o);
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+ }
+
+ if (doneany)
+ /* Kill the last space. */
+ --o;
+
+ pop_variable_scope ();
+ free (varname);
+ free (list);
+
+ return o;
+}
+
+#ifdef CONFIG_WITH_LOOP_FUNCTIONS
+
+/* Helper for func_for that evaluates the INIT and NEXT parts. */
+static void
+helper_eval (char *text, size_t text_len)
+{
+ unsigned int buf_len;
+ char *buf;
+
+ install_variable_buffer (&buf, &buf_len);
+ eval_buffer (text, NULL, text + text_len);
+ restore_variable_buffer (buf, buf_len);
+}
+
+/*
+ $(for init,condition,next,body)
+ */
+static char *
+func_for (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *init = argv[0];
+ const char *cond = argv[1];
+ const char *next = argv[2];
+ size_t next_len = strlen (next);
+ char *next_buf = xmalloc (next_len + 1);
+ const char *body = argv[3];
+ size_t body_len = strlen (body);
+ unsigned int doneany = 0;
+
+ push_new_variable_scope ();
+
+ /* Evaluate INIT. */
+
+ helper_eval (init, strlen (init));
+
+ /* Loop till COND is false. */
+
+ while (expr_eval_if_conditionals (cond, NULL) == 0 /* true */)
+ {
+ /* Expand BODY. */
+
+ if (!doneany)
+ doneany = 1;
+ else
+ o = variable_buffer_output (o, " ", 1);
+ variable_expand_string_2 (o, body, body_len, &o);
+
+ /* Evaluate NEXT. */
+
+ memcpy (next_buf, next, next_len + 1);
+ helper_eval (next_buf, next_len);
+ }
+
+ pop_variable_scope ();
+ free (next_buf);
+
+ return o;
+}
+
+/*
+ $(while condition,body)
+ */
+static char *
+func_while (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *cond = argv[0];
+ const char *body = argv[1];
+ size_t body_len = strlen (body);
+ unsigned int doneany = 0;
+
+ push_new_variable_scope ();
+
+ while (expr_eval_if_conditionals (cond, NULL) == 0 /* true */)
+ {
+ if (!doneany)
+ doneany = 1;
+ else
+ o = variable_buffer_output (o, " ", 1);
+ variable_expand_string_2 (o, body, body_len, &o);
+ }
+
+ pop_variable_scope ();
+
+ return o;
+}
+
+#endif /* CONFIG_WITH_LOOP_FUNCTIONS */
+
+struct a_word
+{
+ struct a_word *next;
+ struct a_word *chain;
+ char *str;
+ int length;
+ int matched;
+};
+
+static unsigned long
+a_word_hash_1 (const void *key)
+{
+ return_STRING_HASH_1 (((struct a_word const *) key)->str);
+}
+
+static unsigned long
+a_word_hash_2 (const void *key)
+{
+ return_STRING_HASH_2 (((struct a_word const *) key)->str);
+}
+
+static int
+a_word_hash_cmp (const void *x, const void *y)
+{
+ int result = ((struct a_word const *) x)->length - ((struct a_word const *) y)->length;
+ if (result)
+ return result;
+ return_STRING_COMPARE (((struct a_word const *) x)->str,
+ ((struct a_word const *) y)->str);
+}
+
+struct a_pattern
+{
+ struct a_pattern *next;
+ char *str;
+ char *percent;
+ int length;
+#ifdef KMK
+ unsigned int sfxlen;
+#endif
+};
+
+static char *
+func_filter_filterout (char *o, char **argv, const char *funcname)
+{
+ struct a_word *wordhead;
+ struct a_word **wordtail;
+ struct a_word *wp;
+ struct a_pattern *pathead;
+ struct a_pattern **pattail;
+ struct a_pattern *pp;
+
+ struct hash_table a_word_table;
+ int is_filter = funcname[CSTRLEN ("filter")] == '\0';
+ const char *pat_iterator = argv[0];
+ const char *word_iterator = argv[1];
+ int literals = 0;
+ int words = 0;
+ int hashing = 0;
+ char *p;
+ unsigned int len;
+
+ /* Chop ARGV[0] up into patterns to match against the words.
+ We don't need to preserve it because our caller frees all the
+ argument memory anyway. */
+
+ pattail = &pathead;
+ while ((p = find_next_token (&pat_iterator, &len)) != 0)
+ {
+ struct a_pattern *pat = alloca (sizeof (struct a_pattern));
+
+ *pattail = pat;
+ pattail = &pat->next;
+
+ if (*pat_iterator != '\0')
+ ++pat_iterator;
+
+ pat->str = p;
+ p[len] = '\0';
+ pat->percent = find_percent (p);
+ if (pat->percent == 0)
+ literals++;
+#ifdef KMK
+ pat->sfxlen = pat->percent ? strlen(pat->percent + 1) : 0;
+#endif
+
+ /* find_percent() might shorten the string so LEN is wrong. */
+ pat->length = strlen (pat->str);
+ }
+ *pattail = 0;
+
+ /* Chop ARGV[1] up into words to match against the patterns. */
+
+ wordtail = &wordhead;
+ while ((p = find_next_token (&word_iterator, &len)) != 0)
+ {
+ struct a_word *word = alloca (sizeof (struct a_word));
+
+ *wordtail = word;
+ wordtail = &word->next;
+
+ if (*word_iterator != '\0')
+ ++word_iterator;
+
+ p[len] = '\0';
+ word->str = p;
+ word->length = len;
+ word->matched = 0;
+ word->chain = 0;
+ words++;
+ }
+ *wordtail = 0;
+
+ /* Only use a hash table if arg list lengths justifies the cost. */
+ hashing = (literals >= 2 && (literals * words) >= 10);
+ if (hashing)
+ {
+ hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2,
+ a_word_hash_cmp);
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ {
+ struct a_word *owp = hash_insert (&a_word_table, wp);
+ if (owp)
+ wp->chain = owp;
+ }
+ }
+
+ if (words)
+ {
+ int doneany = 0;
+
+ /* Run each pattern through the words, killing words. */
+ for (pp = pathead; pp != 0; pp = pp->next)
+ {
+ if (pp->percent)
+ for (wp = wordhead; wp != 0; wp = wp->next)
+#ifdef KMK
+ wp->matched |= pattern_matches_ex (pp->str, pp->percent, pp->sfxlen,
+ wp->str, wp->length);
+#else
+ wp->matched |= pattern_matches (pp->str, pp->percent, wp->str);
+#endif
+ else if (hashing)
+ {
+ struct a_word a_word_key;
+ a_word_key.str = pp->str;
+ a_word_key.length = pp->length;
+ wp = hash_find_item (&a_word_table, &a_word_key);
+ while (wp)
+ {
+ wp->matched |= 1;
+ wp = wp->chain;
+ }
+ }
+ else
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ wp->matched |= (wp->length == pp->length
+ && strneq (pp->str, wp->str, wp->length));
+ }
+
+ /* Output the words that matched (or didn't, for filter-out). */
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ if (is_filter ? wp->matched : !wp->matched)
+ {
+ o = variable_buffer_output (o, wp->str, strlen (wp->str));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+
+ if (doneany)
+ /* Kill the last space. */
+ --o;
+ }
+
+ if (hashing)
+ hash_free (&a_word_table, 0);
+
+ return o;
+}
+
+
+static char *
+func_strip (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *p = argv[0];
+ int doneany = 0;
+
+ while (*p != '\0')
+ {
+ int i=0;
+ const char *word_start;
+
+ NEXT_TOKEN (p);
+ word_start = p;
+ for (i=0; *p != '\0' && !ISSPACE (*p); ++p, ++i)
+ {}
+ if (!i)
+ break;
+ o = variable_buffer_output (o, word_start, i);
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+
+ if (doneany)
+ /* Kill the last space. */
+ --o;
+
+ return o;
+}
+
+/*
+ Print a warning or fatal message.
+*/
+static char *
+func_error (char *o, char **argv, const char *funcname)
+{
+ char **argvp;
+ char *msg, *p;
+ int len;
+
+ /* The arguments will be broken on commas. Rather than create yet
+ another special case where function arguments aren't broken up,
+ just create a format string that puts them back together. */
+ for (len=0, argvp=argv; *argvp != 0; ++argvp)
+ len += strlen (*argvp) + 2;
+
+ p = msg = alloca (len + 1);
+
+ for (argvp=argv; argvp[1] != 0; ++argvp)
+ {
+ strcpy (p, *argvp);
+ p += strlen (*argvp);
+ *(p++) = ',';
+ *(p++) = ' ';
+ }
+ strcpy (p, *argvp);
+
+ switch (*funcname)
+ {
+ case 'e':
+ OS (fatal, reading_file, "%s", msg);
+
+ case 'w':
+ OS (error, reading_file, "%s", msg);
+ break;
+
+ case 'i':
+ outputs (0, msg);
+ outputs (0, "\n");
+ break;
+
+ default:
+ OS (fatal, *expanding_var, "Internal error: func_error: '%s'", funcname);
+ }
+
+ /* The warning function expands to the empty string. */
+ return o;
+}
+
+#ifdef KMK
+/* Compare strings *S1 and *S2.
+ Return negative if the first is less, positive if it is greater,
+ zero if they are equal. */
+
+static int
+version_compare_wrapper (const void *v1, const void *v2)
+{
+ const char *s1 = *((char **)v1);
+ const char *s2 = *((char **)v2);
+ return version_compare (s1, s2);
+}
+#endif /* KMK */
+
+/*
+ chop argv[0] into words, and sort them.
+ */
+static char *
+func_sort (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *t;
+ char **words;
+ int wordi;
+ char *p;
+ unsigned int len;
+
+ /* Find the maximum number of words we'll have. */
+ t = argv[0];
+ wordi = 0;
+ while ((p = find_next_token (&t, NULL)) != 0)
+ {
+ ++t;
+ ++wordi;
+ }
+
+ words = xmalloc ((wordi == 0 ? 1 : wordi) * sizeof (char *));
+
+ /* Now assign pointers to each string in the array. */
+ t = argv[0];
+ wordi = 0;
+ while ((p = find_next_token (&t, &len)) != 0)
+ {
+ if (*t != '\0') /* bird: Fixes access beyond end of string and overflowing words array. */
+ ++t;
+ p[len] = '\0';
+ words[wordi++] = p;
+ }
+
+ if (wordi)
+ {
+ int i;
+
+ /* Now sort the list of words. */
+#ifdef KMK
+ if (funcname[0] == 'v' || funcname[1] == 'v')
+ qsort (words, wordi, sizeof (char *), version_compare_wrapper);
+ else
+ qsort (words, wordi, sizeof (char *), alpha_compare);
+#else
+ qsort (words, wordi, sizeof (char *), alpha_compare);
+#endif
+
+ /* Now write the sorted list, uniquified. */
+#ifdef CONFIG_WITH_RSORT
+ if (*funcname != 'r')
+ {
+ /* sort */
+#endif
+ for (i = 0; i < wordi; ++i)
+ {
+ len = strlen (words[i]);
+ if (i == wordi - 1 || strlen (words[i + 1]) != len
+ || strcmp (words[i], words[i + 1]))
+ {
+ o = variable_buffer_output (o, words[i], len);
+ o = variable_buffer_output (o, " ", 1);
+ }
+ }
+#ifdef CONFIG_WITH_RSORT
+ }
+ else
+ {
+ /* rsort - reverse the result */
+ i = wordi;
+ while (i-- > 0)
+ {
+ len = strlen (words[i]);
+ if (i == 0 || strlen (words[i - 1]) != len
+ || strcmp (words[i], words[i - 1]))
+ {
+ o = variable_buffer_output (o, words[i], len);
+ o = variable_buffer_output (o, " ", 1);
+ }
+ }
+ }
+#endif
+
+ /* Kill the last space. */
+ --o;
+ }
+
+ free (words);
+
+ return o;
+}
+
+/*
+ $(if condition,true-part[,false-part])
+
+ CONDITION is false iff it evaluates to an empty string. White
+ space before and after condition are stripped before evaluation.
+
+ If CONDITION is true, then TRUE-PART is evaluated, otherwise FALSE-PART is
+ evaluated (if it exists). Because only one of the two PARTs is evaluated,
+ you can use $(if ...) to create side-effects (with $(shell ...), for
+ example).
+*/
+
+static char *
+func_if (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *begp = argv[0];
+ const char *endp = begp + strlen (argv[0]) - 1;
+ int result = 0;
+
+ /* Find the result of the condition: if we have a value, and it's not
+ empty, the condition is true. If we don't have a value, or it's the
+ empty string, then it's false. */
+
+ strip_whitespace (&begp, &endp);
+
+ if (begp <= endp)
+ {
+ char *expansion = expand_argument (begp, endp+1);
+
+ result = strlen (expansion);
+ free (expansion);
+ }
+
+ /* If the result is true (1) we want to eval the first argument, and if
+ it's false (0) we want to eval the second. If the argument doesn't
+ exist we do nothing, otherwise expand it and add to the buffer. */
+
+ argv += 1 + !result;
+
+ if (*argv)
+ {
+ char *expansion = expand_argument (*argv, NULL);
+
+ o = variable_buffer_output (o, expansion, strlen (expansion));
+
+ free (expansion);
+ }
+
+ return o;
+}
+
+/*
+ $(or condition1[,condition2[,condition3[...]]])
+
+ A CONDITION is false iff it evaluates to an empty string. White
+ space before and after CONDITION are stripped before evaluation.
+
+ CONDITION1 is evaluated. If it's true, then this is the result of
+ expansion. If it's false, CONDITION2 is evaluated, and so on. If none of
+ the conditions are true, the expansion is the empty string.
+
+ Once a CONDITION is true no further conditions are evaluated
+ (short-circuiting).
+*/
+
+static char *
+func_or (char *o, char **argv, const char *funcname UNUSED)
+{
+ for ( ; *argv ; ++argv)
+ {
+ const char *begp = *argv;
+ const char *endp = begp + strlen (*argv) - 1;
+ char *expansion;
+ int result = 0;
+
+ /* Find the result of the condition: if it's false keep going. */
+
+ strip_whitespace (&begp, &endp);
+
+ if (begp > endp)
+ continue;
+
+ expansion = expand_argument (begp, endp+1);
+ result = strlen (expansion);
+
+ /* If the result is false keep going. */
+ if (!result)
+ {
+ free (expansion);
+ continue;
+ }
+
+ /* It's true! Keep this result and return. */
+ o = variable_buffer_output (o, expansion, result);
+ free (expansion);
+ break;
+ }
+
+ return o;
+}
+
+/*
+ $(and condition1[,condition2[,condition3[...]]])
+
+ A CONDITION is false iff it evaluates to an empty string. White
+ space before and after CONDITION are stripped before evaluation.
+
+ CONDITION1 is evaluated. If it's false, then this is the result of
+ expansion. If it's true, CONDITION2 is evaluated, and so on. If all of
+ the conditions are true, the expansion is the result of the last condition.
+
+ Once a CONDITION is false no further conditions are evaluated
+ (short-circuiting).
+*/
+
+static char *
+func_and (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *expansion;
+
+ while (1)
+ {
+ const char *begp = *argv;
+ const char *endp = begp + strlen (*argv) - 1;
+ int result;
+
+ /* An empty condition is always false. */
+ strip_whitespace (&begp, &endp);
+ if (begp > endp)
+ return o;
+
+ expansion = expand_argument (begp, endp+1);
+ result = strlen (expansion);
+
+ /* If the result is false, stop here: we're done. */
+ if (!result)
+ break;
+
+ /* Otherwise the result is true. If this is the last one, keep this
+ result and quit. Otherwise go on to the next one! */
+
+ if (*(++argv))
+ free (expansion);
+ else
+ {
+ o = variable_buffer_output (o, expansion, result);
+ break;
+ }
+ }
+
+ free (expansion);
+
+ return o;
+}
+
+static char *
+func_wildcard (char *o, char **argv, const char *funcname UNUSED)
+{
+#ifdef _AMIGA
+ o = wildcard_expansion (argv[0], o);
+#else
+ char *p = string_glob (argv[0]);
+ o = variable_buffer_output (o, p, strlen (p));
+#endif
+ return o;
+}
+
+/*
+ $(eval <makefile string>)
+
+ Always resolves to the empty string.
+
+ Treat the arguments as a segment of makefile, and parse them.
+*/
+
+static char *
+func_eval (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *buf;
+ unsigned int len;
+
+ /* Eval the buffer. Pop the current variable buffer setting so that the
+ eval'd code can use its own without conflicting. */
+
+ install_variable_buffer (&buf, &len);
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ eval_buffer (argv[0], NULL);
+#else
+ eval_buffer (argv[0], NULL, strchr (argv[0], '\0'));
+#endif
+
+ restore_variable_buffer (buf, len);
+
+ return o;
+}
+
+
+#ifdef CONFIG_WITH_EVALPLUS
+/* Same as func_eval except that we push and pop the local variable
+ context before evaluating the buffer. */
+static char *
+func_evalctx (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *buf;
+ unsigned int len;
+
+ /* Eval the buffer. Pop the current variable buffer setting so that the
+ eval'd code can use its own without conflicting. */
+
+ install_variable_buffer (&buf, &len);
+
+ push_new_variable_scope ();
+
+ eval_buffer (argv[0], NULL, strchr (argv[0], '\0'));
+
+ pop_variable_scope ();
+
+ restore_variable_buffer (buf, len);
+
+ return o;
+}
+
+/* A mix of func_eval and func_value, saves memory for the expansion.
+ This implements both evalval and evalvalctx, the latter has its own
+ variable context just like evalctx. */
+static char *
+func_evalval (char *o, char **argv, const char *funcname)
+{
+ /* Look up the variable. */
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+ if (v)
+ {
+ char *buf;
+ unsigned int len;
+ int var_ctx;
+ size_t off;
+ const floc *reading_file_saved = reading_file;
+# ifdef CONFIG_WITH_MAKE_STATS
+ unsigned long long uStartTick = CURRENT_CLOCK_TICK();
+# ifndef CONFIG_WITH_COMPILER
+ MAKE_STATS_2(v->evalval_count++);
+# endif
+# endif
+
+ var_ctx = !strcmp (funcname, "evalvalctx");
+ if (var_ctx)
+ push_new_variable_scope ();
+ if (v->fileinfo.filenm)
+ reading_file = &v->fileinfo;
+
+# ifdef CONFIG_WITH_COMPILER
+ /* If this variable has been evaluated more than a few times, it make
+ sense to compile it to speed up the processing. */
+
+ v->evalval_count++;
+ if ( v->evalprog
+ || (v->evalval_count == 3 && kmk_cc_compile_variable_for_eval (v)))
+ {
+ install_variable_buffer (&buf, &len); /* Really necessary? */
+ kmk_exec_eval_variable (v);
+ restore_variable_buffer (buf, len);
+ }
+ else
+# endif
+ {
+ /* Make a copy of the value to the variable buffer first since
+ eval_buffer will make changes to its input. */
+
+ off = o - variable_buffer;
+ variable_buffer_output (o, v->value, v->value_length + 1);
+ o = variable_buffer + off;
+ assert (!o[v->value_length]);
+
+ install_variable_buffer (&buf, &len); /* Really necessary? */
+ eval_buffer (o, NULL, o + v->value_length);
+ restore_variable_buffer (buf, len);
+ }
+
+ reading_file = reading_file_saved;
+ if (var_ctx)
+ pop_variable_scope ();
+
+ MAKE_STATS_2(v->cTicksEvalVal += CURRENT_CLOCK_TICK() - uStartTick);
+ }
+
+ return o;
+}
+
+/* Optimizes the content of one or more variables to save time in
+ the eval functions. This function will collapse line continuations
+ and remove comments. */
+static char *
+func_eval_optimize_variable (char *o, char **argv, const char *funcname)
+{
+ unsigned int i;
+
+ for (i = 0; argv[i]; i++)
+ {
+ struct variable *v = lookup_variable (argv[i], strlen (argv[i]));
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v && v->origin != o_automatic && !v->rdonly_val)
+# else
+ if (v && v->origin != o_automatic)
+# endif
+ {
+ char *eos, *src;
+
+ eos = collapse_continuations (v->value, v->value_length);
+ v->value_length = eos - v->value;
+
+ /* remove comments */
+
+ src = memchr (v->value, '#', v->value_length);
+ if (src)
+ {
+ unsigned char ch = '\0';
+ char *dst = src;
+ do
+ {
+ /* drop blanks preceeding the comment */
+ while (dst > v->value)
+ {
+ ch = (unsigned char)dst[-1];
+ if (!ISBLANK (ch))
+ break;
+ dst--;
+ }
+
+ /* advance SRC to eol / eos. */
+ src = memchr (src, '\n', eos - src);
+ if (!src)
+ break;
+
+ /* drop a preceeding newline if possible (full line comment) */
+ if (dst > v->value && dst[-1] == '\n')
+ dst--;
+
+ /* copy till next comment or eol. */
+ while (src < eos)
+ {
+ ch = *src++;
+ if (ch == '#')
+ break;
+ *dst++ = ch;
+ }
+ }
+ while (ch == '#' && src < eos);
+
+ *dst = '\0';
+ v->value_length = dst - v->value;
+ }
+
+ VARIABLE_CHANGED (v);
+
+# ifdef CONFIG_WITH_COMPILER
+ /* Compile the variable for evalval, evalctx and expansion. */
+
+ if ( v->recursive
+ && !IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v))
+ kmk_cc_compile_variable_for_expand (v);
+ kmk_cc_compile_variable_for_eval (v);
+# endif
+ }
+ else if (v)
+ OSS (error, NILF, _("$(%s ): variable `%s' is of the wrong type\n"), funcname, v->name);
+ }
+
+ return o;
+}
+
+#endif /* CONFIG_WITH_EVALPLUS */
+
+static char *
+func_value (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* Look up the variable. */
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+
+ /* Copy its value into the output buffer without expanding it. */
+ if (v)
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ {
+ assert (v->value_length == strlen (v->value));
+ o = variable_buffer_output (o, v->value, v->value_length);
+ }
+#else
+ o = variable_buffer_output (o, v->value, strlen (v->value));
+#endif
+
+ return o;
+}
+
+/*
+ \r is replaced on UNIX as well. Is this desirable?
+ */
+static void
+fold_newlines (char *buffer, unsigned int *length, int trim_newlines)
+{
+ char *dst = buffer;
+ char *src = buffer;
+ char *last_nonnl = buffer - 1;
+ src[*length] = 0;
+ for (; *src != '\0'; ++src)
+ {
+ if (src[0] == '\r' && src[1] == '\n')
+ continue;
+ if (*src == '\n')
+ {
+ *dst++ = ' ';
+ }
+ else
+ {
+ last_nonnl = dst;
+ *dst++ = *src;
+ }
+ }
+
+ if (!trim_newlines && (last_nonnl < (dst - 2)))
+ last_nonnl = dst - 2;
+
+ *(++last_nonnl) = '\0';
+ *length = last_nonnl - buffer;
+}
+
+pid_t shell_function_pid = 0;
+static int shell_function_completed;
+
+void
+shell_completed (int exit_code, int exit_sig)
+{
+ char buf[256];
+
+ shell_function_pid = 0;
+ if (exit_sig == 0 && exit_code == 127)
+ shell_function_completed = -1;
+ else
+ shell_function_completed = 1;
+
+ sprintf (buf, "%d", exit_code);
+ define_variable_cname (".SHELLSTATUS", buf, o_override, 0);
+}
+
+#ifdef WINDOWS32
+/*untested*/
+
+# ifndef CONFIG_NEW_WIN_CHILDREN
+#include <windows.h>
+#include <io.h>
+#include "sub_proc.h"
+
+int
+windows32_openpipe (int *pipedes, int errfd, pid_t *pid_p, char **command_argv, char **envp)
+{
+ SECURITY_ATTRIBUTES saAttr;
+ HANDLE hIn = INVALID_HANDLE_VALUE;
+ HANDLE hErr = INVALID_HANDLE_VALUE;
+ HANDLE hChildOutRd;
+ HANDLE hChildOutWr;
+ HANDLE hProcess, tmpIn, tmpErr;
+ DWORD e;
+
+ /* Set status for return. */
+ pipedes[0] = pipedes[1] = -1;
+ *pid_p = (pid_t)-1;
+
+ saAttr.nLength = sizeof (SECURITY_ATTRIBUTES);
+ saAttr.bInheritHandle = TRUE;
+ saAttr.lpSecurityDescriptor = NULL;
+
+ /* Standard handles returned by GetStdHandle can be NULL or
+ INVALID_HANDLE_VALUE if the parent process closed them. If that
+ happens, we open the null device and pass its handle to
+ process_begin below as the corresponding handle to inherit. */
+ tmpIn = GetStdHandle (STD_INPUT_HANDLE);
+ if (DuplicateHandle (GetCurrentProcess (), tmpIn,
+ GetCurrentProcess (), &hIn,
+ 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE)
+ {
+ e = GetLastError ();
+ if (e == ERROR_INVALID_HANDLE)
+ {
+ tmpIn = CreateFile ("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (tmpIn != INVALID_HANDLE_VALUE
+ && DuplicateHandle (GetCurrentProcess (), tmpIn,
+ GetCurrentProcess (), &hIn,
+ 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE)
+ CloseHandle (tmpIn);
+ }
+ if (hIn == INVALID_HANDLE_VALUE)
+ {
+ ON (error, NILF,
+ _("windows32_openpipe: DuplicateHandle(In) failed (e=%ld)\n"), e);
+ return -1;
+ }
+ }
+ tmpErr = (HANDLE)_get_osfhandle (errfd);
+ if (DuplicateHandle (GetCurrentProcess (), tmpErr,
+ GetCurrentProcess (), &hErr,
+ 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE)
+ {
+ e = GetLastError ();
+ if (e == ERROR_INVALID_HANDLE)
+ {
+ tmpErr = CreateFile ("NUL", GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (tmpErr != INVALID_HANDLE_VALUE
+ && DuplicateHandle (GetCurrentProcess (), tmpErr,
+ GetCurrentProcess (), &hErr,
+ 0, TRUE, DUPLICATE_SAME_ACCESS) == FALSE)
+ CloseHandle (tmpErr);
+ }
+ if (hErr == INVALID_HANDLE_VALUE)
+ {
+ ON (error, NILF,
+ _("windows32_openpipe: DuplicateHandle(Err) failed (e=%ld)\n"), e);
+ return -1;
+ }
+ }
+
+ if (! CreatePipe (&hChildOutRd, &hChildOutWr, &saAttr, 0))
+ {
+ ON (error, NILF, _("CreatePipe() failed (e=%ld)\n"), GetLastError());
+ return -1;
+ }
+
+ hProcess = process_init_fd (hIn, hChildOutWr, hErr);
+
+ if (!hProcess)
+ {
+ O (error, NILF, _("windows32_openpipe(): process_init_fd() failed\n"));
+ return -1;
+ }
+
+ /* make sure that CreateProcess() has Path it needs */
+ sync_Path_environment ();
+ /* 'sync_Path_environment' may realloc 'environ', so take note of
+ the new value. */
+ envp = environ;
+
+ if (! process_begin (hProcess, command_argv, envp, command_argv[0], NULL))
+ {
+ /* register process for wait */
+ process_register (hProcess);
+
+ /* set the pid for returning to caller */
+ *pid_p = (pid_t) hProcess;
+
+ /* set up to read data from child */
+ pipedes[0] = _open_osfhandle ((intptr_t) hChildOutRd, O_RDONLY);
+
+ /* this will be closed almost right away */
+ pipedes[1] = _open_osfhandle ((intptr_t) hChildOutWr, O_APPEND);
+ return 0;
+ }
+ else
+ {
+ /* reap/cleanup the failed process */
+ process_cleanup (hProcess);
+
+ /* close handles which were duplicated, they weren't used */
+ if (hIn != INVALID_HANDLE_VALUE)
+ CloseHandle (hIn);
+ if (hErr != INVALID_HANDLE_VALUE)
+ CloseHandle (hErr);
+
+ /* close pipe handles, they won't be used */
+ CloseHandle (hChildOutRd);
+ CloseHandle (hChildOutWr);
+
+ return -1;
+ }
+}
+# endif /* !CONFIG_NEW_WIN_CHILDREN */
+#endif
+
+
+#ifdef __MSDOS__
+FILE *
+msdos_openpipe (int* pipedes, int *pidp, char *text)
+{
+ FILE *fpipe=0;
+ /* MSDOS can't fork, but it has 'popen'. */
+ struct variable *sh = lookup_variable ("SHELL", 5);
+ int e;
+ extern int dos_command_running, dos_status;
+
+ /* Make sure not to bother processing an empty line. */
+ NEXT_TOKEN (text);
+ if (*text == '\0')
+ return 0;
+
+ if (sh)
+ {
+ char buf[PATH_MAX + 7];
+ /* This makes sure $SHELL value is used by $(shell), even
+ though the target environment is not passed to it. */
+ sprintf (buf, "SHELL=%s", sh->value);
+ putenv (buf);
+ }
+
+ e = errno;
+ errno = 0;
+ dos_command_running = 1;
+ dos_status = 0;
+ /* If dos_status becomes non-zero, it means the child process
+ was interrupted by a signal, like SIGINT or SIGQUIT. See
+ fatal_error_signal in commands.c. */
+ fpipe = popen (text, "rt");
+ dos_command_running = 0;
+ if (!fpipe || dos_status)
+ {
+ pipedes[0] = -1;
+ *pidp = -1;
+ if (dos_status)
+ errno = EINTR;
+ else if (errno == 0)
+ errno = ENOMEM;
+ if (fpipe)
+ pclose (fpipe);
+ shell_completed (127, 0);
+ }
+ else
+ {
+ pipedes[0] = fileno (fpipe);
+ *pidp = 42; /* Yes, the Meaning of Life, the Universe, and Everything! */
+ errno = e;
+ }
+ return fpipe;
+}
+#endif
+
+/*
+ Do shell spawning, with the naughty bits for different OSes.
+ */
+
+#ifdef VMS
+
+/* VMS can't do $(shell ...) */
+
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
+{
+ fprintf (stderr, "This platform does not support shell\n");
+ die (MAKE_TROUBLE);
+ return NULL;
+}
+
+#define func_shell 0
+
+#else
+#ifndef _AMIGA
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
+{
+ char *batch_filename = NULL;
+ int errfd;
+#ifdef __MSDOS__
+ FILE *fpipe;
+#endif
+ char **command_argv;
+ const char * volatile error_prefix; /* bird: this volatile ~~and the 'o' one~~, is for shutting up gcc warnings */
+ char **envp;
+ int pipedes[2];
+ pid_t pid;
+
+#ifndef __MSDOS__
+#ifdef WINDOWS32
+ /* Reset just_print_flag. This is needed on Windows when batch files
+ are used to run the commands, because we normally refrain from
+ creating batch files under -n. */
+ int j_p_f = just_print_flag;
+ just_print_flag = 0;
+#endif
+
+ /* Construct the argument list. */
+ command_argv = construct_command_argv (argv[0], NULL, NULL, 0,
+ &batch_filename);
+ if (command_argv == 0)
+ {
+#ifdef WINDOWS32
+ just_print_flag = j_p_f;
+#endif
+ return o;
+ }
+#endif /* !__MSDOS__ */
+
+ /* Using a target environment for 'shell' loses in cases like:
+ export var = $(shell echo foobie)
+ bad := $(var)
+ because target_environment hits a loop trying to expand $(var) to put it
+ in the environment. This is even more confusing when 'var' was not
+ explicitly exported, but just appeared in the calling environment.
+
+ See Savannah bug #10593.
+
+ envp = target_environment (NULL);
+ */
+
+ envp = environ;
+
+ /* For error messages. */
+ if (reading_file && reading_file->filenm)
+ {
+ char *p = alloca (strlen (reading_file->filenm)+11+4);
+ sprintf (p, "%s:%lu: ", reading_file->filenm,
+ reading_file->lineno + reading_file->offset);
+ error_prefix = p;
+ }
+ else
+ error_prefix = "";
+
+ /* Set up the output in case the shell writes something. */
+ output_start ();
+
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ errfd = -1; /** @todo fixme */
+#else
+ errfd = (output_context && output_context->err >= 0
+ ? output_context->err : FD_STDERR);
+#endif
+
+#if defined(__MSDOS__)
+ fpipe = msdos_openpipe (pipedes, &pid, argv[0]);
+ if (pipedes[0] < 0)
+ {
+ perror_with_name (error_prefix, "pipe");
+ return o;
+ }
+
+#elif defined(WINDOWS32)
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ pipedes[1] = -1;
+ MkWinChildCreateWithStdOutPipe (command_argv, envp, errfd, &pid, &pipedes[0]);
+# else
+ windows32_openpipe (pipedes, errfd, &pid, command_argv, envp);
+# endif
+ /* Restore the value of just_print_flag. */
+ just_print_flag = j_p_f;
+
+ if (pipedes[0] < 0)
+ {
+ /* Open of the pipe failed, mark as failed execution. */
+ shell_completed (127, 0);
+ perror_with_name (error_prefix, "pipe");
+ return o;
+ }
+
+#else
+ if (pipe (pipedes) < 0)
+ {
+ perror_with_name (error_prefix, "pipe");
+ return o;
+ }
+
+ /* Close handles that are unnecessary for the child process. */
+ CLOSE_ON_EXEC(pipedes[1]);
+ CLOSE_ON_EXEC(pipedes[0]);
+
+ {
+ struct output out;
+ out.syncout = 1;
+ out.out = pipedes[1];
+ out.err = errfd;
+
+ pid = child_execute_job (&out, 1, command_argv, envp);
+ }
+
+ if (pid < 0)
+ {
+ perror_with_name (error_prefix, "fork");
+ return o;
+ }
+#endif
+
+ {
+ char *buffer;
+ unsigned int maxlen, i;
+ int cc;
+
+ /* Record the PID for reap_children. */
+ shell_function_pid = pid;
+#ifndef __MSDOS__
+ shell_function_completed = 0;
+
+ /* Free the storage only the child needed. */
+ free (command_argv[0]);
+ free (command_argv);
+
+ /* Close the write side of the pipe. We test for -1, since
+ pipedes[1] is -1 on MS-Windows, and some versions of MS
+ libraries barf when 'close' is called with -1. */
+ if (pipedes[1] >= 0)
+ close (pipedes[1]);
+#endif
+
+ /* Set up and read from the pipe. */
+
+ maxlen = 200;
+ buffer = xmalloc (maxlen + 1);
+
+ /* Read from the pipe until it gets EOF. */
+ for (i = 0; ; i += cc)
+ {
+ if (i == maxlen)
+ {
+ maxlen += 512;
+ buffer = xrealloc (buffer, maxlen + 1);
+ }
+
+ EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i));
+ if (cc <= 0)
+ break;
+ }
+ buffer[i] = '\0';
+
+ /* Close the read side of the pipe. */
+#ifdef __MSDOS__
+ if (fpipe)
+ {
+ int st = pclose (fpipe);
+ shell_completed (st, 0);
+ }
+#else
+# ifdef _MSC_VER /* Avoid annoying msvcrt when debugging. (bird) */
+ if (pipedes[0] != -1)
+# endif
+ (void) close (pipedes[0]);
+#endif
+
+ /* Loop until child_handler or reap_children() sets
+ shell_function_completed to the status of our child shell. */
+ while (shell_function_completed == 0)
+ reap_children (1, 0);
+
+ if (batch_filename)
+ {
+ DB (DB_VERBOSE, (_("Cleaning up temporary batch file %s\n"),
+ batch_filename));
+ remove (batch_filename);
+ free (batch_filename);
+ }
+ shell_function_pid = 0;
+
+ /* shell_completed() will set shell_function_completed to 1 when the
+ child dies normally, or to -1 if it dies with status 127, which is
+ most likely an exec fail. */
+
+ if (shell_function_completed == -1)
+ {
+ /* This likely means that the execvp failed, so we should just
+ write the error message in the pipe from the child. */
+ fputs (buffer, stderr);
+ fflush (stderr);
+ }
+ else
+ {
+ /* The child finished normally. Replace all newlines in its output
+ with spaces, and put that in the variable output buffer. */
+ fold_newlines (buffer, &i, trim_newlines);
+ o = variable_buffer_output (o, buffer, i);
+ }
+
+ free (buffer);
+ }
+
+ return o;
+}
+
+#else /* _AMIGA */
+
+/* Do the Amiga version of func_shell. */
+
+char *
+func_shell_base (char *o, char **argv, int trim_newlines)
+{
+ /* Amiga can't fork nor spawn, but I can start a program with
+ redirection of my choice. However, this means that we
+ don't have an opportunity to reopen stdout to trap it. Thus,
+ we save our own stdout onto a new descriptor and dup a temp
+ file's descriptor onto our stdout temporarily. After we
+ spawn the shell program, we dup our own stdout back to the
+ stdout descriptor. The buffer reading is the same as above,
+ except that we're now reading from a file. */
+
+#include <dos/dos.h>
+#include <proto/dos.h>
+
+ BPTR child_stdout;
+ char tmp_output[FILENAME_MAX];
+ unsigned int maxlen = 200, i;
+ int cc;
+ char * buffer, * ptr;
+ char ** aptr;
+ int len = 0;
+ char* batch_filename = NULL;
+
+ /* Construct the argument list. */
+ command_argv = construct_command_argv (argv[0], NULL, NULL, 0,
+ &batch_filename);
+ if (command_argv == 0)
+ return o;
+
+ /* Note the mktemp() is a security hole, but this only runs on Amiga.
+ Ideally we would use output_tmpfile(), but this uses a special
+ Open(), not fopen(), and I'm not familiar enough with the code to mess
+ with it. */
+ strcpy (tmp_output, "t:MakeshXXXXXXXX");
+ mktemp (tmp_output);
+ child_stdout = Open (tmp_output, MODE_NEWFILE);
+
+ for (aptr=command_argv; *aptr; aptr++)
+ len += strlen (*aptr) + 1;
+
+ buffer = xmalloc (len + 1);
+ ptr = buffer;
+
+ for (aptr=command_argv; *aptr; aptr++)
+ {
+ strcpy (ptr, *aptr);
+ ptr += strlen (ptr) + 1;
+ *ptr ++ = ' ';
+ *ptr = 0;
+ }
+
+ ptr[-1] = '\n';
+
+ Execute (buffer, NULL, child_stdout);
+ free (buffer);
+
+ Close (child_stdout);
+
+ child_stdout = Open (tmp_output, MODE_OLDFILE);
+
+ buffer = xmalloc (maxlen);
+ i = 0;
+ do
+ {
+ if (i == maxlen)
+ {
+ maxlen += 512;
+ buffer = xrealloc (buffer, maxlen + 1);
+ }
+
+ cc = Read (child_stdout, &buffer[i], maxlen - i);
+ if (cc > 0)
+ i += cc;
+ } while (cc > 0);
+
+ Close (child_stdout);
+
+ fold_newlines (buffer, &i, trim_newlines);
+ o = variable_buffer_output (o, buffer, i);
+ free (buffer);
+ return o;
+}
+#endif /* _AMIGA */
+
+static char *
+func_shell (char *o, char **argv, const char *funcname UNUSED)
+{
+ return func_shell_base (o, argv, 1);
+}
+#endif /* !VMS */
+
+#ifdef EXPERIMENTAL
+
+/*
+ equality. Return is string-boolean, i.e., the empty string is false.
+ */
+static char *
+func_eq (char *o, char **argv, const char *funcname UNUSED)
+{
+ int result = ! strcmp (argv[0], argv[1]);
+ o = variable_buffer_output (o, result ? "1" : "", result);
+ return o;
+}
+
+
+/*
+ string-boolean not operator.
+ */
+static char *
+func_not (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *s = argv[0];
+ int result = 0;
+ NEXT_TOKEN (s);
+ result = ! (*s);
+ o = variable_buffer_output (o, result ? "1" : "", result);
+ return o;
+}
+#endif
+
+
+#ifdef HAVE_DOS_PATHS
+# ifdef __CYGWIN__
+# define IS_ABSOLUTE(n) ((n[0] && n[1] == ':') || STOP_SET (n[0], MAP_DIRSEP))
+# else
+# define IS_ABSOLUTE(n) (n[0] && n[1] == ':')
+# endif
+# define ROOT_LEN 3
+#else
+#define IS_ABSOLUTE(n) (n[0] == '/')
+#define ROOT_LEN 1
+#endif
+
+/* Return the absolute name of file NAME which does not contain any '.',
+ '..' components nor any repeated path separators ('/'). */
+#ifdef KMK
+char *
+#else
+static char *
+#endif
+abspath (const char *name, char *apath)
+{
+ char *dest;
+ const char *start, *end, *apath_limit;
+ unsigned long root_len = ROOT_LEN;
+
+ if (name[0] == '\0' || apath == NULL)
+ return NULL;
+
+#ifdef WINDOWS32 /* bird */
+ dest = unix_slashes_resolved (name, apath, GET_PATH_MAX);
+ if (!dest)
+ return NULL;
+ dest = strchr(apath, '\0');
+
+ (void)end; (void)start; (void)apath_limit;
+
+#elif defined __OS2__ /* bird */
+ if (_abspath(apath, name, GET_PATH_MAX))
+ return NULL;
+ dest = strchr(apath, '\0');
+
+ (void)end; (void)start; (void)apath_limit; (void)dest;
+
+#else /* !WINDOWS32 && !__OS2__ */
+ apath_limit = apath + GET_PATH_MAX;
+
+ if (!IS_ABSOLUTE(name))
+ {
+ /* It is unlikely we would make it until here but just to make sure. */
+ if (!starting_directory)
+ return NULL;
+
+ strcpy (apath, starting_directory);
+
+#ifdef HAVE_DOS_PATHS
+ if (STOP_SET (name[0], MAP_DIRSEP))
+ {
+ if (STOP_SET (name[1], MAP_DIRSEP))
+ {
+ /* A UNC. Don't prepend a drive letter. */
+ apath[0] = name[0];
+ apath[1] = name[1];
+ root_len = 2;
+ }
+ /* We have /foo, an absolute file name except for the drive
+ letter. Assume the missing drive letter is the current
+ drive, which we can get if we remove from starting_directory
+ everything past the root directory. */
+ apath[root_len] = '\0';
+ }
+#endif
+
+ dest = strchr (apath, '\0');
+ }
+ else
+ {
+#if defined(__CYGWIN__) && defined(HAVE_DOS_PATHS)
+ if (STOP_SET (name[0], MAP_DIRSEP))
+ root_len = 1;
+#endif
+#ifdef KMK
+ memcpy (apath, name, root_len);
+ apath[root_len] = '\0';
+ assert (strlen (apath) == root_len);
+#else
+ strncpy (apath, name, root_len);
+ apath[root_len] = '\0';
+#endif
+ dest = apath + root_len;
+ /* Get past the root, since we already copied it. */
+ name += root_len;
+#ifdef HAVE_DOS_PATHS
+ if (! STOP_SET (apath[root_len - 1], MAP_DIRSEP))
+ {
+ /* Convert d:foo into d:./foo and increase root_len. */
+ apath[2] = '.';
+ apath[3] = '/';
+ dest++;
+ root_len++;
+ /* strncpy above copied one character too many. */
+ name--;
+ }
+ else
+ apath[root_len - 1] = '/'; /* make sure it's a forward slash */
+#endif
+ }
+
+ for (start = end = name; *start != '\0'; start = end)
+ {
+ unsigned long len;
+
+ /* Skip sequence of multiple path-separators. */
+ while (STOP_SET (*start, MAP_DIRSEP))
+ ++start;
+
+ /* Find end of path component. */
+ for (end = start; ! STOP_SET (*end, MAP_DIRSEP|MAP_NUL); ++end)
+ ;
+
+ len = end - start;
+
+ if (len == 0)
+ break;
+ else if (len == 1 && start[0] == '.')
+ /* nothing */;
+ else if (len == 2 && start[0] == '.' && start[1] == '.')
+ {
+ /* Back up to previous component, ignore if at root already. */
+ if (dest > apath + root_len)
+ for (--dest; ! STOP_SET (dest[-1], MAP_DIRSEP); --dest)
+ ;
+ }
+ else
+ {
+ if (! STOP_SET (dest[-1], MAP_DIRSEP))
+ *dest++ = '/';
+
+ if (dest + len >= apath_limit)
+ return NULL;
+
+ dest = memcpy (dest, start, len);
+ dest += len;
+ *dest = '\0';
+ }
+ }
+#endif /* !WINDOWS32 && !__OS2__ */
+
+ /* Unless it is root strip trailing separator. */
+ if (dest > apath + root_len && STOP_SET (dest[-1], MAP_DIRSEP))
+ --dest;
+
+ *dest = '\0';
+
+ return apath;
+}
+
+
+static char *
+func_realpath (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* Expand the argument. */
+ const char *p = argv[0];
+ const char *path = 0;
+ int doneany = 0;
+ unsigned int len = 0;
+
+ while ((path = find_next_token (&p, &len)) != 0)
+ {
+ if (len < GET_PATH_MAX)
+ {
+ char *rp;
+ struct stat st;
+ PATH_VAR (in);
+ PATH_VAR (out);
+
+ strncpy (in, path, len);
+ in[len] = '\0';
+
+#ifdef HAVE_REALPATH
+ ENULLLOOP (rp, realpath (in, out));
+#else
+ rp = abspath (in, out);
+#endif
+
+ if (rp)
+ {
+ int r;
+ EINTRLOOP (r, stat (out, &st));
+ if (r == 0)
+ {
+ o = variable_buffer_output (o, out, strlen (out));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+ }
+ }
+
+ /* Kill last space. */
+ if (doneany)
+ --o;
+
+ return o;
+}
+
+static char *
+func_file (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *fn = argv[0];
+
+ if (fn[0] == '>')
+ {
+ FILE *fp;
+#ifdef KMK_FOPEN_NO_INHERIT_MODE
+ const char *mode = "w" KMK_FOPEN_NO_INHERIT_MODE;
+#else
+ const char *mode = "w";
+#endif
+
+ /* We are writing a file. */
+ ++fn;
+ if (fn[0] == '>')
+ {
+#ifdef KMK_FOPEN_NO_INHERIT_MODE
+ mode = "a" KMK_FOPEN_NO_INHERIT_MODE;
+#else
+ mode = "a";
+#endif
+ ++fn;
+ }
+ NEXT_TOKEN (fn);
+
+ if (fn[0] == '\0')
+ O (fatal, *expanding_var, _("file: missing filename"));
+
+ ENULLLOOP (fp, fopen (fn, mode));
+ if (fp == NULL)
+ OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno));
+
+ if (argv[1])
+ {
+ int l = strlen (argv[1]);
+ int nl = l == 0 || argv[1][l-1] != '\n';
+
+ if (fputs (argv[1], fp) == EOF || (nl && fputc ('\n', fp) == EOF))
+ OSS (fatal, reading_file, _("write: %s: %s"), fn, strerror (errno));
+ }
+ if (fclose (fp))
+ OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno));
+ }
+ else if (fn[0] == '<')
+ {
+ char *preo = o;
+ FILE *fp;
+
+ ++fn;
+ NEXT_TOKEN (fn);
+ if (fn[0] == '\0')
+ O (fatal, *expanding_var, _("file: missing filename"));
+
+ if (argv[1])
+ O (fatal, *expanding_var, _("file: too many arguments"));
+
+#ifdef KMK_FOPEN_NO_INHERIT_MODE
+ ENULLLOOP (fp, fopen (fn, "r" KMK_FOPEN_NO_INHERIT_MODE));
+#else
+ ENULLLOOP (fp, fopen (fn, "r"));
+#endif
+ if (fp == NULL)
+ {
+ if (errno == ENOENT)
+ return o;
+ OSS (fatal, reading_file, _("open: %s: %s"), fn, strerror (errno));
+ }
+
+ while (1)
+ {
+ char buf[1024];
+ size_t l = fread (buf, 1, sizeof (buf), fp);
+ if (l > 0)
+ o = variable_buffer_output (o, buf, l);
+
+ if (ferror (fp))
+ if (errno != EINTR)
+ OSS (fatal, reading_file, _("read: %s: %s"), fn, strerror (errno));
+ if (feof (fp))
+ break;
+ }
+ if (fclose (fp))
+ OSS (fatal, reading_file, _("close: %s: %s"), fn, strerror (errno));
+
+ /* Remove trailing newline. */
+ if (o > preo && o[-1] == '\n')
+ if (--o > preo && o[-1] == '\r')
+ --o;
+ }
+ else
+ OS (fatal, *expanding_var, _("file: invalid file operation: %s"), fn);
+
+ return o;
+}
+
+static char *
+func_abspath (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* Expand the argument. */
+ const char *p = argv[0];
+ const char *path = 0;
+ int doneany = 0;
+ unsigned int len = 0;
+
+ while ((path = find_next_token (&p, &len)) != 0)
+ {
+ if (len < GET_PATH_MAX)
+ {
+ PATH_VAR (in);
+ PATH_VAR (out);
+
+ strncpy (in, path, len);
+ in[len] = '\0';
+
+ if (abspath (in, out))
+ {
+ o = variable_buffer_output (o, out, strlen (out));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+ }
+
+ /* Kill last space. */
+ if (doneany)
+ --o;
+
+ return o;
+}
+
+#ifdef CONFIG_WITH_ABSPATHEX
+/* Same as abspath except that the current path may be given as the
+ 2nd argument. */
+static char *
+func_abspathex (char *o, char **argv, const char *funcname UNUSED)
+{
+ char *cwd = argv[1];
+
+ /* cwd needs leading spaces chopped and may be optional,
+ in which case we're exactly like $(abspath ). */
+ if (cwd)
+ while (ISBLANK (*cwd))
+ cwd++;
+ if (!cwd || !*cwd)
+ o = func_abspath (o, argv, funcname);
+ else
+ {
+ /* Expand the argument. */
+ const char *p = argv[0];
+ unsigned int cwd_len = ~0U;
+ char *path = 0;
+ int doneany = 0;
+ unsigned int len = 0;
+ PATH_VAR (in);
+ PATH_VAR (out);
+
+ while ((path = find_next_token (&p, &len)) != 0)
+ {
+ if (len < GET_PATH_MAX)
+ {
+#ifdef HAVE_DOS_PATHS
+ if (path[0] != '/' && path[0] != '\\' && (len < 2 || path[1] != ':') && cwd)
+#else
+ if (path[0] != '/' && cwd)
+#endif
+ {
+ /* relative path, prefix with cwd. */
+ if (cwd_len == ~0U)
+ cwd_len = strlen (cwd);
+ if (cwd_len + len + 1 >= GET_PATH_MAX)
+ continue;
+ memcpy (in, cwd, cwd_len);
+ in[cwd_len] = '/';
+ memcpy (in + cwd_len + 1, path, len);
+ in[cwd_len + len + 1] = '\0';
+ }
+ else
+ {
+ /* absolute path pass it as-is. */
+ memcpy (in, path, len);
+ in[len] = '\0';
+ }
+
+ if (abspath (in, out))
+ {
+ o = variable_buffer_output (o, out, strlen (out));
+ o = variable_buffer_output (o, " ", 1);
+ doneany = 1;
+ }
+ }
+ }
+
+ /* Kill last space. */
+ if (doneany)
+ --o;
+ }
+
+ return o;
+}
+#endif
+
+#ifdef CONFIG_WITH_XARGS
+/* Create one or more command lines avoiding the max argument
+ length restriction of the host OS.
+
+ The last argument is the list of arguments that the normal
+ xargs command would be fed from stdin.
+
+ The first argument is initial command and it's arguments.
+
+ If there are three or more arguments, the 2nd argument is
+ the command and arguments to be used on subsequent
+ command lines. Defaults to the initial command.
+
+ If there are four or more arguments, the 3rd argument is
+ the command to be used at the final command line. Defaults
+ to the sub sequent or initial command .
+
+ A future version of this function may define more arguments
+ and therefor anyone specifying six or more arguments will
+ cause fatal errors.
+
+ Typical usage is:
+ $(xargs ar cas mylib.a,$(objects))
+ or
+ $(xargs ar cas mylib.a,ar as mylib.a,$(objects))
+
+ It will then create one or more "ar mylib.a ..." command
+ lines with proper \n\t separation so it can be used when
+ writing rules. */
+static char *
+func_xargs (char *o, char **argv, const char *funcname UNUSED)
+{
+ int argc;
+ const char *initial_cmd;
+ size_t initial_cmd_len;
+ const char *subsequent_cmd;
+ size_t subsequent_cmd_len;
+ const char *final_cmd;
+ size_t final_cmd_len;
+ const char *args;
+ size_t max_args;
+ int i;
+
+#ifdef ARG_MAX
+ /* ARG_MAX is a bit unreliable (environment), so drop 25% of the max. */
+# define XARGS_MAX (ARG_MAX - (ARG_MAX / 4))
+#else /* FIXME: update configure with a command line length test. */
+# define XARGS_MAX 10240
+#endif
+
+ argc = 0;
+ while (argv[argc])
+ argc++;
+ if (argc > 4)
+ O (fatal, NILF, _("Too many arguments for $(xargs)!\n"));
+
+ /* first: the initial / default command.*/
+ initial_cmd = argv[0];
+ while (ISSPACE (*initial_cmd))
+ initial_cmd++;
+ max_args = initial_cmd_len = strlen (initial_cmd);
+
+ /* second: the command for the subsequent command lines. defaults to the initial cmd. */
+ subsequent_cmd = argc > 2 && argv[1][0] != '\0' ? argv[1] : "\0";
+ while (ISSPACE (*subsequent_cmd))
+ subsequent_cmd++; /* gcc 7.3.0 complains "offset ‘1’ outside bounds of constant string" if constant is "" rather than "\0". */
+ if (*subsequent_cmd)
+ {
+ subsequent_cmd_len = strlen (subsequent_cmd);
+ if (subsequent_cmd_len > max_args)
+ max_args = subsequent_cmd_len;
+ }
+ else
+ {
+ subsequent_cmd = initial_cmd;
+ subsequent_cmd_len = initial_cmd_len;
+ }
+
+ /* third: the final command. defaults to the subseq cmd. */
+ final_cmd = argc > 3 && argv[2][0] != '\0' ? argv[2] : "\0";
+ while (ISSPACE (*final_cmd))
+ final_cmd++; /* gcc 7.3.0: same complaint as for subsequent_cmd++ */
+ if (*final_cmd)
+ {
+ final_cmd_len = strlen (final_cmd);
+ if (final_cmd_len > max_args)
+ max_args = final_cmd_len;
+ }
+ else
+ {
+ final_cmd = subsequent_cmd;
+ final_cmd_len = subsequent_cmd_len;
+ }
+
+ /* last: the arguments to split up into sensible portions. */
+ args = argv[argc - 1];
+
+ /* calc the max argument length. */
+ if (XARGS_MAX <= max_args + 2)
+ ONN (fatal, NILF, _("$(xargs): the commands are longer than the max exec argument length. (%lu <= %lu)\n"),
+ (unsigned long)XARGS_MAX, (unsigned long)max_args + 2);
+ max_args = XARGS_MAX - max_args - 1;
+
+ /* generate the commands. */
+ i = 0;
+ for (i = 0; ; i++)
+ {
+ unsigned int len;
+ const char *iterator = args;
+ const char *end = args;
+ const char *cur;
+ const char *tmp;
+
+ /* scan the arguments till we reach the end or the max length. */
+ while ((cur = find_next_token(&iterator, &len))
+ && (size_t)((cur + len) - args) < max_args)
+ end = cur + len;
+ if (cur && end == args)
+ O (fatal, NILF, _("$(xargs): command + one single arg is too much. giving up.\n"));
+
+ /* emit the command. */
+ if (i == 0)
+ {
+ o = variable_buffer_output (o, (char *)initial_cmd, initial_cmd_len);
+ o = variable_buffer_output (o, " ", 1);
+ }
+ else if (cur)
+ {
+ o = variable_buffer_output (o, "\n\t", 2);
+ o = variable_buffer_output (o, (char *)subsequent_cmd, subsequent_cmd_len);
+ o = variable_buffer_output (o, " ", 1);
+ }
+ else
+ {
+ o = variable_buffer_output (o, "\n\t", 2);
+ o = variable_buffer_output (o, (char *)final_cmd, final_cmd_len);
+ o = variable_buffer_output (o, " ", 1);
+ }
+
+ tmp = end;
+ while (tmp > args && ISSPACE (tmp[-1])) /* drop trailing spaces. */
+ tmp--;
+ o = variable_buffer_output (o, (char *)args, tmp - args);
+
+
+ /* next */
+ if (!cur)
+ break;
+ args = end;
+ while (ISSPACE (*args))
+ args++;
+ }
+
+ return o;
+}
+#endif
+
+#ifdef CONFIG_WITH_STRING_FUNCTIONS
+/*
+ $(length string)
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_length (char *o, char **argv, const char *funcname UNUSED)
+{
+ size_t len = strlen (argv[0]);
+ return math_int_to_variable_buffer (o, len);
+}
+
+/*
+ $(length-var var)
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_length_var (char *o, char **argv, const char *funcname UNUSED)
+{
+ struct variable *var = lookup_variable (argv[0], strlen (argv[0]));
+ return math_int_to_variable_buffer (o, var ? var->value_length : 0);
+}
+
+/* func_insert and func_substr helper. */
+static char *
+helper_pad (char *o, size_t to_add, const char *pad, size_t pad_len)
+{
+ while (to_add > 0)
+ {
+ size_t size = to_add > pad_len ? pad_len : to_add;
+ o = variable_buffer_output (o, pad, size);
+ to_add -= size;
+ }
+ return o;
+}
+
+/*
+ $(insert in, str[, n[, length[, pad]]])
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_insert (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *in = argv[0];
+ math_int in_len = (math_int)strlen (in);
+ const char *str = argv[1];
+ math_int str_len = (math_int)strlen (str);
+ math_int n = 0;
+ math_int length = str_len;
+ const char *pad = " ";
+ size_t pad_len = 16;
+ size_t i;
+
+ if (argv[2] != NULL)
+ {
+ n = math_int_from_string (argv[2]);
+ if (n > 0)
+ n--; /* one-origin */
+ else if (n == 0)
+ n = str_len; /* append */
+ else
+ { /* n < 0: from the end */
+ n = str_len + n;
+ if (n < 0)
+ n = 0;
+ }
+ if (n > 16*1024*1024) /* 16MB */
+ OS (fatal, NILF, _("$(insert ): n=%s is out of bounds\n"), argv[2]);
+
+ if (argv[3] != NULL)
+ {
+ length = math_int_from_string (argv[3]);
+ if (length < 0 || length > 16*1024*1024 /* 16MB */)
+ OS (fatal, NILF, _("$(insert ): length=%s is out of bounds\n"), argv[3]);
+
+ if (argv[4] != NULL)
+ {
+ const char *tmp = argv[4];
+ for (i = 0; tmp[i] == ' '; i++)
+ /* nothing */;
+ if (tmp[i] != '\0')
+ {
+ pad = argv[4];
+ pad_len = strlen (pad);
+ }
+ /* else: it was all default spaces. */
+ }
+ }
+ }
+
+ /* the head of the original string */
+ if (n > 0)
+ {
+ if (n <= str_len)
+ o = variable_buffer_output (o, str, n);
+ else
+ {
+ o = variable_buffer_output (o, str, str_len);
+ o = helper_pad (o, n - str_len, pad, pad_len);
+ }
+ }
+
+ /* insert the string */
+ if (length <= in_len)
+ o = variable_buffer_output (o, in, length);
+ else
+ {
+ o = variable_buffer_output (o, in, in_len);
+ o = helper_pad (o, length - in_len, pad, pad_len);
+ }
+
+ /* the tail of the original string */
+ if (n < str_len)
+ o = variable_buffer_output (o, str + n, str_len - n);
+
+ return o;
+}
+
+/*
+ $(pos needle, haystack[, start])
+ $(lastpos needle, haystack[, start])
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_pos (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *needle = *argv[0] ? argv[0] : " ";
+ size_t needle_len = strlen (needle);
+ const char *haystack = argv[1];
+ size_t haystack_len = strlen (haystack);
+ math_int start = 0;
+ const char *hit;
+
+ if (argv[2] != NULL)
+ {
+ start = math_int_from_string (argv[2]);
+ if (start > 0)
+ start--; /* one-origin */
+ else if (start < 0)
+ start = haystack_len + start; /* from the end */
+ if (start < 0 || start + needle_len > haystack_len)
+ return math_int_to_variable_buffer (o, 0);
+ }
+ else if (funcname[0] == 'l')
+ start = haystack_len - 1;
+
+ /* do the searching */
+ if (funcname[0] != 'l')
+ { /* pos */
+ if (needle_len == 1)
+ hit = strchr (haystack + start, *needle);
+ else
+ hit = strstr (haystack + start, needle);
+ }
+ else
+ { /* last pos */
+ int ch = *needle;
+ size_t off = start + 1;
+
+ hit = NULL;
+ while (off-- > 0)
+ {
+ if ( haystack[off] == ch
+ && ( needle_len == 1
+ || strncmp (&haystack[off], needle, needle_len) == 0))
+ {
+ hit = haystack + off;
+ break;
+ }
+ }
+ }
+
+ return math_int_to_variable_buffer (o, hit ? hit - haystack + 1 : 0);
+}
+
+/*
+ $(substr str, start[, length[, pad]])
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_substr (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *str = argv[0];
+ math_int str_len = (math_int)strlen (str);
+ math_int start = math_int_from_string (argv[1]);
+ math_int length = 0;
+ const char *pad = NULL;
+ size_t pad_len = 0;
+
+ if (argv[2] != NULL)
+ {
+ if (argv[3] != NULL)
+ {
+ pad = argv[3];
+ for (pad_len = 0; pad[pad_len] == ' '; pad_len++)
+ /* nothing */;
+ if (pad[pad_len] != '\0')
+ pad_len = strlen (pad);
+ else
+ {
+ pad = " ";
+ pad_len = 16;
+ }
+ }
+ length = math_int_from_string (argv[2]);
+ if (pad != NULL && length > 16*1024*1024 /* 16MB */)
+ OS (fatal, NILF, _("$(substr ): length=%s is out of bounds\n"), argv[2]);
+ if (pad != NULL && length < 0)
+ OS (fatal, NILF, _("$(substr ): negative length (%s) and padding doesn't mix.\n"), argv[2]);
+ if (length == 0)
+ return o;
+ }
+
+ /* Note that negative start and length are used for referencing from the
+ end of the string. */
+ if (pad == NULL)
+ {
+ if (start > 0)
+ start--; /* one-origin */
+ else
+ {
+ start = str_len + start;
+ if (start <= 0)
+ {
+ if (length < 0)
+ return o;
+ start += length;
+ if (start <= 0)
+ return o;
+ length = start;
+ start = 0;
+ }
+ }
+
+ if (start >= str_len)
+ return o;
+ if (length == 0)
+ length = str_len - start;
+ else if (length < 0)
+ {
+ if (str_len <= -length)
+ return o;
+ length += str_len;
+ if (length <= start)
+ return o;
+ length -= start;
+ }
+ else if (start + length > str_len)
+ length = str_len - start;
+
+ o = variable_buffer_output (o, str + start, length);
+ }
+ else
+ {
+ if (start > 0)
+ {
+ start--; /* one-origin */
+ if (start >= str_len)
+ return length ? helper_pad (o, length, pad, pad_len) : o;
+ if (length == 0)
+ length = str_len - start;
+ }
+ else
+ {
+ start = str_len + start;
+ if (start <= 0)
+ {
+ if (start + length <= 0)
+ return length ? helper_pad (o, length, pad, pad_len) : o;
+ o = helper_pad (o, -start, pad, pad_len);
+ return variable_buffer_output (o, str, length + start);
+ }
+ if (length == 0)
+ length = str_len - start;
+ }
+ if (start + length <= str_len)
+ o = variable_buffer_output (o, str + start, length);
+ else
+ {
+ o = variable_buffer_output (o, str + start, str_len - start);
+ o = helper_pad (o, start + length - str_len, pad, pad_len);
+ }
+ }
+
+ return o;
+}
+
+/*
+ $(translate string, from-set[, to-set[, pad-char]])
+
+ XXX: This doesn't take multibyte locales into account.
+ */
+static char *
+func_translate (char *o, char **argv, const char *funcname UNUSED)
+{
+ const unsigned char *str = (const unsigned char *)argv[0];
+ const unsigned char *from_set = (const unsigned char *)argv[1];
+ const char *to_set = argv[2] != NULL ? argv[2] : "";
+ char trans_tab[1 << CHAR_BIT];
+ int i;
+ char ch;
+
+ /* init the array. */
+ for (i = 0; i < (1 << CHAR_BIT); i++)
+ trans_tab[i] = i;
+
+ while ( (i = *from_set) != '\0'
+ && (ch = *to_set) != '\0')
+ {
+ trans_tab[i] = ch;
+ from_set++;
+ to_set++;
+ }
+
+ if (i != '\0')
+ {
+ ch = '\0'; /* no padding == remove char */
+ if (argv[2] != NULL && argv[3] != NULL)
+ {
+ ch = argv[3][0];
+ if (ch && argv[3][1])
+ OS (fatal, NILF, _("$(translate ): pad=`%s' expected a single char\n"), argv[3]);
+ if (ch == '\0') /* no char == space */
+ ch = ' ';
+ }
+ while ((i = *from_set++) != '\0')
+ trans_tab[i] = ch;
+ }
+
+ /* do the translation */
+ while ((i = *str++) != '\0')
+ {
+ ch = trans_tab[i];
+ if (ch)
+ o = variable_buffer_output (o, &ch, 1);
+ }
+
+ return o;
+}
+#endif /* CONFIG_WITH_STRING_FUNCTIONS */
+
+#ifdef CONFIG_WITH_DEFINED
+/* Similar to ifdef. */
+static char *
+func_defined (char *o, char **argv, const char *funcname UNUSED)
+{
+ struct variable *v = lookup_variable (argv[0], strlen (argv[0]));
+ int result = v != NULL && *v->value != '\0';
+ o = variable_buffer_output (o, result ? "1" : "", result);
+ return o;
+}
+#endif /* CONFIG_WITH_DEFINED*/
+
+#ifdef CONFIG_WITH_TOUPPER_TOLOWER
+static char *
+func_toupper_tolower (char *o, char **argv, const char *funcname)
+{
+ /* Expand the argument. */
+ const char *p = argv[0];
+ while (*p)
+ {
+ /* convert to temporary buffer */
+ char tmp[256];
+ unsigned int i;
+ if (!strcmp(funcname, "toupper"))
+ for (i = 0; i < sizeof(tmp) && *p; i++, p++)
+ tmp[i] = toupper(*p);
+ else
+ for (i = 0; i < sizeof(tmp) && *p; i++, p++)
+ tmp[i] = tolower(*p);
+ o = variable_buffer_output (o, tmp, i);
+ }
+
+ return o;
+}
+#endif /* CONFIG_WITH_TOUPPER_TOLOWER */
+
+#if defined(CONFIG_WITH_VALUE_LENGTH) && defined(CONFIG_WITH_COMPARE)
+
+/* Strip leading spaces and other things off a command. */
+static const char *
+comp_cmds_strip_leading (const char *s, const char *e)
+{
+ while (s < e)
+ {
+ const char ch = *s;
+ if (!ISBLANK (ch)
+ && ch != '@'
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+ && ch != '%'
+#endif
+ && ch != '+'
+ && ch != '-')
+ break;
+ s++;
+ }
+ return s;
+}
+
+/* Worker for func_comp_vars() which is called if the comparision failed.
+ It will do the slow command by command comparision of the commands
+ when there invoked as comp-cmds. */
+static char *
+comp_vars_ne (char *o, const char *s1, const char *e1, const char *s2, const char *e2,
+ char *ne_retval, const char *funcname)
+{
+ /* give up at once if not comp-cmds or comp-cmds-ex. */
+ if (strcmp (funcname, "comp-cmds") != 0
+ && strcmp (funcname, "comp-cmds-ex") != 0)
+ o = variable_buffer_output (o, ne_retval, strlen (ne_retval));
+ else
+ {
+ const char * const s1_start = s1;
+ int new_cmd = 1;
+ int diff;
+ for (;;)
+ {
+ /* if it's a new command, strip leading stuff. */
+ if (new_cmd)
+ {
+ s1 = comp_cmds_strip_leading (s1, e1);
+ s2 = comp_cmds_strip_leading (s2, e2);
+ new_cmd = 0;
+ }
+ if (s1 >= e1 || s2 >= e2)
+ break;
+
+ /*
+ * Inner compare loop which compares one line.
+ * FIXME: parse quoting!
+ */
+ for (;;)
+ {
+ const char ch1 = *s1;
+ const char ch2 = *s2;
+ diff = ch1 - ch2;
+ if (diff)
+ break;
+ if (ch1 == '\n')
+ break;
+ assert (ch1 != '\r');
+
+ /* next */
+ s1++;
+ s2++;
+ if (s1 >= e1 || s2 >= e2)
+ break;
+ }
+
+ /*
+ * If we exited because of a difference try to end-of-command
+ * comparision, e.g. ignore trailing spaces.
+ */
+ if (diff)
+ {
+ /* strip */
+ while (s1 < e1 && ISBLANK (*s1))
+ s1++;
+ while (s2 < e2 && ISBLANK (*s2))
+ s2++;
+ if (s1 >= e1 || s2 >= e2)
+ break;
+
+ /* compare again and check that it's a newline. */
+ if (*s2 != '\n' || *s1 != '\n')
+ break;
+ }
+ /* Break out if we exited because of EOS. */
+ else if (s1 >= e1 || s2 >= e2)
+ break;
+
+ /*
+ * Detect the end of command lines.
+ */
+ if (*s1 == '\n')
+ new_cmd = s1 == s1_start || s1[-1] != '\\';
+ s1++;
+ s2++;
+ }
+
+ /*
+ * Ignore trailing empty lines.
+ */
+ if (s1 < e1 || s2 < e2)
+ {
+ while (s1 < e1 && (ISBLANK (*s1) || *s1 == '\n'))
+ if (*s1++ == '\n')
+ s1 = comp_cmds_strip_leading (s1, e1);
+ while (s2 < e2 && (ISBLANK (*s2) || *s2 == '\n'))
+ if (*s2++ == '\n')
+ s2 = comp_cmds_strip_leading (s2, e2);
+ }
+
+ /* emit the result. */
+ if (s1 == e1 && s2 == e2)
+ o = variable_buffer_output (o, "", 1) - 1; /** @todo check why this was necessary back the... */
+ else
+ o = variable_buffer_output (o, ne_retval, strlen (ne_retval));
+ }
+ return o;
+}
+
+/*
+ $(comp-vars var1,var2,not-equal-return)
+ or
+ $(comp-cmds cmd-var1,cmd-var2,not-equal-return)
+
+ Compares the two variables (that's given by name to avoid unnecessary
+ expanding) and return the string in the third argument if not equal.
+ If equal, nothing is returned.
+
+ comp-vars will to an exact comparision only stripping leading and
+ trailing spaces.
+
+ comp-cmds will compare command by command, ignoring not only leading
+ and trailing spaces on each line but also leading one leading '@',
+ '-', '+' and '%'
+*/
+static char *
+func_comp_vars (char *o, char **argv, const char *funcname)
+{
+ const char *s1, *e1, *x1, *s2, *e2, *x2;
+ char *a1 = NULL, *a2 = NULL;
+ size_t l, l1, l2;
+ struct variable *var1 = lookup_variable (argv[0], strlen (argv[0]));
+ struct variable *var2 = lookup_variable (argv[1], strlen (argv[1]));
+
+ /* the simple cases */
+ if (var1 == var2)
+ return variable_buffer_output (o, "", 0); /* eq */
+ if (!var1 || !var2)
+ return variable_buffer_output (o, argv[2], strlen(argv[2]));
+ if (var1->value == var2->value)
+ return variable_buffer_output (o, "", 0); /* eq */
+ if ( (!var1->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (var1))
+ && (!var2->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (var2)) )
+ {
+ if ( var1->value_length == var2->value_length
+ && !memcmp (var1->value, var2->value, var1->value_length))
+ return variable_buffer_output (o, "", 0); /* eq */
+
+ /* ignore trailing and leading blanks */
+ s1 = var1->value;
+ e1 = s1 + var1->value_length;
+ while (ISBLANK (*s1))
+ s1++;
+ while (e1 > s1 && ISBLANK (e1[-1]))
+ e1--;
+
+ s2 = var2->value;
+ e2 = s2 + var2->value_length;
+ while (ISBLANK (*s2))
+ s2++;
+ while (e2 > s2 && ISBLANK (e2[-1]))
+ e2--;
+
+ if (e1 - s1 != e2 - s2)
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+ if (!memcmp (s1, s2, e1 - s1))
+ return variable_buffer_output (o, "", 0); /* eq */
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+ }
+
+ /* ignore trailing and leading blanks */
+ s1 = var1->value;
+ e1 = s1 + var1->value_length;
+ while (ISBLANK (*s1))
+ s1++;
+ while (e1 > s1 && ISBLANK (e1[-1]))
+ e1--;
+
+ s2 = var2->value;
+ e2 = s2 + var2->value_length;
+ while (ISBLANK (*s2))
+ s2++;
+ while (e2 > s2 && ISBLANK (e2[-1]))
+ e2--;
+
+ /* both empty after stripping? */
+ if (s1 == e1 && s2 == e2)
+ return variable_buffer_output (o, "", 0); /* eq */
+
+ /* optimist. */
+ if ( e1 - s1 == e2 - s2
+ && !memcmp(s1, s2, e1 - s1))
+ return variable_buffer_output (o, "", 0); /* eq */
+
+ /* compare up to the first '$' or the end. */
+ x1 = var1->recursive ? memchr (s1, '$', e1 - s1) : NULL;
+ x2 = var2->recursive ? memchr (s2, '$', e2 - s2) : NULL;
+ if (!x1 && !x2)
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+
+ l1 = x1 ? x1 - s1 : e1 - s1;
+ l2 = x2 ? x2 - s2 : e2 - s2;
+ l = l1 <= l2 ? l1 : l2;
+ if (l && memcmp (s1, s2, l))
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+
+ /* one or both buffers now require expanding. */
+ if (!x1)
+ s1 += l;
+ else
+ {
+ s1 = a1 = allocated_variable_expand ((char *)s1 + l);
+ if (!l)
+ while (ISBLANK (*s1))
+ s1++;
+ e1 = strchr (s1, '\0');
+ while (e1 > s1 && ISBLANK (e1[-1]))
+ e1--;
+ }
+
+ if (!x2)
+ s2 += l;
+ else
+ {
+ s2 = a2 = allocated_variable_expand ((char *)s2 + l);
+ if (!l)
+ while (ISBLANK (*s2))
+ s2++;
+ e2 = strchr (s2, '\0');
+ while (e2 > s2 && ISBLANK (e2[-1]))
+ e2--;
+ }
+
+ /* the final compare */
+ if ( e1 - s1 != e2 - s2
+ || memcmp (s1, s2, e1 - s1))
+ o = comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+ else
+ o = variable_buffer_output (o, "", 1) - 1; /* eq */ /** @todo check why this was necessary back the... */
+ if (a1)
+ free (a1);
+ if (a2)
+ free (a2);
+ return o;
+}
+
+/*
+ $(comp-cmds-ex cmds1,cmds2,not-equal-return)
+
+ Compares the two strings and return the string in the third argument
+ if not equal. If equal, nothing is returned.
+
+ The comparision will be performed command by command, ignoring not
+ only leading and trailing spaces on each line but also leading one
+ leading '@', '-', '+' and '%'.
+*/
+static char *
+func_comp_cmds_ex (char *o, char **argv, const char *funcname)
+{
+ const char *s1, *e1, *s2, *e2;
+ size_t l1, l2;
+
+ /* the simple cases */
+ s1 = argv[0];
+ s2 = argv[1];
+ if (s1 == s2)
+ return variable_buffer_output (o, "", 0); /* eq */
+ l1 = strlen (argv[0]);
+ l2 = strlen (argv[1]);
+
+ if ( l1 == l2
+ && !memcmp (s1, s2, l1))
+ return variable_buffer_output (o, "", 0); /* eq */
+
+ /* ignore trailing and leading blanks */
+ e1 = s1 + l1;
+ s1 = comp_cmds_strip_leading (s1, e1);
+
+ e2 = s2 + l2;
+ s2 = comp_cmds_strip_leading (s2, e2);
+
+ if (e1 - s1 != e2 - s2)
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+ if (!memcmp (s1, s2, e1 - s1))
+ return variable_buffer_output (o, "", 0); /* eq */
+ return comp_vars_ne (o, s1, e1, s2, e2, argv[2], funcname);
+}
+#endif
+
+#ifdef CONFIG_WITH_DATE
+# if defined (_MSC_VER) /* FIXME: !defined (HAVE_STRPTIME) */
+char *strptime(const char *s, const char *format, struct tm *tm)
+{
+ return (char *)"strptime is not implemented";
+}
+# endif
+/* Check if the string is all blanks or not. */
+static int
+all_blanks (const char *s)
+{
+ if (!s)
+ return 1;
+ while (ISSPACE (*s))
+ s++;
+ return *s == '\0';
+}
+
+/* The first argument is the strftime format string, a iso
+ timestamp is the default if nothing is given.
+
+ The second argument is a time value if given. The format
+ is either the format from the first argument or given as
+ an additional third argument. */
+static char *
+func_date (char *o, char **argv, const char *funcname)
+{
+ char *p;
+ char *buf;
+ size_t buf_size;
+ struct tm t;
+ const char *format;
+
+ /* determin the format - use a single word as the default. */
+ format = !strcmp (funcname, "date-utc")
+ ? "%Y-%m-%dT%H:%M:%SZ"
+ : "%Y-%m-%dT%H:%M:%S";
+ if (!all_blanks (argv[0]))
+ format = argv[0];
+
+ /* get the time. */
+ memset (&t, 0, sizeof(t));
+ if (argv[0] && !all_blanks (argv[1]))
+ {
+ const char *input_format = !all_blanks (argv[2]) ? argv[2] : format;
+ p = strptime (argv[1], input_format, &t);
+ if (!p || *p != '\0')
+ {
+ OSSSS (error, NILF, _("$(%s): strptime(%s,%s,) -> %s\n"), funcname,
+ argv[1], input_format, p ? p : "<null>");
+ return variable_buffer_output (o, "", 0);
+ }
+ }
+ else
+ {
+ time_t tval;
+ time (&tval);
+ if (!strcmp (funcname, "date-utc"))
+ t = *gmtime (&tval);
+ else
+ t = *localtime (&tval);
+ }
+
+ /* format it. note that zero isn't necessarily an error, so we'll
+ have to keep shut about failures. */
+ buf_size = 64;
+ buf = xmalloc (buf_size);
+ while (strftime (buf, buf_size, format, &t) == 0)
+ {
+ if (buf_size >= 4096)
+ {
+ *buf = '\0';
+ break;
+ }
+ buf = xrealloc (buf, buf_size <<= 1);
+ }
+ o = variable_buffer_output (o, buf, strlen (buf));
+ free (buf);
+ return o;
+}
+#endif
+
+#ifdef CONFIG_WITH_FILE_SIZE
+/* Prints the size of the specified file. Only one file is
+ permitted, notthing is stripped. -1 is returned if stat
+ fails. */
+static char *
+func_file_size (char *o, char **argv, const char *funcname UNUSED)
+{
+ struct stat st;
+ if (stat (argv[0], &st))
+ return variable_buffer_output (o, "-1", 2);
+ return math_int_to_variable_buffer (o, st.st_size);
+}
+#endif
+
+#ifdef CONFIG_WITH_WHICH
+/* Checks if the specified file exists an is executable.
+ On systems employing executable extensions, the name may
+ be modified to include the extension. */
+static int func_which_test_x (char *file)
+{
+ struct stat st;
+# if defined(WINDOWS32) || defined(__OS2__)
+ char *ext;
+ char *slash;
+
+ /* fix slashes first. */
+ slash = file;
+ while ((slash = strchr (slash, '\\')) != NULL)
+ *slash++ = '/';
+
+ /* straight */
+ if (stat (file, &st) == 0
+ && S_ISREG (st.st_mode))
+ return 1;
+
+ /* don't try add an extension if there already is one */
+ ext = strchr (file, '\0');
+ if (ext - file >= 4
+ && ( !stricmp (ext - 4, ".exe")
+ || !stricmp (ext - 4, ".cmd")
+ || !stricmp (ext - 4, ".bat")
+ || !stricmp (ext - 4, ".com")))
+ return 0;
+
+ /* try the extensions. */
+ strcpy (ext, ".exe");
+ if (stat (file, &st) == 0
+ && S_ISREG (st.st_mode))
+ return 1;
+
+ strcpy (ext, ".cmd");
+ if (stat (file, &st) == 0
+ && S_ISREG (st.st_mode))
+ return 1;
+
+ strcpy (ext, ".bat");
+ if (stat (file, &st) == 0
+ && S_ISREG (st.st_mode))
+ return 1;
+
+ strcpy (ext, ".com");
+ if (stat (file, &st) == 0
+ && S_ISREG (st.st_mode))
+ return 1;
+
+ return 0;
+
+# else
+
+ return access (file, X_OK) == 0
+ && stat (file, &st) == 0
+ && S_ISREG (st.st_mode);
+# endif
+}
+
+/* Searches for the specified programs in the PATH and print
+ their full location if found. Prints nothing if not found. */
+static char *
+func_which (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *path;
+ struct variable *path_var;
+ unsigned i;
+ int first = 1;
+ PATH_VAR (buf);
+
+ path_var = lookup_variable ("PATH", 4);
+ if (path_var)
+ path = path_var->value;
+ else
+ path = ".";
+
+ /* iterate input */
+ for (i = 0; argv[i]; i++)
+ {
+ unsigned int len;
+ const char *iterator = argv[i];
+ char *cur;
+
+ while ((cur = find_next_token (&iterator, &len)))
+ {
+ /* if there is a separator, don't walk the path. */
+ if (memchr (cur, '/', len)
+#ifdef HAVE_DOS_PATHS
+ || memchr (cur, '\\', len)
+ || memchr (cur, ':', len)
+#endif
+ )
+ {
+ if (len + 1 + 4 < GET_PATH_MAX) /* +4 for .exe */
+ {
+ memcpy (buf, cur, len);
+ buf[len] = '\0';
+ if (func_which_test_x (buf))
+ o = variable_buffer_output (o, buf, strlen (buf));
+ }
+ }
+ else
+ {
+ const char *comp = path;
+ for (;;)
+ {
+ const char *src = comp;
+ const char *end = strchr (comp, PATH_SEPARATOR_CHAR);
+ size_t src_len = end ? (size_t)(end - comp) : strlen (comp);
+ if (!src_len)
+ {
+ src_len = 1;
+ src = ".";
+ }
+ if (len + src_len + 2 + 4 < GET_PATH_MAX) /* +4 for .exe */
+ {
+ memcpy (buf, src, src_len);
+ buf [src_len] = '/';
+ memcpy (&buf[src_len + 1], cur, len);
+ buf[src_len + 1 + len] = '\0';
+
+ if (func_which_test_x (buf))
+ {
+ if (!first)
+ o = variable_buffer_output (o, " ", 1);
+ o = variable_buffer_output (o, buf, strlen (buf));
+ first = 0;
+ break;
+ }
+ }
+
+ /* next */
+ if (!end)
+ break;
+ comp = end + 1;
+ }
+ }
+ }
+ }
+
+ return variable_buffer_output (o, "", 0);
+}
+#endif /* CONFIG_WITH_WHICH */
+
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+
+/* Evaluates the expression given in the argument using the
+ same evaluator as for the new 'if' statements, except now
+ we don't force the result into a boolean like for 'if' and
+ '$(if-expr ,,)'. */
+static char *
+func_expr (char *o, char **argv, const char *funcname UNUSED)
+{
+ o = expr_eval_to_string (o, argv[0]);
+ return o;
+}
+
+/* Same as '$(if ,,)' except the first argument is evaluated
+ using the same evaluator as for the new 'if' statements. */
+static char *
+func_if_expr (char *o, char **argv, const char *funcname UNUSED)
+{
+ int rc;
+ char *to_expand;
+
+ /* Evaluate the condition in argv[0] and expand the 2nd or
+ 3rd (optional) argument according to the result. */
+ rc = expr_eval_if_conditionals (argv[0], NULL);
+ to_expand = rc == 0 ? argv[1] : argv[2];
+ if (to_expand && *to_expand)
+ variable_expand_string_2 (o, to_expand, -1, &o);
+
+ return o;
+}
+
+/*
+ $(select when1-cond, when1-body[,whenN-cond, whenN-body]).
+ */
+static char *
+func_select (char *o, char **argv, const char *funcname UNUSED)
+{
+ int i;
+
+ /* Test WHEN-CONDs until one matches. The check for 'otherwise[:]'
+ and 'default[:]' make this a bit more fun... */
+
+ for (i = 0; argv[i] != NULL; i += 2)
+ {
+ const char *cond = argv[i];
+ int is_otherwise = 0;
+
+ if (argv[i + 1] == NULL)
+ O (fatal, NILF, _("$(select ): not an even argument count\n"));
+
+ while (ISSPACE (*cond))
+ cond++;
+ if ( (*cond == 'o' && strncmp (cond, "otherwise", 9) == 0)
+ || (*cond == 'd' && strncmp (cond, "default", 7) == 0))
+ {
+ const char *end = cond + (*cond == 'o' ? 9 : 7);
+ while (ISSPACE (*end))
+ end++;
+ if (*end == ':')
+ do end++;
+ while (ISSPACE (*end));
+ is_otherwise = *end == '\0';
+ }
+
+ if ( is_otherwise
+ || expr_eval_if_conditionals (cond, NULL) == 0 /* true */)
+ {
+ variable_expand_string_2 (o, argv[i + 1], -1, &o);
+ break;
+ }
+ }
+
+ return o;
+}
+
+#endif /* CONFIG_WITH_IF_CONDITIONALS */
+
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+static char *
+func_set_intersects (char *o, char **argv, const char *funcname UNUSED)
+{
+ const char *s1_cur;
+ unsigned int s1_len;
+ const char *s1_iterator = argv[0];
+
+ while ((s1_cur = find_next_token (&s1_iterator, &s1_len)) != 0)
+ {
+ const char *s2_cur;
+ unsigned int s2_len;
+ const char *s2_iterator = argv[1];
+ while ((s2_cur = find_next_token (&s2_iterator, &s2_len)) != 0)
+ if (s2_len == s1_len
+ && strneq (s2_cur, s1_cur, s1_len) )
+ return variable_buffer_output (o, "1", 1); /* found intersection */
+ }
+
+ return o; /* no intersection */
+}
+#endif /* CONFIG_WITH_SET_CONDITIONALS */
+
+#ifdef CONFIG_WITH_STACK
+
+/* Push an item (string without spaces). */
+static char *
+func_stack_push (char *o, char **argv, const char *funcname UNUSED)
+{
+ do_variable_definition(NILF, argv[0], argv[1], o_file, f_append, 0 /* !target_var */);
+ return o;
+}
+
+/* Pops an item off the stack / get the top stack element.
+ (This is what's tricky to do in pure GNU make syntax.) */
+static char *
+func_stack_pop_top (char *o, char **argv, const char *funcname)
+{
+ struct variable *stack_var;
+ const char *stack = argv[0];
+
+ stack_var = lookup_variable (stack, strlen (stack) );
+ if (stack_var)
+ {
+ unsigned int len;
+ const char *iterator = stack_var->value;
+ char *lastitem = NULL;
+ char *cur;
+
+ while ((cur = find_next_token (&iterator, &len)))
+ lastitem = cur;
+
+ if (lastitem != NULL)
+ {
+ if (strcmp (funcname, "stack-popv") != 0)
+ o = variable_buffer_output (o, lastitem, len);
+ if (strcmp (funcname, "stack-top") != 0)
+ {
+ *lastitem = '\0';
+ while (lastitem > stack_var->value && ISSPACE (lastitem[-1]))
+ *--lastitem = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ stack_var->value_length = lastitem - stack_var->value;
+#endif
+ VARIABLE_CHANGED (stack_var);
+ }
+ }
+ }
+ return o;
+}
+#endif /* CONFIG_WITH_STACK */
+
+#if defined (CONFIG_WITH_MATH) || defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_FILE_SIZE)
+/* outputs the number (as a string) into the variable buffer. */
+static char *
+math_int_to_variable_buffer (char *o, math_int num)
+{
+ static const char xdigits[17] = "0123456789abcdef";
+ int negative;
+ char strbuf[24]; /* 16 hex + 2 prefix + sign + term => 20
+ or 20 dec + sign + term => 22 */
+ char *str = &strbuf[sizeof (strbuf) - 1];
+
+ negative = num < 0;
+ if (negative)
+ num = -num;
+
+ *str = '\0';
+
+ do
+ {
+#ifdef HEX_MATH_NUMBERS
+ *--str = xdigits[num & 0xf];
+ num >>= 4;
+#else
+ *--str = xdigits[num % 10];
+ num /= 10;
+#endif
+ }
+ while (num);
+
+#ifdef HEX_MATH_NUMBERS
+ *--str = 'x';
+ *--str = '0';
+#endif
+
+ if (negative)
+ *--str = '-';
+
+ return variable_buffer_output (o, str, &strbuf[sizeof (strbuf) - 1] - str);
+}
+#endif /* CONFIG_WITH_MATH || CONFIG_WITH_NANOTS */
+
+#ifdef CONFIG_WITH_MATH
+
+/* Converts a string to an integer, causes an error if the format is invalid. */
+static math_int
+math_int_from_string (const char *str)
+{
+ const char *start;
+ unsigned base = 0;
+ int negative = 0;
+ math_int num = 0;
+
+ /* strip spaces */
+ while (ISSPACE (*str))
+ str++;
+ if (!*str)
+ {
+ O (error, NILF, _("bad number: empty\n"));
+ return 0;
+ }
+ start = str;
+
+ /* check for +/- */
+ while (*str == '+' || *str == '-' || ISSPACE (*str))
+ if (*str++ == '-')
+ negative = !negative;
+
+ /* check for prefix - we do not accept octal numbers, sorry. */
+ if (*str == '0' && (str[1] == 'x' || str[1] == 'X'))
+ {
+ base = 16;
+ str += 2;
+ }
+ else
+ {
+ /* look for a hex digit, if not found treat it as decimal */
+ const char *p2 = str;
+ for ( ; *p2; p2++)
+ if (isxdigit (*p2) && !isdigit (*p2) && isascii (*p2) )
+ {
+ base = 16;
+ break;
+ }
+ if (base == 0)
+ base = 10;
+ }
+
+ /* must have at least one digit! */
+ if ( !isascii (*str)
+ || !(base == 16 ? isxdigit (*str) : isdigit (*str)) )
+ {
+ OS (error, NILF, _("bad number: '%s'\n"), start);
+ return 0;
+ }
+
+ /* convert it! */
+ while (*str && !ISSPACE (*str))
+ {
+ int ch = *str++;
+ if (ch >= '0' && ch <= '9')
+ ch -= '0';
+ else if (base == 16 && ch >= 'a' && ch <= 'f')
+ ch -= 'a' - 10;
+ else if (base == 16 && ch >= 'A' && ch <= 'F')
+ ch -= 'A' - 10;
+ else
+ {
+ OSNN (error, NILF, _("bad number: '%s' (base=%u, pos=%lu)\n"), start, base, (unsigned long)(str - start));
+ return 0;
+ }
+ num *= base;
+ num += ch;
+ }
+
+ /* check trailing spaces. */
+ while (ISSPACE (*str))
+ str++;
+ if (*str)
+ {
+ OS (error, NILF, _("bad number: '%s'\n"), start);
+ return 0;
+ }
+
+ return negative ? -num : num;
+}
+
+/* Add two or more integer numbers. */
+static char *
+func_int_add (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num += math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Subtract two or more integer numbers. */
+static char *
+func_int_sub (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num -= math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Multiply two or more integer numbers. */
+static char *
+func_int_mul (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num *= math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Divide an integer number by one or more divisors. */
+static char *
+func_int_div (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ math_int divisor;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ {
+ divisor = math_int_from_string (argv[i]);
+ if (!divisor)
+ {
+ OS (error, NILF, _("divide by zero ('%s')\n"), argv[i]);
+ return math_int_to_variable_buffer (o, 0);
+ }
+ num /= divisor;
+ }
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+
+/* Divide and return the remainder. */
+static char *
+func_int_mod (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ math_int divisor;
+
+ num = math_int_from_string (argv[0]);
+ divisor = math_int_from_string (argv[1]);
+ if (!divisor)
+ {
+ OS (error, NILF, _("divide by zero ('%s')\n"), argv[1]);
+ return math_int_to_variable_buffer (o, 0);
+ }
+ num %= divisor;
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* 2-complement. */
+static char *
+func_int_not (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+
+ num = math_int_from_string (argv[0]);
+ num = ~num;
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Bitwise AND (two or more numbers). */
+static char *
+func_int_and (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num &= math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Bitwise OR (two or more numbers). */
+static char *
+func_int_or (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num |= math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Bitwise XOR (two or more numbers). */
+static char *
+func_int_xor (char *o, char **argv, const char *funcname UNUSED)
+{
+ math_int num;
+ int i;
+
+ num = math_int_from_string (argv[0]);
+ for (i = 1; argv[i]; i++)
+ num ^= math_int_from_string (argv[i]);
+
+ return math_int_to_variable_buffer (o, num);
+}
+
+/* Compare two integer numbers. Returns make boolean (true="1"; false=""). */
+static char *
+func_int_cmp (char *o, char **argv, const char *funcname)
+{
+ math_int num1;
+ math_int num2;
+ int rc;
+
+ num1 = math_int_from_string (argv[0]);
+ num2 = math_int_from_string (argv[1]);
+
+ funcname += sizeof ("int-") - 1;
+ if (!strcmp (funcname, "eq"))
+ rc = num1 == num2;
+ else if (!strcmp (funcname, "ne"))
+ rc = num1 != num2;
+ else if (!strcmp (funcname, "gt"))
+ rc = num1 > num2;
+ else if (!strcmp (funcname, "ge"))
+ rc = num1 >= num2;
+ else if (!strcmp (funcname, "lt"))
+ rc = num1 < num2;
+ else /*if (!strcmp (funcname, "le"))*/
+ rc = num1 <= num2;
+
+ return variable_buffer_output (o, rc ? "1" : "", rc);
+}
+
+#endif /* CONFIG_WITH_MATH */
+
+#ifdef CONFIG_WITH_NANOTS
+/* Returns the current timestamp as nano seconds. The time
+ source is a high res monotone one if the platform provides
+ this (and we know about it).
+
+ Tip. Use this with int-sub to profile makefile reading
+ and similar. */
+static char *
+func_nanots (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+ return math_int_to_variable_buffer (o, nano_timestamp ());
+}
+#endif
+
+#ifdef CONFIG_WITH_OS2_LIBPATH
+/* Sets or gets the OS/2 libpath variables.
+
+ The first argument indicates which variable - BEGINLIBPATH,
+ ENDLIBPATH, LIBPATHSTRICT or LIBPATH.
+
+ The second indicates whether this is a get (not present) or
+ set (present) operation. When present it is the new value for
+ the variable. */
+static char *
+func_os2_libpath (char *o, char **argv, const char *funcname UNUSED)
+{
+ char buf[4096];
+ ULONG fVar;
+ APIRET rc;
+
+ /* translate variable name (first arg) */
+ if (!strcmp (argv[0], "BEGINLIBPATH"))
+ fVar = BEGIN_LIBPATH;
+ else if (!strcmp (argv[0], "ENDLIBPATH"))
+ fVar = END_LIBPATH;
+ else if (!strcmp (argv[0], "LIBPATHSTRICT"))
+ fVar = LIBPATHSTRICT;
+ else if (!strcmp (argv[0], "LIBPATH"))
+ fVar = 0;
+ else
+ {
+ OS (error, NILF, _("$(libpath): unknown variable `%s'"), argv[0]);
+ return variable_buffer_output (o, "", 0);
+ }
+
+ if (!argv[1])
+ {
+ /* get the variable value. */
+ if (fVar != 0)
+ {
+ buf[0] = buf[1] = buf[2] = buf[3] = '\0';
+ rc = DosQueryExtLIBPATH (buf, fVar);
+ }
+ else
+ rc = DosQueryHeaderInfo (NULLHANDLE, 0, buf, sizeof(buf), QHINF_LIBPATH);
+ if (rc != NO_ERROR)
+ {
+ OSN (error, NILF, _("$(libpath): failed to query `%s', rc=%d"), argv[0], rc);
+ return variable_buffer_output (o, "", 0);
+ }
+ o = variable_buffer_output (o, buf, strlen (buf));
+ }
+ else
+ {
+ /* set the variable value. */
+ size_t len;
+ size_t len_max = sizeof (buf) < 2048 ? sizeof (buf) : 2048;
+ const char *val;
+ const char *end;
+
+ if (fVar == 0)
+ {
+ O (error, NILF, _("$(libpath): LIBPATH is read-only"));
+ return variable_buffer_output (o, "", 0);
+ }
+
+ /* strip leading and trailing spaces and check for max length. */
+ val = argv[1];
+ while (ISSPACE (*val))
+ val++;
+ end = strchr (val, '\0');
+ while (end > val && ISSPACE (end[-1]))
+ end--;
+
+ len = end - val;
+ if (len >= len_max)
+ {
+ OSNN (error, NILF, _("$(libpath): The new `%s' value is too long (%d bytes, max %d)"),
+ argv[0], len, len_max);
+ return variable_buffer_output (o, "", 0);
+ }
+
+ /* make a stripped copy in low memory and try set it. */
+ memcpy (buf, val, len);
+ buf[len] = '\0';
+ rc = DosSetExtLIBPATH (buf, fVar);
+ if (rc != NO_ERROR)
+ {
+ OSSN (error, NILF, _("$(libpath): failed to set `%s' to `%s', rc=%d"), argv[0], buf, rc);
+ return variable_buffer_output (o, "", 0);
+ }
+
+ o = variable_buffer_output (o, "", 0);
+ }
+ return o;
+}
+#endif /* CONFIG_WITH_OS2_LIBPATH */
+
+#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+/* Retrieve make statistics. */
+static char *
+func_make_stats (char *o, char **argv, const char *funcname UNUSED)
+{
+ char buf[512];
+ int len;
+
+ if (!argv[0] || (!argv[0][0] && !argv[1]))
+ {
+# ifdef CONFIG_WITH_MAKE_STATS
+ len = sprintf (buf, "alloc-cur: %5ld/%3ld %3luMB hash: %5lu %2lu%%",
+ make_stats_allocations,
+ make_stats_reallocations,
+ make_stats_allocated / (1024*1024),
+ make_stats_ht_lookups,
+ (make_stats_ht_collisions * 100) / make_stats_ht_lookups);
+ o = variable_buffer_output (o, buf, len);
+#endif
+ }
+ else
+ {
+ /* selective */
+ int i;
+ for (i = 0; argv[i]; i++)
+ {
+ unsigned long val;
+ if (i != 0)
+ o = variable_buffer_output (o, " ", 1);
+ if (0)
+ continue;
+# ifdef CONFIG_WITH_MAKE_STATS
+ else if (!strcmp(argv[i], "allocations"))
+ val = make_stats_allocations;
+ else if (!strcmp(argv[i], "reallocations"))
+ val = make_stats_reallocations;
+ else if (!strcmp(argv[i], "allocated"))
+ val = make_stats_allocated;
+ else if (!strcmp(argv[i], "ht_lookups"))
+ val = make_stats_ht_lookups;
+ else if (!strcmp(argv[i], "ht_collisions"))
+ val = make_stats_ht_collisions;
+ else if (!strcmp(argv[i], "ht_collisions_pct"))
+ val = (make_stats_ht_collisions * 100) / make_stats_ht_lookups;
+#endif
+ else
+ {
+ o = variable_buffer_output (o, argv[i], strlen (argv[i]));
+ continue;
+ }
+
+ len = sprintf (buf, "%ld", val);
+ o = variable_buffer_output (o, buf, len);
+ }
+ }
+
+ return o;
+}
+#endif /* CONFIG_WITH_MAKE_STATS */
+
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+/* Gets all the commands for a target, separated by newlines.
+
+ This is useful when creating and checking target dependencies since
+ it reduces the amount of work and the memory consuption. A new prefix
+ character '%' has been introduced for skipping certain lines, like
+ for instance the one calling this function and pushing to a dep file.
+ Blank lines are also skipped.
+
+ The commands function takes exactly one argument, which is the name of
+ the target which commands should be returned.
+
+ The commands-sc is identical to commands except that it uses a ';' to
+ separate the commands.
+
+ The commands-usr is similar to commands except that it takes a 2nd
+ argument that is used to separate the commands. */
+char *
+func_commands (char *o, char **argv, const char *funcname)
+{
+ struct file *file;
+ static int recursive = 0;
+
+ if (recursive)
+ {
+ OS (error, reading_file, _("$(%s ) was invoked recursivly"), funcname);
+ return variable_buffer_output (o, "recursive", sizeof ("recursive") - 1);
+ }
+ if (*argv[0] == '\0')
+ {
+ OS (error, reading_file, _("$(%s ) was invoked with an empty target name"), funcname);
+ return o;
+ }
+ recursive = 1;
+
+ file = lookup_file (argv[0]);
+ if (file && file->cmds)
+ {
+ unsigned int i;
+ int cmd_sep_len;
+ struct commands *cmds = file->cmds;
+ const char *cmd_sep;
+
+ if (!strcmp (funcname, "commands"))
+ {
+ cmd_sep = "\n";
+ cmd_sep_len = 1;
+ }
+ else if (!strcmp (funcname, "commands-sc"))
+ {
+ cmd_sep = ";";
+ cmd_sep_len = 1;
+ }
+ else /*if (!strcmp (funcname, "commands-usr"))*/
+ {
+ cmd_sep = argv[1];
+ cmd_sep_len = strlen (cmd_sep);
+ }
+
+ initialize_file_variables (file, 1 /* don't search for pattern vars */);
+ set_file_variables (file, 1 /* early call */);
+ chop_commands (cmds);
+
+ for (i = 0; i < cmds->ncommand_lines; i++)
+ {
+ char *p;
+ char *in, *out, *ref;
+
+ /* Skip it if it has a '%' prefix or is blank. */
+ if (cmds->lines_flags[i] & COMMAND_GETTER_SKIP_IT)
+ continue;
+ p = cmds->command_lines[i];
+ while (ISBLANK (*p))
+ p++;
+ if (*p == '\0')
+ continue;
+
+ /* --- copied from new_job() in job.c --- */
+
+ /* Collapse backslash-newline combinations that are inside variable
+ or function references. These are left alone by the parser so
+ that they will appear in the echoing of commands (where they look
+ nice); and collapsed by construct_command_argv when it tokenizes.
+ But letting them survive inside function invocations loses because
+ we don't want the functions to see them as part of the text. */
+
+ /* IN points to where in the line we are scanning.
+ OUT points to where in the line we are writing.
+ When we collapse a backslash-newline combination,
+ IN gets ahead of OUT. */
+
+ in = out = p;
+ while ((ref = strchr (in, '$')) != 0)
+ {
+ ++ref; /* Move past the $. */
+
+ if (out != in)
+ /* Copy the text between the end of the last chunk
+ we processed (where IN points) and the new chunk
+ we are about to process (where REF points). */
+ memmove (out, in, ref - in);
+
+ /* Move both pointers past the boring stuff. */
+ out += ref - in;
+ in = ref;
+
+ if (*ref == '(' || *ref == '{')
+ {
+ char openparen = *ref;
+ char closeparen = openparen == '(' ? ')' : '}';
+ int count;
+ char *p2;
+
+ *out++ = *in++; /* Copy OPENPAREN. */
+ /* IN now points past the opening paren or brace.
+ Count parens or braces until it is matched. */
+ count = 0;
+ while (*in != '\0')
+ {
+ if (*in == closeparen && --count < 0)
+ break;
+ else if (*in == '\\' && in[1] == '\n')
+ {
+ /* We have found a backslash-newline inside a
+ variable or function reference. Eat it and
+ any following whitespace. */
+
+ int quoted = 0;
+ for (p2 = in - 1; p2 > ref && *p2 == '\\'; --p2)
+ quoted = !quoted;
+
+ if (quoted)
+ /* There were two or more backslashes, so this is
+ not really a continuation line. We don't collapse
+ the quoting backslashes here as is done in
+ collapse_continuations, because the line will
+ be collapsed again after expansion. */
+ *out++ = *in++;
+ else
+ {
+ /* Skip the backslash, newline and
+ any following whitespace. */
+ in = next_token (in + 2);
+
+ /* Discard any preceding whitespace that has
+ already been written to the output. */
+ while (out > ref
+ && ISBLANK (out[-1]))
+ --out;
+
+ /* Replace it all with a single space. */
+ *out++ = ' ';
+ }
+ }
+ else
+ {
+ if (*in == openparen)
+ ++count;
+
+ *out++ = *in++;
+ }
+ }
+ }
+ /* Some of these can be amended ($< perhaps), but we're likely to be called while the
+ dep expansion happens, so it would have to be on a hackish basis. sad... */
+ else if (*ref == '<' || *ref == '*' || *ref == '%' || *ref == '^' || *ref == '+')
+ OSN (error, reading_file, _("$(%s ) does not work reliably with $%c in all cases"), funcname, *ref);
+ }
+
+ /* There are no more references in this line to worry about.
+ Copy the remaining uninteresting text to the output. */
+ if (out != in)
+ strcpy (out, in);
+
+ /* --- copied from new_job() in job.c --- */
+
+ /* Finally, expand the line. */
+ if (i)
+ o = variable_buffer_output (o, cmd_sep, cmd_sep_len);
+ o = variable_expand_for_file_2 (o, cmds->command_lines[i], ~0U, file, NULL);
+
+ /* Skip it if it has a '%' prefix or is blank. */
+ p = o;
+ while (ISBLANK (*o)
+ || *o == '@'
+ || *o == '-'
+ || *o == '+')
+ o++;
+ if (*o != '\0' && *o != '%')
+ o = strchr (o, '\0');
+ else if (i)
+ o = p - cmd_sep_len;
+ else
+ o = p;
+ } /* for each command line */
+ }
+ /* else FIXME: bitch about it? */
+
+ recursive = 0;
+ return o;
+}
+#endif /* CONFIG_WITH_COMMANDS_FUNC */
+#ifdef KMK
+
+/* Useful when debugging kmk and/or makefiles. */
+char *
+func_breakpoint (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+#ifdef _MSC_VER
+ __debugbreak();
+#elif defined(__i386__) || defined(__x86__) || defined(__X86__) || defined(_M_IX86) || defined(__i386) \
+ || defined(__amd64__) || defined(__x86_64__) || defined(__AMD64__) || defined(_M_X64) || defined(__amd64)
+# ifdef __sun__
+ __asm__ __volatile__ ("int $3\n\t");
+# else
+ __asm__ __volatile__ ("int3\n\t");
+# endif
+#else
+ char *p = (char *)0;
+ *p = '\0';
+#endif
+ return o;
+}
+
+/* umask | umask -S. */
+char *
+func_get_umask (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+ char sz[80];
+ int off;
+ mode_t u;
+ int symbolic = 0;
+ const char *psz = argv[0];
+
+ if (psz)
+ {
+ const char *pszEnd = strchr (psz, '\0');
+ strip_whitespace (&psz, &pszEnd);
+
+ if (pszEnd != psz)
+ {
+ if ( STR_N_EQUALS (psz, pszEnd - pszEnd, "S")
+ || STR_N_EQUALS (psz, pszEnd - pszEnd, "-S")
+ || STR_N_EQUALS (psz, pszEnd - pszEnd, "symbolic") )
+ symbolic = 1;
+ else
+ OSS (error, reading_file, _("$(%s ) invalid argument `%s'"),
+ funcname, argv[0]);
+ }
+ }
+
+ u = g_fUMask;
+ assert (u == umask (g_fUMask));
+
+ if (symbolic)
+ {
+ off = 0;
+ sz[off++] = 'u';
+ sz[off++] = '=';
+ if ((u & S_IRUSR) == 0)
+ sz[off++] = 'r';
+ if ((u & S_IWUSR) == 0)
+ sz[off++] = 'w';
+ if ((u & S_IXUSR) == 0)
+ sz[off++] = 'x';
+ sz[off++] = ',';
+ sz[off++] = 'g';
+ sz[off++] = '=';
+ if ((u & S_IRGRP) == 0)
+ sz[off++] = 'r';
+ if ((u & S_IWGRP) == 0)
+ sz[off++] = 'w';
+ if ((u & S_IXGRP) == 0)
+ sz[off++] = 'x';
+ sz[off++] = ',';
+ sz[off++] = 'o';
+ sz[off++] = '=';
+ if ((u & S_IROTH) == 0)
+ sz[off++] = 'r';
+ if ((u & S_IWOTH) == 0)
+ sz[off++] = 'w';
+ if ((u & S_IXOTH) == 0)
+ sz[off++] = 'x';
+ }
+ else
+ off = sprintf (sz, "%.4o", u);
+
+ return variable_buffer_output (o, sz, off);
+}
+
+
+/* umask 0002 | umask u=rwx,g=rwx,o=rx. */
+char *
+func_set_umask (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+ mode_t u;
+ const char *psz;
+
+ /* Figure what kind of input this is. */
+ psz = argv[0];
+ while (ISBLANK (*psz))
+ psz++;
+
+ if (isdigit ((unsigned char)*psz))
+ {
+ u = 0;
+ while (*psz)
+ {
+ u <<= 3;
+ if (*psz < '0' || *psz >= '8')
+ {
+ OSS (error, reading_file, _("$(%s ) illegal number `%s'"), funcname, argv[0]);
+ break;
+ }
+ u += *psz - '0';
+ psz++;
+ }
+
+ if (argv[1] != NULL)
+ OS (error, reading_file, _("$(%s ) too many arguments for octal mode"), funcname);
+
+ umask (u);
+ g_fUMask = umask (u); /* Must get it again as windows modifies it. */
+ }
+ else
+ {
+ OS (error, reading_file, _("$(%s ) symbol mode is not implemented"), funcname);
+ }
+
+ return o;
+}
+
+
+/* Controls the cache in dir-bird-nt.c. */
+
+char *
+func_dircache_ctl (char *o, char **argv UNUSED, const char *funcname UNUSED)
+{
+# ifdef KBUILD_OS_WINDOWS
+ const char *cmd = argv[0];
+ while (ISBLANK (*cmd))
+ cmd++;
+ if (strcmp (cmd, "invalidate") == 0)
+ {
+ if (argv[1] != NULL)
+ O (error, reading_file, "$(dircache-ctl invalidate) takes no parameters");
+ dir_cache_invalid_all ();
+ }
+ else if (strcmp (cmd, "invalidate-and-close-dirs") == 0)
+ {
+ if (argv[1] != NULL)
+ O (error, reading_file, "$(dircache-ctl invalidate) takes no parameters");
+ dir_cache_invalid_all_and_close_dirs (0 /*including_root*/);
+ }
+ else if (strcmp (cmd, "invalidate-missing") == 0)
+ {
+ if (argv[1] != NULL)
+ O (error, reading_file, "$(dircache-ctl invalidate-missing) takes no parameters");
+ dir_cache_invalid_missing ();
+ }
+ else if (strcmp (cmd, "volatile") == 0)
+ {
+ size_t i;
+ for (i = 1; argv[i] != NULL; i++)
+ {
+ const char *dir = argv[i];
+ while (ISBLANK (*dir))
+ dir++;
+ if (*dir)
+ dir_cache_volatile_dir (dir);
+ }
+ }
+ else if (strcmp (cmd, "deleted") == 0)
+ {
+ size_t i;
+ for (i = 1; argv[i] != NULL; i++)
+ {
+ const char *dir = argv[i];
+ while (ISBLANK (*dir))
+ dir++;
+ if (*dir)
+ dir_cache_deleted_directory (dir);
+ }
+ }
+ else
+ OS (error, reading_file, "Unknown $(dircache-ctl ) command: '%s'", cmd);
+# endif
+ return o;
+}
+
+#endif /* KMK */
+#if defined (KMK) || defined (CONFIG_WITH_LAZY_DEPS_VARS)
+
+/* Helper for performer GNU make style quoting of one filename. */
+
+static char *
+helper_quote_make (char *o, const char *name, size_t len, int is_dep,
+ int is_tgt, int quote_trailing_slashes,
+ const char *funcname)
+{
+ unsigned const map_flags = MAP_BLANK
+ | MAP_NEWLINE
+ | MAP_COMMENT
+ | MAP_VARIABLE
+ | MAP_SEMI
+ | MAP_EQUALS
+ | (is_dep ? MAP_PIPE :
+ is_tgt ? MAP_COLON : 0);
+ const char *cur = name;
+ assert (memchr (name, '\0', len) == NULL);
+ if (len > 0)
+ {
+ const char * const end = &name[len];
+ unsigned long len_out = 0;
+ const char *prev = cur;
+ do
+ {
+ char ch = *cur;
+ unsigned int flags = stopchar_map[(unsigned int)ch] & map_flags;
+ if (!flags)
+ cur++; /* likely */
+ else
+ {
+ /* Flush pending output. */
+ if (prev != cur)
+ {
+ o = variable_buffer_output (o, prev, cur - prev);
+ len_out += cur - prev;
+ }
+
+ /* Dollar is quoted by duplicating the dollar: */
+ if (flags & MAP_VARIABLE)
+ {
+ o = variable_buffer_output (o, "$", 1);
+ prev = cur++;
+ }
+ /* The rest is quoted by '\': */
+ else
+ {
+ size_t const max_slashes = cur - prev;
+ size_t slashes = 0;
+ while (slashes < max_slashes && cur[1 - slashes] == '\\')
+ slashes++;
+ if (slashes)
+ {
+ o = variable_buffer_output (o, &cur[0 - slashes], slashes);
+ len_out += slashes;
+ }
+ o = variable_buffer_output (o, "\\", 1);
+ prev = cur++;
+ }
+ }
+ }
+ while ((uintptr_t)cur < (uintptr_t)end);
+
+ /* Flush pending output. */
+ if (prev != cur)
+ {
+ o = variable_buffer_output (o, prev, cur - prev);
+ len_out += cur - prev;
+ }
+
+ /* Escape trailing slashes when needed. */
+ if ( o[-1] == '\\'
+ && quote_trailing_slashes)
+ {
+ size_t slashes = 1;
+ while (slashes < len_out && o[-1 - slashes] == '\\')
+ slashes++;
+ while (slashes-- > 0)
+ o = variable_buffer_output (o, "\\", 1);
+ }
+ }
+ else
+ OS (error, reading_file, "%s: cannot quote empty string", funcname);
+ return o;
+}
+
+# ifdef KMK
+
+/* Helper for func_quote_make that checks if there are more arguments
+ that produces output or not. */
+
+static int func_quote_make_has_more_non_empty_args (char **argv)
+{
+ for (;;)
+ {
+ char *arg = *argv;
+ if (!arg)
+ return 0;
+ if (*arg)
+ return 1;
+ argv++;
+ }
+}
+
+/* Takes zero or more plain strings and escapes (quotes) spaces and other
+ problematic characters, GNU make style.
+
+ There is one slightly problematic aspect of using this, if the input ends
+ with backslashes whether or not they will be reduced or taken as-is depends
+ on whether they appear at the end of a line or not. They are taken as-is
+ when at the end of a line, otherwise they'll be subject to unescaping
+ (unquoting) and reduced by half.
+
+ In addition, the quoting style differs for files on the left side and
+ right side of the recipe colon. Colons aren't escaped are only escaped
+ on the left side (target), and the pipe character is only escaped on the
+ right side (deps).
+
+ For this reason there are four variants of this function. */
+
+static char *func_quote_make (char *o, char **argv, const char *funcname)
+{
+ int const is_dep = funcname[5] == '-' && funcname[6] == 'd';
+ int const is_tgt = funcname[5] == '-' && funcname[6] == 't';
+ int const quote_trailing_slashes = funcname[5] == '\0' || funcname[9] == '\0';
+ char * const o_initial = o;
+ int i;
+
+ assert ( quote_trailing_slashes
+ == (!strcmp (funcname, "quote") || !strcmp (funcname, "quote-dep") || !strcmp (funcname, "quote-tgt")));
+ assert (is_dep == !strncmp (funcname, "quote-dep", sizeof ("quote-dep") - 1));
+ assert (is_tgt == !strncmp (funcname, "quote-tgt", sizeof ("quote-tgt") - 1));
+
+ for (i = 0; argv[i]; i++)
+ {
+ char *arg = argv[i];
+ if (*arg)
+ {
+ /* Add space separator. */
+ if (o != o_initial)
+ o = variable_buffer_output (o, " ", 1);
+
+ /* Output the quoted argument: */
+ if (quote_trailing_slashes)
+ o = helper_quote_make (o, arg, strlen (arg), is_dep, is_tgt,
+ quote_trailing_slashes, funcname);
+ else
+ {
+ char *end = strchr (arg, '\0');
+ int qts = end != arg && end[-1] == '\\'
+ && func_quote_make_has_more_non_empty_args (&argv[i + 1]);
+ o = helper_quote_make (o, arg, strlen (arg), is_dep, is_tgt,
+ qts, funcname);
+ }
+ }
+ else
+ OS (error, reading_file, "%s: cannot munge empty string", funcname);
+ }
+
+ return o;
+}
+
+# endif /* KMK */
+
+/* Worker for func_quote_shell() for escaping a string that's inside
+ double quotes. */
+
+static char *func_escape_shell_in_dq (char *o, const char *arg, size_t len)
+{
+ const char *prev = arg;
+ while (len-- > 0)
+ {
+ char const ch = *arg;
+ switch (ch)
+ {
+ default:
+ arg++;
+ break;
+ case '!':
+ case '$':
+ case '`':
+ case '"':
+ case '\\':
+ case '\n':
+ if (prev != arg)
+ o = variable_buffer_output (o, prev, arg - prev);
+ o = variable_buffer_output (o, "\\", 1);
+ prev = arg;
+ arg++;
+ break;
+ }
+ }
+ if (prev != arg)
+ o = variable_buffer_output (o, prev, arg - prev);
+ return o;
+}
+
+# ifdef KMK
+/* quote-sh-dq */
+
+static char *func_quote_shell_dq (char *o, char **argv, const char *funcname UNUSED)
+{
+ return func_escape_shell_in_dq (o, argv[0], strlen (argv[0]));
+}
+# endif
+
+/* Worker for func_quote_shell() for escaping a string that's inside
+ single quotes. */
+
+static char *func_escape_shell_in_sq (char *o, const char *arg, size_t len)
+{
+ while (len > 0)
+ {
+ char *sq = memchr (arg, '\'', len);
+ if (!sq)
+ return variable_buffer_output (o, arg, len);
+ if (sq != arg)
+ o = variable_buffer_output (o, arg, sq - arg);
+ o = variable_buffer_output (o, "'\\''", 4);
+
+ /* advance */
+ sq++;
+ len -= sq - arg;
+ arg = sq;
+ }
+ return o;
+}
+
+# ifdef KMK
+/* quote-sh-dq */
+
+static char *func_quote_shell_sq (char *o, char **argv, const char *funcname UNUSED)
+{
+ return func_escape_shell_in_sq (o, argv[0], strlen (argv[0]));
+}
+#endif
+
+/* Output a shell argument with quoting as needed. */
+static char *helper_quote_shell (char *o, const char *arg, size_t len,
+ int leading_space)
+{
+ if ( memchr (arg, '$', len) != NULL
+ || memchr (arg, '*', len) != NULL
+ || memchr (arg, '?', len) != NULL
+ || memchr (arg, '[', len) != NULL)
+ {
+ if (leading_space)
+ o = variable_buffer_output (o, " '", 2);
+ else
+ o = variable_buffer_output (o, "'", 1);
+ o = func_escape_shell_in_sq (o, arg, len);
+ o = variable_buffer_output (o, "'", 1);
+ }
+ else if ( memchr (arg, ' ', len) != NULL
+ || memchr (arg, '\t', len) != NULL
+ || memchr (arg, '\\', len) != NULL
+ || memchr (arg, '"', len) != NULL
+ || memchr (arg, '`', len) != NULL
+ || memchr (arg, '!', len) != NULL
+ || memchr (arg, '|', len) != NULL
+ || memchr (arg, '<', len) != NULL
+ || memchr (arg, '>', len) != NULL
+ || memchr (arg, '&', len) != NULL
+ || memchr (arg, ';', len) != NULL
+ || memchr (arg, '(', len) != NULL
+ || memchr (arg, ')', len) != NULL
+ || memchr (arg, '\n', len) != NULL)
+ {
+ if (leading_space)
+ o = variable_buffer_output (o, " \"", 2);
+ else
+ o = variable_buffer_output (o, "\"", 1);
+ o = func_escape_shell_in_dq (o, arg, len);
+ o = variable_buffer_output (o, "\"", 1);
+ }
+ else
+ {
+ if (leading_space)
+ o = variable_buffer_output (o, " ", 1);
+ o = variable_buffer_output (o, arg, len);
+ }
+ return o;
+}
+
+# ifdef KMK
+
+/* Takes zero or more plain strings and escapes/quotes spaces and other
+ problematic characters, bourne make style.
+
+ The quote-sh-dq and quote-sh-sq variants is for escaping strings that
+ going to be put into double quotes and single quotes respectively.
+
+ The normal quote-sh variant assumes it's free to do open and close
+ quotes as it pleases. */
+
+static char *func_quote_shell (char *o, char **argv, const char *funcname UNUSED)
+{
+ int i;
+ for (i = 0; argv[i]; i++)
+ o = helper_quote_shell (o, argv[i], strlen (argv[i]),
+ i > 0 /*need_leading_space*/);
+ return o;
+}
+
+/* Unlinks CUR from *CHAINP and frees it, returning the next element. */
+
+static struct nameseq *
+helper_unlink_and_free_ns (struct nameseq *cur, struct nameseq *prev,
+ struct nameseq **chainp)
+{
+ struct nameseq *freeit = cur; \
+ free ((char *)cur->name);
+ if (prev)
+ prev->next = cur = cur->next;
+ else
+ *chainp = cur = cur->next;
+ free_ns (freeit);
+ return cur;
+}
+
+/* Frees a chain returned by helper_parse_file_list. */
+
+static void free_ns_chain_no_strcache (struct nameseq *ns)
+{
+ while (ns != 0)
+ {
+ struct nameseq *t = ns;
+ free ((char *)ns->name);
+ ns = ns->next;
+ free_ns (t);
+ }
+}
+
+# endif /* KMK */
+
+/* Decoded style options for the $(q* ) and $(*file* ) functions. */
+#define Q_RET_MASK 0x000f
+#define Q_RET_QUOTED 0x0000
+#define Q_RET_QUOTED_DEP 0x0001
+#define Q_RET_QUOTED_DEP_END 0x0002
+#define Q_RET_QUOTED_TGT 0x0003
+#define Q_RET_QUOTED_TGT_END 0x0004
+#define Q_RET_UNQUOTED 0x0005
+#define Q_RET_SHELL 0x0006
+#define Q_RET_SHELL_IN_DQ 0x0007
+#define Q_RET_SHELL_IN_SQ 0x0008
+
+#define Q_IN_MASK 0x0070
+#define Q_IN_QUOTED 0x0000
+#define Q_IN_UNQUOTED 0x0010
+#define Q_IN_QUOTED_DEP 0x0020 /** @todo needed? */
+#define Q_IN_QUOTED_TGT 0x0030 /** @todo needed? */
+#define Q_IN_UNQUOTED_SINGLE 0x0040
+#define Q_IN_SEP_COMMA 0x0080 /* for VMS hacks, file lists only */
+
+#define Q_SEP_MASK 0x0700
+#define Q_SEP_SHIFT 8
+#define Q_SEP_SPACE 0x0000
+#define Q_SEP_TAB 0x0100
+#define Q_SEP_NL 0x0200
+#define Q_SEP_NL_TAB 0x0300
+#define Q_SEP_COMMA 0x0400 /* for VMS, output only */
+
+#define Q_QDEFAULT (Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE)
+#ifndef VMS
+# define Q_QDEFAULT_VMS_TRICKS Q_QDEFAULT
+#else /* VMS: Treat ',' as file separators in input, maybe output too. */
+# define Q_QDEFAULT_VMS_TRICKS (Q_IN_SEP_COMMA | \
+ (!vms_comma_separator ? Q_QDEFAULT \
+ : (Q_QDEFAULT & ~Q_SEP_MASK) | Q_SEP_COMMA)
+#endif
+
+# ifdef KMK
+/* Decodes the optional style argument. This is chiefly for the return
+ style, but can also pick the input and space styles (just because we can). */
+
+static unsigned int helper_file_quoting_style (char *style, unsigned int intstyle)
+{
+ if (style != NULL)
+ {
+ for (;;)
+ {
+ /* Skip blanks: */
+ while (ISBLANK (*style))
+ style++;
+ if (*style != '\0')
+ {
+ /* Find the end of the current word: */
+ char * const start = style;
+ size_t len;
+ char ch;
+ while (!ISBLANK ((ch = *style)) && ch != '\0')
+ style++;
+ len = style - start;
+
+ /* String "switch" to decode the word: */
+
+#define MATCH(a_str) (len == sizeof (a_str) - 1 && memcmp (start, a_str, sizeof (a_str)) == 0)
+ /* return styles: */
+ if (MATCH ("quoted") || MATCH ("q"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED;
+ else if (MATCH ("unquoted") || MATCH ("unq") || MATCH ("u"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_UNQUOTED;
+ else if (MATCH ("quoted-dep") || MATCH ("q-dep") || MATCH ("q-d"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_DEP;
+ else if (MATCH ("quoted-dep-end") || MATCH ("q-dep-end") || MATCH ("q-d-e"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_DEP_END;
+ else if (MATCH ("quoted-tgt") || MATCH ("q-tgt") || MATCH ("q-t"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_TGT;
+ else if (MATCH ("quoted-tgt-end") || MATCH ("q-tgt-end") || MATCH ("q-t-e"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_QUOTED_TGT_END;
+ else if (MATCH ("shell") || MATCH ("sh"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL;
+ else if (MATCH ("shell-in-dq") || MATCH ("sh-i-d"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL_IN_DQ; /* returned string is used inside double shell quotes */
+ else if (MATCH ("shell-in-sq") || MATCH ("sh-i-s"))
+ intstyle = (intstyle & ~Q_RET_MASK) | Q_RET_SHELL_IN_SQ; /* returned string is used inside single shell quotes */
+ /* input styles: */
+ else if (MATCH ("in-quoted") || MATCH ("i-q"))
+ intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED;
+ else if (MATCH ("in-unquoted") || MATCH ("i-unq") || MATCH ("i-u"))
+ intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_UNQUOTED;
+ else if (MATCH ("in-unquoted-single")|| MATCH ("i-unq-s") || MATCH ("i-u-s")
+ || MATCH ("in-unquoted-file") || MATCH ("i-unq-f") || MATCH ("i-u-f"))
+ intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_UNQUOTED_SINGLE;
+ else if (MATCH ("in-quoted-dep") || MATCH ("i-q-dep") || MATCH ("i-q-d"))
+ intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED_DEP;
+ else if (MATCH ("in-quoted-tgt") || MATCH ("i-q-tgt") || MATCH ("i-q-t"))
+ intstyle = (intstyle & ~Q_IN_MASK) | Q_IN_QUOTED_TGT;
+ else if (MATCH ("in-sep-comma") || MATCH ("i-s-com") || MATCH ("i-s-c"))
+ intstyle |= Q_IN_SEP_COMMA; /*?*/
+ /* separator styles (output): */
+ else if (MATCH ("sep-space") || MATCH ("s-space") || MATCH ("s-s"))
+ intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_SPACE;
+ else if (MATCH ("sep-tab") || MATCH ("s-tab") || MATCH ("s-t"))
+ intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_TAB;
+ else if (MATCH ("sep-nl") || MATCH ("s-nl") || MATCH ("s-n"))
+ intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_NL;
+ else if (MATCH ("sep-nl-tab") || MATCH ("s-nl-tab") || MATCH ("s-n-t"))
+ intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_NL_TAB;
+ else if (MATCH ("sep-comma") || MATCH ("s-comma") || MATCH ("s-c"))
+ intstyle = (intstyle & ~Q_SEP_MASK) | Q_SEP_COMMA;
+ else
+ {
+ char savedch = *style;
+ *style = '\0';
+ OS (error, reading_file, "Unknown file style: %s", start);
+ *style = savedch;
+ }
+#undef MATCH
+ }
+ else
+ break;
+ }
+ }
+ return intstyle;
+}
+# endif /* KMK */
+
+/* Output (returns) a separator according to STYLE. */
+
+static char *helper_return_sep (char *o, unsigned int style)
+{
+ /* Note! Must match Q_SEP_MASK! */
+ static struct
+ {
+ const char *sep;
+ size_t len;
+ } const seps[8] =
+ {
+ { " ", 1 },
+ { "\t", 1 },
+ { "\n", 1 },
+ { "\n\t", 2 },
+ { ",", 1 },
+ { " ", 1 },
+ { " ", 1 },
+ { " ", 1 }
+ };
+
+ return variable_buffer_output(o,
+ seps[(style & Q_SEP_MASK) >> Q_SEP_SHIFT].sep,
+ seps[(style & Q_SEP_MASK) >> Q_SEP_SHIFT].len);
+}
+
+/* Removes one separator from the output.
+
+This is typically used when outputting lists where there is no simple way of
+telling whether an entry is the last one or not. So, is_last is always false
+and the last separator is removed afterwards by this function. */
+
+MY_INLINE char *helper_drop_separator (char *o, unsigned int style)
+{
+ o -= (style & Q_SEP_MASK) != Q_SEP_NL_TAB ? 1 : 2;
+ *o = '\0'; /* not strictly necessary */
+ return o;
+}
+
+
+/* Outputs (returns) the given file. */
+
+static char *helper_return_file_len (char *o, const char *file, size_t len,
+ unsigned int style, int is_last)
+{
+ assert (memchr (file, '\0', len) == NULL);
+ switch (style & Q_RET_MASK)
+ {
+ case Q_RET_UNQUOTED:
+ o = variable_buffer_output (o, file, len);
+ break;
+ case Q_RET_QUOTED:
+ o = helper_quote_make (o, file, len, 0 /*is_dep*/, 0 /*is_tgt*/,
+ !is_last /*quote_trailing_slashes*/, NULL);
+ break;
+ case Q_RET_QUOTED_DEP:
+ o = helper_quote_make (o, file, len, 1 /*is_dep*/, 0 /*is_tgt*/,
+ !is_last /*quote_trailing_slashes*/, NULL);
+ break;
+ case Q_RET_QUOTED_DEP_END:
+ o = helper_quote_make (o, file, len, 1 /*is_dep*/, 0 /*is_tgt*/,
+ 0 /*quote_trailing_slashes*/, NULL);
+ break;
+ case Q_RET_QUOTED_TGT:
+ o = helper_quote_make (o, file, len, 0 /*is_dep*/, 1 /*is_tgt*/,
+ !is_last /*quote_trailing_slashes*/, NULL);
+ break;
+ case Q_RET_QUOTED_TGT_END:
+ o = helper_quote_make (o, file, len, 0 /*is_dep*/, 1 /*is_tgt*/,
+ 0 /*quote_trailing_slashes*/, NULL);
+ break;
+ case Q_RET_SHELL:
+ o = helper_quote_shell (o, file, len, 0 /*need_leading_space*/);
+ break;
+ case Q_RET_SHELL_IN_DQ:
+ o = func_escape_shell_in_dq (o, file, len);
+ break;
+ case Q_RET_SHELL_IN_SQ:
+ o = func_escape_shell_in_sq (o, file, len);
+ break;
+ default:
+ assert (0);
+ }
+
+ /* Add separator space if not last. */
+ if (!is_last)
+ o = helper_return_sep (o, style);
+ return o;
+}
+
+/* Outputs (returns) the given file. */
+
+static char *helper_return_file (char *o, const char *file,
+ unsigned int style, int is_last)
+{
+ return helper_return_file_len (o,file, strlen (file), style, is_last);
+}
+
+#endif /* KMK || CONFIG_WITH_LAZY_DEPS_VARS */
+#ifdef KMK
+
+/* Outputs (returns) the given CHAIN and frees it. */
+
+static char *helper_return_and_free_chain (char *o, struct nameseq *chain, unsigned int style)
+{
+ struct nameseq *cur;
+ for (cur = chain; cur; cur = cur->next)
+ o = helper_return_file (o, cur->name, style, cur->next == NULL);
+ free_ns_chain_no_strcache (chain);
+ return o;
+}
+
+
+/* Helper for helper_parse_file_list that globs a name sequence. */
+static struct nameseq *
+helper_glob_chain (struct nameseq *chain)
+{
+ struct nameseq *prev = NULL;
+ struct nameseq *cur = chain;
+ glob_t gl;
+ dir_setup_glob (&gl);
+
+ /** @todo XXX: !NO_ARCHIVES */
+ while (cur)
+ {
+ switch (glob (cur->name, GLOB_NOSORT | GLOB_ALTDIRFUNC, NULL, &gl))
+ {
+ case 0: /* Replace CUR with the names found. */
+ {
+ struct nameseq *subchain = NULL;
+ struct nameseq **ppnext = &subchain;
+ const char ** const names = (const char **)gl.gl_pathv;
+ size_t const num_names = gl.gl_pathc;
+ size_t idx;
+
+ cur = helper_unlink_and_free_ns (cur, prev, &chain);
+
+ for (idx = 0; idx < num_names; idx++)
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ struct nameseq *newp = xcalloc (sizeof (*newp));
+#else
+ struct nameseq *newp = alloccache_calloc (&nameseq_cache);
+#endif
+ newp->name = xstrdup (names[idx]);
+ newp->next = NULL;
+ *ppnext = newp;
+ ppnext = &newp->next;
+ }
+
+ if (subchain) /* parnaoia */
+ {
+ *ppnext = cur;
+ if (prev)
+ prev->next = subchain;
+ else
+ chain = subchain;
+ }
+ break;
+ }
+
+ case GLOB_NOMATCH: /* doesn't exist, remove */
+ cur = helper_unlink_and_free_ns (cur, prev, &chain);
+ break;
+
+ default: /* Keep it. */
+ prev = cur;
+ cur = cur->next;
+ break;
+
+ case GLOB_NOSPACE:
+ OUT_OF_MEM();
+ }
+ globfree (&gl);
+ }
+ return chain;
+}
+
+/* Parses a file/word list according to STYLE and returns a name list.
+
+ The FILELIST parameter may be modified!
+
+ The GLOB param is for wildcard/qwildcard.
+
+ TODO/XXX: Unquote and split up the FILELIST directly. All function
+ arguments are heap copies already, so we are free to modify
+ them. Would be nice to ditch the nameseq in favor of something
+ which also includes the length. */
+
+static struct nameseq *
+helper_parse_file_list (char *filelist, unsigned int style, int glob)
+{
+ if (filelist && *filelist != '\0')
+ {
+ /* Q_IN_SEP_COMMA: VMS tricks for qbasename, qdir, qnotdir and qsuffix
+ where commas are treated as separtors in FILELIST. We simply
+ replace commas in the FILELIST before doing the regular parsing. */
+ if (!(style & Q_IN_SEP_COMMA))
+ { /* typical */ }
+ else
+ {
+ size_t len = strlen (filelist);
+ char *start = filelist;
+ char *comma = (char *)memchr (filelist, ',', len);
+ while (comma)
+ {
+ *comma = ' ';
+ len -= comma - start - 1;
+ if (len)
+ {
+ start = comma + 1;
+ comma = (char *)memchr (start, ',', len);
+ }
+ else
+ break;
+ }
+ }
+
+ switch (style & Q_IN_MASK)
+ {
+ case Q_IN_QUOTED:
+ case Q_IN_QUOTED_DEP: /** @todo ?? */
+ case Q_IN_QUOTED_TGT: /** @todo ?? */
+ return PARSE_FILE_SEQ(&filelist, struct nameseq, MAP_NUL, NULL,
+ !glob
+ ? PARSEFS_NOGLOB | PARSEFS_NOSTRIP | PARSEFS_NOCACHE
+ : PARSEFS_NOSTRIP | PARSEFS_NOCACHE | PARSEFS_EXISTS);
+
+ case Q_IN_UNQUOTED:
+ {
+ struct nameseq *chain = NULL;
+ struct nameseq **ppnext = &chain;
+ const char *it = filelist;
+ const char *cur;
+ unsigned int curlen;
+ while ((cur = find_next_token (&it, &curlen)) != NULL)
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ struct nameseq *newp = xcalloc (sizeof (*newp));
+#else
+ struct nameseq *newp = alloccache_calloc (&nameseq_cache);
+#endif
+ newp->name = xstrndup (cur, curlen);
+ newp->next = NULL;
+ *ppnext = newp;
+ ppnext = &newp->next;
+ }
+ if (!glob)
+ return chain;
+ return helper_glob_chain (chain);
+ }
+
+ case Q_IN_UNQUOTED_SINGLE:
+ if (*filelist != '\0')
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ struct nameseq *newp = xcalloc (sizeof (*newp));
+#else
+ struct nameseq *newp = alloccache_calloc (&nameseq_cache);
+#endif
+ newp->name = xstrdup (filelist);
+ newp->next = NULL;
+ if (!glob)
+ return newp;
+ return helper_glob_chain (newp);
+ }
+ return NULL;
+
+ default:
+ assert (0);
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+/* $(requote style, file1 file2 ... fileN). */
+
+static char *func_requote (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ return helper_return_and_free_chain (o, chain, style);
+}
+
+/* Common worker for func_firstfile() and func_qfirstfile(). */
+
+static char *common_firstfile (char *o, char **argv, unsigned int style)
+{
+ struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+ if (chain)
+ {
+ o = helper_return_file (o, chain->name, style, 1);
+ free_ns_chain_no_strcache (chain);
+ }
+ return o;
+}
+
+/* $(firstfile file1 file2 ... fileN) - same as $(firstfile ), except
+ for files rather than word tokens. See func_firstword(). */
+
+static char *func_firstfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ return common_firstfile(o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE);
+}
+
+/* $(qfirstfile style, file1 file2 ... fileN) - same as $(firstfile ), except
+ for files rather than word tokens. See func_firstword(). */
+
+static char *func_q_firstfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_firstfile(o, &argv[1], style);
+}
+
+/* Common worker for func_lastfile() and func_q_lastfile(). */
+
+static char *common_lastfile (char *o, char **argv, unsigned int style)
+{
+ struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+ if (chain)
+ {
+ struct nameseq *last = chain;
+ while (last->next)
+ last = last->next;
+ o = helper_return_file (o, last->name, style, 1);
+ free_ns_chain_no_strcache (chain);
+ }
+ return o;
+}
+
+/* $(lastfile file1 file2 ... fileN) - same as $(lastfile ), except
+ for files rather than word tokens. See func_lastword(). */
+
+static char *func_lastfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ return common_lastfile (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE);
+}
+
+/* $(qlastfile style, file1 file2 ... fileN) - same as $(lastfile ), except
+ for files rather than word tokens. See func_lastword(). */
+
+static char *func_q_lastfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_lastfile (o, &argv[1], style);
+}
+
+/* Common worker for func_filelist() and func_q_filelist(). */
+
+static char *common_filelist (char *o, char **argv, unsigned int style)
+{
+ int start;
+ int count;
+
+ /* Check the arguments. */
+ check_numeric (argv[0],
+ _("non-numeric first argument to 'filelist' function"));
+ check_numeric (argv[1],
+ _("non-numeric second argument to 'filelist' function"));
+
+ start = atoi (argv[0]);
+ if (start < 1)
+ ON (fatal, *expanding_var,
+ "invalid first argument to 'filelist' function: '%d'", start);
+ start--; /* make zero based */
+
+ count = atoi (argv[1]) - start;
+
+ if (count > 0)
+ {
+ char *line = argv[2];
+ struct nameseq *cur;
+ struct nameseq *chain;
+ chain = helper_parse_file_list (line, style, 0);
+
+ /* Find the beginning of the "start"th word (1-based). */
+ for (cur = chain; cur && start > 1; cur = cur->next)
+ start--;
+
+ /* Output the requested count */
+ while (cur && count-- > 0)
+ o = helper_return_file (o, cur->name, style, count > 0 && cur->next);
+
+ free_ns_chain_no_strcache (chain);
+ }
+
+ return o;
+}
+
+/* $(filelist start, end, file1..fileN) - same as $(wordlist),
+ except for files rather than word tokens. See func_wordlist(). */
+
+static char *func_filelist (char *o, char **argv, const char *funcname UNUSED)
+{
+ return common_filelist (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE);
+}
+
+/* $(qfilelist style, start, end, file1..fileN) - same as $(wordlist),
+ except for files rather than word tokens. See func_wordlist(). */
+
+static char *func_q_filelist (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_filelist (o, &argv[1], style);
+}
+
+/* Common worker for func_countfiles() and func_q_countfiles(). */
+
+static char *common_countfiles (char *o, char **argv, unsigned int style)
+{
+ struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+ struct nameseq *cur;
+ unsigned int files = 0;
+ char retval[32];
+
+ for (cur = chain; cur; cur = cur->next)
+ files++;
+ free_ns_chain_no_strcache (chain);
+
+ return variable_buffer_output (o, retval, sprintf (retval, "%u", files));
+}
+
+/* $(countfiles file1 file2 ... fileN) - same as $(words ), except for
+ files rather than word tokens. See func_words(). */
+
+static char *func_countfiles (char *o, char **argv, const char *funcname UNUSED)
+{
+ return common_countfiles (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE);
+}
+
+/* $(qcountfiles style, file1 file2 ... fileN) - same as $(words ), except for
+ files rather than word tokens and the STYLE argument. See func_words(). */
+
+static char *func_q_countfiles (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_countfiles (o, &argv[1], style);
+}
+
+/* Helper that sets the variable value. */
+
+static void
+helper_set_var_value (struct variable *var, const char *value, size_t len)
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (var->value);
+ var->value = xstrndup (value, len);
+#else
+ if (len >= var->value_alloc_len)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (var->rdonly_val)
+ var->rdonly_val = 0;
+ else
+# endif
+ free (var->value);
+ var->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (len + 1);
+ var->value = xmalloc (var->value_alloc_len);
+ }
+ memcpy (var->value, value, len);
+ var->value[len] = '\0';
+ var->value_length = len;
+ VARIABLE_CHANGED (var);
+#endif
+}
+
+/* Common worker for func_foreachfile and func_qforeachfile. */
+
+static char *
+common_foreachfile (char *o, char **argv, unsigned int style)
+{
+ /* expand only the first two. */
+ char *varname = expand_argument (argv[0], NULL);
+ char *list = expand_argument (argv[1], NULL);
+ const char *body = argv[2];
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ long body_len = strlen (body);
+#endif
+
+ struct nameseq *chain = helper_parse_file_list (list, style, 0);
+ struct nameseq *cur;
+
+ /* Clean up the variable name by removing whitespace. */
+ struct variable *var;
+ char *vp = next_token (varname);
+ char *vp_end = end_of_token (vp);
+ vp_end[0] = '\0';
+
+ push_new_variable_scope ();
+ var = define_variable (vp, vp_end - vp, "", o_automatic, 0);
+
+ /* Don't need the list any more. */
+ free (list);
+ list = NULL;
+
+ /* Loop through the chain. */
+ for (cur = chain; cur; cur = cur->next)
+ {
+ /* Update the variable value: */
+ unsigned int const len = strlen (cur->name);
+ switch (style & Q_RET_MASK)
+ {
+ case Q_RET_UNQUOTED:
+ helper_set_var_value (var, cur->name, len);
+ break;
+ default:
+ { /* Use the output buffer as temporary storage. */
+ size_t const saved_off = o - variable_buffer;
+ size_t quoted_len;
+ char *quoted;
+ o = helper_return_file_len (o, cur->name, len, style, 1 /*is_last*/);
+ quoted = &variable_buffer[saved_off];
+ quoted_len = o - quoted;
+ helper_set_var_value (var, quoted, quoted_len);
+ o = quoted;
+ break;
+ }
+ }
+
+ /* Expand the body: */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ {
+ char *result = allocated_variable_expand (body);
+ o = variable_buffer_output (o, result, strlen (result));
+ free (result);
+ }
+#else
+ variable_expand_string_2 (o, body, body_len, &o);
+#endif
+
+ /* Add separator: */
+ if (cur->next)
+ o = helper_return_sep (o, style);
+ }
+
+ pop_variable_scope ();
+ free (varname);
+
+ return o;
+}
+
+/* $(foreachfile var, filelist, body) - same as $(foreach ), except
+ for file rather than word tokens.
+ See also func_foreach(). */
+
+static char *
+func_foreachfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ return common_foreachfile (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE);
+}
+
+/* $(qforeachfile style, var, filelist, body) - same as $(foreach ), except
+ for file rather than word tokens and flexible variable value encoding.
+ See also func_foreach(). */
+
+static char *
+func_q_foreachfile (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_foreachfile (o, &argv[1], style);
+}
+
+/* Common worker for func_sortfiles() and func_q_sortfiles(). */
+
+static char *common_sortfiles (char *o, char **argv, unsigned int style,
+ int ascending, int version)
+{
+ struct nameseq *chain = helper_parse_file_list (argv[0], style, 0);
+
+ /* Count the number of files to determin the array size and whether we've
+ got anything to sort. */
+ struct nameseq *cur;
+ unsigned int num_files = 0;
+ for (cur = chain; cur; cur = cur->next)
+ num_files++;
+
+ if (num_files <= 1)
+ o = helper_return_and_free_chain (o, chain, style);
+ else
+ {
+ /* Create array of string pointers from the chain. */
+ char **files = (char **)xmalloc (num_files * sizeof (char *));
+ unsigned int idx = 0;
+ for (cur = chain; cur; cur = cur->next)
+ files[idx++] = (char *)cur->name;
+
+ /* Sort it. */
+ if (version)
+ qsort(files, num_files, sizeof(files[0]), version_compare_wrapper);
+ else
+ qsort(files, num_files, sizeof(files[0]), alpha_compare);
+
+ /* Output. We skip equal files. */
+ if (ascending)
+ for (idx = 0; idx < num_files; idx++)
+ {
+ const char *curfile = files[idx];
+ while (idx + 1 < num_files && strcmp(files[idx + 1], curfile) == 0)
+ idx++;
+ o = helper_return_file(o, files[idx], style, idx + 1 >= num_files);
+ }
+ else
+ {
+ idx = num_files;
+ while (idx-- > 0)
+ {
+ const char *curfile = files[idx];
+ while (idx > 0 && strcmp(files[idx - 1], curfile) == 0)
+ idx--;
+ o = helper_return_file (o, curfile, style, idx == 0);
+ }
+ }
+
+ free (files);
+ free_ns_chain_no_strcache (chain);
+ }
+ return o;
+}
+
+/* $(sortfiles file1 ... fileN) and
+ $(rsortfiles file1 ... fileN) - same as $(sort ) and $(rsort ),
+ except for files rather than word tokens. See func_sort(). */
+
+static char *func_sortfiles (char *o, char **argv, const char *funcname)
+{
+ return common_sortfiles (o, argv, Q_IN_QUOTED | Q_RET_QUOTED | Q_SEP_SPACE,
+ funcname[0] != 'r',
+ funcname[0] == 'v' || funcname[1] == 'v');
+}
+
+/* $(qsortfiles style, file1 ... fileN) and
+ $(qrsortfiles style, file1 ... fileN) - same as $(sort ) and $(rsort ),
+ except for files rather than word tokens and the flexible style.
+ See func_sort(). */
+
+static char *func_q_sortfiles (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return common_sortfiles (o, &argv[1], style, funcname[1] != 'r',
+ funcname[1] == 'v' || funcname[2] == 'v');
+}
+
+
+/* Helper for determining whether the given path is absolute or not. */
+
+static int helper_is_abs (const char *path)
+{
+#ifdef HAVE_DOS_PATHS
+ return path[0] == '/'
+ || path[0] == '\\'
+ || (isalpha(path[0]) && path[1] == ':');
+#else
+ return path[0] == '/';
+#endif
+}
+
+/* Worker for func_q_abspath and func_q_abspath_ex. */
+
+static char *worker_abspath (char *o, char *line, const char *cwd,
+ size_t cwd_len, unsigned int style)
+{
+ if (line && *line != '\0')
+ {
+ PATH_VAR (outbuf);
+ struct nameseq *chain = helper_parse_file_list (line, style, 0);
+
+ /* Special case: single path, no cwd - no is_last path trouble */
+ if (chain && !chain->next && !cwd)
+ {
+ if (abspath (chain->name, outbuf))
+ o = helper_return_file(o, outbuf, style, 1);
+ free_ns_chain_no_strcache (chain);
+ }
+ else if (chain)
+ {
+ /* Pass one: replace names by absolute names */
+ struct nameseq *prev = NULL;
+ struct nameseq *cur = chain;
+ while (cur)
+ {
+ /* If relative path and we've got cwd, join cwd and it. */
+ if (cwd && !helper_is_abs (cur->name))
+ {
+ size_t len_name = strlen (cur->name);
+ char *name = xrealloc ((char *)cur->name, cwd_len + 1 + len_name + 1);
+ memmove (&name[cwd_len + 1], &name[0], len_name);
+ memcpy (name, cwd, cwd_len);
+ name[cwd_len] = '/';
+ name[cwd_len + 1 + len_name] = '\0';
+ cur->name = name;
+ }
+
+ if (abspath (cur->name, outbuf))
+ {
+ free ((char *)cur->name);
+ cur->name = xstrdup (outbuf);
+ prev = cur;
+ cur = cur->next;
+ }
+ else /* remove it */
+ cur = helper_unlink_and_free_ns (cur, prev, &chain);
+ }
+
+ /* Pass two: output */
+ o = helper_return_and_free_chain (o, chain, style);
+ }
+ }
+ return o;
+}
+
+/* $(qabspath style, file1 file2 ... fileN) - same as $(abspath ), except
+ for files rather than word tokens. See func_abspath(). */
+
+static char *func_q_abspath (char *o, char **argv, const char *funcname UNUSED)
+{
+ return worker_abspath (o, argv[1], NULL, 0,
+ helper_file_quoting_style (argv[0], Q_QDEFAULT));
+}
+
+# ifdef CONFIG_WITH_ABSPATHEX
+/* $(qabspathex style, file1 file2 ... fileN [,cwd]) - same as $(abspathex ),
+ except for files rather than word tokens. See func_abspath_ex(). */
+
+static char *
+func_q_abspathex (char *o, char **argv, const char *funcname UNUSED)
+{
+ /* cwd needs leading spaces chopped and may be optional,
+ in which case we're exactly like $(abspath ). */
+ char *cwd = argv[2];
+ if (cwd)
+ {
+ while (ISBLANK (*cwd))
+ cwd++;
+ if (*cwd == '\0')
+ cwd = NULL;
+ }
+
+ return worker_abspath (o, argv[1], cwd, cwd ? strlen (cwd) : 0,
+ helper_file_quoting_style (argv[0], Q_QDEFAULT));
+}
+# endif
+
+/* $(qaddprefix style, prefix, file1 ... fileN) and
+ $(qaddsuffix style, prefix, file1 ... fileN) - same as $(addprefix )
+ and $(addsuffix ) except for files rather than word tokens.
+ The suffix/prefix is unquoted on input and subjected to the same quoting
+ styling as the file names.
+ See func_addsuffix_addprefix(). */
+
+static char *func_q_addsuffix_addprefix (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ const char * const fix = argv[1];
+ size_t const fixlen = strlen (fix);
+ struct nameseq *chain = helper_parse_file_list (argv[2], style, 0);
+ if (chain)
+ {
+ size_t tmpsize = (fixlen + 512) & ~(size_t)63;
+ char *tmp = (char *)xmalloc (tmpsize);
+ struct nameseq *cur;
+
+ if (funcname[4] == 'p')
+ {
+ memcpy (tmp, fix, fixlen);
+ for (cur = chain; cur; cur = cur->next)
+ {
+ size_t curlen = strlen (cur->name);
+ if (fixlen + curlen + 1 <= tmpsize)
+ { /* likely */ }
+ else
+ {
+ tmpsize = (fixlen + curlen + 63) & ~(size_t)63;
+ tmp = (char *)xrealloc (tmp, tmpsize);
+ }
+ memcpy (&tmp[fixlen], cur->name, curlen + 1);
+ o = helper_return_file_len (o, tmp, fixlen + curlen,
+ style, cur->next == NULL);
+ }
+ }
+ else
+ for (cur = chain; cur; cur = cur->next)
+ {
+ size_t curlen = strlen (cur->name);
+ if (fixlen + curlen + 1 <= tmpsize)
+ { /* likely */ }
+ else
+ {
+ tmpsize = (fixlen + curlen + 63) & ~(size_t)63;
+ tmp = (char *)xrealloc (tmp, tmpsize);
+ }
+ memcpy (tmp, cur->name, curlen);
+ memcpy (&tmp[curlen], fix, fixlen + 1);
+
+ o = helper_return_file_len (o, tmp, fixlen + curlen,
+ style, cur->next == NULL);
+ }
+ free_ns_chain_no_strcache (chain);
+ }
+ return o;
+}
+
+/* $(qbasename style, path1 .. pathN) and $(qdir style, path1 .. pathN)
+ - same as $(basename ) and $(dir ), except for files rather than word tokens.
+ See func_basename_dir(). */
+
+static char *
+func_q_basename_dir (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ struct nameseq *cur;
+
+ int const is_basename = funcname[1] == 'b';
+ int const is_dir = !is_basename;
+ int const stop = MAP_DIRSEP | (is_basename ? MAP_DOT : 0) | MAP_NUL;
+
+ for (cur = chain; cur; cur = cur->next)
+ {
+ int const is_last = cur->next == NULL;
+ const char * const path = cur->name;
+ const char * const end = strchr (path, '\0');
+
+ /* Locate the last dot or path separator (P): */
+ const char *p = path != end ? end - 1 : end;
+ while (p >= path && !STOP_SET (*p, stop))
+ --p;
+
+ /* Do the outputting: */
+ if (p >= path && (is_dir))
+ o = helper_return_file_len (o, path, ++p - path, style, is_last);
+ else if (p >= path && *p == '.')
+ o = helper_return_file_len (o, path, p - path, style, is_last);
+#ifdef HAVE_DOS_PATHS
+ /* Handle the "d:foobar" case */
+ else if (path[0] && path[1] == ':' && is_dir)
+ o = helper_return_file_len (o, path, 2, style, is_last);
+#endif
+ else if (is_dir)
+#ifdef VMS
+ {
+ extern int vms_report_unix_paths;
+ o = helper_return_file_len (o, vms_report_unix_paths ? "./" : "[]",
+ 2, style, is_last);
+ }
+#else
+# ifndef _AMIGA
+ o = helper_return_file_len (o, "./", 2, style, is_last);
+# else
+ ; /* Just a nop... */
+# endif /* AMIGA */
+#endif /* !VMS */
+ else
+ /* The entire name is the basename. */
+ o = helper_return_file_len (o, path, end - path, style, is_last);
+ }
+
+ free_ns_chain_no_strcache (chain);
+ return o;
+}
+
+/* $(qnotdir style, path1 ... pathN) - same as $(notdir ), except for
+ files rather than word tokens. See func_notdir_suffix(). */
+
+static char *
+func_q_notdir (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ struct nameseq *cur;
+ int const stop = MAP_DIRSEP;
+ (void)funcname;
+
+ for (cur = chain; cur; cur = cur->next)
+ {
+ int const is_last = cur->next == NULL;
+ const char * const path = cur->name;
+ const char * const end = strchr(path, '\0');
+
+ /* Locate the last dot or path separator (P): */
+ const char *p = path != end ? end - 1 : end;
+ while (p >= path && ! STOP_SET (*p, stop))
+ --p;
+
+ if ((uintptr_t)p >= (uintptr_t)path)
+ o = helper_return_file_len (o, p + 1, end - p - 1, style, is_last);
+#ifdef HAVE_DOS_PATHS
+ else if (path[0] && path[1] == ':') /* "d:foo/bar" -> "foo/bar" */
+ o = helper_return_file_len (o, path + 2, end - path - 2, style, is_last);
+#endif
+ else
+ o = helper_return_file_len (o, path, end - path, style, is_last);
+ }
+
+ free_ns_chain_no_strcache (chain);
+ return o;
+}
+
+/* $(qsuffix style, path1 ... pathN) - same as $(suffix ), except for
+ files rather than word tokens. See func_notdir_suffix(). */
+
+static char *
+func_q_suffix (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT_VMS_TRICKS);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ struct nameseq *prev;
+ struct nameseq *cur;
+ int const stop = MAP_DIRSEP | MAP_DOT;
+ (void)funcname;
+
+ /* For suffixes we do a pre-pass that removes elements without suffixes.
+ This simplifies the handling of end-quoting. */
+ prev = NULL;
+ cur = chain;
+ while (cur)
+ {
+ const char * const path = cur->name;
+ if (strchr (path, '.') != NULL)
+ {
+ const char * const end = strchr (path, '\0');
+ const char *p = end - 1;
+ while ((uintptr_t)p >= (uintptr_t)path && ! STOP_SET (*p, stop))
+ --p;
+ if ((uintptr_t)p >= (uintptr_t)path && *p == '.')
+ {
+ if (p != path)
+ memmove ((char *)path, p, end - p + 1);
+ prev = cur;
+ cur = cur->next;
+ }
+ else /* remove it */
+ cur = helper_unlink_and_free_ns (cur, prev, &chain);
+ }
+ else /* remove it */
+ cur = helper_unlink_and_free_ns (cur, prev, &chain);
+ }
+
+ /* Output pass: */
+ return helper_return_and_free_chain (o, chain, style);
+}
+
+# ifdef CONFIG_WITH_ROOT_FUNC
+/*
+ $(qroot style, path...pathN) - same as $(root ), except files rather
+ than space delimited word tokens. See func_root().
+
+ This is mainly for dealing with drive letters and UNC paths on Windows
+ and OS/2.
+ */
+static char *
+func_q_root (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ struct nameseq *prev;
+ struct nameseq *cur;
+
+ /* First pass: Strip non-root components and remove rootless elements. */
+ prev = NULL;
+ cur = chain;
+ while (cur)
+ {
+ const char *path = cur->name;
+ const char *end = NULL;
+ char ch;
+
+# ifdef HAVE_DOS_PATHS
+ if (isalpha(path[0]) && path[1] == ':')
+ end = path + 2;
+ else if ( IS_PATHSEP(path[0])
+ && IS_PATHSEP(path[1])
+ && !IS_PATHSEP(path[2]) && path[2]
+ && path[3])
+ {
+ /* Min recognized UNC: "//./" - find the next slash
+ Typical root: "//srv/shr/" */
+ /* XXX: Check if //./ needs special handling. */
+ end = path + 3;
+ while ((ch = *end) != '\0' && !IS_PATHSEP(ch))
+ end++;
+
+ if (IS_PATHSEP(ch) && !IS_PATHSEP(end[1]))
+ {
+ end++;
+ while ((ch = *end) != '\0' && !IS_PATHSEP(ch))
+ end++;
+ }
+ else
+ end = NULL; /* invalid */
+ }
+ else if (IS_PATHSEP(*end))
+ end = path + 1;
+ else
+ end = NULL;
+
+# elif defined (VMS) || defined (AMGIA)
+ /* XXX: VMS and AMGIA */
+ OS (fatal, NILF, _("$(%s ) is not implemented on this platform"), funcname);
+# else
+ if (IS_PATHSEP(*path))
+ end = path + 1;
+# endif
+ if (end != NULL)
+ {
+ /* Include all subsequent path separators. */
+
+ while ((ch = *end) != '\0' && IS_PATHSEP(ch))
+ end++;
+ *(char *)end = '\0';
+
+ prev = cur;
+ cur = cur->next;
+ }
+ else
+ cur = helper_unlink_and_free_ns(cur, prev, &chain);
+ }
+
+ /* Second pass: Output */
+ return helper_return_and_free_chain (o, chain, style);
+}
+
+/*
+ $(qnotroot style, path1 .. pathN) - same as $(notroot ), except files
+ rather than space delimited word tokens. See func_notroot().
+
+ This is mainly for dealing with drive letters and UNC paths on Windows
+ and OS/2.
+ */
+static char *
+func_q_notroot (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+ struct nameseq *cur;
+
+ for (cur = chain; cur; cur = cur->next)
+ {
+ const char *start = cur->name;
+ char ch;
+
+# ifdef HAVE_DOS_PATHS
+ if (isalpha(start[0]) && start[1] == ':')
+ start += 2;
+ else if ( IS_PATHSEP(start[0])
+ && IS_PATHSEP(start[1])
+ && !IS_PATHSEP(start[2]) && start[2] != '\0'
+ && start[3] != '\0')
+ {
+ /* Min recognized UNC: "//./" - find the next slash
+ Typical root: "//srv/shr/" */
+ /* XXX: Check if //./ needs special handling. */
+ start += 3;
+ while ((ch = *start) != '\0' && !IS_PATHSEP(ch))
+ start++;
+
+ if (IS_PATHSEP(ch) && !IS_PATHSEP(start[1]))
+ {
+ start++;
+ while ((ch = *start) != '\0' && !IS_PATHSEP(ch))
+ start++;
+ }
+ else
+ start = cur->name; /* invalid UNC, pretend it's a couple unixy root slashes. */
+ }
+
+# elif defined (VMS) || defined (AMGIA)
+ /* XXX: VMS and AMGIA */
+ OS (fatal, NILF, _("$(%s) is not implemented on this platform"), funcname);
+# endif
+
+ /* Exclude all subsequent / leading path separators. */
+ while ((ch = *start) != '\0' && IS_PATHSEP(ch))
+ start++;
+
+ if (ch != '\0')
+ o = helper_return_file(o, start, style, cur->next == NULL);
+ else
+ o = helper_return_file_len (o, ".", 1, style, cur->next == NULL);
+ }
+
+ free_ns_chain_no_strcache (chain);
+ return o;
+}
+
+# endif
+
+/* $(qrealpath style, path1 .. pathN) - same as $(realpath ), except files
+ rather than space delimited word tokens. See func_realpath(). */
+
+static char *
+func_q_realpath (char *o, char **argv, const char *funcname UNUSED)
+{
+ PATH_VAR (outbuf);
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 0);
+
+ /* Pass one: Do the realpath/abspath thing and remove anything that fails
+ or doesn't exists. */
+ struct nameseq *cur = chain;
+ struct nameseq *prev = NULL;
+ while (cur)
+ {
+ char *result;
+#ifdef HAVE_REALPATH
+ ENULLLOOP (result, realpath (cur->name, outbuf));
+#else
+ result = abspath (cur->name, outbuf);
+#endif
+ if (result)
+ {
+ struct stat st;
+ int r;
+ EINTRLOOP (r, stat (outbuf, &st));
+ if (r == 0)
+ {
+ free ((char *)cur->name);
+ cur->name = xstrdup (result);
+ prev = cur;
+ cur = cur->next;
+ }
+ else
+ cur = helper_unlink_and_free_ns(cur, prev, &chain);
+ }
+ else
+ cur = helper_unlink_and_free_ns(cur, prev, &chain);
+ }
+
+ /* Pass two: Output. */
+ return helper_return_and_free_chain (o, chain, style);
+}
+
+/* $(qwildcard path1 .. pathN [, style]) - same as $(wildcard ), except files
+ rather than space delimited word tokens. See func_wildcard(). */
+
+static char *
+func_q_wildcard (char *o, char **argv, const char *funcname UNUSED)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ struct nameseq *chain = helper_parse_file_list (argv[1], style, 1 /*glob*/);
+#ifdef _AMIGA
+ OS (fatal, NILF, _("$(%s ) is not implemented on this platform"), funcname);
+#endif
+ return helper_return_and_free_chain (o, chain, style);
+}
+
+static char *
+worker_filter_filterout (char *o, char **argv, unsigned style, int is_filter)
+{
+ struct nameseq *wordchain;
+ struct a_word *wordhead;
+ struct a_word **wordtail;
+ struct a_word *wp;
+ struct nameseq *patchain;
+ struct a_pattern *pathead;
+ struct a_pattern **pattail;
+ struct a_pattern *pp;
+ struct nameseq *cur;
+ struct hash_table a_word_table;
+ int literals;
+ int words;
+ int hashing;
+ unsigned int words_len; /* for output estimation */
+
+ /* Chop ARGV[0] up into patterns to match against the words. */
+ /** @todo this very inefficient as we convert between two list format and
+ * duplicates the patterns on the heap. We could just modify argv[0]
+ * directly. */
+ patchain = helper_parse_file_list (argv[0], style, 0 /*glob*/);
+ pattail = &pathead;
+ for (cur = patchain, literals = 0; cur; cur = cur->next)
+ {
+ struct a_pattern *pat = alloca (sizeof (struct a_pattern));
+
+ *pattail = pat;
+ pattail = &pat->next;
+
+ pat->str = (char *)cur->name; /* (safe - PARSEFS_NOCACHE) */
+ pat->percent = find_percent (pat->str); /* may modify name */
+ if (pat->percent == 0)
+ literals++;
+ pat->sfxlen = pat->percent ? strlen(pat->percent + 1) : 0;
+ pat->length = strlen (pat->str);
+ }
+ *pattail = NULL;
+
+ /* Chop ARGV[1] up into words to match against the patterns. */
+ /** @todo this very inefficient as we convert between two list format and
+ * duplicates the words on the heap. We could just modify argv[1]
+ * directly. */
+ wordchain = helper_parse_file_list (argv[1], style, 0 /*glob*/);
+ wordtail = &wordhead;
+ for (cur = wordchain, words = 0, words_len = 0; cur; cur = cur->next)
+ {
+ struct a_word *word = alloca (sizeof (struct a_word));
+
+ *wordtail = word;
+ wordtail = &word->next;
+
+ word->str = (char *)cur->name; /* (safe - PARSEFS_NOCACHE) */
+ word->length = strlen (cur->name);
+ words_len += word->length + 1;
+ word->matched = 0;
+ word->chain = NULL;
+ words++;
+ }
+ *wordtail = NULL;
+
+ /* Only use a hash table if arg list lengths justifies the cost. */
+ hashing = (literals >= 2 && (literals * words) >= 10);
+ if (hashing)
+ {
+ hash_init (&a_word_table, words, a_word_hash_1, a_word_hash_2,
+ a_word_hash_cmp);
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ {
+ struct a_word *owp = hash_insert (&a_word_table, wp);
+ if (owp)
+ wp->chain = owp;
+ }
+ }
+
+ if (words)
+ {
+ int doneany = 0;
+
+ if (is_filter)
+ words_len = 0;
+
+ /* Run each pattern through the words, killing words. */
+ for (pp = pathead; pp != 0; pp = pp->next)
+ {
+ if (pp->percent)
+ {
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ if (!wp->matched
+ && pattern_matches_ex (pp->str, pp->percent, pp->sfxlen,
+ wp->str, wp->length))
+ {
+ wp->matched = 1;
+ if (is_filter)
+ words_len += wp->length + 1;
+ else
+ words_len -= wp->length + 1;
+ }
+ }
+ else if (hashing)
+ {
+ struct a_word a_word_key;
+ a_word_key.str = pp->str;
+ a_word_key.length = pp->length;
+ wp = hash_find_item (&a_word_table, &a_word_key);
+ while (wp)
+ {
+ if (!wp->matched)
+ {
+ wp->matched = 1;
+ if (is_filter)
+ words_len += wp->length + 1;
+ else
+ words_len -= wp->length + 1;
+ }
+ wp = wp->chain;
+ }
+ }
+ else
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ if (!wp->matched
+ && wp->length == pp->length
+ && strneq (pp->str, wp->str, wp->length))
+ {
+ wp->matched = 1;
+ if (is_filter)
+ words_len += wp->length + 1;
+ else
+ words_len -= wp->length + 1;
+ }
+ }
+
+ /* Output the words that matched (or didn't, for filter-out). */
+ o = ensure_variable_buffer_space (o, words_len);
+
+ for (wp = wordhead; wp != 0; wp = wp->next)
+ if (wp->matched == is_filter)
+ {
+ o = helper_return_file_len (o, wp->str, wp->length,
+ style, 0 /*is_last*/);
+ doneany = 1;
+ }
+
+ /* Kill the last separator. */
+ if (doneany)
+ o = helper_drop_separator (o, style);
+ }
+
+ /* Clean up. */
+ if (hashing)
+ hash_free (&a_word_table, 0);
+ free_ns_chain_no_strcache (wordchain);
+ free_ns_chain_no_strcache (patchain);
+
+ return o;
+}
+
+/* Implements $(qfilter ) and $(qfilter-out ). */
+static char *
+func_q_filter_filterout (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return worker_filter_filterout (o, &argv[1], style, funcname[7] == '\0');
+}
+
+#endif /* KMK */
+
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+
+/* Helper that parses the index argument for the $(deps* ) and $(qdeps* )
+ functions. */
+static unsigned int parse_dep_index (const char *index, const char *funcname)
+{
+ unsigned int idx = 0;
+ if (index)
+ {
+ while (ISSPACE (*index))
+ index++;
+ if (*index != '\0')
+ {
+ char *next = (char *)index;
+ long l = strtol (index, &next, 0);
+ while (ISSPACE (*next))
+ next++;
+ idx = (unsigned int)l;
+ if (*next != '\0' || l < 0 || (long)idx != l)
+ OSS (fatal, NILF, _("%s: invalid index value: `%s'\n"), funcname, index);
+ }
+ }
+ return idx;
+}
+
+/* Helper that calculates the output length for a dependency */
+
+MY_INLINE unsigned int helper_dep_output_len (struct dep *d)
+{
+ const char *c = dep_name (d);
+#ifndef NO_ARCHIVES
+ if (ar_name (c))
+ return strlen (strchr (c, '(') + 1);
+#endif
+#ifdef CONFIG_WITH_STRCACHE2
+ if (!d->need_2nd_expansion)
+ return strcache2_get_len (&file_strcache, c) + 1;
+#endif
+ return strlen (c) + 1;
+}
+
+/* Helper that outputs a depndency. */
+
+MY_INLINE char *helper_dep_output_one (char *o, struct dep *d,
+ unsigned int style, int is_last)
+{
+ const char *c = dep_name (d);
+#ifndef NO_ARCHIVES
+ if (ar_name (c))
+ {
+ c = strchr(c, '(') + 1;
+ o = helper_return_file_len (o, c, strlen(c) - 1, style, is_last);
+ }
+ else
+#endif
+#ifdef CONFIG_WITH_STRCACHE2
+ if (!d->need_2nd_expansion)
+ {
+ unsigned int len = strcache2_get_len (&file_strcache, c);
+ o = helper_return_file_len (o, c, len, style, is_last);
+ }
+ else
+#endif
+ o = helper_return_file (o, c, style, is_last);
+ return o;
+}
+
+/* Implements $^/$(deps )/$(qdeps ) and $+/$(deps-all )/$(qdeps-all ).
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+worker_deps (char *o, char **argv, const char *funcname, unsigned int style,
+ int all_deps)
+{
+ unsigned int idx = parse_dep_index (argv[1], funcname);
+ struct file *file;
+
+ /* Find the file and select the list corresponding to FUNCNAME. */
+
+ file = lookup_file (argv[0]);
+ if (file)
+ {
+ struct dep *deps;
+ struct dep *d;
+ if (!all_deps)
+ {
+ deps = file->deps_no_dupes;
+ if (!deps && file->deps)
+ deps = file->deps_no_dupes = create_uniqute_deps_chain (file->deps);
+ }
+ else
+ deps = file->deps;
+
+ if ( file->double_colon
+ && ( file->double_colon != file
+ || file->last != file))
+ OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"),
+ funcname, file->name);
+
+ if (idx == 0 /* all */)
+ {
+ /* Since this may be a long list, calculate the output space needed if
+ no quoting takes place and no multichar separators are used. */
+
+ unsigned int total_len = 0;
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime)
+ total_len += helper_dep_output_len (d);
+ if (total_len > 0)
+ {
+ o = ensure_variable_buffer_space (o, total_len);
+
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime)
+ o = helper_dep_output_one (o, d, style, 0 /*is_last*/);
+
+ /* nuke the last list separator */
+ o = helper_drop_separator (o, style);
+ }
+ }
+ else
+ {
+ /* Dependency given by index. */
+
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime)
+ if (--idx == 0) /* 1 based indexing */
+ return helper_dep_output_one (o, d, style, 1 /*is_last*/);
+ }
+ }
+
+ return o;
+}
+
+/* Implements $? / $(deps-newer ) / $(qdeps-newer ).
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+worker_deps_newer (char *o, char **argv, const char *funcname, unsigned int style)
+{
+ unsigned int idx = parse_dep_index (argv[1], funcname);
+ struct file *file;
+
+ /* Find the file. */
+
+ file = lookup_file (argv[0]);
+ if (file)
+ {
+ struct dep *deps = file->deps;
+ struct dep *d;
+
+ if ( file->double_colon
+ && ( file->double_colon != file
+ || file->last != file))
+ OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"),
+ funcname, file->name);
+
+ if (idx == 0 /* all */)
+ {
+ unsigned int total_len = 0;
+
+ /* calc the result length. */
+
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime && d->changed)
+ total_len += helper_dep_output_len (d);
+ if (total_len)
+ {
+ o = ensure_variable_buffer_space (o, total_len);
+
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime && d->changed)
+ o = helper_dep_output_one (o, d, style, 0 /*is_last*/);
+
+ /* nuke the last list separator */
+ o = helper_drop_separator (o, style);
+ }
+ }
+ else
+ {
+ /* Dependency given by index. */
+
+ for (d = deps; d; d = d->next)
+ if (!d->ignore_mtime && d->changed)
+ if (--idx == 0) /* 1 based indexing */
+ return helper_dep_output_one (o, d, style, 1 /*is_last*/);
+ }
+ }
+
+ return o;
+}
+
+/* Implements $|, the order only dependency list.
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+worker_deps_order_only (char *o, char **argv, const char *funcname, unsigned int style)
+{
+ unsigned int idx = parse_dep_index (argv[1], funcname);
+ struct file *file;
+
+ /* Find the file. */
+
+ file = lookup_file (argv[0]);
+ if (file)
+ {
+ struct dep *deps = file->deps;
+ struct dep *d;
+
+ if ( file->double_colon
+ && ( file->double_colon != file
+ || file->last != file))
+ OSS (error, NILF, _("$(%s ) cannot be used on files with multiple double colon rules like `%s'\n"),
+ funcname, file->name);
+
+ if (idx == 0 /* all */)
+ {
+ unsigned int total_len = 0;
+
+ /* calc the result length. */
+
+ for (d = deps; d; d = d->next)
+ if (d->ignore_mtime)
+ total_len += helper_dep_output_len (d);
+ if (total_len)
+ {
+ o = ensure_variable_buffer_space (o, total_len);
+
+ for (d = deps; d; d = d->next)
+ if (d->ignore_mtime)
+ o = helper_dep_output_one (o, d, style, 0 /*is_last*/);
+
+ /* nuke the last list separator */
+ o = helper_drop_separator (o, style);
+ }
+ }
+ else
+ {
+ /* Dependency given by index. */
+
+ for (d = deps; d; d = d->next)
+ if (d->ignore_mtime)
+ if (--idx == 0) /* 1 based indexing */
+ return helper_dep_output_one (o, d, style, 1 /*is_last*/);
+ }
+ }
+
+ return o;
+}
+
+/* Implements $^ and $+.
+
+ The first comes with FUNCNAME 'deps', the second as 'deps-all'.
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_deps (char *o, char **argv, const char *funcname)
+{
+# ifdef VMS
+ return worker_deps (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA,
+# else
+ return worker_deps (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE,
+# endif
+ funcname[4] != '\0');
+}
+
+/* Implements $?.
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_deps_newer (char *o, char **argv, const char *funcname)
+{
+# ifdef VMS
+ return worker_deps_newer (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA);
+# else
+ return worker_deps_newer (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE);
+# endif
+}
+
+/* Implements $|, the order only dependency list.
+
+ If no second argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the second argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_deps_order_only (char *o, char **argv, const char *funcname)
+{
+# ifdef VMS
+ return worker_deps_order_only (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_COMMA);
+# else
+ return worker_deps_order_only (o, argv, funcname, Q_RET_UNQUOTED | Q_SEP_SPACE);
+# endif
+}
+
+#endif /* CONFIG_WITH_LAZY_DEPS_VARS */
+#ifdef KMK
+
+/* Implements $(qdeps ) and $(qdeps-all )
+
+ If no third argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the third argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_q_deps (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return worker_deps (o, &argv[1], funcname, style, funcname[5] != '\0');
+}
+
+/* Implements $(qdeps-newer ).
+
+ If no third argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the third argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_q_deps_newer (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return worker_deps_newer (o, &argv[1], funcname, style);
+}
+
+/* Implements $(qdeps-oo ), the order only dependency list.
+
+ If no third argument is given, or if it's empty, or if it's zero,
+ all dependencies will be returned. If the third argument is non-zero
+ the dependency at that position will be returned. If the argument is
+ negative a fatal error is thrown. */
+static char *
+func_q_deps_order_only (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return worker_deps_order_only (o, &argv[1], funcname, style);
+}
+
+/* Implements $(qtarget ), converting a single unquoted file to the given
+ quoting style.
+
+ Typically used like this:
+ $(qtarget sh,$@)
+ $(qone-unquoted sh,$@) */
+static char *
+func_q_one_unquoted (char *o, char **argv, const char *funcname)
+{
+ unsigned int const style = helper_file_quoting_style (argv[0], Q_QDEFAULT);
+ return helper_return_file (o, argv[1], style, 1 /*is_last*/);
+}
+
+#endif /* KMK */
+
+/* Lookup table for builtin functions.
+
+ This doesn't have to be sorted; we use a straight lookup. We might gain
+ some efficiency by moving most often used functions to the start of the
+ table.
+
+ If MAXIMUM_ARGS is 0, that means there is no maximum and all
+ comma-separated values are treated as arguments.
+
+ EXPAND_ARGS means that all arguments should be expanded before invocation.
+ Functions that do namespace tricks (foreach) don't automatically expand. */
+
+static char *func_call (char *o, char **argv, const char *funcname);
+
+#define FT_ENTRY(_name, _min, _max, _exp, _func) \
+ { { (_func) }, STRING_SIZE_TUPLE(_name), (_min), (_max), (_exp), 0 }
+
+static struct function_table_entry function_table_init[] =
+{
+ /* Name MIN MAX EXP? Function */
+ FT_ENTRY ("abspath", 0, 1, 1, func_abspath),
+ FT_ENTRY ("addprefix", 2, 2, 1, func_addsuffix_addprefix),
+ FT_ENTRY ("addsuffix", 2, 2, 1, func_addsuffix_addprefix),
+ FT_ENTRY ("basename", 0, 1, 1, func_basename_dir),
+ FT_ENTRY ("dir", 0, 1, 1, func_basename_dir),
+ FT_ENTRY ("notdir", 0, 1, 1, func_notdir_suffix),
+#ifdef CONFIG_WITH_ROOT_FUNC
+ FT_ENTRY ("root", 0, 1, 1, func_root),
+ FT_ENTRY ("notroot", 0, 1, 1, func_notroot),
+#endif
+ FT_ENTRY ("subst", 3, 3, 1, func_subst),
+ FT_ENTRY ("suffix", 0, 1, 1, func_notdir_suffix),
+ FT_ENTRY ("filter", 2, 2, 1, func_filter_filterout),
+ FT_ENTRY ("filter-out", 2, 2, 1, func_filter_filterout),
+ FT_ENTRY ("findstring", 2, 2, 1, func_findstring),
+#ifdef CONFIG_WITH_DEFINED_FUNCTIONS
+ FT_ENTRY ("firstdefined", 0, 2, 1, func_firstdefined),
+#endif
+ FT_ENTRY ("firstword", 0, 1, 1, func_firstword),
+ FT_ENTRY ("flavor", 0, 1, 1, func_flavor),
+ FT_ENTRY ("join", 2, 2, 1, func_join),
+#ifdef CONFIG_WITH_DEFINED_FUNCTIONS
+ FT_ENTRY ("lastdefined", 0, 2, 1, func_lastdefined),
+#endif
+ FT_ENTRY ("lastword", 0, 1, 1, func_lastword),
+ FT_ENTRY ("patsubst", 3, 3, 1, func_patsubst),
+ FT_ENTRY ("realpath", 0, 1, 1, func_realpath),
+#ifdef CONFIG_WITH_RSORT
+ FT_ENTRY ("rsort", 0, 1, 1, func_sort),
+# ifdef KMK
+ FT_ENTRY ("rversort", 0, 1, 1, func_sort),
+# endif
+#endif
+ FT_ENTRY ("shell", 0, 1, 1, func_shell),
+ FT_ENTRY ("sort", 0, 1, 1, func_sort),
+# ifdef KMK
+ FT_ENTRY ("versort", 0, 1, 1, func_sort),
+# endif
+ FT_ENTRY ("strip", 0, 1, 1, func_strip),
+#ifdef CONFIG_WITH_WHERE_FUNCTION
+ FT_ENTRY ("where", 0, 1, 1, func_where),
+#endif
+ FT_ENTRY ("wildcard", 0, 1, 1, func_wildcard),
+ FT_ENTRY ("word", 2, 2, 1, func_word),
+ FT_ENTRY ("wordlist", 3, 3, 1, func_wordlist),
+ FT_ENTRY ("words", 0, 1, 1, func_words),
+ FT_ENTRY ("origin", 0, 1, 1, func_origin),
+ FT_ENTRY ("foreach", 3, 3, 0, func_foreach),
+#ifdef CONFIG_WITH_LOOP_FUNCTIONS
+ FT_ENTRY ("for", 4, 4, 0, func_for),
+ FT_ENTRY ("while", 2, 2, 0, func_while),
+#endif
+ FT_ENTRY ("call", 1, 0, 1, func_call),
+ FT_ENTRY ("info", 0, 1, 1, func_error),
+ FT_ENTRY ("error", 0, 1, 1, func_error),
+ FT_ENTRY ("warning", 0, 1, 1, func_error),
+ FT_ENTRY ("if", 2, 3, 0, func_if),
+ FT_ENTRY ("or", 1, 0, 0, func_or),
+ FT_ENTRY ("and", 1, 0, 0, func_and),
+ FT_ENTRY ("value", 0, 1, 1, func_value),
+#ifdef EXPERIMENTAL
+ FT_ENTRY ("eq", 2, 2, 1, func_eq),
+ FT_ENTRY ("not", 0, 1, 1, func_not),
+#endif
+ FT_ENTRY ("eval", 0, 1, 1, func_eval),
+#ifdef CONFIG_WITH_EVALPLUS
+ FT_ENTRY ("evalctx", 0, 1, 1, func_evalctx),
+ FT_ENTRY ("evalval", 1, 1, 1, func_evalval),
+ FT_ENTRY ("evalvalctx", 1, 1, 1, func_evalval),
+ FT_ENTRY ("evalcall", 1, 0, 1, func_call),
+ FT_ENTRY ("evalcall2", 1, 0, 1, func_call),
+ FT_ENTRY ("eval-opt-var", 1, 0, 1, func_eval_optimize_variable),
+#endif
+ FT_ENTRY ("file", 1, 2, 1, func_file),
+#ifdef CONFIG_WITH_STRING_FUNCTIONS
+ FT_ENTRY ("length", 1, 1, 1, func_length),
+ FT_ENTRY ("length-var", 1, 1, 1, func_length_var),
+ FT_ENTRY ("insert", 2, 5, 1, func_insert),
+ FT_ENTRY ("pos", 2, 3, 1, func_pos),
+ FT_ENTRY ("lastpos", 2, 3, 1, func_pos),
+ FT_ENTRY ("substr", 2, 4, 1, func_substr),
+ FT_ENTRY ("translate", 2, 4, 1, func_translate),
+#endif
+#ifdef CONFIG_WITH_PRINTF
+ FT_ENTRY ("printf", 1, 0, 1, kmk_builtin_func_printf),
+#endif
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+ FT_ENTRY ("deps", 1, 2, 1, func_deps),
+ FT_ENTRY ("deps-all", 1, 2, 1, func_deps),
+ FT_ENTRY ("deps-newer", 1, 2, 1, func_deps_newer),
+ FT_ENTRY ("deps-oo", 1, 2, 1, func_deps_order_only),
+#endif
+#ifdef CONFIG_WITH_DEFINED
+ FT_ENTRY ("defined", 1, 1, 1, func_defined),
+#endif
+#ifdef CONFIG_WITH_TOUPPER_TOLOWER
+ FT_ENTRY ("toupper", 0, 1, 1, func_toupper_tolower),
+ FT_ENTRY ("tolower", 0, 1, 1, func_toupper_tolower),
+#endif
+#ifdef CONFIG_WITH_ABSPATHEX
+ FT_ENTRY ("abspathex", 0, 2, 1, func_abspathex),
+#endif
+#ifdef CONFIG_WITH_XARGS
+ FT_ENTRY ("xargs", 2, 0, 1, func_xargs),
+#endif
+#if defined(CONFIG_WITH_VALUE_LENGTH) && defined(CONFIG_WITH_COMPARE)
+ FT_ENTRY ("comp-vars", 3, 3, 1, func_comp_vars),
+ FT_ENTRY ("comp-cmds", 3, 3, 1, func_comp_vars),
+ FT_ENTRY ("comp-cmds-ex", 3, 3, 1, func_comp_cmds_ex),
+#endif
+#ifdef CONFIG_WITH_DATE
+ FT_ENTRY ("date", 0, 1, 1, func_date),
+ FT_ENTRY ("date-utc", 0, 3, 1, func_date),
+#endif
+#ifdef CONFIG_WITH_FILE_SIZE
+ FT_ENTRY ("file-size", 1, 1, 1, func_file_size),
+#endif
+#ifdef CONFIG_WITH_WHICH
+ FT_ENTRY ("which", 0, 0, 1, func_which),
+#endif
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+ FT_ENTRY ("expr", 1, 1, 0, func_expr),
+ FT_ENTRY ("if-expr", 2, 3, 0, func_if_expr),
+ FT_ENTRY ("select", 2, 0, 0, func_select),
+#endif
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+ FT_ENTRY ("intersects", 2, 2, 1, func_set_intersects),
+#endif
+#ifdef CONFIG_WITH_STACK
+ FT_ENTRY ("stack-push", 2, 2, 1, func_stack_push),
+ FT_ENTRY ("stack-pop", 1, 1, 1, func_stack_pop_top),
+ FT_ENTRY ("stack-popv", 1, 1, 1, func_stack_pop_top),
+ FT_ENTRY ("stack-top", 1, 1, 1, func_stack_pop_top),
+#endif
+#ifdef CONFIG_WITH_MATH
+ FT_ENTRY ("int-add", 2, 0, 1, func_int_add),
+ FT_ENTRY ("int-sub", 2, 0, 1, func_int_sub),
+ FT_ENTRY ("int-mul", 2, 0, 1, func_int_mul),
+ FT_ENTRY ("int-div", 2, 0, 1, func_int_div),
+ FT_ENTRY ("int-mod", 2, 2, 1, func_int_mod),
+ FT_ENTRY ("int-not", 1, 1, 1, func_int_not),
+ FT_ENTRY ("int-and", 2, 0, 1, func_int_and),
+ FT_ENTRY ("int-or", 2, 0, 1, func_int_or),
+ FT_ENTRY ("int-xor", 2, 0, 1, func_int_xor),
+ FT_ENTRY ("int-eq", 2, 2, 1, func_int_cmp),
+ FT_ENTRY ("int-ne", 2, 2, 1, func_int_cmp),
+ FT_ENTRY ("int-gt", 2, 2, 1, func_int_cmp),
+ FT_ENTRY ("int-ge", 2, 2, 1, func_int_cmp),
+ FT_ENTRY ("int-lt", 2, 2, 1, func_int_cmp),
+ FT_ENTRY ("int-le", 2, 2, 1, func_int_cmp),
+#endif
+#ifdef CONFIG_WITH_NANOTS
+ FT_ENTRY ("nanots", 0, 0, 0, func_nanots),
+#endif
+#ifdef CONFIG_WITH_OS2_LIBPATH
+ FT_ENTRY ("libpath", 1, 2, 1, func_os2_libpath),
+#endif
+#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+ FT_ENTRY ("make-stats", 0, 0, 0, func_make_stats),
+#endif
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+ FT_ENTRY ("commands", 1, 1, 1, func_commands),
+ FT_ENTRY ("commands-sc", 1, 1, 1, func_commands),
+ FT_ENTRY ("commands-usr", 2, 2, 1, func_commands),
+#endif
+#ifdef KMK_HELPERS
+ FT_ENTRY ("kb-src-tool", 1, 2, 0, func_kbuild_source_tool),
+ FT_ENTRY ("kb-obj-base", 1, 2, 0, func_kbuild_object_base),
+ FT_ENTRY ("kb-obj-suff", 1, 2, 0, func_kbuild_object_suffix),
+ FT_ENTRY ("kb-src-prop", 3, 4, 0, func_kbuild_source_prop),
+ FT_ENTRY ("kb-src-one", 0, 1, 0, func_kbuild_source_one),
+ FT_ENTRY ("kb-exp-tmpl", 6, 6, 1, func_kbuild_expand_template),
+#endif
+#ifdef KMK
+ FT_ENTRY ("dircache-ctl", 1, 0, 1, func_dircache_ctl),
+ FT_ENTRY ("breakpoint", 0, 0, 0, func_breakpoint),
+ FT_ENTRY ("set-umask", 1, 3, 1, func_set_umask),
+ FT_ENTRY ("get-umask", 0, 0, 0, func_get_umask),
+#endif
+#ifdef KMK
+ FT_ENTRY ("quote", 1, 0, 1, func_quote_make),
+ FT_ENTRY ("quote-dep", 1, 0, 1, func_quote_make),
+ FT_ENTRY ("quote-tgt", 1, 0, 1, func_quote_make),
+ FT_ENTRY ("quote-depend", 1, 0, 1, func_quote_make),
+ FT_ENTRY ("quote-tgtend", 1, 0, 1, func_quote_make),
+ FT_ENTRY ("quote-sh", 1, 0, 1, func_quote_shell),
+ FT_ENTRY ("quote-sh-dq", 1, 1, 1, func_quote_shell_dq),
+ FT_ENTRY ("quote-sh-sq", 1, 1, 1, func_quote_shell_sq),
+ FT_ENTRY ("requote", 1, 0, 1, func_requote),
+ /* Quoted input and maybe output variants of functions typically
+ working with files: */
+ FT_ENTRY ("firstfile", 0, 1, 1, func_firstfile),
+ FT_ENTRY ("lastfile", 0, 1, 1, func_lastfile),
+ FT_ENTRY ("filelist", 3, 3, 1, func_filelist),
+ FT_ENTRY ("countfiles", 0, 1, 1, func_countfiles),
+ FT_ENTRY ("foreachfile", 3, 3, 0, func_foreachfile),
+ FT_ENTRY ("sortfiles", 0, 1, 1, func_sortfiles),
+ FT_ENTRY ("versortfiles", 0, 1, 1, func_sortfiles),
+# ifdef CONFIG_WITH_RSORT
+ FT_ENTRY ("rsortfiles", 0, 1, 1, func_sortfiles),
+ FT_ENTRY ("rversortfiles", 0, 1, 1, func_sortfiles),
+# endif
+ /* Function variants with preceding style argument and quoting by default. */
+ FT_ENTRY ("qfirstfile", 1+0, 1+1, 1, func_q_firstfile),
+ FT_ENTRY ("qlastfile", 1+0, 1+1, 1, func_q_lastfile),
+ FT_ENTRY ("qfilelist", 1+3, 1+3, 1, func_q_filelist),
+ FT_ENTRY ("qcountfiles", 1+0, 1+1, 1, func_q_countfiles),
+ FT_ENTRY ("qforeachfile", 1+3, 1+3, 0, func_q_foreachfile),
+ FT_ENTRY ("qsortfiles", 1+0, 1+1, 1, func_q_sortfiles),
+ FT_ENTRY ("qversortfiles",1+0, 1+1, 1, func_q_sortfiles),
+# ifdef CONFIG_WITH_RSORT
+ FT_ENTRY ("qrsortfiles", 1+0, 1+1, 1, func_q_sortfiles),
+ FT_ENTRY ("qrversortfiles",1+0,1+1, 1, func_q_sortfiles),
+# endif
+ FT_ENTRY ("qabspath", 1+0, 1+1, 1, func_q_abspath),
+ FT_ENTRY ("qaddprefix", 1+2, 1+2, 1, func_q_addsuffix_addprefix),
+ FT_ENTRY ("qaddsuffix", 1+2, 1+2, 1, func_q_addsuffix_addprefix),
+ FT_ENTRY ("qbasename", 1+0, 1+1, 1, func_q_basename_dir),
+ FT_ENTRY ("qdir", 1+0, 1+1, 1, func_q_basename_dir),
+ FT_ENTRY ("qnotdir", 1+0, 1+1, 1, func_q_notdir),
+# ifdef CONFIG_WITH_ROOT_FUNC
+ FT_ENTRY ("qroot", 1+0, 1+1, 1, func_q_root),
+ FT_ENTRY ("qnotroot", 1+0, 1+1, 1, func_q_notroot),
+# endif
+ FT_ENTRY ("qsuffix", 1+0, 1+1, 1, func_q_suffix),
+ FT_ENTRY ("qrealpath", 1+0, 1+1, 1, func_q_realpath),
+# ifdef CONFIG_WITH_ABSPATHEX
+ FT_ENTRY ("qabspathex", 1+0, 1+2, 1, func_q_abspathex),
+# endif
+ FT_ENTRY ("qwildcard", 1+0, 1+1, 1, func_q_wildcard),
+ FT_ENTRY ("qone-unquoted",1+1, 1+1, 1, func_q_one_unquoted),
+ FT_ENTRY ("qtarget", 1+1, 1+1, 1, func_q_one_unquoted), /* For requoting plain $@ to given style. */
+ FT_ENTRY ("qdeps", 1+1, 1+2, 1, func_q_deps), /* $^ with quoting style */
+ FT_ENTRY ("qdeps-all", 1+1, 1+2, 1, func_q_deps), /* $+ with quoting style */
+ FT_ENTRY ("qdeps-newer", 1+1, 1+2, 1, func_q_deps_newer), /* $? with quoting style */
+ FT_ENTRY ("qdeps-oo", 1+1, 1+2, 1, func_q_deps_order_only), /* $| with quoting style */
+ FT_ENTRY ("qfilter", 1+2, 1+2, 1, func_q_filter_filterout),
+ FT_ENTRY ("qfilter-out", 1+2, 1+2, 1, func_q_filter_filterout),
+ /** @todo XXX: Add more qxxxx variants. */
+#endif
+};
+
+#define FUNCTION_TABLE_ENTRIES (sizeof (function_table_init) / sizeof (struct function_table_entry))
+
+
+/* These must come after the definition of function_table. */
+
+static char *
+expand_builtin_function (char *o, int argc, char **argv,
+ const struct function_table_entry *entry_p)
+{
+ char *p;
+
+ if (argc < (int)entry_p->minimum_args)
+ fatal (*expanding_var, strlen (entry_p->name),
+ _("insufficient number of arguments (%d) to function '%s'"),
+ argc, entry_p->name);
+
+ /* I suppose technically some function could do something with no arguments,
+ but so far no internal ones do, so just test it for all functions here
+ rather than in each one. We can change it later if necessary. */
+
+ if (!argc && !entry_p->alloc_fn)
+ return o;
+
+ if (!entry_p->fptr.func_ptr)
+ OS (fatal, *expanding_var,
+ _("unimplemented on this platform: function '%s'"), entry_p->name);
+
+ if (!entry_p->alloc_fn)
+ return entry_p->fptr.func_ptr (o, argv, entry_p->name);
+
+ /* This function allocates memory and returns it to us.
+ Write it to the variable buffer, then free it. */
+
+ p = entry_p->fptr.alloc_func_ptr (entry_p->name, argc, argv);
+ if (p)
+ {
+ o = variable_buffer_output (o, p, strlen (p));
+ free (p);
+ }
+
+ return o;
+}
+
+/* Check for a function invocation in *STRINGP. *STRINGP points at the
+ opening ( or { and is not null-terminated. If a function invocation
+ is found, expand it into the buffer at *OP, updating *OP, incrementing
+ *STRINGP past the reference and returning nonzero. If not, return zero. */
+
+static int
+handle_function2 (const struct function_table_entry *entry_p, char **op, const char **stringp) /* bird split it up. */
+{
+ char openparen = (*stringp)[0];
+ char closeparen = openparen == '(' ? ')' : '}';
+ const char *beg;
+ const char *end;
+ int count = 0;
+ char *abeg = NULL;
+ char **argv, **argvp;
+ int nargs;
+
+ beg = *stringp + 1;
+
+ /* We found a builtin function. Find the beginning of its arguments (skip
+ whitespace after the name). */
+
+ beg += entry_p->len;
+ NEXT_TOKEN (beg);
+
+ /* Find the end of the function invocation, counting nested use of
+ whichever kind of parens we use. Since we're looking, count commas
+ to get a rough estimate of how many arguments we might have. The
+ count might be high, but it'll never be low. */
+
+ for (nargs=1, end=beg; *end != '\0'; ++end)
+ if (*end == ',')
+ ++nargs;
+ else if (*end == openparen)
+ ++count;
+ else if (*end == closeparen && --count < 0)
+ break;
+
+ if (count >= 0)
+ fatal (*expanding_var, strlen (entry_p->name),
+ _("unterminated call to function '%s': missing '%c'"),
+ entry_p->name, closeparen);
+
+ *stringp = end;
+
+ /* Get some memory to store the arg pointers. */
+ argvp = argv = alloca (sizeof (char *) * (nargs + 2));
+
+ /* Chop the string into arguments, then a nul. As soon as we hit
+ MAXIMUM_ARGS (if it's >0) assume the rest of the string is part of the
+ last argument.
+
+ If we're expanding, store pointers to the expansion of each one. If
+ not, make a duplicate of the string and point into that, nul-terminating
+ each argument. */
+
+ if (entry_p->expand_args)
+ {
+ const char *p;
+ for (p=beg, nargs=0; p <= end; ++argvp)
+ {
+ const char *next;
+
+ ++nargs;
+
+ if (nargs == entry_p->maximum_args
+ || (! (next = find_next_argument (openparen, closeparen, p, end))))
+ next = end;
+
+ *argvp = expand_argument (p, next);
+ p = next + 1;
+ }
+ }
+ else
+ {
+ int len = end - beg;
+ char *p, *aend;
+
+ abeg = xmalloc (len+1);
+ memcpy (abeg, beg, len);
+ abeg[len] = '\0';
+ aend = abeg + len;
+
+ for (p=abeg, nargs=0; p <= aend; ++argvp)
+ {
+ char *next;
+
+ ++nargs;
+
+ if (nargs == entry_p->maximum_args
+ || (! (next = find_next_argument (openparen, closeparen, p, aend))))
+ next = aend;
+
+ *argvp = p;
+ *next = '\0';
+ p = next + 1;
+ }
+ }
+ *argvp = NULL;
+
+ /* Finally! Run the function... */
+ *op = expand_builtin_function (*op, nargs, argv, entry_p);
+
+ /* Free memory. */
+ if (entry_p->expand_args)
+ for (argvp=argv; *argvp != 0; ++argvp)
+ free (*argvp);
+ else
+ free (abeg);
+
+ return 1;
+}
+
+
+int /* bird split it up and hacked it. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+handle_function (char **op, const char **stringp)
+{
+ const struct function_table_entry *entry_p = lookup_function (*stringp + 1);
+ if (!entry_p)
+ return 0;
+ return handle_function2 (entry_p, op, stringp);
+}
+#else /* CONFIG_WITH_VALUE_LENGTH */
+handle_function (char **op, const char **stringp, const char *nameend, const char *eol UNUSED)
+{
+ const char *fname = *stringp + 1;
+ const struct function_table_entry *entry_p =
+ lookup_function_in_hash_tab (fname, nameend - fname);
+ if (!entry_p)
+ return 0;
+ return handle_function2 (entry_p, op, stringp);
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+#ifdef CONFIG_WITH_COMPILER
+/* Used by the "compiler" to get all info about potential functions. */
+make_function_ptr_t
+lookup_function_for_compiler (const char *name, unsigned int len,
+ unsigned char *minargsp, unsigned char *maxargsp,
+ char *expargsp, const char **funcnamep)
+{
+ const struct function_table_entry *entry_p = lookup_function (name, len);
+ if (!entry_p)
+ return 0;
+ *minargsp = entry_p->minimum_args;
+ *maxargsp = entry_p->maximum_args;
+ *expargsp = entry_p->expand_args;
+ *funcnamep = entry_p->name;
+ return entry_p->func_ptr;
+}
+#endif /* CONFIG_WITH_COMPILER */
+
+
+/* User-defined functions. Expand the first argument as either a builtin
+ function or a make variable, in the context of the rest of the arguments
+ assigned to $1, $2, ... $N. $0 is the name of the function. */
+
+static char *
+func_call (char *o, char **argv, const char *funcname UNUSED)
+{
+ static int max_args = 0;
+ char *fname;
+ char *body;
+ int flen;
+ int i;
+ int saved_args;
+ const struct function_table_entry *entry_p;
+ struct variable *v;
+#ifdef CONFIG_WITH_EVALPLUS
+ char *buf;
+ unsigned int len;
+#endif
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ char *fname_end;
+#endif
+#if defined (CONFIG_WITH_EVALPLUS) || defined (CONFIG_WITH_VALUE_LENGTH)
+ char num[11];
+#endif
+
+ /* Clean up the name of the variable to be invoked. */
+ fname = next_token (argv[0]);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ end_of_token (fname)[0] = '\0';
+#else
+ fname_end = end_of_token (fname);
+ *fname_end = '\0';
+#endif
+
+ /* Calling nothing is a no-op */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (*fname == '\0')
+#else
+ if (fname == fname_end)
+#endif
+ return o;
+
+ /* Are we invoking a builtin function? */
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ entry_p = lookup_function (fname);
+#else
+ entry_p = lookup_function (fname, fname_end - fname);
+#endif
+ if (entry_p)
+ {
+ /* How many arguments do we have? */
+ for (i=0; argv[i+1]; ++i)
+ ;
+ return expand_builtin_function (o, i, argv+1, entry_p);
+ }
+
+ /* Not a builtin, so the first argument is the name of a variable to be
+ expanded and interpreted as a function. Find it. */
+ flen = strlen (fname);
+
+ v = lookup_variable (fname, flen);
+
+ if (v == 0)
+ warn_undefined (fname, flen);
+
+ if (v == 0 || *v->value == '\0')
+ return o;
+
+ body = alloca (flen + 4);
+ body[0] = '$';
+ body[1] = '(';
+ memcpy (body + 2, fname, flen);
+ body[flen+2] = ')';
+ body[flen+3] = '\0';
+
+ /* Set up arguments $(1) .. $(N). $(0) is the function name. */
+
+ push_new_variable_scope ();
+
+ for (i=0; *argv; ++i, ++argv)
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ define_variable (num, sprintf (num, "%d", i), *argv, o_automatic, 0);
+#else
+ {
+ char num[11];
+
+ sprintf (num, "%d", i);
+ define_variable (num, strlen (num), *argv, o_automatic, 0);
+ }
+#endif
+
+#ifdef CONFIG_WITH_EVALPLUS
+ /* $(.ARGC) is the argument count. */
+
+ len = sprintf (num, "%d", i - 1);
+ define_variable_vl (".ARGC", sizeof (".ARGC") - 1, num, len,
+ 1 /* dup val */, o_automatic, 0);
+#endif
+
+ /* If the number of arguments we have is < max_args, it means we're inside
+ a recursive invocation of $(call ...). Fill in the remaining arguments
+ in the new scope with the empty value, to hide them from this
+ invocation. */
+
+ for (; i < max_args; ++i)
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ define_variable (num, sprintf (num, "%d", i), "", o_automatic, 0);
+#else
+ {
+ char num[11];
+
+ sprintf (num, "%d", i);
+ define_variable (num, strlen (num), "", o_automatic, 0);
+ }
+#endif
+
+ saved_args = max_args;
+ max_args = i;
+
+#ifdef CONFIG_WITH_EVALPLUS
+ if (!strcmp (funcname, "call"))
+ {
+#endif
+ /* Expand the body in the context of the arguments, adding the result to
+ the variable buffer. */
+
+ v->exp_count = EXP_COUNT_MAX;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ o = variable_expand_string (o, body, flen+3);
+ v->exp_count = 0;
+
+ o += strlen (o);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ variable_expand_string_2 (o, body, flen+3, &o);
+ v->exp_count = 0;
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+#ifdef CONFIG_WITH_EVALPLUS
+ }
+ else
+ {
+ const floc *reading_file_saved = reading_file;
+ char *eos;
+
+ if (!strcmp (funcname, "evalcall"))
+ {
+ /* Evaluate the variable value without expanding it. We
+ need a copy since eval_buffer is destructive. */
+
+ size_t off = o - variable_buffer;
+ eos = variable_buffer_output (o, v->value, v->value_length + 1) - 1;
+ o = variable_buffer + off;
+ if (v->fileinfo.filenm)
+ reading_file = &v->fileinfo;
+ }
+ else
+ {
+ /* Expand the body first and then evaluate the output. */
+
+ v->exp_count = EXP_COUNT_MAX;
+ o = variable_expand_string_2 (o, body, flen+3, &eos);
+ v->exp_count = 0;
+ }
+
+ install_variable_buffer (&buf, &len);
+ eval_buffer (o, NULL, eos);
+ restore_variable_buffer (buf, len);
+ reading_file = reading_file_saved;
+
+ /* Deal with the .RETURN value if present. */
+
+ v = lookup_variable_in_set (".RETURN", sizeof (".RETURN") - 1,
+ current_variable_set_list->set);
+ if (v && v->value_length)
+ {
+ if (v->recursive && !IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR (v))
+ {
+ v->exp_count = EXP_COUNT_MAX;
+ variable_expand_string_2 (o, v->value, v->value_length, &o);
+ v->exp_count = 0;
+ }
+ else
+ o = variable_buffer_output (o, v->value, v->value_length);
+ }
+ }
+#endif /* CONFIG_WITH_EVALPLUS */
+
+ max_args = saved_args;
+
+ pop_variable_scope ();
+
+ return o;
+}
+
+void
+define_new_function (const floc *flocp, const char *name,
+ unsigned int min, unsigned int max, unsigned int flags,
+ gmk_func_ptr func)
+{
+ const char *e = name;
+ struct function_table_entry *ent;
+ size_t len;
+
+ while (STOP_SET (*e, MAP_USERFUNC))
+ e++;
+ len = e - name;
+
+ if (len == 0)
+ O (fatal, flocp, _("Empty function name"));
+ if (*name == '.' || *e != '\0')
+ OS (fatal, flocp, _("Invalid function name: %s"), name);
+ if (len > 255)
+ OS (fatal, flocp, _("Function name too long: %s"), name);
+ if (min > 255)
+ ONS (fatal, flocp,
+ _("Invalid minimum argument count (%u) for function %s"), min, name);
+ if (max > 255 || (max && max < min))
+ ONS (fatal, flocp,
+ _("Invalid maximum argument count (%u) for function %s"), max, name);
+
+ ent = xmalloc (sizeof (struct function_table_entry));
+ ent->name = name;
+ ent->len = len;
+ ent->minimum_args = min;
+ ent->maximum_args = max;
+ ent->expand_args = ANY_SET(flags, GMK_FUNC_NOEXPAND) ? 0 : 1;
+ ent->alloc_fn = 1;
+ ent->fptr.alloc_func_ptr = func;
+
+ hash_insert (&function_table, ent);
+}
+
+void
+hash_init_function_table (void)
+{
+ hash_init (&function_table, FUNCTION_TABLE_ENTRIES * 2,
+ function_table_entry_hash_1, function_table_entry_hash_2,
+ function_table_entry_hash_cmp);
+ hash_load (&function_table, function_table_init,
+ FUNCTION_TABLE_ENTRIES, sizeof (struct function_table_entry));
+#if defined (CONFIG_WITH_OPTIMIZATION_HACKS) || defined (CONFIG_WITH_VALUE_LENGTH)
+ {
+ unsigned int i;
+ for (i = 0; i < FUNCTION_TABLE_ENTRIES; i++)
+ {
+ const char *fn = function_table_init[i].name;
+ while (*fn)
+ {
+ func_char_map[(int)*fn] = 1;
+ fn++;
+ }
+ assert (function_table_init[i].len <= MAX_FUNCTION_LENGTH);
+ assert (function_table_init[i].len >= MIN_FUNCTION_LENGTH);
+ }
+ }
+#endif
+}
diff --git a/src/kmk/getloadavg.c b/src/kmk/getloadavg.c
new file mode 100644
index 0000000..10ae56a
--- /dev/null
+++ b/src/kmk/getloadavg.c
@@ -0,0 +1,1026 @@
+/* Get the system load averages.
+Copyright (C) 1985-2016 Free Software Foundation, Inc.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Compile-time symbols that this file uses:
+
+ HAVE_PSTAT_GETDYNAMIC Define this if your system has the
+ pstat_getdynamic function. I think it
+ is unique to HPUX9. The best way to get the
+ definition is through the AC_FUNC_GETLOADAVG
+ macro that comes with autoconf 2.13 or newer.
+ If that isn't an option, then just put
+ AC_CHECK_FUNCS(pstat_getdynamic) in your
+ configure.in file.
+ FIXUP_KERNEL_SYMBOL_ADDR() Adjust address in returned struct nlist.
+ KERNEL_FILE Pathname of the kernel to nlist.
+ LDAV_CVT() Scale the load average from the kernel.
+ Returns a double.
+ LDAV_SYMBOL Name of kernel symbol giving load average.
+ LOAD_AVE_TYPE Type of the load average array in the kernel.
+ Must be defined unless one of
+ apollo, DGUX, NeXT, or UMAX is defined;
+ or we have libkstat;
+ otherwise, no load average is available.
+ NLIST_STRUCT Include nlist.h, not a.out.h, and
+ the nlist n_name element is a pointer,
+ not an array.
+ HAVE_STRUCT_NLIST_N_UN_N_NAME struct nlist has an n_un member, not n_name.
+ LINUX_LDAV_FILE [__linux__]: File containing load averages.
+
+ Specific system predefines this file uses, aside from setting
+ default values if not emacs:
+
+ apollo
+ BSD Real BSD, not just BSD-like.
+ convex
+ DGUX
+ eunice UNIX emulator under VMS.
+ hpux
+ __MSDOS__ No-op for MSDOS.
+ NeXT
+ sgi
+ sequent Sequent Dynix 3.x.x (BSD)
+ _SEQUENT_ Sequent DYNIX/ptx 1.x.x (SYSV)
+ sony_news NEWS-OS (works at least for 4.1C)
+ UMAX
+ UMAX4_3
+ VMS
+ WINDOWS32 No-op for Windows95/NT.
+ __linux__ Linux: assumes /proc filesystem mounted.
+ Support from Michael K. Johnson.
+ __NetBSD__ NetBSD: assumes /kern filesystem mounted.
+
+ In addition, to avoid nesting many #ifdefs, we internally set
+ LDAV_DONE to indicate that the load average has been computed.
+
+ We also #define LDAV_PRIVILEGED if a program will require
+ special installation to be able to call getloadavg. */
+
+/* This should always be first. */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+
+/* Both the Emacs and non-Emacs sections want this. Some
+ configuration files' definitions for the LOAD_AVE_CVT macro (like
+ sparc.h's) use macros like FSCALE, defined here. */
+#if defined (unix) || defined (__unix)
+# include <sys/param.h>
+#endif
+
+
+/* Exclude all the code except the test program at the end
+ if the system has its own 'getloadavg' function.
+
+ The declaration of 'errno' is needed by the test program
+ as well as the function itself, so it comes first. */
+
+#include <errno.h>
+
+#ifndef errno
+extern int errno;
+#endif
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#endif
+#if !HAVE_SETLOCALE
+# define setlocale(Category, Locale) /* empty */
+#endif
+
+#ifndef HAVE_GETLOADAVG
+
+
+/* The existing Emacs configuration files define a macro called
+ LOAD_AVE_CVT, which accepts a value of type LOAD_AVE_TYPE, and
+ returns the load average multiplied by 100. What we actually want
+ is a macro called LDAV_CVT, which returns the load average as an
+ unmultiplied double.
+
+ For backwards compatibility, we'll define LDAV_CVT in terms of
+ LOAD_AVE_CVT, but future machine config files should just define
+ LDAV_CVT directly. */
+
+# if !defined(LDAV_CVT) && defined(LOAD_AVE_CVT)
+# define LDAV_CVT(n) (LOAD_AVE_CVT (n) / 100.0)
+# endif
+
+# if !defined (BSD) && defined (ultrix)
+/* Ultrix behaves like BSD on Vaxen. */
+# define BSD
+# endif
+
+# ifdef NeXT
+/* NeXT in the 2.{0,1,2} releases defines BSD in <sys/param.h>, which
+ conflicts with the definition understood in this file, that this
+ really is BSD. */
+# undef BSD
+
+/* NeXT defines FSCALE in <sys/param.h>. However, we take FSCALE being
+ defined to mean that the nlist method should be used, which is not true. */
+# undef FSCALE
+# endif
+
+/* Same issues as for NeXT apply to the HURD-based GNU system. */
+# ifdef __GNU__
+# undef BSD
+# undef FSCALE
+# endif /* __GNU__ */
+
+/* Set values that are different from the defaults, which are
+ set a little farther down with #ifndef. */
+
+
+/* Some shorthands. */
+
+# if defined (HPUX) && !defined (hpux)
+# define hpux
+# endif
+
+# if defined (__hpux) && !defined (hpux)
+# define hpux
+# endif
+
+# if defined (__sun) && !defined (sun)
+# define sun
+# endif
+
+# if defined(hp300) && !defined(hpux)
+# define MORE_BSD
+# endif
+
+# if defined(ultrix) && defined(mips)
+# define decstation
+# endif
+
+# if defined (__SVR4) && !defined (SVR4)
+# define SVR4
+# endif
+
+# if (defined(sun) && defined(SVR4)) || defined (SOLARIS2)
+# define SUNOS_5
+# endif
+
+# if defined (__osf__) && (defined (__alpha) || defined (__alpha__))
+# define OSF_ALPHA
+# include <sys/mbuf.h>
+# include <sys/socket.h>
+# include <net/route.h>
+# include <sys/table.h>
+# endif
+
+# if defined (__osf__) && (defined (mips) || defined (__mips__))
+# define OSF_MIPS
+# include <sys/table.h>
+# endif
+
+/* UTek's /bin/cc on the 4300 has no architecture specific cpp define by
+ default, but _MACH_IND_SYS_TYPES is defined in <sys/types.h>. Combine
+ that with a couple of other things and we'll have a unique match. */
+# if !defined (tek4300) && defined (unix) && defined (m68k) && defined (mc68000) && defined (mc68020) && defined (_MACH_IND_SYS_TYPES)
+# define tek4300 /* Define by emacs, but not by other users. */
+# endif
+
+/* AC_FUNC_GETLOADAVG thinks QNX is SVR4, but it isn't. */
+# if defined(__QNX__)
+# undef SVR4
+# endif
+
+/* VAX C can't handle multi-line #ifs, or lines longer than 256 chars. */
+# ifndef LOAD_AVE_TYPE
+
+# ifdef MORE_BSD
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef sun
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef decstation
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef _SEQUENT_
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef sgi
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef SVR4
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef sony_news
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef sequent
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef OSF_ALPHA
+# define LOAD_AVE_TYPE long
+# endif
+
+# if defined (ardent) && defined (titan)
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef tek4300
+# define LOAD_AVE_TYPE long
+# endif
+
+# if defined(alliant) && defined(i860) /* Alliant FX/2800 */
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef _AIX
+# define LOAD_AVE_TYPE long
+# endif
+
+# ifdef convex
+# define LOAD_AVE_TYPE double
+# ifndef LDAV_CVT
+# define LDAV_CVT(n) (n)
+# endif
+# endif
+
+# endif /* No LOAD_AVE_TYPE. */
+
+# ifdef OSF_ALPHA
+/* <sys/param.h> defines an incorrect value for FSCALE on Alpha OSF/1,
+ according to ghazi@noc.rutgers.edu. */
+# undef FSCALE
+# define FSCALE 1024.0
+# endif
+
+# if defined(alliant) && defined(i860) /* Alliant FX/2800 */
+/* <sys/param.h> defines an incorrect value for FSCALE on an
+ Alliant FX/2800 Concentrix 2.2, according to ghazi@noc.rutgers.edu. */
+# undef FSCALE
+# define FSCALE 100.0
+# endif
+
+
+# ifndef FSCALE
+
+/* SunOS and some others define FSCALE in sys/param.h. */
+
+# ifdef MORE_BSD
+# define FSCALE 2048.0
+# endif
+
+# if defined(MIPS) || defined(SVR4) || defined(decstation)
+# define FSCALE 256
+# endif
+
+# if defined (sgi) || defined (sequent)
+/* Sometimes both MIPS and sgi are defined, so FSCALE was just defined
+ above under #ifdef MIPS. But we want the sgi value. */
+# undef FSCALE
+# define FSCALE 1000.0
+# endif
+
+# if defined (ardent) && defined (titan)
+# define FSCALE 65536.0
+# endif
+
+# ifdef tek4300
+# define FSCALE 100.0
+# endif
+
+# ifdef _AIX
+# define FSCALE 65536.0
+# endif
+
+# endif /* Not FSCALE. */
+
+# if !defined (LDAV_CVT) && defined (FSCALE)
+# define LDAV_CVT(n) (((double) (n)) / FSCALE)
+# endif
+
+
+# if defined(sgi) || (defined(mips) && !defined(BSD))
+# define FIXUP_KERNEL_SYMBOL_ADDR(nl) ((nl)[0].n_value &= ~(1 << 31))
+# endif
+
+
+# if !defined (KERNEL_FILE) && defined (sequent)
+# define KERNEL_FILE "/dynix"
+# endif
+
+# if !defined (KERNEL_FILE) && defined (hpux)
+# define KERNEL_FILE "/hp-ux"
+# endif
+
+# if !defined(KERNEL_FILE) && (defined(_SEQUENT_) || defined(MIPS) || defined(SVR4) || defined(ISC) || defined (sgi) || (defined (ardent) && defined (titan)))
+# define KERNEL_FILE "/unix"
+# endif
+
+
+# if !defined (LDAV_SYMBOL) && defined (alliant)
+# define LDAV_SYMBOL "_Loadavg"
+# endif
+
+# if !defined(LDAV_SYMBOL) && ((defined(hpux) && !defined(hp9000s300)) || defined(_SEQUENT_) || defined(SVR4) || defined(ISC) || defined(sgi) || (defined (ardent) && defined (titan)) || defined (_AIX))
+# define LDAV_SYMBOL "avenrun"
+# endif
+
+# ifdef HAVE_UNISTD_H
+# include <unistd.h>
+# endif
+
+# include <stdio.h>
+
+/* LOAD_AVE_TYPE should only get defined if we're going to use the
+ nlist method. */
+# if !defined(LOAD_AVE_TYPE) && (defined(BSD) || defined(LDAV_CVT) || defined(KERNEL_FILE) || defined(LDAV_SYMBOL)) && !defined(__riscos__)
+# define LOAD_AVE_TYPE double
+# endif
+
+# ifdef LOAD_AVE_TYPE
+
+# ifndef VMS
+# ifndef __linux__
+# ifdef HAVE_NLIST_H
+# include <nlist.h>
+# else
+# include <a.out.h>
+# endif
+
+# ifdef SUNOS_5
+# include <fcntl.h>
+# include <kvm.h>
+# include <kstat.h>
+# endif
+
+# if defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC)
+# include <sys/pstat.h>
+# endif
+
+# ifndef KERNEL_FILE
+# define KERNEL_FILE "/vmunix"
+# endif /* KERNEL_FILE */
+
+# ifndef LDAV_SYMBOL
+# define LDAV_SYMBOL "_avenrun"
+# endif /* LDAV_SYMBOL */
+# endif /* __linux__ */
+
+# else /* VMS */
+
+# ifndef eunice
+# include <iodef.h>
+# include <descrip.h>
+# else /* eunice */
+# include <vms/iodef.h>
+# endif /* eunice */
+# endif /* VMS */
+
+# ifndef LDAV_CVT
+# define LDAV_CVT(n) ((double) (n))
+# endif /* !LDAV_CVT */
+
+# endif /* LOAD_AVE_TYPE */
+
+# if defined(__GNU__) && !defined (NeXT)
+/* Note that NeXT Openstep defines __GNU__ even though it should not. */
+/* GNU system acts much like NeXT, for load average purposes,
+ but not exactly. */
+# define NeXT
+# define host_self mach_host_self
+# endif
+
+# ifdef NeXT
+# ifdef HAVE_MACH_MACH_H
+# include <mach/mach.h>
+# else
+# include <mach.h>
+# endif
+# endif /* NeXT */
+
+# ifdef sgi
+# include <sys/sysmp.h>
+# endif /* sgi */
+
+# ifdef UMAX
+# include <stdio.h>
+# include <signal.h>
+# include <sys/time.h>
+# include <sys/wait.h>
+# include <sys/syscall.h>
+
+# ifdef UMAX_43
+# include <machine/cpu.h>
+# include <inq_stats/statistics.h>
+# include <inq_stats/sysstats.h>
+# include <inq_stats/cpustats.h>
+# include <inq_stats/procstats.h>
+# else /* Not UMAX_43. */
+# include <sys/sysdefs.h>
+# include <sys/statistics.h>
+# include <sys/sysstats.h>
+# include <sys/cpudefs.h>
+# include <sys/cpustats.h>
+# include <sys/procstats.h>
+# endif /* Not UMAX_43. */
+# endif /* UMAX */
+
+# ifdef DGUX
+# include <sys/dg_sys_info.h>
+# endif
+
+# if defined(HAVE_FCNTL_H) || defined(_POSIX_VERSION)
+# include <fcntl.h>
+# else
+# include <sys/file.h>
+# endif
+
+
+/* Avoid static vars inside a function since in HPUX they dump as pure. */
+
+# ifdef NeXT
+static processor_set_t default_set;
+static int getloadavg_initialized;
+# endif /* NeXT */
+
+# ifdef UMAX
+static unsigned int cpus = 0;
+static unsigned int samples;
+# endif /* UMAX */
+
+# ifdef DGUX
+static struct dg_sys_info_load_info load_info; /* what-a-mouthful! */
+# endif /* DGUX */
+
+#if !defined(HAVE_LIBKSTAT) && defined(LOAD_AVE_TYPE)
+/* File descriptor open to /dev/kmem or VMS load ave driver. */
+static int channel;
+/* Nonzero iff channel is valid. */
+static int getloadavg_initialized;
+/* Offset in kmem to seek to read load average, or 0 means invalid. */
+static long offset;
+
+#if !defined(VMS) && !defined(sgi) && !defined(__linux__)
+static struct nlist nl[2];
+#endif /* Not VMS or sgi */
+
+#ifdef SUNOS_5
+static kvm_t *kd;
+#endif /* SUNOS_5 */
+
+#endif /* LOAD_AVE_TYPE && !HAVE_LIBKSTAT */
+
+/* Put the 1 minute, 5 minute and 15 minute load averages
+ into the first NELEM elements of LOADAVG.
+ Return the number written (never more than 3, but may be less than NELEM),
+ or -1 if an error occurred. */
+
+int
+getloadavg (double loadavg[], int nelem)
+{
+ int elem = 0; /* Return value. */
+
+# ifdef NO_GET_LOAD_AVG
+# define LDAV_DONE
+ /* Set errno to zero to indicate that there was no particular error;
+ this function just can't work at all on this system. */
+ errno = 0;
+ elem = -1;
+# endif
+
+# if !defined (LDAV_DONE) && defined (HAVE_LIBKSTAT)
+/* Use libkstat because we don't have to be root. */
+# define LDAV_DONE
+ kstat_ctl_t *kc;
+ kstat_t *ksp;
+ kstat_named_t *kn;
+
+ kc = kstat_open ();
+ if (kc == 0)
+ return -1;
+ ksp = kstat_lookup (kc, "unix", 0, "system_misc");
+ if (ksp == 0 )
+ return -1;
+ if (kstat_read (kc, ksp, 0) == -1)
+ return -1;
+
+
+ kn = kstat_data_lookup (ksp, "avenrun_1min");
+ if (kn == 0)
+ {
+ /* Return -1 if no load average information is available. */
+ nelem = 0;
+ elem = -1;
+ }
+
+ if (nelem >= 1)
+ loadavg[elem++] = (double) kn->value.ul/FSCALE;
+
+ if (nelem >= 2)
+ {
+ kn = kstat_data_lookup (ksp, "avenrun_5min");
+ if (kn != 0)
+ {
+ loadavg[elem++] = (double) kn->value.ul/FSCALE;
+
+ if (nelem >= 3)
+ {
+ kn = kstat_data_lookup (ksp, "avenrun_15min");
+ if (kn != 0)
+ loadavg[elem++] = (double) kn->value.ul/FSCALE;
+ }
+ }
+ }
+
+ kstat_close (kc);
+# endif /* HAVE_LIBKSTAT */
+
+# if !defined (LDAV_DONE) && defined (hpux) && defined (HAVE_PSTAT_GETDYNAMIC)
+/* Use pstat_getdynamic() because we don't have to be root. */
+# define LDAV_DONE
+# undef LOAD_AVE_TYPE
+
+ struct pst_dynamic dyn_info;
+ if (pstat_getdynamic (&dyn_info, sizeof (dyn_info), 0, 0) < 0)
+ return -1;
+ if (nelem > 0)
+ loadavg[elem++] = dyn_info.psd_avg_1_min;
+ if (nelem > 1)
+ loadavg[elem++] = dyn_info.psd_avg_5_min;
+ if (nelem > 2)
+ loadavg[elem++] = dyn_info.psd_avg_15_min;
+
+# endif /* hpux && HAVE_PSTAT_GETDYNAMIC */
+
+# if !defined (LDAV_DONE) && defined (__linux__)
+# define LDAV_DONE
+# undef LOAD_AVE_TYPE
+
+# ifndef LINUX_LDAV_FILE
+# define LINUX_LDAV_FILE "/proc/loadavg"
+# endif
+
+ char ldavgbuf[40];
+ double load_ave[3];
+ int fd, count;
+
+ fd = open (LINUX_LDAV_FILE, O_RDONLY);
+ if (fd == -1)
+ return -1;
+ count = read (fd, ldavgbuf, 40);
+ (void) close (fd);
+ if (count <= 0)
+ return -1;
+
+ /* The following sscanf must use the C locale. */
+ setlocale (LC_NUMERIC, "C");
+ count = sscanf (ldavgbuf, "%lf %lf %lf",
+ &load_ave[0], &load_ave[1], &load_ave[2]);
+ setlocale (LC_NUMERIC, "");
+ if (count < 1)
+ return -1;
+
+ for (elem = 0; elem < nelem && elem < count; elem++)
+ loadavg[elem] = load_ave[elem];
+
+ return elem;
+
+# endif /* __linux__ */
+
+# if !defined (LDAV_DONE) && defined (__NetBSD__)
+# define LDAV_DONE
+# undef LOAD_AVE_TYPE
+
+# ifndef NETBSD_LDAV_FILE
+# define NETBSD_LDAV_FILE "/kern/loadavg"
+# endif
+
+ unsigned long int load_ave[3], scale;
+ int count;
+ FILE *fp;
+
+ fp = fopen (NETBSD_LDAV_FILE, "r");
+ if (fp == NULL)
+ return -1;
+ count = fscanf (fp, "%lu %lu %lu %lu\n",
+ &load_ave[0], &load_ave[1], &load_ave[2],
+ &scale);
+ (void) fclose (fp);
+ if (count != 4)
+ return -1;
+
+ for (elem = 0; elem < nelem; elem++)
+ loadavg[elem] = (double) load_ave[elem] / (double) scale;
+
+ return elem;
+
+# endif /* __NetBSD__ */
+
+# if !defined (LDAV_DONE) && defined (NeXT)
+# define LDAV_DONE
+ /* The NeXT code was adapted from iscreen 3.2. */
+
+ host_t host;
+ struct processor_set_basic_info info;
+ unsigned info_count;
+
+ /* We only know how to get the 1-minute average for this system,
+ so even if the caller asks for more than 1, we only return 1. */
+
+ if (!getloadavg_initialized)
+ {
+ if (processor_set_default (host_self (), &default_set) == KERN_SUCCESS)
+ getloadavg_initialized = 1;
+ }
+
+ if (getloadavg_initialized)
+ {
+ info_count = PROCESSOR_SET_BASIC_INFO_COUNT;
+ if (processor_set_info (default_set, PROCESSOR_SET_BASIC_INFO, &host,
+ (processor_set_info_t) &info, &info_count)
+ != KERN_SUCCESS)
+ getloadavg_initialized = 0;
+ else
+ {
+ if (nelem > 0)
+ loadavg[elem++] = (double) info.load_average / LOAD_SCALE;
+ }
+ }
+
+ if (!getloadavg_initialized)
+ return -1;
+# endif /* NeXT */
+
+# if !defined (LDAV_DONE) && defined (UMAX)
+# define LDAV_DONE
+/* UMAX 4.2, which runs on the Encore Multimax multiprocessor, does not
+ have a /dev/kmem. Information about the workings of the running kernel
+ can be gathered with inq_stats system calls.
+ We only know how to get the 1-minute average for this system. */
+
+ struct proc_summary proc_sum_data;
+ struct stat_descr proc_info;
+ double load;
+ register unsigned int i, j;
+
+ if (cpus == 0)
+ {
+ register unsigned int c, i;
+ struct cpu_config conf;
+ struct stat_descr desc;
+
+ desc.sd_next = 0;
+ desc.sd_subsys = SUBSYS_CPU;
+ desc.sd_type = CPUTYPE_CONFIG;
+ desc.sd_addr = (char *) &conf;
+ desc.sd_size = sizeof conf;
+
+ if (inq_stats (1, &desc))
+ return -1;
+
+ c = 0;
+ for (i = 0; i < conf.config_maxclass; ++i)
+ {
+ struct class_stats stats;
+ memset (&stats, '\0', sizeof stats);
+
+ desc.sd_type = CPUTYPE_CLASS;
+ desc.sd_objid = i;
+ desc.sd_addr = (char *) &stats;
+ desc.sd_size = sizeof stats;
+
+ if (inq_stats (1, &desc))
+ return -1;
+
+ c += stats.class_numcpus;
+ }
+ cpus = c;
+ samples = cpus < 2 ? 3 : (2 * cpus / 3);
+ }
+
+ proc_info.sd_next = 0;
+ proc_info.sd_subsys = SUBSYS_PROC;
+ proc_info.sd_type = PROCTYPE_SUMMARY;
+ proc_info.sd_addr = (char *) &proc_sum_data;
+ proc_info.sd_size = sizeof (struct proc_summary);
+ proc_info.sd_sizeused = 0;
+
+ if (inq_stats (1, &proc_info) != 0)
+ return -1;
+
+ load = proc_sum_data.ps_nrunnable;
+ j = 0;
+ for (i = samples - 1; i > 0; --i)
+ {
+ load += proc_sum_data.ps_nrun[j];
+ if (j++ == PS_NRUNSIZE)
+ j = 0;
+ }
+
+ if (nelem > 0)
+ loadavg[elem++] = load / samples / cpus;
+# endif /* UMAX */
+
+# if !defined (LDAV_DONE) && defined (DGUX)
+# define LDAV_DONE
+ /* This call can return -1 for an error, but with good args
+ it's not supposed to fail. The first argument is for no
+ apparent reason of type 'long int *'. */
+ dg_sys_info ((long int *) &load_info,
+ DG_SYS_INFO_LOAD_INFO_TYPE,
+ DG_SYS_INFO_LOAD_VERSION_0);
+
+ if (nelem > 0)
+ loadavg[elem++] = load_info.one_minute;
+ if (nelem > 1)
+ loadavg[elem++] = load_info.five_minute;
+ if (nelem > 2)
+ loadavg[elem++] = load_info.fifteen_minute;
+# endif /* DGUX */
+
+# if !defined (LDAV_DONE) && defined (apollo)
+# define LDAV_DONE
+/* Apollo code from lisch@mentorg.com (Ray Lischner).
+
+ This system call is not documented. The load average is obtained as
+ three long integers, for the load average over the past minute,
+ five minutes, and fifteen minutes. Each value is a scaled integer,
+ with 16 bits of integer part and 16 bits of fraction part.
+
+ I'm not sure which operating system first supported this system call,
+ but I know that SR10.2 supports it. */
+
+ extern void proc1_$get_loadav ();
+ unsigned long load_ave[3];
+
+ proc1_$get_loadav (load_ave);
+
+ if (nelem > 0)
+ loadavg[elem++] = load_ave[0] / 65536.0;
+ if (nelem > 1)
+ loadavg[elem++] = load_ave[1] / 65536.0;
+ if (nelem > 2)
+ loadavg[elem++] = load_ave[2] / 65536.0;
+# endif /* apollo */
+
+# if !defined (LDAV_DONE) && defined (OSF_MIPS)
+# define LDAV_DONE
+
+ struct tbl_loadavg load_ave;
+ table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave));
+ loadavg[elem++]
+ = (load_ave.tl_lscale == 0
+ ? load_ave.tl_avenrun.d[0]
+ : (load_ave.tl_avenrun.l[0] / (double) load_ave.tl_lscale));
+# endif /* OSF_MIPS */
+
+# if !defined (LDAV_DONE) && (defined (__MSDOS__) || defined (WINDOWS32))
+# define LDAV_DONE
+
+ /* A faithful emulation is going to have to be saved for a rainy day. */
+ for ( ; elem < nelem; elem++)
+ {
+ loadavg[elem] = 0.0;
+ }
+# endif /* __MSDOS__ || WINDOWS32 */
+
+# if !defined (LDAV_DONE) && defined (OSF_ALPHA)
+# define LDAV_DONE
+
+ struct tbl_loadavg load_ave;
+ table (TBL_LOADAVG, 0, &load_ave, 1, sizeof (load_ave));
+ for (elem = 0; elem < nelem; elem++)
+ loadavg[elem]
+ = (load_ave.tl_lscale == 0
+ ? load_ave.tl_avenrun.d[elem]
+ : (load_ave.tl_avenrun.l[elem] / (double) load_ave.tl_lscale));
+# endif /* OSF_ALPHA */
+
+# if !defined (LDAV_DONE) && defined (VMS)
+ /* VMS specific code -- read from the Load Ave driver. */
+
+ LOAD_AVE_TYPE load_ave[3];
+ static int getloadavg_initialized = 0;
+# ifdef eunice
+ struct
+ {
+ int dsc$w_length;
+ char *dsc$a_pointer;
+ } descriptor;
+# endif
+
+ /* Ensure that there is a channel open to the load ave device. */
+ if (!getloadavg_initialized)
+ {
+ /* Attempt to open the channel. */
+# ifdef eunice
+ descriptor.dsc$w_length = 18;
+ descriptor.dsc$a_pointer = "$$VMS_LOAD_AVERAGE";
+# else
+ $DESCRIPTOR (descriptor, "LAV0:");
+# endif
+ if (sys$assign (&descriptor, &channel, 0, 0) & 1)
+ getloadavg_initialized = 1;
+ }
+
+ /* Read the load average vector. */
+ if (getloadavg_initialized
+ && !(sys$qiow (0, channel, IO$_READVBLK, 0, 0, 0,
+ load_ave, 12, 0, 0, 0, 0) & 1))
+ {
+ sys$dassgn (channel);
+ getloadavg_initialized = 0;
+ }
+
+ if (!getloadavg_initialized)
+ return -1;
+# endif /* VMS */
+
+# if !defined (LDAV_DONE) && defined(LOAD_AVE_TYPE) && !defined(VMS)
+
+ /* UNIX-specific code -- read the average from /dev/kmem. */
+
+# define LDAV_PRIVILEGED /* This code requires special installation. */
+
+ LOAD_AVE_TYPE load_ave[3];
+
+ /* Get the address of LDAV_SYMBOL. */
+ if (offset == 0)
+ {
+# ifndef sgi
+# ifndef NLIST_STRUCT
+ strcpy (nl[0].n_name, LDAV_SYMBOL);
+ strcpy (nl[1].n_name, "");
+# else /* NLIST_STRUCT */
+# ifdef HAVE_STRUCT_NLIST_N_UN_N_NAME
+ nl[0].n_un.n_name = LDAV_SYMBOL;
+ nl[1].n_un.n_name = 0;
+# else /* not HAVE_STRUCT_NLIST_N_UN_N_NAME */
+ nl[0].n_name = LDAV_SYMBOL;
+ nl[1].n_name = 0;
+# endif /* not HAVE_STRUCT_NLIST_N_UN_N_NAME */
+# endif /* NLIST_STRUCT */
+
+# ifndef SUNOS_5
+ if (
+# if !(defined (_AIX) && !defined (ps2))
+ nlist (KERNEL_FILE, nl)
+# else /* _AIX */
+ knlist (nl, 1, sizeof (nl[0]))
+# endif
+ >= 0)
+ /* Omit "&& nl[0].n_type != 0 " -- it breaks on Sun386i. */
+ {
+# ifdef FIXUP_KERNEL_SYMBOL_ADDR
+ FIXUP_KERNEL_SYMBOL_ADDR (nl);
+# endif
+ offset = nl[0].n_value;
+ }
+# endif /* !SUNOS_5 */
+# else /* sgi */
+ int ldav_off;
+
+ ldav_off = sysmp (MP_KERNADDR, MPKA_AVENRUN);
+ if (ldav_off != -1)
+ offset = (long) ldav_off & 0x7fffffff;
+# endif /* sgi */
+ }
+
+ /* Make sure we have /dev/kmem open. */
+ if (!getloadavg_initialized)
+ {
+# ifndef SUNOS_5
+ channel = open ("/dev/kmem", 0);
+ if (channel >= 0)
+ {
+ /* Set the channel to close on exec, so it does not
+ litter any child's descriptor table. */
+# ifdef F_SETFD
+# ifndef FD_CLOEXEC
+# define FD_CLOEXEC 1
+# endif
+ (void) fcntl (channel, F_SETFD, FD_CLOEXEC);
+# endif
+ getloadavg_initialized = 1;
+ }
+# else /* SUNOS_5 */
+ /* We pass 0 for the kernel, corefile, and swapfile names
+ to use the currently running kernel. */
+ kd = kvm_open (0, 0, 0, O_RDONLY, 0);
+ if (kd != 0)
+ {
+ /* nlist the currently running kernel. */
+ kvm_nlist (kd, nl);
+ offset = nl[0].n_value;
+ getloadavg_initialized = 1;
+ }
+# endif /* SUNOS_5 */
+ }
+
+ /* If we can, get the load average values. */
+ if (offset && getloadavg_initialized)
+ {
+ /* Try to read the load. */
+# ifndef SUNOS_5
+ if (lseek (channel, offset, 0) == -1L
+ || read (channel, (char *) load_ave, sizeof (load_ave))
+ != sizeof (load_ave))
+ {
+ close (channel);
+ getloadavg_initialized = 0;
+ }
+# else /* SUNOS_5 */
+ if (kvm_read (kd, offset, (char *) load_ave, sizeof (load_ave))
+ != sizeof (load_ave))
+ {
+ kvm_close (kd);
+ getloadavg_initialized = 0;
+ }
+# endif /* SUNOS_5 */
+ }
+
+ if (offset == 0 || !getloadavg_initialized)
+ return -1;
+# endif /* LOAD_AVE_TYPE and not VMS */
+
+# if !defined (LDAV_DONE) && defined (LOAD_AVE_TYPE) /* Including VMS. */
+ if (nelem > 0)
+ loadavg[elem++] = LDAV_CVT (load_ave[0]);
+ if (nelem > 1)
+ loadavg[elem++] = LDAV_CVT (load_ave[1]);
+ if (nelem > 2)
+ loadavg[elem++] = LDAV_CVT (load_ave[2]);
+
+# define LDAV_DONE
+# endif /* !LDAV_DONE && LOAD_AVE_TYPE */
+
+# ifdef LDAV_DONE
+ return elem;
+# else
+ /* Set errno to zero to indicate that there was no particular error;
+ this function just can't work at all on this system. */
+ errno = 0;
+ return -1;
+# endif
+}
+
+#endif /* ! HAVE_GETLOADAVG */
+
+#ifdef TEST
+#include "makeint.h"
+
+int
+main (int argc, char **argv)
+{
+ int naptime = 0;
+
+ if (argc > 1)
+ naptime = atoi (argv[1]);
+
+ while (1)
+ {
+ double avg[3];
+ int loads;
+
+ errno = 0; /* Don't be misled if it doesn't set errno. */
+ loads = getloadavg (avg, 3);
+ if (loads == -1)
+ {
+ perror ("Error getting load average");
+ exit (1);
+ }
+ if (loads > 0)
+ printf ("1-minute: %f ", avg[0]);
+ if (loads > 1)
+ printf ("5-minute: %f ", avg[1]);
+ if (loads > 2)
+ printf ("15-minute: %f ", avg[2]);
+ if (loads > 0)
+ putchar ('\n');
+
+ if (naptime == 0)
+ break;
+ sleep (naptime);
+ }
+
+ exit (0);
+}
+#endif /* TEST */
diff --git a/src/kmk/getopt.c b/src/kmk/getopt.c
new file mode 100644
index 0000000..b7999ea
--- /dev/null
+++ b/src/kmk/getopt.c
@@ -0,0 +1,1031 @@
+/* Getopt for GNU.
+NOTE: getopt is now part of the C library, so if you don't know what
+"Keep this file name-space clean" means, talk to drepper@gnu.org
+before changing it!
+
+Copyright (C) 1987-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+# ifndef const
+# define const
+# endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+# include <gnu-versions.h>
+# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+# include <stdlib.h>
+# include <unistd.h>
+#endif /* GNU C library. */
+
+#ifdef VMS
+# include <unixlib.h>
+# if HAVE_STRING_H - 0
+# include <string.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages.
+ When compiling libc, the _ macro is predefined. */
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+/* This version of `getopt' appears to the caller like standard Unix 'getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+ causes problems with re-calling getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+static enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+
+#ifdef __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+# include <string.h>
+# define my_index strchr
+#else
+
+# if HAVE_STRING_H
+# include <string.h>
+# else
+# include <strings.h>
+# endif
+
+#ifndef KMK
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+#ifndef getenv
+extern char *getenv ();
+#endif
+#else /* KMK */
+# include <stdlib.h>
+#endif /* KMK */
+
+static char *
+my_index (const char *str, int chr)
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+# if (!defined __STDC__ || !__STDC__) && !defined strlen
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen (const char *);
+# endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+
+#ifdef _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+
+static int nonoption_flags_max_len;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+ is valid for the getopt call we must make sure that the ARGV passed
+ to getopt is that one passed to the process. */
+static void __attribute__ ((unused))
+store_args_and_env (int argc, char *const *argv)
+{
+ /* XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+ original_argc = argc;
+ original_argv = argv;
+}
+# ifdef text_set_element
+text_set_element (__libc_subinit, store_args_and_env);
+# endif /* text_set_element */
+
+# define SWAP_FLAGS(ch1, ch2) \
+ if (nonoption_flags_len > 0) \
+ { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+#if defined __STDC__ && __STDC__
+static void exchange (char **);
+#endif
+
+static void
+exchange (char **argv)
+{
+ int bottom = first_nonopt;
+ int middle = last_nonopt;
+ int top = optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#ifdef _LIBC
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc (top + 1);
+ if (new_str == NULL)
+ nonoption_flags_len = nonoption_flags_max_len = 0;
+ else
+ {
+ memset (__mempcpy (new_str, __getopt_nonoption_flags,
+ nonoption_flags_max_len),
+ '\0', top + 1 - nonoption_flags_max_len);
+ nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ first_nonopt += (optind - last_nonopt);
+ last_nonopt = optind;
+}
+
+/* Initialize the internal data when the first call is made. */
+
+#if defined __STDC__ && __STDC__
+static const char *_getopt_initialize (int, char *const *, const char *);
+#endif
+static const char *
+_getopt_initialize (int argc, char *const *argv, const char *optstring)
+{
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ first_nonopt = last_nonopt = optind;
+
+ nextchar = NULL;
+
+ posixly_correct = getenv ("POSIXLY_CORRECT");
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (optstring[0] == '-')
+ {
+ ordering = RETURN_IN_ORDER;
+ ++optstring;
+ }
+ else if (optstring[0] == '+')
+ {
+ ordering = REQUIRE_ORDER;
+ ++optstring;
+ }
+ else if (posixly_correct != NULL)
+ ordering = REQUIRE_ORDER;
+ else
+ ordering = PERMUTE;
+
+#ifdef _LIBC
+ if (posixly_correct == NULL
+ && argc == original_argc && argv == original_argv)
+ {
+ if (nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = nonoption_flags_max_len = strlen (orig_str);
+ if (nonoption_flags_max_len < argc)
+ nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *) malloc (nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ nonoption_flags_max_len = -1;
+ else
+ memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
+ '\0', nonoption_flags_max_len - len);
+ }
+ }
+ nonoption_flags_len = nonoption_flags_max_len;
+ }
+ else
+ nonoption_flags_len = 0;
+#endif
+
+ return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int
+_getopt_internal (int argc, char *const *argv, const char *optstring,
+ const struct option *longopts, int *longind, int long_only)
+{
+ optarg = NULL;
+
+ if (optind == 0 || !__getopt_initialized)
+ {
+ if (optind == 0)
+ optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize (argc, argv, optstring);
+ __getopt_initialized = 1;
+ }
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#ifdef _LIBC
+# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0' \
+ || (optind < nonoption_flags_len \
+ && __getopt_nonoption_flags[optind] == '1'))
+#else
+# define NONOPTION_P (argv[optind][0] != '-' || argv[optind][1] == '\0')
+#endif
+
+ if (nextchar == NULL || *nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (last_nonopt > optind)
+ last_nonopt = optind;
+ if (first_nonopt > optind)
+ first_nonopt = optind;
+
+ if (ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange ((char **) argv);
+ else if (last_nonopt != optind)
+ first_nonopt = optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (optind < argc && NONOPTION_P)
+ optind++;
+ last_nonopt = optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (optind != argc && !strcmp (argv[optind], "--"))
+ {
+ optind++;
+
+ if (first_nonopt != last_nonopt && last_nonopt != optind)
+ exchange ((char **) argv);
+ else if (first_nonopt == last_nonopt)
+ first_nonopt = optind;
+ last_nonopt = argc;
+
+ optind = argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (optind == argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (first_nonopt != last_nonopt)
+ optind = first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (ordering == REQUIRE_ORDER)
+ return -1;
+ optarg = argv[optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ nextchar = (argv[optind] + 1
+ + (longopts != NULL && argv[optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (argv[optind][1] == '-'
+ || (long_only && (argv[optind][2] || !my_index (optstring, argv[optind][1])))))
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int) (nameend - nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (opterr)
+ fprintf (stderr, _("%s: option '%s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen (nextchar);
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else
+ {
+ if (opterr)
+ { /* bird: disambiguate */
+ if (argv[optind - 1][1] == '-')
+ /* --option */
+ fprintf (stderr,
+ _("%s: option '--%s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+ else
+ /* +option or -option */
+ fprintf (stderr,
+ _("%s: option '%c%s' doesn't allow an argument\n"),
+ argv[0], argv[optind - 1][0], pfound->name);
+ }
+
+ nextchar += strlen (nextchar);
+
+ optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else
+ {
+ if (opterr)
+ fprintf (stderr,
+ _("%s: option '%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+ nextchar += strlen (nextchar);
+ optopt = pfound->val;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || argv[optind][1] == '-'
+ || my_index (optstring, *nextchar) == NULL)
+ {
+ if (opterr)
+ {
+ if (argv[optind][1] == '-')
+ /* --option */
+ fprintf (stderr, _("%s: unrecognized option '--%s'\n"),
+ argv[0], nextchar);
+ else
+ /* +option or -option */
+ fprintf (stderr, _("%s: unrecognized option '%c%s'\n"),
+ argv[0], argv[optind][0], nextchar);
+ }
+ nextchar = (char *) "";
+ optind++;
+ optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *nextchar++;
+ char *temp = my_index (optstring, c);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*nextchar == '\0')
+ ++optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (opterr)
+ {
+ if (posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, _("%s: illegal option -- %c\n"),
+ argv[0], c);
+ else
+ fprintf (stderr, _("%s: invalid option -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ }
+ else if (optind == argc)
+ {
+ if (opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr, _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (nextchar = nameend = optarg; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, nextchar, nameend - nextchar))
+ {
+ if ((unsigned int) (nameend - nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (opterr)
+ fprintf (stderr, _("%s: option '-W %s' is ambiguous\n"),
+ argv[0], argv[optind]);
+ nextchar += strlen (nextchar);
+ optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ optarg = nameend + 1;
+ else
+ {
+ if (opterr)
+ fprintf (stderr, _("\
+%s: option '-W %s' doesn't allow an argument\n"),
+ argv[0], pfound->name);
+
+ nextchar += strlen (nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (optind < argc)
+ optarg = argv[optind++];
+ else
+ {
+ if (opterr)
+ fprintf (stderr,
+ _("%s: option '%s' requires an argument\n"),
+ argv[0], argv[optind - 1]);
+ nextchar += strlen (nextchar);
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ nextchar += strlen (nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ optind++;
+ }
+ else
+ optarg = NULL;
+ nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*nextchar != '\0')
+ {
+ optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ optind++;
+ }
+ else if (optind == argc)
+ {
+ if (opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ fprintf (stderr,
+ _("%s: option requires an argument -- %c\n"),
+ argv[0], c);
+ }
+ optopt = c;
+ if (optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ optarg = argv[optind++];
+ nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+getopt (int argc, char *const *argv, const char *optstring)
+{
+ return _getopt_internal (argc, argv, optstring,
+ (const struct option *) 0,
+ (int *) 0,
+ 0);
+}
+
+#endif /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+
+ c = getopt (argc, argv, "abc:d:0123456789");
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/getopt.h b/src/kmk/getopt.h
new file mode 100644
index 0000000..8713c12
--- /dev/null
+++ b/src/kmk/getopt.h
@@ -0,0 +1,132 @@
+/* Declarations for getopt.
+Copyright (C) 1989-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef FAKES_NO_GETOPT_H /* bird: hack for rhel4 unistd.h dragging in getopt.h */
+#ifndef _GETOPT_H
+#define _GETOPT_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int optopt;
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+#if defined (__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#if defined (__STDC__) && __STDC__
+#ifdef __GNU_LIBRARY__
+/* Many other libraries have conflicting prototypes for getopt, with
+ differences in the consts, in stdlib.h. To avoid compilation
+ errors, only prototype getopt for the GNU C library. */
+extern int getopt (int argc, char *const *argv, const char *shortopts);
+#else /* not __GNU_LIBRARY__ */
+extern int getopt ();
+#endif /* __GNU_LIBRARY__ */
+extern int getopt_long (int argc, char *const *argv, const char *shortopts,
+ const struct option *longopts, int *longind);
+extern int getopt_long_only (int argc, char *const *argv,
+ const char *shortopts,
+ const struct option *longopts, int *longind);
+
+/* Internal only. Users should not call this directly. */
+extern int _getopt_internal (int argc, char *const *argv,
+ const char *shortopts,
+ const struct option *longopts, int *longind,
+ int long_only);
+#else /* not __STDC__ */
+extern int getopt ();
+extern int getopt_long ();
+extern int getopt_long_only ();
+
+extern int _getopt_internal ();
+#endif /* __STDC__ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* getopt.h */
+#endif /* bird hack */
diff --git a/src/kmk/getopt1.c b/src/kmk/getopt1.c
new file mode 100644
index 0000000..0e38b2f
--- /dev/null
+++ b/src/kmk/getopt1.c
@@ -0,0 +1,176 @@
+/* getopt_long and getopt_long_only entry points for GNU getopt.
+Copyright (C) 1987-1994, 1996-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt.h"
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long (int argc, char *const *argv, const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, argv, options, long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only (int argc, char *const *argv, const char *options,
+ const struct option *long_options, int *opt_index)
+{
+ return _getopt_internal (argc, argv, options, long_options, opt_index, 1);
+}
+
+
+#endif /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ c = getopt_long (argc, argv, "abc:d:0123456789",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (optarg)
+ printf (" with arg %s", optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/gettext.h b/src/kmk/gettext.h
new file mode 100644
index 0000000..c48ffa0
--- /dev/null
+++ b/src/kmk/gettext.h
@@ -0,0 +1,57 @@
+/* Convenience header for conditional use of GNU <libintl.h>.
+Copyright (C) 1995-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _LIBGETTEXT_H
+#define _LIBGETTEXT_H 1
+
+/* NLS can be disabled through the configure --disable-nls option. */
+#if ENABLE_NLS
+
+/* Get declarations of GNU message catalog functions. */
+# include <libintl.h>
+
+#else
+
+/* Disabled NLS.
+ The casts to 'const char *' serve the purpose of producing warnings
+ for invalid uses of the value returned from these functions.
+ On pre-ANSI systems without 'const', the config.h file is supposed to
+ contain "#define const". */
+# define gettext(Msgid) ((const char *) (Msgid))
+# define dgettext(Domainname, Msgid) ((const char *) (Msgid))
+# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid))
+# define ngettext(Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dngettext(Domainname, Msgid1, Msgid2, N) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+ ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+# define textdomain(Domainname) ((const char *) (Domainname))
+# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname))
+# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset))
+
+#endif
+
+/* A pseudo function call that serves as a marker for the automated
+ extraction of messages, but does not call gettext(). The run-time
+ translation is done at a different place in the code.
+ The argument, String, should be a literal string. Concatenated strings
+ and other string expressions won't work.
+ The macro's expansion is not parenthesized, so that it is suitable as
+ initializer for static 'char[]' or 'const char[]' variables. */
+#define gettext_noop(String) String
+
+#endif /* _LIBGETTEXT_H */
diff --git a/src/kmk/glob/COPYING.LIB b/src/kmk/glob/COPYING.LIB
new file mode 100644
index 0000000..bbe3fe1
--- /dev/null
+++ b/src/kmk/glob/COPYING.LIB
@@ -0,0 +1,481 @@
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if
+you distribute copies of the library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, so that any problems introduced by others will not reflect on
+the original authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the source code distributed need not include anything that is normally
+distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ Appendix: How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public
+ License along with this library; if not, write to the Free
+ Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/src/kmk/glob/ChangeLog b/src/kmk/glob/ChangeLog
new file mode 100644
index 0000000..c543c85
--- /dev/null
+++ b/src/kmk/glob/ChangeLog
@@ -0,0 +1,191 @@
+2013-10-20 Paul Smith <psmith@gnu.org>
+
+ * glob.c (glob): Cherry-pick a471e96a5352a5f0bde6d32dd36d33524811a2b1
+ from git://sourceware.org/git/glibc.git to fix SV 18123,
+ https://sourceware.org/bugzilla/show_bug.cgi?id=10278
+
+2008-09-28 Juan Manuel Guerrero <juan.guerrero@gmx.de>
+
+ * glob.c (my_realloc) [__DJGPP__]: Don't define, and don't
+ redefine realloc to call it, since the DJGPP's realloc handles
+ NULL pointers correctly.
+
+2007-12-22 Juan Manuel Guerrero <juan.guerrero@gmx.de> (tiny change)
+
+ * glob.c [__GNU_LIBRARY__ && __DJGPP__]: Add a realloc
+ declaration that matches the one in the DJGPP libc.
+
+2006-02-24 Eli Zaretskii <eliz@gnu.org>
+
+ * glob.c (my_malloc) [WINDOWS32]: Provide a full ISO C prototype,
+ to avoid compiler warnings.
+
+2005-06-25 Paul D. Smith <psmith@gnu.org>
+
+ * fnmatch.h, glob.h [WINDOWS32]: Fix ifdefs in headers.
+ Fixes Savannah bug #13477.
+
+2005-03-11 Paul D. Smith <psmith@gnu.org>
+
+ * glob.c (glob_in_dir): Change FNM_CASEFOLD to be enabled if
+ HAVE_CASE_INSENSITIVE_FS is defined.
+
+2003-01-30 Paul D. Smith <psmith@gnu.org>
+
+ * glob.h: Patch for FreeBSD by Mike Barcroft <mike@freebsd.org>
+ Reported by Gerald Pfeifer <pfeifer@dbai.tuwien.ac.at>. On
+ FreeBSD, declare __size_t to simply size_t.
+
+2002-04-22 Paul D. Smith <psmith@gnu.org>
+
+ * Makefile.am: Use automake 1.6.
+ Use new automake condition USE_LOCAL_GLOB to decide whether or not
+ to build the local GNU glob library or use the system one.
+
+1999-09-12 Paul D. Smith <psmith@gnu.org>
+
+ * fnmatch.c: Last GLIBC version wouldn't compile outside of GLIBC
+ (undefined reference to internal_function). Update to the latest
+ version
+
+1999-09-11 Paul Eggert <eggert@twinsun.com>
+
+ * glob.h (glob): If #defining to glob64, do this before
+ declaring it, so that all declarations and uses match, and
+ do not declare glob64, to avoid a declaration clash.
+ (globfree): Likewise with globfree64.
+
+1999-09-08 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * glob.c (prefix_array) [__MSDOS__,WINDOWS32]: Keep the trailing
+ slash unless DIRNAME is just "x:/".
+
+1999-09-06 Paul D. Smith <psmith@gnu.org>
+
+ * fnmatch.c: Update to latest version from GLIBC.
+
+1999-07-21 Paul D. Smith <psmith@gnu.org>
+
+ * glob.c, glob.h, fnmatch.c, fnmatch.h: Update to latest version
+ from GLIBC.
+
+ * fnmatch.c (internal_fnmatch): Use K&R definition syntax, not ANSI.
+ (__strchrnul): This won't exist outside GLIBC, so create one.
+
+ * glob.c: Move getlogin{,_r} prototypes below glob.h to get __P()
+ macro.
+
+1998-08-05 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Remove; configuration for glob is handled by the
+ make configure.in.
+
+1998-07-29 Paul D. Smith <psmith@gnu.org>
+
+ * glob.c, fnmatch.c: New versions from the GLIBC folks (Ulrich
+ Drepper). Fixes a bug reported by Eli Zaretski. Integrates
+ DOS/Windows32 support.
+
+1998-07-27 Kaveh R. Ghazi <ghazi@caip.rutgers.edu>
+
+ * glob.c (glob): Cast away const on assignment of pattern to dirname.
+ Cast the return type of __alloca() for traditional C compilers.
+
+1998-07-23 Paul D. Smith <psmith@gnu.org>
+
+ * glob.c, fnmatch.c: New versions of these files from the GLIBC
+ folks (Ulrich Drepper). Had to re-integrate some DOS/Windows
+ code.
+
+1998-07-10 Paul D. Smith <psmith@gnu.org>
+
+ * glob.c (glob_in_dir): If no meta chars exist in PATTERN and
+ GLOB_NOCHECK is present, don't look for the file--whether it's
+ found or not, we'll always return it, so why bother searching?
+
+ Also, if we are searching and there are no meta chars, don't
+ bother trying fnmatch() if the strcmp() fails.
+
+1998-05-30 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * glob.c (glob) [__MSDOS__, WINDOWS32]: Compute the directory and
+ filename parts of the pattern correctly when it includes a drive
+ spec. Disallow wildcards in the drive spec. Prevent recursion
+ when dirname is of the form "d:/" or "d:".
+ (prefix_array) [__MSDOS__, WINDOWS32]: Don't append a slash to
+ "d:/" and "d:".
+
+1998-05-13 Paul D. Smith <psmith@gnu.org>
+
+ * SMakefile, Makefile.ami, glob.c, glob.h, fnmatch.c: Updated from
+ the latest glibc version.
+
+1998-04-17 Paul D. Smith <psmith@gnu.org>
+
+ * configure.in: Create a config.h file instead of setting things
+ on the compile line. This is because when build.sh runs it merely
+ passes -DHAVE_CONFIG_H to the glob files, just as it does to the
+ make files.
+ * config.h.in: Created by autoheader.
+
+Tue Aug 12 10:52:34 1997 Paul D. Smith <psmith@baynetworks.com>
+
+ * configure.in: Require autoconf 2.12.
+
+ * glob: Updates from latest GNU libc glob code.
+
+ * glob.c,glob.h,fnmatch.h: Change all WIN32 references to WINDOWS32.
+
+ * glob.h: OSF4 defines macros in such a way that GLOB_ALTDIRFUNC
+ is not defined. Added a test to the #if which defines it if
+ _GNU_SOURCE is defined; that's set by both glob.c and GNU make.
+
+ * glob.c: SunOS4 w/ cc needs #include <stdio.h>, since assert.h
+ requires stderr but doesn't include stdio.h :-/.
+ (next_brace_sub): De-protoize function definition.
+ (glob): Cast __alloca(); on SunOS4 it uses the default return type
+ of int.
+ (glob): Irix defines getlogin_r() to return a char*; move the
+ extern for that into the _LIBC area since it isn't used except in
+ LIBC anyway. Likewise, move extern getlogin() into the "else".
+
+Sat Jul 20 21:55:31 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ Win32 hacks from <Rob_Tulloh@tivoli.com>.
+ * posix/glob.c [WIN32]: Don't include <pwd.h>; don't use d_ino;
+ use void * for my_realloc; include <malloc.h> for alloca.
+ (glob) [WIN32]: Use "c:/users/default" for ~ if no HOME variable.
+ * posix/fnmatch.h [WIN32]: Use prototypes even if [!__STDC__].
+ * posix/glob.h: Likewise.
+
+Fri Jul 19 16:56:41 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * posix/glob.h [!_AMIGA && !VMS]: Check this instead of just [!_AMIGA]
+ for `struct stat;' forward decl.
+
+Sat Jun 22 10:44:09 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * posix/glob.c: Include <alloca.h> only [HAVE_ALLOCA_H], not [sparc].
+
+Fri Jun 21 00:27:51 1996 Roland McGrath <roland@delasyd.gnu.ai.mit.edu>
+
+ * posix/fnmatch.c (fnmatch): Fix \*[*?]+ case to increment name ptr
+ only for ?s, not for *s. Fix from Chet Ramey.
+
+
+Copyright (C) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997,
+1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software
+Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/glob/Makefile.am b/src/kmk/glob/Makefile.am
new file mode 100644
index 0000000..93fd60a
--- /dev/null
+++ b/src/kmk/glob/Makefile.am
@@ -0,0 +1,30 @@
+# -*-Makefile-*-, or close enough
+# Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
+# 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+AUTOMAKE_OPTIONS = foreign
+
+# Only build the library when the system doesn't already have GNU glob.
+if USE_LOCAL_GLOB
+ noinst_LIBRARIES = libglob.a
+endif
+
+libglob_a_SOURCES = glob.c glob.h fnmatch.c fnmatch.h
+
+
+EXTRA_DIST = COPYING.LIB Makefile.ami SCOPTIONS SMakefile \
+ configure.bat
diff --git a/src/kmk/glob/Makefile.ami b/src/kmk/glob/Makefile.ami
new file mode 100644
index 0000000..3fbf7e5
--- /dev/null
+++ b/src/kmk/glob/Makefile.ami
@@ -0,0 +1,67 @@
+# Makefile for standalone libglob.a (fnmatch, glob). -*-Makefile-*-
+# Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2007 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Ultrix 2.2 make doesn't expand the value of VPATH.
+VPATH = /glob/
+# This must repeat the value, because configure will remove `VPATH = .'.
+srcdir = /glob/
+
+CC = sc
+RM = delete
+CPPFLAGS =
+CFLAGS =
+
+# Information determined by configure.
+DEFS = Define HAVE_HEADER_STDC Define HAVE_UNISTD_H Define HAVE_STRING_H \
+ Define HAVE_DIRENT_H
+
+# How to invoke ar.
+AR = join
+ARFLAGS = as
+
+# How to invoke ranlib.
+RANLIB = ;
+
+.PHONY: all
+all: glob.lib
+
+glob.lib : glob.o fnmatch.o
+ $(AR) $(ARFLAGS) $@ glob.o fnmatch.o
+ $(RANLIB) $@
+
+# For some reason, Unix make wants the dependencies on the source files.
+# Otherwise it refuses to use an implicit rule!
+# And, get this: it doesn't work to use $(srcdir)foo.c!!
+glob.o: $(srcdir)glob.h $(srcdir)fnmatch.h glob.c
+fnmatch.o: $(srcdir)fnmatch.h fnmatch.c
+
+OUTPUT_OPTION =
+.c.o:
+ $(CC) IDir "" \
+ $(DEFS) $(CPPFLAGS) $(CFLAGS) $< $(OUTPUT_OPTION)
+
+.PHONY: clean realclean glob-clean glob-realclean distclean
+clean glob-clean:
+ -$(RM) glob.lib "#?.o" core
+distclean glob-realclean: clean
+ -$(RM) TAGS tags Makefile config.status config.h config.log
+realcean: distclean
+
+# For inside the C library.
+glob.tar glob.tar.Z:
+ $(MAKE) -C .. $@
diff --git a/src/kmk/glob/SCOPTIONS b/src/kmk/glob/SCOPTIONS
new file mode 100644
index 0000000..f89daae
--- /dev/null
+++ b/src/kmk/glob/SCOPTIONS
@@ -0,0 +1,13 @@
+ERRORREXX
+OPTIMIZE
+NOVERSION
+OPTIMIZERTIME
+OPTIMIZERALIAS
+DEFINE INCLUDEDIR="include:"
+DEFINE LIBDIR="lib:"
+DEFINE NO_ALLOCA
+DEFINE NO_FLOAT
+DEFINE NO_ARCHIVES
+IGNORE=161
+IGNORE=100
+STARTUP=cres
diff --git a/src/kmk/glob/SMakefile b/src/kmk/glob/SMakefile
new file mode 100644
index 0000000..0476a15
--- /dev/null
+++ b/src/kmk/glob/SMakefile
@@ -0,0 +1,67 @@
+# Makefile for standalone distribution of libglob.a (fnmatch, glob).
+# Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
+# 2005, 2006, 2007 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Ultrix 2.2 make doesn't expand the value of VPATH.
+VPATH = /glob/
+# This must repeat the value, because configure will remove `VPATH = .'.
+srcdir = /glob/
+
+CC = sc
+CPPFLAGS =
+CFLAGS =
+MAKE = smake
+RM = delete
+
+# Information determined by configure.
+DEFS = Define HAVE_HEADER_STDC Define HAVE_UNISTD_H Define HAVE_STRING_H \
+ Define HAVE_DIRENT_H
+
+# How to invoke ar.
+AR = join
+ARFLAGS = as
+
+# How to invoke ranlib.
+RANLIB = ;
+
+.PHONY: all
+all: glob.lib
+
+glob.lib : glob.o fnmatch.o
+ $(AR) $(ARFLAGS) $@ glob.o fnmatch.o
+ $(RANLIB) $@
+
+# For some reason, Unix make wants the dependencies on the source files.
+# Otherwise it refuses to use an implicit rule!
+# And, get this: it doesn't work to use $(srcdir)foo.c!!
+glob.o: $(srcdir)glob.h $(srcdir)fnmatch.h glob.c
+fnmatch.o: $(srcdir)fnmatch.h fnmatch.c
+
+.c.o:
+ $(CC) IDir "" \
+ $(DEFS) $(CPPFLAGS) $(CFLAGS) $< $(OUTPUT_OPTION)
+
+.PHONY: clean realclean glob-clean glob-realclean distclean
+clean glob-clean:
+ -$(RM) -f glob.lib *.o core
+distclean glob-realclean: clean
+ -$(RM) -f TAGS tags Makefile config.status config.h config.log
+realcean: distclean
+
+# For inside the C library.
+glob.tar glob.tar.Z:
+ $(MAKE) -C .. $@
diff --git a/src/kmk/glob/configure.bat b/src/kmk/glob/configure.bat
new file mode 100644
index 0000000..672d733
--- /dev/null
+++ b/src/kmk/glob/configure.bat
@@ -0,0 +1,43 @@
+@echo off
+rem Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
+rem 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+rem This file is part of GNU Make.
+rem
+rem GNU Make is free software; you can redistribute it and/or modify it under
+rem the terms of the GNU General Public License as published by the Free
+rem Software Foundation; either version 3 of the License, or (at your option)
+rem any later version.
+rem
+rem GNU Make is distributed in the hope that it will be useful, but WITHOUT
+rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+rem more details.
+rem
+rem You should have received a copy of the GNU General Public License along
+rem with this program. If not, see <http://www.gnu.org/licenses/>.
+
+echo Configuring glob for DJGPP
+rem This batch file assumes a unix-type "sed" program
+
+echo # Makefile generated by "configure.bat"> Makefile
+
+if exist config.sed del config.sed
+
+echo "s/@srcdir@/./ ">> config.sed
+echo "s/@CC@/gcc/ ">> config.sed
+echo "s/@CFLAGS@/-O2 -g/ ">> config.sed
+echo "s/@CPPFLAGS@/-DHAVE_CONFIG_H -I../ ">> config.sed
+echo "s/@AR@/ar/ ">> config.sed
+echo "s/@RANLIB@/ranlib/ ">> config.sed
+echo "s/@LDFLAGS@// ">> config.sed
+echo "s/@DEFS@// ">> config.sed
+echo "s/@ALLOCA@// ">> config.sed
+echo "s/@LIBS@// ">> config.sed
+echo "s/@LIBOBJS@// ">> config.sed
+echo "s/^Makefile *:/_Makefile:/ ">> config.sed
+echo "s/^config.h *:/_config.h:/ ">> config.sed
+
+sed -e "s/^\"//" -e "s/\"$//" -e "s/[ ]*$//" config.sed > config2.sed
+sed -f config2.sed Makefile.in >> Makefile
+del config.sed
+del config2.sed
diff --git a/src/kmk/glob/fnmatch.c b/src/kmk/glob/fnmatch.c
new file mode 100644
index 0000000..b346e10
--- /dev/null
+++ b/src/kmk/glob/fnmatch.c
@@ -0,0 +1,489 @@
+/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999 Free Software
+Foundation, Inc.
+This file is part of the GNU C Library.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this library; see the file COPYING.LIB. If not, write to the Free
+Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA. */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Enable GNU extensions in fnmatch.h. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+#include <errno.h>
+#include <fnmatch.h>
+#include <ctype.h>
+
+#if HAVE_STRING_H || defined _LIBC
+# include <string.h>
+#else
+# include <strings.h>
+#endif
+
+#if defined STDC_HEADERS || defined _LIBC
+# include <stdlib.h>
+#endif
+
+/* For platform which support the ISO C amendement 1 functionality we
+ support user defined character classes. */
+#if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
+/* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */
+# include <wchar.h>
+# include <wctype.h>
+#endif
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#if defined _LIBC || !defined __GNU_LIBRARY__ || 1 /* bird: Same as for glob.c, don't want trouble. */
+
+
+# if defined STDC_HEADERS || !defined isascii
+# define ISASCII(c) 1
+# else
+# define ISASCII(c) isascii(c)
+# endif
+
+# ifdef isblank
+# define ISBLANK(c) (ISASCII (c) && isblank (c))
+# else
+# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
+# endif
+# ifdef isgraph
+# define ISGRAPH(c) (ISASCII (c) && isgraph (c))
+# else
+# define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c))
+# endif
+
+# define ISPRINT(c) (ISASCII (c) && isprint (c))
+# define ISDIGIT(c) (ISASCII (c) && isdigit (c))
+# define ISALNUM(c) (ISASCII (c) && isalnum (c))
+# define ISALPHA(c) (ISASCII (c) && isalpha (c))
+# define ISCNTRL(c) (ISASCII (c) && iscntrl (c))
+# define ISLOWER(c) (ISASCII (c) && islower (c))
+# define ISPUNCT(c) (ISASCII (c) && ispunct (c))
+# define ISSPACE(c) (ISASCII (c) && isspace (c))
+# define ISUPPER(c) (ISASCII (c) && isupper (c))
+# define ISXDIGIT(c) (ISASCII (c) && isxdigit (c))
+
+# define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
+
+# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
+/* The GNU C library provides support for user-defined character classes
+ and the functions from ISO C amendement 1. */
+# ifdef CHARCLASS_NAME_MAX
+# define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX
+# else
+/* This shouldn't happen but some implementation might still have this
+ problem. Use a reasonable default value. */
+# define CHAR_CLASS_MAX_LENGTH 256
+# endif
+
+# ifdef _LIBC
+# define IS_CHAR_CLASS(string) __wctype (string)
+# else
+# define IS_CHAR_CLASS(string) wctype (string)
+# endif
+# else
+# define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */
+
+# define IS_CHAR_CLASS(string) \
+ (STREQ (string, "alpha") || STREQ (string, "upper") \
+ || STREQ (string, "lower") || STREQ (string, "digit") \
+ || STREQ (string, "alnum") || STREQ (string, "xdigit") \
+ || STREQ (string, "space") || STREQ (string, "print") \
+ || STREQ (string, "punct") || STREQ (string, "graph") \
+ || STREQ (string, "cntrl") || STREQ (string, "blank"))
+# endif
+
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+
+# if !defined _LIBC && !defined getenv && !defined _MSC_VER
+extern char *getenv ();
+# endif
+
+# ifndef errno
+extern int errno;
+# endif
+
+/* This function doesn't exist on most systems. */
+
+# if !defined HAVE___STRCHRNUL && !defined _LIBC
+static char *
+__strchrnul (s, c)
+ const char *s;
+ int c;
+{
+ char *result = strchr (s, c);
+ if (result == NULL)
+ result = strchr (s, '\0');
+ return result;
+}
+# endif
+
+# ifndef internal_function
+/* Inside GNU libc we mark some function in a special way. In other
+ environments simply ignore the marking. */
+# define internal_function
+# endif
+
+/* Match STRING against the filename pattern PATTERN, returning zero if
+ it matches, nonzero if not. */
+static int internal_fnmatch __P ((const char *pattern, const char *string,
+ int no_leading_period, int flags))
+ internal_function;
+static int
+internal_function
+internal_fnmatch (pattern, string, no_leading_period, flags)
+ const char *pattern;
+ const char *string;
+ int no_leading_period;
+ int flags;
+{
+ register const char *p = pattern, *n = string;
+ register unsigned char c;
+
+/* Note that this evaluates C many times. */
+# ifdef _LIBC
+# define FOLD(c) ((flags & FNM_CASEFOLD) ? tolower (c) : (c))
+# else
+# define FOLD(c) ((flags & FNM_CASEFOLD) && ISUPPER (c) ? tolower (c) : (c))
+# endif
+
+ while ((c = *p++) != '\0')
+ {
+ c = FOLD (c);
+
+ switch (c)
+ {
+ case '?':
+ if (*n == '\0')
+ return FNM_NOMATCH;
+ else if (*n == '/' && (flags & FNM_FILE_NAME))
+ return FNM_NOMATCH;
+ else if (*n == '.' && no_leading_period
+ && (n == string
+ || (n[-1] == '/' && (flags & FNM_FILE_NAME))))
+ return FNM_NOMATCH;
+ break;
+
+ case '\\':
+ if (!(flags & FNM_NOESCAPE))
+ {
+ c = *p++;
+ if (c == '\0')
+ /* Trailing \ loses. */
+ return FNM_NOMATCH;
+ c = FOLD (c);
+ }
+ if (FOLD ((unsigned char) *n) != c)
+ return FNM_NOMATCH;
+ break;
+
+ case '*':
+ if (*n == '.' && no_leading_period
+ && (n == string
+ || (n[-1] == '/' && (flags & FNM_FILE_NAME))))
+ return FNM_NOMATCH;
+
+ for (c = *p++; c == '?' || c == '*'; c = *p++)
+ {
+ if (*n == '/' && (flags & FNM_FILE_NAME))
+ /* A slash does not match a wildcard under FNM_FILE_NAME. */
+ return FNM_NOMATCH;
+ else if (c == '?')
+ {
+ /* A ? needs to match one character. */
+ if (*n == '\0')
+ /* There isn't another character; no match. */
+ return FNM_NOMATCH;
+ else
+ /* One character of the string is consumed in matching
+ this ? wildcard, so *??? won't match if there are
+ less than three characters. */
+ ++n;
+ }
+ }
+
+ if (c == '\0')
+ /* The wildcard(s) is/are the last element of the pattern.
+ If the name is a file name and contains another slash
+ this does mean it cannot match. */
+ return ((flags & FNM_FILE_NAME) && strchr (n, '/') != NULL
+ ? FNM_NOMATCH : 0);
+ else
+ {
+ const char *endp;
+
+ endp = __strchrnul (n, (flags & FNM_FILE_NAME) ? '/' : '\0');
+
+ if (c == '[')
+ {
+ int flags2 = ((flags & FNM_FILE_NAME)
+ ? flags : (flags & ~FNM_PERIOD));
+
+ for (--p; n < endp; ++n)
+ if (internal_fnmatch (p, n,
+ (no_leading_period
+ && (n == string
+ || (n[-1] == '/'
+ && (flags
+ & FNM_FILE_NAME)))),
+ flags2)
+ == 0)
+ return 0;
+ }
+ else if (c == '/' && (flags & FNM_FILE_NAME))
+ {
+ while (*n != '\0' && *n != '/')
+ ++n;
+ if (*n == '/'
+ && (internal_fnmatch (p, n + 1, flags & FNM_PERIOD,
+ flags) == 0))
+ return 0;
+ }
+ else
+ {
+ int flags2 = ((flags & FNM_FILE_NAME)
+ ? flags : (flags & ~FNM_PERIOD));
+
+ if (c == '\\' && !(flags & FNM_NOESCAPE))
+ c = *p;
+ c = FOLD (c);
+ for (--p; n < endp; ++n)
+ if (FOLD ((unsigned char) *n) == c
+ && (internal_fnmatch (p, n,
+ (no_leading_period
+ && (n == string
+ || (n[-1] == '/'
+ && (flags
+ & FNM_FILE_NAME)))),
+ flags2) == 0))
+ return 0;
+ }
+ }
+
+ /* If we come here no match is possible with the wildcard. */
+ return FNM_NOMATCH;
+
+ case '[':
+ {
+ /* Nonzero if the sense of the character class is inverted. */
+ static int posixly_correct;
+ register int not;
+ char cold;
+
+ if (posixly_correct == 0)
+ posixly_correct = getenv ("POSIXLY_CORRECT") != NULL ? 1 : -1;
+
+ if (*n == '\0')
+ return FNM_NOMATCH;
+
+ if (*n == '.' && no_leading_period && (n == string
+ || (n[-1] == '/'
+ && (flags
+ & FNM_FILE_NAME))))
+ return FNM_NOMATCH;
+
+ if (*n == '/' && (flags & FNM_FILE_NAME))
+ /* `/' cannot be matched. */
+ return FNM_NOMATCH;
+
+ not = (*p == '!' || (posixly_correct < 0 && *p == '^'));
+ if (not)
+ ++p;
+
+ c = *p++;
+ for (;;)
+ {
+ unsigned char fn = FOLD ((unsigned char) *n);
+
+ if (!(flags & FNM_NOESCAPE) && c == '\\')
+ {
+ if (*p == '\0')
+ return FNM_NOMATCH;
+ c = FOLD ((unsigned char) *p);
+ ++p;
+
+ if (c == fn)
+ goto matched;
+ }
+ else if (c == '[' && *p == ':')
+ {
+ /* Leave room for the null. */
+ char str[CHAR_CLASS_MAX_LENGTH + 1];
+ size_t c1 = 0;
+# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
+ wctype_t wt;
+# endif
+ const char *startp = p;
+
+ for (;;)
+ {
+ if (c1 == CHAR_CLASS_MAX_LENGTH)
+ /* The name is too long and therefore the pattern
+ is ill-formed. */
+ return FNM_NOMATCH;
+
+ c = *++p;
+ if (c == ':' && p[1] == ']')
+ {
+ p += 2;
+ break;
+ }
+ if (c < 'a' || c >= 'z')
+ {
+ /* This cannot possibly be a character class name.
+ Match it as a normal range. */
+ p = startp;
+ c = '[';
+ goto normal_bracket;
+ }
+ str[c1++] = c;
+ }
+ str[c1] = '\0';
+
+# if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
+ wt = IS_CHAR_CLASS (str);
+ if (wt == 0)
+ /* Invalid character class name. */
+ return FNM_NOMATCH;
+
+ if (__iswctype (__btowc ((unsigned char) *n), wt))
+ goto matched;
+# else
+ if ((STREQ (str, "alnum") && ISALNUM ((unsigned char) *n))
+ || (STREQ (str, "alpha") && ISALPHA ((unsigned char) *n))
+ || (STREQ (str, "blank") && ISBLANK ((unsigned char) *n))
+ || (STREQ (str, "cntrl") && ISCNTRL ((unsigned char) *n))
+ || (STREQ (str, "digit") && ISDIGIT ((unsigned char) *n))
+ || (STREQ (str, "graph") && ISGRAPH ((unsigned char) *n))
+ || (STREQ (str, "lower") && ISLOWER ((unsigned char) *n))
+ || (STREQ (str, "print") && ISPRINT ((unsigned char) *n))
+ || (STREQ (str, "punct") && ISPUNCT ((unsigned char) *n))
+ || (STREQ (str, "space") && ISSPACE ((unsigned char) *n))
+ || (STREQ (str, "upper") && ISUPPER ((unsigned char) *n))
+ || (STREQ (str, "xdigit") && ISXDIGIT ((unsigned char) *n)))
+ goto matched;
+# endif
+ }
+ else if (c == '\0')
+ /* [ (unterminated) loses. */
+ return FNM_NOMATCH;
+ else
+ {
+ normal_bracket:
+ if (FOLD (c) == fn)
+ goto matched;
+
+ cold = c;
+ c = *p++;
+
+ if (c == '-' && *p != ']')
+ {
+ /* It is a range. */
+ unsigned char cend = *p++;
+ if (!(flags & FNM_NOESCAPE) && cend == '\\')
+ cend = *p++;
+ if (cend == '\0')
+ return FNM_NOMATCH;
+
+ if (cold <= fn && fn <= FOLD (cend))
+ goto matched;
+
+ c = *p++;
+ }
+ }
+
+ if (c == ']')
+ break;
+ }
+
+ if (!not)
+ return FNM_NOMATCH;
+ break;
+
+ matched:
+ /* Skip the rest of the [...] that already matched. */
+ while (c != ']')
+ {
+ if (c == '\0')
+ /* [... (unterminated) loses. */
+ return FNM_NOMATCH;
+
+ c = *p++;
+ if (!(flags & FNM_NOESCAPE) && c == '\\')
+ {
+ if (*p == '\0')
+ return FNM_NOMATCH;
+ /* XXX 1003.2d11 is unclear if this is right. */
+ ++p;
+ }
+ else if (c == '[' && *p == ':')
+ {
+ do
+ if (*++p == '\0')
+ return FNM_NOMATCH;
+ while (*p != ':' || p[1] == ']');
+ p += 2;
+ c = *p;
+ }
+ }
+ if (not)
+ return FNM_NOMATCH;
+ }
+ break;
+
+ default:
+ if (c != FOLD ((unsigned char) *n))
+ return FNM_NOMATCH;
+ }
+
+ ++n;
+ }
+
+ if (*n == '\0')
+ return 0;
+
+ if ((flags & FNM_LEADING_DIR) && *n == '/')
+ /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */
+ return 0;
+
+ return FNM_NOMATCH;
+
+# undef FOLD
+}
+
+
+int
+fnmatch (pattern, string, flags)
+ const char *pattern;
+ const char *string;
+ int flags;
+{
+ return internal_fnmatch (pattern, string, flags & FNM_PERIOD, flags);
+}
+
+#endif /* _LIBC or not __GNU_LIBRARY__. */
diff --git a/src/kmk/glob/fnmatch.h b/src/kmk/glob/fnmatch.h
new file mode 100644
index 0000000..a788c8e
--- /dev/null
+++ b/src/kmk/glob/fnmatch.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 1991, 1992, 1993, 1996, 1997, 1998, 1999 Free Software
+Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this library; see the file COPYING.LIB. If not, write to the Free
+Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA. */
+
+#ifndef _FNMATCH_H
+#define _FNMATCH_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32
+# if !defined __GLIBC__
+# undef __P
+# define __P(protos) protos
+# endif
+#else /* Not C++ or ANSI C. */
+# undef __P
+# define __P(protos) ()
+/* We can get away without defining `const' here only because in this file
+ it is used only inside the prototype for `fnmatch', which is elided in
+ non-ANSI C where `const' is problematical. */
+#endif /* C++ or ANSI C. */
+
+#ifndef const
+# if (defined __STDC__ && __STDC__) || defined __cplusplus || defined WINDOWS32
+# define __const const
+# else
+# define __const
+# endif
+#endif
+
+/* We #undef these before defining them because some losing systems
+ (HP-UX A.08.07 for example) define these in <unistd.h>. */
+#undef FNM_PATHNAME
+#undef FNM_NOESCAPE
+#undef FNM_PERIOD
+
+/* Bits set in the FLAGS argument to `fnmatch'. */
+#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */
+#define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */
+#define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */
+
+#if !defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _GNU_SOURCE
+# define FNM_FILE_NAME FNM_PATHNAME /* Preferred GNU name. */
+# define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */
+# define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */
+#endif
+
+/* Value returned by `fnmatch' if STRING does not match PATTERN. */
+#define FNM_NOMATCH 1
+
+/* This value is returned if the implementation does not support
+ `fnmatch'. Since this is not the case here it will never be
+ returned but the conformance test suites still require the symbol
+ to be defined. */
+#ifdef _XOPEN_SOURCE
+# define FNM_NOSYS (-1)
+#endif
+
+/* Match NAME against the filename pattern PATTERN,
+ returning zero if it matches, FNM_NOMATCH if not. */
+extern int fnmatch __P ((__const char *__pattern, __const char *__name,
+ int __flags));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* fnmatch.h */
diff --git a/src/kmk/glob/glob.c b/src/kmk/glob/glob.c
new file mode 100644
index 0000000..98827cd
--- /dev/null
+++ b/src/kmk/glob/glob.c
@@ -0,0 +1,1463 @@
+/* Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 Free
+Software Foundation, Inc.
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this library; see the file COPYING.LIB. If not, write to the Free
+Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA. */
+
+/* AIX requires this to be the first thing in the file. */
+#if defined _AIX && !defined __GNUC__
+ #pragma alloca
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Enable GNU extensions in glob.h. */
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE 1
+#endif
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/* Outcomment the following line for production quality code. */
+/* #define NDEBUG 1 */
+#include <assert.h>
+
+#include <stdio.h> /* Needed on stupid SunOS for assert. */
+
+
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GLOB_INTERFACE_VERSION 1
+#if 0 /* bird: Apparently this causes trouble for some debian builds. */
+#if !defined _LIBC && defined __GNU_LIBRARY__ && __GNU_LIBRARY__ > 1
+# include <gnu-versions.h>
+# if _GNU_GLOB_INTERFACE_VERSION == GLOB_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+#endif
+
+#ifndef ELIDE_CODE
+
+#if defined STDC_HEADERS || defined __GNU_LIBRARY__
+# include <stddef.h>
+#endif
+
+#if defined HAVE_UNISTD_H || defined _LIBC
+# include <unistd.h>
+# ifndef POSIX
+# ifdef _POSIX_VERSION
+# define POSIX
+# endif
+# endif
+#endif
+
+#if !defined _AMIGA && !defined VMS && !defined WINDOWS32
+# include <pwd.h>
+#endif
+
+#if !defined __GNU_LIBRARY__ && !defined STDC_HEADERS
+extern int errno;
+#endif
+#ifndef __set_errno
+# define __set_errno(val) errno = (val)
+#endif
+
+#ifndef NULL
+# define NULL 0
+#endif
+
+
+#if defined HAVE_DIRENT_H || defined __GNU_LIBRARY__
+# include <dirent.h>
+# define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+# define dirent direct
+# define NAMLEN(dirent) (dirent)->d_namlen
+# ifdef HAVE_SYS_NDIR_H
+# include <sys/ndir.h>
+# endif
+# ifdef HAVE_SYS_DIR_H
+# include <sys/dir.h>
+# endif
+# ifdef HAVE_NDIR_H
+# include <ndir.h>
+# endif
+# ifdef HAVE_VMSDIR_H
+# include "vmsdir.h"
+# endif /* HAVE_VMSDIR_H */
+#endif
+
+
+/* In GNU systems, <dirent.h> defines this macro for us. */
+#ifdef _D_NAMLEN
+# undef NAMLEN
+# define NAMLEN(d) _D_NAMLEN(d)
+#endif
+
+/* When used in the GNU libc the symbol _DIRENT_HAVE_D_TYPE is available
+ if the `d_type' member for `struct dirent' is available. */
+#ifdef _DIRENT_HAVE_D_TYPE
+# define HAVE_D_TYPE 1
+#endif
+
+
+#if (defined POSIX || defined WINDOWS32) && !defined __GNU_LIBRARY__
+/* Posix does not require that the d_ino field be present, and some
+ systems do not provide it. */
+# define REAL_DIR_ENTRY(dp) 1
+#else
+# define REAL_DIR_ENTRY(dp) (dp->d_ino != 0)
+#endif /* POSIX */
+
+#if defined STDC_HEADERS || defined __GNU_LIBRARY__
+# include <stdlib.h>
+# include <string.h>
+# define ANSI_STRING
+#else /* No standard headers. */
+
+extern char *getenv ();
+
+# ifdef HAVE_STRING_H
+# include <string.h>
+# define ANSI_STRING
+# else
+# include <strings.h>
+# endif
+# ifdef HAVE_MEMORY_H
+# include <memory.h>
+# endif
+
+extern char *malloc (), *realloc ();
+extern void free ();
+
+extern void qsort ();
+extern void abort (), exit ();
+
+#endif /* Standard headers. */
+
+#ifndef ANSI_STRING
+
+# ifndef bzero
+extern void bzero ();
+# endif
+# ifndef bcopy
+extern void bcopy ();
+# endif
+
+# define memcpy(d, s, n) bcopy ((s), (d), (n))
+# define strrchr rindex
+/* memset is only used for zero here, but let's be paranoid. */
+# define memset(s, better_be_zero, n) \
+ ((void) ((better_be_zero) == 0 ? (bzero((s), (n)), 0) : (abort(), 0)))
+#endif /* Not ANSI_STRING. */
+
+#if !defined HAVE_STRCOLL && !defined _LIBC
+# define strcoll strcmp
+#endif
+
+#if !defined HAVE_MEMPCPY && __GLIBC__ - 0 == 2 && __GLIBC_MINOR__ >= 1
+# define HAVE_MEMPCPY 1
+#if 0 /* bird: This messes with the electric.c heap (linux/amd64). Probably missing prototype, so int return. */
+# undef mempcpy
+# define mempcpy(Dest, Src, Len) __mempcpy (Dest, Src, Len)
+#endif
+#endif
+
+#if !defined __GNU_LIBRARY__ && !defined __DJGPP__ && !defined ELECTRIC_HEAP && !defined __APPLE__ /* bird (last two) */
+# ifdef __GNUC__
+__inline
+# endif
+# ifndef __SASC
+# ifdef WINDOWS32
+# include <malloc.h>
+static void *
+my_realloc (void *p, unsigned int n)
+# else
+static char *
+my_realloc (p, n)
+ char *p;
+ unsigned int n;
+# endif
+{
+ /* These casts are the for sake of the broken Ultrix compiler,
+ which warns of illegal pointer combinations otherwise. */
+ if (p == NULL)
+ return (char *) malloc (n);
+ return (char *) realloc (p, n);
+}
+# define realloc my_realloc
+# endif /* __SASC */
+#endif /* __GNU_LIBRARY__ || __DJGPP__ */
+
+
+#if !defined __alloca /*&& !defined __GNU_LIBRARY__ - bird: unresolved __alloca symbol if skipping this for gnu libc. duh. */
+
+# ifdef __GNUC__
+# undef alloca
+# define alloca(n) __builtin_alloca (n)
+# else /* Not GCC. */
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# else /* Not HAVE_ALLOCA_H. */
+# ifndef _AIX
+# ifdef WINDOWS32
+# include <malloc.h>
+# else
+extern char *alloca ();
+# endif /* WINDOWS32 */
+# endif /* Not _AIX. */
+# endif /* sparc or HAVE_ALLOCA_H. */
+# endif /* Not GCC. */
+
+# define __alloca alloca
+
+#endif
+
+#if 1 /*bird: sigh. ndef __GNU_LIBRARY__*/
+# define __stat stat
+# ifdef STAT_MACROS_BROKEN
+# undef S_ISDIR
+# endif
+# ifndef S_ISDIR
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+# endif
+#endif
+
+#ifdef _LIBC
+# ifdef KMK
+# error "_LIBC better not be defined!"
+# endif
+# undef strdup
+# define strdup(str) __strdup (str)
+# define sysconf(id) __sysconf (id)
+# define closedir(dir) __closedir (dir)
+# define opendir(name) __opendir (name)
+# define readdir(str) __readdir (str)
+# define getpwnam_r(name, bufp, buf, len, res) \
+ __getpwnam_r (name, bufp, buf, len, res)
+# ifndef __stat
+# define __stat(fname, buf) __xstat (_STAT_VER, fname, buf)
+# endif
+#endif
+
+#if !(defined STDC_HEADERS || defined __GNU_LIBRARY__)
+# undef size_t
+# define size_t unsigned int
+#endif
+
+/* Some system header files erroneously define these.
+ We want our own definitions from <fnmatch.h> to take precedence. */
+#ifndef __GNU_LIBRARY__
+# undef FNM_PATHNAME
+# undef FNM_NOESCAPE
+# undef FNM_PERIOD
+#endif
+#include <fnmatch.h>
+
+/* Some system header files erroneously define these.
+ We want our own definitions from <glob.h> to take precedence. */
+#ifndef __GNU_LIBRARY__
+# undef GLOB_ERR
+# undef GLOB_MARK
+# undef GLOB_NOSORT
+# undef GLOB_DOOFFS
+# undef GLOB_NOCHECK
+# undef GLOB_APPEND
+# undef GLOB_NOESCAPE
+# undef GLOB_PERIOD
+#endif
+#include <glob.h>
+
+#ifdef HAVE_GETLOGIN_R
+extern int getlogin_r __P ((char *, size_t));
+#else
+extern char *getlogin __P ((void));
+#endif
+
+static
+#if __GNUC__ - 0 >= 2
+inline
+#endif
+const char *next_brace_sub __P ((const char *begin));
+static int glob_in_dir __P ((const char *pattern, const char *directory,
+ int flags,
+ int (*errfunc) (const char *, int),
+ glob_t *pglob));
+static int prefix_array __P ((const char *prefix, char **array, size_t n));
+static int collated_compare __P ((const __ptr_t, const __ptr_t));
+
+#if !defined _LIBC || !defined NO_GLOB_PATTERN_P
+int __glob_pattern_p __P ((const char *pattern, int quote));
+#endif
+
+/* Find the end of the sub-pattern in a brace expression. We define
+ this as an inline function if the compiler permits. */
+static
+#if __GNUC__ - 0 >= 2
+inline
+#endif
+const char *
+next_brace_sub (begin)
+ const char *begin;
+{
+ unsigned int depth = 0;
+ const char *cp = begin;
+
+ while (1)
+ {
+ if (depth == 0)
+ {
+ if (*cp != ',' && *cp != '}' && *cp != '\0')
+ {
+ if (*cp == '{')
+ ++depth;
+ ++cp;
+ continue;
+ }
+ }
+ else
+ {
+ while (*cp != '\0' && (*cp != '}' || depth > 0))
+ {
+ if (*cp == '}')
+ --depth;
+ ++cp;
+ }
+ if (*cp == '\0')
+ /* An incorrectly terminated brace expression. */
+ return NULL;
+
+ continue;
+ }
+ break;
+ }
+
+ return cp;
+}
+
+/* Do glob searching for PATTERN, placing results in PGLOB.
+ The bits defined above may be set in FLAGS.
+ If a directory cannot be opened or read and ERRFUNC is not nil,
+ it is called with the pathname that caused the error, and the
+ `errno' value from the failing call; if it returns non-zero
+ `glob' returns GLOB_ABORTED; if it returns zero, the error is ignored.
+ If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
+ Otherwise, `glob' returns zero. */
+int
+glob (pattern, flags, errfunc, pglob)
+ const char *pattern;
+ int flags;
+ int (*errfunc) __P ((const char *, int));
+ glob_t *pglob;
+{
+ const char *filename;
+ const char *dirname;
+ size_t dirlen;
+ int status;
+ __size_t oldcount; /* bird: correct type. */
+
+ if (pattern == NULL || pglob == NULL || (flags & ~__GLOB_FLAGS) != 0)
+ {
+ __set_errno (EINVAL);
+ return -1;
+ }
+
+ /* POSIX requires all slashes to be matched. This means that with
+ a trailing slash we must match only directories. */
+ if (pattern[0] && pattern[strlen (pattern) - 1] == '/')
+ flags |= GLOB_ONLYDIR;
+
+ if (flags & GLOB_BRACE)
+ {
+ const char *begin = strchr (pattern, '{');
+ if (begin != NULL)
+ {
+ /* Allocate working buffer large enough for our work. Note that
+ we have at least an opening and closing brace. */
+ size_t firstc; /* bird: correct type. */
+ char *alt_start;
+ const char *p;
+ const char *next;
+ const char *rest;
+ size_t rest_len;
+#ifdef __GNUC__
+ char onealt[strlen (pattern) - 1];
+#else
+ char *onealt = (char *) malloc (strlen (pattern) - 1);
+ if (onealt == NULL)
+ {
+ if (!(flags & GLOB_APPEND))
+ globfree (pglob);
+ return GLOB_NOSPACE;
+ }
+#endif
+
+ /* We know the prefix for all sub-patterns. */
+#ifdef HAVE_MEMPCPY
+ alt_start = mempcpy (onealt, pattern, begin - pattern);
+#else
+ memcpy (onealt, pattern, begin - pattern);
+ alt_start = &onealt[begin - pattern];
+#endif
+
+ /* Find the first sub-pattern and at the same time find the
+ rest after the closing brace. */
+ next = next_brace_sub (begin + 1);
+ if (next == NULL)
+ {
+ /* It is an illegal expression. */
+#ifndef __GNUC__
+ free (onealt);
+#endif
+ return glob (pattern, flags & ~GLOB_BRACE, errfunc, pglob);
+ }
+
+ /* Now find the end of the whole brace expression. */
+ rest = next;
+ while (*rest != '}')
+ {
+ rest = next_brace_sub (rest + 1);
+ if (rest == NULL)
+ {
+ /* It is an illegal expression. */
+#ifndef __GNUC__
+ free (onealt);
+#endif
+ return glob (pattern, flags & ~GLOB_BRACE, errfunc, pglob);
+ }
+ }
+ /* Please note that we now can be sure the brace expression
+ is well-formed. */
+ rest_len = strlen (++rest) + 1;
+
+ /* We have a brace expression. BEGIN points to the opening {,
+ NEXT points past the terminator of the first element, and END
+ points past the final }. We will accumulate result names from
+ recursive runs for each brace alternative in the buffer using
+ GLOB_APPEND. */
+
+ if (!(flags & GLOB_APPEND))
+ {
+ /* This call is to set a new vector, so clear out the
+ vector so we can append to it. */
+ pglob->gl_pathc = 0;
+ pglob->gl_pathv = NULL;
+ }
+ firstc = pglob->gl_pathc;
+
+ p = begin + 1;
+ while (1)
+ {
+ int result;
+
+ /* Construct the new glob expression. */
+#ifdef HAVE_MEMPCPY
+ mempcpy (mempcpy (alt_start, p, next - p), rest, rest_len);
+#else
+ memcpy (alt_start, p, next - p);
+ memcpy (&alt_start[next - p], rest, rest_len);
+#endif
+
+ result = glob (onealt,
+ ((flags & ~(GLOB_NOCHECK|GLOB_NOMAGIC))
+ | GLOB_APPEND), errfunc, pglob);
+
+ /* If we got an error, return it. */
+ if (result && result != GLOB_NOMATCH)
+ {
+#ifndef __GNUC__
+ free (onealt);
+#endif
+ if (!(flags & GLOB_APPEND))
+ globfree (pglob);
+ return result;
+ }
+
+ if (*next == '}')
+ /* We saw the last entry. */
+ break;
+
+ p = next + 1;
+ next = next_brace_sub (p);
+ assert (next != NULL);
+ }
+
+#ifndef __GNUC__
+ free (onealt);
+#endif
+
+ if (pglob->gl_pathc != firstc)
+ /* We found some entries. */
+ return 0;
+ else if (!(flags & (GLOB_NOCHECK|GLOB_NOMAGIC)))
+ return GLOB_NOMATCH;
+ }
+ }
+
+ /* Find the filename. */
+ filename = strrchr (pattern, '/');
+#if defined __MSDOS__ || defined WINDOWS32
+ /* The case of "d:pattern". Since `:' is not allowed in
+ file names, we can safely assume that wherever it
+ happens in pattern, it signals the filename part. This
+ is so we could some day support patterns like "[a-z]:foo". */
+ if (filename == NULL)
+ filename = strchr (pattern, ':');
+#endif /* __MSDOS__ || WINDOWS32 */
+ if (filename == NULL)
+ {
+ /* This can mean two things: a simple name or "~name". The later
+ case is nothing but a notation for a directory. */
+ if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && pattern[0] == '~')
+ {
+ dirname = pattern;
+ dirlen = strlen (pattern);
+
+ /* Set FILENAME to NULL as a special flag. This is ugly but
+ other solutions would require much more code. We test for
+ this special case below. */
+ filename = NULL;
+ }
+ else
+ {
+ filename = pattern;
+#ifdef _AMIGA
+ dirname = "";
+#else
+ dirname = ".";
+#endif
+ dirlen = 0;
+ }
+ }
+ else if (filename == pattern)
+ {
+ /* "/pattern". */
+ dirname = "/";
+ dirlen = 1;
+ ++filename;
+ }
+ else
+ {
+ char *newp;
+ dirlen = filename - pattern;
+#if defined __MSDOS__ || defined WINDOWS32
+ if (*filename == ':'
+ || (filename > pattern + 1 && filename[-1] == ':'))
+ {
+ char *drive_spec;
+
+ ++dirlen;
+ drive_spec = (char *) __alloca (dirlen + 1);
+#ifdef HAVE_MEMPCPY
+ *((char *) mempcpy (drive_spec, pattern, dirlen)) = '\0';
+#else
+ memcpy (drive_spec, pattern, dirlen);
+ drive_spec[dirlen] = '\0';
+#endif
+ /* For now, disallow wildcards in the drive spec, to
+ prevent infinite recursion in glob. */
+ if (__glob_pattern_p (drive_spec, !(flags & GLOB_NOESCAPE)))
+ return GLOB_NOMATCH;
+ /* If this is "d:pattern", we need to copy `:' to DIRNAME
+ as well. If it's "d:/pattern", don't remove the slash
+ from "d:/", since "d:" and "d:/" are not the same.*/
+ }
+#endif
+ newp = (char *) __alloca (dirlen + 1);
+#ifdef HAVE_MEMPCPY
+ *((char *) mempcpy (newp, pattern, dirlen)) = '\0';
+#else
+ memcpy (newp, pattern, dirlen);
+ newp[dirlen] = '\0';
+#endif
+ dirname = newp;
+ ++filename;
+
+ if (filename[0] == '\0'
+#if defined __MSDOS__ || defined WINDOWS32
+ && dirname[dirlen - 1] != ':'
+ && (dirlen < 3 || dirname[dirlen - 2] != ':'
+ || dirname[dirlen - 1] != '/')
+#endif
+ && dirlen > 1)
+ /* "pattern/". Expand "pattern", appending slashes. */
+ {
+ int val = glob (dirname, flags | GLOB_MARK, errfunc, pglob);
+ if (val == 0)
+ pglob->gl_flags = ((pglob->gl_flags & ~GLOB_MARK)
+ | (flags & GLOB_MARK));
+ return val;
+ }
+ }
+
+ if (!(flags & GLOB_APPEND))
+ {
+ pglob->gl_pathc = 0;
+ pglob->gl_pathv = NULL;
+ }
+
+ oldcount = pglob->gl_pathc;
+
+#ifndef VMS
+ if ((flags & (GLOB_TILDE|GLOB_TILDE_CHECK)) && dirname[0] == '~')
+ {
+ if (dirname[1] == '\0' || dirname[1] == '/')
+ {
+ /* Look up home directory. */
+#ifdef VMS
+/* This isn't obvious, RTLs of DECC and VAXC know about "HOME" */
+ const char *home_dir = getenv ("SYS$LOGIN");
+#else
+ const char *home_dir = getenv ("HOME");
+#endif
+# ifdef _AMIGA
+ if (home_dir == NULL || home_dir[0] == '\0')
+ home_dir = "SYS:";
+# else
+# ifdef WINDOWS32
+ if (home_dir == NULL || home_dir[0] == '\0')
+ home_dir = "c:/users/default"; /* poor default */
+# else
+# ifdef VMS
+/* Again, this isn't obvious, if "HOME" isn't known "SYS$LOGIN" should be set */
+ if (home_dir == NULL || home_dir[0] == '\0')
+ home_dir = "SYS$DISK:[]";
+# else
+ if (home_dir == NULL || home_dir[0] == '\0')
+ {
+ int success;
+ char *name;
+# if defined HAVE_GETLOGIN_R || defined _LIBC
+ size_t buflen = sysconf (_SC_LOGIN_NAME_MAX) + 1;
+
+ if (buflen == 0)
+ /* `sysconf' does not support _SC_LOGIN_NAME_MAX. Try
+ a moderate value. */
+ buflen = 20;
+ name = (char *) __alloca (buflen);
+
+ success = getlogin_r (name, buflen) >= 0;
+# else
+ success = (name = getlogin ()) != NULL;
+# endif
+ if (success)
+ {
+ struct passwd *p;
+# if defined HAVE_GETPWNAM_R || defined _LIBC
+ size_t pwbuflen = sysconf (_SC_GETPW_R_SIZE_MAX);
+ char *pwtmpbuf;
+ struct passwd pwbuf;
+ int save = errno;
+
+ if (pwbuflen == -1)
+ /* `sysconf' does not support _SC_GETPW_R_SIZE_MAX.
+ Try a moderate value. */
+ pwbuflen = 1024;
+ pwtmpbuf = (char *) __alloca (pwbuflen);
+
+ while (getpwnam_r (name, &pwbuf, pwtmpbuf, pwbuflen, &p)
+ != 0)
+ {
+ if (errno != ERANGE)
+ {
+ p = NULL;
+ break;
+ }
+ pwbuflen *= 2;
+ pwtmpbuf = (char *) __alloca (pwbuflen);
+ __set_errno (save);
+ }
+# else
+ p = getpwnam (name);
+# endif
+ if (p != NULL)
+ home_dir = p->pw_dir;
+ }
+ }
+ if (home_dir == NULL || home_dir[0] == '\0')
+ {
+ if (flags & GLOB_TILDE_CHECK)
+ return GLOB_NOMATCH;
+ else
+ home_dir = "~"; /* No luck. */
+ }
+# endif /* VMS */
+# endif /* WINDOWS32 */
+# endif
+ /* Now construct the full directory. */
+ if (dirname[1] == '\0')
+ dirname = home_dir;
+ else
+ {
+ char *newp;
+ size_t home_len = strlen (home_dir);
+ newp = (char *) __alloca (home_len + dirlen);
+# ifdef HAVE_MEMPCPY
+ mempcpy (mempcpy (newp, home_dir, home_len),
+ &dirname[1], dirlen);
+# else
+ memcpy (newp, home_dir, home_len);
+ memcpy (&newp[home_len], &dirname[1], dirlen);
+# endif
+ dirname = newp;
+ }
+ }
+# if !defined _AMIGA && !defined WINDOWS32 && !defined VMS
+ else
+ {
+ char *end_name = strchr (dirname, '/');
+ const char *user_name;
+ const char *home_dir;
+
+ if (end_name == NULL)
+ user_name = dirname + 1;
+ else
+ {
+ char *newp;
+ newp = (char *) __alloca (end_name - dirname);
+# ifdef HAVE_MEMPCPY
+ *((char *) mempcpy (newp, dirname + 1, end_name - dirname))
+ = '\0';
+# else
+ memcpy (newp, dirname + 1, end_name - dirname);
+ newp[end_name - dirname - 1] = '\0';
+# endif
+ user_name = newp;
+ }
+
+ /* Look up specific user's home directory. */
+ {
+ struct passwd *p;
+# if defined HAVE_GETPWNAM_R || defined _LIBC
+ size_t buflen = sysconf (_SC_GETPW_R_SIZE_MAX);
+ char *pwtmpbuf;
+ struct passwd pwbuf;
+ int save = errno;
+
+ if (buflen == -1)
+ /* `sysconf' does not support _SC_GETPW_R_SIZE_MAX. Try a
+ moderate value. */
+ buflen = 1024;
+ pwtmpbuf = (char *) __alloca (buflen);
+
+ while (getpwnam_r (user_name, &pwbuf, pwtmpbuf, buflen, &p) != 0)
+ {
+ if (errno != ERANGE)
+ {
+ p = NULL;
+ break;
+ }
+ buflen *= 2;
+ pwtmpbuf = __alloca (buflen);
+ __set_errno (save);
+ }
+# else
+ p = getpwnam (user_name);
+# endif
+ if (p != NULL)
+ home_dir = p->pw_dir;
+ else
+ home_dir = NULL;
+ }
+ /* If we found a home directory use this. */
+ if (home_dir != NULL)
+ {
+ char *newp;
+ size_t home_len = strlen (home_dir);
+ size_t rest_len = end_name == NULL ? 0 : strlen (end_name);
+ newp = (char *) __alloca (home_len + rest_len + 1);
+# ifdef HAVE_MEMPCPY
+ *((char *) mempcpy (mempcpy (newp, home_dir, home_len),
+ end_name, rest_len)) = '\0';
+# else
+ memcpy (newp, home_dir, home_len);
+ memcpy (&newp[home_len], end_name, rest_len);
+ newp[home_len + rest_len] = '\0';
+# endif
+ dirname = newp;
+ }
+ else
+ if (flags & GLOB_TILDE_CHECK)
+ /* We have to regard it as an error if we cannot find the
+ home directory. */
+ return GLOB_NOMATCH;
+ }
+# endif /* Not Amiga && not WINDOWS32 && not VMS. */
+ }
+#endif /* Not VMS. */
+
+ /* Now test whether we looked for "~" or "~NAME". In this case we
+ can give the answer now. */
+ if (filename == NULL)
+ {
+ struct stat st;
+
+ /* Return the directory if we don't check for error or if it exists. */
+ if ((flags & GLOB_NOCHECK)
+#ifdef KMK
+ || (flags & GLOB_ALTDIRFUNC
+ ? (*pglob->gl_isdir) (dirname)
+ : __stat (dirname, &st) == 0 && S_ISDIR (st.st_mode))
+#else
+ || (((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_stat) (dirname, &st)
+ : __stat (dirname, &st)) == 0
+ && S_ISDIR (st.st_mode))
+#endif
+ )
+ {
+ pglob->gl_pathv
+ = (char **) realloc (pglob->gl_pathv,
+ (pglob->gl_pathc +
+ ((flags & GLOB_DOOFFS) ?
+ pglob->gl_offs : 0) +
+ 1 + 1) *
+ sizeof (char *));
+ if (pglob->gl_pathv == NULL)
+ return GLOB_NOSPACE;
+
+ if (flags & GLOB_DOOFFS)
+ while (pglob->gl_pathc < pglob->gl_offs)
+ pglob->gl_pathv[pglob->gl_pathc++] = NULL;
+
+#if defined HAVE_STRDUP || defined _LIBC
+ pglob->gl_pathv[pglob->gl_pathc] = strdup (dirname);
+#else
+ {
+ size_t len = strlen (dirname) + 1;
+ char *dircopy = malloc (len);
+ if (dircopy != NULL)
+ pglob->gl_pathv[pglob->gl_pathc] = memcpy (dircopy, dirname,
+ len);
+ }
+#endif
+ if (pglob->gl_pathv[pglob->gl_pathc] == NULL)
+ {
+ free (pglob->gl_pathv);
+ return GLOB_NOSPACE;
+ }
+ pglob->gl_pathv[++pglob->gl_pathc] = NULL;
+ pglob->gl_flags = flags;
+
+ return 0;
+ }
+
+ /* Not found. */
+ return GLOB_NOMATCH;
+ }
+
+ if (__glob_pattern_p (dirname, !(flags & GLOB_NOESCAPE)))
+ {
+ /* The directory name contains metacharacters, so we
+ have to glob for the directory, and then glob for
+ the pattern in each directory found. */
+ glob_t dirs;
+ register __size_t i; /* bird: correct type. */
+
+ status = glob (dirname,
+ ((flags & (GLOB_ERR | GLOB_NOCHECK | GLOB_NOESCAPE))
+ | GLOB_NOSORT | GLOB_ONLYDIR),
+ errfunc, &dirs);
+ if (status != 0)
+ return status;
+
+ /* We have successfully globbed the preceding directory name.
+ For each name we found, call glob_in_dir on it and FILENAME,
+ appending the results to PGLOB. */
+ for (i = 0; i < dirs.gl_pathc; ++i)
+ {
+ int old_pathc;
+
+#ifdef SHELL
+ {
+ /* Make globbing interruptible in the bash shell. */
+ extern int interrupt_state;
+
+ if (interrupt_state)
+ {
+ globfree (&dirs);
+ globfree (&files);
+ return GLOB_ABORTED;
+ }
+ }
+#endif /* SHELL. */
+
+ old_pathc = pglob->gl_pathc;
+ status = glob_in_dir (filename, dirs.gl_pathv[i],
+ ((flags | GLOB_APPEND)
+ & ~(GLOB_NOCHECK | GLOB_ERR)),
+ errfunc, pglob);
+ if (status == GLOB_NOMATCH)
+ /* No matches in this directory. Try the next. */
+ continue;
+
+ if (status != 0)
+ {
+ globfree (&dirs);
+ globfree (pglob);
+ return status;
+ }
+
+ /* Stick the directory on the front of each name. */
+ if (prefix_array (dirs.gl_pathv[i],
+ &pglob->gl_pathv[old_pathc],
+ pglob->gl_pathc - old_pathc))
+ {
+ globfree (&dirs);
+ globfree (pglob);
+ return GLOB_NOSPACE;
+ }
+ }
+
+ flags |= GLOB_MAGCHAR;
+
+ /* We have ignored the GLOB_NOCHECK flag in the `glob_in_dir' calls.
+ But if we have not found any matching entry and thie GLOB_NOCHECK
+ flag was set we must return the list consisting of the disrectory
+ names followed by the filename. */
+ if (pglob->gl_pathc == oldcount)
+ {
+ /* No matches. */
+ if (flags & GLOB_NOCHECK)
+ {
+ size_t filename_len = strlen (filename) + 1;
+ char **new_pathv;
+ struct stat st;
+
+ /* This is an pessimistic guess about the size. */
+ pglob->gl_pathv
+ = (char **) realloc (pglob->gl_pathv,
+ (pglob->gl_pathc +
+ ((flags & GLOB_DOOFFS) ?
+ pglob->gl_offs : 0) +
+ dirs.gl_pathc + 1) *
+ sizeof (char *));
+ if (pglob->gl_pathv == NULL)
+ {
+ globfree (&dirs);
+ return GLOB_NOSPACE;
+ }
+
+ if (flags & GLOB_DOOFFS)
+ while (pglob->gl_pathc < pglob->gl_offs)
+ pglob->gl_pathv[pglob->gl_pathc++] = NULL;
+
+ for (i = 0; i < dirs.gl_pathc; ++i)
+ {
+ const char *dir = dirs.gl_pathv[i];
+ size_t dir_len = strlen (dir);
+
+ /* First check whether this really is a directory. */
+#ifdef KMK
+ if (flags & GLOB_ALTDIRFUNC
+ ? !pglob->gl_isdir (dir)
+ : __stat (dir, &st) != 0 || !S_ISDIR (st.st_mode))
+#else
+ if (((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_stat) (dir, &st) : __stat (dir, &st)) != 0
+ || !S_ISDIR (st.st_mode))
+#endif
+ /* No directory, ignore this entry. */
+ continue;
+
+ pglob->gl_pathv[pglob->gl_pathc] = malloc (dir_len + 1
+ + filename_len);
+ if (pglob->gl_pathv[pglob->gl_pathc] == NULL)
+ {
+ globfree (&dirs);
+ globfree (pglob);
+ return GLOB_NOSPACE;
+ }
+
+#ifdef HAVE_MEMPCPY
+ mempcpy (mempcpy (mempcpy (pglob->gl_pathv[pglob->gl_pathc],
+ dir, dir_len),
+ "/", 1),
+ filename, filename_len);
+#else
+ memcpy (pglob->gl_pathv[pglob->gl_pathc], dir, dir_len);
+ pglob->gl_pathv[pglob->gl_pathc][dir_len] = '/';
+ memcpy (&pglob->gl_pathv[pglob->gl_pathc][dir_len + 1],
+ filename, filename_len);
+#endif
+ ++pglob->gl_pathc;
+ }
+
+ pglob->gl_pathv[pglob->gl_pathc] = NULL;
+ pglob->gl_flags = flags;
+
+ /* Now we know how large the gl_pathv vector must be. */
+ new_pathv = (char **) realloc (pglob->gl_pathv,
+ ((pglob->gl_pathc + 1)
+ * sizeof (char *)));
+ if (new_pathv != NULL)
+ pglob->gl_pathv = new_pathv;
+ }
+ else
+ return GLOB_NOMATCH;
+ }
+
+ globfree (&dirs);
+ }
+ else
+ {
+ status = glob_in_dir (filename, dirname, flags, errfunc, pglob);
+ if (status != 0)
+ return status;
+
+ if (dirlen > 0)
+ {
+ /* Stick the directory on the front of each name. */
+ __size_t ignore = oldcount; /* bird: correct type. */
+
+ if ((flags & GLOB_DOOFFS) && ignore < pglob->gl_offs)
+ ignore = pglob->gl_offs;
+
+ if (prefix_array (dirname,
+ &pglob->gl_pathv[ignore],
+ pglob->gl_pathc - ignore))
+ {
+ globfree (pglob);
+ return GLOB_NOSPACE;
+ }
+ }
+ }
+
+ if (flags & GLOB_MARK)
+ {
+ /* Append slashes to directory names. */
+ __size_t i; /* bird: correct type. */
+ struct stat st;
+ for (i = oldcount; i < pglob->gl_pathc; ++i)
+#ifdef KMK
+ if (flags & GLOB_ALTDIRFUNC
+ ? pglob->gl_isdir (pglob->gl_pathv[i])
+ : __stat (pglob->gl_pathv[i], &st) == 0 && S_ISDIR (st.st_mode) )
+#else
+ if (((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_stat) (pglob->gl_pathv[i], &st)
+ : __stat (pglob->gl_pathv[i], &st)) == 0
+ && S_ISDIR (st.st_mode))
+#endif
+ {
+ size_t len = strlen (pglob->gl_pathv[i]) + 2;
+ char *new = realloc (pglob->gl_pathv[i], len);
+ if (new == NULL)
+ {
+ globfree (pglob);
+ return GLOB_NOSPACE;
+ }
+ strcpy (&new[len - 2], "/");
+ pglob->gl_pathv[i] = new;
+ }
+ }
+
+ if (!(flags & GLOB_NOSORT))
+ {
+ /* Sort the vector. */
+ int non_sort = oldcount;
+
+ if ((flags & GLOB_DOOFFS) && pglob->gl_offs > oldcount)
+ non_sort = pglob->gl_offs;
+
+ qsort ((__ptr_t) &pglob->gl_pathv[non_sort],
+ pglob->gl_pathc - non_sort,
+ sizeof (char *), collated_compare);
+ }
+
+ return 0;
+}
+
+
+/* Free storage allocated in PGLOB by a previous `glob' call. */
+void
+globfree (pglob)
+ register glob_t *pglob;
+{
+ if (pglob->gl_pathv != NULL)
+ {
+ register __size_t i; /* bird: correct type */
+ for (i = 0; i < pglob->gl_pathc; ++i)
+ if (pglob->gl_pathv[i] != NULL)
+ free ((__ptr_t) pglob->gl_pathv[i]);
+ free ((__ptr_t) pglob->gl_pathv);
+ }
+}
+
+
+/* Do a collated comparison of A and B. */
+static int
+collated_compare (a, b)
+ const __ptr_t a;
+ const __ptr_t b;
+{
+ const char *const s1 = *(const char *const * const) a;
+ const char *const s2 = *(const char *const * const) b;
+
+ if (s1 == s2)
+ return 0;
+ if (s1 == NULL)
+ return 1;
+ if (s2 == NULL)
+ return -1;
+ return strcoll (s1, s2);
+}
+
+
+/* Prepend DIRNAME to each of N members of ARRAY, replacing ARRAY's
+ elements in place. Return nonzero if out of memory, zero if successful.
+ A slash is inserted between DIRNAME and each elt of ARRAY,
+ unless DIRNAME is just "/". Each old element of ARRAY is freed. */
+static int
+prefix_array (dirname, array, n)
+ const char *dirname;
+ char **array;
+ size_t n;
+{
+ register size_t i;
+ size_t dirlen = strlen (dirname);
+#if defined __MSDOS__ || defined WINDOWS32
+ int sep_char = '/';
+# define DIRSEP_CHAR sep_char
+#else
+# define DIRSEP_CHAR '/'
+#endif
+
+ if (dirlen == 1 && dirname[0] == '/')
+ /* DIRNAME is just "/", so normal prepending would get us "//foo".
+ We want "/foo" instead, so don't prepend any chars from DIRNAME. */
+ dirlen = 0;
+#if defined __MSDOS__ || defined WINDOWS32
+ else if (dirlen > 1)
+ {
+ if (dirname[dirlen - 1] == '/' && dirname[dirlen - 2] == ':')
+ /* DIRNAME is "d:/". Don't prepend the slash from DIRNAME. */
+ --dirlen;
+ else if (dirname[dirlen - 1] == ':')
+ {
+ /* DIRNAME is "d:". Use `:' instead of `/'. */
+ --dirlen;
+ sep_char = ':';
+ }
+ }
+#endif
+
+ for (i = 0; i < n; ++i)
+ {
+ size_t eltlen = strlen (array[i]) + 1;
+ char *new = (char *) malloc (dirlen + 1 + eltlen);
+ if (new == NULL)
+ {
+ while (i > 0)
+ free ((__ptr_t) array[--i]);
+ return 1;
+ }
+
+#ifdef HAVE_MEMPCPY
+ {
+ char *endp = (char *) mempcpy (new, dirname, dirlen);
+ *endp++ = DIRSEP_CHAR;
+ mempcpy (endp, array[i], eltlen);
+ }
+#else
+ memcpy (new, dirname, dirlen);
+ new[dirlen] = DIRSEP_CHAR;
+ memcpy (&new[dirlen + 1], array[i], eltlen);
+#endif
+ free ((__ptr_t) array[i]);
+ array[i] = new;
+ }
+
+ return 0;
+}
+
+
+/* We must not compile this function twice. */
+#if !defined _LIBC || !defined NO_GLOB_PATTERN_P
+/* Return nonzero if PATTERN contains any metacharacters.
+ Metacharacters can be quoted with backslashes if QUOTE is nonzero. */
+int
+__glob_pattern_p (pattern, quote)
+ const char *pattern;
+ int quote;
+{
+ register const char *p;
+ int open = 0;
+
+ for (p = pattern; *p != '\0'; ++p)
+ switch (*p)
+ {
+ case '?':
+ case '*':
+ return 1;
+
+ case '\\':
+ if (quote && p[1] != '\0')
+ ++p;
+ break;
+
+ case '[':
+ open = 1;
+ break;
+
+ case ']':
+ if (open)
+ return 1;
+ break;
+ }
+
+ return 0;
+}
+# ifdef _LIBC
+weak_alias (__glob_pattern_p, glob_pattern_p)
+# endif
+#endif
+
+
+/* Like `glob', but PATTERN is a final pathname component,
+ and matches are searched for in DIRECTORY.
+ The GLOB_NOSORT bit in FLAGS is ignored. No sorting is ever done.
+ The GLOB_APPEND flag is assumed to be set (always appends). */
+static int
+glob_in_dir (pattern, directory, flags, errfunc, pglob)
+ const char *pattern;
+ const char *directory;
+ int flags;
+ int (*errfunc) __P ((const char *, int));
+ glob_t *pglob;
+{
+ __ptr_t stream = NULL;
+
+ struct globlink
+ {
+ struct globlink *next;
+ char *name;
+ };
+ struct globlink *names = NULL;
+ size_t nfound;
+ int meta;
+ int save;
+
+#ifdef VMS
+ if (*directory == 0)
+ directory = "[]";
+#endif
+ meta = __glob_pattern_p (pattern, !(flags & GLOB_NOESCAPE));
+ if (meta == 0)
+ {
+ if (flags & (GLOB_NOCHECK|GLOB_NOMAGIC))
+ /* We need not do any tests. The PATTERN contains no meta
+ characters and we must not return an error therefore the
+ result will always contain exactly one name. */
+ flags |= GLOB_NOCHECK;
+ else
+ {
+ /* Since we use the normal file functions we can also use stat()
+ to verify the file is there. */
+ struct stat st;
+ size_t patlen = strlen (pattern);
+ size_t dirlen = strlen (directory);
+ char *fullname = (char *) __alloca (dirlen + 1 + patlen + 1);
+
+# ifdef HAVE_MEMPCPY
+ mempcpy (mempcpy (mempcpy (fullname, directory, dirlen),
+ "/", 1),
+ pattern, patlen + 1);
+# else
+ memcpy (fullname, directory, dirlen);
+ fullname[dirlen] = '/';
+ memcpy (&fullname[dirlen + 1], pattern, patlen + 1);
+# endif
+# ifdef KMK
+ if (flags & GLOB_ALTDIRFUNC ? pglob->gl_exists (fullname) : __stat (fullname, &st) == 0)
+# else
+ if (((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_stat) (fullname, &st)
+ : __stat (fullname, &st)) == 0)
+# endif
+ /* We found this file to be existing. Now tell the rest
+ of the function to copy this name into the result. */
+ flags |= GLOB_NOCHECK;
+ }
+
+ nfound = 0;
+ }
+ else
+ {
+ if (pattern[0] == '\0')
+ {
+ /* This is a special case for matching directories like in
+ "*a/". */
+ names = (struct globlink *) __alloca (sizeof (struct globlink));
+ names->name = (char *) malloc (1);
+ if (names->name == NULL)
+ goto memory_error;
+ names->name[0] = '\0';
+ names->next = NULL;
+ nfound = 1;
+ meta = 0;
+ }
+ else
+ {
+ stream = ((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_opendir) (directory)
+ : (__ptr_t) opendir (directory));
+ if (stream == NULL)
+ {
+ if (errno != ENOTDIR
+ && ((errfunc != NULL && (*errfunc) (directory, errno))
+ || (flags & GLOB_ERR)))
+ return GLOB_ABORTED;
+ nfound = 0;
+ meta = 0;
+ }
+ else
+ {
+ int fnm_flags = ((!(flags & GLOB_PERIOD) ? FNM_PERIOD : 0)
+ | ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0)
+#if defined HAVE_CASE_INSENSITIVE_FS
+ | FNM_CASEFOLD
+#endif
+ );
+ nfound = 0;
+ flags |= GLOB_MAGCHAR;
+
+ while (1)
+ {
+ const char *name;
+ size_t len;
+ struct dirent *d = ((flags & GLOB_ALTDIRFUNC)
+ ? (*pglob->gl_readdir) (stream)
+ : readdir ((DIR *) stream));
+ if (d == NULL)
+ break;
+ if (! REAL_DIR_ENTRY (d))
+ continue;
+
+#ifdef HAVE_D_TYPE
+ /* If we shall match only directories use the information
+ provided by the dirent call if possible. */
+ if ((flags & GLOB_ONLYDIR)
+ && d->d_type != DT_UNKNOWN && d->d_type != DT_DIR)
+ continue;
+#endif
+
+ name = d->d_name;
+
+ if (fnmatch (pattern, name, fnm_flags) == 0)
+ {
+ struct globlink *new = (struct globlink *)
+ __alloca (sizeof (struct globlink));
+ len = NAMLEN (d);
+ new->name = (char *) malloc (len + 1);
+ if (new->name == NULL)
+ goto memory_error;
+#ifdef HAVE_MEMPCPY
+ *((char *) mempcpy ((__ptr_t) new->name, name, len))
+ = '\0';
+#else
+ memcpy ((__ptr_t) new->name, name, len);
+ new->name[len] = '\0';
+#endif
+ new->next = names;
+ names = new;
+ ++nfound;
+ }
+ }
+ }
+ }
+ }
+
+ if (nfound == 0 && (flags & GLOB_NOCHECK))
+ {
+ size_t len = strlen (pattern);
+ nfound = 1;
+ names = (struct globlink *) __alloca (sizeof (struct globlink));
+ names->next = NULL;
+ names->name = (char *) malloc (len + 1);
+ if (names->name == NULL)
+ goto memory_error;
+#ifdef HAVE_MEMPCPY
+ *((char *) mempcpy (names->name, pattern, len)) = '\0';
+#else
+ memcpy (names->name, pattern, len);
+ names->name[len] = '\0';
+#endif
+ }
+
+ if (nfound != 0)
+ {
+ pglob->gl_pathv
+ = (char **) realloc (pglob->gl_pathv,
+ (pglob->gl_pathc +
+ ((flags & GLOB_DOOFFS) ? pglob->gl_offs : 0) +
+ nfound + 1) *
+ sizeof (char *));
+ if (pglob->gl_pathv == NULL)
+ goto memory_error;
+
+ if (flags & GLOB_DOOFFS)
+ while (pglob->gl_pathc < pglob->gl_offs)
+ pglob->gl_pathv[pglob->gl_pathc++] = NULL;
+
+ for (; names != NULL; names = names->next)
+ pglob->gl_pathv[pglob->gl_pathc++] = names->name;
+ pglob->gl_pathv[pglob->gl_pathc] = NULL;
+
+ pglob->gl_flags = flags;
+ }
+
+ save = errno;
+ if (stream != NULL)
+ {
+ if (flags & GLOB_ALTDIRFUNC)
+ (*pglob->gl_closedir) (stream);
+ else
+ closedir ((DIR *) stream);
+ }
+ __set_errno (save);
+
+ return nfound == 0 ? GLOB_NOMATCH : 0;
+
+ memory_error:
+ {
+ /*int*/ save = errno;
+ if (flags & GLOB_ALTDIRFUNC)
+ (*pglob->gl_closedir) (stream);
+ else
+ closedir ((DIR *) stream);
+ __set_errno (save);
+ }
+ while (names != NULL)
+ {
+ if (names->name != NULL)
+ free ((__ptr_t) names->name);
+ names = names->next;
+ }
+ return GLOB_NOSPACE;
+}
+
+#endif /* Not ELIDE_CODE. */
diff --git a/src/kmk/glob/glob.h b/src/kmk/glob/glob.h
new file mode 100644
index 0000000..5b82140
--- /dev/null
+++ b/src/kmk/glob/glob.h
@@ -0,0 +1,215 @@
+/* Copyright (C) 1991, 1992, 1995, 1996, 1997, 1998 Free Software Foundation,
+Inc.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public License
+along with this library; see the file COPYING.LIB. If not, write to the Free
+Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
+USA. */
+
+#ifndef _GLOB_H
+#define _GLOB_H 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#undef __ptr_t
+#if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32
+# if !defined __GLIBC__
+# undef __P
+# undef __PMT
+# define __P(protos) protos
+# define __PMT(protos) protos
+# if !defined __GNUC__ || __GNUC__ < 2
+# undef __const
+# define __const const
+# endif
+# endif
+# define __ptr_t void *
+#else /* Not C++ or ANSI C. */
+# undef __P
+# undef __PMT
+# define __P(protos) ()
+# define __PMT(protos) ()
+# undef __const
+# define __const
+# define __ptr_t char *
+#endif /* C++ or ANSI C. */
+
+/* We need `size_t' for the following definitions. */
+#ifndef __size_t
+# if defined __FreeBSD__
+# define __size_t size_t
+# else
+# if defined __GNUC__ && __GNUC__ >= 2
+typedef __SIZE_TYPE__ __size_t;
+# else
+/* This is a guess. */
+/*hb
+ * Conflicts with DECCs already defined type __size_t.
+ * Defining an own type with a name beginning with '__' is no good.
+ * Anyway if DECC is used and __SIZE_T is defined then __size_t is
+ * already defined (and I hope it's exactly the one we need here).
+ */
+# if !(defined __DECC && defined __SIZE_T)
+typedef unsigned long int __size_t;
+# endif
+# endif
+# endif
+#else
+/* The GNU CC stddef.h version defines __size_t as empty. We need a real
+ definition. */
+# undef __size_t
+# define __size_t size_t
+#endif
+
+/* Bits set in the FLAGS argument to `glob'. */
+#define GLOB_ERR (1 << 0)/* Return on read errors. */
+#define GLOB_MARK (1 << 1)/* Append a slash to each name. */
+#define GLOB_NOSORT (1 << 2)/* Don't sort the names. */
+#define GLOB_DOOFFS (1 << 3)/* Insert PGLOB->gl_offs NULLs. */
+#define GLOB_NOCHECK (1 << 4)/* If nothing matches, return the pattern. */
+#define GLOB_APPEND (1 << 5)/* Append to results of a previous call. */
+#define GLOB_NOESCAPE (1 << 6)/* Backslashes don't quote metacharacters. */
+#define GLOB_PERIOD (1 << 7)/* Leading `.' can be matched by metachars. */
+
+#if (!defined _POSIX_C_SOURCE || _POSIX_C_SOURCE < 2 || defined _BSD_SOURCE \
+ || defined _GNU_SOURCE)
+# define GLOB_MAGCHAR (1 << 8)/* Set in gl_flags if any metachars seen. */
+# define GLOB_ALTDIRFUNC (1 << 9)/* Use gl_opendir et al functions. */
+# define GLOB_BRACE (1 << 10)/* Expand "{a,b}" to "a" "b". */
+# define GLOB_NOMAGIC (1 << 11)/* If no magic chars, return the pattern. */
+# define GLOB_TILDE (1 << 12)/* Expand ~user and ~ to home directories. */
+# define GLOB_ONLYDIR (1 << 13)/* Match only directories. */
+# define GLOB_TILDE_CHECK (1 << 14)/* Like GLOB_TILDE but return an error
+ if the user name is not available. */
+# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
+ GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
+ GLOB_PERIOD|GLOB_ALTDIRFUNC|GLOB_BRACE| \
+ GLOB_NOMAGIC|GLOB_TILDE|GLOB_ONLYDIR|GLOB_TILDE_CHECK)
+#else
+# define __GLOB_FLAGS (GLOB_ERR|GLOB_MARK|GLOB_NOSORT|GLOB_DOOFFS| \
+ GLOB_NOESCAPE|GLOB_NOCHECK|GLOB_APPEND| \
+ GLOB_PERIOD)
+#endif
+
+/* Error returns from `glob'. */
+#define GLOB_NOSPACE 1 /* Ran out of memory. */
+#define GLOB_ABORTED 2 /* Read error. */
+#define GLOB_NOMATCH 3 /* No matches found. */
+#define GLOB_NOSYS 4 /* Not implemented. */
+#ifdef _GNU_SOURCE
+/* Previous versions of this file defined GLOB_ABEND instead of
+ GLOB_ABORTED. Provide a compatibility definition here. */
+# define GLOB_ABEND GLOB_ABORTED
+#endif
+
+/* Structure describing a globbing run. */
+#if !defined _AMIGA && !defined VMS /* Buggy compiler. */
+struct stat;
+#endif
+typedef struct
+ {
+ __size_t gl_pathc; /* Count of paths matched by the pattern. */
+ char **gl_pathv; /* List of matched pathnames. */
+ __size_t gl_offs; /* Slots to reserve in `gl_pathv'. */
+ int gl_flags; /* Set to FLAGS, maybe | GLOB_MAGCHAR. */
+
+ /* If the GLOB_ALTDIRFUNC flag is set, the following functions
+ are used instead of the normal file access functions. */
+ void (*gl_closedir) __PMT ((void *));
+ struct dirent *(*gl_readdir) __PMT ((void *));
+ __ptr_t (*gl_opendir) __PMT ((__const char *));
+ int (*gl_lstat) __PMT ((__const char *, struct stat *));
+#if defined(VMS) && defined(__DECC) && !defined(_POSIX_C_SOURCE)
+ int (*gl_stat) __PMT ((__const char *, struct stat *, ...));
+#else
+ int (*gl_stat) __PMT ((__const char *, struct stat *));
+#endif
+#ifdef KMK
+# define GLOB_WITH_EXTENDED_KMK_MEMBERS
+ int (*gl_exists) __PMT ((__const char *));
+ int (*gl_isdir) __PMT ((__const char *));
+#endif
+ } glob_t;
+
+#ifdef _LARGEFILE64_SOURCE
+struct stat64;
+typedef struct
+ {
+ __size_t gl_pathc;
+ char **gl_pathv;
+ __size_t gl_offs;
+ int gl_flags;
+
+ /* If the GLOB_ALTDIRFUNC flag is set, the following functions
+ are used instead of the normal file access functions. */
+ void (*gl_closedir) __PMT ((void *));
+ struct dirent64 *(*gl_readdir) __PMT ((void *));
+ __ptr_t (*gl_opendir) __PMT ((__const char *));
+ int (*gl_lstat) __PMT ((__const char *, struct stat64 *));
+ int (*gl_stat) __PMT ((__const char *, struct stat64 *));
+ } glob64_t;
+#endif
+
+#if _FILE_OFFSET_BITS == 64 && __GNUC__ < 2
+# define glob glob64
+# define globfree globfree64
+#else
+# ifdef _LARGEFILE64_SOURCE
+extern int glob64 __P ((__const char *__pattern, int __flags,
+ int (*__errfunc) (__const char *, int),
+ glob64_t *__pglob));
+
+extern void globfree64 __P ((glob64_t *__pglob));
+# endif
+#endif
+
+/* Do glob searching for PATTERN, placing results in PGLOB.
+ The bits defined above may be set in FLAGS.
+ If a directory cannot be opened or read and ERRFUNC is not nil,
+ it is called with the pathname that caused the error, and the
+ `errno' value from the failing call; if it returns non-zero
+ `glob' returns GLOB_ABEND; if it returns zero, the error is ignored.
+ If memory cannot be allocated for PGLOB, GLOB_NOSPACE is returned.
+ Otherwise, `glob' returns zero. */
+#if _FILE_OFFSET_BITS != 64 || __GNUC__ < 2
+extern int glob __P ((__const char *__pattern, int __flags,
+ int (*__errfunc) (__const char *, int),
+ glob_t *__pglob));
+
+/* Free storage allocated in PGLOB by a previous `glob' call. */
+extern void globfree __P ((glob_t *__pglob));
+#else
+extern int glob __P ((__const char *__pattern, int __flags,
+ int (*__errfunc) (__const char *, int),
+ glob_t *__pglob)) __asm__ ("glob64");
+
+extern void globfree __P ((glob_t *__pglob)) __asm__ ("globfree64");
+#endif
+
+
+#ifdef _GNU_SOURCE
+/* Return nonzero if PATTERN contains any metacharacters.
+ Metacharacters can be quoted with backslashes if QUOTE is nonzero.
+
+ This function is not part of the interface specified by POSIX.2
+ but several programs want to use it. */
+extern int glob_pattern_p __P ((__const char *__pattern, int __quote));
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* glob.h */
diff --git a/src/kmk/gmk-default.scm b/src/kmk/gmk-default.scm
new file mode 100644
index 0000000..e7353f9
--- /dev/null
+++ b/src/kmk/gmk-default.scm
@@ -0,0 +1,53 @@
+;; Contents of the (gnu make) Guile module
+;; Copyright (C) 2011-2016 Free Software Foundation, Inc.
+;; This file is part of GNU Make.
+;;
+;; GNU Make is free software; you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free
+;; Software Foundation; either version 3 of the License, or (at your option)
+;; any later version.
+;;
+;; GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+;; details.
+;;
+;; You should have received a copy of the GNU General Public License along
+;; with this program. If not, see <http://www.gnu.org/licenses/>.
+
+(define (to-string-maybe x)
+ (cond
+ ;; In GNU make, "false" is the empty string
+ ((or (not x)
+ (unspecified? x)
+ (variable? x)
+ (null? x)
+ (and (string? x) (string-null? x)))
+ #f)
+ ;; We want something not false... not sure about this
+ ((eq? x #t) "#t")
+ ;; Basics
+ ((or (symbol? x) (number? x))
+ (object->string x))
+ ((char? x)
+ (string x))
+ ;; Printable string (no special characters)
+ ((and (string? x) (string-every char-set:printing x))
+ x)
+ ;; No idea: fail
+ (else (error "Unknown object:" x))))
+
+(define (obj-to-str x)
+ (let ((acc '()))
+ (define (walk x)
+ (cond ((pair? x) (walk (car x)) (walk (cdr x)))
+ ((to-string-maybe x) => (lambda (s) (set! acc (cons s acc))))))
+ (walk x)
+ (string-join (reverse! acc))))
+
+;; Return the value of the GNU make variable V
+(define (gmk-var v)
+ (gmk-expand (format #f "$(~a)" (obj-to-str v))))
+
+;; Export the public interfaces
+(export gmk-expand gmk-eval gmk-var)
diff --git a/src/kmk/gnumake.h b/src/kmk/gnumake.h
new file mode 100644
index 0000000..b508562
--- /dev/null
+++ b/src/kmk/gnumake.h
@@ -0,0 +1,79 @@
+/* External interfaces usable by dynamic objects loaded into GNU Make.
+ --THIS API IS A "TECHNOLOGY PREVIEW" ONLY. IT IS NOT A STABLE INTERFACE--
+
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _GNUMAKE_H_
+#define _GNUMAKE_H_
+
+/* Specify the location of elements read from makefiles. */
+typedef struct
+ {
+ const char *filenm;
+ unsigned long lineno;
+ } gmk_floc;
+
+typedef char *(*gmk_func_ptr)(const char *nm, unsigned int argc, char **argv);
+
+#ifdef _WIN32
+# ifdef GMK_BUILDING_MAKE
+# define GMK_EXPORT __declspec(dllexport)
+# else
+# define GMK_EXPORT __declspec(dllimport)
+# endif
+#else
+# define GMK_EXPORT
+#endif
+
+/* Free memory returned by the gmk_expand() function. */
+GMK_EXPORT void gmk_free (char *str);
+
+/* Allocate memory in GNU make's context. */
+GMK_EXPORT char *gmk_alloc (unsigned int len);
+
+/* Run $(eval ...) on the provided string BUFFER. */
+GMK_EXPORT void gmk_eval (const char *buffer, const gmk_floc *floc);
+
+/* Run GNU make expansion on the provided string STR.
+ Returns an allocated buffer that the caller must free with gmk_free(). */
+GMK_EXPORT char *gmk_expand (const char *str);
+
+/* Register a new GNU make function NAME (maximum of 255 chars long).
+ When the function is expanded in the makefile, FUNC will be invoked with
+ the appropriate arguments.
+
+ The return value of FUNC must be either NULL, in which case it expands to
+ the empty string, or a pointer to the result of the expansion in a string
+ created by gmk_alloc(). GNU make will free the memory when it's done.
+
+ MIN_ARGS is the minimum number of arguments the function requires.
+ MAX_ARGS is the maximum number of arguments (or 0 if there's no maximum).
+ MIN_ARGS and MAX_ARGS may not exceed 255.
+
+ The FLAGS value may be GMK_FUNC_DEFAULT, or one or more of the following
+ flags OR'd together:
+
+ GMK_FUNC_NOEXPAND: the arguments to the function will be not be expanded
+ before FUNC is called.
+*/
+GMK_EXPORT void gmk_add_function (const char *name, gmk_func_ptr func,
+ unsigned int min_args, unsigned int max_args,
+ unsigned int flags);
+
+#define GMK_FUNC_DEFAULT 0x00
+#define GMK_FUNC_NOEXPAND 0x01
+
+#endif /* _GNUMAKE_H_ */
diff --git a/src/kmk/guile.c b/src/kmk/guile.c
new file mode 100644
index 0000000..1b055c3
--- /dev/null
+++ b/src/kmk/guile.c
@@ -0,0 +1,159 @@
+/* GNU Guile interface for GNU Make.
+Copyright (C) 2011-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#ifdef HAVE_GUILE
+
+#include "gnumake.h"
+
+#include "debug.h"
+#include "filedef.h"
+#include "dep.h"
+#include "variable.h"
+
+#include <libguile.h>
+
+/* Pre-2.0 versions of Guile don't have a typedef for gsubr function types. */
+#if SCM_MAJOR_VERSION < 2
+# define GSUBR_TYPE SCM (*) ()
+/* Guile 1.x doesn't really support i18n. */
+# define EVAL_STRING(_s) scm_c_eval_string (_s)
+#else
+# define GSUBR_TYPE scm_t_subr
+# define EVAL_STRING(_s) scm_eval_string (scm_from_utf8_string (_s))
+#endif
+
+static SCM make_mod = SCM_EOL;
+static SCM obj_to_str = SCM_EOL;
+
+/* Convert an SCM object into a string. */
+static char *
+cvt_scm_to_str (SCM obj)
+{
+ return scm_to_locale_string (scm_call_1 (obj_to_str, obj));
+}
+
+/* Perform the GNU make expansion function. */
+static SCM
+guile_expand_wrapper (SCM obj)
+{
+ char *str = cvt_scm_to_str (obj);
+ SCM ret;
+ char *res;
+
+ DB (DB_BASIC, (_("guile: Expanding '%s'\n"), str));
+ res = gmk_expand (str);
+ ret = scm_from_locale_string (res);
+
+ free (str);
+ free (res);
+
+ return ret;
+}
+
+/* Perform the GNU make eval function. */
+static SCM
+guile_eval_wrapper (SCM obj)
+{
+ char *str = cvt_scm_to_str (obj);
+
+ DB (DB_BASIC, (_("guile: Evaluating '%s'\n"), str));
+ gmk_eval (str, 0);
+
+ return SCM_BOOL_F;
+}
+
+/* Invoked by scm_c_define_module(), in the context of the GNU make module. */
+static void
+guile_define_module (void *data UNUSED)
+{
+/* Ingest the predefined Guile module for GNU make. */
+#include "gmk-default.h"
+
+ /* Register a subr for GNU make's eval capability. */
+ scm_c_define_gsubr ("gmk-expand", 1, 0, 0, (GSUBR_TYPE) guile_expand_wrapper);
+
+ /* Register a subr for GNU make's eval capability. */
+ scm_c_define_gsubr ("gmk-eval", 1, 0, 0, (GSUBR_TYPE) guile_eval_wrapper);
+
+ /* Define the rest of the module. */
+ scm_c_eval_string (GUILE_module_defn);
+}
+
+/* Initialize the GNU make Guile module. */
+static void *
+guile_init (void *arg UNUSED)
+{
+ /* Define the module. */
+ make_mod = scm_c_define_module ("gnu make", guile_define_module, NULL);
+
+ /* Get a reference to the object-to-string translator, for later. */
+ obj_to_str = scm_variable_ref (scm_c_module_lookup (make_mod, "obj-to-str"));
+
+ /* Import the GNU make module exports into the generic space. */
+ scm_c_eval_string ("(use-modules (gnu make))");
+
+ return NULL;
+}
+
+static void *
+internal_guile_eval (void *arg)
+{
+ return cvt_scm_to_str (EVAL_STRING (arg));
+}
+
+/* This is the function registered with make */
+static char *
+func_guile (const char *funcname UNUSED, unsigned int argc UNUSED, char **argv)
+{
+ static int init = 0;
+
+ if (! init)
+ {
+ /* Initialize the Guile interpreter. */
+ scm_with_guile (guile_init, NULL);
+ init = 1;
+ }
+
+ if (argv[0] && argv[0][0] != '\0')
+ return scm_with_guile (internal_guile_eval, argv[0]);
+
+ return NULL;
+}
+
+/* ----- Public interface ----- */
+
+/* We could send the flocp to define_new_function(), but since guile is
+ "kind of" built-in, that didn't seem so useful. */
+int
+guile_gmake_setup (const floc *flocp UNUSED)
+{
+ /* Create a make function "guile". */
+ gmk_add_function ("guile", func_guile, 0, 1, GMK_FUNC_DEFAULT);
+
+ return 1;
+}
+
+#else
+
+int
+guile_gmake_setup (const floc *flocp UNUSED)
+{
+ return 1;
+}
+
+#endif
diff --git a/src/kmk/hash.c b/src/kmk/hash.c
new file mode 100644
index 0000000..070d2e4
--- /dev/null
+++ b/src/kmk/hash.c
@@ -0,0 +1,536 @@
+/* hash.c -- hash table maintenance
+Copyright (C) 1995, 1999, 2002, 2010 Free Software Foundation, Inc.
+Written by Greg McGary <gkm@gnu.org> <greg@mcgary.org>
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "hash.h"
+#ifdef CONFIG_WITH_STRCACHE2
+# include <assert.h>
+#endif
+
+#define CALLOC(t, n) ((t *) xcalloc (sizeof (t) * (n)))
+#define MALLOC(t, n) ((t *) xmalloc (sizeof (t) * (n)))
+#define REALLOC(o, t, n) ((t *) xrealloc ((o), sizeof (t) * (n)))
+#define CLONE(o, t, n) ((t *) memcpy (MALLOC (t, (n)), (o), sizeof (t) * (n)))
+
+static void hash_rehash __P((struct hash_table* ht));
+static unsigned long round_up_2 __P((unsigned long rough));
+
+/* Implement double hashing with open addressing. The table size is
+ always a power of two. The secondary ('increment') hash function
+ is forced to return an odd-value, in order to be relatively prime
+ to the table size. This guarantees that the increment can
+ potentially hit every slot in the table during collision
+ resolution. */
+
+void *hash_deleted_item = &hash_deleted_item;
+
+/* Force the table size to be a power of two, possibly rounding up the
+ given size. */
+
+void
+hash_init (struct hash_table *ht, unsigned long size,
+ hash_func_t hash_1, hash_func_t hash_2, hash_cmp_func_t hash_cmp)
+{
+ ht->ht_size = round_up_2 (size);
+ ht->ht_empty_slots = ht->ht_size;
+ ht->ht_vec = (void**) CALLOC (struct token *, ht->ht_size);
+ if (ht->ht_vec == 0)
+ {
+ fprintf (stderr, _("can't allocate %lu bytes for hash table: memory exhausted"),
+ ht->ht_size * (unsigned long) sizeof (struct token *));
+ exit (MAKE_TROUBLE);
+ }
+
+ ht->ht_capacity = ht->ht_size - (ht->ht_size / 16); /* 93.75% loading factor */
+ ht->ht_fill = 0;
+ ht->ht_collisions = 0;
+ ht->ht_lookups = 0;
+ ht->ht_rehashes = 0;
+ ht->ht_hash_1 = hash_1;
+ ht->ht_hash_2 = hash_2;
+ ht->ht_compare = hash_cmp;
+#ifdef CONFIG_WITH_STRCACHE2
+ ht->ht_strcache = 0;
+ ht->ht_off_string = 0;
+#endif
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+/* Same as hash_init, except that no callbacks are needed since all
+ keys - including the ones being searched for - are from a string
+ cache. This means that any give string will only have one pointer
+ value and that the hash and length can be retrived very cheaply,
+ thus permitting some nice optimizations.
+
+ STRCACHE points to the string cache, while OFF_STRING gives the
+ offset of the string pointer in the item structures the hash table
+ entries points to. */
+void hash_init_strcached (struct hash_table *ht, unsigned long size,
+ struct strcache2 *strcache, unsigned int off_string)
+{
+ hash_init (ht, size, 0, 0, 0);
+ ht->ht_strcache = strcache;
+ ht->ht_off_string = off_string;
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+/* Load an array of items into 'ht'. */
+
+void
+hash_load (struct hash_table *ht, void *item_table,
+ unsigned long cardinality, unsigned long size)
+{
+ char *items = (char *) item_table;
+#ifndef CONFIG_WITH_STRCACHE2
+ while (cardinality--)
+ {
+ hash_insert (ht, items);
+ items += size;
+ }
+#else /* CONFIG_WITH_STRCACHE2 */
+ if (ht->ht_strcache)
+ while (cardinality--)
+ {
+ hash_insert_strcached (ht, items);
+ items += size;
+ }
+ else
+ while (cardinality--)
+ {
+ hash_insert (ht, items);
+ items += size;
+ }
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+/* Returns the address of the table slot matching 'key'. If 'key' is
+ not found, return the address of an empty slot suitable for
+ inserting 'key'. The caller is responsible for incrementing
+ ht_fill on insertion. */
+
+void **
+hash_find_slot (struct hash_table *ht, const void *key)
+{
+ void **slot;
+ void **deleted_slot = 0;
+ unsigned int hash_2 = 0;
+ unsigned int hash_1 = (*ht->ht_hash_1) (key);
+
+#ifdef CONFIG_WITH_STRCACHE2
+ assert (ht->ht_strcache == 0);
+#endif
+
+ MAKE_STATS (ht->ht_lookups++);
+ MAKE_STATS_3 (make_stats_ht_lookups++);
+ for (;;)
+ {
+ hash_1 &= (ht->ht_size - 1);
+ slot = &ht->ht_vec[hash_1];
+
+ if (*slot == 0)
+ return (deleted_slot ? deleted_slot : slot);
+ if (*slot == hash_deleted_item)
+ {
+ if (deleted_slot == 0)
+ deleted_slot = slot;
+ }
+ else
+ {
+ if (key == *slot)
+ return slot;
+ if ((*ht->ht_compare) (key, *slot) == 0)
+ return slot;
+ MAKE_STATS (ht->ht_collisions++);
+ MAKE_STATS_3 (make_stats_ht_collisions++);
+ }
+ if (!hash_2)
+ hash_2 = (*ht->ht_hash_2) (key) | 1;
+ hash_1 += hash_2;
+ }
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+/* hash_find_slot version for tables created with hash_init_strcached. */
+void **
+hash_find_slot_strcached (struct hash_table *ht, const void *key)
+{
+ void **slot;
+ void **deleted_slot = 0;
+ const char *str1 = *(const char **)((const char *)key + ht->ht_off_string);
+ const char *str2;
+ unsigned int hash_1 = strcache2_calc_ptr_hash (ht->ht_strcache, str1);
+ unsigned int hash_2;
+
+#ifdef CONFIG_WITH_STRCACHE2
+ assert (ht->ht_strcache != 0);
+#endif
+
+ MAKE_STATS (ht->ht_lookups++);
+ MAKE_STATS_3 (make_stats_ht_lookups++);
+
+ /* first iteration unrolled. */
+
+ hash_1 &= (ht->ht_size - 1);
+ slot = &ht->ht_vec[hash_1];
+ if (*slot == 0)
+ return slot;
+ if (*slot != hash_deleted_item)
+ {
+ str2 = *(const char **)((const char *)(*slot) + ht->ht_off_string);
+ if (str1 == str2)
+ return slot;
+
+ MAKE_STATS (ht->ht_collisions++);
+ MAKE_STATS_3 (make_stats_ht_collisions++);
+ }
+ else
+ deleted_slot = slot;
+
+ /* the rest of the loop. */
+
+ hash_2 = strcache2_get_hash (ht->ht_strcache, str1) | 1;
+ hash_1 += hash_2;
+ for (;;)
+ {
+ hash_1 &= (ht->ht_size - 1);
+ slot = &ht->ht_vec[hash_1];
+
+ if (*slot == 0)
+ return (deleted_slot ? deleted_slot : slot);
+ if (*slot == hash_deleted_item)
+ {
+ if (deleted_slot == 0)
+ deleted_slot = slot;
+ }
+ else
+ {
+ str2 = *(const char **)((const char *)(*slot) + ht->ht_off_string);
+ if (str1 == str2)
+ return slot;
+
+ MAKE_STATS (ht->ht_collisions++);
+ MAKE_STATS_3 (make_stats_ht_collisions++);
+ }
+
+ hash_1 += hash_2;
+ }
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+void *
+hash_find_item (struct hash_table *ht, const void *key)
+{
+ void **slot = hash_find_slot (ht, key);
+ return ((HASH_VACANT (*slot)) ? 0 : *slot);
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+void *
+hash_find_item_strcached (struct hash_table *ht, const void *key)
+{
+ void **slot = hash_find_slot_strcached (ht, key);
+ return ((HASH_VACANT (*slot)) ? 0 : *slot);
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+void *
+hash_insert (struct hash_table *ht, const void *item)
+{
+ void **slot = hash_find_slot (ht, item);
+ const void *old_item = *slot;
+ hash_insert_at (ht, item, slot);
+ return (void *)((HASH_VACANT (old_item)) ? 0 : old_item);
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+void *
+hash_insert_strcached (struct hash_table *ht, const void *item)
+{
+ void **slot = hash_find_slot_strcached (ht, item);
+ const void *old_item = slot ? *slot : 0;
+ hash_insert_at (ht, item, slot);
+ return (void *)((HASH_VACANT (old_item)) ? 0 : old_item);
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+void *
+hash_insert_at (struct hash_table *ht, const void *item, const void *slot)
+{
+ const void *old_item = *(void **) slot;
+ if (HASH_VACANT (old_item))
+ {
+ ht->ht_fill++;
+ if (old_item == 0)
+ ht->ht_empty_slots--;
+ old_item = item;
+ }
+ *(void const **) slot = item;
+ if (ht->ht_empty_slots < ht->ht_size - ht->ht_capacity)
+ {
+ hash_rehash (ht);
+#ifdef CONFIG_WITH_STRCACHE2
+ if (ht->ht_strcache)
+ return (void *)hash_find_slot_strcached (ht, item);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ return (void *) hash_find_slot (ht, item);
+ }
+ else
+ return (void *) slot;
+}
+
+void *
+hash_delete (struct hash_table *ht, const void *item)
+{
+ void **slot = hash_find_slot (ht, item);
+ return hash_delete_at (ht, slot);
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+void *
+hash_delete_strcached (struct hash_table *ht, const void *item)
+{
+ void **slot = hash_find_slot_strcached (ht, item);
+ return hash_delete_at (ht, slot);
+}
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+void *
+hash_delete_at (struct hash_table *ht, const void *slot)
+{
+ void *item = *(void **) slot;
+ if (!HASH_VACANT (item))
+ {
+ *(void const **) slot = hash_deleted_item;
+ ht->ht_fill--;
+ return item;
+ }
+ else
+ return 0;
+}
+
+void
+hash_free_items (struct hash_table *ht)
+{
+ void **vec = ht->ht_vec;
+ void **end = &vec[ht->ht_size];
+ for (; vec < end; vec++)
+ {
+ void *item = *vec;
+ if (!HASH_VACANT (item))
+ free (item);
+ *vec = 0;
+ }
+ ht->ht_fill = 0;
+ ht->ht_empty_slots = ht->ht_size;
+}
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+void
+hash_free_items_cached (struct hash_table *ht, struct alloccache *cache)
+{
+ void **vec = ht->ht_vec;
+ void **end = &vec[ht->ht_size];
+ for (; vec < end; vec++)
+ {
+ void *item = *vec;
+ if (!HASH_VACANT (item))
+ alloccache_free (cache, item);
+ *vec = 0;
+ }
+ ht->ht_fill = 0;
+ ht->ht_empty_slots = ht->ht_size;
+}
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
+void
+hash_delete_items (struct hash_table *ht)
+{
+ void **vec = ht->ht_vec;
+ void **end = &vec[ht->ht_size];
+ for (; vec < end; vec++)
+ *vec = 0;
+ ht->ht_fill = 0;
+ ht->ht_collisions = 0;
+ ht->ht_lookups = 0;
+ ht->ht_rehashes = 0;
+ ht->ht_empty_slots = ht->ht_size;
+}
+
+void
+hash_free (struct hash_table *ht, int free_items)
+{
+ if (free_items)
+ hash_free_items (ht);
+ else
+ {
+ ht->ht_fill = 0;
+ ht->ht_empty_slots = ht->ht_size;
+ }
+ free (ht->ht_vec);
+ ht->ht_vec = 0;
+ ht->ht_capacity = 0;
+}
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+void
+hash_free_cached (struct hash_table *ht, int free_items, struct alloccache *cache)
+{
+ if (free_items)
+ hash_free_items_cached (ht, cache);
+ else
+ {
+ ht->ht_fill = 0;
+ ht->ht_empty_slots = ht->ht_size;
+ }
+ free (ht->ht_vec);
+ ht->ht_vec = 0;
+ ht->ht_capacity = 0;
+}
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
+void
+hash_map (struct hash_table *ht, hash_map_func_t map)
+{
+ void **slot;
+ void **end = &ht->ht_vec[ht->ht_size];
+
+ for (slot = ht->ht_vec; slot < end; slot++)
+ {
+ if (!HASH_VACANT (*slot))
+ (*map) (*slot);
+ }
+}
+
+void
+hash_map_arg (struct hash_table *ht, hash_map_arg_func_t map, void *arg)
+{
+ void **slot;
+ void **end = &ht->ht_vec[ht->ht_size];
+
+ for (slot = ht->ht_vec; slot < end; slot++)
+ {
+ if (!HASH_VACANT (*slot))
+ (*map) (*slot, arg);
+ }
+}
+
+/* Double the size of the hash table in the event of overflow... */
+
+static void
+hash_rehash (struct hash_table *ht)
+{
+ unsigned long old_ht_size = ht->ht_size;
+ void **old_vec = ht->ht_vec;
+ void **ovp;
+
+ if (ht->ht_fill >= ht->ht_capacity)
+ {
+ ht->ht_size *= 2;
+ ht->ht_capacity = ht->ht_size - (ht->ht_size >> 4);
+ }
+ ht->ht_rehashes++;
+ ht->ht_vec = (void **) CALLOC (struct token *, ht->ht_size);
+
+#ifndef CONFIG_WITH_STRCACHE2
+ for (ovp = old_vec; ovp < &old_vec[old_ht_size]; ovp++)
+ {
+ if (! HASH_VACANT (*ovp))
+ {
+ void **slot = hash_find_slot (ht, *ovp);
+ *slot = *ovp;
+ }
+ }
+#else /* CONFIG_WITH_STRCACHE2 */
+ if (ht->ht_strcache)
+ for (ovp = old_vec; ovp < &old_vec[old_ht_size]; ovp++)
+ {
+ if (! HASH_VACANT (*ovp))
+ {
+ void **slot = hash_find_slot_strcached (ht, *ovp);
+ *slot = *ovp;
+ }
+ }
+ else
+ for (ovp = old_vec; ovp < &old_vec[old_ht_size]; ovp++)
+ {
+ if (! HASH_VACANT (*ovp))
+ {
+ void **slot = hash_find_slot (ht, *ovp);
+ *slot = *ovp;
+ }
+ }
+#endif /* CONFIG_WITH_STRCACHE2 */
+ ht->ht_empty_slots = ht->ht_size - ht->ht_fill;
+ free (old_vec);
+}
+
+void
+hash_print_stats (struct hash_table *ht, FILE *out_FILE)
+{
+ /* GKM FIXME: honor NO_FLOAT */
+ fprintf (out_FILE, _("Load=%ld/%ld=%.0f%%, "), ht->ht_fill, ht->ht_size,
+ 100.0 * (double) ht->ht_fill / (double) ht->ht_size);
+ fprintf (out_FILE, _("Rehash=%d, "), ht->ht_rehashes);
+ MAKE_STATS(
+ fprintf (out_FILE, _("Collisions=%ld/%ld=%.0f%%"), ht->ht_collisions, ht->ht_lookups,
+ (ht->ht_lookups
+ ? (100.0 * (double) ht->ht_collisions / (double) ht->ht_lookups)
+ : 0));
+ );
+}
+
+/* Dump all items into a NULL-terminated vector. Use the
+ user-supplied vector, or malloc one. */
+
+void **
+hash_dump (struct hash_table *ht, void **vector_0, qsort_cmp_t compare)
+{
+ void **vector;
+ void **slot;
+ void **end = &ht->ht_vec[ht->ht_size];
+
+ if (vector_0 == 0)
+ vector_0 = MALLOC (void *, ht->ht_fill + 1);
+ vector = vector_0;
+
+ for (slot = ht->ht_vec; slot < end; slot++)
+ if (!HASH_VACANT (*slot))
+ *vector++ = *slot;
+ *vector = 0;
+
+ if (compare)
+ qsort (vector_0, ht->ht_fill, sizeof (void *), compare);
+ return vector_0;
+}
+
+/* Round a given number up to the nearest power of 2. */
+
+static unsigned long
+round_up_2 (unsigned long n)
+{
+ n |= (n >> 1);
+ n |= (n >> 2);
+ n |= (n >> 4);
+ n |= (n >> 8);
+ n |= (n >> 16);
+
+#if !defined(HAVE_LIMITS_H) || ULONG_MAX > 4294967295
+ /* We only need this on systems where unsigned long is >32 bits. */
+ n |= (n >> 32);
+#endif
+
+ return n + 1;
+}
diff --git a/src/kmk/hash.h b/src/kmk/hash.h
new file mode 100644
index 0000000..0a43369
--- /dev/null
+++ b/src/kmk/hash.h
@@ -0,0 +1,251 @@
+/* hash.h -- decls for hash table
+Copyright (C) 1995, 1999, 2002, 2010 Free Software Foundation, Inc.
+Written by Greg McGary <gkm@gnu.org> <greg@mcgary.org>
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _hash_h_
+#define _hash_h_
+
+#include <stdio.h>
+#include <ctype.h>
+
+#if defined __cplusplus || (defined __STDC__ && __STDC__) || defined WINDOWS32
+# if !defined __GLIBC__ || !defined __P
+# undef __P
+# define __P(protos) protos
+# endif
+#else /* Not C++ or ANSI C. */
+# undef __P
+# define __P(protos) ()
+/* We can get away without defining 'const' here only because in this file
+ it is used only inside the prototype for 'fnmatch', which is elided in
+ non-ANSI C where 'const' is problematical. */
+#endif /* C++ or ANSI C. */
+
+typedef unsigned long (*hash_func_t) __P((void const *key));
+typedef int (*hash_cmp_func_t) __P((void const *x, void const *y));
+typedef void (*hash_map_func_t) __P((void const *item));
+typedef void (*hash_map_arg_func_t) __P((void const *item, void *arg));
+
+struct hash_table
+{
+ void **ht_vec;
+ hash_func_t ht_hash_1; /* primary hash function */
+ hash_func_t ht_hash_2; /* secondary hash function */
+ hash_cmp_func_t ht_compare; /* comparison function */
+ unsigned long ht_size; /* total number of slots (power of 2) */
+ unsigned long ht_capacity; /* usable slots, limited by loading-factor */
+ unsigned long ht_fill; /* items in table */
+ unsigned long ht_empty_slots; /* empty slots not including deleted slots */
+ unsigned long ht_collisions; /* # of failed calls to comparison function */
+ unsigned long ht_lookups; /* # of queries */
+ unsigned int ht_rehashes; /* # of times we've expanded table */
+#ifdef CONFIG_WITH_STRCACHE2
+ struct strcache2 *ht_strcache; /* the string cache pointer. */
+ unsigned int ht_off_string; /* offsetof (struct key, string) */
+#endif
+};
+
+typedef int (*qsort_cmp_t) __P((void const *, void const *));
+
+void hash_init __P((struct hash_table *ht, unsigned long size,
+ hash_func_t hash_1, hash_func_t hash_2, hash_cmp_func_t hash_cmp));
+void hash_load __P((struct hash_table *ht, void *item_table,
+ unsigned long cardinality, unsigned long size));
+void **hash_find_slot __P((struct hash_table *ht, void const *key));
+void *hash_find_item __P((struct hash_table *ht, void const *key));
+void *hash_insert __P((struct hash_table *ht, const void *item));
+void *hash_insert_at __P((struct hash_table *ht, const void *item, void const *slot));
+void *hash_delete __P((struct hash_table *ht, void const *item));
+void *hash_delete_at __P((struct hash_table *ht, void const *slot));
+void hash_delete_items __P((struct hash_table *ht));
+void hash_free_items __P((struct hash_table *ht));
+void hash_free __P((struct hash_table *ht, int free_items));
+#ifdef CONFIG_WITH_ALLOC_CACHES
+void hash_free_items_cached __P((struct hash_table *ht, struct alloccache *cache));
+void hash_free_cached __P((struct hash_table *ht, int free_items, struct alloccache *cache));
+#endif
+void hash_map __P((struct hash_table *ht, hash_map_func_t map));
+void hash_map_arg __P((struct hash_table *ht, hash_map_arg_func_t map, void *arg));
+void hash_print_stats __P((struct hash_table *ht, FILE *out_FILE));
+void **hash_dump __P((struct hash_table *ht, void **vector_0, qsort_cmp_t compare));
+
+#ifdef CONFIG_WITH_STRCACHE2
+void hash_init_strcached __P((struct hash_table *ht, unsigned long size,
+ struct strcache2 *strcache, unsigned int off_strptr));
+void **hash_find_slot_strcached __P((struct hash_table *ht, const void *key));
+void *hash_find_item_strcached __P((struct hash_table *ht, void const *key));
+void *hash_insert_strcached __P((struct hash_table *ht, const void *item));
+void *hash_delete_strcached __P((struct hash_table *ht, void const *item));
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+extern void *hash_deleted_item;
+#define HASH_VACANT(item) ((item) == 0 || (void *) (item) == hash_deleted_item)
+
+
+/* hash and comparison macros for case-sensitive string keys. */
+
+/* Due to the strcache, it's not uncommon for the string pointers to
+ be identical. Take advantage of that to short-circuit string compares. */
+
+#define STRING_HASH_1(KEY, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ while (*++_key_) \
+ (RESULT) += (*_key_ << (_key_[1] & 0xf)); \
+} while (0)
+#define return_STRING_HASH_1(KEY) do { \
+ unsigned long _result_ = 0; \
+ STRING_HASH_1 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define STRING_HASH_2(KEY, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ while (*++_key_) \
+ (RESULT) += (*_key_ << (_key_[1] & 0x7)); \
+} while (0)
+#define return_STRING_HASH_2(KEY) do { \
+ unsigned long _result_ = 0; \
+ STRING_HASH_2 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define STRING_COMPARE(X, Y, RESULT) do { \
+ RESULT = (X) == (Y) ? 0 : strcmp ((X), (Y)); \
+} while (0)
+#define return_STRING_COMPARE(X, Y) do { \
+ return (X) == (Y) ? 0 : strcmp ((X), (Y)); \
+} while (0)
+
+
+#define STRING_N_HASH_1(KEY, N, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ int _n_ = (N); \
+ if (_n_) \
+ while (--_n_ && *++_key_) \
+ (RESULT) += (*_key_ << (_key_[1] & 0xf)); \
+ (RESULT) += *++_key_; \
+} while (0)
+#define return_STRING_N_HASH_1(KEY, N) do { \
+ unsigned long _result_ = 0; \
+ STRING_N_HASH_1 ((KEY), (N), _result_); \
+ return _result_; \
+} while (0)
+
+#define STRING_N_HASH_2(KEY, N, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ int _n_ = (N); \
+ if (_n_) \
+ while (--_n_ && *++_key_) \
+ (RESULT) += (*_key_ << (_key_[1] & 0x7)); \
+ (RESULT) += *++_key_; \
+} while (0)
+#define return_STRING_N_HASH_2(KEY, N) do { \
+ unsigned long _result_ = 0; \
+ STRING_N_HASH_2 ((KEY), (N), _result_); \
+ return _result_; \
+} while (0)
+
+#define STRING_N_COMPARE(X, Y, N, RESULT) do { \
+ RESULT = (X) == (Y) ? 0 : strncmp ((X), (Y), (N)); \
+} while (0)
+#define return_STRING_N_COMPARE(X, Y, N) do { \
+ return (X) == (Y) ? 0 : strncmp ((X), (Y), (N)); \
+} while (0)
+
+#ifdef HAVE_CASE_INSENSITIVE_FS
+
+/* hash and comparison macros for case-insensitive string _key_s. */
+
+#define ISTRING_HASH_1(KEY, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ while (*++_key_) \
+ (RESULT) += ((isupper (*_key_) ? tolower (*_key_) : *_key_) << (_key_[1] & 0xf)); \
+} while (0)
+#define return_ISTRING_HASH_1(KEY) do { \
+ unsigned long _result_ = 0; \
+ ISTRING_HASH_1 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define ISTRING_HASH_2(KEY, RESULT) do { \
+ unsigned char const *_key_ = (unsigned char const *) (KEY) - 1; \
+ while (*++_key_) \
+ (RESULT) += ((isupper (*_key_) ? tolower (*_key_) : *_key_) << (_key_[1] & 0x7)); \
+} while (0)
+#define return_ISTRING_HASH_2(KEY) do { \
+ unsigned long _result_ = 0; \
+ ISTRING_HASH_2 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define ISTRING_COMPARE(X, Y, RESULT) do { \
+ RESULT = (X) == (Y) ? 0 : strcasecmp ((X), (Y)); \
+} while (0)
+#define return_ISTRING_COMPARE(X, Y) do { \
+ return (X) == (Y) ? 0 : strcasecmp ((X), (Y)); \
+} while (0)
+
+#else
+
+#define ISTRING_HASH_1(KEY, RESULT) STRING_HASH_1 ((KEY), (RESULT))
+#define return_ISTRING_HASH_1(KEY) return_STRING_HASH_1 (KEY)
+
+#define ISTRING_HASH_2(KEY, RESULT) STRING_HASH_2 ((KEY), (RESULT))
+#define return_ISTRING_HASH_2(KEY) return_STRING_HASH_2 (KEY)
+
+#define ISTRING_COMPARE(X, Y, RESULT) STRING_COMPARE ((X), (Y), (RESULT))
+#define return_ISTRING_COMPARE(X, Y) return_STRING_COMPARE ((X), (Y))
+
+#endif
+
+/* hash and comparison macros for integer _key_s. */
+
+#define INTEGER_HASH_1(KEY, RESULT) do { \
+ (RESULT) += ((unsigned long)(KEY)); \
+} while (0)
+#define return_INTEGER_HASH_1(KEY) do { \
+ unsigned long _result_ = 0; \
+ INTEGER_HASH_1 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define INTEGER_HASH_2(KEY, RESULT) do { \
+ (RESULT) += ~((unsigned long)(KEY)); \
+} while (0)
+#define return_INTEGER_HASH_2(KEY) do { \
+ unsigned long _result_ = 0; \
+ INTEGER_HASH_2 ((KEY), _result_); \
+ return _result_; \
+} while (0)
+
+#define INTEGER_COMPARE(X, Y, RESULT) do { \
+ (RESULT) = X - Y; \
+} while (0)
+#define return_INTEGER_COMPARE(X, Y) do { \
+ int _result_; \
+ INTEGER_COMPARE (X, Y, _result_); \
+ return _result_; \
+} while (0)
+
+/* hash and comparison macros for address keys. */
+
+#define ADDRESS_HASH_1(KEY, RESULT) INTEGER_HASH_1 (((unsigned long)(KEY)) >> 3, (RESULT))
+#define ADDRESS_HASH_2(KEY, RESULT) INTEGER_HASH_2 (((unsigned long)(KEY)) >> 3, (RESULT))
+#define ADDRESS_COMPARE(X, Y, RESULT) INTEGER_COMPARE ((X), (Y), (RESULT))
+#define return_ADDRESS_HASH_1(KEY) return_INTEGER_HASH_1 (((unsigned long)(KEY)) >> 3)
+#define return_ADDRESS_HASH_2(KEY) return_INTEGER_HASH_2 (((unsigned long)(KEY)) >> 3)
+#define return_ADDRESS_COMPARE(X, Y) return_INTEGER_COMPARE ((X), (Y))
+
+#endif /* not _hash_h_ */
diff --git a/src/kmk/implicit.c b/src/kmk/implicit.c
new file mode 100644
index 0000000..0658470
--- /dev/null
+++ b/src/kmk/implicit.c
@@ -0,0 +1,1011 @@
+/* Implicit rule searching for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "rule.h"
+#include "dep.h"
+#include "debug.h"
+#include "variable.h"
+#include "job.h" /* struct child, used inside commands.h */
+#include "commands.h" /* set_file_variables */
+
+static int pattern_search (struct file *file, int archive,
+ unsigned int depth, unsigned int recursions);
+
+/* For a FILE which has no commands specified, try to figure out some
+ from the implicit pattern rules.
+ Returns 1 if a suitable implicit rule was found,
+ after modifying FILE to contain the appropriate commands and deps,
+ or returns 0 if no implicit rule was found. */
+
+int
+try_implicit_rule (struct file *file, unsigned int depth)
+{
+ DBF (DB_IMPLICIT, _("Looking for an implicit rule for '%s'.\n"));
+
+ /* The order of these searches was previously reversed. My logic now is
+ that since the non-archive search uses more information in the target
+ (the archive search omits the archive name), it is more specific and
+ should come first. */
+
+ if (pattern_search (file, 0, depth, 0))
+ return 1;
+
+#ifndef NO_ARCHIVES
+ /* If this is an archive member reference, use just the
+ archive member name to search for implicit rules. */
+ if (ar_name (file->name))
+ {
+ DBF (DB_IMPLICIT,
+ _("Looking for archive-member implicit rule for '%s'.\n"));
+ if (pattern_search (file, 1, depth, 0))
+ return 1;
+ }
+#endif
+
+ return 0;
+}
+
+
+/* Scans the BUFFER for the next word with whitespace as a separator.
+ Returns the pointer to the beginning of the word. LENGTH hold the
+ length of the word. */
+
+static const char *
+get_next_word (const char *buffer, unsigned int *length)
+{
+ const char *p = buffer, *beg;
+ char c;
+
+ /* Skip any leading whitespace. */
+ NEXT_TOKEN (p);
+
+ beg = p;
+ c = *(p++);
+
+ if (c == '\0')
+ {
+ if (length) /* bird: shut up gcc. */
+ *length = 0;
+ return 0;
+ }
+
+
+ /* We already found the first value of "c", above. */
+ while (1)
+ {
+ char closeparen;
+ int count;
+
+ switch (c)
+ {
+ case '\0':
+ case ' ':
+ case '\t':
+ goto done_word;
+
+ case '$':
+ c = *(p++);
+ if (c == '$')
+ break;
+
+ /* This is a variable reference, so read it to the matching
+ close paren. */
+
+ if (c == '(')
+ closeparen = ')';
+ else if (c == '{')
+ closeparen = '}';
+ else
+ /* This is a single-letter variable reference. */
+ break;
+
+ for (count = 0; *p != '\0'; ++p)
+ {
+ if (*p == c)
+ ++count;
+ else if (*p == closeparen && --count < 0)
+ {
+ ++p;
+ break;
+ }
+ }
+ break;
+
+ case '|':
+ goto done;
+
+ default:
+ break;
+ }
+
+ c = *(p++);
+ }
+ done_word:
+ --p;
+
+ done:
+ if (length)
+ *length = p - beg;
+
+ return beg;
+}
+
+/* This structure stores information about the expanded prerequisites for a
+ pattern rule. NAME is always set to the strcache'd name of the prereq.
+ FILE and PATTERN will be set for intermediate files only. IGNORE_MTIME is
+ copied from the prerequisite we expanded.
+ */
+struct patdeps
+ {
+ const char *name;
+ const char *pattern;
+ struct file *file;
+ unsigned int ignore_mtime : 1;
+ };
+
+/* This structure stores information about pattern rules that we need
+ to try.
+*/
+struct tryrule
+ {
+ struct rule *rule;
+
+ /* Index of the target in this rule that matched the file. */
+ unsigned int matches;
+
+ /* Stem length for this match. */
+ unsigned int stemlen;
+
+ /* Definition order of this rule. Used to implement stable sort.*/
+ unsigned int order;
+
+ /* Nonzero if the LASTSLASH logic was used in matching this rule. */
+ char checked_lastslash;
+ };
+
+int
+stemlen_compare (const void *v1, const void *v2)
+{
+ const struct tryrule *r1 = v1;
+ const struct tryrule *r2 = v2;
+ int r = r1->stemlen - r2->stemlen;
+ return r != 0 ? r : (int)(r1->order - r2->order);
+}
+
+/* Search the pattern rules for a rule with an existing dependency to make
+ FILE. If a rule is found, the appropriate commands and deps are put in FILE
+ and 1 is returned. If not, 0 is returned.
+
+ If ARCHIVE is nonzero, FILE->name is of the form "LIB(MEMBER)". A rule for
+ "(MEMBER)" will be searched for, and "(MEMBER)" will not be chopped up into
+ directory and filename parts.
+
+ If an intermediate file is found by pattern search, the intermediate file
+ is set up as a target by the recursive call and is also made a dependency
+ of FILE.
+
+ DEPTH is used for debugging messages. */
+
+static int
+pattern_search (struct file *file, int archive,
+ unsigned int depth, unsigned int recursions)
+{
+ /* Filename we are searching for a rule for. */
+ const char *filename = archive ? strchr (file->name, '(') : file->name;
+
+ /* Length of FILENAME. */
+ unsigned int namelen = strlen (filename);
+
+ /* The last slash in FILENAME (or nil if there is none). */
+ const char *lastslash;
+
+ /* This is a file-object used as an argument in
+ recursive calls. It never contains any data
+ except during a recursive call. */
+ struct file *int_file = 0;
+
+ /* List of dependencies found recursively. */
+ unsigned int max_deps = max_pattern_deps;
+ struct patdeps *deplist = xmalloc (max_deps * sizeof (struct patdeps));
+ struct patdeps *pat = deplist;
+
+ /* Names of possible dependencies are constructed in this buffer. */
+ char *depname = alloca (namelen + max_pattern_dep_length);
+
+ /* The start and length of the stem of FILENAME for the current rule. */
+ const char *stem = 0;
+ unsigned int stemlen = 0;
+ unsigned int fullstemlen = 0;
+
+ /* Buffer in which we store all the rules that are possibly applicable. */
+ struct tryrule *tryrules = xmalloc (num_pattern_rules * max_pattern_targets
+ * sizeof (struct tryrule));
+
+ /* Number of valid elements in TRYRULES. */
+ unsigned int nrules;
+
+ /* The index in TRYRULES of the rule we found. */
+ unsigned int foundrule;
+
+ /* Nonzero if should consider intermediate files as dependencies. */
+ int intermed_ok;
+
+ /* Nonzero if we have initialized file variables for this target. */
+ int file_vars_initialized = 0;
+
+ /* Nonzero if we have matched a pattern-rule target
+ that is not just '%'. */
+ int specific_rule_matched = 0;
+
+ unsigned int ri; /* uninit checks OK */
+ struct rule *rule;
+
+ char *pathdir = NULL;
+ unsigned long pathlen;
+
+ PATH_VAR (stem_str); /* @@ Need to get rid of stem, stemlen, etc. */
+
+#ifndef NO_ARCHIVES
+ if (archive || ar_name (filename))
+ lastslash = 0;
+ else
+#endif
+ {
+ /* Set LASTSLASH to point at the last slash in FILENAME
+ but not counting any slash at the end. (foo/bar/ counts as
+ bar/ in directory foo/, not empty in directory foo/bar/.) */
+ lastslash = strrchr (filename, '/');
+#ifdef VMS
+ if (lastslash == NULL)
+ lastslash = strrchr (filename, ']');
+ if (lastslash == NULL)
+ lastslash = strrchr (filename, '>');
+ if (lastslash == NULL)
+ lastslash = strrchr (filename, ':');
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* Handle backslashes (possibly mixed with forward slashes)
+ and the case of "d:file". */
+ {
+ char *bslash = strrchr (filename, '\\');
+ if (lastslash == 0 || bslash > lastslash)
+ lastslash = bslash;
+ if (lastslash == 0 && filename[0] && filename[1] == ':')
+ lastslash = filename + 1;
+ }
+#endif
+ if (lastslash != 0 && lastslash[1] == '\0')
+ lastslash = 0;
+ }
+
+ pathlen = lastslash - filename + 1;
+
+ /* First see which pattern rules match this target and may be considered.
+ Put them in TRYRULES. */
+
+ nrules = 0;
+ for (rule = pattern_rules; rule != 0; rule = rule->next)
+ {
+ unsigned int ti;
+
+ /* If the pattern rule has deps but no commands, ignore it.
+ Users cancel built-in rules by redefining them without commands. */
+ if (rule->deps != 0 && rule->cmds == 0)
+ continue;
+
+ /* If this rule is in use by a parent pattern_search,
+ don't use it here. */
+ if (rule->in_use)
+ {
+ DBS (DB_IMPLICIT, (_("Avoiding implicit rule recursion.\n")));
+ continue;
+ }
+
+ for (ti = 0; ti < rule->num; ++ti)
+ {
+ const char *target = rule->targets[ti];
+ const char *suffix = rule->suffixes[ti];
+ char check_lastslash;
+
+ /* Rules that can match any filename and are not terminal
+ are ignored if we're recursing, so that they cannot be
+ intermediate files. */
+ if (recursions > 0 && target[1] == '\0' && !rule->terminal)
+ continue;
+
+ if (rule->lens[ti] > namelen)
+ /* It can't possibly match. */
+ continue;
+
+ /* From the lengths of the filename and the pattern parts,
+ find the stem: the part of the filename that matches the %. */
+ stem = filename + (suffix - target - 1);
+ stemlen = namelen - rule->lens[ti] + 1;
+
+ /* Set CHECK_LASTSLASH if FILENAME contains a directory
+ prefix and the target pattern does not contain a slash. */
+
+ check_lastslash = 0;
+ if (lastslash)
+ {
+#ifdef VMS
+ check_lastslash = strpbrk (target, "/]>:") == NULL;
+#else
+ check_lastslash = strchr (target, '/') == 0;
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* Didn't find it yet: check for DOS-type directories. */
+ if (check_lastslash)
+ {
+ char *b = strchr (target, '\\');
+ check_lastslash = !(b || (target[0] && target[1] == ':'));
+ }
+#endif
+ }
+ if (check_lastslash)
+ {
+ /* If so, don't include the directory prefix in STEM here. */
+ if (pathlen > stemlen)
+ continue;
+ stemlen -= pathlen;
+ stem += pathlen;
+ }
+
+ /* Check that the rule pattern matches the text before the stem. */
+ if (check_lastslash)
+ {
+ if (stem > (lastslash + 1)
+ && !strneq (target, lastslash + 1, stem - lastslash - 1))
+ continue;
+ }
+ else if (stem > filename
+ && !strneq (target, filename, stem - filename))
+ continue;
+
+ /* Check that the rule pattern matches the text after the stem.
+ We could test simply use streq, but this way we compare the
+ first two characters immediately. This saves time in the very
+ common case where the first character matches because it is a
+ period. */
+ if (*suffix != stem[stemlen]
+ || (*suffix != '\0' && !streq (&suffix[1], &stem[stemlen + 1])))
+ continue;
+
+ /* Record if we match a rule that not all filenames will match. */
+ if (target[1] != '\0')
+ specific_rule_matched = 1;
+
+ /* A rule with no dependencies and no commands exists solely to set
+ specific_rule_matched when it matches. Don't try to use it. */
+ if (rule->deps == 0 && rule->cmds == 0)
+ continue;
+
+ /* Record this rule in TRYRULES and the index of the matching
+ target in MATCHES. If several targets of the same rule match,
+ that rule will be in TRYRULES more than once. */
+ tryrules[nrules].rule = rule;
+ tryrules[nrules].matches = ti;
+ tryrules[nrules].stemlen = stemlen + (check_lastslash ? pathlen : 0);
+ tryrules[nrules].order = nrules;
+ tryrules[nrules].checked_lastslash = check_lastslash;
+ ++nrules;
+ }
+ }
+
+ /* Bail out early if we haven't found any rules. */
+ if (nrules == 0)
+ goto done;
+
+ /* Sort the rules to place matches with the shortest stem first. This
+ way the most specific rules will be tried first. */
+ if (nrules > 1)
+ qsort (tryrules, nrules, sizeof (struct tryrule), stemlen_compare);
+
+ /* If we have found a matching rule that won't match all filenames,
+ retroactively reject any non-"terminal" rules that do always match. */
+ if (specific_rule_matched)
+ for (ri = 0; ri < nrules; ++ri)
+ if (!tryrules[ri].rule->terminal)
+ {
+ unsigned int j;
+ for (j = 0; j < tryrules[ri].rule->num; ++j)
+ if (tryrules[ri].rule->targets[j][1] == '\0')
+ {
+ tryrules[ri].rule = 0;
+ break;
+ }
+ }
+
+ /* Try each rule once without intermediate files, then once with them. */
+ for (intermed_ok = 0; intermed_ok < 2; ++intermed_ok)
+ {
+ pat = deplist;
+
+ /* Try each pattern rule till we find one that applies. If it does,
+ expand its dependencies (as substituted) and chain them in DEPS. */
+ for (ri = 0; ri < nrules; ri++)
+ {
+ struct dep *dep;
+ char check_lastslash;
+ unsigned int failed = 0;
+ int file_variables_set = 0;
+ unsigned int deps_found = 0;
+ /* NPTR points to the part of the prereq we haven't processed. */
+ const char *nptr = 0;
+ const char *dir = NULL;
+ int order_only = 0;
+ unsigned int matches;
+
+ rule = tryrules[ri].rule;
+
+ /* RULE is nil when we discover that a rule, already placed in
+ TRYRULES, should not be applied. */
+ if (rule == 0)
+ continue;
+
+ /* Reject any terminal rules if we're looking to make intermediate
+ files. */
+ if (intermed_ok && rule->terminal)
+ continue;
+
+ /* From the lengths of the filename and the matching pattern parts,
+ find the stem: the part of the filename that matches the %. */
+ matches = tryrules[ri].matches;
+ stem = filename + (rule->suffixes[matches]
+ - rule->targets[matches]) - 1;
+ stemlen = (namelen - rule->lens[matches]) + 1;
+ check_lastslash = tryrules[ri].checked_lastslash;
+ if (check_lastslash)
+ {
+ stem += pathlen;
+ stemlen -= pathlen;
+
+ /* We need to add the directory prefix, so set it up. */
+ if (! pathdir)
+ {
+ pathdir = alloca (pathlen + 1);
+ memcpy (pathdir, filename, pathlen);
+ pathdir[pathlen] = '\0';
+ }
+ dir = pathdir;
+ }
+
+ if (stemlen > GET_PATH_MAX)
+ {
+ DBS (DB_IMPLICIT, (_("Stem too long: '%.*s'.\n"),
+ (int) stemlen, stem));
+ continue;
+ }
+
+ DBS (DB_IMPLICIT, (_("Trying pattern rule with stem '%.*s'.\n"),
+ (int) stemlen, stem));
+
+ strncpy (stem_str, stem, stemlen);
+ stem_str[stemlen] = '\0';
+
+ /* If there are no prerequisites, then this rule matches. */
+ if (rule->deps == 0)
+ break;
+
+ /* Temporary assign STEM to file->stem (needed to set file
+ variables below). */
+ file->stem = stem_str;
+
+ /* Mark this rule as in use so a recursive pattern_search won't try
+ to use it. */
+ rule->in_use = 1;
+
+ /* Try each prerequisite; see if it exists or can be created. We'll
+ build a list of prereq info in DEPLIST. Due to 2nd expansion we
+ may have to process multiple prereqs for a single dep entry. */
+
+ pat = deplist;
+ dep = rule->deps;
+ nptr = dep_name (dep);
+ while (1)
+ {
+ struct dep *dl, *d;
+ char *p;
+
+ /* If we're out of name to parse, start the next prereq. */
+ if (! nptr)
+ {
+ dep = dep->next;
+ if (dep == 0)
+ break;
+ nptr = dep_name (dep);
+ }
+
+ /* If we don't need a second expansion, just replace the %. */
+ if (! dep->need_2nd_expansion)
+ {
+ p = strchr (nptr, '%');
+ if (p == 0)
+ strcpy (depname, nptr);
+ else
+ {
+ char *o = depname;
+ if (check_lastslash)
+ {
+ memcpy (o, filename, pathlen);
+ o += pathlen;
+ }
+ memcpy (o, nptr, p - nptr);
+ o += p - nptr;
+ memcpy (o, stem_str, stemlen);
+ o += stemlen;
+ strcpy (o, p + 1);
+ }
+
+ /* Parse the expanded string. It might have wildcards. */
+ p = depname;
+ dl = PARSE_SIMPLE_SEQ (&p, struct dep);
+ for (d = dl; d != NULL; d = d->next)
+ {
+ ++deps_found;
+ d->ignore_mtime = dep->ignore_mtime;
+ }
+
+ /* We've used up this dep, so next time get a new one. */
+ nptr = 0;
+ }
+
+ /* We have to perform second expansion on this prereq. In an
+ ideal world we would take the dependency line, substitute the
+ stem, re-expand the whole line and chop it into individual
+ prerequisites. Unfortunately this won't work because of the
+ "check_lastslash" twist. Instead, we will have to go word by
+ word, taking $()'s into account. For each word we will
+ substitute the stem, re-expand, chop it up, and, if
+ check_lastslash != 0, add the directory part to each
+ resulting prerequisite. */
+ else
+ {
+ int add_dir = 0;
+ unsigned int len;
+ struct dep **dptr;
+
+ nptr = get_next_word (nptr, &len);
+ if (nptr == 0)
+ continue;
+
+ /* See this is a transition to order-only prereqs. */
+ if (! order_only && len == 1 && nptr[0] == '|')
+ {
+ order_only = 1;
+ nptr += len;
+ continue;
+ }
+
+ /* If the dependency name has %, substitute the stem. If we
+ just replace % with the stem value then later, when we do
+ the 2nd expansion, we will re-expand this stem value
+ again. This is not good if you have certain characters
+ in your stem (like $).
+
+ Instead, we will replace % with $* and allow the second
+ expansion to take care of it for us. This way (since $*
+ is a simple variable) there won't be additional
+ re-expansion of the stem. */
+
+ p = lindex (nptr, nptr + len, '%');
+ if (p == 0)
+ {
+ memcpy (depname, nptr, len);
+ depname[len] = '\0';
+ }
+ else
+ {
+ unsigned int i = p - nptr;
+ memcpy (depname, nptr, i);
+ memcpy (depname + i, "$*", 2);
+ memcpy (depname + i + 2, p + 1, len - i - 1);
+ depname[len + 2 - 1] = '\0';
+
+ if (check_lastslash)
+ add_dir = 1;
+ }
+
+ /* Set up for the next word. */
+ nptr += len;
+
+ /* Initialize and set file variables if we haven't already
+ done so. */
+ if (!file_vars_initialized)
+ {
+ initialize_file_variables (file, 0);
+#if defined(CONFIG_WITH_COMMANDS_FUNC) || defined (CONFIG_WITH_DOT_MUST_MAKE)
+ set_file_variables (file, 0 /* real call */);
+#else
+ set_file_variables (file);
+#endif
+ file_vars_initialized = 1;
+ }
+ /* Update the stem value in $* for this rule. */
+ else if (!file_variables_set)
+ {
+ define_variable_for_file (
+ "*", 1, file->stem, o_automatic, 0, file);
+ file_variables_set = 1;
+ }
+
+ /* Perform the 2nd expansion. */
+ p = variable_expand_for_file (depname, file);
+ dptr = &dl;
+
+ /* Parse the results into a deps list. */
+ do
+ {
+ /* Parse the expanded string. */
+ struct dep *dp = PARSE_FILE_SEQ (&p, struct dep,
+ order_only ? MAP_NUL : MAP_PIPE,
+ add_dir ? dir : NULL, PARSEFS_NONE);
+ *dptr = dp;
+
+ for (d = dp; d != NULL; d = d->next)
+ {
+ ++deps_found;
+ if (order_only)
+ d->ignore_mtime = 1;
+ dptr = &d->next;
+ }
+
+ /* If we stopped due to an order-only token, note it. */
+ if (*p == '|')
+ {
+ order_only = 1;
+ ++p;
+ }
+ }
+ while (*p != '\0');
+ }
+
+ /* If there are more than max_pattern_deps prerequisites (due to
+ 2nd expansion), reset it and realloc the arrays. */
+
+ if (deps_found > max_deps)
+ {
+ unsigned int l = pat - deplist;
+ /* This might have changed due to recursion. */
+ max_pattern_deps = MAX(max_pattern_deps, deps_found);
+ max_deps = max_pattern_deps;
+ deplist = xrealloc (deplist,
+ max_deps * sizeof (struct patdeps));
+ pat = deplist + l;
+ }
+
+ /* Go through the nameseq and handle each as a prereq name. */
+ for (d = dl; d != 0; d = d->next)
+ {
+ struct dep *expl_d;
+ int is_rule = d->name == dep_name (dep);
+
+ if (file_impossible_p (d->name))
+ {
+ /* If this prereq has already been ruled "impossible",
+ then the rule fails. Don't bother trying it on the
+ second pass either since we know that will fail. */
+ DBS (DB_IMPLICIT,
+ (is_rule
+ ? _("Rejecting impossible rule prerequisite '%s'.\n")
+ : _("Rejecting impossible implicit prerequisite '%s'.\n"),
+ d->name));
+ tryrules[ri].rule = 0;
+
+ failed = 1;
+ break;
+ }
+
+ memset (pat, '\0', sizeof (struct patdeps));
+ pat->ignore_mtime = d->ignore_mtime;
+
+ DBS (DB_IMPLICIT,
+ (is_rule
+ ? _("Trying rule prerequisite '%s'.\n")
+ : _("Trying implicit prerequisite '%s'.\n"), d->name));
+
+ /* If this prereq is also explicitly mentioned for FILE,
+ skip all tests below since it must be built no matter
+ which implicit rule we choose. */
+
+ for (expl_d = file->deps; expl_d != 0; expl_d = expl_d->next)
+ if (streq (dep_name (expl_d), d->name))
+ break;
+ if (expl_d != 0)
+ {
+ (pat++)->name = d->name;
+ continue;
+ }
+
+ /* The DEP->changed flag says that this dependency resides
+ in a nonexistent directory. So we normally can skip
+ looking for the file. However, if CHECK_LASTSLASH is
+ set, then the dependency file we are actually looking for
+ is in a different directory (the one gotten by prepending
+ FILENAME's directory), so it might actually exist. */
+
+ /* @@ dep->changed check is disabled. */
+ if (lookup_file (d->name) != 0
+ /*|| ((!dep->changed || check_lastslash) && */
+ || file_exists_p (d->name))
+ {
+ (pat++)->name = d->name;
+ continue;
+ }
+
+ /* This code, given FILENAME = "lib/foo.o", dependency name
+ "lib/foo.c", and VPATH=src, searches for
+ "src/lib/foo.c". */
+ {
+ const char *vname = vpath_search (d->name, 0, NULL, NULL);
+ if (vname)
+ {
+ DBS (DB_IMPLICIT,
+ (_("Found prerequisite '%s' as VPATH '%s'\n"),
+ d->name, vname));
+ (pat++)->name = d->name;
+ continue;
+ }
+ }
+
+ /* We could not find the file in any place we should look.
+ Try to make this dependency as an intermediate file, but
+ only on the second pass. */
+
+ if (intermed_ok)
+ {
+ DBS (DB_IMPLICIT,
+ (_("Looking for a rule with intermediate file '%s'.\n"),
+ d->name));
+
+ if (int_file == 0)
+ int_file = alloca (sizeof (struct file));
+ memset (int_file, '\0', sizeof (struct file));
+ int_file->name = d->name;
+
+ if (pattern_search (int_file,
+ 0,
+ depth + 1,
+ recursions + 1))
+ {
+ pat->pattern = int_file->name;
+ int_file->name = d->name;
+ pat->file = int_file;
+ int_file = 0;
+ (pat++)->name = d->name;
+ continue;
+ }
+
+ /* If we have tried to find P as an intermediate file
+ and failed, mark that name as impossible so we won't
+ go through the search again later. */
+ if (int_file->variables)
+ free_variable_set (int_file->variables);
+ if (int_file->pat_variables)
+ free_variable_set (int_file->pat_variables);
+ file_impossible (d->name);
+ }
+
+ /* A dependency of this rule does not exist. Therefore, this
+ rule fails. */
+ failed = 1;
+ break;
+ }
+
+ /* Free the ns chain. */
+ free_dep_chain (dl);
+
+ if (failed)
+ break;
+ }
+
+ /* Reset the stem in FILE. */
+
+ file->stem = 0;
+
+ /* This rule is no longer 'in use' for recursive searches. */
+ rule->in_use = 0;
+
+ if (! failed)
+ /* This pattern rule does apply. Stop looking for one. */
+ break;
+
+ /* This pattern rule does not apply. Keep looking. */
+ }
+
+ /* If we found an applicable rule without intermediate files, don't try
+ with them. */
+ if (ri < nrules)
+ break;
+
+ rule = 0;
+ }
+
+ /* RULE is nil if the loop went through the list but everything failed. */
+ if (rule == 0)
+ goto done;
+
+ foundrule = ri;
+
+ /* If we are recursing, store the pattern that matched FILENAME in
+ FILE->name for use in upper levels. */
+
+ if (recursions > 0)
+ /* Kludge-o-matic */
+ file->name = rule->targets[tryrules[foundrule].matches];
+
+ /* DEPLIST lists the prerequisites for the rule we found. This includes the
+ intermediate files, if any. Convert them into entries on the deps-chain
+ of FILE. */
+
+ while (pat-- > deplist)
+ {
+ struct dep *dep;
+ const char *s;
+
+ if (pat->file != 0)
+ {
+ /* If we need to use an intermediate file, make sure it is entered
+ as a target, with the info that was found for it in the recursive
+ pattern_search call. We know that the intermediate file did not
+ already exist as a target; therefore we can assume that the deps
+ and cmds of F below are null before we change them. */
+
+ struct file *imf = pat->file;
+ struct file *f = lookup_file (imf->name);
+
+ /* We don't want to delete an intermediate file that happened
+ to be a prerequisite of some (other) target. Mark it as
+ secondary. We don't want it to be precious as that disables
+ DELETE_ON_ERROR etc. */
+ if (f != 0)
+ f->secondary = 1;
+ else
+ f = enter_file (imf->name);
+
+ f->deps = imf->deps;
+ f->cmds = imf->cmds;
+ f->stem = imf->stem;
+ f->variables = imf->variables;
+ f->pat_variables = imf->pat_variables;
+ f->pat_searched = imf->pat_searched;
+ f->also_make = imf->also_make;
+ f->is_target = 1;
+ f->intermediate = 1;
+ f->tried_implicit = 1;
+
+ imf = lookup_file (pat->pattern);
+ if (imf != 0 && imf->precious)
+ f->precious = 1;
+
+ for (dep = f->deps; dep != 0; dep = dep->next)
+ {
+ dep->file = enter_file (dep->name);
+ dep->name = 0;
+ dep->file->tried_implicit |= dep->changed;
+ }
+ }
+
+ dep = alloc_dep ();
+ dep->ignore_mtime = pat->ignore_mtime;
+ s = strcache_add (pat->name);
+ if (recursions)
+ dep->name = s;
+ else
+ {
+ dep->file = lookup_file (s);
+ if (dep->file == 0)
+ dep->file = enter_file (s);
+ }
+
+ if (pat->file == 0 && tryrules[foundrule].rule->terminal)
+ {
+ /* If the file actually existed (was not an intermediate file), and
+ the rule that found it was a terminal one, then we want to mark
+ the found file so that it will not have implicit rule search done
+ for it. If we are not entering a 'struct file' for it now, we
+ indicate this with the 'changed' flag. */
+ if (dep->file == 0)
+ dep->changed = 1;
+ else
+ dep->file->tried_implicit = 1;
+ }
+
+ dep->next = file->deps;
+ file->deps = dep;
+ }
+
+ if (!tryrules[foundrule].checked_lastslash)
+ {
+ /* Always allocate new storage, since STEM might be on the stack for an
+ intermediate file. */
+ file->stem = strcache_add_len (stem, stemlen);
+ fullstemlen = stemlen;
+ }
+ else
+ {
+ int dirlen = (lastslash + 1) - filename;
+ char *sp;
+
+ /* We want to prepend the directory from
+ the original FILENAME onto the stem. */
+ fullstemlen = dirlen + stemlen;
+ sp = alloca (fullstemlen + 1);
+ memcpy (sp, filename, dirlen);
+ memcpy (sp + dirlen, stem, stemlen);
+ sp[fullstemlen] = '\0';
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ file->stem = strcache_add (sp);
+#else
+ file->stem = strcache_add_len (sp, fullstemlen);
+#endif
+ }
+
+ file->cmds = rule->cmds;
+ file->is_target = 1;
+
+ /* Set precious flag. */
+ {
+ struct file *f = lookup_file (rule->targets[tryrules[foundrule].matches]);
+ if (f && f->precious)
+ file->precious = 1;
+ }
+
+ /* If this rule builds other targets, too, put the others into FILE's
+ 'also_make' member. */
+
+ if (rule->num > 1)
+ for (ri = 0; ri < rule->num; ++ri)
+ if (ri != tryrules[foundrule].matches)
+ {
+ char *nm = alloca (rule->lens[ri] + fullstemlen + 1);
+ char *p = nm;
+ struct file *f;
+ struct dep *new = alloc_dep ();
+
+ /* GKM FIMXE: handle '|' here too */
+ memcpy (p, rule->targets[ri],
+ rule->suffixes[ri] - rule->targets[ri] - 1);
+ p += rule->suffixes[ri] - rule->targets[ri] - 1;
+ memcpy (p, file->stem, fullstemlen);
+ p += fullstemlen;
+ memcpy (p, rule->suffixes[ri],
+ rule->lens[ri] - (rule->suffixes[ri] - rule->targets[ri])+1);
+ new->name = strcache_add (nm);
+ new->file = enter_file (new->name);
+ new->next = file->also_make;
+
+ /* Set precious flag. */
+ f = lookup_file (rule->targets[ri]);
+ if (f && f->precious)
+ new->file->precious = 1;
+
+ /* Set the is_target flag so that this file is not treated as
+ intermediate by the pattern rule search algorithm and
+ file_exists_p cannot pick it up yet. */
+ new->file->is_target = 1;
+
+ file->also_make = new;
+ }
+
+ done:
+ free (tryrules);
+ free (deplist);
+
+ return rule != 0;
+}
diff --git a/src/kmk/incdep.c b/src/kmk/incdep.c
new file mode 100644
index 0000000..09de6ff
--- /dev/null
+++ b/src/kmk/incdep.c
@@ -0,0 +1,2250 @@
+#ifdef CONFIG_WITH_INCLUDEDEP
+/* $Id: incdep.c 3565 2022-05-24 20:40:24Z bird $ */
+/** @file
+ * incdep - Simple dependency files.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifdef __OS2__
+# define INCL_BASE
+# define INCL_ERRORS
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# ifdef KMK
+# define INCDEP_USE_KFSCACHE
+# endif
+#endif
+
+#include "makeint.h"
+
+#if !defined(WINDOWS32) && !defined(__OS2__)
+# define HAVE_PTHREAD
+#endif
+
+#include <assert.h>
+
+#include <glob.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#include "debug.h"
+#include "strcache2.h"
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+#ifdef WINDOWS32
+# include <io.h>
+# include <process.h>
+# include <Windows.h>
+# define PARSE_IN_WORKER
+#endif
+
+#ifdef INCDEP_USE_KFSCACHE
+# include "nt/kFsCache.h"
+extern PKFSCACHE g_pFsCache; /* dir-nt-bird.c for now */
+#endif
+
+#ifdef __OS2__
+# include <os2.h>
+# include <sys/fmutex.h>
+#endif
+
+#ifdef HAVE_PTHREAD
+# include <pthread.h>
+#endif
+
+#ifdef __APPLE__
+# include <malloc/malloc.h>
+# define PARSE_IN_WORKER
+#endif
+
+#if defined(__gnu_linux__) || defined(__linux__)
+# define PARSE_IN_WORKER
+#endif
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+struct incdep_variable_in_set
+{
+ struct incdep_variable_in_set *next;
+ /* the parameters */
+ struct strcache2_entry *name_entry; /* dep strcache - WRONG */
+ const char *value; /* xmalloc'ed */
+ unsigned int value_length;
+ int duplicate_value; /* 0 */
+ enum variable_origin origin;
+ int recursive;
+ struct variable_set *set;
+ const floc *flocp; /* NILF */
+};
+
+struct incdep_variable_def
+{
+ struct incdep_variable_def *next;
+ /* the parameters */
+ const floc *flocp; /* NILF */
+ struct strcache2_entry *name_entry; /* dep strcache - WRONG */
+ char *value; /* xmalloc'ed, free it */
+ unsigned int value_length;
+ enum variable_origin origin;
+ enum variable_flavor flavor;
+ int target_var;
+};
+
+struct incdep_recorded_file
+{
+ struct incdep_recorded_file *next;
+
+ /* the parameters */
+ struct strcache2_entry *filename_entry; /* dep strcache; converted to a nameseq record. */
+ struct dep *deps; /* All the names are dep strcache entries. */
+ const floc *flocp; /* NILF */
+};
+
+
+/* per dep file structure. */
+struct incdep
+{
+ struct incdep *next;
+ char *file_base;
+ char *file_end;
+
+ int worker_tid;
+#ifdef PARSE_IN_WORKER
+ unsigned int err_line_no;
+ const char *err_msg;
+
+ struct incdep_variable_in_set *recorded_variables_in_set_head;
+ struct incdep_variable_in_set *recorded_variables_in_set_tail;
+
+ struct incdep_variable_def *recorded_variable_defs_head;
+ struct incdep_variable_def *recorded_variable_defs_tail;
+
+ struct incdep_recorded_file *recorded_file_head;
+ struct incdep_recorded_file *recorded_file_tail;
+#endif
+#ifdef INCDEP_USE_KFSCACHE
+ /** Pointer to the fs cache object for this file (it exists and is a file). */
+ PKFSOBJ pFileObj;
+#else
+ char name[1];
+#endif
+};
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+
+/* mutex protecting the globals and an associated condition/event. */
+#ifdef HAVE_PTHREAD
+static pthread_mutex_t incdep_mtx;
+static pthread_cond_t incdep_cond_todo;
+static pthread_cond_t incdep_cond_done;
+
+#elif defined (WINDOWS32)
+static CRITICAL_SECTION incdep_mtx;
+static HANDLE incdep_hev_todo;
+static HANDLE incdep_hev_done;
+static int volatile incdep_hev_todo_waiters;
+static int volatile incdep_hev_done_waiters;
+
+#elif defined (__OS2__)
+static _fmutex incdep_mtx;
+static HEV incdep_hev_todo;
+static HEV incdep_hev_done;
+static int volatile incdep_hev_todo_waiters;
+static int volatile incdep_hev_done_waiters;
+#endif
+
+/* flag indicating whether the threads, lock and event/condvars has
+ been initialized or not. */
+static int incdep_initialized;
+
+/* the list of files that needs reading. */
+static struct incdep * volatile incdep_head_todo;
+static struct incdep * volatile incdep_tail_todo;
+
+/* the number of files that are currently being read. */
+static int volatile incdep_num_reading;
+
+/* the list of files that have been read. */
+static struct incdep * volatile incdep_head_done;
+static struct incdep * volatile incdep_tail_done;
+
+
+/* The handles to the worker threads. */
+#ifdef HAVE_PTHREAD
+# define INCDEP_MAX_THREADS 1
+static pthread_t incdep_threads[INCDEP_MAX_THREADS];
+
+#elif defined (WINDOWS32)
+# define INCDEP_MAX_THREADS 2
+static HANDLE incdep_threads[INCDEP_MAX_THREADS];
+
+#elif defined (__OS2__)
+# define INCDEP_MAX_THREADS 2
+static TID incdep_threads[INCDEP_MAX_THREADS];
+#endif
+
+static struct alloccache incdep_rec_caches[INCDEP_MAX_THREADS];
+static struct alloccache incdep_dep_caches[INCDEP_MAX_THREADS];
+static struct strcache2 incdep_dep_strcaches[INCDEP_MAX_THREADS];
+static struct strcache2 incdep_var_strcaches[INCDEP_MAX_THREADS];
+static unsigned incdep_num_threads;
+
+/* flag indicating whether the worker threads should terminate or not. */
+static int volatile incdep_terminate;
+
+#ifdef __APPLE__
+/* malloc zone for the incdep threads. */
+static malloc_zone_t *incdep_zone;
+#endif
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static void incdep_flush_it (floc *);
+static void eval_include_dep_file (struct incdep *, floc *);
+static void incdep_commit_recorded_file (const char *filename, struct dep *deps,
+ const floc *flocp);
+
+
+/* xmalloc wrapper.
+ For working around multithreaded performance problems found on Darwin,
+ Linux (glibc), and possibly other systems. */
+static void *
+incdep_xmalloc (struct incdep *cur, size_t size)
+{
+ void *ptr;
+
+#ifdef __APPLE__
+ if (cur && cur->worker_tid != -1)
+ {
+ ptr = malloc_zone_malloc (incdep_zone, size);
+ if (!ptr)
+ O (fatal, NILF, _("virtual memory exhausted"));
+ }
+ else
+ ptr = xmalloc (size);
+#else
+ ptr = xmalloc (size);
+#endif
+
+ (void)cur;
+ return ptr;
+}
+
+#if 0
+/* cmalloc wrapper */
+static void *
+incdep_xcalloc (struct incdep *cur, size_t size)
+{
+ void *ptr;
+
+#ifdef __APPLE__
+ if (cur && cur->worker_tid != -1)
+ ptr = malloc_zone_calloc (incdep_zone, size, 1);
+ else
+ ptr = calloc (size, 1);
+#else
+ ptr = calloc (size, 1);
+#endif
+ if (!ptr)
+ fatal (NILF, _("virtual memory exhausted"));
+
+ (void)cur;
+ return ptr;
+}
+#endif /* unused */
+
+/* free wrapper */
+static void
+incdep_xfree (struct incdep *cur, void *ptr)
+{
+ /* free() *must* work for the allocation hacks above because
+ of free_dep_chain. */
+ free (ptr);
+ (void)cur;
+}
+
+/* alloc a dep structure. These are allocated in bunches to save time. */
+struct dep *
+incdep_alloc_dep (struct incdep *cur)
+{
+ struct alloccache *cache;
+ if (cur->worker_tid != -1)
+ cache = &incdep_dep_caches[cur->worker_tid];
+ else
+ cache = &dep_cache;
+ return alloccache_calloc (cache);
+}
+
+/* duplicates the dependency list pointed to by srcdep. */
+static struct dep *
+incdep_dup_dep_list (struct incdep *cur, struct dep const *srcdep)
+{
+ struct alloccache *cache;
+ struct dep *retdep;
+ struct dep *dstdep;
+
+ if (cur->worker_tid != -1)
+ cache = &incdep_dep_caches[cur->worker_tid];
+ else
+ cache = &dep_cache;
+
+ if (srcdep)
+ {
+ retdep = dstdep = alloccache_alloc (cache);
+ for (;;)
+ {
+ dstdep->name = srcdep->name; /* string cached */
+ dstdep->includedep = srcdep->includedep;
+ srcdep = srcdep->next;
+ if (!srcdep)
+ {
+ dstdep->next = NULL;
+ break;
+ }
+ dstdep->next = alloccache_alloc (cache);
+ dstdep = dstdep->next;
+ }
+ }
+ else
+ retdep = NULL;
+ return retdep;
+}
+
+
+/* allocate a record. */
+static void *
+incdep_alloc_rec (struct incdep *cur)
+{
+ return alloccache_alloc (&incdep_rec_caches[cur->worker_tid]);
+}
+
+/* free a record. */
+static void
+incdep_free_rec (struct incdep *cur, void *rec)
+{
+ /*alloccache_free (&incdep_rec_caches[cur->worker_tid], rec); - doesn't work of course. */
+}
+
+
+/* grow a cache. */
+static void *
+incdep_cache_allocator (void *thrd, unsigned int size)
+{
+ (void)thrd;
+#ifdef __APPLE__
+ return malloc_zone_malloc (incdep_zone, size);
+#else
+ return xmalloc (size);
+#endif
+}
+
+/* term a cache. */
+static void
+incdep_cache_deallocator (void *thrd, void *ptr, unsigned int size)
+{
+ (void)thrd;
+ (void)size;
+ free (ptr);
+}
+
+/* acquires the lock */
+void
+incdep_lock(void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_mutex_lock (&incdep_mtx);
+#elif defined (WINDOWS32)
+ EnterCriticalSection (&incdep_mtx);
+#elif defined (__OS2__)
+ _fmutex_request (&incdep_mtx, 0);
+#endif
+}
+
+/* releases the lock */
+void
+incdep_unlock(void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_mutex_unlock (&incdep_mtx);
+#elif defined(WINDOWS32)
+ LeaveCriticalSection (&incdep_mtx);
+#elif defined(__OS2__)
+ _fmutex_release (&incdep_mtx);
+#endif
+}
+
+/* signals the main thread that there is stuff todo. caller owns the lock. */
+static void
+incdep_signal_done (void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_cond_broadcast (&incdep_cond_done);
+#elif defined (WINDOWS32)
+ if (incdep_hev_done_waiters)
+ SetEvent (incdep_hev_done);
+#elif defined (__OS2__)
+ if (incdep_hev_done_waiters)
+ DosPostEventSem (incdep_hev_done);
+#endif
+}
+
+/* waits for a reader to finish reading. caller owns the lock. */
+static void
+incdep_wait_done (void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_cond_wait (&incdep_cond_done, &incdep_mtx);
+
+#elif defined (WINDOWS32)
+ ResetEvent (incdep_hev_done);
+ incdep_hev_done_waiters++;
+ incdep_unlock ();
+ WaitForSingleObject (incdep_hev_done, INFINITE);
+ incdep_lock ();
+ incdep_hev_done_waiters--;
+
+#elif defined (__OS2__)
+ ULONG ulIgnore;
+ DosResetEventSem (incdep_hev_done, &ulIgnore);
+ incdep_hev_done_waiters++;
+ incdep_unlock ();
+ DosWaitEventSem (incdep_hev_done, SEM_INDEFINITE_WAIT);
+ incdep_lock ();
+ incdep_hev_done_waiters--;
+#endif
+}
+
+/* signals the worker threads. caller owns the lock. */
+static void
+incdep_signal_todo (void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_cond_broadcast (&incdep_cond_todo);
+#elif defined(WINDOWS32)
+ if (incdep_hev_todo_waiters)
+ SetEvent (incdep_hev_todo);
+#elif defined(__OS2__)
+ if (incdep_hev_todo_waiters)
+ DosPostEventSem (incdep_hev_todo);
+#endif
+}
+
+/* waits for stuff to arrive in the todo list. caller owns the lock. */
+static void
+incdep_wait_todo (void)
+{
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ pthread_cond_wait (&incdep_cond_todo, &incdep_mtx);
+
+#elif defined (WINDOWS32)
+ ResetEvent (incdep_hev_todo);
+ incdep_hev_todo_waiters++;
+ incdep_unlock ();
+ WaitForSingleObject (incdep_hev_todo, INFINITE);
+ incdep_lock ();
+ incdep_hev_todo_waiters--;
+
+#elif defined (__OS2__)
+ ULONG ulIgnore;
+ DosResetEventSem (incdep_hev_todo, &ulIgnore);
+ incdep_hev_todo_waiters++;
+ incdep_unlock ();
+ DosWaitEventSem (incdep_hev_todo, SEM_INDEFINITE_WAIT);
+ incdep_lock ();
+ incdep_hev_todo_waiters--;
+#endif
+}
+
+/* Reads a dep file into memory. */
+static int
+incdep_read_file (struct incdep *cur, floc *f)
+{
+#ifdef INCDEP_USE_KFSCACHE
+ size_t const cbFile = (size_t)cur->pFileObj->Stats.st_size;
+
+ assert(cur->pFileObj->fHaveStats);
+ cur->file_base = incdep_xmalloc (cur, cbFile + 1);
+ if (cur->file_base)
+ {
+ if (kFsCacheFileSimpleOpenReadClose (g_pFsCache, cur->pFileObj, 0, cur->file_base, cbFile))
+ {
+ cur->file_end = cur->file_base + cbFile;
+ cur->file_base[cbFile] = '\0';
+ return 0;
+ }
+ incdep_xfree (cur, cur->file_base);
+ }
+ OSS (error, f, "%s/%s: error reading file", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName);
+
+#else /* !INCDEP_USE_KFSCACHE */
+ int fd;
+ struct stat st;
+
+ errno = 0;
+# ifdef O_BINARY
+ fd = open (cur->name, O_RDONLY | O_BINARY, 0);
+# else
+ fd = open (cur->name, O_RDONLY, 0);
+# endif
+ if (fd < 0)
+ {
+ /* ignore non-existing dependency files. */
+ int err = errno;
+ if (err == ENOENT || stat (cur->name, &st) != 0)
+ return 1;
+ OSS (error, f, "%s: %s", cur->name, strerror (err));
+ return -1;
+ }
+# ifdef KBUILD_OS_WINDOWS /* fewer kernel calls */
+ if (!birdStatOnFdJustSize (fd, &st.st_size))
+# else
+ if (!fstat (fd, &st))
+# endif
+ {
+ cur->file_base = incdep_xmalloc (cur, st.st_size + 1);
+ if (read (fd, cur->file_base, st.st_size) == st.st_size)
+ {
+ close (fd);
+ cur->file_end = cur->file_base + st.st_size;
+ cur->file_base[st.st_size] = '\0';
+ return 0;
+ }
+
+ /* bail out */
+
+ OSS (error, f, "%s: read: %s", cur->name, strerror (errno));
+ incdep_xfree (cur, cur->file_base);
+ }
+ else
+ OSS (error, f, "%s: fstat: %s", cur->name, strerror (errno));
+
+ close (fd);
+#endif /* !INCDEP_USE_KFSCACHE */
+ cur->file_base = cur->file_end = NULL;
+ return -1;
+}
+
+/* Free the incdep structure. */
+static void
+incdep_freeit (struct incdep *cur)
+{
+#ifdef PARSE_IN_WORKER
+ assert (!cur->recorded_variables_in_set_head);
+ assert (!cur->recorded_variable_defs_head);
+ assert (!cur->recorded_file_head);
+#endif
+
+ incdep_xfree (cur, cur->file_base);
+#ifdef INCDEP_USE_KFSCACHE
+ /** @todo release object ref some day... */
+#endif
+ cur->next = NULL;
+ free (cur);
+}
+
+/* A worker thread. */
+void
+incdep_worker (int thrd)
+{
+ incdep_lock ();
+
+ while (!incdep_terminate)
+ {
+ /* get job from the todo list. */
+
+ struct incdep *cur = incdep_head_todo;
+ if (!cur)
+ {
+ incdep_wait_todo ();
+ continue;
+ }
+ if (cur->next)
+ incdep_head_todo = cur->next;
+ else
+ incdep_head_todo = incdep_tail_todo = NULL;
+ incdep_num_reading++;
+
+ /* read the file. */
+
+ incdep_unlock ();
+ cur->worker_tid = thrd;
+
+ incdep_read_file (cur, NILF);
+#ifdef PARSE_IN_WORKER
+ eval_include_dep_file (cur, NILF);
+#endif
+
+ cur->worker_tid = -1;
+ incdep_lock ();
+
+ /* insert finished job into the done list. */
+
+ incdep_num_reading--;
+ cur->next = NULL;
+ if (incdep_tail_done)
+ incdep_tail_done->next = cur;
+ else
+ incdep_head_done = cur;
+ incdep_tail_done = cur;
+
+ incdep_signal_done ();
+ }
+
+ incdep_unlock ();
+}
+
+/* Thread library specific thread functions wrapping incdep_wroker. */
+#ifdef HAVE_PTHREAD
+static void *
+incdep_worker_pthread (void *thrd)
+{
+ incdep_worker ((size_t)thrd);
+ return NULL;
+}
+
+#elif defined (WINDOWS32)
+static unsigned __stdcall
+incdep_worker_windows (void *thrd)
+{
+ incdep_worker ((size_t)thrd);
+ return 0;
+}
+
+#elif defined (__OS2__)
+static void
+incdep_worker_os2 (void *thrd)
+{
+ incdep_worker ((size_t)thrd);
+}
+#endif
+
+/* Checks if threads are enabled or not.
+
+ This is a special hack so that is possible to disable the threads when in a
+ debian fakeroot environment. Thus, in addition to the KMK_THREADS_DISABLED
+ and KMK_THREADS_ENABLED environment variable check we also check for signs
+ of fakeroot. */
+static int
+incdep_are_threads_enabled (void)
+{
+#if defined (CONFIG_WITHOUT_THREADS)
+ return 0;
+#endif
+
+ /* Generic overrides. */
+ if (getenv ("KMK_THREADS_DISABLED"))
+ {
+ O (message, 1, "Threads disabled (environment)");
+ return 0;
+ }
+ if (getenv ("KMK_THREADS_ENABLED"))
+ return 1;
+
+#if defined (__gnu_linux__) || defined (__linux__) || defined(__GLIBC__)
+ /* Try detect fakeroot. */
+ if (getenv ("FAKEROOTKEY")
+ || getenv ("FAKEROOTUID")
+ || getenv ("FAKEROOTGID")
+ || getenv ("FAKEROOTEUID")
+ || getenv ("FAKEROOTEGID")
+ || getenv ("FAKEROOTSUID")
+ || getenv ("FAKEROOTSGID")
+ || getenv ("FAKEROOTFUID")
+ || getenv ("FAKEROOTFGID")
+ || getenv ("FAKEROOTDONTTRYCHOWN")
+ || getenv ("FAKEROOT_FD_BASE")
+ || getenv ("FAKEROOT_DB_SEARCH_PATHS"))
+ {
+ O (message, 1, "Threads disabled (fakeroot)");
+ return 0;
+ }
+
+ /* LD_PRELOAD could indicate undetected debian fakeroot or some
+ other ingenius library which cannot deal correctly with threads. */
+ if (getenv ("LD_PRELOAD"))
+ {
+ O (message, 1, "Threads disabled (LD_PRELOAD)");
+ return 0;
+ }
+
+#elif defined(__APPLE__) \
+ || defined(__sun__) || defined(__SunOS__) || defined(__sun) || defined(__SunOS) \
+ || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) \
+ || defined(__HAIKU__)
+ /* No broken preload libraries known to be in common use on these platforms... */
+
+#elif defined(_MSC_VER) || defined(_WIN32) || defined(__OS2__)
+ /* No preload mess to care about. */
+
+#else
+# error "Add your self to the appropriate case above and send a patch to bird."
+#endif
+ return 1;
+}
+
+/* Creates the the worker threads. */
+static void
+incdep_init (floc *f)
+{
+ unsigned i;
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ int rc;
+ pthread_attr_t attr;
+
+#elif defined (WINDOWS32)
+ unsigned tid;
+ uintptr_t hThread;
+
+#elif defined (__OS2__)
+ int rc;
+ int tid;
+#endif
+ (void)f;
+
+ /* heap hacks */
+
+#ifdef __APPLE__
+ incdep_zone = malloc_create_zone (0, 0);
+ if (!incdep_zone)
+ incdep_zone = malloc_default_zone ();
+#endif
+
+
+ /* create the mutex and two condition variables / event objects. */
+
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ rc = pthread_mutex_init (&incdep_mtx, NULL);
+ if (rc)
+ ON (fatal, f, _("pthread_mutex_init failed: err=%d"), rc);
+ rc = pthread_cond_init (&incdep_cond_todo, NULL);
+ if (rc)
+ ON (fatal, f, _("pthread_cond_init failed: err=%d"), rc);
+ rc = pthread_cond_init (&incdep_cond_done, NULL);
+ if (rc)
+ ON (fatal, f, _("pthread_cond_init failed: err=%d"), rc);
+
+#elif defined (WINDOWS32)
+ InitializeCriticalSection (&incdep_mtx);
+ incdep_hev_todo = CreateEvent (NULL, TRUE /*bManualReset*/, FALSE /*bInitialState*/, NULL);
+ if (!incdep_hev_todo)
+ ON (fatal, f, _("CreateEvent failed: err=%d"), GetLastError());
+ incdep_hev_done = CreateEvent (NULL, TRUE /*bManualReset*/, FALSE /*bInitialState*/, NULL);
+ if (!incdep_hev_done)
+ ON (fatal, f, _("CreateEvent failed: err=%d"), GetLastError());
+ incdep_hev_todo_waiters = 0;
+ incdep_hev_done_waiters = 0;
+
+#elif defined (__OS2__)
+ _fmutex_create (&incdep_mtx, 0);
+ rc = DosCreateEventSem (NULL, &incdep_hev_todo, 0, FALSE);
+ if (rc)
+ ON (fatal, f, _("DosCreateEventSem failed: rc=%d"), rc);
+ rc = DosCreateEventSem (NULL, &incdep_hev_done, 0, FALSE);
+ if (rc)
+ ON (fatal, f, _("DosCreateEventSem failed: rc=%d"), rc);
+ incdep_hev_todo_waiters = 0;
+ incdep_hev_done_waiters = 0;
+#endif
+
+ /* create the worker threads and associated per thread data. */
+
+ incdep_terminate = 0;
+ if (incdep_are_threads_enabled())
+ {
+ incdep_num_threads = sizeof (incdep_threads) / sizeof (incdep_threads[0]);
+ if (incdep_num_threads + 1 > job_slots)
+ incdep_num_threads = job_slots <= 1 ? 1 : job_slots - 1;
+ for (i = 0; i < incdep_num_threads; i++)
+ {
+ /* init caches */
+ unsigned rec_size = sizeof (struct incdep_variable_in_set);
+ if (rec_size < sizeof (struct incdep_variable_def))
+ rec_size = sizeof (struct incdep_variable_def);
+ if (rec_size < sizeof (struct incdep_recorded_file))
+ rec_size = sizeof (struct incdep_recorded_file);
+ alloccache_init (&incdep_rec_caches[i], rec_size, "incdep rec",
+ incdep_cache_allocator, (void *)(size_t)i);
+ alloccache_init (&incdep_dep_caches[i], sizeof(struct dep), "incdep dep",
+ incdep_cache_allocator, (void *)(size_t)i);
+ strcache2_init (&incdep_dep_strcaches[i],
+ "incdep dep", /* name */
+ 65536, /* hash size */
+ 0, /* default segment size*/
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ 1, /* case insensitive */
+#else
+ 0, /* case insensitive */
+#endif
+ 0); /* thread safe */
+
+ strcache2_init (&incdep_var_strcaches[i],
+ "incdep var", /* name */
+ 32768, /* hash size */
+ 0, /* default segment size*/
+ 0, /* case insensitive */
+ 0); /* thread safe */
+
+ /* create the thread. */
+#if defined (HAVE_PTHREAD) && !defined (CONFIG_WITHOUT_THREADS)
+ rc = pthread_attr_init (&attr);
+ if (rc)
+ ON (fatal, f, _("pthread_attr_init failed: err=%d"), rc);
+ /*rc = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_JOINABLE); */
+ rc = pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
+ if (rc)
+ ON (fatal, f, _("pthread_attr_setdetachstate failed: err=%d"), rc);
+ rc = pthread_create (&incdep_threads[i], &attr,
+ incdep_worker_pthread, (void *)(size_t)i);
+ if (rc)
+ ON (fatal, f, _("pthread_mutex_init failed: err=%d"), rc);
+ pthread_attr_destroy (&attr);
+
+#elif defined (WINDOWS32)
+ tid = 0;
+ hThread = _beginthreadex (NULL, 128*1024, incdep_worker_windows,
+ (void *)i, 0, &tid);
+ if (hThread == 0 || hThread == ~(uintptr_t)0)
+ ON (fatal, f, _("_beginthreadex failed: err=%d"), errno);
+ incdep_threads[i] = (HANDLE)hThread;
+
+#elif defined (__OS2__)
+ tid = _beginthread (incdep_worker_os2, NULL, 128*1024, (void *)i);
+ if (tid <= 0)
+ ON (fatal, f, _("_beginthread failed: err=%d"), errno);
+ incdep_threads[i] = tid;
+#endif
+ }
+ }
+ else
+ incdep_num_threads = 0;
+
+ incdep_initialized = 1;
+}
+
+/* Flushes outstanding work and terminates the worker threads.
+ This is called from snap_deps(). */
+void
+incdep_flush_and_term (void)
+{
+ unsigned i;
+
+ if (!incdep_initialized)
+ return;
+
+ /* flush any out standing work */
+
+ incdep_flush_it (NILF);
+
+ /* tell the threads to terminate */
+
+ incdep_lock ();
+ incdep_terminate = 1;
+ incdep_signal_todo ();
+ incdep_unlock ();
+
+ /* wait for the threads to quit */
+
+ for (i = 0; i < incdep_num_threads; i++)
+ {
+ /* more later? */
+
+ /* terminate or join up the allocation caches. */
+ alloccache_term (&incdep_rec_caches[i], incdep_cache_deallocator, (void *)(size_t)i);
+ alloccache_join (&dep_cache, &incdep_dep_caches[i]);
+ strcache2_term (&incdep_dep_strcaches[i]);
+ strcache2_term (&incdep_var_strcaches[i]);
+ }
+ incdep_num_threads = 0;
+
+ /* destroy the lock and condition variables / event objects. */
+
+ /* later */
+
+ incdep_initialized = 0;
+}
+
+#ifdef PARSE_IN_WORKER
+/* Flushes a strcache entry returning the actual string cache entry.
+ The input is freed! */
+static const char *
+incdep_flush_strcache_entry (struct strcache2_entry *entry)
+{
+ if (!entry->user)
+ entry->user = (void *) strcache2_add_hashed_file (&file_strcache,
+ (const char *)(entry + 1),
+ entry->length, entry->hash);
+ return (const char *)entry->user;
+}
+
+/* Flushes the recorded instructions. */
+static void
+incdep_flush_recorded_instructions (struct incdep *cur)
+{
+ struct incdep_variable_in_set *rec_vis;
+ struct incdep_variable_def *rec_vd;
+ struct incdep_recorded_file *rec_f;
+
+ /* Display saved error. */
+
+ if (cur->err_msg)
+#ifdef INCDEP_USE_KFSCACHE
+ OSSNS (error, NILF, "%s/%s(%d): %s", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName,
+ cur->err_line_no, cur->err_msg);
+#else
+ OSNS (error,NILF, "%s(%d): %s", cur->name, cur->err_line_no, cur->err_msg);
+#endif
+
+
+ /* define_variable_in_set */
+
+ rec_vis = cur->recorded_variables_in_set_head;
+ cur->recorded_variables_in_set_head = cur->recorded_variables_in_set_tail = NULL;
+ if (rec_vis)
+ do
+ {
+ void *free_me = rec_vis;
+ unsigned int name_length = rec_vis->name_entry->length;
+ define_variable_in_set (incdep_flush_strcache_entry (rec_vis->name_entry),
+ name_length,
+ rec_vis->value,
+ rec_vis->value_length,
+ rec_vis->duplicate_value,
+ rec_vis->origin,
+ rec_vis->recursive,
+ rec_vis->set,
+ rec_vis->flocp);
+ rec_vis = rec_vis->next;
+ incdep_free_rec (cur, free_me);
+ }
+ while (rec_vis);
+
+ /* do_variable_definition */
+
+ rec_vd = cur->recorded_variable_defs_head;
+ cur->recorded_variable_defs_head = cur->recorded_variable_defs_tail = NULL;
+ if (rec_vd)
+ do
+ {
+ void *free_me = rec_vd;
+ do_variable_definition_2 (rec_vd->flocp,
+ incdep_flush_strcache_entry (rec_vd->name_entry),
+ rec_vd->value,
+ rec_vd->value_length,
+ 0,
+ rec_vd->value,
+ rec_vd->origin,
+ rec_vd->flavor,
+ rec_vd->target_var);
+ rec_vd = rec_vd->next;
+ incdep_free_rec (cur, free_me);
+ }
+ while (rec_vd);
+
+ /* record_files */
+
+ rec_f = cur->recorded_file_head;
+ cur->recorded_file_head = cur->recorded_file_tail = NULL;
+ if (rec_f)
+ do
+ {
+ void *free_me = rec_f;
+ struct dep *dep;
+
+ for (dep = rec_f->deps; dep; dep = dep->next)
+ dep->name = incdep_flush_strcache_entry ((struct strcache2_entry *)dep->name);
+
+ incdep_commit_recorded_file (incdep_flush_strcache_entry (rec_f->filename_entry),
+ rec_f->deps,
+ rec_f->flocp);
+
+ rec_f = rec_f->next;
+ incdep_free_rec (cur, free_me);
+ }
+ while (rec_f);
+}
+#endif /* PARSE_IN_WORKER */
+
+/* Record / issue a warning about a misformed dep file. */
+static void
+incdep_warn (struct incdep *cur, unsigned int line_no, const char *msg)
+{
+ if (cur->worker_tid == -1)
+#ifdef INCDEP_USE_KFSCACHE
+ OSSNS (error,NILF, "%s/%s(%d): %s", cur->pFileObj->pParent->Obj.pszName, cur->pFileObj->pszName, line_no, msg);
+#else
+ OSNS (error, NILF, "%s(%d): %s", cur->name, line_no, msg);
+#endif
+#ifdef PARSE_IN_WORKER
+ else
+ {
+ cur->err_line_no = line_no;
+ cur->err_msg = msg;
+ }
+#endif
+}
+
+/* Dependency or file strcache allocation / recording. */
+static const char *
+incdep_dep_strcache (struct incdep *cur, const char *str, int len)
+{
+ const char *ret;
+ if (cur->worker_tid == -1)
+ {
+ /* Make sure the string is terminated before we hand it to
+ strcache_add_len so it does have to make a temporary copy
+ of it on the stack. */
+ char ch = str[len];
+ ((char *)str)[len] = '\0';
+ ret = strcache_add_len (str, len);
+ ((char *)str)[len] = ch;
+ }
+ else
+ {
+ /* Add it out the strcache of the thread. */
+ ret = strcache2_add (&incdep_dep_strcaches[cur->worker_tid], str, len);
+ ret = (const char *)strcache2_get_entry(&incdep_dep_strcaches[cur->worker_tid], ret);
+ }
+ return ret;
+}
+
+/* Variable name allocation / recording. */
+static const char *
+incdep_var_strcache (struct incdep *cur, const char *str, int len)
+{
+ const char *ret;
+ if (cur->worker_tid == -1)
+ {
+ /* XXX: we're leaking this memory now! This will be fixed later. */
+ ret = xmalloc (len + 1);
+ memcpy ((char *)ret, str, len);
+ ((char *)ret)[len] = '\0';
+ }
+ else
+ {
+ /* Add it out the strcache of the thread. */
+ ret = strcache2_add (&incdep_var_strcaches[cur->worker_tid], str, len);
+ ret = (const char *)strcache2_get_entry(&incdep_var_strcaches[cur->worker_tid], ret);
+ }
+ return ret;
+}
+
+/* Record / perform a variable definition in a set.
+ The NAME is in the string cache.
+ The VALUE is on the heap.
+ The DUPLICATE_VALUE is always 0. */
+static void
+incdep_record_variable_in_set (struct incdep *cur,
+ const char *name, unsigned int name_length,
+ const char *value,
+ unsigned int value_length,
+ int duplicate_value,
+ enum variable_origin origin,
+ int recursive,
+ struct variable_set *set,
+ const floc *flocp)
+{
+ assert (!duplicate_value);
+ if (cur->worker_tid == -1)
+ define_variable_in_set (name, name_length, value, value_length,
+ duplicate_value, origin, recursive, set, flocp);
+#ifdef PARSE_IN_WORKER
+ else
+ {
+ struct incdep_variable_in_set *rec =
+ (struct incdep_variable_in_set *)incdep_alloc_rec (cur);
+ rec->name_entry = (struct strcache2_entry *)name;
+ rec->value = value;
+ rec->value_length = value_length;
+ rec->duplicate_value = duplicate_value;
+ rec->origin = origin;
+ rec->recursive = recursive;
+ rec->set = set;
+ rec->flocp = flocp;
+
+ rec->next = NULL;
+ if (cur->recorded_variables_in_set_tail)
+ cur->recorded_variables_in_set_tail->next = rec;
+ else
+ cur->recorded_variables_in_set_head = rec;
+ cur->recorded_variables_in_set_tail = rec;
+ }
+#endif
+}
+
+/* Record / perform a variable definition. The VALUE should be disposed of. */
+static void
+incdep_record_variable_def (struct incdep *cur,
+ const floc *flocp,
+ const char *name,
+ unsigned int name_length,
+ char *value,
+ unsigned int value_length,
+ enum variable_origin origin,
+ enum variable_flavor flavor,
+ int target_var)
+{
+ if (cur->worker_tid == -1)
+ do_variable_definition_2 (flocp, name, value, value_length, 0, value,
+ origin, flavor, target_var);
+#ifdef PARSE_IN_WORKER
+ else
+ {
+ struct incdep_variable_def *rec =
+ (struct incdep_variable_def *)incdep_alloc_rec (cur);
+ rec->flocp = flocp;
+ rec->name_entry = (struct strcache2_entry *)name;
+ rec->value = value;
+ rec->value_length = value_length;
+ rec->origin = origin;
+ rec->flavor = flavor;
+ rec->target_var = target_var;
+
+ rec->next = NULL;
+ if (cur->recorded_variable_defs_tail)
+ cur->recorded_variable_defs_tail->next = rec;
+ else
+ cur->recorded_variable_defs_head = rec;
+ cur->recorded_variable_defs_tail = rec;
+ }
+#else
+ (void)name_length;
+#endif
+}
+
+/* Similar to record_files in read.c, only much much simpler. */
+static void
+incdep_commit_recorded_file (const char *filename, struct dep *deps,
+ const floc *flocp)
+{
+ struct file *f;
+
+ /* Perform some validations. */
+ if (filename[0] == '.'
+ && ( streq(filename, ".POSIX")
+ || streq(filename, ".EXPORT_ALL_VARIABLES")
+ || streq(filename, ".INTERMEDIATE")
+ || streq(filename, ".LOW_RESOLUTION_TIME")
+ || streq(filename, ".NOTPARALLEL")
+ || streq(filename, ".ONESHELL")
+ || streq(filename, ".PHONY")
+ || streq(filename, ".PRECIOUS")
+ || streq(filename, ".SECONDARY")
+ || streq(filename, ".SECONDTARGETEXPANSION")
+ || streq(filename, ".SILENT")
+ || streq(filename, ".SHELLFLAGS")
+ || streq(filename, ".SUFFIXES")
+ )
+ )
+ {
+ OS (error, flocp, _("reserved filename '%s' used in dependency file, ignored"), filename);
+ return;
+ }
+
+ /* Lookup or create an entry in the database. */
+ f = enter_file (filename);
+ if (f->double_colon)
+ {
+ OS (error, flocp, _("dependency file '%s' has a double colon entry already, ignoring"), filename);
+ return;
+ }
+ f->is_target = 1;
+
+ /* Append dependencies. */
+ deps = enter_prereqs (deps, NULL);
+ if (deps)
+ {
+ struct dep *last = f->deps;
+ if (!last)
+ f->deps = deps;
+ else
+ {
+ while (last->next)
+ last = last->next;
+ last->next = deps;
+ }
+ }
+}
+
+/* Record a file.*/
+static void
+incdep_record_file (struct incdep *cur,
+ const char *filename,
+ struct dep *deps,
+ const floc *flocp)
+{
+ if (cur->worker_tid == -1)
+ incdep_commit_recorded_file (filename, deps, flocp);
+#ifdef PARSE_IN_WORKER
+ else
+ {
+ struct incdep_recorded_file *rec =
+ (struct incdep_recorded_file *) incdep_alloc_rec (cur);
+
+ rec->filename_entry = (struct strcache2_entry *)filename;
+ rec->deps = deps;
+ rec->flocp = flocp;
+
+ rec->next = NULL;
+ if (cur->recorded_file_tail)
+ cur->recorded_file_tail->next = rec;
+ else
+ cur->recorded_file_head = rec;
+ cur->recorded_file_tail = rec;
+ }
+#endif
+}
+
+/* Counts slashes backwards from SLASH, stopping at START. */
+static size_t incdep_count_slashes_backwards(const char *slash, const char *start)
+{
+ size_t slashes = 1;
+ assert (*slash == '\\');
+ while ((uintptr_t)slash > (uintptr_t)start && slash[0 - slashes] == '\\')
+ slashes++;
+ return slashes;
+}
+
+/* Whitespace cannot be escaped at the end of a line, there has to be
+ some stuff following it other than a line continuation slash.
+
+ So, we look ahead and makes sure that there is something non-whitespaced
+ following this allegedly escaped whitespace.
+
+ This code ASSUMES the file content is zero terminated! */
+static int incdep_verify_escaped_whitespace(const char *ws)
+{
+ char ch;
+
+ assert(ws[-1] == '\\');
+ assert(ISBLANK((unsigned int)ws[0]));
+
+ /* If the character following the '\ ' sequence is not a whitespace,
+ another escape character or null terminator, we're good. */
+ ws += 2;
+ ch = *ws;
+ if (ch != '\\' && !ISSPACE((unsigned int)ch) && ch != '\0')
+ return 1;
+
+ /* Otherwise we'll have to parse forward till we hit the end of the
+ line/file or something. */
+ while ((ch = *ws++) != '\0')
+ {
+ if (ch == '\\')
+ {
+ /* escaped newline? */
+ ch = *ws;
+ if (ch == '\n')
+ ws++;
+ else if (ch == '\r' && ws[1] == '\n')
+ ws += 2;
+ else
+ return 1;
+ }
+ else if (ISBLANK((unsigned int)ch))
+ { /* contine */ }
+ else if (!ISSPACE((unsigned int)ch))
+ return 1;
+ else
+ return 0; /* newline; all trailing whitespace will be ignored. */
+ }
+
+ return 0;
+}
+
+/* Unescapes the next filename and returns cached copy.
+
+ Modifies the input string that START points to.
+
+ When NEXTP is not NULL, ASSUME target filename and that END isn't entirely
+ accurate in case the filename ends with a trailing backslash. There can be
+ more than one filename in a this case. NEXTP will be set to the first
+ character after then filename.
+
+ When NEXTP is NULL, ASSUME exactly one dependency filename and that END is
+ accurately deliminating the string.
+ */
+static const char *
+incdep_unescape_and_cache_filename(struct incdep *curdep, char *start, const char *end,
+ int const is_dep, const char **nextp, unsigned int *linenop)
+{
+ unsigned const esc_mask = MAP_BLANK /* ' ' + '\t' */
+ | MAP_COLON /* ':' */
+ | MAP_COMMENT /* '#' */
+ | MAP_EQUALS /* '=' */
+ | MAP_SEMI /* ';' */
+ | ( is_dep
+ ? MAP_PIPE /* '|' */
+ : MAP_PERCENT); /* '%' */
+ unsigned const all_esc_mask = esc_mask | MAP_BLANK | MAP_NEWLINE;
+ unsigned const stop_mask = nextp ? MAP_BLANK | MAP_NEWLINE | (!is_dep ? MAP_COLON : 0) : 0;
+ char volatile *src;
+ char volatile *dst;
+
+ /*
+ * Skip forward to the first escaped character so we can avoid unnecessary shifting.
+ */
+#if 1
+ src = start;
+ dst = start;
+#elif 1
+ static const char s_szStop[] = "\n\r\t ";
+
+ src = memchr(start, '$', end - start);
+ dst = memchr(start, '\\', end - start);
+ if (src && ((uintptr_t)src < (uintptr_t)dst || dst == NULL))
+ dst = src;
+ else if (dst && ((uintptr_t)dst < (uintptr_t)src || src == NULL))
+ src = dst;
+ else
+ {
+ assert(src == NULL && dst == NULL);
+ if (nextp)
+ {
+ int i = sizeof(s_szStop);
+ while (i-- > 0)
+ {
+ char *stop = memchr(start, s_szStop[i], end - start);
+ if (stop)
+ end = stop;
+ }
+ *nextp = end;
+ }
+ return incdep_dep_strcache (curdep, start, end - start);
+ }
+ if (nextp)
+ {
+ char *stop = src;
+ int i = sizeof(s_szStop);
+ while (i-- > 0)
+ {
+ char *stop2 = memchr(start, s_szStop[i], stop - start);
+ if (stop2)
+ stop = stop2;
+ }
+ if (stop != src)
+ {
+ *nextp = stop;
+ return incdep_dep_strcache (curdep, start, stop - start);
+ }
+ }
+#endif
+
+ /*
+ * Copy char-by-char, undoing escaping as we go along.
+ */
+ while ((uintptr_t)src < (uintptr_t)end)
+ {
+ const char ch = *src++;
+ if (ch != '\\' && ch != '$')
+ {
+ if (!STOP_SET (ch, stop_mask))
+ *dst++ = ch;
+ else
+ {
+ src--;
+ break;
+ }
+ }
+ else
+ {
+ char ch2 = *src++; /* No bounds checking to handle "/dir/file\ : ..." when end points at " :". */
+ if (ch == '$')
+ {
+ if (ch2 != '$') /* $$ -> $ - Ignores secondary expansion! */
+ src--;
+ *dst++ = ch;
+ }
+ else
+ {
+ unsigned int ch2_map;
+
+ /* Eat all the slashes and see what's at the end of them as that's all
+ that's relevant. If there is an escapable char, we'll emit half of
+ the slashes. */
+ size_t const max_slashes = src - start - 1;
+ size_t slashes = 1;
+ while (ch2 == '\\')
+ {
+ slashes++;
+ ch2 = *src++;
+ }
+
+ /* Is it escapable? */
+ ch2_map = stopchar_map[(unsigned char)ch2];
+ if (ch2_map & all_esc_mask)
+ {
+ /* Non-whitespace is simple: Slash slashes, output or stop. */
+ if (!(ch2_map & (MAP_BLANK | MAP_NEWLINE)))
+ {
+ assert(ch2_map & esc_mask);
+ while (slashes >= 2)
+ {
+ *dst++ = '\\';
+ slashes -= 2;
+ }
+ if (slashes || !(stop_mask & ch2_map))
+ *dst++ = ch2;
+ else
+ {
+ src--;
+ break;
+ }
+ }
+ /* Escaped blanks or newlines.
+
+ We have to pretent that we've already replaced any escaped newlines
+ and associated whitespace with a single space here. We also have to
+ pretend trailing whitespace doesn't exist when IS_DEP is non-zero.
+ This makes for pretty interesting times... */
+ else
+ {
+ char ch3;
+
+ /* An Escaped blank is interesting because it is striped unconditionally
+ at the end of a line, regardless of how many escaped newlines may
+ following it. We join the escaped newline handling if we fine one
+ following us. */
+ if (ch2_map & MAP_BLANK)
+ {
+ /* skip whitespace and check for escaped newline. */
+ volatile char * const src_saved = src;
+ while ((ch3 = *src) != '\0' && ISBLANK(ch3))
+ src++;
+ if (ch3 == '\\' && src[1] == '\n')
+ src += 2; /* Escaped blank & newline joins into single space. */
+ else if (ch3 == '\\' && src[1] == '\r' && src[2] == '\n')
+ src += 3; /* -> Join the escaped newline code below on the next line. */
+ else if (STOP_SET(ch3, stop_mask & MAP_NEWLINE))
+ { /* last thing on the line, no blanks to escape. */
+ while (slashes-- > 0)
+ *dst++ = '\\';
+ break;
+ }
+ else
+ {
+ src = src_saved;
+ while (slashes >= 2)
+ {
+ *dst++ = '\\';
+ slashes -= 2;
+ }
+ if (slashes)
+ {
+ *dst++ = ch2;
+ continue;
+ }
+ assert (nextp || (uintptr_t)src >= (uintptr_t)end);
+ break;
+ }
+ }
+ /* Escaped newlines get special treatment as they an any adjacent whitespace
+ gets reduced to a single space, including subsequent escaped newlines.
+ In addition, if this is the final dependency/file and there is no
+ significant new characters following this escaped newline, the replacement
+ space will also be stripped and we won't have anything to escape, meaning
+ that the slashes will remain as is. Finally, none of this space stuff can
+ be stop characters, unless of course a newline isn't escaped. */
+ else
+ {
+ assert (ch2_map & MAP_NEWLINE);
+ if (ch2 == '\r' && *src == '\n')
+ src++;
+ }
+
+ /* common space/newline code */
+ for (;;)
+ {
+ if (linenop)
+ *linenop += 1;
+ while ((uintptr_t)src < (uintptr_t)end && ISBLANK(*src))
+ src++;
+ if ((uintptr_t)src >= (uintptr_t)end)
+ {
+ ch3 = '\0';
+ break;
+ }
+ ch3 = *src;
+ if (ch3 != '\\')
+ break;
+ ch3 = src[1];
+ if (ch3 == '\n')
+ src += 2;
+ else if (ch3 == '\r' && src[2] == '\n')
+ src += 3;
+ else
+ break;
+ }
+
+ if (is_dep && STOP_SET(ch3, stop_mask | MAP_NUL))
+ { /* last thing on the line, no blanks to escape. */
+ while (slashes-- > 0)
+ *dst++ = '\\';
+ break;
+ }
+ while (slashes >= 2)
+ {
+ *dst++ = '\\';
+ slashes -= 2;
+ }
+ if (slashes)
+ *dst++ = ' ';
+ else
+ {
+ assert (nextp || (uintptr_t)src >= (uintptr_t)end);
+ break;
+ }
+ }
+ }
+ /* Just output the slash if non-escapable character: */
+ else
+ {
+ while (slashes-- > 0)
+ *dst++ = '\\';
+ src--;
+ }
+ }
+ }
+ }
+
+ if (nextp)
+ *nextp = (const char *)src;
+ return incdep_dep_strcache(curdep, start, dst - start);
+}
+
+/* no nonsense dependency file including.
+
+ Because nobody wants bogus dependency files to break their incremental
+ builds with hard to comprehend error messages, this function does not
+ use the normal eval routine but does all the parsing itself. This isn't,
+ as much work as it sounds, because the necessary feature set is very
+ limited.
+
+ eval_include_dep_file groks:
+
+ define var
+ endef
+
+ var [|:|?|>]= value [\]
+
+ [\]
+ file: [deps] [\]
+
+ */
+static void
+eval_include_dep_file (struct incdep *curdep, floc *f)
+{
+ unsigned line_no = 1;
+ const char *file_end = curdep->file_end;
+ const char *cur = curdep->file_base;
+ const char *endp;
+
+ /* if no file data, just return immediately. */
+ if (!cur)
+ return;
+
+ /* now parse the file. */
+ while ((uintptr_t)cur < (uintptr_t)file_end)
+ {
+ /* skip empty lines */
+ while ((uintptr_t)cur < (uintptr_t)file_end && ISSPACE (*cur) && *cur != '\n')
+ ++cur;
+ if ((uintptr_t)cur >= (uintptr_t)file_end)
+ break;
+ if (*cur == '#')
+ {
+ cur = memchr (cur, '\n', file_end - cur);
+ if (!cur)
+ break;
+ }
+ if (*cur == '\\')
+ {
+ unsigned eol_len = (file_end - cur > 1 && cur[1] == '\n') ? 2
+ : (file_end - cur > 2 && cur[1] == '\r' && cur[2] == '\n') ? 3
+ : (file_end - cur == 1) ? 1 : 0;
+ if (eol_len)
+ {
+ cur += eol_len;
+ line_no++;
+ continue;
+ }
+ }
+ if (*cur == '\n')
+ {
+ cur++;
+ line_no++;
+ continue;
+ }
+
+ /* define var
+ ...
+ endef */
+ if (strneq (cur, "define ", 7))
+ {
+ const char *var;
+ unsigned var_len;
+ const char *value_start;
+ const char *value_end;
+ char *value;
+ unsigned value_len;
+ int found_endef = 0;
+
+ /* extract the variable name. */
+ cur += 7;
+ while (ISBLANK (*cur))
+ ++cur;
+ value_start = endp = memchr (cur, '\n', file_end - cur);
+ if (!endp)
+ endp = cur;
+ while (endp > cur && ISSPACE (endp[-1]))
+ --endp;
+ var_len = endp - cur;
+ if (!var_len)
+ {
+ incdep_warn (curdep, line_no, "bogus define statement.");
+ break;
+ }
+ var = incdep_var_strcache (curdep, cur, var_len);
+
+ /* find the end of the variable. */
+ cur = value_end = value_start = value_start + 1;
+ ++line_no;
+ while ((uintptr_t)cur < (uintptr_t)file_end)
+ {
+ /* check for endef, don't bother with skipping leading spaces. */
+ if ( file_end - cur >= 5
+ && strneq (cur, "endef", 5))
+ {
+ endp = cur + 5;
+ while ((uintptr_t)endp < (uintptr_t)file_end && ISSPACE (*endp) && *endp != '\n')
+ endp++;
+ if ((uintptr_t)endp >= (uintptr_t)file_end || *endp == '\n')
+ {
+ found_endef = 1;
+ cur = (uintptr_t)endp >= (uintptr_t)file_end ? file_end : endp + 1;
+ break;
+ }
+ }
+
+ /* skip a line ahead. */
+ cur = value_end = memchr (cur, '\n', file_end - cur);
+ if (cur != NULL)
+ ++cur;
+ else
+ cur = value_end = file_end;
+ ++line_no;
+ }
+
+ if (!found_endef)
+ {
+ incdep_warn (curdep, line_no, "missing endef, dropping the rest of the file.");
+ break;
+ }
+ value_len = value_end - value_start;
+ if (memchr (value_start, '\0', value_len))
+ {
+ incdep_warn (curdep, line_no, "'\\0' in define, dropping the rest of the file.");
+ break;
+ }
+
+ /* make a copy of the value, converting \r\n to \n, and define it. */
+ value = incdep_xmalloc (curdep, value_len + 1);
+ endp = memchr (value_start, '\r', value_len);
+ if (endp)
+ {
+ const char *src = value_start;
+ char *dst = value;
+ for (;;)
+ {
+ size_t len = endp - src;
+ memcpy (dst, src, len);
+ dst += len;
+ src = endp;
+ if ((uintptr_t)src + 1 < (uintptr_t)file_end && src[1] == '\n')
+ src++; /* skip the '\r' */
+ if (src >= value_end)
+ break;
+ endp = memchr (endp + 1, '\r', src - value_end);
+ if (!endp)
+ endp = value_end;
+ }
+ value_len = dst - value;
+ }
+ else
+ memcpy (value, value_start, value_len);
+ value [value_len] = '\0';
+
+ incdep_record_variable_in_set (curdep,
+ var, var_len, value, value_len,
+ 0 /* don't duplicate */, o_file,
+ 0 /* defines are recursive but this is faster */,
+ NULL /* global set */, f);
+ }
+
+ /* file: deps
+ OR
+ variable [:]= value */
+ else
+ {
+ const char *equalp;
+ const char *eol;
+
+ /* Look for a colon or an equal sign. In the assignment case, we
+ require it to be on the same line as the variable name to simplify
+ the code. Because of clang, we cannot make the same assumptions
+ with file dependencies. So, start with the equal. */
+
+ assert (*cur != '\n');
+ eol = memchr (cur, '\n', file_end - cur);
+ if (!eol)
+ eol = file_end;
+ equalp = memchr (cur, '=', eol - cur);
+ if (equalp && equalp != cur && (ISSPACE(equalp[-1]) || equalp[-1] != '\\'))
+ {
+ /* An assignment of some sort. */
+ const char *var;
+ unsigned var_len;
+ const char *value_start;
+ const char *value_end;
+ char *value;
+ unsigned value_len;
+ unsigned multi_line = 0;
+ enum variable_flavor flavor;
+
+ /* figure the flavor first. */
+ flavor = f_recursive;
+ if (equalp > cur)
+ {
+ if (equalp[-1] == ':')
+ flavor = f_simple;
+ else if (equalp[-1] == '?')
+ flavor = f_conditional;
+ else if (equalp[-1] == '+')
+ flavor = f_append;
+ else if (equalp[-1] == '>')
+ flavor = f_prepend;
+ }
+
+ /* extract the variable name. */
+ endp = flavor == f_recursive ? equalp : equalp - 1;
+ while (endp > cur && ISBLANK (endp[-1]))
+ --endp;
+ var_len = endp - cur;
+ if (!var_len)
+ {
+ incdep_warn (curdep, line_no, "empty variable. (includedep)");
+ break;
+ }
+ if ( memchr (cur, '$', var_len)
+ || memchr (cur, ' ', var_len)
+ || memchr (cur, '\t', var_len))
+ {
+ incdep_warn (curdep, line_no, "fancy variable name. (includedep)");
+ break;
+ }
+ var = incdep_var_strcache (curdep, cur, var_len);
+
+ /* find the start of the value. */
+ cur = equalp + 1;
+ while ((uintptr_t)cur < (uintptr_t)file_end && ISBLANK (*cur))
+ cur++;
+ value_start = cur;
+
+ /* find the end of the value / line (this isn't 101% correct). */
+ value_end = cur;
+ while ((uintptr_t)cur < (uintptr_t)file_end)
+ {
+ endp = value_end = memchr (cur, '\n', file_end - cur);
+ if (!value_end)
+ value_end = file_end;
+ if (value_end - 1 >= cur && value_end[-1] == '\r')
+ --value_end;
+ if (value_end - 1 < cur || value_end[-1] != '\\')
+ {
+ cur = endp ? endp + 1 : file_end;
+ break;
+ }
+ --value_end;
+ if (value_end - 1 >= cur && value_end[-1] == '\\')
+ {
+ incdep_warn (curdep, line_no, "fancy escaping! (includedep)");
+ cur = NULL;
+ break;
+ }
+ if (!endp)
+ {
+ cur = file_end;
+ break;
+ }
+
+ cur = endp + 1;
+ ++multi_line;
+ ++line_no;
+ }
+ if (!cur)
+ break;
+ ++line_no;
+
+ /* make a copy of the value, converting \r\n to \n, and define it. */
+ value_len = value_end - value_start;
+ value = incdep_xmalloc (curdep, value_len + 1);
+ if (!multi_line)
+ memcpy (value, value_start, value_len);
+ else
+ {
+ /* unescape it */
+ const char *src = value_start;
+ char *dst = value;
+ while (src < value_end)
+ {
+ const char *nextp;
+
+ endp = memchr (src, '\n', value_end - src);
+ if (!endp)
+ nextp = endp = value_end;
+ else
+ nextp = endp + 1;
+ if (endp > src && endp[-1] == '\r')
+ --endp;
+ if (endp > src && endp[-1] == '\\')
+ --endp;
+
+ if (src != value_start)
+ *dst++ = ' ';
+ memcpy (dst, src, endp - src);
+ dst += endp - src;
+ src = nextp;
+ }
+ value_len = dst - value;
+ }
+ value [value_len] = '\0';
+
+ /* do the definition */
+ if (flavor == f_recursive
+ || ( flavor == f_simple
+ && !memchr (value, '$', value_len)))
+ incdep_record_variable_in_set (curdep,
+ var, var_len, value, value_len,
+ 0 /* don't duplicate */, o_file,
+ flavor == f_recursive /* recursive */,
+ NULL /* global set */, f);
+ else
+ incdep_record_variable_def (curdep,
+ f, var, var_len, value, value_len,
+ o_file, flavor, 0 /* not target var */);
+ }
+ else
+ {
+ /* Expecting: file: dependencies */
+
+ int unescape_filename = 0;
+ const char *filename;
+ const char *fnnext;
+ const char *fnend;
+ const char *colonp;
+ struct dep *deps = 0;
+ struct dep **nextdep = &deps;
+ struct dep *dep;
+
+
+ /* Locate the next file colon. If it's not within the bounds of
+ the current line, check that all new line chars are escaped. */
+
+ colonp = memchr (cur, ':', file_end - cur);
+ while ( colonp
+ && ( ( colonp != cur
+ && colonp[-1] == '\\'
+ && incdep_count_slashes_backwards (&colonp[-1], cur) & 1)
+#ifdef HAVE_DOS_PATHS
+ || ( colonp + 1 < file_end
+ && (colonp[1] == '/' || colonp[1] == '\\')
+ && colonp > cur
+ && isalpha ((unsigned char)colonp[-1])
+ && ( colonp == cur + 1
+ || ISBLANK ((unsigned char)colonp[-2])))
+#endif
+ )
+ )
+ colonp = memchr (colonp + 1, ':', file_end - (colonp + 1));
+ if (!colonp)
+ {
+ incdep_warn (curdep, line_no, "no colon.");
+ break;
+ }
+
+ if ((uintptr_t)colonp < (uintptr_t)eol)
+ unescape_filename = memchr (cur, '\\', colonp - cur) != NULL
+ || memchr (cur, '$', colonp - cur) != NULL;
+ else if (memchr (eol, '=', colonp - eol))
+ {
+ incdep_warn (curdep, line_no, "multi line assignment / dependency confusion.");
+ break;
+ }
+ else
+ {
+ const char *sol = cur;
+ do
+ {
+ char *eol2 = (char *)eol - 1;
+ if ((uintptr_t)eol2 >= (uintptr_t)sol && *eol2 == '\r') /* DOS line endings. */
+ eol2--;
+ if ((uintptr_t)eol2 < (uintptr_t)sol || *eol2 != '\\')
+ incdep_warn (curdep, line_no, "no colon.");
+ else if (eol2 != sol && eol2[-1] == '\\')
+ incdep_warn (curdep, line_no, "fancy EOL escape. (includedep)");
+ else
+ {
+ line_no++;
+ sol = eol + 1;
+ eol = memchr (sol, '\n', colonp - sol);
+ continue;
+ }
+ sol = NULL;
+ break;
+ }
+ while (eol != NULL);
+ if (!sol)
+ break;
+ unescape_filename = 1;
+ }
+
+ /* Extract the first filename after trimming and basic checks. */
+ fnend = colonp;
+ while ((uintptr_t)fnend > (uintptr_t)cur && ISBLANK (fnend[-1]))
+ --fnend;
+ if (cur == fnend)
+ {
+ incdep_warn (curdep, line_no, "empty filename.");
+ break;
+ }
+ fnnext = cur;
+ if (!unescape_filename)
+ {
+ while (fnnext != fnend && !ISBLANK (*fnnext))
+ fnnext++;
+ filename = incdep_dep_strcache (curdep, cur, fnnext - cur);
+ }
+ else
+ filename = incdep_unescape_and_cache_filename (curdep, (char *)fnnext, fnend, 0, &fnnext, NULL);
+
+ /* parse any dependencies. */
+ cur = colonp + 1;
+ while ((uintptr_t)cur < (uintptr_t)file_end)
+ {
+ const char *dep_file;
+
+ /* skip blanks and count lines. */
+ char ch = 0;
+ while ((uintptr_t)cur < (uintptr_t)file_end && ISSPACE ((ch = *cur)) && ch != '\n')
+ ++cur;
+ if ((uintptr_t)cur >= (uintptr_t)file_end)
+ break;
+ if (ch == '\n')
+ {
+ cur++;
+ line_no++;
+ break;
+ }
+
+ /* continuation + eol? */
+ if (ch == '\\')
+ {
+ unsigned eol_len = (file_end - cur > 1 && cur[1] == '\n') ? 2
+ : (file_end - cur > 2 && cur[1] == '\r' && cur[2] == '\n') ? 3
+ : (file_end - cur == 1) ? 1 : 0;
+ if (eol_len)
+ {
+ cur += eol_len;
+ line_no++;
+ continue;
+ }
+ }
+
+ /* find the end of the filename and cache it */
+ dep_file = NULL;
+ endp = cur;
+ for (;;)
+ if ((uintptr_t)endp < (uintptr_t)file_end)
+ {
+ ch = *endp;
+ if (ch != '\\' && ch != '$' )
+ {
+ if (!ISSPACE (ch))
+ endp++;
+ else
+ {
+ dep_file = incdep_dep_strcache(curdep, cur, endp - cur);
+ break;
+ }
+ }
+ else
+ {
+ /* potential escape sequence, let the unescaper do the rest. */
+ dep_file = incdep_unescape_and_cache_filename (curdep, (char *)cur, file_end, 1, &endp, &line_no);
+ break;
+ }
+ }
+ else
+ {
+ dep_file = incdep_dep_strcache(curdep, cur, endp - cur);
+ break;
+ }
+
+ /* add it to the list. */
+ *nextdep = dep = incdep_alloc_dep (curdep);
+ dep->includedep = 1;
+ dep->name = dep_file;
+ nextdep = &dep->next;
+
+ cur = endp;
+ }
+
+ /* enter the file with its dependencies. */
+ incdep_record_file (curdep, filename, deps, f);
+
+ /* More files? Record them with the same dependency list. */
+ if ((uintptr_t)fnnext < (uintptr_t)fnend)
+ for (;;)
+ {
+ const char *filename_prev = filename;
+ while (fnnext != fnend && ISBLANK (*fnnext))
+ fnnext++;
+ if (fnnext == fnend)
+ break;
+ if (*fnnext == '\\')
+ {
+ if (fnnext[1] == '\n')
+ {
+ line_no++;
+ fnnext += 2;
+ continue;
+ }
+ if (fnnext[1] == '\r' && fnnext[2] == '\n')
+ {
+ line_no++;
+ fnnext += 3;
+ continue;
+ }
+ }
+
+ if (!unescape_filename)
+ {
+ const char *fnstart = fnnext;
+ while (fnnext != fnend && !ISBLANK (*fnnext))
+ fnnext++;
+ filename = incdep_dep_strcache (curdep, fnstart, fnnext - fnstart);
+ }
+ else
+ filename = incdep_unescape_and_cache_filename (curdep, (char *)fnnext, fnend, 0, &fnnext, NULL);
+ if (filename != filename_prev) /* clang optimization. */
+ incdep_record_file (curdep, filename, incdep_dup_dep_list (curdep, deps), f);
+ }
+ }
+ }
+ }
+
+ /* free the file data */
+ incdep_xfree (curdep, curdep->file_base);
+ curdep->file_base = curdep->file_end = NULL;
+}
+
+/* Flushes the incdep todo and done lists. */
+static void
+incdep_flush_it (floc *f)
+{
+ incdep_lock ();
+ for (;;)
+ {
+ struct incdep *cur = incdep_head_done;
+
+ /* if the done list is empty, grab a todo list entry. */
+ if (!cur && incdep_head_todo)
+ {
+ cur = incdep_head_todo;
+ if (cur->next)
+ incdep_head_todo = cur->next;
+ else
+ incdep_head_todo = incdep_tail_todo = NULL;
+ incdep_unlock ();
+
+ incdep_read_file (cur, f);
+ eval_include_dep_file (cur, f);
+ incdep_freeit (cur);
+
+ incdep_lock ();
+ continue;
+ }
+
+ /* if the todo list and done list are empty we're either done
+ or will have to wait for the thread(s) to finish. */
+ if (!cur && !incdep_num_reading)
+ break; /* done */
+ if (!cur)
+ {
+ while (!incdep_head_done)
+ incdep_wait_done ();
+ cur = incdep_head_done;
+ }
+
+ /* we grab the entire done list and work thru it. */
+ incdep_head_done = incdep_tail_done = NULL;
+ incdep_unlock ();
+
+ while (cur)
+ {
+ struct incdep *next = cur->next;
+#ifdef PARSE_IN_WORKER
+ incdep_flush_recorded_instructions (cur);
+#else
+ eval_include_dep_file (cur, f);
+#endif
+ incdep_freeit (cur);
+ cur = next;
+ }
+
+ incdep_lock ();
+ } /* outer loop */
+ incdep_unlock ();
+}
+
+
+/* splits up a list of file names and feeds it to eval_include_dep_file,
+ employing threads to try speed up the file reading. */
+void
+eval_include_dep (const char *names, floc *f, enum incdep_op op)
+{
+ struct incdep *head = 0;
+ struct incdep *tail = 0;
+ struct incdep *cur;
+ const char *names_iterator = names;
+ const char *name;
+ unsigned int name_len;
+
+ /* loop through NAMES, creating a todo list out of them. */
+
+ while ((name = find_next_token (&names_iterator, &name_len)) != 0)
+ {
+#ifdef INCDEP_USE_KFSCACHE
+ KFSLOOKUPERROR enmError;
+ PKFSOBJ pFileObj = kFsCacheLookupWithLengthA (g_pFsCache, name, name_len, &enmError);
+ if (!pFileObj)
+ continue;
+ if (pFileObj->bObjType != KFSOBJ_TYPE_FILE)
+ {
+ kFsCacheObjRelease (g_pFsCache, pFileObj);
+ continue;
+ }
+
+ cur = xmalloc (sizeof (*cur)); /* not incdep_xmalloc here */
+ cur->pFileObj = pFileObj;
+#else
+ cur = xmalloc (sizeof (*cur) + name_len); /* not incdep_xmalloc here */
+ memcpy (cur->name, name, name_len);
+ cur->name[name_len] = '\0';
+#endif
+
+ cur->file_base = cur->file_end = NULL;
+ cur->worker_tid = -1;
+#ifdef PARSE_IN_WORKER
+ cur->err_line_no = 0;
+ cur->err_msg = NULL;
+ cur->recorded_variables_in_set_head = NULL;
+ cur->recorded_variables_in_set_tail = NULL;
+ cur->recorded_variable_defs_head = NULL;
+ cur->recorded_variable_defs_tail = NULL;
+ cur->recorded_file_head = NULL;
+ cur->recorded_file_tail = NULL;
+#endif
+
+ cur->next = NULL;
+ if (tail)
+ tail->next = cur;
+ else
+ head = cur;
+ tail = cur;
+ }
+
+#ifdef ELECTRIC_HEAP
+ if (1)
+#else
+ if (op == incdep_read_it)
+#endif
+ {
+ /* work our way thru the files directly */
+
+ cur = head;
+ while (cur)
+ {
+ struct incdep *next = cur->next;
+ incdep_read_file (cur, f);
+ eval_include_dep_file (cur, f);
+ incdep_freeit (cur);
+ cur = next;
+ }
+ }
+ else
+ {
+ /* initialize the worker threads and related stuff the first time around. */
+
+ if (!incdep_initialized)
+ incdep_init (f);
+
+ /* queue the files and notify the worker threads. */
+
+ incdep_lock ();
+
+ if (incdep_tail_todo)
+ incdep_tail_todo->next = head;
+ else
+ incdep_head_todo = head;
+ incdep_tail_todo = tail;
+
+ incdep_signal_todo ();
+ incdep_unlock ();
+
+ /* flush the todo queue if we're requested to do so. */
+
+ if (op == incdep_flush)
+ incdep_flush_it (f);
+ }
+}
+
+#endif /* CONFIG_WITH_INCLUDEDEP */
+
diff --git a/src/kmk/inlined_memchr.h b/src/kmk/inlined_memchr.h
new file mode 100644
index 0000000..9666635
--- /dev/null
+++ b/src/kmk/inlined_memchr.h
@@ -0,0 +1,162 @@
+#define _GNU_SOURCE 1
+#include <string.h>
+
+#ifdef _MSC_VER
+_inline void *
+#else
+static __inline__ void *
+#endif
+my_inline_memchr(const void *pv, int ch, register size_t cb)
+{
+ register const unsigned int uch = (unsigned)ch;
+ register const unsigned char *pb = (const unsigned char *)pv;
+#if 0 /* 8-byte loop unroll */
+ while (cb >= 8)
+ {
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ if (pb[4] == uch)
+ return (unsigned char *)pb + 4;
+ if (pb[5] == uch)
+ return (unsigned char *)pb + 5;
+ if (pb[6] == uch)
+ return (unsigned char *)pb + 6;
+ if (pb[7] == uch)
+ return (unsigned char *)pb + 7;
+ cb -= 8;
+ pb += 8;
+ }
+ switch (cb & 7)
+ {
+ case 0:
+ break;
+ case 1:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ break;
+ case 2:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ break;
+ case 3:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ break;
+ case 4:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ break;
+ case 5:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ if (pb[4] == uch)
+ return (unsigned char *)pb + 4;
+ break;
+ case 6:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ if (pb[4] == uch)
+ return (unsigned char *)pb + 4;
+ if (pb[5] == uch)
+ return (unsigned char *)pb + 5;
+ break;
+ case 7:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ if (pb[4] == uch)
+ return (unsigned char *)pb + 4;
+ if (pb[5] == uch)
+ return (unsigned char *)pb + 5;
+ if (pb[6] == uch)
+ return (unsigned char *)pb + 6;
+ break;
+ }
+
+#elif 1 /* 4 byte loop unroll */
+ while (cb >= 4)
+ {
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ if (pb[3] == uch)
+ return (unsigned char *)pb + 3;
+ cb -= 4;
+ pb += 4;
+ }
+ switch (cb & 3)
+ {
+ case 0:
+ break;
+ case 1:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ break;
+ case 2:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ break;
+ case 3:
+ if (*pb == uch)
+ return (unsigned char *)pb;
+ if (pb[1] == uch)
+ return (unsigned char *)pb + 1;
+ if (pb[2] == uch)
+ return (unsigned char *)pb + 2;
+ break;
+ }
+
+#else /* the basic loop */
+ while (cb > 0)
+ {
+ if (*pb == uch)
+ return (void *)pb;
+ cb--;
+ pb++;
+ }
+#endif
+ return 0;
+}
+
+#define memchr my_inline_memchr
+
diff --git a/src/kmk/job.c b/src/kmk/job.c
new file mode 100644
index 0000000..12d2ad3
--- /dev/null
+++ b/src/kmk/job.c
@@ -0,0 +1,3991 @@
+/* Job execution and handling for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "job.h"
+#include "debug.h"
+#include "filedef.h"
+#include "commands.h"
+#include "variable.h"
+#include "os.h"
+#ifdef CONFIG_WITH_KMK_BUILTIN
+# include "kmkbuiltin.h"
+#endif
+#ifdef KMK
+# include "kbuild.h"
+#endif
+
+
+#include <string.h>
+
+/* Default shell to use. */
+#ifdef WINDOWS32
+#include <windows.h>
+
+const char *default_shell = "sh.exe";
+int no_default_sh_exe = 1;
+int batch_mode_shell = 1;
+# ifndef CONFIG_NEW_WIN32_CTRL_EVENT
+HANDLE main_thread;
+# endif
+
+#elif defined (_AMIGA)
+
+const char *default_shell = "";
+extern int MyExecute (char **);
+int batch_mode_shell = 0;
+
+#elif defined (__MSDOS__)
+
+/* The default shell is a pointer so we can change it if Makefile
+ says so. It is without an explicit path so we get a chance
+ to search the $PATH for it (since MSDOS doesn't have standard
+ directories we could trust). */
+const char *default_shell = "command.com";
+int batch_mode_shell = 0;
+
+#elif defined (__EMX__)
+
+const char *default_shell = "sh.exe"; /* bird changed this from "/bin/sh" as that doesn't make sense on OS/2. */
+int batch_mode_shell = 0;
+
+#elif defined (VMS)
+
+# include <descrip.h>
+# include <stsdef.h>
+const char *default_shell = "";
+int batch_mode_shell = 0;
+
+#define strsignal vms_strsignal
+char * vms_strsignal (int status);
+
+#ifndef C_FACILITY_NO
+# define C_FACILITY_NO 0x350000
+#endif
+#ifndef VMS_POSIX_EXIT_MASK
+# define VMS_POSIX_EXIT_MASK (C_FACILITY_NO | 0xA000)
+#endif
+
+#else
+
+const char *default_shell = "/bin/sh";
+int batch_mode_shell = 0;
+
+#endif
+
+#ifdef __MSDOS__
+# include <process.h>
+static int execute_by_shell;
+static int dos_pid = 123;
+int dos_status;
+int dos_command_running;
+#endif /* __MSDOS__ */
+
+#ifdef _AMIGA
+# include <proto/dos.h>
+static int amiga_pid = 123;
+static int amiga_status;
+static char amiga_bname[32];
+static int amiga_batch_file;
+#endif /* Amiga. */
+
+#ifdef VMS
+# ifndef __GNUC__
+# include <processes.h>
+# endif
+# include <starlet.h>
+# include <lib$routines.h>
+static void vmsWaitForChildren (int *);
+#endif
+
+#ifdef WINDOWS32
+# include <windows.h>
+# include <io.h>
+# include <process.h>
+# ifdef CONFIG_NEW_WIN_CHILDREN
+# include "w32/winchildren.h"
+# else
+# include "sub_proc.h"
+# endif
+# include "w32err.h"
+# include "pathstuff.h"
+# define WAIT_NOHANG 1
+#endif /* WINDOWS32 */
+
+#ifdef __EMX__
+# include <process.h>
+#endif
+
+#if defined (HAVE_SYS_WAIT_H) || defined (HAVE_UNION_WAIT)
+# include <sys/wait.h>
+#endif
+
+#ifdef HAVE_WAITPID
+# define WAIT_NOHANG(status) waitpid (-1, (status), WNOHANG)
+#else /* Don't have waitpid. */
+# ifdef HAVE_WAIT3
+# ifndef wait3
+extern int wait3 ();
+# endif
+# define WAIT_NOHANG(status) wait3 ((status), WNOHANG, (struct rusage *) 0)
+# endif /* Have wait3. */
+#endif /* Have waitpid. */
+
+#if !defined (wait) && !defined (POSIX)
+int wait ();
+#endif
+
+#ifndef HAVE_UNION_WAIT
+
+# define WAIT_T int
+
+# ifndef WTERMSIG
+# define WTERMSIG(x) ((x) & 0x7f)
+# endif
+# ifndef WCOREDUMP
+# define WCOREDUMP(x) ((x) & 0x80)
+# endif
+# ifndef WEXITSTATUS
+# define WEXITSTATUS(x) (((x) >> 8) & 0xff)
+# endif
+# ifndef WIFSIGNALED
+# define WIFSIGNALED(x) (WTERMSIG (x) != 0)
+# endif
+# ifndef WIFEXITED
+# define WIFEXITED(x) (WTERMSIG (x) == 0)
+# endif
+
+#else /* Have 'union wait'. */
+
+# define WAIT_T union wait
+# ifndef WTERMSIG
+# define WTERMSIG(x) ((x).w_termsig)
+# endif
+# ifndef WCOREDUMP
+# define WCOREDUMP(x) ((x).w_coredump)
+# endif
+# ifndef WEXITSTATUS
+# define WEXITSTATUS(x) ((x).w_retcode)
+# endif
+# ifndef WIFSIGNALED
+# define WIFSIGNALED(x) (WTERMSIG(x) != 0)
+# endif
+# ifndef WIFEXITED
+# define WIFEXITED(x) (WTERMSIG(x) == 0)
+# endif
+
+#endif /* Don't have 'union wait'. */
+
+#if !defined(HAVE_UNISTD_H) && !defined(WINDOWS32)
+# ifndef _MSC_VER /* bird */
+int dup2 ();
+int execve ();
+void _exit ();
+# endif /* bird */
+# ifndef VMS
+int geteuid ();
+int getegid ();
+int setgid ();
+int getgid ();
+# endif
+#endif
+
+/* Different systems have different requirements for pid_t.
+ Plus we have to support gettext string translation... Argh. */
+static const char *
+pid2str (pid_t pid)
+{
+ static char pidstring[100];
+#if defined(WINDOWS32) && (__GNUC__ > 3 || _MSC_VER > 1300)
+ /* %Id is only needed for 64-builds, which were not supported by
+ older versions of Windows compilers. */
+ sprintf (pidstring, "%Id", pid);
+#else
+ sprintf (pidstring, "%lu", (unsigned long) pid);
+#endif
+ return pidstring;
+}
+
+#ifndef HAVE_GETLOADAVG
+int getloadavg (double loadavg[], int nelem);
+#endif
+
+static void free_child (struct child *);
+static void start_job_command (struct child *child);
+static int load_too_high (void);
+static int job_next_command (struct child *);
+static int start_waiting_job (struct child *);
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+static void print_job_time (struct child *);
+#endif
+
+/* Chain of all live (or recently deceased) children. */
+
+struct child *children = 0;
+
+/* Number of children currently running. */
+
+unsigned int job_slots_used = 0;
+
+/* Nonzero if the 'good' standard input is in use. */
+
+static int good_stdin_used = 0;
+
+/* Chain of children waiting to run until the load average goes down. */
+
+static struct child *waiting_jobs = 0;
+
+/* Non-zero if we use a *real* shell (always so on Unix). */
+
+int unixy_shell = 1;
+
+/* Number of jobs started in the current second. */
+
+unsigned long job_counter = 0;
+
+/* Number of jobserver tokens this instance is currently using. */
+
+unsigned int jobserver_tokens = 0;
+
+
+#ifdef WINDOWS32
+# ifndef CONFIG_NEW_WIN_CHILDREN /* (only used by commands.c) */
+/*
+ * The macro which references this function is defined in makeint.h.
+ */
+int
+w32_kill (pid_t pid, int sig)
+{
+ return ((process_kill ((HANDLE)pid, sig) == TRUE) ? 0 : -1);
+}
+# endif /* !CONFIG_NEW_WIN_CHILDREN */
+
+/* This function creates a temporary file name with an extension specified
+ * by the unixy arg.
+ * Return an xmalloc'ed string of a newly created temp file and its
+ * file descriptor, or die. */
+static char *
+create_batch_file (char const *base, int unixy, int *fd)
+{
+ const char *const ext = unixy ? "sh" : "bat";
+ const char *error_string = NULL;
+ char temp_path[MAXPATHLEN]; /* need to know its length */
+ unsigned path_size = GetTempPath (sizeof temp_path, temp_path);
+ int path_is_dot = 0;
+ /* The following variable is static so we won't try to reuse a name
+ that was generated a little while ago, because that file might
+ not be on disk yet, since we use FILE_ATTRIBUTE_TEMPORARY below,
+ which tells the OS it doesn't need to flush the cache to disk.
+ If the file is not yet on disk, we might think the name is
+ available, while it really isn't. This happens in parallel
+ builds, where Make doesn't wait for one job to finish before it
+ launches the next one. */
+ static unsigned uniq = 0;
+ static int second_loop = 0;
+ const unsigned sizemax = strlen (base) + strlen (ext) + 10;
+
+ if (path_size == 0)
+ {
+ path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
+ path_is_dot = 1;
+ }
+
+ ++uniq;
+ if (uniq >= 0x10000 && !second_loop)
+ {
+ /* If we already had 64K batch files in this
+ process, make a second loop through the numbers,
+ looking for free slots, i.e. files that were
+ deleted in the meantime. */
+ second_loop = 1;
+ uniq = 1;
+ }
+ while (path_size > 0 &&
+ path_size + sizemax < sizeof temp_path &&
+ !(uniq >= 0x10000 && second_loop))
+ {
+ unsigned size = sprintf (temp_path + path_size,
+ "%s%s-%x.%s",
+ temp_path[path_size - 1] == '\\' ? "" : "\\",
+ base, uniq, ext);
+ HANDLE h = CreateFile (temp_path, /* file name */
+ GENERIC_READ | GENERIC_WRITE, /* desired access */
+ 0, /* no share mode */
+ NULL, /* default security attributes */
+ CREATE_NEW, /* creation disposition */
+ FILE_ATTRIBUTE_NORMAL | /* flags and attributes */
+ FILE_ATTRIBUTE_TEMPORARY, /* we'll delete it */
+ NULL); /* no template file */
+
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ const DWORD er = GetLastError ();
+
+ if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS)
+ {
+ ++uniq;
+ if (uniq == 0x10000 && !second_loop)
+ {
+ second_loop = 1;
+ uniq = 1;
+ }
+ }
+
+ /* the temporary path is not guaranteed to exist */
+ else if (path_is_dot == 0)
+ {
+ path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
+ path_is_dot = 1;
+ }
+
+ else
+ {
+ error_string = map_windows32_error_to_string (er);
+ break;
+ }
+ }
+ else
+ {
+ const unsigned final_size = path_size + size + 1;
+ char *const path = xmalloc (final_size);
+ memcpy (path, temp_path, final_size);
+ *fd = _open_osfhandle ((intptr_t)h, 0);
+ if (unixy)
+ {
+ char *p;
+ int ch;
+ for (p = path; (ch = *p) != 0; ++p)
+ if (ch == '\\')
+ *p = '/';
+ }
+ return path; /* good return */
+ }
+ }
+
+ *fd = -1;
+ if (error_string == NULL)
+ error_string = _("Cannot create a temporary file\n");
+ O (fatal, NILF, error_string);
+
+ /* not reached */
+ return NULL;
+}
+#endif /* WINDOWS32 */
+
+#ifdef __EMX__
+/* returns whether path is assumed to be a unix like shell. */
+int
+_is_unixy_shell (const char *path)
+{
+ /* list of non unix shells */
+ const char *known_os2shells[] = {
+ "cmd.exe",
+ "cmd",
+ "4os2.exe",
+ "4os2",
+ "4dos.exe",
+ "4dos",
+ "command.com",
+ "command",
+ NULL
+ };
+
+ /* find the rightmost '/' or '\\' */
+ const char *name = strrchr (path, '/');
+ const char *p = strrchr (path, '\\');
+ unsigned i;
+
+ if (name && p) /* take the max */
+ name = (name > p) ? name : p;
+ else if (p) /* name must be 0 */
+ name = p;
+ else if (!name) /* name and p must be 0 */
+ name = path;
+
+ if (*name == '/' || *name == '\\') name++;
+
+ i = 0;
+ while (known_os2shells[i] != NULL)
+ {
+ if (strcasecmp (name, known_os2shells[i]) == 0)
+ return 0; /* not a unix shell */
+ i++;
+ }
+
+ /* in doubt assume a unix like shell */
+ return 1;
+}
+#endif /* __EMX__ */
+
+/* determines whether path looks to be a Bourne-like shell. */
+int
+is_bourne_compatible_shell (const char *path)
+{
+ /* List of known POSIX (or POSIX-ish) shells. */
+ static const char *unix_shells[] = {
+ "sh",
+ "bash",
+ "ksh",
+ "rksh",
+ "zsh",
+ "ash",
+ "dash",
+ NULL
+ };
+ const char **s;
+
+ /* find the rightmost '/' or '\\' */
+ const char *name = strrchr (path, '/');
+ char *p = strrchr (path, '\\');
+
+ if (name && p) /* take the max */
+ name = (name > p) ? name : p;
+ else if (p) /* name must be 0 */
+ name = p;
+ else if (!name) /* name and p must be 0 */
+ name = path;
+
+ if (*name == '/' || *name == '\\')
+ ++name;
+
+ /* this should be able to deal with extensions on Windows-like systems */
+ for (s = unix_shells; *s != NULL; ++s)
+ {
+#if defined(WINDOWS32) || defined(__MSDOS__)
+ unsigned int len = strlen (*s);
+ if ((strlen (name) >= len && STOP_SET (name[len], MAP_DOT|MAP_NUL))
+ && strncasecmp (name, *s, len) == 0)
+#else
+ if (strcmp (name, *s) == 0)
+#endif
+ return 1; /* a known unix-style shell */
+ }
+
+ /* if not on the list, assume it's not a Bourne-like shell */
+ return 0;
+}
+
+
+/* Write an error message describing the exit status given in
+ EXIT_CODE, EXIT_SIG, and COREDUMP, for the target TARGET_NAME.
+ Append "(ignored)" if IGNORED is nonzero. */
+
+static void
+child_error (struct child *child,
+ int exit_code, int exit_sig, int coredump, int ignored)
+{
+ const char *pre = "*** ";
+ const char *post = "";
+ const char *dump = "";
+ const struct file *f = child->file;
+ const floc *flocp = &f->cmds->fileinfo;
+ const char *nm;
+ size_t l;
+
+ if (ignored && silent_flag)
+ return;
+
+ if (exit_sig && coredump)
+ dump = _(" (core dumped)");
+
+ if (ignored)
+ {
+ pre = "";
+ post = _(" (ignored)");
+ }
+
+ if (! flocp->filenm)
+ nm = _("<builtin>");
+ else
+ {
+ char *a = alloca (strlen (flocp->filenm) + 1 + 11 + 1);
+ sprintf (a, "%s:%lu", flocp->filenm, flocp->lineno + flocp->offset);
+ nm = a;
+ }
+
+ l = strlen (pre) + strlen (nm) + strlen (f->name) + strlen (post);
+
+ OUTPUT_SET (&child->output);
+
+ show_goal_error ();
+
+ if (exit_sig == 0)
+# if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ {
+ const char *exit_name = NULL;
+ switch ((unsigned)exit_code)
+ {
+ case 0xc0000005U: exit_name = "STATUS_ACCESS_VIOLATION"; break;
+ case 0xc000013aU: exit_name = "STATUS_CONTROL_C_EXIT"; break;
+ case 0xc0000374U: exit_name = "STATUS_HEAP_CORRUPTION"; break;
+ case 0xc0000409U: exit_name = "STATUS_STACK_BUFFER_OVERRUN"; break;
+ case 0xc0000417U: exit_name = "STATUS_INVALID_CRUNTIME_PARAMETER"; break;
+ case 0x80000003U: exit_name = "STATUS_BREAKPOINT"; break;
+ case 0x40000015U: exit_name = "STATUS_FATAL_APP_EXIT"; break;
+ case 0x40010004U: exit_name = "DBG_TERMINATE_PROCESS"; break;
+ case 0x40010005U: exit_name = "DBG_CONTROL_C"; break;
+ case 0x40010008U: exit_name = "DBG_CONTROL_BREAK"; break;
+ }
+ if (exit_name)
+ error (NILF, l + strlen (exit_name) + INTSTR_LENGTH,
+ _("%s[%s: %s] Error %d (%s)%s"),
+ pre, nm, f->name, exit_code, exit_name, post);
+ else
+ error (NILF, l + INTSTR_LENGTH + INTSTR_LENGTH,
+ _("%s[%s: %s] Error %d (%#x)%s"),
+ pre, nm, f->name, exit_code, exit_code, post);
+ }
+# else
+ error (NILF, l + INTSTR_LENGTH,
+ _("%s[%s: %s] Error %d%s"), pre, nm, f->name, exit_code, post);
+# endif
+ else
+ {
+ const char *s = strsignal (exit_sig);
+ error (NILF, l + strlen (s) + strlen (dump),
+ "%s[%s: %s] %s%s%s", pre, nm, f->name, s, dump, post);
+ }
+
+ OUTPUT_UNSET ();
+}
+
+
+/* Handle a dead child. This handler may or may not ever be installed.
+
+ If we're using the jobserver feature without pselect(), we need it.
+ First, installing it ensures the read will interrupt on SIGCHLD. Second,
+ we close the dup'd read FD to ensure we don't enter another blocking read
+ without reaping all the dead children. In this case we don't need the
+ dead_children count.
+
+ If we don't have either waitpid or wait3, then make is unreliable, but we
+ use the dead_children count to reap children as best we can. */
+
+static unsigned int dead_children = 0;
+
+RETSIGTYPE
+child_handler (int sig UNUSED)
+{
+ ++dead_children;
+
+ jobserver_signal ();
+
+#if defined __EMX__ && !defined(__INNOTEK_LIBC__) /* bird */
+ /* The signal handler must called only once! */
+ signal (SIGCHLD, SIG_DFL);
+#endif
+}
+
+extern pid_t shell_function_pid;
+
+/* Reap all dead children, storing the returned status and the new command
+ state ('cs_finished') in the 'file' member of the 'struct child' for the
+ dead child, and removing the child from the chain. In addition, if BLOCK
+ nonzero, we block in this function until we've reaped at least one
+ complete child, waiting for it to die if necessary. If ERR is nonzero,
+ print an error message first. */
+
+void
+reap_children (int block, int err)
+{
+#ifndef WINDOWS32
+ WAIT_T status;
+#endif
+ /* Initially, assume we have some. */
+ int reap_more = 1;
+
+#ifdef WAIT_NOHANG
+# define REAP_MORE reap_more
+#else
+# define REAP_MORE dead_children
+#endif
+
+ /* As long as:
+
+ We have at least one child outstanding OR a shell function in progress,
+ AND
+ We're blocking for a complete child OR there are more children to reap
+
+ we'll keep reaping children. */
+
+ while ((children != 0 || shell_function_pid != 0)
+ && (block || REAP_MORE))
+ {
+ unsigned int remote = 0;
+ pid_t pid;
+ int exit_code, exit_sig, coredump;
+ struct child *lastc, *c;
+ int child_failed;
+ int any_remote, any_local;
+ int dontcare;
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ struct child *completed_child = NULL;
+#endif
+
+ if (err && block)
+ {
+ static int printed = 0;
+
+ /* We might block for a while, so let the user know why.
+ Only print this message once no matter how many jobs are left. */
+ fflush (stdout);
+ if (!printed)
+ O (error, NILF, _("*** Waiting for unfinished jobs...."));
+ printed = 1;
+ }
+
+ /* We have one less dead child to reap. As noted in
+ child_handler() above, this count is completely unimportant for
+ all modern, POSIX-y systems that support wait3() or waitpid().
+ The rest of this comment below applies only to early, broken
+ pre-POSIX systems. We keep the count only because... it's there...
+
+ The test and decrement are not atomic; if it is compiled into:
+ register = dead_children - 1;
+ dead_children = register;
+ a SIGCHLD could come between the two instructions.
+ child_handler increments dead_children.
+ The second instruction here would lose that increment. But the
+ only effect of dead_children being wrong is that we might wait
+ longer than necessary to reap a child, and lose some parallelism;
+ and we might print the "Waiting for unfinished jobs" message above
+ when not necessary. */
+
+ if (dead_children > 0)
+ --dead_children;
+
+ any_remote = 0;
+ any_local = shell_function_pid != 0;
+ for (c = children; c != 0; c = c->next)
+ {
+ any_remote |= c->remote;
+ any_local |= ! c->remote;
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ if (c->has_status)
+ {
+ completed_child = c;
+ DB (DB_JOBS, (_("builtin child %p (%s) PID %s %s Status %ld\n"),
+ (void *)c, c->file->name,
+ pid2str (c->pid), c->remote ? _(" (remote)") : "",
+ (long) c->status));
+ }
+ else
+#endif
+ DB (DB_JOBS, (_("Live child %p (%s) PID %s %s\n"),
+ (void *)c, c->file->name, pid2str (c->pid),
+ c->remote ? _(" (remote)") : ""));
+#ifdef VMS
+ break;
+#endif
+ }
+
+ /* First, check for remote children. */
+ if (any_remote)
+ pid = remote_status (&exit_code, &exit_sig, &coredump, 0);
+ else
+ pid = 0;
+
+ if (pid > 0)
+ /* We got a remote child. */
+ remote = 1;
+ else if (pid < 0)
+ {
+ /* A remote status command failed miserably. Punt. */
+ remote_status_lose:
+ pfatal_with_name ("remote_status");
+ }
+ else
+ {
+ /* No remote children. Check for local children. */
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ if (completed_child)
+ {
+ pid = completed_child->pid;
+# if defined(WINDOWS32)
+ exit_code = completed_child->status;
+ exit_sig = 0;
+ coredump = 0;
+# else
+ status = (WAIT_T)completed_child->status;
+# endif
+ }
+ else
+#endif /* CONFIG_WITH_KMK_BUILTIN */
+#if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32)
+ if (any_local)
+ {
+#ifdef VMS
+ /* Todo: This needs more untangling multi-process support */
+ /* Just do single child process support now */
+ vmsWaitForChildren (&status);
+ pid = c->pid;
+
+ /* VMS failure status can not be fully translated */
+ status = $VMS_STATUS_SUCCESS (c->cstatus) ? 0 : (1 << 8);
+
+ /* A Posix failure can be exactly translated */
+ if ((c->cstatus & VMS_POSIX_EXIT_MASK) == VMS_POSIX_EXIT_MASK)
+ status = (c->cstatus >> 3 & 255) << 8;
+#else
+#ifdef WAIT_NOHANG
+ if (!block)
+ pid = WAIT_NOHANG (&status);
+ else
+#endif
+ EINTRLOOP (pid, wait (&status));
+#endif /* !VMS */
+ }
+ else
+ pid = 0;
+
+ if (pid < 0)
+ {
+ /* The wait*() failed miserably. Punt. */
+ pfatal_with_name ("wait");
+ }
+ else if (pid > 0)
+ {
+ /* We got a child exit; chop the status word up. */
+ exit_code = WEXITSTATUS (status);
+ exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
+ coredump = WCOREDUMP (status);
+
+ /* If we have started jobs in this second, remove one. */
+ if (job_counter)
+ --job_counter;
+ }
+ else
+ {
+ /* No local children are dead. */
+ reap_more = 0;
+
+ if (!block || !any_remote)
+ break;
+
+ /* Now try a blocking wait for a remote child. */
+ pid = remote_status (&exit_code, &exit_sig, &coredump, 1);
+ if (pid < 0)
+ goto remote_status_lose;
+ else if (pid == 0)
+ /* No remote children either. Finally give up. */
+ break;
+
+ /* We got a remote child. */
+ remote = 1;
+ }
+#endif /* !__MSDOS__, !Amiga, !WINDOWS32. */
+
+#ifdef __MSDOS__
+ /* Life is very different on MSDOS. */
+ pid = dos_pid - 1;
+ status = dos_status;
+ exit_code = WEXITSTATUS (status);
+ if (exit_code == 0xff)
+ exit_code = -1;
+ exit_sig = WIFSIGNALED (status) ? WTERMSIG (status) : 0;
+ coredump = 0;
+#endif /* __MSDOS__ */
+#ifdef _AMIGA
+ /* Same on Amiga */
+ pid = amiga_pid - 1;
+ status = amiga_status;
+ exit_code = amiga_status;
+ exit_sig = 0;
+ coredump = 0;
+#endif /* _AMIGA */
+#ifdef WINDOWS32
+ {
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ HANDLE hPID;
+ HANDLE hcTID, hcPID;
+ DWORD dwWaitStatus = 0;
+ exit_code = 0;
+ exit_sig = 0;
+ coredump = 0;
+
+# ifndef CONFIG_NEW_WIN32_CTRL_EVENT
+ /* Record the thread ID of the main process, so that we
+ could suspend it in the signal handler. */
+ if (!main_thread)
+ {
+ hcTID = GetCurrentThread ();
+ hcPID = GetCurrentProcess ();
+ if (!DuplicateHandle (hcPID, hcTID, hcPID, &main_thread, 0,
+ FALSE, DUPLICATE_SAME_ACCESS))
+ {
+ DWORD e = GetLastError ();
+ fprintf (stderr,
+ "Determine main thread ID (Error %ld: %s)\n",
+ e, map_windows32_error_to_string (e));
+ }
+ else
+ DB (DB_VERBOSE, ("Main thread handle = %p\n", main_thread));
+ }
+# endif
+
+ /* wait for anything to finish */
+ hPID = process_wait_for_any (block, &dwWaitStatus);
+ if (hPID)
+ {
+ /* was an error found on this process? */
+ int werr = process_last_err (hPID);
+
+ /* get exit data */
+ exit_code = process_exit_code (hPID);
+
+ if (werr)
+ fprintf (stderr, "make (e=%d): %s", exit_code,
+ map_windows32_error_to_string (exit_code));
+
+ /* signal */
+ exit_sig = process_signal (hPID);
+
+ /* cleanup process */
+ process_cleanup (hPID);
+
+ coredump = 0;
+ }
+ else if (dwWaitStatus == WAIT_FAILED)
+ {
+ /* The WaitForMultipleObjects() failed miserably. Punt. */
+ pfatal_with_name ("WaitForMultipleObjects");
+ }
+ else if (dwWaitStatus == WAIT_TIMEOUT)
+ {
+ /* No child processes are finished. Give up waiting. */
+ reap_more = 0;
+ break;
+ }
+
+ pid = (pid_t) hPID;
+# else /* CONFIG_NEW_WIN_CHILDREN */
+# ifndef CONFIG_NEW_WIN32_CTRL_EVENT
+ /* Ctrl-C handler needs to suspend the main thread handle to
+ prevent mayhem when concurrently calling reap_children. */
+ if ( !main_thread
+ && !DuplicateHandle (GetCurrentProcess (), GetCurrentThread (),
+ GetCurrentProcess (), &main_thread, 0,
+ FALSE, DUPLICATE_SAME_ACCESS))
+ fprintf (stderr, "Failed to duplicate main thread handle: %u\n",
+ GetLastError ());
+# endif
+
+ assert (!any_remote);
+ pid = 0;
+ coredump = exit_sig = exit_code = 0;
+ {
+ int rc = MkWinChildWait(block, &pid, &exit_code, &exit_sig, &coredump, &c);
+ if (rc != 0)
+ ON (fatal, NILF, _("MkWinChildWait: %u"), rc);
+ }
+ if (pid == 0)
+ {
+ /* No more children, stop. */
+ reap_more = 0;
+ break;
+ }
+
+ /* If we have started jobs in this second, remove one. */
+ if (job_counter)
+ --job_counter;
+# endif /* CONFIG_NEW_WIN_CHILDREN */
+ }
+#endif /* WINDOWS32 */
+ }
+
+ /* Check if this is the child of the 'shell' function. */
+ if (!remote && pid == shell_function_pid)
+ {
+ shell_completed (exit_code, exit_sig);
+ break;
+ }
+
+ /* Search for a child matching the deceased one. */
+ lastc = 0;
+ for (c = children; c != 0; lastc = c, c = c->next)
+ if (c->pid == pid && c->remote == remote)
+ break;
+
+ if (c == 0)
+ /* An unknown child died.
+ Ignore it; it was inherited from our invoker. */
+ continue;
+
+ /* Determine the failure status: 0 for success, 1 for updating target in
+ question mode, 2 for anything else. */
+ if (exit_sig == 0 && exit_code == 0)
+ child_failed = MAKE_SUCCESS;
+ else if (exit_sig == 0 && exit_code == 1 && question_flag && c->recursive)
+ child_failed = MAKE_TROUBLE;
+ else
+ child_failed = MAKE_FAILURE;
+
+ DB (DB_JOBS, (child_failed
+ ? _("Reaping losing child %p PID %s %s\n")
+ : _("Reaping winning child %p PID %s %s\n"),
+ (void *)c, pid2str (c->pid), c->remote ? _(" (remote)") : ""));
+
+ if (c->sh_batch_file)
+ {
+ int rm_status;
+
+ DB (DB_JOBS, (_("Cleaning up temp batch file %s\n"),
+ c->sh_batch_file));
+
+ errno = 0;
+ rm_status = remove (c->sh_batch_file);
+ if (rm_status)
+ DB (DB_JOBS, (_("Cleaning up temp batch file %s failed (%d)\n"),
+ c->sh_batch_file, errno));
+
+ /* all done with memory */
+ free (c->sh_batch_file);
+ c->sh_batch_file = NULL;
+ }
+
+ /* If this child had the good stdin, say it is now free. */
+ if (c->good_stdin)
+ good_stdin_used = 0;
+
+ dontcare = c->dontcare;
+
+ if (child_failed && !c->noerror && !ignore_errors_flag)
+ {
+ /* The commands failed. Write an error message,
+ delete non-precious targets, and abort. */
+ static int delete_on_error = -1;
+
+ if (!dontcare && child_failed == MAKE_FAILURE)
+#ifdef KMK
+ {
+ child_error (c, exit_code, exit_sig, coredump, 0);
+ if ( ( c->file->cmds->lines_flags[c->command_line - 1]
+ & (COMMANDS_SILENT | COMMANDS_RECURSE))
+ == COMMANDS_SILENT
+# ifdef KBUILD_OS_WINDOWS /* show commands for NT statuses */
+ || (exit_code & 0xc0000000)
+# endif
+ || exit_sig != 0)
+ OS (message, 0, "The failing command:\n%s", c->file->cmds->command_lines[c->command_line - 1]);
+ }
+#else /* !KMK */
+ child_error (c, exit_code, exit_sig, coredump, 0);
+#endif /* !KMK */
+
+ c->file->update_status = child_failed == MAKE_FAILURE ? us_failed : us_question;
+ if (delete_on_error == -1)
+ {
+ struct file *f = lookup_file (".DELETE_ON_ERROR");
+ delete_on_error = f != 0 && f->is_target;
+ }
+ if (exit_sig != 0 || delete_on_error)
+ delete_child_targets (c);
+ }
+ else
+ {
+ if (child_failed)
+ {
+ /* The commands failed, but we don't care. */
+ child_error (c, exit_code, exit_sig, coredump, 1);
+ child_failed = 0;
+ }
+
+ /* If there are more commands to run, try to start them. */
+ if (job_next_command (c))
+ {
+ if (handling_fatal_signal)
+ {
+ /* Never start new commands while we are dying.
+ Since there are more commands that wanted to be run,
+ the target was not completely remade. So we treat
+ this as if a command had failed. */
+ c->file->update_status = us_failed;
+ }
+ else
+ {
+#ifndef NO_OUTPUT_SYNC
+ /* If we're sync'ing per line, write the previous line's
+ output before starting the next one. */
+ if (output_sync == OUTPUT_SYNC_LINE)
+ output_dump (&c->output);
+#endif
+ /* Check again whether to start remotely.
+ Whether or not we want to changes over time.
+ Also, start_remote_job may need state set up
+ by start_remote_job_p. */
+ c->remote = start_remote_job_p (0);
+ start_job_command (c);
+ /* Fatal signals are left blocked in case we were
+ about to put that child on the chain. But it is
+ already there, so it is safe for a fatal signal to
+ arrive now; it will clean up this child's targets. */
+ unblock_sigs ();
+ if (c->file->command_state == cs_running)
+ /* We successfully started the new command.
+ Loop to reap more children. */
+ continue;
+ }
+
+ if (c->file->update_status != us_success)
+ /* We failed to start the commands. */
+ delete_child_targets (c);
+ }
+ else
+ /* There are no more commands. We got through them all
+ without an unignored error. Now the target has been
+ successfully updated. */
+ c->file->update_status = us_success;
+ }
+
+ /* When we get here, all the commands for c->file are finished. */
+
+#ifndef NO_OUTPUT_SYNC
+ /* Synchronize any remaining parallel output. */
+# ifdef KMK
+ c->output.dont_truncate = !err && child_failed && !dontcare && !keep_going_flag && !handling_fatal_signal;
+# endif
+ output_dump (&c->output);
+#endif
+
+ /* At this point c->file->update_status is success or failed. But
+ c->file->command_state is still cs_running if all the commands
+ ran; notice_finish_file looks for cs_running to tell it that
+ it's interesting to check the file's modtime again now. */
+
+ if (! handling_fatal_signal)
+ /* Notice if the target of the commands has been changed.
+ This also propagates its values for command_state and
+ update_status to its also_make files. */
+ notice_finished_file (c->file);
+
+ DB (DB_JOBS, (_("Removing child %p PID %s%s from chain.\n"),
+ (void *)c, pid2str (c->pid), c->remote ? _(" (remote)") : ""));
+
+ /* Block fatal signals while frobnicating the list, so that
+ children and job_slots_used are always consistent. Otherwise
+ a fatal signal arriving after the child is off the chain and
+ before job_slots_used is decremented would believe a child was
+ live and call reap_children again. */
+ block_sigs ();
+
+ /* There is now another slot open. */
+ if (job_slots_used > 0)
+ --job_slots_used;
+
+ /* Remove the child from the chain and free it. */
+ if (lastc == 0)
+ children = c->next;
+ else
+ lastc->next = c->next;
+
+#ifdef KMK /* Repeat the error */
+ /* If the job failed, and the -k flag was not given, die,
+ unless we are already in the process of dying. */
+ if (!err && child_failed && !dontcare && !keep_going_flag &&
+ /* fatal_error_signal will die with the right signal. */
+ !handling_fatal_signal)
+ {
+ unblock_sigs ();
+ die_with_job_output (child_failed, &c->output);
+ }
+#endif
+
+ free_child (c);
+
+ unblock_sigs ();
+
+#ifndef KMK /* See above. */
+ /* If the job failed, and the -k flag was not given, die,
+ unless we are already in the process of dying. */
+ if (!err && child_failed && !dontcare && !keep_going_flag &&
+ /* fatal_error_signal will die with the right signal. */
+ !handling_fatal_signal)
+ die (child_failed);
+#endif
+
+ /* Only block for one child. */
+ block = 0;
+ }
+
+ return;
+}
+
+/* Free the storage allocated for CHILD. */
+
+static void
+free_child (struct child *child)
+{
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ print_job_time (child);
+#endif
+ output_close (&child->output);
+
+ /* bird: Make sure the output_context doesn't point to a freed structure when
+ we return from this function. This is probably an issue elsewhere
+ in the code, however it doesn't cost us much fixing it here. (The
+ access after free was caught in a die() scenario, both in error
+ situations and successful ones.) */
+ if (output_context == &child->output)
+ OUTPUT_UNSET();
+
+ if (!jobserver_tokens)
+ ONS (fatal, NILF, "INTERNAL: Freeing child %p (%s) but no tokens left!\n",
+ (void *)child, child->file->name);
+
+ /* If we're using the jobserver and this child is not the only outstanding
+ job, put a token back into the pipe for it. */
+
+ if (jobserver_enabled () && jobserver_tokens > 1)
+ {
+ jobserver_release (1);
+ DB (DB_JOBS, (_("Released token for child %p (%s).\n"),
+ (void *)child, child->file->name));
+ }
+
+ --jobserver_tokens;
+
+ if (handling_fatal_signal) /* Don't bother free'ing if about to die. */
+ return;
+
+ if (child->command_lines != 0)
+ {
+ register unsigned int i;
+ for (i = 0; i < child->file->cmds->ncommand_lines; ++i)
+ free (child->command_lines[i]);
+ free (child->command_lines);
+ }
+
+ if (child->environment != 0)
+ {
+ register char **ep = child->environment;
+ while (*ep != 0)
+ free (*ep++);
+ free (child->environment);
+ }
+
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ /* Free the chopped command lines for simple targets when
+ there are no more active references to them. */
+
+ child->file->cmds->refs--;
+ if ( !child->file->intermediate
+ && !child->file->pat_variables)
+ free_chopped_commands(child->file->cmds);
+#endif /* CONFIG_WITH_MEMORY_OPTIMIZATIONS */
+
+ free (child);
+}
+
+#ifdef POSIX
+extern sigset_t fatal_signal_set;
+#endif
+
+void
+block_sigs (void)
+{
+#ifdef POSIX
+ (void) sigprocmask (SIG_BLOCK, &fatal_signal_set, (sigset_t *) 0);
+#else
+# ifdef HAVE_SIGSETMASK
+ (void) sigblock (fatal_signal_mask);
+# endif
+#endif
+}
+
+#ifdef POSIX
+void
+unblock_sigs (void)
+{
+ sigset_t empty;
+ sigemptyset (&empty);
+ sigprocmask (SIG_SETMASK, &empty, (sigset_t *) 0);
+}
+#endif
+
+/* Start a job to run the commands specified in CHILD.
+ CHILD is updated to reflect the commands and ID of the child process.
+
+ NOTE: On return fatal signals are blocked! The caller is responsible
+ for calling 'unblock_sigs', once the new child is safely on the chain so
+ it can be cleaned up in the event of a fatal signal. */
+
+static void
+start_job_command (struct child *child)
+{
+ int flags;
+ char *p;
+#ifdef VMS
+ char *argv;
+#else
+ char **argv;
+#endif
+
+ /* If we have a completely empty commandset, stop now. */
+ if (!child->command_ptr)
+ goto next_command;
+
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ if (child->start_ts == -1)
+ child->start_ts = nano_timestamp ();
+#endif
+
+ /* Combine the flags parsed for the line itself with
+ the flags specified globally for this target. */
+ flags = (child->file->command_flags
+ | child->file->cmds->lines_flags[child->command_line - 1]);
+
+ p = child->command_ptr;
+ child->noerror = ((flags & COMMANDS_NOERROR) != 0);
+
+ while (*p != '\0')
+ {
+ if (*p == '@')
+ flags |= COMMANDS_SILENT;
+ else if (*p == '+')
+ flags |= COMMANDS_RECURSE;
+ else if (*p == '-')
+ child->noerror = 1;
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+ else if (*p == '%')
+ flags |= COMMAND_GETTER_SKIP_IT;
+#endif
+ /* Don't skip newlines. */
+ else if (!ISBLANK (*p))
+#ifndef CONFIG_WITH_KMK_BUILTIN
+ break;
+#else /* CONFIG_WITH_KMK_BUILTIN */
+
+ {
+ if ( !(flags & COMMANDS_KMK_BUILTIN)
+ && !strncmp(p, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
+ flags |= COMMANDS_KMK_BUILTIN;
+ break;
+ }
+#endif /* CONFIG_WITH_KMK_BUILTIN */
+ ++p;
+ }
+
+ child->recursive = ((flags & COMMANDS_RECURSE) != 0);
+
+ /* Update the file's command flags with any new ones we found. We only
+ keep the COMMANDS_RECURSE setting. Even this isn't 100% correct; we are
+ now marking more commands recursive than should be in the case of
+ multiline define/endef scripts where only one line is marked "+". In
+ order to really fix this, we'll have to keep a lines_flags for every
+ actual line, after expansion. */
+ child->file->cmds->lines_flags[child->command_line - 1] |= flags & COMMANDS_RECURSE;
+
+ /* POSIX requires that a recipe prefix after a backslash-newline should
+ be ignored. Remove it now so the output is correct. */
+ {
+ char prefix = child->file->cmds->recipe_prefix;
+ char *p1, *p2;
+ p1 = p2 = p;
+ while (*p1 != '\0')
+ {
+ *(p2++) = *p1;
+ if (p1[0] == '\n' && p1[1] == prefix)
+ ++p1;
+ ++p1;
+ }
+ *p2 = *p1;
+ }
+
+ /* Figure out an argument list from this command line. */
+ {
+ char *end = 0;
+#ifdef VMS
+ /* Skip any leading whitespace */
+ while (*p)
+ {
+ if (!ISSPACE (*p))
+ {
+ if (*p != '\\')
+ break;
+ if ((p[1] != '\n') && (p[1] != 'n') && (p[1] != 't'))
+ break;
+ }
+ p++;
+ }
+
+ argv = p;
+ /* Although construct_command_argv contains some code for VMS, it was/is
+ not called/used. Please note, for VMS argv is a string (not an array
+ of strings) which contains the complete command line, which for
+ multi-line variables still includes the newlines. So detect newlines
+ and set 'end' (which is used for child->command_ptr) instead of
+ (re-)writing construct_command_argv */
+ if (!one_shell)
+ {
+ char *s = p;
+ int instring = 0;
+ while (*s)
+ {
+ if (*s == '"')
+ instring = !instring;
+ else if (*s == '\\' && !instring && *(s+1) != 0)
+ s++;
+ else if (*s == '\n' && !instring)
+ {
+ end = s;
+ break;
+ }
+ ++s;
+ }
+ }
+#else
+ argv = construct_command_argv (p, &end, child->file,
+ child->file->cmds->lines_flags[child->command_line - 1],
+ &child->sh_batch_file);
+#endif
+ if (end == NULL)
+ child->command_ptr = NULL;
+ else
+ {
+ *end++ = '\0';
+ child->command_ptr = end;
+ }
+ }
+
+ /* If -q was given, say that updating 'failed' if there was any text on the
+ command line, or 'succeeded' otherwise. The exit status of 1 tells the
+ user that -q is saying 'something to do'; the exit status for a random
+ error is 2. */
+ if (argv != 0 && question_flag && !(flags & COMMANDS_RECURSE))
+ {
+#ifndef VMS
+ free (argv[0]);
+ free (argv);
+#endif
+#ifdef VMS
+ /* On VMS, argv[0] can be a null string here */
+ if (argv[0] != 0)
+ {
+#endif
+ child->file->update_status = us_question;
+ notice_finished_file (child->file);
+ return;
+#ifdef VMS
+ }
+#endif
+ }
+
+ if (touch_flag && !(flags & COMMANDS_RECURSE))
+ {
+ /* Go on to the next command. It might be the recursive one.
+ We construct ARGV only to find the end of the command line. */
+#ifndef VMS
+ if (argv)
+ {
+ free (argv[0]);
+ free (argv);
+ }
+#endif
+ argv = 0;
+ }
+
+ if (argv == 0)
+ {
+ next_command:
+#ifdef __MSDOS__
+ execute_by_shell = 0; /* in case construct_command_argv sets it */
+#endif
+ /* This line has no commands. Go to the next. */
+ if (job_next_command (child))
+ start_job_command (child);
+ else
+ {
+ /* No more commands. Make sure we're "running"; we might not be if
+ (e.g.) all commands were skipped due to -n. */
+ set_command_state (child->file, cs_running);
+ child->file->update_status = us_success;
+ notice_finished_file (child->file);
+ }
+
+ OUTPUT_UNSET();
+ return;
+ }
+
+ /* Are we going to synchronize this command's output? Do so if either we're
+ in SYNC_RECURSE mode or this command is not recursive. We'll also check
+ output_sync separately below in case it changes due to error. */
+ child->output.syncout = output_sync && (output_sync == OUTPUT_SYNC_RECURSE
+ || !(flags & COMMANDS_RECURSE));
+ OUTPUT_SET (&child->output);
+
+#ifndef NO_OUTPUT_SYNC
+ if (! child->output.syncout)
+ /* We don't want to sync this command: to avoid misordered
+ output ensure any already-synced content is written. */
+ output_dump (&child->output);
+#endif
+
+ /* Print the command if appropriate. */
+#ifdef CONFIG_PRETTY_COMMAND_PRINTING
+ if ( pretty_command_printing
+ && (just_print_flag || (!(flags & COMMANDS_SILENT) && !silent_flag))
+ && argv[0][0] != '\0')
+ {
+ unsigned i;
+ for (i = 0; argv[i]; i++)
+ OSSS ( message, 0, "%s'%s'%s", i ? "\t" : "> ", argv[i], argv[i + 1] ? " \\" : "");
+ }
+ else
+#endif /* CONFIG_PRETTY_COMMAND_PRINTING */
+ if (just_print_flag || trace_flag
+ || (!(flags & COMMANDS_SILENT) && !silent_flag))
+ OS (message, 0, "%s", p);
+
+ /* Tell update_goal_chain that a command has been started on behalf of
+ this target. It is important that this happens here and not in
+ reap_children (where we used to do it), because reap_children might be
+ reaping children from a different target. We want this increment to
+ guaranteedly indicate that a command was started for the dependency
+ chain (i.e., update_file recursion chain) we are processing. */
+
+ ++commands_started;
+
+ /* Optimize an empty command. People use this for timestamp rules,
+ so avoid forking a useless shell. Do this after we increment
+ commands_started so make still treats this special case as if it
+ performed some action (makes a difference as to what messages are
+ printed, etc. */
+
+#if !defined(VMS) && !defined(_AMIGA)
+ if (
+#if defined __MSDOS__ || defined (__EMX__)
+ unixy_shell /* the test is complicated and we already did it */
+#else
+ (argv[0] && is_bourne_compatible_shell (argv[0]))
+#endif
+ && (argv[1] && argv[1][0] == '-'
+ &&
+ ((argv[1][1] == 'c' && argv[1][2] == '\0')
+ ||
+ (argv[1][1] == 'e' && argv[1][2] == 'c' && argv[1][3] == '\0')))
+ && (argv[2] && argv[2][0] == ':' && argv[2][1] == '\0')
+ && argv[3] == NULL)
+ {
+ free (argv[0]);
+ free (argv);
+ goto next_command;
+ }
+#endif /* !VMS && !_AMIGA */
+
+ /* If -n was given, recurse to get the next line in the sequence. */
+
+ if (just_print_flag && !(flags & COMMANDS_RECURSE))
+ {
+#ifndef VMS
+ free (argv[0]);
+ free (argv);
+#endif
+ goto next_command;
+ }
+
+ /* We're sure we're going to invoke a command: set up the output. */
+ output_start ();
+
+ /* Flush the output streams so they won't have things written twice. */
+
+ fflush (stdout);
+ fflush (stderr);
+
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ /* If builtin command then pass it on to the builtin shell interpreter. */
+
+ if ((flags & COMMANDS_KMK_BUILTIN) && !just_print_flag)
+ {
+ int rc;
+ char **argv_spawn = NULL;
+ char **p2 = argv;
+ while (*p2 && strncmp (*p2, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
+ p2++;
+ assert (*p2);
+ set_command_state (child->file, cs_running);
+ child->deleted = 0;
+ child->pid = 0;
+ if (p2 != argv)
+ rc = kmk_builtin_command (*p2, child, &argv_spawn, &child->pid);
+ else
+ {
+ int argc = 1;
+ while (argv[argc])
+ argc++;
+ rc = kmk_builtin_command_parsed (argc, argv, child, &argv_spawn, &child->pid);
+ }
+
+# ifndef VMS
+ free (argv[0]);
+ free ((char *) argv);
+# endif
+
+ if (!rc)
+ {
+ /* spawned a child? */
+ if (child->pid)
+ {
+ ++job_counter;
+ return;
+ }
+
+ /* synchronous command execution? */
+ if (!argv_spawn)
+ goto next_command;
+ }
+
+ /* failure? */
+ if (rc)
+ {
+ child->pid = (pid_t)42424242;
+ child->status = rc << 8;
+ child->has_status = 1;
+ unblock_sigs();
+ return;
+ }
+
+ /* conditional check == true; kicking off a child (not kmk_builtin_*). */
+ argv = argv_spawn;
+ }
+#endif /* CONFIG_WITH_KMK_BUILTIN */
+
+ /* Decide whether to give this child the 'good' standard input
+ (one that points to the terminal or whatever), or the 'bad' one
+ that points to the read side of a broken pipe. */
+
+ child->good_stdin = !good_stdin_used;
+ if (child->good_stdin)
+ good_stdin_used = 1;
+
+ child->deleted = 0;
+
+#ifndef _AMIGA
+ /* Set up the environment for the child. */
+ if (child->environment == 0)
+ child->environment = target_environment (child->file);
+#endif
+
+#if !defined(__MSDOS__) && !defined(_AMIGA) && !defined(WINDOWS32)
+
+#ifndef VMS
+ /* start_waiting_job has set CHILD->remote if we can start a remote job. */
+ if (child->remote)
+ {
+ int is_remote, id, used_stdin;
+ if (start_remote_job (argv, child->environment,
+ child->good_stdin ? 0 : get_bad_stdin (),
+ &is_remote, &id, &used_stdin))
+ /* Don't give up; remote execution may fail for various reasons. If
+ so, simply run the job locally. */
+ goto run_local;
+ else
+ {
+ if (child->good_stdin && !used_stdin)
+ {
+ child->good_stdin = 0;
+ good_stdin_used = 0;
+ }
+ child->remote = is_remote;
+ child->pid = id;
+ }
+ }
+ else
+#endif /* !VMS */
+ {
+ /* Fork the child process. */
+
+ char **parent_environ;
+
+ run_local:
+ block_sigs ();
+
+ child->remote = 0;
+
+#ifdef VMS
+ if (!child_execute_job (child, argv))
+ {
+ /* Fork failed! */
+ perror_with_name ("fork", "");
+ goto error;
+ }
+
+#else
+
+ parent_environ = environ;
+
+ jobserver_pre_child (flags & COMMANDS_RECURSE);
+
+ child->pid = child_execute_job (&child->output, child->good_stdin, argv, child->environment);
+
+ environ = parent_environ; /* Restore value child may have clobbered. */
+ jobserver_post_child (flags & COMMANDS_RECURSE);
+
+ if (child->pid < 0)
+ {
+ /* Fork failed! */
+ unblock_sigs ();
+ perror_with_name ("fork", "");
+ goto error;
+ }
+#endif /* !VMS */
+ }
+
+#else /* __MSDOS__ or Amiga or WINDOWS32 */
+#ifdef __MSDOS__
+ {
+ int proc_return;
+
+ block_sigs ();
+ dos_status = 0;
+
+ /* We call 'system' to do the job of the SHELL, since stock DOS
+ shell is too dumb. Our 'system' knows how to handle long
+ command lines even if pipes/redirection is needed; it will only
+ call COMMAND.COM when its internal commands are used. */
+ if (execute_by_shell)
+ {
+ char *cmdline = argv[0];
+ /* We don't have a way to pass environment to 'system',
+ so we need to save and restore ours, sigh... */
+ char **parent_environ = environ;
+
+ environ = child->environment;
+
+ /* If we have a *real* shell, tell 'system' to call
+ it to do everything for us. */
+ if (unixy_shell)
+ {
+ /* A *real* shell on MSDOS may not support long
+ command lines the DJGPP way, so we must use 'system'. */
+ cmdline = argv[2]; /* get past "shell -c" */
+ }
+
+ dos_command_running = 1;
+ proc_return = system (cmdline);
+ environ = parent_environ;
+ execute_by_shell = 0; /* for the next time */
+ }
+ else
+ {
+ dos_command_running = 1;
+ proc_return = spawnvpe (P_WAIT, argv[0], argv, child->environment);
+ }
+
+ /* Need to unblock signals before turning off
+ dos_command_running, so that child's signals
+ will be treated as such (see fatal_error_signal). */
+ unblock_sigs ();
+ dos_command_running = 0;
+
+ /* If the child got a signal, dos_status has its
+ high 8 bits set, so be careful not to alter them. */
+ if (proc_return == -1)
+ dos_status |= 0xff;
+ else
+ dos_status |= (proc_return & 0xff);
+ ++dead_children;
+ child->pid = dos_pid++;
+ }
+#endif /* __MSDOS__ */
+#ifdef _AMIGA
+ amiga_status = MyExecute (argv);
+
+ ++dead_children;
+ child->pid = amiga_pid++;
+ if (amiga_batch_file)
+ {
+ amiga_batch_file = 0;
+ DeleteFile (amiga_bname); /* Ignore errors. */
+ }
+#endif /* Amiga */
+#ifdef WINDOWS32
+ {
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ HANDLE hPID;
+ char* arg0;
+ int outfd = FD_STDOUT;
+ int errfd = FD_STDERR;
+
+ /* make UNC paths safe for CreateProcess -- backslash format */
+ arg0 = argv[0];
+ if (arg0 && arg0[0] == '/' && arg0[1] == '/')
+ for ( ; arg0 && *arg0; arg0++)
+ if (*arg0 == '/')
+ *arg0 = '\\';
+
+ /* make sure CreateProcess() has Path it needs */
+ sync_Path_environment ();
+
+#ifndef NO_OUTPUT_SYNC
+ /* Divert child output if output_sync in use. */
+ if (child->output.syncout)
+ {
+ if (child->output.out >= 0)
+ outfd = child->output.out;
+ if (child->output.err >= 0)
+ errfd = child->output.err;
+ }
+#else
+ outfd = errfd = -1;
+#endif
+ hPID = process_easy (argv, child->environment, outfd, errfd);
+
+ if (hPID != INVALID_HANDLE_VALUE)
+ child->pid = (pid_t) hPID;
+ else
+ {
+ int i;
+ unblock_sigs ();
+ fprintf (stderr,
+ _("process_easy() failed to launch process (e=%ld)\n"),
+ process_last_err (hPID));
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ fprintf (stderr, _("\nCounted %d args in failed launch\n"), i);
+ goto error;
+ }
+# else /* CONFIG_NEW_WIN_CHILDREN */
+ struct variable *shell_var = lookup_variable("SHELL", 5);
+ const char *shell_value = !shell_var ? NULL
+ : !shell_var->recursive || strchr(shell_var->value, '$') == NULL
+ ? shell_var->value : variable_expand (shell_var->value);
+ int rc = MkWinChildCreate(argv, child->environment, shell_value, child, &child->pid);
+ if (rc != 0)
+ {
+ int i;
+ unblock_sigs ();
+ fprintf (stderr, _("failed to launch process (rc=%d)\n"), rc);
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ fprintf (stderr, "\n", argv[i]);
+ goto error;
+ }
+# endif /* CONFIG_NEW_WIN_CHILDREN */
+ }
+#endif /* WINDOWS32 */
+#endif /* __MSDOS__ or Amiga or WINDOWS32 */
+
+ /* Bump the number of jobs started in this second. */
+ ++job_counter;
+
+ /* We are the parent side. Set the state to
+ say the commands are running and return. */
+
+ set_command_state (child->file, cs_running);
+
+ /* Free the storage used by the child's argument list. */
+#ifdef KMK /* leak */
+ cleanup_argv:
+#endif
+#ifndef VMS
+ free (argv[0]);
+ free (argv);
+#endif
+
+ OUTPUT_UNSET();
+ return;
+
+ error:
+ child->file->update_status = us_failed;
+ notice_finished_file (child->file);
+#ifdef KMK /* fix leak */
+ goto cleanup_argv;
+#else
+ OUTPUT_UNSET();
+#endif
+}
+
+/* Try to start a child running.
+ Returns nonzero if the child was started (and maybe finished), or zero if
+ the load was too high and the child was put on the 'waiting_jobs' chain. */
+
+static int
+start_waiting_job (struct child *c)
+{
+ struct file *f = c->file;
+#ifdef DB_KMK
+ DB (DB_KMK, (_("start_waiting_job %p (`%s') command_flags=%#x slots=%d/%d\n"),
+ (void *)c, c->file->name, c->file->command_flags, job_slots_used, job_slots));
+#endif
+
+ /* If we can start a job remotely, we always want to, and don't care about
+ the local load average. We record that the job should be started
+ remotely in C->remote for start_job_command to test. */
+
+ c->remote = start_remote_job_p (1);
+
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ if (c->file->command_flags & COMMANDS_NOTPARALLEL)
+ {
+ DB (DB_KMK, (_("not_parallel %d -> %d (file=%p `%s') [start_waiting_job]\n"),
+ not_parallel, not_parallel + 1, (void *)c->file, c->file->name));
+ assert(not_parallel >= 0);
+ ++not_parallel;
+ }
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+ /* If we are running at least one job already and the load average
+ is too high, make this one wait. */
+ if (!c->remote
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ && ((job_slots_used > 0 && (not_parallel > 0 || load_too_high ()))
+#else
+ && ((job_slots_used > 0 && load_too_high ())
+#endif
+#ifdef WINDOWS32
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ || (process_used_slots () >= MAXIMUM_WAIT_OBJECTS)
+# endif
+#endif
+ ))
+ {
+#ifndef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ /* Put this child on the chain of children waiting for the load average
+ to go down. */
+ set_command_state (f, cs_running);
+ c->next = waiting_jobs;
+ waiting_jobs = c;
+
+#else /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+ /* Put this child on the chain of children waiting for the load average
+ to go down. If not parallel, put it last. */
+ set_command_state (f, cs_running);
+ c->next = waiting_jobs;
+ if (c->next && (c->file->command_flags & COMMANDS_NOTPARALLEL))
+ {
+ struct child *prev = waiting_jobs;
+ while (prev->next)
+ prev = prev->next;
+ c->next = 0;
+ prev->next = c;
+ }
+ else /* FIXME: insert after the last node with COMMANDS_NOTPARALLEL set */
+ waiting_jobs = c;
+ DB (DB_KMK, (_("queued child %p (`%s')\n"), (void *)c, c->file->name));
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+ return 0;
+ }
+
+ /* Start the first command; reap_children will run later command lines. */
+ start_job_command (c);
+
+ switch (f->command_state)
+ {
+ case cs_running:
+ c->next = children;
+ DB (DB_JOBS, (_("Putting child %p (%s) PID %s%s on the chain.\n"),
+ (void *)c, c->file->name, pid2str (c->pid),
+ c->remote ? _(" (remote)") : ""));
+ children = c;
+ /* One more job slot is in use. */
+ ++job_slots_used;
+ unblock_sigs ();
+ break;
+
+ case cs_not_started:
+ /* All the command lines turned out to be empty. */
+ f->update_status = us_success;
+ /* FALLTHROUGH */
+
+ case cs_finished:
+ notice_finished_file (f);
+ free_child (c);
+ break;
+
+ default:
+ assert (f->command_state == cs_finished);
+ break;
+ }
+
+ return 1;
+}
+
+/* Create a 'struct child' for FILE and start its commands running. */
+
+void
+new_job (struct file *file)
+{
+ struct commands *cmds = file->cmds;
+ struct child *c;
+ char **lines;
+ unsigned int i;
+
+ /* Let any previously decided-upon jobs that are waiting
+ for the load to go down start before this new one. */
+ start_waiting_jobs ();
+
+ /* Reap any children that might have finished recently. */
+ reap_children (0, 0);
+
+ /* Chop the commands up into lines if they aren't already. */
+ chop_commands (cmds);
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ cmds->refs++; /* retain the chopped lines. */
+#endif
+
+ /* Start the command sequence, record it in a new
+ 'struct child', and add that to the chain. */
+
+ c = xcalloc (sizeof (struct child));
+ output_init (&c->output);
+
+ c->file = file;
+ c->sh_batch_file = NULL;
+
+ /* Cache dontcare flag because file->dontcare can be changed once we
+ return. Check dontcare inheritance mechanism for details. */
+ c->dontcare = file->dontcare;
+
+ /* Start saving output in case the expansion uses $(info ...) etc. */
+ OUTPUT_SET (&c->output);
+
+ /* Expand the command lines and store the results in LINES. */
+ lines = xmalloc (cmds->ncommand_lines * sizeof (char *));
+ for (i = 0; i < cmds->ncommand_lines; ++i)
+ {
+ /* Collapse backslash-newline combinations that are inside variable
+ or function references. These are left alone by the parser so
+ that they will appear in the echoing of commands (where they look
+ nice); and collapsed by construct_command_argv when it tokenizes.
+ But letting them survive inside function invocations loses because
+ we don't want the functions to see them as part of the text. */
+
+ char *in, *out, *ref;
+
+ /* IN points to where in the line we are scanning.
+ OUT points to where in the line we are writing.
+ When we collapse a backslash-newline combination,
+ IN gets ahead of OUT. */
+
+ in = out = cmds->command_lines[i];
+ while ((ref = strchr (in, '$')) != 0)
+ {
+ ++ref; /* Move past the $. */
+
+ if (out != in)
+ /* Copy the text between the end of the last chunk
+ we processed (where IN points) and the new chunk
+ we are about to process (where REF points). */
+ memmove (out, in, ref - in);
+
+ /* Move both pointers past the boring stuff. */
+ out += ref - in;
+ in = ref;
+
+ if (*ref == '(' || *ref == '{')
+ {
+ char openparen = *ref;
+ char closeparen = openparen == '(' ? ')' : '}';
+ char *outref;
+ int count;
+ char *p;
+
+ *out++ = *in++; /* Copy OPENPAREN. */
+ outref = out;
+ /* IN now points past the opening paren or brace.
+ Count parens or braces until it is matched. */
+ count = 0;
+ while (*in != '\0')
+ {
+ if (*in == closeparen && --count < 0)
+ break;
+ else if (*in == '\\' && in[1] == '\n')
+ {
+ /* We have found a backslash-newline inside a
+ variable or function reference. Eat it and
+ any following whitespace. */
+
+ int quoted = 0;
+ for (p = in - 1; p > ref && *p == '\\'; --p)
+ quoted = !quoted;
+
+ if (quoted)
+ /* There were two or more backslashes, so this is
+ not really a continuation line. We don't collapse
+ the quoting backslashes here as is done in
+ collapse_continuations, because the line will
+ be collapsed again after expansion. */
+ *out++ = *in++;
+ else
+ {
+ /* Skip the backslash, newline, and whitespace. */
+ in += 2;
+ NEXT_TOKEN (in);
+
+ /* Discard any preceding whitespace that has
+ already been written to the output. */
+ while (out > outref && ISBLANK (out[-1]))
+ --out;
+
+ /* Replace it all with a single space. */
+ *out++ = ' ';
+ }
+ }
+ else
+ {
+ if (*in == openparen)
+ ++count;
+
+ *out++ = *in++;
+ }
+ }
+ }
+ }
+
+ /* There are no more references in this line to worry about.
+ Copy the remaining uninteresting text to the output. */
+ if (out != in)
+ memmove (out, in, strlen (in) + 1);
+
+ /* Finally, expand the line. */
+ cmds->fileinfo.offset = i;
+ lines[i] = allocated_variable_expand_for_file (cmds->command_lines[i],
+ file);
+ }
+
+ cmds->fileinfo.offset = 0;
+ c->command_lines = lines;
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ c->start_ts = -1;
+#endif
+
+ /* Fetch the first command line to be run. */
+ job_next_command (c);
+
+ /* Wait for a job slot to be freed up. If we allow an infinite number
+ don't bother; also job_slots will == 0 if we're using the jobserver. */
+
+ if (job_slots != 0)
+ while (job_slots_used == job_slots)
+ reap_children (1, 0);
+
+#ifdef MAKE_JOBSERVER
+ /* If we are controlling multiple jobs make sure we have a token before
+ starting the child. */
+
+ /* This can be inefficient. There's a decent chance that this job won't
+ actually have to run any subprocesses: the command script may be empty
+ or otherwise optimized away. It would be nice if we could defer
+ obtaining a token until just before we need it, in start_job_command.
+ To do that we'd need to keep track of whether we'd already obtained a
+ token (since start_job_command is called for each line of the job, not
+ just once). Also more thought needs to go into the entire algorithm;
+ this is where the old parallel job code waits, so... */
+
+ else if (jobserver_enabled ())
+ while (1)
+ {
+ int got_token;
+
+ DB (DB_JOBS, ("Need a job token; we %shave children\n",
+ children ? "" : "don't "));
+
+ /* If we don't already have a job started, use our "free" token. */
+ if (!jobserver_tokens)
+ break;
+
+ /* Prepare for jobserver token acquisition. */
+ jobserver_pre_acquire ();
+
+ /* Reap anything that's currently waiting. */
+ reap_children (0, 0);
+
+ /* Kick off any jobs we have waiting for an opportunity that
+ can run now (i.e., waiting for load). */
+ start_waiting_jobs ();
+
+ /* If our "free" slot is available, use it; we don't need a token. */
+ if (!jobserver_tokens)
+ break;
+
+ /* There must be at least one child already, or we have no business
+ waiting for a token. */
+ if (!children)
+ O (fatal, NILF, "INTERNAL: no children as we go to sleep on read\n");
+
+ /* Get a token. */
+ got_token = jobserver_acquire (waiting_jobs != NULL);
+
+ /* If we got one, we're done here. */
+ if (got_token == 1)
+ {
+ DB (DB_JOBS, (_("Obtained token for child %p (%s).\n"),
+ (void *)c, c->file->name));
+ break;
+ }
+ }
+#endif
+
+ ++jobserver_tokens;
+
+ /* Trace the build.
+ Use message here so that changes to working directories are logged. */
+ if (trace_flag)
+ {
+ char *newer = allocated_variable_expand_for_file ("$?", c->file);
+ const char *nm;
+
+ if (! cmds->fileinfo.filenm)
+ nm = _("<builtin>");
+ else
+ {
+ char *n = alloca (strlen (cmds->fileinfo.filenm) + 1 + 11 + 1);
+ sprintf (n, "%s:%lu", cmds->fileinfo.filenm, cmds->fileinfo.lineno);
+ nm = n;
+ }
+
+ if (newer[0] == '\0')
+ OSS (message, 0,
+ _("%s: target '%s' does not exist"), nm, c->file->name);
+ else
+ OSSS (message, 0,
+ _("%s: update target '%s' due to: %s"), nm, c->file->name, newer);
+
+ free (newer);
+ }
+
+ /* The job is now primed. Start it running.
+ (This will notice if there is in fact no recipe.) */
+ start_waiting_job (c);
+
+#ifndef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ if (job_slots == 1 || not_parallel)
+ /* Since there is only one job slot, make things run linearly.
+ Wait for the child to die, setting the state to 'cs_finished'. */
+ while (file->command_state == cs_running)
+ reap_children (1, 0);
+
+#else /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+ if (job_slots == 1 || not_parallel < 0)
+ {
+ /* Since there is only one job slot, make things run linearly.
+ Wait for the child to die, setting the state to `cs_finished'. */
+ while (file->command_state == cs_running)
+ reap_children (1, 0);
+ }
+ else if (not_parallel > 0)
+ {
+ /* wait for all live children to finish and then continue
+ with the not-parallel child(s). FIXME: this loop could be better? */
+ while (file->command_state == cs_running
+ && (children != 0 || shell_function_pid != 0) /* reap_child condition */
+ && not_parallel > 0)
+ reap_children (1, 0);
+ }
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+ OUTPUT_UNSET ();
+ return;
+}
+
+/* Move CHILD's pointers to the next command for it to execute.
+ Returns nonzero if there is another command. */
+
+static int
+job_next_command (struct child *child)
+{
+ while (child->command_ptr == 0 || *child->command_ptr == '\0')
+ {
+ /* There are no more lines in the expansion of this line. */
+ if (child->command_line == child->file->cmds->ncommand_lines)
+ {
+ /* There are no more lines to be expanded. */
+ child->command_ptr = 0;
+ child->file->cmds->fileinfo.offset = 0;
+ return 0;
+ }
+ else
+ /* Get the next line to run. */
+ child->command_ptr = child->command_lines[child->command_line++];
+ }
+
+ child->file->cmds->fileinfo.offset = child->command_line - 1;
+ return 1;
+}
+
+/* Determine if the load average on the system is too high to start a new job.
+ The real system load average is only recomputed once a second. However, a
+ very parallel make can easily start tens or even hundreds of jobs in a
+ second, which brings the system to its knees for a while until that first
+ batch of jobs clears out.
+
+ To avoid this we use a weighted algorithm to try to account for jobs which
+ have been started since the last second, and guess what the load average
+ would be now if it were computed.
+
+ This algorithm was provided by Thomas Riedl <thomas.riedl@siemens.com>,
+ who writes:
+
+! calculate something load-oid and add to the observed sys.load,
+! so that latter can catch up:
+! - every job started increases jobctr;
+! - every dying job decreases a positive jobctr;
+! - the jobctr value gets zeroed every change of seconds,
+! after its value*weight_b is stored into the 'backlog' value last_sec
+! - weight_a times the sum of jobctr and last_sec gets
+! added to the observed sys.load.
+!
+! The two weights have been tried out on 24 and 48 proc. Sun Solaris-9
+! machines, using a several-thousand-jobs-mix of cpp, cc, cxx and smallish
+! sub-shelled commands (rm, echo, sed...) for tests.
+! lowering the 'direct influence' factor weight_a (e.g. to 0.1)
+! resulted in significant excession of the load limit, raising it
+! (e.g. to 0.5) took bad to small, fast-executing jobs and didn't
+! reach the limit in most test cases.
+!
+! lowering the 'history influence' weight_b (e.g. to 0.1) resulted in
+! exceeding the limit for longer-running stuff (compile jobs in
+! the .5 to 1.5 sec. range),raising it (e.g. to 0.5) overrepresented
+! small jobs' effects.
+
+ */
+
+#define LOAD_WEIGHT_A 0.25
+#define LOAD_WEIGHT_B 0.25
+
+static int
+load_too_high (void)
+{
+#if defined(__MSDOS__) || defined(VMS) || defined(_AMIGA) || defined(__riscos__) || defined(__HAIKU__)
+ return 1;
+#else
+ static double last_sec;
+ static time_t last_now;
+ double load, guess;
+ time_t now;
+
+#if defined(WINDOWS32) && !defined(CONFIG_NEW_WIN_CHILDREN)
+ /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS children */
+ if (process_used_slots () >= MAXIMUM_WAIT_OBJECTS)
+ return 1;
+#endif
+
+ if (max_load_average < 0)
+ return 0;
+
+ /* Find the real system load average. */
+ make_access ();
+ if (getloadavg (&load, 1) != 1)
+ {
+ static int lossage = -1;
+ /* Complain only once for the same error. */
+ if (lossage == -1 || errno != lossage)
+ {
+ if (errno == 0)
+ /* An errno value of zero means getloadavg is just unsupported. */
+ O (error, NILF,
+ _("cannot enforce load limits on this operating system"));
+ else
+ perror_with_name (_("cannot enforce load limit: "), "getloadavg");
+ }
+ lossage = errno;
+ load = 0;
+ }
+ user_access ();
+
+ /* If we're in a new second zero the counter and correct the backlog
+ value. Only keep the backlog for one extra second; after that it's 0. */
+ now = time (NULL);
+ if (last_now < now)
+ {
+ if (last_now == now - 1)
+ last_sec = LOAD_WEIGHT_B * job_counter;
+ else
+ last_sec = 0.0;
+
+ job_counter = 0;
+ last_now = now;
+ }
+
+ /* Try to guess what the load would be right now. */
+ guess = load + (LOAD_WEIGHT_A * (job_counter + last_sec));
+
+ DB (DB_JOBS, ("Estimated system load = %f (actual = %f) (max requested = %f)\n",
+ guess, load, max_load_average));
+
+ return guess >= max_load_average;
+#endif
+}
+
+/* Start jobs that are waiting for the load to be lower. */
+
+void
+start_waiting_jobs (void)
+{
+ struct child *job;
+
+ if (waiting_jobs == 0)
+ return;
+
+ do
+ {
+ /* Check for recently deceased descendants. */
+ reap_children (0, 0);
+
+ /* Take a job off the waiting list. */
+ job = waiting_jobs;
+ waiting_jobs = job->next;
+
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ /* If it's a not-parallel job, we've already counted it once
+ when it was queued in start_waiting_job, so decrement
+ before sending it to start_waiting_job again. */
+ if (job->file->command_flags & COMMANDS_NOTPARALLEL)
+ {
+ DB (DB_KMK, (_("not_parallel %d -> %d (file=%p `%s') [start_waiting_jobs]\n"),
+ not_parallel, not_parallel - 1, (void *) job->file, job->file->name));
+ assert(not_parallel > 0);
+ --not_parallel;
+ }
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+ /* Try to start that job. We break out of the loop as soon
+ as start_waiting_job puts one back on the waiting list. */
+ }
+ while (start_waiting_job (job) && waiting_jobs != 0);
+
+ return;
+}
+
+#ifndef WINDOWS32
+
+/* EMX: Start a child process. This function returns the new pid. */
+# if defined __EMX__
+int
+child_execute_job (struct output *out, int good_stdin, char **argv, char **envp)
+{
+ int pid;
+ int fdin = good_stdin ? FD_STDIN : get_bad_stdin ();
+ int fdout = FD_STDOUT;
+ int fderr = FD_STDERR;
+ int save_fdin = -1;
+ int save_fdout = -1;
+ int save_fderr = -1;
+
+ /* Divert child output if we want to capture output. */
+ if (out && out->syncout)
+ {
+ if (out->out >= 0)
+ fdout = out->out;
+ if (out->err >= 0)
+ fderr = out->err;
+ }
+
+ /* For each FD which needs to be redirected first make a dup of the standard
+ FD to save and mark it close on exec so our child won't see it. Then
+ dup2() the standard FD to the redirect FD, and also mark the redirect FD
+ as close on exec. */
+ if (fdin != FD_STDIN)
+ {
+ save_fdin = dup (FD_STDIN);
+ if (save_fdin < 0)
+ O (fatal, NILF, _("no more file handles: could not duplicate stdin\n"));
+ CLOSE_ON_EXEC (save_fdin);
+
+ dup2 (fdin, FD_STDIN);
+ CLOSE_ON_EXEC (fdin);
+ }
+
+ if (fdout != FD_STDOUT)
+ {
+ save_fdout = dup (FD_STDOUT);
+ if (save_fdout < 0)
+ O (fatal, NILF,
+ _("no more file handles: could not duplicate stdout\n"));
+ CLOSE_ON_EXEC (save_fdout);
+
+ dup2 (fdout, FD_STDOUT);
+ CLOSE_ON_EXEC (fdout);
+ }
+
+ if (fderr != FD_STDERR)
+ {
+ if (fderr != fdout)
+ {
+ save_fderr = dup (FD_STDERR);
+ if (save_fderr < 0)
+ O (fatal, NILF,
+ _("no more file handles: could not duplicate stderr\n"));
+ CLOSE_ON_EXEC (save_fderr);
+ }
+
+ dup2 (fderr, FD_STDERR);
+ CLOSE_ON_EXEC (fderr);
+ }
+
+ /* Run the command. */
+ pid = exec_command (argv, envp);
+
+ /* Restore stdout/stdin/stderr of the parent and close temporary FDs. */
+ if (save_fdin >= 0)
+ {
+ if (dup2 (save_fdin, FD_STDIN) != FD_STDIN)
+ O (fatal, NILF, _("Could not restore stdin\n"));
+ else
+ close (save_fdin);
+ }
+
+ if (save_fdout >= 0)
+ {
+ if (dup2 (save_fdout, FD_STDOUT) != FD_STDOUT)
+ O (fatal, NILF, _("Could not restore stdout\n"));
+ else
+ close (save_fdout);
+ }
+
+ if (save_fderr >= 0)
+ {
+ if (dup2 (save_fderr, FD_STDERR) != FD_STDERR)
+ O (fatal, NILF, _("Could not restore stderr\n"));
+ else
+ close (save_fderr);
+ }
+
+ return pid;
+}
+
+#elif !defined (_AMIGA) && !defined (__MSDOS__) && !defined (VMS)
+
+/* POSIX:
+ Create a child process executing the command in ARGV.
+ ENVP is the environment of the new program. Returns the PID or -1. */
+int
+child_execute_job (struct output *out, int good_stdin, char **argv, char **envp)
+{
+ int r;
+ int pid;
+ int fdin = good_stdin ? FD_STDIN : get_bad_stdin ();
+ int fdout = FD_STDOUT;
+ int fderr = FD_STDERR;
+
+ /* Divert child output if we want to capture it. */
+ if (out && out->syncout)
+ {
+ if (out->out >= 0)
+ fdout = out->out;
+ if (out->err >= 0)
+ fderr = out->err;
+ }
+
+ pid = vfork();
+ if (pid != 0)
+ return pid;
+
+ /* We are the child. */
+ unblock_sigs ();
+
+#ifdef SET_STACK_SIZE
+ /* Reset limits, if necessary. */
+ if (stack_limit.rlim_cur)
+ setrlimit (RLIMIT_STACK, &stack_limit);
+#endif
+
+ /* For any redirected FD, dup2() it to the standard FD.
+ They are all marked close-on-exec already. */
+ if (fdin != FD_STDIN)
+ EINTRLOOP (r, dup2 (fdin, FD_STDIN));
+ if (fdout != FD_STDOUT)
+ EINTRLOOP (r, dup2 (fdout, FD_STDOUT));
+ if (fderr != FD_STDERR)
+ EINTRLOOP (r, dup2 (fderr, FD_STDERR));
+
+ /* Run the command. */
+ exec_command (argv, envp);
+}
+#endif /* !AMIGA && !__MSDOS__ && !VMS */
+#endif /* !WINDOWS32 */
+
+#if !defined(WINDOWS32) || !defined(CONFIG_NEW_WIN_CHILDREN)
+#ifndef _AMIGA
+/* Replace the current process with one running the command in ARGV,
+ with environment ENVP. This function does not return. */
+
+/* EMX: This function returns the pid of the child process. */
+# ifdef __EMX__
+int
+# else
+void
+# endif
+exec_command (char **argv, char **envp)
+{
+#ifdef VMS
+ /* to work around a problem with signals and execve: ignore them */
+#ifdef SIGCHLD
+ signal (SIGCHLD,SIG_IGN);
+#endif
+ /* Run the program. */
+ execve (argv[0], argv, envp);
+ perror_with_name ("execve: ", argv[0]);
+ _exit (EXIT_FAILURE);
+#else
+#ifdef WINDOWS32
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ HANDLE hPID;
+ HANDLE hWaitPID;
+ int exit_code = EXIT_FAILURE;
+
+ /* make sure CreateProcess() has Path it needs */
+ sync_Path_environment ();
+
+ /* launch command */
+ hPID = process_easy (argv, envp, -1, -1);
+
+ /* make sure launch ok */
+ if (hPID == INVALID_HANDLE_VALUE)
+ {
+ int i;
+ fprintf (stderr, _("process_easy() failed to launch process (e=%ld)\n"),
+ process_last_err (hPID));
+ for (i = 0; argv[i]; i++)
+ fprintf (stderr, "%s ", argv[i]);
+ fprintf (stderr, _("\nCounted %d args in failed launch\n"), i);
+ exit (EXIT_FAILURE);
+ }
+
+ /* wait and reap last child */
+ hWaitPID = process_wait_for_any (1, 0);
+ while (hWaitPID)
+ {
+ /* was an error found on this process? */
+ int err = process_last_err (hWaitPID);
+
+ /* get exit data */
+ exit_code = process_exit_code (hWaitPID);
+
+ if (err)
+ fprintf (stderr, "make (e=%d, rc=%d): %s",
+ err, exit_code, map_windows32_error_to_string (err));
+
+ /* cleanup process */
+ process_cleanup (hWaitPID);
+
+ /* expect to find only last pid, warn about other pids reaped */
+ if (hWaitPID == hPID)
+ break;
+ else
+ {
+ char *pidstr = xstrdup (pid2str ((pid_t)hWaitPID));
+
+ fprintf (stderr,
+ _("make reaped child pid %s, still waiting for pid %s\n"),
+ pidstr, pid2str ((pid_t)hPID));
+ free (pidstr);
+ }
+ }
+
+ /* return child's exit code as our exit code */
+ exit (exit_code);
+# else /* CONFIG_NEW_WIN_CHILDREN */
+
+# endif /* CONFIG_NEW_WIN_CHILDREN */
+#else /* !WINDOWS32 */
+
+# ifdef __EMX__
+ int pid;
+# endif
+
+ /* Be the user, permanently. */
+ child_access ();
+
+# ifdef __EMX__
+ /* Run the program. */
+ pid = spawnvpe (P_NOWAIT, argv[0], argv, envp);
+ if (pid >= 0)
+ return pid;
+
+ /* the file might have a strange shell extension */
+ if (errno == ENOENT)
+ errno = ENOEXEC;
+
+# else
+ /* Run the program. */
+ environ = envp;
+ execvp (argv[0], argv);
+
+# endif /* !__EMX__ */
+
+ switch (errno)
+ {
+ case ENOENT:
+ /* We are in the child: don't use the output buffer.
+ It's not right to run fprintf() here! */
+ if (makelevel == 0)
+ fprintf (stderr, _("%s: %s: Command not found\n"), program, argv[0]);
+ else
+ fprintf (stderr, _("%s[%u]: %s: Command not found\n"),
+ program, makelevel, argv[0]);
+ break;
+ case ENOEXEC:
+ {
+ /* The file is not executable. Try it as a shell script. */
+ const char *shell;
+ char **new_argv;
+ int argc;
+ int i=1;
+
+# ifdef __EMX__
+ /* Do not use $SHELL from the environment */
+ struct variable *p = lookup_variable ("SHELL", 5);
+ if (p)
+ shell = p->value;
+ else
+ shell = 0;
+# else
+ shell = getenv ("SHELL");
+# endif
+ if (shell == 0)
+ shell = default_shell;
+
+ argc = 1;
+ while (argv[argc] != 0)
+ ++argc;
+
+# ifdef __EMX__
+ if (!unixy_shell)
+ ++argc;
+# endif
+
+ new_argv = alloca ((1 + argc + 1) * sizeof (char *));
+ new_argv[0] = (char *)shell;
+
+# ifdef __EMX__
+ if (!unixy_shell)
+ {
+ new_argv[1] = "/c";
+ ++i;
+ --argc;
+ }
+# endif
+
+ new_argv[i] = argv[0];
+ while (argc > 0)
+ {
+ new_argv[i + argc] = argv[argc];
+ --argc;
+ }
+
+# ifdef __EMX__
+ pid = spawnvpe (P_NOWAIT, shell, new_argv, envp);
+ if (pid >= 0)
+ break;
+# else
+ execvp (shell, new_argv);
+# endif
+ if (errno == ENOENT)
+ OS (error, NILF, _("%s: Shell program not found"), shell);
+ else
+ perror_with_name ("execvp: ", shell);
+ break;
+ }
+
+# ifdef __EMX__
+ case EINVAL:
+ /* this nasty error was driving me nuts :-( */
+ O (error, NILF, _("spawnvpe: environment space might be exhausted"));
+ /* FALLTHROUGH */
+# endif
+
+ default:
+ perror_with_name ("execvp: ", argv[0]);
+ break;
+ }
+
+# ifdef __EMX__
+ return pid;
+# else
+ _exit (127);
+# endif
+#endif /* !WINDOWS32 */
+#endif /* !VMS */
+}
+#else /* On Amiga */
+void
+exec_command (char **argv)
+{
+ MyExecute (argv);
+}
+
+void clean_tmp (void)
+{
+ DeleteFile (amiga_bname);
+}
+
+#endif /* On Amiga */
+#endif /* !defined(WINDOWS32) || !defined(CONFIG_NEW_WIN_CHILDREN) */
+
+#ifndef VMS
+/* Figure out the argument list necessary to run LINE as a command. Try to
+ avoid using a shell. This routine handles only ' quoting, and " quoting
+ when no backslash, $ or ' characters are seen in the quotes. Starting
+ quotes may be escaped with a backslash. If any of the characters in
+ sh_chars is seen, or any of the builtin commands listed in sh_cmds
+ is the first word of a line, the shell is used.
+
+ If RESTP is not NULL, *RESTP is set to point to the first newline in LINE.
+ If *RESTP is NULL, newlines will be ignored.
+
+ SHELL is the shell to use, or nil to use the default shell.
+ IFS is the value of $IFS, or nil (meaning the default).
+
+ FLAGS is the value of lines_flags for this command line. It is
+ used in the WINDOWS32 port to check whether + or $(MAKE) were found
+ in this command line, in which case the effect of just_print_flag
+ is overridden. */
+
+static char **
+construct_command_argv_internal (char *line, char **restp, const char *shell,
+ const char *shellflags, const char *ifs,
+ int flags, char **batch_filename UNUSED)
+{
+#ifdef __MSDOS__
+ /* MSDOS supports both the stock DOS shell and ports of Unixy shells.
+ We call 'system' for anything that requires ''slow'' processing,
+ because DOS shells are too dumb. When $SHELL points to a real
+ (unix-style) shell, 'system' just calls it to do everything. When
+ $SHELL points to a DOS shell, 'system' does most of the work
+ internally, calling the shell only for its internal commands.
+ However, it looks on the $PATH first, so you can e.g. have an
+ external command named 'mkdir'.
+
+ Since we call 'system', certain characters and commands below are
+ actually not specific to COMMAND.COM, but to the DJGPP implementation
+ of 'system'. In particular:
+
+ The shell wildcard characters are in DOS_CHARS because they will
+ not be expanded if we call the child via 'spawnXX'.
+
+ The ';' is in DOS_CHARS, because our 'system' knows how to run
+ multiple commands on a single line.
+
+ DOS_CHARS also include characters special to 4DOS/NDOS, so we
+ won't have to tell one from another and have one more set of
+ commands and special characters. */
+ static const char *sh_chars_dos = "*?[];|<>%^&()";
+ static const char *sh_cmds_dos[] =
+ { "break", "call", "cd", "chcp", "chdir", "cls", "copy", "ctty", "date",
+ "del", "dir", "echo", "erase", "exit", "for", "goto", "if", "md",
+ "mkdir", "path", "pause", "prompt", "rd", "rmdir", "rem", "ren",
+ "rename", "set", "shift", "time", "type", "ver", "verify", "vol", ":",
+ 0 };
+
+ static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^";
+ static const char *sh_cmds_sh[] =
+ { "cd", "echo", "eval", "exec", "exit", "login", "logout", "set", "umask",
+ "wait", "while", "for", "case", "if", ":", ".", "break", "continue",
+ "export", "read", "readonly", "shift", "times", "trap", "switch",
+ "unset", "ulimit", 0 };
+
+ const char *sh_chars;
+ const char **sh_cmds;
+
+#elif defined (__EMX__)
+ static const char *sh_chars_dos = "*?[];|<>%^&()";
+ static const char *sh_cmds_dos[] =
+ { "break", "call", "cd", "chcp", "chdir", "cls", "copy", "ctty", "date",
+ "del", "dir", "echo", "erase", "exit", "for", "goto", "if", "md",
+ "mkdir", "path", "pause", "prompt", "rd", "rmdir", "rem", "ren",
+ "rename", "set", "shift", "time", "type", "ver", "verify", "vol", ":",
+ 0 };
+
+ static const char *sh_chars_os2 = "*?[];|<>%^()\"'&";
+ static const char *sh_cmds_os2[] =
+ { "call", "cd", "chcp", "chdir", "cls", "copy", "date", "del", "detach",
+ "dir", "echo", "endlocal", "erase", "exit", "for", "goto", "if", "keys",
+ "md", "mkdir", "move", "path", "pause", "prompt", "rd", "rem", "ren",
+ "rename", "rmdir", "set", "setlocal", "shift", "start", "time", "type",
+ "ver", "verify", "vol", ":", 0 };
+
+ static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^~'";
+ static const char *sh_cmds_sh[] =
+ { "echo", "cd", "eval", "exec", "exit", "login", "logout", "set", "umask",
+ "wait", "while", "for", "case", "if", ":", ".", "break", "continue",
+ "export", "read", "readonly", "shift", "times", "trap", "switch",
+ "unset", 0 };
+
+ const char *sh_chars;
+ const char **sh_cmds;
+
+#elif defined (_AMIGA)
+ static const char *sh_chars = "#;\"|<>()?*$`";
+ static const char *sh_cmds[] =
+ { "cd", "eval", "if", "delete", "echo", "copy", "rename", "set", "setenv",
+ "date", "makedir", "skip", "else", "endif", "path", "prompt", "unset",
+ "unsetenv", "version", 0 };
+
+#elif defined (WINDOWS32)
+ /* We used to have a double quote (") in sh_chars_dos[] below, but
+ that caused any command line with quoted file names be run
+ through a temporary batch file, which introduces command-line
+ limit of 4K charcaters imposed by cmd.exe. Since CreateProcess
+ can handle quoted file names just fine, removing the quote lifts
+ the limit from a very frequent use case, because using quoted
+ file names is commonplace on MS-Windows. */
+ static const char *sh_chars_dos = "|&<>";
+ static const char *sh_cmds_dos[] =
+ { "assoc", "break", "call", "cd", "chcp", "chdir", "cls", "color", "copy",
+ "ctty", "date", "del", "dir", "echo", "echo.", "endlocal", "erase",
+ "exit", "for", "ftype", "goto", "if", "if", "md", "mkdir", "move",
+ "path", "pause", "prompt", "rd", "rem", "ren", "rename", "rmdir",
+ "set", "setlocal", "shift", "time", "title", "type", "ver", "verify",
+ "vol", ":", 0 };
+
+ static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^";
+ static const char *sh_cmds_sh[] =
+ { "cd", "eval", "exec", "exit", "login", "logout", "set", "umask", "wait",
+ "while", "for", "case", "if", ":", ".", "break", "continue", "export",
+ "read", "readonly", "shift", "times", "trap", "switch", "test",
+#ifdef BATCH_MODE_ONLY_SHELL
+ "echo",
+#endif
+ 0 };
+
+ const char *sh_chars;
+ char const * const * sh_cmds; /* kmk: +_sh +const*2 */
+#elif defined(__riscos__)
+ static const char *sh_chars = "";
+ static const char *sh_cmds[] = { 0 };
+#else /* must be UNIX-ish */
+ static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^~!"; /* kmk: +_sh */
+ static const char *sh_cmds_sh[] = /* kmk: +_sh */
+ { ".", ":", "break", "case", "cd", "continue", "eval", "exec", "exit",
+ "export", "for", "if", "login", "logout", "read", "readonly", "set",
+ "shift", "switch", "test", "times", "trap", "ulimit", "umask", "unset",
+ "wait", "while", 0 };
+
+# if 0 /*def HAVE_DOS_PATHS - kmk */
+ /* This is required if the MSYS/Cygwin ports (which do not define
+ WINDOWS32) are compiled with HAVE_DOS_PATHS defined, which uses
+ sh_chars_sh directly (see below). The value must be identical
+ to that of sh_chars immediately above. */
+ static const char *sh_chars_sh = "#;\"*?[]&|<>(){}$`^~!";
+# endif /* HAVE_DOS_PATHS */
+ char const * sh_chars = sh_chars_sh; /* kmk: +_sh +const */
+ char const * const * sh_cmds = sh_cmds_sh; /* kmk: +_sh +const*2 */
+#endif
+#ifdef KMK
+ static const char sh_chars_kash[] = "#;*?[]&|<>(){}$`^~!"; /* note: no \" - good idea? */
+ static const char * const sh_cmds_kash[] = {
+ ".", ":", "break", "case", "cd", "continue", "echo", "eval", "exec", "exit",
+ "export", "for", "if", "login", "logout", "read", "readonly", "set",
+ "shift", "switch", "test", "times", "trap", "umask", "wait", "while", 0 /* +echo, -ulimit, -unset */
+ };
+ int is_kmk_shell = 0;
+#endif
+ int i;
+ char *p;
+#ifndef NDEBUG
+ char *end;
+#endif
+ char *ap;
+ const char *cap;
+ const char *cp;
+ int instring, word_has_equals, seen_nonequals, last_argument_was_empty;
+ char **new_argv = 0;
+ char *argstr = 0;
+#ifdef WINDOWS32
+ int slow_flag = 0;
+
+ if (!unixy_shell)
+ {
+ sh_cmds = sh_cmds_dos;
+ sh_chars = sh_chars_dos;
+ }
+ else
+ {
+ sh_cmds = sh_cmds_sh;
+ sh_chars = sh_chars_sh;
+ }
+#endif /* WINDOWS32 */
+
+ if (restp != NULL)
+ *restp = NULL;
+
+ /* Make sure not to bother processing an empty line but stop at newline. */
+ while (ISBLANK (*line))
+ ++line;
+ if (*line == '\0')
+ return 0;
+
+ if (shellflags == 0)
+ shellflags = posix_pedantic ? "-ec" : "-c";
+
+ /* See if it is safe to parse commands internally. */
+#ifdef KMK /* kmk_ash and kmk_kash are both fine, kmk_ash is the default btw. */
+ if (shell == 0)
+ {
+ is_kmk_shell = 1;
+ shell = (char *)get_default_kbuild_shell ();
+ }
+ else if (!strcmp (shell, get_default_kbuild_shell()))
+ is_kmk_shell = 1;
+ else
+ {
+ const char *psz = strstr (shell, "/kmk_ash");
+ if (psz)
+ psz += sizeof ("/kmk_ash") - 1;
+ else
+ {
+ psz = strstr (shell, "/kmk_kash");
+ if (psz)
+ psz += sizeof ("/kmk_kash") - 1;
+ }
+# if defined (__OS2__) || defined (_WIN32) || defined (WINDOWS32)
+ is_kmk_shell = psz && (*psz == '\0' || !stricmp (psz, ".exe"));
+# else
+ is_kmk_shell = psz && *psz == '\0';
+# endif
+ }
+ if (is_kmk_shell)
+ {
+ sh_chars = sh_chars_kash;
+ sh_cmds = sh_cmds_kash;
+ }
+#else /* !KMK */
+ if (shell == 0)
+ shell = default_shell;
+#endif /* !KMK */
+#ifdef WINDOWS32
+ else if (strcmp (shell, default_shell))
+ {
+ char *s1 = _fullpath (NULL, shell, 0);
+ char *s2 = _fullpath (NULL, default_shell, 0);
+
+ slow_flag = strcmp ((s1 ? s1 : ""), (s2 ? s2 : ""));
+
+ free (s1);
+ free (s2);
+ }
+ if (slow_flag)
+ goto slow;
+#else /* not WINDOWS32 */
+#if defined (__MSDOS__) || defined (__EMX__)
+ else if (strcasecmp (shell, default_shell))
+ {
+ extern int _is_unixy_shell (const char *_path);
+
+ DB (DB_BASIC, (_("$SHELL changed (was '%s', now '%s')\n"),
+ default_shell, shell));
+ unixy_shell = _is_unixy_shell (shell);
+ /* we must allocate a copy of shell: construct_command_argv() will free
+ * shell after this function returns. */
+ default_shell = xstrdup (shell);
+ }
+# ifdef KMK
+ if (is_kmk_shell)
+ { /* done above already */ }
+ else
+# endif
+ if (unixy_shell)
+ {
+ sh_chars = sh_chars_sh;
+ sh_cmds = sh_cmds_sh;
+ }
+ else
+ {
+ sh_chars = sh_chars_dos;
+ sh_cmds = sh_cmds_dos;
+# ifdef __EMX__
+ if (_osmode == OS2_MODE)
+ {
+ sh_chars = sh_chars_os2;
+ sh_cmds = sh_cmds_os2;
+ }
+# endif
+ }
+#else /* !__MSDOS__ */
+ else if (strcmp (shell, default_shell))
+ goto slow;
+#endif /* !__MSDOS__ && !__EMX__ */
+#endif /* not WINDOWS32 */
+
+ if (ifs)
+ for (cap = ifs; *cap != '\0'; ++cap)
+ if (*cap != ' ' && *cap != '\t' && *cap != '\n')
+ goto slow;
+
+ if (shellflags)
+ if (shellflags[0] != '-'
+ || ((shellflags[1] != 'c' || shellflags[2] != '\0')
+ && (shellflags[1] != 'e' || shellflags[2] != 'c' || shellflags[3] != '\0')))
+ goto slow;
+
+ i = strlen (line) + 1;
+
+ /* More than 1 arg per character is impossible. */
+ new_argv = xmalloc (i * sizeof (char *));
+
+ /* All the args can fit in a buffer as big as LINE is. */
+ ap = new_argv[0] = argstr = xmalloc (i);
+#ifndef NDEBUG
+ end = ap + i;
+#endif
+
+ /* I is how many complete arguments have been found. */
+ i = 0;
+ instring = word_has_equals = seen_nonequals = last_argument_was_empty = 0;
+ for (p = line; *p != '\0'; ++p)
+ {
+ assert (ap <= end);
+
+ if (instring)
+ {
+ /* Inside a string, just copy any char except a closing quote
+ or a backslash-newline combination. */
+ if (*p == instring)
+ {
+ instring = 0;
+ if (ap == new_argv[0] || *(ap-1) == '\0')
+ last_argument_was_empty = 1;
+ }
+ else if (*p == '\\' && p[1] == '\n')
+ {
+ /* Backslash-newline is handled differently depending on what
+ kind of string we're in: inside single-quoted strings you
+ keep them; in double-quoted strings they disappear. For
+ DOS/Windows/OS2, if we don't have a POSIX shell, we keep the
+ pre-POSIX behavior of removing the backslash-newline. */
+ if (instring == '"'
+#if defined (__MSDOS__) || defined (__EMX__) || defined (WINDOWS32)
+ || !unixy_shell
+#endif
+ )
+ ++p;
+ else
+ {
+ *(ap++) = *(p++);
+ *(ap++) = *p;
+ }
+ }
+ else if (*p == '\n' && restp != NULL)
+ {
+ /* End of the command line. */
+ *restp = p;
+ goto end_of_line;
+ }
+ /* Backslash, $, and ` are special inside double quotes.
+ If we see any of those, punt.
+ But on MSDOS, if we use COMMAND.COM, double and single
+ quotes have the same effect. */
+ else if (instring == '"' && strchr ("\\$`", *p) != 0 && unixy_shell)
+ goto slow;
+#ifdef WINDOWS32
+ /* Quoted wildcard characters must be passed quoted to the
+ command, so give up the fast route. */
+ else if (instring == '"' && strchr ("*?", *p) != 0 && !unixy_shell)
+ goto slow;
+ else if (instring == '"' && strncmp (p, "\\\"", 2) == 0)
+ *ap++ = *++p;
+#endif
+ else
+ *ap++ = *p;
+ }
+ else if (strchr (sh_chars, *p) != 0)
+#ifdef KMK
+ {
+ /* Tilde is only special if at the start of a path spec,
+ i.e. don't get excited when we by 8.3 files on windows. */
+ if ( *p == '~'
+ && p > line
+ && !ISSPACE (p[-1])
+ && p[-1] != '='
+ && p[-1] != ':'
+ && p[-1] != '"'
+ && p[-1] != '\'')
+ *ap++ = *p;
+ else
+ /* Not inside a string, but it's a special char. */
+ goto slow;
+ }
+#else /* !KMK */
+ /* Not inside a string, but it's a special char. */
+ goto slow;
+#endif /* !KMK */
+ else if (one_shell && *p == '\n')
+ /* In .ONESHELL mode \n is a separator like ; or && */
+ goto slow;
+#ifdef __MSDOS__
+ else if (*p == '.' && p[1] == '.' && p[2] == '.' && p[3] != '.')
+ /* '...' is a wildcard in DJGPP. */
+ goto slow;
+#endif
+ else
+ /* Not a special char. */
+ switch (*p)
+ {
+ case '=':
+ /* Equals is a special character in leading words before the
+ first word with no equals sign in it. This is not the case
+ with sh -k, but we never get here when using nonstandard
+ shell flags. */
+ if (! seen_nonequals && unixy_shell)
+ goto slow;
+ word_has_equals = 1;
+ *ap++ = '=';
+ break;
+
+ case '\\':
+ /* Backslash-newline has special case handling, ref POSIX.
+ We're in the fastpath, so emulate what the shell would do. */
+ if (p[1] == '\n')
+ {
+ /* Throw out the backslash and newline. */
+ ++p;
+
+ /* At the beginning of the argument, skip any whitespace other
+ than newline before the start of the next word. */
+ if (ap == new_argv[i])
+ while (ISBLANK (p[1]))
+ ++p;
+ }
+#ifdef WINDOWS32
+ /* Backslash before whitespace is not special if our shell
+ is not Unixy. */
+ else if (ISSPACE (p[1]) && !unixy_shell)
+ {
+ *ap++ = *p;
+ break;
+ }
+#endif
+ else if (p[1] != '\0')
+ {
+#ifdef HAVE_DOS_PATHS
+ /* Only remove backslashes before characters special to Unixy
+ shells. All other backslashes are copied verbatim, since
+ they are probably DOS-style directory separators. This
+ still leaves a small window for problems, but at least it
+ should work for the vast majority of naive users. */
+
+#ifdef __MSDOS__
+ /* A dot is only special as part of the "..."
+ wildcard. */
+ if (strneq (p + 1, ".\\.\\.", 5))
+ {
+ *ap++ = '.';
+ *ap++ = '.';
+ p += 4;
+ }
+ else
+#endif
+ if (p[1] != '\\' && p[1] != '\''
+ && !ISSPACE (p[1])
+# ifdef KMK
+ && strchr (sh_chars, p[1]) == 0
+ && (p[1] != '"' || !unixy_shell))
+# else
+ && strchr (sh_chars_sh, p[1]) == 0)
+# endif
+ /* back up one notch, to copy the backslash */
+ --p;
+#endif /* HAVE_DOS_PATHS */
+
+ /* Copy and skip the following char. */
+ *ap++ = *++p;
+ }
+ break;
+
+ case '\'':
+ case '"':
+ instring = *p;
+ break;
+
+ case '\n':
+ if (restp != NULL)
+ {
+ /* End of the command line. */
+ *restp = p;
+ goto end_of_line;
+ }
+ else
+ /* Newlines are not special. */
+ *ap++ = '\n';
+ break;
+
+ case ' ':
+ case '\t':
+ /* We have the end of an argument.
+ Terminate the text of the argument. */
+ *ap++ = '\0';
+ new_argv[++i] = ap;
+ last_argument_was_empty = 0;
+
+ /* Update SEEN_NONEQUALS, which tells us if every word
+ heretofore has contained an '='. */
+ seen_nonequals |= ! word_has_equals;
+ if (word_has_equals && ! seen_nonequals)
+ /* An '=' in a word before the first
+ word without one is magical. */
+ goto slow;
+ word_has_equals = 0; /* Prepare for the next word. */
+
+ /* If this argument is the command name,
+ see if it is a built-in shell command.
+ If so, have the shell handle it. */
+ if (i == 1)
+ {
+ register int j;
+ for (j = 0; sh_cmds[j] != 0; ++j)
+ {
+ if (streq (sh_cmds[j], new_argv[0]))
+ goto slow;
+#if defined(__EMX__) || defined(WINDOWS32)
+ /* Non-Unix shells are case insensitive. */
+ if (!unixy_shell
+ && strcasecmp (sh_cmds[j], new_argv[0]) == 0)
+ goto slow;
+#endif
+ }
+ }
+
+ /* Skip whitespace chars, but not newlines. */
+ while (ISBLANK (p[1]))
+ ++p;
+ break;
+
+ default:
+ *ap++ = *p;
+ break;
+ }
+ }
+ end_of_line:
+
+ if (instring)
+ /* Let the shell deal with an unterminated quote. */
+ goto slow;
+
+ /* Terminate the last argument and the argument list. */
+
+ *ap = '\0';
+ if (new_argv[i][0] != '\0' || last_argument_was_empty)
+ ++i;
+ new_argv[i] = 0;
+
+ if (i == 1)
+ {
+ register int j;
+ for (j = 0; sh_cmds[j] != 0; ++j)
+ if (streq (sh_cmds[j], new_argv[0]))
+ goto slow;
+ }
+
+ if (new_argv[0] == 0)
+ {
+ /* Line was empty. */
+ free (argstr);
+ free (new_argv);
+ return 0;
+ }
+
+ return new_argv;
+
+ slow:;
+ /* We must use the shell. */
+
+ if (new_argv != 0)
+ {
+ /* Free the old argument list we were working on. */
+ free (argstr);
+ free (new_argv);
+ }
+
+#ifdef __MSDOS__
+ execute_by_shell = 1; /* actually, call 'system' if shell isn't unixy */
+#endif
+
+#ifdef _AMIGA
+ {
+ char *ptr;
+ char *buffer;
+ char *dptr;
+
+ buffer = xmalloc (strlen (line)+1);
+
+ ptr = line;
+ for (dptr=buffer; *ptr; )
+ {
+ if (*ptr == '\\' && ptr[1] == '\n')
+ ptr += 2;
+ else if (*ptr == '@') /* Kludge: multiline commands */
+ {
+ ptr += 2;
+ *dptr++ = '\n';
+ }
+ else
+ *dptr++ = *ptr++;
+ }
+ *dptr = 0;
+
+ new_argv = xmalloc (2 * sizeof (char *));
+ new_argv[0] = buffer;
+ new_argv[1] = 0;
+ }
+#else /* Not Amiga */
+#ifdef WINDOWS32
+ /*
+ * Not eating this whitespace caused things like
+ *
+ * sh -c "\n"
+ *
+ * which gave the shell fits. I think we have to eat
+ * whitespace here, but this code should be considered
+ * suspicious if things start failing....
+ */
+
+ /* Make sure not to bother processing an empty line. */
+ NEXT_TOKEN (line);
+ if (*line == '\0')
+ return 0;
+#endif /* WINDOWS32 */
+
+ {
+ /* SHELL may be a multi-word command. Construct a command line
+ "$(SHELL) $(.SHELLFLAGS) LINE", with all special chars in LINE escaped.
+ Then recurse, expanding this command line to get the final
+ argument list. */
+
+ char *new_line;
+ unsigned int shell_len = strlen (shell);
+ unsigned int line_len = strlen (line);
+ unsigned int sflags_len = shellflags ? strlen (shellflags) : 0;
+#ifdef WINDOWS32
+ char *command_ptr = NULL; /* used for batch_mode_shell mode */
+#endif
+
+# ifdef __EMX__ /* is this necessary? */
+ if (!unixy_shell && shellflags)
+ ((char *)shellflags)[0] = '/'; /* "/c" */
+# endif
+
+ /* In .ONESHELL mode we are allowed to throw the entire current
+ recipe string at a single shell and trust that the user
+ has configured the shell and shell flags, and formatted
+ the string, appropriately. */
+ if (one_shell)
+ {
+ /* If the shell is Bourne compatible, we must remove and ignore
+ interior special chars [@+-] because they're meaningless to
+ the shell itself. If, however, we're in .ONESHELL mode and
+ have changed SHELL to something non-standard, we should
+ leave those alone because they could be part of the
+ script. In this case we must also leave in place
+ any leading [@+-] for the same reason. */
+
+ /* Remove and ignore interior prefix chars [@+-] because they're
+ meaningless given a single shell. */
+#if defined __MSDOS__ || defined (__EMX__)
+ if (unixy_shell) /* the test is complicated and we already did it */
+#else
+ if (is_bourne_compatible_shell (shell)
+#ifdef WINDOWS32
+ /* If we didn't find any sh.exe, don't behave is if we did! */
+ && !no_default_sh_exe
+#endif
+ )
+#endif
+ {
+ const char *f = line;
+ char *t = line;
+
+ /* Copy the recipe, removing and ignoring interior prefix chars
+ [@+-]: they're meaningless in .ONESHELL mode. */
+ while (f[0] != '\0')
+ {
+ int esc = 0;
+
+ /* This is the start of a new recipe line. Skip whitespace
+ and prefix characters but not newlines. */
+#ifndef CONFIG_WITH_COMMANDS_FUNC
+ while (ISBLANK (*f) || *f == '-' || *f == '@' || *f == '+')
+#else
+ char ch;
+ while (ISBLANK ((ch = *f)) || ch == '-' || ch == '@' || ch == '+' || ch == '%')
+#endif
+ ++f;
+
+ /* Copy until we get to the next logical recipe line. */
+ while (*f != '\0')
+ {
+ *(t++) = *(f++);
+ if (f[-1] == '\\')
+ esc = !esc;
+ else
+ {
+ /* On unescaped newline, we're done with this line. */
+ if (f[-1] == '\n' && ! esc)
+ break;
+
+ /* Something else: reset the escape sequence. */
+ esc = 0;
+ }
+ }
+ }
+ *t = '\0';
+ }
+#ifdef WINDOWS32
+ else /* non-Posix shell (cmd.exe etc.) */
+ {
+ const char *f = line;
+ char *t = line;
+ char *tstart = t;
+ int temp_fd;
+ FILE* batch = NULL;
+ int id = GetCurrentProcessId ();
+ PATH_VAR(fbuf);
+
+ /* Generate a file name for the temporary batch file. */
+ sprintf (fbuf, "make%d", id);
+ *batch_filename = create_batch_file (fbuf, 0, &temp_fd);
+ DB (DB_JOBS, (_("Creating temporary batch file %s\n"),
+ *batch_filename));
+
+ /* Create a FILE object for the batch file, and write to it the
+ commands to be executed. Put the batch file in TEXT mode. */
+ _setmode (temp_fd, _O_TEXT);
+ batch = _fdopen (temp_fd, "wt");
+ fputs ("@echo off\n", batch);
+ DB (DB_JOBS, (_("Batch file contents:\n\t@echo off\n")));
+
+ /* Copy the recipe, removing and ignoring interior prefix chars
+ [@+-]: they're meaningless in .ONESHELL mode. */
+ while (*f != '\0')
+ {
+ /* This is the start of a new recipe line. Skip whitespace
+ and prefix characters but not newlines. */
+#ifndef CONFIG_WITH_COMMANDS_FUNC
+ while (ISBLANK (*f) || *f == '-' || *f == '@' || *f == '+')
+#else
+ char ch;
+ while (ISBLANK ((ch = *f)) || ch == '-' || ch == '@' || ch == '+' || ch == '%')
+#endif
+ ++f;
+
+ /* Copy until we get to the next logical recipe line. */
+ while (*f != '\0')
+ {
+ /* Remove the escaped newlines in the command, and the
+ blanks that follow them. Windows shells cannot handle
+ escaped newlines. */
+ if (*f == '\\' && f[1] == '\n')
+ {
+ f += 2;
+ while (ISBLANK (*f))
+ ++f;
+ }
+ *(t++) = *(f++);
+ /* On an unescaped newline, we're done with this
+ line. */
+ if (f[-1] == '\n')
+ break;
+ }
+ /* Write another line into the batch file. */
+ if (t > tstart)
+ {
+ char c = *t;
+ *t = '\0';
+ fputs (tstart, batch);
+ DB (DB_JOBS, ("\t%s", tstart));
+ tstart = t;
+ *t = c;
+ }
+ }
+ DB (DB_JOBS, ("\n"));
+ fclose (batch);
+
+ /* Create an argv list for the shell command line that
+ will run the batch file. */
+ new_argv = xmalloc (2 * sizeof (char *));
+ new_argv[0] = xstrdup (*batch_filename);
+ new_argv[1] = NULL;
+ return new_argv;
+ }
+#endif /* WINDOWS32 */
+ /* Create an argv list for the shell command line. */
+ {
+ int n = 0;
+
+ new_argv = xmalloc ((4 + sflags_len/2) * sizeof (char *));
+ new_argv[n++] = xstrdup (shell);
+
+ /* Chop up the shellflags (if any) and assign them. */
+ if (! shellflags)
+ new_argv[n++] = xstrdup ("");
+ else
+ {
+ const char *s = shellflags;
+ char *t;
+ unsigned int len;
+ while ((t = find_next_token (&s, &len)) != 0)
+ new_argv[n++] = xstrndup (t, len);
+ }
+
+ /* Set the command to invoke. */
+ new_argv[n++] = line;
+ new_argv[n++] = NULL;
+ }
+ return new_argv;
+ }
+
+ new_line = xmalloc ((shell_len*2) + 1 + sflags_len + 1
+ + (line_len*2) + 1);
+ ap = new_line;
+ /* Copy SHELL, escaping any characters special to the shell. If
+ we don't escape them, construct_command_argv_internal will
+ recursively call itself ad nauseam, or until stack overflow,
+ whichever happens first. */
+ for (cp = shell; *cp != '\0'; ++cp)
+ {
+ if (strchr (sh_chars, *cp) != 0)
+ *(ap++) = '\\';
+ *(ap++) = *cp;
+ }
+ *(ap++) = ' ';
+ if (shellflags)
+ memcpy (ap, shellflags, sflags_len);
+ ap += sflags_len;
+ *(ap++) = ' ';
+#ifdef WINDOWS32
+ command_ptr = ap;
+#endif
+ for (p = line; *p != '\0'; ++p)
+ {
+ if (restp != NULL && *p == '\n')
+ {
+ *restp = p;
+ break;
+ }
+ else if (*p == '\\' && p[1] == '\n')
+ {
+ /* POSIX says we keep the backslash-newline. If we don't have a
+ POSIX shell on DOS/Windows/OS2, mimic the pre-POSIX behavior
+ and remove the backslash/newline. */
+#if defined (__MSDOS__) || defined (__EMX__) || defined (WINDOWS32)
+# define PRESERVE_BSNL unixy_shell
+#else
+# define PRESERVE_BSNL 1
+#endif
+ if (PRESERVE_BSNL)
+ {
+ *(ap++) = '\\';
+ /* Only non-batch execution needs another backslash,
+ because it will be passed through a recursive
+ invocation of this function. */
+ if (!batch_mode_shell)
+ *(ap++) = '\\';
+ *(ap++) = '\n';
+ }
+ ++p;
+ continue;
+ }
+
+ /* DOS shells don't know about backslash-escaping. */
+ if (unixy_shell && !batch_mode_shell &&
+ (*p == '\\' || *p == '\'' || *p == '"'
+ || ISSPACE (*p)
+ || strchr (sh_chars, *p) != 0))
+ *ap++ = '\\';
+#ifdef __MSDOS__
+ else if (unixy_shell && strneq (p, "...", 3))
+ {
+ /* The case of '...' wildcard again. */
+ strcpy (ap, "\\.\\.\\");
+ ap += 5;
+ p += 2;
+ }
+#endif
+ *ap++ = *p;
+ }
+ if (ap == new_line + shell_len + sflags_len + 2)
+ {
+ /* Line was empty. */
+ free (new_line);
+ return 0;
+ }
+ *ap = '\0';
+
+#ifdef WINDOWS32
+ /* Some shells do not work well when invoked as 'sh -c xxx' to run a
+ command line (e.g. Cygnus GNUWIN32 sh.exe on WIN32 systems). In these
+ cases, run commands via a script file. */
+ if (just_print_flag && !(flags & COMMANDS_RECURSE))
+ {
+ /* Need to allocate new_argv, although it's unused, because
+ start_job_command will want to free it and its 0'th element. */
+ new_argv = xmalloc (2 * sizeof (char *));
+ new_argv[0] = xstrdup ("");
+ new_argv[1] = NULL;
+ }
+ else if ((no_default_sh_exe || batch_mode_shell) && batch_filename)
+ {
+ int temp_fd;
+ FILE* batch = NULL;
+ int id = GetCurrentProcessId ();
+ PATH_VAR (fbuf);
+
+ /* create a file name */
+ sprintf (fbuf, "make%d", id);
+ *batch_filename = create_batch_file (fbuf, unixy_shell, &temp_fd);
+
+ DB (DB_JOBS, (_("Creating temporary batch file %s\n"),
+ *batch_filename));
+
+ /* Create a FILE object for the batch file, and write to it the
+ commands to be executed. Put the batch file in TEXT mode. */
+ _setmode (temp_fd, _O_TEXT);
+ batch = _fdopen (temp_fd, "wt");
+ if (!unixy_shell)
+ fputs ("@echo off\n", batch);
+ fputs (command_ptr, batch);
+ fputc ('\n', batch);
+ fclose (batch);
+ DB (DB_JOBS, (_("Batch file contents:%s\n\t%s\n"),
+ !unixy_shell ? "\n\t@echo off" : "", command_ptr));
+
+ /* create argv */
+ new_argv = xmalloc (3 * sizeof (char *));
+ if (unixy_shell)
+ {
+ new_argv[0] = xstrdup (shell);
+ new_argv[1] = *batch_filename; /* only argv[0] gets freed later */
+ }
+ else
+ {
+ new_argv[0] = xstrdup (*batch_filename);
+ new_argv[1] = NULL;
+ }
+ new_argv[2] = NULL;
+ }
+ else
+#endif /* WINDOWS32 */
+
+ if (unixy_shell)
+ new_argv = construct_command_argv_internal (new_line, 0, 0, 0, 0,
+ flags, 0);
+
+#ifdef __EMX__
+ else if (!unixy_shell)
+ {
+ /* new_line is local, must not be freed therefore
+ We use line here instead of new_line because we run the shell
+ manually. */
+ size_t line_len = strlen (line);
+ char *p = new_line;
+ char *q = new_line;
+ memcpy (new_line, line, line_len + 1);
+ /* Replace all backslash-newline combination and also following tabs.
+ Important: stop at the first '\n' because that's what the loop above
+ did. The next line starting at restp[0] will be executed during the
+ next call of this function. */
+ while (*q != '\0' && *q != '\n')
+ {
+ if (q[0] == '\\' && q[1] == '\n')
+ q += 2; /* remove '\\' and '\n' */
+ else
+ *p++ = *q++;
+ }
+ *p = '\0';
+
+# ifndef NO_CMD_DEFAULT
+ if (strnicmp (new_line, "echo", 4) == 0
+ && (new_line[4] == ' ' || new_line[4] == '\t'))
+ {
+ /* the builtin echo command: handle it separately */
+ size_t echo_len = line_len - 5;
+ char *echo_line = new_line + 5;
+
+ /* special case: echo 'x="y"'
+ cmd works this way: a string is printed as is, i.e., no quotes
+ are removed. But autoconf uses a command like echo 'x="y"' to
+ determine whether make works. autoconf expects the output x="y"
+ so we will do exactly that.
+ Note: if we do not allow cmd to be the default shell
+ we do not need this kind of voodoo */
+ if (echo_line[0] == '\''
+ && echo_line[echo_len - 1] == '\''
+ && strncmp (echo_line + 1, "ac_maketemp=",
+ strlen ("ac_maketemp=")) == 0)
+ {
+ /* remove the enclosing quotes */
+ memmove (echo_line, echo_line + 1, echo_len - 2);
+ echo_line[echo_len - 2] = '\0';
+ }
+ }
+# endif
+
+ {
+ /* Let the shell decide what to do. Put the command line into the
+ 2nd command line argument and hope for the best ;-) */
+ size_t sh_len = strlen (shell);
+
+ /* exactly 3 arguments + NULL */
+ new_argv = xmalloc (4 * sizeof (char *));
+ /* Exactly strlen(shell) + strlen("/c") + strlen(line) + 3 times
+ the trailing '\0' */
+ new_argv[0] = xmalloc (sh_len + line_len + 5);
+ memcpy (new_argv[0], shell, sh_len + 1);
+ new_argv[1] = new_argv[0] + sh_len + 1;
+ memcpy (new_argv[1], "/c", 3);
+ new_argv[2] = new_argv[1] + 3;
+ memcpy (new_argv[2], new_line, line_len + 1);
+ new_argv[3] = NULL;
+ }
+ }
+#elif defined(__MSDOS__)
+ else
+ {
+ /* With MSDOS shells, we must construct the command line here
+ instead of recursively calling ourselves, because we
+ cannot backslash-escape the special characters (see above). */
+ new_argv = xmalloc (sizeof (char *));
+ line_len = strlen (new_line) - shell_len - sflags_len - 2;
+ new_argv[0] = xmalloc (line_len + 1);
+ strncpy (new_argv[0],
+ new_line + shell_len + sflags_len + 2, line_len);
+ new_argv[0][line_len] = '\0';
+ }
+#else
+ else
+ fatal (NILF, CSTRLEN (__FILE__) + INTSTR_LENGTH,
+ _("%s (line %d) Bad shell context (!unixy && !batch_mode_shell)\n"),
+ __FILE__, __LINE__);
+#endif
+
+ free (new_line);
+ }
+#endif /* ! AMIGA */
+
+ return new_argv;
+}
+#endif /* !VMS */
+
+/* Figure out the argument list necessary to run LINE as a command. Try to
+ avoid using a shell. This routine handles only ' quoting, and " quoting
+ when no backslash, $ or ' characters are seen in the quotes. Starting
+ quotes may be escaped with a backslash. If any of the characters in
+ sh_chars is seen, or any of the builtin commands listed in sh_cmds
+ is the first word of a line, the shell is used.
+
+ If RESTP is not NULL, *RESTP is set to point to the first newline in LINE.
+ If *RESTP is NULL, newlines will be ignored.
+
+ FILE is the target whose commands these are. It is used for
+ variable expansion for $(SHELL) and $(IFS). */
+
+char **
+construct_command_argv (char *line, char **restp, struct file *file,
+ int cmd_flags, char **batch_filename)
+{
+ char *shell, *ifs, *shellflags;
+ char **argv;
+
+#ifdef VMS
+ char *cptr;
+ int argc;
+
+ argc = 0;
+ cptr = line;
+ for (;;)
+ {
+ while ((*cptr != 0) && (ISSPACE (*cptr)))
+ cptr++;
+ if (*cptr == 0)
+ break;
+ while ((*cptr != 0) && (!ISSPACE (*cptr)))
+ cptr++;
+ argc++;
+ }
+
+ argv = xmalloc (argc * sizeof (char *));
+ if (argv == 0)
+ abort ();
+
+ cptr = line;
+ argc = 0;
+ for (;;)
+ {
+ while ((*cptr != 0) && (ISSPACE (*cptr)))
+ cptr++;
+ if (*cptr == 0)
+ break;
+ DB (DB_JOBS, ("argv[%d] = [%s]\n", argc, cptr));
+ argv[argc++] = cptr;
+ while ((*cptr != 0) && (!ISSPACE (*cptr)))
+ cptr++;
+ if (*cptr != 0)
+ *cptr++ = 0;
+ }
+#else
+ {
+ /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 0;
+
+ shell = allocated_variable_expand_for_file ("$(SHELL)", file);
+#ifdef WINDOWS32
+ /*
+ * Convert to forward slashes so that construct_command_argv_internal()
+ * is not confused.
+ */
+ if (shell)
+ {
+# if 1 /* bird */
+ unix_slashes (shell);
+# else
+ char *p = w32ify (shell, 0);
+ strcpy (shell, p);
+# endif
+ }
+#endif
+#ifdef __EMX__
+ {
+ static const char *unixroot = NULL;
+ static const char *last_shell = "";
+ static int init = 0;
+ if (init == 0)
+ {
+ unixroot = getenv ("UNIXROOT");
+ /* unixroot must be NULL or not empty */
+ if (unixroot && unixroot[0] == '\0') unixroot = NULL;
+ init = 1;
+ }
+
+ /* if we have an unixroot drive and if shell is not default_shell
+ (which means it's either cmd.exe or the test has already been
+ performed) and if shell is an absolute path without drive letter,
+ try whether it exists e.g.: if "/bin/sh" does not exist use
+ "$UNIXROOT/bin/sh" instead. */
+ if (unixroot && shell && strcmp (shell, last_shell) != 0
+ && (shell[0] == '/' || shell[0] == '\\'))
+ {
+ /* trying a new shell, check whether it exists */
+ size_t size = strlen (shell);
+ char *buf = xmalloc (size + 7);
+ memcpy (buf, shell, size);
+ memcpy (buf + size, ".exe", 5); /* including the trailing '\0' */
+ if (access (shell, F_OK) != 0 && access (buf, F_OK) != 0)
+ {
+ /* try the same for the unixroot drive */
+ memmove (buf + 2, buf, size + 5);
+ buf[0] = unixroot[0];
+ buf[1] = unixroot[1];
+ if (access (buf, F_OK) == 0)
+ /* we have found a shell! */
+ /* free(shell); */
+ shell = buf;
+ else
+ free (buf);
+ }
+ else
+ free (buf);
+ }
+ }
+#endif /* __EMX__ */
+
+ shellflags = allocated_variable_expand_for_file ("$(.SHELLFLAGS)", file);
+ ifs = allocated_variable_expand_for_file ("$(IFS)", file);
+
+ warn_undefined_variables_flag = save;
+ }
+
+# ifdef CONFIG_WITH_KMK_BUILTIN
+ /* If it's a kmk_builtin command, make sure we're treated like a
+ unix shell and and don't get batch files. */
+ if ( ( !unixy_shell
+ || batch_mode_shell
+# ifdef WINDOWS32
+ || no_default_sh_exe
+# endif
+ )
+ && line
+ && !strncmp (line, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
+ {
+ int saved_batch_mode_shell = batch_mode_shell;
+ int saved_unixy_shell = unixy_shell;
+# ifdef WINDOWS32
+ int saved_no_default_sh_exe = no_default_sh_exe;
+ no_default_sh_exe = 0;
+# endif
+ unixy_shell = 1;
+ batch_mode_shell = 0;
+ argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs,
+ cmd_flags, batch_filename);
+ batch_mode_shell = saved_batch_mode_shell;
+ unixy_shell = saved_unixy_shell;
+# ifdef WINDOWS32
+ no_default_sh_exe = saved_no_default_sh_exe;
+# endif
+ }
+ else
+# endif /* CONFIG_WITH_KMK_BUILTIN */
+ argv = construct_command_argv_internal (line, restp, shell, shellflags, ifs,
+ cmd_flags, batch_filename);
+
+ free (shell);
+ free (shellflags);
+ free (ifs);
+#endif /* !VMS */
+ return argv;
+}
+
+#if !defined(HAVE_DUP2) && !defined(_AMIGA)
+int
+dup2 (int old, int new)
+{
+ int fd;
+
+ (void) close (new);
+ EINTRLOOP (fd, dup (old));
+ if (fd != new)
+ {
+ (void) close (fd);
+ errno = EMFILE;
+ return -1;
+ }
+
+ return fd;
+}
+#endif /* !HAVE_DUP2 && !_AMIGA */
+
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+/* Prints the time elapsed while executing the commands for the given job. */
+void print_job_time (struct child *c)
+{
+ if ( !handling_fatal_signal
+ && print_time_min != -1
+ && c->start_ts != -1)
+ {
+ big_int elapsed = nano_timestamp () - c->start_ts;
+ if (elapsed >= print_time_min * BIG_INT_C(1000000000))
+ {
+ char buf[64];
+ int len = format_elapsed_nano (buf, sizeof (buf), elapsed);
+ if (len > print_time_width)
+ print_time_width = len;
+ message (1, print_time_width + strlen (c->file->name),
+ _("%*s - %s"), print_time_width, buf, c->file->name);
+ }
+ }
+}
+#endif
+
+/* On VMS systems, include special VMS functions. */
+
+#ifdef VMS
+#include "vmsjobs.c"
+#endif
diff --git a/src/kmk/job.h b/src/kmk/job.h
new file mode 100644
index 0000000..5cd25c2
--- /dev/null
+++ b/src/kmk/job.h
@@ -0,0 +1,175 @@
+/* Definitions for managing subprocesses in GNU Make.
+Copyright (C) 1992-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "output.h"
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+/* How to set close-on-exec for a file descriptor. */
+
+#if !defined(F_SETFD) || !defined(F_GETFD)
+# ifdef WINDOWS32
+# define CLOSE_ON_EXEC(_d) process_noinherit(_d)
+# else
+# define CLOSE_ON_EXEC(_d)
+# endif
+#else
+# ifndef FD_CLOEXEC
+# define FD_CLOEXEC 1
+# endif
+# define CLOSE_ON_EXEC(_d) (void) fcntl ((_d), F_SETFD, FD_CLOEXEC)
+#endif
+
+#ifdef NO_OUTPUT_SYNC
+# define RECORD_SYNC_MUTEX(m) \
+ O (error, NILF, \
+ _("-O[TYPE] (--output-sync[=TYPE]) is not configured for this build."));
+#else
+# ifdef WINDOWS32
+/* For emulations in w32/compat/posixfcn.c. */
+# define F_GETFD 1
+# define F_SETLKW 2
+/* Implementation note: None of the values of l_type below can be zero
+ -- they are compared with a static instance of the struct, so zero
+ means unknown/invalid, see w32/compat/posixfcn.c. */
+# define F_WRLCK 1
+# define F_UNLCK 2
+
+struct flock
+ {
+ short l_type;
+ short l_whence;
+ off_t l_start;
+ off_t l_len;
+ pid_t l_pid;
+ };
+
+/* This type is actually a HANDLE, but we want to avoid including
+ windows.h as much as possible. */
+typedef intptr_t sync_handle_t;
+
+/* Public functions emulated/provided in posixfcn.c. */
+int fcntl (intptr_t fd, int cmd, ...);
+# ifdef CONFIG_NEW_WIN_CHILDREN
+intptr_t create_mutex (char *mtxname, size_t size);
+# else
+intptr_t create_mutex (void);
+# endif
+int same_stream (FILE *f1, FILE *f2);
+
+# define RECORD_SYNC_MUTEX(m) record_sync_mutex(m)
+void record_sync_mutex (const char *str);
+# ifdef CONFIG_NEW_WIN_CHILDREN
+void prepare_mutex_handle_string (const char *mtxname);
+# else
+void prepare_mutex_handle_string (intptr_t hdl);
+# endif
+# else /* !WINDOWS32 */
+
+typedef int sync_handle_t; /* file descriptor */
+
+# define RECORD_SYNC_MUTEX(m) (void)(m)
+
+# endif
+#endif /* !NO_OUTPUT_SYNC */
+
+/* Structure describing a running or dead child process. */
+
+struct child
+ {
+ struct child *next; /* Link in the chain. */
+
+ struct file *file; /* File being remade. */
+
+ char **environment; /* Environment for commands. */
+ char *sh_batch_file; /* Script file for shell commands */
+ char **command_lines; /* Array of variable-expanded cmd lines. */
+ char *command_ptr; /* Ptr into command_lines[command_line]. */
+
+#ifdef VMS
+ char *comname; /* Temporary command file name */
+ int efn; /* Completion event flag number */
+ int cstatus; /* Completion status */
+ int vms_launch_status; /* non-zero if lib$spawn, etc failed */
+#endif
+
+ unsigned int command_line; /* Index into command_lines. */
+ struct output output; /* Output for this child. */
+ pid_t pid; /* Child process's ID number. */
+ unsigned int remote:1; /* Nonzero if executing remotely. */
+ unsigned int noerror:1; /* Nonzero if commands contained a '-'. */
+ unsigned int good_stdin:1; /* Nonzero if this child has a good stdin. */
+ unsigned int deleted:1; /* Nonzero if targets have been deleted. */
+ unsigned int recursive:1; /* Nonzero for recursive command ('+' etc.) */
+ unsigned int dontcare:1; /* Saved dontcare flag. */
+
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ unsigned int has_status:1; /* Nonzero if status is available. */
+ int status; /* Status of the job. */
+#endif
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ big_int start_ts; /* nano_timestamp of the first command. */
+#endif
+ };
+
+extern struct child *children;
+
+/* A signal handler for SIGCHLD, if needed. */
+RETSIGTYPE child_handler (int sig);
+int is_bourne_compatible_shell(const char *path);
+void new_job (struct file *file);
+void reap_children (int block, int err);
+void start_waiting_jobs (void);
+
+char **construct_command_argv (char *line, char **restp, struct file *file,
+ int cmd_flags, char** batch_file);
+
+#ifdef VMS
+int child_execute_job (struct child *child, char *argv);
+#else
+# define FD_STDIN (fileno (stdin))
+# define FD_STDOUT (fileno (stdout))
+# define FD_STDERR (fileno (stderr))
+int child_execute_job (struct output *out, int good_stdin, char **argv, char **envp);
+#endif
+
+#ifdef _AMIGA
+void exec_command (char **argv) __attribute__ ((noreturn));
+#elif defined(__EMX__)
+int exec_command (char **argv, char **envp);
+#elif !defined(WINDOWS32) || !defined(CONFIG_NEW_WIN_CHILDREN)
+void exec_command (char **argv, char **envp) __attribute__ ((noreturn));
+#endif
+
+extern unsigned int job_slots_used;
+
+void block_sigs (void);
+#ifdef POSIX
+void unblock_sigs (void);
+#else
+#ifdef HAVE_SIGSETMASK
+extern int fatal_signal_mask;
+#define unblock_sigs() sigsetmask (0)
+#else
+#define unblock_sigs()
+#endif
+#endif
+
+extern unsigned int jobserver_tokens;
diff --git a/src/kmk/kbuild-object.c b/src/kmk/kbuild-object.c
new file mode 100644
index 0000000..e295636
--- /dev/null
+++ b/src/kmk/kbuild-object.c
@@ -0,0 +1,1409 @@
+/* $Id: kbuild-object.c 3141 2018-03-14 21:58:32Z bird $ */
+/** @file
+ * kBuild objects.
+ */
+
+/*
+ * Copyright (c) 2011-2014 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* No GNU coding style here! */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include "filedef.h"
+#include "variable.h"
+#include "dep.h"
+#include "debug.h"
+#include "kbuild.h"
+
+#include <assert.h>
+#include <stdarg.h>
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define WORD_IS(a_pszWord, a_cchWord, a_szWord2) \
+ ( (a_cchWord) == sizeof(a_szWord2) - 1 && memcmp((a_pszWord), a_szWord2, sizeof(a_szWord2) - 1) == 0)
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/** kBuild object type. */
+enum kBuildType
+{
+ kBuildType_Invalid,
+ kBuildType_Target,
+ kBuildType_Template,
+ kBuildType_Tool,
+ kBuildType_Sdk,
+ kBuildType_Unit
+};
+
+enum kBuildSeverity
+{
+ kBuildSeverity_Warning,
+ kBuildSeverity_Error,
+ kBuildSeverity_Fatal
+};
+
+
+/**
+ * kBuild object data.
+ */
+struct kbuild_object
+{
+ /** The object type. */
+ enum kBuildType enmType;
+ /** Object name length. */
+ size_t cchName;
+ /** The bare name of the define. */
+ char *pszName;
+ /** The file location where this define was declared. */
+ floc FileLoc;
+
+ /** Pointer to the next element in the global list. */
+ struct kbuild_object *pGlobalNext;
+
+ /** The variable set associated with this define. */
+ struct variable_set_list *pVariables;
+
+ /** The parent name, NULL if none. */
+ char *pszParent;
+ /** The length of the parent name. */
+ size_t cchParent;
+ /** Pointer to the parent. Resolved lazily, so it can be NULL even if we
+ * have a parent. */
+ struct kbuild_object *pParent;
+
+ /** The template, NULL if none. Only applicable to targets. Only covers the
+ * primary template, not target or type specific templates.
+ * @todo not sure if this is really necessary. */
+ char const *pszTemplate;
+
+ /** The variable prefix. */
+ char *pszVarPrefix;
+ /** The length of the variable prefix. */
+ size_t cchVarPrefix;
+};
+
+
+/**
+ * The data we stack during eval.
+ */
+struct kbuild_eval_data
+{
+ /** Pointer to the element below us on the stack. */
+ struct kbuild_eval_data *pStackDown;
+ /** Pointer to the object. */
+ struct kbuild_object *pObj;
+ /** The saved current variable set, for restoring in kBuild-endef. */
+ struct variable_set_list *pVariablesSaved;
+};
+
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/** Linked list (LIFO) of kBuild defines.
+ * @todo use a hash! */
+static struct kbuild_object *g_pHeadKbObjs = NULL;
+/** Stack of kBuild evalutation contexts.
+ * This is for dealing with potential recursive kBuild object definition,
+ * generally believed to only happen via $(eval ) or include similar. */
+struct kbuild_eval_data *g_pTopKbEvalData = NULL;
+
+/** Cached variable name '_TEMPLATE'. */
+static const char *g_pszVarNmTemplate = NULL;
+
+/** Zero if compatibility mode is disabled, non-zero if enabled.
+ * If explicitily enabled, the value will be greater than 1. */
+int g_fKbObjCompMode = 1;
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static struct kbuild_object *
+resolve_kbuild_object_parent(struct kbuild_object *pObj, int fQuiet);
+static struct kbuild_object *
+get_kbuild_object_parent(struct kbuild_object *pObj, enum kBuildSeverity enmSeverity);
+
+static struct kbuild_object *
+parse_kbuild_object_variable_accessor(const char *pchExpr, size_t cchExpr,
+ enum kBuildSeverity enmSeverity, const floc *pFileLoc,
+ const char **ppchVarNm, size_t *pcchVarNm, enum kBuildType *penmType);
+
+
+/**
+ * Initializes the kBuild object stuff.
+ *
+ * Requires the variable_cache to be initialized.
+ */
+void init_kbuild_object(void)
+{
+ g_pszVarNmTemplate = strcache2_add(&variable_strcache, STRING_SIZE_TUPLE("_TEMPLATE"));
+}
+
+
+/**
+ * Reports a problem with dynamic severity level.
+ *
+ * @param enmSeverity The severity level.
+ * @param pFileLoc The file location.
+ * @param pszFormat The format string.
+ * @param ... Arguments for the format string.
+ */
+static void kbuild_report_problem(enum kBuildSeverity enmSeverity, const floc *pFileLoc,
+ const char *pszFormat, ...)
+{
+ char szBuf[8192];
+ va_list va;
+
+ va_start(va, pszFormat);
+#ifdef _MSC_VER
+ _vsnprintf(szBuf, sizeof(szBuf), pszFormat, va);
+#else
+ vsnprintf(szBuf, sizeof(szBuf), pszFormat, va);
+#endif
+ va_end(va);
+
+ switch (enmSeverity)
+ {
+ case kBuildSeverity_Warning:
+ OS(message, 0, "%s", szBuf);
+ break;
+ case kBuildSeverity_Error:
+ OS(error, pFileLoc, "%s", szBuf);
+ break;
+ default:
+ case kBuildSeverity_Fatal:
+ OS(fatal, pFileLoc, "%s", szBuf);
+ break;
+ }
+}
+
+
+static const char *
+eval_kbuild_type_to_string(enum kBuildType enmType)
+{
+ switch (enmType)
+ {
+ case kBuildType_Target: return "target";
+ case kBuildType_Template: return "template";
+ case kBuildType_Tool: return "tool";
+ case kBuildType_Sdk: return "sdk";
+ case kBuildType_Unit: return "unit";
+ default:
+ case kBuildType_Invalid: return "invalid";
+ }
+}
+
+/**
+ * Gets the length of the string representation of the given type.
+ *
+ * @returns The string length.
+ * @param enmType The kBuild object type in question.
+ */
+static unsigned
+eval_kbuild_type_to_string_length(enum kBuildType enmType)
+{
+ switch (enmType)
+ {
+ case kBuildType_Target: return sizeof("target") - 1;
+ case kBuildType_Template: return sizeof("template") - 1;
+ case kBuildType_Tool: return sizeof("tool") - 1;
+ case kBuildType_Sdk: return sizeof("sdk") - 1;
+ case kBuildType_Unit: return sizeof("unit") - 1;
+ default:
+ case kBuildType_Invalid: return sizeof("invalid") - 1;
+ }
+}
+
+/**
+ * Converts a string into an kBuild object type.
+ *
+ * @returns The type on success, kBuildType_Invalid on failure.
+ * @param pchWord The pchWord. Not necessarily zero terminated.
+ * @param cchWord The length of the word.
+ */
+static enum kBuildType
+eval_kbuild_type_from_string(const char *pchWord, size_t cchWord)
+{
+ if (cchWord >= 3)
+ {
+ if (*pchWord == 't')
+ {
+ if (WORD_IS(pchWord, cchWord, "target"))
+ return kBuildType_Target;
+ if (WORD_IS(pchWord, cchWord, "template"))
+ return kBuildType_Template;
+ if (WORD_IS(pchWord, cchWord, "tool"))
+ return kBuildType_Tool;
+ }
+ else
+ {
+ if (WORD_IS(pchWord, cchWord, "sdk"))
+ return kBuildType_Sdk;
+ if (WORD_IS(pchWord, cchWord, "unit"))
+ return kBuildType_Unit;
+ }
+ }
+
+ return kBuildType_Invalid;
+}
+
+
+
+#if 0 /* unused */
+/**
+ * Helper function for caching variable name strings.
+ *
+ * @returns The string cache variable name.
+ * @param pszName The variable name.
+ * @param ppszCache Cache variable, static or global. Initialize to
+ * NULL.
+ */
+static const char *
+kbuild_variable_name(const char *pszName, const char **ppszCache)
+{
+ const char *pszRet = *ppszCache;
+ if (!pszRet)
+ *ppszCache = pszRet = strcache2_add(&variable_strcache, pszName, strlen(pszName));
+ return pszRet;
+}
+#endif
+
+static struct kbuild_object *
+lookup_kbuild_object(enum kBuildType enmType, const char *pchName, size_t cchName)
+{
+ /* Linear lookup for now. */
+ struct kbuild_object *pCur = g_pHeadKbObjs;
+ while (pCur)
+ {
+ if ( pCur->enmType == enmType
+ && pCur->cchName == cchName
+ && !memcmp(pCur->pszName, pchName, cchName))
+ return pCur;
+ pCur = pCur->pGlobalNext;
+ }
+ return NULL;
+}
+
+
+/** @name Defining and modifying variables
+ * @{
+ */
+
+/**
+ * Checks if the variable name is valid.
+ *
+ * @returns 1 if valid, 0 if not.
+ * @param pchName The variable name.
+ * @param cchName The length of the variable name.
+ */
+static int
+is_valid_kbuild_object_variable_name(const char *pchName, size_t cchName)
+{
+ if (cchName > 0)
+ {
+ if (!memchr(pchName, '[', cchName))
+ {
+ /** @todo more? */
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static const char *
+kbuild_replace_special_accessors(const char *pchValue, size_t *pcchValue, int *pfDuplicateValue,
+ const floc *pFileLoc)
+{
+ size_t cchValue = *pcchValue;
+ size_t cbAllocated = *pfDuplicateValue ? 0 : cchValue + 1;
+
+ /*
+ * Loop thru each potential special accessor occurance in the string.
+ *
+ * Unfortunately, we don't have a strnstr function in the C library, so
+ * we'll using memchr and doing a few more rounds in this loop.
+ */
+ size_t cchLeft = cchValue;
+ char *pchLeft = (char *)pchValue;
+ for (;;)
+ {
+ int fSuper;
+ char *pch = (char *)memchr(pchLeft, '$', cchLeft);
+ if (!pch)
+ break;
+
+ pch++;
+ cchLeft -= pch - pchLeft;
+ pchLeft = pch;
+
+ /* [@self] is the shorter, quit if there isn't enough room for even it. */
+ if (cchLeft < sizeof("([@self]") - 1)
+ break;
+
+ /* We don't care how many dollars there are in front of a special accessor. */
+ if (*pchLeft == '$')
+ {
+ do
+ {
+ cchLeft--;
+ pchLeft++;
+ } while (cchLeft >= sizeof("([@self]") - 1 && *pchLeft == '$');
+ if (cchLeft < sizeof("([@self]") - 1)
+ break;
+ }
+
+ /* Is it a special accessor? */
+ if ( pchLeft[2] != '@'
+ || pchLeft[1] != '['
+ || pchLeft[0] != '(')
+ continue;
+ pchLeft += 2;
+ cchLeft -= 2;
+ if (!memcmp(pchLeft, STRING_SIZE_TUPLE("@self]")))
+ fSuper = 0;
+ else if ( cchLeft >= sizeof("@super]")
+ && !memcmp(pchLeft, STRING_SIZE_TUPLE("@super]")))
+ fSuper = 1;
+ else
+ continue;
+
+ /*
+ * We've got something to replace. First figure what with and then
+ * resize the value buffer.
+ */
+ if (g_pTopKbEvalData)
+ {
+ struct kbuild_object *pObj = g_pTopKbEvalData->pObj;
+ size_t const cchSpecial = fSuper ? sizeof("@super") - 1 : sizeof("@self") - 1;
+ size_t cchName;
+ size_t cchType;
+ long cchDelta;
+ const char *pszName;
+
+ if (fSuper)
+ {
+ pObj = get_kbuild_object_parent(pObj, kBuildSeverity_Error);
+ if (!pObj)
+ continue;
+ }
+ pszName = pObj->pszName;
+ cchName = pObj->cchName;
+ cchType = eval_kbuild_type_to_string_length(pObj->enmType);
+ cchDelta = cchType + 1 + cchName - cchSpecial;
+
+ if (cchValue + cchDelta >= cbAllocated)
+ {
+ size_t offLeft = pchLeft - pchValue;
+ char *pszNewValue;
+
+ cbAllocated = cchValue + cchDelta + 1;
+ if (cchValue < 1024)
+ cbAllocated = (cbAllocated + 31) & ~(size_t)31;
+ else
+ cbAllocated = (cbAllocated + 255) & ~(size_t)255;
+ pszNewValue = (char *)xmalloc(cbAllocated);
+
+ memcpy(pszNewValue, pchValue, offLeft);
+ memcpy(pszNewValue + offLeft + cchSpecial + cchDelta,
+ pchLeft + cchSpecial,
+ cchLeft - cchSpecial + 1);
+
+ if (*pfDuplicateValue == 0)
+ free((char *)pchValue);
+ else
+ *pfDuplicateValue = 0;
+
+ pchValue = pszNewValue;
+ pchLeft = pszNewValue + offLeft;
+ }
+ else
+ {
+ assert(*pfDuplicateValue == 0);
+ memmove(pchLeft + cchSpecial + cchDelta,
+ pchLeft + cchSpecial,
+ cchLeft - cchSpecial + 1);
+ }
+
+ cchLeft += cchDelta;
+ cchValue += cchDelta;
+ *pcchValue = cchValue;
+
+ memcpy(pchLeft, eval_kbuild_type_to_string(pObj->enmType), cchType);
+ pchLeft += cchType;
+ *pchLeft++ = '@';
+ memcpy(pchLeft, pszName, cchName);
+ pchLeft += cchName;
+ cchLeft -= cchType + 1 + cchName;
+ }
+ else
+ error(pFileLoc, 20, _("The '$([%.*s...' accessor can only be used in the context of a kBuild object"),
+ (int)MIN(cchLeft, 20), pchLeft);
+ }
+
+ return pchValue;
+}
+
+static struct variable *
+define_kbuild_object_variable_cached(struct kbuild_object *pObj, const char *pszName,
+ const char *pchValue, size_t cchValue,
+ int fDuplicateValue, enum variable_origin enmOrigin,
+ int fRecursive, int fNoSpecialAccessors, const floc *pFileLoc)
+{
+ struct variable *pVar;
+ size_t cchName = strcache2_get_len(&variable_strcache, pszName);
+
+ if (fRecursive && !fNoSpecialAccessors)
+ pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);
+
+ pVar = define_variable_in_set(pszName, cchName,
+ pchValue, cchValue, fDuplicateValue,
+ enmOrigin, fRecursive,
+ pObj->pVariables->set,
+ pFileLoc);
+
+ /* Single underscore prefixed variables gets a global alias. */
+ if ( pszName[0] == '_'
+ && pszName[1] != '_'
+ && g_fKbObjCompMode)
+ {
+ struct variable *pAlias;
+ size_t cchPrefixed = pObj->cchVarPrefix + cchName;
+ char *pszPrefixed = xmalloc(cchPrefixed + 1);
+ memcpy(pszPrefixed, pObj->pszVarPrefix, pObj->cchVarPrefix);
+ memcpy(&pszPrefixed[pObj->cchVarPrefix], pszName, cchName);
+ pszPrefixed[cchPrefixed] = '\0';
+
+ pAlias = define_variable_alias_in_set(pszPrefixed, cchPrefixed, pVar, enmOrigin,
+ &global_variable_set, pFileLoc);
+ if (!pAlias->alias)
+ OS(error, pFileLoc, _("Error defining alias '%s'"), pszPrefixed);
+ }
+
+ return pVar;
+}
+
+#if 0
+struct variable *
+define_kbuild_object_variable(struct kbuild_object *pObj, const char *pchName, size_t cchName,
+ const char *pchValue, size_t cchValue,
+ int fDuplicateValue, enum variable_origin enmOrigin,
+ int fRecursive, const floc *pFileLoc)
+{
+ return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchName, cchName),
+ pchValue, cchValue,
+ fDuplicateValue, enmOrigin,
+ fRecursive, pFileLoc);
+}
+#endif
+
+/**
+ * Try define a kBuild object variable via a possible accessor
+ * ([type@object]var).
+ *
+ * @returns Pointer to the defined variable on success.
+ * @retval VAR_NOT_KBUILD_ACCESSOR if it isn't an accessor.
+ *
+ * @param pchName The variable name, not cached.
+ * @param cchName The variable name length. This will not be ~0U.
+ * @param pszValue The variable value. If @a fDuplicateValue is clear,
+ * this should be assigned as the actual variable
+ * value, otherwise it will be duplicated. In the
+ * latter case it might not be properly null
+ * terminated.
+ * @param cchValue The value length.
+ * @param fDuplicateValue Whether @a pszValue need to be duplicated on the
+ * heap or is already there.
+ * @param enmOrigin The variable origin.
+ * @param fRecursive Whether it's a recursive variable.
+ * @param pFileLoc The location of the variable definition.
+ */
+struct variable *
+try_define_kbuild_object_variable_via_accessor(const char *pchName, size_t cchName,
+ const char *pszValue, size_t cchValue, int fDuplicateValue,
+ enum variable_origin enmOrigin, int fRecursive,
+ floc const *pFileLoc)
+{
+ struct kbuild_object *pObj;
+ const char *pchVarNm;
+ size_t cchVarNm;
+
+ pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc,
+ &pchVarNm, &cchVarNm, NULL);
+ if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
+ {
+ assert(pObj != NULL);
+ if (!is_valid_kbuild_object_variable_name(pchVarNm, cchVarNm))
+ fatal(pFileLoc, cchVarNm + cchName, _("Invalid kBuild object variable name: '%.*s' ('%.*s')"),
+ (int)cchVarNm, pchVarNm, (int)cchName, pchName);
+ return define_kbuild_object_variable_cached(pObj, strcache2_add(&variable_strcache, pchVarNm, cchVarNm),
+ pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive,
+ 0 /*fNoSpecialAccessors*/, pFileLoc);
+ }
+
+ return VAR_NOT_KBUILD_ACCESSOR;
+}
+
+/**
+ * Define a kBuild object variable in the topmost kBuild object.
+ *
+ * This won't be an variable accessor.
+ *
+ * @returns Pointer to the defined variable on success.
+ *
+ * @param pchName The variable name, not cached.
+ * @param cchName The variable name length. This will not be ~0U.
+ * @param pszValue The variable value. If @a fDuplicateValue is clear,
+ * this should be assigned as the actual variable
+ * value, otherwise it will be duplicated. In the
+ * latter case it might not be properly null
+ * terminated.
+ * @param cchValue The value length.
+ * @param fDuplicateValue Whether @a pszValue need to be duplicated on the
+ * heap or is already there.
+ * @param enmOrigin The variable origin.
+ * @param fRecursive Whether it's a recursive variable.
+ * @param pFileLoc The location of the variable definition.
+ */
+struct variable *
+define_kbuild_object_variable_in_top_obj(const char *pchName, size_t cchName,
+ const char *pszValue, size_t cchValue, int fDuplicateValue,
+ enum variable_origin enmOrigin, int fRecursive,
+ floc const *pFileLoc)
+{
+ assert(g_pTopKbEvalData != NULL);
+
+ if (!is_valid_kbuild_object_variable_name(pchName, cchName))
+ fatal(pFileLoc, cchName, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName);
+
+ return define_kbuild_object_variable_cached(g_pTopKbEvalData->pObj, strcache2_add(&variable_strcache, pchName, cchName),
+ pszValue, cchValue, fDuplicateValue, enmOrigin, fRecursive,
+ 0 /*fNoSpecialAccessors*/, pFileLoc);
+}
+
+/**
+ * Implements appending and prepending to a kBuild object variable.
+ *
+ * The variable is either accessed thru an accessor or by the topmost kBuild
+ * object.
+ *
+ * @returns Pointer to the defined variable on success.
+ *
+ * @param pchName The variable name, not cached.
+ * @param cchName The variable name length. This will not be ~0U.
+ * @param pszValue The variable value. Must be duplicated.
+ * @param cchValue The value length.
+ * @param fSimpleValue Whether we've already figured that it's a simple
+ * value. This is for optimizing appending/prepending
+ * to an existing simple value variable.
+ * @param enmOrigin The variable origin.
+ * @param fAppend Append if set, prepend if clear.
+ * @param pFileLoc The location of the variable definition.
+ */
+struct variable *
+kbuild_object_variable_pre_append(const char *pchName, size_t cchName,
+ const char *pchValue, size_t cchValue, int fSimpleValue,
+ enum variable_origin enmOrigin, int fAppend,
+ const floc *pFileLoc)
+{
+ struct kbuild_object *pObj;
+ struct variable VarKey;
+
+ /*
+ * Resolve the relevant kBuild object first.
+ */
+ if (cchName > 3 && pchName[0] == '[')
+ {
+ const char *pchVarNm;
+ size_t cchVarNm;
+ pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Fatal, pFileLoc,
+ &pchVarNm, &cchVarNm, NULL);
+ if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
+ {
+ pchName = pchVarNm;
+ cchName = cchVarNm;
+ }
+ else
+ pObj = g_pTopKbEvalData->pObj;
+ }
+ else
+ pObj = g_pTopKbEvalData->pObj;
+
+ /*
+ * Make sure the variable name is valid. Raise fatal error if not.
+ */
+ if (!is_valid_kbuild_object_variable_name(pchName, cchName))
+ fatal(pFileLoc, cchName, _("Invalid kBuild object variable name: '%.*s'"), (int)cchName, pchName);
+
+ /*
+ * Get the cached name and look it up in the object's variables.
+ */
+ VarKey.name = strcache2_lookup(&variable_strcache, pchName, cchName);
+ if (VarKey.name)
+ {
+ struct variable *pVar;
+
+ VarKey.length = cchName;
+ pVar = (struct variable *)hash_find_item_strcached(&pObj->pVariables->set->table, &VarKey);
+ if (pVar)
+ {
+ /* Append/prepend to existing variable. */
+ int fDuplicateValue = 1;
+ if (pVar->recursive && !fSimpleValue)
+ pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);
+
+ pVar = do_variable_definition_append(pFileLoc, pVar, pchValue, cchValue, fSimpleValue, enmOrigin, fAppend);
+
+ if (fDuplicateValue == 0)
+ free((char *)pchValue);
+ return pVar;
+ }
+
+ /*
+ * Not found. Check ancestors if the 'override' directive isn't applied.
+ */
+ if (pObj->pszParent && enmOrigin != o_override)
+ {
+ struct kbuild_object *pParent = pObj;
+ for (;;)
+ {
+ pParent = resolve_kbuild_object_parent(pParent, 0 /*fQuiet*/);
+ if (!pParent)
+ break;
+
+ pVar = (struct variable *)hash_find_item_strcached(&pParent->pVariables->set->table, &VarKey);
+ if (pVar)
+ {
+ if (pVar->value_length != ~0U)
+ assert(pVar->value_length == strlen(pVar->value));
+ else
+ pVar->value_length = strlen(pVar->value);
+
+ /*
+ * Combine the two values and define the variable in the
+ * specified child object. We must disregard 'origin' a
+ * little here, so we must do the gritty stuff our selves.
+ */
+ if ( pVar->recursive
+ || fSimpleValue
+ || !cchValue
+ || memchr(pchValue, '$', cchValue) == NULL )
+ {
+ int fDuplicateValue = 1;
+ size_t cchNewValue;
+ char *pszNewValue;
+ char *pszTmp;
+
+ /* Just join up the two values. */
+ if (pVar->recursive && !fSimpleValue)
+ pchValue = kbuild_replace_special_accessors(pchValue, &cchValue, &fDuplicateValue, pFileLoc);
+ if (pVar->value_length == 0)
+ {
+ cchNewValue = cchValue;
+ pszNewValue = xstrndup(pchValue, cchValue);
+ }
+ else if (!cchValue)
+ {
+ cchNewValue = pVar->value_length;
+ pszNewValue = xmalloc(cchNewValue + 1);
+ memcpy(pszNewValue, pVar->value, cchNewValue + 1);
+ }
+ else
+ {
+ cchNewValue = pVar->value_length + 1 + cchValue;
+ pszNewValue = xmalloc(cchNewValue + 1);
+ if (fAppend)
+ {
+ memcpy(pszNewValue, pVar->value, pVar->value_length);
+ pszTmp = pszNewValue + pVar->value_length;
+ *pszTmp++ = ' ';
+ memcpy(pszTmp, pchValue, cchValue);
+ pszTmp[cchValue] = '\0';
+ }
+ else
+ {
+ memcpy(pszNewValue, pchValue, cchValue);
+ pszTmp = pszNewValue + cchValue;
+ *pszTmp++ = ' ';
+ memcpy(pszNewValue, pVar->value, pVar->value_length);
+ pszTmp[pVar->value_length] = '\0';
+ }
+ }
+
+ /* Define the new variable in the child. */
+ pVar = define_kbuild_object_variable_cached(pObj, VarKey.name,
+ pszNewValue, cchNewValue, 0 /*fDuplicateValue*/,
+ enmOrigin, pVar->recursive, 1 /*fNoSpecialAccessors*/,
+ pFileLoc);
+ if (fDuplicateValue == 0)
+ free((char *)pchValue);
+ }
+ else
+ {
+ /* Lazy bird: Copy the variable from the ancestor and
+ then do a normal append/prepend on it. */
+ pVar = define_kbuild_object_variable_cached(pObj, VarKey.name,
+ pVar->value, pVar->value_length, 1 /*fDuplicateValue*/,
+ enmOrigin, pVar->recursive, 1 /*fNoSpecialAccessors*/,
+ pFileLoc);
+ append_expanded_string_to_variable(pVar, pchValue, cchValue, fAppend);
+ }
+ return pVar;
+ }
+ }
+ }
+ }
+ else
+ VarKey.name = strcache2_add(&variable_strcache, pchName, cchName);
+
+ /* Variable not found. */
+ return define_kbuild_object_variable_cached(pObj, VarKey.name,
+ pchValue, cchValue, 1 /*fDuplicateValue*/, enmOrigin,
+ 1 /*fRecursive */, 0 /*fNoSpecialAccessors*/, pFileLoc);
+}
+
+/** @} */
+
+
+static char *
+allocate_expanded_next_token(const char **ppszCursor, const char *pszEos, size_t *pcchToken, int fStrip)
+{
+ unsigned int cchToken;
+ char *pszToken = find_next_token_eos(ppszCursor, pszEos, &cchToken);
+ if (pszToken)
+ {
+ pszToken = allocated_variable_expand_2(pszToken, cchToken, &cchToken);
+ if (pszToken)
+ {
+ if (fStrip)
+ {
+ unsigned int off = 0;
+ while (MY_IS_BLANK(pszToken[off]))
+ off++;
+ if (off)
+ {
+ cchToken -= off;
+ memmove(pszToken, &pszToken[off], cchToken + 1);
+ }
+
+ while (cchToken > 0 && MY_IS_BLANK(pszToken[cchToken - 1]))
+ pszToken[--cchToken] = '\0';
+ }
+
+ assert(cchToken == strlen(pszToken));
+ if (pcchToken)
+ *pcchToken = cchToken;
+ return pszToken;
+ }
+ }
+
+ if (pcchToken)
+ *pcchToken = 0;
+ return NULL;
+}
+
+static struct kbuild_object *
+resolve_kbuild_object_parent(struct kbuild_object *pObj, int fQuiet)
+{
+ if ( !pObj->pParent
+ && pObj->pszParent)
+ {
+ struct kbuild_object *pCur = g_pHeadKbObjs;
+ while (pCur)
+ {
+ if ( pCur->enmType == pObj->enmType
+ && !strcmp(pCur->pszName, pObj->pszParent))
+ {
+ if ( pCur->pszParent
+ && ( pCur->pParent == pObj
+ || !strcmp(pCur->pszParent, pObj->pszName)) )
+ OSS(fatal, &pObj->FileLoc, _("'%s' and '%s' are both trying to be each other children..."),
+ pObj->pszName, pCur->pszName);
+
+ pObj->pParent = pCur;
+ pObj->pVariables->next = pObj->pVariables;
+ return pCur;
+ }
+
+ pCur = pCur->pGlobalNext;
+ }
+
+ /* Not found. */
+ if (!fQuiet)
+ OSS(error, &pObj->FileLoc, _("Could not locate parent '%s' of '%s'"), pObj->pszParent, pObj->pszName);
+ }
+ return pObj->pParent;
+}
+
+/**
+ * Get the parent of the given object, it is expected to have one.
+ *
+ * @returns Pointer to the parent. NULL if we survive failure.
+ * @param pObj The kBuild object.
+ * @param enmSeverity The severity of a missing parent.
+ */
+static struct kbuild_object *
+get_kbuild_object_parent(struct kbuild_object *pObj, enum kBuildSeverity enmSeverity)
+{
+ struct kbuild_object *pParent = pObj->pParent;
+ if (pParent)
+ return pParent;
+
+ pParent = resolve_kbuild_object_parent(pObj, 1 /*fQuiet - complain below */);
+ if (pParent)
+ return pParent;
+
+ if (pObj->pszParent)
+ kbuild_report_problem(enmSeverity, &pObj->FileLoc,
+ _("Could not local parent '%s' for kBuild object '%s'"),
+ pObj->pszParent, pObj->pszName);
+ else
+ kbuild_report_problem(enmSeverity, &pObj->FileLoc,
+ _("kBuild object '%s' has no parent ([@super])"),
+ pObj->pszName);
+ return NULL;
+}
+
+static int
+eval_kbuild_define_xxxx(struct kbuild_eval_data **ppData, const floc *pFileLoc,
+ const char *pszLine, const char *pszEos, int fIgnoring, enum kBuildType enmType)
+{
+ unsigned int cch;
+ char ch;
+ char *psz;
+ const char *pszPrefix;
+ struct kbuild_object *pObj;
+ struct kbuild_eval_data *pData;
+
+ if (fIgnoring)
+ return 0;
+
+ /*
+ * Create a new kBuild object.
+ */
+ pObj = xmalloc(sizeof(*pObj));
+ pObj->enmType = enmType;
+ pObj->pszName = NULL;
+ pObj->cchName = 0;
+ pObj->FileLoc = *pFileLoc;
+
+ pObj->pGlobalNext = g_pHeadKbObjs;
+ g_pHeadKbObjs = pObj;
+
+ pObj->pVariables = create_new_variable_set();
+
+ pObj->pszParent = NULL;
+ pObj->cchParent = 0;
+ pObj->pParent = NULL;
+
+ pObj->pszTemplate = NULL;
+
+ pObj->pszVarPrefix = NULL;
+ pObj->cchVarPrefix = 0;
+
+ /*
+ * The first word is the name.
+ */
+ pObj->pszName = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchName, 1 /*strip*/);
+ if (!pObj->pszName || !*pObj->pszName)
+ O(fatal, pFileLoc, _("The kBuild define requires a name"));
+
+ psz = pObj->pszName;
+ while ((ch = *psz++) != '\0')
+ if (!isgraph(ch))
+ {
+ OSS(error, pFileLoc, _("The 'kBuild-define-%s' name '%s' contains one or more invalid characters"),
+ eval_kbuild_type_to_string(enmType), pObj->pszName);
+ break;
+ }
+
+ /*
+ * Calc the variable prefix.
+ */
+ switch (enmType)
+ {
+ case kBuildType_Target: pszPrefix = ""; break;
+ case kBuildType_Template: pszPrefix = "TEMPLATE_"; break;
+ case kBuildType_Tool: pszPrefix = "TOOL_"; break;
+ case kBuildType_Sdk: pszPrefix = "SDK_"; break;
+ case kBuildType_Unit: pszPrefix = "UNIT_"; break;
+ default:
+ ON(fatal, pFileLoc, _("enmType=%d"), enmType);
+ return -1;
+ }
+ cch = strlen(pszPrefix);
+ pObj->cchVarPrefix = cch + pObj->cchName;
+ pObj->pszVarPrefix = xmalloc(pObj->cchVarPrefix + 1);
+ memcpy(pObj->pszVarPrefix, pszPrefix, cch);
+ memcpy(&pObj->pszVarPrefix[cch], pObj->pszName, pObj->cchName);
+
+ /*
+ * Parse subsequent words.
+ */
+ psz = find_next_token_eos(&pszLine, pszEos, &cch);
+ while (psz)
+ {
+ if (WORD_IS(psz, cch, "extending"))
+ {
+ /* Inheritance directive. */
+ if (pObj->pszParent != NULL)
+ O(fatal, pFileLoc, _("'extending' can only occure once"));
+ pObj->pszParent = allocate_expanded_next_token(&pszLine, pszEos, &pObj->cchParent, 1 /*strip*/);
+ if (!pObj->pszParent || !*pObj->pszParent)
+ O(fatal, pFileLoc, _("'extending' requires a parent name"));
+ }
+ else if (WORD_IS(psz, cch, "using"))
+ {
+ char *pszTemplate;
+ size_t cchTemplate;
+
+ /* Template directive. */
+ if (enmType != kBuildType_Target)
+ O(fatal, pFileLoc, _("'using <template>' can only be used with 'kBuild-define-target'"));
+ if (pObj->pszTemplate != NULL)
+ O(fatal, pFileLoc, _("'using' can only occure once"));
+
+ pszTemplate = allocate_expanded_next_token(&pszLine, pszEos, &cchTemplate, 1 /*fStrip*/);
+ if (!pszTemplate || !*pszTemplate)
+ O(fatal, pFileLoc, _("'using' requires a template name"));
+
+ define_kbuild_object_variable_cached(pObj, g_pszVarNmTemplate, pszTemplate, cchTemplate,
+ 0 /*fDuplicateValue*/, o_default, 0 /*fRecursive*/,
+ 1 /*fNoSpecialAccessors*/, pFileLoc);
+
+ }
+ else
+ fatal(pFileLoc, cch, _("Don't know what '%.*s' means"), (int)cch, psz);
+
+ /* next token */
+ psz = find_next_token_eos(&pszLine, pszEos, &cch);
+ }
+
+ /*
+ * Try resolve the parent.
+ */
+ resolve_kbuild_object_parent(pObj, 1 /*fQuiet*/);
+
+ /*
+ * Create an eval stack entry and change the current variable set.
+ */
+ pData = xmalloc(sizeof(*pData));
+ pData->pObj = pObj;
+ pData->pVariablesSaved = current_variable_set_list;
+ current_variable_set_list = pObj->pVariables;
+
+ pData->pStackDown = *ppData;
+ *ppData = pData;
+ g_pTopKbEvalData = pData;
+
+ return 0;
+}
+
+static int
+eval_kbuild_endef_xxxx(struct kbuild_eval_data **ppData, const floc *pFileLoc,
+ const char *pszLine, const char *pszEos, int fIgnoring, enum kBuildType enmType)
+{
+ struct kbuild_eval_data *pData;
+ struct kbuild_object *pObj;
+ size_t cchName;
+ char *pszName;
+
+ if (fIgnoring)
+ return 0;
+
+ /*
+ * Is there something to pop?
+ */
+ pData = *ppData;
+ if (!pData)
+ {
+ OSS(error, pFileLoc, _("kBuild-endef-%s is missing kBuild-define-%s"),
+ eval_kbuild_type_to_string(enmType), eval_kbuild_type_to_string(enmType));
+ return 0;
+ }
+
+ /*
+ * ... and does it have a matching kind?
+ */
+ pObj = pData->pObj;
+ if (pObj->enmType != enmType)
+ OSSS(error, pFileLoc, _("'kBuild-endef-%s' does not match 'kBuild-define-%s %s'"),
+ eval_kbuild_type_to_string(enmType), eval_kbuild_type_to_string(pObj->enmType), pObj->pszName);
+
+ /*
+ * The endef-kbuild may optionally be followed by the target name.
+ * It should match the name given to the kBuild-define.
+ */
+ pszName = allocate_expanded_next_token(&pszLine, pszEos, &cchName, 1 /*fStrip*/);
+ if (pszName)
+ {
+ if ( cchName != pObj->cchName
+ || strcmp(pszName, pObj->pszName))
+ OSSSS(error, pFileLoc, _("'kBuild-endef-%s %s' does not match 'kBuild-define-%s %s'"),
+ eval_kbuild_type_to_string(enmType), pszName,
+ eval_kbuild_type_to_string(pObj->enmType), pObj->pszName);
+ free(pszName);
+ }
+
+ /*
+ * Pop a define off the stack.
+ */
+ assert(pData == g_pTopKbEvalData);
+ *ppData = g_pTopKbEvalData = pData->pStackDown;
+ pData->pStackDown = NULL;
+ current_variable_set_list = pData->pVariablesSaved;
+ pData->pVariablesSaved = NULL;
+ free(pData);
+
+ return 0;
+}
+
+int eval_kbuild_read_hook(struct kbuild_eval_data **kdata, const floc *flocp,
+ const char *pchWord, size_t cchWord, const char *line, const char *eos, int ignoring)
+{
+ enum kBuildType enmType;
+
+ /*
+ * Skip the 'kBuild-' prefix that the caller already matched.
+ */
+ assert(memcmp(pchWord, "kBuild-", sizeof("kBuild-") - 1) == 0);
+ pchWord += sizeof("kBuild-") - 1;
+ cchWord -= sizeof("kBuild-") - 1;
+
+ /*
+ * String switch.
+ */
+ if ( cchWord >= sizeof("define-") - 1
+ && strneq(pchWord, "define-", sizeof("define-") - 1))
+ {
+ enmType = eval_kbuild_type_from_string(pchWord + sizeof("define-") - 1, cchWord - sizeof("define-") + 1);
+ if (enmType != kBuildType_Invalid)
+ return eval_kbuild_define_xxxx(kdata, flocp, line, eos, ignoring, enmType);
+ }
+ else if ( cchWord >= sizeof("endef-") - 1
+ && strneq(pchWord, "endef-", sizeof("endef-") - 1))
+ {
+ enmType = eval_kbuild_type_from_string(pchWord + sizeof("endif-") - 1, cchWord - sizeof("endif-") + 1);
+ if (enmType != kBuildType_Invalid)
+ return eval_kbuild_endef_xxxx(kdata, flocp, line, eos, ignoring, enmType);
+ }
+ else if (WORD_IS(pchWord, cchWord, "endef"))
+ {
+ /* Terminate whatever definition is on top. */
+
+ }
+
+ /*
+ * Everything that is prefixed with 'kBuild-' is reserved for language
+ * extensions, at least until legacy assignments/whatever turns up.
+ */
+ error(flocp, cchWord, _("Unknown syntax 'kBuild-%.*s'"), (int)cchWord, pchWord);
+ return 0;
+}
+
+
+/** @name kBuild object variable accessor related functions
+ * @{
+ */
+
+/**
+ * Checks if the given name is an object variable accessor.
+ *
+ * @returns 1 if it is, 0 if it isn't.
+ * @param pchName The potential kBuild variable accessor
+ * expression.
+ * @param cchName Length of the expression.
+ */
+int is_kbuild_object_variable_accessor(const char *pchName, size_t cchName)
+{
+ char const *pchTmp;
+
+ /* See lookup_kbuild_object_variable for the rules. */
+ if (cchName >= 1+1+1+1 && *pchName == '[')
+ {
+ pchName++;
+ cchName--;
+
+ pchTmp = memchr(pchName, '@', cchName);
+ if (pchTmp)
+ {
+ cchName -= pchTmp + 1 - pchName;
+ pchName = pchTmp + 1;
+ pchTmp = memchr(pchName, ']', cchName);
+ if (pchTmp)
+ {
+ cchName -= pchTmp + 1 - pchName;
+ if (cchName > 0)
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/**
+ * Parses a kBuild object variable accessor, resolving the object.
+ *
+ * @returns Pointer to the variable if found.
+ * @retval NULL if the object (or type) couldn't be resolved.
+ * @retval KOBJ_NOT_KBUILD_ACCESSOR if no a kBuild variable accessor.
+ *
+ * @param pchExpr The kBuild variable accessor expression.
+ * @param cchExpr Length of the expression.
+ * @param enmSeverity The minimum severity level for errors.
+ * @param pFileLoc The file location any errors should be reported
+ * at. Optional.
+ * @param ppchVarNm Where to return the pointer to the start of the
+ * variable name within the string @a pchExpr
+ * points to. Mandatory.
+ * @param pcchVarNm Where to return the length of the variable name.
+ * Mandatory.
+ * @param penmType Where to return the object type. Optional.
+ */
+static struct kbuild_object *
+parse_kbuild_object_variable_accessor(const char *pchExpr, size_t cchExpr,
+ enum kBuildSeverity enmSeverity, const floc *pFileLoc,
+ const char **ppchVarNm, size_t *pcchVarNm, enum kBuildType *penmType)
+{
+ const char * const pchOrgExpr = pchExpr;
+ size_t const cchOrgExpr = cchExpr;
+ char const *pchTmp;
+
+ /*
+ * To accept this as an kBuild accessor, we require:
+ * 1. Open bracket.
+ * 2. At sign separating the type from the name.
+ * 3. Closing bracket.
+ * 4. At least one character following it.
+ */
+ if (cchExpr >= 1+1+1+1 && *pchExpr == '[')
+ {
+ pchExpr++;
+ cchExpr--;
+
+ pchTmp = memchr(pchExpr, '@', cchExpr);
+ if (pchTmp)
+ {
+ const char * const pchType = pchExpr;
+ size_t const cchType = pchTmp - pchExpr;
+
+ cchExpr -= cchType + 1;
+ pchExpr = pchTmp + 1;
+ pchTmp = memchr(pchExpr, ']', cchExpr);
+ if (pchTmp)
+ {
+ const char * const pchObjName = pchExpr;
+ size_t const cchObjName = pchTmp - pchExpr;
+
+ cchExpr -= cchObjName + 1;
+ pchExpr = pchTmp + 1;
+ if (cchExpr > 0)
+ {
+
+ /*
+ * It's an kBuild define variable accessor, alright.
+ */
+ *pcchVarNm = cchExpr;
+ *ppchVarNm = pchExpr;
+
+ /* Deal with known special accessors: [@self]VAR, [@super]VAR. */
+ if (cchType == 0)
+ {
+ int fSuper;
+
+ if (WORD_IS(pchObjName, cchObjName, "self"))
+ fSuper = 0;
+ else if (WORD_IS(pchObjName, cchObjName, "super"))
+ fSuper = 1;
+ else
+ {
+ kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
+ _("Invalid special kBuild object accessor: '%.*s'"),
+ (int)cchOrgExpr, pchOrgExpr);
+ if (penmType)
+ *penmType = kBuildType_Invalid;
+ return NULL;
+ }
+ if (g_pTopKbEvalData)
+ {
+ struct kbuild_object *pObj = g_pTopKbEvalData->pObj;
+ struct kbuild_object *pParent;
+
+ if (penmType)
+ *penmType = pObj->enmType;
+
+ if (!fSuper)
+ return pObj;
+
+ pParent = get_kbuild_object_parent(pObj, MAX(enmSeverity, kBuildSeverity_Error));
+ if (pParent)
+ return pParent;
+ }
+ else
+ kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
+ _("The '%.*s' accessor can only be used in the context of a kBuild object"),
+ (int)cchOrgExpr, pchOrgExpr);
+ if (penmType)
+ *penmType = kBuildType_Invalid;
+ }
+ else
+ {
+ /* Genric accessor. Check the type and look up the object. */
+ enum kBuildType enmType = eval_kbuild_type_from_string(pchType, cchType);
+ if (penmType)
+ *penmType = enmType;
+ if (enmType != kBuildType_Invalid)
+ {
+ struct kbuild_object *pObj = lookup_kbuild_object(enmType, pchObjName, cchObjName);
+ if (pObj)
+ return pObj;
+
+ /* failed. */
+ kbuild_report_problem(enmSeverity, pFileLoc,
+ _("kBuild object '%s' not found in kBuild variable accessor '%.*s'"),
+ (int)cchObjName, pchObjName, (int)cchOrgExpr, pchOrgExpr);
+ }
+ else
+ kbuild_report_problem(MAX(enmSeverity, kBuildSeverity_Error), pFileLoc,
+ _("Invalid type '%.*s' specified in kBuild variable accessor '%.*s'"),
+ (int)cchType, pchType, (int)cchOrgExpr, pchOrgExpr);
+ }
+ return NULL;
+ }
+ }
+ }
+ }
+
+ *ppchVarNm = NULL;
+ *pcchVarNm = 0;
+ if (penmType)
+ *penmType = kBuildType_Invalid;
+ return KOBJ_NOT_KBUILD_ACCESSOR;
+}
+
+/**
+ * Looks up a variable in a kBuild object.
+ *
+ * The caller has done minimal matching, i.e. starting square brackets and
+ * minimum length. We do the rest here.
+ *
+ * @returns Pointer to the variable if found.
+ * @retval NULL if not found.
+ * @retval VAR_NOT_KBUILD_ACCESSOR if no a kBuild variable accessor.
+ *
+ * @param pchName The kBuild variable accessor expression.
+ * @param cchName Length of the expression.
+ */
+struct variable *
+lookup_kbuild_object_variable_accessor(const char *pchName, size_t cchName)
+{
+ /*const char * const pchOrgName = pchName;*/
+ /*size_t const cchOrgName = cchName;*/
+ const char * pchVarNm;
+ size_t cchVarNm;
+ struct kbuild_object *pObj;
+
+ pObj = parse_kbuild_object_variable_accessor(pchName, cchName, kBuildSeverity_Warning, NULL, &pchVarNm, &cchVarNm, NULL);
+ if (pObj != KOBJ_NOT_KBUILD_ACCESSOR)
+ {
+ if (pObj)
+ {
+ /*
+ * Do the variable lookup.
+ */
+ const char *pszCachedName = strcache2_lookup(&variable_strcache, pchVarNm, cchVarNm);
+ if (pszCachedName)
+ {
+ struct variable VarKey;
+ struct variable *pVar;
+ VarKey.name = pszCachedName;
+ VarKey.length = cchName;
+
+ pVar = (struct variable *)hash_find_item_strcached(&pObj->pVariables->set->table, &VarKey);
+ if (pVar)
+ return pVar;
+
+ /*
+ * Not found, check ancestors if any.
+ */
+ if (pObj->pszParent || pObj->pszTemplate)
+ {
+ struct kbuild_object *pParent = pObj;
+ for (;;)
+ {
+ pParent = resolve_kbuild_object_parent(pParent, 0 /*fQuiet*/);
+ if (!pParent)
+ break;
+ pVar = (struct variable *)hash_find_item_strcached(&pParent->pVariables->set->table, &VarKey);
+ if (pVar)
+ return pVar;
+ }
+ }
+ }
+ }
+
+ /* Not found one way or the other. */
+ return NULL;
+ }
+
+ /* Not a kBuild object variable accessor. */
+ return VAR_NOT_KBUILD_ACCESSOR;
+}
+
+/** @} */
+
+void print_kbuild_data_base(void)
+{
+ struct kbuild_object *pCur;
+
+ puts(_("\n# kBuild defines"));
+
+ for (pCur = g_pHeadKbObjs; pCur; pCur = pCur->pGlobalNext)
+ {
+ printf("\nkBuild-define-%s %s",
+ eval_kbuild_type_to_string(pCur->enmType), pCur->pszName);
+ if (pCur->pszParent)
+ printf(" extending %s", pCur->pszParent);
+ if (pCur->pszTemplate)
+ printf(" using %s", pCur->pszTemplate);
+ putchar('\n');
+
+ print_variable_set(pCur->pVariables->set, "", 0);
+
+ printf("kBuild-endef-%s %s\n",
+ eval_kbuild_type_to_string(pCur->enmType), pCur->pszName);
+ }
+ /** @todo hash stats. */
+}
+
+void print_kbuild_define_stats(void)
+{
+ /* later when hashing stuff */
+}
+
diff --git a/src/kmk/kbuild.c b/src/kmk/kbuild.c
new file mode 100644
index 0000000..56a0b28
--- /dev/null
+++ b/src/kmk/kbuild.c
@@ -0,0 +1,3030 @@
+/* $Id: kbuild.c 3425 2020-08-21 12:45:06Z bird $ */
+/** @file
+ * kBuild specific make functionality.
+ */
+
+/*
+ * Copyright (c) 2006-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* No GNU coding style here! */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define NO_MEMCOPY_HACK
+#include "makeint.h"
+#include "filedef.h"
+#include "variable.h"
+#include "dep.h"
+#include "debug.h"
+#ifdef WINDOWS32
+# include "pathstuff.h"
+# include <Windows.h>
+#endif
+#if defined(__APPLE__)
+# include <mach-o/dyld.h>
+#endif
+#if defined(__FreeBSD__)
+# include <dlfcn.h>
+# include <sys/link_elf.h>
+#endif
+
+#include "kbuild.h"
+#include "k/kDefs.h"
+
+#include <assert.h>
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** Helper for passing a string constant to kbuild_get_variable_n. */
+#define ST(strconst) strconst, sizeof(strconst) - 1
+
+#if 1
+# define my_memcpy(dst, src, len) \
+ do { \
+ if (len > 8) \
+ memcpy(dst, src, len); \
+ else \
+ switch (len) \
+ { \
+ case 8: dst[7] = src[7]; /* fall thru */ \
+ case 7: dst[6] = src[6]; /* fall thru */ \
+ case 6: dst[5] = src[5]; /* fall thru */ \
+ case 5: dst[4] = src[4]; /* fall thru */ \
+ case 4: dst[3] = src[3]; /* fall thru */ \
+ case 3: dst[2] = src[2]; /* fall thru */ \
+ case 2: dst[1] = src[1]; /* fall thru */ \
+ case 1: dst[0] = src[0]; /* fall thru */ \
+ case 0: break; \
+ } \
+ } while (0)
+#elif defined(__GNUC__)
+# define my_memcpy __builtin_memcpy
+#elif defined(_MSC_VER)
+# pragma instrinic(memcpy)
+# define my_memcpy memcpy
+#endif
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/** The argv[0] passed to main. */
+static const char *g_pszExeName;
+/** The initial working directory. */
+static char *g_pszInitialCwd;
+
+
+/**
+ * Initialize kBuild stuff.
+ *
+ * @param argc Number of arguments to main().
+ * @param argv The main() argument vector.
+ */
+void init_kbuild(int argc, char **argv)
+{
+ int rc;
+ PATH_VAR(szTmp);
+
+ /*
+ * Get the initial cwd for use in my_abspath.
+ */
+#ifdef WINDOWS32
+ if (getcwd_fs(szTmp, GET_PATH_MAX) != 0)
+#else
+ if (getcwd(szTmp, GET_PATH_MAX) != 0)
+#endif
+ g_pszInitialCwd = xstrdup(szTmp);
+ else
+ O(fatal, NILF, _("getcwd failed"));
+
+ /*
+ * Determin the executable name.
+ */
+ rc = -1;
+#if defined(__APPLE__)
+ {
+ const char *pszImageName = _dyld_get_image_name(0);
+ if (pszImageName)
+ {
+ size_t cchImageName = strlen(pszImageName);
+ if (cchImageName < GET_PATH_MAX)
+ {
+ memcpy(szTmp, pszImageName, cchImageName + 1);
+ rc = 0;
+ }
+ }
+ }
+
+#elif defined(__FreeBSD__)
+ rc = readlink("/proc/curproc/file", szTmp, GET_PATH_MAX - 1);
+ if (rc < 0 || rc == GET_PATH_MAX - 1)
+ {
+ rc = -1;
+# if 0 /* doesn't work because l_name isn't always absolute, it's just argv0 from exec or something. */
+ /* /proc is optional, try rtdl. */
+ void *hExe = dlopen(NULL, 0);
+ rc = -1;
+ if (hExe)
+ {
+ struct link_map const *pLinkMap = 0;
+ if (dlinfo(hExe, RTLD_DI_LINKMAP, &pLinkMap) == 0)
+ {
+ const char *pszImageName = pLinkMap->l_name;
+ size_t cchImageName = strlen(pszImageName);
+ if (cchImageName < GET_PATH_MAX)
+ {
+ memcpy(szTmp, pszImageName, cchImageName + 1);
+ rc = 0;
+ }
+ }
+
+ }
+# endif
+ }
+ else
+ szTmp[rc] = '\0';
+
+#elif defined(__gnu_linux__) || defined(__linux__)
+ rc = readlink("/proc/self/exe", szTmp, GET_PATH_MAX - 1);
+ if (rc < 0 || rc == GET_PATH_MAX - 1)
+ rc = -1;
+ else
+ szTmp[rc] = '\0';
+
+#elif defined(__OS2__)
+ _execname(szTmp, GET_PATH_MAX);
+ rc = 0;
+
+#elif defined(__sun__)
+ {
+ char szTmp2[64];
+ snprintf(szTmp2, sizeof(szTmp2), "/proc/%ld/path/a.out", (long)getpid());
+ rc = readlink(szTmp2, szTmp, GET_PATH_MAX - 1);
+ if (rc < 0 || rc == GET_PATH_MAX - 1)
+ rc = -1;
+ else
+ szTmp[rc] = '\0';
+ }
+
+#elif defined(WINDOWS32)
+ if (GetModuleFileName(GetModuleHandle(NULL), szTmp, GET_PATH_MAX))
+ rc = 0;
+
+#endif
+
+#if !defined(__OS2__) && !defined(WINDOWS32)
+ /* fallback, try use the path to locate the binary. */
+ if ( rc < 0
+ && access(argv[0], X_OK))
+ {
+ size_t cchArgv0 = strlen(argv[0]);
+ const char *pszPath = getenv("PATH");
+ char *pszCopy = xstrdup(pszPath ? pszPath : ".");
+ char *psz = pszCopy;
+ while (*psz)
+ {
+ size_t cch;
+ char *pszEnd = strchr(psz, PATH_SEPARATOR_CHAR);
+ if (!pszEnd)
+ pszEnd = strchr(psz, '\0');
+ cch = pszEnd - psz;
+ if (cch + cchArgv0 + 2 <= GET_PATH_MAX)
+ {
+ memcpy(szTmp, psz, cch);
+ szTmp[cch] = '/';
+ memcpy(&szTmp[cch + 1], argv[0], cchArgv0 + 1);
+ if (!access(szTmp, X_OK))
+ {
+ rc = 0;
+ break;
+ }
+ }
+
+ /* next */
+ psz = pszEnd;
+ while (*psz == PATH_SEPARATOR_CHAR)
+ psz++;
+ }
+ free(pszCopy);
+ }
+#endif
+
+ if (rc < 0)
+ g_pszExeName = argv[0];
+ else
+ g_pszExeName = xstrdup(szTmp);
+
+ (void)argc;
+}
+
+
+/**
+ * Wrapper that ensures correct starting_directory.
+ */
+static char *my_abspath(const char *pszIn, char *pszOut)
+{
+ char *pszSaved, *pszRet;
+
+ pszSaved = starting_directory;
+ starting_directory = g_pszInitialCwd;
+ pszRet = abspath(pszIn, pszOut);
+ starting_directory = pszSaved;
+
+ return pszRet;
+}
+
+
+/**
+ * Determin the KBUILD_PATH value.
+ *
+ * @returns Pointer to static a buffer containing the value (consider it read-only).
+ */
+const char *get_kbuild_path(void)
+{
+ static const char *s_pszPath = NULL;
+ if (!s_pszPath)
+ {
+ PATH_VAR(szTmpPath);
+ const char *pszEnvVar = getenv("KBUILD_PATH");
+ if ( !pszEnvVar
+ || !my_abspath(pszEnvVar, szTmpPath))
+ {
+ pszEnvVar = getenv("PATH_KBUILD");
+ if ( !pszEnvVar
+ || !my_abspath(pszEnvVar, szTmpPath))
+ {
+#ifdef KBUILD_PATH
+ return s_pszPath = KBUILD_PATH;
+#else
+ /* $(abspath $(KBUILD_BIN_PATH)/../..)*/
+ size_t cch = strlen(get_kbuild_bin_path());
+ char *pszTmp2 = alloca(cch + sizeof("/../.."));
+ strcat(strcpy(pszTmp2, get_kbuild_bin_path()), "/../..");
+ if (!my_abspath(pszTmp2, szTmpPath))
+ O(fatal, NILF, _("failed to determin KBUILD_PATH"));
+#endif
+ }
+ }
+ s_pszPath = xstrdup(szTmpPath);
+ }
+ return s_pszPath;
+}
+
+
+/**
+ * Determin the KBUILD_BIN_PATH value.
+ *
+ * @returns Pointer to static a buffer containing the value (consider it read-only).
+ */
+const char *get_kbuild_bin_path(void)
+{
+ static const char *s_pszPath = NULL;
+ if (!s_pszPath)
+ {
+ PATH_VAR(szTmpPath);
+
+ const char *pszEnvVar = getenv("KBUILD_BIN_PATH");
+ if ( !pszEnvVar
+ || !my_abspath(pszEnvVar, szTmpPath))
+ {
+ pszEnvVar = getenv("PATH_KBUILD_BIN");
+ if ( !pszEnvVar
+ || !my_abspath(pszEnvVar, szTmpPath))
+ {
+#ifdef KBUILD_PATH
+ return s_pszPath = KBUILD_BIN_PATH;
+#else
+ /* $(abspath $(dir $(ARGV0)).) */
+ size_t cch = strlen(g_pszExeName);
+ char *pszTmp2 = alloca(cch + sizeof("."));
+ char *pszSep = pszTmp2 + cch - 1;
+ memcpy(pszTmp2, g_pszExeName, cch);
+# ifdef HAVE_DOS_PATHS
+ while (pszSep >= pszTmp2 && *pszSep != '/' && *pszSep != '\\' && *pszSep != ':')
+# else
+ while (pszSep >= pszTmp2 && *pszSep != '/')
+# endif
+ pszSep--;
+ if (pszSep >= pszTmp2)
+ strcpy(pszSep + 1, ".");
+ else
+ strcpy(pszTmp2, ".");
+
+ if (!my_abspath(pszTmp2, szTmpPath))
+ OSS(fatal, NILF, _("failed to determin KBUILD_BIN_PATH (pszTmp2=%s szTmpPath=%s)"), pszTmp2, szTmpPath);
+#endif /* !KBUILD_PATH */
+ }
+ }
+ s_pszPath = xstrdup(szTmpPath);
+ }
+ return s_pszPath;
+}
+
+
+/**
+ * Determin the location of default kBuild shell.
+ *
+ * @returns Pointer to static a buffer containing the location (consider it read-only).
+ */
+const char *get_default_kbuild_shell(void)
+{
+ static char *s_pszDefaultShell = NULL;
+ if (!s_pszDefaultShell)
+ {
+#if defined(__OS2__) || defined(_WIN32) || defined(WINDOWS32)
+ static const char s_szShellName[] = "/kmk_ash.exe";
+#else
+ static const char s_szShellName[] = "/kmk_ash";
+#endif
+ const char *pszBin = get_kbuild_bin_path();
+ size_t cchBin = strlen(pszBin);
+ s_pszDefaultShell = xmalloc(cchBin + sizeof(s_szShellName));
+ memcpy(s_pszDefaultShell, pszBin, cchBin);
+ memcpy(&s_pszDefaultShell[cchBin], s_szShellName, sizeof(s_szShellName));
+ }
+ return s_pszDefaultShell;
+}
+
+#ifdef KMK_HELPERS
+
+/**
+ * Applies the specified default path to any relative paths in *ppsz.
+ *
+ * @param pDefPath The default path.
+ * @param ppsz Pointer to the string pointer. If we expand anything, *ppsz
+ * will be replaced and the caller is responsible for calling free() on it.
+ * @param pcch IN: *pcch contains the current string length.
+ * OUT: *pcch contains the new string length.
+ * @param pcchAlloc *pcchAlloc contains the length allocated for the string. Can be NULL.
+ * @param fCanFree Whether *ppsz should be freed when we replace it.
+ */
+static void
+kbuild_apply_defpath(struct variable *pDefPath, char **ppsz, unsigned int *pcch, unsigned int *pcchAlloc, int fCanFree)
+{
+ unsigned int cchInCur;
+ unsigned int cchMaxRelative = 0;
+ const char *pszInCur;
+
+ /*
+ * The first pass, count the relative paths.
+ */
+ const char *pszIterator = *ppsz;
+ const char * const pszEos = pszIterator + *pcch;
+ unsigned int cRelativePaths = 0;
+ assert(*pszEos == '\0');
+ while ((pszInCur = find_next_file_token(&pszIterator, pszEos, &cchInCur)) != NULL)
+ {
+ /* is relative? */
+#ifdef HAVE_DOS_PATHS
+ if (pszInCur[0] != '/' && pszInCur[0] != '\\' && (cchInCur < 2 || pszInCur[1] != ':'))
+#else
+ if (pszInCur[0] != '/')
+#endif
+ {
+ cRelativePaths++;
+ if (cchInCur > cchMaxRelative)
+ cchMaxRelative = cchInCur;
+ }
+ }
+
+ /*
+ * The second pass construct the new string.
+ */
+ if (cRelativePaths)
+ {
+ size_t const cchAbsPathBuf = MAX(GET_PATH_MAX, pDefPath->value_length + cchInCur + 1 + 16);
+ char *pszAbsPathOut = (char *)alloca(cchAbsPathBuf);
+ char *pszAbsPathIn = (char *)alloca(cchAbsPathBuf);
+ size_t cchAbsDefPath;
+ size_t cchOut;
+ char *pszOut;
+ char *pszOutCur;
+ const char *pszInNextCopy = *ppsz;
+
+ /* make defpath absolute and have a trailing slash first. */
+ if (abspath(pDefPath->value, pszAbsPathIn) == NULL)
+ memcpy(pszAbsPathIn, pDefPath->value, pDefPath->value_length);
+ cchAbsDefPath = strlen(pszAbsPathIn);
+#ifdef HAVE_DOS_PATHS
+ if (pszAbsPathIn[cchAbsDefPath - 1] != '/' && pszAbsPathIn[cchAbsDefPath - 1] != '\\')
+#else
+ if (pszAbsPathIn[cchAbsDefPath - 1] != '/')
+#endif
+ pszAbsPathIn[cchAbsDefPath++] = '/';
+
+ cchOut = *pcch + cRelativePaths * cchAbsDefPath + 1;
+ pszOutCur = pszOut = xmalloc(cchOut);
+
+ cRelativePaths = 0;
+ pszIterator = *ppsz;
+ while ((pszInCur = find_next_file_token(&pszIterator, pszEos, &cchInCur)))
+ {
+ /* is relative? */
+#ifdef HAVE_DOS_PATHS
+ if (pszInCur[0] != '/' && pszInCur[0] != '\\' && (cchInCur < 2 || pszInCur[1] != ':'))
+#else
+ if (pszInCur[0] != '/')
+#endif
+ {
+ const char *pszToCopy;
+ size_t cchToCopy;
+
+ /* Create the abspath input. */
+ memcpy(&pszAbsPathIn[cchAbsDefPath], pszInCur, cchInCur);
+ pszAbsPathIn[cchAbsDefPath + cchInCur] = '\0';
+
+ pszToCopy = abspath(pszAbsPathIn, pszAbsPathOut);
+ if (!pszToCopy)
+ pszToCopy = pszAbsPathIn;
+
+ /* copy leading input */
+ if (pszInCur != pszInNextCopy)
+ {
+ const size_t cchCopy = pszInCur - pszInNextCopy;
+ memcpy(pszOutCur, pszInNextCopy, cchCopy);
+ pszOutCur += cchCopy;
+ }
+ pszInNextCopy = pszInCur + cchInCur;
+
+ /* copy out the abspath. */
+ cchToCopy = strlen(pszToCopy);
+ assert(cchToCopy <= cchAbsDefPath + cchInCur);
+ memcpy(pszOutCur, pszToCopy, cchToCopy);
+ pszOutCur += cchToCopy;
+ }
+ /* else: Copy absolute paths as bulk when we hit then next relative one or the end. */
+ }
+
+ /* the final copy (includes the nil). */
+ cchInCur = *ppsz + *pcch - pszInNextCopy;
+ memcpy(pszOutCur, pszInNextCopy, cchInCur);
+ pszOutCur += cchInCur;
+ *pszOutCur = '\0';
+ assert((size_t)(pszOutCur - pszOut) < cchOut);
+
+ /* set return values */
+ if (fCanFree)
+ free(*ppsz);
+ *ppsz = pszOut;
+ *pcch = pszOutCur - pszOut;
+ if (pcchAlloc)
+ *pcchAlloc = cchOut;
+ }
+}
+
+/**
+ * Gets a variable that must exist.
+ * Will cause a fatal failure if the variable doesn't exist.
+ *
+ * @returns Pointer to the variable.
+ * @param pszName The variable name.
+ * @param cchName The name length.
+ */
+MY_INLINE struct variable *
+kbuild_get_variable_n(const char *pszName, size_t cchName)
+{
+ struct variable *pVar = lookup_variable(pszName, cchName);
+ if (!pVar)
+ fatal(NILF, cchName, _("variable `%.*s' isn't defined!"), (int)cchName, pszName);
+ if (pVar->recursive)
+ fatal(NILF, cchName, _("variable `%.*s' is defined as `recursive' instead of `simple'!"), (int)cchName, pszName);
+
+ MY_ASSERT_MSG(strlen(pVar->value) == pVar->value_length,
+ ("%u != %u %.*s\n", pVar->value_length, (unsigned int)strlen(pVar->value), (int)cchName, pVar->name));
+ return pVar;
+}
+
+
+/**
+ * Gets a variable that must exist and can be recursive.
+ * Will cause a fatal failure if the variable doesn't exist.
+ *
+ * @returns Pointer to the variable.
+ * @param pszName The variable name.
+ */
+static struct variable *
+kbuild_get_recursive_variable(const char *pszName)
+{
+ struct variable *pVar = lookup_variable(pszName, strlen(pszName));
+ if (!pVar)
+ OS(fatal, NILF, _("variable `%s' isn't defined!"), pszName);
+
+ MY_ASSERT_MSG(strlen(pVar->value) == pVar->value_length,
+ ("%u != %u %s\n", pVar->value_length, (unsigned int)strlen(pVar->value), pVar->name));
+ return pVar;
+}
+
+
+/**
+ * Gets a variable that doesn't have to exit, but if it does can be recursive.
+ *
+ * @returns Pointer to the variable.
+ * NULL if not found.
+ * @param pszName The variable name. Doesn't need to be terminated.
+ * @param cchName The name length.
+ */
+static struct variable *
+kbuild_query_recursive_variable_n(const char *pszName, size_t cchName)
+{
+ struct variable *pVar = lookup_variable(pszName, cchName);
+ MY_ASSERT_MSG(!pVar || strlen(pVar->value) == pVar->value_length,
+ ("%u != %u %.*s\n", pVar->value_length, (unsigned int)strlen(pVar->value), (int)cchName, pVar->name));
+ return pVar;
+}
+
+
+/**
+ * Gets a variable that doesn't have to exit, but if it does can be recursive.
+ *
+ * @returns Pointer to the variable.
+ * NULL if not found.
+ * @param pszName The variable name.
+ */
+static struct variable *
+kbuild_query_recursive_variable(const char *pszName)
+{
+ return kbuild_query_recursive_variable_n(pszName, strlen(pszName));
+}
+
+
+/**
+ * Converts the specified variable into a 'simple' one.
+ * @returns pVar.
+ * @param pVar The variable.
+ */
+static struct variable *
+kbuild_simplify_variable(struct variable *pVar)
+{
+ if (memchr(pVar->value, '$', pVar->value_length))
+ {
+ unsigned int value_len;
+ char *pszExpanded = allocated_variable_expand_2(pVar->value, pVar->value_length, &value_len);
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (pVar->rdonly_val)
+ pVar->rdonly_val = 0;
+ else
+#endif
+ free(pVar->value);
+ assert(pVar->origin != o_automatic);
+ pVar->value = pszExpanded;
+ pVar->value_length = value_len;
+ pVar->value_alloc_len = value_len + 1;
+ }
+ pVar->recursive = 0;
+ VARIABLE_CHANGED(pVar);
+ return pVar;
+}
+
+
+/**
+ * Looks up a variable.
+ * The value_length field is valid upon successful return.
+ *
+ * @returns Pointer to the variable. NULL if not found.
+ * @param pszName The variable name.
+ * @param cchName The name length.
+ */
+MY_INLINE struct variable *
+kbuild_lookup_variable_n(const char *pszName, size_t cchName)
+{
+ struct variable *pVar = lookup_variable(pszName, cchName);
+ if (pVar)
+ {
+ MY_ASSERT_MSG(strlen(pVar->value) == pVar->value_length,
+ ("%u != %u %.*s\n", pVar->value_length, (unsigned int)strlen(pVar->value), (int)cchName, pVar->name));
+
+ /* Make sure the variable is simple, convert it if necessary.
+ Note! Must NOT do this for the dynamic INCS of sdks/ReorderCompilerIncs.kmk */
+ if (!pVar->recursive)
+ { /* likely */ }
+ else if ( cchName < sizeof("SDK_ReorderCompilerIncs_INCS.") - 1U
+ || pszName[0] != 'S'
+ || pszName[4] != 'R'
+ || memcmp(pszName, "SDK_ReorderCompilerIncs_INCS.", sizeof("SDK_ReorderCompilerIncs_INCS.") - 1U) == 0)
+ kbuild_simplify_variable(pVar);
+ }
+ return pVar;
+}
+
+
+/**
+ * Looks up an non-empty variable when simplified and spaces skipped.
+ *
+ * This handy when emulating $(firstword )/$(lastword ) behaviour.
+ *
+ * @returns Pointer to the variable. NULL if not found.
+ * @param pszName The variable name.
+ * @param cchName The name length.
+ */
+MY_INLINE struct variable *
+kbuild_lookup_not_empty_variable_n(const char *pszName, size_t cchName)
+{
+ struct variable *pVar = kbuild_lookup_variable_n(pszName, cchName);
+ if (pVar && !pVar->recursive)
+ {
+ /*
+ * Skip spaces and make sure it's non-zero.
+ */
+ char *psz = pVar->value;
+ if (!ISSPACE(*psz))
+ { /* kind of likely */ }
+ else
+ do
+ psz++;
+ while (ISSPACE(*psz));
+
+ if (*psz)
+ { /*kind of likely */ }
+ else
+ pVar = NULL;
+ }
+ return pVar;
+}
+
+
+/**
+ * Looks up a variable.
+ * The value_length field is valid upon successful return.
+ *
+ * @returns Pointer to the variable. NULL if not found.
+ * @param pszName The variable name.
+ */
+MY_INLINE struct variable *
+kbuild_lookup_variable(const char *pszName)
+{
+ return kbuild_lookup_variable_n(pszName, strlen(pszName));
+}
+
+
+/**
+ * Looks up a variable and applies default a path to all relative paths.
+ * The value_length field is valid upon successful return.
+ *
+ * @returns Pointer to the variable. NULL if not found.
+ * @param pDefPath The default path.
+ * @param pszName The variable name.
+ * @param cchName The name length.
+ */
+MY_INLINE struct variable *
+kbuild_lookup_variable_defpath_n(struct variable *pDefPath, const char *pszName, size_t cchName)
+{
+ struct variable *pVar = kbuild_lookup_variable_n(pszName, cchName);
+ if (pVar && pDefPath)
+ {
+ assert(pVar->origin != o_automatic);
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ assert(!pVar->rdonly_val);
+#endif
+ kbuild_apply_defpath(pDefPath, &pVar->value, &pVar->value_length, &pVar->value_alloc_len, 1);
+ }
+ return pVar;
+}
+
+
+/**
+ * Looks up a variable and applies default a path to all relative paths.
+ * The value_length field is valid upon successful return.
+ *
+ * @returns Pointer to the variable. NULL if not found.
+ * @param pDefPath The default path.
+ * @param pszName The variable name.
+ */
+MY_INLINE struct variable *
+kbuild_lookup_variable_defpath(struct variable *pDefPath, const char *pszName)
+{
+ struct variable *pVar = kbuild_lookup_variable(pszName);
+ if (pVar && pDefPath)
+ {
+ assert(pVar->origin != o_automatic);
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ assert(!pVar->rdonly_val);
+#endif
+ kbuild_apply_defpath(pDefPath, &pVar->value, &pVar->value_length, &pVar->value_alloc_len, 1);
+ }
+ return pVar;
+}
+
+
+/**
+ * Gets the first defined property variable.
+ *
+ * When pBldType is given, additional property variations are consulted.
+ * See _TARGET_TOOL/r2433 in footer.kmk for the target-level difference.
+ * Similar extended property lookup is applied to the source part if the
+ * fExtendedSource parameter is non-zero (not done yet as it'll be expensive).
+ *
+ * Since r3415 this function will use $(target)_2_$(type)TOOL to cache the
+ * result of the target specific part of the lookup (_TARGET_TOOL).
+ */
+static struct variable *
+kbuild_first_prop(struct variable *pTarget, struct variable *pSource,
+ struct variable *pTool, struct variable *pType,
+ struct variable *pBldTrg, struct variable *pBldTrgArch, struct variable *pBldType,
+ const char *pszPropF1, char cchPropF1,
+ const char *pszPropF2, char cchPropF2,
+ int fExtendedSource,
+ const char *pszVarName)
+{
+ struct variable *pVar;
+ size_t cchBuf;
+ char *pszBuf;
+ char *psz, *psz1, *psz2, *psz3, *psz4, *pszEnd;
+ int fCacheIt = 1;
+
+ fExtendedSource = fExtendedSource && pBldType != NULL;
+
+ /* calc and allocate a too big name buffer. */
+ cchBuf = cchPropF2 + 1
+ + cchPropF1 + 1
+ + pTarget->value_length + 1
+ + pSource->value_length + 1
+ + (pTool ? pTool->value_length + 1 : 0)
+ + pType->value_length + 1
+ + pBldTrg->value_length + 1
+ + pBldTrgArch->value_length + 1
+ + (pBldType ? pBldType->value_length + 1 : 0);
+ pszBuf = xmalloc(cchBuf);
+
+#define ADD_VAR(pVar) do { my_memcpy(psz, (pVar)->value, (pVar)->value_length); psz += (pVar)->value_length; } while (0)
+#define ADD_STR(pszStr, cchStr) do { my_memcpy(psz, (pszStr), (cchStr)); psz += (cchStr); } while (0)
+#define ADD_CSTR(pszStr) do { my_memcpy(psz, pszStr, sizeof(pszStr) - 1); psz += sizeof(pszStr) - 1; } while (0)
+#define ADD_CH(ch) do { *psz++ = (ch); } while (0)
+
+ /*
+ * $(target)_$(source)_$(type)$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * $(target)_$(source)_$(type)$(propf2)
+ */
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CH('_');
+ ADD_VAR(pSource);
+ ADD_CH('_');
+ psz1 = psz;
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+#define DO_VARIATIONS(a_fExtended) do { \
+ psz2 = psz; \
+ ADD_CH('.'); \
+ ADD_VAR(pBldTrg); \
+ psz3 = psz; \
+ ADD_CH('.'); \
+ ADD_VAR(pBldTrgArch); \
+ psz4 = psz; \
+ if ((a_fExtended) && pBldType) \
+ { \
+ ADD_CH('.'); \
+ ADD_VAR(pBldType); \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz - pszBuf); \
+ \
+ /* <lead>.$(bld_trg).$(bld_trg_arch) */ \
+ if (!pVar) \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz4 - pszBuf); \
+ \
+ /* <lead>.$(bld_trg).$(bld_type) */ \
+ if (!pVar) \
+ { \
+ psz = psz3 + 1; \
+ ADD_VAR(pBldType); \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz - pszBuf); \
+ } \
+ \
+ /* <lead>.$(bld_trg_arch) */ \
+ if (!pVar) \
+ { \
+ psz = psz2 + 1; \
+ ADD_VAR(pBldTrgArch); \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz - pszBuf); \
+ } \
+ \
+ /* <lead>.$(bld_trg) */ \
+ if (!pVar) \
+ { \
+ psz = psz2 + 1; \
+ ADD_VAR(pBldTrg); \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz - pszBuf); \
+ } \
+ \
+ /* <lead>.$(bld_type) */ \
+ if (!pVar) \
+ { \
+ psz = psz2 + 1; \
+ ADD_VAR(pBldType); \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz - pszBuf); \
+ } \
+ } \
+ else \
+ { \
+ /* <lead>.$(bld_trg).$(bld_trg_arch) */ \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz4 - pszBuf); \
+ \
+ /* <lead>.$(bld_trg) */ \
+ if (!pVar) \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz3 - pszBuf); \
+ } \
+ \
+ /* <lead> */ \
+ if (!pVar) \
+ pVar = kbuild_lookup_not_empty_variable_n(pszBuf, psz2 - pszBuf); \
+} while (0)
+ DO_VARIATIONS(fExtendedSource);
+
+ /*
+ * $(target)_$(source)_$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type) [omit $(type) prefix to $(propf2)]
+ * ...
+ * $(target)_$(source)_$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = psz1; /* rewind to '$(target)_$(source)_' */
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(fExtendedSource);
+ }
+
+ /*
+ * $(source)_$(type)$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * $(source)_$(type)$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = pszBuf;
+ ADD_VAR(pSource);
+ ADD_CH('_');
+ psz1 = psz;
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(fExtendedSource);
+
+ /*
+ * $(source)_$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type) [omit $(type) prefix to $(propf2)]
+ * ...
+ * $(source)_$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = psz1; /* rewind to '$(source)_' */
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(fExtendedSource);
+ }
+ }
+
+ /*
+ * Check the cache: $(target)_2_$(type)$(propf2)
+ */
+ if (pVar)
+ fCacheIt = 0;
+ else if (fCacheIt)
+ {
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_STR("_2_", 3);
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+ pVar = kbuild_lookup_variable_n(pszBuf, psz - pszBuf);
+
+ /* If found, this can be duplicated and returned directly. No value stripping
+ needed as we defined it (or at least should have) ourselves. */
+ if (pVar)
+ {
+ pVar = define_variable_vl(pszVarName, strlen(pszVarName), pVar->value, pVar->value_length,
+ 1 /* duplicate */, o_local, 0 /* !recursive */);
+ free(pszBuf);
+ return pVar;
+ }
+ }
+
+ /*
+ * $(target)_$(type)$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * $(target)_$(type)$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CH('_');
+ psz1 = psz;
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(1);
+
+ /*
+ * $(target)_$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type) [omit $(type) prefix to $(propf2)]
+ * ...
+ * $(target)_$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = psz1; /* rewind to '$(target)_' */
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(1);
+ }
+ }
+
+ /*
+ * TOOL_$(tool)_$(type)$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * TOOL_$(tool)_$(type)$(propf2)
+ */
+ if (!pVar && pTool)
+ {
+ psz = pszBuf;
+ ADD_CSTR("TOOL_");
+ ADD_VAR(pTool);
+ ADD_CH('_');
+ psz1 = psz;
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(1);
+
+ /*
+ * TOOL_$(tool)_$(propf2).$(bld_trg).$(bld_trg_arch).$(bld_type) [omit $(type) prefix to $(propf2)]
+ * ...
+ * TOOL_$(tool)_$(propf2)
+ */
+ if (!pVar)
+ {
+ psz = psz1; /* rewind to 'TOOL_$(tool)_' */
+ ADD_STR(pszPropF2, cchPropF2);
+ DO_VARIATIONS(1);
+ }
+ }
+
+ /*
+ * $(type)$(propf1).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * $(type)$(propf1)
+ */
+ if (!pVar)
+ {
+ psz = pszBuf;
+ ADD_VAR(pType);
+ ADD_STR(pszPropF1, cchPropF1);
+ DO_VARIATIONS(1);
+
+ /*
+ * $(propf1).$(bld_trg).$(bld_trg_arch).$(bld_type)
+ * ...
+ * $(propf1)
+ */
+ if (!pVar)
+ {
+ psz = pszBuf;
+ ADD_STR(pszPropF1, cchPropF1);
+ DO_VARIATIONS(1);
+ }
+ }
+
+ /*
+ * Done!
+ */
+ if (pVar)
+ {
+ /* strip it */
+ psz = pVar->value;
+ pszEnd = psz + pVar->value_length;
+ while (ISBLANK(*psz))
+ psz++;
+ while (pszEnd > psz && ISBLANK(pszEnd[-1]))
+ pszEnd--;
+ if (pszEnd > psz)
+ {
+ char chSaved = *pszEnd;
+ *pszEnd = '\0';
+ pVar = define_variable_vl(pszVarName, strlen(pszVarName), psz, pszEnd - psz,
+ 1 /* duplicate */, o_local, 0 /* !recursive */);
+ *pszEnd = chSaved;
+ }
+ else
+ pVar = NULL;
+ }
+
+ /* Cache the result if needed. */
+ if (fCacheIt)
+ {
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_STR("_2_", 3);
+ ADD_VAR(pType);
+ ADD_STR(pszPropF2, cchPropF2);
+ define_variable_vl_global(pszBuf, psz - pszBuf, pVar ? pVar->value : "", pVar ? pVar->value_length : 0,
+ 1 /* duplicate */, o_file, 0 /* !recursive */, NILF);
+ }
+
+#undef ADD_VAR
+#undef ADD_STR
+#undef ADD_CSTR
+#undef ADD_CH
+ free(pszBuf);
+ return pVar;
+}
+
+
+/*
+ *
+_SOURCE_TOOL = $(strip $(firstword \
+ $($(target)_$(source)_$(type)TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_$(source)_$(type)TOOL.$(bld_trg)) \
+ $($(target)_$(source)_$(type)TOOL) \
+ $($(target)_$(source)_TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_$(source)_TOOL.$(bld_trg)) \
+ $($(target)_$(source)_TOOL) \
+ $($(source)_$(type)TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(source)_$(type)TOOL.$(bld_trg)) \
+ $($(source)_$(type)TOOL) \
+ $($(source)_TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(source)_TOOL.$(bld_trg)) \
+ $($(source)_TOOL) \
+ $($(target)_$(type)TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_$(type)TOOL.$(bld_trg)) \
+ $($(target)_$(type)TOOL) \
+ $($(target)_TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_TOOL.$(bld_trg)) \
+ $($(target)_TOOL) \
+ \ - the rest depends on pBldType, see _TARGET_TOOL and kbuild_first_prop.
+ $($(type)TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $($(type)TOOL.$(bld_trg)) \
+ $($(type)TOOL) \
+ $(TOOL.$(bld_trg).$(bld_trg_arch)) \
+ $(TOOL.$(bld_trg)) \
+ $(TOOL) ))
+*/
+static struct variable *
+kbuild_get_source_tool(struct variable *pTarget, struct variable *pSource, struct variable *pType,
+ struct variable *pBldTrg, struct variable *pBldTrgArch, struct variable *pBldType,
+ const char *pszVarName)
+{
+ struct variable *pVar = kbuild_first_prop(pTarget, pSource, NULL, pType, pBldTrg, pBldTrgArch, pBldType,
+ "TOOL", sizeof("TOOL") - 1,
+ "TOOL", sizeof("TOOL") - 1,
+ 0 /*fExtendedSource*/, pszVarName);
+ if (!pVar)
+ OSS(fatal, NILF, _("no tool for source `%s' in target `%s'!"), pSource->value, pTarget->value);
+ return pVar;
+}
+
+
+/**
+ * Helper for func_kbuild_source_tool, func_kbuild_source_one, ++.
+ */
+static int kbuild_version_to_int(const char *pszVersion, int fStrict)
+{
+ int iVer = 0;
+ if (pszVersion && pszVersion[0])
+ {
+ switch (pszVersion[0] | (pszVersion[1] << 8))
+ {
+ case '2': iVer = 2; break;
+ case '3': iVer = 3; break;
+ case '4': iVer = 4; break;
+ case '5': iVer = 5; break;
+ case '6': iVer = 6; break;
+ case '7': iVer = 7; break;
+ case '8': iVer = 8; break;
+ case '9': iVer = 9; break;
+ case '0': iVer = 0; break;
+ case '1': iVer = 1; break;
+ default:
+ while (ISBLANK(*pszVersion))
+ pszVersion++;
+ if (*pszVersion)
+ {
+ char *pszEnd = NULL;
+ long lVer;
+ errno = 0;
+ lVer = strtol(pszVersion, &pszEnd, 10);
+ iVer = (int)lVer;
+ if (fStrict)
+ {
+ if (lVer == 0 && errno != 0)
+ OSN(fatal, NILF, _("invalid version argument '%s': errno=%d"), pszVersion, errno);
+ else if (iVer != (int)lVer || iVer < 0)
+ OS(fatal, NILF, _("version argument out of range '%s'"), pszVersion);
+ else if (pszEnd)
+ {
+ while (ISBLANK(*pszEnd))
+ pszEnd++;
+ if (*pszEnd)
+ OS(fatal, NILF, _("version is not numerical '%s'"), pszVersion);
+ }
+ }
+ }
+ break;
+ }
+ }
+ return iVer;
+}
+
+
+/**
+ * "kb-src-tool <varname> [ver=0]" - implements _SOURCE_TOOL.
+ *
+ * Since r3415 an extended set of keyword variations is used on the target, tool
+ * and global properties.
+ */
+char *
+func_kbuild_source_tool(char *o, char **argv, const char *pszFuncName)
+{
+ const int iVer = kbuild_version_to_int(argv[1], 1 /*strict*/);
+ struct variable *pVar = kbuild_get_source_tool(kbuild_get_variable_n(ST("target")),
+ kbuild_get_variable_n(ST("source")),
+ kbuild_get_variable_n(ST("type")),
+ kbuild_get_variable_n(ST("bld_trg")),
+ kbuild_get_variable_n(ST("bld_trg_arch")),
+ kbuild_get_variable_n(ST("bld_type")),
+ argv[0]);
+ if (pVar)
+ o = variable_buffer_output(o, pVar->value, pVar->value_length);
+ (void)pszFuncName; (void)iVer;
+ return o;
+}
+
+
+/**
+ * Similar to _TARGET_TOOL since r3415.
+ */
+static struct variable *
+kbuild_get_object_suffix(struct variable *pTarget, struct variable *pSource,
+ struct variable *pTool, struct variable *pType,
+ struct variable *pBldTrg, struct variable *pBldTrgArch, struct variable *pBldType,
+ const char *pszVarName)
+{
+ struct variable *pVar = kbuild_first_prop(pTarget, pSource, pTool, pType, pBldTrg, pBldTrgArch, pBldType,
+ "SUFF_OBJ", sizeof("SUFF_OBJ") - 1,
+ "OBJSUFF", sizeof("OBJSUFF") - 1,
+ 0 /*fExtendedSource*/, pszVarName);
+ if (!pVar)
+ OSS(fatal, NILF, _("no OBJSUFF attribute or SUFF_OBJ default for source `%s' in target `%s'!"),
+ pSource->value, pTarget->value);
+ return pVar;
+}
+
+
+/**
+ * "kb-obj-suff <varname> [ver=0]"
+ *
+ * Since r3415 an extended set of keyword variations is used on the target, tool
+ * and global properties.
+ */
+char *
+func_kbuild_object_suffix(char *o, char **argv, const char *pszFuncName)
+{
+ const int iVer = kbuild_version_to_int(argv[1], 1 /*strict*/);
+ struct variable *pVar = kbuild_get_object_suffix(kbuild_get_variable_n(ST("target")),
+ kbuild_get_variable_n(ST("source")),
+ kbuild_get_variable_n(ST("tool")),
+ kbuild_get_variable_n(ST("type")),
+ kbuild_get_variable_n(ST("bld_trg")),
+ kbuild_get_variable_n(ST("bld_trg_arch")),
+ kbuild_get_variable_n(ST("bld_type")),
+ argv[0]);
+ if (pVar)
+ o = variable_buffer_output(o, pVar->value, pVar->value_length);
+ (void)pszFuncName; (void)iVer;
+ return o;
+
+}
+
+
+/*
+## Figure out where to put object files.
+# @param $1 source file
+# @param $2 normalized main target
+# @remark There are two major hacks here:
+# 1. Source files in the output directory are translated into a gen/ subdir.
+# 2. Catch anyone specifying $(PATH_SUB_CURRENT)/sourcefile.c.
+_OBJECT_BASE = $(PATH_TARGET)/$(2)/$(call no-root-slash,$(call no-drive,$(basename \
+ $(patsubst $(PATH_ROOT)/%,%,$(patsubst $(PATH_SUB_CURRENT)/%,%,$(patsubst $(PATH_TARGET)/$(2)/%,gen/%,$(1)))))))
+*/
+static struct variable *
+kbuild_get_object_base(struct variable *pTarget, struct variable *pSource, const char *pszVarName)
+{
+ struct variable *pPathTarget = kbuild_get_variable_n(ST("PATH_TARGET"));
+ struct variable *pPathRoot = kbuild_get_variable_n(ST("PATH_ROOT"));
+ struct variable *pPathSubCur = kbuild_get_variable_n(ST("PATH_SUB_CURRENT"));
+ const char *pszSrcPrefix = NULL;
+ size_t cchSrcPrefix = 0;
+ size_t cchSrc = 0;
+ const char *pszSrcEnd;
+ char *pszSrc;
+ char *pszResult;
+ char *psz;
+ char *pszDot;
+ size_t cch;
+
+ /*
+ * Strip the source filename of any unnecessary leading path and root specs.
+ */
+ if ( pSource->value_length > pPathTarget->value_length
+ && !strncmp(pSource->value, pPathTarget->value, pPathTarget->value_length))
+ {
+ pszSrc = pSource->value + pPathTarget->value_length;
+ pszSrcPrefix = "gen/";
+ cchSrcPrefix = sizeof("gen/") - 1;
+ if ( *pszSrc == '/'
+ && !strncmp(pszSrc + 1, pTarget->value, pTarget->value_length)
+ && ( pszSrc[pTarget->value_length + 1] == '/'
+ || pszSrc[pTarget->value_length + 1] == '\0'))
+ pszSrc += 1 + pTarget->value_length;
+ }
+ else if ( pSource->value_length > pPathRoot->value_length
+ && !strncmp(pSource->value, pPathRoot->value, pPathRoot->value_length))
+ {
+ pszSrc = pSource->value + pPathRoot->value_length;
+ if ( *pszSrc == '/'
+ && !strncmp(pszSrc + 1, pPathSubCur->value, pPathSubCur->value_length)
+ && ( pszSrc[pPathSubCur->value_length + 1] == '/'
+ || pszSrc[pPathSubCur->value_length + 1] == '\0'))
+ pszSrc += 1 + pPathSubCur->value_length;
+ }
+ else
+ pszSrc = pSource->value;
+
+ /* skip root specification */
+#ifdef HAVE_DOS_PATHS
+ if (isalpha(pszSrc[0]) && pszSrc[1] == ':')
+ pszSrc += 2;
+#endif
+ while (*pszSrc == '/'
+#ifdef HAVE_DOS_PATHS
+ || *pszSrc == '\\'
+#endif
+ )
+ pszSrc++;
+
+ /* drop the source extension. */
+ pszSrcEnd = pSource->value + pSource->value_length;
+ for (;;)
+ {
+ pszSrcEnd--;
+ if ( pszSrcEnd <= pszSrc
+ || *pszSrcEnd == '/'
+#ifdef HAVE_DOS_PATHS
+ || *pszSrcEnd == '\\'
+ || *pszSrcEnd == ':'
+#endif
+ )
+ {
+ pszSrcEnd = pSource->value + pSource->value_length;
+ break;
+ }
+ if (*pszSrcEnd == '.')
+ break;
+ }
+
+ /*
+ * Assemble the string on the heap and define the objbase variable
+ * which we then return.
+ */
+ cchSrc = pszSrcEnd - pszSrc;
+ cch = pPathTarget->value_length
+ + 1 /* slash */
+ + pTarget->value_length
+ + 1 /* slash */
+ + cchSrcPrefix
+ + cchSrc
+ + 1;
+ psz = pszResult = xmalloc(cch);
+
+ memcpy(psz, pPathTarget->value, pPathTarget->value_length); psz += pPathTarget->value_length;
+ *psz++ = '/';
+ memcpy(psz, pTarget->value, pTarget->value_length); psz += pTarget->value_length;
+ *psz++ = '/';
+ if (pszSrcPrefix)
+ {
+ memcpy(psz, pszSrcPrefix, cchSrcPrefix);
+ psz += cchSrcPrefix;
+ }
+ pszDot = psz;
+ memcpy(psz, pszSrc, cchSrc); psz += cchSrc;
+ *psz = '\0';
+
+ /* convert '..' path elements in the source to 'dt'. */
+ while ((pszDot = memchr(pszDot, '.', psz - pszDot)) != NULL)
+ {
+ if ( pszDot[1] == '.'
+ && ( pszDot == psz
+ || pszDot[-1] == '/'
+#ifdef HAVE_DOS_PATHS
+ || pszDot[-1] == '\\'
+ || pszDot[-1] == ':'
+#endif
+ )
+ && ( !pszDot[2]
+ || pszDot[2] == '/'
+#ifdef HAVE_DOS_PATHS
+ || pszDot[2] == '\\'
+ || pszDot[2] == ':'
+#endif
+ )
+ )
+ {
+ *pszDot++ = 'd';
+ *pszDot++ = 't';
+ }
+ else
+ pszDot++;
+ }
+
+ /*
+ * Define the variable in the current set and return it.
+ */
+ return define_variable_vl(pszVarName, strlen(pszVarName), pszResult, cch - 1,
+ 0 /* use pszResult */, o_local, 0 /* !recursive */);
+}
+
+
+/**
+ * "kb-obj-base <var> [ver]" Implements _OBJECT_BASE.
+ */
+char *
+func_kbuild_object_base(char *o, char **argv, const char *pszFuncName)
+{
+ const int iVer = kbuild_version_to_int(argv[1], 1 /*strict*/);
+ struct variable *pVar = kbuild_get_object_base(kbuild_lookup_variable("target"),
+ kbuild_lookup_variable("source"),
+ argv[0]);
+ if (pVar)
+ o = variable_buffer_output(o, pVar->value, pVar->value_length);
+ (void)pszFuncName; (void)iVer;
+ return o;
+}
+
+
+struct kbuild_sdks
+{
+ char *apsz[4];
+ struct variable *pa;
+ unsigned c;
+ unsigned iGlobal;
+ unsigned cGlobal;
+ unsigned iTarget;
+ unsigned cTarget;
+ unsigned iSource;
+ unsigned cSource;
+ unsigned iTargetSource;
+ unsigned cTargetSource;
+ unsigned int cchMax;
+};
+
+
+/* Fills in the SDK struct (remember to free it). */
+static void
+kbuild_get_sdks(struct kbuild_sdks *pSdks, struct variable *pTarget, struct variable *pSource,
+ struct variable *pBldType, struct variable *pBldTrg, struct variable *pBldTrgArch)
+{
+ unsigned i;
+ unsigned j;
+ size_t cchTmp, cch;
+ char *pszTmp;
+ unsigned cchCur;
+ char *pszCur;
+ const char *pszIterator;
+
+ /** @todo rewrite this to avoid sprintf and allocated_varaible_expand_2. */
+
+ /* basic init. */
+ pSdks->cchMax = 0;
+ pSdks->pa = NULL;
+ pSdks->c = 0;
+ i = 0;
+
+ /* determin required tmp variable name space. */
+ cchTmp = sizeof("$(__SDKS) $(__SDKS.) $(__SDKS.) $(__SDKS.) $(__SDKS..)")
+ + (pTarget->value_length + pSource->value_length) * 5
+ + pBldType->value_length
+ + pBldTrg->value_length
+ + pBldTrgArch->value_length
+ + pBldTrg->value_length + pBldTrgArch->value_length;
+ pszTmp = alloca(cchTmp);
+
+ /* the global sdks. */
+ pSdks->iGlobal = i;
+ pSdks->cGlobal = 0;
+ cch = sprintf(pszTmp, "$(SDKS) $(SDKS.%s) $(SDKS.%s) $(SDKS.%s) $(SDKS.%s.%s)",
+ pBldType->value,
+ pBldTrg->value,
+ pBldTrgArch->value,
+ pBldTrg->value, pBldTrgArch->value);
+ pszIterator = pSdks->apsz[0] = allocated_variable_expand_2(pszTmp, cch, NULL);
+ while ((pszCur = find_next_token(&pszIterator, &cchCur)) != 0)
+ pSdks->cGlobal++;
+ i += pSdks->cGlobal;
+
+ /* the target sdks.*/
+ pSdks->iTarget = i;
+ pSdks->cTarget = 0;
+ cch = sprintf(pszTmp, "$(%s_SDKS) $(%s_SDKS.%s) $(%s_SDKS.%s) $(%s_SDKS.%s) $(%s_SDKS.%s.%s)",
+ pTarget->value,
+ pTarget->value, pBldType->value,
+ pTarget->value, pBldTrg->value,
+ pTarget->value, pBldTrgArch->value,
+ pTarget->value, pBldTrg->value, pBldTrgArch->value);
+ pszIterator = pSdks->apsz[1] = allocated_variable_expand_2(pszTmp, cch, NULL);
+ while ((pszCur = find_next_token(&pszIterator, &cchCur)) != 0)
+ pSdks->cTarget++;
+ i += pSdks->cTarget;
+
+ /* the source sdks.*/
+ pSdks->iSource = i;
+ pSdks->cSource = 0;
+ cch = sprintf(pszTmp, "$(%s_SDKS) $(%s_SDKS.%s) $(%s_SDKS.%s) $(%s_SDKS.%s) $(%s_SDKS.%s.%s)",
+ pSource->value,
+ pSource->value, pBldType->value,
+ pSource->value, pBldTrg->value,
+ pSource->value, pBldTrgArch->value,
+ pSource->value, pBldTrg->value, pBldTrgArch->value);
+ pszIterator = pSdks->apsz[2] = allocated_variable_expand_2(pszTmp, cch, NULL);
+ while ((pszCur = find_next_token(&pszIterator, &cchCur)) != 0)
+ pSdks->cSource++;
+ i += pSdks->cSource;
+
+ /* the target + source sdks. */
+ pSdks->iTargetSource = i;
+ pSdks->cTargetSource = 0;
+ cch = sprintf(pszTmp, "$(%s_%s_SDKS) $(%s_%s_SDKS.%s) $(%s_%s_SDKS.%s) $(%s_%s_SDKS.%s) $(%s_%s_SDKS.%s.%s)",
+ pTarget->value, pSource->value,
+ pTarget->value, pSource->value, pBldType->value,
+ pTarget->value, pSource->value, pBldTrg->value,
+ pTarget->value, pSource->value, pBldTrgArch->value,
+ pTarget->value, pSource->value, pBldTrg->value, pBldTrgArch->value);
+ assert(cch < cchTmp); (void)cch;
+ pszIterator = pSdks->apsz[3] = allocated_variable_expand_2(pszTmp, cch, NULL);
+ while ((pszCur = find_next_token(&pszIterator, &cchCur)) != 0)
+ pSdks->cTargetSource++;
+ i += pSdks->cTargetSource;
+
+ pSdks->c = i;
+ if (!i)
+ return;
+
+ /*
+ * Allocate the variable array and create the variables.
+ */
+ pSdks->pa = (struct variable *)xmalloc(sizeof(pSdks->pa[0]) * i);
+ memset(pSdks->pa, 0, sizeof(pSdks->pa[0]) * i);
+ for (i = j = 0; j < sizeof(pSdks->apsz) / sizeof(pSdks->apsz[0]); j++)
+ {
+ pszIterator = pSdks->apsz[j];
+ while ((pszCur = find_next_token(&pszIterator, &cchCur)) != 0)
+ {
+ pSdks->pa[i].value = pszCur;
+ pSdks->pa[i].value_length = cchCur;
+ i++;
+ }
+ }
+ assert(i == pSdks->c);
+
+ /* terminate them (find_next_token won't work if we terminate them in the previous loop). */
+ while (i-- > 0)
+ {
+ pSdks->pa[i].value[pSdks->pa[i].value_length] = '\0';
+
+ /* calc the max variable length too. */
+ if (pSdks->cchMax < (unsigned int)pSdks->pa[i].value_length)
+ pSdks->cchMax = pSdks->pa[i].value_length;
+ }
+}
+
+
+/* releases resources allocated in the kbuild_get_sdks. */
+static void
+kbuild_put_sdks(struct kbuild_sdks *pSdks)
+{
+ unsigned j;
+ for (j = 0; j < sizeof(pSdks->apsz) / sizeof(pSdks->apsz[0]); j++)
+ free(pSdks->apsz[j]);
+ free(pSdks->pa);
+}
+
+/** Per variable struct used by kbuild_collect_source_prop and helpers. */
+struct kbuild_collect_variable
+{
+ struct variable *pVar;
+ unsigned int cchExp;
+ char *pszExp;
+};
+
+/**
+ * Helper for kbuild_collect_source_prop.
+ *
+ * Frees up any memory we've allocated for the variable values.
+ */
+static struct variable *
+kbuild_collect_source_prop_create_var(size_t cchTotal, int cVars, struct kbuild_collect_variable *paVars, int iDirection,
+ const char *pszVarName, size_t cchVarName, enum variable_origin enmVarOrigin)
+{
+ char *pszResult;
+ struct variable *pVar;
+ if (!cVars || !cchTotal)
+ {
+ pszResult = xmalloc(1);
+ *pszResult = '\0';
+ }
+ else
+ {
+ int iVar;
+ char *psz = pszResult = xmalloc(cchTotal + 1);
+ if (iDirection == 1)
+ {
+ for (iVar = 0; iVar < cVars; iVar++)
+ {
+ my_memcpy(psz, paVars[iVar].pszExp, paVars[iVar].cchExp);
+ psz += paVars[iVar].cchExp;
+ *psz++ = ' ';
+ if (paVars[iVar].pszExp != paVars[iVar].pVar->value)
+ free(paVars[iVar].pszExp);
+ }
+ }
+ else
+ {
+ iVar = cVars;
+ while (iVar-- > 0)
+ {
+ my_memcpy(psz, paVars[iVar].pszExp, paVars[iVar].cchExp);
+ psz += paVars[iVar].cchExp;
+ *psz++ = ' ';
+ if (paVars[iVar].pszExp != paVars[iVar].pVar->value)
+ free(paVars[iVar].pszExp);
+ }
+
+ }
+ assert(psz != pszResult);
+ assert(cchTotal == (size_t)(psz - pszResult));
+ psz[-1] = '\0';
+ cchTotal--;
+
+ }
+ if (enmVarOrigin == o_local)
+ pVar = define_variable_vl(pszVarName, cchVarName, pszResult, cchTotal,
+ 0 /* take pszResult */ , enmVarOrigin, 0 /* !recursive */);
+ else
+ pVar = define_variable_vl_global(pszVarName, cchVarName, pszResult, cchTotal,
+ 0 /* take pszResult */ , enmVarOrigin, 0 /* !recursive */, NILF);
+ return pVar;
+}
+
+/* this kind of stuff:
+
+defs := $(kb-src-exp defs)
+ $(TOOL_$(tool)_DEFS)\
+ $(TOOL_$(tool)_DEFS.$(bld_type))\
+ $(TOOL_$(tool)_DEFS.$(bld_trg))\
+ $(TOOL_$(tool)_DEFS.$(bld_trg_arch))\
+ $(TOOL_$(tool)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(TOOL_$(tool)_DEFS.$(bld_trg_cpu))\
+ $(TOOL_$(tool)_$(type)DEFS)\
+ $(TOOL_$(tool)_$(type)DEFS.$(bld_type))\
+ $(foreach sdk, $(SDKS.$(bld_trg)) \
+ $(SDKS.$(bld_trg).$(bld_trg_arch)) \
+ $(SDKS.$(bld_type)) \
+ $(SDKS),\
+ $(SDK_$(sdk)_DEFS)\
+ $(SDK_$(sdk)_DEFS.$(bld_type))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_cpu))\
+ $(SDK_$(sdk)_$(type)DEFS)\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_type))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_cpu)))\
+ $(DEFS)\
+ $(DEFS.$(bld_type))\
+ $(DEFS.$(bld_trg))\
+ $(DEFS.$(bld_trg_arch))\
+ $(DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(DEFS.$(bld_trg_cpu))\
+ $($(type)DEFS)\
+ $($(type)DEFS.$(bld_type))\
+ $($(type)DEFS.$(bld_trg))\
+ $($(type)DEFS.$(bld_trg_arch))\
+ $($(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(type)DEFS.$(bld_trg_cpu))\
+ $(foreach sdk, $($(target)_SDKS.$(bld_trg)) \
+ $($(target)_SDKS.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_SDKS.$(bld_type)) \
+ $($(target)_SDKS),\
+ $(SDK_$(sdk)_DEFS)\
+ $(SDK_$(sdk)_DEFS.$(bld_type))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_cpu))\
+ $(SDK_$(sdk)_$(type)DEFS)\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_type))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_cpu)))\
+ $($(target)_DEFS)\
+ $($(target)_DEFS.$(bld_type))\
+ $($(target)_DEFS.$(bld_trg))\
+ $($(target)_DEFS.$(bld_trg_arch))\
+ $($(target)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(target)_DEFS.$(bld_trg_cpu))\
+ $($(target)_$(type)DEFS)\
+ $($(target)_$(type)DEFS.$(bld_type))\
+ $($(target)_$(type)DEFS.$(bld_trg))\
+ $($(target)_$(type)DEFS.$(bld_trg_arch))\
+ $($(target)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(target)_$(type)DEFS.$(bld_trg_cpu))\
+ $(foreach sdk, $($(source)_SDKS.$(bld_trg)) \
+ $($(source)_SDKS.$(bld_trg).$(bld_trg_arch)) \
+ $($(source)_SDKS.$(bld_type)) \
+ $($(source)_SDKS),\
+ $(SDK_$(sdk)_DEFS)\
+ $(SDK_$(sdk)_DEFS.$(bld_type))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_cpu))\
+ $(SDK_$(sdk)_$(type)DEFS)\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_type))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_cpu)))\
+ $($(source)_DEFS)\
+ $($(source)_DEFS.$(bld_type))\
+ $($(source)_DEFS.$(bld_trg))\
+ $($(source)_DEFS.$(bld_trg_arch))\
+ $($(source)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(source)_DEFS.$(bld_trg_cpu))\
+ $($(source)_$(type)DEFS)\
+ $($(source)_$(type)DEFS.$(bld_type))\
+ $($(source)_$(type)DEFS.$(bld_trg))\
+ $($(source)_$(type)DEFS.$(bld_trg_arch))\
+ $($(source)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(source)_$(type)DEFS.$(bld_trg_cpu))\
+ $(foreach sdk, $($(target)_$(source)_SDKS.$(bld_trg)) \
+ $($(target)_$(source)_SDKS.$(bld_trg).$(bld_trg_arch)) \
+ $($(target)_$(source)_SDKS.$(bld_type)) \
+ $($(target)_$(source)_SDKS),\
+ $(SDK_$(sdk)_DEFS)\
+ $(SDK_$(sdk)_DEFS.$(bld_type))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_DEFS.$(bld_trg_cpu))\
+ $(SDK_$(sdk)_$(type)DEFS)\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_type))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $(SDK_$(sdk)_$(type)DEFS.$(bld_trg_cpu)))\
+ $($(target)_$(source)_DEFS)\
+ $($(target)_$(source)_DEFS.$(bld_type))\
+ $($(target)_$(source)_DEFS.$(bld_trg))\
+ $($(target)_$(source)_DEFS.$(bld_trg_arch))\
+ $($(target)_$(source)_DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(target)_$(source)_DEFS.$(bld_trg_cpu))\
+ $($(target)_$(source)_$(type)DEFS)\
+ $($(target)_$(source)_$(type)DEFS.$(bld_type))\
+ $($(target)_$(source)_$(type)DEFS.$(bld_trg))\
+ $($(target)_$(source)_$(type)DEFS.$(bld_trg_arch))\
+ $($(target)_$(source)_$(type)DEFS.$(bld_trg).$(bld_trg_arch))\
+ $($(target)_$(source)_$(type)DEFS.$(bld_trg_cpu))
+*/
+static struct variable *
+kbuild_collect_source_prop(struct variable *pTarget, struct variable *pSource,
+ struct variable *pTool, struct kbuild_sdks *pSdks,
+ struct variable *pType, struct variable *pBldType,
+ struct variable *pBldTrg, struct variable *pBldTrgArch, struct variable *pBldTrgCpu,
+ struct variable *pDefPath,
+ const char *pszProp, size_t cchProp,
+ const char *pszVarName, size_t cchVarName,
+ int iDirection)
+{
+ struct variable *pVar;
+ unsigned iSdk, iSdkEnd;
+ int cVars, iVar;
+ size_t cchTotal, cchBuf;
+ char *pszBuf, *psz, *psz2, *psz3;
+ struct kbuild_collect_variable *paVars;
+
+ assert(iDirection == 1 || iDirection == -1);
+
+ /*
+ * Calc and allocate a too big name buffer.
+ */
+ cchBuf = cchProp + 1
+ + pTarget->value_length + 1
+ + pSource->value_length + 1
+ + pSdks->cchMax + 1
+ + (pTool ? pTool->value_length + 1 : 0)
+ + pType->value_length + 1
+ + pBldTrg->value_length + 1
+ + pBldTrgArch->value_length + 1
+ + pBldTrgCpu->value_length + 1
+ + pBldType->value_length + 1
+ + sizeof("_2_");
+ pszBuf = xmalloc(cchBuf);
+
+ /*
+ * Get the variables.
+ *
+ * The compiler will get a heart attack when it sees this code ... ;-)
+ */
+ cVars = 12 * (pSdks->c + 5);
+ paVars = (struct kbuild_collect_variable *)alloca(cVars * sizeof(paVars[0]));
+
+ iVar = 0;
+ cchTotal = 0;
+
+#define ADD_VAR(pVar) do { my_memcpy(psz, (pVar)->value, (pVar)->value_length); psz += (pVar)->value_length; } while (0)
+#define ADD_STR(pszStr, cchStr) do { my_memcpy(psz, (pszStr), (cchStr)); psz += (cchStr); } while (0)
+#define ADD_CSTR(pszStr) do { my_memcpy(psz, pszStr, sizeof(pszStr) - 1); psz += sizeof(pszStr) - 1; } while (0)
+#define ADD_CH(ch) do { *psz++ = (ch); } while (0)
+#define DO_VAR_LOOKUP() \
+ do { \
+ pVar = kbuild_lookup_variable_n(pszBuf, psz - pszBuf); \
+ if (pVar) \
+ { \
+ paVars[iVar].pVar = pVar; \
+ if ( !pVar->recursive \
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar)) \
+ { \
+ paVars[iVar].pszExp = pVar->value; \
+ paVars[iVar].cchExp = pVar->value_length; \
+ if (pDefPath && paVars[iVar].cchExp) \
+ kbuild_apply_defpath(pDefPath, &paVars[iVar].pszExp, &paVars[iVar].cchExp, NULL, 0); \
+ if (paVars[iVar].cchExp) \
+ { \
+ cchTotal += paVars[iVar].cchExp + 1; \
+ iVar++; \
+ } \
+ } \
+ else \
+ { \
+ paVars[iVar].pszExp = allocated_variable_expand_2(pVar->value, pVar->value_length, &paVars[iVar].cchExp); \
+ if (pDefPath && paVars[iVar].cchExp) \
+ kbuild_apply_defpath(pDefPath, &paVars[iVar].pszExp, &paVars[iVar].cchExp, NULL, 1); \
+ if (paVars[iVar].cchExp) \
+ { \
+ cchTotal += paVars[iVar].cchExp + 1; \
+ iVar++; \
+ } \
+ else \
+ free(paVars[iVar].pszExp); \
+ } \
+ } \
+ } while (0)
+#define DO_SINGLE_PSZ3_VARIATION() \
+ do { \
+ DO_VAR_LOOKUP(); \
+ \
+ ADD_CH('.'); \
+ psz3 = psz; \
+ ADD_VAR(pBldType); \
+ DO_VAR_LOOKUP(); \
+ \
+ psz = psz3; \
+ ADD_VAR(pBldTrg); \
+ DO_VAR_LOOKUP(); \
+ \
+ psz = psz3; \
+ ADD_VAR(pBldTrgArch); \
+ DO_VAR_LOOKUP(); \
+ \
+ psz = psz3; \
+ ADD_VAR(pBldTrg); \
+ ADD_CH('.'); \
+ ADD_VAR(pBldTrgArch); \
+ DO_VAR_LOOKUP(); \
+ \
+ psz = psz3; \
+ ADD_VAR(pBldTrgCpu); \
+ DO_VAR_LOOKUP(); \
+ } while (0)
+
+#define DO_DOUBLE_PSZ2_VARIATION() \
+ do { \
+ psz2 = psz; \
+ ADD_STR(pszProp, cchProp); \
+ DO_SINGLE_PSZ3_VARIATION(); \
+ \
+ /* add prop before type */ \
+ psz = psz2; \
+ ADD_VAR(pType); \
+ ADD_STR(pszProp, cchProp); \
+ DO_SINGLE_PSZ3_VARIATION(); \
+ } while (0)
+
+ /*
+ * The first bunch part may be cached on the target already, check that first:
+ */
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CSTR("_2_");
+ ADD_VAR(pType);
+ ADD_STR(pszProp, cchProp);
+ ADD_CH('_');
+ ADD_VAR(pBldType);
+ *psz = '\0';
+ pVar = kbuild_lookup_variable_n(pszBuf, psz - pszBuf);
+ if (pVar)
+ assert(iVar == 0);
+ else
+ {
+ /* the tool (lowest priority). */
+ psz = pszBuf;
+ ADD_CSTR("TOOL_");
+ ADD_VAR(pTool);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+
+ /* the global sdks. */
+ iSdkEnd = iDirection == 1 ? pSdks->iGlobal + pSdks->cGlobal : pSdks->iGlobal - 1;
+ for (iSdk = iDirection == 1 ? pSdks->iGlobal : pSdks->iGlobal + pSdks->cGlobal - 1;
+ iSdk != iSdkEnd;
+ iSdk += iDirection)
+ {
+ struct variable *pSdk = &pSdks->pa[iSdk];
+ psz = pszBuf;
+ ADD_CSTR("SDK_");
+ ADD_VAR(pSdk);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+ }
+
+ /* the globals. */
+ psz = pszBuf;
+ DO_DOUBLE_PSZ2_VARIATION();
+
+ /* the target sdks. */
+ iSdkEnd = iDirection == 1 ? pSdks->iTarget + pSdks->cTarget : pSdks->iTarget - 1;
+ for (iSdk = iDirection == 1 ? pSdks->iTarget : pSdks->iTarget + pSdks->cTarget - 1;
+ iSdk != iSdkEnd;
+ iSdk += iDirection)
+ {
+ struct variable *pSdk = &pSdks->pa[iSdk];
+ psz = pszBuf;
+ ADD_CSTR("SDK_");
+ ADD_VAR(pSdk);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+ }
+
+ /* the target. */
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+
+ /*
+ * Cache the result thus far (frees up anything we might've allocated above).
+ */
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CSTR("_2_");
+ ADD_VAR(pType);
+ ADD_STR(pszProp, cchProp);
+ ADD_CH('_');
+ ADD_VAR(pBldType);
+ *psz = '\0';
+ assert(iVar <= cVars);
+ pVar = kbuild_collect_source_prop_create_var(cchTotal, iVar, paVars, iDirection, pszBuf, psz - pszBuf, o_file);
+ assert(pVar);
+ }
+
+ /* Now we use the cached target variable. */
+ paVars[0].pVar = pVar;
+ paVars[0].pszExp = pVar->value;
+ paVars[0].cchExp = pVar->value_length;
+ cchTotal = paVars[0].cchExp + 1;
+ iVar = 1;
+
+ /* the source sdks. */
+ iSdkEnd = iDirection == 1 ? pSdks->iSource + pSdks->cSource : pSdks->iSource - 1;
+ for (iSdk = iDirection == 1 ? pSdks->iSource : pSdks->iSource + pSdks->cSource - 1;
+ iSdk != iSdkEnd;
+ iSdk += iDirection)
+ {
+ struct variable *pSdk = &pSdks->pa[iSdk];
+ psz = pszBuf;
+ ADD_CSTR("SDK_");
+ ADD_VAR(pSdk);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+ }
+
+ /* the source. */
+ psz = pszBuf;
+ ADD_VAR(pSource);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+
+ /* the target + source sdks. */
+ iSdkEnd = iDirection == 1 ? pSdks->iTargetSource + pSdks->cTargetSource : pSdks->iTargetSource - 1;
+ for (iSdk = iDirection == 1 ? pSdks->iTargetSource : pSdks->iTargetSource + pSdks->cTargetSource - 1;
+ iSdk != iSdkEnd;
+ iSdk += iDirection)
+ {
+ struct variable *pSdk = &pSdks->pa[iSdk];
+ psz = pszBuf;
+ ADD_CSTR("SDK_");
+ ADD_VAR(pSdk);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+ }
+
+ /* the target + source. */
+ psz = pszBuf;
+ ADD_VAR(pTarget);
+ ADD_CH('_');
+ ADD_VAR(pSource);
+ ADD_CH('_');
+ DO_DOUBLE_PSZ2_VARIATION();
+
+ free(pszBuf);
+
+ /*
+ * Construct the result value (frees up anything we might've allocated above).
+ */
+ assert(iVar <= cVars);
+ return kbuild_collect_source_prop_create_var(cchTotal, iVar, paVars, iDirection, pszVarName, cchVarName, o_local);
+
+#undef ADD_VAR
+#undef ADD_STR
+#undef ADD_CSTR
+#undef ADD_CH
+#undef DO_VAR_LOOKUP
+#undef DO_DOUBLE_PSZ2_VARIATION
+#undef DO_SINGLE_PSZ3_VARIATION
+}
+
+
+/* "kb-src-prop <prop> <var> <dir> [defpath] [ver]"
+ get a source property. */
+char *
+func_kbuild_source_prop(char *o, char **argv, const char *pszFuncName)
+{
+ struct variable *pTarget = kbuild_get_variable_n(ST("target"));
+ struct variable *pSource = kbuild_get_variable_n(ST("source"));
+ struct variable *pDefPath = NULL;
+ struct variable *pType = kbuild_get_variable_n(ST("type"));
+ struct variable *pTool = kbuild_get_variable_n(ST("tool"));
+ struct variable *pBldType = kbuild_get_variable_n(ST("bld_type"));
+ struct variable *pBldTrg = kbuild_get_variable_n(ST("bld_trg"));
+ struct variable *pBldTrgArch = kbuild_get_variable_n(ST("bld_trg_arch"));
+ struct variable *pBldTrgCpu = kbuild_get_variable_n(ST("bld_trg_cpu"));
+ struct variable *pVar;
+ struct kbuild_sdks Sdks;
+ int iVer = 0;
+ int iDirection;
+ if (!strcmp(argv[2], "left-to-right"))
+ iDirection = 1;
+ else if (!strcmp(argv[2], "right-to-left"))
+ iDirection = -1;
+ else
+ OS(fatal, NILF, _("incorrect direction argument `%s'!"), argv[2]);
+ if (argv[3])
+ {
+ const char *psz = argv[3];
+ while (ISSPACE(*psz))
+ psz++;
+ if (*psz)
+ pDefPath = kbuild_get_variable_n(ST("defpath"));
+ if (argv[4])
+ iVer = kbuild_version_to_int(argv[4], 1 /*strict*/);
+ }
+
+ kbuild_get_sdks(&Sdks, pTarget, pSource, pBldType, pBldTrg, pBldTrgArch);
+
+ pVar = kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType,
+ pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu,
+ pDefPath,
+ argv[0], strlen(argv[0]),
+ argv[1], strlen(argv[1]),
+ iDirection);
+ if (pVar)
+ o = variable_buffer_output(o, pVar->value, pVar->value_length);
+
+ kbuild_put_sdks(&Sdks);
+ (void)pszFuncName; (void)iVer;
+ return o;
+}
+
+
+/*
+dep := $(obj)$(SUFF_DEP)
+obj := $(outbase)$(objsuff)
+dirdep := $(call DIRDEP,$(dir $(outbase)))
+PATH_$(target)_$(source) := $(patsubst %/,%,$(dir $(outbase)))
+*/
+static struct variable *
+kbuild_set_object_name_and_dep_and_dirdep_and_PATH_target_source(struct variable *pTarget, struct variable *pSource,
+ struct variable *pOutBase, struct variable *pObjSuff,
+ const char *pszVarName, struct variable **ppDep,
+ struct variable **ppDirDep)
+{
+ struct variable *pDepSuff = kbuild_get_variable_n(ST("SUFF_DEP"));
+ struct variable *pObj;
+ size_t cch = pOutBase->value_length + pObjSuff->value_length + pDepSuff->value_length + 1;
+ char *pszResult = alloca(cch);
+ char *pszName, *psz;
+
+ /*
+ * dep.
+ */
+ psz = pszResult;
+ memcpy(psz, pOutBase->value, pOutBase->value_length); psz += pOutBase->value_length;
+ memcpy(psz, pObjSuff->value, pObjSuff->value_length); psz += pObjSuff->value_length;
+ memcpy(psz, pDepSuff->value, pDepSuff->value_length + 1);
+ *ppDep = define_variable_vl("dep", 3, pszResult, cch - 1, 1 /*dup*/, o_local, 0 /* !recursive */);
+
+ /*
+ * obj
+ */
+ *psz = '\0';
+ pObj = define_variable_vl(pszVarName, strlen(pszVarName), pszResult, psz - pszResult,
+ 1/* dup */, o_local, 0 /* !recursive */);
+
+ /*
+ * PATH_$(target)_$(source) - this is global!
+ */
+ /* calc variable name. */
+ cch = sizeof("PATH_")-1 + pTarget->value_length + sizeof("_")-1 + pSource->value_length;
+ psz = pszName = alloca(cch + 1);
+ memcpy(psz, "PATH_", sizeof("PATH_") - 1); psz += sizeof("PATH_") - 1;
+ memcpy(psz, pTarget->value, pTarget->value_length); psz += pTarget->value_length;
+ *psz++ = '_';
+ memcpy(psz, pSource->value, pSource->value_length + 1);
+
+ /* strip off the filename. */
+ psz = pszResult + pOutBase->value_length;
+ for (;;)
+ {
+ psz--;
+ if (psz <= pszResult)
+ OS(fatal, NULL, "whut!?! no path? result=`%s'", pszResult);
+#ifdef HAVE_DOS_PATHS
+ if (*psz == ':')
+ {
+ psz++;
+ break;
+ }
+#endif
+ if ( *psz == '/'
+#ifdef HAVE_DOS_PATHS
+ || *psz == '\\'
+#endif
+ )
+ {
+ while ( psz - 1 > pszResult
+ && psz[-1] == '/'
+#ifdef HAVE_DOS_PATHS
+ || psz[-1] == '\\'
+#endif
+ )
+ psz--;
+#ifdef HAVE_DOS_PATHS
+ if (psz == pszResult + 2 && pszResult[1] == ':')
+ psz++;
+#endif
+ break;
+ }
+ }
+ *psz = '\0';
+
+ /* set global variable */
+ define_variable_vl_global(pszName, cch, pszResult, psz - pszResult, 1/*dup*/, o_file, 0 /* !recursive */, NILF);
+
+ /*
+ * dirdep
+ */
+ if ( psz[-1] != '/'
+#ifdef HAVE_DOS_PATHS
+ && psz[-1] != '\\'
+ && psz[-1] != ':'
+#endif
+ )
+ {
+ *psz++ = '/';
+ *psz = '\0';
+ }
+ *ppDirDep = define_variable_vl("dirdep", 6, pszResult, psz - pszResult, 1/*dup*/, o_local, 0 /* !recursive */);
+
+ return pObj;
+}
+
+
+/**
+ *
+ *
+ * Setup the base variables for def_target_source_c_cpp_asm_new:
+ * @code
+
+X := $(kb-src-tool tool)
+x := $(kb-obj-base outbase)
+x := $(kb-obj-suff objsuff)
+obj := $(outbase)$(objsuff)
+PATH_$(target)_$(source) := $(patsubst %/,%,$(dir $(outbase)))
+
+x := $(kb-src-prop DEFS,defs,left-to-right)
+x := $(kb-src-prop INCS,incs,right-to-left)
+x := $(kb-src-prop FLAGS,flags,right-to-left)
+
+x := $(kb-src-prop DEPS,deps,left-to-right)
+dirdep := $(call DIRDEP,$(dir $(outbase)))
+dep := $(obj)$(SUFF_DEP)
+ * @endcode
+ *
+ * argv[0] is the function version. Prior to r1792 (early 0.1.5) this
+ * was undefined and footer.kmk always passed an empty string.
+ *
+ * Version 2, as implemented in r1797, will make use of the async
+ * includedep queue feature. This means the files will be read by one or
+ * more background threads, leaving the eval'ing to be done later on by
+ * the main thread (in snap_deps).
+ *
+ * Version 3, as implemented in rXXXX, will check
+ * TOOL_$(tool)_COMPILE_$(type)_USES_KOBJCACHE and use
+ * def_target_source_rule_v3plus_objcache if it's non-empty or
+ * def_target_source_rule_v3plus if it's empty (version 2 and older will use
+ * def_target_source_rule). Version 3 will also skip defining several
+ * properties that it considers legacy (todo: which).
+ *
+ * Version 4, as implemented in r3415, will use the extended tool resolution at
+ * the target level (not source) as implemented by the _TARGET_TOOL update in
+ * r2433.
+ *
+ * With r3415 the $(target)_2_$(type)TOOL will be used for caching purposes
+ * regardless of version.
+ */
+char *
+func_kbuild_source_one(char *o, char **argv, const char *pszFuncName)
+{
+ static int s_fNoCompileDepsDefined = -1;
+ struct variable *pTarget = kbuild_get_variable_n(ST("target"));
+ struct variable *pSource = kbuild_get_variable_n(ST("source"));
+ struct variable *pDefPath = kbuild_get_variable_n(ST("defpath"));
+ struct variable *pType = kbuild_get_variable_n(ST("type"));
+ struct variable *pBldType = kbuild_get_variable_n(ST("bld_type"));
+ struct variable *pBldTrg = kbuild_get_variable_n(ST("bld_trg"));
+ struct variable *pBldTrgArch= kbuild_get_variable_n(ST("bld_trg_arch"));
+ struct variable *pBldTrgCpu = kbuild_get_variable_n(ST("bld_trg_cpu"));
+ const int iVer = kbuild_version_to_int(argv[0], 0 /*strict*/);
+ struct variable *pTool = kbuild_get_source_tool(pTarget, pSource, pType, pBldTrg, pBldTrgArch,
+ iVer >= 4 ? pBldType : NULL, "tool");
+ struct variable *pOutBase = kbuild_get_object_base(pTarget, pSource, "outbase");
+ struct variable *pObjSuff = kbuild_get_object_suffix(pTarget, pSource, pTool, pType, pBldTrg, pBldTrgArch,
+ iVer >= 4 ? pBldType : NULL, "objsuff");
+ struct variable *pDeps, *pOrderDeps, *pDirDep, *pDep, *pVar, *pOutput, *pOutputMaybe;
+#if 0 /* not used */
+ struct variable *pDefs, *pIncs, *pFlags;
+#endif
+ struct variable *pObj = kbuild_set_object_name_and_dep_and_dirdep_and_PATH_target_source(pTarget, pSource, pOutBase, pObjSuff, "obj", &pDep, &pDirDep);
+ int fInstallOldObjsVar = 0;
+ char *pszDstVar, *pszDst, *pszSrcVar, *pszSrc, *pszVal, *psz;
+ char *pszSavedVarBuf;
+ unsigned cchSavedVarBuf;
+ size_t cch;
+ struct kbuild_sdks Sdks;
+
+ /*
+ * Gather properties.
+ */
+ kbuild_get_sdks(&Sdks, pTarget, pSource, pBldType, pBldTrg, pBldTrgArch);
+
+ if (pDefPath && !pDefPath->value_length)
+ pDefPath = NULL;
+
+
+ /*pDefs =*/ kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType, pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu, NULL,
+ ST("DEFS"), ST("defs"), 1 /* left-to-right */);
+ /*pIncs =*/ kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType, pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu, pDefPath,
+ ST("INCS"), ST("incs"), -1 /* right-to-left */);
+ /*pFlags =*/ kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType, pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu, NULL,
+ ST("FLAGS"), ST("flags"), 1 /* left-to-right */);
+ pDeps = kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType, pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu, pDefPath,
+ ST("DEPS"), ST("deps"), 1 /* left-to-right */);
+ pOrderDeps = kbuild_collect_source_prop(pTarget, pSource, pTool, &Sdks, pType, pBldType, pBldTrg, pBldTrgArch, pBldTrgCpu, pDefPath,
+ ST("ORDERDEPS"), ST("orderdeps"), 1 /* left-to-right */);
+
+ /*
+ * If we've got a default path, we must expand the source now.
+ * If we do this too early, "<source>_property = stuff" won't work becuase
+ * our 'source' value isn't what the user expects.
+ */
+ if (pDefPath)
+ {
+ /** @todo assert(pSource->origin != o_automatic); We're changing 'source'
+ * from the foreach loop! */
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ assert(!pSource->rdonly_val);
+#endif
+ kbuild_apply_defpath(pDefPath, &pSource->value, &pSource->value_length, &pSource->value_alloc_len, 1 /* can free */);
+ }
+
+ /*
+ # dependencies
+ ifndef NO_COMPILE_DEPS
+ _DEPFILES_INCLUDED += $(dep)
+ $(if $(wildcard $(dep)),$(eval include $(dep)))
+ endif
+ */
+ if (s_fNoCompileDepsDefined == -1)
+ s_fNoCompileDepsDefined = kbuild_lookup_variable_n(ST("NO_COMPILE_DEPS")) != NULL;
+ if (!s_fNoCompileDepsDefined)
+ {
+ pVar = kbuild_query_recursive_variable_n("_DEPFILES_INCLUDED", sizeof("_DEPFILES_INCLUDED") - 1);
+ if (pVar)
+ {
+ if (pVar->recursive)
+ pVar = kbuild_simplify_variable(pVar);
+ append_string_to_variable(pVar, pDep->value, pDep->value_length, 1 /* append */);
+ }
+ else
+ define_variable_vl_global("_DEPFILES_INCLUDED", sizeof("_DEPFILES_INCLUDED") - 1,
+ pDep->value, pDep->value_length,
+ 1 /* duplicate_value */,
+ o_file,
+ 0 /* recursive */,
+ NULL /* flocp */);
+
+ eval_include_dep(pDep->value, NILF, iVer >= 2 ? incdep_queue : incdep_read_it);
+ }
+
+ /*
+ # call the tool
+ $(target)_$(source)_CMDS_ := $(TOOL_$(tool)_COMPILE_$(type)_CMDS)
+ $(target)_$(source)_OUTPUT_ := $(TOOL_$(tool)_COMPILE_$(type)_OUTPUT)
+ $(target)_$(source)_OUTPUT_MAYBE_ := $(TOOL_$(tool)_COMPILE_$(type)_OUTPUT_MAYBE)
+ $(target)_$(source)_DEPEND_ := $(TOOL_$(tool)_COMPILE_$(type)_DEPEND) $(deps) $(source)
+ $(target)_$(source)_DEPORD_ := $(TOOL_$(tool)_COMPILE_$(type)_DEPORD) $(dirdep)
+ */
+ /** @todo Make all these local variables, if someone needs the info later it
+ * can be recalculated. (Ticket #80.) */
+ cch = sizeof("TOOL_") + pTool->value_length + sizeof("_COMPILE_") + pType->value_length + sizeof("_USES_KOBJCACHE");
+ if (cch < pTarget->value_length + sizeof("$(_2_OBJS)"))
+ cch = pTarget->value_length + sizeof("$(_2_OBJS)");
+ psz = pszSrcVar = alloca(cch);
+ memcpy(psz, "TOOL_", sizeof("TOOL_") - 1); psz += sizeof("TOOL_") - 1;
+ memcpy(psz, pTool->value, pTool->value_length); psz += pTool->value_length;
+ memcpy(psz, "_COMPILE_", sizeof("_COMPILE_") - 1); psz += sizeof("_COMPILE_") - 1;
+ memcpy(psz, pType->value, pType->value_length); psz += pType->value_length;
+ pszSrc = psz;
+
+ cch = pTarget->value_length + 1 + pSource->value_length + sizeof("_OUTPUT_MAYBE_");
+ psz = pszDstVar = alloca(cch);
+ memcpy(psz, pTarget->value, pTarget->value_length); psz += pTarget->value_length;
+ *psz++ = '_';
+ memcpy(psz, pSource->value, pSource->value_length); psz += pSource->value_length;
+ pszDst = psz;
+
+ memcpy(pszSrc, "_CMDS", sizeof("_CMDS"));
+ memcpy(pszDst, "_CMDS_", sizeof("_CMDS_"));
+ pVar = kbuild_get_recursive_variable(pszSrcVar);
+ if (iVer <= 2)
+ do_variable_definition_2(NILF, pszDstVar, pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+ do_variable_definition_2(NILF, "kbsrc_cmds", pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+
+ memcpy(pszSrc, "_OUTPUT", sizeof("_OUTPUT"));
+ memcpy(pszDst, "_OUTPUT_", sizeof("_OUTPUT_"));
+ pVar = kbuild_get_recursive_variable(pszSrcVar);
+ if (iVer <= 2)
+ pOutput = do_variable_definition_2(NILF, pszDstVar, pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+ pOutput = do_variable_definition_2(NILF, "kbsrc_output", pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+
+ memcpy(pszSrc, "_OUTPUT_MAYBE", sizeof("_OUTPUT_MAYBE"));
+ memcpy(pszDst, "_OUTPUT_MAYBE_", sizeof("_OUTPUT_MAYBE_"));
+ pVar = kbuild_query_recursive_variable(pszSrcVar);
+ if (pVar)
+ {
+ if (iVer <= 2)
+ pOutputMaybe = do_variable_definition_2(NILF, pszDstVar, pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+ pOutputMaybe = do_variable_definition_2(NILF, "kbsrc_output_maybe", pVar->value, pVar->value_length,
+ !pVar->recursive, 0, o_local, f_simple, 0 /* !target_var */);
+ }
+ else
+ {
+ if (iVer <= 2)
+ pOutputMaybe = do_variable_definition_2(NILF, pszDstVar, "", 0, 1, 0, o_local, f_simple, 0 /* !target_var */);
+ pOutputMaybe = do_variable_definition_2(NILF, "kbsrc_output_maybe", "", 0, 1, 0, o_local, f_simple, 0 /* !target_var */);
+ }
+
+ memcpy(pszSrc, "_DEPEND", sizeof("_DEPEND"));
+ memcpy(pszDst, "_DEPEND_", sizeof("_DEPEND_"));
+ pVar = kbuild_get_recursive_variable(pszSrcVar);
+ psz = pszVal = xmalloc(pVar->value_length + 1 + pDeps->value_length + 1 + pSource->value_length + 1);
+ memcpy(psz, pVar->value, pVar->value_length); psz += pVar->value_length;
+ *psz++ = ' ';
+ memcpy(psz, pDeps->value, pDeps->value_length); psz += pDeps->value_length;
+ *psz++ = ' ';
+ memcpy(psz, pSource->value, pSource->value_length + 1);
+ if (iVer <= 2)
+ do_variable_definition_2(NILF, pszDstVar, pszVal, pVar->value_length + 1 + pDeps->value_length + 1 + pSource->value_length,
+ !pVar->recursive && !pDeps->recursive && !pSource->recursive,
+ NULL, o_local, f_simple, 0 /* !target_var */);
+ do_variable_definition_2(NILF, "kbsrc_depend", pszVal, pVar->value_length + 1 + pDeps->value_length + 1 + pSource->value_length,
+ !pVar->recursive && !pDeps->recursive && !pSource->recursive,
+ pszVal, o_local, f_simple, 0 /* !target_var */);
+
+ memcpy(pszSrc, "_DEPORD", sizeof("_DEPORD"));
+ memcpy(pszDst, "_DEPORD_", sizeof("_DEPORD_"));
+ pVar = kbuild_get_recursive_variable(pszSrcVar);
+ psz = pszVal = xmalloc(pVar->value_length + 1 + pDirDep->value_length + 1 + pOrderDeps->value_length + 1);
+ memcpy(psz, pVar->value, pVar->value_length); psz += pVar->value_length;
+ *psz++ = ' ';
+ memcpy(psz, pDirDep->value, pDirDep->value_length); psz += pDirDep->value_length;
+ *psz++ = ' ';
+ memcpy(psz, pOrderDeps->value, pOrderDeps->value_length + 1);
+ if (iVer <= 2)
+ do_variable_definition_2(NILF, pszDstVar, pszVal,
+ pVar->value_length + 1 + pDirDep->value_length + 1 + pOrderDeps->value_length,
+ !pVar->recursive && !pDirDep->recursive && !pOrderDeps->recursive,
+ NULL, o_local, f_simple, 0 /* !target_var */);
+ do_variable_definition_2(NILF, "kbsrc_depord", pszVal,
+ pVar->value_length + 1 + pDirDep->value_length + 1 + pOrderDeps->value_length,
+ !pVar->recursive && !pDirDep->recursive && !pOrderDeps->recursive,
+ pszVal, o_local, f_simple, 0 /* !target_var */);
+
+ /*
+ _OUT_FILES += $($(target)_$(source)_OUTPUT_) $($(target)_$(source)_OUTPUT_MAYBE_)
+ */
+ pVar = kbuild_get_variable_n(ST("_OUT_FILES"));
+ append_string_to_variable(pVar, pOutput->value, pOutput->value_length, 1 /* append */);
+ if (pOutputMaybe->value_length)
+ append_string_to_variable(pVar, pOutputMaybe->value, pOutputMaybe->value_length, 1 /* append */);
+
+ /*
+ $(target)_2_OBJS += $(obj)
+ */
+ memcpy(pszDstVar + pTarget->value_length, "_2_OBJS", sizeof("_2_OBJS"));
+ pVar = kbuild_query_recursive_variable_n(pszDstVar, pTarget->value_length + sizeof("_2_OBJS") - 1);
+ fInstallOldObjsVar |= iVer <= 2 && (!pVar || !pVar->value_length);
+ if (pVar)
+ {
+ if (pVar->recursive)
+ pVar = kbuild_simplify_variable(pVar);
+ append_string_to_variable(pVar, pObj->value, pObj->value_length, 1 /* append */);
+ }
+ else
+ define_variable_vl_global(pszDstVar, pTarget->value_length + sizeof("_2_OBJS") - 1,
+ pObj->value, pObj->value_length,
+ 1 /* duplicate_value */,
+ o_file,
+ 0 /* recursive */,
+ NULL /* flocp */);
+
+ /*
+ * Install legacy variable.
+ */
+ if (fInstallOldObjsVar)
+ {
+ /* $(target)_OBJS_ = $($(target)_2_OBJS)*/
+ memcpy(pszDstVar + pTarget->value_length, "_OBJS_", sizeof("_OBJS_"));
+
+ pszSrcVar[0] = '$';
+ pszSrcVar[1] = '(';
+ memcpy(pszSrcVar + 2, pTarget->value, pTarget->value_length);
+ psz = pszSrcVar + 2 + pTarget->value_length;
+ memcpy(psz, "_2_OBJS)", sizeof("_2_OBJS)"));
+
+ define_variable_vl_global(pszDstVar, pTarget->value_length + sizeof("_OBJS_") - 1,
+ pszSrcVar, pTarget->value_length + sizeof("$(_2_OBJS)") - 1,
+ 1 /* duplicate_value */,
+ o_file,
+ 1 /* recursive */,
+ NULL /* flocp */);
+ }
+
+ /*
+ $(eval $(def_target_source_rule))
+ */
+ if (iVer > 2)
+ {
+ /*ifneq ($(TOOL_$(tool)_COMPILE_$(type)_USES_KOBJCACHE),)*/
+ int fUsesObjCache = 0;
+ memcpy(pszSrc, "_USES_KOBJCACHE", sizeof("_USES_KOBJCACHE"));
+ pVar = lookup_variable(pszSrcVar, pszSrc + sizeof("_USES_KOBJCACHE") - 1 - pszSrcVar);
+ if (pVar)
+ {
+ if ( !pVar->recursive
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ fUsesObjCache = pVar->value_length > 0;
+ else
+ {
+ unsigned int cchTmp = 0;
+ char *pszTmp = allocated_variable_expand_2(pVar->value, pVar->value_length, &cchTmp);
+ free(pszTmp);
+ fUsesObjCache = cchTmp > 0;
+ }
+ }
+ if (!fUsesObjCache)
+ pVar = kbuild_get_recursive_variable("def_target_source_rule_v3plus");
+ else
+ pVar = kbuild_get_recursive_variable("def_target_source_rule_v3plus_objcache");
+ }
+ else
+ pVar = kbuild_get_recursive_variable("def_target_source_rule");
+ pszVal = variable_expand_string_2(o, pVar->value, pVar->value_length, &psz);
+ assert(!((size_t)pszVal & 3));
+
+ install_variable_buffer(&pszSavedVarBuf, &cchSavedVarBuf);
+ eval_buffer(pszVal, NULL, psz);
+ restore_variable_buffer(pszSavedVarBuf, cchSavedVarBuf);
+
+ kbuild_put_sdks(&Sdks);
+ (void)pszFuncName;
+
+ *pszVal = '\0';
+ return pszVal;
+}
+
+/*
+
+## Inherit one template property in a non-accumulative manner.
+# @param $(prop) Property name
+# @param $(target) Target name
+# @todo fix the precedence order for some properties.
+define def_inherit_template_one
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop)
+ifndef $(target)_$(prop)
+$(target)_$(prop) := $(TEMPLATE_$($(target)_TEMPLATE)_$(prop))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg)
+ifndef $(target)_$(prop).$(bld_trg)
+$(target)_$(prop).$(bld_trg) := $(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch)
+ifndef $(target)_$(prop).$(bld_trg).$(bld_trg_arch)
+$(target)_$(prop).$(bld_trg).$(bld_trg_arch) := $(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch)
+ifndef $(target)_$(prop).$(bld_trg_arch)
+$(target)_$(prop).$(bld_trg_arch) := $(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu)
+ifndef $(target)_$(prop).$(bld_trg_cpu)
+$(target)_$(prop).$(bld_trg_cpu) := $(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu))
+endif
+endif
+endef
+
+## Inherit one template property in a non-accumulative manner, deferred expansion.
+# @param 1: $(prop) Property name
+# @param 2: $(target) Target name
+# @todo fix the precedence order for some properties.
+# @remark this define relies on double evaluation
+define def_inherit_template_one_deferred
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop)
+ifndef $(target)_$(prop)
+$(target)_$(prop) = $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg)
+ifndef $(target)_$(prop).$(bld_trg)
+$(target)_$(prop).$(bld_trg) = $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch)
+ifndef $(target)_$(prop).$(bld_trg).$(bld_trg_arch)
+$(target)_$(prop).$(bld_trg).$(bld_trg_arch) = $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch)
+ifndef $(target)_$(prop).$(bld_trg_arch)
+$(target)_$(prop).$(bld_trg_arch) = $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch))
+endif
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu)
+ifndef $(target)_$(prop).$(bld_trg_cpu)
+$(target)_$(prop).$(bld_trg_cpu) = $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu))
+endif
+endif
+endef
+
+## Inherit one acculumlative template property where the 'most significant' items are at the left end.
+# @param $(prop) Property name
+# @param $(target) Target name
+define def_inherit_template_one_accumulate_l
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop)
+ ifeq ($$(flavor $(target)_$(prop)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop))
+ endif
+$(target)_$(prop) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(KBUILD_TYPE)
+ ifeq ($$(flavor $(target)_$(prop).$(KBUILD_TYPE)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(KBUILD_TYPE))
+ endif
+$(target)_$(prop).$(KBUILD_TYPE) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(KBUILD_TYPE))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg))
+ endif
+$(target)_$(prop).$(bld_trg) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg).$(bld_trg_arch)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg).$(bld_trg_arch))
+ endif
+$(target)_$(prop).$(bld_trg).$(bld_trg_arch) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg_cpu)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg_cpu))
+ endif
+$(target)_$(prop).$(bld_trg_cpu) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg_arch)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg_arch))
+ endif
+$(target)_$(prop).$(bld_trg_arch) += $$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch))
+endif
+endef
+
+## Inherit one acculumlative template property where the 'most significant' items are at the right end.
+# @param $(prop) Property name
+# @param $(target) Target name
+define def_inherit_template_one_accumulate_r
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop)
+ ifeq ($$(flavor $(target)_$(prop)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop))
+ endif
+$(target)_$(prop) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(KBUILD_TYPE)
+ ifeq ($$(flavor $(target)_$(prop).$(KBUILD_TYPE)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(KBUILD_TYPE))
+ endif
+$(target)_$(prop).$(KBUILD_TYPE) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(KBUILD_TYPE))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg))
+ endif
+$(target)_$(prop).$(bld_trg) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg).$(bld_trg_arch)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg).$(bld_trg_arch))
+ endif
+$(target)_$(prop).$(bld_trg).$(bld_trg_arch) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg).$(bld_trg_arch))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg_cpu)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg_cpu))
+ endif
+$(target)_$(prop).$(bld_trg_cpu) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_cpu))
+endif
+ifdef TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch)
+ ifeq ($$(flavor $(target)_$(prop).$(bld_trg_arch)),simple)
+ $$(evalcall2 def_simple_2_recursive,$(target)_$(prop).$(bld_trg_arch))
+ endif
+$(target)_$(prop).$(bld_trg_arch) <=$$(TEMPLATE_$($(target)_TEMPLATE)_$(prop).$(bld_trg_arch))
+endif
+endef
+
+
+## Inherit template properties for on target.
+# @param $(target) Target name.
+define def_inherit_template
+# sanity check.
+ifdef _$(target)_ALREADY_PROCESSED
+ $(error kBuild: The target $(target) appears more than once in the target lists! Please correct the makefile(s))
+endif
+_$(target)_ALREADY_PROCESSED := 1
+
+# Inherit any default template.
+ifdef TEMPLATE
+ifeq ($($(target)_TEMPLATE),)
+$(eval $(target)_TEMPLATE:=$(TEMPLATE))
+endif
+endif
+# Expand the template if specified.
+ifneq ($($(target)_TEMPLATE),)
+$(foreach prop,$(PROPS_SINGLE),$(evalval def_inherit_template_one))
+$(foreach prop,$(PROPS_DEFERRED),$(eval $(def_inherit_template_one_deferred))) # exploits the 2 evaluation, so no value!
+$(foreach prop,$(PROPS_ACCUMULATE_L),$(eval $(def_inherit_template_one_accumulate_l))) # += works fine without value
+$(foreach prop,$(PROPS_ACCUMULATE_R),$(eval $(def_inherit_template_one_accumulate_r))) # use <= (kmk addition)
+endif
+endef
+
+
+Invoked like this:
+ $(kb-exp-tmpl 1,$(_ALL_TARGET_TARGETS),$(KBUILD_TARGET),$(KBUILD_TARGET_ARCH),$(KBUILD_TARGET_CPU),$(KBUILD_TYPE))
+*/
+char *
+func_kbuild_expand_template(char *o, char **argv, const char *pszFuncName)
+{
+ const char *pszVersion = argv[0];
+ const char *pszBldTrg = argv[2];
+ const char *pszBldTrgArch = argv[3];
+ const char *pszBldTrgCpu = argv[4];
+ const char *pszBldType = argv[5];
+ size_t cchBldTrg = strlen(pszBldTrg);
+ size_t cchBldTrgArch = strlen(pszBldTrgArch);
+ size_t cchBldTrgCpu = strlen(pszBldTrgCpu);
+ size_t cchBldType = strlen(pszBldType);
+ size_t cchMaxBld = cchBldTrg + cchBldTrgArch + cchBldTrgCpu + cchBldType; /* too big, but so what. */
+ struct kbet_key
+ {
+ unsigned int cch;
+ char *psz;
+ } aKeys[6];
+ unsigned int const cKeys = 6;
+ unsigned int iKey;
+ struct variable *pDefTemplate;
+ struct variable *pProps;
+ struct kbet_prop
+ {
+ const char *pch;
+ unsigned int cch;
+ enum kbet_prop_enum { kPropSingle, kPropDeferred, kPropAccumulateL, kPropAccumulateR }
+ enmType;
+ } *paProps;
+ unsigned int cProps;
+ unsigned int iProp;
+ size_t cchMaxProp;
+ struct variable *pVarTrg;
+ struct variable *pVarSrc;
+ const char *pszIter;
+ const char *pszTarget;
+ unsigned int cchTarget;
+ char *pszSrc = 0;
+ char *pszSrcRef = 0;
+ char *pszSrcBuf = 0;
+ size_t cchSrcBuf = 0;
+ char *pszTrg = 0;
+ size_t cchTrg = 0;
+
+ /*
+ * Validate input.
+ */
+ if (pszVersion[0] != '1' || pszVersion[1])
+ OSS(fatal, NULL, "%s: Unsupported version `%s'", pszFuncName, pszVersion);
+
+ if (!cchBldTrg)
+ OS(fatal, NULL, "%s: missing bldtrg", pszFuncName);
+
+ if (!cchBldTrgArch)
+ OS(fatal, NULL, "%s: missing bld_trg_arch", pszFuncName);
+
+ if (!cchBldTrgCpu)
+ OS(fatal, NULL, "%s: missing bld_trg_cpu", pszFuncName);
+
+ if (!cchBldType)
+ OS(fatal, NULL, "%s: missing bld_type", pszFuncName);
+
+ /*
+ * Prepare the keywords, prepending dots for quicker copying.
+ * This allows for an inner loop when processing properties, saving code
+ * at the expense of a few xmallocs.
+ */
+ /* the first entry is empty. */
+ aKeys[0].cch = 0;
+ aKeys[0].psz = NULL;
+
+ /* .$(bld_type) */
+ aKeys[1].cch = cchBldType + 1;
+ aKeys[1].psz = xmalloc (aKeys[1].cch + 1);
+ aKeys[1].psz[0] = '.';
+ memcpy(aKeys[1].psz + 1, pszBldType, cchBldType + 1);
+
+ /* .$(bld_trg) */
+ aKeys[2].cch = cchBldTrg + 1;
+ aKeys[2].psz = xmalloc (aKeys[2].cch + 1);
+ aKeys[2].psz[0] = '.';
+ memcpy(aKeys[2].psz + 1, pszBldTrg, cchBldTrg + 1);
+
+ /* .$(bld_trg).$(bld_trg_arch) */
+ aKeys[3].cch = cchBldTrg + 1 + cchBldTrgArch + 1;
+ aKeys[3].psz = xmalloc (aKeys[3].cch + 1);
+ aKeys[3].psz[0] = '.';
+ memcpy(aKeys[3].psz + 1, pszBldTrg, cchBldTrg);
+ aKeys[3].psz[1 + cchBldTrg] = '.';
+ memcpy(aKeys[3].psz + 1 + cchBldTrg + 1, pszBldTrgArch, cchBldTrgArch + 1);
+
+ /* .$(bld_trg_cpu) */
+ aKeys[4].cch = cchBldTrgCpu + 1;
+ aKeys[4].psz = xmalloc (aKeys[4].cch + 1);
+ aKeys[4].psz[0] = '.';
+ memcpy(aKeys[4].psz + 1, pszBldTrgCpu, cchBldTrgCpu + 1);
+
+ /* .$(bld_trg_arch) */
+ aKeys[5].cch = cchBldTrgArch + 1;
+ aKeys[5].psz = xmalloc (aKeys[5].cch + 1);
+ aKeys[5].psz[0] = '.';
+ memcpy(aKeys[5].psz + 1, pszBldTrgArch, cchBldTrgArch + 1);
+
+
+ /*
+ * Prepare the properties, folding them into an array.
+ * This way we won't have to reparse them for each an every target, though
+ * it comes at the expense of one or more heap calls.
+ */
+#define PROP_ALLOC_INC 128
+ iProp = 0;
+ cProps = PROP_ALLOC_INC;
+ paProps = xmalloc(sizeof(*pProps) * cProps);
+
+ pProps = kbuild_get_variable_n(ST("PROPS_SINGLE"));
+ pszIter = pProps->value;
+ while ((paProps[iProp].pch = find_next_token(&pszIter, &paProps[iProp].cch)))
+ {
+ paProps[iProp].enmType = kPropSingle;
+ if (++iProp >= cProps)
+ {
+ cProps += PROP_ALLOC_INC;
+ paProps = xrealloc(paProps, sizeof(*paProps) * cProps);
+ }
+
+ }
+
+ pProps = kbuild_get_variable_n(ST("PROPS_DEFERRED"));
+ pszIter = pProps->value;
+ while ((paProps[iProp].pch = find_next_token(&pszIter, &paProps[iProp].cch)))
+ {
+ paProps[iProp].enmType = kPropDeferred;
+ if (++iProp >= cProps)
+ {
+ cProps += PROP_ALLOC_INC;
+ paProps = xrealloc(paProps, sizeof(*paProps) * cProps);
+ }
+ }
+
+ pProps = kbuild_get_variable_n(ST("PROPS_ACCUMULATE_L"));
+ pszIter = pProps->value;
+ while ((paProps[iProp].pch = find_next_token(&pszIter, &paProps[iProp].cch)))
+ {
+ paProps[iProp].enmType = kPropAccumulateL;
+ if (++iProp >= cProps)
+ {
+ cProps += PROP_ALLOC_INC;
+ paProps = xrealloc(paProps, sizeof(*paProps) * cProps);
+ }
+ }
+
+ pProps = kbuild_get_variable_n(ST("PROPS_ACCUMULATE_R"));
+ pszIter = pProps->value;
+ while ((paProps[iProp].pch = find_next_token(&pszIter, &paProps[iProp].cch)))
+ {
+ paProps[iProp].enmType = kPropAccumulateR;
+ if (++iProp >= cProps)
+ {
+ cProps += PROP_ALLOC_INC;
+ paProps = xrealloc(paProps, sizeof(*paProps) * cProps);
+ }
+ }
+#undef PROP_ALLOC_INC
+ cProps = iProp;
+
+ /* find the max prop length. */
+ cchMaxProp = paProps[0].cch;
+ while (--iProp > 0)
+ if (paProps[iProp].cch > cchMaxProp)
+ cchMaxProp = paProps[iProp].cch;
+
+ /*
+ * Query and prepare (strip) the default template
+ * (given by the TEMPLATE variable).
+ */
+ pDefTemplate = kbuild_lookup_variable_n(ST("TEMPLATE"));
+ if (pDefTemplate)
+ {
+ if ( pDefTemplate->value_length
+ && ( ISSPACE(pDefTemplate->value[0])
+ || ISSPACE(pDefTemplate->value[pDefTemplate->value_length - 1])))
+ {
+ unsigned int off;
+ if (pDefTemplate->rdonly_val)
+ OS(fatal, NULL, "%s: TEMPLATE is read-only", pszFuncName);
+
+ /* head */
+ for (off = 0; ISSPACE(pDefTemplate->value[off]); off++)
+ /* nothing */;
+ if (off)
+ {
+ pDefTemplate->value_length -= off;
+ memmove(pDefTemplate->value, pDefTemplate->value + off, pDefTemplate->value_length + 1);
+ }
+
+ /* tail */
+ off = pDefTemplate->value_length;
+ while (off > 0 && ISSPACE(pDefTemplate->value[off - 1]))
+ off--;
+ pDefTemplate->value_length = off;
+ pDefTemplate->value[off] = '\0';
+
+ VARIABLE_CHANGED(pDefTemplate);
+ }
+
+ if (!pDefTemplate->value_length)
+ pDefTemplate = NULL;
+ }
+
+ /*
+ * Iterate the target list.
+ */
+ pszIter = argv[1];
+ while ((pszTarget = find_next_token(&pszIter, &cchTarget)))
+ {
+ char *pszTrgProp, *pszSrcProp;
+ char *pszTrgKey, *pszSrcKey;
+ struct variable *pTmpl;
+ const char *pszTmpl;
+ size_t cchTmpl, cchMax;
+
+ /* resize the target buffer. */
+ cchMax = cchTarget + cchMaxProp + cchMaxBld + 10;
+ if (cchTrg < cchMax)
+ {
+ cchTrg = (cchMax + 31U) & ~(size_t)31;
+ pszTrg = xrealloc(pszTrg, cchTrg);
+ }
+
+ /*
+ * Query the TEMPLATE property, if not found or zero-length fall back on the default.
+ */
+ memcpy(pszTrg, pszTarget, cchTarget);
+ pszTrgProp = pszTrg + cchTarget;
+ memcpy(pszTrgProp, "_TEMPLATE", sizeof("_TEMPLATE"));
+ pszTrgProp++; /* after '_'. */
+
+ /** @todo Change this to a recursive lookup with simplification below. That
+ * will allow target_TEMPLATE = $(NO_SUCH_TEMPLATE) instead of having
+ * to use target_TEMPLATE = DUMMY */
+ pTmpl = kbuild_lookup_variable_n(pszTrg, cchTarget + sizeof("_TEMPLATE") - 1);
+ if (!pTmpl || !pTmpl->value_length)
+ {
+ if (!pDefTemplate)
+ continue; /* no template */
+ pszTmpl = pDefTemplate->value;
+ cchTmpl = pDefTemplate->value_length;
+ }
+ else
+ {
+ pszTmpl = pTmpl->value;
+ cchTmpl = pTmpl->value_length;
+ while (ISSPACE(*pszTmpl))
+ cchTmpl--, pszTmpl++;
+ if (!cchTmpl)
+ continue; /* no template */
+ }
+
+ /* resize the source buffer. */
+ cchMax = sizeof("TEMPLATE_") + cchTmpl + cchMaxProp + cchMaxBld + 10 + sizeof(void *);
+ if (cchSrcBuf < cchMax)
+ {
+ cchSrcBuf = (cchMax + 31U) & ~(size_t)31;
+ pszSrcBuf = xrealloc(pszSrcBuf, cchSrcBuf);
+ pszSrc = pszSrcBuf + sizeof(void *); assert(sizeof(void *) >= 2);
+ pszSrcRef = pszSrc - 2;
+ pszSrcRef[0] = '$';
+ pszSrcRef[1] = '(';
+ }
+
+ /* prepare the source buffer */
+ memcpy(pszSrc, "TEMPLATE_", sizeof("TEMPLATE_") - 1);
+ pszSrcProp = pszSrc + sizeof("TEMPLATE_") - 1;
+ memcpy(pszSrcProp, pszTmpl, cchTmpl);
+ pszSrcProp += cchTmpl;
+ *pszSrcProp++ = '_';
+
+ /*
+ * Process properties.
+ * Note! The single and deferred are handled in the same way now.
+ */
+#define BY_REF_LIMIT 64 /*(cchSrcVar * 4 > 64 ? cchSrcVar * 4 : 64)*/
+
+ for (iProp = 0; iProp < cProps; iProp++)
+ {
+ memcpy(pszTrgProp, paProps[iProp].pch, paProps[iProp].cch);
+ pszTrgKey = pszTrgProp + paProps[iProp].cch;
+
+ memcpy(pszSrcProp, paProps[iProp].pch, paProps[iProp].cch);
+ pszSrcKey = pszSrcProp + paProps[iProp].cch;
+
+ for (iKey = 0; iKey < cKeys; iKey++)
+ {
+ char *pszTrgEnd;
+ size_t cchSrcVar;
+
+ /* lookup source, skip ahead if it doesn't exist. */
+ memcpy(pszSrcKey, aKeys[iKey].psz, aKeys[iKey].cch);
+ cchSrcVar = pszSrcKey - pszSrc + aKeys[iKey].cch;
+ pszSrc[cchSrcVar] = '\0';
+ pVarSrc = kbuild_query_recursive_variable_n(pszSrc, cchSrcVar);
+ if (!pVarSrc)
+ continue;
+
+ /* lookup target, skip ahead if it exists. */
+ memcpy(pszTrgKey, aKeys[iKey].psz, aKeys[iKey].cch);
+ pszTrgEnd = pszTrgKey + aKeys[iKey].cch;
+ *pszTrgEnd = '\0';
+ pVarTrg = kbuild_query_recursive_variable_n(pszTrg, pszTrgEnd - pszTrg);
+
+ switch (paProps[iProp].enmType)
+ {
+ case kPropAccumulateL:
+ case kPropAccumulateR:
+ if (pVarTrg)
+ {
+ /* Append to existing variable. If the source is recursive,
+ or we append by reference, we'll have to make sure the
+ target is recusive as well. */
+ if ( !pVarTrg->recursive
+ && ( pVarSrc->value_length >= BY_REF_LIMIT
+ || pVarSrc->recursive))
+ pVarTrg->recursive = 1;
+
+ if (pVarSrc->value_length < BY_REF_LIMIT)
+ append_string_to_variable(pVarTrg, pVarSrc->value, pVarSrc->value_length,
+ paProps[iProp].enmType == kPropAccumulateL /* append */);
+ else
+ {
+ pszSrc[cchSrcVar] = ')';
+ pszSrc[cchSrcVar + 1] = '\0';
+ append_string_to_variable(pVarTrg, pszSrcRef, 2 + cchSrcVar + 1,
+ paProps[iProp].enmType == kPropAccumulateL /* append */);
+ }
+ break;
+ }
+ /* else: the target variable doesn't exist, create it. */
+ /* fall thru */
+
+ case kPropSingle:
+ case kPropDeferred:
+ if (pVarTrg)
+ continue; /* skip ahead if it already exists. */
+
+ /* copy the variable if its short, otherwise reference it. */
+ if (pVarSrc->value_length < BY_REF_LIMIT)
+ define_variable_vl_global(pszTrg, pszTrgEnd - pszTrg,
+ pVarSrc->value, pVarSrc->value_length,
+ 1 /* duplicate_value */,
+ o_file,
+ pVarSrc->recursive,
+ NULL /* flocp */);
+ else
+ {
+ pszSrc[cchSrcVar] = ')';
+ pszSrc[cchSrcVar + 1] = '\0';
+ define_variable_vl_global(pszTrg, pszTrgEnd - pszTrg,
+ pszSrcRef, 2 + cchSrcVar + 1,
+ 1 /* duplicate_value */,
+ o_file,
+ 1 /* recursive */,
+ NULL /* flocp */);
+ }
+ break;
+
+ }
+
+ } /* foreach key */
+ } /* foreach prop */
+#undef BY_REF_LIMIT
+ } /* foreach target */
+
+ /*
+ * Cleanup.
+ */
+ free(pszSrcBuf);
+ free(pszTrg);
+ free(paProps);
+ for (iKey = 1; iKey < cKeys; iKey++)
+ free(aKeys[iKey].psz);
+
+ return o;
+}
+
+#endif /* KMK_HELPERS */
+
diff --git a/src/kmk/kbuild.h b/src/kmk/kbuild.h
new file mode 100644
index 0000000..5d9a85f
--- /dev/null
+++ b/src/kmk/kbuild.h
@@ -0,0 +1,77 @@
+/* $Id: kbuild.h 3140 2018-03-14 21:28:10Z bird $ */
+/** @file
+ * kBuild specific make functionality.
+ */
+
+/*
+ * Copyright (c) 2006-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___kBuild_h
+#define ___kBuild_h
+
+char *func_kbuild_source_tool(char *o, char **argv, const char *pszFuncName);
+char *func_kbuild_object_base(char *o, char **argv, const char *pszFuncName);
+char *func_kbuild_object_suffix(char *o, char **argv, const char *pszFuncName);
+char *func_kbuild_source_prop(char *o, char **argv, const char *pszFuncName);
+char *func_kbuild_source_one(char *o, char **argv, const char *pszFuncName);
+char *func_kbuild_expand_template(char *o, char **argv, const char *pszFuncName);
+
+void init_kbuild(int argc, char **argv);
+const char *get_kbuild_path(void);
+const char *get_kbuild_bin_path(void);
+const char *get_default_kbuild_shell(void);
+
+/** @name kBuild objects
+ * @{ */
+struct kbuild_eval_data;
+struct kbuild_object;
+
+extern struct kbuild_eval_data *g_pTopKbEvalData;
+
+
+/** Special return value indicating variable name isn't an accessor. */
+#define KOBJ_NOT_KBUILD_ACCESSOR ( (struct kbuild_object *)~(size_t)0 )
+
+/** Special lookup_kbuild_object_variable return value. */
+#define VAR_NOT_KBUILD_ACCESSOR ( (struct variable *)~(size_t)0 )
+
+struct variable *lookup_kbuild_object_variable_accessor(const char *pchName, size_t cchName);
+int is_kbuild_object_variable_accessor(const char *pchName, size_t cchName);
+struct variable *try_define_kbuild_object_variable_via_accessor(const char *pszName, size_t cchName,
+ const char *pszValue, size_t cchValue, int fDuplicateValue,
+ enum variable_origin enmOrigin, int fRecursive,
+ floc const *pFileLoc);
+struct variable *define_kbuild_object_variable_in_top_obj(const char *pszName, size_t cchName,
+ const char *pszValue, size_t cchValue, int fDuplicateValue,
+ enum variable_origin enmOrigin, int fRecursive,
+ floc const *pFileLoc);
+struct variable *kbuild_object_variable_pre_append(const char *pchName, size_t cchName,
+ const char *pchValue, size_t cchValue, int fSimpleValue,
+ enum variable_origin enmOrigin, int fAppend,
+ const floc *pFileLoc);
+int eval_kbuild_read_hook(struct kbuild_eval_data **kdata, const floc *flocp,
+ const char *word, size_t wlen, const char *line, const char *eos, int ignoring);
+void print_kbuild_data_base(void);
+void print_kbuild_define_stats(void);
+void init_kbuild_object(void);
+/** @} */
+
+#endif
+
diff --git a/src/kmk/kbuildprf.c b/src/kmk/kbuildprf.c
new file mode 100644
index 0000000..9fb4b97
--- /dev/null
+++ b/src/kmk/kbuildprf.c
@@ -0,0 +1,49 @@
+#include <stdio.h>
+#include <io.h>
+#include <errno.h>
+#include <Windows.h>
+
+__int64 prf_now(void)
+{
+ return GetTickCount();
+}
+
+#undef open
+int prf_open(const char *name, int of, int mask)
+{
+ int fd;
+// int err;
+// __int64 t = prf_now();
+ fd = _open(name, of, mask);
+// err = errno;
+// t = prf_now() - t;
+// fprintf(stderr, "open(%s, %#x) -> %d/%d in %lu\n", name, of, fd, err, (long)t);
+// errno = err;
+ return fd;
+}
+
+#undef close
+int prf_close(int fd)
+{
+ int rc;
+ rc = _close(fd);
+ return rc;
+}
+
+
+#undef read
+int prf_read(int fd, void *buf, long cb)
+{
+ int cbRead;
+ cbRead = _read(fd, buf, cb);
+ return cbRead;
+}
+
+#undef lseek
+long prf_lseek(int fd, long off, int whence)
+{
+ long rc;
+ rc = _lseek(fd, off, whence);
+ return rc;
+}
+
diff --git a/src/kmk/kdepdb.c b/src/kmk/kdepdb.c
new file mode 100644
index 0000000..260e695
--- /dev/null
+++ b/src/kmk/kdepdb.c
@@ -0,0 +1,1087 @@
+/* $Id: incdep.c 2283 2009-02-24 04:54:00Z bird $ */
+/** @file
+ * kdepdb - Dependency database.
+ */
+
+/*
+ * Copyright (c) 2009-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include "../lib/k/kDefs.h"
+#include "../lib/k/kTypes.h"
+#include <assert.h>
+#include <glob.h>
+
+#include "dep.h"
+#include "filedef.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#include "debug.h"
+#include "strcache2.h"
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+#if K_OS == K_WINDOWS
+# include <Windows.h>
+#else
+# include <unistd.h>
+# include <sys/mman.h>
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** @def KDEPDB_ASSERT_SIZE
+ * Check the size of an on-disk type.
+ *
+ * @param Type The type which size it being checked.
+ * @param Size The size it should have.
+ */
+#ifdef __GNUC__
+# define KDEPDB_ASSERT_SIZE(Type, Size) \
+ extern int kDepDbAssertSize[1] __attribute__((unused)), \
+ kDepDbAssertSize[sizeof(Type) == (Size)] __attribute__((unused))
+#else
+# define KDEPDB_ASSERT_SIZE(Type, Size) \
+ typedef int kDepDbAssertSize[sizeof(Type) == (Size)]
+#endif
+KDEPDB_ASSERT_SIZE(KU8, 1);
+KDEPDB_ASSERT_SIZE(KU16, 2);
+KDEPDB_ASSERT_SIZE(KU32, 4);
+KDEPDB_ASSERT_SIZE(KU64, 8);
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/**
+ * File header.
+ *
+ * @remarks All on-disk formats are in little-endian format.
+ */
+typedef struct KDEPDBHDR
+{
+ /** The file magic. */
+ KU8 szMagic[8];
+ /** The major file format version. */
+ KU8 uVerMajor;
+ /** The minor file format version. */
+ KU8 uVerMinor;
+ /** Reserved \#2. */
+ KU16 uReserved2;
+ /** Reserved \#1. */
+ KU32 uReserved1;
+ /** The internal name of this file. */
+ KU8 szName[16];
+} KDEPDBHDR;
+KDEPDB_ASSERT_SIZE(KDEPDBHDR, 32);
+
+/** The file header magic value. */
+#define KDEPDBHDR_MAGIC "kDepDb\0"
+/** The current major file format version number. */
+#define KDEPDBHDR_VERSION_MAJOR 0
+/** The current minor file format version number.
+ * Numbers above 240 indicate unsupported development variants. */
+#define KDEPDBHDR_VERSION_MINOR 240
+
+
+/**
+ * Hash table file.
+ *
+ * The hash table is recreated in a new file when we have to grow it.
+ */
+typedef struct KDEPDBHASH
+{
+ /** The file header. */
+ KDEPDBHDR Hdr;
+ /** The number of hash table entries. */
+ KU32 cEntries;
+ /** The number of hash table entries with content. */
+ KU32 cUsedEntries;
+ /** The number of collisions on insert. */
+ KU32 cCollisions;
+ /** Reserved member \#5. */
+ KU32 uReserved5;
+ /** Reserved member \#4. */
+ KU32 uReserved4;
+ /** Reserved member \#3. */
+ KU32 uReserved3;
+ /** Reserved member \#2. */
+ KU32 uReserved2;
+ /** Reserved member \#1. */
+ KU32 uReserved1;
+ /** The hash table. */
+ KU32 auEntries[32];
+} KDEPDBHASH;
+KDEPDB_ASSERT_SIZE(KDEPDBHASH, 32+32+4*32);
+
+/** The item value indicating that it is unused. */
+#define KDEPDBHASH_UNUSED KU32_C(0xffffffff)
+/** The item indicating that it hash been deleted. */
+#define KDEPDBHASH_DELETED KU32_C(0xfffffffe)
+/** The first special item value. */
+#define KDEPDBHASH_END KU32_C(0xfffffff0)
+
+
+/**
+ * A string table string entry.
+ *
+ * This should be a multiple of 32 bytes.
+ */
+typedef struct KDEPDBSTRING
+{
+ /** The hash number for the string. */
+ KU32 uHash;
+ /** The string length, excluding the zero terminator. */
+ KU32 cchString;
+ /** The string. */
+ KU8 szString[24];
+} KDEPDBSTRING;
+KDEPDB_ASSERT_SIZE(KDEPDBSTRING, 32);
+
+
+/**
+ * String table file.
+ *
+ * The file is insertion only and will grow forever.
+ */
+typedef struct KDEPDBSTRTAB
+{
+ /** The file header. */
+ KDEPDBHDR Hdr;
+ /** The end of the valid string table indexes. */
+ KU32 iStringEnd;
+ /** Reserved member \#7. */
+ KU32 uReserved7;
+ /** Reserved member \#6. */
+ KU32 uReserved6;
+ /** Reserved member \#5. */
+ KU32 uReserved5;
+ /** Reserved member \#4. */
+ KU32 uReserved4;
+ /** Reserved member \#3. */
+ KU32 uReserved3;
+ /** Reserved member \#2. */
+ KU32 uReserved2;
+ /** Reserved member \#1. */
+ KU32 uReserved1;
+ /** The string table. */
+ KDEPDBSTRING aStrings[1];
+} KDEPDBSTRTAB;
+KDEPDB_ASSERT_SIZE(KDEPDBSTRTAB, 32+32+32);
+
+/** The end of the valid string table indexes (exclusive). */
+#define KDEPDBG_STRTAB_IDX_END KU32_C(0x80000000)
+/** The string was not found. */
+#define KDEPDBG_STRTAB_IDX_NOT_FOUND KU32_C(0xfffffffd)
+/** Error during string table operation. */
+#define KDEPDBG_STRTAB_IDX_ERROR KU32_C(0xfffffffe)
+/** Generic invalid string table index. */
+#define KDEPDBG_STRTAB_IDX_INVALID KU32_C(0xffffffff)
+
+
+/**
+ * Directory entry.
+ */
+typedef struct KDEPDBDIRENTRY
+{
+ /** The string table index of the entry name.
+ * Unused entries are set to KDEPDBG_STRTAB_IDX_INVALID. */
+ KU32 iName;
+ /** The actual data stream size.
+ * Unused entries are set to KU32_MAX. */
+ KU32 cbData;
+ /** The number of blocks allocated for this stream.
+ * Unused entries are set to KU32_MAX. */
+ KU32 cBlocks;
+ /** The start block number.
+ * The stream is a contiguous sequence of blocks. This optimizes and
+ * simplifies reading the stream at the expense of operations extending it.
+ *
+ * In unused entries, this serves as the free chain pointer with KU32_MAX as
+ * nil value. */
+ KU32 iStartBlock;
+} KDEPDBDIRENTRY;
+KDEPDB_ASSERT_SIZE(KDEPDBDIRENTRY, 16);
+
+/**
+ * Directory file.
+ */
+typedef struct KDEPDBDIR
+{
+ /** The file header. */
+ KDEPDBHDR Hdr;
+ /** The number of entries. */
+ KU32 cEntries;
+ /** The head of the free chain. (Index into aEntries.) */
+ KU32 iFreeHead;
+ /** Reserved member \#6. */
+ KU32 uReserved6;
+ /** Reserved member \#5. */
+ KU32 uReserved5;
+ /** Reserved member \#4. */
+ KU32 uReserved4;
+ /** Reserved member \#3. */
+ KU32 uReserved3;
+ /** Reserved member \#2. */
+ KU32 uReserved2;
+ /** Reserved member \#1. */
+ KU32 uReserved1;
+ /** Directory entries. */
+ KDEPDBDIRENTRY aEntries[2];
+} KDEPDBDIR;
+KDEPDB_ASSERT_SIZE(KDEPDBDIR, 32+32+32);
+
+
+/**
+ * A block allocation bitmap.
+ *
+ * This can track 2^(12+8) = 2^20 = 1M blocks.
+ */
+typedef struct KDEPDBDATABITMAP
+{
+ /** Bitmap where each bit is a block.
+ * 0 indicates unused blocks and 1 indicates used ones. */
+ KU8 bm[4096];
+} KDEPDBDATABITMAP;
+KDEPDB_ASSERT_SIZE(KDEPDBDATABITMAP, 4096);
+
+/**
+ * Data file.
+ *
+ * The block numbering starts with this structure as block 0.
+ */
+typedef struct KDEPDBDATA
+{
+ /** The file header. */
+ KDEPDBHDR Hdr;
+ /** The size of a block. */
+ KU32 cbBlock;
+ /** Reserved member \#7. */
+ KU32 uReserved7;
+ /** Reserved member \#6. */
+ KU32 uReserved6;
+ /** Reserved member \#5. */
+ KU32 uReserved5;
+ /** Reserved member \#4. */
+ KU32 uReserved4;
+ /** Reserved member \#3. */
+ KU32 uReserved3;
+ /** Reserved member \#2. */
+ KU32 uReserved2;
+ /** Reserved member \#1. */
+ KU32 uReserved1;
+
+ /** Block numbers for the allocation bitmaps. */
+ KU32 aiBitmaps[4096];
+} KDEPDBDATA;
+
+/** The end of the valid block indexes (exclusive). */
+#define KDEPDB_BLOCK_IDX_END KU32_C(0xfffffff0)
+/** The index of an unallocated bitmap block. */
+#define KDEPDB_BLOCK_IDX_UNALLOCATED KU32_C(0xffffffff)
+
+
+/**
+ * Stream storing dependencies.
+ *
+ * The stream name gives the output file name, so all that we need is the list
+ * of files it depends on. These are serialized as a list of string table
+ * indexes.
+ */
+typedef struct KDEPDBDEPSTREAM
+{
+ /** String table indexes for the dependencies. */
+ KU32 aiDeps[1];
+} KDEPDBDEPSTREAM;
+
+
+/**
+ * A file handle structure.
+ */
+typedef struct KDEPDBFH
+{
+#if K_OS == K_OS_WINDOWS
+ /** The file handle. */
+ HANDLE hFile;
+ /** The mapping object handle. */
+ HANDLE hMapObj;
+#else
+ /** The file handle. */
+ int fd;
+#endif
+ /** The current file size. */
+ KU32 cb;
+} KDEPDBFH;
+
+
+/**
+ * Internal control structure for a string table.
+ */
+typedef struct KDEPDBINTSTRTAB
+{
+ /** The hash file. */
+ KDEPDBHASH *pHash;
+ /** The handle of the hash file. */
+ KDEPDBFH hHash;
+ /** The string table file. */
+ KDEPDBSTRTAB *pStrTab;
+ /** The handle of the string table file. */
+ KDEPDBFH hStrTab;
+ /** The end of the allocated string table indexes (i.e. when to grow the
+ * file). */
+ KU32 iStringAlloced;
+} KDEPDBINTSTRTAB;
+
+
+/**
+ * Internal control structure for a data set.
+ *
+ * This governs the directory file, the directory hash file and the data file.
+ */
+typedef struct KDEPDBINTDATASET
+{
+ /** The hash file. */
+ KDEPDBHASH pHash;
+ /** The size of the hash file. */
+ KU32 cbHash;
+ /** The size of the directory file. */
+ KU32 cbDir;
+ /** The mapping of the directory file. */
+ KDEPDBHASH pDir;
+ /** The data file. */
+ KDEPDBDATA pData;
+ /** The size of the data file. */
+ KU32 cbData;
+ /** The handle of the hash file. */
+ KDEPDBFH hHash;
+ /** The handle of the directory file. */
+ KDEPDBFH hDir;
+ /** The handle of the data file. */
+ KDEPDBFH hData;
+} KDEPDBINTDATASET;
+
+
+/**
+ * The database instance.
+ *
+ * To simplifiy things the database uses 8 files for storing the different kinds
+ * of data. This greatly reduces the complexity compared to a single file
+ * solution.
+ */
+typedef struct KDEPDB
+{
+ /** The string table. */
+ KDEPDBINTSTRTAB StrTab;
+ /** The variable data set. */
+ KDEPDBINTDATASET DepSet;
+ /** The command data set. */
+ KDEPDBINTDATASET CmdSet;
+} KDEPDB;
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static void *kDepDbAlloc(KSIZE cb);
+static void kDepDbFree(void *pv);
+static void kDepDbFHInit(KDEPDBFH *pFH);
+static int kDepDbFHUpdateSize(KDEPDBFH *pFH);
+static int kDepDbFHOpen(KDEPDBFH *pFH, const char *pszFilename, KBOOL fCreate, KBOOL *pfNew);
+static int kDepDbFHClose(KDEPDBFH *pFH);
+static int kDepDbFHWriteAt(KDEPDBFH *pFH, KU32 off, void const *pvBuf, KSIZE cbBuf);
+static int kDepDbFHMap(KDEPDBFH *pFH, void **ppvMap);
+static int kDepDbFHUnmap(KDEPDBFH *pFH, void **ppvMap);
+static int kDepDbFHGrow(KDEPDBFH *pFH, KSIZE cbNew, void **ppvMap);
+static KU32 kDepDbHashString(const char *pszString, size_t cchString);
+
+
+/** xmalloc wrapper. */
+static void *kDepDbAlloc(KSIZE cb)
+{
+ return xmalloc(cb);
+}
+
+/** free wrapper. */
+static void kDepDbFree(void *pv)
+{
+ if (pv)
+ free(pv);
+}
+
+
+/**
+ * Initializes the file handle structure so closing it without first opening it
+ * will work smoothly.
+ *
+ * @param pFH The file handle structure.
+ */
+static void kDepDbFHInit(KDEPDBFH *pFH)
+{
+#if K_OS == K_OS_WINDOWS
+ pFH->hFile = INVALID_HANDLE_VALUE;
+ pFH->hMapObj = INVALID_HANDLE_VALUE;
+#else
+ pFH->fd = -1;
+#endif
+ pFH->cb = 0;
+}
+
+/**
+ * Updates the file size.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ * @param pFH The file handle structure.
+ */
+static int kDepDbFHUpdateSize(KDEPDBFH *pFH)
+{
+#if K_OS == K_OS_WINDOWS
+ DWORD rc;
+ DWORD dwHigh;
+ DWORD dwLow;
+
+ SetLastError(0);
+ dwLow = GetFileSize(File, &High);
+ rc = GetLastError();
+ if (rc)
+ {
+ pFH->cb = 0;
+ return (int)rc;
+ }
+ if (High)
+ pFH->cb = KU32_MAX;
+ else
+ pFH->cb = dwLow;
+#else
+ off_t cb;
+
+ cb = lseek(pFH->fd, 0, SEEK_END);
+ if (cb == -1)
+ {
+ pFH->cb = 0;
+ return errno;
+ }
+ pFH->cb = cb;
+ if ((off_t)pFH->cb != cb)
+ pFH->cb = KU32_MAX;
+#endif
+ return 0;
+}
+
+/**
+ * Opens an existing file or creates a new one.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ * @param pszFilename The name of the file.
+ * @param fCreate Whether we should create the file or not.
+ * @param pfCreated Where to return whether we created it or not.
+ */
+static int kDepDbFHOpen(KDEPDBFH *pFH, const char *pszFilename, KBOOL fCreate, KBOOL *pfCreated)
+{
+ int rc;
+#if K_OS == K_OS_WINDOWS
+ SECURITY_ATTRIBUTES SecAttr;
+
+ SecAttr.bInheritHandle = FALSE;
+ SecAttr.lpSecurityDescriptor = NULL;
+ SecAttr.nLength = 0;
+ pFH->cb = 0;
+ SetLastError(0);
+ pFH->hFile = CreateFile(pszFilename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, &SecAttr,
+ fCreate ? OPEN_ALWAYS : OPEN_EXISTING, 0, NULL);
+ if (pFH->hFile == INVALID_HANDLE_VALUE)
+ return GetLastError();
+ *pfCreated = GetLastError() == 0;
+
+#else
+ int fFlags = O_RDWR;
+# ifdef O_BINARY
+ fFlags |= O_BINARY;
+# endif
+ pFH->cb = 0;
+ pFH->fd = open(pszFilename, fFlags, 0);
+ if (pFH->fd >= 0)
+ *pfCreated = K_FALSE;
+ else if (!fCreate)
+ return errno;
+ else
+ {
+ pFH->fd = open(pszFilename, fFlags | O_EXCL | O_CREAT, 0666);
+ if (pFH->fd < 0)
+ return errno;
+ *pfCreated = K_TRUE;
+ }
+ fcntl(pFH->fd, F_SETFD, FD_CLOEXEC);
+#endif
+
+ /* update the size */
+ rc = kDepDbFHUpdateSize(pFH);
+ if (rc)
+ kDepDbFHClose(pFH);
+ return rc;
+}
+
+/**
+ * Closes an open file.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ */
+static int kDepDbFHClose(KDEPDBFH *pFH)
+{
+#if K_OS == K_OS_WINDOWS
+ if (pFH->hFile != INVALID_HANDLE_VALUE)
+ {
+ if (!CloseHandle(pFH->hFile))
+ return GetLastError();
+ pFH->hFile = INVALID_HANDLE_VALUE;
+ }
+
+#else
+ if (pFH->fd >= 0)
+ {
+ if (close(pFH->fd) != 0)
+ return errno;
+ pFH->fd = -1;
+ }
+#endif
+ pFH->cb = 0;
+ return 0;
+}
+
+/**
+ * Writes to a file.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ * @param off The offset into the file to start writing at.
+ * @param pvBuf What to write.
+ * @param cbBuf How much to write.
+ */
+static int kDepDbFHWriteAt(KDEPDBFH *pFH, KU32 off, void const *pvBuf, KSIZE cbBuf)
+{
+#if K_OS == K_OS_WINDOWS
+ ULONG cbWritten;
+
+ if (SetFilePointer(pFH->hFile, off, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
+ return GetLastError();
+
+ if (!WriteFile(pFH->hFile, pvBuf, cbBuf, &cbWritten, NULL))
+ return GetLastError();
+ if (cbWritten != cbBuf)
+ return -1;
+
+#else
+ ssize_t cbWritten;
+ if (lseek(pFH->fd, off, SEEK_SET) == -1)
+ return errno;
+ errno = 0;
+ cbWritten = write(pFH->fd, pvBuf, cbBuf);
+ if ((size_t)cbWritten != cbBuf)
+ return errno ? errno : EIO;
+#endif
+ return kDepDbFHUpdateSize(pFH);
+}
+
+
+/**
+ * Creates a memory mapping of the file.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ * @param ppvMap Where to return the map address.
+ */
+static int kDepDbFHMap(KDEPDBFH *pFH, void **ppvMap)
+{
+#if K_OS == K_OS_WINDOWS
+ *ppvMap = NULL;
+ return -1;
+#else
+ *ppvMap = mmap(NULL, pFH->cb, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pFH->fd, 0);
+ if (*ppvMap == (void *)-1)
+ {
+ *ppvMap = NULL;
+ return errno;
+ }
+#endif
+ return 0;
+}
+
+
+/**
+ * Flushes and destroys a memory of the file.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ * @param ppvMap The pointer to the mapping pointer. This will be set to
+ * NULL on success.
+ */
+static int kDepDbFHUnmap(KDEPDBFH *pFH, void **ppvMap)
+{
+#if K_OS == K_OS_WINDOWS
+ return -1;
+#else
+ if (msync(*ppvMap, pFH->cb, MS_SYNC) == -1)
+ return errno;
+ if (munmap(*ppvMap, pFH->cb) == -1)
+ return errno;
+ *ppvMap = NULL;
+#endif
+ return 0;
+}
+
+
+/**
+ * Grows the memory mapping of the file.
+ *
+ * The content of the new space is undefined.
+ *
+ * @returns 0 on success. Some non-zero native error code on failure.
+ *
+ * @param pFH The file handle structure.
+ * @param cbNew The new mapping size.
+ * @param ppvMap The pointer to the mapping pointer. This may change and
+ * may be set to NULL on failure.
+ */
+static int kDepDbFHGrow(KDEPDBFH *pFH, KSIZE cbNew, void **ppvMap)
+{
+#if K_OS == K_OS_WINDOWS
+ return -1;
+#else
+ if ((KU32)cbNew != cbNew)
+ return ERANGE;
+ if (cbNew <= pFH->cb)
+ return 0;
+
+ if (munmap(*ppvMap, pFH->cb) == -1)
+ return errno;
+ *ppvMap = NULL;
+
+ pFH->cb = cbNew;
+ return kDepDbFHMap(pFH, ppvMap);
+#endif
+}
+
+
+/** Macro for reading an potentially unaligned 16-bit word from a string. */
+# if K_ARCH == K_ARCH_AMD64 \
+ || K_ARCH == K_ARCH_X86_32 \
+ || K_ARCH == K_ARCH_X86_16
+# define kDepDbHashString_get_unaligned_16bits(ptr) ( *((const KU16 *)(ptr)) )
+# elif K_ENDIAN == K_ENDIAN_LITTLE
+# define kDepDbHashString_get_unaligned_16bits(ptr) ( (((const KU8 *)(ptr))[0]) \
+ | (((const KU8 *)(ptr))[1] << 8) )
+# else
+# define kDepDbHashString_get_unaligned_16bits(ptr) ( (((const KU8 *)(ptr))[0] << 8) \
+ | (((const KU8 *)(ptr))[1]) )
+# endif
+
+
+/**
+ * Hash a string.
+ *
+ * @returns Hash value.
+ *
+ * @param pszString The string to hash.
+ * @param cchString How much to hash.
+ */
+static KU32 kDepDbHashString(const char *pszString, size_t cchString)
+{
+ /*
+ * Paul Hsieh hash SuperFast function:
+ * http://www.azillionmonkeys.com/qed/hash.html
+ */
+ /** @todo A path for well aligned data should be added to speed up execution on
+ * alignment sensitive systems. */
+ unsigned int uRem;
+ KU32 uHash;
+ KU32 uTmp;
+
+ assert(sizeof(KU8) == sizeof(char));
+
+ /* main loop, walking on 2 x KU16 */
+ uHash = cchString;
+ uRem = cchString & 3;
+ cchString >>= 2;
+ while (cchString > 0)
+ {
+ uHash += kDepDbHashString_get_unaligned_16bits(pszString);
+ uTmp = (kDepDbHashString_get_unaligned_16bits(pszString + 2) << 11) ^ uHash;
+ uHash = (uHash << 16) ^ uTmp;
+ pszString += 2 * sizeof(KU16);
+ uHash += uHash >> 11;
+ cchString--;
+ }
+
+ /* the remainder */
+ switch (uRem)
+ {
+ case 3:
+ uHash += kDepDbHashString_get_unaligned_16bits(pszString);
+ uHash ^= uHash << 16;
+ uHash ^= pszString[sizeof(KU16)] << 18;
+ uHash += uHash >> 11;
+ break;
+ case 2:
+ uHash += kDepDbHashString_get_unaligned_16bits(pszString);
+ uHash ^= uHash << 11;
+ uHash += uHash >> 17;
+ break;
+ case 1:
+ uHash += *pszString;
+ uHash ^= uHash << 10;
+ uHash += uHash >> 1;
+ break;
+ }
+
+ /* force "avalanching" of final 127 bits. */
+ uHash ^= uHash << 3;
+ uHash += uHash >> 5;
+ uHash ^= uHash << 4;
+ uHash += uHash >> 17;
+ uHash ^= uHash << 25;
+ uHash += uHash >> 6;
+
+ return uHash;
+}
+
+
+/***
+ * Looks up a string in the string table.
+ *
+ * @returns The string table index.
+ * @retval KDEPDBG_STRTAB_IDX_NOT_FOUND is not found.
+ * @retval KDEPDBG_STRTAB_IDX_ERROR on internal inconsistency.
+ *
+ * @param pStrTab The string table.
+ * @param pszString The string.
+ * @param cchStringIn The string length.
+ * @param uHash The hash of the string.
+ */
+static KU32 kDepDbStrTabLookupHashed(KDEPDBINTSTRTAB const *pStrTab, const char *pszString, size_t cchStringIn, KU32 uHash)
+{
+ KU32 const cchString = (KU32)cchStringIn;
+ KDEPDBHASH const *pHash = pStrTab->pHash;
+ KDEPDBSTRING const *paStrings = &pStrTab->pStrTab->aStrings[0];
+ KU32 const iStringEnd = K_LE2H_U32(pStrTab->pStrTab->iStringEnd);
+ KU32 iHash;
+
+ /* sanity */
+ if (cchString != cchStringIn)
+ return KDEPDBG_STRTAB_IDX_NOT_FOUND;
+
+ /*
+ * Hash lookup of the string.
+ */
+ iHash = uHash % pHash->cEntries;
+ for (;;)
+ {
+ KU32 iString = K_LE2H_U32(pHash->auEntries[iHash]);
+ if (iString < iStringEnd)
+ {
+ KDEPDBSTRING const *pString = &paStrings[iString];
+ if ( K_LE2H_U32(pString->uHash) == uHash
+ && K_LE2H_U32(pString->cchString) == cchString
+ && !memcmp(pString->szString, pszString, cchString))
+ return iString;
+ }
+ else if (iString == KDEPDBHASH_UNUSED)
+ return KDEPDBG_STRTAB_IDX_NOT_FOUND;
+ else if (iString != KDEPDBHASH_DELETED)
+ return KDEPDBG_STRTAB_IDX_ERROR;
+
+ /* advance */
+ iHash = (iHash + 1) % pHash->cEntries;
+ }
+}
+
+
+/**
+ * Doubles the hash table size and rehashes it.
+ *
+ * @returns 0 on success, -1 on failure.
+ * @param pStrTab The string table.
+ * @todo Rebuild from string table, we'll be accessing it anyways.
+ */
+static int kDepDbStrTabReHash(KDEPDBINTSTRTAB *pStrTab)
+{
+ KDEPDBSTRING const *paStrings = &pStrTab->pStrTab->aStrings[0];
+ KU32 const iStringEnd = K_LE2H_U32(pStrTab->pStrTab->iStringEnd);
+ KDEPDBHASH *pHash = pStrTab->pHash;
+ KDEPDBHASH HashHdr = *pHash;
+ KU32 *pauNew;
+ KU32 cEntriesNew;
+ KU32 i;
+
+ /*
+ * Calc the size of the new hash table.
+ */
+ if (pHash->cEntries >= KU32_C(0x80000000))
+ return -1;
+ cEntriesNew = 1024;
+ while (cEntriesNew <= pHash->cEntries)
+ cEntriesNew <<= 1;
+
+ /*
+ * Allocate and initialize an empty hash table in memory.
+ */
+ pauNew = kDepDbAlloc(cEntriesNew * sizeof(KU32));
+ if (!pauNew)
+ return -1;
+ i = cEntriesNew;
+ while (i-- > 0)
+ pauNew[i] = KDEPDBHASH_UNUSED;
+
+ /*
+ * Popuplate the new table.
+ */
+ HashHdr.cEntries = K_LE2H_U32(cEntriesNew);
+ HashHdr.cCollisions = 0;
+ HashHdr.cUsedEntries = 0;
+ i = pHash->cEntries;
+ while (i-- > 0)
+ {
+ KU32 iString = K_LE2H_U32(pHash->auEntries[i]);
+ if (iString < iStringEnd)
+ {
+ KU32 iHash = (paStrings[iString].uHash % cEntriesNew);
+ if (pauNew[iHash] != K_H2LE_U32(KDEPDBHASH_UNUSED))
+ {
+ do
+ {
+ iHash = (iHash + 1) % cEntriesNew;
+ HashHdr.cCollisions++;
+ } while (pauNew[iHash] != K_H2LE_U32(KDEPDBHASH_UNUSED));
+ }
+ pauNew[iHash] = iString;
+ HashHdr.cUsedEntries++;
+ }
+ else if ( iString != KDEPDBHASH_UNUSED
+ && iString != KDEPDBHASH_DELETED)
+ {
+ kDepDbFree(pauNew);
+ return -1;
+ }
+ }
+ HashHdr.cCollisions = K_H2LE_U32(HashHdr.cCollisions);
+ HashHdr.cUsedEntries = K_H2LE_U32(HashHdr.cUsedEntries);
+
+ /*
+ * Unmap the hash, write the new hash table and map it again.
+ */
+ if (!kDepDbFHUnmap(&pStrTab->hHash, (void **)&pStrTab->pHash))
+ {
+ if ( !kDepDbFHWriteAt(&pStrTab->hHash, 0, &HashHdr, K_OFFSETOF(KDEPDBHASH, auEntries))
+ && !kDepDbFHWriteAt(&pStrTab->hHash, K_OFFSETOF(KDEPDBHASH, auEntries), pauNew, sizeof(pauNew[0]) * cEntriesNew))
+ {
+ kDepDbFree(pauNew);
+ pauNew = NULL;
+ if (!kDepDbFHMap(&pStrTab->hHash, (void **)&pStrTab->pHash))
+ return 0;
+ }
+ else
+ kDepDbFHWriteAt(&pStrTab->hHash, 0, "\0\0\0\0", 4); /* file is screwed, trash the magic. */
+ }
+
+ kDepDbFree(pauNew);
+ return -1;
+}
+
+
+/**
+ * Add a string to the string table.
+ *
+ * If already in the table, the index of the existing entry is returned.
+ *
+ * @returns String index on success,
+ * @retval KDEPDBG_STRTAB_IDX_ERROR on I/O and inconsistency errors.
+ *
+ * @param pStrTab The string table.
+ * @param pszString The string to add.
+ * @param cchStringIn The length of the string.
+ * @param uHash The hash of the string.
+ */
+static KU32 kDepDbStrTabAddHashed(KDEPDBINTSTRTAB *pStrTab, const char *pszString, size_t cchStringIn, KU32 uHash)
+{
+ KU32 const cchString = (KU32)cchStringIn;
+ KDEPDBHASH *pHash = pStrTab->pHash;
+ KDEPDBSTRING *paStrings = &pStrTab->pStrTab->aStrings[0];
+ KU32 const iStringEnd = K_LE2H_U32(pStrTab->pStrTab->iStringEnd);
+ KU32 iInsertAt = KDEPDBHASH_UNUSED;
+ KU32 cCollisions = 0;
+ KU32 iHash;
+ KU32 iString;
+ KU32 cEntries;
+ KDEPDBSTRING *pNewString;
+
+ /* sanity */
+ if (cchString != cchStringIn)
+ return KDEPDBG_STRTAB_IDX_NOT_FOUND;
+
+ /*
+ * Hash lookup of the string, finding either an existing copy or where to
+ * insert the new string at in the hash table.
+ */
+ iHash = uHash % pHash->cEntries;
+ for (;;)
+ {
+ iString = K_LE2H_U32(pHash->auEntries[iHash]);
+ if (iString < iStringEnd)
+ {
+ KDEPDBSTRING const *pString = &paStrings[iString];
+ if ( K_LE2H_U32(pString->uHash) == uHash
+ && K_LE2H_U32(pString->cchString) == cchString
+ && !memcmp(pString->szString, pszString, cchString))
+ return iString;
+ }
+ else
+ {
+ if (iInsertAt == KDEPDBHASH_UNUSED)
+ iInsertAt = iHash;
+ if (iString == KDEPDBHASH_UNUSED)
+ break;
+ if (iString != KDEPDBHASH_DELETED)
+ return KDEPDBG_STRTAB_IDX_ERROR;
+ }
+
+ /* advance */
+ cCollisions++;
+ iHash = (iHash + 1) % pHash->cEntries;
+ }
+
+ /*
+ * Add string to the string table.
+ * The string table file is grown in 256KB increments and ensuring at least 64KB unused new space.
+ */
+ cEntries = cchString + 1 <= sizeof(paStrings[0].szString)
+ ? 1
+ : (cchString + 1 - sizeof(paStrings[0].szString) + sizeof(KDEPDBSTRING) - 1) / sizeof(KDEPDBSTRING);
+ if (iStringEnd + cEntries > pStrTab->iStringAlloced)
+ {
+ KSIZE cbNewSize = K_ALIGN_Z((iStringEnd + cEntries) * sizeof(KDEPDBSTRING) + 64*1024, 256*1024);
+ KU32 iStringAlloced = (pStrTab->hStrTab.cb - K_OFFSETOF(KDEPDBSTRTAB, aStrings)) / sizeof(KDEPDBSTRING);
+ if ( iStringAlloced <= pStrTab->iStringAlloced
+ || iStringAlloced >= KDEPDBG_STRTAB_IDX_END
+ || iStringAlloced >= KDEPDBHASH_END)
+ return KDEPDBG_STRTAB_IDX_ERROR;
+ if (kDepDbFHGrow(&pStrTab->hStrTab, cbNewSize, (void **)&pStrTab->pStrTab) != 0)
+ return KDEPDBG_STRTAB_IDX_ERROR;
+ pStrTab->iStringAlloced = iStringAlloced;
+ paStrings = &pStrTab->pStrTab->aStrings[0];
+ }
+
+ pNewString = &paStrings[iStringEnd];
+ pNewString->uHash = K_H2LE_U32(uHash);
+ pNewString->cchString = K_H2LE_U32(cchString);
+ memcpy(&pNewString->szString, pszString, cchString);
+ pNewString->szString[cchString] = '\0';
+
+ pStrTab->pStrTab->iStringEnd = K_H2LE_U32(iStringEnd + cEntries);
+
+ /*
+ * Insert hash table entry, rehash it if necessary.
+ */
+ pHash->auEntries[iInsertAt] = K_H2LE_U32(iStringEnd);
+ pHash->cUsedEntries = K_H2LE_U32(K_LE2H_U32(pHash->cUsedEntries) + 1);
+ pHash->cCollisions = K_H2LE_U32(K_LE2H_U32(pHash->cCollisions) + cCollisions);
+ if ( K_LE2H_U32(pHash->cUsedEntries) > K_LE2H_U32(pHash->cEntries) / 3 * 2
+ && kDepDbStrTabReHash(pStrTab) != 0)
+ return KDEPDBG_STRTAB_IDX_ERROR;
+
+ return iStringEnd;
+}
+
+
+/** Wrapper for kDepDbStrTabLookupHashed. */
+static KU32 kDepDbStrTabLookupN(KDEPDBINTSTRTAB const *pStrTab, const char *pszString, size_t cchString)
+{
+ return kDepDbStrTabLookupHashed(pStrTab, pszString, cchString, kDepDbHashString(pszString, cchString));
+}
+
+
+/** Wrapper for kDepDbStrTabAddHashed. */
+static KU32 kDepDbStrTabAddN(KDEPDBINTSTRTAB *pStrTab, const char *pszString, size_t cchString)
+{
+ return kDepDbStrTabAddHashed(pStrTab, pszString, cchString, kDepDbHashString(pszString, cchString));
+}
+
+
+/** Wrapper for kDepDbStrTabLookupHashed. */
+static KU32 kDepDbStrTabLookup(KDEPDBINTSTRTAB const *pStrTab, const char *pszString)
+{
+ return kDepDbStrTabLookupN(pStrTab, pszString, strlen(pszString));
+}
+
+
+/** Wrapper for kDepDbStrTabAddHashed. */
+static KU32 kDepDbStrTabAdd(KDEPDBINTSTRTAB *pStrTab, const char *pszString)
+{
+ return kDepDbStrTabAddN(pStrTab, pszString, strlen(pszString));
+}
+
+
+/**
+ * Opens the string table files, creating them if necessary.
+ */
+static int kDepDbStrTabInit(KDEPDBINTSTRTAB *pStrTab, const char *pszFilenameBase)
+{
+ size_t cchFilenameBase = strlen(pszFilenameBase);
+ char szPath[4096];
+ int rc;
+ KBOOL fNew;
+
+ /* Basic member init, so kDepDbStrTabTerm always works. */
+ pStrTab->pHash = NULL;
+ kDepDbFHInit(&pStrTab->hHash);
+ pStrTab->pStrTab = NULL;
+ kDepDbFHInit(&pStrTab->hStrTab);
+ pStrTab->iStringAlloced = 0;
+
+ /* check the length. */
+ if (cchFilenameBase + sizeof(".strtab.hash") > sizeof(szPath))
+ return -1;
+
+ /*
+ * Open the string table first.
+ */
+ memcpy(szPath, pszFilenameBase, cchFilenameBase);
+ memcpy(&szPath[cchFilenameBase], ".strtab", sizeof(".strtab"));
+ rc = kDepDbFHOpen(&pStrTab->hStrTab, szPath, K_TRUE, &fNew);
+
+
+ return -1;
+}
+
diff --git a/src/kmk/kmk_cc_exec.c b/src/kmk/kmk_cc_exec.c
new file mode 100644
index 0000000..6f60477
--- /dev/null
+++ b/src/kmk/kmk_cc_exec.c
@@ -0,0 +1,7613 @@
+#ifdef CONFIG_WITH_COMPILER
+/* $Id: kmk_cc_exec.c 3233 2018-09-24 10:39:36Z bird $ */
+/** @file
+ * kmk_cc - Make "Compiler".
+ */
+
+/*
+ * Copyright (c) 2015-2017 knut st. osmundsen <bird-kBuild-spam-xiiv@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "makeint.h"
+
+#include "dep.h"
+#include "variable.h"
+#include "rule.h"
+#include "debug.h"
+#include "hash.h"
+#include <ctype.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#include <stdarg.h>
+#include <assert.h>
+#include "k/kDefs.h"
+#include "k/kTypes.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @def KMK_CC_WITH_STATS
+ * Enables the collection of extra statistics. */
+#ifndef KMK_CC_WITH_STATS
+# ifdef CONFIG_WITH_MAKE_STATS
+# define KMK_CC_WITH_STATS
+# endif
+#endif
+
+/** @def KMK_CC_STRICT
+ * Indicates whether assertions and other checks are enabled. */
+#ifndef KMK_CC_STRICT
+# ifndef NDEBUG
+# define KMK_CC_STRICT
+# endif
+#endif
+
+#ifdef KMK_CC_STRICT
+# ifdef _MSC_VER
+# define KMK_CC_ASSERT(a_TrueExpr) do { if (!(a_TrueExpr)) __debugbreak(); } while (0)
+# elif defined(__GNUC__) && (defined(KBUILD_ARCH_X86) || defined(KBUILD_ARCH_AMD64))
+# define KMK_CC_ASSERT(a_TrueExpr) do { if (!(a_TrueExpr)) __asm__ __volatile__("int3;nop"); } while (0)
+# else
+# define KMK_CC_ASSERT(a_TrueExpr) assert(a_TrueExpr)
+# endif
+#else
+# define KMK_CC_ASSERT(a_TrueExpr) do {} while (0)
+#endif
+#define KMK_CC_ASSERT_ALIGNED(a_uValue, a_uAlignment) \
+ KMK_CC_ASSERT( ((a_uValue) & ((a_uAlignment) - 1)) == 0 )
+
+
+/** @def KMK_CC_OFFSETOF
+ * Offsetof for simple stuff. */
+#if defined(__GNUC__)
+# define KMK_CC_OFFSETOF(a_Struct, a_Member) __builtin_offsetof(a_Struct, a_Member)
+#else
+# define KMK_CC_OFFSETOF(a_Struct, a_Member) ( (uintptr_t)&( ((a_Struct *)(void *)0)->a_Member) )
+#endif
+
+/** def KMK_CC_SIZEOF_MEMBER */
+#define KMK_CC_SIZEOF_MEMBER(a_Struct, a_Member) ( sizeof( ((a_Struct *)(void *)0x1000)->a_Member) )
+
+/** @def KMK_CC_SIZEOF_VAR_STRUCT
+ * Size of a struct with a variable sized array as the final member. */
+#define KMK_CC_SIZEOF_VAR_STRUCT(a_Struct, a_FinalArrayMember, a_cArray) \
+ ( KMK_CC_OFFSETOF(a_Struct, a_FinalArrayMember) + KMK_CC_SIZEOF_MEMBER(a_Struct, a_FinalArrayMember) * (a_cArray) )
+
+
+
+/** @def KMK_CC_STATIC_ASSERT_EX
+ * Compile time assertion with text.
+ */
+#ifdef _MSC_VER_
+# if _MSC_VER >= 1600
+# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) static_assert(a_Expr, a_szExpl)
+# else
+# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) typedef int RTASSERTVAR[(a_Expr) ? 1 : 0]
+# endif
+#elif defined(__GNUC__) && defined(__GXX_EXPERIMENTAL_CXX0X__)
+# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) static_assert(a_Expr, a_szExpl)
+#elif !defined(__GNUC__) && !defined(__IBMC__) && !defined(__IBMCPP__)
+# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) typedef int KMK_CC_STATIC_ASSERT_EX_TYPE[(a_Expr) ? 1 : 0]
+#else
+# define KMK_CC_STATIC_ASSERT_EX(a_Expr, a_szExpl) extern int KMK_CC_STATIC_ASSERT_EX_VAR[(a_Expr) ? 1 : 0]
+extern int KMK_CC_STATIC_ASSERT_EX_VAR[1];
+#endif
+/** @def KMK_CC_STATIC_ASSERT
+ * Compile time assertion, simple variant.
+ */
+#define KMK_CC_STATIC_ASSERT(a_Expr) KMK_CC_STATIC_ASSERT_EX(a_Expr, #a_Expr)
+
+
+/** Aligns a size for the block allocator. */
+#define KMK_CC_BLOCK_ALIGN_SIZE(a_cb) ( ((a_cb) + (sizeof(void *) - 1U)) & ~(uint32_t)(sizeof(void *) - 1U) )
+
+/** How to declare a no-return function.
+ * Place between scope (if any) and return type. */
+#ifdef _MSC_VER
+# define KMK_CC_FN_NO_RETURN __declspec(noreturn)
+#elif defined(__GNUC__)
+# define KMK_CC_FN_NO_RETURN __attribute__((__noreturn__))
+#endif
+
+/** Block allocator logging. */
+//#define KMK_CC_BLOCK_LOGGING_ENABLED
+#ifdef KMK_CC_BLOCK_LOGGING_ENABLED
+# define KMK_CC_BLOCK_DPRINTF_UNPACK(...) __VA_ARGS__
+# define KMK_CC_BLOCK_DPRINTF(a) fprintf(stderr, KMK_CC_BLOCK_DPRINTF_UNPACK a)
+#else
+# define KMK_CC_BLOCK_DPRINTF(a) do { } while (0)
+#endif
+
+
+/** @defgroup grp_kmk_cc_evalprog Makefile Evaluation
+ * @{
+ */
+#define KMK_CC_EVAL_LOGGING_ENABLED
+#ifdef KMK_CC_EVAL_LOGGING_ENABLED
+# define KMK_CC_EVAL_DPRINTF_UNPACK(...) __VA_ARGS__
+# define KMK_CC_EVAL_DPRINTF(a) fprintf(stderr, KMK_CC_EVAL_DPRINTF_UNPACK a)
+#else
+# define KMK_CC_EVAL_DPRINTF(a) do { } while (0)
+#endif
+
+/** @name KMK_CC_EVAL_QUALIFIER_XXX - Variable qualifiers.
+ * @{ */
+#define KMK_CC_EVAL_QUALIFIER_LOCAL 1
+#define KMK_CC_EVAL_QUALIFIER_EXPORT 2
+#define KMK_CC_EVAL_QUALIFIER_OVERRIDE 4
+#define KMK_CC_EVAL_QUALIFIER_PRIVATE 8
+/** @} */
+
+/** Eval: Max makefile size we accept as input (in bytes). */
+#define KMK_CC_EVAL_MAX_COMPILE_SIZE (16*1024*1024)
+
+/** Eval: Max nesting depth of makefile conditionals.
+ * Affects stack usage in kmk_cc_eval_compile_worker. */
+#define KMK_CC_EVAL_MAX_IF_DEPTH 32
+/** Eval: Maximum number of escaped end of line sequences to track.
+ * Affects stack usage in kmk_cc_eval_compile_worker, but not the actual
+ * number of consequtive escaped newlines in the input file/variable. */
+#define KMK_CC_EVAL_MAX_ESC_EOLS 2
+
+/** Minimum keyword length. */
+#define KMK_CC_EVAL_KEYWORD_MIN 2
+/** Maximum keyword length. */
+#define KMK_CC_EVAL_KEYWORD_MAX 16
+
+/** @name KMK_CC_EVAL_CH_XXX - flags found in g_abEvalCcChars.
+ * @{ */
+/** Normal character, nothing special. */
+#define KMK_CC_EVAL_CH_NORMAL UINT16_C(0)
+/** Blank character. */
+#define KMK_CC_EVAL_CH_BLANK UINT16_C(1)
+#define KMK_CC_EVAL_IS_BLANK(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_BLANK)
+/** Space character. */
+#define KMK_CC_EVAL_CH_SPACE UINT16_C(2)
+#define KMK_CC_EVAL_IS_SPACE(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE)
+/** Space character or potential EOL escape backslash. */
+#define KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH UINT16_C(4)
+#define KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH)
+/** Anything we need to take notice of when parsing something could be a
+ * variable name or a recipe.
+ * All space characters, backslash (EOL escape), variable expansion dollar,
+ * variable assignment operator chars, recipe colon and recipe percent. */
+#define KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE UINT16_C(8)
+#define KMK_CC_EVAL_IS_SPACE_VAR_OR_RECIPE(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE)
+/** Dollar character (possible variable expansion). */
+#define KMK_CC_EVAL_CH_DOLLAR UINT16_C(16)
+#define KMK_CC_EVAL_IS_DOLLAR(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_DOLLAR)
+/** Dollar character (possible variable expansion). */
+#define KMK_CC_EVAL_CH_BACKSLASH UINT16_C(32)
+#define KMK_CC_EVAL_IS_BACKSLASH(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_BACKSLASH)
+/** Possible EOL character. */
+#define KMK_CC_EVAL_CH_EOL_CANDIDATE UINT16_C(64)
+#define KMK_CC_EVAL_IS_EOL_CANDIDATE(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_EOL_CANDIDATE)
+/** First character in a keyword. */
+#define KMK_CC_EVAL_CH_1ST_IN_KEYWORD UINT16_C(128)
+#define KMK_CC_EVAL_IS_1ST_IN_KEYWORD(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_1ST_IN_KEYWORD)
+/** Second character in a keyword. */
+#define KMK_CC_EVAL_CH_2ND_IN_KEYWORD UINT16_C(256)
+#define KMK_CC_EVAL_IS_2ND_IN_KEYWORD(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_2ND_IN_KEYWORD)
+/** First character in a variable qualifier keyword or 'define'. */
+#define KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD UINT16_C(512)
+#define KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD)
+/** Used when parsing variable names, looking for the end of a nested
+ * variable reference. Matches parentheses and backslash (escaped eol). */
+#define KMK_CC_EVAL_CH_PAREN_OR_SLASH UINT16_C(1024)
+#define KMK_CC_EVAL_IS_PAREN_OR_SLASH(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_PAREN_OR_SLASH)
+/** Used when parsing ifeq/ifneq (,) sequences.
+ * Matches parentheses, comma and dollar (for non-plain string detection). */
+#define KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR UINT16_C(2048)
+#define KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR)
+
+/** Test of space or dollar characters. */
+#define KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_DOLLAR))
+/** Test of space, dollar or backslash (possible EOL escape) characters. */
+#define KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(a_ch) (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_DOLLAR | KMK_CC_EVAL_CH_BACKSLASH))
+/** Test of space, dollar, backslash (possible EOL escape) or variable
+ * assingment characters. */
+#define KMK_CC_EVAL_IS_SPACE_DOLLAR_SLASH_OR_ASSIGN(a_ch) \
+ (KMK_CC_EVAL_BM_GET(g_abEvalCcChars, a_ch) & (KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE | KMK_CC_EVAL_CH_DOLLAR))
+/** @} */
+
+/** Sets a bitmap entry.
+ * @param a_abBitmap Typically g_abEvalCcChars.
+ * @param a_ch The character to set.
+ * @param a_uVal The value to OR in. */
+#define KMK_CC_EVAL_BM_OR(g_abBitmap, a_ch, a_uVal) do { (g_abBitmap)[(unsigned char)(a_ch)] |= (a_uVal); } while (0)
+
+/** Gets a bitmap entry.
+ * @returns The value corresponding to @a a_ch.
+ * @param a_abBitmap Typically g_abEvalCcChars.
+ * @param a_ch The character to set. */
+#define KMK_CC_EVAL_BM_GET(g_abBitmap, a_ch) ( (g_abBitmap)[(unsigned char)(a_ch)] )
+
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Block of instructions.
+ *
+ * To avoid wasting space on "next" pointers, as well as a lot of time walking
+ * these chains when destroying programs, we work with blocks of instructions.
+ */
+typedef struct kmk_cc_block
+{
+ /** The pointer to the next block (LIFO). */
+ struct kmk_cc_block *pNext;
+ /** The size of this block. */
+ uint32_t cbBlock;
+ /** The offset of the next free byte in the block. When set to cbBlock the
+ * block is 100% full. */
+ uint32_t offNext;
+} KMKCCBLOCK;
+typedef KMKCCBLOCK *PKMKCCBLOCK;
+
+
+/** @defgroup grp_kmk_cc_exp String Expansion
+ * @{*/
+
+/**
+ * String expansion statistics.
+ */
+typedef struct KMKCCEXPSTATS
+{
+ /** Recent average size. */
+ uint32_t cchAvg;
+} KMKCCEXPSTATS;
+typedef KMKCCEXPSTATS *PKMKCCEXPSTATS;
+
+/**
+ * Expansion instructions.
+ */
+typedef enum KMKCCEXPINSTR
+{
+ /** Copy a plain string. */
+ kKmkCcExpInstr_CopyString = 0,
+ /** Insert an expanded variable value, which name we already know. */
+ kKmkCcExpInstr_PlainVariable,
+ /** Insert an expanded variable value, the name is dynamic (sub prog). */
+ kKmkCcExpInstr_DynamicVariable,
+ /** Insert an expanded variable value, which name we already know, doing
+ * search an replace on a string. */
+ kKmkCcExpInstr_SearchAndReplacePlainVariable,
+ /** Insert the output of function that requires no argument expansion. */
+ kKmkCcExpInstr_PlainFunction,
+ /** Insert the output of function that requires dynamic expansion of one ore
+ * more arguments. (Dynamic is perhaps not such a great name, but whatever.) */
+ kKmkCcExpInstr_DynamicFunction,
+ /** Jump to a new instruction block. */
+ kKmkCcExpInstr_Jump,
+ /** We're done, return. Has no specific structure. */
+ kKmkCcExpInstr_Return,
+ /** The end of valid instructions (exclusive). */
+ kKmkCcExpInstr_End
+} KMKCCEXPINSTR;
+
+/** Instruction core. */
+typedef struct kmk_cc_exp_core
+{
+ /** The instruction opcode number (KMKCCEXPINSTR). */
+ KMKCCEXPINSTR enmOpcode;
+} KMKCCEXPCORE;
+typedef KMKCCEXPCORE *PKMKCCEXPCORE;
+
+/**
+ * String expansion subprogram.
+ */
+#pragma pack(1) /* save some precious bytes */
+typedef struct kmk_cc_exp_subprog
+{
+ /** Pointer to the first instruction. */
+ PKMKCCEXPCORE pFirstInstr;
+ /** Statistics. */
+ KMKCCEXPSTATS Stats;
+} KMKCCEXPSUBPROG;
+#pragma pack()
+typedef KMKCCEXPSUBPROG *PKMKCCEXPSUBPROG;
+KMK_CC_STATIC_ASSERT(sizeof(KMKCCEXPSUBPROG) == 12 || sizeof(void *) != 8);
+
+
+/**
+ * String expansion subprogram or plain string.
+ */
+#pragma pack(1) /* save some precious bytes */
+typedef struct kmk_cc_exp_subprog_or_string
+{
+ /** Either a plain string pointer or a subprogram. */
+ union
+ {
+ /** Subprogram for expanding this argument. */
+ KMKCCEXPSUBPROG Subprog;
+ /** Pointer to the plain string. */
+ struct
+ {
+ /** Pointer to the string. */
+ const char *psz;
+ /** String length. */
+ uint32_t cch;
+ } Plain;
+ } u;
+ /** Set if subprogram (u.Subprog), clear if plain string (u.Plain). */
+ uint8_t fSubprog;
+ /** Set if the plain string is kept in the variable_strcache.
+ * @remarks Here rather than in u.Plain to make use of alignment padding. */
+ uint8_t fPlainIsInVarStrCache;
+ /** Context/user specific. */
+ uint8_t bUser;
+ /** Context/user specific #2. */
+ uint8_t bUser2;
+} KMKCCEXPSUBPROGORPLAIN;
+#pragma pack()
+typedef KMKCCEXPSUBPROGORPLAIN *PKMKCCEXPSUBPROGORPLAIN;
+KMK_CC_STATIC_ASSERT( sizeof(void *) == 8
+ ? sizeof(KMKCCEXPSUBPROGORPLAIN) == 16
+ : sizeof(void *) == 4
+ ? sizeof(KMKCCEXPSUBPROGORPLAIN) == 12
+ : 1);
+
+/**
+ * kKmkCcExpInstr_CopyString instruction format.
+ */
+typedef struct kmk_cc_exp_copy_string
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** The number of bytes to copy. */
+ uint32_t cchCopy;
+ /** Pointer to the source string (not terminated at cchCopy). */
+ const char *pachSrc;
+} KMKCCEXPCOPYSTRING;
+typedef KMKCCEXPCOPYSTRING *PKMKCCEXPCOPYSTRING;
+
+/**
+ * kKmkCcExpInstr_PlainVariable instruction format.
+ */
+typedef struct kmk_cc_exp_plain_variable
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** The name of the variable (points into variable_strcache). */
+ const char *pszName;
+} KMKCCEXPPLAINVAR;
+typedef KMKCCEXPPLAINVAR *PKMKCCEXPPLAINVAR;
+
+/**
+ * kKmkCcExpInstr_DynamicVariable instruction format.
+ */
+typedef struct kmk_cc_exp_dynamic_variable
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** The subprogram that will give us the variable name. */
+ KMKCCEXPSUBPROG Subprog;
+ /** Where to continue after this instruction. (This is necessary since the
+ * instructions of the subprogram are emitted after this instruction.) */
+ PKMKCCEXPCORE pNext;
+} KMKCCEXPDYNVAR;
+typedef KMKCCEXPDYNVAR *PKMKCCEXPDYNVAR;
+
+/**
+ * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction format.
+ */
+typedef struct kmk_cc_exp_sr_plain_variable
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** Where to continue after this instruction. (This is necessary since the
+ * instruction contains string data of variable size.) */
+ PKMKCCEXPCORE pNext;
+ /** The name of the variable (points into variable_strcache). */
+ const char *pszName;
+ /** Search pattern. */
+ const char *pszSearchPattern;
+ /** Replacement pattern. */
+ const char *pszReplacePattern;
+ /** Offset into pszSearchPattern of the significant '%' char. */
+ uint32_t offPctSearchPattern;
+ /** Offset into pszReplacePattern of the significant '%' char. */
+ uint32_t offPctReplacePattern;
+} KMKCCEXPSRPLAINVAR;
+typedef KMKCCEXPSRPLAINVAR *PKMKCCEXPSRPLAINVAR;
+
+/**
+ * Instruction format parts common to both kKmkCcExpInstr_PlainFunction and
+ * kKmkCcExpInstr_DynamicFunction.
+ */
+typedef struct kmk_cc_exp_function_core
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** Number of arguments. */
+ uint32_t cArgs; /**< @todo uint16_t to save 7 bytes of unecessary alignment padding on 64-bit systems, or merge fDirty into this member. */
+ /** Set if the function could be modifying the input arguments. */
+ uint8_t fDirty;
+ /** Where to continue after this instruction. (This is necessary since the
+ * instructions are of variable size and may be followed by string data.) */
+ PKMKCCEXPCORE pNext;
+ /**
+ * Pointer to the function table entry.
+ *
+ * @returns New variable buffer position.
+ * @param pchDst Current variable buffer position.
+ * @param papszArgs Pointer to a NULL terminated array of argument strings.
+ * @param pszFuncName The name of the function being called.
+ */
+ char * (*pfnFunction)(char *pchDst, char **papszArgs, const char *pszFuncName);
+ /** Pointer to the function name in the variable string cache. */
+ const char *pszFuncName;
+} KMKCCEXPFUNCCORE;
+typedef KMKCCEXPFUNCCORE *PKMKCCEXPFUNCCORE;
+
+/**
+ * Instruction format for kKmkCcExpInstr_PlainFunction.
+ */
+typedef struct kmk_cc_exp_plain_function
+{
+ /** The bits comment to both plain and dynamic functions. */
+ KMKCCEXPFUNCCORE FnCore;
+ /** Variable sized argument list (cArgs + 1 in length, last entry is NULL).
+ * The string pointers are to memory following this instruction, to memory in
+ * the next block or to memory in the variable / makefile we're working on
+ * (if zero terminated appropriately). */
+ const char *apszArgs[1];
+} KMKCCEXPPLAINFUNC;
+typedef KMKCCEXPPLAINFUNC *PKMKCCEXPPLAINFUNC;
+/** Calculates the size of an KMKCCEXPPLAINFUNC structure with the apszArgs
+ * member holding a_cArgs entries plus a NULL terminator. */
+#define KMKCCEXPPLAINFUNC_SIZE(a_cArgs) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEXPDYNFUNC, aArgs, (a_cArgs) + 1)
+
+/**
+ * Instruction format for kKmkCcExpInstr_DynamicFunction.
+ */
+typedef struct kmk_cc_exp_dyn_function
+{
+ /** The bits comment to both plain and dynamic functions. */
+ KMKCCEXPFUNCCORE FnCore;
+ /** Variable sized argument list (FnCore.cArgs in length).
+ * The subprograms / strings are allocated after this array (or in the next
+ * block). */
+ KMKCCEXPSUBPROGORPLAIN aArgs[1];
+} KMKCCEXPDYNFUNC;
+typedef KMKCCEXPDYNFUNC *PKMKCCEXPDYNFUNC;
+/** Calculates the size of an KMKCCEXPDYNFUNC structure with the apszArgs
+ * member holding a_cArgs entries (no zero terminator). */
+#define KMKCCEXPDYNFUNC_SIZE(a_cArgs) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEXPDYNFUNC, aArgs, a_cArgs)
+
+/**
+ * Instruction format for kKmkCcExpInstr_Jump.
+ */
+typedef struct kmk_cc_exp_jump
+{
+ /** The core instruction. */
+ KMKCCEXPCORE Core;
+ /** Where to jump to (new instruction block, typically). */
+ PKMKCCEXPCORE pNext;
+} KMKCCEXPJUMP;
+typedef KMKCCEXPJUMP *PKMKCCEXPJUMP;
+
+/**
+ * String expansion program.
+ */
+typedef struct kmk_cc_expandprog
+{
+ /** Pointer to the first instruction for this program. */
+ PKMKCCEXPCORE pFirstInstr;
+ /** List of blocks for this program (LIFO). */
+ PKMKCCBLOCK pBlockTail;
+ /** Statistics. */
+ KMKCCEXPSTATS Stats;
+#ifdef KMK_CC_STRICT
+ /** The hash of the input string. Used to check that we get all the change
+ * notifications we require. */
+ uint32_t uInputHash;
+#endif
+ /** Reference count. */
+ uint32_t volatile cRefs;
+} KMKCCEXPPROG;
+/** Pointer to a string expansion program. */
+typedef KMKCCEXPPROG *PKMKCCEXPPROG;
+
+/** @} */
+
+
+/** @addtogroup grp_kmk_cc_evalprog
+ * @{ */
+
+/** Pointer to a makefile evaluation program. */
+typedef struct kmk_cc_evalprog *PKMKCCEVALPROG;
+
+/**
+ * Makefile evaluation instructions.
+ */
+typedef enum KMKCCEVALINSTR
+{
+ /** Jump instruction - KMKCCEVALJUMP. */
+ kKmkCcEvalInstr_jump = 0,
+
+ /** [local|override|export] variable = value - KMKCCEVALASSIGN.
+ * @note Can be used for target-specific variables. */
+ kKmkCcEvalInstr_assign_recursive,
+ /** [local|override|export] variable := value - KMKCCEVALASSIGN.
+ * Also: [local|override|export] define variable := ... endef
+ * @note Can be used for target-specific variables. */
+ kKmkCcEvalInstr_assign_simple,
+ /** [local|override|export] variable += value - KMKCCEVALASSIGN.
+ * Also: [local|override|export] define variable += ... endef
+ * @note Can be used for target-specific variables. */
+ kKmkCcEvalInstr_assign_append,
+ /** [local|override|export] variable <= value - KMKCCEVALASSIGN.
+ * Also: [local|override|export] define variable <= ... endef
+ * @note Can be used for target-specific variables. */
+ kKmkCcEvalInstr_assign_prepend,
+ /** [local|override|export] variable ?= value - KMKCCEVALASSIGN.
+ * @note Can be used for target-specific variables. */
+ kKmkCcEvalInstr_assign_if_new,
+ /* [local|override|export] define variable[=] ... endef - KMKCCEVALASSIGNDEF. */
+ kKmkCcEvalInstr_define_recursive,
+ /* [local|override|export] define variable ?= ... endef - KMKCCEVALASSIGNDEF. */
+ kKmkCcEvalInstr_define_if_new,
+
+ /** export variable1 [variable2...] - KMKCCEVALVARIABLES. */
+ kKmkCcEvalInstr_export,
+ /** unexport variable1 [variable2...] - KMKCCEVALVARIABLES. */
+ kKmkCcEvalInstr_unexport,
+ /** export - KMKCCEVALCORE. */
+ kKmkCcEvalInstr_export_all,
+ /** unexport - KMKCCEVALCORE. */
+ kKmkCcEvalInstr_unexport_all,
+ /** [local|override] undefine - KMKCCEVALVARIABLES. */
+ kKmkCcEvalInstr_undefine,
+
+ /** [else] ifdef variable - KMKCCEVALIFDEFPLAIN. */
+ kKmkCcEvalInstr_ifdef_plain,
+ /** [else] ifndef variable - KMKCCEVALIFDEFPLAIN. */
+ kKmkCcEvalInstr_ifndef_plain,
+ /** [else] ifdef variable - KMKCCEVALIFDEFDYNAMIC. */
+ kKmkCcEvalInstr_ifdef_dynamic,
+ /** [else] ifndef variable - KMKCCEVALIFDEFDYNAMIC. */
+ kKmkCcEvalInstr_ifndef_dynamic,
+ /** [else] ifeq (a,b) - KMKCCEVALIFEQ. */
+ kKmkCcEvalInstr_ifeq,
+ /** [else] ifeq (a,b) - KMKCCEVALIFEQ. */
+ kKmkCcEvalInstr_ifneq,
+ /** [else] if1of (set-a,set-b) - KMKCCEVALIF1OF. */
+ kKmkCcEvalInstr_if1of,
+ /** [else] ifn1of (set-a,set-b) - KMKCCEVALIF1OF. */
+ kKmkCcEvalInstr_ifn1of,
+ /** [else] if expr - KMKCCEVALIFEXPR. */
+ kKmkCcEvalInstr_if,
+
+ /** include file1 [file2...] - KMKCCEVALINCLUDE. */
+ kKmkCcEvalInstr_include,
+ /** [sinclude|-include] file1 [file2...] - KMKCCEVALINCLUDE. */
+ kKmkCcEvalInstr_include_silent,
+ /** includedep file1 [file2...] - KMKCCEVALINCLUDE. */
+ kKmkCcEvalInstr_includedep,
+ /** includedep-queue file1 [file2...] - KMKCCEVALINCLUDE. */
+ kKmkCcEvalInstr_includedep_queue,
+ /** includedep-flush file1 [file2...] - KMKCCEVALINCLUDE. */
+ kKmkCcEvalInstr_includedep_flush,
+
+ /** Recipe without commands (defines dependencies) - KMKCCEVALRECIPE. */
+ kKmkCcEvalInstr_recipe_no_commands,
+ /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
+ kKmkCcEvalInstr_recipe_start_normal,
+ /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
+ kKmkCcEvalInstr_recipe_start_double_colon,
+ /** Recipe with commands (defines dependencies) - KMKCCEVALRECIPE. */
+ kKmkCcEvalInstr_recipe_start_pattern,
+ /** Adds more commands to the current recipe - KMKCCEVALRECIPECOMMANDS. */
+ kKmkCcEvalInstr_recipe_commands,
+ /** Special instruction for indicating the end of the recipe commands - KMKCCEVALCORE. */
+ kKmkCcEvalInstr_recipe_end,
+ /** Cancel previously defined pattern rule - KMKCCEVALRECIPE. */
+ kKmkCcEvalInstr_recipe_cancel_pattern,
+
+/** @todo target variables. */
+
+ /** vpath pattern directories - KMKCCEVALVPATH. */
+ kKmkCcEvalInstr_vpath,
+ /** vpath pattern directories - KMKCCEVALVPATH. */
+ kKmkCcEvalInstr_vpath_clear_pattern,
+ /** vpath - KMKCCEVALCORE. */
+ kKmkCcEvalInstr_vpath_clear_all,
+
+ /** Make 'code' needing expanding and evaluation - KMKCCEVALEXPAND.
+ * @note That this could in theory be used to start a recipe. This will be
+ * detected by the interpreter and loading will for now fail. A
+ * strategy for implement support for it would require picking up
+ * potential commands following the statements too. */
+ kKmkCcEvalInstr_expand,
+
+ /** The end of valid instructions (exclusive). */
+ kKmkCcEvalInstr_End
+} KMKCCEVALINSTR;
+
+/**
+ * Instruction core common to all instructions.
+ */
+typedef struct kmk_cc_eval_core
+{
+ /** The instruction opcode number (KMKCCEVALINSTR). */
+ KMKCCEVALINSTR enmOpcode;
+ /** The line number in the source this statement is associated with. */
+ unsigned iLine;
+} KMKCCEVALCORE;
+/** Pointer to an instruction core structure. */
+typedef KMKCCEVALCORE *PKMKCCEVALCORE;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_jump.
+ */
+typedef struct kmk_cc_eval_jump
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** Where to jump to (new instruction block or endif, typically). */
+ PKMKCCEVALCORE pNext;
+} KMKCCEVALJUMP;
+typedef KMKCCEVALJUMP *PKMKCCEVALJUMP;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_assign_recursive,
+ * kKmkCcEvalInstr_assign_simple, kKmkCcEvalInstr_assign_append,
+ * kKmkCcEvalInstr_assign_prepend and kKmkCcEvalInstr_assign_if_new.
+ */
+typedef struct kmk_cc_eval_assign
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** Whether the 'export' qualifier was used. */
+ uint8_t fExport;
+ /** Whether the 'override' qualifier was used. */
+ uint8_t fOverride;
+ /** Whether the 'local' qualifier was used. */
+ uint8_t fLocal;
+ /** Whether the 'private' qualifier was used. */
+ uint8_t fPrivate;
+ /** The variable name.
+ * @remarks Plain text names are in variable_strcache. */
+ KMKCCEXPSUBPROGORPLAIN Variable;
+ /** The value or value expression. */
+ KMKCCEXPSUBPROGORPLAIN Value;
+ /** Pointer to the next instruction. */
+ PKMKCCEVALCORE pNext;
+} KMKCCEVALASSIGN;
+typedef KMKCCEVALASSIGN *PKMKCCEVALASSIGN;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_define_recursive and
+ * kKmkCcEvalInstr_define_if_new.
+ */
+typedef struct kmk_cc_eval_assign_define
+{
+ /** The assignment core structure. */
+ KMKCCEVALASSIGN AssignCore;
+ /** Makefile evaluation program compiled from the define.
+ * NULL if it does not compile.
+ * @todo Let's see if this is actually doable... */
+ PKMKCCEVALPROG pEvalProg;
+} KMKCCEVALASSIGNDEF;
+typedef KMKCCEVALASSIGNDEF *PKMKCCEVALASSIGNDEF;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_export, kKmkCcEvalInstr_unexport and
+ * kKmkCcEvalInstr_undefine.
+ */
+typedef struct kmk_cc_eval_variables
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** The number of variables named in aVars. */
+ uint32_t cVars;
+ /** Whether the 'local' qualifier was used (undefine only). */
+ uint8_t fLocal;
+ /** Pointer to the next instruction. */
+ PKMKCCEVALCORE pNext;
+ /** The variable names.
+ * Expressions will be expanded and split on space.
+ * @remarks Plain text names are in variable_strcache. */
+ KMKCCEXPSUBPROGORPLAIN aVars[1];
+} KMKCCEVALVARIABLES;
+typedef KMKCCEVALVARIABLES *PKMKCCEVALVARIABLES;
+/** Calculates the size of an KMKCCEVALVARIABLES structure for @a a_cVars. */
+#define KMKCCEVALVARIABLES_SIZE(a_cVars) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALVARIABLES, aVars, a_cVars)
+
+/**
+ * Core structure for all conditionals (kKmkCcEvalInstr_if*).
+ */
+typedef struct kmk_cc_eval_if_core
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** Condition true: Pointer to the next instruction. */
+ PKMKCCEVALCORE pNextTrue;
+ /** Condition false: Pointer to the next instruction (i.e. 'else if*'
+ * or whatever follows 'else' / 'endif'. */
+ PKMKCCEVALCORE pNextFalse;
+ /** Pointer to the previous conditional for 'else if*' directives.
+ * This is only to assist the compilation process. */
+ struct kmk_cc_eval_if_core *pPrevCond;
+ /** Pointer to the jump out of the true block, if followed by 'else'.
+ * This is only to assist the compilation process. */
+ PKMKCCEVALJUMP pTrueEndJump;
+} KMKCCEVALIFCORE;
+typedef KMKCCEVALIFCORE *PKMKCCEVALIFCORE;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_ifdef_plain and
+ * kKmkCcEvalInstr_ifndef_plain.
+ * The variable name is known at compilation time.
+ */
+typedef struct kmk_cc_eval_ifdef_plain
+{
+ /** The 'if' core structure. */
+ KMKCCEVALIFCORE IfCore;
+ /** The name of the variable (points into variable_strcache). */
+ const char *pszName;
+} KMKCCEVALIFDEFPLAIN;
+typedef KMKCCEVALIFDEFPLAIN *PKMKCCEVALIFDEFPLAIN;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_ifdef_dynamic and
+ * kKmkCcEvalInstr_ifndef_dynamic.
+ * The variable name is dynamically expanded at run time.
+ */
+typedef struct kmk_cc_eval_ifdef_dynamic
+{
+ /** The 'if' core structure. */
+ KMKCCEVALIFCORE IfCore;
+ /** Alignment padding, MBZ. */
+ KU32 uPadding;
+ /** The subprogram that will give us the variable name. */
+ KMKCCEXPSUBPROG NameSubprog;
+} KMKCCEVALIFDEFDYNAMIC;
+typedef KMKCCEVALIFDEFDYNAMIC *PKMKCCEVALIFDEFDYNAMIC;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_ifeq and kKmkCcEvalInstr_ifneq.
+ */
+typedef struct kmk_cc_eval_ifeq
+{
+ /** The 'if' core structure. */
+ KMKCCEVALIFCORE IfCore;
+ /** The left hand side string expression (dynamic or plain). */
+ KMKCCEXPSUBPROGORPLAIN Left;
+ /** The rigth hand side string expression (dynamic or plain). */
+ KMKCCEXPSUBPROGORPLAIN Right;
+} KMKCCEVALIFEQ;
+typedef KMKCCEVALIFEQ *PKMKCCEVALIFEQ;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_if1of and kKmkCcEvalInstr_ifn1of.
+ *
+ * @todo This can be optimized further by pre-hashing plain text items. One of
+ * the sides are usually plain text.
+ */
+typedef struct kmk_cc_eval_if1of
+{
+ /** The 'if' core structure. */
+ KMKCCEVALIFCORE IfCore;
+ /** The left hand side string expression (dynamic or plain). */
+ KMKCCEXPSUBPROGORPLAIN Left;
+ /** The rigth hand side string expression (dynamic or plain). */
+ KMKCCEXPSUBPROGORPLAIN Right;
+} KMKCCEVALIF1OF;
+typedef KMKCCEVALIF1OF *PKMKCCEVALIF1OF;
+
+/**
+ * Instruction format for kKmkCcEvalInstr_if.
+ *
+ * @todo Parse and compile the expression. At least strip whitespace in it.
+ */
+typedef struct kmk_cc_eval_if_expr
+{
+ /** The 'if' core structure. */
+ KMKCCEVALIFCORE IfCore;
+ /** The expression string length. */
+ uint16_t cchExpr;
+ /** The expression string. */
+ char szExpr[1];
+} KMKCCEVALIFEXPR;
+typedef KMKCCEVALIFEXPR *PKMKCCEVALIFEXPR;
+/** Calculates the size of an KMKCCEVALIFEXPR structure for @a a_cchExpr long
+ * expression string (terminator is automatically added). */
+#define KMKCCEVALIFEXPR_SIZE(a_cchExpr) KMK_CC_BLOCK_ALIGN_SIZE(KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALIFEXPR, szExpr, (a_cchExpr) + 1))
+
+/**
+ * Instruction format for kKmkCcEvalInstr_include,
+ * kKmkCcEvalInstr_include_silent, kKmkCcEvalInstr_includedep,
+ * kKmkCcEvalInstr_includedep_queue, kKmkCcEvalInstr_includedep_flush.
+ */
+typedef struct kmk_cc_eval_include
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** The number of files. */
+ uint32_t cFiles;
+ /** Pointer to the next instruction (subprogs and strings after this one). */
+ PKMKCCEVALCORE pNext;
+ /** The files to be included.
+ * Expressions will be expanded and split on space.
+ * @todo Plain text file name could be replaced by file string cache entries. */
+ KMKCCEXPSUBPROGORPLAIN aFiles[1];
+} KMKCCEVALINCLUDE;
+typedef KMKCCEVALINCLUDE *PKMKCCEVALINCLUDE;
+/** Calculates the size of an KMKCCEVALINCLUDE structure for @a a_cFiles files. */
+#define KMKCCEVALINCLUDE_SIZE(a_cFiles) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALINCLUDE, aFiles, a_cFiles)
+
+/**
+ * Instruction format for kKmkCcEvalInstr_recipe_no_commands,
+ * kKmkCcEvalInstr_recipe_start_normal,
+ * kKmkCcEvalInstr_recipe_start_double_colon, kKmkCcEvalInstr_includedep_queue,
+ * kKmkCcEvalInstr_recipe_start_pattern.
+ */
+typedef struct kmk_cc_eval_recipe
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** The total number of files and dependencies in aFilesAndDeps. */
+ uint16_t cFilesAndDeps;
+
+ /** Number of targets (from index 0).
+ * This is always 1 if this is an explicit multitarget or pattern recipe,
+ * indicating the main target. */
+ uint16_t cTargets;
+ /** Explicit multitarget & patterns: First always made target. */
+ uint16_t iFirstAlwaysMadeTargets;
+ /** Explicit multitarget & patterns: Number of always targets. */
+ uint16_t cAlwaysMadeTargets;
+ /** Explicit multitarget: First maybe made target. */
+ uint16_t iFirstMaybeTarget;
+ /** Explicit multitarget: Number of maybe made targets. */
+ uint16_t cMaybeTargets;
+
+ /** First dependency. */
+ uint16_t iFirstDep;
+ /** Number of ordinary dependencies. */
+ uint16_t cDeps;
+ /** First order only dependency. */
+ uint16_t iFirstOrderOnlyDep;
+ /** Number of ordinary dependencies. */
+ uint16_t cOrderOnlyDeps;
+
+ /** Pointer to the next instruction (subprogs and strings after this one). */
+ PKMKCCEVALCORE pNext;
+ /** The .MUST_MAKE variable value, if present.
+ * If not present, this is a zero length plain string. */
+ KMKCCEXPSUBPROGORPLAIN MustMake;
+ /** The target files and dependencies.
+ * This is sorted into several sections, as defined by the above indexes and
+ * counts. Expressions will be expanded and split on space.
+ *
+ * The KMKCCEXPSUBPROGORPLAIN::bUser member one of KMKCCEVALRECIPE_FD_XXX.
+ *
+ * @todo Plain text file name could be replaced by file string cache entries. */
+ KMKCCEXPSUBPROGORPLAIN aFilesAndDeps[1];
+} KMKCCEVALRECIPE;
+typedef KMKCCEVALRECIPE *PKMKCCEVALRECIPE;
+/** Calculates the size of an KMKCCEVALRECIPE structure for @a a_cFiles
+ * files. */
+#define KMKCCEVALRECIPE_SIZE(a_cFilesAndDeps) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALRECIPE, aFilesAndDeps, a_cFilesAndDeps)
+/** @name KMKCCEVALRECIPE_FD_XXX - Values for KMKCCEVALRECIPE::aFilesAndDeps[x].bUser
+ * @{ */
+#define KMKCCEVALRECIPE_FD_NORMAL 0
+#define KMKCCEVALRECIPE_FD_SEC_EXP 1
+#define KMKCCEVALRECIPE_FD_SPECIAL_POSIX 2
+#define KMKCCEVALRECIPE_FD_SPECIAL_SECONDEXPANSION 3
+#define KMKCCEVALRECIPE_FD_SPECIAL_ONESHELL 4
+/** @} */
+
+
+/**
+ * Instruction format for kKmkCcEvalInstr_recipe_commands.
+ */
+typedef struct kmk_cc_eval_recipe_commands
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** The number of commands. */
+ uint32_t cCommands;
+ /** Pointer to the next instruction (subprogs and strings after this one). */
+ PKMKCCEVALCORE pNext;
+ /** Commands to add to the current recipe.
+ * Expressions will be expanded and split on newline? */
+ KMKCCEXPSUBPROGORPLAIN aCommands[1];
+} KMKCCEVALRECIPECOMMANDS;
+typedef KMKCCEVALRECIPECOMMANDS *PKMKCCEVALRECIPECOMMANDS;
+/** Calculates the size of an KMKCCEVALRECIPECOMMANDS structure for
+ * @a a_cCommands commands. */
+#define KMKCCEVALRECIPECOMMANDS_SIZE(a_cCommands) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALRECIPECOMMANDS, aCommands, a_cCommands)
+
+/**
+ * Instruction format for kKmkCcEvalInstr_vpath and
+ * kKmkCcEvalInstr_vpath_clear_pattern.
+ */
+typedef struct kmk_cc_eval_vpath
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** The number of search directories.
+ * This will be zero for kKmkCcEvalInstr_vpath_clear_pattern. */
+ uint32_t cDirs;
+ /** Pointer to the next instruction (subprogs and strings after this one). */
+ PKMKCCEVALCORE pNext;
+ /** The pattern. */
+ KMKCCEXPSUBPROGORPLAIN Pattern;
+ /** The directory. Expressions will be expanded and split on space. */
+ KMKCCEXPSUBPROGORPLAIN aDirs[1];
+} KMKCCEVALVPATH;
+typedef KMKCCEVALVPATH *PKMKCCEVALVPATH;
+/** Calculates the size of an KMKCCEVALVPATH structure for @a a_cFiles files. */
+#define KMKCCEVALVPATH_SIZE(a_cFiles) KMK_CC_SIZEOF_VAR_STRUCT(KMKCCEVALVPATH, aDirs, a_cDirs)
+
+
+/**
+ * Instruction format for kKmkCcEvalInstr_expand.
+ */
+typedef struct kmk_cc_eval_expand
+{
+ /** The core instruction. */
+ KMKCCEVALCORE Core;
+ /** Alignment padding, MBZ. */
+ KU32 uPadding;
+ /** The expansion subprogram that to execute and evaluate the output of. */
+ KMKCCEXPSUBPROG Subprog;
+} KMKCCEVALEXPAND;
+typedef KMKCCEVALEXPAND *PKMKCCEVALEXPAND;
+
+
+/**
+ * Makefile evaluation program.
+ */
+typedef struct kmk_cc_evalprog
+{
+ /** Pointer to the first instruction for this program. */
+ PKMKCCEVALCORE pFirstInstr;
+ /** List of blocks for this program (LIFO). */
+ PKMKCCBLOCK pBlockTail;
+ /** The name of the file containing this program. */
+ const char *pszFilename;
+ /** The name of the variable containing this program, if applicable. */
+ const char *pszVarName;
+#ifdef KMK_CC_STRICT
+ /** The hash of the input string. Used to check that we get all the change
+ * notifications we require. */
+ uint32_t uInputHash;
+#endif
+ /** Reference count. */
+ uint32_t volatile cRefs;
+} KMKCCEVALPROG;
+typedef KMKCCEVALPROG *PKMKCCEVALPROG;
+
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static uint32_t g_cVarForExpandCompilations = 0;
+static uint32_t g_cVarForExpandExecs = 0;
+static uint32_t g_cVarForEvalCompilations = 0;
+static uint32_t g_cVarForEvalExecs = 0;
+static uint32_t g_cFileForEvalCompilations = 0;
+static uint32_t g_cFileForEvalExecs = 0;
+#ifdef KMK_CC_WITH_STATS
+static uint32_t g_cBlockAllocated = 0;
+static uint32_t g_cbAllocated = 0;
+
+static uint32_t g_cBlocksAllocatedExpProgs = 0;
+static uint32_t g_cbAllocatedExpProgs = 0;
+static uint32_t g_cSingleBlockExpProgs = 0;
+static uint32_t g_cTwoBlockExpProgs = 0;
+static uint32_t g_cMultiBlockExpProgs = 0;
+static uint32_t g_cbUnusedMemExpProgs = 0;
+
+static uint32_t g_cBlocksAllocatedEvalProgs = 0;
+static uint32_t g_cbAllocatedEvalProgs = 0;
+static uint32_t g_cSingleBlockEvalProgs = 0;
+static uint32_t g_cTwoBlockEvalProgs = 0;
+static uint32_t g_cMultiBlockEvalProgs = 0;
+static uint32_t g_cbUnusedMemEvalProgs = 0;
+
+#endif
+
+/** Generic character classification, taking an 'unsigned char' index.
+ * ASSUMES unsigned char is 8-bits. */
+static uint16_t g_abEvalCcChars[256];
+
+
+/**
+ * Makefile evaluation keywords.
+ */
+static const char * const g_apszEvalKeywords[] =
+{
+ "define",
+ "export",
+ "else",
+ "endef",
+ "endif",
+ "ifdef",
+ "ifndef",
+ "ifeq",
+ "ifneq",
+ "if1of",
+ "ifn1of",
+ "if",
+ "include",
+ "includedep",
+ "includedep-queue",
+ "includedep-flush",
+ "local",
+ "override",
+ "private",
+ "sinclude",
+ "unexport",
+ "undefine",
+ "vpath",
+ "-include",
+};
+
+
+/** This is parallel to KMKCCEVALINSTR. */
+static const char * const g_apszEvalInstrNms[] =
+{
+ "jump",
+ "assign_recursive",
+ "assign_simple",
+ "assign_append",
+ "assign_prepend",
+ "assign_if_new",
+ "define_recursive",
+ "define_if_new",
+ "export",
+ "unexport",
+ "export_all",
+ "unexport_all",
+ "undefine",
+ "ifdef_plain",
+ "ifndef_plain",
+ "ifdef_dynamic",
+ "ifndef_dynamic",
+ "ifeq",
+ "ifneq",
+ "if1of",
+ "ifn1of",
+ "if",
+ "include",
+ "include_silent",
+ "includedep",
+ "includedep_queue",
+ "includedep_flush",
+ "recipe_no_commands",
+ "recipe_start_normal",
+ "recipe_start_double_colon",
+ "recipe_start_pattern",
+ "recipe_commands",
+ "recipe_end",
+ "recipe_cancel_pattern",
+ "vpath",
+ "vpath_clear_pattern",
+ "vpath_clear_all",
+};
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubprog);
+static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubprog, uint32_t *pcch);
+
+
+/**
+ * Initializes global variables for the 'compiler'.
+ */
+void kmk_cc_init(void)
+{
+ unsigned i;
+
+ /*
+ * Initialize the bitmap.
+ */
+ memset(g_abEvalCcChars, 0, sizeof(g_abEvalCcChars));
+
+ /* blank chars */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ' ', KMK_CC_EVAL_CH_BLANK);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\t', KMK_CC_EVAL_CH_BLANK);
+
+ /* space chars and zero terminator. */
+#define MY_SPACE_BITS KMK_CC_EVAL_CH_SPACE | KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ' ', MY_SPACE_BITS);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\t', MY_SPACE_BITS);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\n', MY_SPACE_BITS | KMK_CC_EVAL_CH_EOL_CANDIDATE);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\v', MY_SPACE_BITS);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\f', MY_SPACE_BITS);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\r', MY_SPACE_BITS | KMK_CC_EVAL_CH_EOL_CANDIDATE);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_SPACE_OR_BACKSLASH | KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);
+#undef MY_SPACE_BITS
+
+ /* keywords */
+ for (i = 0; i < K_ELEMENTS(g_apszEvalKeywords); i++)
+ {
+#ifdef KMK_CC_STRICT
+ size_t cch = strlen(g_apszEvalKeywords[i]);
+ KMK_CC_ASSERT(cch >= KMK_CC_EVAL_KEYWORD_MIN);
+ KMK_CC_ASSERT(cch <= KMK_CC_EVAL_KEYWORD_MAX);
+#endif
+
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, g_apszEvalKeywords[i][0], KMK_CC_EVAL_CH_1ST_IN_KEYWORD);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, g_apszEvalKeywords[i][1], KMK_CC_EVAL_CH_2ND_IN_KEYWORD);
+ }
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'd', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* define */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'e', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* export, endef */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'l', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* local */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'o', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* override */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'p', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* private */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, 'u', KMK_CC_EVAL_CH_1ST_IN_VARIABLE_KEYWORD); /* undefine, unexport */
+
+ /* Assignment punctuation and recipe stuff. */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '=', KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ':', KMK_CC_EVAL_CH_SPACE_VAR_OR_RECIPE);
+
+ /* For locating the end of variable expansion. */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '(', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ')', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '{', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '}', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_PAREN_OR_SLASH);
+
+ /* For parsing ifeq and if1of expressions. (GNU weirdly does not respect {} style function references.) */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '(', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ')', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, ',', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '$', KMK_CC_EVAL_CH_PAREN_COMMA_OR_DOLLAR);
+
+ /* Misc. */
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '$', KMK_CC_EVAL_CH_DOLLAR);
+ KMK_CC_EVAL_BM_OR(g_abEvalCcChars, '\\', KMK_CC_EVAL_CH_BACKSLASH);
+
+ /*
+ * Check that the eval instruction names match up.
+ */
+ KMK_CC_ASSERT(strcmp(g_apszEvalInstrNms[kKmkCcEvalInstr_ifneq], "ifneq") == 0);
+ KMK_CC_ASSERT(strcmp(g_apszEvalInstrNms[kKmkCcEvalInstr_vpath_clear_all], "vpath_clear_all") == 0);
+}
+
+
+/**
+ * Prints stats (for kmk -p).
+ */
+void kmk_cc_print_stats(void)
+{
+#ifdef KMK_CC_WITH_STATS
+ uint32_t const cEvalCompilations = g_cFileForEvalCompilations + g_cVarForEvalCompilations;
+#endif
+
+ puts(_("\n# The kmk 'compiler' and kmk 'program executor':\n"));
+
+ printf(_("# Variables compiled for string expansion: %6u\n"), g_cVarForExpandCompilations);
+ printf(_("# Variables string expansion runs: %6u\n"), g_cVarForExpandExecs);
+ printf(_("# String expansion runs per compile: %6u\n"), g_cVarForExpandExecs / g_cVarForExpandCompilations);
+#ifdef KMK_CC_WITH_STATS
+ printf(_("# Single alloc block exp progs: %6u (%u%%)\n"
+ "# Two alloc block exp progs: %6u (%u%%)\n"
+ "# Three or more alloc block exp progs: %6u (%u%%)\n"
+ ),
+ g_cSingleBlockExpProgs, (uint32_t)((uint64_t)g_cSingleBlockExpProgs * 100 / g_cVarForExpandCompilations),
+ g_cTwoBlockExpProgs, (uint32_t)((uint64_t)g_cTwoBlockExpProgs * 100 / g_cVarForExpandCompilations),
+ g_cMultiBlockExpProgs, (uint32_t)((uint64_t)g_cMultiBlockExpProgs * 100 / g_cVarForExpandCompilations));
+ printf(_("# Total amount of memory for exp progs: %8u bytes\n"
+ "# in: %6u blocks\n"
+ "# avg block size: %6u bytes\n"
+ "# unused memory: %8u bytes (%u%%)\n"
+ "# avg unused memory per block: %6u bytes\n"
+ "\n"),
+ g_cbAllocatedExpProgs, g_cBlocksAllocatedExpProgs, g_cbAllocatedExpProgs / g_cBlocksAllocatedExpProgs,
+ g_cbUnusedMemExpProgs, (uint32_t)((uint64_t)g_cbUnusedMemExpProgs * 100 / g_cbAllocatedExpProgs),
+ g_cbUnusedMemExpProgs / g_cBlocksAllocatedExpProgs);
+ puts("");
+#endif
+ printf(_("# Variables compiled for string eval: %6u\n"), g_cVarForEvalCompilations);
+ printf(_("# Variables string eval runs: %6u\n"), g_cVarForEvalExecs);
+ printf(_("# String evals runs per compile: %6u\n"), g_cVarForEvalExecs / g_cVarForEvalCompilations);
+ printf(_("# Files compiled: %6u\n"), g_cFileForEvalCompilations);
+ printf(_("# Files runs: %6u\n"), g_cFileForEvalExecs);
+ printf(_("# Files eval runs per compile: %6u\n"), g_cFileForEvalExecs / g_cFileForEvalCompilations);
+#ifdef KMK_CC_WITH_STATS
+ printf(_("# Single alloc block eval progs: %6u (%u%%)\n"
+ "# Two alloc block eval progs: %6u (%u%%)\n"
+ "# Three or more alloc block eval progs: %6u (%u%%)\n"
+ ),
+ g_cSingleBlockEvalProgs, (uint32_t)((uint64_t)g_cSingleBlockEvalProgs * 100 / cEvalCompilations),
+ g_cTwoBlockEvalProgs, (uint32_t)((uint64_t)g_cTwoBlockEvalProgs * 100 / cEvalCompilations),
+ g_cMultiBlockEvalProgs, (uint32_t)((uint64_t)g_cMultiBlockEvalProgs * 100 / cEvalCompilations));
+ printf(_("# Total amount of memory for eval progs: %8u bytes\n"
+ "# in: %6u blocks\n"
+ "# avg block size: %6u bytes\n"
+ "# unused memory: %8u bytes (%u%%)\n"
+ "# avg unused memory per block: %6u bytes\n"
+ "\n"),
+ g_cbAllocatedEvalProgs, g_cBlocksAllocatedEvalProgs, g_cbAllocatedEvalProgs / g_cBlocksAllocatedEvalProgs,
+ g_cbUnusedMemEvalProgs, (uint32_t)((uint64_t)g_cbUnusedMemEvalProgs * 100 / g_cbAllocatedEvalProgs),
+ g_cbUnusedMemEvalProgs / g_cBlocksAllocatedEvalProgs);
+ puts("");
+ printf(_("# Total amount of block mem allocated: %8u bytes\n"), g_cbAllocated);
+ printf(_("# Total number of block allocated: %8u\n"), g_cBlockAllocated);
+ printf(_("# Average block size: %8u byte\n"), g_cbAllocated / g_cBlockAllocated);
+#endif
+
+ puts("");
+}
+
+
+/*
+ *
+ * Various utility functions.
+ * Various utility functions.
+ * Various utility functions.
+ *
+ */
+
+/**
+ * Counts the number of dollar chars in the string.
+ *
+ * @returns Number of dollar chars.
+ * @param pchStr The string to search (does not need to be zero
+ * terminated).
+ * @param cchStr The length of the string.
+ */
+static uint32_t kmk_cc_count_dollars(const char *pchStr, uint32_t cchStr)
+{
+ uint32_t cDollars = 0;
+ const char *pch;
+ while ((pch = memchr(pchStr, '$', cchStr)) != NULL)
+ {
+ cDollars++;
+ cchStr -= pch - pchStr + 1;
+ pchStr = pch + 1;
+ }
+ return cDollars;
+}
+
+#ifdef KMK_CC_STRICT
+/**
+ * Used to check that function arguments are left alone.
+ * @returns Updated hash.
+ * @param uHash The current hash value.
+ * @param psz The string to hash.
+ */
+static uint32_t kmk_cc_debug_string_hash(uint32_t uHash, const char *psz)
+{
+ unsigned char ch;
+ while ((ch = *(unsigned char const *)psz++) != '\0')
+ uHash = (uHash << 6) + (uHash << 16) - uHash + (unsigned char)ch;
+ return uHash;
+}
+
+/**
+ * Used to check that function arguments are left alone.
+ * @returns Updated hash.
+ * @param uHash The current hash value.
+ * @param pch The string to hash, not terminated.
+ * @param cch The number of chars to hash.
+ */
+static uint32_t kmk_cc_debug_string_hash_n(uint32_t uHash, const char *pch, uint32_t cch)
+{
+ while (cch-- > 0)
+ {
+ unsigned char ch = *(unsigned char const *)pch++;
+ uHash = (uHash << 6) + (uHash << 16) - uHash + (unsigned char)ch;
+ }
+ return uHash;
+}
+
+#endif
+
+
+
+/*
+ *
+ * The allocator.
+ * The allocator.
+ * The allocator.
+ *
+ */
+
+
+/**
+ * For the first allocation using the block allocator.
+ *
+ * @returns Pointer to the first allocation (@a cbFirst in size).
+ * @param ppBlockTail Where to return the pointer to the first block.
+ * @param cbFirst The size of the first allocation.
+ * @param cbHint Hint about how much memory we might be needing.
+ */
+static void *kmk_cc_block_alloc_first(PKMKCCBLOCK *ppBlockTail, size_t cbFirst, size_t cbHint)
+{
+ uint32_t cbBlock;
+ PKMKCCBLOCK pNewBlock;
+
+ KMK_CC_ASSERT_ALIGNED(cbFirst, sizeof(void *));
+ KMK_CC_ASSERT(cbFirst <= 128);
+
+ /*
+ * Turn the hint into a block size.
+ */
+ cbHint += cbFirst;
+ if (cbHint <= 512)
+ {
+ if (cbHint <= 256)
+ {
+ if (cbFirst <= 64)
+ cbBlock = 128;
+ else
+ cbBlock = 256;
+ }
+ else
+ cbBlock = 256;
+ }
+ else if (cbHint < 2048)
+ cbBlock = 1024;
+ else if (cbHint < 3072)
+ cbBlock = 2048;
+ else
+ cbBlock = 4096;
+
+ /*
+ * Allocate and initialize the first block.
+ */
+ pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
+ pNewBlock->cbBlock = cbBlock;
+ pNewBlock->offNext = sizeof(*pNewBlock) + cbFirst;
+ pNewBlock->pNext = NULL;
+ *ppBlockTail = pNewBlock;
+
+#ifdef KMK_CC_WITH_STATS
+ g_cBlockAllocated++;
+ g_cbAllocated += cbBlock;
+#endif
+
+ return pNewBlock + 1;
+}
+
+
+/**
+ * Used for getting the address of the next instruction.
+ *
+ * @returns Pointer to the next allocation.
+ * @param pBlockTail The allocator tail pointer.
+ */
+static void *kmk_cc_block_get_next_ptr(PKMKCCBLOCK pBlockTail)
+{
+ return (char *)pBlockTail + pBlockTail->offNext;
+}
+
+
+/**
+ * Realigns the allocator after doing byte or string allocations.
+ *
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ */
+static void kmk_cc_block_realign(PKMKCCBLOCK *ppBlockTail)
+{
+ PKMKCCBLOCK pBlockTail = *ppBlockTail;
+ uint32_t offNext = pBlockTail->offNext;
+ if (offNext & (sizeof(void *) - 1U))
+ {
+ pBlockTail->offNext = KMK_CC_BLOCK_ALIGN_SIZE(offNext);
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_realign: offNext=%#x -> %#x\n", offNext, pBlockTail->offNext));
+ KMK_CC_ASSERT(pBlockTail->cbBlock - pBlockTail->offNext >= sizeof(KMKCCEXPJUMP));
+ }
+}
+
+
+/**
+ * Grows the allocation with another block, byte allocator case.
+ *
+ * @returns Pointer to the byte allocation.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static void *kmk_cc_block_byte_alloc_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pOldBlock = *ppBlockTail;
+ PKMKCCBLOCK pPrevBlock = pOldBlock->pNext;
+ PKMKCCBLOCK pNewBlock;
+ uint32_t cbBlock;
+
+ /*
+ * Check if there accidentally is some space left in the previous block first.
+ */
+ if ( pPrevBlock
+ && pPrevBlock->cbBlock - pPrevBlock->offNext >= cb)
+ {
+ void *pvRet = (char *)pPrevBlock + pPrevBlock->offNext;
+ pPrevBlock->offNext += cb;
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc_grow: %p LB %#x offNext=%#x [prev]\n", pvRet, cb, pPrevBlock->offNext));
+ return pvRet;
+ }
+
+ /*
+ * Allocate a new block.
+ */
+
+ /* Figure the block size. */
+ cbBlock = pOldBlock->cbBlock;
+ while (cbBlock - sizeof(KMKCCEXPJUMP) - sizeof(*pNewBlock) < cb)
+ cbBlock *= 2;
+
+ /* Allocate and initialize the block it with the new instruction already accounted for. */
+ pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
+ pNewBlock->cbBlock = cbBlock;
+ pNewBlock->offNext = sizeof(*pNewBlock) + cb;
+ pNewBlock->pNext = pOldBlock;
+ *ppBlockTail = pNewBlock;
+
+#ifdef KMK_CC_WITH_STATS
+ g_cBlockAllocated++;
+ g_cbAllocated += cbBlock;
+#endif
+
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc_grow: %p LB %#x offNext=%#x\n", pNewBlock + 1, cb, pNewBlock->offNext));
+ return pNewBlock + 1;
+}
+
+
+/**
+ * Make a byte allocation.
+ *
+ * Must call kmk_cc_block_realign() when done doing byte and string allocations.
+ *
+ * @returns Pointer to the byte allocation (byte aligned).
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static void *kmk_cc_block_byte_alloc(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pBlockTail = *ppBlockTail;
+ uint32_t cbLeft = pBlockTail->cbBlock - pBlockTail->offNext;
+
+ KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP));
+ if (cbLeft >= cb + sizeof(KMKCCEXPJUMP))
+ {
+ void *pvRet = (char *)pBlockTail + pBlockTail->offNext;
+ pBlockTail->offNext += cb;
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_byte_alloc: %p LB %#x offNext=%#x\n", pvRet, cb, pBlockTail->offNext));
+ return pvRet;
+ }
+ return kmk_cc_block_byte_alloc_grow(ppBlockTail, cb);
+}
+
+
+/**
+ * Duplicates the given string in a byte allocation.
+ *
+ * Must call kmk_cc_block_realign() when done doing byte and string allocations.
+ *
+ * @returns Pointer to the byte allocation (byte aligned).
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static const char *kmk_cc_block_strdup(PKMKCCBLOCK *ppBlockTail, const char *pachStr, uint32_t cchStr)
+{
+ char *pszCopy;
+ if (cchStr)
+ {
+ pszCopy = kmk_cc_block_byte_alloc(ppBlockTail, cchStr + 1);
+ memcpy(pszCopy, pachStr, cchStr);
+ pszCopy[cchStr] = '\0';
+ return pszCopy;
+ }
+ return "";
+}
+
+
+/**
+ * Grows the allocation with another block, string expansion program case.
+ *
+ * @returns Pointer to a string expansion instruction core.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static PKMKCCEXPCORE kmk_cc_block_alloc_exp_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pOldBlock = *ppBlockTail;
+ PKMKCCBLOCK pNewBlock;
+ PKMKCCEXPCORE pRet;
+ PKMKCCEXPJUMP pJump;
+
+ /* Figure the block size. */
+ uint32_t cbBlock = !pOldBlock->pNext ? 128 : pOldBlock->cbBlock;
+ while (cbBlock - sizeof(KMKCCEXPJUMP) - sizeof(*pNewBlock) < cb)
+ cbBlock *= 2;
+
+ /* Allocate and initialize the block it with the new instruction already accounted for. */
+ pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
+ pNewBlock->cbBlock = cbBlock;
+ pNewBlock->offNext = sizeof(*pNewBlock) + cb;
+ pNewBlock->pNext = pOldBlock;
+ *ppBlockTail = pNewBlock;
+
+#ifdef KMK_CC_WITH_STATS
+ g_cBlockAllocated++;
+ g_cbAllocated += cbBlock;
+#endif
+
+ pRet = (PKMKCCEXPCORE)(pNewBlock + 1);
+ KMK_CC_ASSERT(((size_t)pRet & (sizeof(void *) - 1)) == 0);
+
+ /* Emit jump. */
+ pJump = (PKMKCCEXPJUMP)((char *)pOldBlock + pOldBlock->offNext);
+ pJump->Core.enmOpcode = kKmkCcExpInstr_Jump;
+ pJump->pNext = pRet;
+ pOldBlock->offNext += sizeof(*pJump);
+ KMK_CC_ASSERT(pOldBlock->offNext <= pOldBlock->cbBlock);
+
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_exp_grow: %p LB %#x offNext=%#x\n", pRet, cb, pNewBlock->offNext));
+ return pRet;
+}
+
+
+/**
+ * Allocates a string expansion instruction of size @a cb.
+ *
+ * @returns Pointer to a string expansion instruction core.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static PKMKCCEXPCORE kmk_cc_block_alloc_exp(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pBlockTail = *ppBlockTail;
+ uint32_t cbLeft = pBlockTail->cbBlock - pBlockTail->offNext;
+
+ KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP));
+ KMK_CC_ASSERT( (cb & (sizeof(void *) - 1)) == 0 || cb == sizeof(KMKCCEXPCORE) /* final */ );
+ KMK_CC_ASSERT((pBlockTail->offNext & (sizeof(void *) - 1)) == 0);
+
+ if (cbLeft >= cb + sizeof(KMKCCEXPJUMP))
+ {
+ PKMKCCEXPCORE pRet = (PKMKCCEXPCORE)((char *)pBlockTail + pBlockTail->offNext);
+ pBlockTail->offNext += cb;
+ KMK_CC_ASSERT(((size_t)pRet & (sizeof(void *) - 1)) == 0);
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_exp: %p LB %#x offNext=%#x\n", pRet, cb, pBlockTail->offNext));
+ return pRet;
+ }
+ return kmk_cc_block_alloc_exp_grow(ppBlockTail, cb);
+}
+
+
+/**
+ * Grows the allocation with another block, makefile evaluation program case.
+ *
+ * @returns Pointer to a makefile evaluation instruction core.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static PKMKCCEVALCORE kmk_cc_block_alloc_eval_grow(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pOldBlock = *ppBlockTail;
+ PKMKCCBLOCK pNewBlock;
+ PKMKCCEVALCORE pRet;
+ PKMKCCEVALJUMP pJump;
+
+ /* Figure the block size. */
+ uint32_t cbBlock = !pOldBlock->pNext ? 128 : pOldBlock->cbBlock;
+ while (cbBlock - sizeof(KMKCCEVALJUMP) - sizeof(*pNewBlock) < cb)
+ cbBlock *= 2;
+
+ /* Allocate and initialize the block it with the new instruction already accounted for. */
+ pNewBlock = (PKMKCCBLOCK)xmalloc(cbBlock);
+ pNewBlock->cbBlock = cbBlock;
+ pNewBlock->offNext = sizeof(*pNewBlock) + cb;
+ pNewBlock->pNext = pOldBlock;
+ *ppBlockTail = pNewBlock;
+
+#ifdef KMK_CC_WITH_STATS
+ g_cBlockAllocated++;
+ g_cbAllocated += cbBlock;
+#endif
+
+ pRet = (PKMKCCEVALCORE)(pNewBlock + 1);
+
+ /* Emit jump. */
+ pJump = (PKMKCCEVALJUMP)((char *)pOldBlock + pOldBlock->offNext);
+ pJump->Core.enmOpcode = kKmkCcEvalInstr_jump;
+ pJump->pNext = pRet;
+ pOldBlock->offNext += sizeof(*pJump);
+ KMK_CC_ASSERT(pOldBlock->offNext <= pOldBlock->cbBlock);
+ KMK_CC_ASSERT((pNewBlock->offNext & (sizeof(void *) - 1)) == 0);
+
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_eval_grow: %p LB %#x offNext=%#x (*ppBlockTail=%p, was %p)\n",
+ pRet, cb, pNewBlock->offNext, *ppBlockTail, pOldBlock));
+ return pRet;
+}
+
+
+/**
+ * Allocates a makefile evaluation instruction of size @a cb.
+ *
+ * @returns Pointer to a makefile evaluation instruction core.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param cb The number of bytes to allocate.
+ */
+static PKMKCCEVALCORE kmk_cc_block_alloc_eval(PKMKCCBLOCK *ppBlockTail, uint32_t cb)
+{
+ PKMKCCBLOCK pBlockTail = *ppBlockTail;
+ uint32_t cbLeft = pBlockTail->cbBlock - pBlockTail->offNext;
+
+ KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEVALJUMP));
+ KMK_CC_ASSERT( (cb & (sizeof(void *) - 1)) == 0 );
+ KMK_CC_ASSERT((pBlockTail->offNext & (sizeof(void *) - 1)) == 0);
+
+ if (cbLeft >= cb + sizeof(KMKCCEVALJUMP))
+ {
+ PKMKCCEVALCORE pRet = (PKMKCCEVALCORE)((char *)pBlockTail + pBlockTail->offNext);
+ pBlockTail->offNext += cb;
+ KMK_CC_BLOCK_DPRINTF(("kmk_cc_block_alloc_eval: %p LB %#x offNext=%#x\n", pRet, cb, pBlockTail->offNext));
+ return pRet;
+ }
+ return kmk_cc_block_alloc_eval_grow(ppBlockTail, cb);
+}
+
+
+/**
+ * Frees all memory used by an allocator.
+ *
+ * @param ppBlockTail The allocator tail pointer.
+ */
+static void kmk_cc_block_free_list(PKMKCCBLOCK pBlockTail)
+{
+ while (pBlockTail)
+ {
+ PKMKCCBLOCK pThis = pBlockTail;
+ pBlockTail = pBlockTail->pNext;
+ free(pThis);
+ }
+}
+
+
+/*
+ *
+ * The string expansion compiler.
+ * The string expansion compiler.
+ * The string expansion compiler.
+ *
+ */
+
+
+/**
+ * Emits a kKmkCcExpInstr_Return.
+ *
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ */
+static void kmk_cc_exp_emit_return(PKMKCCBLOCK *ppBlockTail)
+{
+ PKMKCCEXPCORE pCore = kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pCore));
+ pCore->enmOpcode = kKmkCcExpInstr_Return;
+ kmk_cc_block_realign(ppBlockTail);
+}
+
+
+/**
+ * Checks if a function is known to mess up the arguments its given.
+ *
+ * When executing calls to "dirty" functions, all arguments must be duplicated
+ * on the heap.
+ *
+ * @returns 1 if dirty, 0 if clean.
+ * @param pszFunction The function name.
+ */
+static uint8_t kmk_cc_is_dirty_function(const char *pszFunction)
+{
+ switch (pszFunction[0])
+ {
+ default:
+ return 0;
+
+ case 'e':
+ if (!strcmp(pszFunction, "eval"))
+ return 1;
+ if (!strcmp(pszFunction, "evalctx"))
+ return 1;
+ return 0;
+
+ case 'f':
+ if (!strcmp(pszFunction, "filter"))
+ return 1;
+ if (!strcmp(pszFunction, "filter-out"))
+ return 1;
+ if (!strcmp(pszFunction, "for"))
+ return 1;
+ return 0;
+
+ case 's':
+ if (!strcmp(pszFunction, "sort"))
+ return 1;
+ return 0;
+ }
+}
+
+
+/**
+ * Emits a function call instruction taking arguments that needs expanding.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pszFunction The function name (const string from function.c).
+ * @param pchArgs Pointer to the arguments expression string, leading
+ * any blanks has been stripped.
+ * @param cchArgs The length of the arguments expression string.
+ * @param cArgs Number of arguments found.
+ * @param chOpen The char used to open the function call.
+ * @param chClose The char used to close the function call.
+ * @param pfnFunction The function implementation.
+ * @param cMaxArgs Maximum number of arguments the function takes.
+ */
+static int kmk_cc_exp_emit_dyn_function(PKMKCCBLOCK *ppBlockTail, const char *pszFunction,
+ const char *pchArgs, uint32_t cchArgs, uint32_t cArgs, char chOpen, char chClose,
+ make_function_ptr_t pfnFunction, unsigned char cMaxArgs)
+{
+ uint32_t iArg;
+
+ /*
+ * The function instruction has variable size. The maximum argument count
+ * isn't quite like the minium one. Zero means no limit. While a non-zero
+ * value means that any commas beyond the max will be taken to be part of
+ * the final argument.
+ */
+ uint32_t cActualArgs = cArgs <= cMaxArgs || !cMaxArgs ? cArgs : cMaxArgs;
+ PKMKCCEXPDYNFUNC pInstr = (PKMKCCEXPDYNFUNC)kmk_cc_block_alloc_exp(ppBlockTail, KMKCCEXPDYNFUNC_SIZE(cActualArgs));
+ pInstr->FnCore.Core.enmOpcode = kKmkCcExpInstr_DynamicFunction;
+ pInstr->FnCore.cArgs = cActualArgs;
+ pInstr->FnCore.pfnFunction = pfnFunction;
+ pInstr->FnCore.pszFuncName = pszFunction;
+ pInstr->FnCore.fDirty = kmk_cc_is_dirty_function(pszFunction);
+
+ /*
+ * Parse the arguments. Plain arguments gets duplicated in the program
+ * memory so that they are terminated and no extra processing is necessary
+ * later on. ASSUMES that the function implementations do NOT change
+ * argument memory. Other arguments the compiled into their own expansion
+ * sub programs.
+ */
+ iArg = 0;
+ for (;;)
+ {
+ /* Find the end of the argument. Check for $. */
+ char ch = '\0';
+ uint8_t fDollar = 0;
+ int32_t cDepth = 0;
+ uint32_t cchThisArg = 0;
+ while (cchThisArg < cchArgs)
+ {
+ ch = pchArgs[cchThisArg];
+ if (ch == chClose)
+ {
+ KMK_CC_ASSERT(cDepth > 0);
+ if (cDepth > 0)
+ cDepth--;
+ }
+ else if (ch == chOpen)
+ cDepth++;
+ else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs)
+ break;
+ else if (ch == '$')
+ fDollar = 1;
+ cchThisArg++;
+ }
+
+ pInstr->aArgs[iArg].fSubprog = fDollar;
+ if (fDollar)
+ {
+ /* Compile it. */
+ int rc;
+ kmk_cc_block_realign(ppBlockTail);
+ rc = kmk_cc_exp_compile_subprog(ppBlockTail, pchArgs, cchThisArg, &pInstr->aArgs[iArg].u.Subprog);
+ if (rc != 0)
+ return rc;
+ }
+ else
+ {
+ /* Duplicate it. */
+ pInstr->aArgs[iArg].u.Plain.psz = kmk_cc_block_strdup(ppBlockTail, pchArgs, cchThisArg);
+ pInstr->aArgs[iArg].u.Plain.cch = cchThisArg;
+ }
+ iArg++;
+ if (ch != ',')
+ break;
+ pchArgs += cchThisArg + 1;
+ cchArgs -= cchThisArg + 1;
+ }
+ KMK_CC_ASSERT(iArg == cActualArgs);
+
+ /*
+ * Realign the allocator and take down the address of the next instruction.
+ */
+ kmk_cc_block_realign(ppBlockTail);
+ pInstr->FnCore.pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+ return 0;
+}
+
+
+/**
+ * Emits a function call instruction taking plain arguments.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pszFunction The function name (const string from function.c).
+ * @param pchArgs Pointer to the arguments string, leading any blanks
+ * has been stripped.
+ * @param cchArgs The length of the arguments string.
+ * @param cArgs Number of arguments found.
+ * @param chOpen The char used to open the function call.
+ * @param chClose The char used to close the function call.
+ * @param pfnFunction The function implementation.
+ * @param cMaxArgs Maximum number of arguments the function takes.
+ */
+static void kmk_cc_exp_emit_plain_function(PKMKCCBLOCK *ppBlockTail, const char *pszFunction,
+ const char *pchArgs, uint32_t cchArgs, uint32_t cArgs, char chOpen, char chClose,
+ make_function_ptr_t pfnFunction, unsigned char cMaxArgs)
+{
+ uint32_t iArg;
+
+ /*
+ * The function instruction has variable size. The maximum argument count
+ * isn't quite like the minium one. Zero means no limit. While a non-zero
+ * value means that any commas beyond the max will be taken to be part of
+ * the final argument.
+ */
+ uint32_t cActualArgs = cArgs <= cMaxArgs || !cMaxArgs ? cArgs : cMaxArgs;
+ PKMKCCEXPPLAINFUNC pInstr = (PKMKCCEXPPLAINFUNC)kmk_cc_block_alloc_exp(ppBlockTail, KMKCCEXPPLAINFUNC_SIZE(cActualArgs));
+ pInstr->FnCore.Core.enmOpcode = kKmkCcExpInstr_PlainFunction;
+ pInstr->FnCore.cArgs = cActualArgs;
+ pInstr->FnCore.pfnFunction = pfnFunction;
+ pInstr->FnCore.pszFuncName = pszFunction;
+ pInstr->FnCore.fDirty = kmk_cc_is_dirty_function(pszFunction);
+
+ /*
+ * Parse the arguments. Plain arguments gets duplicated in the program
+ * memory so that they are terminated and no extra processing is necessary
+ * later on. ASSUMES that the function implementations do NOT change
+ * argument memory.
+ */
+ iArg = 0;
+ for (;;)
+ {
+ /* Find the end of the argument. */
+ char ch = '\0';
+ int32_t cDepth = 0;
+ uint32_t cchThisArg = 0;
+ while (cchThisArg < cchArgs)
+ {
+ ch = pchArgs[cchThisArg];
+ if (ch == chClose)
+ {
+ KMK_CC_ASSERT(cDepth > 0);
+ if (cDepth > 0)
+ cDepth--;
+ }
+ else if (ch == chOpen)
+ cDepth++;
+ else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs)
+ break;
+ cchThisArg++;
+ }
+
+ /* Duplicate it. */
+ pInstr->apszArgs[iArg++] = kmk_cc_block_strdup(ppBlockTail, pchArgs, cchThisArg);
+ if (ch != ',')
+ break;
+ pchArgs += cchThisArg + 1;
+ cchArgs -= cchThisArg + 1;
+ }
+
+ KMK_CC_ASSERT(iArg == cActualArgs);
+ pInstr->apszArgs[iArg] = NULL;
+
+ /*
+ * Realign the allocator and take down the address of the next instruction.
+ */
+ kmk_cc_block_realign(ppBlockTail);
+ pInstr->FnCore.pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+}
+
+
+/**
+ * Emits a kKmkCcExpInstr_DynamicVariable.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pchNameExpr The name of the variable (ASSUMED presistent
+ * thru-out the program life time).
+ * @param cchNameExpr The length of the variable name. If zero,
+ * nothing will be emitted.
+ */
+static int kmk_cc_exp_emit_dyn_variable(PKMKCCBLOCK *ppBlockTail, const char *pchNameExpr, uint32_t cchNameExpr)
+{
+ PKMKCCEXPDYNVAR pInstr;
+ int rc;
+ KMK_CC_ASSERT(cchNameExpr > 0);
+
+ pInstr = (PKMKCCEXPDYNVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcExpInstr_DynamicVariable;
+
+ rc = kmk_cc_exp_compile_subprog(ppBlockTail, pchNameExpr, cchNameExpr, &pInstr->Subprog);
+
+ pInstr->pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+ return rc;
+}
+
+
+/**
+ * Emits either a kKmkCcExpInstr_PlainVariable or
+ * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction.
+ *
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pchName The name of the variable. (Does not need to be
+ * valid beyond the call.)
+ * @param cchName The length of the variable name. If zero,
+ * nothing will be emitted.
+ */
+static void kmk_cc_exp_emit_plain_variable_maybe_sr(PKMKCCBLOCK *ppBlockTail, const char *pchName, uint32_t cchName)
+{
+ if (cchName > 0)
+ {
+ /*
+ * Hopefully, we're not expected to do any search and replace on the
+ * expanded variable string later... Requires both ':' and '='.
+ */
+ const char *pchEqual;
+ const char *pchColon = (const char *)memchr(pchName, ':', cchName);
+ if ( pchColon == NULL
+ || (pchEqual = (const char *)memchr(pchColon + 1, ':', cchName - (pchColon - pchName - 1))) == NULL
+ || pchEqual == pchEqual + 1)
+ {
+ PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcExpInstr_PlainVariable;
+ pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName);
+ }
+ else if (pchColon != pchName)
+ {
+ /*
+ * Okay, we need to do search and replace the variable value.
+ * This is performed by patsubst_expand_pat using '%' patterns.
+ */
+ uint32_t cchName2 = (uint32_t)(pchColon - pchName);
+ uint32_t cchSearch = (uint32_t)(pchEqual - pchColon - 1);
+ uint32_t cchReplace = cchName - cchName2 - cchSearch - 2;
+ const char *pchPct;
+ char *psz;
+ PKMKCCEXPSRPLAINVAR pInstr;
+
+ pInstr = (PKMKCCEXPSRPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcExpInstr_SearchAndReplacePlainVariable;
+ pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName2);
+
+ /* Figure out the search pattern, unquoting percent chars.. */
+ psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchSearch + 2);
+ psz[0] = '%';
+ memcpy(psz + 1, pchColon + 1, cchSearch);
+ psz[1 + cchSearch] = '\0';
+ pchPct = find_percent(psz + 1); /* also performs unquoting */
+ if (pchPct)
+ {
+ pInstr->pszSearchPattern = psz + 1;
+ pInstr->offPctSearchPattern = (uint32_t)(pchPct - psz - 1);
+ }
+ else
+ {
+ pInstr->pszSearchPattern = psz;
+ pInstr->offPctSearchPattern = 0;
+ }
+
+ /* Figure out the replacement pattern, unquoting percent chars.. */
+ if (cchReplace == 0)
+ {
+ pInstr->pszReplacePattern = "%";
+ pInstr->offPctReplacePattern = 0;
+ }
+ else
+ {
+ psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchReplace + 2);
+ psz[0] = '%';
+ memcpy(psz + 1, pchEqual + 1, cchReplace);
+ psz[1 + cchReplace] = '\0';
+ pchPct = find_percent(psz + 1); /* also performs unquoting */
+ if (pchPct)
+ {
+ pInstr->pszReplacePattern = psz + 1;
+ pInstr->offPctReplacePattern = (uint32_t)(pchPct - psz - 1);
+ }
+ else
+ {
+ pInstr->pszReplacePattern = psz;
+ pInstr->offPctReplacePattern = 0;
+ }
+ }
+
+ /* Note down where the next instruction is after realigning the allocator. */
+ kmk_cc_block_realign(ppBlockTail);
+ pInstr->pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+ }
+ }
+}
+
+
+/**
+ * Emits a kKmkCcExpInstr_CopyString.
+ *
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pchStr The string to emit (ASSUMED presistent thru-out
+ * the program life time).
+ * @param cchStr The number of chars to copy. If zero, nothing
+ * will be emitted.
+ */
+static void kmk_cc_exp_emit_copy_string(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr)
+{
+ if (cchStr > 0)
+ {
+ PKMKCCEXPCOPYSTRING pInstr = (PKMKCCEXPCOPYSTRING)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcExpInstr_CopyString;
+ pInstr->cchCopy = cchStr;
+ pInstr->pachSrc = pchStr;
+ }
+}
+
+
+/**
+ * String expansion compilation function common to both normal and sub programs.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pchStr The expression to compile.
+ * @param cchStr The length of the expression to compile.
+ */
+static int kmk_cc_exp_compile_common(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr)
+{
+ /*
+ * Process the string.
+ */
+ while (cchStr > 0)
+ {
+ /* Look for dollar sign, marks variable expansion or dollar-escape. */
+ int rc;
+ const char *pchDollar = memchr(pchStr, '$', cchStr);
+ if (pchDollar)
+ {
+ /*
+ * Check for multiple dollar chars.
+ */
+ uint32_t offDollar = (uint32_t)(pchDollar - pchStr);
+ uint32_t cDollars = 1;
+ while ( offDollar + cDollars < cchStr
+ && pchStr[offDollar + cDollars] == '$')
+ cDollars++;
+
+ /*
+ * Emit a string copy for any preceeding stuff, including half of
+ * the dollars we found (dollar escape: $$ -> $).
+ * (kmk_cc_exp_emit_copy_string ignore zero length strings).
+ */
+ kmk_cc_exp_emit_copy_string(ppBlockTail, pchStr, offDollar + cDollars / 2);
+ pchStr += offDollar + cDollars;
+ cchStr -= offDollar + cDollars;
+
+ /*
+ * Odd number of dollar chars means there is a variable to expand
+ * or function to call.
+ */
+ if (cDollars & 1)
+ {
+ if (cchStr > 0)
+ {
+ char const chOpen = *pchStr;
+ if (chOpen == '(' || chOpen == '{')
+ {
+ /* There are several alternative ways of finding the ending
+ parenthesis / braces.
+
+ GNU make does one thing for functions and variable containing
+ any '$' chars before the first closing char. While for
+ variables where a closing char comes before any '$' char, a
+ simplified approach is taken. This means that for example:
+
+ Given VAR=var, the expressions "$(var())" and
+ "$($(VAR)())" would be expanded differently.
+ In the first case the variable "var(" would be
+ used and in the second "var()".
+
+ This code will not duplicate this weird behavior, but work
+ the same regardless of whether there is a '$' char before
+ the first closing char. */
+ make_function_ptr_t pfnFunction;
+ const char *pszFunction;
+ unsigned char cMaxArgs;
+ unsigned char cMinArgs;
+ char fExpandArgs;
+ char const chClose = chOpen == '(' ? ')' : '}';
+ char ch = 0;
+ uint32_t cchName = 0;
+ uint32_t cDepth = 1;
+ uint32_t cMaxDepth = 1;
+ cDollars = 0;
+
+ pchStr++;
+ cchStr--;
+
+ /* First loop: Identify potential function calls and dynamic expansion. */
+ KMK_CC_ASSERT(!func_char_map[(unsigned char)chOpen]);
+ KMK_CC_ASSERT(!func_char_map[(unsigned char)chClose]);
+ KMK_CC_ASSERT(!func_char_map[(unsigned char)'$']);
+ while (cchName < cchStr)
+ {
+ ch = pchStr[cchName];
+ if (!func_char_map[(unsigned char)ch])
+ break;
+ cchName++;
+ }
+
+ if ( cchName >= MIN_FUNCTION_LENGTH
+ && cchName <= MAX_FUNCTION_LENGTH
+ && (ISBLANK(ch) || ch == chClose || cchName == cchStr)
+ && (pfnFunction = lookup_function_for_compiler(pchStr, cchName, &cMinArgs, &cMaxArgs,
+ &fExpandArgs, &pszFunction)) != NULL)
+ {
+ /*
+ * It's a function invocation, we should count parameters while
+ * looking for the end.
+ * Note! We use cchName for the length of the argument list.
+ */
+ uint32_t cArgs = 1;
+ if (ch != chClose)
+ {
+ /* Skip leading spaces before the first arg. */
+ cchName++;
+ while (cchName < cchStr && ISBLANK(pchStr[cchName]))
+ cchName++;
+
+ pchStr += cchName;
+ cchStr -= cchName;
+ cchName = 0;
+
+ while (cchName < cchStr)
+ {
+ ch = pchStr[cchName];
+ if (ch == ',')
+ {
+ if (cDepth == 1)
+ cArgs++;
+ }
+ else if (ch == chClose)
+ {
+ if (!--cDepth)
+ break;
+ }
+ else if (ch == chOpen)
+ {
+ if (++cDepth > cMaxDepth)
+ cMaxDepth = cDepth;
+ }
+ else if (ch == '$')
+ cDollars++;
+ cchName++;
+ }
+ }
+ else
+ {
+ pchStr += cchName;
+ cchStr -= cchName;
+ cchName = 0;
+ }
+ if (cArgs < cMinArgs)
+ {
+ fatal(NULL, _("Function '%s' takes a minimum of %d arguments: %d given"),
+ pszFunction, (int)cMinArgs, (int)cArgs);
+ return -1; /* not reached */
+ }
+ if (cDepth != 0)
+ {
+ fatal(NULL, chOpen == '('
+ ? _("Missing closing parenthesis calling '%s'") : _("Missing closing braces calling '%s'"),
+ pszFunction);
+ return -1; /* not reached */
+ }
+ if (cMaxDepth > 16 && fExpandArgs)
+ {
+ fatal(NULL, _("Too many levels of nested function arguments expansions: %s"), pszFunction);
+ return -1; /* not reached */
+ }
+ if (!fExpandArgs || cDollars == 0)
+ kmk_cc_exp_emit_plain_function(ppBlockTail, pszFunction, pchStr, cchName,
+ cArgs, chOpen, chClose, pfnFunction, cMaxArgs);
+ else
+ {
+ rc = kmk_cc_exp_emit_dyn_function(ppBlockTail, pszFunction, pchStr, cchName,
+ cArgs, chOpen, chClose, pfnFunction, cMaxArgs);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ else
+ {
+ /*
+ * Variable, find the end while checking whether anything needs expanding.
+ */
+ if (ch == chClose)
+ cDepth = 0;
+ else if (cchName < cchStr)
+ {
+ if (ch != '$')
+ {
+ /* Second loop: Look for things that needs expanding. */
+ while (cchName < cchStr)
+ {
+ ch = pchStr[cchName];
+ if (ch == chClose)
+ {
+ if (!--cDepth)
+ break;
+ }
+ else if (ch == chOpen)
+ {
+ if (++cDepth > cMaxDepth)
+ cMaxDepth = cDepth;
+ }
+ else if (ch == '$')
+ break;
+ cchName++;
+ }
+ }
+ if (ch == '$')
+ {
+ /* Third loop: Something needs expanding, just find the end. */
+ cDollars = 1;
+ cchName++;
+ while (cchName < cchStr)
+ {
+ ch = pchStr[cchName];
+ if (ch == chClose)
+ {
+ if (!--cDepth)
+ break;
+ }
+ else if (ch == chOpen)
+ {
+ if (++cDepth > cMaxDepth)
+ cMaxDepth = cDepth;
+ }
+ cchName++;
+ }
+ }
+ }
+ if (cDepth > 0) /* After warning, we just assume they're all there. */
+ error(NULL, chOpen == '(' ? _("Missing closing parenthesis ") : _("Missing closing braces"));
+ if (cMaxDepth >= 16)
+ {
+ fatal(NULL, _("Too many levels of nested variable expansions: '%.*s'"), (int)cchName + 2, pchStr - 1);
+ return -1; /* not reached */
+ }
+ if (cDollars == 0)
+ kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, cchName);
+ else
+ {
+ rc = kmk_cc_exp_emit_dyn_variable(ppBlockTail, pchStr, cchName);
+ if (rc != 0)
+ return rc;
+ }
+ }
+ pchStr += cchName + 1;
+ cchStr -= cchName + (cDepth == 0);
+ }
+ else
+ {
+ /* Single character variable name. */
+ kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, 1);
+ pchStr++;
+ cchStr--;
+ }
+ }
+ else
+ {
+ error(NULL, _("Unexpected end of string after $"));
+ break;
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Nothing more to expand, the remainder is a simple string copy.
+ */
+ kmk_cc_exp_emit_copy_string(ppBlockTail, pchStr, cchStr);
+ break;
+ }
+ }
+
+ /*
+ * Emit final instruction.
+ */
+ kmk_cc_exp_emit_return(ppBlockTail);
+ return 0;
+}
+
+
+/**
+ * Initializes string expansion program statistics.
+ * @param pStats Pointer to the statistics structure to init.
+ */
+static void kmk_cc_exp_stats_init(PKMKCCEXPSTATS pStats)
+{
+ pStats->cchAvg = 0;
+}
+
+
+/**
+ * Compiles a string expansion subprogram.
+ *
+ * The caller typically make a call to kmk_cc_block_get_next_ptr after this
+ * function returns to figure out where to continue executing.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param ppBlockTail Pointer to the allocator tail pointer.
+ * @param pchStr Pointer to the string to compile an expansion
+ * program for (ASSUMED to be valid for the
+ * lifetime of the program).
+ * @param cchStr The length of the string to compile. Expected to
+ * be at least on char long.
+ * @param pSubprog The subprogram structure to initialize.
+ */
+static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubprog)
+{
+ KMK_CC_ASSERT(cchStr > 0);
+ pSubprog->pFirstInstr = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+ kmk_cc_exp_stats_init(&pSubprog->Stats);
+ return kmk_cc_exp_compile_common(ppBlockTail, pchStr, cchStr);
+}
+
+
+/**
+ * Compiles a string expansion program.
+ *
+ * @returns Pointer to the program on success, NULL on failure.
+ * @param pchStr Pointer to the string to compile an expansion
+ * program for (ASSUMED to be valid for the
+ * lifetime of the program).
+ * @param cchStr The length of the string to compile. Expected to
+ * be at least on char long.
+ */
+static PKMKCCEXPPROG kmk_cc_exp_compile(const char *pchStr, uint32_t cchStr)
+{
+ /*
+ * Estimate block size, allocate one and initialize it.
+ */
+ PKMKCCEXPPROG pProg;
+ PKMKCCBLOCK pBlock;
+ pProg = kmk_cc_block_alloc_first(&pBlock, sizeof(*pProg),
+ (kmk_cc_count_dollars(pchStr, cchStr) + 4) * 8);
+ if (pProg)
+ {
+ pProg->pBlockTail = pBlock;
+ pProg->pFirstInstr = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(pBlock);
+ kmk_cc_exp_stats_init(&pProg->Stats);
+ pProg->cRefs = 1;
+#ifdef KMK_CC_STRICT
+ pProg->uInputHash = kmk_cc_debug_string_hash_n(0, pchStr, cchStr);
+#endif
+
+ /*
+ * Join forces with the subprogram compilation code.
+ */
+ if (kmk_cc_exp_compile_common(&pProg->pBlockTail, pchStr, cchStr) == 0)
+ {
+#ifdef KMK_CC_WITH_STATS
+ pBlock = pProg->pBlockTail;
+ if (!pBlock->pNext)
+ g_cSingleBlockExpProgs++;
+ else if (!pBlock->pNext->pNext)
+ g_cTwoBlockExpProgs++;
+ else
+ g_cMultiBlockExpProgs++;
+ for (; pBlock; pBlock = pBlock->pNext)
+ {
+ g_cBlocksAllocatedExpProgs++;
+ g_cbAllocatedExpProgs += pBlock->cbBlock;
+ g_cbUnusedMemExpProgs += pBlock->cbBlock - pBlock->offNext;
+ }
+#endif
+ return pProg;
+ }
+ kmk_cc_block_free_list(pProg->pBlockTail);
+ }
+ return NULL;
+}
+
+
+/**
+ * Updates the recursive_without_dollar member of a variable structure.
+ *
+ * This avoid compiling string expansion programs with only a CopyString
+ * instruction. By setting recursive_without_dollar to 1, code calling
+ * kmk_cc_compile_variable_for_expand and kmk_exec_expand_to_var_buf will
+ * instead treat start treating it as a simple variable, which is faster.
+ *
+ * @returns The updated recursive_without_dollar value.
+ * @param pVar Pointer to the variable.
+ */
+static int kmk_cc_update_variable_recursive_without_dollar(struct variable *pVar)
+{
+ int fValue;
+ KMK_CC_ASSERT(pVar->recursive_without_dollar == 0);
+
+ if (memchr(pVar->value, '$', pVar->value_length))
+ fValue = -1;
+ else
+ fValue = 1;
+ pVar->recursive_without_dollar = fValue;
+
+ return fValue;
+}
+
+
+/**
+ * Compiles a variable for string expansion.
+ *
+ * @returns Pointer to the string expansion program on success, NULL if no
+ * program was created.
+ * @param pVar Pointer to the variable.
+ */
+struct kmk_cc_expandprog *kmk_cc_compile_variable_for_expand(struct variable *pVar)
+{
+ KMK_CC_ASSERT(strlen(pVar->value) == pVar->value_length);
+ KMK_CC_ASSERT(!pVar->expandprog);
+ KMK_CC_ASSERT(pVar->recursive_without_dollar <= 0);
+
+ if ( !pVar->expandprog
+ && pVar->recursive)
+ {
+ if ( pVar->recursive_without_dollar < 0
+ || ( pVar->recursive_without_dollar == 0
+ && kmk_cc_update_variable_recursive_without_dollar(pVar) < 0) )
+ {
+ pVar->expandprog = kmk_cc_exp_compile(pVar->value, pVar->value_length);
+ g_cVarForExpandCompilations++;
+ }
+ }
+ return pVar->expandprog;
+}
+
+
+/**
+ * String expansion execution worker for outputting a variable.
+ *
+ * @returns The new variable buffer position.
+ * @param pVar The variable to reference.
+ * @param pchDst The current variable buffer position.
+ */
+static char *kmk_exec_expand_worker_reference_variable(struct variable *pVar, char *pchDst)
+{
+ if (pVar->value_length > 0)
+ {
+ if (!pVar->recursive || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ pchDst = variable_buffer_output(pchDst, pVar->value, pVar->value_length);
+ else
+ pchDst = reference_recursive_variable(pchDst, pVar);
+ }
+ else if (pVar->append)
+ pchDst = reference_recursive_variable(pchDst, pVar);
+ return pchDst;
+}
+
+
+/**
+ * Executes a stream string expansion instructions, outputting to the current
+ * varaible buffer.
+ *
+ * @returns The new variable buffer position.
+ * @param pInstrCore The instruction to start executing at.
+ * @param pchDst The current variable buffer position.
+ */
+static char *kmk_exec_expand_instruction_stream_to_var_buf(PKMKCCEXPCORE pInstrCore, char *pchDst)
+{
+ for (;;)
+ {
+ switch (pInstrCore->enmOpcode)
+ {
+ case kKmkCcExpInstr_CopyString:
+ {
+ PKMKCCEXPCOPYSTRING pInstr = (PKMKCCEXPCOPYSTRING)pInstrCore;
+ pchDst = variable_buffer_output(pchDst, pInstr->pachSrc, pInstr->cchCopy);
+
+ pInstrCore = &(pInstr + 1)->Core;
+ break;
+ }
+
+ case kKmkCcExpInstr_PlainVariable:
+ {
+ PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)pInstrCore;
+ struct variable *pVar = lookup_variable_strcached(pInstr->pszName);
+ if (pVar)
+ pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
+ else
+ warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));
+
+ pInstrCore = &(pInstr + 1)->Core;
+ break;
+ }
+
+ case kKmkCcExpInstr_DynamicVariable:
+ {
+ PKMKCCEXPDYNVAR pInstr = (PKMKCCEXPDYNVAR)pInstrCore;
+ struct variable *pVar;
+ uint32_t cchName;
+ char *pszName = kmk_exec_expand_subprog_to_tmp(&pInstr->Subprog, &cchName);
+ char *pszColon = (char *)memchr(pszName, ':', cchName);
+ char *pszEqual;
+ if ( pszColon == NULL
+ || (pszEqual = (char *)memchr(pszColon + 1, '=', &pszName[cchName] - pszColon - 1)) == NULL
+ || pszEqual == pszColon + 1)
+ {
+ pVar = lookup_variable(pszName, cchName);
+ if (pVar)
+ pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
+ else
+ warn_undefined(pszName, cchName);
+ }
+ else if (pszColon != pszName)
+ {
+ /*
+ * Oh, we have to do search and replace. How tedious.
+ * Since the variable name is a temporary buffer, we can transform
+ * the strings into proper search and replacement patterns directly.
+ */
+ pVar = lookup_variable(pszName, pszColon - pszName);
+ if (pVar)
+ {
+ char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
+ char *pszSearchPat = pszColon + 1;
+ char *pszReplacePat = pszEqual + 1;
+ const char *pchPctSearchPat;
+ const char *pchPctReplacePat;
+
+ *pszEqual = '\0';
+ pchPctSearchPat = find_percent(pszSearchPat);
+ pchPctReplacePat = find_percent(pszReplacePat);
+
+ if (!pchPctReplacePat)
+ {
+ if (pszReplacePat[-2] != '\0') /* On the offchance that a pct was unquoted by find_percent. */
+ {
+ memmove(pszName + 1, pszSearchPat, pszReplacePat - pszSearchPat);
+ if (pchPctSearchPat)
+ pchPctSearchPat -= pszSearchPat - &pszName[1];
+ pszSearchPat = &pszName[1];
+ }
+ pchPctReplacePat = --pszReplacePat;
+ *pszReplacePat = '%';
+ }
+
+ if (!pchPctSearchPat)
+ {
+ pchPctSearchPat = --pszSearchPat;
+ *pszSearchPat = '%';
+ }
+
+ pchDst = patsubst_expand_pat(pchDst, pszExpandedVarValue,
+ pszSearchPat, pszReplacePat,
+ pchPctSearchPat, pchPctReplacePat);
+
+ if (pVar->recursive)
+ free((void *)pszExpandedVarValue);
+ }
+ else
+ warn_undefined(pszName, pszColon - pszName);
+ }
+ free(pszName);
+
+ pInstrCore = pInstr->pNext;
+ break;
+ }
+
+
+ case kKmkCcExpInstr_SearchAndReplacePlainVariable:
+ {
+ PKMKCCEXPSRPLAINVAR pInstr = (PKMKCCEXPSRPLAINVAR)pInstrCore;
+ struct variable *pVar = lookup_variable_strcached(pInstr->pszName);
+ if (pVar)
+ {
+ char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
+ pchDst = patsubst_expand_pat(pchDst,
+ pszExpandedVarValue,
+ pInstr->pszSearchPattern,
+ pInstr->pszReplacePattern,
+ &pInstr->pszSearchPattern[pInstr->offPctSearchPattern],
+ &pInstr->pszReplacePattern[pInstr->offPctReplacePattern]);
+ if (pVar->recursive)
+ free((void *)pszExpandedVarValue);
+ }
+ else
+ warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));
+
+ pInstrCore = pInstr->pNext;
+ break;
+ }
+
+ case kKmkCcExpInstr_PlainFunction:
+ {
+ PKMKCCEXPPLAINFUNC pInstr = (PKMKCCEXPPLAINFUNC)pInstrCore;
+ uint32_t iArg;
+ if (!pInstr->FnCore.fDirty)
+ {
+#ifdef KMK_CC_STRICT
+ uint32_t uCrcBefore = 0;
+ uint32_t uCrcAfter = 0;
+ iArg = pInstr->FnCore.cArgs;
+ while (iArg-- > 0)
+ uCrcBefore = kmk_cc_debug_string_hash(uCrcBefore, pInstr->apszArgs[iArg]);
+#endif
+
+ pchDst = pInstr->FnCore.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->FnCore.pszFuncName);
+
+#ifdef KMK_CC_STRICT
+ iArg = pInstr->FnCore.cArgs;
+ while (iArg-- > 0)
+ uCrcAfter = kmk_cc_debug_string_hash(uCrcAfter, pInstr->apszArgs[iArg]);
+ KMK_CC_ASSERT(uCrcBefore == uCrcAfter);
+#endif
+ }
+ else
+ {
+ char **papszShadowArgs = xmalloc((pInstr->FnCore.cArgs * 2 + 1) * sizeof(papszShadowArgs[0]));
+ char **papszArgs = &papszShadowArgs[pInstr->FnCore.cArgs];
+
+ iArg = pInstr->FnCore.cArgs;
+ papszArgs[iArg] = NULL;
+ while (iArg-- > 0)
+ papszArgs[iArg] = papszShadowArgs[iArg] = xstrdup(pInstr->apszArgs[iArg]);
+
+ pchDst = pInstr->FnCore.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->FnCore.pszFuncName);
+
+ iArg = pInstr->FnCore.cArgs;
+ while (iArg-- > 0)
+ free(papszShadowArgs[iArg]);
+ free(papszShadowArgs);
+ }
+
+ pInstrCore = pInstr->FnCore.pNext;
+ break;
+ }
+
+ case kKmkCcExpInstr_DynamicFunction:
+ {
+ PKMKCCEXPDYNFUNC pInstr = (PKMKCCEXPDYNFUNC)pInstrCore;
+ char **papszArgsShadow = xmalloc( (pInstr->FnCore.cArgs * 2 + 1) * sizeof(char *));
+ char **papszArgs = &papszArgsShadow[pInstr->FnCore.cArgs];
+ uint32_t iArg;
+
+ if (!pInstr->FnCore.fDirty)
+ {
+#ifdef KMK_CC_STRICT
+ uint32_t uCrcBefore = 0;
+ uint32_t uCrcAfter = 0;
+#endif
+ iArg = pInstr->FnCore.cArgs;
+ papszArgs[iArg] = NULL;
+ while (iArg-- > 0)
+ {
+ char *pszArg;
+ if (pInstr->aArgs[iArg].fSubprog)
+ pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.Subprog, NULL);
+ else
+ pszArg = (char *)pInstr->aArgs[iArg].u.Plain.psz;
+ papszArgsShadow[iArg] = pszArg;
+ papszArgs[iArg] = pszArg;
+#ifdef KMK_CC_STRICT
+ uCrcBefore = kmk_cc_debug_string_hash(uCrcBefore, pszArg);
+#endif
+ }
+ pchDst = pInstr->FnCore.pfnFunction(pchDst, papszArgs, pInstr->FnCore.pszFuncName);
+
+ iArg = pInstr->FnCore.cArgs;
+ while (iArg-- > 0)
+ {
+#ifdef KMK_CC_STRICT
+ KMK_CC_ASSERT(papszArgsShadow[iArg] == papszArgs[iArg]);
+ uCrcAfter = kmk_cc_debug_string_hash(uCrcAfter, papszArgsShadow[iArg]);
+#endif
+ if (pInstr->aArgs[iArg].fSubprog)
+ free(papszArgsShadow[iArg]);
+ }
+ KMK_CC_ASSERT(uCrcBefore == uCrcAfter);
+ }
+ else
+ {
+ iArg = pInstr->FnCore.cArgs;
+ papszArgs[iArg] = NULL;
+ while (iArg-- > 0)
+ {
+ char *pszArg;
+ if (pInstr->aArgs[iArg].fSubprog)
+ pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.Subprog, NULL);
+ else
+ pszArg = xstrdup(pInstr->aArgs[iArg].u.Plain.psz);
+ papszArgsShadow[iArg] = pszArg;
+ papszArgs[iArg] = pszArg;
+ }
+
+ pchDst = pInstr->FnCore.pfnFunction(pchDst, papszArgs, pInstr->FnCore.pszFuncName);
+
+ iArg = pInstr->FnCore.cArgs;
+ while (iArg-- > 0)
+ free(papszArgsShadow[iArg]);
+ }
+ free(papszArgsShadow);
+
+ pInstrCore = pInstr->FnCore.pNext;
+ break;
+ }
+
+ case kKmkCcExpInstr_Jump:
+ {
+ PKMKCCEXPJUMP pInstr = (PKMKCCEXPJUMP)pInstrCore;
+ pInstrCore = pInstr->pNext;
+ break;
+ }
+
+ case kKmkCcExpInstr_Return:
+ return pchDst;
+
+ default:
+ fatal(NULL, _("Unknown string expansion opcode: %d (%#x)"),
+ (int)pInstrCore->enmOpcode, (int)pInstrCore->enmOpcode);
+ return NULL;
+ }
+ }
+}
+
+
+/**
+ * Updates the string expansion statistics.
+ *
+ * @param pStats The statistics structure to update.
+ * @param cchResult The result lenght.
+ */
+void kmk_cc_exp_stats_update(PKMKCCEXPSTATS pStats, uint32_t cchResult)
+{
+ /*
+ * The average is simplified and not an exact average for every
+ * expansion that has taken place.
+ */
+ pStats->cchAvg = (pStats->cchAvg * 7 + cchResult) / 8;
+}
+
+
+/**
+ * Execute a string expansion subprogram, outputting to a new heap buffer.
+ *
+ * @returns Pointer to the output buffer (hand to free when done).
+ * @param pSubprog The subprogram to execute.
+ * @param pcchResult Where to return the size of the result. Optional.
+ */
+static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubprog, uint32_t *pcchResult)
+{
+ char *pchOldVarBuf;
+ unsigned int cbOldVarBuf;
+ char *pchDst;
+ char *pszResult;
+ uint32_t cchResult;
+
+ /*
+ * Temporarily replace the variable buffer while executing the instruction
+ * stream for this subprogram.
+ */
+ pchDst = install_variable_buffer_with_hint(&pchOldVarBuf, &cbOldVarBuf,
+ pSubprog->Stats.cchAvg ? pSubprog->Stats.cchAvg + 32 : 256);
+
+ pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pSubprog->pFirstInstr, pchDst);
+
+ /* Ensure that it's terminated. */
+ pchDst = variable_buffer_output(pchDst, "\0", 1) - 1;
+
+ /* Grab the result buffer before restoring the previous one. */
+ pszResult = variable_buffer;
+ cchResult = (uint32_t)(pchDst - pszResult);
+ if (pcchResult)
+ *pcchResult = cchResult;
+ kmk_cc_exp_stats_update(&pSubprog->Stats, cchResult);
+
+ variable_buffer = pchOldVarBuf;
+ variable_buffer_length = cbOldVarBuf;
+
+ return pszResult;
+}
+
+
+/**
+ * Execute a string expansion program, outputting to the current variable
+ * buffer.
+ *
+ * @returns New variable buffer position.
+ * @param pProg The program to execute.
+ * @param pchDst The current varaible buffer position.
+ */
+static char *kmk_exec_expand_prog_to_var_buf(PKMKCCEXPPROG pProg, char *pchDst)
+{
+ uint32_t cchResult;
+ uint32_t offStart = (uint32_t)(pchDst - variable_buffer);
+
+ if (pProg->Stats.cchAvg >= variable_buffer_length - offStart)
+ pchDst = ensure_variable_buffer_space(pchDst, offStart + pProg->Stats.cchAvg + 32);
+
+ KMK_CC_ASSERT(pProg->cRefs > 0);
+ pProg->cRefs++;
+
+ pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pProg->pFirstInstr, pchDst);
+
+ pProg->cRefs--;
+ KMK_CC_ASSERT(pProg->cRefs > 0);
+
+ cchResult = (uint32_t)(pchDst - variable_buffer);
+ KMK_CC_ASSERT(cchResult >= offStart);
+ cchResult -= offStart;
+ kmk_cc_exp_stats_update(&pProg->Stats, cchResult);
+ g_cVarForExpandExecs++;
+
+ return pchDst;
+}
+
+
+/**
+ * Expands a variable into a variable buffer using its expandprog.
+ *
+ * @returns The new variable buffer position.
+ * @param pVar Pointer to the variable. Must have a program.
+ * @param pchDst Pointer to the current variable buffer position.
+ */
+char *kmk_exec_expand_to_var_buf(struct variable *pVar, char *pchDst)
+{
+ KMK_CC_ASSERT(pVar->expandprog);
+ KMK_CC_ASSERT(pVar->expandprog->uInputHash == kmk_cc_debug_string_hash(0, pVar->value));
+ return kmk_exec_expand_prog_to_var_buf(pVar->expandprog, pchDst);
+}
+
+
+
+
+
+/*
+ *
+ * Makefile evaluation programs.
+ * Makefile evaluation programs.
+ * Makefile evaluation programs.
+ *
+ */
+
+static size_t kmk_cc_eval_detect_eol_style(char *pchFirst, char *pchSecond, const char *pszContent, size_t cchContent)
+{
+ /* Look for LF first. */
+ const char *pszTmp = (const char *)memchr(pszContent, '\n', cchContent);
+ if (pszTmp)
+ {
+ /* CRLF? */
+ if (pszTmp != pszContent && pszTmp[-1] == '\r')
+ {
+ *pchFirst = '\r';
+ *pchSecond = '\n';
+ return 2;
+ }
+
+ /* No, LF or LFCR. (pszContent is zero terminated, so no bounds checking necessary.) */
+ *pchFirst = '\n';
+ if (pszTmp[1] != '\r')
+ {
+ *pchSecond = 0;
+ return 1;
+ }
+ *pchSecond = '\r';
+ return 2;
+ }
+
+ /* Probably no EOLs here. */
+ if (memchr(pszContent, '\r', cchContent) == NULL)
+ {
+ *pchSecond = *pchFirst = 0;
+ return 0;
+ }
+
+ /* kind of unlikely */
+ *pchFirst = '\r';
+ *pchSecond = 0;
+ return 1;
+}
+
+
+#if 0
+/**
+ * Checks whether we've got an EOL escape sequence or not.
+ *
+ * @returns non-zero if escaped EOL, 0 if not (i.e. actual EOL).
+ * @param pszContent The string pointer @a offEol is relative to.
+ * @param offEol The offset of the first EOL char.
+ */
+static unsigned kmk_cc_eval_is_eol_escape_seq(const char *pszContent, size_t offEol)
+{
+ /* The caller has already checked out two backslashes. */
+ size_t offFirstBackslash = offEol;
+ KMK_CC_ASSERT(offFirstBackslash >= 2);
+ offFirstBackslash -= 2;
+
+ /* Find the first backslash. */
+ while (offFirstBackslash > 0 && pszContent[offFirstBackslash - 1] == '\\')
+ offFirstBackslash--;
+
+ /* Odd number -> escaped EOL; Even number -> real EOL; */
+ return (offEol - offFirstBackslash) & 1;
+}
+#endif
+
+
+
+/**
+ * Tokens (for KMKCCEVALWORD).
+ */
+typedef enum kmk_cc_eval_token
+{
+ /** Invalid token value 0. */
+ kKmkCcEvalToken_Invalid = 0,
+
+ /** Plain word. */
+ kKmkCcEvalToken_WordPlain,
+ /** Plain word with one or more escaped EOLs. (Currently not possible.) */
+ kKmkCcEvalToken_WordPlainWithEscEol,
+ /** Word that maybe in need of expanding. */
+ kKmkCcEvalToken_WordWithDollar,
+ /** Word that is in need of expanding and include one or more escped EOLs. */
+ kKmkCcEvalToken_WordWithDollarAndEscEol,
+
+ /** Recipe colon. */
+ kKmkCcEvalToken_colon,
+ /** Recipe double colon. */
+ kKmkCcEvalToken_double_colon,
+ /** Recipe multi target plus. */
+ kKmkCcEvalToken_plus,
+ /** Recipe multi target plus-maybe (+|). */
+ kKmkCcEvalToken_plus_maybe,
+ /** Recipe semicolon. */
+ kKmkCcEvalToken_semicolon,
+
+ /** End of valid token values (not included). */
+ kKmkCcEvalToken_End
+} KMKCCEVALTOKEN;
+
+/**
+ * A tokenized word.
+ */
+typedef struct kmk_cc_eval_word
+{
+ /** The token word (lexeme). */
+ const char *pchWord;
+ /** The length of the word (lexeme). */
+ uint32_t cchWord;
+ /** The token classification. */
+ KMKCCEVALTOKEN enmToken;
+} KMKCCEVALWORD;
+typedef KMKCCEVALWORD *PKMKCCEVALWORD;
+typedef KMKCCEVALWORD const *PCKMKCCEVALWORD;
+
+
+/**
+ * Escaped end-of-line sequence in the current line.
+ */
+typedef struct KMKCCEVALESCEOL
+{
+ /** Offset at which the EOL escape sequence starts for a non-command line. */
+ size_t offEsc;
+ /** Offset of the newline sequence. */
+ size_t offEol;
+} KMKCCEVALESCEOL;
+typedef KMKCCEVALESCEOL *PKMKCCEVALESCEOL;
+
+
+/**
+ * String copy segment.
+ */
+typedef struct KMKCCEVALSTRCPYSEG
+{
+ /** The start. */
+ const char *pchSrc;
+ /** The number of chars to copy and whether to prepend space.
+ * Negative values indicates that we should prepend a space. */
+ ssize_t cchSrcAndPrependSpace;
+} KMKCCEVALSTRCPYSEG;
+typedef KMKCCEVALSTRCPYSEG *PKMKCCEVALSTRCPYSEG;
+typedef KMKCCEVALSTRCPYSEG const *PCKMKCCEVALSTRCPYSEG;
+
+
+typedef struct KMKCCEVALCOMPILER
+{
+ /** Pointer to the KMKCCEVALPROG::pBlockTail member. */
+ PKMKCCBLOCK *ppBlockTail;
+
+ /** @name Line parsing state.
+ * @{ */
+ /** Offset of newline escape sequences in the current line.
+ * This is only applicable if cEscEols is not zero. */
+ PKMKCCEVALESCEOL paEscEols;
+ /** The number of number of paEscEols entries we've allocated. */
+ unsigned cEscEolsAllocated;
+ /** Number of escaped EOLs (line count - 1). */
+ unsigned cEscEols;
+ /** The paEscEols entry corresponding to the current parsing location.
+ * Still to be seen how accurate this can be made to be. */
+ unsigned iEscEol;
+
+ /** The current line number for error handling / debugging (1-based). */
+ unsigned iLine;
+ /** The start offset (into pchContent) of the current line. */
+ size_t offLine;
+ /** Length of the current line, sans the final EOL and comments. */
+ size_t cchLine;
+ /** Length of the current line, sans the final EOL but with comments. */
+ size_t cchLineWithComments;
+ /** For 'define' only, the start offset of the next line. Modified to the
+ * line following 'endef'. */
+ size_t offNext;
+
+ /** The first char in an EOL sequence.
+ * We ASSUMES that this char won't appear in any other sequence in the file,
+ * thus skipping matching any subsequent chars. */
+ char chFirstEol;
+ /** The second char in an EOL sequence, if applicable. */
+ char chSecondEol;
+
+ /** The length of the EOL sequence. */
+ size_t cchEolSeq;
+ /** The minimum length of an esacped EOL sequence (cchEolSeq + 1). */
+ size_t cchEscEolSeq;
+
+ /** String copy segments. */
+ PKMKCCEVALSTRCPYSEG paStrCopySegs;
+ /** The number of segments that has been prepared. */
+ unsigned cStrCopySegs;
+ /** The number of segments we've allocated. */
+ unsigned cStrCopySegsAllocated;
+ /** @} */
+
+
+ /** @name Recipe state.
+ * @{ */
+ /** Set if we're working on a recipe. */
+ PKMKCCEVALRECIPE pRecipe;
+ /** Set for ignoring recipes without targets (SunOS 4 Make). */
+ uint8_t fNoTargetRecipe;
+ /** The command prefix character. */
+ char chCmdPrefix;
+ /** @} */
+
+ /** @name Tokenzied words.
+ * @{ */
+ unsigned cWords;
+ unsigned cWordsAllocated;
+ PKMKCCEVALWORD paWords;
+ /** @} */
+
+ /** @name Conditionals.
+ * @{ */
+ /** Current conditional stack depth. */
+ unsigned cIfs;
+ /** The conditional directive stack. */
+ PKMKCCEVALIFCORE apIfs[KMK_CC_EVAL_MAX_IF_DEPTH];
+ /** @} */
+
+ /** The program being compiled. */
+ PKMKCCEVALPROG pEvalProg;
+ /** Pointer to the content. */
+ const char *pszContent;
+ /** The amount of input to parse. */
+ size_t cchContent;
+} KMKCCEVALCOMPILER;
+typedef KMKCCEVALCOMPILER *PKMKCCEVALCOMPILER;
+
+
+static void kmk_cc_eval_init_compiler(PKMKCCEVALCOMPILER pCompiler, PKMKCCEVALPROG pEvalProg, unsigned iLine,
+ const char *pszContent, size_t cchContent)
+{
+ pCompiler->ppBlockTail = &pEvalProg->pBlockTail;
+
+ pCompiler->pRecipe = NULL;
+ pCompiler->fNoTargetRecipe = 0;
+ pCompiler->chCmdPrefix = cmd_prefix;
+
+ pCompiler->cWordsAllocated = 0;
+ pCompiler->paWords = NULL;
+
+ pCompiler->cEscEolsAllocated = 0;
+ pCompiler->paEscEols = NULL;
+ pCompiler->iLine = iLine;
+
+ pCompiler->cStrCopySegsAllocated = 0;
+ pCompiler->paStrCopySegs = NULL;
+
+ pCompiler->cIfs = 0;
+
+ pCompiler->pEvalProg = pEvalProg;
+ pCompiler->pszContent = pszContent;
+ pCompiler->cchContent = cchContent;
+
+ /* Detect EOL style. */
+ pCompiler->cchEolSeq = kmk_cc_eval_detect_eol_style(&pCompiler->chFirstEol, &pCompiler->chSecondEol,
+ pszContent, cchContent);
+ pCompiler->cchEscEolSeq = 1 + pCompiler->cchEolSeq;
+}
+
+
+static void kmk_cc_eval_delete_compiler(PKMKCCEVALCOMPILER pCompiler)
+{
+ if (pCompiler->paWords)
+ free(pCompiler->paWords);
+ if (pCompiler->paEscEols)
+ free(pCompiler->paEscEols);
+}
+
+
+/**
+ * Translates a makefile source pointer to a line number and offset.
+ *
+ * @returns Line number (1-based)
+ * @param pCompiler The compiler state.
+ * @param pszWhere There location to translate.
+ * @param piColumn Where to return the line offset (1-based).
+ */
+static unsigned kmk_cc_eval_translate_location(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, unsigned *piColumn)
+{
+ unsigned iLine = pCompiler->iLine;
+ size_t offLine = pCompiler->offLine;
+ size_t off = pchWhere - pCompiler->pszContent;
+ unsigned i = 0;
+ while ( i < pCompiler->cEscEols
+ && off > pCompiler->paEscEols[i].offEol)
+ {
+ offLine = pCompiler->paEscEols[i].offEol + 1 + pCompiler->cchEolSeq;
+ iLine++;
+ i++;
+ }
+ KMK_CC_ASSERT(off <= pCompiler->cchContent);
+ if (piColumn)
+ *piColumn = (unsigned)(off - offLine) + 1;
+ return iLine;
+}
+
+
+static void KMK_CC_FN_NO_RETURN kmk_cc_eval_fatal(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, const char *pszMsg, ...)
+{
+ va_list va;
+ log_working_directory(1);
+
+ /*
+ * If we have a pointer location, use it to figure out the exact line and column.
+ */
+ if (pchWhere)
+ {
+ unsigned iColumn;
+ unsigned iLine = kmk_cc_eval_translate_location(pCompiler, pchWhere, &iColumn);
+
+ if (pCompiler->pEvalProg->pszVarName)
+ fprintf(stderr, "%s:%u:%u: *** fatal parsing error in %s: ",
+ pCompiler->pEvalProg->pszFilename, iLine, iColumn, pCompiler->pEvalProg->pszVarName);
+ else
+ fprintf(stderr, "%s:%u:%u: *** fatal parsing error: ",
+ pCompiler->pEvalProg->pszFilename, iLine, iColumn);
+ }
+ else if (pCompiler->pEvalProg->pszVarName)
+ fprintf(stderr, "%s:%u: *** fatal parsing error in %s: ",
+ pCompiler->pEvalProg->pszFilename, pCompiler->iLine, pCompiler->pEvalProg->pszVarName);
+ else
+ fprintf(stderr, "%s:%u: *** fatal parsing error: ",
+ pCompiler->pEvalProg->pszFilename, pCompiler->iLine);
+
+ /*
+ * Print the message and die.
+ */
+ va_start(va, pszMsg);
+ vfprintf(stderr, pszMsg, va);
+ va_end(va);
+ fputs(". Stop.\n", stderr);
+
+ for (;;)
+ die(2);
+}
+
+
+static KMK_CC_FN_NO_RETURN void
+kmk_cc_eval_fatal_eol(PKMKCCEVALCOMPILER pCompiler, const char *pchEol, unsigned iLine, size_t offLine)
+{
+ pCompiler->iLine = iLine;
+ pCompiler->offLine = offLine;
+
+ for (;;)
+ kmk_cc_eval_fatal(pCompiler, pchEol, "Missing 2nd EOL character: found %#x instead of %#x\n",
+ pchEol, pCompiler->chSecondEol);
+}
+
+
+static void kmk_cc_eval_warn(PKMKCCEVALCOMPILER pCompiler, const char *pchWhere, const char *pszMsg, ...)
+{
+ va_list va;
+
+ log_working_directory(1);
+
+ /*
+ * If we have a pointer location, use it to figure out the exact line and column.
+ */
+ if (pchWhere)
+ {
+ unsigned iColumn;
+ unsigned iLine = kmk_cc_eval_translate_location(pCompiler, pchWhere, &iColumn);
+
+ if (pCompiler->pEvalProg->pszVarName)
+ fprintf(stderr, "%s:%u:%u: *** warning in %s: ",
+ pCompiler->pEvalProg->pszFilename, iLine, iColumn, pCompiler->pEvalProg->pszVarName);
+ else
+ fprintf(stderr, "%s:%u:%u: *** warning: ",
+ pCompiler->pEvalProg->pszFilename, iLine, iColumn);
+ }
+ else if (pCompiler->pEvalProg->pszVarName)
+ fprintf(stderr, "%s:%u: *** warning in %s: ",
+ pCompiler->pEvalProg->pszFilename, pCompiler->iLine, pCompiler->pEvalProg->pszVarName);
+ else
+ fprintf(stderr, "%s:%u: *** warning: ",
+ pCompiler->pEvalProg->pszFilename, pCompiler->iLine);
+
+ /*
+ * Print the message.
+ */
+ va_start(va, pszMsg);
+ vfprintf(stderr, pszMsg, va);
+ va_end(va);
+ fputs(".\n", stderr);
+}
+
+
+/**
+ * Compiles a string expansion subprogram.
+ *
+ * @param pCompiler The compiler state.
+ * @param pszExpr The expression to compile.
+ * @param cchExpr The length of the expression.
+ * @param pSubprog The subprogram to compile.
+ */
+static void kmk_cc_eval_compile_string_exp_subprog(PKMKCCEVALCOMPILER pCompiler, const char *pszExpr, size_t cchExpr,
+ PKMKCCEXPSUBPROG pSubprog)
+{
+ int rc = kmk_cc_exp_compile_subprog(pCompiler->ppBlockTail, pszExpr, cchExpr, pSubprog);
+ if (rc == 0)
+ return;
+ kmk_cc_eval_fatal(pCompiler, NULL, "String expansion compile error");
+}
+
+
+/**
+ * Initializes a subprogam or plain operand structure.
+ *
+ * @param pCompiler The compiler state.
+ * @param pOperand The subprogram or plain structure to init.
+ * @param pszString The string.
+ * @param cchString The length of the string.
+ * @param fPlain Whether it's plain or not. If not, we'll compile it.
+ */
+static void kmk_cc_eval_init_subprogram_or_plain(PKMKCCEVALCOMPILER pCompiler, PKMKCCEXPSUBPROGORPLAIN pOperand,
+ const char *pszString, size_t cchString, int fPlain)
+{
+ pOperand->fPlainIsInVarStrCache = 0;
+ pOperand->bUser = 0;
+ pOperand->bUser2 = 0;
+ pOperand->fSubprog = fPlain;
+ if (fPlain)
+ {
+ pOperand->u.Plain.cch = cchString;
+ pOperand->u.Plain.psz = pszString;
+ }
+ else
+ kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszString, cchString, &pOperand->u.Subprog);
+}
+
+/**
+ * Initializes an array of subprogram-or-plain (spp) operands from a word array.
+ *
+ * The words will be duplicated and the caller must therefore call
+ * kmk_cc_block_realign() when done (it's not done here as the caller may
+ * initialize several string operands and we don't want any unnecessary
+ * fragmentation).
+ *
+ * @param pCompiler The compiler state.
+ * @param cWords The number of words to copy.
+ * @param paSrc The source words.
+ * @param paDst The destination subprogram-or-plain array.
+ */
+static void kmk_cc_eval_init_spp_array_from_duplicated_words(PKMKCCEVALCOMPILER pCompiler, unsigned cWords,
+ PKMKCCEVALWORD paSrc, PKMKCCEXPSUBPROGORPLAIN paDst)
+{
+ unsigned i;
+ for (i = 0; i < cWords; i++)
+ {
+ const char *pszCopy = kmk_cc_block_strdup(pCompiler->ppBlockTail, paSrc[i].pchWord, paSrc[i].cchWord);
+ paDst[i].fPlainIsInVarStrCache = 0;
+ paDst[i].bUser = 0;
+ paDst[i].bUser2 = 0;
+ if (paSrc[i].enmToken == kKmkCcEvalToken_WordWithDollar)
+ {
+ paDst[i].fSubprog = 1;
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, paSrc[i].cchWord, &paDst[i].u.Subprog);
+ }
+ else
+ {
+ paDst[i].fSubprog = 0;
+ paDst[i].u.Plain.cch = paSrc[i].cchWord;
+ paDst[i].u.Plain.psz = pszCopy;
+ }
+ KMK_CC_EVAL_DPRINTF((" %s\n", pszCopy));
+ }
+}
+
+
+
+/** @name KMK_CC_WORD_COMP_CONST_XXX - Optimal(/insane) constant work matching.
+ * @{
+ */
+#if (defined(KBUILD_ARCH_X86) || defined(KBUILD_ARCH_AMD64)) /* Unaligned access is reasonably cheap. */ \
+ && !defined(GCC_ADDRESS_SANITIZER)
+# define KMK_CC_WORD_COMP_CONST_2(a_pchLine, a_pszWord) \
+ ( *(uint16_t const *)(a_pchLine) == *(uint16_t const *)(a_pszWord) )
+# define KMK_CC_WORD_COMP_CONST_3(a_pchLine, a_pszWord) \
+ ( *(uint16_t const *)(a_pchLine) == *(uint16_t const *)(a_pszWord) \
+ && (a_pchLine)[2] == (a_pszWord)[2] )
+# define KMK_CC_WORD_COMP_CONST_4(a_pchLine, a_pszWord) \
+ ( *(uint32_t const *)(a_pchLine) == *(uint32_t const *)(a_pszWord) )
+# define KMK_CC_WORD_COMP_CONST_5(a_pchLine, a_pszWord) \
+ ( *(uint32_t const *)(a_pchLine) == *(uint32_t const *)(a_pszWord) \
+ && (a_pchLine)[4] == (a_pszWord)[4] )
+# define KMK_CC_WORD_COMP_CONST_6(a_pchLine, a_pszWord) \
+ ( *(uint32_t const *)(a_pchLine) == *(uint32_t const *)(a_pszWord) \
+ && ((uint16_t const *)(a_pchLine))[2] == ((uint16_t const *)(a_pszWord))[2] )
+# define KMK_CC_WORD_COMP_CONST_7(a_pchLine, a_pszWord) \
+ ( *(uint32_t const *)(a_pchLine) == *(uint32_t const *)(a_pszWord) \
+ && ((uint16_t const *)(a_pchLine))[2] == ((uint16_t const *)(a_pszWord))[2] \
+ && (a_pchLine)[6] == (a_pszWord)[6] )
+# define KMK_CC_WORD_COMP_CONST_8(a_pchLine, a_pszWord) \
+ ( *(uint64_t const *)(a_pchLine) == *(uint64_t const *)(a_pszWord) )
+# define KMK_CC_WORD_COMP_CONST_10(a_pchLine, a_pszWord) \
+ ( *(uint64_t const *)(a_pchLine) == *(uint64_t const *)(a_pszWord) \
+ && ((uint16_t const *)(a_pchLine))[4] == ((uint16_t const *)(a_pszWord))[4] )
+# define KMK_CC_WORD_COMP_CONST_16(a_pchLine, a_pszWord) \
+ ( *(uint64_t const *)(a_pchLine) == *(uint64_t const *)(a_pszWord) \
+ && ((uint64_t const *)(a_pchLine))[1] == ((uint64_t const *)(a_pszWord))[1] )
+#else
+# define KMK_CC_WORD_COMP_CONST_2(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] )
+# define KMK_CC_WORD_COMP_CONST_3(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] )
+# define KMK_CC_WORD_COMP_CONST_4(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] )
+# define KMK_CC_WORD_COMP_CONST_5(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] )
+# define KMK_CC_WORD_COMP_CONST_6(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] \
+ && (a_pchLine)[5] == (a_pszWord)[5] )
+# define KMK_CC_WORD_COMP_CONST_7(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] \
+ && (a_pchLine)[5] == (a_pszWord)[5] \
+ && (a_pchLine)[6] == (a_pszWord)[6] )
+# define KMK_CC_WORD_COMP_CONST_8(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] \
+ && (a_pchLine)[5] == (a_pszWord)[5] \
+ && (a_pchLine)[6] == (a_pszWord)[6] \
+ && (a_pchLine)[7] == (a_pszWord)[7] )
+# define KMK_CC_WORD_COMP_CONST_10(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] \
+ && (a_pchLine)[5] == (a_pszWord)[5] \
+ && (a_pchLine)[6] == (a_pszWord)[6] \
+ && (a_pchLine)[7] == (a_pszWord)[7] \
+ && (a_pchLine)[8] == (a_pszWord)[8] \
+ && (a_pchLine)[9] == (a_pszWord)[9] )
+# define KMK_CC_WORD_COMP_CONST_16(a_pchLine, a_pszWord) \
+ ( (a_pchLine)[0] == (a_pszWord)[0] \
+ && (a_pchLine)[1] == (a_pszWord)[1] \
+ && (a_pchLine)[2] == (a_pszWord)[2] \
+ && (a_pchLine)[3] == (a_pszWord)[3] \
+ && (a_pchLine)[4] == (a_pszWord)[4] \
+ && (a_pchLine)[5] == (a_pszWord)[5] \
+ && (a_pchLine)[6] == (a_pszWord)[6] \
+ && (a_pchLine)[7] == (a_pszWord)[7] \
+ && (a_pchLine)[8] == (a_pszWord)[8] \
+ && (a_pchLine)[9] == (a_pszWord)[9] \
+ && (a_pchLine)[10] == (a_pszWord)[10] \
+ && (a_pchLine)[11] == (a_pszWord)[11] \
+ && (a_pchLine)[12] == (a_pszWord)[12] \
+ && (a_pchLine)[13] == (a_pszWord)[13] \
+ && (a_pchLine)[14] == (a_pszWord)[14] \
+ && (a_pchLine)[15] == (a_pszWord)[15])
+#endif
+
+/** See if the given string match a constant string. */
+#define KMK_CC_STRCMP_CONST(a_pchLeft, a_cchLeft, a_pszConst, a_cchConst) \
+ ( (a_cchLeft) == (a_cchConst) \
+ && KMK_CC_WORD_COMP_CONST_##a_cchConst(a_pchLeft, a_pszConst) )
+
+/** See if a starting of a given length starts with a constant word. */
+#define KMK_CC_EVAL_WORD_COMP_IS_EOL(a_pCompiler, a_pchLine, a_cchLine) \
+ ( (a_cchLine) == 0 \
+ || KMK_CC_EVAL_IS_SPACE((a_pchLine)[0]) \
+ || ((a_pchLine)[0] == '\\' && (a_pchLine)[1] == (a_pCompiler)->chFirstEol) ) \
+
+/** See if a starting of a given length starts with a constant word. */
+#define KMK_CC_EVAL_WORD_COMP_CONST(a_pCompiler, a_pchLine, a_cchLine, a_pszWord, a_cchWord) \
+ ( (a_cchLine) >= (a_cchWord) \
+ && ( (a_cchLine) == (a_cchWord) \
+ || KMK_CC_EVAL_IS_SPACE((a_pchLine)[a_cchWord]) \
+ || ((a_pchLine)[a_cchWord] == '\\' && (a_pchLine)[(a_cchWord) + 1] == (a_pCompiler)->chFirstEol) ) \
+ && KMK_CC_WORD_COMP_CONST_##a_cchWord(a_pchLine, a_pszWord) )
+/** @} */
+
+
+/**
+ * Checks if a_ch is a space after a word.
+ *
+ * Since there is always a terminating zero, the user can safely access a char
+ * beyond @a a_cchLeft. However, that byte isn't necessarily a zero terminator
+ * character, so we have to check @a a_cchLeft whether we're at the end of the
+ * parsing input string.
+ *
+ * @returns true / false.
+ * @param a_pCompiler The compiler instance data.
+ * @param a_ch The character to inspect.
+ * @param a_ch2 The character following it, in case of escaped EOL.
+ * @param a_cchLeft The number of chars left to parse (from @a a_ch).
+ */
+#define KMK_CC_EVAL_IS_SPACE_AFTER_WORD(a_pCompiler, a_ch, a_ch2, a_cchLeft) \
+ ( a_cchLeft == 0 \
+ || KMK_CC_EVAL_IS_SPACE(a_ch) \
+ || ((a_ch) == '\\' && (a_ch2) == (a_pCompiler)->chFirstEol) )
+
+
+/**
+ * Common path for space skipping worker functions when escaped EOLs may be
+ * involed.
+ *
+ * @returns Points to the first non-space character or end of input.
+ * @param pchWord The current position. There is some kind of char
+ * @param cchLeft The current number of chars left to parse in the
+ * current line.
+ * @param pcchLeft Where to store the updated @a cchLeft value.
+ * @param pCompiler The compiler instance data.
+ */
+static const char *kmk_cc_eval_skip_spaces_with_esc_eol(const char *pchWord, size_t cchLeft, size_t *pcchLeft,
+ PKMKCCEVALCOMPILER pCompiler)
+{
+ /*
+ * Skip further spaces. We unrolls 4 loops here.
+ * ASSUMES cchEscEolSeq is either 2 or 3!
+ */
+ KMK_CC_ASSERT(pCompiler->cchEscEolSeq == 2 || pCompiler->cchEscEolSeq == 3);
+ KMK_CC_ASSERT(pCompiler->iEscEol < pCompiler->cEscEols);
+ while (cchLeft >= 4)
+ {
+ /* First char. */
+ char ch = pchWord[0];
+ if (KMK_CC_EVAL_IS_SPACE(ch))
+ { /* maybe likely */ }
+ else if ( ch == '\\'
+ && pchWord[1] == pCompiler->chFirstEol)
+ {
+ pchWord += pCompiler->cchEscEolSeq;
+ cchLeft -= pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+ continue;
+ }
+ else
+ {
+ *pcchLeft = cchLeft;
+ return pchWord;
+ }
+
+ /* Second char. */
+ ch = pchWord[1];
+ if (KMK_CC_EVAL_IS_SPACE(ch))
+ { /* maybe likely */ }
+ else if ( ch == '\\'
+ && pchWord[2] == pCompiler->chFirstEol)
+ {
+ pchWord += 1 + pCompiler->cchEscEolSeq;
+ cchLeft -= 1 + pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+ continue;
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 1;
+ return pchWord + 1;
+ }
+
+ /* Third char. */
+ ch = pchWord[2];
+ if (KMK_CC_EVAL_IS_SPACE(ch))
+ { /* maybe likely */ }
+ else if ( ch == '\\'
+ && pchWord[3] == pCompiler->chFirstEol
+ && cchLeft >= 2 + pCompiler->cchEscEolSeq)
+ {
+ pchWord += 2 + pCompiler->cchEscEolSeq;
+ cchLeft -= 2 + pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+ continue;
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 2;
+ return pchWord + 2;
+ }
+
+ /* Third char. */
+ ch = pchWord[3];
+ if (KMK_CC_EVAL_IS_SPACE(ch))
+ {
+ pchWord += 4;
+ cchLeft -= 4;
+ }
+ else if ( ch == '\\'
+ && cchLeft >= 3 + pCompiler->cchEscEolSeq
+ && pchWord[4] == pCompiler->chFirstEol)
+ {
+ pchWord += 3 + pCompiler->cchEscEolSeq;
+ cchLeft -= 3 + pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 3;
+ return pchWord + 3;
+ }
+ }
+
+ /*
+ * Simple loop for the final three chars.
+ */
+ while (cchLeft > 0)
+ {
+ /* First char. */
+ char ch = *pchWord;
+ if (KMK_CC_EVAL_IS_SPACE(ch))
+ {
+ pchWord += 1;
+ cchLeft -= 1;
+ }
+ else if ( ch == '\\'
+ && cchLeft > pCompiler->cchEolSeq
+ && pchWord[1] == pCompiler->chFirstEol)
+ {
+ pchWord += pCompiler->cchEscEolSeq;
+ cchLeft -= pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+ }
+ else
+ break;
+ }
+
+ *pcchLeft = cchLeft;
+ return pchWord;
+}
+
+
+/**
+ * Common path for space skipping worker functions when no escaped EOLs need
+ * considering.
+ *
+ * @returns Points to the first non-space character or end of input.
+ * @param pchWord The current position. There is some kind of char
+ * @param cchLeft The current number of chars left to parse in the
+ * current line.
+ * @param pcchLeft Where to store the updated @a cchLeft value.
+ * @param pCompiler The compiler instance data.
+ */
+static const char *kmk_cc_eval_skip_spaces_without_esc_eol(const char *pchWord, size_t cchLeft, size_t *pcchLeft,
+ PKMKCCEVALCOMPILER pCompiler)
+{
+ /*
+ * 4x loop unroll.
+ */
+ while (cchLeft >= 4)
+ {
+ if (KMK_CC_EVAL_IS_SPACE(pchWord[0]))
+ {
+ if (KMK_CC_EVAL_IS_SPACE(pchWord[1]))
+ {
+ if (KMK_CC_EVAL_IS_SPACE(pchWord[2]))
+ {
+ if (KMK_CC_EVAL_IS_SPACE(pchWord[3]))
+ {
+ pchWord += 4;
+ cchLeft -= 4;
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 3;
+ return pchWord + 3;
+ }
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 2;
+ return pchWord + 2;
+ }
+ }
+ else
+ {
+ *pcchLeft = cchLeft - 1;
+ return pchWord + 1;
+ }
+ }
+ else
+ {
+ *pcchLeft = cchLeft;
+ return pchWord;
+ }
+ }
+
+ /*
+ * The last 3. Not entirely sure if this yield good code.
+ */
+ switch (cchLeft & 3)
+ {
+ case 3:
+ if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
+ break;
+ pchWord++;
+ cchLeft--;
+ case 2:
+ if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
+ break;
+ pchWord++;
+ cchLeft--;
+ case 1:
+ if (!KMK_CC_EVAL_IS_SPACE(*pchWord))
+ break;
+ pchWord++;
+ cchLeft--;
+ case 0:
+ break;
+ }
+
+ *pcchLeft = cchLeft;
+ return pchWord;
+}
+
+
+/**
+ * Used to skip spaces after a word.
+ *
+ * We ASSUME that the first char is a space or that we've reached the end of the
+ * string (a_cchLeft == 0).
+ *
+ * @param a_pCompiler The compiler instance data.
+ * @param a_pchWord The current input position, this will be moved to
+ * the start of the next word or end of the input.
+ * @param a_cchLeft The number of chars left to parse. This will be
+ * updated.
+ */
+#define KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(a_pCompiler, a_pchWord, a_cchLeft) \
+ do { \
+ /* Skip the first char which is known to be a space, end of line or end of input. */ \
+ if ((a_cchLeft) > 0) \
+ { \
+ char const chSkipBlanksFirst = *(a_pchWord); \
+ KMK_CC_ASSERT(KMK_CC_EVAL_IS_SPACE_AFTER_WORD(a_pCompiler, chSkipBlanksFirst, (a_pchWord)[1], a_cchLeft)); \
+ if (chSkipBlanksFirst != '\\') \
+ { \
+ (a_pchWord) += 1; \
+ (a_cchLeft) -= 1; \
+ \
+ /* Another space or escaped EOL? Then there are probably more then, so call worker function. */ \
+ if ((a_cchLeft) > 0) \
+ { \
+ char const chSkipBlanksSecond = *(a_pchWord); \
+ if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipBlanksSecond)) \
+ (a_pchWord) = kmk_cc_eval_skip_spaces_after_word_slow(a_pchWord, &(a_cchLeft), \
+ chSkipBlanksSecond, a_pCompiler); \
+ } \
+ } \
+ else /* escape sequences can be complicated. */ \
+ (a_pchWord) = kmk_cc_eval_skip_spaces_after_word_slow(a_pchWord, &(a_cchLeft), \
+ chSkipBlanksFirst, a_pCompiler); \
+ } \
+ } while (0)
+
+/**
+ * The slow path of KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD.
+ *
+ * This is called to handles escaped EOL sequences, as these can involve
+ * multiple backslashes and therefore doesn't led themselves well to inlined
+ * code.
+ *
+ * The other case this is used for is to handle more than once space, since it's
+ * likely that when there are two there might be more. No point in inlining
+ * that, better do some loop unrolling instead.
+ *
+ * @returns Points to the first non-space character or end of input.
+ * @param pchWord The current position. There is some kind of char
+ * @param pcchLeft Pointer to the cchLeft variable, this is both
+ * input and output.
+ * @param ch The current character.
+ * @param pCompiler The compiler instance data.
+ */
+static const char *kmk_cc_eval_skip_spaces_after_word_slow(const char *pchWord, size_t *pcchLeft, char ch,
+ PKMKCCEVALCOMPILER pCompiler)
+{
+ size_t cchLeft = *pcchLeft;
+
+ /*
+ * It's all very simple when we don't have to consider escaped EOLs.
+ */
+ if (pCompiler->iEscEol >= pCompiler->cEscEols)
+ {
+ if (ch != '\\')
+ {
+ pchWord += 1;
+ cchLeft -= 1;
+ }
+ else
+ return pchWord;
+ return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+ }
+
+ /*
+ * Skip the pending space or EOL found by the caller. We need to
+ * confirm the EOL.
+ *
+ * Note! We only need to care about simple backslash+EOL sequences here
+ * since we're either at the end of a validated word, or we've already
+ * skipped one space. In the former case, someone else has already
+ * validated the escape esequence, in the latter case multiple
+ * backslashes would indicate a new word that that we should return.
+ */
+ if (ch != '\\')
+ {
+ pchWord += 1;
+ cchLeft -= 1;
+ }
+ else if ( cchLeft >= pCompiler->cchEscEolSeq
+ && pchWord[1] == pCompiler->chFirstEol)
+ {
+ KMK_CC_ASSERT(pCompiler->cchEolSeq == 1 || pchWord[2] == pCompiler->chSecondEol);
+ pchWord += pCompiler->cchEscEolSeq;
+ cchLeft -= pCompiler->cchEscEolSeq;
+ pCompiler->iEscEol++;
+
+ if (pCompiler->iEscEol < pCompiler->cEscEols)
+ { /* likely */ }
+ else return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+ }
+ else
+ return pchWord;
+ return kmk_cc_eval_skip_spaces_with_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+}
+
+
+/**
+ * Skip zero or more spaces.
+ *
+ * This macro deals with a single space, if there are more or we're hittin some
+ * possible escaped EOL sequence, work is deferred to a worker function.
+ *
+ * @param a_pCompiler The compiler state.
+ * @param a_pchWord The current input position. Advanced past spaces.
+ * @param a_cchLeft The amount of input left to parse. Will be updated.
+ */
+#define KMK_CC_EVAL_SKIP_SPACES(a_pCompiler, a_pchWord, a_cchLeft) \
+ do { \
+ if ((a_cchLeft) > 0) \
+ { \
+ char chSkipSpaces = *(a_pchWord); \
+ if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipSpaces)) \
+ { \
+ if (chSkipSpaces != '\\') \
+ { \
+ (a_pchWord) += 1; \
+ (a_cchLeft) -= 1; \
+ chSkipSpaces = *(a_pchWord); \
+ if (KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(chSkipSpaces)) \
+ (a_pchWord) = kmk_cc_eval_skip_spaces_slow(a_pchWord, &(a_cchLeft), chSkipSpaces, a_pCompiler); \
+ } \
+ else \
+ (a_pchWord) = kmk_cc_eval_skip_spaces_slow(a_pchWord, &(a_cchLeft), chSkipSpaces, a_pCompiler); \
+ } \
+ } \
+ } while (0)
+
+
+/**
+ * Worker for KMK_CC_EVAL_SKIP_SPACES.
+ *
+ * @returns Points to the first non-space character or end of input.
+ * @param pchWord The current position. There is some kind of char
+ * @param pcchLeft Pointer to the cchLeft variable, this is both
+ * input and output.
+ * @param ch The current character.
+ * @param pCompiler The compiler instance data.
+ */
+static const char *kmk_cc_eval_skip_spaces_slow(const char *pchWord, size_t *pcchLeft, char ch, PKMKCCEVALCOMPILER pCompiler)
+{
+ size_t cchLeft = *pcchLeft;
+#ifdef KMK_CC_STRICT
+ size_t offWordCcStrict = pchWord - pCompiler->pszContent;
+#endif
+ KMK_CC_ASSERT(cchLeft > 0);
+ KMK_CC_ASSERT(cchLeft <= pCompiler->cchLine);
+ KMK_CC_ASSERT(*pchWord == ch);
+ KMK_CC_ASSERT(KMK_CC_EVAL_IS_SPACE_OR_BACKSLASH(ch));
+ KMK_CC_ASSERT(offWordCcStrict >= pCompiler->offLine);
+ KMK_CC_ASSERT(offWordCcStrict < pCompiler->offLine + pCompiler->cchLine);
+ KMK_CC_ASSERT( pCompiler->iEscEol >= pCompiler->cEscEols
+ || offWordCcStrict <= pCompiler->paEscEols[pCompiler->iEscEol].offEsc);
+ KMK_CC_ASSERT( pCompiler->iEscEol >= pCompiler->cEscEols
+ || pCompiler->iEscEol == 0
+ || offWordCcStrict >= pCompiler->paEscEols[pCompiler->iEscEol - 1].offEol + pCompiler->cchEolSeq);
+
+ /*
+ * If we don't need to consider escaped EOLs, things are much much simpler.
+ */
+ if (pCompiler->iEscEol >= pCompiler->cEscEols)
+ {
+ if (ch != '\\')
+ {
+ pchWord++;
+ cchLeft--;
+ }
+ else
+ return pchWord;
+ return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+ }
+
+ /*
+ * Possible escaped EOL complications.
+ */
+ if (ch != '\\')
+ {
+ pchWord++;
+ cchLeft--;
+ }
+ else
+ {
+ size_t cchSkip;
+ size_t offWord;
+ unsigned iEscEol = pCompiler->iEscEol;
+ if (iEscEol >= pCompiler->cEscEols)
+ return pchWord;
+
+ offWord = pchWord - pCompiler->pszContent;
+ if (offWord < pCompiler->paEscEols[iEscEol].offEsc)
+ return pchWord;
+ KMK_CC_ASSERT(offWord == pCompiler->paEscEols[iEscEol].offEsc);
+
+ cchSkip = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq - offWord;
+ pchWord += cchSkip;
+ cchLeft -= cchSkip;
+ pCompiler->iEscEol = ++iEscEol;
+
+ if (iEscEol < pCompiler->cEscEols)
+ { /* likely */ }
+ else return kmk_cc_eval_skip_spaces_without_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+ }
+ return kmk_cc_eval_skip_spaces_with_esc_eol(pchWord, cchLeft, pcchLeft, pCompiler);
+}
+
+
+#if 0 /* unused - probably forever. */
+/**
+ * Skips to the end of a variable name.
+ *
+ * This may advance pCompiler->iEscEol.
+ *
+ * @returns Pointer to the first char after the variable name.
+ * @param pCompiler The compiler state.
+ * @param pchWord The current position. Must be at the start of the
+ * variable name.
+ * @param cchLeft The number of chars left to parse in the current line.
+ * @param pcchLeft The to store the updated count of characters left to
+ * parse.
+ * @param pfPlain Where to store the plain variable name indicator.
+ * Returns 0 if plain, and 1 if there are variable
+ * references in it.
+ */
+static const char *kmk_cc_eval_skip_var_name(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
+ size_t *pcchLeft, int *pfPlain)
+{
+ const char * const pszContent = pCompiler->pszContent;
+ size_t off = pchWord - pszContent;
+ size_t const offLineEnd = off + cchLeft;
+ int fPlain = 1;
+ unsigned iEscEol = pCompiler->iEscEol;
+
+ /* Check our expectations. */
+ KMK_CC_ASSERT(cchLeft);
+ KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));
+ KMK_CC_ASSERT(iEscEol <= pCompiler->cEscEols);
+ KMK_CC_ASSERT( iEscEol >= pCompiler->cEscEols
+ || off < pCompiler->paEscEols[iEscEol].offEol);
+ KMK_CC_ASSERT(off >= (iEscEol == 0 ? pCompiler->offLine : pCompiler->paEscEols[iEscEol - 1].offEol + pCompiler->cchEolSeq));
+
+ /*
+ * The outer loop parses plain text. Variable expansion ($) is handled
+ * by the inner loop.
+ */
+ while (off < offLineEnd)
+ {
+ char ch = pszContent[off];
+ if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
+ off++;
+ else if (KMK_CC_EVAL_IS_SPACE(ch))
+ break;
+ else if (ch == '$')
+ {
+ off++;
+ if (off < offLineEnd)
+ {
+ char const chOpen = pszContent[off];
+ if (chOpen == '(' || chOpen == '{')
+ {
+ /*
+ * Got a $(VAR) or ${VAR} to deal with here. This may
+ * include nested variable references and span multiple
+ * lines (at least for function calls).
+ *
+ * We scan forward till we've found the corresponding
+ * closing parenthesis, considering any open parentheses
+ * of the same kind as worth counting, even if there are
+ * no dollar preceeding them, just like GNU make does.
+ */
+ size_t const offStart = off - 1;
+ char const chClose = chOpen == '(' ? ')' : '}';
+ unsigned cOpen = 1;
+ off++;
+ for (;;)
+ {
+ if (off < offLineEnd)
+ {
+ ch = pszContent[off];
+ if (!(KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch)))
+ off++;
+ else
+ {
+ off++;
+ if (ch == chClose)
+ {
+ if (--cOpen == 0)
+ break;
+ }
+ else if (ch == chOpen)
+ cOpen++;
+ else if ( ch == '\\'
+ && iEscEol < pCompiler->cEscEols
+ && off == pCompiler->paEscEols[iEscEol].offEsc)
+ {
+ off = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
+ pCompiler->iEscEol = ++iEscEol;
+ }
+ }
+ }
+ else if (cOpen == 1)
+ kmk_cc_eval_fatal(pCompiler, &pszContent[offStart],
+ "Variable reference is missing '%c'", chClose);
+ else
+ kmk_cc_eval_fatal(pCompiler, &pszContent[offStart],
+ "%u variable references are missing '%c'", cOpen, chClose);
+ }
+ }
+ /* Single char variable name. */
+ else if (!KMK_CC_EVAL_IS_SPACE(chOpen))
+ { /* likely */ }
+ else
+ kmk_cc_eval_fatal(pCompiler, &pszContent[off], "Expected variable name after '$', not end of line");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, &pszContent[off], "Expected variable name after '$', not end of line");
+ fPlain = 0;
+ }
+ /* Deal with potential escaped EOL. */
+ else if ( ch != '\\'
+ || iEscEol >= pCompiler->cEscEols
+ || off != pCompiler->paEscEols[iEscEol].offEsc )
+ off++;
+ else
+ break;
+ }
+
+ *pcchLeft = offLineEnd - off;
+ *pfPlain = fPlain;
+ return &pszContent[off];
+}
+#endif /* unused */
+
+
+#if 0 /* unused atm */
+/**
+ * Prepares for copying a command line.
+ *
+ * The current version of this code will not modify any of the paEscEols
+ * entries, unlike our kmk_cc_eval_prep_normal_line sibling function.
+ *
+ * @returns The number of chars that will be copied by
+ * kmk_cc_eval_copy_prepped_command_line().
+ * @param pCompiler The compiler instance data.
+ * @param pchLeft Pointer to the first char to copy from the current line.
+ * This does not have to the start of a word.
+ * @param cchLeft The number of chars left on the current line starting at
+ * @a pchLeft.
+ */
+static size_t kmk_cc_eval_prep_command_line(PKMKCCEVALCOMPILER pCompiler, const char * const pchLeft, size_t cchLeft)
+{
+ size_t cchRet;
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned const cEscEols = pCompiler->cEscEols;
+
+ KMK_CC_ASSERT(cchLeft > 0);
+ KMK_CC_ASSERT(iEscEol <= cEscEols);
+
+ if (iEscEol >= cEscEols)
+ {
+ /*
+ * No escaped EOLs left, dead simple.
+ */
+ cchRet = cchLeft;
+ }
+ else
+ {
+ /*
+ * Compared to the normal prepping of a line, this is actually
+ * really simple. We need to account for two kind of conversions:
+ * - One leading tab is skipped after escaped EOL.
+ * - Convert EOL to LF.
+ */
+ const char * const pszContent = pCompiler->pszContent;
+ size_t const cchEolSeq = pCompiler->cchEolSeq;
+
+#ifdef KMK_CC_STRICT
+ size_t const offLeft = pchLeft - pszContent;
+ KMK_CC_ASSERT(offLeft + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
+ KMK_CC_ASSERT(offLeft + cchLeft <= pCompiler->cchContent);
+ KMK_CC_ASSERT(offLeft < pCompiler->paEscEols[iEscEol].offEsc);
+ KMK_CC_ASSERT(offLeft >= (iEscEol ? pCompiler->paEscEols[cEscEols - 1].offEol + pCompiler->cchEolSeq : pCompiler->offLine));
+#endif
+
+ cchRet = cchLeft;
+ if (cchEolSeq > 1)
+ cchRet -= (cchEolSeq - 1) * cEscEols;
+ do
+ {
+ if (pszContent[pCompiler->paEscEols[cchEolSeq].offEol])
+ cchRet--;
+ iEscEol++;
+ } while (iEscEol < cEscEols);
+ }
+ return cchRet;
+}
+
+
+/**
+ * Copies a command line to the buffer @a pszDst points to.
+ *
+ * Must only be used immediately after kmk_cc_eval_prep_command_line().
+ *
+ * @returns
+ * @param pCompiler The compiler instance data.
+ * @param pchLeft Pointer to the first char to copy from the current line.
+ * This does not have to the start of a word.
+ * @param cchPrepped The return value of kmk_cc_eval_prep_command_line().
+ * @param pszDst The destination buffer, must be at least @a cchPrepped
+ * plus one (terminator) char big.
+ */
+static void kmk_cc_eval_copy_prepped_command_line(PKMKCCEVALCOMPILER pCompiler, const char *pchLeft,
+ size_t cchPrepped, char *pszDst)
+{
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned const cEscEols = pCompiler->cEscEols;
+ if (iEscEol >= cEscEols)
+ {
+ /* Single line. */
+ memcpy(pszDst, pchLeft, cchPrepped);
+ pszDst[cchPrepped] = '\0';
+ }
+ else
+ {
+ /* Multiple lines with normalized EOL and maybe one stripped leading TAB. */
+ char * const pszDstStart = pszDst;
+ const char * const pszContent = pCompiler->pszContent;
+ size_t const cchEolSeq = pCompiler->cchEolSeq;
+ size_t offLeft = pchLeft - pCompiler->pszContent;
+ size_t cchCopy;
+
+ do
+ {
+ size_t offEol = pCompiler->paEscEols[iEscEol].offEsc;
+ cchCopy = offEol - offLeft;
+ KMK_CC_ASSERT(offEol >= offLeft);
+
+ memcpy(pszDst, &pszContent[offLeft], cchCopy);
+ pszDst += cchCopy;
+ *pszDst += '\n';
+
+ offLeft = offEol + cchEolSeq;
+ if (pszContent[offLeft] == '\t')
+ offLeft++;
+ } while (iEscEol < cEscEols);
+
+ cchCopy = cchPrepped - (pszDst - pszDstStart);
+ KMK_CC_ASSERT(cchCopy <= cchPrepped);
+ memcpy(pszDst, &pszContent[offLeft], cchCopy);
+ pszDst += cchCopy;
+
+ *pszDst = '\0';
+ KMK_CC_ASSERT(pszDst == &pszDstStart[cchPrepped]);
+ }
+}
+#endif /* unused atm */
+
+
+static size_t kmk_cc_eval_parse_var_exp(PKMKCCEVALCOMPILER pCompiler, const char *pch, size_t cchLeft, size_t off)
+{
+ off++;
+ if (off < cchLeft)
+ {
+ char const chOpen = pch[++off];
+ if (chOpen == '(' || chOpen == '{')
+ {
+ /*
+ * Got a $(VAR) or ${VAR} to deal with here. This may include nested
+ * variable references and span multiple lines (at least for function
+ * calls).
+ *
+ * We scan forward till we've found the corresponding closing
+ * parenthesis, considering any open parentheses of the same kind as
+ * worth counting, even if there are no dollar preceeding them, just
+ * like GNU make does.
+ */
+ size_t const offStart = off - 1;
+ char const chClose = chOpen == '(' ? ')' : '}';
+ unsigned cOpen = 1;
+ off++;
+ for (;;)
+ {
+ if (off < cchLeft)
+ {
+ char ch = pch[off];
+ if (!(KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch)))
+ off++;
+ else
+ {
+ off++;
+ if (ch == chClose)
+ {
+ if (--cOpen == 0)
+ break;
+ }
+ else if (ch == chOpen)
+ cOpen++;
+ else if ( ch == '\\'
+ && pCompiler->iEscEol < pCompiler->cEscEols
+ && (size_t)(&pch[off] - pCompiler->pszContent)
+ == pCompiler->paEscEols[pCompiler->iEscEol].offEsc)
+ {
+ off += pCompiler->paEscEols[pCompiler->iEscEol].offEol
+ - pCompiler->paEscEols[pCompiler->iEscEol].offEsc
+ + pCompiler->cchEolSeq;
+ pCompiler->iEscEol++;
+ }
+ }
+ }
+ else if (cOpen == 1)
+ kmk_cc_eval_fatal(pCompiler, &pch[offStart], "Variable reference is missing '%c'", chClose);
+ else
+ kmk_cc_eval_fatal(pCompiler, &pch[offStart],
+ "%u variable references are missing '%c'", cOpen, chClose);
+ }
+ }
+ /* Single char variable name. */
+ else if (!KMK_CC_EVAL_IS_SPACE(chOpen))
+ { /* likely */ }
+ else
+ kmk_cc_eval_fatal(pCompiler, &pch[off], "Expected variable name after '$', not space ");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, &pch[off], "Expected variable name after '$', end of line");
+ return off;
+}
+
+/**
+ * Helper for ensuring that we've got sufficient number of words allocated.
+ */
+#define KMK_CC_EVAL_ENSURE_WORDS(a_pCompiler, a_cRequiredWords) \
+ do { \
+ if ((a_cRequiredWords) < (a_pCompiler)->cWordsAllocated) \
+ { /* likely */ } \
+ else \
+ { \
+ unsigned cEnsureWords = ((a_cRequiredWords) + 3 /*15*/) & ~(unsigned)3/*15*/; \
+ KMK_CC_ASSERT((a_cRequiredWords) < 0x8000); \
+ (a_pCompiler)->paWords = (PKMKCCEVALWORD)xrealloc((a_pCompiler)->paWords, \
+ cEnsureWords * sizeof((a_pCompiler)->paWords)[0]); \
+ } \
+ } while (0)
+
+
+/**
+ * Word parser helper function for dealing with dollars, simple variant that
+ * doesn't need to take multiple lines into account.
+ *
+ * @returns New word length placing us after the
+ * @param pCompiler The compiler state.
+ * @param cchWord Offset of the dollar into pchWord.
+ * @param pchWord The word we're currently parsing.
+ * @param cchLeft How much we've got left to parse.
+ */
+K_INLINE size_t kmk_cc_eval_parse_along_dollar_simple(PKMKCCEVALCOMPILER pCompiler, size_t cchWord,
+ const char *pchWord, size_t cchLeft)
+{
+ const size_t cchStart = cchWord;
+ cchWord++;
+ if (cchWord < cchLeft)
+ {
+ /*
+ * Got a $(VAR) or ${VAR} to deal with here. This may include nested variable
+ * references and span multiple lines (at least for function calls).
+ *
+ * We scan forward till we've found the corresponding closing parenthesis,
+ * considering any open parentheses of the same kind as worth counting, even
+ * if there are no dollar preceeding them, just like GNU make does.
+ *
+ * We leave the other parenthesis type to the expansion compiler to deal with.
+ */
+ unsigned cOpens = 1;
+ char const chOpen = pchWord[cchWord++];
+ if (chOpen == '(')
+ {
+ for (;;)
+ {
+ if (cchWord < cchLeft)
+ {
+ char const ch = pchWord[cchWord++];
+ if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
+ { /* likely */ }
+ else if (ch == ')')
+ {
+ if (--cOpens == 0)
+ return cchWord;
+ }
+ else if (ch == '(')
+ cOpens++;
+ }
+ else
+ break;
+ }
+ }
+ else if (chOpen == '{')
+ {
+ for (;;)
+ {
+ if (cchWord < cchLeft)
+ {
+ char const ch = pchWord[cchWord++];
+ if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
+ { /* likely */ }
+ else if (ch == '}')
+ {
+ if (--cOpens == 0)
+ return cchWord;
+ }
+ else if (ch == '{')
+ cOpens++;
+ }
+ else
+ break;
+ }
+ }
+ else
+ return cchWord;
+
+ /* Unterminated. */
+ if (cOpens == 1)
+ kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
+ "Variable reference is missing '%c'", chOpen == '(' ? ')' : '}');
+ else
+ kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
+ "%u variable references are missing '%c'", cOpens, chOpen == '(' ? ')' : '}');
+ }
+ else
+ kmk_cc_eval_warn(pCompiler, &pchWord[cchWord - 1], "found '$' at end of line");
+ return cchWord;
+}
+
+
+/**
+ * Word parser helper function for dealing with dollars, complicated variant
+ * that takes escaped EOLs into account.
+ *
+ * @returns New word length placing us after the
+ * @param pCompiler The compiler state.
+ * @param cchWord Offset of the dollar into pchWord.
+ * @param pchWord The word we're currently parsing.
+ * @param cchLeft How much we've got left to parse.
+ */
+static size_t kmk_cc_eval_parse_along_dollar_esc_eol(PKMKCCEVALCOMPILER pCompiler, size_t cchWord,
+ const char *pchWord, size_t cchLeft)
+{
+ const size_t cchStart = cchWord;
+ cchWord++;
+ if (cchWord < cchLeft)
+ {
+ /*
+ * Got a $(VAR) or ${VAR} to deal with here. This may include nested variable
+ * references and span multiple lines (at least for function calls).
+ *
+ * We scan forward till we've found the corresponding closing parenthesis,
+ * considering any open parentheses of the same kind as worth counting, even
+ * if there are no dollar preceeding them, just like GNU make does.
+ *
+ * We leave the other parenthesis type to the expansion compiler to deal with.
+ */
+ unsigned cOpens = 1;
+ char const chOpen = pchWord[cchWord++];
+ if (chOpen == '(')
+ {
+ for (;;)
+ {
+ if (cchWord < cchLeft)
+ {
+ char const ch = pchWord[cchWord++];
+ if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
+ { /* likely */ }
+ else if (ch == ')')
+ {
+ if (--cOpens == 0)
+ return cchWord;
+ }
+ else if (ch == '(')
+ cOpens++;
+ else if (ch == '\\')
+ {
+ unsigned const iEscEol = pCompiler->iEscEol;
+ if ( iEscEol < pCompiler->cEscEols
+ && (size_t)(&pchWord[cchWord] - pCompiler->pszContent) == pCompiler->paEscEols[iEscEol].offEsc)
+ {
+ cchWord += pCompiler->paEscEols[iEscEol].offEol
+ - pCompiler->paEscEols[iEscEol].offEsc
+ + pCompiler->cchEolSeq;
+ pCompiler->iEscEol = iEscEol + 1;
+ }
+ }
+ }
+ else
+ break;
+ }
+ }
+ else if (chOpen == '{')
+ {
+ for (;;)
+ {
+ if (cchWord < cchLeft)
+ {
+ char const ch = pchWord[cchWord++];
+ if (!KMK_CC_EVAL_IS_PAREN_OR_SLASH(ch))
+ { /* likely */ }
+ else if (ch == '}')
+ {
+ if (--cOpens == 0)
+ return cchWord;
+ }
+ else if (ch == '{')
+ cOpens++;
+ else if (ch == '\\')
+ {
+ unsigned const iEscEol = pCompiler->iEscEol;
+ if ( iEscEol < pCompiler->cEscEols
+ && (size_t)(&pchWord[cchWord] - pCompiler->pszContent) == pCompiler->paEscEols[iEscEol].offEsc)
+ {
+ cchWord += pCompiler->paEscEols[iEscEol].offEol
+ - pCompiler->paEscEols[iEscEol].offEsc
+ + pCompiler->cchEolSeq;
+ pCompiler->iEscEol = iEscEol + 1;
+ }
+ }
+ }
+ else
+ break;
+ }
+ }
+ else
+ return cchWord;
+
+ /* Unterminated. */
+ if (cOpens == 1)
+ kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
+ "Variable reference is missing '%c'", chOpen == '(' ? ')' : '}');
+ else
+ kmk_cc_eval_fatal(pCompiler, &pchWord[cchStart],
+ "%u variable references are missing '%c'", cOpens, chOpen == '(' ? ')' : '}');
+ }
+ else
+ kmk_cc_eval_warn(pCompiler, &pchWord[cchWord - 1], "found '$' at end of line");
+ return cchWord;
+}
+
+
+/**
+ * Parses the remainder of the line into simple words.
+ *
+ * The resulting words are classified as either kKmkCcEvalToken_WordPlain,
+ * kKmkCcEvalToken_WordWithDollar, or kKmkCcEvalToken_WordWithDollarAndEscEol.
+ *
+ * @returns Number of words.
+ * @param pCompiler The compiler state.
+ * @param pchWord Where to start, we expect this to be at a word.
+ * @param cchLeft The number of chars left to parse on this line.
+ * This is expected to be non-zero.
+ */
+static unsigned kmk_cc_eval_parse_words(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned cEscEols = pCompiler->cEscEols;
+ unsigned cWords = 0;
+
+ /* Precoditions. */
+ KMK_CC_ASSERT(cchLeft > 0);
+ KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));
+
+ /*
+ * If we don't have to deal with escaped EOLs, the find-end-of word search
+ * becomes a little bit simpler. Since this function will be used a lot
+ * for simple lines with single words, this could maybe save a nano second
+ * or two.
+ */
+ if (iEscEol >= cEscEols)
+ {
+ do
+ {
+ size_t cchSkipAfter = 0;
+ size_t cchWord = 0;
+ KMKCCEVALTOKEN enmToken = kKmkCcEvalToken_WordPlain;
+
+ /* Find the end of the current word. */
+ while (cchWord < cchLeft)
+ {
+ char ch = pchWord[cchWord];
+ if (!KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(ch))
+ cchWord++;
+ else if (ch == '$')
+ {
+#ifdef XXXX
+ cchWord = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cchWord);
+ enmToken = kKmkCcEvalToken_WordWithDollar;
+#else
+ enmToken = kKmkCcEvalToken_WordWithDollar;
+ cchWord = kmk_cc_eval_parse_along_dollar_simple(pCompiler, cchWord, pchWord, cchLeft);
+#endif
+ }
+ else
+ break;
+ }
+
+ /* Add the word. */
+ KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
+ pCompiler->paWords[cWords].pchWord = pchWord;
+ pCompiler->paWords[cWords].cchWord = cchWord;
+ pCompiler->paWords[cWords].enmToken = enmToken;
+ cWords++;
+
+ /* Skip the work and any trailing blanks. */
+ cchWord += cchSkipAfter;
+ pchWord += cchWord;
+ cchLeft -= cchWord;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ } while (cchLeft > 0);
+ }
+ /*
+ * Have to deal with escaped EOLs.
+ */
+ else
+ {
+ const char *pszContent = pCompiler->pszContent;
+ do
+ {
+ size_t cchSkipAfter = 0;
+ size_t cchWord = 0;
+ KMKCCEVALTOKEN enmToken = kKmkCcEvalToken_WordPlain;
+
+ /* Find the end of the current word. */
+ while (cchWord < cchLeft)
+ {
+ char ch = pchWord[cchWord];
+ if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
+ cchWord++;
+ else if (ch == '$')
+#ifdef XXXX
+ {
+ const unsigned iEscEolBefore = pCompiler->iEscEol;
+ cchWord = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cchWord);
+ enmToken = pCompiler->iEscEol == iEscEolBefore
+ ? kKmkCcEvalToken_WordWithDollar : kKmkCcEvalToken_WordWithDollarAndEscEol;
+ }
+#else
+ {
+ enmToken = kKmkCcEvalToken_WordWithDollar;
+ cchWord = kmk_cc_eval_parse_along_dollar_esc_eol(pCompiler, cchWord, pchWord, cchLeft);
+ }
+#endif
+ else if (ch != '\\')
+ break;
+ else if ((size_t)(&pchWord[cchWord] - pszContent) != pCompiler->paEscEols[iEscEol].offEsc)
+ cchWord++;
+ else
+ {
+ cchSkipAfter = pCompiler->paEscEols[iEscEol].offEol - pCompiler->paEscEols[iEscEol].offEsc
+ + pCompiler->cchEolSeq;
+ iEscEol++;
+ break;
+ }
+ }
+
+ /* Add the word. */
+ KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
+ pCompiler->paWords[cWords].pchWord = pchWord;
+ pCompiler->paWords[cWords].cchWord = cchWord;
+ pCompiler->paWords[cWords].enmToken = enmToken;
+ cWords++;
+
+ /* Skip the work and any trailing blanks. */
+ cchWord += cchSkipAfter;
+ pchWord += cchWord;
+ cchLeft -= cchWord;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ } while (cchLeft > 0);
+ }
+ pCompiler->cWords = cWords;
+ return cWords;
+}
+
+
+/**
+ * Parses the remainder of the line into target words.
+ *
+ * The resulting words are classified as either kKmkCcEvalToken_WordPlain or
+ * kKmkCcEvalToken_WordWithDollar.
+ *
+ * @returns Number of words.
+ * @param pCompiler The compiler state.
+ * @param pchWord Where to start, we expect this to be at a word.
+ * @param cchLeft The number of chars left to parse on this line.
+ * This is expected to be non-zero.
+ */
+static unsigned kmk_cc_eval_parse_recipe_words(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned cEscEols = pCompiler->cEscEols;
+ unsigned cWords = 0;
+
+ /* Precoditions. */
+ KMK_CC_ASSERT(cchLeft > 0);
+ KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord));
+
+ /*
+ * If we don't have to deal with escaped EOLs, the find-end-of word search
+ * becomes a little bit simpler. Since this function will be used a lot
+ * for simple lines with single words, this could maybe save a nano second
+ * or two.
+ */
+ if (iEscEol >= cEscEols)
+ {
+ do
+ {
+ size_t cchSkipAfter = 0;
+ size_t cchWord = 0;
+ KMKCCEVALTOKEN enmToken = kKmkCcEvalToken_WordPlain;
+
+ /* Find the end of the current word. */
+ while (cchWord < cchLeft)
+ {
+ char ch = pchWord[cchWord];
+ if (!KMK_CC_EVAL_IS_SPACE_OR_DOLLAR(ch))
+ cchWord++;
+ else if (ch == '$')
+ {
+ enmToken = kKmkCcEvalToken_WordWithDollar;
+ cchWord = kmk_cc_eval_parse_along_dollar_simple(pCompiler, cchWord, pchWord, cchLeft);
+ }
+ else
+ break;
+ }
+
+ /* Add the word. */
+ KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
+ pCompiler->paWords[cWords].pchWord = pchWord;
+ pCompiler->paWords[cWords].cchWord = cchWord;
+ pCompiler->paWords[cWords].enmToken = enmToken;
+ cWords++;
+
+ /* Skip the work and any trailing blanks. */
+ cchWord += cchSkipAfter;
+ pchWord += cchWord;
+ cchLeft -= cchWord;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ } while (cchLeft > 0);
+ }
+ /*
+ * Have to deal with escaped EOLs.
+ */
+ else
+ {
+ const char *pszContent = pCompiler->pszContent;
+ do
+ {
+ size_t cchSkipAfter = 0;
+ size_t cchWord = 0;
+ KMKCCEVALTOKEN enmToken = kKmkCcEvalToken_WordPlain;
+
+ /* Find the end of the current word. */
+ while (cchWord < cchLeft)
+ {
+ char ch = pchWord[cchWord];
+ if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_OR_SLASH(ch))
+ cchWord++;
+ else if (ch == '$')
+ {
+ enmToken = kKmkCcEvalToken_WordWithDollar;
+ cchWord = kmk_cc_eval_parse_along_dollar_esc_eol(pCompiler, cchWord, pchWord, cchLeft);
+ }
+ else if (ch != '\\')
+ break;
+ else if ((size_t)(&pchWord[cchWord] - pszContent) != pCompiler->paEscEols[iEscEol].offEsc)
+ cchWord++;
+ else
+ {
+ cchSkipAfter = pCompiler->paEscEols[iEscEol].offEol - pCompiler->paEscEols[iEscEol].offEsc
+ + pCompiler->cchEolSeq;
+ iEscEol++;
+ break;
+ }
+ }
+
+ /* Add the word. */
+ KMK_CC_EVAL_ENSURE_WORDS(pCompiler, cWords + 1);
+ pCompiler->paWords[cWords].pchWord = pchWord;
+ pCompiler->paWords[cWords].cchWord = cchWord;
+ pCompiler->paWords[cWords].enmToken = enmToken;
+ cWords++;
+
+ /* Skip the work and any trailing blanks. */
+ cchWord += cchSkipAfter;
+ pchWord += cchWord;
+ cchLeft -= cchWord;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ } while (cchLeft > 0);
+ }
+ pCompiler->cWords = cWords;
+ return cWords;
+}
+
+
+
+
+/**
+ * Gather string from segments and optional space insertion trick.
+ *
+ * @param pszDst The destination buffer.
+ * @param paSegs The source segments.
+ * @param cSegs The number of segments.
+ * @param cchDstPrepped The size of pszDst, excluding the terminator.
+ */
+static void kmk_cc_eval_strcpyv(char *pszDst, PCKMKCCEVALSTRCPYSEG paSegs, unsigned cSegs, size_t cchDstPrepped)
+{
+ const char *pszDstStart = pszDst;
+ unsigned iSeg = 0;
+ while (iSeg < cSegs)
+ {
+ size_t cchToCopy;
+ if (paSegs[iSeg].cchSrcAndPrependSpace >= 0)
+ cchToCopy = paSegs[iSeg].cchSrcAndPrependSpace;
+ else
+ {
+ cchToCopy = -paSegs[iSeg].cchSrcAndPrependSpace;
+ *pszDst++ = ' ';
+ }
+
+ memcpy(pszDst, paSegs[iSeg].pchSrc, cchToCopy);
+ pszDst += cchToCopy;
+
+ iSeg++;
+ }
+ *pszDst = '\0';
+ KMK_CC_ASSERT(pszDst == &pszDstStart[cchDstPrepped]); K_NOREF(pszDstStart); K_NOREF(cchDstPrepped);
+}
+
+
+/**
+ * Allocate a byte buffer and ocpy the prepared string segments into it.
+ *
+ * The caller must call kmk_cc_block_realign!
+ *
+ * @returns Pointer to the duplicated string.
+ * @param pCompiler The compiler instance data.
+ * @param cchPrepped The length of the prepped string segments.
+ */
+static char *kmk_cc_eval_strdup_prepped(PKMKCCEVALCOMPILER pCompiler, size_t cchPrepped)
+{
+ char *pszCopy = kmk_cc_block_byte_alloc(pCompiler->ppBlockTail, cchPrepped + 1);
+ kmk_cc_eval_strcpyv(pszCopy, pCompiler->paStrCopySegs, pCompiler->cStrCopySegs, cchPrepped);
+ return pszCopy;
+}
+
+
+/**
+ * Strip trailing spaces from prepped copy
+ *
+ * @param paSegs The segments to strip trailing chars from.
+ * @param pcSegs The number of segments (in/out).
+ * @param pcchDstPrepped The total number of chars prepped (in/out).
+ */
+static void kmk_cc_eval_strip_right_v(PKMKCCEVALSTRCPYSEG paSegs, unsigned *pcSegs, size_t *pcchDstPrepped)
+{
+ /*
+ * Work our way thru the segments, from the end obviously.
+ */
+ size_t cchDstPrepped = *pcchDstPrepped;
+ unsigned cSegs = *pcSegs;
+ while (cSegs > 0)
+ {
+ unsigned iSeg = cSegs - 1;
+ const char *pszSrc = paSegs[iSeg].pchSrc;
+ size_t cchSrc = paSegs[iSeg].cchSrcAndPrependSpace >= 0
+ ? paSegs[iSeg].cchSrcAndPrependSpace : -paSegs[iSeg].cchSrcAndPrependSpace;
+ if (cchSrc)
+ {
+ /*
+ * Check for trailing spaces.
+ */
+ size_t cchSrcOrg;
+ if (!KMK_CC_EVAL_IS_SPACE(pszSrc[cchSrc - 1]))
+ {
+ /* Special case: No trailing spaces at all. No need to update
+ input/output variables. */
+ if (cSegs == *pcSegs)
+ return;
+ break;
+ }
+
+ /* Skip the rest of the trailing spaces. */
+ cchSrcOrg = cchSrc;
+ do
+ cchSrc--;
+ while (cchSrc > 0 && KMK_CC_EVAL_IS_SPACE(pszSrc[cchSrc - 1]));
+
+ if (cchSrc > 0)
+ {
+ /*
+ * There are non-space chars in this segment. So, update the
+ * segment and total char count and we're done.
+ */
+ cchDstPrepped -= cchSrcOrg - cchSrc;
+ if (paSegs[iSeg].cchSrcAndPrependSpace < 0)
+ paSegs[iSeg].cchSrcAndPrependSpace = -(ssize_t)cchSrc;
+ else
+ paSegs[iSeg].cchSrcAndPrependSpace = cchSrc;
+ break;
+ }
+
+ /*
+ * Skip the whole segment.
+ */
+ cchDstPrepped -= cchSrcOrg + (paSegs[iSeg].cchSrcAndPrependSpace < 0);
+ }
+ cSegs--;
+ }
+ *pcchDstPrepped = cchDstPrepped;
+ *pcSegs = cSegs;
+}
+
+/**
+ * Helper for ensuring that we've got sufficient number of string copy segments.
+ */
+#define KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(a_pCompiler, a_cRequiredSegs) \
+ do { \
+ if ((a_cRequiredSegs) < (a_pCompiler)->cStrCopySegsAllocated) \
+ { /* likely */ } \
+ else \
+ { \
+ unsigned cEnsureSegs = ((a_cRequiredSegs) + 3 /*15*/) & ~(unsigned)3/*15*/; \
+ KMK_CC_ASSERT((a_cRequiredSegs) < 0x8000); \
+ (a_pCompiler)->paStrCopySegs = (PKMKCCEVALSTRCPYSEG)xmalloc(cEnsureSegs * sizeof((a_pCompiler)->paStrCopySegs)[0]); \
+ } \
+ } while (0)
+
+
+/**
+ * Prepares for copying a normal line, extended version.
+ *
+ * This does not assume that we start on a word, it can handle any starting
+ * character. It can also prepare partial copies.
+ *
+ * In addition to the returned information, this will store instruction in
+ * paEscEols for the following kmk_cc_eval_strcpyv() call.
+ *
+ * This will advance pCompiler->iEscEol, so that it's possible to use the common
+ * macros and helpers for parsing what comes afterwards.
+ *
+ * @returns The number of chars that will be copied by kmk_cc_eval_strcpyv().
+ * @param pCompiler The compiler instance data.
+ * @param pchWord Pointer to the first char to copy from the
+ * current line. This must be the start of a
+ * word.
+ * @param cchLeft The number of chars left on the current line
+ * starting at @a pchWord.
+ */
+static size_t kmk_cc_eval_prep_normal_line_ex(PKMKCCEVALCOMPILER pCompiler, const char * const pchWord, size_t cchLeft)
+{
+ size_t cchRet;
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned const cEscEols = pCompiler->cEscEols;
+
+ KMK_CC_ASSERT(iEscEol <= cEscEols);
+
+ if (cchLeft > 0)
+ {
+ /*
+ * If there are no escaped EOLs left, just copy exactly
+ * what was passed in.
+ */
+ if (iEscEol >= cEscEols)
+ {
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, 1);
+ pCompiler->cStrCopySegs = 1;
+ pCompiler->paStrCopySegs[0].pchSrc = pchWord;
+ pCompiler->paStrCopySegs[0].cchSrcAndPrependSpace = cchRet = cchLeft;
+ }
+ /*
+ * Ok, we have to deal with escaped EOLs and do the proper
+ * replacement of escaped newlines with space. The deal is that we
+ * collaps all whitespace before and after one or more newlines into a
+ * single space. (FreeBSD make does this differently, by the by.)
+ */
+ else
+ {
+ const char * const pszContent = pCompiler->pszContent;
+ size_t offWord = pchWord - pCompiler->pszContent;
+ size_t const offLineEnd = offWord + cchLeft; /* Note! Not necessarily end of line.*/
+ size_t offEsc;
+ size_t fPendingSpace = 0;
+ unsigned cSegs = 0;
+ size_t cchSeg;
+
+ /* Go nuts checking our preconditions here. */
+ KMK_CC_ASSERT(offWord >= pCompiler->offLine);
+ KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
+ KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->cchContent);
+ KMK_CC_ASSERT(offWord <= pCompiler->paEscEols[iEscEol].offEsc);
+ KMK_CC_ASSERT(offWord >= (iEscEol ? pCompiler->paEscEols[cEscEols - 1].offEol + pCompiler->cchEolSeq
+ : pCompiler->offLine));
+ KMK_CC_ASSERT(offWord < offLineEnd);
+
+ /* Make sure we've got more than enough segments to fill in. */
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cEscEols - iEscEol + 2);
+
+ /*
+ * All but the last line.
+ */
+ cchRet = 0;
+ do
+ {
+ KMK_CC_ASSERT(offWord < offLineEnd);
+ offEsc = pCompiler->paEscEols[iEscEol].offEsc;
+ if (offWord < offEsc)
+ {
+ /* Strip trailing spaces. */
+ while (offEsc > offWord && KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
+ offEsc--;
+ cchSeg = offEsc - offWord;
+ if (cchSeg)
+ {
+ /* Add segment. */
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offWord];
+ if (offEsc < offLineEnd)
+ {
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
+ ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
+ cchRet += cchSeg + fPendingSpace;
+ cSegs += 1;
+ fPendingSpace = 1;
+ }
+ else
+ {
+ cchSeg = offLineEnd - offWord;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
+ ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
+ pCompiler->cStrCopySegs = cSegs + 1;
+ pCompiler->iEscEol = iEscEol;
+ return cchRet + cchSeg + fPendingSpace;
+ }
+ }
+ }
+ else
+ KMK_CC_ASSERT(offWord == offEsc);
+
+ /* Next line. */
+ offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
+ iEscEol++;
+
+ /* Strip leading spaces. */
+ while (offWord < offLineEnd && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
+ offWord++;
+ if (offWord >= offLineEnd)
+ {
+ pCompiler->cStrCopySegs = cSegs;
+ pCompiler->iEscEol = iEscEol;
+ return cchRet;
+ }
+ } while (iEscEol < cEscEols);
+
+ /*
+ * The last line.
+ */
+ cchSeg = offLineEnd - offWord;
+ cchRet += cchSeg;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offWord];
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
+ ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
+ pCompiler->cStrCopySegs = cSegs + 1;
+ pCompiler->iEscEol = iEscEol;
+ }
+ }
+ /*
+ * Odd case: Nothing to copy.
+ */
+ else
+ {
+ cchRet = 0;
+ pCompiler->cStrCopySegs = 0;
+ }
+ return cchRet;
+}
+
+
+/**
+ * Prepares for copying a normal line, from the given position all the way to
+ * the end.
+ *
+ * In addition to the returned information, this will store instruction in
+ * paStrCopySegs and cSTrCopySeg for the following kmk_cc_eval_strcpyv() call.
+ *
+ * @returns The number of chars that will be copied by kmk_cc_eval_strcpyv().
+ * @param pCompiler The compiler instance data.
+ * @param pchWord Pointer to the first char to copy from the
+ * current line. This must be the start of a
+ * word.
+ * @param cchLeft The number of chars left on the current line
+ * starting at @a pchWord.
+ */
+static size_t kmk_cc_eval_prep_normal_line(PKMKCCEVALCOMPILER pCompiler, const char * const pchWord, size_t cchLeft)
+{
+ size_t cchRet;
+ unsigned iEscEol = pCompiler->iEscEol;
+ unsigned const cEscEols = pCompiler->cEscEols;
+
+ KMK_CC_ASSERT(cchLeft > 0);
+ KMK_CC_ASSERT(!KMK_CC_EVAL_IS_SPACE(*pchWord)); /* The fact that we're standing at a word, is exploited below. */
+ KMK_CC_ASSERT(iEscEol <= cEscEols);
+
+ /*
+ * If there are no escaped EOLs left, just copy what was specified,
+ * optionally sans any trailing spaces.
+ */
+ if (iEscEol >= cEscEols)
+ {
+ cchRet = cchLeft;
+
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, 1);
+ pCompiler->cStrCopySegs = 1;
+ pCompiler->paStrCopySegs[0].pchSrc = pchWord;
+ pCompiler->paStrCopySegs[0].cchSrcAndPrependSpace = cchRet;
+ }
+ /*
+ * Ok, we have to deal with escaped EOLs and do the proper
+ * replacement of escaped newlines with space. The deal is that we
+ * collaps all whitespace before and after one or more newlines into a
+ * single space. (FreeBSD make does this differently, by the by.)
+ */
+ else
+ {
+ const char *pszContent = pCompiler->pszContent;
+ size_t offWord = pchWord - pCompiler->pszContent;
+ size_t offEsc;
+ size_t fPendingSpace;
+ size_t cchSeg;
+ unsigned cSegs = 0;
+
+ /* Go nuts checking our preconditions here. */
+ KMK_CC_ASSERT(offWord >= pCompiler->offLine);
+ KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->offLine + pCompiler->cchLine);
+ KMK_CC_ASSERT(offWord + cchLeft <= pCompiler->cchContent);
+ KMK_CC_ASSERT(offWord < pCompiler->paEscEols[iEscEol].offEsc);
+ KMK_CC_ASSERT(offWord >= (iEscEol ? pCompiler->paEscEols[iEscEol - 1].offEol + pCompiler->cchEolSeq : pCompiler->offLine));
+
+ /* Make sure we've got more than enough segments to fill in. */
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cEscEols - iEscEol + 2);
+
+ /*
+ * First line - We're at the start of a word, so no left stripping needed.
+ */
+ offEsc = pCompiler->paEscEols[iEscEol].offEsc;
+ KMK_CC_ASSERT(offEsc > offWord);
+ while (KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
+ offEsc--;
+ KMK_CC_ASSERT(offEsc > offWord);
+
+ fPendingSpace = 1;
+ cchRet = offEsc - offWord;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchRet;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = pchWord;
+ cSegs++;
+
+ offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
+ iEscEol++;
+
+ /*
+ * All but the last line.
+ */
+ while (iEscEol < cEscEols)
+ {
+ offEsc = pCompiler->paEscEols[iEscEol].offEsc;
+
+ /* Strip leading spaces. */
+ while (offWord < offEsc && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
+ offWord++;
+
+ if (offWord < offEsc)
+ {
+ /* Strip trailing spaces. */
+ while (KMK_CC_EVAL_IS_SPACE(pszContent[offEsc - 1]))
+ offEsc--;
+ cchSeg = offEsc - offWord;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
+ cchRet += cchSeg + fPendingSpace;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offWord];
+ cSegs += 1;
+ fPendingSpace = 1;
+ }
+
+ /* Next. */
+ offWord = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq;
+ iEscEol++;
+ }
+
+ /*
+ * Final line. We must calculate the end of line offset our selves here.
+ */
+ offEsc = &pchWord[cchLeft] - pszContent;
+ while (offWord < offEsc && KMK_CC_EVAL_IS_SPACE(pszContent[offWord]))
+ offWord++;
+
+ if (offWord < offEsc)
+ {
+ cchSeg = offEsc - offWord;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace ? -(ssize_t)cchSeg : (ssize_t)cchSeg;
+ cchRet += cchSeg + fPendingSpace;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offWord];
+ cSegs += 1;
+ }
+
+ pCompiler->cStrCopySegs = cSegs;
+ }
+ return cchRet;
+}
+
+
+/**
+ * Common worker for all kmk_cc_eval_do_if*() functions.
+ *
+ * @param pCompiler The compiler state.
+ * @param pIfCore The new IF statement.
+ * @param fInElse Set if this is an 'else if' (rather than just 'if').
+ */
+static void kmk_cc_eval_do_if_core(PKMKCCEVALCOMPILER pCompiler, PKMKCCEVALIFCORE pIfCore, int fInElse)
+{
+ unsigned iIf = pCompiler->cIfs;
+ if (!fInElse)
+ {
+ /* Push an IF statement. */
+ if (iIf < KMK_CC_EVAL_MAX_IF_DEPTH)
+ {
+ pCompiler->cIfs = iIf + 1;
+ pCompiler->apIfs[iIf] = pIfCore;
+ pIfCore->pPrevCond = NULL;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, NULL, "Too deep IF nesting");
+ }
+ else if (iIf > 0)
+ {
+ /* Link an IF statement. */
+ iIf--;
+ pIfCore->pPrevCond = pCompiler->apIfs[iIf];
+ pCompiler->apIfs[iIf] = pIfCore;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, NULL, "'else if' without 'if'");
+ pIfCore->pNextTrue = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
+ pIfCore->pNextFalse = NULL; /* This is set by else or endif. */
+ pIfCore->pTrueEndJump = NULL; /* This is set by else or endif. */
+}
+
+
+/**
+ * Deals with 'if expr' and 'else if expr' statements.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'if'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fInElse Set if this is an 'else if' (rather than just 'if').
+ */
+static int kmk_cc_eval_do_if(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ PKMKCCEVALIFEXPR pInstr;
+ size_t cchExpr = kmk_cc_eval_prep_normal_line(pCompiler, pchWord, cchLeft);
+ kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &cchExpr);
+
+ pInstr = (PKMKCCEVALIFEXPR)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, KMKCCEVALIFEXPR_SIZE(cchExpr));
+ kmk_cc_eval_strcpyv(pInstr->szExpr, pCompiler->paStrCopySegs, pCompiler->cStrCopySegs, cchExpr);
+ pInstr->cchExpr = cchExpr;
+ pInstr->IfCore.Core.enmOpcode = kKmkCcEvalInstr_if;
+ pInstr->IfCore.Core.iLine = pCompiler->iLine;
+ kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
+ return 1;
+}
+
+
+/**
+ * Deals with 'ifdef var', 'ifndef var', 'else ifdef var' and 'else ifndef var'
+ * statements.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'if[n]def'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fInElse Set if this is an 'else if' (rather than just 'if').
+ * @param fPositiveStmt Set if 'ifdef', clear if 'ifndef'.
+ */
+static int kmk_cc_eval_do_ifdef(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ /*
+ * Skip to the end of the variable name.
+ * GNU make just does normal word parsing, so lets do that too.
+ */
+ unsigned const iSavedEscEol = pCompiler->iEscEol;
+ unsigned cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
+ if (cWords == 0)
+ {
+ PCKMKCCEVALWORD pWord = pCompiler->paWords;
+ if (pWord->enmToken == kKmkCcEvalToken_WordPlain)
+ {
+ PKMKCCEVALIFDEFPLAIN pInstr;
+ pInstr = (PKMKCCEVALIFDEFPLAIN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifdef_plain : kKmkCcEvalInstr_ifndef_plain;
+ pInstr->IfCore.Core.iLine = pCompiler->iLine;
+ pInstr->pszName = strcache2_add(&variable_strcache, pWord->pchWord, pWord->cchWord);
+ kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
+ }
+ else
+ {
+ PKMKCCEVALIFDEFDYNAMIC pInstr;
+ size_t cchCopy;
+ char const *pszCopy;
+
+ pInstr = (PKMKCCEVALIFDEFDYNAMIC)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+
+ /** @todo Make the subprogram embed necessary strings. */
+ if (pWord->enmToken != kKmkCcEvalToken_WordWithDollar)
+ {
+ pszCopy = kmk_cc_block_strdup(pCompiler->ppBlockTail, pWord->pchWord, pWord->cchWord);
+ cchCopy = pWord->cchWord;
+ }
+ else
+ {
+ KMK_CC_ASSERT(pWord->enmToken == kKmkCcEvalToken_WordWithDollarAndEscEol);
+ pCompiler->iEscEol = iSavedEscEol;
+ cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pWord->pchWord, cchLeft);
+ pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, cchCopy);
+ }
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+
+ pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifdef_dynamic : kKmkCcEvalInstr_ifndef_dynamic;
+ pInstr->IfCore.Core.iLine = pCompiler->iLine;
+ pInstr->uPadding = 0;
+ kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, cchCopy, &pInstr->NameSubprog);
+
+ kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
+ }
+ }
+ else
+ {
+ KMK_CC_ASSERT(cWords > 1);
+ kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[1].pchWord,
+ "Bogus stuff after 'if%sdef' variable name", fPositiveStmt ? "" : "n");
+ }
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
+ return 1;
+}
+
+
+/**
+ * Deals with 'ifeq (a,b)', 'ifeq "a" "b"', 'ifneq (a,b)', 'ifneq "a" "b"',
+ * 'else ifeq (a,b)', 'else ifeq "a" "b"', 'else ifneq (a,b)' and
+ * 'else ifneq "a" "b"' statements.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'if[n]eq'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fInElse Set if this is an 'else if' (rather than just 'if').
+ * @param fPositiveStmt Set if 'ifeq', clear if 'ifneq'.
+ */
+static int kmk_cc_eval_do_ifeq(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ /*
+ * There are two forms:
+ *
+ * ifeq (string1, string2)
+ * ifeq "string1" 'string2'
+ *
+ */
+ const char * const pchEnd = &pchWord[cchLeft];
+ PKMKCCEVALIFEQ pInstr = (PKMKCCEVALIFEQ)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+
+ struct
+ {
+ char *pszCopy;
+ size_t cchCopy;
+ int fPlain;
+ } Left, Right;
+
+ char ch = *pchWord;
+ if (ch == '(')
+ {
+ int cCounts;
+ size_t off;
+
+ /*
+ * The left side ends with a comma. We respect parentheses, but
+ * not curly brackets.
+ */
+
+ /* Skip the parenthesis. */
+ pchWord++;
+ cchLeft--;
+
+ /* Find the comma, checking for non-plainness. */
+ cCounts = 0;
+ Left.fPlain = 1;
+ for (off = 0; off < cchLeft; off++)
+ {
+ ch = pchWord[off];
+ if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
+ { /* likely */ }
+ else if (ch == '$')
+ Left.fPlain = 0;
+ else if (ch == '(')
+ cCounts++;
+ else if (ch == ')')
+ cCounts--; /** @todo warn if it goes negative. */
+ else if (ch == ',' && cCounts == 0)
+ break;
+ else
+ KMK_CC_ASSERT(cCounts > 0);
+ }
+ if (ch == ',' && cCounts == 0) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ',' before end of line");
+
+ /* Copy out the string. */
+ Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
+ kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Left.cchCopy);
+ Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);
+
+ /* Skip past the comma and any following spaces. */
+ pchWord += off + 1;
+ cchLeft -= off + 1;
+ if ( cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
+ && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+
+ /*
+ * Ditto for the right side, only it ends with a closing parenthesis.
+ */
+ cCounts = 1;
+ Right.fPlain = 1;
+ for (off = 0; off < cchLeft; off++)
+ {
+ ch = pchWord[off];
+ if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
+ { /* likely */ }
+ else if (ch == '$')
+ Right.fPlain = 0;
+ else if (ch == '(')
+ cCounts++;
+ else if (ch == ')')
+ {
+ if (--cCounts == 0)
+ break;
+ }
+ else
+ KMK_CC_ASSERT(cCounts > 0 || ch == ',');
+ }
+ if (ch == ')' && cCounts == 0) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ')' before end of line");
+
+ /* Copy out the string. */
+ Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
+ kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Right.cchCopy);
+ Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);
+
+ /* Skip past the parenthesis. */
+ pchWord += off + 1;
+ cchLeft -= off + 1;
+ }
+ else if (ch == '"' || ch == '\'')
+ {
+ const char *pchTmp;
+
+ /*
+ * Quoted left side.
+ */
+ /* Skip leading quote. */
+ pchWord++;
+ cchLeft--;
+
+ /* Locate the end quote. */
+ pchTmp = (const char *)memchr(pchWord, ch, cchLeft);
+ if (pchTmp) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, pchWord - 1, "Unbalanced quote in first if%seq string", fPositiveStmt ? "" : "n");
+
+ Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, pchTmp - pchWord);
+ Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);
+ Left.fPlain = memchr(Left.pszCopy, '$', Left.cchCopy) == NULL;
+
+ /* skip end quote */
+ pchWord = pchTmp + 1;
+ cchLeft = pchEnd - pchWord;
+
+ /* Skip anything inbetween the left and right hand side (not mandatory). */
+ if ( cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
+ && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+
+ /*
+ * Quoted right side.
+ */
+ if ( cchLeft > 0
+ && ( (ch = *pchWord) != '"' || ch == '\'') )
+ {
+ /* Skip leading quote. */
+ pchWord++;
+ cchLeft--;
+
+ /* Locate the end quote. */
+ pchTmp = (const char *)memchr(pchWord, ch, cchLeft);
+ if (pchTmp) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, pchWord - 1, "Unbalanced quote in second if%seq string", fPositiveStmt ? "" : "n");
+
+ Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, pchTmp - pchWord);
+ Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);
+ Right.fPlain = memchr(Right.pszCopy, '$', Right.cchCopy) == NULL;
+
+ /* skip end quote */
+ pchWord = pchTmp + 1;
+ cchLeft = pchEnd - pchWord;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected a second quoted string for 'if%seq'",
+ fPositiveStmt ? "" : "n");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected parentheses or quoted string after 'if%seq'",
+ fPositiveStmt ? "" : "n");
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+
+ /*
+ * Initialize the instruction.
+ */
+ pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_ifeq : kKmkCcEvalInstr_ifneq;
+ pInstr->IfCore.Core.iLine = pCompiler->iLine;
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Left, Left.pszCopy, Left.cchCopy, Left.fPlain);
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Right, Right.pszCopy, Right.cchCopy, Right.fPlain);
+ kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
+
+ /*
+ * Make sure there is nothing following the variable name.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'if%sdef' variable name", fPositiveStmt ? "" : "n");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
+ return 1;
+}
+
+
+/**
+ * Deals with 'if1of (set-a,set-b)', 'ifn1of (set-a,set-b)',
+ * 'else if1of (set-a,set-b)' and 'else ifn1of (set-a,set-b)' statements.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'if[n]1of'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fInElse Set if this is an 'else if' (rather than just 'if').
+ * @param fPositiveStmt Set if 'if1of', clear if 'ifn1of'.
+ */
+static int kmk_cc_eval_do_if1of(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, int fInElse, int fPositiveStmt)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ /*
+ * This code is (currently) very similar to kmk_cc_eval_do_ifeq.
+ * However, we may want to add hashing optimizations of plain text,
+ * and we don't want to support the quoted form as it is not necessary
+ * and may interfere with support for quoted words later on.
+ */
+ PKMKCCEVALIF1OF pInstr = (PKMKCCEVALIF1OF)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+
+ struct
+ {
+ char *pszCopy;
+ size_t cchCopy;
+ int fPlain;
+ } Left, Right;
+
+ char ch = *pchWord;
+ if (ch == '(')
+ {
+ int cCounts;
+ size_t off;
+
+ /*
+ * The left side ends with a comma. We respect parentheses, but
+ * not curly brackets.
+ */
+
+ /* Skip the parenthesis. */
+ pchWord++;
+ cchLeft--;
+
+ /* Find the comma, checking for non-plainness. */
+ cCounts = 0;
+ Left.fPlain = 1;
+ for (off = 0; off < cchLeft; off++)
+ {
+ ch = pchWord[off];
+ if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
+ { /* likely */ }
+ else if (ch == '$')
+ Left.fPlain = 0;
+ else if (ch == '(')
+ cCounts++;
+ else if (ch == ')')
+ cCounts--; /** @todo warn if it goes negative. */
+ else if (ch == ',' && cCounts == 0)
+ break;
+ else
+ KMK_CC_ASSERT(cCounts > 0);
+ }
+ if (ch == ',' && cCounts == 0) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ',' before end of line");
+
+ /* Copy out the string. */
+ Left.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
+ kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Left.cchCopy);
+ Left.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Left.cchCopy);
+
+ /* Skip past the comma and any following spaces. */
+ pchWord += off + 1;
+ cchLeft -= off + 1;
+ if ( cchLeft /** @todo replace with straight 'isspace' that takes escaped EOLs into account. */
+ && KMK_CC_EVAL_IS_SPACE_AFTER_WORD(pCompiler, pchWord[0], pchWord[1], cchLeft))
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+
+ /*
+ * Ditto for the right side, only it ends with a closing parenthesis.
+ */
+ cCounts = 1;
+ Right.fPlain = 1;
+ for (off = 0; off < cchLeft; off++)
+ {
+ ch = pchWord[off];
+ if (!KMK_CC_EVAL_IS_PAREN_COMMA_OR_DOLLAR(ch))
+ { /* likely */ }
+ else if (ch == '$')
+ Right.fPlain = 0;
+ else if (ch == '(')
+ cCounts++;
+ else if (ch == ')')
+ {
+ if (--cCounts == 0)
+ break;
+ }
+ else
+ KMK_CC_ASSERT(cCounts > 0 || ch == ',');
+ }
+ if (ch == ')' && cCounts == 0) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, &pchWord[off], "Expected ')' before end of line");
+
+ /* Copy out the string. */
+ Right.cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchWord, off);
+ kmk_cc_eval_strip_right_v(pCompiler->paStrCopySegs, &pCompiler->cStrCopySegs, &Right.cchCopy);
+ Right.pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, Right.cchCopy);
+
+ /* Skip past the parenthesis. */
+ pchWord += off + 1;
+ cchLeft -= off + 1;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected parentheses after 'if%s1of'", fPositiveStmt ? "" : "n");
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+
+ /*
+ * Initialize the instruction.
+ */
+ pInstr->IfCore.Core.enmOpcode = fPositiveStmt ? kKmkCcEvalInstr_if1of : kKmkCcEvalInstr_ifn1of;
+ pInstr->IfCore.Core.iLine = pCompiler->iLine;
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Left, Left.pszCopy, Left.cchCopy, Left.fPlain);
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Right, Right.pszCopy, Right.cchCopy, Right.fPlain);
+ kmk_cc_eval_do_if_core(pCompiler, &pInstr->IfCore, fInElse);
+
+ /*
+ * Make sure there is nothing following the variable name.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'if%s1of' variable name", fPositiveStmt ? "" : "n");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected expression after 'if' directive");
+ return 1;
+}
+
+
+/**
+ * Deals with 'else' and 'else ifxxx' statements.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ */
+static int kmk_cc_eval_do_else(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ /*
+ * There must be an 'if' on the stack.
+ */
+ unsigned iIf = pCompiler->cIfs;
+ if (iIf > 0)
+ {
+ PKMKCCEVALIFCORE pIfCore = pCompiler->apIfs[--iIf];
+ if (!pIfCore->pTrueEndJump)
+ {
+ /* Emit a jump instruction that will take us from the 'True' block to the 'endif'. */
+ PKMKCCEVALJUMP pInstr = (PKMKCCEVALJUMP)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcEvalInstr_jump;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->pNext = NULL;
+ pIfCore->pTrueEndJump = pInstr;
+
+ /* The next instruction is the first in the 'False' block of the current 'if'.
+ Should this be an 'else if', this will be the 'if' instruction emitted below. */
+ pIfCore->pNextFalse = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
+ }
+ else if (iIf == 0)
+ kmk_cc_eval_fatal(pCompiler, pchWord, "2nd 'else' for 'if' at line %u", pIfCore->Core.iLine);
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "2nd 'else' in a row - missing 'endif' for 'if' at line %u?",
+ pIfCore->Core.iLine);
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "'else' without 'if'");
+
+ /*
+ * Check for 'else ifxxx'. There can be nothing else following an else.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ if ( cchLeft > 2
+ && KMK_CC_WORD_COMP_CONST_2(pchWord, "if"))
+ {
+ pchWord += 2;
+ cchLeft -= 2;
+
+ if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
+ return kmk_cc_eval_do_if(pCompiler, pchWord, cchLeft, 1 /* in else */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "eq", 2))
+ return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 2, cchLeft - 2, 1 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "def", 3))
+ return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "neq", 3))
+ return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 0 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "1of", 3))
+ return kmk_cc_eval_do_if1of(pCompiler, pchWord + 3, cchLeft - 3, 1 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "ndef", 4))
+ return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 4, cchLeft - 4, 1 /* in else */, 0 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "n1of", 4))
+ return kmk_cc_eval_do_if1of(pCompiler, pchWord + 4, cchLeft - 4, 1 /* in else */, 0 /* positive */);
+
+ pchWord -= 2;
+ cchLeft += 2;
+ }
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'else'");
+ }
+
+ return 1;
+}
+
+
+/**
+ * Deals with the 'endif' statement.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ */
+static int kmk_cc_eval_do_endif(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ /*
+ * There must be an 'if' on the stack. We'll POP it.
+ */
+ unsigned iIf = pCompiler->cIfs;
+ if (iIf > 0)
+ {
+ PKMKCCEVALCORE pNextInstr;
+ PKMKCCEVALIFCORE pIfCore = pCompiler->apIfs[--iIf];
+ pCompiler->cIfs = iIf; /* POP! */
+
+ /* Update the jump targets for all IFs at this level. */
+ pNextInstr = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
+ do
+ {
+ if (pIfCore->pTrueEndJump)
+ {
+ /* Make the true block jump here, to the 'endif'. The false block is already here. */
+ pIfCore->pTrueEndJump->pNext = pNextInstr;
+ KMK_CC_ASSERT(pIfCore->pNextFalse);
+ }
+ else
+ {
+ /* No 'else'. The false-case jump here, to the 'endif'. */
+ KMK_CC_ASSERT(!pIfCore->pNextFalse);
+ pIfCore->pNextFalse = pNextInstr;
+ }
+
+ pIfCore = pIfCore->pPrevCond;
+ } while (pIfCore);
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "'endif' without 'if'");
+
+ /*
+ * There shouldn't be anything trailing an 'endif'.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (!cchLeft) { /* likely */ }
+ else kmk_cc_eval_fatal(pCompiler, pchWord, "Bogus stuff after 'else'");
+
+ return 1;
+}
+
+
+/**
+ * Parses a 'include file...', 'sinclude file...', '-include file...',
+ * 'includedep file...', 'includedep-queue file...' and
+ * 'includedep-flush file...'
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after the include directive.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param enmOpcode The opcode for the include directive we're parsing.
+ */
+static int kmk_cc_eval_do_include(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, KMKCCEVALINSTR enmOpcode)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ /*
+ * Split what's left up into words.
+ */
+/** @todo GNU make supports escape sequences for spaces here (they confusingly refers to this as quoting). So, it's possible
+ * to include C:/Program\ Files/kBuild/footer.kmk if we wanted to. It my intention to add support for double and/or single
+ * quoted files names to offer an alternative way of addressing this. */
+ unsigned cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
+ KMK_CC_EVAL_DPRINTF(("%s: cWords=%d\n", g_apszEvalInstrNms[enmOpcode], cWords));
+ if (cWords)
+ {
+ PKMKCCEVALINCLUDE pInstr = (PKMKCCEVALINCLUDE)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail,
+ KMKCCEVALINCLUDE_SIZE(cWords));
+ pInstr->Core.enmOpcode = enmOpcode;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->cFiles = cWords;
+ kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aFiles);
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ }
+ else
+ KMK_CC_ASSERT(0);
+ }
+ else
+ KMK_CC_EVAL_DPRINTF(("%s: include without args\n", g_apszEvalInstrNms[enmOpcode]));
+ return 1;
+}
+
+
+static int kmk_cc_eval_do_vpath(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ kmk_cc_eval_fatal(pCompiler, NULL, "vpath directive is not implemented\n");
+ return 1;
+}
+
+
+/**
+ * Called when any previous recipe must have ended and can be finalized.
+ *
+ * This occurs when encountering an assignement, a new recipe or the end of the
+ * complication unit.
+ *
+ * @param pCompiler The compiler state.
+ */
+static void kmk_cc_eval_end_of_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord)
+{
+ if (pCompiler->pRecipe)
+ {
+ /** @todo do stuff here. */
+ kmk_cc_eval_fatal(pCompiler, pchWord, "end-of-recipe handling not implemented yet");
+ }
+}
+
+
+static void kmk_cc_eval_handle_command(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ kmk_cc_eval_fatal(pCompiler, pchWord, "command handling not implemented yet");
+}
+
+
+/**
+ * Pick up the recipe parsing at the colon.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord0 The first word.
+ * @param cchWord0 The length of the first word.
+ * @param enmToken0 The classification of the first word.
+ * @param pchColon The colon.
+ * @param cchLeft How much is left, starting at the colon.
+ */
+static int kmk_cc_eval_handle_recipe_cont_colon(PKMKCCEVALCOMPILER pCompiler, const char *pchWord0, size_t cchWord0,
+ KMKCCEVALTOKEN enmToken0, const char *pchColon, size_t cchLeft)
+{
+ kmk_cc_eval_fatal(pCompiler, pchWord0, "recipe handling not implemented yet (#1)");
+ return 1;
+}
+
+
+/**
+ * Pick up the recipe parsing at the 2nd word (after it was determined not to be
+ * an assignment operator after all).
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord0 The first word.
+ * @param cchWord0 The length of the first word.
+ * @param pchWord Where to continue parsing.
+ * @param cchLeft How much is left to parse.
+ */
+static int kmk_cc_eval_handle_recipe_cont_2nd_word(PKMKCCEVALCOMPILER pCompiler, const char *pchWord0, size_t cchWord0,
+ const char *pchWord, size_t cchLeft)
+{
+// const char *pchColon = memchr()
+
+ kmk_cc_eval_fatal(pCompiler, pchWord, "recipe handling not implemented yet (#2)");
+ return 1;
+}
+
+
+static void kmk_cc_eval_handle_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ const char *pszColon = NULL;//memchr(pchWord, cchLeft);
+ if (pszColon)
+ {
+
+
+ kmk_cc_eval_fatal(pCompiler, pchWord, "recipe handling not implemented yet (#3)");
+ }
+ else if (pchWord[0] == '$')
+ {
+
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord,
+ "Not variable assignment. Could be a complicated indirect recipe definition, however that's currently not supported.");
+
+}
+
+
+
+/**
+ * Common worker for handling export (non-assign), undefine and unexport.
+ *
+ * For instructions using the KMKCCEVALVARIABLES structure.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First non-space chare after the keyword.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifiers.
+ */
+static int kmk_cc_eval_do_with_variable_list(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
+ KMKCCEVALINSTR enmOpcode, unsigned fQualifiers)
+{
+ if (cchLeft)
+ {
+ /*
+ * Parse the variable name list. GNU make is using normal word
+ * handling here, so we can share code with the include directives.
+ */
+ unsigned cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
+#ifdef KMK_CC_EVAL_LOGGING_ENABLED
+ unsigned iWord;
+ KMK_CC_EVAL_DPRINTF(("%s: cWords=%d\n", g_apszEvalInstrNms[enmOpcode], cWords));
+ for (iWord = 0; iWord < cWords; iWord++)
+ KMK_CC_EVAL_DPRINTF((" word[%u]: len=%#05x t=%d '%*.*s'\n", iWord, (int)pCompiler->paWords[iWord].cchWord,
+ (int)pCompiler->paWords[iWord].enmToken, (int)pCompiler->paWords[iWord].cchWord,
+ (int)pCompiler->paWords[iWord].cchWord, pCompiler->paWords[iWord].pchWord));
+#endif
+ if (cWords)
+ {
+ PKMKCCEVALVARIABLES pInstr = (PKMKCCEVALVARIABLES)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail,
+ KMKCCEVALVARIABLES_SIZE(cWords));
+ pInstr->Core.enmOpcode = enmOpcode;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->cVars = cWords;
+ kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aVars);
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ }
+ else
+ KMK_CC_ASSERT(0);
+ }
+ /* else: NOP */
+ return 1;
+}
+
+
+/**
+ * Parses a '[qualifiers] undefine variable [..]' expression.
+ *
+ * A 'undefine' directive is final, any qualifiers must preceed it. So, we just
+ * have to extract the variable names now.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifiers.
+ */
+static int kmk_cc_eval_do_var_undefine(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (!cchLeft)
+ kmk_cc_eval_fatal(pCompiler, pchWord, "undefine requires a variable name");
+
+ /** @todo GNU make doesn't actually do the list thing for undefine, it seems
+ * to assume everything after it is a single variable... Going with
+ * simple common code for now. */
+ return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_undefine, fQualifiers);
+}
+
+
+/**
+ * Parses a '[qualifiers] unexport variable [..]' expression.
+ *
+ * A 'unexport' directive is final, any qualifiers must preceed it. So, we just
+ * have to extract the variable names now.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifiers.
+ */
+static int kmk_cc_eval_do_var_unexport(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
+{
+ PKMKCCEVALCORE pInstr;
+
+ /*
+ * Join paths with undefine and export, unless it's an unexport all directive.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_unexport, fQualifiers);
+
+ /*
+ * We're unexporting all variables.
+ */
+ pInstr = kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->enmOpcode = kKmkCcEvalInstr_unexport_all;
+ pInstr->iLine = pCompiler->iLine;
+ return 1;
+}
+
+
+/**
+ * Parse the value of a 'define' at pCompiler->offNext.
+ *
+ * This will update 'offNext' to the start of the line following the 'endef'
+ * matching the 'define' of the value.
+ *
+ * The value is prepared for kmk_cc_eval_strcpyv.
+ *
+ * @returns The value length that we've prepared for copying.
+ * @param pCompiler The compiler state.
+ * @param pfPlainValue Where to return whether this is a plain value or
+ * one needing expansion.
+ */
+static size_t kmk_cc_eval_parse_define_value(PKMKCCEVALCOMPILER pCompiler, int *pfPlainValue)
+{
+ /*
+ * Now we need to find the matching 'endef', we support nested ones.
+ *
+ * We look for the lines starting with 'endef' and 'define', like GNU
+ * make does even if we really should also be checking for variable
+ * qualifiers too.
+ *
+ * As we go on looking, we prepare the value in paStrCopySegs.
+ *
+ * Note! We duplicate code/logic from the top level compile loop here.
+ */
+ const char * const pszContent = pCompiler->pszContent;
+ size_t cchContent = pCompiler->cchContent;
+ int const chFirstEol = pCompiler->chFirstEol;
+ size_t const cchEolSeq = pCompiler->cchEolSeq;
+
+ unsigned cNestings = 1;
+ size_t offNext = pCompiler->offNext;
+ unsigned iLine = pCompiler->iLine;
+
+ unsigned cSegs = 0;
+ size_t cchValue = 0;
+ int fPlainValue = 1;
+
+ for (;;)
+ {
+ /*
+ * Find end of line, preparing to copy it.
+ */
+ if (offNext < cchContent)
+ {
+ unsigned const cSegsAtStartOfLine = cSegs;
+ size_t const cchValueStartOfLine = cchValue;
+ size_t offFirstWord = offNext;
+ const char *pchLine = &pszContent[offNext];
+ size_t cchLine;
+ const char *pchTmp;
+
+ pCompiler->cEscEols = 0;
+ pCompiler->iEscEol = 0;
+
+ /* Add newline if necessary and make sure we've got a segment handy. */
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 2);
+ if (cSegs)
+ {
+ cchValue++;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = 1;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = "\n";
+ cSegs++;
+ }
+
+ /* Simple case: No escaped EOL, nor the end of the input. */
+ pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
+ if ( pchTmp
+ && ( &pszContent[offNext] == pchTmp
+ || pchTmp[-1] != '\\'))
+ {
+ if ( cchEolSeq == 1
+ || pchTmp[1] == pCompiler->chSecondEol)
+ {
+ offNext = pchTmp - pszContent;
+ cchLine = pchTmp - pchLine;
+
+ cchValue += cchLine;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchLine;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = pchLine;
+ cSegs++;
+
+ while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+
+ offNext += cchEolSeq;
+ }
+ else
+ kmk_cc_eval_fatal_eol(pCompiler, pchTmp, iLine, offNext);
+ }
+ /* The complicated, less common cases. */
+ else
+ {
+ size_t fPendingSpace = 0;
+ for (;;)
+ {
+ /* Find the first non-space char on this line. We always need it. */
+ size_t offThisFirstWord = offNext;
+ size_t offEol = pchTmp ? pchTmp - pszContent : cchContent;
+ if (offFirstWord == offNext)
+ {
+ while (offFirstWord < offEol && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+ if (pCompiler->cEscEols > 0)
+ offThisFirstWord = offFirstWord;
+ }
+ else
+ while (offThisFirstWord < offEol && KMK_CC_EVAL_IS_SPACE(pszContent[offThisFirstWord]))
+ offThisFirstWord++;
+
+ /* We normally need one, so just make sure once. */
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 1);
+
+ if (pchTmp)
+ {
+ if ( cchEolSeq == 1
+ || pchTmp[1] == pCompiler->chSecondEol)
+ {
+ size_t const offThis = offNext;
+ size_t offEsc;
+ int fDone;
+ offNext = pchTmp - pszContent;
+
+ /* Is it an escape sequence? */
+ if ( !offNext
+ || pchTmp[-1] != '\\')
+ fDone = 1;
+ else if (offNext < 2 || pchTmp[-2] != '\\')
+ {
+ offEsc = offNext - 1;
+ fDone = 0;
+ }
+ else
+ {
+ /* Count how many backslashes there are. Must be odd number to be an escape
+ sequence. Normally we keep half of them, except for command lines. */
+ size_t cSlashes = 2;
+ while (offNext >= cSlashes && pchTmp[0 - cSlashes] == '\\')
+ cSlashes--;
+ fDone = !(cSlashes & 1);
+ offEsc = offNext - (cSlashes >> 1);
+ }
+
+ /* Anything to copy? */
+/** @todo fixme tomorrow! */
+ cchLine = offThisFirstWord - offNext;
+ if (cchLine)
+ {
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
+ ? -(ssize_t)cchLine : (ssize_t)cchLine;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offThisFirstWord];
+ cSegs++;
+ }
+
+ if (fDone)
+ {
+ cchLine = &pszContent[offNext] - pchLine;
+ offNext += cchEolSeq;
+ break;
+ }
+
+ /* Record it. */
+ if (pCompiler->cEscEols < pCompiler->cEscEolsAllocated) { /* likely */ }
+ else
+ {
+ KMK_CC_ASSERT(pCompiler->cEscEols == pCompiler->cEscEolsAllocated);
+ pCompiler->cEscEolsAllocated = pCompiler->cEscEolsAllocated
+ ? pCompiler->cEscEolsAllocated * 2 : 2;
+ pCompiler->paEscEols = (PKMKCCEVALESCEOL)xrealloc(pCompiler->paEscEols,
+ pCompiler->cEscEolsAllocated
+ * sizeof(pCompiler->paEscEols[0]));
+ }
+ pCompiler->paEscEols[pCompiler->cEscEols].offEsc = offEsc;
+ pCompiler->paEscEols[pCompiler->cEscEols].offEol = offNext;
+ pCompiler->cEscEols++;
+
+ /* Anything to copy? */
+ cchLine = offThisFirstWord - offNext;
+ if (cchLine)
+ {
+ }
+
+ /* Advance. */
+ offNext += cchEolSeq;
+ if (offFirstWord == offEsc)
+ {
+ offFirstWord = offNext;
+ pCompiler->iEscEol++;
+ }
+ }
+ else
+ kmk_cc_eval_fatal_eol(pCompiler, pchTmp, pCompiler->iLine, off);
+ }
+ else
+ {
+ /* End of input. Happens only once per compilation, nothing to optimize for. */
+
+ if (offFirstWord == offNext)
+ while (offFirstWord < cchContent && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+
+ KMK_CC_EVAL_ENSURE_STRCOPY_SEGS(pCompiler, cSegs + 2);
+ if (cSegs == cSegsAtStartOfLine)
+ {
+ /* No escaped EOLs. */
+ cchLine = &pszContent[cchContent] - pchLine;
+ cchValue += cchLine;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = cchLine;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = pchLine;
+ cSegs++;
+ }
+ else
+ {
+ if (offFirstWordThisLine < cchContent)
+ {
+ cchLine = cchContent - offFirstWordThisLine;
+ cchValue += cchLine;
+ pCompiler->paStrCopySegs[cSegs].cchSrcAndPrependSpace = fPendingSpace
+ ? -(ssize_t)cchLine : (ssize_t)cchLine;
+ pCompiler->paStrCopySegs[cSegs].pchSrc = &pszContent[offFirstWordThisLine];
+ cSegs++;
+ }
+ cchLine = &pszContent[cchContent] - pchLine;
+ }
+ offNext = cchContent;
+ break;
+ }
+ pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
+ }
+ }
+ KMK_CC_ASSERT(offNext <= cchContent);
+ KMK_CC_ASSERT(offNext >= off + cchLine);
+ KMK_CC_ASSERT(off + cchLine <= cchContent && cchLine <= cchContent);
+ KMK_CC_ASSERT(offFirstWord <= off + cchLine);
+ KMK_CC_ASSERT(offFirstWord >= off);
+ KMK_CC_ASSERT(pszContent[offFirstWord] != ' ' && pszContent[offFirstWord] != '\t');
+
+ KMK_CC_EVAL_DPRINTF(("#%03u: %*.*s\n", pCompiler->iLine, (int)cchLine, (int)cchLine, &pszContent[off]));
+
+ /*
+ * Look for 'endef' and 'define' directives.
+ */
+ cchLine -= offFirstWord - off;
+ if ( cchLine >= 5 /* shortest word is 5 chars ('endef', 'local') */
+ && pchLine[0] != pCompiler->chCmdPrefix)
+ {
+ pchTmp = &pszContent[offFirstWord];
+ if (!KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(*pchTmp))
+ { /* Kind of likely (and saves one indent). */ }
+ else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "endef", 5))
+ {
+ cNestings--;
+ if (cNestings == 0)
+ {
+ cchValue = cchValueStartOfLine;
+ cSegs = cSegsAtStartOfLine;
+ break;
+ }
+ }
+ else
+ for (;;)
+ {
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "define", 6))
+ {
+ cNestings++;
+ break;
+ }
+
+ /* GNU make doesn't do this, but I think it makes sense. */
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "local", 5))
+ {
+ pchTmp += 5;
+ cchLine -= 5;
+ }
+ else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "export", 6))
+ {
+ pchTmp += 6;
+ cchLine -= 6;
+ }
+ else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "private", 7))
+ {
+ pchTmp += 7;
+ cchLine -= 7;
+ }
+ else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchTmp, cchLine, "override", 8))
+ {
+ pchTmp += 8;
+ cchLine -= 8;
+ }
+ else
+ break;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchTmp, cchLine);
+ }
+ }
+
+ /*
+ * Advance to the next line.
+ */
+ iLine += pCompiler->cEscEols + 1;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, NULL, )
+ }
+
+ /*
+ * Update globals and return values.
+ */
+ pCompiler->offNext = offNext;
+ pCompiler->iLine = iLine;
+ pCompiler->cStrCopySegs = cSegs;
+
+ *pfPlainValue = fPlainValue;
+ return cchValue;
+}
+
+/**
+ * Parses a 'define variable' expression.
+ *
+ * A 'define' directive is final, any qualifiers must preceed it. So, we just
+ * have to extract the variable name now, well and find the corresponding
+ * 'endef'.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifiers.
+ */
+static int kmk_cc_eval_do_var_define(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft, unsigned fQualifiers)
+{
+ /*
+ * Now comes the variable name. It may optionally be followed by an
+ * assignment operator to indicate what kind of variable is being defined.
+ */
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ unsigned cWords = cchLeft ? kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft) : 0;
+ if (cWords >= 1)
+ {
+ /*
+ * Check for variable assignment operator. Kind of tedious...
+ */
+ KMKCCEVALINSTR enmOpcode;
+ PKMKCCEVALWORD pVarWord = pCompiler->paWords;
+ if ( cWords == 1
+ && ( pVarWord->cchWord == 0
+ || pVarWord->pchWord[pVarWord->cchWord - 1] != '='))
+ enmOpcode = kKmkCcEvalInstr_define_recursive; /* very likely */
+ else if ( pVarWord->cchWord > 0
+ && pVarWord->pchWord[pVarWord->cchWord - 1] == '=')
+ {
+ if (pVarWord->cchWord == 1)
+ enmOpcode = kKmkCcEvalInstr_define_recursive;
+ else
+ {
+ char chPenultimate = pVarWord->pchWord[pVarWord->cchWord - 2];
+ if (chPenultimate == '?')
+ enmOpcode = kKmkCcEvalInstr_define_if_new;
+ else if (chPenultimate == ':')
+ enmOpcode = kKmkCcEvalInstr_assign_simple;
+ else if (chPenultimate == '+')
+ enmOpcode = kKmkCcEvalInstr_assign_append;
+ else if (chPenultimate == '<')
+ enmOpcode = kKmkCcEvalInstr_assign_prepend;
+ else
+ enmOpcode = kKmkCcEvalInstr_define_recursive;
+ }
+ pVarWord->cchWord -= enmOpcode == kKmkCcEvalInstr_define_recursive ? 1 : 2;
+ if (cWords > 1)
+ kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[1].pchWord,
+ "Bogus stuff after 'define' variable name and assignment operator");
+ }
+ else
+ {
+ PCKMKCCEVALWORD pOpWord = &pCompiler->paWords[1];
+ KMK_CC_ASSERT(cWords > 1);
+ if ( pOpWord->cchWord == 1
+ && pOpWord->pchWord[0] == '=')
+ enmOpcode = kKmkCcEvalInstr_define_recursive;
+ else if ( pOpWord->cchWord == 2
+ && pOpWord->pchWord[1] == '=')
+ {
+ char chFirst = pVarWord->pchWord[0];
+ if (chFirst == '?')
+ enmOpcode = kKmkCcEvalInstr_define_if_new;
+ else if (chFirst == ':')
+ enmOpcode = kKmkCcEvalInstr_assign_simple;
+ else if (chFirst == '+')
+ enmOpcode = kKmkCcEvalInstr_assign_append;
+ else if (chFirst == '<')
+ enmOpcode = kKmkCcEvalInstr_assign_prepend;
+ else
+ kmk_cc_eval_fatal(pCompiler, pOpWord->pchWord, "Bogus stuff after 'define' variable name");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pOpWord->pchWord, "Bogus stuff after 'define' variable name");
+ if (cWords > 2)
+ kmk_cc_eval_fatal(pCompiler, pCompiler->paWords[2].pchWord,
+ "Bogus stuff after 'define' variable name and assignment operator");
+ }
+
+ /*
+ * The variable name must not be empty.
+ */
+ if (pVarWord->cchWord)
+ {
+ int const fPlainVarNm = pVarWord->enmToken == kKmkCcEvalToken_WordPlain;
+ const char * pchVarNm = pVarWord->pchWord;
+ size_t cchVarNm = pVarWord->cchWord;
+ PKMKCCEVALASSIGN pInstr;
+ size_t cchValue;
+ const char *pszValue;
+ int fPlainValue;
+
+ if ( enmOpcode == kKmkCcEvalInstr_define_recursive
+ || enmOpcode == kKmkCcEvalInstr_define_if_new)
+ {
+ PKMKCCEVALASSIGNDEF pInstrDef;
+ pInstrDef = (PKMKCCEVALASSIGNDEF)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstrDef));
+ pInstr = &pInstrDef->AssignCore;
+ pInstrDef->pEvalProg = NULL; /** @todo consider this later at some point, need some trial and error approach. */
+ }
+ else
+ pInstr = (PKMKCCEVALASSIGN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+
+ pInstr->Core.enmOpcode = enmOpcode;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->fExport = (fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT) != 0;
+ pInstr->fOverride = (fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE) != 0;
+ pInstr->fPrivate = (fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE) != 0;
+ pInstr->fLocal = (fQualifiers & KMK_CC_EVAL_QUALIFIER_LOCAL) != 0;
+
+ cchValue = kmk_cc_eval_parse_define_value(pCompiler, &fPlainValue);
+ pszValue = kmk_cc_eval_strdup_prepped(pCompiler, cchValue);
+ if (fPlainVarNm)
+ pchVarNm = strcache2_add(&variable_strcache, pchVarNm, cchVarNm);
+ else
+ {
+/** @todo fix work copying. */
+// pCompiler->iEscEol = iEscEolVarNm;
+ cchVarNm = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchVarNm, cchVarNm);
+ pchVarNm = kmk_cc_eval_strdup_prepped(pCompiler, cchVarNm);
+ }
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ KMK_CC_EVAL_DPRINTF(("%s: define '%s'\n%s\nendef\n", g_apszEvalInstrNms[enmOpcode], pchVarNm, pszValue));
+
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Variable, pchVarNm, cchVarNm, fPlainVarNm);
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Value, pszValue, cchValue, fPlainValue);
+
+ pInstr->pNext = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Empty variable name after 'define'");
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected variable name after 'define'");
+ return 1;
+}
+
+
+/**
+ * Emits a 'expand(-and-eval)' instruction.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchSubprog The subprogram that needs expanding.
+ * @param cchSubprog The length of the subprogram.
+ * @param iEscEol The escaped EOL index corresponding to pchSubprog.
+ */
+static int kmk_cc_eval_emit_expand(PKMKCCEVALCOMPILER pCompiler, const char *pchSubprog, size_t cchSubprog, unsigned iEscEolVarNm)
+{
+ /*
+ * We're unexporting all variables.
+ */
+ size_t cchCopy;
+ char *pszCopy;
+ PKMKCCEVALEXPAND pInstr;
+
+ pCompiler->iEscEol = iEscEolVarNm;
+ cchCopy = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchSubprog, cchSubprog);
+ pszCopy = kmk_cc_eval_strdup_prepped(pCompiler, cchCopy);
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+
+ pInstr = (PKMKCCEVALEXPAND)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = kKmkCcEvalInstr_expand;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->uPadding = 0;
+ /** @todo Make the subprogram embed necessary strings. */
+ kmk_cc_eval_compile_string_exp_subprog(pCompiler, pszCopy, cchCopy, &pInstr->Subprog);
+
+ return 1;
+}
+
+
+static int kmk_cc_eval_handle_assignment_or_recipe(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
+ unsigned fQualifiers)
+{
+ /*
+ * We're currently at a word which may or may not be a variable name
+ * followed by an assignment operator, alternatively it must be a recipe.
+ * We need to figure this out and deal with it in the most efficient
+ * manner as this is a very common occurence.
+ */
+ unsigned const iEscEolVarNm = pCompiler->iEscEol;
+ int fPlainVarNm = 1;
+ const char *pchVarNm = pchWord;
+ size_t cchVarNm;
+ size_t cch = 0;
+ size_t cchSubprog = 0;
+ char ch;
+
+ /*
+ * The variable name. Complicate by there being no requirement of a space
+ * preceeding the assignment operator, as well as that the variable name
+ * may include variable references with spaces (function++) in them.
+ */
+ for (;;)
+ {
+ if (cch < cchLeft)
+ { /*likely*/ }
+ else
+ {
+ /* Single word, join paths with word + whitespace. */
+ KMK_CC_ASSERT(cch == cchLeft);
+ cchVarNm = cch;
+ pchWord += cch;
+ cchLeft -= cch;
+ break;
+ }
+
+ ch = pchWord[cch];
+ if (!KMK_CC_EVAL_IS_SPACE_DOLLAR_SLASH_OR_ASSIGN(ch))
+ cch++;
+ /* Space? */
+ else if (KMK_CC_EVAL_IS_SPACE(ch))
+ {
+ cchVarNm = cch;
+ pchWord += cch;
+ cchLeft -= cch;
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ break;
+ }
+ /* Variable expansion may contain spaces, so handle specially. */
+ else if (ch == '$')
+ {
+ size_t const offStart = cch;
+ cch = kmk_cc_eval_parse_var_exp(pCompiler, pchWord, cchLeft, cch);
+ cchSubprog += cch - offStart;
+ fPlainVarNm = 0;
+ }
+ /* Check out potential recipe, simple assignment or DOS drive letter separator. */
+ else if (ch == ':')
+ {
+ if ( cch + 1 < cchLeft
+ && pchWord[cch + 1] != '=')
+ {
+ cchVarNm = cch;
+ pchWord += cch;
+ cchLeft -= cch;
+ break;
+ }
+#ifdef HAVE_DOS_PATHS
+ /* Don't confuse the first colon in:
+ C:/Windows/System32/Kernel32.dll: C:/Windows/System32/NtDll.dll
+ for a recipe, it is only the second one which counts. */
+ if ( cch == 1
+ && isalpha((unsigned char)pchWord[0]))
+ cch++;
+ else
+#endif
+ if (!fQualifiers)
+ return kmk_cc_eval_handle_recipe_cont_colon(pCompiler, pchWord, cch,
+ fPlainVarNm
+ ? kKmkCcEvalToken_WordPlain : kKmkCcEvalToken_WordWithDollar,
+ pchWord + cch, cchLeft - cch);
+ /** @todo we may have words already preparsed here so restarting is easy... */
+ return 0; /* retry with preceeding keywords */
+ }
+ /* Check out assignment operator. */
+ else if (ch == '=')
+ {
+ if (cch > 0)
+ {
+ char chPrev = pchWord[cch - 1];
+ if (chPrev == ':' || chPrev == '+' || chPrev == '?' || chPrev == '<')
+ cch--;
+ cchVarNm = cch;
+ pchWord += cch;
+ cchLeft -= cch;
+ break;
+ }
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Empty variable name.");
+ }
+ /* Check out potential escaped EOL sequence. */
+ else if (ch == '\\')
+ {
+ unsigned const iEscEol = pCompiler->iEscEol;
+ if (iEscEol >= pCompiler->cEscEols)
+ cch++;
+ else
+ {
+ size_t offCur = &pchWord[cch] - pCompiler->pszContent;
+ if (offCur < pCompiler->paEscEols[iEscEol].offEol)
+ cch++;
+ else
+ {
+ cchVarNm = cch;
+ KMK_CC_ASSERT(offCur == pCompiler->paEscEols[iEscEol].offEol);
+ cch = pCompiler->paEscEols[iEscEol].offEol + pCompiler->cchEolSeq - offCur;
+ pCompiler->iEscEol = iEscEol + 1;
+ pchWord += cch;
+ cchLeft -= cch;
+ KMK_CC_EVAL_SKIP_SPACES(pCompiler, pchWord, cchLeft);
+ break;
+ }
+ }
+ }
+ else
+ KMK_CC_ASSERT(0);
+ }
+
+ /*
+ * Check for assignment operator.
+ */
+ if (cchLeft)
+ {
+ size_t cchValue;
+ PKMKCCEVALASSIGN pInstr;
+ KMKCCEVALINSTR enmOpcode;
+ int fPlainValue;
+ char *pszValue;
+
+ ch = *pchWord;
+ if (ch == '=')
+ {
+ enmOpcode = kKmkCcEvalInstr_assign_recursive;
+ pchWord++;
+ cchLeft--;
+ }
+ else if (cchLeft >= 2 && pchWord[1] == '=')
+ {
+ if (ch == ':')
+ enmOpcode = kKmkCcEvalInstr_assign_simple;
+ else if (ch == '+')
+ enmOpcode = kKmkCcEvalInstr_assign_append;
+ else if (ch == '<')
+ enmOpcode = kKmkCcEvalInstr_assign_prepend;
+ else if (ch == '?')
+ enmOpcode = kKmkCcEvalInstr_assign_if_new;
+ else if (!fQualifiers)
+ return kmk_cc_eval_handle_recipe_cont_2nd_word(pCompiler, pchVarNm, cchVarNm, pchWord, cchLeft);
+ else
+ return 0; /* retry without preceding keywords */
+ pchWord += 2;
+ cchLeft -= 2;
+ }
+ else if (!fQualifiers)
+ return kmk_cc_eval_handle_recipe_cont_2nd_word(pCompiler, pchVarNm, cchVarNm, pchWord, cchLeft);
+ else
+ return 0; /* retry without preceding keywords */
+
+ /*
+ * Skip leading spaces, if any and prep the value for copying.
+ */
+ KMK_CC_EVAL_SKIP_SPACES(pCompiler, pchWord, cchLeft);
+ cchValue = kmk_cc_eval_prep_normal_line(pCompiler, pchWord, cchLeft);
+ fPlainValue = memchr(pchWord, '$', cchLeft) == NULL;
+
+ /*
+ * Emit the instruction.
+ */
+ kmk_cc_eval_end_of_recipe(pCompiler, pchWord);
+
+ pInstr = (PKMKCCEVALASSIGN)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->Core.enmOpcode = enmOpcode;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->fExport = (fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT) != 0;
+ pInstr->fOverride = (fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE) != 0;
+ pInstr->fPrivate = (fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE) != 0;
+ pInstr->fLocal = (fQualifiers & KMK_CC_EVAL_QUALIFIER_LOCAL) != 0;
+
+ /* We copy the value before messing around with the variable name since
+ we have to do more iEolEsc saves & restores the other way around. */
+ pszValue = kmk_cc_eval_strdup_prepped(pCompiler, cchValue);
+ if (fPlainVarNm)
+ pchVarNm = strcache2_add(&variable_strcache, pchVarNm, cchVarNm);
+ else
+ {
+ pCompiler->iEscEol = iEscEolVarNm;
+ cchVarNm = kmk_cc_eval_prep_normal_line_ex(pCompiler, pchVarNm, cchVarNm);
+ pchVarNm = kmk_cc_eval_strdup_prepped(pCompiler, cchVarNm);
+ }
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ KMK_CC_EVAL_DPRINTF(("%s: '%s' '%s'\n", g_apszEvalInstrNms[enmOpcode], pchVarNm, pszValue));
+
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Variable, pchVarNm, cchVarNm, fPlainVarNm);
+ kmk_cc_eval_init_subprogram_or_plain(pCompiler, &pInstr->Value, pszValue, cchValue, fPlainValue);
+
+ pInstr->pNext = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(*pCompiler->ppBlockTail);
+ }
+ /*
+ * This could be one or more function calls.
+ */
+ else if (!fPlainVarNm && cchVarNm == cchSubprog && fQualifiers == 0)
+ return kmk_cc_eval_emit_expand(pCompiler, pchVarNm, cchVarNm, iEscEolVarNm);
+ else if (!fPlainVarNm)
+ kmk_cc_eval_fatal(pCompiler, pchWord,
+ "Not variable assignment. Could be a complicated indirect recipe definition, however that's currently not supported.");
+ else
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Neither recipe nor variable assignment");
+ return 1;
+}
+
+
+/**
+ * Parses a 'local [override] variable = value', 'local define variable', and
+ * 'local undefine variable [...]' expressions.
+ *
+ * The 'local' directive must be first and it does not permit any qualifiers at
+ * the moment. Should any be added later, they will have to come after 'local'.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'local'.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifiers.
+ */
+static int kmk_cc_eval_do_var_local(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ /*
+ * Check for 'local define' and 'local undefine'
+ */
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6)) /* final */
+ return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft + 6, KMK_CC_EVAL_QUALIFIER_LOCAL);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8)) /* final */
+ return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft + 8, KMK_CC_EVAL_QUALIFIER_LOCAL);
+
+ /*
+ * Simpler to just join paths with the rest here, even if we could
+ * probably optimize the parsing a little if we liked.
+ */
+ return kmk_cc_eval_handle_assignment_or_recipe(pCompiler, pchWord, cchLeft, KMK_CC_EVAL_QUALIFIER_LOCAL);
+ }
+ kmk_cc_eval_fatal(pCompiler, pchWord, "Expected variable name, assignment operator and value after 'local'");
+ return 1;
+}
+
+
+/**
+ * We've found one variable qualification keyword, now continue parsing and see
+ * if this is some kind of variable assignment expression or not.
+ *
+ * @returns 1 if variable assignment, 0 if not.
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after the first qualifier.
+ * @param cchLeft The number of chars left to parse on this line.
+ * @param fQualifiers The qualifier.
+ */
+static int kmk_cc_eval_try_handle_var_with_keywords(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft,
+ unsigned fQualifiers)
+{
+ for (;;)
+ {
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+ if (cchLeft)
+ {
+ char ch = *pchWord;
+ if (KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(ch))
+ {
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6)) /* final */
+ return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft - 6, fQualifiers);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8)) /* final */
+ return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft -86, fQualifiers);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8)) /* final */
+ return kmk_cc_eval_do_var_unexport(pCompiler, pchWord + 8, cchLeft - 8, fQualifiers);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6))
+ {
+ if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_EXPORT))
+ fQualifiers |= KMK_CC_EVAL_QUALIFIER_EXPORT;
+ else
+ kmk_cc_eval_warn(pCompiler, pchWord, "'export' qualifier repeated");
+ pchWord += 6;
+ cchLeft -= 6;
+ continue;
+ }
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8))
+ {
+ if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_OVERRIDE))
+ fQualifiers |= KMK_CC_EVAL_QUALIFIER_OVERRIDE;
+ else
+ kmk_cc_eval_warn(pCompiler, pchWord, "'override' qualifier repeated");
+ pchWord += 8;
+ cchLeft -= 8;
+ continue;
+ }
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7))
+ {
+ if (!(fQualifiers & KMK_CC_EVAL_QUALIFIER_PRIVATE))
+ fQualifiers |= KMK_CC_EVAL_QUALIFIER_PRIVATE;
+ else
+ kmk_cc_eval_warn(pCompiler, pchWord, "'private' qualifier repeated");
+ pchWord += 7;
+ cchLeft -= 7;
+ continue;
+ }
+ }
+
+ /*
+ * Not a keyword, likely variable name followed by an assignment
+ * operator and a value. Do a rough check for the assignment operator
+ * and join paths with the unqualified assignment handling code.
+ */
+ {
+ const char *pchEqual = (const char *)memchr(pchWord, '=', cchLeft);
+ if (pchEqual)
+ return kmk_cc_eval_handle_assignment_or_recipe(pCompiler, pchWord, cchLeft, fQualifiers);
+ }
+ return 0;
+ }
+ else
+ kmk_cc_eval_fatal(pCompiler, NULL,
+ "Expected assignment operator or variable directive after variable qualifier(s)\n");
+ }
+}
+
+
+/**
+ * Parses 'export [variable]' and 'export [qualifiers] variable = value'
+ * expressions.
+ *
+ * When we find the 'export' directive at the start of a line, we need to
+ * continue parsing with till we can tell the difference between the two forms.
+ *
+ * @returns 1 to indicate we've handled a keyword (see
+ * kmk_cc_eval_try_handle_keyword).
+ * @param pCompiler The compiler state.
+ * @param pchWord First char after 'define'.
+ * @param cchLeft The number of chars left to parse on this line.
+ */
+static int kmk_cc_eval_handle_var_export(PKMKCCEVALCOMPILER pCompiler, const char *pchWord, size_t cchLeft)
+{
+ KMK_CC_EVAL_SKIP_SPACES_AFTER_WORD(pCompiler, pchWord, cchLeft);
+
+ if (cchLeft)
+ {
+ unsigned iSavedEscEol;
+ unsigned cWords;
+
+ /*
+ * We need to figure out whether this is an assignment or a export statement,
+ * in the latter case join paths with 'export' and 'undefine'.
+ */
+ const char *pchEqual = (const char *)memchr(pchWord, '=', cchLeft);
+ if (!pchEqual)
+ return kmk_cc_eval_do_with_variable_list(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_export, 0 /*fQualifiers*/);
+
+ /*
+ * Found an '=', could be an assignment. Let's take the easy way out
+ * and just parse the whole statement into words like we would do if
+ * it wasn't an assignment, and then check the words out for
+ * assignment keywords and operators.
+ */
+ iSavedEscEol = pCompiler->iEscEol;
+ cWords = kmk_cc_eval_parse_words(pCompiler, pchWord, cchLeft);
+ if (cWords)
+ {
+ PKMKCCEVALVARIABLES pInstr;
+ PKMKCCEVALWORD pWord = pCompiler->paWords;
+ unsigned iWord = 0;
+ while (iWord < cWords)
+ {
+ /* Trailing assignment operator or terminal assignment directive ('undefine'
+ and 'unexport' makes no sense here but GNU make ignores that). */
+ if ( ( pWord->cchWord > 1
+ && pWord->pchWord[pWord->cchWord - 1] == '=')
+ || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "define", 6)
+ || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "undefine", 8)
+ || KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "unexport", 8) )
+ {
+ pCompiler->iEscEol = iSavedEscEol;
+ return kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord, cchLeft, KMK_CC_EVAL_QUALIFIER_EXPORT);
+ }
+
+ /* If not a variable assignment qualifier, it must be a variable name
+ followed by an assignment operator. */
+ if (iWord + 1 < cWords)
+ {
+ if ( !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "export", 6)
+ && !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "private", 7)
+ && !KMK_CC_STRCMP_CONST(pWord->pchWord, pWord->cchWord, "override", 8))
+ {
+ pWord++;
+ if ( pWord->cchWord > 0
+ && ( pWord->pchWord[0] == '='
+ || ( pWord->cchWord > 1
+ && pWord->pchWord[1] == '='
+ && ( pWord->pchWord[0] == ':'
+ || pWord->pchWord[0] == '+'
+ || pWord->pchWord[0] == '?'
+ || pWord->pchWord[0] == '<') ) ) )
+ {
+ pCompiler->iEscEol = iSavedEscEol;
+ return kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord, cchLeft,
+ KMK_CC_EVAL_QUALIFIER_EXPORT);
+ }
+ break;
+ }
+ }
+ else
+ break;
+ /* next */
+ pWord++;
+ iWord++;
+ }
+
+ /*
+ * It's not an assignment.
+ * (This is the same as kmk_cc_eval_do_with_variable_list does.)
+ */
+ pInstr = (PKMKCCEVALVARIABLES)kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, KMKCCEVALVARIABLES_SIZE(cWords));
+ pInstr->Core.enmOpcode = kKmkCcEvalInstr_export;
+ pInstr->Core.iLine = pCompiler->iLine;
+ pInstr->cVars = cWords;
+ kmk_cc_eval_init_spp_array_from_duplicated_words(pCompiler, cWords, pCompiler->paWords, pInstr->aVars);
+ kmk_cc_block_realign(pCompiler->ppBlockTail);
+ }
+ else
+ KMK_CC_ASSERT(0);
+ }
+ else
+ {
+ /*
+ * We're exporting all variables.
+ */
+ PKMKCCEVALCORE pInstr = kmk_cc_block_alloc_eval(pCompiler->ppBlockTail, sizeof(*pInstr));
+ pInstr->enmOpcode = kKmkCcEvalInstr_export_all;
+ pInstr->iLine = pCompiler->iLine;
+ }
+ return 1;
+}
+
+
+/**
+ * When entering this function we know that the first two character in the first
+ * word both independently occurs in keywords.
+ *
+ * @returns 1 if make directive or qualified variable assignment, 0 if neither.
+ * @param pCompiler The compiler state.
+ * @param ch The first char.
+ * @param pchWord Pointer to the first word.
+ * @param cchLeft Number of characters left to parse starting at
+ * @a cchLeft.
+ */
+int kmk_cc_eval_try_handle_keyword(PKMKCCEVALCOMPILER pCompiler, char ch, const char *pchWord, size_t cchLeft)
+{
+ unsigned iSavedEscEol = pCompiler->iEscEol;
+
+ KMK_CC_ASSERT(cchLeft >= 2);
+ KMK_CC_ASSERT(ch == pchWord[0]);
+ KMK_CC_ASSERT(KMK_CC_EVAL_IS_1ST_IN_KEYWORD(pchWord[0]));
+ KMK_CC_ASSERT(KMK_CC_EVAL_IS_2ND_IN_KEYWORD(pchWord[1]));
+
+ /*
+ * If it's potentially a variable related keyword, check that out first.
+ */
+ if (KMK_CC_EVAL_IS_1ST_IN_VARIABLE_KEYWORD(ch))
+ {
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "local", 5))
+ return kmk_cc_eval_do_var_local(pCompiler, pchWord + 5, cchLeft - 5);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6))
+ return kmk_cc_eval_do_var_define(pCompiler, pchWord + 6, cchLeft - 6, 0);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6))
+ return kmk_cc_eval_handle_var_export(pCompiler, pchWord + 6, cchLeft - 6);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8))
+ return kmk_cc_eval_do_var_undefine(pCompiler, pchWord + 8, cchLeft - 8, 0);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8))
+ return kmk_cc_eval_do_var_unexport(pCompiler, pchWord + 8, cchLeft - 8, 0);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8))
+ {
+ if (kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord + 8, cchLeft - 8, KMK_CC_EVAL_QUALIFIER_OVERRIDE))
+ return 1;
+ pCompiler->iEscEol = iSavedEscEol;
+ }
+ else if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7))
+ {
+ if (kmk_cc_eval_try_handle_var_with_keywords(pCompiler, pchWord + 7, cchLeft - 7, KMK_CC_EVAL_QUALIFIER_PRIVATE))
+ return 1;
+ pCompiler->iEscEol = iSavedEscEol;
+ }
+ }
+
+ /*
+ * Check out the other keywords.
+ */
+ if (ch == 'i') /* Lots of directives starting with 'i'. */
+ {
+ char ch2 = pchWord[1];
+ pchWord += 2;
+ cchLeft -= 2;
+
+ /* 'if...' */
+ if (ch2 == 'f')
+ {
+ if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
+ return kmk_cc_eval_do_if(pCompiler, pchWord, cchLeft, 0 /* in else */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "eq", 2))
+ return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 2, cchLeft - 2, 0 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "def", 3))
+ return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "neq", 3))
+ return kmk_cc_eval_do_ifeq( pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 0 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "1of", 3))
+ return kmk_cc_eval_do_if1of(pCompiler, pchWord + 3, cchLeft - 3, 0 /* in else */, 1 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "ndef", 4))
+ return kmk_cc_eval_do_ifdef(pCompiler, pchWord + 4, cchLeft - 4, 0 /* in else */, 0 /* positive */);
+
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "n1of", 4))
+ return kmk_cc_eval_do_if1of(pCompiler, pchWord + 4, cchLeft - 4, 0 /* in else */, 0 /* positive */);
+ }
+ /* include... */
+ else if (ch2 == 'n' && cchLeft >= 5 && KMK_CC_WORD_COMP_CONST_5(pchWord, "clude") ) /* 'in...' */
+ {
+ pchWord += 5;
+ cchLeft -= 5;
+ if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
+ return kmk_cc_eval_do_include(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_include);
+ if (cchLeft >= 3 && KMK_CC_WORD_COMP_CONST_3(pchWord, "dep"))
+ {
+ pchWord += 3;
+ cchLeft -= 3;
+ if (KMK_CC_EVAL_WORD_COMP_IS_EOL(pCompiler, pchWord, cchLeft))
+ return kmk_cc_eval_do_include(pCompiler, pchWord, cchLeft, kKmkCcEvalInstr_includedep);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-queue", 6))
+ return kmk_cc_eval_do_include(pCompiler, pchWord + 6, cchLeft - 6, kKmkCcEvalInstr_includedep_queue);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-flush", 6))
+ return kmk_cc_eval_do_include(pCompiler, pchWord + 6, cchLeft - 6, kKmkCcEvalInstr_includedep_flush);
+ }
+ }
+ }
+ else if (ch == 'e') /* A few directives starts with 'e'. */
+ {
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "else", 4))
+ return kmk_cc_eval_do_else(pCompiler, pchWord + 4, cchLeft - 4);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "endif", 5))
+ return kmk_cc_eval_do_endif(pCompiler, pchWord + 5, cchLeft - 5);
+ /* export and endef are handled elsewhere, though stray endef's may end up here... */
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "export", 6));
+
+ }
+ else /* the rest. */
+ {
+ if ( KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "sinclude", 8)
+ || KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "-include", 8))
+ return kmk_cc_eval_do_include(pCompiler, pchWord + 8, cchLeft - 8, kKmkCcEvalInstr_include_silent);
+ if (KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "vpath", 5))
+ return kmk_cc_eval_do_vpath(pCompiler, pchWord + 5, cchLeft - 5);
+
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "local", 5));
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "define", 6));
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "private", 7));
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "override", 8));
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "unexport", 8));
+ KMK_CC_ASSERT(!KMK_CC_EVAL_WORD_COMP_CONST(pCompiler, pchWord, cchLeft, "undefine", 8));
+ }
+
+ pCompiler->iEscEol = iSavedEscEol;
+ return 0;
+}
+
+
+
+
+static int kmk_cc_eval_compile_worker(PKMKCCEVALPROG pEvalProg, const char *pszContent, size_t cchContent, unsigned iLine)
+{
+ const char *pchTmp;
+
+ /*
+ * Compiler state.
+ */
+ KMKCCEVALCOMPILER Compiler;
+ kmk_cc_eval_init_compiler(&Compiler, pEvalProg, iLine, pszContent, cchContent);
+ KMK_CC_EVAL_DPRINTF(("\nkmk_cc_eval_compile_worker - begin (%s/%s/%d)\n", pEvalProg->pszFilename, pEvalProg->pszVarName, iLine));
+
+ {
+ /*
+ * Line state.
+ */
+ size_t cchLine; /* The length of the current line (w/o comments). */
+ size_t offNext = 0; /* The offset of the next line. */
+ size_t off = 0; /* The offset into pszContent of the current line. */
+
+ /* Try for some register/whatever optimzations. */
+ int const chFirstEol = Compiler.chFirstEol;
+ size_t const cchEolSeq = Compiler.cchEolSeq;
+
+ /*
+ * Process input lines.
+ *
+ * The code here concerns itself with getting the next line in an efficient
+ * manner, very basic classification and trying out corresponding handlers.
+ * The real work is done in the handlers.
+ */
+ while (offNext < cchContent)
+ {
+ size_t offFirstWord;
+
+ /*
+ * Find the end of the next line.
+ */
+ KMK_CC_ASSERT(off == offNext);
+
+ /* Simple case: No escaped EOL, nor the end of the input. */
+ pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
+ if ( pchTmp
+ && ( &pszContent[offNext] == pchTmp
+ || pchTmp[-1] != '\\') )
+ {
+ if ( cchEolSeq == 1
+ || pchTmp[1] == Compiler.chSecondEol)
+ {
+ /* Frequent: Blank line. */
+ if (&pszContent[offNext] == pchTmp)
+ {
+ KMK_CC_EVAL_DPRINTF(("#%03u: <empty>\n", Compiler.iLine));
+ Compiler.iLine++;
+ off = offNext += cchEolSeq;
+ continue;
+ }
+ if (pszContent[offNext] == '#')
+ {
+ KMK_CC_EVAL_DPRINTF(("#%03u: <comment-col-0>\n", Compiler.iLine));
+ Compiler.iLine++;
+ offNext = pchTmp - pszContent;
+ off = offNext += cchEolSeq;
+ continue;
+ }
+
+ offNext = pchTmp - pszContent;
+ cchLine = offNext - off;
+
+ offFirstWord = off;
+ while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+
+ offNext += cchEolSeq;
+ Compiler.cEscEols = 0;
+ Compiler.iEscEol = 0;
+ }
+ else
+ kmk_cc_eval_fatal_eol(&Compiler, pchTmp, Compiler.iLine, off);
+ }
+ /* The complicated, less common cases. */
+ else
+ {
+ Compiler.cEscEols = 0;
+ Compiler.iEscEol = 0;
+ offFirstWord = offNext;
+ for (;;)
+ {
+ if (pchTmp)
+ {
+ if ( cchEolSeq == 1
+ || pchTmp[1] == Compiler.chSecondEol)
+ {
+ size_t offEsc;
+ if (offFirstWord != offNext)
+ offNext = pchTmp - pszContent;
+ else
+ {
+ offNext = pchTmp - pszContent;
+ while (offFirstWord < offNext && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+ }
+
+
+ /* Is it an escape sequence? */
+ if ( !offNext
+ || pchTmp[-1] != '\\')
+ {
+ cchLine = offNext - off;
+ offNext += cchEolSeq;
+ break;
+ }
+ if (offNext < 2 || pchTmp[-2] != '\\')
+ offEsc = offNext - 1;
+ else
+ {
+ /* Count how many backslashes there are. Must be odd number to be an escape
+ sequence. Normally we keep half of them, except for command lines. */
+ size_t cSlashes = 2;
+ while (offNext >= cSlashes && pchTmp[0 - cSlashes] == '\\')
+ cSlashes--;
+ if (!(cSlashes & 1))
+ {
+ cchLine = offNext - off;
+ offNext += cchEolSeq;
+ break;
+ }
+ offEsc = offNext - (cSlashes >> 1);
+ }
+
+ /* Record it. */
+ if (Compiler.cEscEols < Compiler.cEscEolsAllocated) { /* likely */ }
+ else
+ {
+ KMK_CC_ASSERT(Compiler.cEscEols == Compiler.cEscEolsAllocated);
+ Compiler.cEscEolsAllocated = Compiler.cEscEolsAllocated
+ ? Compiler.cEscEolsAllocated * 2 : 2;
+ Compiler.paEscEols = (PKMKCCEVALESCEOL)xrealloc(Compiler.paEscEols,
+ Compiler.cEscEolsAllocated
+ * sizeof(Compiler.paEscEols[0]));
+ }
+ Compiler.paEscEols[Compiler.cEscEols].offEsc = offEsc;
+ Compiler.paEscEols[Compiler.cEscEols].offEol = offNext;
+ Compiler.cEscEols++;
+
+ /* Advance. */
+ offNext += cchEolSeq;
+ if (offFirstWord == offEsc)
+ {
+ offFirstWord = offNext;
+ Compiler.iEscEol++;
+ }
+ }
+ else
+ kmk_cc_eval_fatal_eol(&Compiler, pchTmp, Compiler.iLine, off);
+ }
+ else
+ {
+ /* End of input. Happens only once per compilation, nothing to optimize for. */
+ if (offFirstWord == offNext)
+ while (offFirstWord < cchContent && KMK_CC_EVAL_IS_SPACE(pszContent[offFirstWord]))
+ offFirstWord++;
+ offNext = cchContent;
+ cchLine = cchContent - off;
+ break;
+ }
+ pchTmp = (const char *)memchr(&pszContent[offNext], chFirstEol, cchContent - offNext);
+ }
+ }
+ KMK_CC_ASSERT(offNext <= cchContent);
+ KMK_CC_ASSERT(offNext >= off + cchLine);
+ KMK_CC_ASSERT(off + cchLine <= cchContent && cchLine <= cchContent);
+ KMK_CC_ASSERT(offFirstWord <= off + cchLine);
+ KMK_CC_ASSERT(offFirstWord >= off);
+ KMK_CC_ASSERT(pszContent[offFirstWord] != ' ' && pszContent[offFirstWord] != '\t');
+
+ KMK_CC_EVAL_DPRINTF(("#%03u: %*.*s\n", Compiler.iLine, (int)cchLine, (int)cchLine, &pszContent[off]));
+
+ /*
+ * Skip blank lines.
+ */
+ if (offFirstWord < off + cchLine)
+ {
+ /*
+ * Command? Ignore command prefix if no open recipe (SunOS 4 behavior).
+ */
+ if ( pszContent[off] == Compiler.chCmdPrefix
+ && (Compiler.pRecipe || Compiler.fNoTargetRecipe))
+ {
+ if (!Compiler.fNoTargetRecipe)
+ kmk_cc_eval_handle_command(&Compiler, &pszContent[off], cchLine);
+ }
+ /*
+ * Since it's not a command line, we can now skip comment lines
+ * even with a tab indentation. If it's not a comment line, we
+ * tentatively strip any trailing comment.
+ */
+ else if (pszContent[offFirstWord] != '#')
+ {
+ const char *pchWord = &pszContent[offFirstWord];
+ size_t cchLeft = off + cchLine - offFirstWord;
+ char ch;
+
+ Compiler.cchLineWithComments = cchLine;
+ pchTmp = (const char *)memchr(pchWord, '#', cchLeft);
+ if (pchTmp)
+ {
+ cchLeft = pchTmp - pchWord;
+ cchLine = pchTmp - &pszContent[off];
+ }
+ Compiler.cchLine = cchLine; /** @todo only used by assertions. */
+ Compiler.offLine = off; /** @todo only used by fatal errors. */
+
+#ifdef KMK_CC_STRICT
+ Compiler.cWords = 0x424242;
+#endif
+
+ /*
+ * If not a directive or variable qualifier, it's either a variable
+ * assignment or a recipe.
+ */
+ ch = *pchWord;
+ if ( !KMK_CC_EVAL_IS_1ST_IN_KEYWORD(ch)
+ || !KMK_CC_EVAL_IS_2ND_IN_KEYWORD(pchWord[1]))
+ {
+ if (memchr(pchWord, '=', cchLeft))
+ kmk_cc_eval_handle_assignment_or_recipe(&Compiler, pchWord, cchLeft, 0 /*fQualifiers*/);
+ else
+ kmk_cc_eval_handle_recipe(&Compiler, pchWord, cchLeft);
+ }
+ else
+ {
+ /* Possible directive or variable qualifier. */
+ Compiler.offNext = offNext;
+ if (kmk_cc_eval_try_handle_keyword(&Compiler, ch, pchWord, cchLeft))
+ offNext = Compiler.offNext;
+ /* No, that wasn't it... */
+ else if (memchr(pchWord, '=', cchLeft))
+ kmk_cc_eval_handle_assignment_or_recipe(&Compiler, pchWord, cchLeft, 0 /*fQualifiers*/);
+ else
+ kmk_cc_eval_handle_recipe(&Compiler, pchTmp, pchWord, cchLeft);
+ }
+ }
+ }
+
+ /*
+ * Advance to the next line.
+ */
+ off = offNext;
+ Compiler.iLine += Compiler.cEscEols + 1;
+ }
+ }
+
+ /*
+ * Check whether
+ */
+
+ kmk_cc_eval_delete_compiler(&Compiler);
+ KMK_CC_EVAL_DPRINTF(("kmk_cc_eval_compile_worker - done (%s/%s)\n\n", pEvalProg->pszFilename, pEvalProg->pszVarName));
+ return 0;
+}
+
+
+
+static PKMKCCEVALPROG kmk_cc_eval_compile(const char *pszContent, size_t cchContent,
+ const char *pszFilename, unsigned iLine, const char *pszVarName)
+{
+ /*
+ * Estimate block size, allocate one and initialize it.
+ */
+ PKMKCCEVALPROG pEvalProg;
+ PKMKCCBLOCK pBlock;
+ pEvalProg = kmk_cc_block_alloc_first(&pBlock, sizeof(*pEvalProg), cchContent / 32); /** @todo adjust */
+ if (pEvalProg)
+ {
+ pEvalProg->pBlockTail = pBlock;
+ pEvalProg->pFirstInstr = (PKMKCCEVALCORE)kmk_cc_block_get_next_ptr(pBlock);
+ pEvalProg->pszFilename = pszFilename ? pszFilename : "<unknown>";
+ pEvalProg->pszVarName = pszVarName;
+ pEvalProg->cRefs = 1;
+#ifdef KMK_CC_STRICT
+ pEvalProg->uInputHash = kmk_cc_debug_string_hash_n(0, pszContent, cchContent);
+#endif
+
+ /*
+ * Do the actual compiling.
+ */
+#ifdef CONFIG_WITH_EVAL_COMPILER
+ if (kmk_cc_eval_compile_worker(pEvalProg, pszContent, cchContent, iLine) == 0)
+#else
+ if (0)
+#endif
+ {
+#ifdef KMK_CC_WITH_STATS
+ pBlock = pEvalProg->pBlockTail;
+ if (!pBlock->pNext)
+ g_cSingleBlockEvalProgs++;
+ else if (!pBlock->pNext->pNext)
+ g_cTwoBlockEvalProgs++;
+ else
+ g_cMultiBlockEvalProgs++;
+ for (; pBlock; pBlock = pBlock->pNext)
+ {
+ g_cBlocksAllocatedEvalProgs++;
+ g_cbAllocatedEvalProgs += pBlock->cbBlock;
+ g_cbUnusedMemEvalProgs += pBlock->cbBlock - pBlock->offNext;
+ }
+#endif
+ return pEvalProg;
+ }
+ kmk_cc_block_free_list(pEvalProg->pBlockTail);
+ }
+ return NULL;
+}
+
+
+/**
+ * Compiles a variable direct evaluation as is, setting v->evalprog on success.
+ *
+ * @returns Pointer to the program on success, NULL if no program was created.
+ * @param pVar Pointer to the variable.
+ */
+struct kmk_cc_evalprog *kmk_cc_compile_variable_for_eval(struct variable *pVar)
+{
+ PKMKCCEVALPROG pEvalProg = pVar->evalprog;
+ if (!pEvalProg)
+ {
+#ifdef CONFIG_WITH_EVAL_COMPILER
+ pEvalProg = kmk_cc_eval_compile(pVar->value, pVar->value_length,
+ pVar->fileinfo.filenm, pVar->fileinfo.lineno, pVar->name);
+ pVar->evalprog = pEvalProg;
+#endif
+ g_cVarForEvalCompilations++;
+ }
+ return pEvalProg;
+}
+
+
+/**
+ * Compiles a makefile for
+ *
+ * @returns Pointer to the program on success, NULL if no program was created.
+ * @param pVar Pointer to the variable.
+ */
+struct kmk_cc_evalprog *kmk_cc_compile_file_for_eval(FILE *pFile, const char *pszFilename)
+{
+ PKMKCCEVALPROG pEvalProg;
+
+ /*
+ * Read the entire file into a zero terminate memory buffer.
+ */
+ size_t cchContent = 0;
+ char *pszContent = NULL;
+ struct stat st;
+ if (!fstat(fileno(pFile), &st))
+ {
+ if ( st.st_size > (off_t)KMK_CC_EVAL_MAX_COMPILE_SIZE
+ && st.st_size < 0)
+ fatal(NULL, _("Makefile too large to compile: %ld bytes (%#lx) - max %uMB"),
+ (long)st.st_size, (long)st.st_size, KMK_CC_EVAL_MAX_COMPILE_SIZE / 1024 / 1024);
+ cchContent = (size_t)st.st_size;
+ pszContent = (char *)xmalloc(cchContent + 1);
+
+ cchContent = fread(pszContent, 1, cchContent, pFile);
+ if (ferror(pFile))
+ fatal(NULL, _("Read error: %s"), strerror(errno));
+ }
+ else
+ {
+ size_t cbAllocated = 2048;
+ do
+ {
+ cbAllocated *= 2;
+ if (cbAllocated > KMK_CC_EVAL_MAX_COMPILE_SIZE)
+ fatal(NULL, _("Makefile too large to compile: max %uMB"), KMK_CC_EVAL_MAX_COMPILE_SIZE / 1024 / 1024);
+ pszContent = (char *)xrealloc(pszContent, cbAllocated);
+ cchContent += fread(&pszContent[cchContent], 1, cbAllocated - 1 - cchContent, pFile);
+ if (ferror(pFile))
+ fatal(NULL, _("Read error: %s"), strerror(errno));
+ } while (!feof(pFile));
+ }
+ pszContent[cchContent] = '\0';
+
+ /*
+ * Call common function to do the compilation.
+ */
+ pEvalProg = kmk_cc_eval_compile(pszContent, cchContent, pszFilename, 1, NULL /*pszVarName*/);
+ g_cFileForEvalCompilations++;
+
+ free(pszContent);
+ if (!pEvalProg)
+ fseek(pFile, 0, SEEK_SET);
+ return pEvalProg;
+}
+
+
+/**
+ * Equivalent of eval_buffer, only it's using the evalprog of the variable.
+ *
+ * @param pVar Pointer to the variable. Must have a program.
+ */
+void kmk_exec_eval_variable(struct variable *pVar)
+{
+ KMK_CC_ASSERT(pVar->evalprog);
+ assert(0);
+}
+
+
+/**
+ * Worker for eval_makefile.
+ *
+ * @param pEvalProg The program pointer.
+ */
+void kmk_exec_eval_file(struct kmk_cc_evalprog *pEvalProg)
+{
+ KMK_CC_ASSERT(pEvalProg);
+ assert(0);
+
+}
+
+
+
+/*
+ *
+ * Program destruction hooks.
+ * Program destruction hooks.
+ * Program destruction hooks.
+ *
+ */
+
+
+/**
+ * Called when a variable with expandprog or/and evalprog changes.
+ *
+ * @param pVar Pointer to the variable.
+ */
+void kmk_cc_variable_changed(struct variable *pVar)
+{
+ PKMKCCEXPPROG pProg = pVar->expandprog;
+
+ KMK_CC_ASSERT(pVar->evalprog || pProg);
+
+ if (pVar->evalprog)
+ {
+ kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
+ pVar->evalprog = NULL;
+ }
+
+ if (pProg)
+ {
+ if (pProg->cRefs == 1)
+ kmk_cc_block_free_list(pProg->pBlockTail);
+ else
+ fatal(NULL, _("Modifying a variable (%s) while its expansion program is running is not supported"), pVar->name);
+ pVar->expandprog = NULL;
+ }
+}
+
+
+/**
+ * Called when a variable with expandprog or/and evalprog is deleted.
+ *
+ * @param pVar Pointer to the variable.
+ */
+void kmk_cc_variable_deleted(struct variable *pVar)
+{
+ PKMKCCEXPPROG pProg = pVar->expandprog;
+
+ KMK_CC_ASSERT(pVar->evalprog || pProg);
+
+ if (pVar->evalprog)
+ {
+ kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
+ pVar->evalprog = NULL;
+ }
+
+ if (pProg)
+ {
+ if (pProg->cRefs == 1)
+ kmk_cc_block_free_list(pProg->pBlockTail);
+ else
+ fatal(NULL, _("Deleting a variable (%s) while its expansion program is running is not supported"), pVar->name);
+ pVar->expandprog = NULL;
+ }
+}
+
+
+
+
+
+
+
+#endif /* CONFIG_WITH_COMPILER */
+
diff --git a/src/kmk/kmk_cc_exec.h b/src/kmk/kmk_cc_exec.h
new file mode 100644
index 0000000..39c5142
--- /dev/null
+++ b/src/kmk/kmk_cc_exec.h
@@ -0,0 +1,48 @@
+/* $Id: kmk_cc_exec.h 3154 2018-03-15 23:35:33Z bird $ */
+/** @file
+ * kmk_cc - Make "Compiler".
+ */
+
+/*
+ * Copyright (c) 2015-2017 knut st. osmundsen <bird-kBuild-spam-xviiv@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___kmk_cc_and_exec_h
+#define ___kmk_cc_and_exec_h
+#ifdef CONFIG_WITH_COMPILER
+
+#include <stdio.h>
+
+
+void kmk_cc_init(void);
+void kmk_cc_print_stats(void);
+
+struct variable;
+extern struct kmk_cc_expandprog *kmk_cc_compile_variable_for_expand(struct variable *pVar);
+extern struct kmk_cc_evalprog *kmk_cc_compile_variable_for_eval(struct variable *pVar);
+extern struct kmk_cc_evalprog *kmk_cc_compile_file_for_eval(FILE *pFile, const char *pszFilename);
+extern char *kmk_exec_expand_to_var_buf(struct variable *pVar, char *pchDst);
+extern void kmk_exec_eval_file(struct kmk_cc_evalprog *pProg);
+extern void kmk_exec_eval_variable(struct variable *pVar);
+extern void kmk_cc_variable_changed(struct variable *pVar);
+extern void kmk_cc_variable_deleted(struct variable *pVar);
+
+
+#endif /* CONFIG_WITH_COMPILER */
+#endif
diff --git a/src/kmk/kmkbuiltin.c b/src/kmk/kmkbuiltin.c
new file mode 100644
index 0000000..8f00e98
--- /dev/null
+++ b/src/kmk/kmkbuiltin.c
@@ -0,0 +1,507 @@
+/* $Id: kmkbuiltin.c 3389 2020-06-26 17:16:26Z bird $ */
+/** @file
+ * kMk Builtin command execution.
+ */
+
+/*
+ * Copyright (c) 2005-2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <assert.h>
+#include <sys/stat.h>
+#ifdef _MSC_VER
+# include <io.h>
+#endif
+
+#include "makeint.h"
+#include "job.h"
+#include "variable.h"
+#if defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+# include "w32/winchildren.h"
+#endif
+#include "kmkbuiltin/err.h"
+#include "kmkbuiltin.h"
+
+#ifndef _MSC_VER
+extern char **environ;
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+extern int print_stats_flag;
+#endif
+
+
+
+int kmk_builtin_command(const char *pszCmd, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned)
+{
+ int argc;
+ char **argv;
+ int rc;
+ char *pszzCmd;
+ char *pszDst;
+ int fOldStyle = 0;
+
+ /*
+ * Check and skip the prefix.
+ */
+ if (strncmp(pszCmd, "kmk_builtin_", sizeof("kmk_builtin_") - 1))
+ {
+ fprintf(stderr, "kmk_builtin: Invalid command prefix '%s'!\n", pszCmd);
+ return 1;
+ }
+
+ /*
+ * Parse arguments.
+ */
+ rc = 0;
+ argc = 0;
+ argv = NULL;
+ pszzCmd = pszDst = (char *)strdup(pszCmd);
+ if (!pszDst)
+ {
+ fprintf(stderr, "kmk_builtin: out of memory. argc=%d\n", argc);
+ return 1;
+ }
+ do
+ {
+ const char * const pszSrcStart = pszCmd;
+ char ch;
+ char chQuote;
+
+ /*
+ * Start new argument.
+ */
+ if (!(argc % 16))
+ {
+ void *pv = realloc(argv, sizeof(char *) * (argc + 17));
+ if (!pv)
+ {
+ fprintf(stderr, "kmk_builtin: out of memory. argc=%d\n", argc);
+ rc = 1;
+ break;
+ }
+ argv = (char **)pv;
+ }
+ argv[argc++] = pszDst;
+ argv[argc] = NULL;
+
+ if (!fOldStyle)
+ {
+ /*
+ * Process the next argument, bourne style.
+ */
+ chQuote = 0;
+ ch = *pszCmd++;
+ do
+ {
+ /* Unquoted mode? */
+ if (chQuote == 0)
+ {
+ if (ch != '\'' && ch != '"')
+ {
+ if (!isspace(ch))
+ {
+ if (ch != '\\')
+ *pszDst++ = ch;
+ else
+ {
+ ch = *pszCmd++;
+ if (ch == '\n') /* escaped end-of-line */
+ break;
+ if (ch == '\r' && *pszCmd == '\n') /* escaped end-of-line */
+ {
+ pszCmd++;
+ break;
+ }
+ if (ch)
+ *pszDst++ = ch;
+ else
+ {
+ fprintf(stderr, "kmk_builtin: Incomplete escape sequence in argument %d: %s\n",
+ argc, pszSrcStart);
+ rc = 1;
+ break;
+ }
+ }
+ }
+ else
+ break;
+ }
+ else
+ chQuote = ch;
+ }
+ /* Quoted mode */
+ else if (ch != chQuote)
+ {
+ if ( ch != '\\'
+ || chQuote == '\'')
+ *pszDst++ = ch;
+ else
+ {
+ ch = *pszCmd++;
+ if (ch)
+ {
+ if ( ch != '\\'
+ && ch != '"'
+ && ch != '`'
+ && ch != '$'
+ && ch != '\n')
+ *pszDst++ = '\\';
+ *pszDst++ = ch;
+ }
+ else
+ {
+ fprintf(stderr, "kmk_builtin: Unbalanced quote in argument %d: %s\n", argc, pszSrcStart);
+ rc = 1;
+ break;
+ }
+ }
+ }
+ else
+ chQuote = 0;
+ } while ((ch = *pszCmd++) != '\0');
+ }
+ else
+ {
+ /*
+ * Old style in case we ever need it.
+ */
+ ch = *pszCmd++;
+ if (ch != '"' && ch != '\'')
+ {
+ do
+ *pszDst++ = ch;
+ while ((ch = *pszCmd++) != '\0' && !isspace(ch));
+ }
+ else
+ {
+ chQuote = ch;
+ for (;;)
+ {
+ char *pszEnd = strchr(pszCmd, chQuote);
+ if (pszEnd)
+ {
+ fprintf(stderr, "kmk_builtin: Unbalanced quote in argument %d: %s\n", argc, pszSrcStart);
+ rc = 1;
+ break;
+ }
+ memcpy(pszDst, pszCmd, pszEnd - pszCmd);
+ pszDst += pszEnd - pszCmd;
+ if (pszEnd[1] != chQuote)
+ break;
+ *pszDst++ = chQuote;
+ }
+ }
+ }
+ *pszDst++ = '\0';
+
+ /*
+ * Skip argument separators (IFS=space() for now). Check for EOS.
+ */
+ if (ch != 0)
+ while ( (ch = *pszCmd)
+ && ( isspace(ch)
+ || (ch == '\\' && (pszCmd[1] == '\n' || (pszCmd[1] == '\r' && pszCmd[2] == '\n')))))
+ pszCmd++;
+ if (ch == 0)
+ break;
+ } while (rc == 0);
+
+ /*
+ * Execute the command if parsing was successful.
+ */
+ if (rc == 0)
+ rc = kmk_builtin_command_parsed(argc, argv, pChild, ppapszArgvToSpawn, pPidSpawned);
+
+ /* clean up and return. */
+ free(argv);
+ free(pszzCmd);
+ return rc;
+}
+
+
+/**
+ * kmk built command.
+ */
+static const KMKBUILTINENTRY g_aBuiltIns[] =
+{
+#define BUILTIN_ENTRY(a_fn, a_sz, a_uFnSignature, fMtSafe, fNeedEnv) \
+ { { { sizeof(a_sz) - 1, a_sz, } }, { (uintptr_t)a_fn }, a_uFnSignature, fMtSafe, fNeedEnv }
+
+ /* More frequently used commands: */
+ BUILTIN_ENTRY(kmk_builtin_append, "append", FN_SIG_MAIN_SPAWNS, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_printf, "printf", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_echo, "echo", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_install, "install", FN_SIG_MAIN, 1, 0),
+ BUILTIN_ENTRY(kmk_builtin_kDepObj, "kDepObj", FN_SIG_MAIN, 1, 0),
+#ifdef KBUILD_OS_WINDOWS
+ BUILTIN_ENTRY(kmk_builtin_kSubmit, "kSubmit", FN_SIG_MAIN_SPAWNS, 0, 1),
+ BUILTIN_ENTRY(kmk_builtin_kill, "kill", FN_SIG_MAIN, 0, 0),
+#endif
+ BUILTIN_ENTRY(kmk_builtin_mkdir, "mkdir", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_mv, "mv", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_redirect, "redirect", FN_SIG_MAIN_SPAWNS, 1, 1),
+ BUILTIN_ENTRY(kmk_builtin_rm, "rm", FN_SIG_MAIN, 1, 1),
+ BUILTIN_ENTRY(kmk_builtin_rmdir, "rmdir", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_test, "test", FN_SIG_MAIN_TO_SPAWN, 0, 0),
+ /* Less frequently used commands: */
+ BUILTIN_ENTRY(kmk_builtin_kDepIDB, "kDepIDB", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_chmod, "chmod", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_cp, "cp", FN_SIG_MAIN, 1, 1),
+ BUILTIN_ENTRY(kmk_builtin_expr, "expr", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_ln, "ln", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_md5sum, "md5sum", FN_SIG_MAIN, 1, 0),
+ BUILTIN_ENTRY(kmk_builtin_cmp, "cmp", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_cat, "cat", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_touch, "touch", FN_SIG_MAIN, 0, 0),
+ BUILTIN_ENTRY(kmk_builtin_sleep, "sleep", FN_SIG_MAIN, 1, 0),
+ BUILTIN_ENTRY(kmk_builtin_dircache, "dircache", FN_SIG_MAIN, 0, 0),
+};
+
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+/** Statistics running in parallel to g_aBuiltIns. */
+struct
+{
+ big_int cNs;
+ unsigned cTimes;
+ unsigned cAsyncTimes;
+} g_aBuiltInStats[sizeof(g_aBuiltIns) / sizeof(g_aBuiltIns[0])];
+#endif
+
+
+int kmk_builtin_command_parsed(int argc, char **argv, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned)
+{
+ /*
+ * Check and skip the prefix.
+ */
+ static const char s_szPrefix[] = "kmk_builtin_";
+ const char *pszCmd = argv[0];
+ if (strncmp(pszCmd, s_szPrefix, sizeof(s_szPrefix) - 1) == 0)
+ {
+ struct KMKBUILTINENTRY const *pEntry;
+ size_t cchAndStart;
+#if K_ENDIAN == K_ENDIAN_BIG
+ size_t cch;
+#endif
+ int cLeft;
+
+ pszCmd += sizeof(s_szPrefix) - 1;
+
+ /*
+ * Calc the length and start word to avoid calling memcmp/strcmp on each entry.
+ */
+#if K_ARCH_BITS != 64 && K_ARCH_BITS != 32
+# error "PORT ME!"
+#endif
+ cchAndStart = strlen(pszCmd);
+#if K_ENDIAN == K_ENDIAN_BIG
+ cch = cchAndStart;
+ cchAndStart <<= K_ARCH_BITS - 8;
+ switch (cch)
+ {
+ default: /* fall thru */
+# if K_ARCH_BITS >= 64
+ case 7: cchAndStart |= (size_t)pszCmd[6]; /* fall thru */
+ case 6: cchAndStart |= (size_t)pszCmd[5] << (K_ARCH_BITS - 56); /* fall thru */
+ case 5: cchAndStart |= (size_t)pszCmd[4] << (K_ARCH_BITS - 48); /* fall thru */
+ case 4: cchAndStart |= (size_t)pszCmd[3] << (K_ARCH_BITS - 40); /* fall thru */
+# endif
+ /* fall thru - gcc 8.2.0 is confused by # endif */
+ case 3: cchAndStart |= (size_t)pszCmd[2] << (K_ARCH_BITS - 32); /* fall thru */
+ case 2: cchAndStart |= (size_t)pszCmd[1] << (K_ARCH_BITS - 24); /* fall thru */
+ case 1: cchAndStart |= (size_t)pszCmd[0] << (K_ARCH_BITS - 16); /* fall thru */
+ case 0: break;
+ }
+#else
+ switch (cchAndStart)
+ {
+ default: /* fall thru */
+# if K_ARCH_BITS >= 64
+ case 7: cchAndStart |= (size_t)pszCmd[6] << 56; /* fall thru */
+ case 6: cchAndStart |= (size_t)pszCmd[5] << 48; /* fall thru */
+ case 5: cchAndStart |= (size_t)pszCmd[4] << 40; /* fall thru */
+ case 4: cchAndStart |= (size_t)pszCmd[3] << 32; /* fall thru */
+# endif
+ /* fall thru - gcc 8.2.0 is confused by # endif */
+ case 3: cchAndStart |= (size_t)pszCmd[2] << 24; /* fall thru */
+ case 2: cchAndStart |= (size_t)pszCmd[1] << 16; /* fall thru */
+ case 1: cchAndStart |= (size_t)pszCmd[0] << 8; /* fall thru */
+ case 0: break;
+ }
+#endif
+
+ /*
+ * Look up the builtin command in the table.
+ */
+ pEntry = &g_aBuiltIns[0];
+ cLeft = sizeof(g_aBuiltIns) / sizeof(g_aBuiltIns[0]);
+ while (cLeft-- > 0)
+ if ( pEntry->uName.cchAndStart != cchAndStart
+ || ( pEntry->uName.s.cch >= sizeof(cchAndStart)
+ && memcmp(pEntry->uName.s.sz, pszCmd, pEntry->uName.s.cch) != 0) )
+ pEntry++;
+ else
+ {
+ /*
+ * That's a match!
+ *
+ * First get the environment if it is actually needed. This is
+ * especially important when we run on a worker thread as it must
+ * not under any circumstances do stuff like target_environment.
+ */
+ int rc;
+ char **papszEnvVars = NULL;
+ if (pEntry->fNeedEnv)
+ {
+ papszEnvVars = pChild->environment;
+ if (!papszEnvVars)
+ pChild->environment = papszEnvVars = target_environment(pChild->file);
+ }
+
+#if defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+ /*
+ * If the built-in is multi thread safe, we will run it on a job slot thread.
+ */
+ if (pEntry->fMtSafe)
+ {
+ rc = MkWinChildCreateBuiltIn(pEntry, argc, argv, papszEnvVars, pChild, pPidSpawned);
+# ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+ g_aBuiltInStats[pEntry - &g_aBuiltIns[0]].cAsyncTimes++;
+# endif
+ }
+ else
+#endif
+ {
+ /*
+ * Call the worker function.
+ */
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+ big_int nsStart = print_stats_flag ? nano_timestamp() : 0;
+#endif
+ KMKBUILTINCTX Ctx;
+ assert(g_fUMask == umask(g_fUMask));
+
+ Ctx.pszProgName = pEntry->uName.s.sz;
+ Ctx.pOut = pChild ? &pChild->output : NULL;
+
+ if (pEntry->uFnSignature == FN_SIG_MAIN)
+ rc = pEntry->u.pfnMain(argc, argv, papszEnvVars, &Ctx);
+ else if (pEntry->uFnSignature == FN_SIG_MAIN_SPAWNS)
+ rc = pEntry->u.pfnMainSpawns(argc, argv, papszEnvVars, &Ctx, pChild, pPidSpawned);
+ else if (pEntry->uFnSignature == FN_SIG_MAIN_TO_SPAWN)
+ {
+ /*
+ * When we got something to execute, check if the child is a kmk_builtin thing.
+ * We recurse here, both because I'm lazy and because it's easier to debug a
+ * problem then (the call stack shows what's been going on).
+ */
+ rc = pEntry->u.pfnMainToSpawn(argc, argv, papszEnvVars, &Ctx, ppapszArgvToSpawn);
+ if ( !rc
+ && *ppapszArgvToSpawn
+ && !strncmp(**ppapszArgvToSpawn, s_szPrefix, sizeof(s_szPrefix) - 1))
+ {
+ char **argv_new = *ppapszArgvToSpawn;
+ int argc_new = 1;
+ while (argv_new[argc_new])
+ argc_new++;
+
+ assert(argv_new[0] != argv[0]);
+ assert(!*pPidSpawned);
+
+ *ppapszArgvToSpawn = NULL;
+ rc = kmk_builtin_command_parsed(argc_new, argv_new, pChild, ppapszArgvToSpawn, pPidSpawned);
+
+ free(argv_new[0]);
+ free(argv_new);
+ }
+ }
+ else
+ rc = 99;
+
+ assert(g_fUMask == umask(g_fUMask)); /* builtin command must preserve umask! */
+
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+ if (print_stats_flag)
+ {
+ uintptr_t iEntry = pEntry - &g_aBuiltIns[0];
+ g_aBuiltInStats[iEntry].cTimes++;
+ g_aBuiltInStats[iEntry].cNs += nano_timestamp() - nsStart;
+ }
+#endif
+ }
+ return rc;
+ }
+
+ /*
+ * No match! :-(
+ */
+ fprintf(stderr, "kmk_builtin: Unknown command '%s%s'!\n", s_szPrefix, pszCmd);
+ }
+ else
+ fprintf(stderr, "kmk_builtin: Invalid command prefix '%s'!\n", pszCmd);
+ return 1;
+}
+
+#ifndef KBUILD_OS_WINDOWS
+/** Dummy. */
+int kmk_builtin_dircache(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ (void)argc; (void)argv; (void)envp; (void)pCtx;
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+/**
+ * Prints the statistiscs to the given output stream.
+ */
+extern void kmk_builtin_print_stats(FILE *pOutput, const char *pszPrefix)
+{
+ const unsigned cEntries = sizeof(g_aBuiltInStats) / sizeof(g_aBuiltInStats[0]);
+ unsigned i;
+ assert(print_stats_flag);
+ fprintf(pOutput, "\n%skmk built-in command statistics:\n", pszPrefix);
+ for (i = 0; i < cEntries; i++)
+ if (g_aBuiltInStats[i].cTimes > 0)
+ {
+ char szTotal[64];
+ char szAvg[64];
+ format_elapsed_nano(szTotal, sizeof(szTotal), g_aBuiltInStats[i].cNs);
+ format_elapsed_nano(szAvg, sizeof(szAvg), g_aBuiltInStats[i].cNs / g_aBuiltInStats[i].cTimes);
+ fprintf(pOutput, "%s kmk_builtin_%-9s: %4u times, %9s total, %9s/call\n",
+ pszPrefix, g_aBuiltIns[i].uName.s.sz, g_aBuiltInStats[i].cTimes, szTotal, szAvg);
+ }
+ else if (g_aBuiltInStats[i].cAsyncTimes > 0)
+ fprintf(pOutput, "%s kmk_builtin_%-9s: %4u times in worker thread\n",
+ pszPrefix, g_aBuiltIns[i].uName.s.sz, g_aBuiltInStats[i].cAsyncTimes);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin.h b/src/kmk/kmkbuiltin.h
new file mode 100644
index 0000000..955a04d
--- /dev/null
+++ b/src/kmk/kmkbuiltin.h
@@ -0,0 +1,184 @@
+/* $Id: kmkbuiltin.h 3352 2020-06-05 00:31:50Z bird $ */
+/** @file
+ * kMk Builtin command handling.
+ */
+
+/*
+ * Copyright (c) 2005-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___kmk_kmkbuiltin_h___
+#define ___kmk_kmkbuiltin_h___
+
+#ifdef _MSC_VER
+# ifndef pid_t /* see config.h.win */
+# define pid_t intptr_t /* Note! sub_proc.c needs it to be pointer sized. */
+# endif
+#else
+# include <sys/types.h>
+#endif
+#include <fcntl.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+
+/* For the GNU/hurd weirdo. */
+#ifndef PATH_MAX
+# ifdef MAXPATHLEN
+# define PATH_MAX MAXPATHLEN
+# else
+# define PATH_MAX 4096
+# endif
+#endif
+#ifndef MAXPATHLEN
+# define MAXPATHLEN PATH_MAX
+#endif
+
+/** This is for telling fopen() to get a close-on-exec handle.
+ * @todo glibc 2.7+ and recent cygwin supports 'e' for doing this. */
+#ifndef KMK_FOPEN_NO_INHERIT_MODE
+# ifdef _MSC_VER
+# define KMK_FOPEN_NO_INHERIT_MODE "N"
+# else
+# define KMK_FOPEN_NO_INHERIT_MODE ""
+# endif
+#endif
+
+/** This is for telling open() to open to return a close-on-exec descriptor. */
+#ifdef _O_NOINHERIT
+# define KMK_OPEN_NO_INHERIT _O_NOINHERIT
+#elif defined(O_NOINHERIT)
+# define KMK_OPEN_NO_INHERIT O_NOINHERIT
+#elif defined(O_CLOEXEC)
+# define KMK_OPEN_NO_INHERIT O_CLOEXEC
+#else
+# define KMK_OPEN_NO_INHERIT 0
+#endif
+
+
+#include "kbuild_version.h"
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+# include "output.h"
+#endif
+
+struct child;
+int kmk_builtin_command(const char *pszCmd, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned);
+int kmk_builtin_command_parsed(int argc, char **argv, struct child *pChild, char ***ppapszArgvToSpawn, pid_t *pPidSpawned);
+
+
+/**
+ * KMK built-in command execution context.
+ */
+typedef struct KMKBUILTINCTX
+{
+ /** The program name to use in error messages. */
+ const char *pszProgName;
+ /** The KMK output synchronizer. */
+ struct output *pOut;
+#if defined(KBUILD_OS_WINDOWS) && !defined(KMK_BUILTIN_STANDALONE)
+ /** Pointer to the worker thread, if we're on one. */
+ void *pvWorker;
+#endif
+} KMKBUILTINCTX;
+/** Pointer to kmk built-in command execution context. */
+typedef KMKBUILTINCTX *PKMKBUILTINCTX;
+
+/**
+ * kmk built-in command entry.
+ */
+typedef struct KMKBUILTINENTRY
+{
+ union
+ {
+ struct
+ {
+ unsigned char cch;
+ char sz[15];
+ } s;
+ size_t cchAndStart;
+ } uName;
+ union
+ {
+ uintptr_t uPfn;
+#define FN_SIG_MAIN 0
+ int (* pfnMain)(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+#define FN_SIG_MAIN_SPAWNS 1
+ int (* pfnMainSpawns)(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPid);
+#define FN_SIG_MAIN_TO_SPAWN 2
+ int (* pfnMainToSpawn)(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, char ***ppapszArgvToSpawn);
+ } u;
+ size_t uFnSignature : 8;
+ size_t fMtSafe : 1; /**< Safe for multi threaded execution. */
+ size_t fNeedEnv : 1; /**< Needs the (target) environment. */
+} KMKBUILTINENTRY;
+/** Pointer to kmk built-in command entry. */
+typedef KMKBUILTINENTRY const *PCKMKBUILTINENTRY;
+
+extern int kmk_builtin_append(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned);
+extern int kmk_builtin_cp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_cat(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_chmod(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_cmp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_dircache(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_echo(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_expr(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_install(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_ln(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_md5sum(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_mkdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_mv(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_printf(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_redirect(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned);
+extern int kmk_builtin_rm(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_rmdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_sleep(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_test(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, char ***ppapszArgvSpawn);
+extern int kmk_builtin_touch(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+#ifdef KBUILD_OS_WINDOWS
+extern int kmk_builtin_kSubmit(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned);
+extern int kSubmitSubProcGetResult(intptr_t pvUser, int fBlock, int *prcExit, int *piSigNo);
+extern int kSubmitSubProcKill(intptr_t pvUser, int iSignal);
+extern void kSubmitSubProcCleanup(intptr_t pvUser);
+extern int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+#endif
+extern int kmk_builtin_kDepIDB(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+extern int kmk_builtin_kDepObj(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx);
+
+extern char *kmk_builtin_func_printf(char *o, char **argv, const char *funcname);
+
+/* common-env-and-cwd-opt.c: */
+extern int kBuiltinOptEnvSet(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue);
+extern int kBuiltinOptEnvAppend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue);
+extern int kBuiltinOptEnvPrepend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue);
+extern int kBuiltinOptEnvUnset(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszVarToRemove);
+extern int kBuiltinOptEnvZap(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity);
+extern void kBuiltinOptEnvCleanup(char ***ppapszEnv, unsigned cEnvVars, unsigned *pcAllocatedEnvVars);
+extern int kBuiltinOptChDir(PKMKBUILTINCTX pCtx, char *pszCwd, size_t cbCwdBuf, const char *pszValue);
+
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+extern void kmk_builtin_print_stats(FILE *pOutput, const char *pszPrefix);
+#endif
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/Makefile.kup b/src/kmk/kmkbuiltin/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kmk/kmkbuiltin/Makefile.kup
diff --git a/src/kmk/kmkbuiltin/append.c b/src/kmk/kmkbuiltin/append.c
new file mode 100644
index 0000000..c34847e
--- /dev/null
+++ b/src/kmk/kmkbuiltin/append.c
@@ -0,0 +1,459 @@
+/* $Id: append.c 3246 2018-12-25 21:02:04Z bird $ */
+/** @file
+ * kMk Builtin command - append text to file.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#ifndef KMK_BUILTIN_STANDALONE
+# include "makeint.h"
+# include "filedef.h"
+# include "variable.h"
+#else
+# include "config.h"
+#endif
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef _MSC_VER
+# include <io.h>
+#endif
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+# include "../w32/winchildren.h"
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define STR_TUPLE(a_sz) a_sz, (sizeof(a_sz) - 1)
+
+/** No-inherit open flag. */
+#ifdef _O_NOINHERIT
+# define MY_O_NOINHERIT _O_NOINHERIT
+#elif defined(O_NOINHERIT)
+# define MY_O_NOINHERIT O_NOINHERIT
+#elif defined(O_CLOEXEC)
+# define MY_O_NOINHERIT O_CLOEXEC
+#else
+# define MY_O_NOINHERIT 0
+#endif
+
+/** Binary mode open flag. */
+#ifdef _O_BINARY
+# define MY_O_BINARY _O_BINARY
+#elif defined(O_BINARY)
+# define MY_O_BINARY O_BINARY
+#else
+# define MY_O_BINARY 0
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Append output buffer.
+ */
+typedef struct KMKBUILTINAPPENDBUF
+{
+ /** Buffer pointer. */
+ char *pszBuf;
+ /** The buffer allocation size. */
+ size_t cbBuf;
+ /** The current buffer offset. */
+ size_t offBuf;
+ /** Set if we ran out of memory. */
+ int fOutOfMemory;
+} KMKBUILTINAPPENDBUF;
+
+
+/**
+ * Appends a substring to the output buffer.
+ *
+ * @param pBuf The output buffer.
+ * @param pch The substring pointer.
+ * @param cch The substring length.
+ */
+static void write_to_buf(KMKBUILTINAPPENDBUF *pBuf, const char *pch, size_t cch)
+{
+ size_t const offCur = pBuf->offBuf;
+ size_t offNew = offCur + cch;
+
+ if (offNew >= pBuf->cbBuf)
+ {
+ size_t cbNew = offNew + 1 + 256;
+ void *pvNew;
+ cbNew = (cbNew + 511) & ~(size_t)511;
+ pvNew = realloc(pBuf->pszBuf, cbNew);
+ if (pvNew)
+ pBuf->pszBuf = (char *)pvNew;
+ else
+ {
+ free(pBuf->pszBuf);
+ pBuf->pszBuf = NULL;
+ pBuf->cbBuf = 0;
+ pBuf->offBuf = offNew;
+ pBuf->fOutOfMemory = 1;
+ return;
+ }
+ }
+
+ memcpy(&pBuf->pszBuf[offCur], pch, cch);
+ pBuf->pszBuf[offNew] = '\0';
+ pBuf->offBuf = offNew;
+}
+
+/**
+ * Adds a string to the output buffer.
+ *
+ * @param pBuf The output buffer.
+ * @param psz The string.
+ */
+static void string_to_buf(KMKBUILTINAPPENDBUF *pBuf, const char *psz)
+{
+ write_to_buf(pBuf, psz, strlen(psz));
+}
+
+
+/**
+ * Prints the usage and return 1.
+ */
+static int kmk_builtin_append_usage(const char *arg0, FILE *pf)
+{
+ fprintf(pf,
+ "usage: %s [-dcnNtv] file [string ...]\n"
+ " or: %s --version\n"
+ " or: %s --help\n"
+ "\n"
+ "Options:\n"
+ " -d Enclose the output in define ... endef, taking the name from\n"
+ " the first argument following the file name.\n"
+ " -c Output the command for specified target(s). [builtin only]\n"
+ " -i look for --insert-command=trg and --insert-variable=var. [builtin only]\n"
+ " -n Insert a newline between the strings.\n"
+ " -N Suppress the trailing newline.\n"
+ " -t Truncate the file instead of appending\n"
+ " -v Output the value(s) for specified variable(s). [builtin only]\n"
+ ,
+ arg0, arg0, arg0);
+ return 1;
+}
+
+/**
+ * Appends text to a textfile, creating the textfile if necessary.
+ */
+int kmk_builtin_append(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ static const char s_szNewLine[] = "\r\n";
+#else
+ static const char s_szNewLine[] = "\n";
+#endif
+ KMKBUILTINAPPENDBUF OutBuf = { NULL, 0, 0, 0 };
+ const char *pszFilename;
+ int rc = 88;
+ int i;
+ int fFirst;
+ int fNewline = 0;
+ int fNoTrailingNewline = 0;
+ int fTruncate = 0;
+ int fDefine = 0;
+ int fVariables = 0;
+ int fCommands = 0;
+#ifndef KMK_BUILTIN_STANDALONE
+ int fLookForInserts = 0;
+#else
+ (void)pChild; (void)pPidSpawned;
+#endif
+
+ /*
+ * Parse options.
+ */
+ i = 1;
+ while (i < argc
+ && argv[i][0] == '-'
+ && argv[i][1] != '\0' /* '-' is a file */
+ && strchr("-cdinNtv", argv[i][1]) /* valid option char */
+ )
+ {
+ char *psz = &argv[i][1];
+ if (*psz != '-')
+ {
+ do
+ {
+ switch (*psz)
+ {
+ case 'c':
+ if (fVariables)
+ {
+ errx(pCtx, 1, "Option '-c' clashes with '-v'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fCommands = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-c' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ case 'd':
+ if (fVariables)
+ {
+ errx(pCtx, 1, "Option '-d' must come before '-v'!");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+ fDefine = 1;
+ break;
+ case 'i':
+ if (fVariables || fCommands)
+ {
+ errx(pCtx, 1, fVariables ? "Option '-i' clashes with '-v'." : "Option '-i' clashes with '-c'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fLookForInserts = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-C' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ case 'n':
+ fNewline = 1;
+ break;
+ case 'N':
+ fNoTrailingNewline = 1;
+ break;
+ case 't':
+ fTruncate = 1;
+ break;
+ case 'v':
+ if (fCommands)
+ {
+ errx(pCtx, 1, "Option '-v' clashes with '-c'.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ fVariables = 1;
+ break;
+#else
+ errx(pCtx, 1, "Option '-v' isn't supported in external mode.");
+ return kmk_builtin_append_usage(argv[0], stderr);
+#endif
+ default:
+ errx(pCtx, 1, "Invalid option '%c'! (%s)", *psz, argv[i]);
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+ } while (*++psz);
+ }
+ else if (!strcmp(psz, "-help"))
+ {
+ kmk_builtin_append_usage(argv[0], stdout);
+ return 0;
+ }
+ else if (!strcmp(psz, "-version"))
+ return kbuild_version(argv[0]);
+ else
+ break;
+ i++;
+ }
+
+ /*
+ * Take down the filename.
+ */
+ if (i + fDefine < argc)
+ pszFilename = argv[i++];
+ else
+ {
+ if (i <= argc)
+ errx(pCtx, 1, "missing filename!");
+ else
+ errx(pCtx, 1, "missing define name!");
+ return kmk_builtin_append_usage(argv[0], stderr);
+ }
+
+ /* Start of no-return zone! */
+
+ /*
+ * Start define?
+ */
+ if (fDefine)
+ {
+ write_to_buf(&OutBuf, STR_TUPLE("define "));
+ string_to_buf(&OutBuf, argv[i]);
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ i++;
+ }
+
+ /*
+ * Append the argument strings to the file
+ */
+ fFirst = 1;
+ for (; i < argc; i++)
+ {
+ const char *psz = argv[i];
+ size_t cch = strlen(psz);
+ if (!fFirst)
+ {
+ if (fNewline)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ else
+ write_to_buf(&OutBuf, STR_TUPLE(" "));
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ if (fCommands)
+ {
+ char *pszOldBuf;
+ unsigned cchOldBuf;
+ char *pchEnd;
+
+ install_variable_buffer(&pszOldBuf, &cchOldBuf);
+
+ pchEnd = func_commands(variable_buffer, &argv[i], "commands");
+ write_to_buf(&OutBuf, variable_buffer, pchEnd - variable_buffer);
+
+ restore_variable_buffer(pszOldBuf, cchOldBuf);
+ }
+ else if (fVariables)
+ {
+ struct variable *pVar = lookup_variable(psz, cch);
+ if (!pVar)
+ continue;
+ if ( !pVar->recursive
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ write_to_buf(&OutBuf, pVar->value, pVar->value_length);
+ else
+ {
+ char *pszExpanded = allocated_variable_expand(pVar->value);
+ string_to_buf(&OutBuf, pszExpanded);
+ free(pszExpanded);
+ }
+ }
+ else if (fLookForInserts && strncmp(psz, "--insert-command=", 17) == 0)
+ {
+ char *pszOldBuf;
+ unsigned cchOldBuf;
+ char *pchEnd;
+
+ install_variable_buffer(&pszOldBuf, &cchOldBuf);
+
+ psz += 17;
+ pchEnd = func_commands(variable_buffer, (char **)&psz, "commands");
+ write_to_buf(&OutBuf, variable_buffer, pchEnd - variable_buffer);
+
+ restore_variable_buffer(pszOldBuf, cchOldBuf);
+ }
+ else if (fLookForInserts && strncmp(psz, "--insert-variable=", 18) == 0)
+ {
+ struct variable *pVar = lookup_variable(psz + 18, cch);
+ if (!pVar)
+ continue;
+ if ( !pVar->recursive
+ || IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(pVar))
+ write_to_buf(&OutBuf, pVar->value, pVar->value_length);
+ else
+ {
+ char *pszExpanded = allocated_variable_expand(pVar->value);
+ string_to_buf(&OutBuf, pszExpanded);
+ free(pszExpanded);
+ }
+ }
+ else
+#endif
+ write_to_buf(&OutBuf, psz, cch);
+ fFirst = 0;
+ }
+
+ /*
+ * End the define?
+ */
+ if (fDefine)
+ {
+ if (fFirst)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+ write_to_buf(&OutBuf, STR_TUPLE("endef"));
+ }
+
+ /*
+ * Add final newline (unless supressed) and check for errors.
+ */
+ if (!fNoTrailingNewline)
+ write_to_buf(&OutBuf, STR_TUPLE(s_szNewLine));
+
+ /*
+ * Write the buffer (unless we ran out of heap already).
+ */
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS) && defined(CONFIG_NEW_WIN_CHILDREN)
+ if (!OutBuf.fOutOfMemory)
+ {
+ rc = MkWinChildCreateAppend(pszFilename, &OutBuf.pszBuf, OutBuf.offBuf, fTruncate, pChild, pPidSpawned);
+ if (rc != 0)
+ rc = errx(pCtx, rc, "MkWinChildCreateAppend failed: %u", rc);
+ if (OutBuf.pszBuf)
+ free(OutBuf.pszBuf);
+ }
+ else
+#endif
+ if (!OutBuf.fOutOfMemory)
+ {
+ int fd = open(pszFilename,
+ fTruncate ? O_WRONLY | O_TRUNC | O_CREAT | MY_O_NOINHERIT | MY_O_BINARY
+ : O_WRONLY | O_APPEND | O_CREAT | MY_O_NOINHERIT | MY_O_BINARY,
+ 0666);
+ if (fd >= 0)
+ {
+ ssize_t cbWritten = write(fd, OutBuf.pszBuf, OutBuf.offBuf);
+ if (cbWritten == (ssize_t)OutBuf.offBuf)
+ rc = 0;
+ else
+ rc = err(pCtx, 1, "error writing %lu bytes to '%s'", (unsigned long)OutBuf.offBuf, pszFilename);
+ if (close(fd) < 0)
+ rc = err(pCtx, 1, "error closing '%s'", pszFilename);
+ }
+ else
+ rc = err(pCtx, 1, "failed to open '%s'", pszFilename);
+ free(OutBuf.pszBuf);
+ }
+ else
+ rc = errx(pCtx, 1, "out of memory for output buffer! (%u needed)", OutBuf.offBuf + 1);
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_append", NULL };
+ return kmk_builtin_append(argc, argv, envp, &Ctx, NULL, NULL);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cat.c b/src/kmk/kmkbuiltin/cat.c
new file mode 100644
index 0000000..7b52d0f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cat.c
@@ -0,0 +1,416 @@
+/*-
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Fall.
+ *
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cat.c 8.2 (Berkeley) 4/27/95";
+#endif
+#endif /* not lint */
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cat/cat.c,v 1.32 2005/01/10 08:39:20 imp Exp $");
+#endif
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#define NO_UDOM_SUPPORT /* kmk */
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+#ifndef NO_UDOM_SUPPORT
+# include <sys/socket.h>
+# include <sys/un.h>
+# include <errno.h>
+#endif
+
+#include <ctype.h>
+#include "err.h"
+#include <fcntl.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stddef.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct CATINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int bflag, eflag, nflag, sflag, tflag, vflag;
+ /*int rval;*/
+ const char *filename;
+ /* function level statics from raw_cat (needs freeing): */
+ size_t bsize;
+ char *buf;
+} CATINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr);
+static int scanfiles(CATINSTANCE *pThis, char *argv[], int cooked);
+static int cook_cat(CATINSTANCE *pThis, FILE *);
+static int raw_cat(CATINSTANCE *pThis, int);
+
+#ifndef NO_UDOM_SUPPORT
+static int udom_open(PKMKBUILTINCTX pCtx, const char *path, int flags);
+#endif
+
+int
+kmk_builtin_cat(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ CATINSTANCE This;
+ int ch, rc;
+
+ /* kmk: reinitialize globals */
+ This.pCtx = pCtx;
+ This.bflag = This.eflag = This.nflag = This.sflag = This.tflag = This.vflag = 0;
+ This.filename = NULL;
+ This.bsize = 0;
+ This.buf = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "benstuv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'b':
+ This.bflag = This.nflag = 1; /* -b implies -n */
+ break;
+ case 'e':
+ This.eflag = This.vflag = 1; /* -e implies -v */
+ break;
+ case 'n':
+ This.nflag = 1;
+ break;
+ case 's':
+ This.sflag = 1;
+ break;
+ case 't':
+ This.tflag = This.vflag = 1; /* -t implies -v */
+ break;
+ case 'u':
+#ifdef KMK_BUILTIN_STANDALONE /* don't allow messing with stdout */
+ setbuf(stdout, NULL);
+#endif
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ default:
+ return usage(pCtx, 1);
+ }
+ argv += gos.optind;
+
+ if (This.bflag || This.eflag || This.nflag || This.sflag || This.tflag || This.vflag)
+ rc = scanfiles(&This, argv, 1);
+ else
+ rc = scanfiles(&This, argv, 0);
+ if (This.buf) {
+ free(This.buf);
+ This.buf = NULL;
+ }
+#ifdef KMK_BUILTIN_STANDALONE /* don't allow messing with stdout */
+ if (fclose(stdout))
+ return err(pCtx, 1, "stdout");
+#endif
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cat", NULL };
+ setlocale(LC_CTYPE, "");
+ return kmk_builtin_cat(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-benstuv] [file ...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName);
+ return 1;
+}
+
+static int
+scanfiles(CATINSTANCE *pThis, char *argv[], int cooked)
+{
+ int i = 0;
+ char *path;
+ FILE *fp;
+ int rc2 = 0;
+ int rc = 0;
+
+ while ((path = argv[i]) != NULL || i == 0) {
+ int fd;
+
+ if (path == NULL || strcmp(path, "-") == 0) {
+ pThis->filename = "stdin";
+ fd = STDIN_FILENO;
+ } else {
+ pThis->filename = path;
+ fd = open(path, O_RDONLY | KMK_OPEN_NO_INHERIT);
+#ifndef NO_UDOM_SUPPORT
+ if (fd < 0 && errno == EOPNOTSUPP)
+ fd = udom_open(pThis, path, O_RDONLY);
+#endif
+ }
+ if (fd < 0) {
+ warn(pThis->pCtx, "%s", path);
+ rc2 = 1; /* non fatal */
+ } else if (cooked) {
+ if (fd == STDIN_FILENO)
+ rc = cook_cat(pThis, stdin);
+ else {
+ fp = fdopen(fd, "r");
+ rc = cook_cat(pThis, fp);
+ fclose(fp);
+ }
+ } else {
+ rc = raw_cat(pThis, fd);
+ if (fd != STDIN_FILENO)
+ close(fd);
+ }
+ if (rc || path == NULL)
+ break;
+ ++i;
+ }
+ return !rc ? rc2 : rc;
+}
+
+static int
+cat_putchar(PKMKBUILTINCTX pCtx, char ch)
+{
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pCtx->pOut) {
+ output_write_text(pCtx->pOut, 0, &ch, 1);
+ return 0;
+ }
+#endif
+ return putchar(ch);
+}
+
+static int
+cook_cat(CATINSTANCE *pThis, FILE *fp)
+{
+ int ch, gobble, line, prev;
+ int rc = 0;
+
+ /* Reset EOF condition on stdin. */
+ if (fp == stdin && feof(stdin))
+ clearerr(stdin);
+
+ line = gobble = 0;
+ for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) {
+ if (prev == '\n') {
+ if (pThis->sflag) {
+ if (ch == '\n') {
+ if (gobble)
+ continue;
+ gobble = 1;
+ } else
+ gobble = 0;
+ }
+ if (pThis->nflag && (!pThis->bflag || ch != '\n')) {
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%6d\t", ++line);
+ if (ferror(stdout))
+ break;
+ }
+ }
+ if (ch == '\n') {
+ if (pThis->eflag && cat_putchar(pThis->pCtx, '$') == EOF)
+ break;
+ } else if (ch == '\t') {
+ if (pThis->tflag) {
+ if (cat_putchar(pThis->pCtx, '^') == EOF || cat_putchar(pThis->pCtx, 'I') == EOF)
+ break;
+ continue;
+ }
+ } else if (pThis->vflag) {
+ if (!isascii(ch) && !isprint(ch)) {
+ if (cat_putchar(pThis->pCtx, 'M') == EOF || cat_putchar(pThis->pCtx, '-') == EOF)
+ break;
+ ch = toascii(ch);
+ }
+ if (iscntrl(ch)) {
+ if (cat_putchar(pThis->pCtx, '^') == EOF ||
+ cat_putchar(pThis->pCtx, ch == '\177' ? '?' :
+ ch | 0100) == EOF)
+ break;
+ continue;
+ }
+ }
+ if (cat_putchar(pThis->pCtx, ch) == EOF)
+ break;
+ }
+ if (ferror(fp)) {
+ warn(pThis->pCtx, "%s", pThis->filename);
+ rc = 1;
+ clearerr(fp);
+ }
+ if (ferror(stdout))
+ return err(pThis->pCtx, 1, "stdout");
+ return rc;
+}
+
+static int
+raw_cat(CATINSTANCE *pThis, int rfd)
+{
+ int off, wfd = fileno(stdout);
+ ssize_t nr, nw;
+
+ wfd = fileno(stdout);
+ if (pThis->buf == NULL) {
+ struct stat sbuf;
+ if (fstat(wfd, &sbuf))
+ return err(pThis->pCtx, 1, "%s", pThis->filename);
+#ifdef KBUILD_OS_WINDOWS
+ pThis->bsize = 16384;
+#else
+ pThis->bsize = MAX(sbuf.st_blksize, 1024);
+#endif
+ if ((pThis->buf = malloc(pThis->bsize)) == NULL)
+ return err(pThis->pCtx, 1, "buffer");
+ }
+ while ((nr = read(rfd, pThis->buf, pThis->bsize)) > 0)
+ for (off = 0; nr; nr -= nw, off += nw) {
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->pCtx->pOut)
+ nw = output_write_text(pThis->pCtx->pOut, 0, pThis->buf + off, nr);
+ else
+#endif
+ nw = write(wfd, pThis->buf + off, (size_t)nr);
+ if (nw < 0)
+ return err(pThis->pCtx, 1, "stdout");
+ }
+ if (nr < 0) {
+ warn(pThis->pCtx, "%s", pThis->filename);
+ return 1;
+ }
+ return 0;
+}
+
+#ifndef NO_UDOM_SUPPORT
+
+static int
+udom_open(CATINSTANCE *pThis, const char *path, int flags)
+{
+ struct sockaddr_un sou;
+ int fd;
+ unsigned int len;
+
+ bzero(&sou, sizeof(sou));
+
+ /*
+ * Construct the unix domain socket address and attempt to connect
+ */
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd >= 0) {
+ sou.sun_family = AF_UNIX;
+ if ((len = strlcpy(sou.sun_path, path,
+ sizeof(sou.sun_path))) >= sizeof(sou.sun_path)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+ len = offsetof(struct sockaddr_un, sun_path[len+1]);
+
+ if (connect(fd, (void *)&sou, len) < 0) {
+ close(fd);
+ fd = -1;
+ }
+ }
+
+ /*
+ * handle the open flags by shutting down appropriate directions
+ */
+ if (fd >= 0) {
+ switch(flags & O_ACCMODE) {
+ case O_RDONLY:
+ if (shutdown(fd, SHUT_WR) == -1)
+ warn(pThis->pCtx, NULL);
+ break;
+ case O_WRONLY:
+ if (shutdown(fd, SHUT_RD) == -1)
+ warn(pThis->pCtx, NULL);
+ break;
+ default:
+ break;
+ }
+ }
+ return(fd);
+}
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/chmod.c b/src/kmk/kmkbuiltin/chmod.c
new file mode 100644
index 0000000..0061924
--- /dev/null
+++ b/src/kmk/kmkbuiltin/chmod.c
@@ -0,0 +1,288 @@
+/*-
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)chmod.c 8.8 (Berkeley) 4/1/94";
+#endif /* not lint */
+#endif
+/*#include <sys/cdefs.h> */
+/*__FBSDID("$FreeBSD: src/bin/chmod/chmod.c,v 1.33 2005/01/10 08:39:20 imp Exp $");*/
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include "fts.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+#else
+# include "mscfakes.h"
+#endif
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+extern void bsd_strmode(mode_t mode, char *p);
+
+#if (defined(__APPLE__) && !defined(_DARWIN_FEATURE_UNIX_CONFORMANCE)) || defined(__OpenBSD__)
+extern int lchmod(const char *, mode_t);
+#endif
+
+static int usage(PKMKBUILTINCTX pCtx, int is_err);
+
+
+int
+kmk_builtin_chmod(int argc, char *argv[], char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ FTS *ftsp;
+ FTSENT *p;
+ mode_t *set;
+ int Hflag, Lflag, Rflag, ch, fflag, fts_options, hflag, rval;
+ int vflag;
+ char *mode;
+ mode_t newmode;
+ int (*change_mode)(const char *, mode_t);
+
+ set = NULL;
+ Hflag = Lflag = Rflag = fflag = hflag = vflag = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "HLPRXfghorstuvwx", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = 0;
+ break;
+ case 'P':
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ Rflag = 1;
+ break;
+ case 'f':
+ fflag = 1;
+ break;
+ case 'h':
+ /*
+ * In System V (and probably POSIX.2) the -h option
+ * causes chmod to change the mode of the symbolic
+ * link. 4.4BSD's symbolic links didn't have modes,
+ * so it was an undocumented noop. In FreeBSD 3.0,
+ * lchmod(2) is introduced and this option does real
+ * work.
+ */
+ hflag = 1;
+ break;
+ /*
+ * XXX
+ * "-[rwx]" are valid mode commands. If they are the entire
+ * argument, getopt has moved past them, so decrement optind.
+ * Regardless, we're done argument processing.
+ */
+ case 'g': case 'o': case 'r': case 's':
+ case 't': case 'u': case 'w': case 'X': case 'x':
+ if (argv[gos.optind - 1][0] == '-' &&
+ argv[gos.optind - 1][1] == ch &&
+ argv[gos.optind - 1][2] == '\0')
+ --gos.optind;
+ goto done;
+ case 'v':
+ vflag++;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+done: argv += gos.optind;
+ argc -= gos.optind;
+
+ if (argc < 2)
+ return usage(pCtx, 1);
+
+ if (Rflag) {
+ fts_options = FTS_PHYSICAL;
+ if (hflag)
+ return errx(pCtx, 1,
+ "the -R and -h options may not be specified together.");
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else
+ fts_options = hflag ? FTS_PHYSICAL : FTS_LOGICAL;
+#ifndef KMK_BUILTIN_STANDALONE
+ fts_options |= FTS_NOCHDIR; /* Don't change the CWD while inside kmk. */
+#endif
+
+ if (hflag)
+ change_mode = lchmod;
+ else
+ change_mode = chmod;
+
+ mode = *argv;
+ if ((set = bsd_setmode(mode)) == NULL)
+ return errx(pCtx, 1, "invalid file mode: %s", mode);
+
+ if ((ftsp = fts_open(++argv, fts_options, 0)) == NULL)
+ return err(pCtx, 1, "fts_open");
+ for (rval = 0; (p = fts_read(ftsp)) != NULL;) {
+ switch (p->fts_info) {
+ case FTS_D: /* Change it at FTS_DP. */
+ if (!Rflag)
+ fts_set(ftsp, p, FTS_SKIP);
+ continue;
+ case FTS_DNR: /* Warn, chmod, continue. */
+ warnx(pCtx, "fts: %s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ break;
+ case FTS_ERR: /* Warn, continue. */
+ case FTS_NS:
+ warnx(pCtx, "fts: %s: %s", p->fts_path, strerror(p->fts_errno));
+ rval = 1;
+ continue;
+ case FTS_SL: /* Ignore. */
+ case FTS_SLNONE:
+ /*
+ * The only symlinks that end up here are ones that
+ * don't point to anything and ones that we found
+ * doing a physical walk.
+ */
+ if (!hflag)
+ continue;
+ /* else */
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ newmode = bsd_getmode(set, p->fts_statp->st_mode);
+ if ((newmode & ALLPERMS) == (p->fts_statp->st_mode & ALLPERMS))
+ continue;
+ if ((*change_mode)(p->fts_accpath, newmode) && !fflag) {
+ warn(pCtx, "%schmod: %s", hflag ? "l" : "", p->fts_path);
+ rval = 1;
+ } else {
+ if (vflag) {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s", p->fts_path);
+
+ if (vflag > 1) {
+ char m1[12], m2[12];
+
+ bsd_strmode(p->fts_statp->st_mode, m1);
+ bsd_strmode((p->fts_statp->st_mode &
+ S_IFMT) | newmode, m2);
+
+ kmk_builtin_ctx_printf(pCtx, 0, ": 0%o [%s] -> 0%o [%s]",
+ (unsigned int)p->fts_statp->st_mode, m1,
+ (unsigned int)((p->fts_statp->st_mode & S_IFMT) | newmode), m2);
+ }
+ kmk_builtin_ctx_printf(pCtx, 0, "\n");
+ }
+
+ }
+ }
+ if (errno)
+ rval = err(pCtx, 1, "fts_read");
+ free(set);
+ fts_close(ftsp);
+ return rval;
+}
+
+int
+usage(PKMKBUILTINCTX pCtx, int is_err)
+{
+ kmk_builtin_ctx_printf(pCtx, is_err,
+ "usage: %s [-fhv] [-R [-H | -L | -P]] mode file ...\n"
+ " or: %s --version\n"
+ " or: %s --help\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+
+ return 1;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_chmod", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_chmod(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cmp.c b/src/kmk/kmkbuiltin/cmp.c
new file mode 100644
index 0000000..238d044
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp.c
@@ -0,0 +1,153 @@
+/* $NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n");
+static char sccsid[] = "@(#)cmp.c 8.3 (Berkeley) 4/2/94";
+__RCSID("$NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $"); */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef _MSC_VER
+# define MSC_DO_64_BIT_IO /* for correct off_t */
+#endif
+#include "config.h"
+#include <sys/types.h>
+#include "err.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+#else
+# include "mscfakes.h"
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+#include "cmp_extern.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int usage(PKMKBUILTINCTX pCtx, int is_err);
+
+int
+kmk_builtin_cmp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ off_t skip1 = 0, skip2 = 0;
+ int lflag = 0, sflag = 0;
+ int ch;
+ char *file1, *file2;
+
+ getopt_initialize_r(&gos, argc, argv, "ls", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ {
+ switch (ch)
+ {
+ case 'l': /* print all differences */
+ lflag = 1;
+ break;
+ case 's': /* silent run */
+ sflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ }
+ argv += gos.optind;
+ argc -= gos.optind;
+
+ if (argc < 2 || argc > 4)
+ return usage(pCtx, 1);
+
+ file1 = argv[0];
+ file2 = argv[1];
+
+ if (argc > 2)
+ {
+ char *ep;
+
+ errno = 0;
+ skip1 = strtoll(argv[2], &ep, 0);
+ if (errno || ep == argv[2])
+ return errx(pCtx, ERR_EXIT, "strtoll(%s,,) failed", argv[2]);
+
+ if (argc == 4)
+ {
+ skip2 = strtoll(argv[3], &ep, 0);
+ if (errno || ep == argv[3])
+ return errx(pCtx, ERR_EXIT, "strtoll(%s,,) failed", argv[3]);
+ }
+ }
+
+ return cmp_file_and_file_ex(pCtx, file1, skip1, file2, skip2, sflag, lflag, 0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int is_err)
+{
+ kmk_builtin_ctx_printf(pCtx, is_err,
+ "usage: %s [-l | -s] file1 file2 [skip1 [skip2]]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return ERR_EXIT;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cmp", NULL };
+ setlocale(LC_ALL, "");
+ return kmk_builtin_cmp(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/cmp_extern.h b/src/kmk/kmkbuiltin/cmp_extern.h
new file mode 100644
index 0000000..94e69d3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp_extern.h
@@ -0,0 +1,49 @@
+/* $NetBSD: extern.h,v 1.7 2007/08/21 14:09:53 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)extern.h 8.3 (Berkeley) 4/2/94
+ */
+
+#define OK_EXIT 0
+#define DIFF_EXIT 1
+#define ERR_EXIT 2 /* error exit code */
+
+int cmp_file_and_file(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, int sflag, int lflag, int special);
+int cmp_file_and_file_ex(PKMKBUILTINCTX pCtx, const char *file1, off_t skip1,
+ const char *file2, off_t skip2, int sflag, int lflag, int special);
+int cmp_fd_and_file(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ const char *file2, int sflag, int lflag, int special);
+int cmp_fd_and_file_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ const char *file2, off_t skip2, int sflag, int lflag, int special);
+int cmp_fd_and_fd(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ int fd2, const char *file2, int sflag, int lflag, int special);
+int cmp_fd_and_fd_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2, int sflag, int lflag, int special);
+
diff --git a/src/kmk/kmkbuiltin/cmp_util.c b/src/kmk/kmkbuiltin/cmp_util.c
new file mode 100644
index 0000000..0228f38
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cmp_util.c
@@ -0,0 +1,562 @@
+/* $NetBSD: cmp.c,v 1.15 2006/01/19 20:44:57 garbled Exp $ */
+/* $NetBSD: misc.c,v 1.11 2007/08/22 16:59:19 christos Exp $ */
+/* $NetBSD: regular.c,v 1.20 2006/06/03 21:47:55 christos Exp $ */
+/* $NetBSD: special.c,v 1.12 2007/08/21 14:09:54 christos Exp $ */
+
+/*
+ * Copyright (c) 1987, 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n");*/
+
+#ifdef _MSC_VER
+# define MSC_DO_64_BIT_IO
+#endif
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#if defined(__FreeBSD__) || defined(__NetBSD__) /** @todo more mmap capable OSes. */
+# define CMP_USE_MMAP
+# include <sys/param.h>
+# include <sys/mman.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef _MSC_VER
+# include <unistd.h>
+# ifndef O_BINARY
+# define O_BINARY 0
+# endif
+#else
+# include "mscfakes.h"
+#endif
+#include "err.h"
+
+#include "cmp_extern.h"
+
+
+static int
+errmsg(PKMKBUILTINCTX pCtx, const char *file, off_t byte, off_t line, int lflag)
+{
+ if (lflag)
+#ifdef _MSC_VER
+ return err(pCtx, ERR_EXIT, "%s: char %I64d, line %lld", file, (__int64)byte, (long long)line);
+#else
+ return err(pCtx, ERR_EXIT, "%s: char %lld, line %lld", file, (long long)byte, (long long)line);
+#endif
+ return err(pCtx, ERR_EXIT, "%s", file);
+}
+
+
+static int
+eofmsg(PKMKBUILTINCTX pCtx, const char *file, off_t byte, off_t line, int sflag, int lflag)
+{
+ if (!sflag)
+ {
+ if (!lflag)
+ warnx(pCtx, "EOF on %s", file);
+ else
+ {
+#ifdef _MSC_VER
+ if (line > 0)
+ warnx(pCtx, "EOF on %s: char %I64d, line %I64d", file, (__int64)byte, (__int64)line);
+ else
+ warnx(pCtx, "EOF on %s: char %I64d", file, (__int64)byte);
+#else
+ if (line > 0)
+ warnx(pCtx, "EOF on %s: char %lld, line %lld", file, (long long)byte, (long long)line);
+ else
+ warnx(pCtx, "EOF on %s: char %lld", file, (long long)byte);
+#endif
+ }
+ }
+ return DIFF_EXIT;
+}
+
+
+static int
+diffmsg(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, off_t byte, off_t line, int sflag)
+{
+ if (!sflag)
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s differ: char %I64d, line %I64d\n",
+ file1, file2, (__int64)byte, (__int64)line);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s differ: char %lld, line %lld\n",
+ file1, file2, (long long)byte, (long long)line);
+#endif
+ return DIFF_EXIT;
+}
+
+
+/**
+ * Compares two files, where one or both are non-regular ones.
+ */
+static int
+c_special(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2,
+ int lflag, int sflag)
+{
+ int fd1dup, fd2dup;
+ FILE *fp1;
+ int rc;
+
+ /* duplicate because fdopen+fclose will otherwise close the handle. */
+ fd1dup = dup(fd1);
+ if (fd1 < 0)
+ return err(pCtx, ERR_EXIT, "%s", file1);
+ fp1 = fdopen(fd1dup, "rb");
+ if (!fp1)
+ fp1 = fdopen(fd1dup, "r");
+ if (!fp1)
+ {
+ err(pCtx, ERR_EXIT, "%s", file1);
+ close(fd1dup);
+ return ERR_EXIT;
+ }
+
+ fd2dup = dup(fd2);
+ if (fd2dup >= 0)
+ {
+ FILE *fp2 = fdopen(fd2dup, "rb");
+ if (!fp2)
+ fp2 = fdopen(fd2dup, "r");
+ if (fp2)
+ {
+ off_t byte;
+ off_t line;
+ int ch1 = 0;
+ int ch2 = 0;
+
+ /* skipping ahead */
+ rc = OK_EXIT;
+ for (byte = line = 1; skip1--; byte++)
+ {
+ ch1 = getc(fp1);
+ if (ch1 == EOF)
+ break;
+ if (ch1 == '\n')
+ line++;
+ }
+ for (byte = line = 1; skip2--; byte++)
+ {
+ ch2 = getc(fp2);
+ if (ch2 == EOF)
+ break;
+ if (ch2 == '\n')
+ line++;
+ }
+ if (ch2 != EOF && ch1 != EOF)
+ {
+ /* compare byte by byte */
+ for (byte = line = 1;; ++byte)
+ {
+ ch1 = getc(fp1);
+ ch2 = getc(fp2);
+ if (ch1 == EOF || ch2 == EOF)
+ break;
+ if (ch1 != ch2)
+ {
+ if (!lflag)
+ {
+ rc = diffmsg(pCtx, file1, file2, byte, line, sflag);
+ break;
+ }
+ rc = DIFF_EXIT;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6i64d %3o %3o\n", (__int64)byte, ch1, ch2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch1, ch2);
+#endif
+ }
+ if (ch1 == '\n')
+ ++line;
+ }
+ }
+
+ /* Check for errors and length differences (EOF). */
+ if (ferror(fp1) && rc != ERR_EXIT)
+ rc = errmsg(pCtx, file1, byte, line, lflag);
+ if (ferror(fp2) && rc != ERR_EXIT)
+ rc = errmsg(pCtx, file2, byte, line, lflag);
+ if (rc == OK_EXIT)
+ {
+ if (feof(fp1))
+ {
+ if (!feof(fp2))
+ rc = eofmsg(pCtx, file1, byte, line, sflag, lflag);
+ }
+ else if (feof(fp2))
+ rc = eofmsg(pCtx, file2, byte, line, sflag, lflag);
+ }
+
+ fclose(fp2);
+ }
+ else
+ {
+ rc = err(pCtx, ERR_EXIT, "%s", file2);
+ close(fd2dup);
+ }
+ }
+ else
+ rc = err(pCtx, ERR_EXIT, "%s", file2);
+
+ fclose(fp1);
+ return rc;
+}
+
+
+#ifdef CMP_USE_MMAP
+/**
+ * Compare two files using mmap.
+ */
+static int
+c_regular(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
+{
+ unsigned char ch, *p1, *p2, *b1, *b2;
+ off_t byte, length, line;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ return DIFF_EXIT;
+
+ if (skip1 > len1)
+ return eofmsg(pCtx, file1, len1 + 1, 0, sflag, lflag);
+ len1 -= skip1;
+ if (skip2 > len2)
+ return eofmsg(pCtx, file2, len2 + 1, 0, sflag, lflag);
+ len2 -= skip2;
+
+ byte = line = 1;
+ dfound = 0;
+ length = len1 <= len2 ? len1 : len2;
+ for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz)
+ {
+ if (blk_sz > length)
+ blk_sz = length;
+ b1 = p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd1, skip1);
+ if (p1 == MAP_FAILED)
+ goto l_mmap_failed;
+
+ b2 = p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE | MAP_SHARED, fd2, skip2);
+ if (p2 == MAP_FAILED)
+ {
+ munmap(p1, blk_sz);
+ goto l_mmap_failed;
+ }
+
+ blk_cnt = blk_sz;
+ for (; blk_cnt--; ++p1, ++p2, ++byte)
+ {
+ if ((ch = *p1) != *p2)
+ {
+ if (!lflag)
+ {
+ munmap(b1, blk_sz);
+ munmap(b2, blk_sz);
+ return diffmsg(pCtx, file1, file2, byte, line, sflag);
+ }
+ dfound = 1;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch, *p2);
+#endif
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ munmap(p1 - blk_sz, blk_sz);
+ munmap(p2 - blk_sz, blk_sz);
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ return eofmsg(pCtx, len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
+ if (dfound)
+ return DIFF_EXIT;
+ return OK_EXIT;
+
+l_mmap_failed:
+ return c_special(pCtx, fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
+}
+
+#else /* non-mmap c_regular: */
+
+/**
+ * Compare two files without mmaping them.
+ */
+static int
+c_regular(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1, off_t len1,
+ int fd2, const char *file2, off_t skip2, off_t len2, int sflag, int lflag)
+{
+ unsigned char ch, *p1, *p2, *b1 = 0, *b2 = 0;
+ off_t byte, length, line, bytes_read;
+ int dfound;
+ size_t blk_sz, blk_cnt;
+
+ if (sflag && len1 != len2)
+ return DIFF_EXIT;
+
+ if (skip1 > len1)
+ return eofmsg(pCtx, file1, len1 + 1, 0, sflag, lflag);
+ len1 -= skip1;
+ if (skip2 > len2)
+ return eofmsg(pCtx, file2, len2 + 1, 0, sflag, lflag);
+ len2 -= skip2;
+
+ if (skip1 && lseek(fd1, skip1, SEEK_SET) < 0)
+ goto l_special;
+ if (skip2 && lseek(fd2, skip2, SEEK_SET) < 0)
+ {
+ if (skip1 && lseek(fd1, 0, SEEK_SET) < 0)
+ return err(pCtx, 1, "seek failed");
+ goto l_special;
+ }
+
+#define CMP_BUF_SIZE (128*1024)
+
+ b1 = malloc(CMP_BUF_SIZE);
+ b2 = malloc(CMP_BUF_SIZE);
+ if (!b1 || !b2)
+ goto l_malloc_failed;
+
+ byte = line = 1;
+ dfound = 0;
+ length = len1;
+ if (length > len2)
+ length = len2;
+ for (blk_sz = CMP_BUF_SIZE; length != 0; length -= blk_sz)
+ {
+ if ((off_t)blk_sz > length)
+ blk_sz = (size_t)length;
+
+ bytes_read = read(fd1, b1, blk_sz);
+ if (bytes_read != (off_t)blk_sz)
+ goto l_read_error;
+
+ bytes_read = read(fd2, b2, blk_sz);
+ if (bytes_read != (off_t)blk_sz)
+ goto l_read_error;
+
+ blk_cnt = blk_sz;
+ p1 = b1;
+ p2 = b2;
+ for (; blk_cnt--; ++p1, ++p2, ++byte)
+ {
+ if ((ch = *p1) != *p2)
+ {
+ if (!lflag)
+ {
+ free(b1);
+ free(b2);
+ return diffmsg(pCtx, file1, file2, byte, line, sflag);
+ }
+ dfound = 1;
+#ifdef _MSC_VER
+ kmk_builtin_ctx_printf(pCtx, 0, "%6I64d %3o %3o\n", (__int64)byte, ch, *p2);
+#else
+ kmk_builtin_ctx_printf(pCtx, 0, "%6lld %3o %3o\n", (long long)byte, ch, *p2);
+#endif
+ }
+ if (ch == '\n')
+ ++line;
+ }
+ skip1 += blk_sz;
+ skip2 += blk_sz;
+ }
+
+ if (len1 != len2)
+ return eofmsg(pCtx, len1 > len2 ? file2 : file1, byte, line, sflag, lflag);
+ if (dfound)
+ return DIFF_EXIT;
+ return OK_EXIT;
+
+l_read_error:
+ if ( lseek(fd1, 0, SEEK_SET) < 0
+ || lseek(fd2, 0, SEEK_SET) < 0)
+ {
+ err(pCtx, 1, "seek failed");
+ free(b1);
+ free(b2);
+ return 1;
+ }
+l_malloc_failed:
+ free(b1);
+ free(b2);
+l_special:
+ return c_special(pCtx, fd1, file1, skip1, fd2, file2, skip2, lflag, sflag);
+}
+#endif /* non-mmap c_regular */
+
+
+/**
+ * Compares two open files.
+ */
+int
+cmp_fd_and_fd_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ int fd2, const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ struct stat st1, st2;
+ int rc;
+
+ if (fstat(fd1, &st1))
+ return err(pCtx, ERR_EXIT, "%s", file1);
+ if (fstat(fd2, &st2))
+ return err(pCtx, ERR_EXIT, "%s", file2);
+
+ if ( !S_ISREG(st1.st_mode)
+ || !S_ISREG(st2.st_mode)
+ || special)
+ rc = c_special(pCtx, fd1, file1, skip1,
+ fd2, file2, skip2, sflag, lflag);
+ else
+ rc = c_regular(pCtx, fd1, file1, skip1, st1.st_size,
+ fd2, file2, skip2, st2.st_size, sflag, lflag);
+ return rc;
+}
+
+
+/**
+ * Compares two open files.
+ */
+int
+cmp_fd_and_fd(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ int fd2, const char *file2,
+ int sflag, int lflag, int special)
+{
+ return cmp_fd_and_fd_ex(pCtx, fd1, file1, 0, fd2, file2, 0, sflag, lflag, special);
+}
+
+
+/**
+ * Compares an open file with another that isn't open yet.
+ */
+int
+cmp_fd_and_file_ex(PKMKBUILTINCTX pCtx, int fd1, const char *file1, off_t skip1,
+ const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ int rc;
+ int fd2;
+
+ if (!strcmp(file2, "-"))
+ {
+ fd2 = 0 /* stdin */;
+ special = 1;
+ file2 = "stdin";
+ }
+ else
+ fd2 = open(file2, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (fd2 >= 0)
+ {
+ rc = cmp_fd_and_fd_ex(pCtx, fd1, file1, skip1,
+ fd2, file2, skip2, sflag, lflag, special);
+ close(fd2);
+ }
+ else
+ {
+ if (!sflag)
+ warn(pCtx, "%s", file2);
+ rc = ERR_EXIT;
+ }
+ return rc;
+}
+
+
+/**
+ * Compares an open file with another that isn't open yet.
+ */
+int
+cmp_fd_and_file(PKMKBUILTINCTX pCtx, int fd1, const char *file1,
+ const char *file2,
+ int sflag, int lflag, int special)
+{
+ return cmp_fd_and_file_ex(pCtx, fd1, file1, 0,
+ file2, 0, sflag, lflag, special);
+}
+
+
+/**
+ * Opens and compare two files.
+ */
+int
+cmp_file_and_file_ex(PKMKBUILTINCTX pCtx, const char *file1, off_t skip1,
+ const char *file2, off_t skip2,
+ int sflag, int lflag, int special)
+{
+ int fd1;
+ int rc;
+
+ if (lflag && sflag)
+ return errx(pCtx, ERR_EXIT, "only one of -l and -s may be specified");
+
+ if (!strcmp(file1, "-"))
+ {
+ if (!strcmp(file2, "-"))
+ return errx(pCtx, ERR_EXIT, "standard input may only be specified once");
+ file1 = "stdin";
+ fd1 = 1;
+ special = 1;
+ }
+ else
+ fd1 = open(file1, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (fd1 >= 0)
+ {
+ rc = cmp_fd_and_file_ex(pCtx, fd1, file1, skip1,
+ file2, skip2, sflag, lflag, special);
+ close(fd1);
+ }
+ else
+ {
+ if (!sflag)
+ warn(pCtx, "%s", file1);
+ rc = ERR_EXIT;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Opens and compare two files.
+ */
+int
+cmp_file_and_file(PKMKBUILTINCTX pCtx, const char *file1, const char *file2, int sflag, int lflag, int special)
+{
+ return cmp_file_and_file_ex(pCtx, file1, 0, file2, 0, sflag, lflag, special);
+}
+
diff --git a/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c b/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c
new file mode 100644
index 0000000..a7f6d58
--- /dev/null
+++ b/src/kmk/kmkbuiltin/common-env-and-cwd-opt.c
@@ -0,0 +1,516 @@
+/* $Id: common-env-and-cwd-opt.c 3332 2020-04-19 23:08:16Z bird $ */
+/** @file
+ * kMk Builtin command - Commmon environment and CWD option handling code.
+ */
+
+/*
+ * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+/** The environment variable compare function.
+ * We must use case insensitive compare on windows (Path vs PATH). */
+#ifdef KBUILD_OS_WINDOWS
+# define KSUBMIT_ENV_NCMP _strnicmp
+#else
+# define KSUBMIT_ENV_NCMP strncmp
+#endif
+
+
+/**
+ * Duplicates a read-only enviornment vector.
+ *
+ * @returns The duplicate enviornment.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The read-only vector.
+ * @param cEnvVars The number of variables.
+ * @param pcAllocatedEnvVars The allocated papszEnv size. This is zero on
+ * input and non-zero on successful return.
+ * @param cVerbosity The verbosity level.
+ */
+static char **kBuiltinOptEnvDuplicate(PKMKBUILTINCTX pCtx, char **papszEnv, unsigned cEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity)
+{
+ unsigned cAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
+ char **papszEnvNew = malloc(cAllocatedEnvVars * sizeof(papszEnvNew[0]));
+ assert(*pcAllocatedEnvVars == 0);
+ if (papszEnvNew)
+ {
+ unsigned i;
+ for (i = 0; i < cEnvVars; i++)
+ {
+ papszEnvNew[i] = strdup(papszEnv[i]);
+ if (!papszEnvNew)
+ {
+ while (i-- > 0)
+ free(papszEnvNew[i]);
+ free(papszEnvNew);
+ errx(pCtx, 1, "out of memory for duplicating environment variables!", i);
+ return NULL;
+ }
+ }
+ papszEnvNew[i] = NULL;
+ *pcAllocatedEnvVars = cAllocatedEnvVars;
+ }
+ else
+ errx(pCtx, 1, "out of memory for duplicating environment vector!");
+ return papszEnvNew;
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvSet and kBuiltinOptEnvAppendPrepend that adds
+ * a new variable to the environment.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The environment vector.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+static int kBuiltinOptEnvAddVar(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ /* Append new variable. We probably need to resize the vector. */
+ char **papszEnv = *ppapszEnv;
+ unsigned cEnvVars = *pcEnvVars;
+ if ((cEnvVars + 2) > *pcAllocatedEnvVars)
+ {
+ *pcAllocatedEnvVars = (cEnvVars + 2 + 0xf) & ~(unsigned)0xf;
+ papszEnv = (char **)realloc(papszEnv, *pcAllocatedEnvVars * sizeof(papszEnv[0]));
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory growing environment vector!");
+ *ppapszEnv = papszEnv;
+ }
+ papszEnv[cEnvVars] = strdup(pszValue);
+ if (!papszEnv[cEnvVars])
+ return errx(pCtx, 1, "out of memory adding environment variable!");
+ papszEnv[++cEnvVars] = NULL;
+ *pcEnvVars = cEnvVars;
+ if (cVerbosity > 0)
+ warnx(pCtx, "added '%s'", papszEnv[cEnvVars - 1]);
+ return 0;
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvSet and kBuiltinOptEnvAppendPrepend that
+ * remove duplicates.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param papszEnv The environment vector.
+ * @param cEnvVars Number of environment variables.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ * @param cchVar The length of the variable part of @a pszValue.
+ * @param iEnvVar Where to start searching after.
+ */
+static int kBuiltinOptEnvRemoveDuplicates(PKMKBUILTINCTX pCtx, char **papszEnv, unsigned cEnvVars, int cVerbosity,
+ const char *pszValue, size_t cchVar, unsigned iEnvVar)
+{
+ for (iEnvVar++; iEnvVar < cEnvVars; iEnvVar++)
+ if ( KSUBMIT_ENV_NCMP(papszEnv[iEnvVar], pszValue, cchVar) == 0
+ && papszEnv[iEnvVar][cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "removing duplicate '%s'", papszEnv[iEnvVar]);
+ free(papszEnv[iEnvVar]);
+ cEnvVars--;
+ if (iEnvVar != cEnvVars)
+ papszEnv[iEnvVar] = papszEnv[cEnvVars];
+ papszEnv[cEnvVars] = NULL;
+ iEnvVar--;
+ }
+ return 0;
+}
+
+
+/**
+ * Handles the --set var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvSet(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ const char *pszEqual = strchr(pszValue, '=');
+ if (pszEqual)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned iEnvVar;
+ unsigned cEnvVars = *pcEnvVars;
+ size_t const cchVar = pszEqual - pszValue;
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating enviornment (setenv)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ {
+ char *pszCur = papszEnv[iEnvVar];
+ if ( KSUBMIT_ENV_NCMP(pszCur, pszValue, cchVar) == 0
+ && pszCur[cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "replacing '%s' with '%s'", papszEnv[iEnvVar], pszValue);
+ free(papszEnv[iEnvVar]);
+ papszEnv[iEnvVar] = strdup(pszValue);
+ if (!papszEnv[iEnvVar])
+ return errx(pCtx, 1, "out of memory for modified environment variable!");
+
+ return kBuiltinOptEnvRemoveDuplicates(pCtx, papszEnv, cEnvVars, cVerbosity, pszValue, cchVar, iEnvVar);
+ }
+ }
+ return kBuiltinOptEnvAddVar(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue);
+ }
+ return errx(pCtx, 1, "Missing '=': -E %s", pszValue);
+}
+
+
+/**
+ * Common worker for kBuiltinOptEnvAppend and kBuiltinOptEnvPrepend.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+static int kBuiltinOptEnvAppendPrepend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue, int fAppend)
+{
+ const char *pszEqual = strchr(pszValue, '=');
+ if (pszEqual)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned iEnvVar;
+ unsigned cEnvVars = *pcEnvVars;
+ size_t const cchVar = pszEqual - pszValue;
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating environment (append)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ {
+ char *pszCur = papszEnv[iEnvVar];
+ if ( KSUBMIT_ENV_NCMP(pszCur, pszValue, cchVar) == 0
+ && pszCur[cchVar] == '=')
+ {
+ size_t cchOldValue = strlen(pszCur) - cchVar - 1;
+ size_t cchNewValue = strlen(pszValue) - cchVar - 1;
+ char *pszNew = malloc(cchVar + 1 + cchOldValue + cchNewValue + 1);
+ if (!pszNew)
+ return errx(pCtx, 1, "out of memory appending to environment variable!");
+ if (fAppend)
+ {
+ memcpy(pszNew, pszCur, cchVar + 1 + cchOldValue);
+ memcpy(&pszNew[cchVar + 1 + cchOldValue], &pszValue[cchVar + 1], cchNewValue + 1);
+ }
+ else
+ {
+ memcpy(pszNew, pszCur, cchVar + 1); /* preserve variable name case */
+ memcpy(&pszNew[cchVar + 1], &pszValue[cchVar + 1], cchNewValue);
+ memcpy(&pszNew[cchVar + 1 + cchNewValue], &pszCur[cchVar + 1], cchOldValue + 1);
+ }
+
+ if (cVerbosity > 0)
+ warnx(pCtx, "replacing '%s' with '%s'", pszCur, pszNew);
+ free(pszCur);
+ papszEnv[iEnvVar] = pszNew;
+
+ return kBuiltinOptEnvRemoveDuplicates(pCtx, papszEnv, cEnvVars, cVerbosity, pszValue, cchVar, iEnvVar);
+ }
+ }
+ return kBuiltinOptEnvAddVar(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue);
+ }
+ return errx(pCtx, 1, "Missing '=': -%c %s", fAppend ? 'A' : 'D', pszValue);
+}
+
+
+/**
+ * Handles the --append var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvAppend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ return kBuiltinOptEnvAppendPrepend(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue, 1 /*fAppend*/);
+}
+
+
+/**
+ * Handles the --prepend var=value option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the variable holding max size of the
+ * environment vector.
+ * @param cVerbosity The verbosity level.
+ * @param pszValue The var=value string to apply.
+ */
+int kBuiltinOptEnvPrepend(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszValue)
+{
+ return kBuiltinOptEnvAppendPrepend(pCtx, ppapszEnv, pcEnvVars, pcAllocatedEnvVars, cVerbosity, pszValue, 0 /*fAppend*/);
+}
+
+
+/**
+ * Handles the --unset var option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ * @param cVerbosity The verbosity level.
+ * @param pszVarToRemove The name of the variable to remove.
+ */
+int kBuiltinOptEnvUnset(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars,
+ int cVerbosity, const char *pszVarToRemove)
+{
+ if (strchr(pszVarToRemove, '=') == NULL)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned cRemoved = 0;
+ size_t const cchVar = strlen(pszVarToRemove);
+ unsigned cEnvVars = *pcEnvVars;
+ unsigned iEnvVar;
+
+ for (iEnvVar = 0; iEnvVar < cEnvVars; iEnvVar++)
+ if ( KSUBMIT_ENV_NCMP(papszEnv[iEnvVar], pszVarToRemove, cchVar) == 0
+ && papszEnv[iEnvVar][cchVar] == '=')
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, !cRemoved ? "removing '%s'" : "removing duplicate '%s'", papszEnv[iEnvVar]);
+
+ if (!*pcAllocatedEnvVars)
+ {
+ papszEnv = kBuiltinOptEnvDuplicate(pCtx, papszEnv, cEnvVars, pcAllocatedEnvVars, cVerbosity);
+ if (!papszEnv)
+ return errx(pCtx, 1, "out of memory duplicating environment (unset)!");
+ *ppapszEnv = papszEnv;
+ }
+
+ free(papszEnv[iEnvVar]);
+ cEnvVars--;
+ if (iEnvVar != cEnvVars)
+ papszEnv[iEnvVar] = papszEnv[cEnvVars];
+ papszEnv[cEnvVars] = NULL;
+ cRemoved++;
+ iEnvVar--;
+ }
+ *pcEnvVars = cEnvVars;
+
+ if (cVerbosity > 0 && !cRemoved)
+ warnx(pCtx, "not found '%s'", pszVarToRemove);
+ }
+ else
+ return errx(pCtx, 1, "Found invalid variable name character '=' in: -U %s", pszVarToRemove);
+ return 0;
+}
+
+
+/**
+ * Handles the --zap-env & --ignore-environment options.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param ppapszEnv The environment vector pointer.
+ * @param pcEnvVars Pointer to the variable holding the number of
+ * environment variables held by @a papszEnv.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ * @param cVerbosity The verbosity level.
+ */
+int kBuiltinOptEnvZap(PKMKBUILTINCTX pCtx, char ***ppapszEnv, unsigned *pcEnvVars, unsigned *pcAllocatedEnvVars, int cVerbosity)
+{
+ if (*pcAllocatedEnvVars > 0)
+ {
+ char **papszEnv = *ppapszEnv;
+ unsigned i = *pcEnvVars;
+ while (i-- > 0)
+ {
+ free(papszEnv[i]);
+ papszEnv[i] = NULL;
+ }
+ }
+ else
+ {
+ char **papszEnv = calloc(4, sizeof(char *));
+ if (!papszEnv)
+ return err(pCtx, 1, "out of memory!");
+ *ppapszEnv = papszEnv;
+ *pcAllocatedEnvVars = 4;
+ }
+ *pcEnvVars = 0;
+ return 0;
+}
+
+
+/**
+ * Cleans up afterwards, if necessary.
+ *
+ * @param ppapszEnv The environment vector pointer.
+ * @param cEnvVars The number of variables in the vector.
+ * @param pcAllocatedEnvVars Pointer to the size of the vector allocation.
+ * The size is zero when read-only (CRT, GNU make)
+ * environment.
+ */
+void kBuiltinOptEnvCleanup(char ***ppapszEnv, unsigned cEnvVars, unsigned *pcAllocatedEnvVars)
+{
+ char **papszEnv = *ppapszEnv;
+ *ppapszEnv = NULL;
+ if (*pcAllocatedEnvVars > 0)
+ {
+ *pcAllocatedEnvVars = 0;
+ while (cEnvVars-- > 0)
+ {
+ free(papszEnv[cEnvVars]);
+ papszEnv[cEnvVars] = NULL;
+ }
+ free(papszEnv);
+ }
+}
+
+
+/**
+ * Handles the --chdir dir option.
+ *
+ * @returns 0 on success, non-zero exit code on error.
+ * @param pCtx The built-in command context.
+ * @param pszCwd The CWD buffer. Contains current CWD on input,
+ * modified by @a pszValue on output.
+ * @param cbCwdBuf The size of the CWD buffer.
+ * @param pszValue The --chdir value to apply.
+ */
+int kBuiltinOptChDir(PKMKBUILTINCTX pCtx, char *pszCwd, size_t cbCwdBuf, const char *pszValue)
+{
+ size_t cchNewCwd = strlen(pszValue);
+ size_t offDst;
+ if (cchNewCwd)
+ {
+#ifdef HAVE_DOS_PATHS
+ if (*pszValue == '/' || *pszValue == '\\')
+ {
+ if (pszValue[1] == '/' || pszValue[1] == '\\')
+ offDst = 0; /* UNC */
+ else if (pszCwd[1] == ':' && isalpha(pszCwd[0]))
+ offDst = 2; /* Take drive letter from CWD. */
+ else
+ return errx(pCtx, 1, "UNC relative CWD not implemented: cur='%s' new='%s'", pszCwd, pszValue);
+ }
+ else if ( pszValue[1] == ':'
+ && isalpha(pszValue[0]))
+ {
+ if (pszValue[2] == '/'|| pszValue[2] == '\\')
+ offDst = 0; /* DOS style absolute path. */
+ else if ( pszCwd[1] == ':'
+ && tolower(pszCwd[0]) == tolower(pszValue[0]) )
+ {
+ pszValue += 2; /* Same drive as CWD, append drive relative path from value. */
+ cchNewCwd -= 2;
+ offDst = strlen(pszCwd);
+ }
+ else
+ {
+ /* Get current CWD on the specified drive and append value. */
+ int iDrive = tolower(pszValue[0]) - 'a' + 1;
+ if (!_getdcwd(iDrive, pszCwd, cbCwdBuf))
+ return err(pCtx, 1, "_getdcwd(%d,,) failed", iDrive);
+ pszValue += 2;
+ cchNewCwd -= 2;
+ }
+ }
+#else
+ if (*pszValue == '/')
+ offDst = 0;
+#endif
+ else
+ offDst = strlen(pszCwd); /* Relative path, append to the existing CWD value. */
+
+ /* Do the copying. */
+#ifdef HAVE_DOS_PATHS
+ if (offDst > 0 && pszCwd[offDst - 1] != '/' && pszCwd[offDst - 1] != '\\')
+#else
+ if (offDst > 0 && pszCwd[offDst - 1] != '/')
+#endif
+ pszCwd[offDst++] = '/';
+ if (offDst + cchNewCwd >= cbCwdBuf)
+ return errx(pCtx, 1, "Too long CWD: %*.*s%s", offDst, offDst, pszCwd, pszValue);
+ memcpy(&pszCwd[offDst], pszValue, cchNewCwd + 1);
+ }
+ /* else: relative, no change - quitely ignore. */
+ return 0;
+}
+
diff --git a/src/kmk/kmkbuiltin/cp.c b/src/kmk/kmkbuiltin/cp.c
new file mode 100644
index 0000000..6719fc2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp.c
@@ -0,0 +1,779 @@
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * David Hitz of Auspex Systems Inc.
+ *
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1988, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)cp.c 8.2 (Berkeley) 4/1/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cp/cp.c,v 1.50 2004/04/06 20:06:44 markm Exp $");
+#endif
+
+/*
+ * Cp copies source files to target files.
+ *
+ * The global PATH_T structure "to" always contains the path to the
+ * current target file. Since fts(3) does not change directories,
+ * this path can be either absolute or dot-relative.
+ *
+ * The basic algorithm is to initialize "to" and use fts(3) to traverse
+ * the file hierarchy rooted in the argument list. A trivial case is the
+ * case of 'cp file1 file2'. The more interesting case is the case of
+ * 'cp file1 file2 ... fileN dir' where the hierarchy is traversed and the
+ * path (relative to the root of the traversal) is appended to dir (stored
+ * in "to") to form the final target path.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include "err.h"
+#include <errno.h>
+#include "fts.h"
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#include "k/kDefs.h"
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "cp_extern.h"
+#include "kmkbuiltin.h"
+#include "kbuild_protection.h"
+
+#if defined(_MSC_VER) || defined(__gnu_linux__) || defined(__linux__)
+extern size_t strlcpy(char *, const char *, size_t);
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef S_IFWHT
+#define S_IFWHT 0
+#define S_ISWHT(s) 0
+#define undelete(s) (-1)
+#endif
+
+#ifndef S_ISTXT
+#ifdef S_ISVTX
+#define S_ISTXT S_ISVTX
+#else
+#define S_ISTXT 0
+#endif
+#endif /* !S_ISTXT */
+
+#ifndef __unused
+# define __unused
+#endif
+
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
+#else
+# define IS_SLASH(ch) ((ch) == '/')
+#endif
+
+#define STRIP_TRAILING_SLASH(p) { \
+ while ((p).p_end > (p).p_path + 1 && IS_SLASH((p).p_end[-1])) \
+ *--(p).p_end = 0; \
+}
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct CPINSTANCE
+{
+ CPUTILSINSTANCE Utils;
+ int Rflag, rflag;
+ int cp_ignore_non_existing, cp_changed_only;
+ KBUILDPROTECTION g_ProtData;
+} CPINSTANCE;
+
+/* have wrappers for globals in cp_extern! */
+
+
+enum op { FILE_TO_FILE, FILE_TO_DIR, DIR_TO_DNE };
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+enum cp_arg {
+ CP_OPT_HELP = 261,
+ CP_OPT_VERSION,
+ CP_OPT_IGNORE_NON_EXISTING,
+ CP_OPT_CHANGED,
+ CP_OPT_DISABLE_PROTECTION,
+ CP_OPT_ENABLE_PROTECTION,
+ CP_OPT_ENABLE_FULL_PROTECTION,
+ CP_OPT_DISABLE_FULL_PROTECTION,
+ CP_OPT_PROTECTION_DEPTH
+};
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, CP_OPT_HELP },
+ { "version", no_argument, 0, CP_OPT_VERSION },
+ { "ignore-non-existing", no_argument, 0, CP_OPT_IGNORE_NON_EXISTING },
+ { "changed", no_argument, 0, CP_OPT_CHANGED },
+ { "disable-protection", no_argument, 0, CP_OPT_DISABLE_PROTECTION },
+ { "enable-protection", no_argument, 0, CP_OPT_ENABLE_PROTECTION },
+ { "enable-full-protection", no_argument, 0, CP_OPT_ENABLE_FULL_PROTECTION },
+ { "disable-full-protection", no_argument, 0, CP_OPT_DISABLE_FULL_PROTECTION },
+ { "protection-depth", required_argument, 0, CP_OPT_PROTECTION_DEPTH },
+ { 0, 0, 0, 0 },
+};
+
+static char emptystring[] = "";
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+volatile sig_atomic_t g_cp_info;
+#endif
+
+extern mode_t g_fUMask;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int copy(CPINSTANCE *pThis, char * const *, enum op, int);
+#ifdef FTSCALL
+static int FTSCALL mastercmp(const FTSENT * const *, const FTSENT * const *);
+#else
+static int mastercmp(const FTSENT **, const FTSENT **);
+#endif
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+static void siginfo(int __unused);
+#endif
+static int usage(PKMKBUILTINCTX, int);
+
+int
+kmk_builtin_cp(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ CPINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat to_stat, tmp_stat;
+ enum op type;
+ int Hflag, Lflag, Pflag, ch, fts_options, r, have_trailing_slash, rc;
+ char *target;
+
+ /* init globals */
+ This.Utils.pCtx = pCtx;
+ This.Utils.to.p_end = This.Utils.to.p_path;
+ This.Utils.to.target_end = emptystring;
+ memset(This.Utils.to.p_path, 0, sizeof(This.Utils.to.p_path));
+ This.Utils.fflag = 0;
+ This.Utils.iflag = 0;
+ This.Utils.nflag = 0;
+ This.Utils.pflag = 0;
+ This.Utils.vflag = 0;
+ This.Rflag = 0;
+ This.rflag = 0;
+ This.cp_ignore_non_existing = This.cp_changed_only = 0;
+ kBuildProtectionInit(&This.g_ProtData, pCtx);
+
+ Hflag = Lflag = Pflag = 0;
+ getopt_initialize_r(&gos, argc, argv, "HLPRfinprv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'H':
+ Hflag = 1;
+ Lflag = Pflag = 0;
+ break;
+ case 'L':
+ Lflag = 1;
+ Hflag = Pflag = 0;
+ break;
+ case 'P':
+ Pflag = 1;
+ Hflag = Lflag = 0;
+ break;
+ case 'R':
+ This.Rflag = 1;
+ break;
+ case 'f':
+ This.Utils.fflag = 1;
+ This.Utils.iflag = This.Utils.nflag = 0;
+ break;
+ case 'i':
+ This.Utils.iflag = 1;
+ This.Utils.fflag = This.Utils.nflag = 0;
+ break;
+ case 'n':
+ This.Utils.nflag = 1;
+ This.Utils.fflag = This.Utils.iflag = 0;
+ break;
+ case 'p':
+ This.Utils.pflag = 1;
+ break;
+ case 'r':
+ This.rflag = 1;
+ break;
+ case 'v':
+ This.Utils.vflag = 1;
+ break;
+ case CP_OPT_HELP:
+ usage(pCtx, 0);
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 0;
+ case CP_OPT_VERSION:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return kbuild_version(argv[0]);
+ case CP_OPT_IGNORE_NON_EXISTING:
+ This.cp_ignore_non_existing = 1;
+ break;
+ case CP_OPT_CHANGED:
+ This.cp_changed_only = 1;
+ break;
+ case CP_OPT_DISABLE_PROTECTION:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case CP_OPT_ENABLE_PROTECTION:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case CP_OPT_ENABLE_FULL_PROTECTION:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case CP_OPT_DISABLE_FULL_PROTECTION:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case CP_OPT_PROTECTION_DEPTH:
+ if (kBuildProtectionSetDepth(&This.g_ProtData, gos.optarg)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 1;
+ }
+ break;
+ default:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 2) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+
+ fts_options = FTS_NOCHDIR | FTS_PHYSICAL;
+ if (This.rflag) {
+ if (This.Rflag) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return errx(pCtx, 1,
+ "the -R and -r options may not be specified together.");
+ }
+ if (Hflag || Lflag || Pflag)
+ errx(pCtx, 1,
+ "the -H, -L, and -P options may not be specified with the -r option.");
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ if (This.Rflag) {
+ if (Hflag)
+ fts_options |= FTS_COMFOLLOW;
+ if (Lflag) {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL;
+ }
+ } else {
+ fts_options &= ~FTS_PHYSICAL;
+ fts_options |= FTS_LOGICAL | FTS_COMFOLLOW;
+ }
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ (void)signal(SIGINFO, siginfo);
+#endif
+
+ /* Save the target base in "to". */
+ target = argv[--argc];
+ if (strlcpy(This.Utils.to.p_path, target, sizeof(This.Utils.to.p_path)) >= sizeof(This.Utils.to.p_path)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return errx(pCtx, 1, "%s: name too long", target);
+ }
+ This.Utils.to.p_end = This.Utils.to.p_path + strlen(This.Utils.to.p_path);
+ if (This.Utils.to.p_path == This.Utils.to.p_end) {
+ *This.Utils.to.p_end++ = '.';
+ *This.Utils.to.p_end = 0;
+ }
+ have_trailing_slash = IS_SLASH(This.Utils.to.p_end[-1]);
+ if (have_trailing_slash)
+ STRIP_TRAILING_SLASH(This.Utils.to);
+ This.Utils.to.target_end = This.Utils.to.p_end;
+
+ /* Set end of argument list for fts(3). */
+ argv[argc] = NULL;
+
+ /*
+ * Cp has two distinct cases:
+ *
+ * cp [-R] source target
+ * cp [-R] source1 ... sourceN directory
+ *
+ * In both cases, source can be either a file or a directory.
+ *
+ * In (1), the target becomes a copy of the source. That is, if the
+ * source is a file, the target will be a file, and likewise for
+ * directories.
+ *
+ * In (2), the real target is not directory, but "directory/source".
+ */
+ r = stat(This.Utils.to.p_path, &to_stat);
+ if (r == -1 && errno != ENOENT) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return err(pCtx, 1, "stat: %s", This.Utils.to.p_path);
+ }
+ if (r == -1 || !S_ISDIR(to_stat.st_mode)) {
+ /*
+ * Case (1). Target is not a directory.
+ */
+ if (argc > 1) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ /*
+ * Need to detect the case:
+ * cp -R dir foo
+ * Where dir is a directory and foo does not exist, where
+ * we want pathname concatenations turned on but not for
+ * the initial mkdir().
+ */
+ if (r == -1) {
+ if (This.rflag || (This.Rflag && (Lflag || Hflag)))
+ stat(*argv, &tmp_stat);
+ else
+ lstat(*argv, &tmp_stat);
+
+ if (S_ISDIR(tmp_stat.st_mode) && (This.Rflag || This.rflag))
+ type = DIR_TO_DNE;
+ else
+ type = FILE_TO_FILE;
+ } else
+ type = FILE_TO_FILE;
+
+ if (have_trailing_slash && type == FILE_TO_FILE) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ if (r == -1)
+ return errx(pCtx, 1, "directory %s does not exist",
+ This.Utils.to.p_path);
+ else
+ return errx(pCtx, 1, "%s is not a directory", This.Utils.to.p_path);
+ }
+ } else
+ /*
+ * Case (2). Target is a directory.
+ */
+ type = FILE_TO_DIR;
+
+ /* Finally, check that the "to" directory isn't protected. */
+ rc = 1;
+ if (!kBuildProtectionScanEnv(&This.g_ProtData, envp, "KMK_CP_")
+ && !kBuildProtectionEnforce(&This.g_ProtData,
+ This.Rflag || This.rflag
+ ? KBUILDPROTECTIONTYPE_RECURSIVE
+ : KBUILDPROTECTIONTYPE_FULL,
+ This.Utils.to.p_path)) {
+ rc = copy(&This, argv, type, fts_options);
+ }
+
+ kBuildProtectionTerm(&This.g_ProtData);
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_cp", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_cp(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+copy(CPINSTANCE *pThis, char * const *argv, enum op type, int fts_options)
+{
+ struct stat to_stat;
+ FTS *ftsp;
+ FTSENT *curr;
+ int base = 0, dne, badcp, rval;
+ size_t nlen;
+ char *p, *target_mid;
+ mode_t mask, mode;
+
+ /*
+ * Keep an inverted copy of the umask, for use in correcting
+ * permissions on created directories when not using -p.
+ */
+ mask = g_fUMask;
+ assert(mask == umask(mask));
+ mask = ~mask;
+
+ if ((ftsp = fts_open(argv, fts_options, mastercmp)) == NULL)
+ return err(pThis->Utils.pCtx, 1, "fts_open");
+ for (badcp = rval = 0; (curr = fts_read(ftsp)) != NULL; badcp = 0) {
+ int copied = 0;
+
+ switch (curr->fts_info) {
+ case FTS_NS:
+ if ( pThis->cp_ignore_non_existing
+ && curr->fts_errno == ENOENT) {
+ if (pThis->Utils.vflag) {
+ warnx(pThis->Utils.pCtx, "fts: %s: %s", curr->fts_path,
+ strerror(curr->fts_errno));
+ }
+ continue;
+ }
+ /* fall thru */
+ case FTS_DNR:
+ case FTS_ERR:
+ warnx(pThis->Utils.pCtx, "fts: %s: %s",
+ curr->fts_path, strerror(curr->fts_errno));
+ badcp = rval = 1;
+ continue;
+ case FTS_DC: /* Warn, continue. */
+ warnx(pThis->Utils.pCtx, "%s: directory causes a cycle", curr->fts_path);
+ badcp = rval = 1;
+ continue;
+ default:
+ ;
+ }
+
+ /*
+ * If we are in case (2) or (3) above, we need to append the
+ * source name to the target name.
+ */
+ if (type != FILE_TO_FILE) {
+ /*
+ * Need to remember the roots of traversals to create
+ * correct pathnames. If there's a directory being
+ * copied to a non-existent directory, e.g.
+ * cp -R a/dir noexist
+ * the resulting path name should be noexist/foo, not
+ * noexist/dir/foo (where foo is a file in dir), which
+ * is the case where the target exists.
+ *
+ * Also, check for "..". This is for correct path
+ * concatenation for paths ending in "..", e.g.
+ * cp -R .. /tmp
+ * Paths ending in ".." are changed to ".". This is
+ * tricky, but seems the easiest way to fix the problem.
+ *
+ * XXX
+ * Since the first level MUST be FTS_ROOTLEVEL, base
+ * is always initialized.
+ */
+ if (curr->fts_level == FTS_ROOTLEVEL) {
+ if (type != DIR_TO_DNE) {
+ p = strrchr(curr->fts_path, '/');
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ if (strrchr(curr->fts_path, '\\') > p)
+ p = strrchr(curr->fts_path, '\\');
+#endif
+ base = (p == NULL) ? 0 :
+ (int)(p - curr->fts_path + 1);
+
+ if (!strcmp(&curr->fts_path[base],
+ ".."))
+ base += 1;
+ } else
+ base = curr->fts_pathlen;
+ }
+
+ p = &curr->fts_path[base];
+ nlen = curr->fts_pathlen - base;
+ target_mid = pThis->Utils.to.target_end;
+ if (!IS_SLASH(*p) && !IS_SLASH(target_mid[-1]))
+ *target_mid++ = '/';
+ *target_mid = 0;
+ if (target_mid - pThis->Utils.to.p_path + nlen >= PATH_MAX) {
+ warnx(pThis->Utils.pCtx, "%s%s: name too long (not copied)",
+ pThis->Utils.to.p_path, p);
+ badcp = rval = 1;
+ continue;
+ }
+ (void)strncat(target_mid, p, nlen);
+ pThis->Utils.to.p_end = target_mid + nlen;
+ *pThis->Utils.to.p_end = 0;
+ STRIP_TRAILING_SLASH(pThis->Utils.to);
+ }
+
+ if (curr->fts_info == FTS_DP) {
+ /*
+ * We are nearly finished with this directory. If we
+ * didn't actually copy it, or otherwise don't need to
+ * change its attributes, then we are done.
+ */
+ if (!curr->fts_number)
+ continue;
+ /*
+ * If -p is in effect, set all the attributes.
+ * Otherwise, set the correct permissions, limited
+ * by the umask. Optimise by avoiding a chmod()
+ * if possible (which is usually the case if we
+ * made the directory). Note that mkdir() does not
+ * honour setuid, setgid and sticky bits, but we
+ * normally want to preserve them on directories.
+ */
+ if (pThis->Utils.pflag) {
+ if (copy_file_attribs(&pThis->Utils, curr->fts_statp, -1))
+ rval = 1;
+ } else {
+ mode = curr->fts_statp->st_mode;
+ if ((mode & (S_ISUID | S_ISGID | S_ISTXT)) ||
+ ((mode | S_IRWXU) & mask) != (mode & mask))
+ if (chmod(pThis->Utils.to.p_path, mode & mask) != 0){
+ warn(pThis->Utils.pCtx, "chmod: %s", pThis->Utils.to.p_path);
+ rval = 1;
+ }
+ }
+ continue;
+ }
+
+ /* Not an error but need to remember it happened */
+ if (stat(pThis->Utils.to.p_path, &to_stat) == -1)
+ dne = 1;
+ else {
+ if (to_stat.st_dev == curr->fts_statp->st_dev &&
+ to_stat.st_dev != 0 &&
+ to_stat.st_ino == curr->fts_statp->st_ino &&
+ to_stat.st_ino != 0) {
+ warnx(pThis->Utils.pCtx, "%s and %s are identical (not copied).",
+ pThis->Utils.to.p_path, curr->fts_path);
+ badcp = rval = 1;
+ if (S_ISDIR(curr->fts_statp->st_mode))
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ continue;
+ }
+ if (!S_ISDIR(curr->fts_statp->st_mode) &&
+ S_ISDIR(to_stat.st_mode)) {
+ warnx(pThis->Utils.pCtx, "cannot overwrite directory %s with "
+ "non-directory %s",
+ pThis->Utils.to.p_path, curr->fts_path);
+ badcp = rval = 1;
+ continue;
+ }
+ dne = 0;
+ }
+
+ switch (curr->fts_statp->st_mode & S_IFMT) {
+#ifdef S_IFLNK
+ case S_IFLNK:
+ /* Catch special case of a non-dangling symlink */
+ if ((fts_options & FTS_LOGICAL) ||
+ ((fts_options & FTS_COMFOLLOW) &&
+ curr->fts_level == 0)) {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ } else {
+ if (copy_link(&pThis->Utils, curr, !dne))
+ badcp = rval = 1;
+ }
+ break;
+#endif
+ case S_IFDIR:
+ if (!pThis->Rflag && !pThis->rflag) {
+ warnx(pThis->Utils.pCtx, "%s is a directory (not copied).",
+ curr->fts_path);
+ (void)fts_set(ftsp, curr, FTS_SKIP);
+ badcp = rval = 1;
+ break;
+ }
+ /*
+ * If the directory doesn't exist, create the new
+ * one with the from file mode plus owner RWX bits,
+ * modified by the umask. Trade-off between being
+ * able to write the directory (if from directory is
+ * 555) and not causing a permissions race. If the
+ * umask blocks owner writes, we fail..
+ */
+ if (dne) {
+ if (mkdir(pThis->Utils.to.p_path,
+ curr->fts_statp->st_mode | S_IRWXU) < 0)
+ return err(pThis->Utils.pCtx, 1, "mkdir: %s", pThis->Utils.to.p_path);
+ } else if (!S_ISDIR(to_stat.st_mode)) {
+ errno = ENOTDIR;
+ return err(pThis->Utils.pCtx, 1, "to-mode: %s", pThis->Utils.to.p_path);
+ }
+ /*
+ * Arrange to correct directory attributes later
+ * (in the post-order phase) if this is a new
+ * directory, or if the -p flag is in effect.
+ */
+ curr->fts_number = pThis->Utils.pflag || dne;
+ break;
+#ifdef S_IFBLK
+ case S_IFBLK:
+#endif
+ case S_IFCHR:
+ if (pThis->Rflag) {
+ if (copy_special(&pThis->Utils, curr->fts_statp, !dne))
+ badcp = rval = 1;
+ } else {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ }
+ break;
+#ifdef S_IFIFO
+ case S_IFIFO:
+#endif
+ if (pThis->Rflag) {
+ if (copy_fifo(&pThis->Utils, curr->fts_statp, !dne))
+ badcp = rval = 1;
+ } else {
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ }
+ break;
+ default:
+ if (copy_file(&pThis->Utils, curr, dne, pThis->cp_changed_only, &copied))
+ badcp = rval = 1;
+ break;
+ }
+ if (pThis->Utils.vflag && !badcp)
+ kmk_builtin_ctx_printf(pThis->Utils.pCtx, 0, copied ? "%s -> %s\n" : "%s matches %s - not copied\n",
+ curr->fts_path, pThis->Utils.to.p_path);
+ }
+ if (errno)
+ return err(pThis->Utils.pCtx, 1, "fts_read");
+ return (rval);
+}
+
+/*
+ * mastercmp --
+ * The comparison function for the copy order. The order is to copy
+ * non-directory files before directory files. The reason for this
+ * is because files tend to be in the same cylinder group as their
+ * parent directory, whereas directories tend not to be. Copying the
+ * files first reduces seeking.
+ */
+#ifdef FTSCALL
+static int FTSCALL mastercmp(const FTSENT * const *a, const FTSENT * const *b)
+#else
+static int mastercmp(const FTSENT **a, const FTSENT **b)
+#endif
+{
+ int a_info, b_info;
+
+ a_info = (*a)->fts_info;
+ if (a_info == FTS_ERR || a_info == FTS_NS || a_info == FTS_DNR)
+ return (0);
+ b_info = (*b)->fts_info;
+ if (b_info == FTS_ERR || b_info == FTS_NS || b_info == FTS_DNR)
+ return (0);
+ if (a_info == FTS_D)
+ return (-1);
+ if (b_info == FTS_D)
+ return (1);
+ return (0);
+}
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+static void
+siginfo(int sig __unused)
+{
+
+ g_cp_info = 1;
+}
+#endif
+
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+"usage: %s [options] src target\n"
+" or: %s [options] src1 ... srcN directory\n"
+" or: %s --help\n"
+" or: %s --version\n"
+"\n"
+"Options:\n"
+" -R Recursive copy.\n"
+" -H Follow symbolic links on the commandline. Only valid with -R.\n"
+" -L Follow all symbolic links. Only valid with -R.\n"
+" -P Do not follow symbolic links. Default. Only valid with -R\n"
+" -f Force. Overrides -i and -n.\n"
+" -i Iteractive. Overrides -n and -f.\n"
+" -n Don't overwrite any files. Overrides -i and -f.\n"
+" -v Verbose.\n"
+" --ignore-non-existing\n"
+" Don't fail if the specified source file doesn't exist.\n"
+" --changed\n"
+" Only copy if changed (i.e. compare first).\n"
+" --disable-protection\n"
+" Will disable the protection file protection applied with -R.\n"
+" --enable-protection\n"
+" Will enable the protection file protection applied with -R.\n"
+" --enable-full-protection\n"
+" Will enable the protection file protection for all operations.\n"
+" --disable-full-protection\n"
+" Will disable the protection file protection for all operations.\n"
+" --protection-depth\n"
+" Number or path indicating the file protection depth. Default: %d\n"
+"\n"
+"Environment:\n"
+" KMK_CP_DISABLE_PROTECTION\n"
+" Same as --disable-protection. Overrides command line.\n"
+" KMK_CP_ENABLE_PROTECTION\n"
+" Same as --enable-protection. Overrides everyone else.\n"
+" KMK_CP_ENABLE_FULL_PROTECTION\n"
+" Same as --enable-full-protection. Overrides everyone else.\n"
+" KMK_CP_DISABLE_FULL_PROTECTION\n"
+" Same as --disable-full-protection. Overrides command line.\n"
+" KMK_CP_PROTECTION_DEPTH\n"
+" Same as --protection-depth. Overrides command line.\n"
+"\n"
+"The file protection of the top %d layers of the file hierarchy is there\n"
+"to try prevent makefiles from doing bad things to your system. This\n"
+"protection is not bulletproof, but should help prevent you from shooting\n"
+"yourself in the foot.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName,
+ kBuildProtectionDefaultDepth(), kBuildProtectionDefaultDepth());
+ return 1;
+}
diff --git a/src/kmk/kmkbuiltin/cp_extern.h b/src/kmk/kmkbuiltin/cp_extern.h
new file mode 100644
index 0000000..bcae631
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp_extern.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ *
+ * @(#)extern.h 8.2 (Berkeley) 4/1/94
+ * $FreeBSD: src/bin/cp/extern.h,v 1.19 2004/04/06 20:06:44 markm Exp $
+ */
+
+#include "kmkbuiltin.h" /* for PATH_MAX on GNU/hurd */
+
+typedef struct {
+ char *p_end; /* pointer to NULL at end of path */
+ char *target_end; /* pointer to end of target base */
+ char p_path[PATH_MAX]; /* pointer to the start of a path */
+} PATH_T;
+
+typedef struct CPUTILSINSTANCE {
+ PKMKBUILTINCTX pCtx;
+ /*extern*/ PATH_T to;
+ /*extern*/ int fflag, iflag, nflag, pflag, vflag;
+} CPUTILSINSTANCE;
+
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+extern volatile sig_atomic_t g_cp_info;
+#endif
+
+int copy_fifo(CPUTILSINSTANCE *pThis, struct stat *, int);
+int copy_file(CPUTILSINSTANCE *pThis, const FTSENT *, int, int, int *);
+int copy_link(CPUTILSINSTANCE *pThis, const FTSENT *, int);
+int copy_special(CPUTILSINSTANCE *pThis, struct stat *, int);
+int copy_file_attribs(CPUTILSINSTANCE *pThis, struct stat *, int);
diff --git a/src/kmk/kmkbuiltin/cp_utils.c b/src/kmk/kmkbuiltin/cp_utils.c
new file mode 100644
index 0000000..e97e75d
--- /dev/null
+++ b/src/kmk/kmkbuiltin/cp_utils.c
@@ -0,0 +1,397 @@
+/*-
+ * Copyright (c) 1991, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)utils.c 8.3 (Berkeley) 4/1/94";
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/cp/utils.c,v 1.43 2004/04/06 20:06:44 markm Exp $");
+#endif
+#endif /* not lint */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define MSC_DO_64_BIT_IO
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+# include <sys/mman.h>
+#endif
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include "fts.h"
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#else
+# include <sys/time.h>
+#endif
+#include "cp_extern.h"
+#include "cmp_extern.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define cp_pct(x,y) (int)(100.0 * (double)(x) / (double)(y))
+
+#ifndef MAXBSIZE
+# define MAXBSIZE 0x10000
+#endif
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#ifndef S_ISVTX
+# define S_ISVTX 0
+#endif
+
+
+int
+copy_file(CPUTILSINSTANCE *pThis, const FTSENT *entp, int dne, int changed_only, int *pcopied)
+{
+ /*static*/ char buf[MAXBSIZE];
+ struct stat *fs;
+ int ch, checkch, from_fd, rcount, rval, to_fd;
+ ssize_t wcount;
+ size_t wresid;
+ size_t wtotal;
+ char *bufp;
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+ char *p;
+#endif
+
+ *pcopied = 0;
+
+ if ((from_fd = open(entp->fts_path, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ warn(pThis->pCtx, "open: %s", entp->fts_path);
+ return (1);
+ }
+
+ fs = entp->fts_statp;
+
+ /*
+ * If the file exists and we're interactive, verify with the user.
+ * If the file DNE, set the mode to be the from file, minus setuid
+ * bits, modified by the umask; arguably wrong, but it makes copying
+ * executables work right and it's been that way forever. (The
+ * other choice is 666 or'ed with the execute bits on the from file
+ * modified by the umask.)
+ */
+ if (!dne) {
+ /* compare the files first if requested */
+ if (changed_only) {
+ if (cmp_fd_and_file(pThis->pCtx, from_fd, entp->fts_path, pThis->to.p_path,
+ 1 /* silent */, 0 /* lflag */,
+ 0 /* special */) == OK_EXIT) {
+ close(from_fd);
+ return (0);
+ }
+ if (lseek(from_fd, 0, SEEK_SET) != 0) {
+ close(from_fd);
+ if ((from_fd = open(entp->fts_path, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ warn(pThis->pCtx, "open: %s", entp->fts_path);
+ return (1);
+ }
+ }
+ }
+
+#define YESNO "(y/n [n]) "
+ if (pThis->nflag) {
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s not overwritten\n", pThis->to.p_path);
+ return (0);
+ } else if (pThis->iflag) {
+ (void)fprintf(stderr, "overwrite %s? %s",
+ pThis->to.p_path, YESNO);
+ checkch = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (checkch != 'y' && checkch != 'Y') {
+ (void)close(from_fd);
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not overwritten\n");
+ return (1);
+ }
+ }
+
+ if (pThis->fflag) {
+ /* remove existing destination file name,
+ * create a new file */
+ (void)unlink(pThis->to.p_path);
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY | KMK_OPEN_NO_INHERIT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+ } else
+ /* overwrite existing destination file name */
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ } else
+ to_fd = open(pThis->to.p_path, O_WRONLY | O_TRUNC | O_CREAT | O_BINARY | KMK_OPEN_NO_INHERIT,
+ fs->st_mode & ~(S_ISUID | S_ISGID));
+
+ if (to_fd == -1) {
+ warn(pThis->pCtx, "open: %s", pThis->to.p_path);
+ (void)close(from_fd);
+ return (1);
+ }
+
+ rval = 0;
+ *pcopied = 1;
+
+ /*
+ * Mmap and write if less than 8M (the limit is so we don't totally
+ * trash memory on big files. This is really a minor hack, but it
+ * wins some CPU back.
+ */
+#ifdef VM_AND_BUFFER_CACHE_SYNCHRONIZED
+ if (S_ISREG(fs->st_mode) && fs->st_size > 0 &&
+ fs->st_size <= 8 * 1048576) {
+ if ((p = mmap(NULL, (size_t)fs->st_size, PROT_READ,
+ MAP_SHARED, from_fd, (off_t)0)) == MAP_FAILED) {
+ warn(pThis->pCtx, "mmap: %s", entp->fts_path);
+ rval = 1;
+ } else {
+ wtotal = 0;
+ for (bufp = p, wresid = fs->st_size; ;
+ bufp += wcount, wresid -= (size_t)wcount) {
+ wcount = write(to_fd, bufp, wresid);
+ wtotal += wcount;
+# if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ if (g_cp_info) {
+ g_cp_info = 0;
+ kmk_builtin_ctx_printf(pThis->pCtx, 1,
+ "%s -> %s %3d%%\n",
+ entp->fts_path, pThis->to.p_path,
+ cp_pct(wtotal, fs->st_size));
+
+ }
+#endif
+ if (wcount >= (ssize_t)wresid || wcount <= 0)
+ break;
+ }
+ if (wcount != (ssize_t)wresid) {
+ warn(pThis->pCtx, "write[%zd != %zu]: %s", wcount, wresid, pThis->to.p_path);
+ rval = 1;
+ }
+ /* Some systems don't unmap on close(2). */
+ if (munmap(p, fs->st_size) < 0) {
+ warn(pThis->pCtx, "munmap: %s", entp->fts_path);
+ rval = 1;
+ }
+ }
+ } else
+#endif
+ {
+ wtotal = 0;
+ while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
+ for (bufp = buf, wresid = rcount; ;
+ bufp += wcount, wresid -= wcount) {
+ wcount = write(to_fd, bufp, wresid);
+ wtotal += wcount;
+#if defined(SIGINFO) && defined(KMK_BUILTIN_STANDALONE)
+ if (g_cp_info) {
+ g_cp_info = 0;
+ kmk_builtin_ctx_printf(pThis->pCtx, 1,
+ "%s -> %s %3d%%\n",
+ entp->fts_path, pThis->to.p_path,
+ cp_pct(wtotal, fs->st_size));
+
+ }
+#endif
+ if (wcount >= (ssize_t)wresid || wcount <= 0)
+ break;
+ }
+ if (wcount != (ssize_t)wresid) {
+ warn(pThis->pCtx, "write[%zd != %zu]: %s", wcount, wresid, pThis->to.p_path);
+ rval = 1;
+ break;
+ }
+ }
+ if (rcount < 0) {
+ warn(pThis->pCtx, "read: %s", entp->fts_path);
+ rval = 1;
+ }
+ }
+
+ /*
+ * Don't remove the target even after an error. The target might
+ * not be a regular file, or its attributes might be important,
+ * or its contents might be irreplaceable. It would only be safe
+ * to remove it if we created it and its length is 0.
+ */
+
+ if (pThis->pflag && copy_file_attribs(pThis, fs, to_fd))
+ rval = 1;
+ (void)close(from_fd);
+ if (close(to_fd)) {
+ warn(pThis->pCtx, "close: %s", pThis->to.p_path);
+ rval = 1;
+ }
+ return (rval);
+}
+
+int
+copy_link(CPUTILSINSTANCE *pThis, const FTSENT *p, int exists)
+{
+ int len;
+ char llink[PATH_MAX];
+
+ if ((len = readlink(p->fts_path, llink, sizeof(llink) - 1)) == -1) {
+ warn(pThis->pCtx, "readlink: %s", p->fts_path);
+ return (1);
+ }
+ llink[len] = '\0';
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (symlink(llink, pThis->to.p_path)) {
+ warn(pThis->pCtx, "symlink: %s", llink);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, p->fts_statp, -1) : 0);
+}
+
+int
+copy_fifo(CPUTILSINSTANCE *pThis, struct stat *from_stat, int exists)
+{
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (mkfifo(pThis->to.p_path, from_stat->st_mode)) {
+ warn(pThis->pCtx, "mkfifo: %s", pThis->to.p_path);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, from_stat, -1) : 0);
+}
+
+int
+copy_special(CPUTILSINSTANCE *pThis, struct stat *from_stat, int exists)
+{
+ if (exists && unlink(pThis->to.p_path)) {
+ warn(pThis->pCtx, "unlink: %s", pThis->to.p_path);
+ return (1);
+ }
+ if (mknod(pThis->to.p_path, from_stat->st_mode, from_stat->st_rdev)) {
+ warn(pThis->pCtx, "mknod: %s", pThis->to.p_path);
+ return (1);
+ }
+ return (pThis->pflag ? copy_file_attribs(pThis, from_stat, -1) : 0);
+}
+
+int
+copy_file_attribs(CPUTILSINSTANCE *pThis, struct stat *fs, int fd)
+{
+ /*static*/ struct timeval tv[2];
+ struct stat ts;
+ int rval, gotstat, islink, fdval;
+
+ rval = 0;
+ fdval = fd != -1;
+ islink = !fdval && S_ISLNK(fs->st_mode);
+ fs->st_mode &= S_ISUID | S_ISGID | S_ISVTX |
+ S_IRWXU | S_IRWXG | S_IRWXO;
+
+#ifdef HAVE_ST_TIMESPEC
+ TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
+ TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
+#else
+ tv[0].tv_sec = fs->st_atime;
+ tv[1].tv_sec = fs->st_mtime;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+#endif
+ if (islink ? lutimes(pThis->to.p_path, tv) : utimes(pThis->to.p_path, tv)) {
+ warn(pThis->pCtx, "%sutimes: %s", islink ? "l" : "", pThis->to.p_path);
+ rval = 1;
+ }
+ if (fdval ? fstat(fd, &ts) :
+ (islink ? lstat(pThis->to.p_path, &ts) : stat(pThis->to.p_path, &ts)))
+ gotstat = 0;
+ else {
+ gotstat = 1;
+ ts.st_mode &= S_ISUID | S_ISGID | S_ISVTX |
+ S_IRWXU | S_IRWXG | S_IRWXO;
+ }
+ /*
+ * Changing the ownership probably won't succeed, unless we're root
+ * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting
+ * the mode; current BSD behavior is to remove all setuid bits on
+ * chown. If chown fails, lose setuid/setgid bits.
+ */
+ if (!gotstat || fs->st_uid != ts.st_uid || fs->st_gid != ts.st_gid)
+ if (fdval ? fchown(fd, fs->st_uid, fs->st_gid) :
+ (islink ? lchown(pThis->to.p_path, fs->st_uid, fs->st_gid) :
+ chown(pThis->to.p_path, fs->st_uid, fs->st_gid))) {
+ if (errno != EPERM) {
+ warn(pThis->pCtx, "chown: %s", pThis->to.p_path);
+ rval = 1;
+ }
+ fs->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+
+ if (!gotstat || fs->st_mode != ts.st_mode)
+ if (fdval ? fchmod(fd, fs->st_mode) :
+ (islink ? lchmod(pThis->to.p_path, fs->st_mode) :
+ chmod(pThis->to.p_path, fs->st_mode))) {
+ warn(pThis->pCtx, "chmod: %s", pThis->to.p_path);
+ rval = 1;
+ }
+
+#ifdef HAVE_ST_FLAGS
+ if (!gotstat || fs->st_flags != ts.st_flags)
+ if (fdval ?
+ fchflags(fd, fs->st_flags) :
+ (islink ? (errno = ENOSYS) :
+ chflags(pThis->to.p_path, fs->st_flags))) {
+ warn(pThis->pCtx, "chflags: %s", pThis->to.p_path);
+ rval = 1;
+ }
+#endif
+
+ return (rval);
+}
+
diff --git a/src/kmk/kmkbuiltin/darwin.c b/src/kmk/kmkbuiltin/darwin.c
new file mode 100644
index 0000000..583d6e1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/darwin.c
@@ -0,0 +1,55 @@
+/* $Id: darwin.c 2591 2012-06-17 20:45:31Z bird $ */
+/** @file
+ * Missing BSD functions on Darwin / Mac OS X.
+ */
+
+/*
+ * Copyright (c) 2006-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+
+int lchmod(const char *path, mode_t mode)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return chmod(path, mode);
+}
+
+
+int lutimes(const char *path, const struct timeval *tvs)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return utimes(path, tvs);
+}
+
diff --git a/src/kmk/kmkbuiltin/echo.c b/src/kmk/kmkbuiltin/echo.c
new file mode 100644
index 0000000..11dc227
--- /dev/null
+++ b/src/kmk/kmkbuiltin/echo.c
@@ -0,0 +1,125 @@
+/* $Id: echo.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * kMk Builtin command - echo
+ */
+
+/*
+ * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef _MSC_VER
+# include <io.h>
+#endif
+
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+int kmk_builtin_echo(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rcExit = 0;
+ int iFirst = 1;
+ int i;
+ char *pszBuf;
+ size_t cbBuf;
+
+ /*
+ * Check for the -n option.
+ */
+ int fNoNewLine = 0;
+ if ( argc > iFirst
+ && strcmp(argv[iFirst], "-n") == 0)
+ {
+ iFirst++;
+ fNoNewLine = 1;
+ }
+
+ /*
+ * Calc buffer size and allocate it.
+ */
+ cbBuf = 1 + 1;
+ for (i = 1; i < argc; i++)
+ cbBuf += (i > iFirst) + strlen(argv[i]);
+ pszBuf = (char *)malloc(cbBuf);
+ if (pszBuf)
+ {
+ /*
+ * Assembler the output into the buffer.
+ */
+ char *pszDst = pszBuf;
+ for (i = iFirst; i < argc; i++)
+ {
+ const char *pszArg = argv[i];
+ size_t cchArg = strlen(pszArg);
+
+ /* Check for "\c" in final argument (same as -n). */
+ if (i + 1 >= argc
+ && cchArg >= 2
+ && pszArg[cchArg - 2] == '\\'
+ && pszArg[cchArg - 1] == 'c')
+ {
+ fNoNewLine = 1;
+ cchArg -= 2;
+ }
+ if (i > iFirst)
+ *pszDst++ = ' ';
+ memcpy(pszDst, pszArg, cchArg);
+ pszDst += cchArg;
+ }
+ if (!fNoNewLine)
+ *pszDst++ = '\n';
+ *pszDst = '\0';
+
+ /*
+ * Push it out.
+ */
+#ifndef KMK_BUILTIN_STANDALONE
+ if (output_write_text(pCtx->pOut, 0, pszBuf, pszDst - pszBuf) == -1)
+ rcExit = err(pCtx, 1, "output_write_text");
+#else
+ if (write(STDOUT_FILENO, pszBuf, pszDst - pszBuf) == -1)
+ rcExit = err(pCtx, 1, "write");
+#endif
+ free(pszBuf);
+ }
+ else
+ rcExit = err(pCtx, 1, "malloc(%lu)", (unsigned long)cbBuf);
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_echo", NULL };
+ return kmk_builtin_echo(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/err.c b/src/kmk/kmkbuiltin/err.c
new file mode 100644
index 0000000..bbab335
--- /dev/null
+++ b/src/kmk/kmkbuiltin/err.c
@@ -0,0 +1,340 @@
+/* $Id: err.c 3237 2018-12-25 04:11:26Z bird $ */
+/** @file
+ * Override err.h so we get the program name right.
+ */
+
+/*
+ * Copyright (c) 2005-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# endif
+# ifdef HAVE_STDINT_H
+# include <stdint.h>
+# endif
+#else
+# include <stdlib.h>
+# define snprintf _snprintf
+#endif
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include "err.h"
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+# include "../output.h"
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/* This is a trick to speed up console output on windows. */
+# include "console.h"
+# undef fwrite
+# define fwrite maybe_con_fwrite
+#endif
+
+int err(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ int error = errno;
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: error: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+ cchMsg += cchMsg2 = snprintf(&pszMsg[cchMsg], cbMsg - cchMsg, ": %s\n", strerror(error));
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ errno = error;
+ return eval;
+ }
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ errno = error;
+ return eval;
+ }
+ }
+}
+
+
+int errx(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: error: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 2
+ && cchMsg2 >= 0)
+ {
+ /* ensure newline */
+ if (pszMsg[cchMsg - 1] != '\n')
+ {
+ pszMsg[cchMsg++] = '\n';
+ pszMsg[cchMsg] = '\0';
+ }
+
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return eval;
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return eval;
+ }
+ }
+}
+
+void warn(PKMKBUILTINCTX pCtx, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ int error = errno;
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+ cchMsg += cchMsg2 = snprintf(&pszMsg[cchMsg], cbMsg - cchMsg, ": %s\n", strerror(error));
+ if ( cchMsg < (int)cbMsg - 1
+ && cchMsg2 >= 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ errno = error;
+ return;
+ }
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ errno = error;
+ return;
+ }
+ }
+}
+
+void warnx(PKMKBUILTINCTX pCtx, const char *fmt, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg = snprintf(pszMsg, cbMsg, "%s: ", pCtx->pszProgName);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+ int cchMsg2;
+ va_list va;
+ va_start(va, fmt);
+ cchMsg += cchMsg2 = vsnprintf(&pszMsg[cchMsg], cbMsg - cchMsg, fmt, va);
+ va_end(va);
+
+ if ( cchMsg < (int)cbMsg - 2
+ && cchMsg2 >= 0)
+ {
+ /* ensure newline */
+ if (pszMsg[cchMsg - 1] != '\n')
+ {
+ pszMsg[cchMsg++] = '\n';
+ pszMsg[cchMsg] = '\0';
+ }
+
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, 1 /*is_err*/, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fflush(stdout);
+ fwrite(pszMsg, cchMsg, 1, stderr);
+ fflush(stderr); /* paranoia */
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return;
+ }
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return;
+ }
+ }
+}
+
+void kmk_builtin_ctx_printf(PKMKBUILTINCTX pCtx, int fIsErr, const char *pszFormat, ...)
+{
+ /*
+ * We format into a buffer and pass that onto output.c or fwrite.
+ */
+ char *pszToFree = NULL;
+ char szMsgStack[4096];
+ char *pszMsg = szMsgStack;
+ size_t cbMsg = sizeof(szMsgStack);
+ for (;;)
+ {
+ int cchMsg;
+ va_list va;
+ va_start(va, pszFormat);
+ cchMsg = vsnprintf(pszMsg, cbMsg, pszFormat, va);
+ va_end(va);
+ if (cchMsg < (int)cbMsg - 1 && cchMsg > 0)
+ {
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(KWORKER)
+ if (pCtx->pOut)
+ output_write_text(pCtx->pOut, fIsErr, pszMsg, cchMsg);
+ else
+#endif
+ {
+ fwrite(pszMsg, cchMsg, 1, fIsErr ? stderr : stdout);
+ fflush(fIsErr ? stderr : stdout);
+ }
+ if (pszToFree)
+ free(pszToFree);
+ return;
+ }
+
+ /* double the buffer size and retry */
+ if (pszToFree)
+ free(pszToFree);
+ cbMsg *= 2;
+ pszToFree = malloc(cbMsg);
+ if (!pszToFree)
+ {
+ fprintf(stderr, "out of memory!\n");
+ return;
+ }
+ }
+}
+
diff --git a/src/kmk/kmkbuiltin/err.h b/src/kmk/kmkbuiltin/err.h
new file mode 100644
index 0000000..4150e42
--- /dev/null
+++ b/src/kmk/kmkbuiltin/err.h
@@ -0,0 +1,38 @@
+/* $Id: err.h 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Override err.h stuff so we get the program names right.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___err_h
+#define ___err_h
+
+#include "../kmkbuiltin.h"
+
+int err(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...);
+int errx(PKMKBUILTINCTX pCtx, int eval, const char *fmt, ...);
+void warn(PKMKBUILTINCTX pCtx, const char *fmt, ...);
+void warnx(PKMKBUILTINCTX pCtx, const char *fmt, ...);
+void kmk_builtin_ctx_printf(PKMKBUILTINCTX pCtx, int fIsErr, const char *pszFormat, ...);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/expr.c b/src/kmk/kmkbuiltin/expr.c
new file mode 100644
index 0000000..350c2a3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/expr.c
@@ -0,0 +1,617 @@
+/* $OpenBSD: expr.c,v 1.17 2006/06/21 18:28:24 deraadt Exp $ */
+/* $NetBSD: expr.c,v 1.3.6.1 1996/06/04 20:41:47 cgd Exp $ */
+
+/*
+ * Written by J.T. Conklin <jtc@netbsd.org>.
+ * Public domain.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+#ifdef KMK_WITH_REGEX
+#include <regex.h>
+#endif
+#include <setjmp.h>
+#include <assert.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+typedef struct EXPRINSTANCE *PEXPRINSTANCE;
+
+static struct val *make_int(PEXPRINSTANCE, int);
+static struct val *make_str(PEXPRINSTANCE, char *);
+static void free_value(PEXPRINSTANCE, struct val *);
+static int is_integer(struct val *, int *);
+static int to_integer(PEXPRINSTANCE, struct val *);
+static void to_string(PEXPRINSTANCE, struct val *);
+static int is_zero_or_null(PEXPRINSTANCE, struct val *);
+static void nexttoken(PEXPRINSTANCE, int);
+static struct val *eval6(PEXPRINSTANCE);
+static struct val *eval5(PEXPRINSTANCE);
+static struct val *eval4(PEXPRINSTANCE);
+static struct val *eval3(PEXPRINSTANCE);
+static struct val *eval2(PEXPRINSTANCE);
+static struct val *eval1(PEXPRINSTANCE);
+static struct val *eval0(PEXPRINSTANCE);
+
+enum token {
+ OR, AND, EQ, LT, GT, ADD, SUB, MUL, DIV, MOD, MATCH, RP, LP,
+ NE, LE, GE, OPERAND, EOI
+};
+
+struct val {
+ enum {
+ integer,
+ string
+ } type;
+
+ union {
+ char *s;
+ int i;
+ } u;
+};
+
+typedef struct EXPRINSTANCE {
+ PKMKBUILTINCTX pCtx;
+ enum token token;
+ struct val *tokval;
+ char **av;
+ jmp_buf g_expr_jmp;
+ void **recorded_allocations;
+ int num_recorded_allocations;
+} EXPRINSTANCE;
+
+
+static void expr_mem_record_alloc(PEXPRINSTANCE pThis, void *ptr)
+{
+ if (!(pThis->num_recorded_allocations & 31)) {
+ void *newtab = realloc(pThis->recorded_allocations, (pThis->num_recorded_allocations + 33) * sizeof(void *));
+ if (!newtab)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ pThis->recorded_allocations = (void **)newtab;
+ }
+ pThis->recorded_allocations[pThis->num_recorded_allocations++] = ptr;
+}
+
+
+static void expr_mem_record_free(PEXPRINSTANCE pThis, void *ptr)
+{
+ int i = pThis->num_recorded_allocations;
+ while (i-- > 0)
+ if (pThis->recorded_allocations[i] == ptr) {
+ pThis->num_recorded_allocations--;
+ pThis->recorded_allocations[i] = pThis->recorded_allocations[pThis->num_recorded_allocations];
+ return;
+ }
+ assert(i >= 0);
+}
+
+static void expr_mem_init(PEXPRINSTANCE pThis)
+{
+ pThis->num_recorded_allocations = 0;
+ pThis->recorded_allocations = NULL;
+}
+
+static void expr_mem_cleanup(PEXPRINSTANCE pThis)
+{
+ if (pThis->recorded_allocations) {
+ while (pThis->num_recorded_allocations-- > 0)
+ free(pThis->recorded_allocations[pThis->num_recorded_allocations]);
+ free(pThis->recorded_allocations);
+ pThis->recorded_allocations = NULL;
+ }
+}
+
+
+static struct val *
+make_int(PEXPRINSTANCE pThis, int i)
+{
+ struct val *vp;
+
+ vp = (struct val *) malloc(sizeof(*vp));
+ if (vp == NULL)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, vp);
+ vp->type = integer;
+ vp->u.i = i;
+ return vp;
+}
+
+
+static struct val *
+make_str(PEXPRINSTANCE pThis, char *s)
+{
+ struct val *vp;
+
+ vp = (struct val *) malloc(sizeof(*vp));
+ if (vp == NULL || ((vp->u.s = strdup(s)) == NULL))
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, vp->u.s);
+ expr_mem_record_alloc(pThis, vp);
+ vp->type = string;
+ return vp;
+}
+
+
+static void
+free_value(PEXPRINSTANCE pThis, struct val *vp)
+{
+ if (vp->type == string) {
+ expr_mem_record_free(pThis, vp->u.s);
+ free(vp->u.s);
+ }
+ free(vp);
+ expr_mem_record_free(pThis, vp);
+}
+
+
+/* determine if vp is an integer; if so, return it's value in *r */
+static int
+is_integer(struct val *vp, int *r)
+{
+ char *s;
+ int neg;
+ int i;
+
+ if (vp->type == integer) {
+ *r = vp->u.i;
+ return 1;
+ }
+
+ /*
+ * POSIX.2 defines an "integer" as an optional unary minus
+ * followed by digits.
+ */
+ s = vp->u.s;
+ i = 0;
+
+ neg = (*s == '-');
+ if (neg)
+ s++;
+
+ while (*s) {
+ if (!isdigit(*s))
+ return 0;
+
+ i *= 10;
+ i += *s - '0';
+
+ s++;
+ }
+
+ if (neg)
+ i *= -1;
+
+ *r = i;
+ return 1;
+}
+
+
+/* coerce to vp to an integer */
+static int
+to_integer(PEXPRINSTANCE pThis, struct val *vp)
+{
+ int r;
+
+ if (vp->type == integer)
+ return 1;
+
+ if (is_integer(vp, &r)) {
+ expr_mem_record_free(pThis, vp->u.s);
+ free(vp->u.s);
+ vp->u.i = r;
+ vp->type = integer;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* coerce to vp to an string */
+static void
+to_string(PEXPRINSTANCE pThis, struct val *vp)
+{
+ char *tmp;
+
+ if (vp->type == string)
+ return;
+
+ if (asprintf(&tmp, "%d", vp->u.i) == -1)
+ longjmp(pThis->g_expr_jmp, err(pThis->pCtx, 3, NULL));
+ expr_mem_record_alloc(pThis, tmp);
+
+ vp->type = string;
+ vp->u.s = tmp;
+}
+
+static int
+is_zero_or_null(PEXPRINSTANCE pThis, struct val *vp)
+{
+ if (vp->type == integer) {
+ return (vp->u.i == 0);
+ } else {
+ return (*vp->u.s == 0 || (to_integer(pThis, vp) && vp->u.i == 0));
+ }
+ /* NOTREACHED */
+}
+
+static void
+nexttoken(PEXPRINSTANCE pThis, int pat)
+{
+ char *p;
+
+ if ((p = *pThis->av) == NULL) {
+ pThis->token = EOI;
+ return;
+ }
+ pThis->av++;
+
+
+ if (pat == 0 && p[0] != '\0') {
+ if (p[1] == '\0') {
+ const char *x = "|&=<>+-*/%:()";
+ char *i; /* index */
+
+ if ((i = strchr(x, *p)) != NULL) {
+ pThis->token = i - x;
+ return;
+ }
+ } else if (p[1] == '=' && p[2] == '\0') {
+ switch (*p) {
+ case '<':
+ pThis->token = LE;
+ return;
+ case '>':
+ pThis->token = GE;
+ return;
+ case '!':
+ pThis->token = NE;
+ return;
+ }
+ }
+ }
+ pThis->tokval = make_str(pThis, p);
+ pThis->token = OPERAND;
+ return;
+}
+
+#ifdef __GNUC__
+__attribute__((noreturn))
+#endif
+#ifdef _MSC_VER
+__declspec(noreturn)
+#endif
+static void
+error(PEXPRINSTANCE pThis)
+{
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "syntax error"));
+ /* NOTREACHED */
+}
+
+static struct val *
+eval6(PEXPRINSTANCE pThis)
+{
+ struct val *v;
+
+ if (pThis->token == OPERAND) {
+ nexttoken(pThis, 0);
+ return pThis->tokval;
+
+ } else if (pThis->token == RP) {
+ nexttoken(pThis, 0);
+ v = eval0(pThis);
+
+ if (pThis->token != LP) {
+ error(pThis);
+ /* NOTREACHED */
+ }
+ nexttoken(pThis, 0);
+ return v;
+ } else {
+ error(pThis);
+ }
+ /* NOTREACHED */
+}
+
+/* Parse and evaluate match (regex) expressions */
+static struct val *
+eval5(PEXPRINSTANCE pThis)
+{
+#ifdef KMK_WITH_REGEX
+ regex_t rp;
+ regmatch_t rm[2];
+ char errbuf[256];
+ int eval;
+ struct val *r;
+ struct val *v;
+#endif
+ struct val *l;
+
+ l = eval6(pThis);
+ while (pThis->token == MATCH) {
+#ifdef KMK_WITH_REGEX
+ nexttoken(pThis, 1);
+ r = eval6(pThis);
+
+ /* coerce to both arguments to strings */
+ to_string(pThis, l);
+ to_string(pThis, r);
+
+ /* compile regular expression */
+ if ((eval = regcomp(&rp, r->u.s, 0)) != 0) {
+ regerror(eval, &rp, errbuf, sizeof(errbuf));
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "%s", errbuf));
+ }
+
+ /* compare string against pattern -- remember that patterns
+ are anchored to the beginning of the line */
+ if (regexec(&rp, l->u.s, 2, rm, 0) == 0 && rm[0].rm_so == 0) {
+ if (rm[1].rm_so >= 0) {
+ *(l->u.s + rm[1].rm_eo) = '\0';
+ v = make_str(pThis, l->u.s + rm[1].rm_so);
+
+ } else {
+ v = make_int(pThis, (int)(rm[0].rm_eo - rm[0].rm_so));
+ }
+ } else {
+ if (rp.re_nsub == 0) {
+ v = make_int(pThis, 0);
+ } else {
+ v = make_str(pThis, "");
+ }
+ }
+
+ /* free arguments and pattern buffer */
+ free_value(pThis, l);
+ free_value(pThis, r);
+ regfree(&rp);
+
+ l = v;
+#else
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "regex not supported, sorry."));
+#endif
+ }
+
+ return l;
+}
+
+/* Parse and evaluate multiplication and division expressions */
+static struct val *
+eval4(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+
+ l = eval5(pThis);
+ while ((op = pThis->token) == MUL || op == DIV || op == MOD) {
+ nexttoken(pThis, 0);
+ r = eval5(pThis);
+
+ if (!to_integer(pThis, l) || !to_integer(pThis, r)) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "non-numeric argument"));
+ }
+
+ if (op == MUL) {
+ l->u.i *= r->u.i;
+ } else {
+ if (r->u.i == 0) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "division by zero"));
+ }
+ if (op == DIV) {
+ l->u.i /= r->u.i;
+ } else {
+ l->u.i %= r->u.i;
+ }
+ }
+
+ free_value(pThis, r);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate addition and subtraction expressions */
+static struct val *
+eval3(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+
+ l = eval4(pThis);
+ while ((op = pThis->token) == ADD || op == SUB) {
+ nexttoken(pThis, 0);
+ r = eval4(pThis);
+
+ if (!to_integer(pThis, l) || !to_integer(pThis, r)) {
+ longjmp(pThis->g_expr_jmp, errx(pThis->pCtx, 2, "non-numeric argument"));
+ }
+
+ if (op == ADD) {
+ l->u.i += r->u.i;
+ } else {
+ l->u.i -= r->u.i;
+ }
+
+ free_value(pThis, r);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate comparison expressions */
+static struct val *
+eval2(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+ enum token op;
+ int v = 0, li, ri;
+
+ l = eval3(pThis);
+ while ((op = pThis->token) == EQ || op == NE || op == LT || op == GT ||
+ op == LE || op == GE) {
+ nexttoken(pThis, 0);
+ r = eval3(pThis);
+
+ if (is_integer(l, &li) && is_integer(r, &ri)) {
+ switch (op) {
+ case GT:
+ v = (li > ri);
+ break;
+ case GE:
+ v = (li >= ri);
+ break;
+ case LT:
+ v = (li < ri);
+ break;
+ case LE:
+ v = (li <= ri);
+ break;
+ case EQ:
+ v = (li == ri);
+ break;
+ case NE:
+ v = (li != ri);
+ break;
+ default:
+ break;
+ }
+ } else {
+ to_string(pThis, l);
+ to_string(pThis, r);
+
+ switch (op) {
+ case GT:
+ v = (strcoll(l->u.s, r->u.s) > 0);
+ break;
+ case GE:
+ v = (strcoll(l->u.s, r->u.s) >= 0);
+ break;
+ case LT:
+ v = (strcoll(l->u.s, r->u.s) < 0);
+ break;
+ case LE:
+ v = (strcoll(l->u.s, r->u.s) <= 0);
+ break;
+ case EQ:
+ v = (strcoll(l->u.s, r->u.s) == 0);
+ break;
+ case NE:
+ v = (strcoll(l->u.s, r->u.s) != 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ free_value(pThis, l);
+ free_value(pThis, r);
+ l = make_int(pThis, v);
+ }
+
+ return l;
+}
+
+/* Parse and evaluate & expressions */
+static struct val *
+eval1(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+
+ l = eval2(pThis);
+ while (pThis->token == AND) {
+ nexttoken(pThis, 0);
+ r = eval2(pThis);
+
+ if (is_zero_or_null(pThis, l) || is_zero_or_null(pThis, r)) {
+ free_value(pThis, l);
+ free_value(pThis, r);
+ l = make_int(pThis, 0);
+ } else {
+ free_value(pThis, r);
+ }
+ }
+
+ return l;
+}
+
+/* Parse and evaluate | expressions */
+static struct val *
+eval0(PEXPRINSTANCE pThis)
+{
+ struct val *l, *r;
+
+ l = eval1(pThis);
+ while (pThis->token == OR) {
+ nexttoken(pThis, 0);
+ r = eval1(pThis);
+
+ if (is_zero_or_null(pThis, l)) {
+ free_value(pThis, l);
+ l = r;
+ } else {
+ free_value(pThis, r);
+ }
+ }
+
+ return l;
+}
+
+
+int
+kmk_builtin_expr(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ EXPRINSTANCE This;
+ struct val *vp;
+ int rval;
+
+ if (argc > 1 && !strcmp(argv[1], "--"))
+ argv++;
+
+ /* Init globals */
+ This.pCtx = pCtx;
+ This.token = 0;
+ This.tokval = 0;
+ This.av = argv + 1;
+ expr_mem_init(&This);
+
+ rval = setjmp(This.g_expr_jmp);
+ if (!rval) {
+ nexttoken(&This, 0);
+ vp = eval0(&This);
+
+ if (This.token != EOI) {
+ error(&This);
+ /* NOTREACHED */
+ }
+
+ if (vp->type == integer)
+ kmk_builtin_ctx_printf(pCtx, 0, "%d\n", vp->u.i);
+ else
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", vp->u.s);
+
+ rval = is_zero_or_null(&This, vp);
+ }
+ /* else: longjmp */
+
+ /* cleanup */
+ expr_mem_cleanup(&This);
+ return rval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_expr", NULL };
+ (void) setlocale(LC_ALL, "");
+ return kmk_builtin_expr(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/fts.c b/src/kmk/kmkbuiltin/fts.c
new file mode 100644
index 0000000..10098b9
--- /dev/null
+++ b/src/kmk/kmkbuiltin/fts.c
@@ -0,0 +1,1461 @@
+/* $NetBSD: __fts13.c,v 1.44 2005/01/19 00:59:48 mycroft Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifdef __sun__
+# define _POSIX_C_SOURCE 199506L /* for dirfd() */
+# define __EXTENSIONS__ 1 /* for u_short and friends */
+#endif
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+/*#include <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)fts.c 8.6 (Berkeley) 8/14/94";
+#else
+__RCSID("$NetBSD: __fts13.c,v 1.44 2005/01/19 00:59:48 mycroft Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#include "config.h"
+/*#include "namespace.h"*/
+#ifndef _MSC_VER
+#include <sys/param.h>
+#endif
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "ftsfake.h"
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "kmkbuiltin.h" /* MAXPATHLEN for GNU/hurd */
+
+#ifdef __sun__
+# include "solfakes.h"
+# define dirfd(dir) ((dir)->d_fd)
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+# define dirfd(dir) -1
+#endif
+
+#if ! HAVE_NBTOOL_CONFIG_H
+# if !defined(__sun__) && !defined(__gnu_linux__) && !defined(__HAIKU__)
+# define HAVE_STRUCT_DIRENT_D_NAMLEN 1
+# endif
+#endif
+
+#if 0
+#ifdef __weak_alias
+#ifdef __LIBC12_SOURCE__
+__weak_alias(fts_children,_fts_children)
+__weak_alias(fts_close,_fts_close)
+__weak_alias(fts_open,_fts_open)
+__weak_alias(fts_read,_fts_read)
+__weak_alias(fts_set,_fts_set)
+#endif /* __LIBC12_SOURCE__ */
+#endif /* __weak_alias */
+#endif
+
+#ifdef __LIBC12_SOURCE__
+#define STAT stat12
+#else
+#define STAT stat
+#endif
+
+#ifdef __LIBC12_SOURCE__
+__warn_references(fts_children,
+ "warning: reference to compatibility fts_children();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_close,
+ "warning: reference to compatibility fts_close();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_open,
+ "warning: reference to compatibility fts_open();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_read,
+ "warning: reference to compatibility fts_read();"
+ " include <fts.h> for correct reference")
+__warn_references(fts_set,
+ "warning: reference to compatibility fts_set();"
+ " include <fts.h> for correct reference")
+#endif
+
+static FTSENT *fts_alloc(FTS *, const char *, size_t);
+static FTSENT *fts_build(FTS *, int);
+static void fts_lfree(FTSENT *);
+static void fts_load(FTS *, FTSENT *);
+static size_t fts_maxarglen(char * const *);
+static size_t fts_pow2(size_t);
+static int fts_palloc(FTS *, size_t);
+static void fts_padjust(FTS *, FTSENT *);
+static FTSENT *fts_sort(FTS *, FTSENT *, size_t);
+static u_short fts_stat(FTS *, FTSENT *, int);
+#ifdef _MSC_VER
+static u_short fts_stat_dirent(FTS *sp, FTSENT *p, int follow, struct dirent *pDirEnt);
+#endif
+static int fts_safe_changedir(const FTS *, const FTSENT *, int,
+ const char *);
+
+#ifdef _MSC_VER
+# undef HAVE_FCHDIR
+#endif
+
+#if defined(__EMX__) || defined(_MSC_VER)
+# define NEED_STRRSLASH
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+#else
+# define HAVE_FCHDIR
+# define IS_SLASH(ch) ( (ch) == '/' )
+#endif
+
+#define ISDOT(a) (a[0] == '.' && (!a[1] || (a[1] == '.' && !a[2])))
+
+#define CLR(opt) (sp->fts_options &= ~(opt))
+#define ISSET(opt) (sp->fts_options & (opt))
+#define SET(opt) (sp->fts_options |= (opt))
+
+#define CHDIR(sp, path) (!ISSET(FTS_NOCHDIR) && chdir(path))
+#ifdef HAVE_FCHDIR
+#define FCHDIR(sp, fd) (!ISSET(FTS_NOCHDIR) && fchdir(fd))
+#else
+#define FCHDIR(sp, rdir) CHDIR(sp, rdir)
+#endif
+
+
+/* fts_build flags */
+#define BCHILD 1 /* fts_children */
+#define BNAMES 2 /* fts_children, names only */
+#define BREAD 3 /* fts_read */
+
+#ifndef DTF_HIDEW
+#undef FTS_WHITEOUT
+#endif
+
+#ifndef _DIAGASSERT
+#define _DIAGASSERT assert
+#endif
+
+
+FTS *
+fts_open(argv, options, compar)
+ char * const *argv;
+ int options;
+ int (*compar)(const FTSENT **, const FTSENT **);
+{
+ FTS *sp;
+ FTSENT *p, *root;
+ size_t nitems;
+ FTSENT *parent, *tmp = NULL; /* pacify gcc */
+ size_t len;
+
+ _DIAGASSERT(argv != NULL);
+
+ /* Options check. */
+ if (options & ~FTS_OPTIONMASK) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Allocate/initialize the stream */
+ if ((sp = malloc((u_int)sizeof(FTS))) == NULL)
+ return (NULL);
+ memset(sp, 0, sizeof(FTS));
+ sp->fts_compar = compar;
+ sp->fts_options = options;
+
+ /* Logical walks turn on NOCHDIR; symbolic links are too hard. */
+ if (ISSET(FTS_LOGICAL))
+ SET(FTS_NOCHDIR);
+
+ /*
+ * Start out with 1K of path space, and enough, in any case,
+ * to hold the user's paths.
+ */
+ if (fts_palloc(sp, MAX(fts_maxarglen(argv), MAXPATHLEN)))
+ goto mem1;
+
+ /* Allocate/initialize root's parent. */
+ if ((parent = fts_alloc(sp, "", 0)) == NULL)
+ goto mem2;
+ parent->fts_level = FTS_ROOTPARENTLEVEL;
+
+ /* Allocate/initialize root(s). */
+ for (root = NULL, nitems = 0; *argv; ++argv, ++nitems) {
+ /* Don't allow zero-length paths. */
+ if ((len = strlen(*argv)) == 0) {
+ errno = ENOENT;
+ goto mem3;
+ }
+
+ if ((p = fts_alloc(sp, *argv, len)) == NULL)
+ goto mem3;
+ p->fts_level = FTS_ROOTLEVEL;
+ p->fts_parent = parent;
+ p->fts_accpath = p->fts_name;
+ p->fts_info = fts_stat(sp, p, ISSET(FTS_COMFOLLOW));
+
+ /* Command-line "." and ".." are real directories. */
+ if (p->fts_info == FTS_DOT)
+ p->fts_info = FTS_D;
+
+ /*
+ * If comparison routine supplied, traverse in sorted
+ * order; otherwise traverse in the order specified.
+ */
+ if (compar) {
+ p->fts_link = root;
+ root = p;
+ } else {
+ p->fts_link = NULL;
+ if (root == NULL)
+ tmp = root = p;
+ else {
+ tmp->fts_link = p;
+ tmp = p;
+ }
+ }
+ }
+ if (compar && nitems > 1)
+ root = fts_sort(sp, root, nitems);
+
+ /*
+ * Allocate a dummy pointer and make fts_read think that we've just
+ * finished the node before the root(s); set p->fts_info to FTS_INIT
+ * so that everything about the "current" node is ignored.
+ */
+ if ((sp->fts_cur = fts_alloc(sp, "", 0)) == NULL)
+ goto mem3;
+ sp->fts_cur->fts_link = root;
+ sp->fts_cur->fts_info = FTS_INIT;
+
+ /*
+ * If using chdir(2), grab a file descriptor pointing to dot to insure
+ * that we can get back here; this could be avoided for some paths,
+ * but almost certainly not worth the effort. Slashes, symbolic links,
+ * and ".." are all fairly nasty problems. Note, if we can't get the
+ * descriptor we run anyway, just more slowly.
+ */
+ if (!ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((sp->fts_rfd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+ SET(FTS_NOCHDIR);
+ else if (fcntl(sp->fts_rfd, F_SETFD, FD_CLOEXEC) == -1) {
+ close(sp->fts_rfd);
+ SET(FTS_NOCHDIR);
+ }
+#else
+ if ((sp->fts_rdir = getcwd(NULL, 0)) != NULL)
+ SET(FTS_NOCHDIR);
+#endif
+ }
+
+ return (sp);
+
+mem3: fts_lfree(root);
+ free(parent);
+mem2: free(sp->fts_path);
+mem1: free(sp);
+ return (NULL);
+}
+
+#ifdef NEED_STRRSLASH
+static char *strrslash(register char *psz)
+{
+ register char ch;
+ char *pszLast = NULL;
+ for (; (ch = *psz); psz++)
+ switch (ch)
+ {
+ case '/':
+ case '\\':
+ case ':':
+ pszLast = psz;
+ break;
+ }
+ return pszLast;
+}
+#endif
+
+static void
+fts_load(sp, p)
+ FTS *sp;
+ FTSENT *p;
+{
+ size_t len;
+ char *cp;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /*
+ * Load the stream structure for the next traversal. Since we don't
+ * actually enter the directory until after the preorder visit, set
+ * the fts_accpath field specially so the chdir gets done to the right
+ * place and the user can access the first node. From fts_open it's
+ * known that the path will fit.
+ */
+ len = p->fts_pathlen = p->fts_namelen;
+ memmove(sp->fts_path, p->fts_name, len + 1);
+#ifdef NEED_STRRSLASH
+ if ((cp = strrslash(p->fts_name)) && (cp != p->fts_name || cp[1])) {
+#else
+ if ((cp = strrchr(p->fts_name, '/')) && (cp != p->fts_name || cp[1])) {
+#endif
+ len = strlen(++cp);
+ memmove(p->fts_name, cp, len + 1);
+ p->fts_namelen = len;
+ }
+ p->fts_accpath = p->fts_path = sp->fts_path;
+ sp->fts_dev = p->fts_dev;
+}
+
+int
+fts_close(sp)
+ FTS *sp;
+{
+ FTSENT *freep, *p;
+ int saved_errno = 0;
+
+ _DIAGASSERT(sp != NULL);
+
+ /*
+ * This still works if we haven't read anything -- the dummy structure
+ * points to the root list, so we step through to the end of the root
+ * list which has a valid parent pointer.
+ */
+ if (sp->fts_cur) {
+#ifndef _MSC_VER
+ if (ISSET(FTS_SYMFOLLOW))
+ (void)close(sp->fts_cur->fts_symfd);
+#endif
+ for (p = sp->fts_cur; p->fts_level >= FTS_ROOTLEVEL;) {
+ freep = p;
+ p = p->fts_link ? p->fts_link : p->fts_parent;
+ free(freep);
+ }
+ free(p);
+ }
+
+ /* Free up child linked list, sort array, path buffer. */
+ if (sp->fts_child)
+ fts_lfree(sp->fts_child);
+ if (sp->fts_array)
+ free(sp->fts_array);
+ free(sp->fts_path);
+
+ /* Return to original directory, save errno if necessary. */
+ if (!ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if (fchdir(sp->fts_rfd))
+ saved_errno = errno;
+ (void)close(sp->fts_rfd);
+#else
+ if (chdir(sp->fts_rdir))
+ saved_errno = errno;
+ free(sp->fts_rdir);
+ sp->fts_rdir = NULL;
+#endif
+ }
+
+ /* Free up the stream pointer. */
+ free(sp);
+ /* ISSET() is illegal after this, since the macro touches sp */
+
+ /* Set errno and return. */
+ if (saved_errno) {
+ errno = saved_errno;
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Special case a root of "/" so that slashes aren't appended which would
+ * cause paths to be written as "//foo".
+ */
+#define NAPPEND(p) \
+ (p->fts_level == FTS_ROOTLEVEL && p->fts_pathlen == 1 && \
+ IS_SLASH(p->fts_path[0]) ? 0 : p->fts_pathlen)
+
+FTSENT *
+fts_read(sp)
+ FTS *sp;
+{
+ FTSENT *p, *tmp;
+ int instr;
+ char *t;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+
+ /* If finished or unrecoverable error, return NULL. */
+ if (sp->fts_cur == NULL || ISSET(FTS_STOP))
+ return (NULL);
+
+ /* Set current node pointer. */
+ p = sp->fts_cur;
+
+ /* Save and zero out user instructions. */
+ instr = p->fts_instr;
+ p->fts_instr = FTS_NOINSTR;
+
+ /* Any type of file may be re-visited; re-stat and re-turn. */
+ if (instr == FTS_AGAIN) {
+ p->fts_info = fts_stat(sp, p, 0);
+ return (p);
+ }
+
+ /*
+ * Following a symlink -- SLNONE test allows application to see
+ * SLNONE and recover. If indirecting through a symlink, have
+ * keep a pointer to current location. If unable to get that
+ * pointer, follow fails.
+ */
+ if (instr == FTS_FOLLOW &&
+ (p->fts_info == FTS_SL || p->fts_info == FTS_SLNONE)) {
+ p->fts_info = fts_stat(sp, p, 1);
+ if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((p->fts_symfd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ } else if (fcntl(p->fts_symfd, F_SETFD, FD_CLOEXEC) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ close(p->fts_symfd);
+ } else
+ p->fts_flags |= FTS_SYMFOLLOW;
+#endif
+ }
+ return (p);
+ }
+
+ /* Directory in pre-order. */
+ if (p->fts_info == FTS_D) {
+ /* If skipped or crossed mount point, do post-order visit. */
+ if (instr == FTS_SKIP ||
+ (ISSET(FTS_XDEV) && p->fts_dev != sp->fts_dev)) {
+#ifdef HAVE_FCHDIR
+ if (p->fts_flags & FTS_SYMFOLLOW)
+ (void)close(p->fts_symfd);
+#endif
+ if (sp->fts_child) {
+ fts_lfree(sp->fts_child);
+ sp->fts_child = NULL;
+ }
+ p->fts_info = FTS_DP;
+ return (p);
+ }
+
+ /* Rebuild if only read the names and now traversing. */
+ if (sp->fts_child && ISSET(FTS_NAMEONLY)) {
+ CLR(FTS_NAMEONLY);
+ fts_lfree(sp->fts_child);
+ sp->fts_child = NULL;
+ }
+
+ /*
+ * Cd to the subdirectory.
+ *
+ * If have already read and now fail to chdir, whack the list
+ * to make the names come out right, and set the parent errno
+ * so the application will eventually get an error condition.
+ * Set the FTS_DONTCHDIR flag so that when we logically change
+ * directories back to the parent we don't do a chdir.
+ *
+ * If haven't read do so. If the read fails, fts_build sets
+ * FTS_STOP or the fts_info field of the node.
+ */
+ if (sp->fts_child) {
+ if (fts_safe_changedir(sp, p, -1, p->fts_accpath)) {
+ p->fts_errno = errno;
+ p->fts_flags |= FTS_DONTCHDIR;
+ for (p = sp->fts_child; p; p = p->fts_link)
+ p->fts_accpath =
+ p->fts_parent->fts_accpath;
+ }
+ } else if ((sp->fts_child = fts_build(sp, BREAD)) == NULL) {
+ if (ISSET(FTS_STOP))
+ return (NULL);
+ return (p);
+ }
+ p = sp->fts_child;
+ sp->fts_child = NULL;
+ goto name;
+ }
+
+ /* Move to the next node on this level. */
+next: tmp = p;
+ if ((p = p->fts_link) != NULL) {
+ free(tmp);
+
+ /*
+ * If reached the top, return to the original directory, and
+ * load the paths for the next root.
+ */
+ if (p->fts_level == FTS_ROOTLEVEL) {
+#ifdef HAVE_FCHDIR
+ if (FCHDIR(sp, sp->fts_rfd)) {
+#else
+ if (CHDIR(sp, sp->fts_rdir)) {
+#endif
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ fts_load(sp, p);
+ return (sp->fts_cur = p);
+ }
+
+ /*
+ * User may have called fts_set on the node. If skipped,
+ * ignore. If followed, get a file descriptor so we can
+ * get back if necessary.
+ */
+ if (p->fts_instr == FTS_SKIP)
+ goto next;
+ if (p->fts_instr == FTS_FOLLOW) {
+ p->fts_info = fts_stat(sp, p, 1);
+ if (p->fts_info == FTS_D && !ISSET(FTS_NOCHDIR)) {
+#ifdef HAVE_FCHDIR
+ if ((p->fts_symfd =
+ open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ } else if (fcntl(p->fts_symfd, F_SETFD, FD_CLOEXEC) == -1) {
+ p->fts_errno = errno;
+ p->fts_info = FTS_ERR;
+ close(p->fts_symfd);
+ } else
+ p->fts_flags |= FTS_SYMFOLLOW;
+#endif
+ }
+ p->fts_instr = FTS_NOINSTR;
+ }
+
+name: t = sp->fts_path + NAPPEND(p->fts_parent);
+ *t++ = '/';
+ memmove(t, p->fts_name, (size_t)(p->fts_namelen + 1));
+ return (sp->fts_cur = p);
+ }
+
+ /* Move up to the parent node. */
+ p = tmp->fts_parent;
+ free(tmp);
+
+ if (p->fts_level == FTS_ROOTPARENTLEVEL) {
+ /*
+ * Done; free everything up and set errno to 0 so the user
+ * can distinguish between error and EOF.
+ */
+ free(p);
+ errno = 0;
+ return (sp->fts_cur = NULL);
+ }
+
+ /* Nul terminate the pathname. */
+ sp->fts_path[p->fts_pathlen] = '\0';
+
+ /*
+ * Return to the parent directory. If at a root node or came through
+ * a symlink, go back through the file descriptor. Otherwise, cd up
+ * one directory.
+ */
+ if (p->fts_level == FTS_ROOTLEVEL) {
+#ifdef HAVE_FCHDIR
+ if (FCHDIR(sp, sp->fts_rfd)) {
+#else
+ if (CHDIR(sp, sp->fts_rdir)) {
+#endif
+ SET(FTS_STOP);
+ return (NULL);
+ }
+#ifdef HAVE_FCHDIR
+ } else if (p->fts_flags & FTS_SYMFOLLOW) {
+ if (FCHDIR(sp, p->fts_symfd)) {
+ saved_errno = errno;
+ (void)close(p->fts_symfd);
+ errno = saved_errno;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ (void)close(p->fts_symfd);
+#else
+ (void)saved_errno;
+#endif
+ } else if (!(p->fts_flags & FTS_DONTCHDIR) &&
+ fts_safe_changedir(sp, p->fts_parent, -1, "..")) {
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ p->fts_info = p->fts_errno ? FTS_ERR : FTS_DP;
+ return (sp->fts_cur = p);
+}
+
+/*
+ * Fts_set takes the stream as an argument although it's not used in this
+ * implementation; it would be necessary if anyone wanted to add global
+ * semantics to fts using fts_set. An error return is allowed for similar
+ * reasons.
+ */
+/* ARGSUSED */
+int
+fts_set(sp, p, instr)
+ FTS *sp;
+ FTSENT *p;
+ int instr;
+{
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ if (instr && instr != FTS_AGAIN && instr != FTS_FOLLOW &&
+ instr != FTS_NOINSTR && instr != FTS_SKIP) {
+ errno = EINVAL;
+ return (1);
+ }
+ p->fts_instr = instr;
+ return (0);
+}
+
+FTSENT *
+fts_children(sp, instr)
+ FTS *sp;
+ int instr;
+{
+ FTSENT *p;
+#ifdef HAVE_FCHDIR
+ int fd;
+#else
+ char *pszRoot;
+ int rc;
+#endif
+
+ _DIAGASSERT(sp != NULL);
+
+ if (instr && instr != FTS_NAMEONLY) {
+ errno = EINVAL;
+ return (NULL);
+ }
+
+ /* Set current node pointer. */
+ p = sp->fts_cur;
+
+ /*
+ * Errno set to 0 so user can distinguish empty directory from
+ * an error.
+ */
+ errno = 0;
+
+ /* Fatal errors stop here. */
+ if (ISSET(FTS_STOP))
+ return (NULL);
+
+ /* Return logical hierarchy of user's arguments. */
+ if (p->fts_info == FTS_INIT)
+ return (p->fts_link);
+
+ /*
+ * If not a directory being visited in pre-order, stop here. Could
+ * allow FTS_DNR, assuming the user has fixed the problem, but the
+ * same effect is available with FTS_AGAIN.
+ */
+ if (p->fts_info != FTS_D /* && p->fts_info != FTS_DNR */)
+ return (NULL);
+
+ /* Free up any previous child list. */
+ if (sp->fts_child)
+ fts_lfree(sp->fts_child);
+
+ if (instr == FTS_NAMEONLY) {
+ SET(FTS_NAMEONLY);
+ instr = BNAMES;
+ } else
+ instr = BCHILD;
+
+ /*
+ * If using chdir on a relative path and called BEFORE fts_read does
+ * its chdir to the root of a traversal, we can lose -- we need to
+ * chdir into the subdirectory, and we don't know where the current
+ * directory is, so we can't get back so that the upcoming chdir by
+ * fts_read will work.
+ */
+ if (p->fts_level != FTS_ROOTLEVEL || IS_SLASH(p->fts_accpath[0]) ||
+ ISSET(FTS_NOCHDIR))
+ return (sp->fts_child = fts_build(sp, instr));
+
+#ifdef HAVE_FCHDIR
+ if ((fd = open(".", O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+#else
+ if ((pszRoot = getcwd(NULL, 0)) == NULL)
+#endif
+ return (sp->fts_child = NULL);
+ sp->fts_child = fts_build(sp, instr);
+#ifdef HAVE_FCHDIR
+ if (fchdir(fd)) {
+ (void)close(fd);
+ return (NULL);
+ }
+ (void)close(fd);
+#else
+ rc = chdir(pszRoot);
+ free(pszRoot);
+ if (rc)
+ return (NULL);
+#endif
+
+ return (sp->fts_child);
+}
+
+/*
+ * This is the tricky part -- do not casually change *anything* in here. The
+ * idea is to build the linked list of entries that are used by fts_children
+ * and fts_read. There are lots of special cases.
+ *
+ * The real slowdown in walking the tree is the stat calls. If FTS_NOSTAT is
+ * set and it's a physical walk (so that symbolic links can't be directories),
+ * we can do things quickly. First, if it's a 4.4BSD file system, the type
+ * of the file is in the directory entry. Otherwise, we assume that the number
+ * of subdirectories in a node is equal to the number of links to the parent.
+ * The former skips all stat calls. The latter skips stat calls in any leaf
+ * directories and for any files after the subdirectories in the directory have
+ * been found, cutting the stat calls by about 2/3.
+ */
+static FTSENT *
+fts_build(sp, type)
+ FTS *sp;
+ int type;
+{
+ struct dirent *dp;
+ FTSENT *p, *head;
+ size_t nitems;
+ FTSENT *cur, *tail;
+ DIR *dirp;
+ int adjust, cderrno, descend, len, level, nlinks, saved_errno, nostat;
+ size_t maxlen;
+#ifdef FTS_WHITEOUT
+ int oflag;
+#endif
+ char *cp = NULL; /* pacify gcc */
+
+ _DIAGASSERT(sp != NULL);
+
+ /* Set current node pointer. */
+ cur = sp->fts_cur;
+
+ /*
+ * Open the directory for reading. If this fails, we're done.
+ * If being called from fts_read, set the fts_info field.
+ */
+#if defined(FTS_WHITEOUT) && !defined(__OS2__)
+ if (ISSET(FTS_WHITEOUT))
+ oflag = DTF_NODUP|DTF_REWIND;
+ else
+ oflag = DTF_HIDEW|DTF_NODUP|DTF_REWIND;
+#elif defined(_MSC_VER)
+# define __opendir2(path, flag) birdDirOpenExtraInfo(path)
+#else
+#define __opendir2(path, flag) opendir(path)
+#endif
+ if ((dirp = __opendir2(cur->fts_accpath, oflag)) == NULL) {
+ if (type == BREAD) {
+ cur->fts_info = FTS_DNR;
+ cur->fts_errno = errno;
+ }
+ return (NULL);
+ }
+
+ /*
+ * Nlinks is the number of possible entries of type directory in the
+ * directory if we're cheating on stat calls, 0 if we're not doing
+ * any stat calls at all, -1 if we're doing stats on everything.
+ */
+ if (type == BNAMES) {
+ nlinks = 0;
+ nostat = 1;
+ } else if (ISSET(FTS_NOSTAT) && ISSET(FTS_PHYSICAL)) {
+ nlinks = cur->fts_nlink - (ISSET(FTS_SEEDOT) ? 0 : 2);
+ nostat = 1;
+ } else {
+ nlinks = -1;
+ nostat = 0;
+ }
+
+#ifdef notdef
+ (void)printf("nlinks == %d (cur: %d)\n", nlinks, cur->fts_nlink);
+ (void)printf("NOSTAT %d PHYSICAL %d SEEDOT %d\n",
+ ISSET(FTS_NOSTAT), ISSET(FTS_PHYSICAL), ISSET(FTS_SEEDOT));
+#endif
+ /*
+ * If we're going to need to stat anything or we want to descend
+ * and stay in the directory, chdir. If this fails we keep going,
+ * but set a flag so we don't chdir after the post-order visit.
+ * We won't be able to stat anything, but we can still return the
+ * names themselves. Note, that since fts_read won't be able to
+ * chdir into the directory, it will have to return different path
+ * names than before, i.e. "a/b" instead of "b". Since the node
+ * has already been visited in pre-order, have to wait until the
+ * post-order visit to return the error. There is a special case
+ * here, if there was nothing to stat then it's not an error to
+ * not be able to stat. This is all fairly nasty. If a program
+ * needed sorted entries or stat information, they had better be
+ * checking FTS_NS on the returned nodes.
+ */
+ cderrno = 0;
+ if (nlinks || type == BREAD) {
+#ifdef HAVE_FCHDIR
+ if (fts_safe_changedir(sp, cur, dirfd(dirp), NULL)) {
+#else
+ if (fts_safe_changedir(sp, cur, dirfd(dirp), cur->fts_accpath)) {
+#endif
+ if (nlinks && type == BREAD)
+ cur->fts_errno = errno;
+ cur->fts_flags |= FTS_DONTCHDIR;
+ descend = 0;
+ cderrno = errno;
+ } else
+ descend = 1;
+ } else
+ descend = 0;
+
+ /*
+ * Figure out the max file name length that can be stored in the
+ * current path -- the inner loop allocates more path as necessary.
+ * We really wouldn't have to do the maxlen calculations here, we
+ * could do them in fts_read before returning the path, but it's a
+ * lot easier here since the length is part of the dirent structure.
+ *
+ * If not changing directories set a pointer so that can just append
+ * each new name into the path.
+ */
+ len = NAPPEND(cur);
+ if (ISSET(FTS_NOCHDIR)) {
+ cp = sp->fts_path + len;
+ *cp++ = '/';
+ }
+ len++;
+ maxlen = sp->fts_pathlen - len;
+
+ level = cur->fts_level + 1;
+
+ /* Read the directory, attaching each entry to the `link' pointer. */
+ adjust = 0;
+ for (head = tail = NULL, nitems = 0; (dp = readdir(dirp)) != NULL;) {
+ size_t dlen;
+
+ if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
+ continue;
+
+#if HAVE_STRUCT_DIRENT_D_NAMLEN
+ dlen = dp->d_namlen;
+#else
+ dlen = strlen(dp->d_name);
+#endif
+ if ((p = fts_alloc(sp, dp->d_name, dlen)) == NULL)
+ goto mem1;
+ if (dlen >= maxlen) { /* include space for NUL */
+ if (fts_palloc(sp, len + dlen + 1)) {
+ /*
+ * No more memory for path or structures. Save
+ * errno, free up the current structure and the
+ * structures already allocated.
+ */
+mem1: saved_errno = errno;
+ if (p)
+ free(p);
+ fts_lfree(head);
+ (void)closedir(dirp);
+ errno = saved_errno;
+ cur->fts_info = FTS_ERR;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+ adjust = 1;
+ if (ISSET(FTS_NOCHDIR))
+ cp = sp->fts_path + len;
+ maxlen = sp->fts_pathlen - len;
+ }
+
+ p->fts_pathlen = len + dlen;
+ p->fts_parent = sp->fts_cur;
+ p->fts_level = level;
+
+#ifdef FTS_WHITEOUT
+ if (dp->d_type == DT_WHT)
+ p->fts_flags |= FTS_ISW;
+#endif
+
+ if (cderrno) {
+ if (nlinks) {
+ p->fts_info = FTS_NS;
+ p->fts_errno = cderrno;
+ } else
+ p->fts_info = FTS_NSOK;
+ p->fts_accpath = cur->fts_accpath;
+ } else if (nlinks == 0
+#ifdef DT_DIR
+ || (nostat &&
+ dp->d_type != DT_DIR && dp->d_type != DT_UNKNOWN)
+#endif
+ ) {
+ p->fts_accpath =
+ ISSET(FTS_NOCHDIR) ? p->fts_path : p->fts_name;
+ p->fts_info = FTS_NSOK;
+ } else {
+ /* Build a file name for fts_stat to stat. */
+ if (ISSET(FTS_NOCHDIR)) {
+ p->fts_accpath = p->fts_path;
+ memmove(cp, p->fts_name,
+ (size_t)(p->fts_namelen + 1));
+ } else
+ p->fts_accpath = p->fts_name;
+
+ /* Stat it. */
+#ifdef _MSC_VER
+ p->fts_info = fts_stat_dirent(sp, p, 0, dp);
+#else
+ p->fts_info = fts_stat(sp, p, 0);
+#endif
+
+ /* Decrement link count if applicable. */
+ if (nlinks > 0 && (p->fts_info == FTS_D ||
+ p->fts_info == FTS_DC || p->fts_info == FTS_DOT))
+ --nlinks;
+ }
+
+ /* We walk in directory order so "ls -f" doesn't get upset. */
+ p->fts_link = NULL;
+ if (head == NULL)
+ head = tail = p;
+ else {
+ tail->fts_link = p;
+ tail = p;
+ }
+ ++nitems;
+ }
+ (void)closedir(dirp);
+
+ /*
+ * If had to realloc the path, adjust the addresses for the rest
+ * of the tree.
+ */
+ if (adjust)
+ fts_padjust(sp, head);
+
+ /*
+ * If not changing directories, reset the path back to original
+ * state.
+ */
+ if (ISSET(FTS_NOCHDIR)) {
+ if (cp - 1 > sp->fts_path)
+ --cp;
+ *cp = '\0';
+ }
+
+ /*
+ * If descended after called from fts_children or after called from
+ * fts_read and nothing found, get back. At the root level we use
+ * the saved fd; if one of fts_open()'s arguments is a relative path
+ * to an empty directory, we wind up here with no other way back. If
+ * can't get back, we're done.
+ */
+ if (descend && (type == BCHILD || !nitems) &&
+ (cur->fts_level == FTS_ROOTLEVEL ?
+#ifdef HAVE_FCHDIR
+ FCHDIR(sp, sp->fts_rfd) :
+#else
+ CHDIR(sp, sp->fts_rdir) :
+#endif
+ fts_safe_changedir(sp, cur->fts_parent, -1, ".."))) {
+ cur->fts_info = FTS_ERR;
+ SET(FTS_STOP);
+ return (NULL);
+ }
+
+ /* If didn't find anything, return NULL. */
+ if (!nitems) {
+ if (type == BREAD)
+ cur->fts_info = FTS_DP;
+ return (NULL);
+ }
+
+ /* Sort the entries. */
+ if (sp->fts_compar && nitems > 1)
+ head = fts_sort(sp, head, nitems);
+ return (head);
+}
+
+#ifdef _MSC_VER
+/** Special version of fts_stat that takes the information from the directory
+ * entry returned by readdir().
+ *
+ * Directory listing returns all the stat information on systems likes
+ * Windows and OS/2. */
+static u_short
+fts_stat_dirent(FTS *sp, FTSENT *p, int follow, struct dirent *pDirEnt)
+{
+ FTSENT *t;
+ dev_t dev;
+ ino_t ino;
+ struct STAT *sbp, sb;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /* If user needs stat info, stat buffer already allocated. */
+ sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
+
+ /*
+ * Copy over the stat info from the direntry.
+ */
+ *sbp = pDirEnt->d_stat;
+
+ /*
+ * If doing a logical walk, or application requested FTS_FOLLOW, do
+ * a stat(2) on symlinks. If that fails, assume non-existent
+ * symlink and set the errno from the stat call.
+ */
+ if (S_ISLNK(sbp->st_mode) && (ISSET(FTS_LOGICAL) || follow)) {
+ if (stat(p->fts_accpath, sbp)) {
+ saved_errno = errno;
+ errno = 0;
+ return (FTS_SLNONE);
+ }
+ }
+
+ if (S_ISDIR(sbp->st_mode)) {
+ /*
+ * Set the device/inode. Used to find cycles and check for
+ * crossing mount points. Also remember the link count, used
+ * in fts_build to limit the number of stat calls. It is
+ * understood that these fields are only referenced if fts_info
+ * is set to FTS_D.
+ */
+ dev = p->fts_dev = sbp->st_dev;
+ ino = p->fts_ino = sbp->st_ino;
+ p->fts_nlink = sbp->st_nlink;
+
+ if (ISDOT(p->fts_name))
+ return (FTS_DOT);
+
+ /*
+ * Cycle detection is done by brute force when the directory
+ * is first encountered. If the tree gets deep enough or the
+ * number of symbolic links to directories is high enough,
+ * something faster might be worthwhile.
+ */
+
+#ifdef _MSC_VER
+ if (ino && dev) /** @todo ino emulation on windows... */
+#endif
+ for (t = p->fts_parent;
+ t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
+ if (ino == t->fts_ino && dev == t->fts_dev) {
+ p->fts_cycle = t;
+ return (FTS_DC);
+ }
+ return (FTS_D);
+ }
+ if (S_ISLNK(sbp->st_mode))
+ return (FTS_SL);
+ if (S_ISREG(sbp->st_mode))
+ return (FTS_F);
+ return (FTS_DEFAULT);
+}
+
+#endif /* fts_stat_dirent */
+
+static u_short
+fts_stat(sp, p, follow)
+ FTS *sp;
+ FTSENT *p;
+ int follow;
+{
+ FTSENT *t;
+ dev_t dev;
+ ino_t ino;
+ struct STAT *sbp, sb;
+ int saved_errno;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(p != NULL);
+
+ /* If user needs stat info, stat buffer already allocated. */
+ sbp = ISSET(FTS_NOSTAT) ? &sb : p->fts_statp;
+
+#ifdef FTS_WHITEOUT
+ /* check for whiteout */
+ if (p->fts_flags & FTS_ISW) {
+ if (sbp != &sb) {
+ memset(sbp, '\0', sizeof (*sbp));
+ sbp->st_mode = S_IFWHT;
+ }
+ return (FTS_W);
+ }
+#endif
+
+ /*
+ * If doing a logical walk, or application requested FTS_FOLLOW, do
+ * a stat(2). If that fails, check for a non-existent symlink. If
+ * fail, set the errno from the stat call.
+ */
+ if (ISSET(FTS_LOGICAL) || follow) {
+ if (stat(p->fts_accpath, sbp)) {
+ saved_errno = errno;
+ if (!lstat(p->fts_accpath, sbp)) {
+ errno = 0;
+ return (FTS_SLNONE);
+ }
+ p->fts_errno = saved_errno;
+ goto err;
+ }
+ } else if (lstat(p->fts_accpath, sbp)) {
+ p->fts_errno = errno;
+err: memset(sbp, 0, sizeof(struct STAT));
+ return (FTS_NS);
+ }
+
+ if (S_ISDIR(sbp->st_mode)) {
+ /*
+ * Set the device/inode. Used to find cycles and check for
+ * crossing mount points. Also remember the link count, used
+ * in fts_build to limit the number of stat calls. It is
+ * understood that these fields are only referenced if fts_info
+ * is set to FTS_D.
+ */
+ dev = p->fts_dev = sbp->st_dev;
+ ino = p->fts_ino = sbp->st_ino;
+ p->fts_nlink = sbp->st_nlink;
+
+ if (ISDOT(p->fts_name))
+ return (FTS_DOT);
+
+ /*
+ * Cycle detection is done by brute force when the directory
+ * is first encountered. If the tree gets deep enough or the
+ * number of symbolic links to directories is high enough,
+ * something faster might be worthwhile.
+ */
+
+#ifdef _MSC_VER
+ if (ino && dev) /** @todo ino emulation on windows... */
+#endif
+ for (t = p->fts_parent;
+ t->fts_level >= FTS_ROOTLEVEL; t = t->fts_parent)
+ if (ino == t->fts_ino && dev == t->fts_dev) {
+ p->fts_cycle = t;
+ return (FTS_DC);
+ }
+ return (FTS_D);
+ }
+ if (S_ISLNK(sbp->st_mode))
+ return (FTS_SL);
+ if (S_ISREG(sbp->st_mode))
+ return (FTS_F);
+ return (FTS_DEFAULT);
+}
+
+static FTSENT *
+fts_sort(sp, head, nitems)
+ FTS *sp;
+ FTSENT *head;
+ size_t nitems;
+{
+ FTSENT **ap, *p;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(head != NULL);
+
+ /*
+ * Construct an array of pointers to the structures and call qsort(3).
+ * Reassemble the array in the order returned by qsort. If unable to
+ * sort for memory reasons, return the directory entries in their
+ * current order. Allocate enough space for the current needs plus
+ * 40 so don't realloc one entry at a time.
+ */
+ if (nitems > sp->fts_nitems) {
+ FTSENT **new;
+
+ new = realloc(sp->fts_array, sizeof(FTSENT *) * (nitems + 40));
+ if (new == 0)
+ return (head);
+ sp->fts_array = new;
+ sp->fts_nitems = nitems + 40;
+ }
+ for (ap = sp->fts_array, p = head; p; p = p->fts_link)
+ *ap++ = p;
+ qsort((void *)sp->fts_array, nitems, sizeof(FTSENT *),
+ (int (*)(const void *, const void *))sp->fts_compar);
+ for (head = *(ap = sp->fts_array); --nitems; ++ap)
+ ap[0]->fts_link = ap[1];
+ ap[0]->fts_link = NULL;
+ return (head);
+}
+
+static FTSENT *
+fts_alloc(sp, name, namelen)
+ FTS *sp;
+ const char *name;
+ size_t namelen;
+{
+ FTSENT *p;
+ size_t len;
+
+ _DIAGASSERT(sp != NULL);
+ _DIAGASSERT(name != NULL);
+
+#if defined(ALIGNBYTES) && defined(ALIGN)
+ /*
+ * The file name is a variable length array and no stat structure is
+ * necessary if the user has set the nostat bit. Allocate the FTSENT
+ * structure, the file name and the stat structure in one chunk, but
+ * be careful that the stat structure is reasonably aligned. Since the
+ * fts_name field is declared to be of size 1, the fts_name pointer is
+ * namelen + 2 before the first possible address of the stat structure.
+ */
+ len = sizeof(FTSENT) + namelen;
+ if (!ISSET(FTS_NOSTAT))
+ len += sizeof(struct STAT) + ALIGNBYTES;
+ if ((p = malloc(len)) == NULL)
+ return (NULL);
+
+ if (!ISSET(FTS_NOSTAT))
+ p->fts_statp =
+ (struct STAT *)ALIGN((u_long)(p->fts_name + namelen + 2));
+#else
+ (void)len;
+ if ((p = malloc(sizeof(FTSENT) + namelen)) == NULL)
+ return (NULL);
+
+ if (!ISSET(FTS_NOSTAT))
+ if ((p->fts_statp = malloc(sizeof(struct STAT))) == NULL) {
+ free(p);
+ return (NULL);
+ }
+#endif
+
+ /* Copy the name plus the trailing NULL. */
+ memmove(p->fts_name, name, namelen + 1);
+
+ p->fts_namelen = namelen;
+ p->fts_path = sp->fts_path;
+ p->fts_errno = 0;
+ p->fts_flags = 0;
+ p->fts_instr = FTS_NOINSTR;
+ p->fts_number = 0;
+ p->fts_pointer = NULL;
+ return (p);
+}
+
+static void
+fts_lfree(head)
+ FTSENT *head;
+{
+ FTSENT *p;
+
+ /* XXX: head may be NULL ? */
+
+ /* Free a linked list of structures. */
+ while ((p = head) != NULL) {
+ head = head->fts_link;
+
+#if !defined(ALIGNBYTES) || !defined(ALIGN)
+ if (p->fts_statp)
+ free(p->fts_statp);
+#endif
+ free(p);
+ }
+}
+
+static size_t
+fts_pow2(x)
+ size_t x;
+{
+
+ x--;
+ x |= x>>1;
+ x |= x>>2;
+ x |= x>>4;
+ x |= x>>8;
+ x |= x>>16;
+#if LONG_BIT > 32
+ x |= x>>32;
+#endif
+#if LONG_BIT > 64
+ x |= x>>64;
+#endif
+ x++;
+ return (x);
+}
+
+/*
+ * Allow essentially unlimited paths; find, rm, ls should all work on any tree.
+ * Most systems will allow creation of paths much longer than MAXPATHLEN, even
+ * though the kernel won't resolve them. Round up the new size to a power of 2,
+ * so we don't realloc the path 2 bytes at a time.
+ */
+static int
+fts_palloc(sp, size)
+ FTS *sp;
+ size_t size;
+{
+ char *new;
+
+ _DIAGASSERT(sp != NULL);
+
+#if 1
+ /* Protect against fts_pathlen overflow. */
+ if (size > USHRT_MAX + 1) {
+ errno = ENOMEM;
+ return (1);
+ }
+#endif
+ size = fts_pow2(size);
+ new = realloc(sp->fts_path, size);
+ if (new == 0)
+ return (1);
+ sp->fts_path = new;
+ sp->fts_pathlen = size;
+ return (0);
+}
+
+/*
+ * When the path is realloc'd, have to fix all of the pointers in structures
+ * already returned.
+ */
+static void
+fts_padjust(sp, head)
+ FTS *sp;
+ FTSENT *head;
+{
+ FTSENT *p;
+ char *addr;
+
+ _DIAGASSERT(sp != NULL);
+
+#define ADJUST(p) do { \
+ if ((p)->fts_accpath != (p)->fts_name) \
+ (p)->fts_accpath = \
+ addr + ((p)->fts_accpath - (p)->fts_path); \
+ (p)->fts_path = addr; \
+} while (/*CONSTCOND*/0)
+
+ addr = sp->fts_path;
+
+ /* Adjust the current set of children. */
+ for (p = sp->fts_child; p; p = p->fts_link)
+ ADJUST(p);
+
+ /* Adjust the rest of the tree, including the current level. */
+ for (p = head; p->fts_level >= FTS_ROOTLEVEL;) {
+ ADJUST(p);
+ p = p->fts_link ? p->fts_link : p->fts_parent;
+ }
+}
+
+static size_t
+fts_maxarglen(argv)
+ char * const *argv;
+{
+ size_t len, max;
+
+ _DIAGASSERT(argv != NULL);
+
+ for (max = 0; *argv; ++argv)
+ if ((len = strlen(*argv)) > max)
+ max = len;
+ return (max + 1);
+}
+
+/*
+ * Change to dir specified by fd or p->fts_accpath without getting
+ * tricked by someone changing the world out from underneath us.
+ * Assumes p->fts_dev and p->fts_ino are filled in.
+ */
+static int
+fts_safe_changedir(sp, p, fd, path)
+ const FTS *sp;
+ const FTSENT *p;
+ int fd;
+ const char *path;
+{
+ int oldfd = fd, ret = -1;
+ struct STAT sb;
+
+ if (ISSET(FTS_NOCHDIR))
+ return 0;
+
+#ifdef HAVE_FCHDIR
+ if (oldfd < 0) {
+ if (!path) /* shuts up gcc nonull checks*/
+ return -1;
+ fd = open(path, O_RDONLY | KMK_OPEN_NO_INHERIT);
+ if (fd == -1)
+ return -1;
+ }
+
+ if (fstat(fd, &sb) == -1)
+ goto bail;
+#else
+ if (stat(path, &sb))
+ goto bail;
+#endif
+
+ if (sb.st_ino != p->fts_ino || sb.st_dev != p->fts_dev) {
+ errno = ENOENT;
+ goto bail;
+ }
+
+#ifdef HAVE_FCHDIR
+ ret = fchdir(fd);
+#else
+ ret = chdir(path);
+#endif
+
+bail:
+#ifdef HAVE_FCHDIR
+ if (oldfd < 0) {
+ int save_errno = errno;
+ (void)close(fd);
+ errno = save_errno;
+ }
+#endif
+ return ret;
+}
diff --git a/src/kmk/kmkbuiltin/ftsfake.h b/src/kmk/kmkbuiltin/ftsfake.h
new file mode 100644
index 0000000..2518d30
--- /dev/null
+++ b/src/kmk/kmkbuiltin/ftsfake.h
@@ -0,0 +1,159 @@
+/* $NetBSD: fts.h,v 1.12 2005/02/03 04:39:32 perry Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)fts.h 8.3 (Berkeley) 8/14/94
+ */
+
+#ifndef _FTS_H_
+#define _FTS_H_
+
+#ifdef _MSC_VER
+# include "kmkbuiltin/mscfakes.h"
+#endif
+
+typedef struct {
+ struct _ftsent *fts_cur; /* current node */
+ struct _ftsent *fts_child; /* linked list of children */
+ struct _ftsent **fts_array; /* sort array */
+ dev_t fts_dev; /* starting device # */
+ char *fts_path; /* path for this descent */
+#if defined(_MSC_VER) || defined(__OS2__)
+ char *fts_rdir; /* path of root */
+#else
+ int fts_rfd; /* fd for root */
+#endif
+ u_int fts_pathlen; /* sizeof(path) */
+ u_int fts_nitems; /* elements in the sort array */
+ int (*fts_compar) /* compare function */
+ (const struct _ftsent **, const struct _ftsent **);
+
+#define FTS_COMFOLLOW 0x001 /* follow command line symlinks */
+#define FTS_LOGICAL 0x002 /* logical walk */
+#define FTS_NOCHDIR 0x004 /* don't change directories */
+#define FTS_NOSTAT 0x008 /* don't get stat info */
+#define FTS_PHYSICAL 0x010 /* physical walk */
+#define FTS_SEEDOT 0x020 /* return dot and dot-dot */
+#define FTS_XDEV 0x040 /* don't cross devices */
+#ifndef _MSC_VER
+#define FTS_WHITEOUT 0x080 /* return whiteout information */
+#endif
+#define FTS_OPTIONMASK 0x0ff /* valid user option mask */
+
+#define FTS_NAMEONLY 0x100 /* (private) child names only */
+#define FTS_STOP 0x200 /* (private) unrecoverable error */
+ int fts_options; /* fts_open options, global flags */
+} FTS;
+
+typedef struct _ftsent {
+ struct _ftsent *fts_cycle; /* cycle node */
+ struct _ftsent *fts_parent; /* parent directory */
+ struct _ftsent *fts_link; /* next file in directory */
+ long fts_number; /* local numeric value */
+ void *fts_pointer; /* local address value */
+ char *fts_accpath; /* access path */
+ char *fts_path; /* root path */
+ int fts_errno; /* errno for this node */
+#ifndef _MSC_VER
+ int fts_symfd; /* fd for symlink */
+#endif
+ u_short fts_pathlen; /* strlen(fts_path) */
+ u_short fts_namelen; /* strlen(fts_name) */
+
+ ino_t fts_ino; /* inode */
+ dev_t fts_dev; /* device */
+#ifdef __LIBC12_SOURCE__
+ u_int16_t fts_nlink; /* link count */
+#else
+#ifndef _MSC_VER
+ nlink_t fts_nlink; /* link count */
+#else
+ int fts_nlink; /* link count */
+#endif
+#endif
+
+#define FTS_ROOTPARENTLEVEL -1
+#define FTS_ROOTLEVEL 0
+ short fts_level; /* depth (-1 to N) */
+
+#define FTS_D 1 /* preorder directory */
+#define FTS_DC 2 /* directory that causes cycles */
+#define FTS_DEFAULT 3 /* none of the above */
+#define FTS_DNR 4 /* unreadable directory */
+#define FTS_DOT 5 /* dot or dot-dot */
+#define FTS_DP 6 /* postorder directory */
+#define FTS_ERR 7 /* error; errno is set */
+#define FTS_F 8 /* regular file */
+#define FTS_INIT 9 /* initialized only */
+#define FTS_NS 10 /* stat(2) failed */
+#define FTS_NSOK 11 /* no stat(2) requested */
+#define FTS_SL 12 /* symbolic link */
+#define FTS_SLNONE 13 /* symbolic link without target */
+#ifndef _MSC_VER
+#define FTS_W 14 /* whiteout object */
+#endif
+ u_short fts_info; /* user flags for FTSENT structure */
+
+#define FTS_DONTCHDIR 0x01 /* don't chdir .. to the parent */
+#define FTS_SYMFOLLOW 0x02 /* followed a symlink to get here */
+#ifndef _MSC_VER
+#define FTS_ISW 0x04 /* this is a whiteout object */
+#endif
+ u_short fts_flags; /* private flags for FTSENT structure */
+
+#define FTS_AGAIN 1 /* read node again */
+#define FTS_FOLLOW 2 /* follow symbolic link */
+#define FTS_NOINSTR 3 /* no instructions */
+#define FTS_SKIP 4 /* discard node */
+ u_short fts_instr; /* fts_set() instructions */
+
+#ifdef __LIBC12_SOURCE__
+ struct stat12 *fts_statp; /* stat(2) information */
+#else
+ struct stat *fts_statp; /* stat(2) information */
+#endif
+ char fts_name[1]; /* file name */
+} FTSENT;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+FTSENT *fts_children(FTS *, int);
+int fts_close(FTS *);
+FTS *fts_open(char * const *, int,
+ int (*)(const FTSENT **, const FTSENT **));
+FTSENT *fts_read(FTS *);
+int fts_set(FTS *, FTSENT *, int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !_FTS_H_ */
diff --git a/src/kmk/kmkbuiltin/getopt1_r.c b/src/kmk/kmkbuiltin/getopt1_r.c
new file mode 100644
index 0000000..29e4d21
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt1_r.c
@@ -0,0 +1,186 @@
+/* Reentrant version of getopt_long and getopt_long_only.
+
+Based on ../getopt*.*:
+
+ getopt_long and getopt_long_only entry points for GNU getopt.
+Copyright (C) 1987-1994, 1996-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "getopt_r.h"
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+#ifndef const
+#define const
+#endif
+#endif
+
+#include <stdio.h>
+
+#if 0
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+#include <gnu-versions.h>
+#if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+#define ELIDE_CODE
+#endif
+#endif
+#endif
+
+#if 1 //ndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+#include <stdlib.h>
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+int
+getopt_long_r (struct getopt_state_r *gos, int *opt_index)
+{
+ return _getopt_internal_r (gos, gos->long_options, opt_index, 0);
+}
+
+/* Like getopt_long, but '-' as well as '--' can indicate a long option.
+ If an option that starts with '-' (not '--') doesn't match a long option,
+ but does match a short option, it is parsed as a short option
+ instead. */
+
+int
+getopt_long_only_r (struct getopt_state_r *gos, int *opt_index)
+{
+ return _getopt_internal_r (gos, gos->long_options, opt_index, 0);
+}
+
+
+#endif /* #if 1 */ /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+#include <stdio.h>
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+
+ while (1)
+ {
+ int this_option_optind = optind ? optind : 1;
+ int option_index = 0;
+ struct getopt_state_r gos;
+ static struct option long_options[] =
+ {
+ {"add", 1, 0, 0},
+ {"append", 0, 0, 0},
+ {"delete", 1, 0, 0},
+ {"verbose", 0, 0, 0},
+ {"create", 0, 0, 0},
+ {"file", 1, 0, 0},
+ {0, 0, 0, 0}
+ };
+
+ getopt_initialize_r (&gos, argc, argv, "abc:d:0123456789", long_options, NULL, NULL);
+ c = getopt_long_r (&geo, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case 0:
+ printf ("option %s", long_options[option_index].name);
+ if (geo.optarg)
+ printf (" with arg %s", geo.optarg);
+ printf ("\n");
+ break;
+
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", geo.optarg);
+ break;
+
+ case 'd':
+ printf ("option d with value '%s'\n", geo.optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (geo.optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (optind < argc)
+ printf ("%s ", argv[geo.optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/kmkbuiltin/getopt_r.c b/src/kmk/kmkbuiltin/getopt_r.c
new file mode 100644
index 0000000..c531ba5
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt_r.c
@@ -0,0 +1,1090 @@
+/* Reentrant version of getopt.
+
+Based on ../getopt*.*:
+
+ Getopt for GNU.
+NOTE: getopt is now part of the C library, so if you don't know what
+"Keep this file name-space clean" means, talk to drepper@gnu.org
+before changing it!
+
+Copyright (C) 1987-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+/* This tells Alpha OSF/1 not to define a getopt prototype in <stdio.h>.
+ Ditto for AIX 3.2 and <stdlib.h>. */
+#ifndef _NO_PROTO
+# define _NO_PROTO
+#endif
+
+#define FAKES_NO_GETOPT_H /* bird */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if !defined __STDC__ || !__STDC__
+/* This is a separate conditional since some stdc systems
+ reject `defined (const)'. */
+# ifndef const
+# define const
+# endif
+#endif
+
+#include <stdio.h>
+
+#if 0
+/* Comment out all this code if we are using the GNU C Library, and are not
+ actually compiling the library itself. This code is part of the GNU C
+ Library, but also included in many other GNU distributions. Compiling
+ and linking in this code is a waste when using the GNU C library
+ (especially if it is a shared library). Rather than having every GNU
+ program understand `configure --with-gnu-libc' and omit the object files,
+ it is simpler to just do this in the source for each such file. */
+
+#define GETOPT_INTERFACE_VERSION 2
+#if !defined _LIBC && defined __GLIBC__ && __GLIBC__ >= 2
+# include <gnu-versions.h>
+# if _GNU_GETOPT_INTERFACE_VERSION == GETOPT_INTERFACE_VERSION
+# define ELIDE_CODE
+# endif
+#endif
+#endif
+
+#if 1 //ndef ELIDE_CODE
+
+
+/* This needs to come after some library #include
+ to get __GNU_LIBRARY__ defined. */
+#ifdef __GNU_LIBRARY__
+/* Don't include stdlib.h for non-GNU C libraries because some of them
+ contain conflicting prototypes for getopt. */
+# include <stdlib.h>
+# include <unistd.h>
+#endif /* GNU C library. */
+#include <stdlib.h> /* bird: we don't define getopt, so we're good. */
+
+#ifdef VMS
+# include <unixlib.h>
+# if HAVE_STRING_H - 0
+# include <string.h>
+# endif
+#endif
+
+/* This is for other GNU distributions with internationalized messages.
+ When compiling libc, the _ macro is predefined. */
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+/* This version of `getopt' appears to the caller like standard Unix 'getopt'
+ but it behaves differently for the user, since it allows the user
+ to intersperse the options with the other arguments.
+
+ As `getopt' works, it permutes the elements of ARGV so that,
+ when it is done, all the options precede everything else. Thus
+ all application programs are extended to handle flexible argument order.
+
+ Setting the environment variable POSIXLY_CORRECT disables permutation.
+ Then the behavior is completely standard.
+
+ GNU application programs can use a third alternative mode in which
+ they can distinguish the relative order of options and other arguments. */
+
+#include "getopt_r.h"
+#include "err.h"
+#include <assert.h>
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+char *optarg = NULL;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* 1003.2 says this must be 1 before any call. */
+int optind = 1;
+
+/* Formerly, initialization of getopt depended on optind==0, which
+ causes problems with re-calling getopt as programs generally don't
+ know that. */
+
+int __getopt_initialized = 0;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int optopt = '?';
+#endif /* Moved to state_getopt_r in getopt_r.h. */
+
+/* Describe how to deal with options that follow non-option ARGV-elements.
+
+ If the caller did not specify anything,
+ the default is REQUIRE_ORDER if the environment variable
+ POSIXLY_CORRECT is defined, PERMUTE otherwise.
+
+ REQUIRE_ORDER means don't recognize them as options;
+ stop option processing when the first non-option is seen.
+ This is what Unix does.
+ This mode of operation is selected by either setting the environment
+ variable POSIXLY_CORRECT, or using `+' as the first character
+ of the list of option characters.
+
+ PERMUTE is the default. We permute the contents of ARGV as we scan,
+ so that eventually all the non-options are at the end. This allows options
+ to be given in any order, even with programs that were not written to
+ expect this.
+
+ RETURN_IN_ORDER is an option available to programs that were written
+ to expect options and other ARGV-elements in any order and that care about
+ the ordering of the two. We describe each non-option ARGV-element
+ as if it were the argument of an option with character code 1.
+ Using `-' as the first character of the list of option characters
+ selects this mode of operation.
+
+ The special argument `--' forces an end of option-scanning regardless
+ of the value of `ordering'. In the case of RETURN_IN_ORDER, only
+ `--' can cause `getopt' to return -1 with `optind' != ARGC. */
+
+/*static*/ enum
+{
+ REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER
+} /*ordering*/;
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* Value of POSIXLY_CORRECT environment variable. */
+static char *posixly_correct;
+#endif
+
+
+#if 1 //def __GNU_LIBRARY__
+/* We want to avoid inclusion of string.h with non-GNU libraries
+ because there are many ways it can cause trouble.
+ On some systems, it contains special magic macros that don't work
+ in GCC. */
+# include <string.h>
+//# define my_index strchr
+#else
+
+# if HAVE_STRING_H
+# include <string.h>
+# else
+# include <strings.h>
+# endif
+
+#if 0 //def
+/* Avoid depending on library functions or files
+ whose names are inconsistent. */
+#ifndef getenv
+extern char *getenv ();
+#endif
+#endif
+
+static char *
+my_index (const char *str, int chr)
+{
+ while (*str)
+ {
+ if (*str == chr)
+ return (char *) str;
+ str++;
+ }
+ return 0;
+}
+
+/* If using GCC, we can safely declare strlen this way.
+ If not using GCC, it is ok not to declare it. */
+#ifdef __GNUC__
+/* Note that Motorola Delta 68k R3V7 comes with GCC but not stddef.h.
+ That was relevant to code that was here before. */
+# if (!defined __STDC__ || !__STDC__) && !defined strlen
+/* gcc with -traditional declares the built-in strlen to return int,
+ and has done so at least since version 2.4.5. -- rms. */
+extern int strlen (const char *);
+# endif /* not __STDC__ */
+#endif /* __GNUC__ */
+
+#endif /* not __GNU_LIBRARY__ */
+
+/* Handle permutation of arguments. */
+
+#if 0 /* Moved to state_getopt_r in getopt_r.h. */
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+static int first_nonopt;
+static int last_nonopt;
+#endif
+
+#if 0 //def _LIBC
+/* Bash 2.0 gives us an environment variable containing flags
+ indicating ARGV elements that should not be considered arguments. */
+
+/* Defined in getopt_init.c */
+extern char *__getopt_nonoption_flags;
+
+static int nonoption_flags_max_len;
+static int nonoption_flags_len;
+
+static int original_argc;
+static char *const *original_argv;
+
+/* Make sure the environment variable bash 2.0 puts in the environment
+ is valid for the getopt call we must make sure that the ARGV passed
+ to getopt is that one passed to the process. */
+static void __attribute__ ((unused))
+store_args_and_env (int argc, char *const *argv)
+{
+ /* XXX This is no good solution. We should rather copy the args so
+ that we can compare them later. But we must not use malloc(3). */
+ original_argc = argc;
+ original_argv = argv;
+}
+# ifdef text_set_element
+text_set_element (__libc_subinit, store_args_and_env);
+# endif /* text_set_element */
+
+# define SWAP_FLAGS(ch1, ch2) \
+ if (nonoption_flags_len > 0) \
+ { \
+ char __tmp = __getopt_nonoption_flags[ch1]; \
+ __getopt_nonoption_flags[ch1] = __getopt_nonoption_flags[ch2]; \
+ __getopt_nonoption_flags[ch2] = __tmp; \
+ }
+#else /* !_LIBC */
+# define SWAP_FLAGS(ch1, ch2) do { } while (0)
+#endif /* _LIBC */
+
+/* Exchange two adjacent subsequences of ARGV.
+ One subsequence is elements [first_nonopt,last_nonopt)
+ which contains all the non-options that have been skipped so far.
+ The other is elements [last_nonopt,optind), which contains all
+ the options processed since those non-options were skipped.
+
+ `first_nonopt' and `last_nonopt' are relocated so that they describe
+ the new indices of the non-options in ARGV after they are moved. */
+
+static void
+exchange (struct getopt_state_r *gos, char **argv)
+{
+ int bottom = gos->first_nonopt;
+ int middle = gos->last_nonopt;
+ int top = gos->optind;
+ char *tem;
+
+ /* Exchange the shorter segment with the far end of the longer segment.
+ That puts the shorter segment into the right place.
+ It leaves the longer segment in the right place overall,
+ but it consists of two parts that need to be swapped next. */
+
+#if 0 //def _LIBC
+ /* First make sure the handling of the `__getopt_nonoption_flags'
+ string can work normally. Our top argument must be in the range
+ of the string. */
+ if (nonoption_flags_len > 0 && top >= nonoption_flags_max_len)
+ {
+ /* We must extend the array. The user plays games with us and
+ presents new arguments. */
+ char *new_str = malloc (top + 1);
+ if (new_str == NULL)
+ nonoption_flags_len = nonoption_flags_max_len = 0;
+ else
+ {
+ memset (__mempcpy (new_str, __getopt_nonoption_flags,
+ nonoption_flags_max_len),
+ '\0', top + 1 - nonoption_flags_max_len);
+ nonoption_flags_max_len = top + 1;
+ __getopt_nonoption_flags = new_str;
+ }
+ }
+#endif
+
+ while (top > middle && middle > bottom)
+ {
+ if (top - middle > middle - bottom)
+ {
+ /* Bottom segment is the short one. */
+ int len = middle - bottom;
+ register int i;
+
+ /* Swap it with the top part of the top segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[top - (middle - bottom) + i];
+ argv[top - (middle - bottom) + i] = tem;
+ SWAP_FLAGS (bottom + i, top - (middle - bottom) + i);
+ }
+ /* Exclude the moved bottom segment from further swapping. */
+ top -= len;
+ }
+ else
+ {
+ /* Top segment is the short one. */
+ int len = top - middle;
+ register int i;
+
+ /* Swap it with the bottom part of the bottom segment. */
+ for (i = 0; i < len; i++)
+ {
+ tem = argv[bottom + i];
+ argv[bottom + i] = argv[middle + i];
+ argv[middle + i] = tem;
+ SWAP_FLAGS (bottom + i, middle + i);
+ }
+ /* Exclude the moved top segment from further swapping. */
+ bottom += len;
+ }
+ }
+
+ /* Update records for the slots the non-options now occupy. */
+
+ gos->first_nonopt += (gos->optind - gos->last_nonopt);
+ gos->last_nonopt = gos->optind;
+}
+
+/* Initialize the internal data */
+
+void
+getopt_initialize_r (struct getopt_state_r *gos, int argc,
+ char * const *argv, const char *shortopts,
+ const struct option *long_options,
+ char **envp, struct KMKBUILTINCTX *pCtx)
+{
+ assert (shortopts != NULL);
+
+ /* General initialization. */
+ gos->optarg = NULL;
+ gos->optind = 1;
+ gos->__getopt_initialized = (void *)(uintptr_t)&exchange;
+ gos->opterr = 1;
+ gos->optopt = '?';
+ gos->argc = argc;
+ gos->argv = argv;
+ gos->optstring = shortopts;
+ gos->len_optstring = strlen (shortopts);
+ gos->long_options = long_options;
+ gos->pCtx = pCtx;
+
+ /* Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ gos->first_nonopt = gos->last_nonopt = gos->optind;
+
+ gos->nextchar = NULL;
+
+ if (!envp)
+ gos->posixly_correct = getenv("POSIXLY_CORRECT");
+ else
+ {
+ const char *psz;
+ size_t i = 0;
+ gos->posixly_correct = NULL;
+ while ((psz = envp[i]) != NULL)
+ {
+ if ( psz[0] == 'P'
+ && strncmp (psz, "POSIXLY_CORRECT=", sizeof("POSIXLY_CORRECT=") - 1) == 0)
+ {
+ gos->posixly_correct = psz + sizeof("POSIXLY_CORRECT=") - 1;
+ break;
+ }
+ i++;
+ }
+ }
+
+ /* Determine how to handle the ordering of options and nonoptions. */
+
+ if (shortopts[0] == '-')
+ {
+ gos->ordering = RETURN_IN_ORDER;
+ gos->optstring++;
+ gos->len_optstring--;
+ }
+ else if (shortopts[0] == '+')
+ {
+ gos->ordering = REQUIRE_ORDER;
+ gos->optstring++;
+ gos->len_optstring--;
+ }
+ else if (gos->posixly_correct != NULL)
+ gos->ordering = REQUIRE_ORDER;
+ else
+ gos->ordering = PERMUTE;
+
+#if 0 //def _LIBC
+ if (posixly_correct == NULL
+ && argc == original_argc && argv == original_argv)
+ {
+ if (nonoption_flags_max_len == 0)
+ {
+ if (__getopt_nonoption_flags == NULL
+ || __getopt_nonoption_flags[0] == '\0')
+ nonoption_flags_max_len = -1;
+ else
+ {
+ const char *orig_str = __getopt_nonoption_flags;
+ int len = nonoption_flags_max_len = strlen (orig_str);
+ if (nonoption_flags_max_len < argc)
+ nonoption_flags_max_len = argc;
+ __getopt_nonoption_flags =
+ (char *) malloc (nonoption_flags_max_len);
+ if (__getopt_nonoption_flags == NULL)
+ nonoption_flags_max_len = -1;
+ else
+ memset (__mempcpy (__getopt_nonoption_flags, orig_str, len),
+ '\0', nonoption_flags_max_len - len);
+ }
+ }
+ nonoption_flags_len = nonoption_flags_max_len;
+ }
+ else
+ nonoption_flags_len = 0;
+#endif
+
+ //return optstring;
+}
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `getopt' finds another option character, it returns that character,
+ updating `optind' and `nextchar' so that the next call to `getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `getopt' returns -1.
+ Then `optind' is the index in ARGV of the first ARGV-element
+ that is not an option. (The ARGV-elements have been permuted
+ so that those that are not options now come last.)
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `optarg'. Two colons mean an option that
+ wants an optional arg; if there is text in the current ARGV-element,
+ it is returned in `optarg', otherwise `optarg' is set to zero.
+
+ If OPTSTRING starts with `-' or `+', it requests different methods of
+ handling the non-option ARGV-elements.
+ See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
+
+ Long-named options begin with `--' instead of `-'.
+ Their names may be abbreviated as long as the abbreviation is unique
+ or is an exact match for some defined option. If they have an
+ argument, it follows the option name in the same ARGV-element, separated
+ from the option name by a `=', or else the in next ARGV-element.
+ When `getopt' finds a long-named option, it returns 0 if that option's
+ `flag' field is nonzero, the value of the option's `val' field
+ if the `flag' field is zero.
+
+ The elements of ARGV aren't really const, because we permute them.
+ But we pretend they're const in the prototype to be compatible
+ with other systems.
+
+ LONGOPTS is a vector of `struct option' terminated by an
+ element containing a name which is zero.
+
+ LONGIND returns the index in LONGOPT of the long-named option found.
+ It is only valid when a long-named option has been found by the most
+ recent call.
+
+ If LONG_ONLY is nonzero, '-' as well as '--' can introduce
+ long-named options. */
+
+int
+_getopt_internal_r (struct getopt_state_r *gos, const struct option *longopts,
+ int *longind, int long_only)
+{
+ assert (gos->__getopt_initialized == (void *)(uintptr_t)&exchange);
+ gos->optarg = NULL;
+
+#if 0 /* requires explicit call now */
+ if (gos->optind == 0 || !gos->__getopt_initialized)
+ {
+ if (gos->optind == 0)
+ gos->optind = 1; /* Don't scan ARGV[0], the program name. */
+ optstring = _getopt_initialize_r (gos, gos->argc, gos->argv, optstring);
+ gos->__getopt_initialized = 1;
+ }
+#else
+ assert (gos->__getopt_initialized == (void *)(uintptr_t)&exchange);
+#endif
+
+ /* Test whether ARGV[optind] points to a non-option argument.
+ Either it does not have option syntax, or there is an environment flag
+ from the shell indicating it is not an option. The later information
+ is only used when the used in the GNU libc. */
+#if 0 //def _LIBC
+# define NONOPTION_P (gos->argv[gos->optind][0] != '-' || gos->argv[gos->optind][1] == '\0' \
+ || (gos->optind < gos->nonoption_flags_len \
+ && gos->__getopt_nonoption_flags[gos->optind] == '1'))
+#else
+# define NONOPTION_P (gos->argv[gos->optind][0] != '-' || gos->argv[gos->optind][1] == '\0')
+#endif
+
+ if (gos->nextchar == NULL || *gos->nextchar == '\0')
+ {
+ /* Advance to the next ARGV-element. */
+
+ /* Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
+ moved back by the user (who may also have changed the arguments). */
+ if (gos->last_nonopt > gos->optind)
+ gos->last_nonopt = gos->optind;
+ if (gos->first_nonopt > gos->optind)
+ gos->first_nonopt = gos->optind;
+
+ if (gos->ordering == PERMUTE)
+ {
+ /* If we have just processed some options following some non-options,
+ exchange them so that the options come first. */
+
+ if (gos->first_nonopt != gos->last_nonopt && gos->last_nonopt != gos->optind)
+ exchange (gos, (char **) gos->argv);
+ else if (gos->last_nonopt != gos->optind)
+ gos->first_nonopt = gos->optind;
+
+ /* Skip any additional non-options
+ and extend the range of non-options previously skipped. */
+
+ while (gos->optind < gos->argc && NONOPTION_P)
+ gos->optind++;
+ gos->last_nonopt = gos->optind;
+ }
+
+ /* The special ARGV-element `--' means premature end of options.
+ Skip it like a null option,
+ then exchange with previous non-options as if it were an option,
+ then skip everything else like a non-option. */
+
+ if (gos->optind != gos->argc && !strcmp (gos->argv[gos->optind], "--"))
+ {
+ gos->optind++;
+
+ if (gos->first_nonopt != gos->last_nonopt && gos->last_nonopt != gos->optind)
+ exchange (gos, (char **) gos->argv);
+ else if (gos->first_nonopt == gos->last_nonopt)
+ gos->first_nonopt = gos->optind;
+ gos->last_nonopt = gos->argc;
+
+ gos->optind = gos->argc;
+ }
+
+ /* If we have done all the ARGV-elements, stop the scan
+ and back over any non-options that we skipped and permuted. */
+
+ if (gos->optind == gos->argc)
+ {
+ /* Set the next-arg-index to point at the non-options
+ that we previously skipped, so the caller will digest them. */
+ if (gos->first_nonopt != gos->last_nonopt)
+ gos->optind = gos->first_nonopt;
+ return -1;
+ }
+
+ /* If we have come to a non-option and did not permute it,
+ either stop the scan or describe it to the caller and pass it by. */
+
+ if (NONOPTION_P)
+ {
+ if (gos->ordering == REQUIRE_ORDER)
+ return -1;
+ gos->optarg = gos->argv[gos->optind++];
+ return 1;
+ }
+
+ /* We have found another option-ARGV-element.
+ Skip the initial punctuation. */
+
+ gos->nextchar = (gos->argv[gos->optind] + 1
+ + (longopts != NULL && gos->argv[gos->optind][1] == '-'));
+ }
+
+ /* Decode the current option-ARGV-element. */
+
+ /* Check whether the ARGV-element is a long option.
+
+ If long_only and the ARGV-element has the form "-f", where f is
+ a valid short option, don't consider it an abbreviated form of
+ a long option that starts with f. Otherwise there would be no
+ way to give the -f short option.
+
+ On the other hand, if there's a long option "fubar" and
+ the ARGV-element is "-fu", do consider that an abbreviation of
+ the long option, just like "--fu", and not "-f" with arg "u".
+
+ This distinction seems to be the most useful approach. */
+
+ if (longopts != NULL
+ && (gos->argv[gos->optind][1] == '-'
+ || (long_only
+ && ( gos->argv[gos->optind][2]
+ || !memchr (gos->optstring, gos->argv[gos->optind][1], gos->len_optstring) )
+ )
+ )
+ )
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = -1;
+ int option_index;
+
+ for (nameend = gos->nextchar; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, gos->nextchar, nameend - gos->nextchar))
+ {
+ if ((unsigned int) (nameend - gos->nextchar)
+ == (unsigned int) strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+
+ if (ambig && !exact)
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2, _("%s: option '%s' is ambiguous"),
+ gos->argv[0], gos->argv[gos->optind]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optind++;
+ gos->optopt = 0;
+ return '?';
+ }
+
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ gos->optind++;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ gos->optarg = nameend + 1;
+ else
+ {
+ if (gos->opterr)
+ { /* bird: disambiguate */
+ if (gos->argv[gos->optind - 1][1] == '-')
+ /* --option */
+ errx (gos->pCtx, 2,
+ _("%s: option '--%s' doesn't allow an argument\n"),
+ gos->argv[0], pfound->name);
+ else
+ /* +option or -option */
+ errx (gos->pCtx, 2,
+ _("%s: option '%c%s' doesn't allow an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1][0], pfound->name);
+ }
+
+ gos->nextchar += strlen (gos->nextchar);
+
+ gos->optopt = pfound->val;
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (gos->optind < gos->argc)
+ gos->optarg = gos->argv[gos->optind++];
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '%s' requires an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optopt = pfound->val;
+ return gos->optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ gos->nextchar += strlen (gos->nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+
+ /* Can't find it as a long option. If this is not getopt_long_only,
+ or the option starts with '--' or is not a valid short
+ option, then it's an error.
+ Otherwise interpret it as a short option. */
+ if (!long_only || gos->argv[gos->optind][1] == '-'
+ || memchr(gos->optstring, *gos->nextchar, gos->len_optstring) == NULL)
+ {
+ if (gos->opterr)
+ {
+ if (gos->argv[gos->optind][1] == '-')
+ /* --option */
+ errx (gos->pCtx, 2, _("%s: unrecognized option '--%s'\n"),
+ gos->argv[0], gos->nextchar);
+ else
+ /* +option or -option */
+ errx (gos->pCtx, 2, _("%s: unrecognized option '%c%s'\n"),
+ gos->argv[0], gos->argv[gos->optind][0], gos->nextchar);
+ }
+ gos->nextchar = (char *) "";
+ gos->optind++;
+ gos->optopt = 0;
+ return '?';
+ }
+ }
+
+ /* Look at and handle the next short option-character. */
+
+ {
+ char c = *gos->nextchar++;
+ char *temp = (char *)memchr (gos->optstring, c, gos->len_optstring);
+
+ /* Increment `optind' when we start to process its last character. */
+ if (*gos->nextchar == '\0')
+ ++gos->optind;
+
+ if (temp == NULL || c == ':')
+ {
+ if (gos->opterr)
+ {
+ if (gos->posixly_correct)
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2, _("%s: illegal option -- %c\n"),
+ gos->argv[0], c);
+ else
+ errx (gos->pCtx, 2, _("%s: invalid option -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ return '?';
+ }
+ /* Convenience. Treat POSIX -W foo same as long option --foo */
+ if (temp[0] == 'W' && temp[1] == ';')
+ {
+ char *nameend;
+ const struct option *p;
+ const struct option *pfound = NULL;
+ int exact = 0;
+ int ambig = 0;
+ int indfound = 0;
+ int option_index;
+
+ /* This is an option that requires an argument. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ gos->optind++;
+ }
+ else if (gos->optind == gos->argc)
+ {
+ if (gos->opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2, _("%s: option requires an argument -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ if (gos->optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ return c;
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ gos->optarg = gos->argv[gos->optind++];
+
+ /* optarg is now the argument, see if it's in the
+ table of longopts. */
+
+ for (gos->nextchar = nameend = gos->optarg; *nameend && *nameend != '='; nameend++)
+ /* Do nothing. */ ;
+
+ /* Test all long options for either exact match
+ or abbreviated matches. */
+ for (p = longopts, option_index = 0; p->name; p++, option_index++)
+ if (!strncmp (p->name, gos->nextchar, nameend - gos->nextchar))
+ {
+ if ((unsigned int) (nameend - gos->nextchar) == strlen (p->name))
+ {
+ /* Exact match found. */
+ pfound = p;
+ indfound = option_index;
+ exact = 1;
+ break;
+ }
+ else if (pfound == NULL)
+ {
+ /* First nonexact match found. */
+ pfound = p;
+ indfound = option_index;
+ }
+ else
+ /* Second or later nonexact match found. */
+ ambig = 1;
+ }
+ if (ambig && !exact)
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2, _("%s: option '-W %s' is ambiguous\n"),
+ gos->argv[0], gos->argv[gos->optind]);
+ gos->nextchar += strlen (gos->nextchar);
+ gos->optind++;
+ return '?';
+ }
+ if (pfound != NULL)
+ {
+ option_index = indfound;
+ if (*nameend)
+ {
+ /* Don't test has_arg with >, because some C compilers don't
+ allow it to be used on enums. */
+ if (pfound->has_arg)
+ gos->optarg = nameend + 1;
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '-W %s' doesn't allow an argument\n"),
+ gos->argv[0], pfound->name);
+
+ gos->nextchar += strlen (gos->nextchar);
+ return '?';
+ }
+ }
+ else if (pfound->has_arg == 1)
+ {
+ if (gos->optind < gos->argc)
+ gos->optarg = gos->argv[gos->optind++];
+ else
+ {
+ if (gos->opterr)
+ errx (gos->pCtx, 2,
+ _("%s: option '%s' requires an argument\n"),
+ gos->argv[0], gos->argv[gos->optind - 1]);
+ gos->nextchar += strlen (gos->nextchar);
+ return gos->optstring[0] == ':' ? ':' : '?';
+ }
+ }
+ gos->nextchar += strlen (gos->nextchar);
+ if (longind != NULL)
+ *longind = option_index;
+ if (pfound->flag)
+ {
+ *(pfound->flag) = pfound->val;
+ return 0;
+ }
+ return pfound->val;
+ }
+ gos->nextchar = NULL;
+ return 'W'; /* Let the application handle it. */
+ }
+ if (temp[1] == ':')
+ {
+ if (temp[2] == ':')
+ {
+ /* This is an option that accepts an argument optionally. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ gos->optind++;
+ }
+ else
+ gos->optarg = NULL;
+ gos->nextchar = NULL;
+ }
+ else
+ {
+ /* This is an option that requires an argument. */
+ if (*gos->nextchar != '\0')
+ {
+ gos->optarg = gos->nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ gos->optind++;
+ }
+ else if (gos->optind == gos->argc)
+ {
+ if (gos->opterr)
+ {
+ /* 1003.2 specifies the format of this message. */
+ errx (gos->pCtx, 2,
+ _("%s: option requires an argument -- %c\n"),
+ gos->argv[0], c);
+ }
+ gos->optopt = c;
+ if (gos->optstring[0] == ':')
+ c = ':';
+ else
+ c = '?';
+ }
+ else
+ /* We already incremented `optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ gos->optarg = gos->argv[gos->optind++];
+ gos->nextchar = NULL;
+ }
+ }
+ return c;
+ }
+}
+
+int
+getopt_r (struct getopt_state_r *gos)
+{
+ return _getopt_internal_r (gos, NULL, NULL, 0);
+}
+
+#endif /* #if 1 */ /* Not ELIDE_CODE. */
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `getopt'. */
+
+int
+main (int argc, char **argv)
+{
+ int c;
+ int digit_optind = 0;
+ struct getopt_state_r = gos;
+
+ getopt_initialize_r (&gos, argc, argv, "abc:d:0123456789", NULL, NULL, NULL);
+
+ while (1)
+ {
+ int this_option_optind = gos.optind ? gos.optind : 1;
+
+ c = getopt_r (&gos);
+ if (c == -1)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_optind != 0 && digit_optind != this_option_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_optind = this_option_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value '%s'\n", optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (gos.optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (gos.optind < argc)
+ printf ("%s ", argv[gos.optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/src/kmk/kmkbuiltin/getopt_r.h b/src/kmk/kmkbuiltin/getopt_r.h
new file mode 100644
index 0000000..b9f3c8b
--- /dev/null
+++ b/src/kmk/kmkbuiltin/getopt_r.h
@@ -0,0 +1,182 @@
+/* Reentrant version of getopt.
+
+Based on ../getopt*.*:
+
+ Declarations for getopt.
+Copyright (C) 1989-2016 Free Software Foundation, Inc.
+
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@gnu.org.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+Modifications:
+ Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+*/
+
+/* Not quite safe to mix when converting code. */
+#ifdef _GETOPT_H
+# define _GETOPT_H "getopt.h was included already"
+# error "getopt.h was included already"
+#endif
+
+#ifndef INCLUDED_GETOPT_R_H
+#define INCLUDED_GETOPT_R_H 1
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct getopt_state_r
+{
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+/*extern*/ char *optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns -1, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/*extern*/ int optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+/*extern*/ int opterr;
+
+/* Set to an option character which was unrecognized. */
+
+/*extern*/ int optopt;
+
+
+/* Internal state: */
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+/*static*/ char *nextchar;
+
+/* REQUIRE_ORDER, PERMUTE or RETURN_IN_ORDER, see getopt_r.c. */
+/*static*/ int ordering;
+
+/* Value of POSIXLY_CORRECT environment variable. */
+/*static*/ const char *posixly_correct; /* bird: added 'const' */
+
+/* Describe the part of ARGV that contains non-options that have
+ been skipped. `first_nonopt' is the index in ARGV of the first of them;
+ `last_nonopt' is the index after the last of them. */
+
+/*static*/ int first_nonopt;
+/*static*/ int last_nonopt;
+
+/* Mainly for asserting usage sanity. */
+/*static*/ void *__getopt_initialized;
+
+/* New internal state (to resubmitting same parameters in each call): */
+ /* new: the argument vector length. */
+ int argc;
+ /* new: the argument vector. */
+ char * const *argv;
+ /* new: the short option string (can be NULL/empty). */
+ const char *optstring;
+ /* new: the short option string length. */
+ size_t len_optstring;
+ /* new: the long options (can be NULL) */
+ const struct option *long_options;
+ /* Output context for err.h. */
+ struct KMKBUILTINCTX *pCtx;
+} getopt_state_r;
+
+
+#ifndef no_argument
+
+/* Describe the long-named options requested by the application.
+ The LONG_OPTIONS argument to getopt_long or getopt_long_only is a vector
+ of `struct option' terminated by an element containing a name which is
+ zero.
+
+ The field `has_arg' is:
+ no_argument (or 0) if the option does not take an argument,
+ required_argument (or 1) if the option requires an argument,
+ optional_argument (or 2) if the option takes an optional argument.
+
+ If the field `flag' is not NULL, it points to a variable that is set
+ to the value given in the field `val' when the option is found, but
+ left unchanged if the option is not found.
+
+ To have a long-named option do something other than set an `int' to
+ a compiled-in constant, such as set a value from `optarg', set the
+ option's `flag' field to zero and its `val' field to a nonzero
+ value (the equivalent single-letter option character, if there is
+ one). For long options that have a zero `flag' field, `getopt'
+ returns the contents of the `val' field. */
+
+struct option
+{
+#if defined (__STDC__) && __STDC__
+ const char *name;
+#else
+ char *name;
+#endif
+ /* has_arg can't be an enum because some compilers complain about
+ type mismatches in all the code that assumes it is an int. */
+ int has_arg;
+ int *flag;
+ int val;
+};
+
+/* Names for the values of the `has_arg' field of `struct option'. */
+
+#define no_argument 0
+#define required_argument 1
+#define optional_argument 2
+
+#endif /* Same as ../getopt.h. Fix later? */
+
+extern void getopt_initialize_r (struct getopt_state_r *gos, int argc,
+ char *const *argv, const char *shortopts,
+ const struct option *longopts,
+ char **envp, struct KMKBUILTINCTX *pCtx);
+extern int getopt_r (struct getopt_state_r *gos);
+extern int getopt_long_r (struct getopt_state_r *gos, int *longind);
+extern int getopt_long_only_r (struct getopt_state_r *gos, int *longind);
+
+/* Internal only. Users should not call this directly. */
+extern int _getopt_internal_r (struct getopt_state_r *gos,
+ const struct option *longopts,
+ int *longind, int long_only);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* getopt_r.h */
diff --git a/src/kmk/kmkbuiltin/haikufakes.c b/src/kmk/kmkbuiltin/haikufakes.c
new file mode 100644
index 0000000..15b4a3a
--- /dev/null
+++ b/src/kmk/kmkbuiltin/haikufakes.c
@@ -0,0 +1,53 @@
+/* $Id: haikufakes.c 2546 2011-10-01 19:49:54Z bird $ */
+/** @file
+ * Fake Unix/BSD stuff for Haiku.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include "haikufakes.h"
+
+
+int haiku_lchmod(const char *pszPath, mode_t mode)
+{
+ /*
+ * Weed out symbolic links.
+ */
+ struct stat s;
+ if ( !lstat(pszPath, &s)
+ && S_ISLNK(s.st_mode))
+ {
+ errno = -ENOSYS;
+ return -1;
+ }
+
+ return chmod(pszPath, mode);
+}
+
diff --git a/src/kmk/kmkbuiltin/haikufakes.h b/src/kmk/kmkbuiltin/haikufakes.h
new file mode 100644
index 0000000..eff20bb
--- /dev/null
+++ b/src/kmk/kmkbuiltin/haikufakes.h
@@ -0,0 +1,42 @@
+/* $Id: haikufakes.h 2656 2012-09-10 20:39:16Z bird $ */
+/** @file
+ * Unix/BSD fakes for Haiku.
+ */
+
+/*
+ * Copyright (c) 2005-2012 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___haikufakes_h
+#define ___haikufakes_h
+
+#define EX_OK 0
+#define EX_OSERR 1
+#define EX_NOUSER 1
+#define EX_USAGE 1
+#define EX_TEMPFAIL 1
+#define EX_SOFTWARE 1
+
+#define lutimes(path, tvs) utimes(path, tvs)
+#define lchmod haiku_lchmod
+
+extern int haiku_lchmod(const char *pszPath, mode_t mode);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/install.c b/src/kmk/kmkbuiltin/install.c
new file mode 100644
index 0000000..a6d1ca8
--- /dev/null
+++ b/src/kmk/kmkbuiltin/install.c
@@ -0,0 +1,1248 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. 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.
+ */
+
+#if 0 /*ndef lint*/
+static const char copyright[] =
+"@(#) Copyright (c) 1987, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)xinstall.c 8.1 (Berkeley) 7/21/93";
+#endif /* not lint */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/usr.bin/xinstall/xinstall.c,v 1.66 2005/01/25 14:34:57 ssouhlal Exp $");
+#endif
+
+#define FAKES_NO_GETOPT_H
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+# if !defined(__HAIKU__) && !defined(__gnu_hurd__)
+# include <sys/mount.h>
+# endif
+# include <sys/wait.h>
+# include <sys/time.h>
+#endif /* !_MSC_VER */
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#ifdef __NetBSD__
+# include <util.h>
+# define strtofflags(a, b, c) string_to_flags(a, b, c)
+#endif
+#include <unistd.h>
+#if defined(__EMX__) || defined(_MSC_VER)
+# include <process.h>
+#endif
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#include "kmkbuiltin.h"
+#include "k/kDefs.h" /* for K_OS */
+#include "dos2unix.h"
+
+
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+
+#ifndef MAXBSIZE
+# define MAXBSIZE 0x20000
+#endif
+
+#define MAX_CMP_SIZE (16 * 1024 * 1024)
+
+#define DIRECTORY 0x01 /* Tell install it's a directory. */
+#define SETFLAGS 0x02 /* Tell install to set flags. */
+#define NOCHANGEBITS (UF_IMMUTABLE | UF_APPEND | SF_IMMUTABLE | SF_APPEND)
+#define BACKUP_SUFFIX ".old"
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+#ifndef EFTYPE
+# define EFTYPE EINVAL
+#endif
+
+#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
+# define IS_SLASH(ch) ((ch) == '/' || (ch) == '\\')
+#else
+# define IS_SLASH(ch) ((ch) == '/')
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct INSTALLINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+
+ gid_t gid;
+ uid_t uid;
+ int dobackup, docompare, dodir, dopreserve, dostrip, nommap, safecopy, verbose, mode_given;
+ mode_t mode;
+ const char *suffix;
+ int ignore_perm_errors;
+ int hard_link_files_when_possible;
+ int verbose_hard_link_refusal;
+ int verbose_hard_link_mode_mismatch;
+ int dos2unix;
+} INSTALLINSTANCE;
+typedef INSTALLINSTANCE *PINSTALLINSTANCE;
+
+enum
+{
+ kInstOpt_Help = 261,
+ kInstOpt_Version,
+ kInstOpt_Verbose,
+ kInstOpt_Quiet,
+ kInstOpt_IgnorePermErrors,
+ kInstOpt_NoIgnorePermErrors,
+ kInstOpt_HardLinkFilesWhenPossible,
+ kInstOpt_NoHardLinkFilesWhenPossible,
+ kInstOpt_Dos2Unix,
+ kInstOpt_Unix2Dos,
+ kInstOpt_VerboseHardLinkRefusal,
+ kInstOpt_QuietHardLinkRefusal,
+ kInstOpt_VerboseHardLinkModeMismatch,
+ kInstOpt_QuietHardLinkModeMismatch
+};
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, kInstOpt_Help },
+ { "version", no_argument, 0, kInstOpt_Version },
+ { "quiet", no_argument, 0, kInstOpt_Quiet },
+ { "ignore-perm-errors", no_argument, 0, kInstOpt_IgnorePermErrors },
+ { "no-ignore-perm-errors", no_argument, 0, kInstOpt_NoIgnorePermErrors },
+ { "hard-link-files-when-possible", no_argument, 0, kInstOpt_HardLinkFilesWhenPossible },
+ { "no-hard-link-files-when-possible", no_argument, 0, kInstOpt_NoHardLinkFilesWhenPossible },
+ { "verbose-hard-link-refusal", no_argument, 0, kInstOpt_VerboseHardLinkRefusal },
+ { "quiet-hard-link-refusal", no_argument, 0, kInstOpt_QuietHardLinkRefusal },
+ { "verbose-hard-link-mode-mismatch", no_argument, 0, kInstOpt_VerboseHardLinkModeMismatch },
+ { "quiet-hard-link-mode-mismatch", no_argument, 0, kInstOpt_QuietHardLinkModeMismatch },
+ { "dos2unix", no_argument, 0, kInstOpt_Dos2Unix },
+ { "unix2dos", no_argument, 0, kInstOpt_Unix2Dos },
+#if 1 /* GNU coreutils compatibility: */
+ { "compare", no_argument, 0, 'C' },
+ { "directory", no_argument, 0, 'd' },
+ { "group", required_argument, 0, 'g' },
+ { "mode", required_argument, 0, 'm' },
+ { "owner", required_argument, 0, 'o' },
+ { "strip", no_argument, 0, 's' },
+ { "suffix", required_argument, 0, 'S' },
+ { "verbose", no_argument, 0, 'v' },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+
+static int copy(PINSTALLINSTANCE, int, const char *, int *, const char *);
+static int compare(int, size_t, int, size_t);
+static int create_newfile(PINSTALLINSTANCE, const char *, int, struct stat *);
+static int create_tempfile(const char *, char *, size_t);
+static int install(PINSTALLINSTANCE, const char *, const char *, u_long, u_int);
+static int install_dir(PINSTALLINSTANCE, char *);
+static u_long numeric_id(PINSTALLINSTANCE, const char *, const char *);
+static int strip(PINSTALLINSTANCE, const char *);
+static int usage(PKMKBUILTINCTX, int);
+static char *last_slash(const char *);
+static KBOOL needs_dos2unix_conversion(const char *pszFilename);
+static KBOOL needs_unix2dos_conversion(const char *pszFilename);
+
+int
+kmk_builtin_install(int argc, char *argv[], char ** envp, PKMKBUILTINCTX pCtx)
+{
+ INSTALLINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat from_sb, to_sb;
+ mode_t *set;
+ u_long fset = 0;
+ int ch, no_target;
+ u_int iflags;
+ char *flags;
+ const char *group, *owner, *to_name;
+ (void)envp;
+
+ /* Initialize global instance data. */
+ This.pCtx = pCtx;
+ This.mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+ This.suffix = BACKUP_SUFFIX;
+ This.gid = 0;
+ This.uid = 0;
+ This.dobackup = 0;
+ This.docompare = 0;
+ This.dodir = 0;
+ This.dopreserve = 0;
+ This.dostrip = 0;
+ This.nommap = 0;
+ This.safecopy = 0;
+ This.verbose = 0;
+ This.mode_given = 0;
+ This.ignore_perm_errors = geteuid() != 0;
+ This.hard_link_files_when_possible = 0;
+ This.verbose_hard_link_refusal = 0;
+ This.verbose_hard_link_mode_mismatch = 0;
+ This.dos2unix = 0;
+
+ iflags = 0;
+ group = owner = NULL;
+ getopt_initialize_r(&gos, argc, argv, "B:bCcdf:g:Mm:o:pSsv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'B':
+ This.suffix = gos.optarg;
+ /* FALLTHROUGH */
+ case 'b':
+ This.dobackup = 1;
+ break;
+ case 'C':
+ This.docompare = 1;
+ break;
+ case 'c':
+ /* For backwards compatibility. */
+ break;
+ case 'd':
+ This.dodir = 1;
+ break;
+ case 'f':
+#if defined(UF_IMMUTABLE) && K_OS != K_OS_GNU_KFBSD && K_OS != K_OS_GNU_HURD
+ flags = optarg;
+ if (strtofflags(&flags, &fset, NULL))
+ return errx(pCtx, EX_USAGE, "%s: invalid flag", flags);
+ iflags |= SETFLAGS;
+#else
+ (void)flags;
+#endif
+ break;
+ case 'g':
+ group = gos.optarg;
+ break;
+ case 'M':
+ This.nommap = 1;
+ break;
+ case 'm':
+ if (!(set = bsd_setmode(gos.optarg)))
+ return errx(pCtx, EX_USAGE, "invalid file mode: %s", gos.optarg);
+ This.mode = bsd_getmode(set, 0);
+ free(set);
+ This.mode_given = 1;
+ break;
+ case 'o':
+ owner = gos.optarg;
+ break;
+ case 'p':
+ This.docompare = This.dopreserve = 1;
+ break;
+ case 'S':
+ This.safecopy = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case 's':
+ This.dostrip = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case 'v':
+ case kInstOpt_Verbose:
+ This.verbose = 1;
+ break;
+ case kInstOpt_Quiet:
+ This.verbose = 0;
+ break;
+ case kInstOpt_Help:
+ usage(pCtx, 0);
+ return 0;
+ case kInstOpt_Version:
+ return kbuild_version(argv[0]);
+ case kInstOpt_IgnorePermErrors:
+ This.ignore_perm_errors = 1;
+ break;
+ case kInstOpt_NoIgnorePermErrors:
+ This.ignore_perm_errors = 0;
+ break;
+ case kInstOpt_HardLinkFilesWhenPossible:
+ This.hard_link_files_when_possible = 1;
+ break;
+ case kInstOpt_NoHardLinkFilesWhenPossible:
+ This.hard_link_files_when_possible = 0;
+ break;
+ case kInstOpt_VerboseHardLinkRefusal:
+ This.verbose_hard_link_refusal = 1;
+ break;
+ case kInstOpt_QuietHardLinkRefusal:
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case kInstOpt_VerboseHardLinkModeMismatch:
+ This.verbose_hard_link_mode_mismatch = 1;
+ break;
+ case kInstOpt_QuietHardLinkModeMismatch:
+ This.verbose_hard_link_mode_mismatch = 0;
+ break;
+ case kInstOpt_Dos2Unix:
+ This.dos2unix = 1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case kInstOpt_Unix2Dos:
+ This.dos2unix = -1;
+ This.verbose_hard_link_refusal = 0;
+ break;
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ /* some options make no sense when creating directories */
+ if (This.dostrip && This.dodir) {
+ warnx(pCtx, "-d and -s may not be specified together");
+ return usage(pCtx, 1);
+ }
+
+ /* must have at least two arguments, except when creating directories */
+ if (argc == 0 || (argc == 1 && !This.dodir))
+ return usage(pCtx, 1);
+
+ /* and unix2dos doesn't combine well with a couple of other options. */
+ if (This.dos2unix != 0) {
+ if (This.docompare) {
+ warnx(pCtx, "-C/-p and --dos2unix/unix2dos may not be specified together");
+ return usage(pCtx, 1);
+ }
+ if (This.dostrip) {
+ warnx(pCtx, "-s and --dos2unix/unix2dos may not be specified together");
+ return usage(pCtx, 1);
+ }
+ }
+
+ /* need to make a temp copy so we can compare stripped version */
+ if (This.docompare && This.dostrip)
+ This.safecopy = 1;
+
+ /* get group and owner id's */
+ if (group != NULL) {
+#ifndef _MSC_VER
+ struct group *gp;
+ if ((gp = getgrnam(group)) != NULL)
+ This.gid = gp->gr_gid;
+ else
+#endif
+ {
+ This.gid = (gid_t)numeric_id(&This, group, "group");
+ if (This.gid == (gid_t)-1)
+ return 1;
+ }
+ } else
+ This.gid = (gid_t)-1;
+
+ if (owner != NULL) {
+#ifndef _MSC_VER
+ struct passwd *pp;
+ if ((pp = getpwnam(owner)) != NULL)
+ This.uid = pp->pw_uid;
+ else
+#endif
+ {
+ This.uid = (uid_t)numeric_id(&This, owner, "user");
+ if (This.uid == (uid_t)-1)
+ return 1;
+ }
+ } else
+ This.uid = (uid_t)-1;
+
+ if (This.dodir) {
+ for (; *argv != NULL; ++argv) {
+ int rc = install_dir(&This, *argv);
+ if (rc)
+ return rc;
+ }
+ return EX_OK;
+ /* NOTREACHED */
+ }
+
+ no_target = stat(to_name = argv[argc - 1], &to_sb);
+ if (!no_target && S_ISDIR(to_sb.st_mode)) {
+ for (; *argv != to_name; ++argv) {
+ int rc = install(&This, *argv, to_name, fset, iflags | DIRECTORY);
+ if (rc)
+ return rc;
+ }
+ return EX_OK;
+ }
+
+ /* can't do file1 file2 directory/file */
+ if (argc != 2) {
+ warnx(pCtx, "wrong number or types of arguments");
+ return usage(pCtx, 1);
+ }
+
+ if (!no_target) {
+ if (stat(*argv, &from_sb))
+ return err(pCtx, EX_OSERR, "%s", *argv);
+ if (!S_ISREG(to_sb.st_mode)) {
+ errno = EFTYPE;
+ return err(pCtx, EX_OSERR, "%s", to_name);
+ }
+ if (to_sb.st_dev == from_sb.st_dev &&
+ to_sb.st_dev != 0 &&
+ to_sb.st_ino == from_sb.st_ino &&
+ to_sb.st_ino != 0 &&
+ !This.hard_link_files_when_possible)
+ return errx(pCtx, EX_USAGE,
+ "%s and %s are the same file", *argv, to_name);
+ }
+ return install(&This, *argv, to_name, fset, iflags);
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_install", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_install(argc, argv, envp, &Ctx);
+}
+#endif
+
+static u_long
+numeric_id(PINSTALLINSTANCE pThis, const char *name, const char *type)
+{
+ u_long val;
+ char *ep;
+
+ /*
+ * XXX
+ * We know that uid_t's and gid_t's are unsigned longs.
+ */
+ errno = 0;
+ val = strtoul(name, &ep, 10);
+ if (errno)
+ return err(pThis->pCtx, -1, "%s", name);
+ if (*ep != '\0')
+ return errx(pThis->pCtx, -1, "unknown %s %s", type, name);
+ return (val);
+}
+
+/*
+ * install --
+ * build a path name and install the file
+ */
+static int
+install(PINSTALLINSTANCE pThis, const char *from_name, const char *to_name, u_long fset, u_int flags)
+{
+ struct stat from_sb, temp_sb, to_sb;
+ struct timeval tvb[2];
+ int devnull, files_match, from_fd, serrno, target;
+ int tempcopy, temp_fd, to_fd;
+ char backup[MAXPATHLEN], *p, pathbuf[MAXPATHLEN], tempfile[MAXPATHLEN];
+ int rc = EX_OK;
+
+ files_match = 0;
+ from_fd = -1;
+ to_fd = -1;
+ temp_fd = -1;
+
+ /* If try to install NULL file to a directory, fails. */
+ if (flags & DIRECTORY
+#if defined(__EMX__) || defined(_MSC_VER)
+ || ( stricmp(from_name, _PATH_DEVNULL)
+ && stricmp(from_name, "nul")
+# ifdef __EMX__
+ && stricmp(from_name, "/dev/nul")
+# endif
+ )
+#else
+ || strcmp(from_name, _PATH_DEVNULL)
+#endif
+ ) {
+ if (stat(from_name, &from_sb))
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+ if (!S_ISREG(from_sb.st_mode)) {
+ errno = EFTYPE;
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+ }
+ /* Build the target path. */
+ if (flags & DIRECTORY) {
+ (void)snprintf(pathbuf, sizeof(pathbuf), "%s/%s",
+ to_name,
+ (p = last_slash(from_name)) ? ++p : from_name);
+ to_name = pathbuf;
+ }
+ devnull = 0;
+ } else {
+ devnull = 1;
+ }
+
+ target = stat(to_name, &to_sb) == 0;
+
+ /* Only install to regular files. */
+ if (target && !S_ISREG(to_sb.st_mode)) {
+ errno = EFTYPE;
+ warn(pThis->pCtx, "%s", to_name);
+ return EX_OK;
+ }
+
+ /* Only copy safe if the target exists. */
+ tempcopy = pThis->safecopy && target;
+
+ /* Try hard linking if wanted and possible. */
+ if (pThis->hard_link_files_when_possible)
+ {
+#ifdef KBUILD_OS_OS2
+ const char *why_not = "not supported on OS/2";
+#else
+ const char *why_not = NULL;
+ if (devnull) {
+ why_not = "/dev/null";
+ } else if (pThis->dostrip) {
+ why_not = "strip (-s)";
+ } else if (pThis->docompare) {
+ why_not = "compare (-C)";
+ } else if (pThis->dobackup) {
+ why_not = "backup (-b/-B)";
+ } else if (pThis->safecopy) {
+ why_not = "safe copy (-S)";
+ } else if (lstat(from_name, &temp_sb)) {
+ why_not = "lstat on source failed";
+ } else if (S_ISLNK(temp_sb.st_mode)) {
+ why_not = "symlink";
+ } else if (!S_ISREG(temp_sb.st_mode)) {
+ why_not = "not regular file";
+# if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ } else if ((pThis->mode & S_IWUSR) != (from_sb.st_mode & S_IWUSR)) {
+# else
+ } else if (pThis->mode != (from_sb.st_mode & ALLPERMS)) {
+# endif
+ if ( pThis->verbose_hard_link_mode_mismatch
+ || pThis->verbose_hard_link_refusal)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "install: warning: Not hard linking, mode differs: 0%03o, desires 0%03o\n"
+ "install: src path '%s'\n"
+ "install: dst path '%s'\n",
+ (from_sb.st_mode & ALLPERMS), pThis->mode, from_name, to_name);
+ why_not = NULL;
+ } else if (pThis->uid != (uid_t)-1 && pThis->uid != from_sb.st_uid) {
+ why_not = "uid mismatch";
+ } else if (pThis->gid != (gid_t)-1 && pThis->gid != from_sb.st_gid) {
+ why_not = "gid mismatch";
+ } else if (pThis->dos2unix > 0 && needs_dos2unix_conversion(from_name)) {
+ why_not = "dos2unix";
+ } else if (pThis->dos2unix < 0 && needs_unix2dos_conversion(from_name)) {
+ why_not = "unix2dos";
+ } else {
+ int rcLink = link(from_name, to_name);
+ if (rcLink != 0 && errno == EEXIST) {
+ unlink(to_name);
+ rcLink = link(from_name, to_name);
+ }
+ if (rcLink == 0) {
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "install: %s -> %s (hardlinked)\n", from_name, to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: hard linking '%s' to '%s' failed: %s\n",
+ to_name, from_name, strerror(errno));
+ why_not = NULL;
+ }
+#endif
+ if (why_not && pThis->verbose_hard_link_refusal)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: not hard linking '%s' to '%s' because: %s\n",
+ to_name, from_name, why_not);
+
+ /* Can't hard link or we failed, continue as nothing happend. */
+ }
+
+ if (!devnull && (from_fd = open(from_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0)
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+
+ /* If we don't strip, we can compare first. */
+ if (pThis->docompare && !pThis->dostrip && target) {
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ if (devnull)
+ files_match = to_sb.st_size == 0;
+ else
+ files_match = !compare(from_fd, (size_t)from_sb.st_size,
+ to_fd, (size_t)to_sb.st_size);
+
+ /* Close "to" file unless we match. */
+ if (!files_match) {
+ (void)close(to_fd);
+ to_fd = -1;
+ }
+ }
+
+ if (!files_match) {
+ if (tempcopy) {
+ to_fd = create_tempfile(to_name, tempfile,
+ sizeof(tempfile));
+ if (to_fd < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
+ goto l_done;
+ }
+ } else {
+ if ((to_fd = create_newfile(pThis, to_name, target, &to_sb)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
+ }
+ if (!devnull) {
+ rc = copy(pThis, from_fd, from_name, &to_fd, tempcopy ? tempfile : to_name);
+ if (rc)
+ goto l_done;
+ }
+ }
+
+ if (pThis->dostrip) {
+#if defined(__EMX__) || defined(_MSC_VER)
+ /* close before we strip. */
+ close(to_fd);
+ to_fd = -1;
+#endif
+ rc = strip(pThis, tempcopy ? tempfile : to_name);
+ if (rc)
+ goto l_done;
+
+ /*
+ * Re-open our fd on the target, in case we used a strip
+ * that does not work in-place -- like GNU binutils strip.
+ */
+#if !defined(__EMX__) && !defined(_MSC_VER)
+ close(to_fd);
+#endif
+ to_fd = open(tempcopy ? tempfile : to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0);
+ if (to_fd < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "stripping %s", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * Compare the stripped temp file with the target.
+ */
+ if (pThis->docompare && pThis->dostrip && target) {
+ temp_fd = to_fd;
+
+ /* Re-open to_fd using the real target name. */
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+
+ if (fstat(temp_fd, &temp_sb)) {
+ serrno = errno;
+ (void)unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s", tempfile);
+ goto l_done;
+ }
+
+ if (compare(temp_fd, (size_t)temp_sb.st_size,
+ to_fd, (size_t)to_sb.st_size) == 0) {
+ /*
+ * If target has more than one link we need to
+ * replace it in order to snap the extra links.
+ * Need to preserve target file times, though.
+ */
+#if !defined(_MSC_VER) && !defined(__EMX__)
+ if (to_sb.st_nlink != 1) {
+ tvb[0].tv_sec = to_sb.st_atime;
+ tvb[0].tv_usec = 0;
+ tvb[1].tv_sec = to_sb.st_mtime;
+ tvb[1].tv_usec = 0;
+ (void)utimes(tempfile, tvb);
+ } else
+#endif
+ {
+
+ files_match = 1;
+ (void)unlink(tempfile);
+ }
+ (void) close(temp_fd);
+ temp_fd = -1;
+ }
+ }
+
+ /*
+ * Move the new file into place if doing a safe copy
+ * and the files are different (or just not compared).
+ */
+ if (tempcopy && !files_match) {
+#ifdef UF_IMMUTABLE
+ /* Try to turn off the immutable bits. */
+ if (to_sb.st_flags & NOCHANGEBITS)
+ (void)chflags(to_name, to_sb.st_flags & ~NOCHANGEBITS);
+#endif
+ if (pThis->dobackup) {
+ if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", to_name, pThis->suffix)
+ != strlen(to_name) + strlen(pThis->suffix)) {
+ unlink(tempfile);
+ rc = errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long",
+ to_name);
+ goto l_done;
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", to_name, backup);
+ if (rename(to_name, backup) < 0) {
+ serrno = errno;
+ unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", to_name,
+ backup);
+ goto l_done;
+ }
+ }
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", from_name, to_name);
+ if (rename(tempfile, to_name) < 0) {
+ serrno = errno;
+ unlink(tempfile);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "rename: %s to %s", tempfile, to_name);
+ goto l_done;
+ }
+
+ /* Re-open to_fd so we aren't hosed by the rename(2). */
+ (void) close(to_fd);
+ if ((to_fd = open(to_name, O_RDONLY | O_BINARY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * Preserve the timestamp of the source file if necessary.
+ */
+ if (pThis->dopreserve && !files_match && !devnull) {
+ tvb[0].tv_sec = from_sb.st_atime;
+ tvb[0].tv_usec = 0;
+ tvb[1].tv_sec = from_sb.st_mtime;
+ tvb[1].tv_usec = 0;
+ (void)utimes(to_name, tvb);
+ }
+
+ if (fstat(to_fd, &to_sb) == -1) {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s", to_name);
+ goto l_done;
+ }
+
+ /*
+ * Set owner, group, mode for target; do the chown first,
+ * chown may lose the setuid bits.
+ */
+#ifdef UF_IMMUTABLE
+ if ((pThis->gid != (gid_t)-1 && pThis->gid != to_sb.st_gid) ||
+ (pThis->uid != (uid_t)-1 && pThis->uid != to_sb.st_uid) ||
+ (pThis->mode != (to_sb.st_mode & ALLPERMS))) {
+ /* Try to turn off the immutable bits. */
+ if (to_sb.st_flags & NOCHANGEBITS)
+ (void)fchflags(to_fd, to_sb.st_flags & ~NOCHANGEBITS);
+ }
+#endif
+
+ if ((pThis->gid != (gid_t)-1 && pThis->gid != to_sb.st_gid) ||
+ (pThis->uid != (uid_t)-1 && pThis->uid != to_sb.st_uid))
+ if (fchown(to_fd, pThis->uid, pThis->gid) == -1) {
+ if (errno == EPERM && pThis->ignore_perm_errors) {
+ warn(pThis->pCtx, "%s: ignoring chown uid=%d gid=%d failure",
+ to_name, (int)pThis->uid, (int)pThis->gid);
+ } else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR,"%s: chown/chgrp", to_name);
+ goto l_done;
+ }
+ }
+
+ if (pThis->mode != (to_sb.st_mode & ALLPERMS))
+ if (fchmod(to_fd, pThis->mode)) {
+ serrno = errno;
+ if (serrno == EPERM && pThis->ignore_perm_errors) {
+ fchmod(to_fd, pThis->mode & (ALLPERMS & ~0007000));
+ errno = errno;
+ warn(pThis->pCtx, "%s: ignoring chmod 0%o failure", to_name, (int)(pThis->mode & ALLPERMS));
+ } else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s: chmod", to_name);
+ goto l_done;
+ }
+ }
+
+ /*
+ * If provided a set of flags, set them, otherwise, preserve the
+ * flags, except for the dump flag.
+ * NFS does not support flags. Ignore EOPNOTSUPP flags if we're just
+ * trying to turn off UF_NODUMP. If we're trying to set real flags,
+ * then warn if the the fs doesn't support it, otherwise fail.
+ */
+#ifdef UF_IMMUTABLE
+ if ( !devnull
+ && (flags & SETFLAGS || (from_sb.st_flags & ~UF_NODUMP) != to_sb.st_flags)
+ && fchflags(to_fd, flags & SETFLAGS ? fset : from_sb.st_flags & ~UF_NODUMP)) {
+ if (flags & SETFLAGS) {
+ if (errno == EOPNOTSUPP)
+ warn(pThis->pCtx, "%s: chflags", to_name);
+ else {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ rc = err(pThis->pCtx, EX_OSERR, "%s: chflags", to_name);
+ goto l_done;
+ }
+ }
+ }
+#endif
+
+l_done:
+ if (to_fd >= 0)
+ (void)close(to_fd);
+ if (temp_fd >= 0)
+ (void)close(temp_fd);
+ if (from_fd >= 0 && !devnull)
+ (void)close(from_fd);
+ return rc;
+}
+
+/*
+ * compare --
+ * compare two files; non-zero means files differ
+ */
+static int
+compare(int from_fd, size_t from_len, int to_fd, size_t to_len)
+{
+ char buf1[MAXBSIZE];
+ char buf2[MAXBSIZE];
+ int n1, n2;
+ int rv;
+
+ if (from_len != to_len)
+ return 1;
+
+ if (from_len <= MAX_CMP_SIZE) {
+ rv = 0;
+ lseek(from_fd, 0, SEEK_SET);
+ lseek(to_fd, 0, SEEK_SET);
+ while (rv == 0) {
+ n1 = read(from_fd, buf1, sizeof(buf1));
+ if (n1 == 0)
+ break; /* EOF */
+ else if (n1 > 0) {
+ n2 = read(to_fd, buf2, n1);
+ if (n2 == n1)
+ rv = memcmp(buf1, buf2, n1);
+ else
+ rv = 1; /* out of sync */
+ } else
+ rv = 1; /* read failure */
+ }
+ lseek(from_fd, 0, SEEK_SET);
+ lseek(to_fd, 0, SEEK_SET);
+ } else
+ rv = 1; /* don't bother in this case */
+
+ return rv;
+}
+
+/*
+ * create_tempfile --
+ * create a temporary file based on path and open it
+ */
+int
+create_tempfile(const char *path, char *temp, size_t tsize)
+{
+ static char s_szTemplate[] = "INS@XXXXXX";
+ const char *p = last_slash(path);
+ if (p) {
+ size_t cchDir = ++p - path;
+ if (cchDir + sizeof(s_szTemplate) <= tsize) {
+ memcpy(temp, path, cchDir);
+ memcpy(&temp[cchDir], s_szTemplate, sizeof(s_szTemplate));
+ } else
+ return EOVERFLOW;
+ } else if (tsize >= sizeof(s_szTemplate))
+ memcpy(temp, s_szTemplate, sizeof(s_szTemplate));
+ else
+ return EOVERFLOW;
+
+ return (mkstemp(temp));
+}
+
+/*
+ * create_newfile --
+ * create a new file, overwriting an existing one if necessary
+ */
+int
+create_newfile(PINSTALLINSTANCE pThis, const char *path, int target, struct stat *sbp)
+{
+ char backup[MAXPATHLEN];
+ int saved_errno = 0;
+ int newfd;
+
+ if (target) {
+ /*
+ * Unlink now... avoid ETXTBSY errors later. Try to turn
+ * off the append/immutable bits -- if we fail, go ahead,
+ * it might work.
+ */
+#ifdef UF_IMMUTABLE
+ if (sbp->st_flags & NOCHANGEBITS)
+ (void)chflags(path, sbp->st_flags & ~NOCHANGEBITS);
+#endif
+
+ if (pThis->dobackup) {
+ if ( (size_t)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix)
+ != strlen(path) + strlen(pThis->suffix)) {
+ errx(pThis->pCtx, EX_OSERR, "%s: backup filename too long", path);
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ (void)snprintf(backup, MAXPATHLEN, "%s%s", path, pThis->suffix);
+ if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: %s -> %s\n", path, backup);
+ if (rename(path, backup) < 0) {
+ err(pThis->pCtx, EX_OSERR, "rename: %s to %s", path, backup);
+ return -1;
+ }
+ } else
+ if (unlink(path) < 0)
+ saved_errno = errno;
+ }
+
+ newfd = open(path, O_CREAT | O_RDWR | O_TRUNC | O_BINARY | KMK_OPEN_NO_INHERIT, S_IRUSR | S_IWUSR);
+ if (newfd < 0 && saved_errno != 0)
+ errno = saved_errno;
+ return newfd;
+}
+
+/*
+ * Write error handler.
+ */
+static int write_error(PINSTALLINSTANCE pThis, int *ptr_to_fd, const char *to_name, int nw)
+{
+ int serrno = errno;
+ (void)close(*ptr_to_fd);
+ *ptr_to_fd = -1;
+ (void)unlink(to_name);
+ errno = nw > 0 ? EIO : serrno;
+ return err(pThis->pCtx, EX_OSERR, "%s", to_name);
+}
+
+/*
+ * Read error handler.
+ */
+static int read_error(PINSTALLINSTANCE pThis, const char *from_name, int *ptr_to_fd, const char *to_name)
+{
+ int serrno = errno;
+ (void)close(*ptr_to_fd);
+ *ptr_to_fd = -1;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_OSERR, "%s", from_name);
+}
+
+/*
+ * copy --
+ * copy from one file to another
+ */
+static int
+copy(PINSTALLINSTANCE pThis, int from_fd, const char *from_name, int *ptr_to_fd, const char *to_name)
+{
+ KBOOL fPendingCr = K_FALSE;
+ KSIZE cchDst;
+ int nr, nw;
+ char buf[MAXBSIZE];
+ int to_fd = *ptr_to_fd;
+
+ /* Rewind file descriptors. */
+ if (lseek(from_fd, (off_t)0, SEEK_SET) == (off_t)-1)
+ return err(pThis->pCtx, EX_OSERR, "lseek: %s", from_name);
+ if (lseek(to_fd, (off_t)0, SEEK_SET) == (off_t)-1)
+ return err(pThis->pCtx, EX_OSERR, "lseek: %s", to_name);
+
+ if (pThis->dos2unix == 0) {
+ /*
+ * Copy bytes, no conversion.
+ */
+ while ((nr = read(from_fd, buf, sizeof(buf))) > 0)
+ if ((nw = write(to_fd, buf, nr)) != nr)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ } else if (pThis->dos2unix > 0) {
+ /*
+ * CRLF -> LF is a reduction, so we can work with full buffers.
+ */
+ while ((nr = read(from_fd, buf, sizeof(buf))) > 0) {
+ if ( fPendingCr
+ && buf[0] != '\n'
+ && (nw = write(to_fd, "\r", 1)) != 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ fPendingCr = dos2unix_convert_to_unix(buf, nr, buf, &cchDst);
+
+ nw = write(to_fd, buf, cchDst);
+ if (nw != (int)cchDst)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ }
+ } else {
+ /*
+ * LF -> CRLF is an expansion, so we work with half buffers, reading
+ * into the upper half of the buffer and expanding into the full buffer.
+ * The conversion will never expand to more than the double size.
+ *
+ * Note! We do not convert valid CRLF line endings. This gives us
+ * valid DOS text, but no round-trip conversion.
+ */
+ char * const pchSrc = &buf[sizeof(buf) / 2];
+ while ((nr = read(from_fd, pchSrc, sizeof(buf) / 2)) > 0) {
+ if ( fPendingCr
+ && pchSrc[0] != '\n'
+ && (nw = write(to_fd, "\r", 1))!= 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ fPendingCr = dos2unix_convert_to_dos(pchSrc, nr, buf, &cchDst);
+
+ nw = write(to_fd, buf, cchDst);
+ if (nw != (int)cchDst)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+ }
+ }
+
+ /* Check for read error. */
+ if (nr != 0)
+ return read_error(pThis, from_name, ptr_to_fd, to_name);
+
+ /* When converting, we might have a pending final CR to write. */
+ if ( fPendingCr
+ && (nw = write(to_fd, "\r", 1))!= 1)
+ return write_error(pThis, ptr_to_fd, to_name, nw);
+
+ return EX_OK;
+}
+
+/*
+ * strip --
+ * use strip(1) to strip the target file
+ */
+static int
+strip(PINSTALLINSTANCE pThis, const char *to_name)
+{
+#if defined(__EMX__) || defined(_MSC_VER)
+ const char *stripbin = getenv("STRIPBIN");
+ if (stripbin == NULL)
+ stripbin = "strip";
+ (void)pThis;
+ return spawnlp(P_WAIT, stripbin, stripbin, to_name, NULL);
+#else
+ const char *stripbin;
+ int serrno, status;
+ pid_t pid;
+
+ pid = fork();
+ switch (pid) {
+ case -1:
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_TEMPFAIL, "fork");
+ case 0:
+ stripbin = getenv("STRIPBIN");
+ if (stripbin == NULL)
+ stripbin = "strip";
+ execlp(stripbin, stripbin, to_name, (char *)NULL);
+ err(pThis->pCtx, EX_OSERR, "exec(%s)", stripbin);
+ exit(EX_OSERR);
+ default:
+ if (waitpid(pid, &status, 0) == -1 || status) {
+ serrno = errno;
+ (void)unlink(to_name);
+ errno = serrno;
+ return err(pThis->pCtx, EX_SOFTWARE, "waitpid");
+ /* NOTREACHED */
+ }
+ }
+ return 0;
+#endif
+}
+
+/*
+ * install_dir --
+ * build directory heirarchy
+ */
+static int
+install_dir(PINSTALLINSTANCE pThis, char *path)
+{
+ char *p;
+ struct stat sb;
+ int ch;
+
+ for (p = path;; ++p)
+ if ( !*p
+ || ( p != path
+ && IS_SLASH(*p)
+#if defined(_MSC_VER) /* stat("C:") fails (VC++ v10). Just skip it since it's unnecessary. */
+ && (p - path != 2 || p[-1] != ':')
+#endif
+ )) {
+ ch = *p;
+ *p = '\0';
+ if (stat(path, &sb)) {
+ if (errno != ENOENT || mkdir(path, 0755) < 0) {
+ return err(pThis->pCtx, EX_OSERR, "mkdir %s", path);
+ /* NOTREACHED */
+ } else if (pThis->verbose)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "install: mkdir %s\n", path);
+ } else if (!S_ISDIR(sb.st_mode))
+ return errx(pThis->pCtx, EX_OSERR, "%s exists but is not a directory", path);
+ if (!(*p = ch))
+ break;
+ }
+
+ if ((pThis->gid != (gid_t)-1 || pThis->uid != (uid_t)-1) && chown(path, pThis->uid, pThis->gid))
+ warn(pThis->pCtx, "chown %u:%u %s", pThis->uid, pThis->gid, path);
+ if (chmod(path, pThis->mode))
+ warn(pThis->pCtx, "chmod %o %s", pThis->mode, path);
+ return EX_OK;
+}
+
+/*
+ * usage --
+ * print a usage message and die
+ */
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+"usage: %s [-bCcpSsv] [--[no-]hard-link-files-when-possible]\n"
+" [--verbose-hard-link-refusal] [--verbose-hard-link-mode-mismatch]\n"
+" [--[no-]ignore-perm-errors] [-B suffix] [-f flags] [-g group]\n"
+" [-m mode] [-o owner] [--dos2unix|--unix2dos] file1 file2\n"
+" or: %s [-bCcpSsv] [--[no-]ignore-perm-errors] [-B suffix] [-f flags]\n"
+" [-g group] [-m mode] [-o owner] file1 ... fileN directory\n"
+" or: %s -d [-v] [-g group] [-m mode] [-o owner] directory ...\n"
+" or: %s --help\n"
+" or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
+
+/* figures out where the last slash or colon is. */
+static char *
+last_slash(const char *path)
+{
+#if defined(__WIN32__) || defined(__WIN64__) || defined(__OS2__)
+ char *p = (char *)strrchr(path, '/');
+ if (p)
+ {
+ char *p2 = strrchr(p, '\\');
+ if (p2)
+ p = p2;
+ }
+ else
+ {
+ p = (char *)strrchr(path, '\\');
+ if (!p && isalpha(path[0]) && path[1] == ':')
+ p = (char *)&path[1];
+ }
+ return p;
+#else
+ return strrchr(path, '/');
+#endif
+}
+
+/**
+ * Checks if @a pszFilename actually needs dos2unix conversion.
+ *
+ * @returns boolean.
+ * @param pszFilename The name of the file to check.
+ */
+static KBOOL needs_dos2unix_conversion(const char *pszFilename)
+{
+ KU32 fStyle = 0;
+ int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
+ return iErr != 0
+ || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_UNIX;
+}
+
+/**
+ * Checks if @a pszFilename actually needs unix2dos conversion.
+ *
+ * @returns boolean.
+ * @param pszFilename The name of the file to check.
+ */
+static KBOOL needs_unix2dos_conversion(const char *pszFilename)
+{
+ KU32 fStyle = 0;
+ int iErr = dos2unix_analyze_file(pszFilename, &fStyle, NULL, NULL);
+ return iErr != 0
+ || (fStyle & (DOS2UNIX_STYLE_MASK | DOS2UNIX_F_BINARY)) != DOS2UNIX_STYLE_DOS;
+}
+
diff --git a/src/kmk/kmkbuiltin/kDepIDB.c b/src/kmk/kmkbuiltin/kDepIDB.c
new file mode 100644
index 0000000..24cadc1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kDepIDB.c
@@ -0,0 +1,860 @@
+/* $Id: kDepIDB.c 3315 2020-03-31 01:12:19Z bird $ */
+/** @file
+ * kDepIDB - Extract dependency information from a MS Visual C++ .idb file.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if !defined(_MSC_VER)
+# include <unistd.h>
+#else
+# include <io.h>
+#endif
+#include "k/kDefs.h"
+#include "k/kTypes.h"
+#include "kDep.h"
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/*#define DEBUG*/
+#ifdef DEBUG
+# define dprintf(a) printf a
+# define dump(pb, cb, offBase) depHexDump(pb,cb,offBase)
+#else
+# define dprintf(a) do {} while (0)
+# define dump(pb, cb, offBase) do {} while (0)
+#endif
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct KDEPIDBGLOBALS
+{
+ PKMKBUILTINCTX pCtx;
+ DEPGLOBALS Core;
+} KDEPIDBGLOBALS;
+typedef KDEPIDBGLOBALS *PKDEPIDBGLOBALS;
+
+
+/**
+ * Scans a stream (chunk of data really) for dependencies.
+ *
+ * @returns 0 on success.
+ * @returns !0 on failure.
+ * @param pThis The kDepIDB instance.
+ * @param pbStream The stream bits.
+ * @param cbStream The size of the stream.
+ * @param pszPrefix The dependency prefix.
+ * @param cchPrefix The size of the prefix.
+ */
+static int ScanStream(PKDEPIDBGLOBALS pThis, KU8 *pbStream, size_t cbStream, const char *pszPrefix, size_t cchPrefix)
+{
+ const KU8 *pbCur = pbStream;
+ size_t cbLeft = cbStream;
+ register char chFirst = *pszPrefix;
+ while (cbLeft > cchPrefix + 2)
+ {
+ if ( *pbCur != chFirst
+ || memcmp(pbCur, pszPrefix, cchPrefix))
+ {
+ pbCur++;
+ cbLeft--;
+ }
+ else
+ {
+ size_t cchDep;
+ pbCur += cchPrefix;
+ cchDep = strlen((const char *)pbCur);
+ depAdd(&pThis->Core, (const char *) pbCur, cchDep);
+ dprintf(("%05x: '%s'\n", pbCur - pbStream, pbCur));
+
+ pbCur += cchDep;
+ cbLeft -= cchDep + cchPrefix;
+ }
+ }
+
+ return 0;
+}
+
+
+/*/////////////////////////////////////////////////////////////////////////////
+//
+//
+// P D B 7 . 0
+//
+//
+/////////////////////////////////////////////////////////////////////////////*/
+
+/** A PDB 7.0 Page number. */
+typedef KU32 PDB70PAGE;
+/** Pointer to a PDB 7.0 Page number. */
+typedef PDB70PAGE *PPDB70PAGE;
+
+/**
+ * A PDB 7.0 stream.
+ */
+typedef struct PDB70STREAM
+{
+ /** The size of the stream. */
+ KU32 cbStream;
+} PDB70STREAM, *PPDB70STREAM;
+
+
+/** The PDB 7.00 signature. */
+#define PDB_SIGNATURE_700 "Microsoft C/C++ MSF 7.00\r\n\x1A" "DS\0\0"
+/**
+ * The PDB 7.0 header.
+ */
+typedef struct PDB70HDR
+{
+ /** The signature string. */
+ KU8 szSignature[sizeof(PDB_SIGNATURE_700)];
+ /** The page size. */
+ KU32 cbPage;
+ /** The start page. */
+ PDB70PAGE iStartPage;
+ /** The number of pages in the file. */
+ PDB70PAGE cPages;
+ /** The root stream directory. */
+ KU32 cbRoot;
+ /** Unknown function, always 0. */
+ KU32 u32Reserved;
+ /** The page index of the root page table. */
+ PDB70PAGE iRootPages;
+} PDB70HDR, *PPDB70HDR;
+
+/**
+ * The PDB 7.0 root directory.
+ */
+typedef struct PDB70ROOT
+{
+ /** The number of streams */
+ KU32 cStreams;
+ /** Array of streams. */
+ PDB70STREAM aStreams[1];
+ /* KU32 aiPages[] */
+} PDB70ROOT, *PPDB70ROOT;
+
+/**
+ * The PDB 7.0 name stream (#1) header.
+ */
+typedef struct PDB70NAMES
+{
+ /** The structure version. */
+ KU32 Version;
+ /** Timestamp. */
+ KU32 TimeStamp;
+ /** Unknown. */
+ KU32 Unknown1;
+ /** GUID. */
+ KU32 u32Guid[4];
+ /** The size of the following name table. */
+ KU32 cbNames;
+ /** The name table. */
+ char szzNames[1];
+} PDB70NAMES, *PPDB70NAMES;
+
+/** The version / magic of the names structure. */
+#define PDB70NAMES_VERSION 20000404
+
+
+static int Pdb70ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cbFile)
+{
+ if (pHdr->cbPage * pHdr->cPages != cbFile)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iStartPage=%u cPages=%u.",
+ pHdr->iStartPage, pHdr->cPages);
+ if (pHdr->iRootPages >= pHdr->cPages && pHdr->iRootPages <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - iRootPages=%u cPage=%u.",
+ pHdr->iStartPage, pHdr->cPages);
+ return 0;
+}
+
+#ifdef DEBUG
+static size_t Pdb70Align(PPDB70HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return ((cb + pHdr->cbPage - 1) / pHdr->cbPage) * pHdr->cbPage;
+}
+#endif /* DEBUG */
+
+static size_t Pdb70Pages(PPDB70HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
+}
+
+static void *Pdb70AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, size_t cb, PPDB70PAGE paiPageMap)
+{
+ const size_t cbPage = pHdr->cbPage;
+ size_t cPages = Pdb70Pages(pHdr, cb);
+ KU8 *pbBuf = malloc(cPages * cbPage + 1);
+ if (pbBuf)
+ {
+ size_t iPage = 0;
+ while (iPage < cPages)
+ {
+ size_t off = paiPageMap[iPage];
+ if (off < pHdr->cPages)
+ {
+ off *= cbPage;
+ memcpy(pbBuf + iPage * cbPage, (KU8 *)pHdr + off, cbPage);
+ dump(pbBuf + iPage * cbPage, iPage + 1 < cPages ? cbPage : cb % cbPage, off);
+ }
+ else
+ {
+ warnx(pThis->pCtx, "warning: Invalid page index %u (max %u)!\n", (unsigned)off, pHdr->cPages);
+ memset(pbBuf + iPage * cbPage, 0, cbPage);
+ }
+
+ iPage++;
+ }
+ pbBuf[cPages * cbPage] = '\0';
+ }
+ else
+ {
+ errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * cbPage + 1));
+ return NULL;
+ }
+ return pbBuf;
+}
+
+static PPDB70ROOT Pdb70AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr)
+{
+ /*
+ * The tricky bit here is to find the right length. Really?
+ * (Todo: Check if we can just use the stream #0 size..)
+ */
+ PPDB70PAGE piPageMap = (KU32 *)((KU8 *)pHdr + pHdr->iRootPages * pHdr->cbPage);
+ PPDB70ROOT pRoot = Pdb70AllocAndRead(pThis, pHdr, pHdr->cbRoot, piPageMap);
+ if (pRoot)
+ {
+#if 1
+ /* This stuff is probably unnecessary: */
+ /* size = stream header + array of stream. */
+ size_t cb = K_OFFSETOF(PDB70ROOT, aStreams[pRoot->cStreams]);
+ free(pRoot);
+ pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap);
+ if (pRoot)
+ {
+ /* size += page tables. */
+ unsigned iStream = pRoot->cStreams;
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ cb += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB70PAGE);
+ free(pRoot);
+ pRoot = Pdb70AllocAndRead(pThis, pHdr, cb, piPageMap);
+ if (pRoot)
+ {
+ /* validate? */
+ return pRoot;
+ }
+ }
+#else
+ /* validate? */
+ return pRoot;
+#endif
+ }
+ return NULL;
+}
+
+static void *Pdb70AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB70HDR pHdr, PPDB70ROOT pRoot, unsigned iStream, size_t *pcbStream)
+{
+ const size_t cbStream = pRoot->aStreams[iStream].cbStream;
+ PPDB70PAGE paiPageMap;
+ if ( iStream >= pRoot->cStreams
+ || cbStream == ~(KU32)0)
+ {
+ errx(pThis->pCtx, 1, "Invalid stream %d", iStream);
+ return NULL;
+ }
+
+ paiPageMap = (PPDB70PAGE)&pRoot->aStreams[pRoot->cStreams];
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ paiPageMap += Pdb70Pages(pHdr, pRoot->aStreams[iStream].cbStream);
+
+ if (pcbStream)
+ *pcbStream = cbStream;
+ return Pdb70AllocAndRead(pThis, pHdr, cbStream, paiPageMap);
+}
+
+static int Pdb70Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile)
+{
+ PPDB70HDR pHdr = (PPDB70HDR)pbFile;
+ PPDB70ROOT pRoot;
+ PPDB70NAMES pNames;
+ size_t cbStream = 0;
+ unsigned fDone = 0;
+ unsigned iStream;
+ int rc = 0;
+ dprintf(("pdb70\n"));
+
+ /*
+ * Validate the header and read the root stream.
+ */
+ if (Pdb70ValidateHeader(pThis, pHdr, cbFile))
+ return 1;
+ pRoot = Pdb70AllocAndReadRoot(pThis, pHdr);
+ if (!pRoot)
+ return 1;
+
+ /*
+ * The names we want are usually all found in the 'Names' stream, that is #1.
+ */
+ dprintf(("Reading the names stream....\n"));
+ pNames = Pdb70AllocAndReadStream(pThis, pHdr, pRoot, 1, &cbStream);
+ if (pNames)
+ {
+ dprintf(("Names: Version=%u cbNames=%u (%#x)\n", pNames->Version, pNames->cbNames, pNames->cbNames));
+ if ( pNames->Version == PDB70NAMES_VERSION
+ && pNames->cbNames > 32
+ && pNames->cbNames + K_OFFSETOF(PDB70NAMES, szzNames) <= pRoot->aStreams[1].cbStream)
+ {
+ /*
+ * Iterate the names and add the /mr/inversedeps/ ones to the dependency list.
+ */
+ const char *psz = &pNames->szzNames[0];
+ size_t cb = pNames->cbNames;
+ size_t off = 0;
+ dprintf(("0x0000 #0: %6d bytes [root / toc]\n", pRoot->aStreams[0].cbStream));
+ for (iStream = 1; cb > 0; iStream++)
+ {
+ int fAdded = 0;
+ size_t cch = strlen(psz);
+ if ( cch >= sizeof("/mr/inversedeps/")
+ && !memcmp(psz, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1))
+ {
+ depAdd(&pThis->Core, psz + sizeof("/mr/inversedeps/") - 1, cch - (sizeof("/mr/inversedeps/") - 1));
+ fAdded = 1;
+ }
+ dprintf(("%#06x #%d: %6d bytes %s%s\n", off, iStream,
+ iStream < pRoot->cStreams ? pRoot->aStreams[iStream].cbStream : -1,
+ psz, fAdded ? " [dep]" : ""));
+ (void)fAdded;
+
+ /* next */
+ if (cch >= cb)
+ {
+ dprintf(("warning! cch=%d cb=%d\n", cch, cb));
+ cch = cb - 1;
+ }
+ cb -= cch + 1;
+ psz += cch + 1;
+ off += cch + 1;
+ }
+ rc = 0;
+ fDone = 1;
+ }
+ else
+ dprintf(("Unknown version or bad size: Version=%u cbNames=%d cbStream=%d\n",
+ pNames->Version, pNames->cbNames, cbStream));
+ free(pNames);
+ }
+
+ if (!fDone)
+ {
+ /*
+ * Iterate the streams in the root and scan their content for
+ * dependencies.
+ */
+ rc = 0;
+ for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
+ {
+ KU8 *pbStream;
+ if ( pRoot->aStreams[iStream].cbStream == ~(KU32)0
+ || !pRoot->aStreams[iStream].cbStream)
+ continue;
+ dprintf(("Stream #%d: %#x bytes (%#x aligned)\n", iStream, pRoot->aStreams[iStream].cbStream,
+ Pdb70Align(pHdr, pRoot->aStreams[iStream].cbStream)));
+ pbStream = (KU8 *)Pdb70AllocAndReadStream(pThis, pHdr, pRoot, iStream, &cbStream);
+ if (pbStream)
+ {
+ rc = ScanStream(pThis, pbStream, cbStream, "/mr/inversedeps/", sizeof("/mr/inversedeps/") - 1);
+ free(pbStream);
+ }
+ else
+ rc = 1;
+ }
+ }
+
+ free(pRoot);
+ return rc;
+}
+
+
+
+/*/////////////////////////////////////////////////////////////////////////////
+//
+//
+// P D B 2 . 0
+//
+//
+/////////////////////////////////////////////////////////////////////////////*/
+
+
+/** A PDB 2.0 Page number. */
+typedef KU16 PDB20PAGE;
+/** Pointer to a PDB 2.0 Page number. */
+typedef PDB20PAGE *PPDB20PAGE;
+
+/**
+ * A PDB 2.0 stream.
+ */
+typedef struct PDB20STREAM
+{
+ /** The size of the stream. */
+ KU32 cbStream;
+ /** Some unknown value. */
+ KU32 u32Unknown;
+} PDB20STREAM, *PPDB20STREAM;
+
+/** The PDB 2.00 signature. */
+#define PDB_SIGNATURE_200 "Microsoft C/C++ program database 2.00\r\n\x1A" "JG\0"
+/**
+ * The PDB 2.0 header.
+ */
+typedef struct PDB20HDR
+{
+ /** The signature string. */
+ KU8 szSignature[sizeof(PDB_SIGNATURE_200)];
+ /** The page size. */
+ KU32 cbPage;
+ /** The start page - whatever that is... */
+ PDB20PAGE iStartPage;
+ /** The number of pages in the file. */
+ PDB20PAGE cPages;
+ /** The root stream directory. */
+ PDB20STREAM RootStream;
+ /** The root page table. */
+ PDB20PAGE aiRootPageMap[1];
+} PDB20HDR, *PPDB20HDR;
+
+/**
+ * The PDB 2.0 root directory.
+ */
+typedef struct PDB20ROOT
+{
+ /** The number of streams */
+ KU16 cStreams;
+ /** Reserved or high part of cStreams. */
+ KU16 u16Reserved;
+ /** Array of streams. */
+ PDB20STREAM aStreams[1];
+} PDB20ROOT, *PPDB20ROOT;
+
+
+static int Pdb20ValidateHeader(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cbFile)
+{
+ if (pHdr->cbPage * pHdr->cPages != cbFile)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ if (pHdr->iStartPage >= pHdr->cPages && pHdr->iStartPage <= 0)
+ return errx(pThis->pCtx, 1, "Bad PDB 2.0 header - cbPage * cPages != cbFile.");
+ return 0;
+}
+
+static size_t Pdb20Pages(PPDB20HDR pHdr, size_t cb)
+{
+ if (cb == ~(KU32)0 || !cb)
+ return 0;
+ return (cb + pHdr->cbPage - 1) / pHdr->cbPage;
+}
+
+static void *Pdb20AllocAndRead(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, size_t cb, PPDB20PAGE paiPageMap)
+{
+ size_t cPages = Pdb20Pages(pHdr, cb);
+ KU8 *pbBuf = malloc(cPages * pHdr->cbPage + 1);
+ if (pbBuf)
+ {
+ size_t iPage = 0;
+ while (iPage < cPages)
+ {
+ size_t off = paiPageMap[iPage];
+ off *= pHdr->cbPage;
+ memcpy(pbBuf + iPage * pHdr->cbPage, (KU8 *)pHdr + off, pHdr->cbPage);
+ iPage++;
+ }
+ pbBuf[cPages * pHdr->cbPage] = '\0';
+ }
+ else
+ errx(pThis->pCtx, 1, "failed to allocate %lu bytes", (unsigned long)(cPages * pHdr->cbPage + 1));
+ return pbBuf;
+}
+
+static PPDB20ROOT Pdb20AllocAndReadRoot(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr)
+{
+ /*
+ * The tricky bit here is to find the right length.
+ * (Todo: Check if we can just use the stream size..)
+ */
+ PPDB20ROOT pRoot = Pdb20AllocAndRead(pThis, pHdr, sizeof(*pRoot), &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* size = stream header + array of stream. */
+ size_t cb = K_OFFSETOF(PDB20ROOT, aStreams[pRoot->cStreams]);
+ free(pRoot);
+ pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* size += page tables. */
+ unsigned iStream = pRoot->cStreams;
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ cb += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream) * sizeof(PDB20PAGE);
+ free(pRoot);
+ pRoot = Pdb20AllocAndRead(pThis, pHdr, cb, &pHdr->aiRootPageMap[0]);
+ if (pRoot)
+ {
+ /* validate? */
+ return pRoot;
+ }
+ }
+ }
+ return NULL;
+
+}
+
+static void *Pdb20AllocAndReadStream(PKDEPIDBGLOBALS pThis, PPDB20HDR pHdr, PPDB20ROOT pRoot, unsigned iStream, size_t *pcbStream)
+{
+ size_t cbStream = pRoot->aStreams[iStream].cbStream;
+ PPDB20PAGE paiPageMap;
+ if ( iStream >= pRoot->cStreams
+ || cbStream == ~(KU32)0)
+ {
+ errx(pThis->pCtx, 1, "Invalid stream %d", iStream);
+ return NULL;
+ }
+
+ paiPageMap = (PPDB20PAGE)&pRoot->aStreams[pRoot->cStreams];
+ while (iStream-- > 0)
+ if (pRoot->aStreams[iStream].cbStream != ~(KU32)0)
+ paiPageMap += Pdb20Pages(pHdr, pRoot->aStreams[iStream].cbStream);
+
+ if (pcbStream)
+ *pcbStream = cbStream;
+ return Pdb20AllocAndRead(pThis, pHdr, cbStream, paiPageMap);
+}
+
+static int Pdb20Process(PKDEPIDBGLOBALS pThis, KU8 *pbFile, size_t cbFile)
+{
+ PPDB20HDR pHdr = (PPDB20HDR)pbFile;
+ PPDB20ROOT pRoot;
+ unsigned iStream;
+ int rc = 0;
+
+ /*
+ * Validate the header and read the root stream.
+ */
+ if (Pdb20ValidateHeader(pThis, pHdr, cbFile))
+ return 1;
+ pRoot = Pdb20AllocAndReadRoot(pThis, pHdr);
+ if (!pRoot)
+ return 1;
+
+ /*
+ * Iterate the streams in the root and scan their content for
+ * dependencies.
+ */
+ rc = 0;
+ for (iStream = 0; iStream < pRoot->cStreams && !rc; iStream++)
+ {
+ KU8 *pbStream;
+ if (pRoot->aStreams[iStream].cbStream == ~(KU32)0)
+ continue;
+ pbStream = (KU8 *)Pdb20AllocAndReadStream(pThis, pHdr, pRoot, iStream, NULL);
+ if (pbStream)
+ {
+ rc = ScanStream(pThis, pbStream, pRoot->aStreams[iStream].cbStream, "/ipm/header/", sizeof("/ipm/header/") - 1);
+ free(pbStream);
+ }
+ else
+ rc = 1;
+ }
+
+ free(pRoot);
+ return rc;
+}
+
+
+/**
+ * Make an attempt at parsing a Visual C++ IDB file.
+ */
+static int ProcessIDB(PKDEPIDBGLOBALS pThis, FILE *pInput)
+{
+ size_t cbFile;
+ KU8 *pbFile;
+ void *pvOpaque;
+ int rc = 0;
+
+ /*
+ * Read the file into memory.
+ */
+ pbFile = (KU8 *)depReadFileIntoMemory(pInput, &cbFile, &pvOpaque);
+ if (!pbFile)
+ return 1;
+
+ /*
+ * Figure out which parser to use.
+ */
+ if (!memcmp(pbFile, PDB_SIGNATURE_700, sizeof(PDB_SIGNATURE_700)))
+ rc = Pdb70Process(pThis, pbFile, cbFile);
+ else if (!memcmp(pbFile, PDB_SIGNATURE_200, sizeof(PDB_SIGNATURE_200)))
+ rc = Pdb20Process(pThis, pbFile, cbFile);
+ else
+ rc = errx(pThis->pCtx, 1, "Doesn't recognize the header of the Visual C++ IDB file.");
+
+ depFreeFileMemory(pbFile, pvOpaque);
+ return rc;
+}
+
+
+static void kDepIDBUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s -o <output> -t <target> [-fqs] <vc idb-file>\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kDepIDB(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ KDEPIDBGLOBALS This;
+
+ /* Arguments. */
+ FILE *pOutput = NULL;
+ const char *pszOutput = NULL;
+ FILE *pInput = NULL;
+ const char *pszTarget = NULL;
+ int fStubs = 0;
+ int fFixCase = 0;
+ /* Argument parsing. */
+ int fInput = 0; /* set when we've found input argument. */
+ int fQuiet = 0;
+
+ /* Init the instance data. */
+ This.pCtx = pCtx;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kDepIDBUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *psz = &argv[i][1];
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "-quiet"))
+ psz = "q";
+ else if (!strcmp(psz, "-help"))
+ psz = "?";
+ else if (!strcmp(psz, "-version"))
+ psz = "V";
+ }
+
+ switch (*psz)
+ {
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ pszOutput = &argv[i][2];
+ if (pOutput)
+ return errx(pCtx, 2, "only one output file!");
+ if (!*pszOutput)
+ {
+ if (++i >= argc)
+ return errx(pCtx, 2, "The '-o' argument is missing the filename.");
+ pszOutput = argv[i];
+ }
+ if (pszOutput[0] == '-' && !pszOutput[1])
+ pOutput = stdout;
+ else
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ return err(pCtx, 1, "Failed to create output file '%s'", pszOutput);
+ break;
+ }
+
+ /*
+ * Target name.
+ */
+ case 't':
+ {
+ if (pszTarget)
+ return errx(pCtx, 2, "only one target!");
+ pszTarget = &argv[i][2];
+ if (!*pszTarget)
+ {
+ if (++i >= argc)
+ return errx(pCtx, 2, "The '-t' argument is missing the target name.");
+ pszTarget = argv[i];
+ }
+ break;
+ }
+
+ /*
+ * Fix case.
+ */
+ case 'f':
+ {
+ fFixCase = 1;
+ break;
+ }
+
+ /*
+ * Quiet.
+ */
+ case 'q':
+ {
+ fQuiet = 1;
+ break;
+ }
+
+ /*
+ * Generate stubs.
+ */
+ case 's':
+ {
+ fStubs = 1;
+ break;
+ }
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kDepIDBUsage(pCtx, 0);
+ return 0;
+ case 'V':
+ case 'v':
+ return kbuild_version(pCtx->pszProgName);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s.'", argv[i]);
+ kDepIDBUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ pInput = fopen(argv[i], "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pInput)
+ return err(pCtx, 1, "Failed to open input file '%s'", argv[i]);
+ fInput = 1;
+ }
+
+ /*
+ * End of the line?
+ */
+ if (fInput)
+ {
+ if (++i < argc)
+ return errx(pCtx, 2, "No arguments shall follow the input spec.");
+ break;
+ }
+ }
+
+ /*
+ * Got all we require?
+ */
+ if (!pInput)
+ return errx(pCtx, 2, "No input!");
+ if (!pOutput)
+ return errx(pCtx, 2, "No output!");
+ if (!pszTarget)
+ return errx(pCtx, 2, "No target!");
+
+ /*
+ * Do the parsing.
+ */
+ depInit(&This.Core);
+ i = ProcessIDB(&This, pInput);
+ fclose(pInput);
+
+ /*
+ * Write the dependecy file.
+ */
+ if (!i)
+ {
+ depOptimize(&This.Core, fFixCase, fQuiet, NULL /*pszIgnoredExt*/);
+ depPrintTargetWithDeps(&This.Core, pOutput, pszTarget, 1 /*fEscapeTarget*/);
+ if (fStubs)
+ depPrintStubs(&This.Core, pOutput);
+ }
+
+ /*
+ * Close the output, delete output on failure.
+ */
+ if (!i && ferror(pOutput))
+ i = errx(pCtx, 1, "Error writing to '%s'.", pszOutput);
+ fclose(pOutput);
+ if (i)
+ {
+ if (unlink(pszOutput))
+ warnx(pCtx, "warning: failed to remove output file '%s' on failure.", pszOutput);
+ }
+
+ depCleanup(&This.Core);
+ return i;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_kDepIDB", NULL };
+ return kmk_builtin_kDepIDB(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kDepObj.c b/src/kmk/kmkbuiltin/kDepObj.c
new file mode 100644
index 0000000..280f15f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kDepObj.c
@@ -0,0 +1,1250 @@
+/* $Id: kDepObj.c 3364 2020-06-08 19:29:42Z bird $ */
+/** @file
+ * kDepObj - Extract dependency information from an object file.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#define MSCFAKES_NO_WINDOWS_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdarg.h>
+#if !defined(_MSC_VER)
+# include <unistd.h>
+#else
+# include <io.h>
+typedef intptr_t ssize_t;
+#endif
+#include "k/kDefs.h"
+#include "k/kTypes.h"
+#include "k/kLdrFmts/pe.h"
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "kDep.h"
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#if 0
+# define dprintf(a) printf a
+# define dump(pb, cb, offBase) depHexDump(pb,cb,offBase)
+# define WITH_DPRINTF
+#else
+# define dprintf(a) do {} while (0)
+# define dump(pb, cb, offBase) do {} while (0)
+# undef WITH_DPRINTF
+#endif
+
+/** @name OMF defines
+ * @{ */
+#define KDEPOMF_THEADR 0x80
+#define KDEPOMF_LHEADR 0x82
+#define KDEPOMF_COMENT 0x88
+#define KDEPOMF_CMTCLS_DEPENDENCY 0xe9
+#define KDEPOMF_CMTCLS_DBGTYPE 0xa1
+#define KDEPOMF_LINNUM 0x94
+#define KDEPOMF_LINNUM32 0x95
+/** @} */
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+/** @name OMF Structures
+ * @{ */
+#pragma pack(1)
+/** OMF record header. */
+typedef struct KDEPOMFHDR
+{
+ /** The record type. */
+ KU8 bType;
+ /** The size of the record, excluding this header. */
+ KU16 cbRec;
+} KDEPOMFHDR;
+typedef KDEPOMFHDR *PKDEPOMFHDR;
+typedef const KDEPOMFHDR *PCKDEPOMFHDR;
+
+/** OMF string. */
+typedef struct KDEPOMFSTR
+{
+ KU8 cch;
+ char ach[1];
+} KDEPOMFSTR;
+typedef KDEPOMFSTR *PKDEPOMFSTR;
+typedef const KDEPOMFSTR *PCKDEPOMFSTR;
+
+/** THEADR/LHEADR. */
+typedef struct KDEPOMFTHEADR
+{
+ KDEPOMFHDR Hdr;
+ KDEPOMFSTR Name;
+} KDEPOMFTHEADR;
+typedef KDEPOMFTHEADR *PKDEPOMFTHEADR;
+typedef const KDEPOMFTHEADR *PCKDEPOMFTHEADR;
+
+/** Dependency File. */
+typedef struct KDEPOMFDEPFILE
+{
+ KDEPOMFHDR Hdr;
+ KU8 fType;
+ KU8 bClass;
+ KU16 wDosTime;
+ KU16 wDosDate;
+ KDEPOMFSTR Name;
+} KDEPOMFDEPFILE;
+typedef KDEPOMFDEPFILE *PKDEPOMFDEPFILE;
+typedef const KDEPOMFDEPFILE *PCKDEPOMFDEPFILE;
+
+#pragma pack()
+/** @} */
+
+
+/** @name COFF Structures
+ * @{ */
+#pragma pack(1)
+
+typedef struct KDEPCVSYMHDR
+{
+ /** The record size minus the size field. */
+ KU16 cb;
+ /** The record type. */
+ KU16 uType;
+} KDEPCVSYMHDR;
+typedef KDEPCVSYMHDR *PKDEPCVSYMHDR;
+typedef const KDEPCVSYMHDR *PCKDEPCVSYMHDR;
+
+/** @name Selection of KDEPCVSYMHDR::uType values.
+ * @{ */
+#define K_CV8_S_MSTOOL KU16_C(0x1116)
+/** @} */
+
+typedef struct KDEPCV8SYMHDR
+{
+ /** The record type. */
+ KU32 uType;
+ /** The record size minus the size field. */
+ KU32 cb;
+} KDEPCV8SYMHDR;
+typedef KDEPCV8SYMHDR *PKDEPCV8SYMHDR;
+typedef const KDEPCV8SYMHDR *PCKDEPCV8SYMHDR;
+
+/** @name Known KDEPCV8SYMHDR::uType Values.
+ * @{ */
+#define K_CV8_SYMBOL_INFO KU32_C(0x000000f1)
+#define K_CV8_LINE_NUMBERS KU32_C(0x000000f2)
+#define K_CV8_STRING_TABLE KU32_C(0x000000f3)
+#define K_CV8_SOURCE_FILES KU32_C(0x000000f4)
+#define K_CV8_COMDAT_XXXXX KU32_C(0x000000f5) /**< no idea about the format... */
+/** @} */
+
+#pragma pack()
+/** @} */
+
+/**
+ * Globals.
+ */
+typedef struct KDEPOBJGLOBALS
+{
+ /** The command execution context. */
+ PKMKBUILTINCTX pCtx;
+ /** Core instance. */
+ DEPGLOBALS Core;
+ /** The file. */
+ const char *pszFile;
+} KDEPOBJGLOBALS;
+/** Pointer to kDepObj globals. */
+typedef KDEPOBJGLOBALS *PKDEPOBJGLOBALS;
+
+
+
+/**
+ * Prints an error message.
+ *
+ * @returns rc.
+ * @param pThis kObjDep instance data.
+ * @param rc The return code, for making one line return statements.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ * @todo Promote this to kDep.c.
+ */
+static int kDepErr(PKDEPOBJGLOBALS pThis, int rc, const char *pszFormat, ...)
+{
+ char szMsg[2048];
+ va_list va;
+ va_start(va, pszFormat);
+ vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va);
+ va_end(va);
+ szMsg[sizeof(szMsg) - 1] = '\0';
+
+ if (pThis->pszFile)
+ warnx(pThis->pCtx, "%s: error: %s", pThis->pszFile, szMsg);
+ else
+ errx(pThis->pCtx, rc, "%s", szMsg);
+ return rc;
+}
+
+
+/**
+ * Gets an index from the data.
+ *
+ * @returns The index, KU16_MAX on buffer underflow.
+ * @param puData The current data stream position (in/out).
+ * @param pcbLeft Number of bytes left (in/out).
+ */
+static KU16 kDepObjOMFGetIndex(KPCUINT *puData, KU16 *pcbLeft)
+{
+ KU16 u16;
+
+ if (*pcbLeft >= 1 && *pcbLeft != KU16_MAX)
+ {
+ *pcbLeft -= 1;
+ u16 = *puData->pb++;
+ if (u16 & KU16_C(0x80))
+ {
+ if (*pcbLeft >= 1)
+ {
+ *pcbLeft -= 1;
+ u16 = ((u16 & KU16_C(0x7f)) << 8) | *puData->pb++;
+ }
+ else
+ u16 = KU16_MAX;
+ }
+ }
+ else
+ u16 = KU16_MAX;
+ return u16;
+}
+
+
+/**
+ * Parses the OMF file.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbFile The start of the file.
+ * @param cbFile The file size.
+ */
+int kDepObjOMFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ PCKDEPOMFHDR pHdr = (PCKDEPOMFHDR)pbFile;
+ KSIZE cbLeft = cbFile;
+ char uDbgType = 0; /* H or C */
+ KU8 uDbgVer = KU8_MAX;
+ KU32 iSrc = 0;
+ KU32 iMaybeSrc = 0;
+ KU8 uLinNumType = KU8_MAX;
+ KU16 cLinNums = 0;
+ KU32 cLinFiles = 0;
+ KU32 iLinFile = 0;
+
+ /*
+ * Iterate thru the records until we hit the end or an invalid one.
+ */
+ while ( cbLeft >= sizeof(*pHdr)
+ && cbLeft >= pHdr->cbRec + sizeof(*pHdr))
+ {
+ KPCUINT uData;
+ uData.pv = pHdr + 1;
+
+ /* process selected record types. */
+ dprintf(("%#07" KUPTR_PRI ": %#04x %#05x\n", (const KU8*)pHdr - pbFile, pHdr->bType, pHdr->cbRec));
+ switch (pHdr->bType)
+ {
+ /*
+ * The T/L Header contains the source name. When emitting CodeView 4
+ * and earlier (like masm and watcom does), all includes used by the
+ * line number tables have their own THEADR record.
+ */
+ case KDEPOMF_THEADR:
+ case KDEPOMF_LHEADR:
+ {
+ PCKDEPOMFTHEADR pTHeadr = (PCKDEPOMFTHEADR)pHdr;
+ if (1 + pTHeadr->Name.cch + 1 != pHdr->cbRec)
+ return kDepErr(pThis, 1, "%#07x - Bad %cHEADR record, length mismatch.",
+ (const KU8*)pHdr - pbFile, pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L');
+ if ( ( pTHeadr->Name.cch > 2
+ && pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == '.'
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'o'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'O'))
+ || ( pTHeadr->Name.cch > 4
+ && pTHeadr->Name.ach[pTHeadr->Name.cch - 4] == '.'
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'o'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 3] == 'O')
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'b'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 2] == 'B')
+ && ( pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'j'
+ || pTHeadr->Name.ach[pTHeadr->Name.cch - 1] == 'J'))
+ )
+ dprintf(("%cHEADR: %.*s [ignored]\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach));
+ else
+ {
+ dprintf(("%cHEADR: %.*s\n", pHdr->bType == KDEPOMF_THEADR ? 'T' : 'L', pTHeadr->Name.cch, pTHeadr->Name.ach));
+ depAdd(&pThis->Core, pTHeadr->Name.ach, pTHeadr->Name.cch);
+ iMaybeSrc++;
+ }
+ uLinNumType = KU8_MAX;
+ break;
+ }
+
+ case KDEPOMF_COMENT:
+ {
+ KU8 uClass;
+
+ if (pHdr->cbRec < 2 + 1)
+ return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, too small.", (const KU8*)pHdr - pbFile);
+ if (uData.pb[0] & 0x3f)
+ return kDepErr(pThis, 1, "%#07x - Bad COMMENT record, reserved flags set.", (const KU8*)pHdr - pbFile);
+ uClass = uData.pb[1];
+ uData.pb += 2;
+ switch (uClass)
+ {
+ /*
+ * Borland dependency file comment (famously used by wmake and Watcom).
+ */
+ case KDEPOMF_CMTCLS_DEPENDENCY:
+ {
+ PCKDEPOMFDEPFILE pDep = (PCKDEPOMFDEPFILE)pHdr;
+ if (K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1 != pHdr->cbRec + sizeof(*pHdr))
+ {
+ /* Empty record indicates the end of the dependency files,
+ no need to go on. */
+ if (pHdr->cbRec == 2 + 1)
+ return 0;
+ return kDepErr(pThis, 1, "%#07lx - Bad DEPENDENCY FILE record, length mismatch. (%u/%u)",
+ (long)((const KU8 *)pHdr - pbFile),
+ K_OFFSETOF(KDEPOMFDEPFILE, Name.ach[pDep->Name.cch]) + 1,
+ (unsigned)(pHdr->cbRec + sizeof(*pHdr)));
+ }
+ depAdd(&pThis->Core, pDep->Name.ach, pDep->Name.cch);
+ iSrc++;
+ break;
+ }
+
+ /*
+ * Pick up the debug type so we can parse the LINNUM records.
+ */
+ case KDEPOMF_CMTCLS_DBGTYPE:
+ if (pHdr->cbRec < 2 + 3 + 1)
+ break; /* ignore, Borland used this for something else apparently. */
+ if ( !(uData.pb[1] == 'C' && uData.pb[2] == 'V')
+ && !(uData.pb[1] == 'H' && uData.pb[2] == 'L'))
+ {
+ dprintf(("Unknown debug type: %c%c (%u)\n", uData.pb[1], uData.pb[2], uData.pb[0]));
+ break;
+ }
+ uDbgType = uData.pb[1];
+ uDbgVer = uData.pb[0];
+ dprintf(("Debug Type %s ver %u\n", uDbgType == 'H' ? "HLL" : "CodeView", uDbgVer));
+ break;
+
+ }
+ break; /* COMENT */
+ }
+
+ /*
+ * LINNUM + THEADR == sigar.
+ */
+ case KDEPOMF_LINNUM:
+ if (uDbgType == 'C')
+ iMaybeSrc |= KU32_C(0x80000000);
+ dprintf(("LINNUM:\n"));
+ break;
+
+ /*
+ * The HLL v4 and v6 file names table will include all files when present, which
+ * is perfect for generating dependencies.
+ */
+ case KDEPOMF_LINNUM32:
+ if ( uDbgType == 'H'
+ && uDbgVer >= 3
+ && uDbgVer <= 6)
+ {
+ /* skip two indexes (group & segment) */
+ KU16 cbRecLeft = pHdr->cbRec - 1;
+ KU16 uGrp = kDepObjOMFGetIndex(&uData, &cbRecLeft);
+ KU16 uSeg = kDepObjOMFGetIndex(&uData, &cbRecLeft);
+ if (uSeg == KU16_MAX)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record", (long)((const KU8 *)pHdr - pbFile));
+ K_NOREF(uGrp);
+
+ if (uLinNumType == KU8_MAX)
+ {
+#ifdef WITH_DPRINTF
+ static const char * const s_apsz[5] =
+ {
+ "source file", "listing file", "source & listing file", "file names table", "path table"
+ };
+#endif
+ KU16 uLine;
+ KU8 uReserved;
+ KU16 uSeg2;
+ KU32 cbLinNames;
+
+ if (cbRecLeft < 2+1+1+2+2+4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too short", (long)((const KU8 *)pHdr - pbFile));
+ cbRecLeft -= 2+1+1+2+2+4;
+ uLine = *uData.pu16++;
+ uLinNumType = *uData.pu8++;
+ uReserved = *uData.pu8++; K_NOREF(uReserved);
+ cLinNums = *uData.pu16++; K_NOREF(cLinNums);
+ uSeg2 = *uData.pu16++; K_NOREF(uSeg2);
+ cbLinNames = *uData.pu32++; K_NOREF(cbLinNames);
+
+ dprintf(("LINNUM32: uGrp=%#x uSeg=%#x uSeg2=%#x uLine=%#x (MBZ) uReserved=%#x\n",
+ uGrp, uSeg, uSeg2, uLine, uReserved));
+ dprintf(("LINNUM32: cLinNums=%#x (%u) cbLinNames=%#x (%u) uLinNumType=%#x (%s)\n",
+ cLinNums, cLinNums, cbLinNames, cbLinNames, uLinNumType,
+ uLinNumType < K_ELEMENTS(s_apsz) ? s_apsz[uLinNumType] : "??"));
+
+ if (uLine != 0)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, line %#x (MBZ)", (long)((const KU8 *)pHdr - pbFile), uLine);
+ cLinFiles = iLinFile = KU32_MAX;
+ if ( uLinNumType == 3 /* file names table */
+ || uLinNumType == 4 /* path table */)
+ cLinNums = 0; /* no line numbers */
+ else if (uLinNumType > 4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, type %#x unknown", (long)((const KU8 *)pHdr - pbFile), uLinNumType);
+ }
+ else
+ dprintf(("LINNUM32: uGrp=%#x uSeg=%#x\n", uGrp, uSeg));
+
+
+ /* Skip file numbers (we parse them to follow the stream correctly). */
+ if (uLinNumType != 3 && uLinNumType != 4)
+ {
+ static const KU16 s_acbTypes[3] = { 2+2+4, 4+4+4, 2+2+4+4+4 };
+ KU16 cbEntry = s_acbTypes[uLinNumType];
+
+ while (cLinNums && cbRecLeft)
+ {
+ if (cbRecLeft < cbEntry)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete line entry", (long)((const KU8 *)pHdr - pbFile));
+
+ switch (uLinNumType)
+ {
+ case 0: /* source file */
+ dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI " at %#010" KX32_PRI "\n",
+ uData.pu16[0], uData.pu16[1], uData.pu32[1]));
+ break;
+ case 1: /* listing file */
+ dprintf((" Line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n",
+ uData.pu32[0], uData.pu32[1], uData.pu32[2]));
+ break;
+ case 2: /* source & listing file */
+ dprintf((" Line %6" KU16_PRI " of file %2" KU16_PRI ", listning line %6" KU32_PRI ", statement %6" KU32_PRI " at %#010" KX32_PRI "\n",
+ uData.pu16[0], uData.pu16[1], uData.pu32[1], uData.pu32[2], uData.pu32[3]));
+ break;
+ }
+ uData.pb += cbEntry;
+ cbRecLeft -= cbEntry;
+ cLinNums--;
+ }
+
+ /* If at end of the announced line number entiries, we may find a file names table
+ here (who is actually emitting this?). */
+ if (!cLinNums)
+ {
+ uLinNumType = cbRecLeft > 0 ? 3 : KU8_MAX;
+ dprintf(("End-of-line-numbers; uLinNumType=%u cbRecLeft=%#x\n", uLinNumType, cbRecLeft));
+ }
+ }
+
+ if (uLinNumType == 3 || uLinNumType == 4)
+ {
+ /* Read the file/path table header (first time only). */
+ if (cLinFiles == KU32_MAX && iLinFile == KU32_MAX)
+ {
+ KU32 iFirstCol;
+ KU32 cCols;
+
+ if (cbRecLeft < 4+4+4)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, incomplete file/path table header", (long)((const KU8 *)pHdr - pbFile));
+ cbRecLeft -= 4+4+4;
+
+ iFirstCol = *uData.pu32++; K_NOREF(iFirstCol);
+ cCols = *uData.pu32++; K_NOREF(cCols);
+ cLinFiles = *uData.pu32++;
+ dprintf(("%s table header: cLinFiles=%#" KX32_PRI " (%" KU32_PRI ") iFirstCol=%" KU32_PRI " cCols=%" KU32_PRI"\n",
+ uLinNumType == 3 ? "file names" : "path", cLinFiles, cLinFiles, iFirstCol, cCols));
+ if (cLinFiles == KU32_MAX)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, too many file/path table entries.", (long)((const KU8 *)pHdr - pbFile));
+ iLinFile = 0;
+ }
+
+ /* Parse the file names / path table. */
+ while (iLinFile < cLinFiles && cbRecLeft)
+ {
+ KU16 cbName = *uData.pb++;
+ if (cbRecLeft < 1 + cbName)
+ return kDepErr(pThis, 1, "%#07lx - Bad LINNUM32 record, file/path table entry too long.", (long)((const KU8 *)pHdr - pbFile));
+ iLinFile++;
+ dprintf(("#%" KU32_PRI": %.*s\n", iLinFile, cbName, uData.pch));
+ if (uLinNumType == 3)
+ {
+ depAdd(&pThis->Core, uData.pch, cbName);
+ iSrc++;
+ }
+ cbRecLeft -= 1 + cbName;
+ uData.pb += cbName;
+ }
+
+ /* The end? */
+ if (iLinFile == cLinFiles)
+ {
+ uLinNumType = KU8_MAX;
+ dprintf(("End-of-file/path-table; cbRecLeft=%#x\n", cbRecLeft));
+ }
+ }
+ }
+ else
+ dprintf(("LINNUM32: Unknown or unsupported format\n"));
+ break;
+
+ }
+
+ /* advance */
+ cbLeft -= pHdr->cbRec + sizeof(*pHdr);
+ pHdr = (PCKDEPOMFHDR)((const KU8 *)(pHdr + 1) + pHdr->cbRec);
+ }
+
+ if (cbLeft)
+ return kDepErr(pThis, 1, "%#07x - Unexpected EOF. cbLeft=%#x", (const KU8*)pHdr - pbFile, cbLeft);
+
+ if (iSrc == 0 && iMaybeSrc <= 1)
+ {
+ dprintf(("kDepObjOMFParse: No cylindrical smoking thing: iSrc=0 iMaybeSrc=%#" KX32_PRI"\n", iMaybeSrc));
+ return 2;
+ }
+ dprintf(("kDepObjOMFParse: iSrc=%" KU32_PRI " iMaybeSrc=%#" KX32_PRI "\n", iSrc, iMaybeSrc));
+ return 0;
+}
+
+
+/**
+ * Checks if this file is an OMF file or not.
+ *
+ * @returns K_TRUE if it's OMF, K_FALSE otherwise.
+ *
+ * @param pb The start of the file.
+ * @param cb The file size.
+ */
+KBOOL kDepObjOMFTest(const KU8 *pbFile, KSIZE cbFile)
+{
+ PCKDEPOMFTHEADR pHdr = (PCKDEPOMFTHEADR)pbFile;
+
+ if (cbFile <= sizeof(*pHdr))
+ return K_FALSE;
+ if ( pHdr->Hdr.bType != KDEPOMF_THEADR
+ && pHdr->Hdr.bType != KDEPOMF_LHEADR)
+ return K_FALSE;
+ if (pHdr->Hdr.cbRec + sizeof(pHdr->Hdr) >= cbFile)
+ return K_FALSE;
+ if (pHdr->Hdr.cbRec != 1 + pHdr->Name.cch + 1)
+ return K_FALSE;
+
+ return K_TRUE;
+}
+
+
+/**
+ * Parses a CodeView 8 symbol section.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbSyms Pointer to the start of the symbol section.
+ * @param cbSyms Size of the symbol section.
+ */
+int kDepObjCOFFParseCV8SymbolSection(PKDEPOBJGLOBALS pThis, const KU8 *pbSyms, KU32 cbSyms)
+{
+ char const * pchStrTab = NULL;
+ KU32 cbStrTab = 0;
+ KPCUINT uSrcFiles = {0};
+ KU32 cbSrcFiles = 0;
+ KU32 off = 4;
+ KU32 iSrc = 0;
+
+ if (cbSyms < 16)
+ return 1;
+
+ /*
+ * The parsing loop.
+ */
+ while (off < cbSyms)
+ {
+ PCKDEPCV8SYMHDR pHdr = (PCKDEPCV8SYMHDR)(pbSyms + off);
+ KPCUINT uData;
+ KU32 cbData;
+ uData.pv = pHdr + 1;
+
+ if (off + sizeof(*pHdr) >= cbSyms)
+ {
+ kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbSyms=%#" KX32_PRI "",
+ off, cbSyms);
+ return 1; /* FIXME */
+ }
+
+ cbData = pHdr->cb;
+ if (off + cbData + sizeof(*pHdr) > cbSyms)
+ {
+ kDepErr(pThis, 1, "CV symbol table entry at %08" KX32_PRI " is too long; cbData=%#" KX32_PRI " cbSyms=%#" KX32_PRI,
+ off, cbData, cbSyms);
+ return 1; /* FIXME */
+ }
+
+ /* If the size is 0, assume it covers the rest of the section. VC++ 2003 has
+ been observed doing thing. */
+ if (!cbData)
+ cbData = cbSyms - off;
+
+ switch (pHdr->uType)
+ {
+ case K_CV8_SYMBOL_INFO:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Symbol Info\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_LINE_NUMBERS:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Line numbers\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_STRING_TABLE:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": String table\n", off, cbData));
+ if (pchStrTab)
+ warnx(pThis->pCtx, "%s: warning: Found yet another string table!", pThis->pszFile);
+ pchStrTab = uData.pch;
+ cbStrTab = cbData;
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_SOURCE_FILES:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Source files\n", off, cbData));
+ if (uSrcFiles.pb)
+ warnx(pThis->pCtx, "%s: warning: Found yet another source files table!", pThis->pszFile);
+ uSrcFiles = uData;
+ cbSrcFiles = cbData;
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ case K_CV8_COMDAT_XXXXX:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": 0xf5 Unknown COMDAT stuff\n", off, cbData));
+ /*dump(uData.pb, cbData, 0);*/
+ break;
+
+ default:
+ dprintf(("%06" KX32_PRI " %06" KX32_PRI ": Unknown type %#" KX32_PRI "\n",
+ off, cbData, pHdr->uType));
+ dump(uData.pb, cbData, 0);
+ break;
+ }
+
+ /* next */
+ cbData = (cbData + 3) & ~KU32_C(3);
+ off += cbData + sizeof(*pHdr);
+ }
+
+ /*
+ * Did we find any source files and strings?
+ */
+ if (!pchStrTab || !uSrcFiles.pv)
+ {
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: pchStrTab=%p uSrcFiles.pv=%p\n", pchStrTab, uSrcFiles.pv));
+ return 2;
+ }
+
+ /*
+ * Iterate the source file table.
+ */
+ off = 0;
+ while (off < cbSrcFiles)
+ {
+ KU32 offFile;
+ const char *pszFile;
+ KSIZE cchFile;
+ KU16 u16Type;
+ KPCUINT uSrc;
+ KU32 cbSrc;
+
+ /*
+ * Validate and parse the entry (variable length record are fun).
+ */
+ if (off + 8 > cbSrcFiles)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrcFiles=%#" KX32_PRI,
+ off, cbSrcFiles);
+ uSrc.pb = uSrcFiles.pb + off;
+ u16Type = uSrc.pu16[2];
+ switch (u16Type)
+ {
+ case 0x0110: cbSrc = 6 + 16 + 2; break; /* MD5 */
+ case 0x0214: cbSrc = 6 + 20 + 2; break; /* SHA1 */ /** @todo check this */
+ case 0x0320: cbSrc = 6 + 32 + 2; break; /* SHA-256 */
+ default: cbSrc = 6 + 0 + 2; break;
+ }
+ if (off + cbSrc > cbSrcFiles)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is too long; cbSrc=%#" KX32_PRI " cbSrcFiles=%#" KX32_PRI,
+ off, cbSrc, cbSrcFiles);
+
+ offFile = *uSrc.pu32;
+ if (offFile > cbStrTab)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " is out side the string table; offFile=%#" KX32_PRI " cbStrTab=%#" KX32_PRI,
+ off, offFile, cbStrTab);
+ pszFile = pchStrTab + offFile;
+ cchFile = strlen(pszFile);
+ if (cchFile == 0)
+ return kDepErr(pThis, 1, "CV source file entry at %08" KX32_PRI " has an empty file name; offFile=%#x" KX32_PRI,
+ off, offFile);
+
+ /*
+ * Display the result and add it to the dependency database.
+ */
+ depAdd(&pThis->Core, pszFile, cchFile);
+#ifdef WITH_DPRINTF
+ dprintf(("#%03" KU32_PRI ": ", iSrc));
+ {
+ KU32 off = 6;
+ for (;off < cbSrc - 2; off++)
+ dprintf(("%02" KX8_PRI, uSrc.pb[off]));
+ if (cbSrc == 6)
+ dprintf(("type=%#06" KX16_PRI, u16Type));
+ dprintf((" '%s'\n", pszFile));
+ }
+#endif
+
+
+ /* next */
+ iSrc++;
+ off += cbSrc;
+ }
+
+ if (iSrc == 0)
+ {
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: No cylindrical smoking thing: iSrc=0\n"));
+ return 2;
+ }
+ dprintf(("kDepObjCOFFParseCV8SymbolSection: iSrc=%" KU32_PRI "\n", iSrc));
+ return 0;
+}
+
+
+/**
+ * Parses the OMF file.
+ *
+ * @returns 0 on success, 1 on failure, 2 if no dependencies was found.
+ * @param pThis The kDepObj instance data.
+ * @param pbFile The start of the file.
+ * @param cbFile The file size.
+ */
+int kDepObjCOFFParse(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile;
+ ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile;
+ IMAGE_SECTION_HEADER const *paSHdrs;
+ KU32 cSHdrs;
+ unsigned iSHdr;
+ KPCUINT u;
+ KBOOL fDebugS = K_FALSE;
+ int rcRet = 2;
+ int rc;
+
+ if ( pBigObjHdr->Sig1 == 0
+ && pBigObjHdr->Sig2 == KU16_MAX)
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1);
+ cSHdrs = pBigObjHdr->NumberOfSections;
+ }
+ else
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader);
+ cSHdrs = pFileHdr->NumberOfSections;
+ }
+
+
+ dprintf(("COFF file!\n"));
+
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ {
+ if ( !memcmp(paSHdrs[iSHdr].Name, ".debug$S", sizeof(".debug$S") - 1)
+ && paSHdrs[iSHdr].SizeOfRawData > 4)
+ {
+ u.pb = pbFile + paSHdrs[iSHdr].PointerToRawData;
+ dprintf(("CV symbol table: version=%x\n", *u.pu32));
+ if (*u.pu32 == 0x000000004)
+ rc = kDepObjCOFFParseCV8SymbolSection(pThis, u.pb, paSHdrs[iSHdr].SizeOfRawData);
+ else
+ rc = 2;
+ dprintf(("rc=%d\n", rc));
+ if (rcRet == 2)
+ rcRet = rc;
+ if (rcRet != 2)
+ return rc;
+ fDebugS = K_TRUE;
+ }
+ dprintf(("#%d: %.8s\n", iSHdr, paSHdrs[iSHdr].Name));
+ }
+
+ /* If we found no dependencies but did find a .debug$S section, check if
+ this is a case where the compile didn't emit any because there is no
+ code in this compilation unit. */
+ if (rcRet == 2)
+ {
+ if (fDebugS)
+ {
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ if (!memcmp(paSHdrs[iSHdr].Name, ".text", sizeof(".text") - 1))
+ return kDepErr(pThis, 2, "%s: no dependencies (has text).", pThis->pszFile);
+ warnx(pThis->pCtx, "%s: no dependencies, but also no text, so probably (mostly) harmless.", pThis->pszFile);
+ return 0;
+ }
+ kDepErr(pThis, 2, "%s: no dependencies.", pThis->pszFile);
+ }
+
+ return rcRet;
+}
+
+
+/**
+ * Checks if this file is a COFF file or not.
+ *
+ * @returns K_TRUE if it's COFF, K_FALSE otherwise.
+ *
+ * @param pThis The kDepObj instance data.
+ * @param pb The start of the file.
+ * @param cb The file size.
+ */
+KBOOL kDepObjCOFFTest(PKDEPOBJGLOBALS pThis, const KU8 *pbFile, KSIZE cbFile)
+{
+ IMAGE_FILE_HEADER const *pFileHdr = (IMAGE_FILE_HEADER const *)pbFile;
+ ANON_OBJECT_HEADER_BIGOBJ const *pBigObjHdr = (ANON_OBJECT_HEADER_BIGOBJ const *)pbFile;
+ IMAGE_SECTION_HEADER const *paSHdrs;
+ KU32 cSHdrs;
+ KU32 iSHdr;
+ KSIZE cbHdrs;
+
+ if (cbFile <= sizeof(*pFileHdr))
+ return K_FALSE;
+
+ /*
+ * Deal with -bigobj output first.
+ */
+ if ( pBigObjHdr->Sig1 == 0
+ && pBigObjHdr->Sig2 == KU16_MAX)
+ {
+ static const KU8 s_abClsId[16] = { ANON_OBJECT_HEADER_BIGOBJ_CLS_ID_BYTES };
+
+ paSHdrs = (IMAGE_SECTION_HEADER const *)(pBigObjHdr + 1);
+ cSHdrs = pBigObjHdr->NumberOfSections;
+ cbHdrs = sizeof(IMAGE_SECTION_HEADER) * cSHdrs;
+
+ if (cbFile <= sizeof(*pBigObjHdr))
+ return K_FALSE;
+
+ if (pBigObjHdr->Version != 2)
+ return K_FALSE;
+ if (memcmp(&pBigObjHdr->ClassID[0], s_abClsId, sizeof(pBigObjHdr->ClassID)) != 0)
+ return K_FALSE;
+
+ if ( pBigObjHdr->Machine != IMAGE_FILE_MACHINE_I386
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_AMD64
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARMNT
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_ARM64
+ && pBigObjHdr->Machine != IMAGE_FILE_MACHINE_EBC)
+ {
+ kDepErr(pThis, 1, "bigobj Machine not supported: %#x", pBigObjHdr->Machine);
+ return K_FALSE;
+ }
+ if (pBigObjHdr->Flags != 0)
+ {
+ kDepErr(pThis, 1, "bigobj Flags field is non-zero: %#x", pBigObjHdr->Flags);
+ return K_FALSE;
+ }
+ if (pBigObjHdr->SizeOfData != 0)
+ {
+ kDepErr(pThis, 1, "bigobj SizeOfData field is non-zero: %#x", pBigObjHdr->SizeOfData);
+ return K_FALSE;
+ }
+
+ if ( pBigObjHdr->PointerToSymbolTable != 0
+ && ( pBigObjHdr->PointerToSymbolTable < cbHdrs
+ || pBigObjHdr->PointerToSymbolTable > cbFile))
+ return K_FALSE;
+ if ( pBigObjHdr->PointerToSymbolTable == 0
+ && pBigObjHdr->NumberOfSymbols != 0)
+ return K_FALSE;
+ }
+ /*
+ * Look for normal COFF object.
+ */
+ else
+ {
+ paSHdrs = (IMAGE_SECTION_HEADER const *)((KU8 const *)(pFileHdr + 1) + pFileHdr->SizeOfOptionalHeader);
+ cSHdrs = pFileHdr->NumberOfSections;
+ cbHdrs = (const KU8 *)&paSHdrs[cSHdrs] - (const KU8 *)pbFile;
+
+ if ( pFileHdr->Machine != IMAGE_FILE_MACHINE_I386
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_AMD64
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARMNT
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_ARM64
+ && pFileHdr->Machine != IMAGE_FILE_MACHINE_EBC)
+ return K_FALSE;
+
+ if (pFileHdr->SizeOfOptionalHeader != 0)
+ return K_FALSE; /* COFF files doesn't have an optional header */
+
+ if ( pFileHdr->PointerToSymbolTable != 0
+ && ( pFileHdr->PointerToSymbolTable < cbHdrs
+ || pFileHdr->PointerToSymbolTable > cbFile))
+ return K_FALSE;
+ if ( pFileHdr->PointerToSymbolTable == 0
+ && pFileHdr->NumberOfSymbols != 0)
+ return K_FALSE;
+ if ( pFileHdr->Characteristics
+ & ( IMAGE_FILE_DLL
+ | IMAGE_FILE_SYSTEM
+ | IMAGE_FILE_UP_SYSTEM_ONLY
+ | IMAGE_FILE_NET_RUN_FROM_SWAP
+ | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
+ | IMAGE_FILE_EXECUTABLE_IMAGE
+ | IMAGE_FILE_RELOCS_STRIPPED))
+ return K_FALSE;
+ }
+ if ( cSHdrs <= 1
+ || cSHdrs > cbFile)
+ return K_FALSE;
+ if (cbHdrs >= cbFile)
+ return K_FALSE;
+
+ /*
+ * Check the section headers.
+ */
+ for (iSHdr = 0; iSHdr < cSHdrs; iSHdr++)
+ {
+ if ( paSHdrs[iSHdr].PointerToRawData != 0
+ && ( paSHdrs[iSHdr].PointerToRawData < cbHdrs
+ || paSHdrs[iSHdr].PointerToRawData >= cbFile
+ || paSHdrs[iSHdr].PointerToRawData + paSHdrs[iSHdr].SizeOfRawData > cbFile))
+ return K_FALSE;
+ if ( paSHdrs[iSHdr].PointerToRelocations != 0
+ && ( paSHdrs[iSHdr].PointerToRelocations < cbHdrs
+ || paSHdrs[iSHdr].PointerToRelocations >= cbFile
+ || paSHdrs[iSHdr].PointerToRelocations + paSHdrs[iSHdr].NumberOfRelocations * 10 > cbFile)) /* IMAGE_RELOCATION */
+ return K_FALSE;
+ if ( paSHdrs[iSHdr].PointerToLinenumbers != 0
+ && ( paSHdrs[iSHdr].PointerToLinenumbers < cbHdrs
+ || paSHdrs[iSHdr].PointerToLinenumbers >= cbFile
+ || paSHdrs[iSHdr].PointerToLinenumbers + paSHdrs[iSHdr].NumberOfLinenumbers * 6 > cbFile)) /* IMAGE_LINENUMBER */
+ return K_FALSE;
+ }
+
+ return K_TRUE;
+}
+
+
+/**
+ * Read the file into memory and parse it.
+ */
+static int kDepObjProcessFile(PKDEPOBJGLOBALS pThis, FILE *pInput)
+{
+ size_t cbFile;
+ KU8 *pbFile;
+ void *pvOpaque;
+ int rc = 0;
+
+ /*
+ * Read the file into memory.
+ */
+ pbFile = (KU8 *)depReadFileIntoMemory(pInput, &cbFile, &pvOpaque);
+ if (!pbFile)
+ return 1;
+
+ /*
+ * See if it's an OMF file, then process it.
+ */
+ if (kDepObjOMFTest(pbFile, cbFile))
+ rc = kDepObjOMFParse(pThis, pbFile, cbFile);
+ else if (kDepObjCOFFTest(pThis, pbFile, cbFile))
+ rc = kDepObjCOFFParse(pThis, pbFile, cbFile);
+ else
+ rc = kDepErr(pThis, 1, "Doesn't recognize the header of the OMF/COFF file.");
+
+ depFreeFileMemory(pbFile, pvOpaque);
+ return rc;
+}
+
+
+static void kDebObjUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s -o <output> -t <target> [-fqs] [-e <ignore-ext>] <OMF or COFF file>\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kDepObj(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ KDEPOBJGLOBALS This;
+
+ /* Arguments. */
+ FILE *pOutput = NULL;
+ const char *pszOutput = NULL;
+ FILE *pInput = NULL;
+ const char *pszTarget = NULL;
+ int fStubs = 0;
+ int fFixCase = 0;
+ const char *pszIgnoreExt = NULL;
+ /* Argument parsing. */
+ int fInput = 0; /* set when we've found input argument. */
+ int fQuiet = 0;
+
+ /* Init instance data. */
+ This.pCtx = pCtx;
+ This.pszFile = NULL;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kDebObjUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *pszValue;
+ const char *psz = &argv[i][1];
+ char chOpt;
+ chOpt = *psz++;
+ if (chOpt == '-')
+ {
+ /* Convert long to short option. */
+ if (!strcmp(psz, "quiet"))
+ chOpt = 'q';
+ else if (!strcmp(psz, "help"))
+ chOpt = '?';
+ else if (!strcmp(psz, "version"))
+ chOpt = 'V';
+ else
+ {
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kDebObjUsage(pCtx, 1);
+ return 2;
+ }
+ psz = "";
+ }
+
+ /*
+ * Requires value?
+ */
+ switch (chOpt)
+ {
+ case 'o':
+ case 't':
+ case 'e':
+ if (*psz)
+ pszValue = psz;
+ else if (++i < argc)
+ pszValue = argv[i];
+ else
+ return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt);
+ break;
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+
+ switch (chOpt)
+ {
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ if (pOutput)
+ return errx(pCtx, 2, "only one output file!");
+ pszOutput = pszValue;
+ if (pszOutput[0] == '-' && !pszOutput[1])
+ pOutput = stdout;
+ else
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ return err(pCtx, 1, "Failed to create output file '%s'", pszOutput);
+ break;
+ }
+
+ /*
+ * Target name.
+ */
+ case 't':
+ {
+ if (pszTarget)
+ return errx(pCtx, 2, "only one target!");
+ pszTarget = pszValue;
+ break;
+ }
+
+ /*
+ * Fix case.
+ */
+ case 'f':
+ {
+ fFixCase = 1;
+ break;
+ }
+
+ /*
+ * Quiet.
+ */
+ case 'q':
+ {
+ fQuiet = 1;
+ break;
+ }
+
+ /*
+ * Generate stubs.
+ */
+ case 's':
+ {
+ fStubs = 1;
+ break;
+ }
+
+ /*
+ * Extension to ignore.
+ */
+ case 'e':
+ {
+ if (pszIgnoreExt)
+ return errx(pCtx, 2, "The '-e' option can only be used once!");
+ pszIgnoreExt = pszValue;
+ break;
+ }
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kDebObjUsage(pCtx, 0);
+ return 0;
+ case 'V':
+ case 'v':
+ return kbuild_version(argv[0]);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kDebObjUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ pInput = fopen(argv[i], "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pInput)
+ return err(pCtx, 1, "Failed to open input file '%s'", argv[i]);
+ This.pszFile = argv[i];
+ fInput = 1;
+ }
+
+ /*
+ * End of the line?
+ */
+ if (fInput)
+ {
+ if (++i < argc)
+ return errx(pCtx, 2, "No arguments shall follow the input spec.");
+ break;
+ }
+ }
+
+ /*
+ * Got all we require?
+ */
+ if (!pInput)
+ return errx(pCtx, 2, "No input!");
+ if (!pOutput)
+ return errx(pCtx, 2, "No output!");
+ if (!pszTarget)
+ return errx(pCtx, 2, "No target!");
+
+ /*
+ * Do the parsing.
+ */
+ depInit(&This.Core);
+ i = kDepObjProcessFile(&This, pInput);
+ fclose(pInput);
+
+ /*
+ * Write the dependecy file.
+ */
+ if (!i)
+ {
+ depOptimize(&This.Core, fFixCase, fQuiet, pszIgnoreExt);
+ depPrintTargetWithDeps(&This.Core, pOutput, pszTarget, 1 /*fEscapeTarget*/);
+ if (fStubs)
+ depPrintStubs(&This.Core, pOutput);
+ }
+
+ /*
+ * Close the output, delete output on failure.
+ */
+ if (!i && ferror(pOutput))
+ i = errx(pCtx, 1, "Error writing to '%s'", pszOutput);
+ fclose(pOutput);
+ if (i)
+ {
+ if (unlink(pszOutput))
+ warn(pCtx, "warning: failed to remove output file '%s' on failure.", pszOutput);
+ }
+
+ depCleanup(&This.Core);
+ return i;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kDepObj", NULL };
+ return kmk_builtin_kDepObj(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kSubmit.c b/src/kmk/kmkbuiltin/kSubmit.c
new file mode 100644
index 0000000..dffa198
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kSubmit.c
@@ -0,0 +1,2116 @@
+/* $Id: kSubmit.c 3413 2020-08-20 08:20:15Z bird $ */
+/** @file
+ * kMk Builtin command - submit job to a kWorker.
+ */
+
+/*
+ * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#ifdef __APPLE__
+# define _POSIX_C_SOURCE 1 /* 10.4 sdk and unsetenv */
+#endif
+#include "makeint.h"
+#include "job.h"
+#include "variable.h"
+#include "pathstuff.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include <direct.h>
+# include <process.h>
+#else
+# include <unistd.h>
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "../w32/winchildren.h"
+# endif
+# include "nt/nt_child_inject_standard_handles.h"
+#endif
+
+#include "kbuild.h"
+#include "kmkbuiltin.h"
+#include "err.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Hashes a pid. */
+#define KWORKER_PID_HASH(a_pid) ((size_t)(a_pid) % 61)
+
+#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct WORKERINSTANCE *PWORKERINSTANCE;
+typedef struct WORKERINSTANCE
+{
+ /** Pointer to the next worker instance. */
+ PWORKERINSTANCE pNext;
+ /** Pointer to the previous worker instance. */
+ PWORKERINSTANCE pPrev;
+ /** Pointer to the next worker with the same pid hash slot. */
+ PWORKERINSTANCE pNextPidHash;
+ /** 32 or 64. */
+ unsigned cBits;
+ /** The process ID of the kWorker process. */
+ pid_t pid;
+ union
+ {
+ struct
+ {
+ /** The exit code. */
+ int32_t rcExit;
+ /** Set to 1 if the worker is exiting. */
+ uint8_t bWorkerExiting;
+ uint8_t abUnused[3];
+ } s;
+ uint8_t ab[8];
+ } Result;
+ /** Number of result bytes read alread. */
+ size_t cbResultRead;
+
+#ifdef KBUILD_OS_WINDOWS
+ /** The process handle. */
+ HANDLE hProcess;
+ /** The bi-directional pipe we use to talk to the kWorker process. */
+ HANDLE hPipe;
+ /** For overlapped read (have valid event semaphore). */
+ OVERLAPPED OverlappedRead;
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ /** Standard output catcher (reused). */
+ PWINCCWPIPE pStdOut;
+ /** Standard error catcher (reused). */
+ PWINCCWPIPE pStdErr;
+# endif
+#else
+ /** The socket descriptor we use to talk to the kWorker process. */
+ int fdSocket;
+#endif
+
+ /** --debug-dump-history-on-failure. */
+ int fDebugDumpHistoryOnFailure;
+ /** Current history index (must mod with aHistory element count). */
+ unsigned iHistory;
+ /** History. */
+ struct
+ {
+ /** Pointer to the message, NULL if none. */
+ void *pvMsg;
+ /** The message size, zero if not present. */
+ size_t cbMsg;
+ } aHistory[4];
+
+ /** What it's busy with. NULL if idle. */
+ struct child *pBusyWith;
+} WORKERINSTANCE;
+
+
+typedef struct WORKERLIST
+{
+ /** The head of the list. NULL if empty. */
+ PWORKERINSTANCE pHead;
+ /** The tail of the list. NULL if empty. */
+ PWORKERINSTANCE pTail;
+ /** Number of list entries. */
+ size_t cEntries;
+} WORKERLIST;
+typedef WORKERLIST *PWORKERLIST;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** List of idle worker.*/
+static WORKERLIST g_IdleList;
+/** List of busy workers. */
+static WORKERLIST g_BusyList;
+/** PID hash table for the workers.
+ * @sa KWORKER_PID_HASH() */
+static PWORKERINSTANCE g_apPidHash[61];
+
+#ifdef KBUILD_OS_WINDOWS
+/** For naming the pipes.
+ * Also indicates how many worker instances we've spawned. */
+static unsigned g_uWorkerSeqNo = 0;
+#endif
+/** Set if we've registred the atexit handler already. */
+static int g_fAtExitRegistered = 0;
+
+/** @var g_cArchBits
+ * The bit count of the architecture this binary is compiled for. */
+/** @var g_szArch
+ * The name of the architecture this binary is compiled for. */
+/** @var g_cArchBits
+ * The bit count of the alternative architecture. */
+/** @var g_szAltArch
+ * The name of the alternative architecture. */
+#if defined(KBUILD_ARCH_AMD64)
+static unsigned g_cArchBits = 64;
+static char const g_szArch[] = "amd64";
+static unsigned g_cAltArchBits = 32;
+static char const g_szAltArch[] = "x86";
+#elif defined(KBUILD_ARCH_X86)
+static unsigned g_cArchBits = 32;
+static char const g_szArch[] = "x86";
+static unsigned g_cAltArchBits = 64;
+static char const g_szAltArch[] = "amd64";
+#else
+# error "Port me!"
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/** The processor group allocator state. */
+static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator;
+# if K_ARCH_BITS == 64
+/** The processor group allocator state for 32-bit processes. */
+static MKWINCHILDCPUGROUPALLOCSTATE g_SubmitProcessorGroupAllocator32;
+# endif
+#endif
+
+#ifdef KBUILD_OS_WINDOWS
+/** Pointer to kernel32!SetThreadGroupAffinity. */
+static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY *);
+#endif
+
+
+
+/**
+ * Unlinks a worker instance from a list.
+ *
+ * @param pList The list.
+ * @param pWorker The worker.
+ */
+static void kSubmitListUnlink(PWORKERLIST pList, PWORKERINSTANCE pWorker)
+{
+ PWORKERINSTANCE pNext = pWorker->pNext;
+ PWORKERINSTANCE pPrev = pWorker->pPrev;
+
+ if (pNext)
+ {
+ assert(pNext->pPrev == pWorker);
+ pNext->pPrev = pPrev;
+ }
+ else
+ {
+ assert(pList->pTail == pWorker);
+ pList->pTail = pPrev;
+ }
+
+ if (pPrev)
+ {
+ assert(pPrev->pNext == pWorker);
+ pPrev->pNext = pNext;
+ }
+ else
+ {
+ assert(pList->pHead == pWorker);
+ pList->pHead = pNext;
+ }
+
+ assert(!pList->pHead || pList->pHead->pPrev == NULL);
+ assert(!pList->pTail || pList->pTail->pNext == NULL);
+
+ assert(pList->cEntries > 0);
+ pList->cEntries--;
+
+ pWorker->pNext = NULL;
+ pWorker->pPrev = NULL;
+}
+
+
+/**
+ * Appends a worker instance to the tail of a list.
+ *
+ * @param pList The list.
+ * @param pWorker The worker.
+ */
+static void kSubmitListAppend(PWORKERLIST pList, PWORKERINSTANCE pWorker)
+{
+ PWORKERINSTANCE pTail = pList->pTail;
+
+ assert(pTail != pWorker);
+ assert(pList->pHead != pWorker);
+
+ pWorker->pNext = NULL;
+ pWorker->pPrev = pTail;
+ if (pTail != NULL)
+ {
+ assert(pTail->pNext == NULL);
+ pTail->pNext = pWorker;
+ }
+ else
+ {
+ assert(pList->pHead == NULL);
+ pList->pHead = pWorker;
+ }
+ pList->pTail = pWorker;
+
+ assert(pList->pHead->pPrev == NULL);
+ assert(pList->pTail->pNext == NULL);
+
+ pList->cEntries++;
+}
+
+
+/**
+ * Remove worker from the process ID hash table.
+ *
+ * @param pWorker The worker.
+ */
+static void kSubmitPidHashRemove(PWORKERINSTANCE pWorker)
+{
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ if (g_apPidHash[idxHash] == pWorker)
+ g_apPidHash[idxHash] = pWorker->pNext;
+ else
+ {
+ PWORKERINSTANCE pPrev = g_apPidHash[idxHash];
+ while (pPrev && pPrev->pNext != pWorker)
+ pPrev = pPrev->pNext;
+ assert(pPrev != NULL);
+ if (pPrev)
+ pPrev->pNext = pWorker->pNext;
+ }
+ pWorker->pid = -1;
+}
+
+
+/**
+ * Looks up a worker by its process ID.
+ *
+ * @returns Pointer to the worker instance if found. NULL if not.
+ * @param pid The process ID of the worker.
+ */
+static PWORKERINSTANCE kSubmitFindWorkerByPid(pid_t pid)
+{
+ PWORKERINSTANCE pWorker = g_apPidHash[KWORKER_PID_HASH(pid)];
+ while (pWorker && pWorker->pid != pid)
+ pWorker = pWorker->pNextPidHash;
+ return pWorker;
+}
+
+
+/**
+ * Calcs the path to the kWorker binary for the worker.
+ *
+ * @returns
+ * @param pCtx The command execution context.
+ * @param pWorker The worker (for its bitcount).
+ * @param pszExecutable The output buffer.
+ * @param cbExecutable The output buffer size.
+ */
+static int kSubmitCalcExecutablePath(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, char *pszExecutable, size_t cbExecutable)
+{
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+ static const char s_szWorkerName[] = "kWorker.exe";
+#else
+ static const char s_szWorkerName[] = "kWorker";
+#endif
+ const char *pszBinPath = get_kbuild_bin_path();
+ size_t const cchBinPath = strlen(pszBinPath);
+ size_t cchExecutable;
+ if ( pWorker->cBits == g_cArchBits
+ ? cchBinPath + 1 + sizeof(s_szWorkerName) <= cbExecutable
+ : cchBinPath + 1 - sizeof(g_szArch) + sizeof(g_szAltArch) + sizeof(s_szWorkerName) <= cbExecutable )
+ {
+ memcpy(pszExecutable, pszBinPath, cchBinPath);
+ cchExecutable = cchBinPath;
+
+ /* Replace the arch bin directory extension with the alternative one if requested. */
+ if (pWorker->cBits != g_cArchBits)
+ {
+ if ( cchBinPath < sizeof(g_szArch)
+ || memcmp(&pszExecutable[cchBinPath - sizeof(g_szArch) + 1], g_szArch, sizeof(g_szArch) - 1) != 0)
+ return errx(pCtx, 1, "KBUILD_BIN_PATH does not end with main architecture (%s) as expected: %s",
+ pszBinPath, g_szArch);
+ cchExecutable -= sizeof(g_szArch) - 1;
+ memcpy(&pszExecutable[cchExecutable], g_szAltArch, sizeof(g_szAltArch) - 1);
+ cchExecutable += sizeof(g_szAltArch) - 1;
+ }
+
+ /* Append a slash and the worker name. */
+ pszExecutable[cchExecutable++] = '/';
+ memcpy(&pszExecutable[cchExecutable], s_szWorkerName, sizeof(s_szWorkerName));
+ return 0;
+ }
+ return errx(pCtx, 1, "KBUILD_BIN_PATH is too long");
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+/**
+ * Calcs the UTF-16 path to the kWorker binary for the worker.
+ *
+ * @returns
+ * @param pCtx The command execution context.
+ * @param pWorker The worker (for its bitcount).
+ * @param pwszExecutable The output buffer.
+ * @param cwcExecutable The output buffer size.
+ */
+static int kSubmitCalcExecutablePathW(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, wchar_t *pwszExecutable, size_t cwcExecutable)
+{
+ char szExecutable[MAX_PATH];
+ int rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, sizeof(szExecutable));
+ if (rc == 0)
+ {
+ int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, szExecutable, strlen(szExecutable) + 1,
+ pwszExecutable, cwcExecutable);
+ if (cwc > 0)
+ return 0;
+ return errx(pCtx, 1, "MultiByteToWideChar failed on '%s': %u", szExecutable, GetLastError());
+ }
+ return rc;
+}
+#endif
+
+
+/**
+ * Creates a new worker process.
+ *
+ * @returns 0 on success, non-zero value on failure.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker structure. Caller does the linking
+ * (as we might be reusing an existing worker
+ * instance because a worker shut itself down due
+ * to high resource leak level).
+ * @param cVerbosity The verbosity level.
+ */
+static int kSubmitSpawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity)
+{
+ int rc;
+#ifdef KBUILD_OS_WINDOWS
+ wchar_t wszExecutable[MAX_PATH];
+#else
+ PATH_VAR(szExecutable);
+#endif
+
+ /*
+ * Get the output path so it can be passed on as a volatile.
+ */
+ const char *pszVarVolatile;
+ struct variable *pVarVolatile = lookup_variable(TUPLE("PATH_OUT"));
+ if (pVarVolatile)
+ pszVarVolatile = "PATH_OUT";
+ else
+ {
+ pVarVolatile = lookup_variable(TUPLE("PATH_OUT_BASE"));
+ if (pVarVolatile)
+ pszVarVolatile = "PATH_OUT_BASE";
+ else
+ warn(pCtx, "Neither PATH_OUT_BASE nor PATH_OUT was found.");
+ }
+ if (pVarVolatile && strchr(pVarVolatile->value, '"'))
+ return errx(pCtx, -1, "%s contains double quotes.", pszVarVolatile);
+ if (pVarVolatile && strlen(pVarVolatile->value) >= GET_PATH_MAX)
+ return errx(pCtx, -1, "%s is too long (max %u)", pszVarVolatile, GET_PATH_MAX);
+
+ /*
+ * Construct the executable path.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ rc = kSubmitCalcExecutablePathW(pCtx, pWorker, wszExecutable, K_ELEMENTS(wszExecutable));
+#else
+ rc = kSubmitCalcExecutablePath(pCtx, pWorker, szExecutable, GET_PATH_MAX);
+#endif
+ if (rc == 0)
+ {
+#ifdef KBUILD_OS_WINDOWS
+ static DWORD s_fDenyRemoteClients = ~(DWORD)0;
+ wchar_t wszPipeName[128];
+ HANDLE hWorkerPipe;
+ int iProcessorGroup;
+
+# if K_ARCH_BITS == 64
+ /** @todo make it return -1 if not applicable (e.g only one group). */
+ if (pWorker->cBits != 32)
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator);
+ else
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator32);
+# else
+ iProcessorGroup = MkWinChildAllocateCpuGroup(&g_SubmitProcessorGroupAllocator);
+# endif
+
+ /*
+ * Create the bi-directional pipe with overlapping I/O enabled.
+ */
+ if (s_fDenyRemoteClients == ~(DWORD)0)
+ s_fDenyRemoteClients = GetVersion() >= 0x60000 ? PIPE_REJECT_REMOTE_CLIENTS : 0;
+ _snwprintf(wszPipeName, sizeof(wszPipeName), L"\\\\.\\pipe\\kmk-%u-kWorker-%u-%u",
+ GetCurrentProcessId(), g_uWorkerSeqNo++, GetTickCount());
+ hWorkerPipe = CreateNamedPipeW(wszPipeName,
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE /* win2k sp2+ */,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | s_fDenyRemoteClients,
+ 1 /* cMaxInstances */,
+ 64 /*cbOutBuffer*/,
+ 65536 /*cbInBuffer*/,
+ 0 /*cMsDefaultTimeout -> 50ms*/,
+ NULL /* pSecAttr - no inherit */);
+ if (hWorkerPipe != INVALID_HANDLE_VALUE)
+ {
+ pWorker->hPipe = CreateFileW(wszPipeName,
+ GENERIC_READ | GENERIC_WRITE,
+ 0 /* dwShareMode - no sharing */,
+ NULL /*pSecAttr - no inherit */,
+ OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED,
+ NULL /*hTemplate*/);
+ if (pWorker->hPipe != INVALID_HANDLE_VALUE)
+ {
+ pWorker->OverlappedRead.hEvent = CreateEventW(NULL /*pSecAttrs - no inherit*/, TRUE /*bManualReset*/,
+ TRUE /*bInitialState*/, NULL /*pwszName*/);
+ if (pWorker->OverlappedRead.hEvent != NULL)
+ {
+ extern int process_priority; /* main.c */
+ wchar_t wszCommandLine[MAX_PATH * 3 + 32];
+ wchar_t *pwszDst = wszCommandLine;
+ size_t cwcDst = K_ELEMENTS(wszCommandLine);
+ int cwc;
+ DWORD fFlags;
+ STARTUPINFOW StartupInfo;
+ PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 };
+
+ /*
+ * Compose the command line.
+ */
+ cwc = _snwprintf(pwszDst, cwcDst, L"\"%s\" ", wszExecutable);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ if (pVarVolatile && *pVarVolatile->value)
+ {
+ char chEnd = strchr(pVarVolatile->value, '\0')[-1];
+ if (chEnd == '\\')
+ cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S.\"", pVarVolatile->value);
+ else
+ cwc = _snwprintf(pwszDst, cwcDst, L" --volatile \"%S\"", pVarVolatile->value);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ }
+ if (iProcessorGroup >= 0)
+ {
+ cwc = _snwprintf(pwszDst, cwcDst, L" --group %d", iProcessorGroup);
+ assert(cwc > 0 && cwc < cwcDst);
+ pwszDst += cwc;
+ cwcDst -= cwc;
+ }
+ *pwszDst = '\0';
+
+ /*
+ * Fill in the startup information.
+ */
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoW(&StartupInfo);
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+ StartupInfo.lpReserved2 = NULL;
+ StartupInfo.cbReserved2 = 0;
+
+ /*
+ * Flags and such.
+ */
+ fFlags = CREATE_SUSPENDED;
+ switch (process_priority)
+ {
+ case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
+ case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
+ case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
+ case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
+ case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
+ }
+
+ /*
+ * Create the worker process.
+ */
+ if (CreateProcessW(wszExecutable, wszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
+ FALSE /*fInheritHandles*/, fFlags, NULL /*pwszzEnvironment*/,
+ NULL /*pwszCwd*/, &StartupInfo, &ProcInfo))
+ {
+ char szErrMsg[256];
+ BOOL afReplace[3] = { TRUE, FALSE, FALSE };
+ HANDLE ahReplace[3] = { hWorkerPipe, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
+ if (pWorker->pStdOut)
+ {
+ afReplace[1] = TRUE;
+ afReplace[2] = TRUE;
+ ahReplace[1] = pWorker->pStdOut->hPipeChild;
+ ahReplace[2] = pWorker->pStdErr->hPipeChild;
+ }
+
+ rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahReplace, szErrMsg, sizeof(szErrMsg));
+ if (rc == 0)
+ {
+ BOOL fRet;
+ switch (process_priority)
+ {
+ case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
+ case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
+ case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
+ case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
+ case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
+ default: fRet = TRUE;
+ }
+ if (!fRet)
+ warnx(pCtx, "warning: failed to set kWorker thread priority: %u\n", GetLastError());
+
+ if (iProcessorGroup >= 0 && g_pfnSetThreadGroupAffinity)
+ {
+ GROUP_AFFINITY OldAff = { 0, 0, 0, 0, 0 };
+ GROUP_AFFINITY NewAff = { 0 /* == all active apparently */, (WORD)iProcessorGroup, 0, 0, 0 };
+ if (!g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &NewAff, &OldAff))
+ warnx(pCtx, "warning: Failed to set processor group to %d: %u\n",
+ iProcessorGroup, GetLastError());
+ }
+
+ /*
+ * Now, we just need to resume the thread.
+ */
+ if (ResumeThread(ProcInfo.hThread))
+ {
+ CloseHandle(hWorkerPipe);
+ CloseHandle(ProcInfo.hThread);
+ pWorker->pid = ProcInfo.dwProcessId;
+ pWorker->hProcess = ProcInfo.hProcess;
+ if (cVerbosity > 0)
+ warnx(pCtx, "created %d bit worker %d\n", pWorker->cBits, pWorker->pid);
+ return 0;
+ }
+
+ /*
+ * Failed, bail out.
+ */
+ rc = errx(pCtx, -3, "ResumeThread failed: %u", GetLastError());
+ }
+ else
+ rc = errx(pCtx, -3, "%s", szErrMsg);
+ TerminateProcess(ProcInfo.hProcess, 1234);
+ CloseHandle(ProcInfo.hThread);
+ CloseHandle(ProcInfo.hProcess);
+ }
+ else
+ rc = errx(pCtx, -2, "CreateProcessW failed: %u (exe=%S cmdline=%S)",
+ GetLastError(), wszExecutable, wszCommandLine);
+ CloseHandle(pWorker->OverlappedRead.hEvent);
+ pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
+ }
+ else
+ rc = errx(pCtx, -1, "CreateEventW failed: %u", GetLastError());
+ CloseHandle(pWorker->hPipe);
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+ }
+ else
+ rc = errx(pCtx, -1, "Opening named pipe failed: %u", GetLastError());
+ CloseHandle(hWorkerPipe);
+ }
+ else
+ rc = errx(pCtx, -1, "CreateNamedPipeW failed: %u", GetLastError());
+
+#else
+ /*
+ * Create a socket pair.
+ */
+ int aiPair[2] = { -1, -1 };
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, aiPair) == 0)
+ {
+ pWorker->fdSocket = aiPair[1];
+
+ rc = -1;
+ }
+ else
+ rc = err(pCtx, -1, "socketpair");
+#endif
+ }
+ else
+ rc = errx(pCtx, -1, "KBUILD_BIN_PATH is too long");
+ return rc;
+}
+
+
+/**
+ * Selects an idle worker or spawns a new one.
+ *
+ * @returns Pointer to the selected worker instance. NULL on error.
+ * @param pCtx The command execution context.
+ * @param pWorker The idle worker instance to respawn.
+ * On failure this will be freed!
+ * @param cBitsWorker The worker bitness - 64 or 32.
+ */
+static int kSubmitRespawnWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity)
+{
+ /*
+ * Clean up after the old worker.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ DWORD rcWait;
+
+ /* Close the pipe handle first, breaking the pipe in case it's not already
+ busted up. Close the event semaphore too before waiting for the process. */
+ if (pWorker->hPipe != INVALID_HANDLE_VALUE)
+ {
+ if (!CloseHandle(pWorker->hPipe))
+ warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError());
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+ }
+
+ if (!CloseHandle(pWorker->OverlappedRead.hEvent))
+ warnx(pCtx, "CloseHandle(pWorker->OverlappedRead.hEvent): %u", GetLastError());
+ pWorker->OverlappedRead.hEvent = INVALID_HANDLE_VALUE;
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ /* It's probably shutdown already, if not give it 10 milliseconds before
+ we terminate it forcefully. */
+ rcWait = WaitForSingleObject(pWorker->hProcess, 10);
+ if (rcWait != WAIT_OBJECT_0)
+ {
+ BOOL fRc = TerminateProcess(pWorker->hProcess, 127);
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ rcWait = WaitForSingleObject(pWorker->hProcess, 100);
+ if (rcWait != WAIT_OBJECT_0)
+ warnx(pCtx, "WaitForSingleObject returns %u (and TerminateProcess %d)", rcWait, fRc);
+ }
+
+ if (pWorker->pStdOut)
+ MkWinChildcareWorkerDrainPipes(NULL, pWorker->pStdOut, pWorker->pStdErr);
+
+ if (!CloseHandle(pWorker->hProcess))
+ warnx(pCtx, "CloseHandle(pWorker->hProcess): %u", GetLastError());
+ pWorker->hProcess = INVALID_HANDLE_VALUE;
+
+#else
+ pid_t pidWait;
+ int rc;
+
+ if (pWorker->fdSocket != -1)
+ {
+ if (close(pWorker->fdSocket) != 0)
+ warn(pCtx, "close(pWorker->fdSocket)");
+ pWorker->fdSocket = -1;
+ }
+
+ kill(pWorker->pid, SIGTERM);
+ pidWait = waitpid(pWorker->pid, &rc, 0);
+ if (pidWait != pWorker->pid)
+ warn(pCtx, "waitpid(pWorker->pid,,0)");
+#endif
+
+ /*
+ * Unlink it from the hash table.
+ */
+ kSubmitPidHashRemove(pWorker);
+
+ /*
+ * Respawn it.
+ */
+ if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0)
+ {
+ /*
+ * Insert it into the process ID hash table and idle list.
+ */
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ pWorker->pNextPidHash = g_apPidHash[idxHash];
+ g_apPidHash[idxHash] = pWorker;
+ return 0;
+ }
+
+ kSubmitListUnlink(&g_IdleList, pWorker);
+ free(pWorker);
+ return -1;
+}
+
+
+/**
+ * Selects an idle worker or spawns a new one.
+ *
+ * @returns Pointer to the selected worker instance. NULL on error.
+ * @param cBitsWorker The worker bitness - 64 or 32.
+ */
+static PWORKERINSTANCE kSubmitSelectWorkSpawnNewIfNecessary(PKMKBUILTINCTX pCtx, unsigned cBitsWorker, int cVerbosity)
+{
+ /*
+ * Lookup up an idle worker.
+ */
+ PWORKERINSTANCE pWorker = g_IdleList.pHead;
+ while (pWorker)
+ {
+ if (pWorker->cBits == cBitsWorker)
+ return pWorker;
+ pWorker = pWorker->pNext;
+ }
+
+ /*
+ * Create a new worker instance.
+ */
+ pWorker = (PWORKERINSTANCE)xcalloc(sizeof(*pWorker));
+ pWorker->cBits = cBitsWorker;
+#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS)
+ if (output_sync != OUTPUT_SYNC_NONE)
+ {
+ pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, g_uWorkerSeqNo << 1);
+ pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, g_uWorkerSeqNo << 1);
+ }
+ if ( output_sync == OUTPUT_SYNC_NONE
+ || ( pWorker->pStdOut != NULL
+ && pWorker->pStdErr != NULL))
+#endif
+ {
+ if (kSubmitSpawnWorker(pCtx, pWorker, cVerbosity) == 0)
+ {
+ /*
+ * Insert it into the process ID hash table and idle list.
+ */
+ size_t idxHash = KWORKER_PID_HASH(pWorker->pid);
+ pWorker->pNextPidHash = g_apPidHash[idxHash];
+ g_apPidHash[idxHash] = pWorker;
+
+ kSubmitListAppend(&g_IdleList, pWorker);
+ return pWorker;
+ }
+ }
+#if defined(CONFIG_NEW_WIN_CHILDREN) && defined(KBUILD_OS_WINDOWS)
+ if (pWorker->pStdErr)
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
+ if (pWorker->pStdOut)
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
+#endif
+
+ free(pWorker);
+ return NULL;
+}
+
+
+/**
+ * Composes a JOB mesage for a worker.
+ *
+ * @returns Pointer to the message.
+ * @param pszExecutable The executable to run.
+ * @param papszArgs The argument vector.
+ * @param papszEnvVars The environment vector.
+ * @param pszCwd The current directory.
+ * @param fWatcomBrainDamage The wcc/wcc386 workaround.
+ * @param fNoPchCaching Whether to disable precompiled header caching.
+ * @param pszSpecialEnv Environment variable (name=value) subject to
+ * special expansion in kWorker. NULL if none.
+ * @param papszPostCmdArgs The post command and it's arguments.
+ * @param cPostCmdArgs Number of post command argument, including the
+ * command. Zero if no post command scheduled.
+ * @param pcbMsg Where to return the message length.
+ */
+static void *kSubmitComposeJobMessage(const char *pszExecutable, char **papszArgs, char **papszEnvVars,
+ const char *pszCwd, int fWatcomBrainDamage, int fNoPchCaching, const char *pszSpecialEnv,
+ char **papszPostCmdArgs, uint32_t cPostCmdArgs, uint32_t *pcbMsg)
+{
+ size_t cbTmp;
+ size_t cbSpecialEnv;
+ uint32_t i;
+ uint32_t cbMsg;
+ uint32_t cArgs;
+ uint32_t cEnvVars;
+ uint8_t *pbMsg;
+ uint8_t *pbCursor;
+
+ /*
+ * Adjust input.
+ */
+ if (!pszExecutable)
+ pszExecutable = papszArgs[0];
+
+ /*
+ * Calculate the message length first.
+ */
+ cbMsg = sizeof(cbMsg);
+ cbMsg += sizeof("JOB");
+ cbMsg += strlen(pszExecutable) + 1;
+ cbMsg += strlen(pszCwd) + 1;
+
+ cbMsg += sizeof(cArgs);
+ for (i = 0; papszArgs[i] != NULL; i++)
+ cbMsg += 1 + strlen(papszArgs[i]) + 1;
+ cArgs = i;
+
+ cbMsg += sizeof(cArgs);
+ for (i = 0; papszEnvVars[i] != NULL; i++)
+ cbMsg += strlen(papszEnvVars[i]) + 1;
+ cEnvVars = i;
+
+ cbMsg += 1; /* fWatcomBrainDamage */
+ cbMsg += 1; /* fNoPchCaching */
+
+ cbSpecialEnv = pszSpecialEnv ? strchr(pszSpecialEnv, '=') - pszSpecialEnv : 0;
+ cbMsg += cbSpecialEnv + 1;
+
+ cbMsg += sizeof(cPostCmdArgs);
+ for (i = 0; i < cPostCmdArgs; i++)
+ cbMsg += strlen(papszPostCmdArgs[i]) + 1;
+
+ /*
+ * Compose the message.
+ */
+ pbMsg = pbCursor = xmalloc(cbMsg);
+
+ /* header */
+ memcpy(pbCursor, &cbMsg, sizeof(cbMsg));
+ pbCursor += sizeof(cbMsg);
+ memcpy(pbCursor, "JOB", sizeof("JOB"));
+ pbCursor += sizeof("JOB");
+
+ /* executable. */
+ cbTmp = strlen(pszExecutable) + 1;
+ memcpy(pbCursor, pszExecutable, cbTmp);
+ pbCursor += cbTmp;
+
+ /* cwd */
+ cbTmp = strlen(pszCwd) + 1;
+ memcpy(pbCursor, pszCwd, cbTmp);
+ pbCursor += cbTmp;
+
+ /* arguments */
+ memcpy(pbCursor, &cArgs, sizeof(cArgs));
+ pbCursor += sizeof(cArgs);
+ for (i = 0; papszArgs[i] != NULL; i++)
+ {
+ *pbCursor++ = 0; /* Argument expansion flags (MSC, EMX). */
+ cbTmp = strlen(papszArgs[i]) + 1;
+ memcpy(pbCursor, papszArgs[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cArgs);
+
+ /* environment */
+ memcpy(pbCursor, &cEnvVars, sizeof(cEnvVars));
+ pbCursor += sizeof(cEnvVars);
+ for (i = 0; papszEnvVars[i] != NULL; i++)
+ {
+ cbTmp = strlen(papszEnvVars[i]) + 1;
+ memcpy(pbCursor, papszEnvVars[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cEnvVars);
+
+ /* flags */
+ *pbCursor++ = fWatcomBrainDamage != 0;
+ *pbCursor++ = fNoPchCaching != 0;
+
+ /* Special environment variable name. */
+ memcpy(pbCursor, pszSpecialEnv, cbSpecialEnv);
+ pbCursor += cbSpecialEnv;
+ *pbCursor++ = '\0';
+
+ /* post command */
+ memcpy(pbCursor, &cPostCmdArgs, sizeof(cPostCmdArgs));
+ pbCursor += sizeof(cPostCmdArgs);
+ for (i = 0; i < cPostCmdArgs; i++)
+ {
+ cbTmp = strlen(papszPostCmdArgs[i]) + 1;
+ memcpy(pbCursor, papszPostCmdArgs[i], cbTmp);
+ pbCursor += cbTmp;
+ }
+ assert(i == cPostCmdArgs);
+
+ assert(pbCursor - pbMsg == (size_t)cbMsg);
+
+ /*
+ * Done.
+ */
+ *pcbMsg = cbMsg;
+ return pbMsg;
+}
+
+
+/**
+ * Sends the job message to the given worker, respawning the worker if
+ * necessary.
+ *
+ * @returns 0 on success, non-zero on failure.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorker The work to send the request to. The worker is
+ * on the idle list.
+ * @param pvMsg The message to send.
+ * @param cbMsg The size of the message.
+ * @param fNoRespawning Set if
+ * @param cVerbosity The verbosity level.
+ */
+static int kSubmitSendJobMessage(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, void const *pvMsg, uint32_t cbMsg,
+ int fNoRespawning, int cVerbosity)
+{
+ int cRetries;
+
+ /*
+ * Respawn the worker if it stopped by itself and we closed the pipe already.
+ */
+#ifdef KBUILD_OS_WINDOWS
+ if (pWorker->hPipe == INVALID_HANDLE_VALUE)
+#else
+ if (pWorker->fdSocket == -1)
+#endif
+ {
+ if (!fNoRespawning)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "Respawning worker (#1)...\n");
+ if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0)
+ return 2;
+ }
+
+ }
+
+ /*
+ * Restart-on-broken-pipe loop. Necessary?
+ */
+ for (cRetries = !fNoRespawning ? 1 : 0; ; cRetries--)
+ {
+ /*
+ * Try write the message.
+ */
+ uint32_t cbLeft = cbMsg;
+ uint8_t const *pbLeft = (uint8_t const *)pvMsg;
+#ifdef KBUILD_OS_WINDOWS
+ DWORD dwErr;
+ DWORD cbWritten;
+ while (WriteFile(pWorker->hPipe, pbLeft, cbLeft, &cbWritten, NULL /*pOverlapped*/))
+ {
+ assert(cbWritten <= cbLeft);
+ cbLeft -= cbWritten;
+ if (!cbLeft)
+ return 0;
+
+ /* This scenario shouldn't really ever happen. But just in case... */
+ pbLeft += cbWritten;
+ }
+ dwErr = GetLastError();
+ if ( ( dwErr != ERROR_BROKEN_PIPE
+ && dwErr != ERROR_NO_DATA)
+ || cRetries <= 0)
+ return errx(pCtx, 1, "Error writing to worker: %u", dwErr);
+#else
+ ssize_t cbWritten
+ while ((cbWritten = write(pWorker->fdSocket, pbLeft, cbLeft)) >= 0)
+ {
+ assert(cbWritten <= cbLeft);
+ cbLeft -= cbWritten;
+ if (!cbLeft)
+ return 0;
+
+ pbLeft += cbWritten;
+ }
+ if ( ( errno != EPIPE
+ && errno != ENOTCONN
+ && errno != ECONNRESET))
+ || cRetries <= 0)
+ return err(pCtx, 1, "Error writing to worker");
+# error "later"
+#endif
+
+ /*
+ * Broken connection. Try respawn the worker.
+ */
+ if (cVerbosity > 0)
+ warnx(pCtx, "Respawning worker (#2)...\n");
+ if (kSubmitRespawnWorker(pCtx, pWorker, cVerbosity) != 0)
+ return 2;
+ }
+}
+
+
+/**
+ * Closes the connection on a worker that said it is going to exit now.
+ *
+ * This is a way of dealing with imperfect resource management in the worker, it
+ * will monitor it a little and trigger a respawn when it looks bad.
+ *
+ * This function just closes the pipe / socket connection to the worker. The
+ * kSubmitSendJobMessage function will see this a trigger a respawn the next
+ * time the worker is engaged. This will usually mean there's a little delay in
+ * which the process can terminate without us having to actively wait for it.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ */
+static void kSubmitCloseConnectOnExitingWorker(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker)
+{
+#ifdef KBUILD_OS_WINDOWS
+ if (!CloseHandle(pWorker->hPipe))
+ warnx(pCtx, "CloseHandle(pWorker->hPipe): %u", GetLastError());
+ pWorker->hPipe = INVALID_HANDLE_VALUE;
+#else
+ if (close(pWorker->fdSocket) != 0)
+ warn(pCtx, "close(pWorker->fdSocket)");
+ pWorker->fdSocket = -1;
+#endif
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Handles read failure.
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ * @param dwErr The error code.
+ * @param pszWhere Where it failed.
+ */
+static int kSubmitWinReadFailed(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, DWORD dwErr, const char *pszWhere)
+{
+ DWORD dwExitCode;
+
+ if (pWorker->cbResultRead == 0)
+ errx(pCtx, 1, "%s/ReadFile failed: %u", pszWhere, dwErr);
+ else
+ errx(pCtx, 1, "%s/ReadFile failed: %u (read %u bytes)", pszWhere, dwErr, pWorker->cbResultRead);
+ assert(dwErr != 0);
+
+ /* Complete the result. */
+ pWorker->Result.s.rcExit = 127;
+ pWorker->Result.s.bWorkerExiting = 1;
+ pWorker->cbResultRead = sizeof(pWorker->Result);
+
+ if (GetExitCodeProcess(pWorker->hProcess, &dwExitCode))
+ {
+ if (dwExitCode != 0)
+ pWorker->Result.s.rcExit = dwExitCode;
+ }
+
+ return dwErr != 0 ? (int)(dwErr & 0x7fffffff) : 0x7fffffff;
+
+}
+
+
+/**
+ * Used by
+ * @returns 0 if we got the whole result, -1 if I/O is pending, and windows last
+ * error on ReadFile failure.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance.
+ */
+static int kSubmitReadMoreResultWin(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, const char *pszWhere)
+{
+ /*
+ * Set up the result read, telling the sub_proc.c unit about it.
+ */
+ while (pWorker->cbResultRead < sizeof(pWorker->Result))
+ {
+ DWORD cbRead = 0;
+
+ BOOL fRc = ResetEvent(pWorker->OverlappedRead.hEvent);
+ assert(fRc); (void)fRc;
+
+ pWorker->OverlappedRead.Offset = 0;
+ pWorker->OverlappedRead.OffsetHigh = 0;
+
+ if (!ReadFile(pWorker->hPipe, &pWorker->Result.ab[pWorker->cbResultRead],
+ sizeof(pWorker->Result) - pWorker->cbResultRead,
+ &cbRead,
+ &pWorker->OverlappedRead))
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_IO_PENDING)
+ return -1;
+ return kSubmitWinReadFailed(pCtx, pWorker, dwErr, pszWhere);
+ }
+
+ pWorker->cbResultRead += cbRead;
+ assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
+ }
+ return 0;
+}
+
+#endif /* KBUILD_OS_WINDOWS */
+
+
+/**
+ * Adds the given message to the history.
+ *
+ * @returns Pointer to old message, or NULL if no old msg to free.
+ * @param pWorker The worker instance.
+ * @param pvMsg The message.
+ * @param cbMsg The message size.
+ */
+static void *kSubmitUpdateHistory(PWORKERINSTANCE pWorker, void *pvMsg, size_t cbMsg)
+{
+ unsigned iHistory = pWorker->iHistory % K_ELEMENTS(pWorker->aHistory);
+ void *pvRet;
+ pWorker->iHistory++;
+ pvRet = pWorker->aHistory[iHistory].pvMsg;
+ pWorker->aHistory[iHistory].pvMsg = pvMsg;
+ pWorker->aHistory[iHistory].cbMsg = cbMsg;
+ return pvRet;
+}
+
+typedef struct HISTORYDUMPBUF
+{
+ char *pszBuf;
+ size_t cbBuf;
+ size_t off;
+ PKMKBUILTINCTX pCtx;
+} HISTORYDUMPBUF;
+
+
+static void kSubmitDumpHistoryWrite(HISTORYDUMPBUF *pBuf, const char *pch, size_t cch)
+{
+ if (pBuf->off + cch >= pBuf->cbBuf)
+ {
+ size_t cbNew = pBuf->cbBuf ? pBuf->cbBuf * 2 : 65536;
+ while (pBuf->off + cch >= cbNew)
+ cbNew *= 2;
+ pBuf->pszBuf = (char *)xrealloc(pBuf->pszBuf, cbNew);
+ pBuf->cbBuf = cbNew;
+ }
+
+ memcpy(&pBuf->pszBuf[pBuf->off], pch, cch);
+ pBuf->off += cch;
+ pBuf->pszBuf[pBuf->off] = '\0';
+}
+
+static void kSubmitDumpHistoryPrintf(HISTORYDUMPBUF *pBuf, const char *pszFormat, ...)
+{
+ char szTmp[32];
+ va_list va;
+ va_start(va, pszFormat);
+ for (;;)
+ {
+ const char *pszPct = strchr(pszFormat, '%');
+ if (!pszPct)
+ {
+ kSubmitDumpHistoryWrite(pBuf, pszFormat, strlen(pszFormat));
+ return;
+ }
+ if (pszPct != pszFormat)
+ {
+ kSubmitDumpHistoryWrite(pBuf, pszFormat, pszPct - pszFormat);
+ pszFormat = pszPct;
+ }
+ pszFormat++;
+ switch (*pszFormat++)
+ {
+ case 's':
+ {
+ const char * const psz = va_arg(va, const char *);
+ size_t const cch = strlen(psz);
+ if (cch == 0 || memchr(psz, '\'', cch))
+ {
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("\"")); /** @todo what if there are '"' in the string? */
+ kSubmitDumpHistoryWrite(pBuf, psz, cch);
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("\""));
+ }
+ else if ( !memchr(psz, ' ', cch)
+ && !memchr(psz, '\\', cch)
+ && !memchr(psz, '\t', cch)
+ && !memchr(psz, '\n', cch)
+ && !memchr(psz, '\r', cch)
+ && !memchr(psz, '&', cch)
+ && !memchr(psz, ';', cch)
+ && !memchr(psz, '|', cch))
+ kSubmitDumpHistoryWrite(pBuf, psz, cch);
+ else
+ {
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("'"));
+ kSubmitDumpHistoryWrite(pBuf, psz, strlen(psz));
+ kSubmitDumpHistoryWrite(pBuf, TUPLE("'"));
+ }
+ break;
+ }
+
+ case 'd':
+ {
+ int iValue = va_arg(va, int);
+ kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%d", iValue));
+ break;
+ }
+
+ case 'u':
+ {
+ unsigned uValue = va_arg(va, unsigned);
+ kSubmitDumpHistoryWrite(pBuf, szTmp, snprintf(szTmp, sizeof(szTmp), "%u", uValue));
+ break;
+ }
+
+ case '%':
+ kSubmitDumpHistoryWrite(pBuf, "%s", 1);
+ break;
+
+ default:
+ assert(0);
+ }
+ }
+ va_end(va);
+}
+
+static void kSubmitDumpHistoryFlush(HISTORYDUMPBUF *pBuf)
+{
+ if (pBuf->off > 0)
+ output_write_text(pBuf->pCtx->pOut, 1, pBuf->pszBuf, pBuf->off);
+ pBuf->off = 0;
+}
+
+/**
+ * Dumps the history for this worker to stderr in the given context.
+ *
+ * @param pCtx The command execution context. (Typically not a
+ * real context.)
+ * @param pWorker The worker instance.
+ */
+static void kSubmitDumpHistory(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker)
+{
+ HISTORYDUMPBUF Buf = { NULL, 0, 0, pCtx };
+ int iHistory = pWorker->iHistory;
+ unsigned cDumped = 0;
+
+ while (cDumped < K_ELEMENTS(pWorker->aHistory) && iHistory > 0)
+ {
+ unsigned const idx = (unsigned)--iHistory % K_ELEMENTS(pWorker->aHistory);
+ const char *pszMsg = (const char *)pWorker->aHistory[idx].pvMsg;
+ ssize_t cbMsg = pWorker->aHistory[idx].cbMsg;
+ const char *pszExe;
+ const char *pszCwd;
+ uint32_t i;
+ uint32_t cArgs;
+ const char *pszArgs;
+ size_t cbArgs;
+ uint32_t cEnvVars;
+ const char *pszEnvVars;
+ size_t cbEnvVars;
+ const char *pszSpecialEnv;
+ char fNoPchCaching;
+ char fWatcomBrainDamage;
+ uint32_t cPostArgs;
+ const char *pszPostArgs;
+ size_t cbPostArgs;
+
+ cDumped++;
+ if (!pszMsg || !cbMsg)
+ break;
+
+#define SKIP_BYTES(a_cbSkip) do { pszMsg += (a_cbSkip); cbMsg -= (a_cbSkip); } while (0)
+#define SKIP_STR() do { size_t const cbToSkip = strlen(pszMsg) + 1; SKIP_BYTES(cbToSkip); } while (0)
+#define SKIP_STRING_ARRAY(a_cStrings, a_cbPreable) do { \
+ for (i = 0; i < (a_cStrings) && cbMsg > 0; i++) { \
+ size_t const cbToSkip = (a_cbPreable) + strlen(pszMsg + (a_cbPreable)) + 1; \
+ SKIP_BYTES(cbToSkip); \
+ } } while (0)
+
+ /* Decode it: */
+ SKIP_BYTES(sizeof(uint32_t) + sizeof("JOB"));
+ pszExe = pszMsg;
+ SKIP_STR();
+ pszCwd = pszMsg;
+ SKIP_STR();
+
+ cArgs = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszArgs = pszMsg;
+ SKIP_STRING_ARRAY(cArgs, 1 /*fbFlags*/);
+ cbArgs = pszMsg - pszArgs;
+
+ cEnvVars = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszEnvVars = pszMsg;
+ SKIP_STRING_ARRAY(cEnvVars, 0);
+ cbEnvVars = pszMsg - pszEnvVars;
+
+ fWatcomBrainDamage = pszMsg[0] != '\0';
+ fNoPchCaching = pszMsg[1] != '\0';
+ SKIP_BYTES(2);
+
+ pszSpecialEnv = pszMsg;
+ SKIP_STR();
+
+ cPostArgs = *(uint32_t *)pszMsg;
+ SKIP_BYTES(sizeof(uint32_t));
+ pszPostArgs = pszMsg;
+ SKIP_STRING_ARRAY(cPostArgs, 0);
+ cbPostArgs = pszMsg - pszPostArgs;
+
+ /* Produce parseable output: */
+ kSubmitDumpHistoryPrintf(&Buf, "kWorker %u/%u:\n\tkSubmit", (long)pWorker->pid, iHistory, pszExe);
+ if (fNoPchCaching)
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" --no-pch-caching"));
+ if (fWatcomBrainDamage)
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" --watcom-brain-damage"));
+ if (pszSpecialEnv)
+ kSubmitDumpHistoryPrintf(&Buf, " --special-env %s", pszSpecialEnv);
+ kSubmitDumpHistoryPrintf(&Buf, " --chdir %s \\\n", pszCwd);
+
+ pszMsg = pszEnvVars;
+ cbMsg = cbEnvVars;
+ for (i = 0; i < cEnvVars && cbMsg > 0; i++)
+ {
+ kSubmitDumpHistoryPrintf(&Buf, "\t--putenv %s \\\n", pszMsg);
+ SKIP_STR();
+ }
+
+ if (cPostArgs > 0)
+ {
+ kSubmitDumpHistoryWrite(&Buf, TUPLE("\t--post-cmd "));
+ pszMsg = pszPostArgs;
+ cbMsg = cbPostArgs;
+ for (i = 0; i < cPostArgs && cbMsg > 0; i++)
+ {
+ kSubmitDumpHistoryPrintf(&Buf, " %s", pszMsg);
+ SKIP_STR();
+ }
+ kSubmitDumpHistoryWrite(&Buf, TUPLE(" \\\n"));
+ }
+ kSubmitDumpHistoryWrite(&Buf, TUPLE("\t-- \\\n"));
+
+ pszMsg = pszArgs;
+ cbMsg = cbArgs;
+ for (i = 0; i < cArgs && cbMsg > 0; i++)
+ {
+ SKIP_BYTES(1);
+ kSubmitDumpHistoryPrintf(&Buf, i + 1 < cArgs ? "\t%s \\\n" : "\t%s\n", pszMsg);
+ SKIP_STR();
+ }
+
+#undef SKIP_BYTES
+#undef SKIP_STR
+#undef SKIP_STRING_ARRAY
+ }
+
+ kSubmitDumpHistoryFlush(&Buf);
+ free(Buf.pszBuf);
+}
+
+
+/**
+ * Marks the worker active.
+ *
+ * On windows this involves setting up the async result read and telling
+ * sub_proc.c about the process.
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pWorker The worker instance to mark as active.
+ * @param cVerbosity The verbosity level.
+ * @param pChild The kmk child to associate the job with.
+ * @param pPidSpawned If @a *pPidSpawned is non-zero if the child is
+ * running, otherwise the worker is already done
+ * and we've returned the exit code of the job.
+ */
+static int kSubmitMarkActive(PKMKBUILTINCTX pCtx, PWORKERINSTANCE pWorker, int cVerbosity, struct child *pChild, pid_t *pPidSpawned)
+{
+#ifdef KBUILD_OS_WINDOWS
+ int rc;
+#endif
+
+ pWorker->cbResultRead = 0;
+
+#ifdef KBUILD_OS_WINDOWS
+ /*
+ * Setup the async result read on windows. If we're slow and the worker
+ * very fast, this may actually get the result immediately.
+ */
+l_again:
+ rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitMarkActive");
+ if (rc == -1)
+ {
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ if (process_kmk_register_submit(pWorker->OverlappedRead.hEvent, (intptr_t)pWorker, pPidSpawned) == 0)
+ { /* likely */ }
+ else
+ {
+ /* We need to do the waiting here because sub_proc.c has too much to do. */
+ warnx(pCtx, "Too many processes for sub_proc.c to handle!");
+ WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE);
+ goto l_again;
+ }
+# else
+ if (MkWinChildCreateSubmit((intptr_t)pWorker->OverlappedRead.hEvent, pWorker,
+ pWorker->pStdOut, pWorker->pStdErr, pChild, pPidSpawned) == 0)
+ { /* likely */ }
+ else
+ {
+ /* We need to do the waiting here because sub_proc.c has too much to do. */
+ warnx(pCtx, "MkWinChildCreateSubmit failed!");
+ WaitForSingleObject(pWorker->OverlappedRead.hEvent, INFINITE);
+ goto l_again;
+ }
+# endif
+ }
+ else
+ {
+ assert(rc == 0 || pWorker->Result.s.rcExit != 0);
+ if (pWorker->Result.s.bWorkerExiting)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+ if (pWorker->Result.s.rcExit && 1)
+ kSubmitDumpHistory(pCtx, pWorker);
+ *pPidSpawned = 0;
+ return pWorker->Result.s.rcExit;
+ }
+#endif
+
+ /*
+ * Mark it busy and move it to the active instance.
+ */
+ pWorker->pBusyWith = pChild;
+#ifndef KBUILD_OS_WINDOWS
+ *pPidSpawned = pWorker->pid;
+#endif
+
+ kSubmitListUnlink(&g_IdleList, pWorker);
+ kSubmitListAppend(&g_BusyList, pWorker);
+ return 0;
+}
+
+
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Retrieve the worker child result.
+ *
+ * If incomplete, we restart the ReadFile operation like kSubmitMarkActive does.
+ *
+ * @returns 0 on success, -1 if ReadFile was restarted.
+ * @param pvUser The worker instance.
+ * @param fBlock if we're to block waiting for the result or not.
+ * @param prcExit Where to return the exit code.
+ * @param piSigNo Where to return the signal number.
+ */
+int kSubmitSubProcGetResult(intptr_t pvUser, int fBlock, int *prcExit, int *piSigNo)
+{
+ PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser;
+ KMKBUILTINCTX FakeCtx = { "kSubmit/GetResult", NULL };
+ PKMKBUILTINCTX pCtx = &FakeCtx;
+
+ /*
+ * Get the overlapped result. There should be one since we're here
+ * because of a satisfied WaitForMultipleObject.
+ */
+ DWORD cbRead = 0;
+ if (GetOverlappedResult(pWorker->hPipe, &pWorker->OverlappedRead, &cbRead, fBlock ? TRUE : FALSE))
+ {
+ pWorker->cbResultRead += cbRead;
+ assert(pWorker->cbResultRead <= sizeof(pWorker->Result));
+
+ /* More to be read? */
+ while (pWorker->cbResultRead < sizeof(pWorker->Result))
+ {
+ int rc = kSubmitReadMoreResultWin(pCtx, pWorker, "kSubmitSubProcGetResult/more");
+ if (rc == -1)
+ return -1;
+ assert(rc == 0 || pWorker->Result.s.rcExit != 0);
+ }
+ assert(pWorker->cbResultRead == sizeof(pWorker->Result));
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_IO_INCOMPLETE && !fBlock)
+ return -1;
+ kSubmitWinReadFailed(pCtx, pWorker, dwErr, "kSubmitSubProcGetResult/result");
+ }
+
+ /*
+ * Okay, we've got a result.
+ */
+ *prcExit = pWorker->Result.s.rcExit;
+ switch (pWorker->Result.s.rcExit)
+ {
+ default: *piSigNo = 0; break;
+ case CONTROL_C_EXIT: *piSigNo = SIGINT; break;
+ case STATUS_INTEGER_DIVIDE_BY_ZERO: *piSigNo = SIGFPE; break;
+ case STATUS_ACCESS_VIOLATION: *piSigNo = SIGSEGV; break;
+ case STATUS_PRIVILEGED_INSTRUCTION:
+ case STATUS_ILLEGAL_INSTRUCTION: *piSigNo = SIGILL; break;
+ }
+ if (pWorker->Result.s.rcExit && pWorker->fDebugDumpHistoryOnFailure)
+ kSubmitDumpHistory(pCtx, pWorker);
+ if (pWorker->Result.s.bWorkerExiting)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+
+ return 0;
+}
+
+
+int kSubmitSubProcKill(intptr_t pvUser, int iSignal)
+{
+ return -1;
+}
+
+
+/**
+ * Called by process_cleanup when it's done with the worker.
+ *
+ * @param pvUser The worker instance.
+ */
+void kSubmitSubProcCleanup(intptr_t pvUser)
+{
+ PWORKERINSTANCE pWorker = (PWORKERINSTANCE)pvUser;
+ kSubmitListUnlink(&g_BusyList, pWorker);
+ kSubmitListAppend(&g_IdleList, pWorker);
+}
+
+#endif /* KBUILD_OS_WINDOWS */
+
+
+/**
+ * atexit callback that trigger worker termination.
+ */
+static void kSubmitAtExitCallback(void)
+{
+ PWORKERINSTANCE pWorker;
+ DWORD msStartTick;
+ DWORD cKillRaids = 0;
+ KMKBUILTINCTX FakeCtx = { "kSubmit/atexit", NULL };
+ PKMKBUILTINCTX pCtx = &FakeCtx;
+
+ /*
+ * Tell all the workers to exit by breaking the connection.
+ */
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ kSubmitCloseConnectOnExitingWorker(pCtx, pWorker);
+
+ /*
+ * Wait a little while for them to stop.
+ */
+ Sleep(0);
+ msStartTick = GetTickCount();
+ for (;;)
+ {
+ /*
+ * Collect handles of running processes.
+ */
+ PWORKERINSTANCE apWorkers[MAXIMUM_WAIT_OBJECTS];
+ HANDLE ahHandles[MAXIMUM_WAIT_OBJECTS];
+ DWORD cHandles = 0;
+
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ {
+ if (cHandles < MAXIMUM_WAIT_OBJECTS)
+ {
+ apWorkers[cHandles] = pWorker;
+ ahHandles[cHandles] = pWorker->hProcess;
+ }
+ cHandles++;
+ }
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ {
+ if (cHandles < MAXIMUM_WAIT_OBJECTS)
+ {
+ apWorkers[cHandles] = pWorker;
+ ahHandles[cHandles] = pWorker->hProcess;
+ }
+ cHandles++;
+ }
+ if (cHandles == 0)
+ return;
+
+ /*
+ * Wait for the processes.
+ */
+ for (;;)
+ {
+ DWORD cMsElapsed = GetTickCount() - msStartTick;
+ DWORD dwWait = WaitForMultipleObjects(cHandles <= MAXIMUM_WAIT_OBJECTS ? cHandles : MAXIMUM_WAIT_OBJECTS,
+ ahHandles, FALSE /*bWaitAll*/,
+ cMsElapsed < 5000 ? 5000 - cMsElapsed + 16 : 16);
+ if ( dwWait >= WAIT_OBJECT_0
+ && dwWait <= WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS)
+ {
+ size_t idx = dwWait - WAIT_OBJECT_0;
+ CloseHandle(apWorkers[idx]->hProcess);
+ apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
+
+ if (cHandles <= MAXIMUM_WAIT_OBJECTS)
+ {
+ /* Restart the wait with the worker removed, or quit if it was the last worker. */
+ cHandles--;
+ if (!cHandles)
+ return;
+ if (idx != cHandles)
+ {
+ apWorkers[idx] = apWorkers[cHandles];
+ ahHandles[idx] = ahHandles[cHandles];
+ }
+ continue;
+ }
+ /* else: Reconstruct the wait array so we get maximum coverage. */
+ }
+ else if (dwWait == WAIT_TIMEOUT)
+ {
+ /* Terminate the whole bunch. */
+ cKillRaids++;
+ if (cKillRaids == 1 && getenv("KMK_KSUBMIT_NO_KILL") == NULL)
+ {
+ warnx(pCtx, "Killing %u lingering worker processe(s)!\n", cHandles);
+ for (pWorker = g_IdleList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
+ for (pWorker = g_BusyList.pHead; pWorker != NULL; pWorker = pWorker->pNext)
+ if (pWorker->hProcess != INVALID_HANDLE_VALUE)
+ TerminateProcess(pWorker->hProcess, WAIT_TIMEOUT);
+ }
+ else
+ {
+ warnx(pCtx, "Giving up on the last %u worker processe(s). :-(\n", cHandles);
+ return;
+ }
+ }
+ else
+ {
+ /* Some kind of wait error. Could be a bad handle, check each and remove
+ bad ones as well as completed ones. */
+ size_t idx;
+ warnx(pCtx, "WaitForMultipleObjects unexpectedly returned %#u (err=%u)\n",
+ dwWait, GetLastError());
+ for (idx = 0; idx < cHandles; idx++)
+ {
+ dwWait = WaitForSingleObject(ahHandles[idx], 0 /*ms*/);
+ if (dwWait != WAIT_TIMEOUT)
+ {
+ CloseHandle(apWorkers[idx]->hProcess);
+ apWorkers[idx]->hProcess = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+ break;
+ } /* wait loop */
+ } /* outer wait loop */
+}
+
+
+static int kmk_builtin_kSubmit_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-Z|--zap-env] [-E|--set <var=val>] [-U|--unset <var=val>]\n"
+ " [-A|--append <var=val>] [-D|--prepend <var=val>]\n"
+ " [-s|--special-env <var=val>] [-C|--chdir <dir>]\n"
+ " [--wcc-brain-damage] [--no-pch-caching]\n"
+ " [-3|--32-bit] [-6|--64-bit] [-v] [--debug-dump-history]\n"
+ " [-P|--post-cmd <cmd> [args]] -- <program> [args]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ " -Z, --zap-env, -i, --ignore-environment\n"
+ " Zaps the environment. Position dependent.\n"
+ " -E, --set <var>=[value]\n"
+ " Sets an environment variable putenv fashion. Position dependent.\n"
+ " -U, --unset <var>\n"
+ " Removes an environment variable. Position dependent.\n"
+ " -A, --append <var>=<value>\n"
+ " Appends the given value to the environment variable.\n"
+ " -D,--prepend <var>=<value>\n"
+ " Prepends the given value to the environment variable.\n"
+ " -s,--special-env <var>=<value>\n"
+ " Same as --set, but flags the variable for further expansion\n"
+ " within kWorker. Replacements:\n"
+ " @@PROCESSOR_GROUP@@ - The processor group number.\n"
+ " @@AUTHENTICATION_ID@@ - The authentication ID from the process token.\n"
+ " @@PID@@ - The kWorker process ID.\n"
+ " @@@@ - Escaped \"@@\".\n"
+ " @@DEBUG_COUNTER@@ - An ever increasing counter (starts at zero).\n"
+ " -C, --chdir <dir>\n"
+ " Specifies the current directory for the program. Relative paths\n"
+ " are relative to the previous -C option. Default is getcwd value.\n"
+ " -3, --32-bit\n"
+ " Selects a 32-bit kWorker process. Default: kmk bit count\n"
+ " -6, --64-bit\n"
+ " Selects a 64-bit kWorker process. Default: kmk bit count\n"
+ " --wcc-brain-damage\n"
+ " Works around wcc and wcc386 (Open Watcom) not following normal\n"
+ " quoting conventions on Windows, OS/2, and DOS.\n"
+ " --no-pch-caching\n"
+ " Do not cache precompiled header files because they're being created.\n"
+ " -v,--verbose\n"
+ " More verbose execution.\n"
+ " --debug-dump-history\n"
+ " Dump the history as of the submitted command. Handy for debugging\n"
+ " trouble caused by a previous job.\n"
+ " --debug-dump-history-on-failure, --no-debug-dump-history-on-failure\n"
+ " Dump the history on failure. Can also be enabled by a non-empty\n"
+ " KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE variable (first invocation only).\n"
+ " -P|--post-cmd <cmd> ...\n"
+ " For running a built-in command on the output, specifying the command\n"
+ " and all it's parameters. Currently supported commands:\n"
+ " kDepObj\n"
+ " -V,--version\n"
+ " Show the version number.\n"
+ " -h,--help\n"
+ " Show this usage information.\n"
+ "\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 2;
+}
+
+
+int kmk_builtin_kSubmit(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+#ifdef KBUILD_OS_WINDOWS
+ static int s_fInitialized = 0;
+#endif
+ int rcExit = 0;
+ int iArg;
+ unsigned cAllocatedEnvVars;
+ unsigned cEnvVars;
+ char **papszEnvVars;
+ const char *pszExecutable = NULL;
+ const char *pszSpecialEnv = NULL;
+ int iPostCmd = argc;
+ int cPostCmdArgs = 0;
+ unsigned cBitsWorker = g_cArchBits;
+ int fWatcomBrainDamage = 0;
+ int fNoPchCaching = 0;
+ int fDebugDumpHistory = 0;
+ static int s_fDebugDumpHistoryOnFailure = -1;
+ int fDebugDumpHistoryOnFailure = s_fDebugDumpHistoryOnFailure;
+ int cVerbosity = 0;
+ size_t const cbCwdBuf = GET_PATH_MAX;
+ PATH_VAR(szCwd);
+
+#ifdef KBUILD_OS_WINDOWS
+ /*
+ * First time thru we must perform some initializations.
+ */
+ if (s_fInitialized)
+ { }
+ else
+ {
+ MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator);
+# if K_ARCH_BITS == 64
+ MkWinChildInitCpuGroupAllocator(&g_SubmitProcessorGroupAllocator32);
+# endif
+ *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(GetModuleHandleW(L"KERNEL32.DLL"), "SetThreadGroupAffinity");
+ s_fInitialized = 1;
+ }
+#endif
+ if (fDebugDumpHistoryOnFailure != -1)
+ { /* likely */ }
+ else
+ {
+ struct variable *pVar = lookup_variable(TUPLE("KMK_KSUBMIT_DUMP_HISTORY_ON_FAILURE"));
+ fDebugDumpHistoryOnFailure = pVar && *pVar->value != '\0';
+ s_fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure;
+ }
+
+ /*
+ * Create default program environment.
+ *
+ * Note! We only clean up the environment on successful return, assuming
+ * make will stop after that.
+ */
+ if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
+ { /* likely */ }
+ else
+ return err(pCtx, 1, "getcwd_fs failed\n");
+
+ /* The environment starts out in read-only mode and will be duplicated if modified. */
+ cAllocatedEnvVars = 0;
+ papszEnvVars = envp;
+ cEnvVars = 0;
+ while (papszEnvVars[cEnvVars] != NULL)
+ cEnvVars++;
+
+ /*
+ * Parse the command line.
+ */
+ for (iArg = 1; iArg < argc; iArg++)
+ {
+ const char *pszArg = argv[iArg];
+ if (*pszArg == '-')
+ {
+ char chOpt = *++pszArg;
+ pszArg++;
+ if (chOpt != '-')
+ {
+ if (chOpt != '\0')
+ { /* likely */ }
+ else
+ {
+ errx(pCtx, 1, "Incomplete option: '-'");
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ }
+ else
+ {
+ /* '--' indicates where the bits to execute start. */
+ if (*pszArg == '\0')
+ {
+ iArg++;
+ break;
+ }
+
+ if ( strcmp(pszArg, "wcc-brain-damage") == 0
+ || strcmp(pszArg, "watcom-brain-damage") == 0)
+ {
+ fWatcomBrainDamage = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "no-pch-caching") == 0)
+ {
+ fNoPchCaching = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "debug-dump-history") == 0)
+ {
+ fDebugDumpHistory = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "debug-dump-history-on-failure") == 0)
+ {
+ fDebugDumpHistoryOnFailure = 1;
+ continue;
+ }
+
+ if (strcmp(pszArg, "no-debug-dump-history-on-failure") == 0)
+ {
+ fDebugDumpHistoryOnFailure = 0;
+ continue;
+ }
+
+ /* convert to short. */
+ if (strcmp(pszArg, "help") == 0)
+ chOpt = 'h';
+ else if (strcmp(pszArg, "version") == 0)
+ chOpt = 'V';
+ else if (strcmp(pszArg, "set") == 0)
+ chOpt = 'E';
+ else if (strcmp(pszArg, "append") == 0)
+ chOpt = 'A';
+ else if (strcmp(pszArg, "prepend") == 0)
+ chOpt = 'D';
+ else if (strcmp(pszArg, "unset") == 0)
+ chOpt = 'U';
+ else if ( strcmp(pszArg, "zap-env") == 0
+ || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
+ chOpt = 'Z';
+ else if (strcmp(pszArg, "chdir") == 0)
+ chOpt = 'C';
+ else if (strcmp(pszArg, "set-special") == 0)
+ chOpt = 's';
+ else if (strcmp(pszArg, "post-cmd") == 0)
+ chOpt = 'P';
+ else if (strcmp(pszArg, "32-bit") == 0)
+ chOpt = '3';
+ else if (strcmp(pszArg, "64-bit") == 0)
+ chOpt = '6';
+ else if (strcmp(pszArg, "verbose") == 0)
+ chOpt = 'v';
+ else if (strcmp(pszArg, "executable") == 0)
+ chOpt = 'e';
+ else
+ {
+ errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ pszArg = "";
+ }
+
+ do
+ {
+ /* Get option value first, if the option takes one. */
+ const char *pszValue = NULL;
+ switch (chOpt)
+ {
+ case 'A':
+ case 'C':
+ case 'E':
+ case 'U':
+ case 'D':
+ case 'e':
+ case 's':
+ if (*pszArg != '\0')
+ pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
+ else if (++iArg < argc)
+ pszValue = argv[iArg];
+ else
+ {
+ errx(pCtx, 1, "Option -%c requires a value!", chOpt);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ break;
+ }
+
+ switch (chOpt)
+ {
+ case 'Z':
+ case 'i': /* GNU env compatibility. */
+ rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'E':
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'A':
+ rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'D':
+ rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'U':
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'C':
+ rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 's':
+ if (pszSpecialEnv)
+ return errx(pCtx, 1, "The -s option can only be used once!");
+ pszSpecialEnv = pszValue;
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ if (rcExit == 0)
+ break;
+ return rcExit;
+
+ case 'P':
+ if (cPostCmdArgs > 0)
+ return errx(pCtx, 1, "The -P option can only be used once!");
+ if (*pszArg != '\0')
+ return errx(pCtx, 1, "The cmd part of the -P needs to be a separate argument!");
+ iPostCmd = ++iArg;
+ if (iArg >= argc)
+ return errx(pCtx, 1, "The -P option requires a command following it!");
+ while (iArg < argc && strcmp(argv[iArg], "--") != 0)
+ iArg++;
+ cPostCmdArgs = iArg - iPostCmd;
+ iArg--;
+ break;
+
+ case '3':
+ cBitsWorker = 32;
+ break;
+
+ case '6':
+ cBitsWorker = 64;
+ break;
+
+ case 'e':
+ pszExecutable = pszValue;
+ break;
+
+ case 'v':
+ cVerbosity++;
+ break;
+
+ case 'h':
+ kmk_builtin_kSubmit_usage(pCtx, 0);
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return 0;
+
+ case 'V':
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return kbuild_version(argv[0]);
+ }
+ } while ((chOpt = *pszArg++) != '\0');
+ }
+ else
+ {
+ errx(pCtx, 1, "Unknown argument: '%s'", pszArg);
+ return kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+ }
+
+ /*
+ * Check that we've got something to execute.
+ */
+ if (iArg < argc)
+ {
+ uint32_t cbMsg;
+ void *pvMsg = kSubmitComposeJobMessage(pszExecutable, &argv[iArg], papszEnvVars, szCwd,
+ fWatcomBrainDamage, fNoPchCaching, pszSpecialEnv,
+ &argv[iPostCmd], cPostCmdArgs, &cbMsg);
+ PWORKERINSTANCE pWorker = kSubmitSelectWorkSpawnNewIfNecessary(pCtx, cBitsWorker, cVerbosity);
+ if (pWorker)
+ {
+ /* Before we send off the job, we should dump pending output, since
+ the kWorker process currently does not coordinate its output with
+ the output.c mechanics. */
+#ifdef CONFIG_NEW_WIN_CHILDREN
+ if (pCtx->pOut && !pWorker->pStdOut)
+#else
+ if (pCtx->pOut)
+#endif
+ output_dump(pCtx->pOut);
+ pWorker->fDebugDumpHistoryOnFailure = fDebugDumpHistoryOnFailure;
+ rcExit = kSubmitSendJobMessage(pCtx, pWorker, pvMsg, cbMsg, 0 /*fNoRespawning*/, cVerbosity);
+ if (rcExit == 0)
+ {
+ pvMsg = kSubmitUpdateHistory(pWorker, pvMsg, cbMsg);
+ if (fDebugDumpHistory)
+ kSubmitDumpHistory(pCtx, pWorker);
+ rcExit = kSubmitMarkActive(pCtx, pWorker, cVerbosity, pChild, pPidSpawned);
+ }
+
+ if (!g_fAtExitRegistered)
+ if (atexit(kSubmitAtExitCallback) == 0)
+ g_fAtExitRegistered = 1;
+ }
+ else
+ rcExit = 1;
+ free(pvMsg);
+ }
+ else
+ {
+ errx(pCtx, 1, "Nothing to executed!");
+ rcExit = kmk_builtin_kSubmit_usage(pCtx, 1);
+ }
+
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ return rcExit;
+}
+
diff --git a/src/kmk/kmkbuiltin/kbuild_protection.c b/src/kmk/kmkbuiltin/kbuild_protection.c
new file mode 100644
index 0000000..373fd88
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kbuild_protection.c
@@ -0,0 +1,376 @@
+/* $Id: kbuild_protection.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Simple File Protection.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#if defined(_MSC_VER) || defined(__OS2__)
+# include <limits.h>
+# include <direct.h>
+#else
+# include <unistd.h>
+#endif
+#include "kbuild_protection.h"
+#include "err.h"
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define KBUILD_PROTECTION_MAGIC 0x00111100
+
+#if defined(__EMX__) || defined(_MSC_VER)
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+# define DEFAULT_PROTECTION_DEPTH 1
+#else
+# define IS_SLASH(ch) ( (ch) == '/' )
+# define DEFAULT_PROTECTION_DEPTH 2
+#endif
+
+
+
+/**
+ * Counts the components in the specified sub path.
+ * This is a helper for count_path_components.
+ *
+ * etc = 1
+ * etc/ = 1
+ * etc/x11 = 2
+ * and so and and so forth.
+ */
+static int countSubPathComponents(const char *pszPath, int cDepth)
+{
+ for (;;)
+ {
+ const char *pszEnd;
+ size_t cch;
+
+ /* skip slashes. */
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ if (!*pszPath)
+ break;
+
+ /* find end of component. */
+ pszEnd = pszPath;
+ while (!IS_SLASH(*pszEnd) && *pszEnd)
+ pszEnd++;
+
+ /* count it, checking for '..' and '.'. */
+ cch = pszEnd - pszPath;
+ if (cch == 2 && pszPath[0] == '.' && pszPath[1] == '.')
+ {
+ if (cDepth > 0)
+ cDepth--;
+ }
+ else if (cch != 1 || pszPath[0] != '.')
+ cDepth++;
+
+ /* advance */
+ if (!*pszEnd)
+ break;
+ pszPath = pszEnd + 1;
+ }
+ return cDepth;
+}
+
+
+/**
+ * Parses the specified path counting the number of components
+ * relative to root.
+ *
+ * We don't check symbolic links and such, just some simple and cheap
+ * path parsing.
+ *
+ * @param pszPath The path to process.
+ *
+ * @returns 0 or higher on success.
+ * On failure an error is printed, eval is set and -1 is returned.
+ */
+static int countPathComponents(PCKBUILDPROTECTION pThis, const char *pszPath)
+{
+ int cComponents = 0;
+
+ /*
+ * Deal with root, UNC, drive letter.
+ */
+#if defined(_MSC_VER) || defined(__OS2__)
+ if (IS_SLASH(pszPath[0]) && IS_SLASH(pszPath[1]) && !IS_SLASH(pszPath[2]))
+ {
+ /* skip the root - UNC */
+ pszPath += 3;
+ while (!IS_SLASH(*pszPath) && *pszPath) /* server name */
+ pszPath++;
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ while (!IS_SLASH(*pszPath) && *pszPath) /* share name */
+ pszPath++;
+ while (IS_SLASH(*pszPath))
+ pszPath++;
+ }
+ else
+ {
+ unsigned uDriveLetter = (unsigned)toupper(pszPath[0]) - (unsigned)'A';
+ if (uDriveLetter <= (unsigned)('Z' - 'A') && pszPath[1] == ':')
+ uDriveLetter++; /* A == 1 */
+ else
+ uDriveLetter = 0; /* 0 == default */
+
+ if (!IS_SLASH(pszPath[uDriveLetter ? 2 : 0]))
+ {
+ /*
+ * Relative path, must count cwd depth first.
+ */
+#ifdef __OS2__ /** @todo remove when ticket 194 has been fixed */
+ char *pszCwd = _getdcwd(uDriveLetter, NULL, PATH_MAX);
+#else
+ char *pszCwd = _getdcwd(uDriveLetter, NULL, 0);
+#endif
+ char *pszTmp = pszCwd;
+ if (!pszTmp)
+ {
+ err(pThis->pCtx, 1, "_getdcwd");
+ return -1;
+ }
+
+ if (IS_SLASH(pszTmp[0]) && IS_SLASH(pszTmp[1]))
+ {
+ /* skip the root - UNC */
+ pszTmp += 2;
+ while (!IS_SLASH(*pszTmp) && *pszTmp) /* server name */
+ pszTmp++;
+ while (IS_SLASH(*pszTmp))
+ pszTmp++;
+ while (!IS_SLASH(*pszTmp) && *pszTmp) /* share name */
+ pszTmp++;
+ }
+ else
+ {
+ /* skip the drive letter and while we're at it, the root slash too. */
+ pszTmp += 1 + (pszTmp[1] == ':');
+ }
+ cComponents = countSubPathComponents(pszTmp, 0);
+ free(pszCwd);
+ }
+ else
+ {
+ /* skip the drive letter and while we're at it, the root slash too. */
+ pszPath += uDriveLetter ? 3 : 1;
+ }
+ }
+#else /* !WIN && !OS2 */
+ if (!IS_SLASH(pszPath[0]))
+ {
+ /*
+ * Relative path, must count cwd depth first.
+ */
+ char szCwd[4096];
+ if (!getcwd(szCwd, sizeof(szCwd)))
+ {
+ err(pThis->pCtx, 1, "getcwd");
+ return -1;
+ }
+ cComponents = countSubPathComponents(szCwd, 0);
+ }
+#endif /* !WIN && !OS2 */
+
+ /*
+ * We're now past any UNC or drive letter crap, possibly positioned
+ * at the root slash or at the start of a path component at the
+ * given depth. Count the remainder.
+ */
+ return countSubPathComponents(pszPath, cComponents);
+}
+
+
+/**
+ * Initializes the instance data.
+ *
+ * @param pThis Pointer to the instance data.
+ */
+void kBuildProtectionInit(PKBUILDPROTECTION pThis, PKMKBUILTINCTX pCtx)
+{
+ pThis->uMagic = KBUILD_PROTECTION_MAGIC;
+ pThis->pCtx = pCtx;
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] = 0;
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] = 1;
+ pThis->cProtectionDepth = DEFAULT_PROTECTION_DEPTH;
+}
+
+
+/**
+ * Destroys the instance data.
+ *
+ * @param pThis Pointer to the instance data.
+ */
+void kBuildProtectionTerm(PKBUILDPROTECTION pThis)
+{
+ pThis->uMagic = 0;
+}
+
+
+void kBuildProtectionEnable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+ pThis->afTypes[enmType] |= 1;
+}
+
+
+void kBuildProtectionDisable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+ pThis->afTypes[enmType] &= ~1U;
+}
+
+
+/**
+ * Sets the protection depth according to the option argument.
+ *
+ * @param pszValue The value.
+ *
+ * @returns 0 on success, -1 and errx on failure.
+ */
+int kBuildProtectionSetDepth(PKBUILDPROTECTION pThis, const char *pszValue)
+{
+ /* skip leading blanks, they don't count either way. */
+ while (isspace(*pszValue))
+ pszValue++;
+
+ /* number or path? */
+ if (!isdigit(*pszValue) || strpbrk(pszValue, ":/\\"))
+ pThis->cProtectionDepth = countPathComponents(pThis, pszValue);
+ else
+ {
+ char *pszMore = 0;
+ pThis->cProtectionDepth = strtol(pszValue, &pszMore, 0);
+ if (pThis->cProtectionDepth != 0 && pszMore)
+ {
+ /* trailing space is harmless. */
+ while (isspace(*pszMore))
+ pszMore++;
+ }
+ if (!pThis->cProtectionDepth || pszValue == pszMore || *pszMore)
+ return errx(pThis->pCtx, 1, "bogus protection depth: %s", pszValue);
+ }
+
+ if (pThis->cProtectionDepth < 1)
+ return errx(pThis->pCtx, 1, "bogus protection depth: %s", pszValue);
+ return 0;
+}
+
+
+/**
+ * Scans the environment for option overrides.
+ *
+ * @param pThis Pointer to the instance data.
+ * @param papszEnv The environment array.
+ * @param pszPrefix The variable prefix.
+ *
+ * @returns 0 on success, -1 and err*() on failure.
+ */
+int kBuildProtectionScanEnv(PKBUILDPROTECTION pThis, char **papszEnv, const char *pszPrefix)
+{
+ unsigned i;
+ const size_t cchPrefix = strlen(pszPrefix);
+
+ for (i = 0; papszEnv[i]; i++)
+ {
+ const char *pszVar = papszEnv[i];
+ if (!strncmp(pszVar, pszPrefix, cchPrefix))
+ {
+ pszVar += cchPrefix;
+ if (!strncmp(pszVar, "PROTECTION_DEPTH=", sizeof("PROTECTION_DEPTH=") - 1))
+ {
+ const char *pszVal = pszVar + sizeof("PROTECTION_DEPTH=") - 1;
+ if (kBuildProtectionSetDepth(pThis, pszVal))
+ return -1;
+ }
+ else if (!strncmp(pszVar, "DISABLE_PROTECTION=", sizeof("DISABLE_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] &= ~1U;
+ else if (!strncmp(pszVar, "ENABLE_PROTECTION=", sizeof("ENABLE_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_RECURSIVE] |= 3;
+ else if (!strncmp(pszVar, "DISABLE_FULL_PROTECTION=", sizeof("DISABLE_FULL_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] &= ~1U;
+ else if (!strncmp(pszVar, "ENABLE_FULL_PROTECTION=", sizeof("ENABLE_FULL_PROTECTION=") - 1))
+ pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] |= 3;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Protect the upper layers of the file system against accidental
+ * or malicious deletetion attempt from within a makefile.
+ *
+ * @param pszPath The path to check.
+ * @param required_depth The minimum number of components in the
+ * path counting from the root.
+ *
+ * @returns 0 on success.
+ * On failure an error is printed and -1 is returned.
+ */
+int kBuildProtectionEnforce(PCKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType, const char *pszPath)
+{
+ assert(pThis->uMagic == KBUILD_PROTECTION_MAGIC);
+ assert(enmType < KBUILDPROTECTIONTYPE_MAX && enmType >= KBUILDPROTECTIONTYPE_FIRST);
+
+ if ( (pThis->afTypes[enmType] & 3)
+ || (pThis->afTypes[KBUILDPROTECTIONTYPE_FULL] & 3))
+ {
+ /*
+ * Count the path and compare it with the required depth.
+ */
+ int cComponents = countPathComponents(pThis, pszPath);
+ if (cComponents < 0)
+ return -1;
+ if ((unsigned int)cComponents <= pThis->cProtectionDepth)
+ {
+ errx(pThis->pCtx, 1, "%s: protected", pszPath);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Retrieve the default path protection depth.
+ *
+ * @returns the default value.
+ */
+int kBuildProtectionDefaultDepth(void)
+{
+ return DEFAULT_PROTECTION_DEPTH;
+}
+
diff --git a/src/kmk/kmkbuiltin/kbuild_protection.h b/src/kmk/kmkbuiltin/kbuild_protection.h
new file mode 100644
index 0000000..4e8aed1
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kbuild_protection.h
@@ -0,0 +1,67 @@
+/* $Id: kbuild_protection.h 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * Simple File Protection.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___kbuild_protection_h
+#define ___kbuild_protection_h
+
+
+/**
+ * The different protection types.
+ */
+typedef enum
+{
+ KBUILDPROTECTIONTYPE_FIRST = 0,
+ KBUILDPROTECTIONTYPE_RECURSIVE = KBUILDPROTECTIONTYPE_FIRST,
+ KBUILDPROTECTIONTYPE_FULL,
+ KBUILDPROTECTIONTYPE_MAX
+} KBUILDPROTECTIONTYPE;
+
+
+/**
+ * The instance data.
+ * Don't touch.
+ */
+typedef struct KBUILDPROTECTION
+{
+ unsigned int uMagic;
+ unsigned int cProtectionDepth;
+ struct KMKBUILTINCTX *pCtx;
+ unsigned char afTypes[KBUILDPROTECTIONTYPE_MAX];
+} KBUILDPROTECTION;
+typedef KBUILDPROTECTION *PKBUILDPROTECTION;
+typedef const KBUILDPROTECTION *PCKBUILDPROTECTION;
+
+
+void kBuildProtectionInit(PKBUILDPROTECTION pThis, struct KMKBUILTINCTX *pCtx);
+void kBuildProtectionTerm(PKBUILDPROTECTION pThis);
+int kBuildProtectionScanEnv(PKBUILDPROTECTION pThis, char **papszEnv, const char *pszPrefix);
+void kBuildProtectionEnable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType);
+void kBuildProtectionDisable(PKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType);
+int kBuildProtectionSetDepth(PKBUILDPROTECTION pThis, const char *pszValue);
+int kBuildProtectionEnforce(PCKBUILDPROTECTION pThis, KBUILDPROTECTIONTYPE enmType, const char *pszPath);
+int kBuildProtectionDefaultDepth(void);
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/kill.c b/src/kmk/kmkbuiltin/kill.c
new file mode 100644
index 0000000..7ba6f17
--- /dev/null
+++ b/src/kmk/kmkbuiltin/kill.c
@@ -0,0 +1,653 @@
+/* $Id: kill.c 3352 2020-06-05 00:31:50Z bird $ */
+/** @file
+ * kmk kill - Process killer.
+ */
+
+/*
+ * Copyright (c) 2007-2020 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <assert.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef WINDOWS32
+# include <process.h>
+# include <Windows.h>
+# include <tlhelp32.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "err.h"
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** @name kmkKillMatchName flags
+ * @{ */
+#define KMKKILL_FN_EXE_SUFF 1
+#define KMKKILL_FN_WILDCARD 2
+#define KMKKILL_FN_WITH_PATH 4
+#define KMKKILL_FN_ROOT_SLASH 8
+/** @} */
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct KMKKILLGLOBALS
+{
+ PKMKBUILTINCTX pCtx;
+ int cVerbosity;
+ const char *pszCur;
+} KMKKILLGLOBALS;
+
+
+
+/**
+ * Kill one process by it's PID.
+ *
+ * The name is optional and only for messaging.
+ */
+static int kmkKillProcessByPid(KMKKILLGLOBALS *pThis, pid_t pid, const char *pszName)
+{
+ int rcExit;
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE /*bInherit*/, pid);
+ if (!hProcess)
+ rcExit = errx(pThis->pCtx, 1, "Failed to open pid %u (%#x%s%s): %u",
+ pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
+ else
+ {
+ if (!TerminateProcess(hProcess, DBG_TERMINATE_PROCESS))
+ rcExit = errx(pThis->pCtx, 1, "TerminateProcess failed on pid %u (%#x%s%s): %u",
+ pid, pid, pszName ? ", " : "", pszName ? pszName : "", GetLastError());
+ else if (pThis->cVerbosity > 0)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "Terminated %u (%#x)%s%s\n",
+ pid, pid, pszName ? " - " : "", pszName ? pszName : "");
+ CloseHandle(hProcess);
+ }
+ return rcExit;
+}
+
+
+/**
+ * Does simple wilcard filename matching.
+ *
+ * @returns 1 on match, 0 on mismatch.
+ * @param pszPattern The pattern.
+ * @param cchPattern The length of the pattern.
+ * @param pchFilename The filename to match. This does not have to be
+ * terminated at @a cchFilename.
+ * @param cchFilename The length of the filename that we should match.
+ * @param cDepth The recursion depth.
+ */
+static int kmkKillMatchWildcard(const char *pszPattern, size_t cchPattern,
+ const char *pchFilename, size_t cchFilename, unsigned cDepth)
+{
+ while (cchPattern > 0 && cchFilename > 0)
+ {
+ char chPat = *pszPattern++;
+ cchPattern--;
+ if (chPat != '*')
+ {
+ if (chPat != '?')
+ {
+ char chExe = *pchFilename;
+ if ( chExe != chPat
+ && tolower(chExe) != tolower(chPat))
+ return 0;
+ }
+ pchFilename++;
+ cchFilename--;
+ }
+ else
+ {
+ size_t off, cchExeMin;
+
+ while (cchPattern > 0 && *pszPattern == '*')
+ {
+ pszPattern++;
+ cchPattern--;
+ }
+
+ /* Trailing '*'? */
+ if (!cchPattern)
+ return 1;
+
+ /* Final wildcard? Match the tail. */
+ if (memchr(pszPattern, '*', cchPattern) == NULL)
+ {
+ if (memchr(pszPattern, '?', cchPattern) == NULL)
+ return cchFilename >= cchPattern
+ && strnicmp(&pchFilename[cchFilename - cchPattern], pszPattern, cchPattern) == 0;
+
+ /* Only '?', so we know the exact length of this '*' match and can do a single tail matching. */
+ return cchFilename >= cchPattern
+ && kmkKillMatchWildcard(pszPattern, cchPattern, &pchFilename[cchFilename - cchPattern], cchPattern, cDepth + 1);
+ }
+
+ /*
+ * More wildcards ('*'), so we need to need to try out all matching
+ * length for this one. We start by counting non-asterisks chars
+ * in the remaining pattern so we know when to stop trying.
+ * This must be at least 1 char.
+ */
+ if (cDepth >= 32)
+ return 0;
+
+ for (off = cchExeMin = 0; off < cchPattern; off++)
+ cchExeMin += pszPattern[off] != '*';
+ assert(cchExeMin > 0);
+
+ while (cchFilename >= cchExeMin)
+ {
+ if (kmkKillMatchWildcard(pszPattern, cchPattern, pchFilename, cchFilename, cDepth + 1))
+ return 1;
+ /* next */
+ pchFilename++;
+ cchFilename--;
+ }
+ return 0;
+ }
+ }
+
+ /* If there is more filename left, we won't have a match. */
+ if (cchFilename != 0)
+ return 0;
+
+ /* If there is pattern left, we still have a match if it's all asterisks. */
+ while (cchPattern > 0 && *pszPattern == '*')
+ {
+ pszPattern++;
+ cchPattern--;
+ }
+ return cchPattern == 0;
+}
+
+
+/**
+ * Matches a process name against the given pattern.
+ *
+ * @returns 1 if it matches, 0 if it doesn't.
+ * @param pszPattern The pattern to match against.
+ * @param cchPattern The pattern length.
+ * @param fPattern Pattern characteristics.
+ * @param pszExeFile The EXE filename to match (includes path).
+ * @param cchExeFile The length of the EXE filename.
+ */
+static int kmkKillMatchName(const char *pszPattern, size_t cchPattern, unsigned fPattern,
+ const char *pszExeFile, size_t cchExeFile)
+{
+ /*
+ * Automatically match the exe suffix if not present in the pattern.
+ */
+ if ( !(fPattern & KMKKILL_FN_EXE_SUFF)
+ && cchExeFile > 4
+ && stricmp(&pszExeFile[cchExeFile - 4], ".exe") == 0)
+ cchExeFile -= sizeof(".exe") - 1;
+
+ /*
+ * If no path in the pattern, move pszExeFile up to the filename part.
+ */
+ if (!(fPattern & KMKKILL_FN_WITH_PATH)
+ && ( memchr(pszExeFile, '\\', cchExeFile) != NULL
+ || memchr(pszExeFile, '/', cchExeFile) != NULL
+ || memchr(pszExeFile, ':', cchExeFile) != NULL))
+ {
+ size_t offFilename = cchExeFile;
+ char ch;
+ while ( offFilename > 0
+ && (ch = pszExeFile[offFilename - 1]) != '\\'
+ && ch != '//'
+ && ch != ':')
+ offFilename--;
+ cchExeFile -= offFilename;
+ pszExeFile += offFilename;
+ }
+
+ /*
+ * Wildcard? This only works for the filename part.
+ */
+ if (fPattern & KMKKILL_FN_WILDCARD)
+ return kmkKillMatchWildcard(pszPattern, cchPattern, pszExeFile, cchExeFile, 0);
+
+ /*
+ * No-wildcard pattern.
+ */
+ if (cchExeFile >= cchPattern)
+ {
+ if (strnicmp(&pszExeFile[cchExeFile - cchPattern], pszPattern, cchPattern) == 0)
+ return cchExeFile == cchPattern
+ || (fPattern & KMKKILL_FN_ROOT_SLASH)
+ || pszExeFile[cchExeFile - cchPattern - 1] == '\\'
+ || pszExeFile[cchExeFile - cchPattern - 1] == '/'
+ || pszExeFile[cchExeFile - cchPattern - 1] == ':';
+
+ /*
+ * Might be slash directions or multiple consequtive slashes
+ * making a difference here, so compare char-by-char from the end.
+ */
+ if (fPattern & KMKKILL_FN_WITH_PATH)
+ {
+ while (cchPattern > 0 && cchExeFile > 0)
+ {
+ char chPat = pszPattern[--cchPattern];
+ char chExe = pszExeFile[--cchExeFile];
+ if (chPat == chExe)
+ {
+ if (chPat != '\\' && chPat != '/')
+ {
+ if (chPat != ':' || cchPattern > 0)
+ continue;
+ return 1;
+ }
+ }
+ else
+ {
+ chPat = tolower(chPat);
+ chExe = tolower(chExe);
+ if (chPat == chExe)
+ continue;
+ if (chPat == '/')
+ chPat = '\\';
+ if (chExe == '/')
+ chExe = '\\';
+ if (chPat != chExe)
+ return 0;
+ }
+
+ while (cchPattern > 0 && ((chPat = pszPattern[cchPattern - 1] == '\\') || chPat == '/'))
+ cchPattern--;
+ if (!cchPattern)
+ return 1;
+
+ while (cchExeFile > 0 && ((chExe = pszExeFile[cchExeFile - 1] == '\\') || chExe == '/'))
+ cchExeFile--;
+ }
+
+ if ( cchExeFile == 0
+ || pszExeFile[cchExeFile - 1] == '\\'
+ || pszExeFile[cchExeFile - 1] == '/'
+ || pszExeFile[cchExeFile - 1] == ':')
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Analyzes pattern for kmkKillMatchName().
+ *
+ * @returns Pattern characteristics.
+ * @param pszPattern The pattern.
+ * @param cchPattern The pattern length.
+ */
+static unsigned kmkKillAnalyzePattern(const char *pszPattern, size_t cchPattern)
+{
+ unsigned fPattern = 0;
+ if (cchPattern > 4 && stricmp(&pszPattern[cchPattern - 4], ".exe") == 0)
+ fPattern |= KMKKILL_FN_EXE_SUFF;
+ if (memchr(pszPattern, '*', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WILDCARD;
+ if (memchr(pszPattern, '?', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WILDCARD;
+ if (memchr(pszPattern, '/', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WITH_PATH;
+ if (memchr(pszPattern, '\\', cchPattern) != NULL)
+ fPattern |= KMKKILL_FN_WITH_PATH;
+ if (*pszPattern == '\\' || *pszPattern == '/')
+ fPattern |= KMKKILL_FN_ROOT_SLASH;
+ return fPattern;
+}
+
+
+/**
+ * Enumerate processes and kill the ones matching the pattern.
+ */
+static int kmkKillProcessByName(KMKKILLGLOBALS *pThis, const char *pszPattern)
+{
+ HANDLE hSnapshot;
+ int rcExit = 0;
+ size_t const cchPattern = strlen(pszPattern);
+ unsigned const fPattern = kmkKillAnalyzePattern(pszPattern, cchPattern);
+ if (cchPattern == 0)
+ return errx(pThis->pCtx, 1, "Empty process name!");
+ if ((fPattern & (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH)) == (KMKKILL_FN_WILDCARD | KMKKILL_FN_WITH_PATH))
+ return errx(pThis->pCtx, 1, "Wildcard and paths cannot be mixed: %s", pszPattern);
+
+ hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (!hSnapshot)
+ rcExit = errx(pThis->pCtx, 1, "CreateToolhelp32Snapshot failed: %d", GetLastError());
+ else
+ {
+ union
+ {
+ PROCESSENTRY32 Entry;
+ char achBuf[8192];
+ } u;
+
+ memset(&u, 0, sizeof(u));
+ u.Entry.dwSize = sizeof(u) - sizeof(".exe");
+ SetLastError(NO_ERROR);
+ if (Process32First(hSnapshot, &u.Entry))
+ {
+ for (;;)
+ {
+ /* Kill it if it matches. */
+ u.achBuf[sizeof(u.achBuf) - sizeof(".exe")] = '\0';
+ if ( u.Entry.szExeFile[0] != '\0'
+ && kmkKillMatchName(pszPattern, cchPattern, fPattern, u.Entry.szExeFile, strlen(u.Entry.szExeFile)))
+ {
+ int rcExit2 = kmkKillProcessByPid(pThis, u.Entry.th32ProcessID, u.Entry.szExeFile);
+ if (rcExit2 != 0 && rcExit == 0)
+ rcExit = rcExit2;
+ }
+
+ /* next */
+ u.Entry.dwSize = sizeof(u) - sizeof(".exe");
+ SetLastError(NO_ERROR);
+ if (!Process32Next(hSnapshot, &u.Entry))
+ {
+ if (GetLastError() != ERROR_NO_MORE_FILES)
+ rcExit = errx(pThis->pCtx, 1, "Process32Next failed: %d", GetLastError());
+ break;
+ }
+ }
+ }
+ else
+ rcExit = errx(pThis->pCtx, 1, "Process32First failed: %d", GetLastError());
+ CloseHandle(hSnapshot);
+ }
+ return rcExit;
+
+}
+
+
+/**
+ * Worker for handling one command line process target.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ */
+static int kmkKillProcess(KMKKILLGLOBALS *pThis, const char *pszTarget)
+{
+ /*
+ * Try treat the target as a pid first, then a name pattern.
+ */
+ char *pszNext = NULL;
+ long pid;
+ errno = 0;
+ pid = strtol(pszTarget, &pszNext, 0);
+ if (pszNext && *pszNext == '\0' && errno == 0)
+ return kmkKillProcessByPid(pThis, pid, NULL);
+ return kmkKillProcessByName(pThis, pszTarget);
+}
+
+
+/**
+ * Worker for handling one command line job target.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ */
+static int kmkKillJob(KMKKILLGLOBALS *pThis, const char *pszTarget)
+{
+ int rcExit = 0;
+ HANDLE hTempJob = NULL;
+ BOOL fIsInJob = FALSE;
+
+ /*
+ * Open the job object.
+ */
+ DWORD fRights = JOB_OBJECT_QUERY | JOB_OBJECT_TERMINATE | JOB_OBJECT_ASSIGN_PROCESS;
+ HANDLE hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
+ if (!hJob)
+ {
+ fRights &= ~JOB_OBJECT_ASSIGN_PROCESS;
+ hJob = OpenJobObjectA(fRights, FALSE /*bInheritHandle*/, pszTarget);
+ if (!hJob)
+ return errx(pThis->pCtx, 1, "Failed to open job '%s': %u", pszTarget, GetLastError());
+ }
+
+ /*
+ * Are we a member of this job? If so, temporarily move
+ * to a different object so we don't kill ourselves.
+ */
+ if (IsProcessInJob(hJob, GetCurrentProcess(), &fIsInJob))
+ {
+ if (fIsInJob)
+ {
+ /** @todo this probably isn't working. */
+ if (pThis->cVerbosity >= 1)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0,
+ "kmk_kill: Is myself (%u) a member of target job (%s)", getpid(), pszTarget);
+ if (!(fRights & JOB_OBJECT_ASSIGN_PROCESS))
+ rcExit = errx(pThis->pCtx, 1,
+ "Is myself member of the target job and don't have the JOB_OBJECT_ASSIGN_PROCESS right.");
+ else
+ {
+ hTempJob = CreateJobObjectA(NULL, NULL);
+ if (hTempJob)
+ {
+ if (!(AssignProcessToJobObject(hTempJob, GetCurrentProcess())))
+ rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(temp, me) failed: %u", GetLastError());
+ }
+ }
+ }
+ }
+ else
+ rcExit = errx(pThis->pCtx, 1, "IsProcessInJob(%s, me) failed: %u", pszTarget, GetLastError());
+
+ /*
+ * Do the killing.
+ */
+ if (rcExit == 0)
+ {
+ if (!TerminateJobObject(hJob, DBG_TERMINATE_PROCESS))
+ rcExit = errx(pThis->pCtx, 1, "TerminateJobObject(%s) failed: %u", pszTarget, GetLastError());
+ }
+
+ /*
+ * Cleanup.
+ */
+ if (hTempJob)
+ {
+ if (!(AssignProcessToJobObject(hJob, GetCurrentProcess())))
+ rcExit = errx(pThis->pCtx, 1, "AssignProcessToJobObject(%s, me) failed: %u", pszTarget, GetLastError());
+ CloseHandle(hTempJob);
+ }
+ CloseHandle(hJob);
+
+ return rcExit;
+}
+
+
+/**
+ * Show usage.
+ */
+static void kmkKillUsage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [options] <job-name|process-name|pid> [options] [...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options that decide what to kill next:\n"
+ " -p|--process Processes, either by name or by PID. (default)\n"
+ " -j|--job Windows jobs.\n"
+ "\n"
+ "Other options:\n"
+ " -q|--quiet Quiet operation. Only real errors are displayed.\n"
+ " -v|--verbose Increase verbosity.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+}
+
+
+int kmk_builtin_kill(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rcExit = 0;
+ int i;
+ KMKKILLGLOBALS This;
+ enum { kTargetJobs, kTargetProcesses } enmTarget = kTargetProcesses;
+
+ /* Globals. */
+ This.pCtx = pCtx;
+ This.cVerbosity = 1;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc <= 1)
+ {
+ kmkKillUsage(pCtx, 0);
+ return 1;
+ }
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-')
+ {
+ const char *pszValue;
+ const char *psz = &argv[i][1];
+ char chOpt;
+ chOpt = *psz++;
+ if (chOpt == '-')
+ {
+ /* Convert long to short option. */
+ if (!strcmp(psz, "job"))
+ chOpt = 'j';
+ else if (!strcmp(psz, "process"))
+ chOpt = 'p';
+ else if (!strcmp(psz, "quiet"))
+ chOpt = 'q';
+ else if (!strcmp(psz, "verbose"))
+ chOpt = 'v';
+ else if (!strcmp(psz, "help"))
+ chOpt = '?';
+ else if (!strcmp(psz, "version"))
+ chOpt = 'V';
+ else
+ {
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kmkKillUsage(pCtx, 1);
+ return 2;
+ }
+ psz = "";
+ }
+
+ /*
+ * Requires value?
+ */
+ switch (chOpt)
+ {
+ /*case '':
+ if (*psz)
+ pszValue = psz;
+ else if (++i < argc)
+ pszValue = argv[i];
+ else
+ return errx(pCtx, 2, "The '-%c' option takes a value.", chOpt);
+ break;*/
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+
+ switch (chOpt)
+ {
+ /*
+ * What to kill
+ */
+ case 'j':
+ enmTarget = kTargetJobs;
+ break;
+
+ case 'p':
+ enmTarget = kTargetProcesses;
+ break;
+
+ /*
+ * How to kill processes...
+ */
+
+
+ /*
+ * Noise level.
+ */
+ case 'q':
+ This.cVerbosity = 0;
+ break;
+
+ case 'v':
+ This.cVerbosity += 1;
+ break;
+
+ /*
+ * The mandatory version & help.
+ */
+ case '?':
+ kmkKillUsage(pCtx, 0);
+ return rcExit;
+ case 'V':
+ return kbuild_version(argv[0]);
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ errx(pCtx, 2, "Invalid argument '%s'.", argv[i]);
+ kmkKillUsage(pCtx, 1);
+ return 2;
+ }
+ }
+ else
+ {
+ /*
+ * Kill something.
+ */
+ int rcExitOne;
+ This.pszCur = argv[i];
+ if (enmTarget == kTargetJobs)
+ rcExitOne = kmkKillJob(&This, argv[i]);
+ else
+ rcExitOne = kmkKillProcess(&This, argv[i]);
+ if (rcExitOne != 0 && rcExit == 0)
+ rcExit = rcExitOne;
+ }
+ }
+
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_kill", NULL };
+ return kmk_builtin_kill(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/ln.c b/src/kmk/kmkbuiltin/ln.c
new file mode 100644
index 0000000..4f80475
--- /dev/null
+++ b/src/kmk/kmkbuiltin/ln.c
@@ -0,0 +1,287 @@
+/*-
+ * Copyright (c) 1987, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1987, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/ln/ln.c,v 1.33 2005/02/09 17:37:37 ru Exp $");
+#endif /* no $id */
+
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct LNINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int fflag; /* Unlink existing files. */
+ int hflag; /* Check new name for symlink first. */
+ int iflag; /* Interactive mode. */
+ int sflag; /* Symbolic, not hard, link. */
+ int vflag; /* Verbose output. */
+ int (*linkf)(const char *, const char *); /* System link call. */
+ char linkch;
+} LNINSTANCE;
+typedef LNINSTANCE *PLNINSTANCE;
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+static int linkit(PLNINSTANCE,const char *, const char *, int);
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_ln(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ LNINSTANCE This;
+ struct getopt_state_r gos;
+ struct stat sb;
+ char *sourcedir;
+ int ch, exitval;
+
+ /* initialize globals. */
+ This.pCtx = pCtx;
+ This.fflag = 0;
+ This.hflag = 0;
+ This.iflag = 0;
+ This.sflag = 0;
+ This.vflag = 0;
+ This.linkch = 0;
+ This.linkf = NULL;
+
+ getopt_initialize_r(&gos, argc, argv, "fhinsv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'f':
+ This.fflag = 1;
+ This.iflag = 0;
+ break;
+ case 'h':
+ case 'n':
+ This.hflag = 1;
+ break;
+ case 'i':
+ This.iflag = 1;
+ This.fflag = 0;
+ break;
+ case 's':
+ This.sflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+
+ argv += gos.optind;
+ argc -= gos.optind;
+
+ This.linkf = This.sflag ? symlink : link;
+ This.linkch = This.sflag ? '-' : '=';
+
+ switch(argc) {
+ case 0:
+ return usage(pCtx, 1);
+ /* NOTREACHED */
+ case 1: /* ln target */
+ return linkit(&This, argv[0], ".", 1);
+ case 2: /* ln target source */
+ return linkit(&This, argv[0], argv[1], 0);
+ default:
+ ;
+ }
+ /* ln target1 target2 directory */
+ sourcedir = argv[argc - 1];
+ if (This.hflag && lstat(sourcedir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
+ /*
+ * We were asked not to follow symlinks, but found one at
+ * the target--simulate "not a directory" error
+ */
+ errno = ENOTDIR;
+ return err(pCtx, 1, "st_mode: %s", sourcedir);
+ }
+ if (stat(sourcedir, &sb))
+ return err(pCtx, 1, "stat: %s", sourcedir);
+ if (!S_ISDIR(sb.st_mode))
+ return usage(pCtx, 1);
+ for (exitval = 0; *argv != sourcedir; ++argv)
+ exitval |= linkit(&This, *argv, sourcedir, 1);
+ return exitval;
+}
+
+static int
+linkit(PLNINSTANCE pThis, const char *target, const char *source, int isdir)
+{
+ struct stat sb;
+ const char *p;
+ int ch, exists, first;
+ char path[PATH_MAX];
+
+ if (!pThis->sflag) {
+ /* If target doesn't exist, quit now. */
+ if (stat(target, &sb)) {
+ warn(pThis->pCtx, "stat: %s", target);
+ return (1);
+ }
+ /* Only symbolic links to directories. */
+ if (S_ISDIR(sb.st_mode)) {
+ errno = EISDIR;
+ warn(pThis->pCtx, "st_mode: %s", target);
+ return (1);
+ }
+ }
+
+ /*
+ * If the source is a directory (and not a symlink if hflag),
+ * append the target's name.
+ */
+ if (isdir ||
+ (lstat(source, &sb) == 0 && S_ISDIR(sb.st_mode)) ||
+ (!pThis->hflag && stat(source, &sb) == 0 && S_ISDIR(sb.st_mode))) {
+#if defined(_MSC_VER) || defined(__OS2__)
+ char *p2 = strrchr(target, '\\');
+ p = strrchr(target, '/');
+ if (p2 != NULL && (p == NULL || p2 > p))
+ p = p2;
+ if (p == NULL)
+#else
+ if ((p = strrchr(target, '/')) == NULL)
+#endif
+ p = target;
+ else
+ ++p;
+ if (snprintf(path, sizeof(path), "%s/%s", source, p) >=
+ (ssize_t)sizeof(path)) {
+ errno = ENAMETOOLONG;
+ warn(pThis->pCtx, "snprintf: %s", target);
+ return (1);
+ }
+ source = path;
+ }
+
+ exists = !lstat(source, &sb);
+ /*
+ * If the file exists, then unlink it forcibly if -f was specified
+ * and interactively if -i was specified.
+ */
+ if (pThis->fflag && exists) {
+ if (unlink(source)) {
+ warn(pThis->pCtx, "unlink: %s", source);
+ return (1);
+ }
+ } else if (pThis->iflag && exists) {
+ fflush(stdout);
+ fprintf(stderr, "replace %s? ", source);
+
+ first = ch = getchar();
+ while(ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y') {
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not replaced\n");
+ return (1);
+ }
+
+ if (unlink(source)) {
+ warn(pThis->pCtx, "unlink: %s", source);
+ return (1);
+ }
+ }
+
+ /* Attempt the link. */
+ if ((*pThis->linkf)(target, source)) {
+ warn(pThis->pCtx, "%s: %s", pThis->linkf == link ? "link" : "symlink", source);
+ return (1);
+ }
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s %c> %s\n", source, pThis->linkch, target);
+ return (0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx,fIsErr,
+ "usage: %s [-fhinsv] source_file [target_file]\n"
+ " or: %s [-fhinsv] source_file ... target_dir\n"
+ " or: %s source_file target_file\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_ln", NULL };
+ return kmk_builtin_ln(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/md5sum.c b/src/kmk/kmkbuiltin/md5sum.c
new file mode 100644
index 0000000..dc8d497
--- /dev/null
+++ b/src/kmk/kmkbuiltin/md5sum.c
@@ -0,0 +1,874 @@
+/* $Id: md5sum.c 3219 2018-03-30 22:30:15Z bird $ */
+/** @file
+ * md5sum.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#ifdef _MSC_VER
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+#include <sys/stat.h>
+#include "err.h"
+#include "kmkbuiltin.h"
+#include "../../lib/md5.h"
+#include <k/kTypes.h>
+
+/*#define MD5SUM_USE_STDIO*/
+
+
+/**
+ * Prints the usage and return 1.
+ */
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: md5sum [-bt] [-o list-file] file(s)\n"
+ " or: md5sum [-btwq] -c list-file(s)\n"
+ " or: md5sum [-btq] -C MD5 file\n"
+ "\n"
+ " -c, --check Check MD5 and files found in the specified list file(s).\n"
+ " The default is to compute MD5 sums of the specified files\n"
+ " and print them to stdout in list form.\n"
+ " -C, --check-file This is followed by an MD5 sum and the file to check.\n"
+ " -b, --binary Read files in binary mode. (default)\n"
+ " -t, --text Read files in text mode.\n"
+ " -m, --manifest Output in kBuild fetch 'manifest' format.\n"
+ " -p, --progress Show progress indicator on large files.\n"
+ " -o, --output Name of the output list file. Useful with -p.\n"
+ " -q, --status Be quiet.\n"
+ " -w, --warn Ignored. Always warn, unless quiet.\n"
+ " -h, --help This usage info.\n"
+ " -v, --version Show version information and exit.\n"
+ );
+ return 1;
+}
+
+
+/**
+ * Makes a string out of the given digest.
+ *
+ * @param pDigest The MD5 digest.
+ * @param pszDigest Where to put the digest string. Must be able to
+ * hold at least 33 bytes.
+ */
+static void digest_to_string(unsigned char pDigest[16], char *pszDigest)
+{
+ unsigned i;
+ for (i = 0; i < 16; i++)
+ {
+ static char s_achDigits[17] = "0123456789abcdef";
+ pszDigest[i*2] = s_achDigits[(pDigest[i] >> 4)];
+ pszDigest[i*2 + 1] = s_achDigits[(pDigest[i] & 0xf)];
+ }
+ pszDigest[i*2] = '\0';
+}
+
+
+/**
+ * Attempts to convert a string to a MD5 digest.
+ *
+ * @returns 0 on success, 1-based position of the failure first error.
+ * @param pszDigest The string to interpret.
+ * @param pDigest Where to put the MD5 digest.
+ */
+static int string_to_digest(const char *pszDigest, unsigned char pDigest[16])
+{
+ unsigned i;
+ unsigned iBase = 1;
+
+ /* skip blanks */
+ while ( *pszDigest == ' '
+ || *pszDigest == '\t'
+ || *pszDigest == '\n'
+ || *pszDigest == '\r')
+ pszDigest++, iBase++;
+
+ /* convert the digits. */
+ memset(pDigest, 0, 16);
+ for (i = 0; i < 32; i++, pszDigest++)
+ {
+ int iDigit;
+ if (*pszDigest >= '0' && *pszDigest <= '9')
+ iDigit = *pszDigest - '0';
+ else if (*pszDigest >= 'a' && *pszDigest <= 'f')
+ iDigit = *pszDigest - 'a' + 10;
+ else if (*pszDigest >= 'A' && *pszDigest <= 'F')
+ iDigit = *pszDigest - 'A' + 10;
+ else
+ return i + iBase;
+ if (i & 1)
+ pDigest[i >> 1] |= iDigit;
+ else
+ pDigest[i >> 1] |= iDigit << 4;
+ }
+
+ /* the rest of the string must now be blanks. */
+ while ( *pszDigest == ' '
+ || *pszDigest == '\t'
+ || *pszDigest == '\n'
+ || *pszDigest == '\r')
+ pszDigest++, i++;
+
+ return *pszDigest ? i + iBase : 0;
+}
+
+
+/**
+ * Opens the specified file for md5 sum calculation.
+ *
+ * @returns Opaque pointer on success, NULL and errno on failure.
+ * @param pszFilename The filename.
+ * @param fText Whether text or binary mode should be used.
+ */
+static void *open_file(const char *pszFilename, unsigned fText)
+{
+#if defined(MD5SUM_USE_STDIO)
+ FILE *pFile;
+
+ errno = 0;
+ pFile = fopen(pszFilename,
+ fText ? "r" KMK_FOPEN_NO_INHERIT_MODE
+ : "rb" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pFile && errno == EINVAL && !fText)
+ pFile = fopen(pszFilename, "r" KMK_FOPEN_NO_INHERIT_MODE);
+ return pFile;
+
+#else
+ int fd;
+ int fFlags;
+
+ /* figure out the appropriate flags. */
+ fFlags = O_RDONLY | KMK_OPEN_NO_INHERIT;
+#ifdef O_SEQUENTIAL
+ fFlags |= _O_SEQUENTIAL;
+#elif defined(_O_SEQUENTIAL)
+ fFlags |= _O_SEQUENTIAL;
+#endif
+#ifdef O_BINARY
+ if (!fText) fFlags |= O_BINARY;
+#elif defined(_O_BINARY)
+ if (!fText) fFlags |= _O_BINARY;
+#endif
+#ifdef O_TEXT
+ if (fText) fFlags |= O_TEXT;
+#elif defined(O_TEXT)
+ if (fText) fFlags |= _O_TEXT;
+#else
+ (void)fText;
+#endif
+
+ errno = 0;
+ fd = open(pszFilename, fFlags, 0755);
+ if (fd >= 0)
+ {
+ int *pFd = malloc(sizeof(*pFd));
+ if (pFd)
+ {
+ *pFd = fd;
+ return pFd;
+ }
+ close(fd);
+ errno = ENOMEM;
+ }
+
+ return NULL;
+#endif
+}
+
+
+/**
+ * Closes a file opened by open_file.
+ *
+ * @param pvFile The opaque pointer returned by open_file.
+ */
+static void close_file(void *pvFile)
+{
+#if defined(MD5SUM_USE_STDIO)
+ fclose((FILE *)pvFile);
+#else
+ close(*(int *)pvFile);
+ free(pvFile);
+#endif
+}
+
+
+/**
+ * Reads from a file opened by open_file.
+ *
+ * @returns Number of bytes read on success.
+ * 0 on EOF.
+ * Negated errno on read error.
+ * @param pvFile The opaque pointer returned by open_file.
+ * @param pvBuf Where to put the number of read bytes.
+ * @param cbBuf The max number of bytes to read.
+ * Must be less than a INT_MAX.
+ */
+static int read_file(void *pvFile, void *pvBuf, size_t cbBuf)
+{
+#if defined(MD5SUM_USE_STDIO)
+ int cb;
+
+ errno = 0;
+ cb = (int)fread(pvBuf, 1, cbBuf, (FILE *)pvFile);
+ if (cb >= 0)
+ return (int)cb;
+ if (!errno)
+ return -EINVAL;
+ return -errno;
+#else
+ int cb;
+
+ errno = 0;
+ cb = (int)read(*(int *)pvFile, pvBuf, (int)cbBuf);
+ if (cb >= 0)
+ return (int)cb;
+ if (!errno)
+ return -EINVAL;
+ return -errno;
+#endif
+}
+
+
+/**
+ * Gets the size of the file.
+ * This is informational and not necessarily 100% accurate.
+ *
+ * @returns File size.
+ * @param pvFile The opaque pointer returned by open_file
+ */
+static KU64 size_file(void *pvFile)
+{
+#if defined(_MSC_VER)
+ __int64 cb;
+# if defined(MD5SUM_USE_STDIO)
+ cb = _filelengthi64(fileno((FILE *)pvFile));
+# else
+ cb = _filelengthi64(*(int *)pvFile);
+# endif
+ if (cb >= 0)
+ return cb;
+
+#elif defined(MD5SUM_USE_STDIO)
+ struct stat st;
+ if (!fstat(fileno((FILE *)pvFile), &st))
+ return st.st_size;
+
+#else
+ struct stat st;
+ if (!fstat(*(int *)pvFile, &st))
+ return st.st_size;
+#endif
+ return 1024;
+}
+
+
+/**
+ * Calculates the md5sum of the sepecified file stream.
+ *
+ * @returns errno on failure, 0 on success.
+ * @param pvFile The file stream.
+ * @param pDigest Where to store the MD5 digest.
+ * @param fProgress Whether to show a progress bar.
+ * @param pcbFile Where to return the file size. Optional.
+ */
+static int calc_md5sum(void *pvFile, unsigned char pDigest[16], unsigned fProgress, KU64 *pcbFile)
+{
+ int cb;
+ int rc = 0;
+ struct MD5Context Ctx;
+ unsigned uPercent = 0;
+ KU64 off = 0;
+ KU64 const cbFile = size_file(pvFile);
+
+ /* Get a decent sized buffer assuming we'll be spending more time reading
+ from the storage than doing MD5 sums. (2MB was choosen based on recent
+ SATA storage benchmarks which used that block size for sequential
+ tests.) We align the buffer address on a 16K boundrary to avoid most
+ transfer alignment issues. */
+ char *pabBufAligned;
+ size_t const cbBufAlign = 16*1024 - 1;
+ size_t const cbBufMax = 2048*1024;
+ size_t cbBuf = cbFile >= cbBufMax ? cbBufMax : ((size_t)cbFile + cbBufAlign) & ~(size_t)cbBufAlign;
+ char *pabBuf = (char *)malloc(cbBuf + cbBufAlign);
+ if (pabBuf)
+ pabBufAligned = (char *)(((uintptr_t)pabBuf + cbBufAlign) & ~(uintptr_t)cbBufAlign );
+ else
+ {
+ do
+ {
+ cbBuf /= 2;
+ pabBuf = (char *)malloc(cbBuf);
+ } while (!pabBuf && cbBuf > 4096);
+ if (!pabBuf)
+ return ENOMEM;
+ pabBufAligned = pabBuf;
+ }
+
+ if (cbFile < cbBuf * 4)
+ fProgress = 0;
+
+ MD5Init(&Ctx);
+ for (;;)
+ {
+ /* process a chunk. */
+ cb = read_file(pvFile, pabBufAligned, cbBuf);
+ if (cb > 0)
+ MD5Update(&Ctx, (unsigned char *)pabBufAligned, cb);
+ else if (!cb)
+ break;
+ else
+ {
+ rc = -cb;
+ break;
+ }
+ off += cb;
+
+ /* update the progress indicator. */
+ if (fProgress)
+ {
+ unsigned uNewPercent;
+ uNewPercent = (unsigned)(((double)off / cbFile) * 100);
+ if (uNewPercent != uPercent)
+ {
+ if (uPercent)
+ printf("\b\b\b\b");
+ printf("%3d%%", uNewPercent);
+ fflush(stdout);
+ uPercent = uNewPercent;
+ }
+ }
+ }
+ MD5Final(pDigest, &Ctx);
+
+ if (pcbFile)
+ *pcbFile = off;
+
+ if (fProgress)
+ printf("\b\b\b\b \b\b\b\b");
+
+ free(pabBuf);
+ return rc;
+}
+
+
+/**
+ * Checks the if the specified digest matches the digest of the file stream.
+ *
+ * @returns 0 on match, -1 on mismatch, errno value (positive) on failure.
+ * @param pvFile The file stream.
+ * @param Digest The MD5 digest.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_md5sum(void *pvFile, unsigned char Digest[16], unsigned fProgress)
+{
+ unsigned char DigestFile[16];
+ int rc;
+
+ rc = calc_md5sum(pvFile, DigestFile, fProgress, NULL);
+ if (!rc)
+ rc = memcmp(Digest, DigestFile, 16) ? -1 : 0;
+ return rc;
+}
+
+
+/**
+ * Checks if the specified file matches the given MD5 digest.
+ *
+ * @returns 0 if it matches, 1 if it doesn't or an error occurs.
+ * @param pCtx The command execution context.
+ * @param pszFilename The name of the file to check.
+ * @param pszDigest The MD5 digest string.
+ * @param fText Whether to open the file in text or binary mode.
+ * @param fQuiet Whether to go about this in a quiet fashion or not.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_one_file(PKMKBUILTINCTX pCtx, const char *pszFilename, const char *pszDigest, unsigned fText,
+ unsigned fQuiet, unsigned fProgress)
+{
+ unsigned char Digest[16];
+ int rc;
+
+ rc = string_to_digest(pszDigest, Digest);
+ if (!rc)
+ {
+ void *pvFile;
+
+ pvFile = open_file(pszFilename, fText);
+ if (pvFile)
+ {
+ if (!fQuiet)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s: ", pszFilename);
+ rc = check_md5sum(pvFile, Digest, fProgress);
+ close_file(pvFile);
+ if (!fQuiet)
+ {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", !rc ? "OK" : rc < 0 ? "FAILURE" : "ERROR");
+ if (rc > 0)
+ errx(pCtx, 1, "Error reading '%s': %s", pszFilename, strerror(rc));
+ }
+ if (rc)
+ rc = 1;
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ }
+ else
+ {
+ errx(pCtx, 1, "Malformed MD5 digest '%s'!", pszDigest);
+ errx(pCtx, 1, " %*s^", rc - 1, "");
+ rc = 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Checks the specified md5.lst file.
+ *
+ * @returns 0 if all checks out file, 1 if one or more fails or there are read errors.
+ * @param pCtx The command execution context.
+ * @param pszFilename The name of the file.
+ * @param fText The default mode, text or binary. Only used when fBinaryTextOpt is true.
+ * @param fBinaryTextOpt Whether a -b or -t option was specified and should be used.
+ * @param fQuiet Whether to be quiet.
+ * @param fProgress Whether to show an progress indicator on large files.
+ */
+static int check_files(PKMKBUILTINCTX pCtx, const char *pszFilename, int fText, int fBinaryTextOpt,
+ int fQuiet, unsigned fProgress)
+{
+ int rc = 0;
+ FILE *pFile;
+
+ /*
+ * Try open the md5.lst file and process it line by line.
+ */
+ pFile = fopen(pszFilename, "r" KMK_FOPEN_NO_INHERIT_MODE);
+ if (pFile)
+ {
+ int iLine = 0;
+ char szLine[8192];
+ while (fgets(szLine, sizeof(szLine), pFile))
+ {
+ const char *pszDigest;
+ int fLineText;
+ char *psz;
+ int rc2;
+
+ iLine++;
+ psz = szLine;
+
+ /* leading blanks */
+ while (*psz == ' ' || *psz == '\t' || *psz == '\n')
+ psz++;
+
+ /* skip blank or comment lines. */
+ if (!*psz || *psz == '#' || *psz == ';' || *psz == '/')
+ continue;
+
+ /* remove the trailing newline. */
+ rc2 = (int)strlen(psz);
+ if (psz[rc2 - 1] == '\n')
+ psz[rc2 - (rc2 >= 2 && psz[rc2 - 2] == '\r' ? 2 : 1)] = '\0';
+
+ /* skip to the end of the digest and terminate it. */
+ pszDigest = psz;
+ while (*psz != ' ' && *psz != '\t' && *psz)
+ psz++;
+ if (*psz)
+ {
+ *psz++ = '\0';
+
+ /* blanks */
+ while (*psz == ' ' || *psz == '\t' || *psz == '\n')
+ psz++;
+
+ /* check for binary asterix */
+ if (*psz != '*')
+ fLineText = fBinaryTextOpt ? fText : 0;
+ else
+ {
+ fLineText = 0;
+ psz++;
+ }
+ if (*psz)
+ {
+ unsigned char Digest[16];
+
+ /* the rest is filename. */
+ pszFilename = psz;
+
+ /*
+ * Do the job.
+ */
+ rc2 = string_to_digest(pszDigest, Digest);
+ if (!rc2)
+ {
+ void *pvFile = open_file(pszFilename, fLineText);
+ if (pvFile)
+ {
+ if (!fQuiet)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s: ", pszFilename);
+ rc2 = check_md5sum(pvFile, Digest, fProgress);
+ close_file(pvFile);
+ if (!fQuiet)
+ {
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", !rc2 ? "OK" : rc2 < 0 ? "FAILURE" : "ERROR");
+ if (rc2 > 0)
+ errx(pCtx, 1, "Error reading '%s': %s", pszFilename, strerror(rc2));
+ }
+ if (rc2)
+ rc = 1;
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ }
+ else if (!fQuiet)
+ {
+ errx(pCtx, 1, "%s (%d): Ignoring malformed digest '%s' (digest)", pszFilename, iLine, pszDigest);
+ errx(pCtx, 1, "%s (%d): %*s^", pszFilename, iLine, rc2 - 1, "");
+ }
+ }
+ else if (!fQuiet)
+ errx(pCtx, 1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
+ }
+ else if (!fQuiet)
+ errx(pCtx, 1, "%s (%d): Ignoring malformed line!", pszFilename, iLine);
+ } /* while more lines */
+
+ fclose(pFile);
+ }
+ else
+ {
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Calculates the MD5 sum for one file and prints it.
+ *
+ * @returns 0 on success, 1 on any kind of failure.
+ * @param pCtx Command context.
+ * @param pszFilename The file to process.
+ * @param fText The mode to open the file in.
+ * @param fQuiet Whether to be quiet or verbose about errors.
+ * @param fManifest Whether to format the output like a fetch manifest.
+ * @param fProgress Whether to show an progress indicator on large files.
+ * @param pOutput Where to write the list. Progress is always written to stdout.
+ */
+static int md5sum_file(PKMKBUILTINCTX pCtx, const char *pszFilename, unsigned fText, unsigned fQuiet, unsigned fProgress,
+ unsigned fManifest, FILE *pOutput)
+{
+ int rc;
+ void *pvFile;
+
+ /*
+ * Calculate and print the MD5 sum for one file.
+ */
+ pvFile = open_file(pszFilename, fText);
+ if (pvFile)
+ {
+ unsigned char Digest[16];
+ KU64 cbFile = 0;
+
+ if (fProgress && pOutput)
+ fprintf(stdout, "%s: ", pszFilename);
+
+ rc = calc_md5sum(pvFile, Digest, fProgress, &cbFile);
+ close_file(pvFile);
+
+ if (fProgress && pOutput)
+ {
+ size_t cch = strlen(pszFilename) + 2;
+ while (cch-- > 0)
+ fputc('\b', stdout);
+ }
+
+ if (!rc)
+ {
+ char szDigest[36];
+ digest_to_string(Digest, szDigest);
+ if (!fManifest)
+ {
+ if (pOutput)
+ fprintf(pOutput, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
+ kmk_builtin_ctx_printf(pCtx, 0, "%s %s%s\n", szDigest, fText ? "" : "*", pszFilename);
+ }
+ else
+ {
+ if (pOutput)
+ fprintf(pOutput, "%s_SIZE := %" KU64_PRI "\n%s_MD5 := %s\n", pszFilename, cbFile, pszFilename, szDigest);
+ kmk_builtin_ctx_printf(pCtx, 0, "%s_SIZE := %" KU64_PRI "\n%s_MD5 := %s\n",
+ pszFilename, cbFile, pszFilename, szDigest);
+ }
+ if (pOutput)
+ fflush(pOutput);
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(rc));
+ rc = 1;
+ }
+ }
+ else
+ {
+ if (!fQuiet)
+ errx(pCtx, 1, "Failed to open '%s': %s", pszFilename, strerror(errno));
+ rc = 1;
+ }
+ return rc;
+}
+
+
+
+/**
+ * md5sum, calculates and checks the md5sum of files.
+ * Somewhat similar to the GNU coreutil md5sum command.
+ */
+int kmk_builtin_md5sum(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int i;
+ int rc = 0;
+ int fText = 0;
+ int fBinaryTextOpt = 0;
+ int fQuiet = 0;
+ int fChecking = 0;
+ int fManifest = 0;
+ int fProgress = 0;
+ int fNoMoreOptions = 0;
+ const char *pszOutput = NULL;
+ FILE *pOutput = NULL;
+
+ /*
+ * Print usage if no arguments.
+ */
+ if (argc <= 1)
+ return usage(pCtx, 1);
+
+ /*
+ * Process the arguments, FIFO style.
+ */
+ i = 1;
+ while (i < argc)
+ {
+ char *psz = argv[i];
+ if (!fNoMoreOptions && psz[0] == '-' && psz[1] == '-' && !psz[2])
+ fNoMoreOptions = 1;
+ else if (*psz == '-' && !fNoMoreOptions)
+ {
+ psz++;
+
+ /* convert long options for gnu just for fun */
+ if (*psz == '-')
+ {
+ if (!strcmp(psz, "-binary"))
+ psz = "b";
+ else if (!strcmp(psz, "-text"))
+ psz = "t";
+ else if (!strcmp(psz, "-check"))
+ psz = "c";
+ else if (!strcmp(psz, "-check-file"))
+ psz = "C";
+ else if (!strcmp(psz, "-manifest"))
+ psz = "m";
+ else if (!strcmp(psz, "-output"))
+ psz = "o";
+ else if (!strcmp(psz, "-progress"))
+ psz = "p";
+ else if (!strcmp(psz, "-status"))
+ psz = "q";
+ else if (!strcmp(psz, "-warn"))
+ psz = "w";
+ else if (!strcmp(psz, "-help"))
+ psz = "h";
+ else if (!strcmp(psz, "-version"))
+ psz = "v";
+ }
+
+ /* short options */
+ do
+ {
+ switch (*psz)
+ {
+ case 'c':
+ fChecking = 1;
+ break;
+
+ case 'b':
+ fText = 0;
+ fBinaryTextOpt = 1;
+ break;
+
+ case 't':
+ fText = 1;
+ fBinaryTextOpt = 1;
+ break;
+
+ case 'm':
+ fManifest = 1;
+ break;
+
+ case 'p':
+ fProgress = 1 && isatty(fileno(stdout))
+#ifndef KMK_BUILTIN_STANDALONE
+ && (!pCtx->pOut || !pCtx->pOut->syncout)
+#endif
+ ;
+ break;
+
+ case 'q':
+ fQuiet = 1;
+ break;
+
+ case 'w':
+ /* ignored */
+ break;
+
+ case 'h':
+ usage(pCtx, 0);
+ return 0;
+
+ case 'v':
+ return kbuild_version(argv[0]);
+
+ /*
+ * -C md5 file
+ */
+ case 'C':
+ {
+ const char *pszFilename;
+ const char *pszDigest;
+
+ if (psz[1])
+ pszDigest = &psz[1];
+ else if (i + 1 < argc)
+ pszDigest = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-C' is missing the MD5 sum!");
+ return 1;
+ }
+ if (i + 1 < argc)
+ pszFilename = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-C' is missing the filename!");
+ return 1;
+ }
+
+ rc |= check_one_file(pCtx, pszFilename, pszDigest, fText, fQuiet, fProgress && !fQuiet);
+ psz = "\0";
+ break;
+ }
+
+ /*
+ * Output file.
+ */
+ case 'o':
+ {
+ if (fChecking)
+ {
+ errx(pCtx, 1, "'-o' cannot be used with -c or -C!");
+ return 1;
+ }
+
+ if (psz[1])
+ pszOutput = &psz[1];
+ else if (i + 1 < argc)
+ pszOutput = argv[++i];
+ else
+ {
+ errx(pCtx, 1, "'-o' is missing the file name!");
+ return 1;
+ }
+
+ psz = "\0";
+ break;
+ }
+
+ default:
+ errx(pCtx, 1, "Invalid option '%c'! (%s)", *psz, argv[i]);
+ return usage(pCtx, 1);
+ }
+ } while (*++psz);
+ }
+ else if (fChecking)
+ rc |= check_files(pCtx, argv[i], fText, fBinaryTextOpt, fQuiet, fProgress && !fQuiet);
+ else
+ {
+ /* lazily open the output if specified. */
+ if (pszOutput)
+ {
+ if (pOutput)
+ fclose(pOutput);
+ pOutput = fopen(pszOutput, "w" KMK_FOPEN_NO_INHERIT_MODE);
+ if (!pOutput)
+ {
+ rc = err(pCtx, 1, "fopen(\"%s\", \"w" KMK_FOPEN_NO_INHERIT_MODE "\") failed", pszOutput);
+ break;
+ }
+ pszOutput = NULL;
+ }
+
+ rc |= md5sum_file(pCtx, argv[i], fText, fQuiet, fProgress && !fQuiet && !fManifest, fManifest, pOutput);
+ }
+ i++;
+ }
+
+ if (pOutput)
+ fclose(pOutput);
+ return rc;
+}
+
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_md5sum", NULL };
+ return kmk_builtin_md5sum(argc, argv, envp, &Ctx);
+}
+#endif
+
+
diff --git a/src/kmk/kmkbuiltin/mkdir.c b/src/kmk/kmkbuiltin/mkdir.c
new file mode 100644
index 0000000..65e961f
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mkdir.c
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1983, 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1983, 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mkdir.c 8.2 (Berkeley) 1/25/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/mkdir/mkdir.c,v 1.28 2004/04/06 20:06:48 markm Exp $");
+#endif
+
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <assert.h>
+#ifndef _MSC_VER
+# include <libgen.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "getopt_r.h"
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include <malloc.h>
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+extern mode_t g_fUMask;
+
+extern void * bsd_setmode(const char *p);
+extern mode_t bsd_getmode(const void *bbox, mode_t omode);
+
+static int build(PKMKBUILTINCTX pCtx, char *, mode_t, int);
+static int usage(PKMKBUILTINCTX pCtx, int fIsErr);
+
+
+int
+kmk_builtin_mkdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ struct getopt_state_r gos;
+ int ch, exitval, success, pflag, vflag;
+ mode_t omode, *set = (mode_t *)NULL;
+ char *mode;
+
+ omode = pflag = vflag = 0;
+ mode = NULL;
+
+ getopt_initialize_r(&gos, argc, argv, "m:pv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'm':
+ mode = gos.optarg;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+
+ argc -= gos.optind;
+ argv += gos.optind;
+ if (argv[0] == NULL)
+ return usage(pCtx, 1);
+
+ if (mode == NULL) {
+ omode = S_IRWXU | S_IRWXG | S_IRWXO;
+ } else {
+ if ((set = bsd_setmode(mode)) == NULL)
+ return errx(pCtx, 1, "invalid file mode: %s", mode);
+ omode = bsd_getmode(set, S_IRWXU | S_IRWXG | S_IRWXO);
+ free(set);
+ }
+
+ for (exitval = 0; *argv != NULL; ++argv) {
+ success = 1;
+ if (pflag) {
+ if (build(pCtx, *argv, omode, vflag))
+ success = 0;
+ } else if (mkdir(*argv, omode) < 0) {
+ if (errno == ENOTDIR || errno == ENOENT)
+ warn(pCtx, "mkdir: %s", dirname(*argv));
+ else
+ warn(pCtx, "mkdir: %s", *argv);
+ success = 0;
+ } else if (vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", *argv);
+
+ if (!success)
+ exitval = 1;
+ /*
+ * The mkdir() and umask() calls both honor only the low
+ * nine bits, so if you try to set a mode including the
+ * sticky, setuid, setgid bits you lose them. Don't do
+ * this unless the user has specifically requested a mode,
+ * as chmod will (obviously) ignore the umask.
+ */
+ if (success && mode != NULL && chmod(*argv, omode) == -1) {
+ warn(pCtx, "chmod: %s", *argv);
+ exitval = 1;
+ }
+ }
+ return exitval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+mode_t g_fUMask;
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_mkdir", NULL };
+ umask(g_fUMask = umask(0077));
+ return kmk_builtin_mkdir(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+build(PKMKBUILTINCTX pCtx, char *path, mode_t omode, int vflag)
+{
+ struct stat sb;
+ mode_t numask, oumask;
+ int first, last, retval;
+ char *p;
+
+ const size_t len = strlen(path);
+ p = alloca(len + 1);
+ path = memcpy(p, path, len + 1);
+
+#if defined(_MSC_VER) || defined(__EMX__)
+ /* correct slashes. */
+ p = strchr(path, '\\');
+ while (p) {
+ *p++ = '/';
+ p = strchr(p, '\\');
+ }
+#endif
+
+ p = path;
+ oumask = 0;
+ retval = 0;
+#if defined(_MSC_VER) || defined(__EMX__)
+ if ( ( (p[0] >= 'A' && p[0] <= 'Z')
+ || (p[0] >= 'a' && p[0] <= 'z'))
+ && p[1] == ':')
+ p += 2; /* skip the drive letter */
+ else if ( p[0] == '/'
+ && p[1] == '/'
+ && p[2] != '/')
+ {
+ /* UNC */
+ p += 2;
+ while (*p && *p != '/') /* server */
+ p++;
+ while (*p == '/')
+ p++;
+ while (*p && *p != '/') /* share */
+ p++;
+ }
+#endif
+ if (p[0] == '/') /* Skip leading '/'. */
+ ++p;
+ for (first = 1, last = 0; !last ; ++p) {
+ if (p[0] == '\0')
+ last = 1;
+ else if (p[0] != '/')
+ continue;
+ *p = '\0';
+ if (!last && p[1] == '\0')
+ last = 1;
+ if (first) {
+ /*
+ * POSIX 1003.2:
+ * For each dir operand that does not name an existing
+ * directory, effects equivalent to those cased by the
+ * following command shall occcur:
+ *
+ * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
+ * mkdir [-m mode] dir
+ *
+ * We change the user's umask and then restore it,
+ * instead of doing chmod's.
+ */
+#ifdef KMK_BUILTIN_STANDALONE
+ oumask = umask(0077);
+#else
+ oumask = g_fUMask;
+ assert(oumask == umask(g_fUMask));
+#endif
+ numask = oumask & ~(S_IWUSR | S_IXUSR);
+ if (numask != oumask)
+ (void)umask(numask);
+ first = 0;
+ }
+ if (last && oumask != numask)
+ (void)umask(oumask);
+ if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
+ if (errno == EEXIST || errno == EISDIR
+ || errno == ENOSYS /* (solaris crap) */
+ || errno == EACCES /* (ditto) */) {
+ if (stat(path, &sb) < 0) {
+ warn(pCtx, "stat: %s", path);
+ retval = 1;
+ break;
+ }
+ if (!S_ISDIR(sb.st_mode)) {
+ if (last)
+ errno = EEXIST;
+ else
+ errno = ENOTDIR;
+ warn(pCtx, "st_mode: %s", path);
+ retval = 1;
+ break;
+ }
+ } else {
+ warn(pCtx, "mkdir: %s", path);
+ retval = 1;
+ break;
+ }
+ } else if (vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", path);
+ if (!last)
+ *p = '/';
+ }
+ if (!first && !last && oumask != numask)
+ (void)umask(oumask);
+ return (retval);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-pv] [-m mode] directory ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
+
diff --git a/src/kmk/kmkbuiltin/mscfakes.c b/src/kmk/kmkbuiltin/mscfakes.c
new file mode 100644
index 0000000..7256cb6
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mscfakes.c
@@ -0,0 +1,839 @@
+/* $Id: mscfakes.c 3387 2020-06-26 16:51:19Z bird $ */
+/** @file
+ * Fake Unix stuff for MSC.
+ */
+
+/*
+ * Copyright (c) 2005-2015 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <io.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/timeb.h>
+#include "err.h"
+#include "mscfakes.h"
+
+#include "nt/ntutimes.h"
+#undef utimes
+#undef lutimes
+
+#include "console.h"
+
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static BOOL isPipeFd(int fd);
+
+
+/**
+ * Makes corrections to a directory path that ends with a trailing slash.
+ *
+ * @returns temporary buffer to free.
+ * @param ppszPath The path pointer. This is updated when necessary.
+ * @param pfMustBeDir This is set if it must be a directory, otherwise it's cleared.
+ */
+static char *
+msc_fix_path(const char **ppszPath, int *pfMustBeDir)
+{
+ const char *pszPath = *ppszPath;
+ const char *psz;
+ char *pszNew;
+ *pfMustBeDir = 0;
+
+ /*
+ * Skip any compusory trailing slashes
+ */
+ if (pszPath[0] == '/' || pszPath[0] == '\\')
+ {
+ if ( (pszPath[1] == '/' || pszPath[1] == '\\')
+ && pszPath[2] != '/'
+ && pszPath[2] != '\\')
+ /* unc */
+ pszPath += 2;
+ else
+ /* root slash(es) */
+ pszPath++;
+ }
+ else if ( isalpha(pszPath[0])
+ && pszPath[1] == ':')
+ {
+ if (pszPath[2] == '/' || pszPath[2] == '\\')
+ /* drive w/ slash */
+ pszPath += 3;
+ else
+ /* drive relative path. */
+ pszPath += 2;
+ }
+ /* else: relative path, no skipping necessary. */
+
+ /*
+ * Any trailing slashes to drop off?
+ */
+ psz = strchr(pszPath, '\0');
+ if (pszPath <= psz)
+ return NULL;
+ if ( psz[-1] != '/'
+ || psz[-1] != '\\')
+ return NULL;
+
+ /* figure how many, make a copy and strip them off. */
+ while ( psz > pszPath
+ && ( psz[-1] == '/'
+ || psz[-1] == '\\'))
+ psz--;
+ pszNew = strdup(pszPath);
+ pszNew[psz - pszPath] = '\0';
+
+ *pfMustBeDir = 1;
+ *ppszPath = pszNew; /* use this one */
+ return pszNew;
+}
+
+
+int
+birdSetErrno(unsigned dwErr)
+{
+ switch (dwErr)
+ {
+ default:
+ case ERROR_INVALID_FUNCTION: errno = EINVAL; break;
+ case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break;
+ case ERROR_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_INVALID_HANDLE: errno = EBADF; break;
+ case ERROR_ARENA_TRASHED: errno = ENOMEM; break;
+ case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break;
+ case ERROR_INVALID_BLOCK: errno = ENOMEM; break;
+ case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break;
+ case ERROR_BAD_FORMAT: errno = ENOEXEC; break;
+ case ERROR_INVALID_ACCESS: errno = EINVAL; break;
+ case ERROR_INVALID_DATA: errno = EINVAL; break;
+ case ERROR_INVALID_DRIVE: errno = ENOENT; break;
+ case ERROR_CURRENT_DIRECTORY: errno = EACCES; break;
+ case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break;
+ case ERROR_NO_MORE_FILES: errno = ENOENT; break;
+ case ERROR_LOCK_VIOLATION: errno = EACCES; break;
+ case ERROR_BAD_NETPATH: errno = ENOENT; break;
+ case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_BAD_NET_NAME: errno = ENOENT; break;
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_CANNOT_MAKE: errno = EACCES; break;
+ case ERROR_FAIL_I24: errno = EACCES; break;
+ case ERROR_INVALID_PARAMETER: errno = EINVAL; break;
+ case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break;
+ case ERROR_DRIVE_LOCKED: errno = EACCES; break;
+ case ERROR_BROKEN_PIPE: errno = EPIPE; break;
+ case ERROR_DISK_FULL: errno = ENOSPC; break;
+ case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break;
+ case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break;
+ case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break;
+ case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break;
+ case ERROR_NEGATIVE_SEEK: errno = EINVAL; break;
+ case ERROR_SEEK_ON_DEVICE: errno = EACCES; break;
+ case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break;
+ case ERROR_NOT_LOCKED: errno = EACCES; break;
+ case ERROR_BAD_PATHNAME: errno = ENOENT; break;
+ case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break;
+ case ERROR_LOCK_FAILED: errno = EACCES; break;
+ case ERROR_ALREADY_EXISTS: errno = EEXIST; break;
+ case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break;
+ case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break;
+#ifdef EMLINK
+ case ERROR_TOO_MANY_LINKS: errno = EMLINK; break;
+#endif
+ }
+
+ return -1;
+}
+
+char *dirname(char *path)
+{
+ /** @todo later */
+ return path;
+}
+
+
+int lchmod(const char *pszPath, mode_t mode)
+{
+ int rc = 0;
+ int fMustBeDir;
+ char *pszPathFree = msc_fix_path(&pszPath, &fMustBeDir);
+
+ /*
+ * Get the current attributes
+ */
+ DWORD fAttr = GetFileAttributes(pszPath);
+ if (fAttr == INVALID_FILE_ATTRIBUTES)
+ rc = birdSetErrno(GetLastError());
+ else if (fMustBeDir & !(fAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ errno = ENOTDIR;
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Modify the attributes and try set them.
+ */
+ if (mode & _S_IWRITE)
+ {
+ fAttr &= ~FILE_ATTRIBUTE_READONLY;
+ if (fAttr == 0)
+ fAttr = FILE_ATTRIBUTE_NORMAL;
+ }
+ else
+ {
+ fAttr &= ~FILE_ATTRIBUTE_NORMAL;
+ fAttr |= FILE_ATTRIBUTE_READONLY;
+ }
+ if (!SetFileAttributes(pszPath, fAttr))
+ rc = birdSetErrno(GetLastError());
+ }
+
+ if (pszPathFree)
+ {
+ int saved_errno = errno;
+ free(pszPathFree);
+ errno = saved_errno;
+ }
+ return rc;
+}
+
+
+int msc_chmod(const char *pszPath, mode_t mode)
+{
+ int rc = 0;
+ int fMustBeDir;
+ char *pszPathFree = msc_fix_path(&pszPath, &fMustBeDir);
+
+ /*
+ * Get the current attributes.
+ */
+ DWORD fAttr = GetFileAttributes(pszPath);
+ if (fAttr == INVALID_FILE_ATTRIBUTES)
+ rc = birdSetErrno(GetLastError());
+ else if (fMustBeDir & !(fAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ errno = ENOTDIR;
+ rc = -1;
+ }
+ else if (fAttr & FILE_ATTRIBUTE_REPARSE_POINT)
+ {
+ errno = ENOSYS; /** @todo resolve symbolic link / rewrite to NtSetInformationFile. */
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Modify the attributes and try set them.
+ */
+ if (mode & _S_IWRITE)
+ {
+ fAttr &= ~FILE_ATTRIBUTE_READONLY;
+ if (fAttr == 0)
+ fAttr = FILE_ATTRIBUTE_NORMAL;
+ }
+ else
+ {
+ fAttr &= ~FILE_ATTRIBUTE_NORMAL;
+ fAttr |= FILE_ATTRIBUTE_READONLY;
+ }
+ if (!SetFileAttributes(pszPath, fAttr))
+ rc = birdSetErrno(GetLastError());
+ }
+
+ if (pszPathFree)
+ {
+ int saved_errno = errno;
+ free(pszPathFree);
+ errno = saved_errno;
+ }
+ return rc;
+}
+
+
+typedef BOOL (WINAPI *PFNCREATEHARDLINKA)(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES);
+int link(const char *pszDst, const char *pszLink)
+{
+ static PFNCREATEHARDLINKA s_pfnCreateHardLinkA = NULL;
+ static int s_fTried = FALSE;
+
+ /* The API was introduced in Windows 2000, so resolve it dynamically. */
+ if (!s_pfnCreateHardLinkA)
+ {
+ if (!s_fTried)
+ {
+ HMODULE hmod = LoadLibrary("KERNEL32.DLL");
+ if (hmod)
+ *(FARPROC *)&s_pfnCreateHardLinkA = GetProcAddress(hmod, "CreateHardLinkA");
+ s_fTried = TRUE;
+ }
+ if (!s_pfnCreateHardLinkA)
+ {
+ errno = ENOSYS;
+ return -1;
+ }
+ }
+
+ if (s_pfnCreateHardLinkA(pszLink, pszDst, NULL))
+ return 0;
+ return birdSetErrno(GetLastError());
+}
+
+
+int mkdir_msc(const char *path, mode_t mode)
+{
+ int rc = (mkdir)(path);
+ if (rc)
+ {
+ size_t len = strlen(path);
+ if (len > 0 && (path[len - 1] == '/' || path[len - 1] == '\\'))
+ {
+ char *str = strdup(path);
+ while (len > 0 && (str[len - 1] == '/' || str[len - 1] == '\\'))
+ str[--len] = '\0';
+ rc = (mkdir)(str);
+ free(str);
+ }
+ }
+ return rc;
+}
+
+int rmdir_msc(const char *path)
+{
+ int rc = (rmdir)(path);
+ if (rc)
+ {
+ size_t len = strlen(path);
+ if (len > 0 && (path[len - 1] == '/' || path[len - 1] == '\\'))
+ {
+ char *str = strdup(path);
+ while (len > 0 && (str[len - 1] == '/' || str[len - 1] == '\\'))
+ str[--len] = '\0';
+ rc = (rmdir)(str);
+ free(str);
+ }
+ }
+ return rc;
+}
+
+
+static int doname(char *pszX, char *pszEnd)
+{
+ static char s_szChars[] = "Xabcdefghijklmnopqrstuwvxyz1234567890";
+ int rc = 0;
+ do
+ {
+ char ch;
+
+ pszEnd++;
+ ch = *(strchr(s_szChars, *pszEnd) + 1);
+ if (ch)
+ {
+ *pszEnd = ch;
+ return 0;
+ }
+ *pszEnd = 'a';
+ } while (pszEnd != pszX);
+ return 1;
+}
+
+
+int mkstemp(char *temp)
+{
+ char *pszX = strchr(temp, 'X');
+ char *pszEnd = strchr(pszX, '\0');
+ int cTries = 1000;
+ while (--cTries > 0)
+ {
+ int fd;
+ if (doname(pszX, pszEnd))
+ return -1;
+ fd = open(temp, _O_EXCL | _O_CREAT | _O_BINARY | _O_RDWR | KMK_OPEN_NO_INHERIT, 0777);
+ if (fd >= 0)
+ return fd;
+ }
+ return -1;
+}
+
+
+/** Unix to DOS. */
+static char *fix_slashes(char *psz)
+{
+ char *pszRet = psz;
+ for (; *psz; psz++)
+ if (*psz == '/')
+ *psz = '\\';
+ return pszRet;
+}
+
+
+/** Calcs the SYMBOLIC_LINK_FLAG_DIRECTORY flag for CreatesymbolcLink. */
+static DWORD is_directory(const char *pszPath, const char *pszRelativeTo)
+{
+ size_t cchPath = strlen(pszPath);
+ struct stat st;
+ if (cchPath > 0 && pszPath[cchPath - 1] == '\\' || pszPath[cchPath - 1] == '/')
+ return 1; /* SYMBOLIC_LINK_FLAG_DIRECTORY */
+
+ if (stat(pszPath, &st))
+ {
+ size_t cchRelativeTo = strlen(pszRelativeTo);
+ char *psz = malloc(cchPath + cchRelativeTo + 4);
+ memcpy(psz, pszRelativeTo, cchRelativeTo);
+ memcpy(psz + cchRelativeTo, "\\", 1);
+ memcpy(psz + cchRelativeTo + 1, pszPath, cchPath + 1);
+ if (stat(pszPath, &st))
+ st.st_mode = _S_IFREG;
+ free(psz);
+ }
+
+ return (st.st_mode & _S_IFMT) == _S_IFDIR ? 1 : 0;
+}
+
+
+int symlink(const char *pszDst, const char *pszLink)
+{
+ static BOOLEAN (WINAPI *s_pfnCreateSymbolicLinkA)(LPCSTR, LPCSTR, DWORD) = 0;
+ static BOOL s_fTried = FALSE;
+
+ if (!s_fTried)
+ {
+ HMODULE hmod = LoadLibrary("KERNEL32.DLL");
+ if (hmod)
+ *(FARPROC *)&s_pfnCreateSymbolicLinkA = GetProcAddress(hmod, "CreateSymbolicLinkA");
+ s_fTried = TRUE;
+ }
+
+ if (s_pfnCreateSymbolicLinkA)
+ {
+ char *pszDstCopy = fix_slashes(strdup(pszDst));
+ char *pszLinkCopy = fix_slashes(strdup(pszLink));
+ BOOLEAN fRc = s_pfnCreateSymbolicLinkA(pszLinkCopy, pszDstCopy,
+ is_directory(pszDstCopy, pszLinkCopy));
+ DWORD err = GetLastError();
+ free(pszDstCopy);
+ free(pszLinkCopy);
+ if (fRc)
+ return 0;
+ switch (err)
+ {
+ case ERROR_NOT_SUPPORTED: errno = ENOSYS; break;
+ case ERROR_ALREADY_EXISTS:
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_DIRECTORY: errno = ENOTDIR; break;
+ case ERROR_ACCESS_DENIED:
+ case ERROR_PRIVILEGE_NOT_HELD: errno = EPERM; break;
+ default: errno = EINVAL; break;
+ }
+ return -1;
+ }
+
+ fprintf(stderr, "warning: symlink() is available on this version of Windows!\n");
+ errno = ENOSYS;
+ return -1;
+}
+
+
+#if _MSC_VER < 1400
+int snprintf(char *buf, size_t size, const char *fmt, ...)
+{
+ int cch;
+ va_list args;
+ va_start(args, fmt);
+ cch = vsprintf(buf, fmt, args);
+ va_end(args);
+ return cch;
+}
+#endif
+
+
+/* We override the libc write function (in our modules only, unfortunately) so
+ we can kludge our way around a ENOSPC problem observed on build servers
+ capturing STDOUT and STDERR via pipes. Apparently this may happen when the
+ pipe buffer is full, even with the mscfake_init hack in place.
+
+ XXX: Probably need to hook into fwrite as well. */
+ssize_t msc_write(int fd, const void *pvSrc, size_t cbSrc)
+{
+#define MSC_WRITE_MAX_CHUNK (UINT_MAX / 32)
+ ssize_t cbRet;
+ if (cbSrc <= MSC_WRITE_MAX_CHUNK)
+ {
+ /* Console output optimization: */
+ if (cbSrc > 0 && is_console(fd))
+ return maybe_con_write(fd, pvSrc, cbSrc);
+
+#ifndef MSC_WRITE_TEST
+ cbRet = _write(fd, pvSrc, (unsigned int)cbSrc);
+#else
+ cbRet = -1; errno = ENOSPC;
+#endif
+ if (cbRet < 0)
+ {
+ /* ENOSPC on pipe kludge. */
+ unsigned int cbLimit;
+ int cSinceLastSuccess;
+
+ if (cbSrc == 0)
+ return 0;
+ if (errno != ENOSPC)
+ return -1;
+#ifndef MSC_WRITE_TEST
+ if (!isPipeFd(fd))
+ {
+ errno = ENOSPC;
+ return -1;
+ }
+#endif
+
+ /* Likely a full pipe buffer, try write smaller amounts and do some
+ sleeping inbetween each unsuccessful one. */
+ cbLimit = (unsigned)(cbSrc / 4);
+ if (cbLimit < 4)
+ cbLimit = 4;
+ else if (cbLimit > 512)
+ cbLimit = 512;
+ cSinceLastSuccess = 0;
+ cbRet = 0;
+#ifdef MSC_WRITE_TEST
+ cbLimit = 4;
+#endif
+
+ while ((ssize_t)cbSrc > 0)
+ {
+ unsigned int cbAttempt = cbSrc > cbLimit ? cbLimit : (unsigned int)cbSrc;
+ ssize_t cbActual = _write(fd, pvSrc, cbAttempt);
+ if (cbActual > 0)
+ {
+ /* For some reason, it seems like we cannot trust _write to return
+ a number that's less or equal to the number of bytes we passed
+ in to the call. (Also reason for signed check in loop.) */
+ if (cbActual > cbAttempt)
+ cbActual = cbAttempt;
+
+ pvSrc = (char *)pvSrc + cbActual;
+ cbSrc -= cbActual;
+ cbRet += cbActual;
+#ifndef MSC_WRITE_TEST
+ if (cbLimit < 32)
+ cbLimit = 32;
+#endif
+ cSinceLastSuccess = 0;
+ }
+ else if (errno != ENOSPC)
+ return -1;
+ else
+ {
+ /* Delay for about 30 seconds, then just give up. */
+ cSinceLastSuccess++;
+ if (cSinceLastSuccess > 1860)
+ return -1;
+ if (cSinceLastSuccess <= 2)
+ Sleep(0);
+ else if (cSinceLastSuccess <= 66)
+ {
+ if (cbLimit >= 8)
+ cbLimit /= 2; /* Just in case the pipe buffer is very very small. */
+ Sleep(1);
+ }
+ else
+ Sleep(16);
+ }
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Type limit exceeded. Split the job up.
+ */
+ cbRet = 0;
+ while (cbSrc > 0)
+ {
+ size_t cbToWrite = cbSrc > MSC_WRITE_MAX_CHUNK ? MSC_WRITE_MAX_CHUNK : cbSrc;
+ ssize_t cbWritten = msc_write(fd, pvSrc, cbToWrite);
+ if (cbWritten > 0)
+ {
+ pvSrc = (char *)pvSrc + (size_t)cbWritten;
+ cbSrc -= (size_t)cbWritten;
+ cbRet += (size_t)cbWritten;
+ }
+ else if (cbWritten == 0 || cbRet > 0)
+ break;
+ else
+ return -1;
+ }
+ }
+ return cbRet;
+}
+
+ssize_t writev(int fd, const struct iovec *vector, int count)
+{
+ ssize_t size = 0;
+ if (count > 0)
+ {
+ int i;
+
+ /* To get consistent console output, we must try combine the segments
+ when outputing to the console. */
+ if (count > 1 && is_console(fd))
+ {
+ char *pbTmp;
+ ssize_t cbTotal;
+ if (count == 1)
+ return maybe_con_write(fd, vector[0].iov_base, (int)vector[0].iov_len);
+
+ cbTotal = 0;
+ for (i = 0; i < count; i++)
+ cbTotal += vector[i].iov_len;
+ pbTmp = malloc(cbTotal);
+ if (pbTmp)
+ {
+ char *pbCur = pbTmp;
+ for (i = 0; i < count; i++)
+ {
+ memcpy(pbCur, vector[i].iov_base, vector[i].iov_len);
+ pbCur += vector[i].iov_len;
+ }
+ size = maybe_con_write(fd, pbTmp, cbTotal);
+ free(pbTmp);
+ return size;
+ }
+
+ /* fall back on segment by segment output. */
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ int cb = msc_write(fd, vector[i].iov_base, (int)vector[i].iov_len);
+ if (cb < 0)
+ return cb;
+ size += cb;
+ }
+ }
+ return size;
+}
+
+
+intmax_t strtoimax(const char *nptr, char **endptr, int base)
+{
+ if (*nptr != '-')
+ return _strtoui64(nptr, endptr, base);
+ return -(intmax_t)_strtoui64(nptr + 1, endptr, base);
+}
+
+
+uintmax_t strtoumax(const char *nptr, char **endptr, int base)
+{
+ return _strtoui64(nptr, endptr, base);
+}
+
+
+int asprintf(char **strp, const char *fmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, fmt);
+ rc = vasprintf(strp, fmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+int vasprintf(char **strp, const char *fmt, va_list va)
+{
+ int rc;
+ char *psz;
+ size_t cb = 1024;
+
+ *strp = NULL;
+ for (;;)
+ {
+ va_list va2;
+
+ psz = malloc(cb);
+ if (!psz)
+ return -1;
+
+#ifdef va_copy
+ va_copy(va2, va);
+ rc = vsnprintf(psz, cb, fmt, va2);
+ va_end(vaCopy);
+#else
+ va2 = va;
+ rc = vsnprintf(psz, cb, fmt, va2);
+#endif
+ if (rc < 0 || (size_t)rc < cb)
+ break;
+ cb *= 2;
+ free(psz);
+ }
+
+ *strp = psz;
+ return rc;
+}
+
+
+int utimes(const char *pszPath, const struct msc_timeval *paTimes)
+{
+ if (paTimes)
+ {
+ BirdTimeVal_T aTimes[2];
+ aTimes[0].tv_sec = paTimes[0].tv_sec;
+ aTimes[0].tv_usec = paTimes[0].tv_usec;
+ aTimes[1].tv_sec = paTimes[1].tv_sec;
+ aTimes[1].tv_usec = paTimes[1].tv_usec;
+ return birdUtimes(pszPath, aTimes);
+ }
+ return birdUtimes(pszPath, NULL);
+}
+
+
+int lutimes(const char *pszPath, const struct msc_timeval *paTimes)
+{
+ if (paTimes)
+ {
+ BirdTimeVal_T aTimes[2];
+ aTimes[0].tv_sec = paTimes[0].tv_sec;
+ aTimes[0].tv_usec = paTimes[0].tv_usec;
+ aTimes[1].tv_sec = paTimes[1].tv_sec;
+ aTimes[1].tv_usec = paTimes[1].tv_usec;
+ return birdUtimes(pszPath, aTimes);
+ }
+ return birdUtimes(pszPath, NULL);
+}
+
+
+int gettimeofday(struct msc_timeval *pNow, void *pvIgnored)
+{
+ struct __timeb64 Now;
+ int rc = _ftime64_s(&Now);
+ if (rc == 0)
+ {
+ pNow->tv_sec = Now.time;
+ pNow->tv_usec = Now.millitm * 1000;
+ return 0;
+ }
+ errno = rc;
+ return -1;
+}
+
+
+struct tm *localtime_r(const __time64_t *pNow, struct tm *pResult)
+{
+ int rc = _localtime64_s(pResult, pNow);
+ if (rc == 0)
+ return pResult;
+ errno = rc;
+ return NULL;
+}
+
+
+__time64_t timegm(struct tm *pNow)
+{
+ return _mkgmtime64(pNow);
+}
+
+
+/**
+ * Checks if the given file descriptor is a pipe or not.
+ *
+ * @returns TRUE if pipe, FALSE if not.
+ * @param fd The libc file descriptor number.
+ */
+static BOOL isPipeFd(int fd)
+{
+ /* Is pipe? */
+ HANDLE hFile = (HANDLE)_get_osfhandle(fd);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ DWORD fType = GetFileType(hFile);
+ fType &= ~FILE_TYPE_REMOTE;
+ if (fType == FILE_TYPE_PIPE)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/**
+ * This is a kludge to make pipe handles blocking.
+ *
+ * @returns TRUE if it's now blocking, FALSE if not a pipe or we failed to fix
+ * the blocking mode.
+ * @param fd The libc file descriptor number.
+ */
+static BOOL makePipeBlocking(int fd)
+{
+ if (isPipeFd(fd))
+ {
+ /* Try fix it. */
+ HANDLE hFile = (HANDLE)_get_osfhandle(fd);
+ DWORD fState = 0;
+ if (GetNamedPipeHandleState(hFile, &fState, NULL, NULL, NULL, NULL, 0))
+ {
+ fState &= ~PIPE_NOWAIT;
+ fState |= PIPE_WAIT;
+ if (SetNamedPipeHandleState(hFile, &fState, NULL, NULL))
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+/**
+ * Initializes the msc fake stuff.
+ * @returns 0 on success (non-zero would indicate failure, see rterr.h).
+ */
+int mscfake_init(void)
+{
+ /*
+ * Kludge against _write returning ENOSPC on non-blocking pipes.
+ */
+ makePipeBlocking(STDOUT_FILENO);
+ makePipeBlocking(STDERR_FILENO);
+
+ return 0;
+}
+
+/*
+ * Do this before main is called.
+ */
+#pragma section(".CRT$XIA", read)
+#pragma section(".CRT$XIU", read)
+#pragma section(".CRT$XIZ", read)
+typedef int (__cdecl *PFNCRTINIT)(void);
+static __declspec(allocate(".CRT$XIU")) PFNCRTINIT g_MscFakeInitVectorEntry = mscfake_init;
+
diff --git a/src/kmk/kmkbuiltin/mscfakes.h b/src/kmk/kmkbuiltin/mscfakes.h
new file mode 100644
index 0000000..95f76b2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mscfakes.h
@@ -0,0 +1,183 @@
+/* $Id: mscfakes.h 3213 2018-03-30 21:03:28Z bird $ */
+/** @file
+ * Unix fakes for MSC.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___mscfakes_h
+#define ___mscfakes_h
+#ifdef _MSC_VER
+
+#define timeval windows_timeval
+
+/* Include the config file (kmk's) so we don't need to duplicate stuff from it here. */
+#include "config.h"
+
+#include <io.h>
+#include <direct.h>
+#include <time.h>
+#include <stdarg.h>
+#include <malloc.h>
+#ifndef FAKES_NO_GETOPT_H
+# include "getopt.h"
+#endif
+#ifndef MSCFAKES_NO_WINDOWS_H
+# include <Windows.h>
+#endif
+
+#include <sys/stat.h>
+#include <io.h>
+#include <direct.h>
+#include "nt/ntstat.h"
+#include "nt/ntunlink.h"
+#ifdef MSC_DO_64_BIT_IO
+# if _MSC_VER >= 1400 /* We want 64-bit file lengths here when possible. */
+# define off_t __int64
+# define lseek _lseeki64
+# endif
+#endif
+
+#undef timeval
+
+#undef PATH_MAX
+#define PATH_MAX _MAX_PATH
+#undef MAXPATHLEN
+#define MAXPATHLEN _MAX_PATH
+
+#define EX_OK 0
+#define EX_OSERR 1
+#define EX_NOUSER 1
+#define EX_USAGE 1
+
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+#define F_OK 0
+#define X_OK 1
+#define W_OK 2
+#define R_OK 4
+
+#define EFTYPE EINVAL
+
+#define _PATH_DEVNULL "/dev/null"
+
+#ifndef MAX
+# define MAX(a,b) ((a) >= (b) ? (a) : (b))
+#endif
+
+typedef int mode_t;
+typedef unsigned short nlink_t;
+#if 0 /* found in config.h */
+typedef unsigned short uid_t;
+typedef unsigned short gid_t;
+#endif
+typedef intptr_t ssize_t;
+typedef unsigned long u_long;
+typedef unsigned int u_int;
+typedef unsigned short u_short;
+
+#if _MSC_VER >= 1600
+# include <stdint.h>
+#else
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed int int32_t;
+#endif
+
+struct msc_timeval
+{
+ __time64_t tv_sec;
+ long tv_usec;
+};
+#define timeval msc_timeval
+
+struct iovec
+{
+ char *iov_base;
+ size_t iov_len;
+};
+
+typedef __int64 intmax_t;
+#if 0 /* found in config.h */
+typedef unsigned __int64 uintmax_t;
+#endif
+
+#define chown(path, uid, gid) 0 /** @todo implement fchmod! */
+char *dirname(char *path);
+#define fsync(fd) 0
+#define fchown(fd,uid,gid) 0
+#define fchmod(fd, mode) 0 /** @todo implement fchmod! */
+#define geteuid() 0
+#define getegid() 0
+int lchmod(const char *path, mode_t mode);
+int msc_chmod(const char *path, mode_t mode);
+#define chmod msc_chmod
+#define lchown(path, uid, gid) chown(path, uid, gid)
+int link(const char *pszDst, const char *pszLink);
+int mkdir_msc(const char *path, mode_t mode);
+#define mkdir(path, mode) mkdir_msc(path, mode)
+#define mkfifo(path, mode) -1
+#define mknod(path, mode, devno) -1
+int mkstemp(char *temp);
+#define readlink(link, buf, size) -1
+#define reallocf(old, size) realloc(old, size)
+int rmdir_msc(const char *path);
+#define rmdir(path) rmdir_msc(path)
+intmax_t strtoimax(const char *nptr, char **endptr, int base);
+uintmax_t strtoumax(const char *nptr, char **endptr, int base);
+#define strtoll(a,b,c) strtoimax(a,b,c)
+#define strtoull(a,b,c) strtoumax(a,b,c)
+int asprintf(char **strp, const char *fmt, ...);
+int vasprintf(char **strp, const char *fmt, va_list ap);
+#if _MSC_VER < 1400
+int snprintf(char *buf, size_t size, const char *fmt, ...);
+#else
+#define snprintf _snprintf
+#endif
+int symlink(const char *pszDst, const char *pszLink);
+int utimes(const char *pszPath, const struct msc_timeval *paTimes);
+int lutimes(const char *pszPath, const struct msc_timeval *paTimes);
+ssize_t writev(int fd, const struct iovec *vector, int count);
+
+int gettimeofday(struct msc_timeval *pNow, void *pvIgnored);
+struct tm *localtime_r(const __time64_t *pNow, struct tm *pResult);
+__time64_t timegm(struct tm *pNow);
+#undef mktime
+#define mktime _mktime64
+
+/* bird write ENOSPC hacks. */
+#undef write
+#define write msc_write
+ssize_t msc_write(int fd, const void *pvSrc, size_t cbSrc);
+
+/*
+ * MSC fake internals / helpers.
+ */
+int birdSetErrno(unsigned dwErr);
+
+#endif /* _MSC_VER */
+#endif
+
diff --git a/src/kmk/kmkbuiltin/mv.c b/src/kmk/kmkbuiltin/mv.c
new file mode 100644
index 0000000..7db962c
--- /dev/null
+++ b/src/kmk/kmkbuiltin/mv.c
@@ -0,0 +1,529 @@
+/*-
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Ken Smith of The State University of New York at Buffalo.
+ *
+ * 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.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1989, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mv.c 8.2 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/mv/mv.c,v 1.46 2005/09/05 04:36:08 csjp Exp $");
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/types.h>
+#ifndef _MSC_VER
+# ifdef CROSS_DEVICE_MOVE
+# include <sys/acl.h>
+# endif
+# include <sys/param.h>
+# include <sys/time.h>
+# include <sys/wait.h>
+# if !defined(__HAIKU__) && !defined(__gnu_hurd__)
+# include <sys/mount.h>
+# endif
+#endif
+#include <sys/stat.h>
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <paths.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct MVINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int fflg, iflg, nflg, vflg;
+} MVINSTANCE;
+typedef MVINSTANCE *PMVINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void bsd_strmode(mode_t mode, char *p); /* strmode.c */
+
+static int do_move(PMVINSTANCE, char *, char *);
+#if 0 // def CROSS_DEVICE_MOVE
+static int fastcopy(char *, char *, struct stat *);
+static int copy(char *, char *);
+#endif
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_mv(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ MVINSTANCE This;
+ struct getopt_state_r gos;
+ size_t baselen, len;
+ int rval;
+ char *p, *endp;
+ struct stat sb;
+ int ch;
+ char path[PATH_MAX];
+
+ /* Initialize instance. */
+ This.pCtx = pCtx;
+ This.fflg = 0;
+ This.iflg = 0;
+ This.nflg = 0;
+ This.vflg = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "finv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'i':
+ This.iflg = 1;
+ This.fflg = This.nflg = 0;
+ break;
+ case 'f':
+ This.fflg = 1;
+ This.iflg = This.nflg = 0;
+ break;
+ case 'n':
+ This.nflg = 1;
+ This.fflg = This.iflg = 0;
+ break;
+ case 'v':
+ This.vflg = 1;
+ break;
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 2)
+ return usage(pCtx, 1);
+
+ /*
+ * If the stat on the target fails or the target isn't a directory,
+ * try the move. More than 2 arguments is an error in this case.
+ */
+ if (stat(argv[argc - 1], &sb) || !S_ISDIR(sb.st_mode)) {
+ if (argc > 2)
+ return usage(pCtx, 1);
+ return do_move(&This, argv[0], argv[1]);
+ }
+
+ /* It's a directory, move each file into it. */
+ baselen = strlen(argv[argc - 1]);
+ if (baselen > sizeof(path) - 1)
+ return errx(pCtx, 1, "%s: destination pathname too long", *argv);
+ memcpy(path, argv[argc - 1], baselen);
+ endp = &path[baselen];
+ *endp = '\0';
+#if defined(_MSC_VER) || defined(__EMX__)
+ if (!baselen || (*(endp - 1) != '/' && *(endp - 1) != '\\' && *(endp - 1) != ':')) {
+#else
+ if (!baselen || *(endp - 1) != '/') {
+#endif
+ *endp++ = '/';
+ ++baselen;
+ }
+ for (rval = 0; --argc; ++argv) {
+ /*
+ * Find the last component of the source pathname. It
+ * may have trailing slashes.
+ */
+ p = *argv + strlen(*argv);
+#if defined(_MSC_VER) || defined(__EMX__)
+ while (p != *argv && (p[-1] == '/' || p[-1] == '\\'))
+ --p;
+ while (p != *argv && p[-1] != '/' && p[-1] != '/' && p[-1] != ':')
+ --p;
+#else
+ while (p != *argv && p[-1] == '/')
+ --p;
+ while (p != *argv && p[-1] != '/')
+ --p;
+#endif
+
+ if ((baselen + (len = strlen(p))) >= PATH_MAX) {
+ warnx(pCtx, "%s: destination pathname too long", *argv);
+ rval = 1;
+ } else {
+ memmove(endp, p, (size_t)len + 1);
+ if (do_move(&This, *argv, path))
+ rval = 1;
+ }
+ }
+ return rval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_mv", NULL };
+ return kmk_builtin_mv(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+do_move(PMVINSTANCE pThis, char *from, char *to)
+{
+ struct stat sb;
+ int ask, ch, first;
+ char modep[15];
+
+ /*
+ * Check access. If interactive and file exists, ask user if it
+ * should be replaced. Otherwise if file exists but isn't writable
+ * make sure the user wants to clobber it.
+ */
+ if (!pThis->fflg && !access(to, F_OK)) {
+
+ /* prompt only if source exist */
+ if (lstat(from, &sb) == -1) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+
+#define YESNO "(y/n [n]) "
+ ask = 0;
+ if (pThis->nflg) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s not overwritten\n", to);
+ return (0);
+ } else if (pThis->iflg) {
+ (void)fprintf(stderr, "overwrite %s? %s", to, YESNO);
+ ask = 1;
+ } else if (access(to, W_OK) && !stat(to, &sb)) {
+ bsd_strmode(sb.st_mode, modep);
+#if 0 /* probably not thread safe, also BSDism. */
+ (void)fprintf(stderr, "override %s%s%s/%s for %s? %s",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid((unsigned long)sb.st_uid, 0),
+ group_from_gid((unsigned long)sb.st_gid, 0), to, YESNO);
+#else
+ (void)fprintf(stderr, "override %s%s%lu/%lu for %s? %s",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ (unsigned long)sb.st_uid, (unsigned long)sb.st_gid,
+ to, YESNO);
+#endif
+ ask = 1;
+ }
+ if (ask) {
+ fflush(stderr);
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ if (first != 'y' && first != 'Y') {
+ kmk_builtin_ctx_printf(pThis->pCtx, 1, "not overwritten\n");
+ return (0);
+ }
+ }
+ }
+ if (!rename(from, to)) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+ }
+#ifdef _MSC_VER
+ if (errno == EEXIST) {
+ remove(to);
+ if (!rename(from, to)) {
+ if (pThis->vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+ }
+ }
+#endif
+
+ if (errno == EXDEV) {
+#if 1 //ndef CROSS_DEVICE_MOVE
+ warnx(pThis->pCtx, "cannot move `%s' to a different device: `%s'", from, to);
+ return (1);
+#else
+ struct statfs sfs;
+ char path[PATH_MAX];
+
+ /*
+ * If the source is a symbolic link and is on another
+ * filesystem, it can be recreated at the destination.
+ */
+ if (lstat(from, &sb) == -1) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+ if (!S_ISLNK(sb.st_mode)) {
+ /* Can't mv(1) a mount point. */
+ if (realpath(from, path) == NULL) {
+ warnx(pThis->pCtx, "cannot resolve %s: %s", from, path);
+ return (1);
+ }
+ if (!statfs(path, &sfs) &&
+ !strcmp(path, sfs.f_mntonname)) {
+ warnx(pThis->pCtx, "cannot rename a mount point");
+ return (1);
+ }
+ }
+#endif
+ } else {
+ warn(pThis->pCtx, "rename %s to %s", from, to);
+ return (1);
+ }
+
+#if 0//def CROSS_DEVICE_MOVE
+ /*
+ * If rename fails because we're trying to cross devices, and
+ * it's a regular file, do the copy internally; otherwise, use
+ * cp and rm.
+ */
+ if (lstat(from, &sb)) {
+ warn(pThis->pCtx, "%s", from);
+ return (1);
+ }
+ return (S_ISREG(sb.st_mode) ?
+ fastcopy(pThis, from, to, &sb) : copy(pThis, from, to));
+#endif
+}
+
+#if 0 //def CROSS_DEVICE_MOVE - using static buffers and fork.
+int
+static fastcopy(char *from, char *to, struct stat *sbp)
+{
+ struct timeval tval[2];
+ static u_int blen;
+ static char *bp;
+ mode_t oldmode;
+ int nread, from_fd, to_fd;
+ acl_t acl;
+
+ if ((from_fd = open(from, O_RDONLY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ warn("%s", from);
+ return (1);
+ }
+ if (blen < sbp->st_blksize) {
+ if (bp != NULL)
+ free(bp);
+ if ((bp = malloc((size_t)sbp->st_blksize)) == NULL) {
+ blen = 0;
+ warnx("malloc failed");
+ return (1);
+ }
+ blen = sbp->st_blksize;
+ }
+ while ((to_fd =
+ open(to, O_CREAT | O_EXCL | O_TRUNC | O_WRONLY | KMK_OPEN_NO_INHERIT, 0)) < 0) {
+ if (errno == EEXIST && unlink(to) == 0)
+ continue;
+ warn("%s", to);
+ (void)close(from_fd);
+ return (1);
+ }
+ while ((nread = read(from_fd, bp, (size_t)blen)) > 0)
+ if (write(to_fd, bp, (size_t)nread) != nread) {
+ warn("%s", to);
+ goto err;
+ }
+ if (nread < 0) {
+ warn("%s", from);
+err: if (unlink(to))
+ warn("%s: remove", to);
+ (void)close(from_fd);
+ (void)close(to_fd);
+ return (1);
+ }
+
+ oldmode = sbp->st_mode & ALLPERMS;
+ if (fchown(to_fd, sbp->st_uid, sbp->st_gid)) {
+ warn("%s: set owner/group (was: %lu/%lu)", to,
+ (u_long)sbp->st_uid, (u_long)sbp->st_gid);
+ if (oldmode & (S_ISUID | S_ISGID)) {
+ warnx(
+"%s: owner/group changed; clearing suid/sgid (mode was 0%03o)",
+ to, oldmode);
+ sbp->st_mode &= ~(S_ISUID | S_ISGID);
+ }
+ }
+ /*
+ * POSIX 1003.2c states that if _POSIX_ACL_EXTENDED is in effect
+ * for dest_file, then it's ACLs shall reflect the ACLs of the
+ * source_file.
+ */
+ if (fpathconf(to_fd, _PC_ACL_EXTENDED) == 1 &&
+ fpathconf(from_fd, _PC_ACL_EXTENDED) == 1) {
+ acl = acl_get_fd(from_fd);
+ if (acl == NULL)
+ warn("failed to get acl entries while setting %s",
+ from);
+ else if (acl_set_fd(to_fd, acl) < 0)
+ warn("failed to set acl entries for %s", to);
+ }
+ (void)close(from_fd);
+ if (fchmod(to_fd, sbp->st_mode))
+ warn("%s: set mode (was: 0%03o)", to, oldmode);
+ /*
+ * XXX
+ * NFS doesn't support chflags; ignore errors unless there's reason
+ * to believe we're losing bits. (Note, this still won't be right
+ * if the server supports flags and we were trying to *remove* flags
+ * on a file that we copied, i.e., that we didn't create.)
+ */
+ errno = 0;
+ if (fchflags(to_fd, (u_long)sbp->st_flags))
+ if (errno != EOPNOTSUPP || sbp->st_flags != 0)
+ warn("%s: set flags (was: 0%07o)", to, sbp->st_flags);
+
+ tval[0].tv_sec = sbp->st_atime;
+ tval[1].tv_sec = sbp->st_mtime;
+ tval[0].tv_usec = tval[1].tv_usec = 0;
+ if (utimes(to, tval))
+ warn("%s: set times", to);
+
+ if (close(to_fd)) {
+ warn("%s", to);
+ return (1);
+ }
+
+ if (unlink(from)) {
+ warn("%s: remove", from);
+ return (1);
+ }
+ if (vflg)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s -> %s\n", from, to);
+ return (0);
+}
+
+int
+copy(char *from, char *to)
+{
+ int pid, status;
+
+ if ((pid = fork()) == 0) {
+ execl(_PATH_CP, "mv", vflg ? "-PRpv" : "-PRp", "--", from, to,
+ (char *)NULL);
+ warn("%s", _PATH_CP);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_CP);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_CP);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_CP, WEXITSTATUS(status));
+ return (1);
+ }
+ if (!(pid = vfork())) {
+ execl(_PATH_RM, "mv", "-rf", "--", from, (char *)NULL);
+ warn("%s", _PATH_RM);
+ _exit(1);
+ }
+ if (waitpid(pid, &status, 0) == -1) {
+ warn("%s: waitpid", _PATH_RM);
+ return (1);
+ }
+ if (!WIFEXITED(status)) {
+ warnx("%s: did not terminate normally", _PATH_RM);
+ return (1);
+ }
+ if (WEXITSTATUS(status)) {
+ warnx("%s: terminated with %d (non-zero) status",
+ _PATH_RM, WEXITSTATUS(status));
+ return (1);
+ }
+ return (0);
+}
+#endif /* CROSS_DEVICE_MOVE */
+
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-f | -i | -n] [-v] source target\n"
+ " or: %s [-f | -i | -n] [-v] source ... directory\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return EX_USAGE;
+}
diff --git a/src/kmk/kmkbuiltin/openbsd.c b/src/kmk/kmkbuiltin/openbsd.c
new file mode 100644
index 0000000..b6b59db
--- /dev/null
+++ b/src/kmk/kmkbuiltin/openbsd.c
@@ -0,0 +1,54 @@
+/* $Id: openbsd.c 2421 2010-10-17 21:27:53Z bird $ */
+/** @file
+ * Missing BSD functions in OpenBSD.
+ */
+
+/*
+ * Copyright (c) 2006-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+int lchmod(const char *path, mode_t mode)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return chmod(path, mode);
+}
+
+
+int lutimes(const char *path, const struct timeval *tvs)
+{
+ struct stat st;
+ if (lstat(path, &st))
+ return -1;
+ if (S_ISLNK(st.st_mode))
+ return 0; /* pretend success */
+ return utimes(path, tvs);
+}
+
diff --git a/src/kmk/kmkbuiltin/osdep.c b/src/kmk/kmkbuiltin/osdep.c
new file mode 100644
index 0000000..e9f1a77
--- /dev/null
+++ b/src/kmk/kmkbuiltin/osdep.c
@@ -0,0 +1,48 @@
+/* $Id: osdep.c 2656 2012-09-10 20:39:16Z bird $ */
+/** @file
+ * Include all the OS dependent bits when bootstrapping.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <config.h>
+
+/** @todo replace this by proper configure.in tests. */
+
+#if defined(_MSC_VER)
+# include "mscfakes.c"
+# include "fts.c"
+
+#elif defined(__sun__)
+# include "solfakes.c"
+# include "fts.c"
+
+#elif defined(__APPLE__)
+# include "darwin.c"
+
+#elif defined(__OpenBSD__)
+# include "openbsd.c"
+
+#elif defined(__HAIKU__)
+# include "haikufakes.c"
+
+#endif
+
diff --git a/src/kmk/kmkbuiltin/printf.c b/src/kmk/kmkbuiltin/printf.c
new file mode 100644
index 0000000..9dc5956
--- /dev/null
+++ b/src/kmk/kmkbuiltin/printf.c
@@ -0,0 +1,954 @@
+/* $NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>
+#ifndef lint
+#if !defined(BUILTIN) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n");
+#endif
+#endif
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
+#else
+__RCSID("$NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $");
+#endif
+#endif*/ /* not lint */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#if !defined(KMK_BUILTIN_STANDALONE) && !defined(BUILTIN) && !defined(SHELL)
+# include "../makeint.h"
+# include "../filedef.h"
+# include "../variable.h"
+#else
+# include "config.h"
+#endif
+#include <sys/types.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "getopt_r.h"
+#ifdef __sun__
+# include "solfakes.h"
+#endif
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+
+#include "../kmkbuiltin.h"
+
+#ifdef KBUILD_OS_WINDOWS
+/* This is a trick to speed up console output on windows. */
+# include "console.h"
+# undef fwrite
+# define fwrite maybe_con_fwrite
+#endif
+
+#if 0
+#ifdef BUILTIN /* csh builtin */
+#define kmk_builtin_printf progprintf
+#endif
+
+#ifdef SHELL /* sh (aka ash) builtin */
+#define kmk_builtin_printf printfcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if 0 /*def __GNUC__ - bird: gcc complains about non-ISO-standard escape. */
+#define ESCAPE '\e'
+#else
+#define ESCAPE 033
+#endif
+
+#define PF(f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)wrap_printf(pThis, f, fieldwidth, precision, func); \
+ else \
+ (void)wrap_printf(pThis, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)wrap_printf(pThis, f, precision, func); \
+ else \
+ (void)wrap_printf(pThis, f, func); \
+}
+
+#define APF(cpp, f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)asprintf(cpp, f, fieldwidth, precision, func); \
+ else \
+ (void)asprintf(cpp, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)asprintf(cpp, f, precision, func); \
+ else \
+ (void)asprintf(cpp, f, func); \
+}
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct PRINTFINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ /* former globals */
+ size_t b_length;
+ char *b_fmt;
+ int rval;
+ char **gargv;
+#ifndef KMK_BUILTIN_STANDALONE
+ char *g_o;
+#endif
+ /* former function level statics in common_printf(); both need freeing. */
+ char *a, *t;
+
+ /* former function level statics in conv_expand(); needs freeing. */
+ char *conv_str;
+
+ /* Buffer the output because windows doesn't do line buffering of stdout. */
+ size_t g_cchBuf;
+ char g_achBuf[256];
+} PRINTFINSTANCE;
+typedef PRINTFINSTANCE *PPRINTFINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int common_printf(PPRINTFINSTANCE pThis, char *argv[], PKMKBUILTINCTX pCtx);
+static int common_printf_inner(PPRINTFINSTANCE pThis, char *argv[]);
+static void conv_escape_str(PPRINTFINSTANCE, char *, void (*)(PPRINTFINSTANCE, int));
+static char *conv_escape(PPRINTFINSTANCE, char *, char *);
+static const char *conv_expand(PPRINTFINSTANCE, const char *);
+static int getchr(PPRINTFINSTANCE);
+static double getdouble(PPRINTFINSTANCE);
+static int getwidth(PPRINTFINSTANCE);
+static intmax_t getintmax(PPRINTFINSTANCE);
+static uintmax_t getuintmax(PPRINTFINSTANCE);
+static char *getstr(PPRINTFINSTANCE);
+static char *mklong(PPRINTFINSTANCE, const char *, int, char[64]);
+static void check_conversion(PPRINTFINSTANCE, const char *, const char *);
+static int usage(PKMKBUILTINCTX, int);
+
+static int flush_buffer(PPRINTFINSTANCE);
+static void b_count(PPRINTFINSTANCE, int);
+static void b_output(PPRINTFINSTANCE, int);
+static int wrap_putchar(PPRINTFINSTANCE, int ch);
+static int wrap_printf(PPRINTFINSTANCE, const char *, ...);
+
+
+
+int kmk_builtin_printf(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ PRINTFINSTANCE This;
+ struct getopt_state_r gos;
+ int ch;
+
+ getopt_initialize_r(&gos, argc, argv, "", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1) {
+ switch (ch) {
+ case 261:
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 1)
+ return usage(pCtx, 1);
+
+#ifndef KMK_BUILTIN_STANDALONE
+ This.g_o = NULL;
+#endif
+ return common_printf(&This, argv, pCtx);
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_printf", NULL };
+ setlocale(LC_ALL, "");
+ return kmk_builtin_printf(argc, argv, envp, &Ctx);
+}
+#else /* KMK_BUILTIN_STANDALONE */
+/* entry point used by function.c $(printf ..,..). */
+char *kmk_builtin_func_printf(char *o, char **argv, const char *funcname)
+{
+ PRINTFINSTANCE This;
+ int rc;
+ int argc;
+
+ for (argc = 0; argv[argc] != NULL; argc++)
+ /* nothing */;
+ if (argc == 0)
+ fatal(NILF, strlen(funcname) + INTSTR_LENGTH, _("$(%s): no format string\n"), funcname);
+
+ This.g_o = o;
+ rc = common_printf(&This, argv, NULL);
+ o = This.g_o;
+
+ if (rc != 0)
+ fatal(NILF, strlen(funcname) + INTSTR_LENGTH, _("$(%s): failure rc=%d\n"), funcname, rc);
+ return o;
+}
+#endif /* KMK_BUILTIN_STANDALONE */
+
+static int common_printf(PPRINTFINSTANCE pThis, char *argv[], PKMKBUILTINCTX pCtx)
+{
+ int rc;
+
+ /* Init all but g_o. */
+ pThis->pCtx = pCtx;
+ pThis->b_length = 0;
+ pThis->b_fmt = NULL;
+ pThis->rval = 0;
+ pThis->gargv = NULL;
+ pThis->g_cchBuf = 0;
+ pThis->a = NULL;
+ pThis->t = NULL;
+ pThis->conv_str = NULL;
+
+ rc = common_printf_inner(pThis, argv);
+
+ /* Cleanup allocations. */
+ if (pThis->a) {
+ free(pThis->a);
+ pThis->a = NULL;
+ }
+ if (pThis->t) {
+ free(pThis->t);
+ pThis->t = NULL;
+ }
+ if (pThis->conv_str) {
+ free(pThis->conv_str);
+ pThis->conv_str = NULL;
+ }
+ return rc;
+}
+
+static int common_printf_inner(PPRINTFINSTANCE pThis, char *argv[])
+{
+ char *fmt, *start;
+ int fieldwidth, precision;
+ char nextch;
+ char *format;
+ int ch;
+ char longbuf[64];
+
+ format = *argv;
+ pThis->gargv = ++argv;
+
+#define SKIP1 "#-+ 0"
+#define SKIP2 "*0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; (ch = *fmt++) != '\0';) {
+ if (ch == '\\') {
+ char c_ch;
+ fmt = conv_escape(pThis, fmt, &c_ch);
+ wrap_putchar(pThis, c_ch);
+ continue;
+ }
+ if (ch != '%' || (*fmt == '%' && ++fmt)) {
+ (void)wrap_putchar(pThis, ch);
+ continue;
+ }
+
+ /* Ok - we've found a format specification,
+ Save its address for a later printf(). */
+ start = fmt - 1;
+
+ /* skip to field width */
+ fmt += strspn(fmt, SKIP1);
+ fieldwidth = *fmt == '*' ? getwidth(pThis) : -1;
+
+ /* skip to possible '.', get following precision */
+ fmt += strspn(fmt, SKIP2);
+ if (*fmt == '.')
+ ++fmt;
+ precision = *fmt == '*' ? getwidth(pThis) : -1;
+
+ fmt += strspn(fmt, SKIP2);
+
+ ch = *fmt;
+ if (!ch) {
+ flush_buffer(pThis);
+ warnx(pThis->pCtx, "missing format character");
+ return (1);
+ }
+ /* null terminate format string to we can use it
+ as an argument to printf. */
+ nextch = fmt[1];
+ fmt[1] = 0;
+ switch (ch) {
+
+ case 'B': {
+ const char *p = conv_expand(pThis, getstr(pThis));
+ *fmt = 's';
+ PF(start, p);
+ break;
+ }
+ case 'b': {
+ /* There has to be a better way to do this,
+ * but the string we generate might have
+ * embedded nulls. */
+ char *cp = getstr(pThis);
+ /* Free on entry in case shell longjumped out */
+ if (pThis->a != NULL) {
+ free(pThis->a);
+ pThis->a = NULL;
+ }
+ if (pThis->t != NULL) {
+ free(pThis->t);
+ pThis->t = NULL;
+ }
+ /* Count number of bytes we want to output */
+ pThis->b_length = 0;
+ conv_escape_str(pThis, cp, b_count);
+ pThis->t = malloc(pThis->b_length + 1);
+ if (pThis->t == NULL)
+ break;
+ memset(pThis->t, 'x', pThis->b_length);
+ pThis->t[pThis->b_length] = 0;
+ /* Get printf to calculate the lengths */
+ *fmt = 's';
+ APF(&pThis->a, start, pThis->t);
+ pThis->b_fmt = pThis->a;
+ /* Output leading spaces and data bytes */
+ conv_escape_str(pThis, cp, b_output);
+ /* Add any trailing spaces */
+ wrap_printf(pThis, "%s", pThis->b_fmt);
+ break;
+ }
+ case 'c': {
+ char p = getchr(pThis);
+ PF(start, p);
+ break;
+ }
+ case 's': {
+ char *p = getstr(pThis);
+ PF(start, p);
+ break;
+ }
+ case 'd':
+ case 'i': {
+ intmax_t p = getintmax(pThis);
+ char *f = mklong(pThis, start, ch, longbuf);
+ PF(f, p);
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ uintmax_t p = getuintmax(pThis);
+ char *f = mklong(pThis, start, ch, longbuf);
+ PF(f, p);
+ break;
+ }
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G': {
+ double p = getdouble(pThis);
+ PF(start, p);
+ break;
+ }
+ default:
+ flush_buffer(pThis);
+ warnx(pThis->pCtx, "%s: invalid directive", start);
+ return 1;
+ }
+ *fmt++ = ch;
+ *fmt = nextch;
+ /* escape if a \c was encountered */
+ if (pThis->rval & 0x100) {
+ flush_buffer(pThis);
+ return pThis->rval & ~0x100;
+ }
+ }
+ } while (pThis->gargv != argv && *pThis->gargv);
+
+ flush_buffer(pThis);
+ return pThis->rval;
+}
+
+
+/* helper functions for conv_escape_str */
+
+static void
+/*ARGSUSED*/
+b_count(PPRINTFINSTANCE pThis, int ch)
+{
+ pThis->b_length++;
+ (void)ch;
+}
+
+/* Output one converted character for every 'x' in the 'format' */
+
+static void
+b_output(PPRINTFINSTANCE pThis, int ch)
+{
+ for (;;) {
+ switch (*pThis->b_fmt++) {
+ case 0:
+ pThis->b_fmt--;
+ return;
+ case ' ':
+ wrap_putchar(pThis, ' ');
+ break;
+ default:
+ wrap_putchar(pThis, ch);
+ return;
+ }
+ }
+}
+
+static int wrap_putchar(PPRINTFINSTANCE pThis, int ch)
+{
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->g_o) {
+ char sz[2];
+ sz[0] = ch; sz[1] = '\0';
+ pThis->g_o = variable_buffer_output(pThis->g_o, sz, 1);
+ }
+ else
+#endif
+ /* Buffered output. */
+ if (pThis->g_cchBuf + 1 < sizeof(pThis->g_achBuf)) {
+ pThis->g_achBuf[pThis->g_cchBuf++] = ch;
+ } else {
+ int rc = flush_buffer(pThis);
+ pThis->g_achBuf[pThis->g_cchBuf++] = ch;
+ if (rc)
+ return -1;
+ }
+ return 0;
+}
+
+static int wrap_printf(PPRINTFINSTANCE pThis, const char * fmt, ...)
+{
+ ssize_t cchRet;
+ va_list va;
+ char *pszTmp;
+
+ va_start(va, fmt);
+ cchRet = vasprintf(&pszTmp, fmt, va);
+ va_end(va);
+ if (cchRet >= 0) {
+#ifndef KMK_BUILTIN_STANDALONE
+ if (pThis->g_o) {
+ pThis->g_o = variable_buffer_output(pThis->g_o, pszTmp, cchRet);
+ } else
+#endif
+ {
+ if (cchRet + pThis->g_cchBuf <= sizeof(pThis->g_achBuf)) {
+ /* We've got space in the buffer. */
+ memcpy(&pThis->g_achBuf[pThis->g_cchBuf], pszTmp, cchRet);
+ pThis->g_cchBuf += cchRet;
+ } else {
+ /* Try write out complete lines. */
+ const char *pszLeft = pszTmp;
+ ssize_t cchLeft = cchRet;
+
+ while (cchLeft > 0) {
+ const char *pchNewLine = strchr(pszLeft, '\n');
+ ssize_t cchLine = pchNewLine ? pchNewLine - pszLeft + 1 : cchLeft;
+ if (pThis->g_cchBuf + cchLine <= sizeof(pThis->g_achBuf)) {
+ memcpy(&pThis->g_achBuf[pThis->g_cchBuf], pszLeft, cchLine);
+ pThis->g_cchBuf += cchLine;
+ } else {
+ if (flush_buffer(pThis) < 0) {
+ return -1;
+ }
+#ifndef KMK_BUILTIN_STANDALONE
+ if (output_write_text(pThis->pCtx->pOut, 0,pszLeft, cchLine) < 1)
+#else
+ if (fwrite(pszLeft, cchLine, 1, stdout) < 1)
+#endif
+
+ return -1;
+ }
+ pszLeft += cchLine;
+ cchLeft -= cchLine;
+ }
+ }
+ }
+ free(pszTmp);
+ }
+ return (int)cchRet;
+}
+
+/**
+ * Flushes the g_abBuf/g_cchBuf.
+ */
+static int flush_buffer(PPRINTFINSTANCE pThis)
+{
+ ssize_t cchToWrite = pThis->g_cchBuf;
+ if (cchToWrite > 0) {
+#ifndef KMK_BUILTIN_STANDALONE
+ ssize_t cchWritten = output_write_text(pThis->pCtx->pOut, 0, pThis->g_achBuf, cchToWrite);
+#else
+ ssize_t cchWritten = fwrite(pThis->g_achBuf, 1, cchToWrite, stdout);
+#endif
+ pThis->g_cchBuf = 0;
+ if (cchWritten >= cchToWrite) {
+ /* likely */
+ } else {
+ ssize_t off = cchWritten;
+ if (cchWritten >= 0) {
+ off = cchWritten;
+ } else if (errno == EINTR) {
+ cchWritten = 0;
+ } else {
+ return -1;
+ }
+
+ while (off < cchToWrite) {
+#ifndef KMK_BUILTIN_STANDALONE
+ cchWritten = output_write_text(pThis->pCtx->pOut, 0, &pThis->g_achBuf[off], cchToWrite - off);
+#else
+ cchWritten = fwrite(&pThis->g_achBuf[off], 1, cchToWrite - off, stdout);
+#endif
+ if (cchWritten > 0) {
+ off += cchWritten;
+ } else if (errno == EINTR) {
+ /* nothing */
+ } else {
+ return -1;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string if a \c escape is encountered.
+ */
+static void
+conv_escape_str(PPRINTFINSTANCE pThis, char *str, void (*do_putchar)(PPRINTFINSTANCE, int))
+{
+ int value;
+ int ch;
+ char c;
+
+ while ((ch = *str++) != '\0') {
+ if (ch != '\\') {
+ do_putchar(pThis, ch);
+ continue;
+ }
+
+ ch = *str++;
+ if (ch == 'c') {
+ /* \c as in SYSV echo - abort all processing.... */
+ pThis->rval |= 0x100;
+ break;
+ }
+
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (ch == '0') {
+ int octnum = 0, i;
+ for (i = 0; i < 3; i++) {
+ if (!isdigit((unsigned char)*str) || *str > '7')
+ break;
+ octnum = (octnum << 3) | (*str++ - '0');
+ }
+ do_putchar(pThis, octnum);
+ continue;
+ }
+
+ /* \[M][^|-]C as defined by vis(3) */
+ if (ch == 'M' && *str == '-') {
+ do_putchar(pThis, 0200 | str[1]);
+ str += 2;
+ continue;
+ }
+ if (ch == 'M' && *str == '^') {
+ str++;
+ value = 0200;
+ ch = '^';
+ } else
+ value = 0;
+ if (ch == '^') {
+ ch = *str++;
+ if (ch == '?')
+ value |= 0177;
+ else
+ value |= ch & 037;
+ do_putchar(pThis, value);
+ continue;
+ }
+
+ /* Finally test for sequences valid in the format string */
+ str = conv_escape(pThis, str - 1, &c);
+ do_putchar(pThis, c);
+ }
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static char *
+conv_escape(PPRINTFINSTANCE pThis, char *str, char *conv_ch)
+{
+ int value;
+ int ch;
+ char num_buf[4], *num_end;
+
+ ch = *str++;
+
+ switch (ch) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ num_buf[0] = ch;
+ ch = str[0];
+ num_buf[1] = ch;
+ num_buf[2] = ch ? str[1] : 0;
+ num_buf[3] = 0;
+ value = strtoul(num_buf, &num_end, 8);
+ str += num_end - (num_buf + 1);
+ break;
+
+ case 'x':
+ /* Hexadecimal character constants are not required to be
+ supported (by SuS v1) because there is no consistent
+ way to detect the end of the constant.
+ Supporting 2 byte constants is a compromise. */
+ ch = str[0];
+ num_buf[0] = ch;
+ num_buf[1] = ch ? str[1] : 0;
+ num_buf[2] = 0;
+ value = strtoul(num_buf, &num_end, 16);
+ str += num_end - num_buf;
+ break;
+
+ case '\\': value = '\\'; break; /* backslash */
+ case '\'': value = '\''; break; /* single quote */
+ case '"': value = '"'; break; /* double quote */
+ case 'a': value = '\a'; break; /* alert */
+ case 'b': value = '\b'; break; /* backspace */
+ case 'e': value = ESCAPE; break; /* escape */
+ case 'f': value = '\f'; break; /* form-feed */
+ case 'n': value = '\n'; break; /* newline */
+ case 'r': value = '\r'; break; /* carriage-return */
+ case 't': value = '\t'; break; /* tab */
+ case 'v': value = '\v'; break; /* vertical-tab */
+
+ default:
+ warnx(pThis->pCtx, "unknown escape sequence `\\%c'", ch);
+ pThis->rval = 1;
+ value = ch;
+ break;
+ }
+
+ *conv_ch = value;
+ return str;
+}
+
+/* expand a string so that everything is printable */
+
+static const char *
+conv_expand(PPRINTFINSTANCE pThis, const char *str)
+{
+ static const char no_memory[] = "<no memory>";
+ char *cp;
+ int ch;
+
+ if (pThis->conv_str)
+ free(pThis->conv_str);
+ /* get a buffer that is definitely large enough.... */
+ pThis->conv_str = cp = malloc(4 * strlen(str) + 1);
+ if (!cp)
+ return no_memory;
+
+ while ((ch = *(const unsigned char *)str++) != '\0') {
+ switch (ch) {
+ /* Use C escapes for expected control characters */
+ case '\\': ch = '\\'; break; /* backslash */
+ case '\'': ch = '\''; break; /* single quote */
+ case '"': ch = '"'; break; /* double quote */
+ case '\a': ch = 'a'; break; /* alert */
+ case '\b': ch = 'b'; break; /* backspace */
+ case ESCAPE: ch = 'e'; break; /* escape */
+ case '\f': ch = 'f'; break; /* form-feed */
+ case '\n': ch = 'n'; break; /* newline */
+ case '\r': ch = 'r'; break; /* carriage-return */
+ case '\t': ch = 't'; break; /* tab */
+ case '\v': ch = 'v'; break; /* vertical-tab */
+ default:
+ /* Copy anything printable */
+ if (isprint(ch)) {
+ *cp++ = ch;
+ continue;
+ }
+ /* Use vis(3) encodings for the rest */
+ *cp++ = '\\';
+ if (ch & 0200) {
+ *cp++ = 'M';
+ ch &= ~0200;
+ }
+ if (ch == 0177) {
+ *cp++ = '^';
+ *cp++ = '?';
+ continue;
+ }
+ if (ch < 040) {
+ *cp++ = '^';
+ *cp++ = ch | 0100;
+ continue;
+ }
+ *cp++ = '-';
+ *cp++ = ch;
+ continue;
+ }
+ *cp++ = '\\';
+ *cp++ = ch;
+ }
+
+ *cp = 0;
+ return pThis->conv_str;
+}
+
+static char *
+mklong(PPRINTFINSTANCE pThis, const char *str, int ch, char copy[64])
+{
+ size_t len;
+
+ len = strlen(str) - 1;
+ if (len > 64 - 5) {
+ warnx(pThis->pCtx, "format %s too complex\n", str);
+ len = 4;
+ }
+ (void)memmove(copy, str, len);
+#ifndef _MSC_VER
+ copy[len++] = 'j';
+#else
+ copy[len++] = 'I';
+ copy[len++] = '6';
+ copy[len++] = '4';
+#endif
+ copy[len++] = ch;
+ copy[len] = '\0';
+ return copy;
+}
+
+static int
+getchr(PPRINTFINSTANCE pThis)
+{
+ if (!*pThis->gargv)
+ return 0;
+ return (int)**pThis->gargv++;
+}
+
+static char *
+getstr(PPRINTFINSTANCE pThis)
+{
+ static char empty[] = "";
+ if (!*pThis->gargv)
+ return empty;
+ return *pThis->gargv++;
+}
+
+static int
+getwidth(PPRINTFINSTANCE pThis)
+{
+ long val;
+ char *s, *ep;
+
+ s = *pThis->gargv;
+ if (!s)
+ return (0);
+ pThis->gargv++;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ check_conversion(pThis, s, ep);
+
+ /* Arbitrarily 'restrict' field widths to 1Mbyte */
+ if (val < 0 || val > 1 << 20) {
+ warnx(pThis->pCtx, "%s: invalid field width", s);
+ return 0;
+ }
+
+ return val;
+}
+
+static intmax_t
+getintmax(PPRINTFINSTANCE pThis)
+{
+ intmax_t val;
+ char *cp, *ep;
+
+ cp = *pThis->gargv;
+ if (cp == NULL)
+ return 0;
+ pThis->gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp+1);
+
+ errno = 0;
+ val = strtoimax(cp, &ep, 0);
+ check_conversion(pThis, cp, ep);
+ return val;
+}
+
+static uintmax_t
+getuintmax(PPRINTFINSTANCE pThis)
+{
+ uintmax_t val;
+ char *cp, *ep;
+
+ cp = *pThis->gargv;
+ if (cp == NULL)
+ return 0;
+ pThis->gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp + 1);
+
+ /* strtoumax won't error -ve values */
+ while (isspace(*(unsigned char *)cp))
+ cp++;
+ if (*cp == '-') {
+ warnx(pThis->pCtx, "%s: expected positive numeric value", cp);
+ pThis->rval = 1;
+ return 0;
+ }
+
+ errno = 0;
+ val = strtoumax(cp, &ep, 0);
+ check_conversion(pThis, cp, ep);
+ return val;
+}
+
+static double
+getdouble(PPRINTFINSTANCE pThis)
+{
+ double val;
+ char *ep;
+ char *s;
+
+ s = *pThis->gargv;
+ if (!s)
+ return (0.0);
+ pThis->gargv++;
+
+ if (*s == '\"' || *s == '\'')
+ return (double) s[1];
+
+ errno = 0;
+ val = strtod(s, &ep);
+ check_conversion(pThis, s, ep);
+ return val;
+}
+
+static void
+check_conversion(PPRINTFINSTANCE pThis, const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx(pThis->pCtx, "%s: expected numeric value", s);
+ else
+ warnx(pThis->pCtx, "%s: not completely converted", s);
+ pThis->rval = 1;
+ } else if (errno == ERANGE) {
+ warnx(pThis->pCtx, "%s: %s", s, strerror(ERANGE));
+ pThis->rval = 1;
+ }
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s format [arg ...]\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
diff --git a/src/kmk/kmkbuiltin/redirect.c b/src/kmk/kmkbuiltin/redirect.c
new file mode 100644
index 0000000..f0d97d2
--- /dev/null
+++ b/src/kmk/kmkbuiltin/redirect.c
@@ -0,0 +1,2066 @@
+/* $Id: redirect.c 3564 2022-03-08 11:12:18Z bird $ */
+/** @file
+ * kmk_redirect - Do simple program <-> file redirection (++).
+ */
+
+/*
+ * Copyright (c) 2007-2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#if defined(__APPLE__)
+/*# define _POSIX_C_SOURCE 1 / * 10.4 sdk and unsetenv * / - breaks O_CLOEXEC on 10.8 */
+#endif
+#include "makeint.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(KBUILD_OS_WINDOWS) || defined(KBUILD_OS_OS2)
+# include <process.h>
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# include <Windows.h>
+#endif
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include "quote_argv.h"
+#else
+# ifdef __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
+# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050
+# define USE_POSIX_SPAWN
+# endif
+# elif !defined(KBUILD_OS_WINDOWS) && !defined(KBUILD_OS_OS2)
+# define USE_POSIX_SPAWN
+# endif
+# include <unistd.h>
+# ifdef USE_POSIX_SPAWN
+# include <spawn.h>
+# endif
+# include <sys/wait.h>
+#endif
+
+#include <k/kDefs.h>
+#include <k/kTypes.h>
+#include "err.h"
+#include "kbuild_version.h"
+#ifdef KBUILD_OS_WINDOWS
+# include "nt/nt_child_inject_standard_handles.h"
+#endif
+#if defined(__gnu_hurd__) && !defined(KMK_BUILTIN_STANDALONE) /* need constant */
+# undef GET_PATH_MAX
+# undef PATH_MAX
+# define GET_PATH_MAX PATH_MAX
+#endif
+#include "kmkbuiltin.h"
+#ifdef KMK
+# ifdef KBUILD_OS_WINDOWS
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "../w32/winchildren.h"
+# endif
+# include "pathstuff.h"
+# endif
+#endif
+
+#ifdef __OS2__
+# define INCL_BASE
+# include <os2.h>
+# ifndef LIBPATHSTRICT
+# define LIBPATHSTRICT 3
+# endif
+#endif
+
+#ifndef KMK_BUILTIN_STANDALONE
+extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/* String + strlen tuple. */
+#define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1
+
+/** Only standard handles on windows. */
+#ifdef KBUILD_OS_WINDOWS
+# define ONLY_TARGET_STANDARD_HANDLES
+#endif
+
+
+static int kmk_redirect_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "Usage: %s [-[rwa+tb]<fd> <file>] [-d<fd>=<src-fd>] [-c<fd>] [--stdin-pipe]\n"
+ " [-Z] [-E <var=val>] [-A <var=val>] [-P <var=val>] [-D <var>]\n"
+ " [-C <dir>] [--wcc-brain-damage] [-v] -- <program> [args]\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ "-[rwa+tb]<fd> <file>\n"
+ " The rwa+tb is like for fopen, if not specified it defaults to w+.\n"
+ " The <fd> is either a number or an alias for the standard handles:\n"
+ " i = stdin\n"
+ " o = stdout\n"
+ " e = stderr\n"
+ "-d\n"
+ " The -d switch duplicate the right hand file descriptor (src-fd) to the left\n"
+ " hand side one (fd). The latter is limited to standard handles on windows.\n"
+ "-c <fd>, --close <fd>\n"
+ " The -c switch will close the specified file descriptor. Limited to standard\n"
+ " handles on windows.\n"
+ "--stdin-pipe\n"
+ " The --stdin-pipe switch will replace stdin with the read end of an\n"
+ " anonymous pipe. This is for tricking things like rsh.exe that blocks\n"
+ " reading on stdin.\n"
+ "-Z, --zap-env, --ignore-environment\n"
+ " The -Z switch zaps the environment.\n"
+ "-E <var=val>, --set <var=val>, --env <var=val>\n"
+ " The -E (--set, --env) switch is for making changes to the environment\n"
+ " in an putenv fashion.\n"
+ "-A <var=val>, --append <var=val>\n"
+ " The -A switch appends to an environment variable in a putenv fashion.\n"
+ "-D <var=val>, --prepend <var=val>\n"
+ " The -D switch prepends to an environment variable in a putenv fashion.\n"
+ "-U <var>, --unset <var>\n"
+ " The -U switch deletes an environment variable.\n"
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ "-C <dir>, --chdir <dir>\n"
+ " The -C switch is for changing the current directory. Please specify an\n"
+ " absolute program path as it's platform dependent whether this takes\n"
+ " effect before or after the executable is located.\n"
+ "--wcc-brain-damage, --watcom-brain-damage\n"
+ " The --wcc-brain-damage switch is to work around wcc and wcc386\n"
+ " (Open Watcom) not following normal quoting conventions on Windows and OS/2.\n"
+ "-v, --verbose\n"
+ " The -v switch is for making the thing more verbose.\n"
+ "\n"
+ "On OS/2 the kernel variables BEGINLIBPATH, ENDLIBPATH and LIBPATHSTRICT can be\n"
+ "accessed as-if they were regular enviornment variables.\n"
+ "\n"
+ "This command was originally just a quick hack to avoid invoking the shell\n"
+ "on Windows (cygwin) where forking is very expensive and has exhibited\n"
+ "stability issues on SMP machines. It has since grown into something like\n"
+ "/usr/bin/env on steroids.\n"
+ /* 0 1 2 3 4 5 6 7 8 */
+ /* 012345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 2;
+}
+
+
+/**
+ * Decoded file descriptor operations.
+ */
+typedef struct REDIRECTORDERS
+{
+ enum {
+ kRedirectOrder_Invalid = 0,
+ kRedirectOrder_Close,
+ kRedirectOrder_Open,
+ kRedirectOrder_Dup
+ } enmOrder;
+ /** The target file handle. */
+ int fdTarget;
+ /** The source file name, -1 on close only.
+ * This is an opened file if pszFilename is set. */
+ int fdSource;
+ /** Whether to remove the file on failure cleanup. */
+ int fRemoveOnFailure;
+ /** The open flags (for O_TEXT/O_BINARY) on windows. */
+ int fOpen;
+ /** The filename - NULL if close only. */
+ const char *pszFilename;
+ /** The other pipe end, needs closing in cleanup. */
+ int fdOtherPipeEnd;
+#ifndef USE_POSIX_SPAWN
+ /** Saved file descriptor. */
+ int fdSaved;
+ /** Saved flags. */
+ int fSaved;
+#endif
+} REDIRECTORDERS;
+
+
+static KBOOL kRedirectHasConflict(int fd, unsigned cOrders, REDIRECTORDERS *paOrders)
+{
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ return fd < 3;
+#else
+ while (cOrders-- > 0)
+ if (paOrders[cOrders].fdTarget == fd)
+ return K_TRUE;
+ return K_FALSE;
+#endif
+}
+
+
+/**
+ * Creates a pair of pipe descriptors that does not conflict with any previous
+ * orders.
+ *
+ * The pipe is open with both descriptors being inherited by the child as it's
+ * supposed to be a dummy pipe for stdin that won't break.
+ *
+ * @returns 0 on success, exit code on failure (error message displayed).
+ * @param pCtx The command execution context.
+ * @param paFds Where to return the pipe descriptors
+ * @param cOrders The number of orders.
+ * @param paOrders The order array.
+ * @param fdTarget The target descriptor (0).
+ */
+static int kRedirectCreateStdInPipeWithoutConflict(PKMKBUILTINCTX pCtx, int paFds[2],
+ unsigned cOrders, REDIRECTORDERS *paOrders, int fdTarget)
+{
+ struct
+ {
+ int aFds[2];
+ } aTries[32];
+ unsigned cTries = 0;
+
+ while (cTries < K_ELEMENTS(aTries))
+ {
+#ifdef _MSC_VER
+ int rc = _pipe(aTries[cTries].aFds, 0, _O_BINARY);
+#else
+ int rc = pipe(aTries[cTries].aFds);
+#endif
+ if (rc >= 0)
+ {
+ if ( !kRedirectHasConflict(aTries[cTries].aFds[0], cOrders, paOrders)
+ && !kRedirectHasConflict(aTries[cTries].aFds[1], cOrders, paOrders)
+#ifndef _MSC_VER
+ && aTries[cTries].aFds[0] != fdTarget
+ && aTries[cTries].aFds[1] != fdTarget
+#endif
+ )
+ {
+ paFds[0] = aTries[cTries].aFds[0];
+ paFds[1] = aTries[cTries].aFds[1];
+
+ while (cTries-- > 0)
+ {
+ close(aTries[cTries].aFds[0]);
+ close(aTries[cTries].aFds[1]);
+ }
+ return 0;
+ }
+ }
+ else
+ {
+ err(pCtx, -1, "failed to create stdin pipe (try #%u)", cTries + 1);
+ break;
+ }
+ cTries++;
+ }
+ if (cTries >= K_ELEMENTS(aTries))
+ errx(pCtx, -1, "failed to find a conflict free pair of pipe descriptor for stdin!");
+
+ /* cleanup */
+ while (cTries-- > 0)
+ {
+ close(aTries[cTries].aFds[0]);
+ close(aTries[cTries].aFds[1]);
+ }
+ return 1;
+}
+
+
+/**
+ * Creates a file descriptor for @a pszFilename that does not conflict with any
+ * previous orders.
+ *
+ * We need to be careful that there isn't a close or dup targetting the
+ * temporary file descriptor we return. Also, we need to take care with the
+ * descriptor's inheritability. It should only be inheritable if the returned
+ * descriptor matches the target descriptor (@a fdTarget).
+ *
+ * @returns File descriptor on success, -1 & err/errx on failure.
+ *
+ * The returned file descriptor is not inherited (i.e. close-on-exec),
+ * unless it matches @a fdTarget
+ *
+ * @param pCtx The command execution context.
+ * @param pszFilename The filename to open.
+ * @param fOpen The open flags.
+ * @param fMode The file creation mode (if applicable).
+ * @param cOrders The number of orders.
+ * @param paOrders The order array.
+ * @param fRemoveOnFailure Whether to remove the file on failure.
+ * @param fdTarget The target descriptor.
+ */
+static int kRedirectOpenWithoutConflict(PKMKBUILTINCTX pCtx, const char *pszFilename, int fOpen, mode_t fMode,
+ unsigned cOrders, REDIRECTORDERS *paOrders, int fRemoveOnFailure, int fdTarget)
+{
+#ifdef _O_NOINHERIT
+ int const fNoInherit = _O_NOINHERIT;
+#elif defined(O_NOINHERIT)
+ int const fNoInherit = O_NOINHERIT;
+#elif defined(O_CLOEXEC)
+ int const fNoInherit = O_CLOEXEC;
+#else
+ int const fNoInherit = 0;
+# define USE_FD_CLOEXEC
+#endif
+ int aFdTries[32];
+ unsigned cTries;
+ int fdOpened;
+
+#ifdef KBUILD_OS_WINDOWS
+ if (strcmp(pszFilename, "/dev/null") == 0)
+ pszFilename = "nul";
+#endif
+
+ /* Open it first. */
+ fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
+ if (fdOpened < 0)
+ return err(pCtx, -1, "open(%s,%#x,) failed", pszFilename, fOpen);
+
+ /* Check for conflicts. */
+ if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
+ {
+#ifndef KBUILD_OS_WINDOWS
+ if (fdOpened != fdTarget)
+ return fdOpened;
+# ifndef USE_FD_CLOEXEC
+ if (fcntl(fdOpened, F_SETFD, 0) != -1)
+# endif
+#endif
+ return fdOpened;
+ }
+
+ /*
+ * Do conflict resolving.
+ */
+ cTries = 1;
+ aFdTries[cTries++] = fdOpened;
+ while (cTries < K_ELEMENTS(aFdTries))
+ {
+ fdOpened = open(pszFilename, fOpen | fNoInherit, fMode);
+ if (fdOpened >= 0)
+ {
+ if (!kRedirectHasConflict(fdOpened, cOrders, paOrders))
+ {
+#ifndef KBUILD_OS_WINDOWS
+# ifdef USE_FD_CLOEXEC
+ if ( fdOpened == fdTarget
+ || fcntl(fdOpened, F_SETFD, FD_CLOEXEC) != -1)
+# else
+ if ( fdOpened != fdTarget
+ || fcntl(fdOpened, F_SETFD, 0) != -1)
+# endif
+#endif
+ {
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ return fdOpened;
+ }
+ }
+
+ }
+ else
+ {
+ err(pCtx, -1, "open(%s,%#x,) #%u failed", pszFilename, cTries + 1, fOpen);
+ break;
+ }
+ aFdTries[cTries++] = fdOpened;
+ }
+
+ /*
+ * Give up.
+ */
+ if (fdOpened >= 0)
+ errx(pCtx, -1, "failed to find a conflict free file descriptor for '%s'!", pszFilename);
+
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ return -1;
+}
+
+
+/**
+ * Cleans up the file operation orders.
+ *
+ * This does not restore stuff, just closes handles we've opened for the child.
+ *
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param fFailed Set if it's a failure.
+ */
+static void kRedirectCleanupFdOrders(unsigned cOrders, REDIRECTORDERS *paOrders, KBOOL fFailure)
+{
+ unsigned i = cOrders;
+ while (i-- > 0)
+ {
+ if ( paOrders[i].enmOrder == kRedirectOrder_Open
+ && paOrders[i].fdSource != -1)
+ {
+ close(paOrders[i].fdSource);
+ paOrders[i].fdSource = -1;
+
+ if (paOrders[i].fdOtherPipeEnd >= 0)
+ {
+ close(paOrders[i].fdOtherPipeEnd);
+ paOrders[i].fdOtherPipeEnd = -1;
+ }
+
+ if ( fFailure
+ && paOrders[i].fRemoveOnFailure
+ && paOrders[i].pszFilename)
+ remove(paOrders[i].pszFilename);
+ }
+ }
+}
+
+#if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
+
+/**
+ * Wrapper that chooses between fprintf and kmk_builtin_ctx_printf to get
+ * an error message to the user.
+ *
+ * @param pCtx The command execution context.
+ * @param pWorkingStdErr Work stderr.
+ * @param pszFormat The message format string.
+ * @param ... Format arguments.
+ */
+static void safe_err_printf(PKMKBUILTINCTX pCtx, FILE *pWorkingStdErr, const char *pszFormat, ...)
+{
+ char szMsg[4096];
+ size_t cchMsg;
+ va_list va;
+
+ va_start(va, pszFormat);
+ vsnprintf(szMsg, sizeof(szMsg) - 1, pszFormat, va);
+ va_end(va);
+ szMsg[sizeof(szMsg) - 1] = '\0';
+ cchMsg = strlen(szMsg);
+
+#ifdef KMK_BUILTIN_STANDALONE
+ (void)pCtx;
+#else
+ if (pCtx->pOut && pCtx->pOut->syncout)
+ output_write_text(pCtx->pOut, 1, szMsg, cchMsg);
+ else
+#endif
+ fwrite(szMsg, cchMsg, 1, pWorkingStdErr);
+}
+
+
+/**
+ * Saves a file handle to one which isn't inherited and isn't affected by the
+ * file orders.
+ *
+ * @returns 0 on success, non-zero exit code on failure.
+ * @param pCtx The command execution context.
+ * @param pToSave Pointer to the file order to save the target
+ * descriptor of.
+ * @param cOrders Number of file orders.
+ * @param paOrders The array of file orders.
+ * @param ppWorkingStdErr Pointer to a pointer to a working stderr. This will
+ * get replaced if we're saving stderr, so that we'll
+ * keep having a working one to report failures to.
+ */
+static int kRedirectSaveHandle(PKMKBUILTINCTX pCtx, REDIRECTORDERS *pToSave, unsigned cOrders,
+ REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ int fdToSave = pToSave->fdTarget;
+ int rcRet = 10;
+
+ /*
+ * First, check if there's actually handle here that needs saving.
+ */
+ pToSave->fSaved = fcntl(pToSave->fdTarget, F_GETFD, 0);
+ if (pToSave->fSaved != -1)
+ {
+ /*
+ * Try up to 32 times to get a duplicate descriptor that doesn't conflict.
+ */
+ int aFdTries[32];
+ int cTries = 0;
+ do
+ {
+ /* Duplicate the handle (windows makes this complicated). */
+ int fdDup;
+ fdDup = dup(fdToSave);
+ if (fdDup == -1)
+ {
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup(%#x) failed: %u\n", pCtx->pszProgName, fdToSave, strerror(errno));
+ break;
+ }
+ /* Is the duplicate usable? */
+ if (!kRedirectHasConflict(fdDup, cOrders, paOrders))
+ {
+ pToSave->fdSaved = fdDup;
+ if ( *ppWorkingStdErr == stderr
+ && fdToSave == fileno(*ppWorkingStdErr))
+ {
+ *ppWorkingStdErr = fdopen(fdDup, "wt");
+ if (*ppWorkingStdErr == NULL)
+ {
+ safe_err_printf(pCtx, stderr, "%s: fdopen(%d,\"wt\") failed: %s\n", pCtx->pszProgName, fdDup, strerror(errno));
+ *ppWorkingStdErr = stderr;
+ close(fdDup);
+ break;
+ }
+ }
+ rcRet = 0;
+ break;
+ }
+
+ /* Not usuable, stash it and try again. */
+ aFdTries[cTries++] = fdDup;
+ } while (cTries < K_ELEMENTS(aFdTries));
+
+ /*
+ * Clean up unused duplicates.
+ */
+ while (cTries-- > 0)
+ close(aFdTries[cTries]);
+ }
+ else
+ {
+ /*
+ * Nothing to save.
+ */
+ pToSave->fdSaved = -1;
+ rcRet = 0;
+ }
+ return rcRet;
+}
+
+
+/**
+ * Restores the target file descriptors affected by the file operation orders.
+ *
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param ppWorkingStdErr Pointer to a pointer to the working stderr. If this
+ * is one of the saved file descriptors, we'll restore
+ * it to stderr.
+ */
+static void kRedirectRestoreFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ int iSavedErrno = errno;
+ unsigned i = cOrders;
+ while (i-- > 0)
+ {
+ if (paOrders[i].fdSaved != -1)
+ {
+ KBOOL fRestoreStdErr = *ppWorkingStdErr != stderr
+ && paOrders[i].fdSaved == fileno(*ppWorkingStdErr);
+ if (dup2(paOrders[i].fdSaved, paOrders[i].fdTarget) != -1)
+ {
+ close(paOrders[i].fdSaved);
+ paOrders[i].fdSaved = -1;
+
+ if (fRestoreStdErr)
+ {
+ *ppWorkingStdErr = stderr;
+ assert(fileno(stderr) == paOrders[i].fdTarget);
+ }
+ }
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdSaved, paOrders[i].fdTarget, strerror(errno));
+ }
+
+ if (paOrders[i].fSaved != -1)
+ {
+ if (fcntl(paOrders[i].fdTarget, F_SETFD, paOrders[i].fSaved & FD_CLOEXEC) != -1)
+ paOrders[i].fSaved = -1;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,%s) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdTarget, paOrders[i].fSaved & FD_CLOEXEC ? "FD_CLOEXEC" : "0",
+ strerror(errno));
+ }
+ }
+ errno = iSavedErrno;
+}
+
+
+/**
+ * Executes the file operation orders.
+ *
+ * @returns 0 on success, exit code on failure.
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders File operation orders to execute.
+ * @param ppWorkingStdErr Where to return a working stderr (mainly for
+ * kRedirectRestoreFdOrders).
+ */
+static int kRedirectExecFdOrders(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders, FILE **ppWorkingStdErr)
+{
+ unsigned i;
+
+ *ppWorkingStdErr = stderr;
+ for (i = 0; i < cOrders; i++)
+ {
+ int rcExit = 10;
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Close:
+ {
+ /* If the handle isn't used by any of the following operation,
+ just mark it as non-inheritable if necessary. */
+ int const fdTarget = paOrders[i].fdTarget;
+ unsigned j;
+ for (j = i + 1; j < cOrders; j++)
+ if (paOrders[j].fdTarget == fdTarget)
+ break;
+ if (j >= cOrders)
+ {
+ paOrders[j].fSaved = fcntl(fdTarget, F_GETFD, 0);
+ if (paOrders[j].fSaved != -1)
+ {
+ if (paOrders[j].fSaved & FD_CLOEXEC)
+ rcExit = 0;
+ else if ( fcntl(fdTarget, F_SETFD, FD_CLOEXEC) != -1
+ || errno == EBADF)
+ rcExit = 0;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_SETFD,FD_CLOEXEC) failed: %s\n",
+ pCtx->pszProgName, fdTarget, strerror(errno));
+ }
+ else if (errno == EBADF)
+ rcExit = 0;
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: fcntl(%d,F_GETFD,0) failed: %s\n",
+ pCtx->pszProgName, fdTarget, strerror(errno));
+ }
+ else
+ rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
+ break;
+ }
+
+ case kRedirectOrder_Dup:
+ case kRedirectOrder_Open:
+ rcExit = kRedirectSaveHandle(pCtx, &paOrders[i], cOrders, paOrders, ppWorkingStdErr);
+ if (rcExit == 0)
+ {
+ if (dup2(paOrders[i].fdSource, paOrders[i].fdTarget) != -1)
+ rcExit = 0;
+ else
+ {
+ if (paOrders[i].enmOrder == kRedirectOrder_Open)
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d [%s],%d) failed: %s\n", pCtx->pszProgName,
+ paOrders[i].fdSource, paOrders[i].pszFilename, paOrders[i].fdTarget, strerror(errno));
+ else
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: dup2(%d,%d) failed: %s\n",
+ pCtx->pszProgName, paOrders[i].fdSource, paOrders[i].fdTarget, strerror(errno));
+ rcExit = 10;
+ }
+ }
+ break;
+
+ default:
+ safe_err_printf(pCtx, *ppWorkingStdErr, "%s: error! invalid enmOrder=%d\n", pCtx->pszProgName, paOrders[i].enmOrder);
+ rcExit = 99;
+ break;
+ }
+
+ if (rcExit != 0)
+ {
+ kRedirectRestoreFdOrders(pCtx, i, paOrders, ppWorkingStdErr);
+ return rcExit;
+ }
+ }
+
+ return 0;
+}
+
+#endif /* !USE_POSIX_SPAWN */
+#ifdef KBUILD_OS_WINDOWS
+
+/**
+ * Registers the child process with a care provider or waits on it to complete.
+ *
+ * @returns 0 or non-zero success indicator or child exit code, depending on
+ * the value pfIsChildExitCode points to.
+ * @param pCtx The command execution context.
+ * @param hProcess The child process handle.
+ * @param cVerbosity The verbosity level.
+ * @param pPidSpawned Where to return the PID of the spawned child
+ * when we're inside KMK and we're return without
+ * waiting.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectPostnatalCareOnWindows(PKMKBUILTINCTX pCtx, HANDLE hProcess, unsigned cVerbosity,
+ pid_t *pPidSpawned, KBOOL *pfIsChildExitCode)
+{
+ int rcExit;
+ DWORD dwTmp;
+
+# ifndef KMK_BUILTIN_STANDALONE
+ /*
+ * Try register the child with a childcare provider, i.e. winchildren.c
+ * or sub_proc.c.
+ */
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ if (process_kmk_register_redirect(hProcess, pPidSpawned) == 0)
+# else
+ if ( pPidSpawned
+ && MkWinChildCreateRedirect((intptr_t)hProcess, pPidSpawned) == 0)
+# endif
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ *pfIsChildExitCode = K_FALSE;
+ return 0;
+ }
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ warn(pCtx, "sub_proc is out of slots, waiting for child...");
+# else
+ if (pPidSpawned)
+ warn(pCtx, "MkWinChildCreateRedirect failed...");
+# endif
+# endif
+
+ /*
+ * Either the provider is outbooked or we're not in a context (like
+ * standalone) where we get help with waiting and must to it ourselves
+ */
+ dwTmp = WaitForSingleObject(hProcess, INFINITE);
+ if (dwTmp != WAIT_OBJECT_0)
+ warnx(pCtx, "WaitForSingleObject failed: %#x\n", dwTmp);
+
+ if (GetExitCodeProcess(hProcess, &dwTmp))
+ rcExit = (int)dwTmp;
+ else
+ {
+ warnx(pCtx, "GetExitCodeProcess failed: %u\n", GetLastError());
+ TerminateProcess(hProcess, 127);
+ rcExit = 127;
+ }
+
+ CloseHandle(hProcess);
+ *pfIsChildExitCode = K_TRUE;
+ return rcExit;
+}
+
+
+/**
+ * Tries to locate the executable image.
+ *
+ * This isn't quite perfect yet...
+ *
+ * @returns pszExecutable or pszBuf with valid string.
+ * @param pszExecutable The specified executable.
+ * @param pszBuf Buffer to return a modified path in.
+ * @param cbBuf Size of return buffer.
+ * @param pszPath The search path.
+ */
+static const char *kRedirectCreateProcessWindowsFindImage(const char *pszExecutable, char *pszBuf, size_t cbBuf,
+ const char *pszPath)
+{
+ /*
+ * Analyze the name.
+ */
+ size_t const cchExecutable = strlen(pszExecutable);
+ BOOL fHavePath = FALSE;
+ BOOL fHaveSuffix = FALSE;
+ size_t off = cchExecutable;
+ while (off > 0)
+ {
+ char ch = pszExecutable[--off];
+ if (ch == '.')
+ {
+ fHaveSuffix = TRUE;
+ break;
+ }
+ if (ch == '\\' || ch == '/' || ch == ':')
+ {
+ fHavePath = TRUE;
+ break;
+ }
+ }
+ if (!fHavePath)
+ while (off > 0)
+ {
+ char ch = pszExecutable[--off];
+ if (ch == '\\' || ch == '/' || ch == ':')
+ {
+ fHavePath = TRUE;
+ break;
+ }
+ }
+ /*
+ * If no path, search the path value.
+ */
+ if (!fHavePath)
+ {
+ char *pszFilename;
+ DWORD cchFound = SearchPathA(pszPath, pszExecutable, fHaveSuffix ? NULL : ".exe", cbBuf, pszBuf, &pszFilename);
+ if (cchFound)
+ return pszBuf;
+ }
+
+ /*
+ * If no suffix, try add .exe.
+ */
+ if ( !fHaveSuffix
+ && GetFileAttributesA(pszExecutable) == INVALID_FILE_ATTRIBUTES
+ && cchExecutable + 4 < cbBuf)
+ {
+ memcpy(pszBuf, pszExecutable, cchExecutable);
+ memcpy(&pszBuf[cchExecutable], ".exe", 5);
+ if (GetFileAttributesA(pszBuf) != INVALID_FILE_ATTRIBUTES)
+ return pszBuf;
+ }
+
+ return pszExecutable;
+}
+
+
+/**
+ * Turns the orders into input for nt_child_inject_standard_handles and
+ * winchildren.c
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pCtx The command execution context.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param pafReplace Replace (TRUE) or leave alone (FALSE) indicator
+ * for each of the starndard handles.
+ * @param pahChild Array of standard handles for injecting into the
+ * child. Parallel to pafReplace.
+ */
+static int kRedirectOrderToWindowsHandles(PKMKBUILTINCTX pCtx, unsigned cOrders, REDIRECTORDERS *paOrders,
+ BOOL pafReplace[3], HANDLE pahChild[3])
+{
+ int i;
+ for (i = 0; i < (int)cOrders; i++)
+ {
+ int fdTarget = paOrders[i].fdTarget;
+ assert(fdTarget >= 0 && fdTarget < 3);
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Open:
+ if ( (paOrders[i].fOpen & O_APPEND)
+ && lseek(paOrders[i].fdSource, 0, SEEK_END) < 0)
+ return err(pCtx, 10, "lseek-to-end failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
+ /* fall thru */
+ case kRedirectOrder_Dup:
+ pahChild[fdTarget] = (HANDLE)_get_osfhandle(paOrders[i].fdSource);
+ if (pahChild[fdTarget] == NULL || pahChild[fdTarget] == INVALID_HANDLE_VALUE)
+ return err(pCtx, 10, "_get_osfhandle failed on %d (for %d)", paOrders[i].fdSource, fdTarget);
+ break;
+
+ case kRedirectOrder_Close:
+ pahChild[fdTarget] = NULL;
+ break;
+
+ default:
+ assert(0);
+ }
+ pafReplace[fdTarget] = TRUE;
+ }
+ return 0;
+}
+
+
+/**
+ * Alternative approach on windows that use CreateProcess and doesn't require
+ * any serialization wrt handles and CWD.
+ *
+ * @returns 0 on success, non-zero on failure to create.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param phProcess Where to return process handle.
+ */
+static int kRedirectCreateProcessWindows(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
+ char **papszEnvVars, const char *pszCwd, unsigned cOrders,
+ REDIRECTORDERS *paOrders, HANDLE *phProcess)
+{
+ size_t cbArgs;
+ char *pszCmdLine;
+ size_t cbEnv;
+ char *pszzEnv;
+ char *pch;
+ int i;
+ int rc;
+
+ /*
+ * Start by making the the command line. We just need to put spaces
+ * between the arguments since quote_argv don't the quoting already.
+ */
+ cbArgs = 0;
+ for (i = 0; i < cArgs; i++)
+ cbArgs += strlen(papszArgs[i]) + 1;
+ pszCmdLine = pch = (char *)malloc(cbArgs);
+ if (!pszCmdLine)
+ return errx(pCtx, 9, "out of memory!");
+ for (i = 0; i < cArgs; i++)
+ {
+ size_t cch;
+ if (i != 0)
+ *pch++ = ' ';
+ cch = strlen(papszArgs[i]);
+ memcpy(pch, papszArgs[i], cch);
+ pch += cch;
+ }
+ *pch++ = '\0';
+ assert(pch - pszCmdLine == cbArgs);
+
+ /*
+ * The environment vector is also simple.
+ */
+ cbEnv = 0;
+ for (i = 0; papszEnvVars[i]; i++)
+ cbEnv += strlen(papszEnvVars[i]) + 1;
+ cbEnv++;
+ pszzEnv = pch = (char *)malloc(cbEnv);
+ if (pszzEnv)
+ {
+ char szAbsExe[1024];
+ const char *pszPathVal = NULL;
+ STARTUPINFOA StartupInfo;
+ PROCESS_INFORMATION ProcInfo = { NULL, NULL, 0, 0 };
+
+ for (i = 0; papszEnvVars[i]; i++)
+ {
+ size_t cbSrc = strlen(papszEnvVars[i]) + 1;
+ memcpy(pch, papszEnvVars[i], cbSrc);
+ if ( !pszPathVal
+ && cbSrc >= 5
+ && pch[4] == '='
+ && (pch[0] == 'P' || pch[0] == 'p')
+ && (pch[1] == 'A' || pch[1] == 'a')
+ && (pch[2] == 'T' || pch[2] == 't')
+ && (pch[3] == 'H' || pch[3] == 'h'))
+ pszPathVal = &pch[5];
+ pch += cbSrc;
+ }
+ *pch++ = '\0';
+ assert(pch - pszzEnv == cbEnv);
+
+ /*
+ * Locate the executable.
+ */
+ pszExecutable = kRedirectCreateProcessWindowsFindImage(pszExecutable, szAbsExe, sizeof(szAbsExe), pszPathVal);
+
+ /*
+ * Do basic startup info preparation.
+ */
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoA(&StartupInfo);
+ StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
+ StartupInfo.cbReserved2 = 0;
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+
+ /*
+ * If there are no redirection orders, we're good.
+ */
+ if (!cOrders)
+ {
+ if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
+ FALSE /*fInheritHandles*/, 0 /*fFlags*/, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
+ {
+ CloseHandle(ProcInfo.hThread);
+ *phProcess = ProcInfo.hProcess;
+# ifndef KMK_BUILTIN_STANDALONE
+ kmk_cache_exec_image_a(pszExecutable);
+# endif
+ rc = 0;
+ }
+ else
+ rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
+ }
+ else
+ {
+ /*
+ * Execute the orders, ending up with three handles we need to
+ * implant into the guest process.
+ *
+ * This isn't 100% perfect wrt O_APPEND, but it'll have to do for now.
+ */
+ BOOL afReplace[3] = { FALSE, FALSE, FALSE };
+ HANDLE ahChild[3] = { NULL, NULL, NULL };
+ rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
+ if (rc == 0)
+ {
+ /*
+ * Start the process in suspended animation so we can inject handles.
+ */
+ if (CreateProcessA(pszExecutable, pszCmdLine, NULL /*pProcAttrs*/, NULL /*pThreadAttrs*/,
+ FALSE /*fInheritHandles*/, CREATE_SUSPENDED, pszzEnv, pszCwd, &StartupInfo, &ProcInfo))
+ {
+ unsigned i;
+
+ /* Inject the handles and try make it start executing. */
+ char szErrMsg[128];
+ rc = nt_child_inject_standard_handles(ProcInfo.hProcess, afReplace, ahChild, szErrMsg, sizeof(szErrMsg));
+ if (rc)
+ rc = errx(pCtx, 10, "%s", szErrMsg);
+ else if (!ResumeThread(ProcInfo.hThread))
+ rc = errx(pCtx, 10, "ResumeThread failed: %u", GetLastError());
+
+ /* Duplicate the write end of any stdin pipe handles into the child. */
+ for (i = 0; i < cOrders; i++)
+ if (paOrders[i].fdOtherPipeEnd >= 0)
+ {
+ HANDLE hIgnored = INVALID_HANDLE_VALUE;
+ HANDLE hPipeW = (HANDLE)_get_osfhandle(paOrders[i].fdOtherPipeEnd);
+ if (!DuplicateHandle(GetCurrentProcess(), hPipeW, ProcInfo.hProcess, &hIgnored, 0 /*fDesiredAccess*/,
+ TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
+ rc = errx(pCtx, 10, "DuplicateHandle failed on other stdin pipe end %d/%p: %u",
+ paOrders[i].fdOtherPipeEnd, hPipeW, GetLastError());
+ }
+
+ /* Kill it if any of that fails. */
+ if (rc != 0)
+ TerminateProcess(ProcInfo.hProcess, rc);
+
+ CloseHandle(ProcInfo.hThread);
+ *phProcess = ProcInfo.hProcess;
+# ifndef KMK_BUILTIN_STANDALONE
+ kmk_cache_exec_image_a(pszExecutable);
+# endif
+ rc = 0;
+ }
+ else
+ rc = errx(pCtx, 10, "CreateProcessA(%s) failed: %u", pszExecutable, GetLastError());
+ }
+ }
+ free(pszzEnv);
+ }
+ else
+ rc = errx(pCtx, 9, "out of memory!");
+ free(pszCmdLine);
+ return rc;
+}
+
+# if !defined(KMK_BUILTIN_STANDALONE) && defined(CONFIG_NEW_WIN_CHILDREN)
+/**
+ * Pass the problem on to winchildren.c when we're on one of its workers.
+ *
+ * @returns 0 on success, non-zero on failure to create.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param phProcess Where to return process handle.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectExecProcessWithinOnWorker(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs,
+ char **papszEnvVars, const char *pszCwd, unsigned cOrders,
+ REDIRECTORDERS *paOrders, KBOOL *pfIsChildExitCode)
+{
+ BOOL afReplace[3] = { FALSE, FALSE, FALSE };
+ HANDLE ahChild[3] = { NULL, NULL, NULL };
+ int rc = kRedirectOrderToWindowsHandles(pCtx, cOrders, paOrders, afReplace, ahChild);
+ if (rc == 0)
+ {
+ rc = MkWinChildBuiltInExecChild(pCtx->pvWorker, pszExecutable, papszArgs, TRUE /*fQuotedArgv*/,
+ papszEnvVars, pszCwd, afReplace, ahChild);
+ *pfIsChildExitCode = K_TRUE;
+ }
+ return rc;
+}
+# endif /* !KMK_BUILTIN_STANDALONE */
+
+#endif /* KBUILD_OS_WINDOWS */
+
+/**
+ * Does the child spawning .
+ *
+ * @returns Exit code.
+ * @param pCtx The command execution context.
+ * @param pszExecutable The child process executable.
+ * @param cArgs Number of arguments.
+ * @param papszArgs The child argument vector.
+ * @param fWatcomBrainDamage Whether MSC need to do quoting according to
+ * weird Watcom WCC rules.
+ * @param papszEnvVars The child environment vector.
+ * @param pszCwd The current working directory of the child.
+ * @param pszSavedCwd The saved current working directory. This is
+ * NULL if the CWD doesn't need changing.
+ * @param cOrders Number of file operation orders.
+ * @param paOrders The file operation orders.
+ * @param pFileActions The posix_spawn file actions.
+ * @param cVerbosity The verbosity level.
+ * @param pPidSpawned Where to return the PID of the spawned child
+ * when we're inside KMK and we're return without
+ * waiting.
+ * @param pfIsChildExitCode Where to indicate whether the return exit code
+ * is from the child or from our setup efforts.
+ */
+static int kRedirectDoSpawn(PKMKBUILTINCTX pCtx, const char *pszExecutable, int cArgs, char **papszArgs, int fWatcomBrainDamage,
+ char **papszEnvVars, const char *pszCwd, const char *pszSavedCwd,
+ unsigned cOrders, REDIRECTORDERS *paOrders,
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_t *pFileActions,
+#endif
+ unsigned cVerbosity,
+#ifdef KMK
+ pid_t *pPidSpawned,
+#endif
+ KBOOL *pfIsChildExitCode)
+{
+ int rcExit = 0;
+ int i;
+#ifdef _MSC_VER
+ char **papszArgsOriginal = papszArgs;
+#endif
+ *pfIsChildExitCode = K_FALSE;
+
+#ifdef _MSC_VER
+ /*
+ * Do MSC parameter quoting.
+ */
+ papszArgs = malloc((cArgs + 1) * sizeof(papszArgs[0]));
+ if (papszArgs)
+ memcpy(papszArgs, papszArgsOriginal, (cArgs + 1) * sizeof(papszArgs[0]));
+ else
+ return errx(pCtx, 9, "out of memory!");
+
+ rcExit = quote_argv(cArgs, papszArgs, fWatcomBrainDamage, 0 /*fFreeOrLeak*/);
+ if (rcExit == 0)
+#endif
+ {
+ /*
+ * Display what we're about to execute if we're in verbose mode.
+ */
+ if (cVerbosity > 0)
+ {
+ for (i = 0; i < cArgs; i++)
+ warnx(pCtx, "debug: argv[%i]=%s<eos>", i, papszArgs[i]);
+ for (i = 0; i < (int)cOrders; i++)
+ switch (paOrders[i].enmOrder)
+ {
+ case kRedirectOrder_Close:
+ warnx(pCtx, "debug: close %d\n", paOrders[i].fdTarget);
+ break;
+ case kRedirectOrder_Dup:
+ warnx(pCtx, "debug: dup %d to %d\n", paOrders[i].fdSource, paOrders[i].fdTarget);
+ break;
+ case kRedirectOrder_Open:
+ warnx(pCtx, "debug: open '%s' (%#x) as [%d ->] %d\n",
+ paOrders[i].pszFilename, paOrders[i].fOpen, paOrders[i].fdSource, paOrders[i].fdTarget);
+ break;
+ default:
+ warnx(pCtx, "error! invalid enmOrder=%d", paOrders[i].enmOrder);
+ assert(0);
+ break;
+ }
+ if (pszSavedCwd)
+ warnx(pCtx, "debug: chdir %s\n", pszCwd);
+ }
+
+#ifndef KBUILD_OS_WINDOWS
+ /*
+ * Change working directory if so requested.
+ */
+ if (pszSavedCwd)
+ {
+ if (chdir(pszCwd) < 0)
+ rcExit = errx(pCtx, 10, "Failed to change directory to '%s'", pszCwd);
+ }
+#endif /* KBUILD_OS_WINDOWS */
+ if (rcExit == 0)
+ {
+# if !defined(USE_POSIX_SPAWN) && !defined(KBUILD_OS_WINDOWS)
+ /*
+ * Execute the file orders.
+ */
+ FILE *pWorkingStdErr = NULL;
+ rcExit = kRedirectExecFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (rcExit == 0)
+# endif
+ {
+# ifdef KMK
+ /*
+ * We're spawning from within kmk.
+ */
+# ifdef KBUILD_OS_WINDOWS
+ /* Windows is slightly complicated due to handles and winchildren.c. */
+ if (pPidSpawned)
+ *pPidSpawned = 0;
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ if (pCtx->pvWorker && !pPidSpawned)
+ rcExit = kRedirectExecProcessWithinOnWorker(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders,
+ pfIsChildExitCode);
+ else
+# endif
+ {
+ HANDLE hProcess = INVALID_HANDLE_VALUE;
+ rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
+ if (rcExit == 0)
+ rcExit = kRedirectPostnatalCareOnWindows(pCtx, hProcess, cVerbosity, pPidSpawned, pfIsChildExitCode);
+ }
+
+# elif defined(KBUILD_OS_OS2)
+ *pPidSpawned = _spawnvpe(P_NOWAIT, pszExecutable, papszArgs, papszEnvVars);
+ kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (*pPidSpawned != -1)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ }
+ else
+ {
+ rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
+ *pPidSpawned = 0;
+ }
+# else
+ rcExit = posix_spawnp(pPidSpawned, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
+ if (rcExit == 0)
+ {
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", *pPidSpawned);
+ }
+ else
+ {
+ rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
+ *pPidSpawned = 0;
+ }
+# endif
+
+#else /* !KMK */
+ /*
+ * Spawning from inside the kmk_redirect executable.
+ */
+# ifdef KBUILD_OS_WINDOWS
+ HANDLE hProcess = INVALID_HANDLE_VALUE;
+ rcExit = kRedirectCreateProcessWindows(pCtx, pszExecutable, cArgs, papszArgs, papszEnvVars,
+ pszSavedCwd ? pszCwd : NULL, cOrders, paOrders, &hProcess);
+ if (rcExit == 0)
+ {
+ DWORD dwWait;
+ do
+ dwWait = WaitForSingleObject(hProcess, INFINITE);
+ while (dwWait == WAIT_IO_COMPLETION || dwWait == WAIT_TIMEOUT);
+
+ dwWait = 11;
+ if (GetExitCodeProcess(hProcess, &dwWait))
+ {
+ *pfIsChildExitCode = K_TRUE;
+ rcExit = dwWait;
+ }
+ else
+ rcExit = errx(pCtx, 11, "GetExitCodeProcess(%s) failed: %u", pszExecutable, GetLastError());
+ }
+
+#elif defined(KBUILD_OS_OS2)
+ errno = 0;
+ rcExit = (int)_spawnvpe(P_WAIT, pszExecutable, papszArgs, papszEnvVars);
+ kRedirectRestoreFdOrders(pCtx, cOrders, paOrders, &pWorkingStdErr);
+ if (rcExit != -1 || errno == 0)
+ {
+ *pfIsChildExitCode = K_TRUE;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: exit code: %d", rcExit);
+ }
+ else
+ rcExit = err(pCtx, 10, "_spawnvpe(%s) failed", pszExecutable);
+
+# else
+ pid_t pidChild = 0;
+ rcExit = posix_spawnp(&pidChild, pszExecutable, pFileActions, NULL /*pAttr*/, papszArgs, papszEnvVars);
+ if (rcExit == 0)
+ {
+ *pfIsChildExitCode = K_TRUE;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: spawned %d", pidChild);
+
+ /* Wait for the child. */
+ for (;;)
+ {
+ int rcExitRaw = 1;
+ pid_t pid = waitpid(pidChild, &rcExitRaw, 0 /*block*/);
+ if (pid == pidChild)
+ {
+ rcExit = WIFEXITED(rcExitRaw) ? WEXITSTATUS(rcExitRaw) : 63;
+ if (cVerbosity > 0)
+ warnx(pCtx, "debug: %d exit code: %d (%d)", pidChild, rcExit, rcExitRaw);
+ break;
+ }
+ if ( errno != EINTR
+# ifdef ERESTART
+ && errno != ERESTART
+# endif
+ )
+ {
+ rcExit = err(pCtx, 11, "waitpid failed");
+ kill(pidChild, SIGKILL);
+ break;
+ }
+ }
+ }
+ else
+ rcExit = errx(pCtx, 10, "posix_spawnp(%s) failed: %s", pszExecutable, strerror(rcExit));
+# endif
+#endif /* !KMK */
+ }
+ }
+
+#ifndef KBUILD_OS_WINDOWS
+ /*
+ * Restore the current directory.
+ */
+ if (pszSavedCwd)
+ {
+ if (chdir(pszSavedCwd) < 0)
+ warn(pCtx, "Failed to restore directory to '%s'", pszSavedCwd);
+ }
+#endif
+ }
+#ifdef _MSC_VER
+ else
+ rcExit = errx(pCtx, 9, "quite_argv failed: %u", rcExit);
+
+ /* Restore the original argv strings, freeing the quote_argv replacements. */
+ i = cArgs;
+ while (i-- > 0)
+ if (papszArgs[i] != papszArgsOriginal[i])
+ free(papszArgs[i]);
+ free(papszArgs);
+#endif
+ return rcExit;
+}
+
+
+/**
+ * The function that does almost everything here... ugly.
+ */
+int kmk_builtin_redirect(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, struct child *pChild, pid_t *pPidSpawned)
+{
+ int rcExit = 0;
+ KBOOL fChildExitCode = K_FALSE;
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_t FileActions;
+#endif
+ unsigned cOrders = 0;
+ REDIRECTORDERS aOrders[32];
+
+ int iArg;
+ const char *pszExecutable = NULL;
+ char **papszEnvVars = NULL;
+ unsigned cAllocatedEnvVars;
+ unsigned cEnvVars;
+ int fWatcomBrainDamage = 0;
+ int cVerbosity = 0;
+ char *pszSavedCwd = NULL;
+ size_t const cbCwdBuf = GET_PATH_MAX;
+ PATH_VAR(szCwd);
+#ifdef KBUILD_OS_OS2
+ ULONG ulLibPath;
+ char *apszSavedLibPaths[LIBPATHSTRICT + 1] = { NULL, NULL, NULL, NULL };
+#endif
+
+
+ if (argc <= 1)
+ return kmk_redirect_usage(pCtx, 1);
+
+ /*
+ * Create default program environment.
+ */
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ if (getcwd_fs(szCwd, cbCwdBuf) != NULL)
+#else
+ if (getcwd(szCwd, cbCwdBuf) != NULL)
+#endif
+ { /* likely */ }
+ else
+ return err(pCtx, 9, "getcwd failed");
+
+ /* We start out with a read-only enviornment from kmk or the crt, and will
+ duplicate it if we make changes to it. */
+ cAllocatedEnvVars = 0;
+ papszEnvVars = envp;
+ cEnvVars = 0;
+ while (papszEnvVars[cEnvVars] != NULL)
+ cEnvVars++;
+
+#ifdef USE_POSIX_SPAWN
+ /*
+ * Init posix attributes with stdout/err redirections according to pCtx.
+ */
+ rcExit = posix_spawn_file_actions_init(&FileActions);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 9, "posix_spawn_file_actions_init failed: %s", strerror(rcExit));
+# if !defined(KMK_BUILTIN_STANDALONE) && !defined(CONFIG_WITH_OUTPUT_IN_MEMORY)
+ if (pCtx->pOut && rcExit == 0)
+ {
+ if (pCtx->pOut->out >= 0)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->out, 1);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->out, strerror(rcExit));
+ }
+ if (pCtx->pOut->err >= 0 && rcExit == 0)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, pCtx->pOut->err, 2);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d, 1) failed: %s", pCtx->pOut->err, strerror(rcExit));
+ }
+ }
+# endif
+#endif
+
+ /*
+ * Parse arguments.
+ */
+ for (iArg = 1; rcExit == 0 && iArg < argc; iArg++)
+ {
+ char *pszArg = argv[iArg];
+ if (*pszArg == '-')
+ {
+ int fd;
+ char chOpt;
+ const char *pszValue;
+
+ chOpt = *++pszArg;
+ pszArg++;
+ if (chOpt == '-')
+ {
+ /* '--' indicates where the bits to execute start. Check if we're
+ relaunching ourselves here and just continue parsing if we are. */
+ if (*pszArg == '\0')
+ {
+ iArg++;
+ if ( iArg >= argc
+ || ( strcmp(argv[iArg], "kmk_builtin_redirect") != 0
+ && strcmp(argv[iArg], argv[0]) != 0))
+ break;
+ continue;
+ }
+
+ if ( strcmp(pszArg, "wcc-brain-damage") == 0
+ || strcmp(pszArg, "watcom-brain-damage") == 0)
+ {
+ fWatcomBrainDamage = 1;
+ continue;
+ }
+
+ /* convert to short. */
+ if (strcmp(pszArg, "help") == 0)
+ chOpt = 'h';
+ else if (strcmp(pszArg, "version") == 0)
+ chOpt = 'V';
+ else if ( strcmp(pszArg, "set") == 0
+ || strcmp(pszArg, "env") == 0)
+ chOpt = 'E';
+ else if (strcmp(pszArg, "append") == 0)
+ chOpt = 'A';
+ else if (strcmp(pszArg, "prepend") == 0)
+ chOpt = 'D';
+ else if (strcmp(pszArg, "unset") == 0)
+ chOpt = 'U';
+ else if ( strcmp(pszArg, "zap-env") == 0
+ || strcmp(pszArg, "ignore-environment") == 0 /* GNU env compatibility. */ )
+ chOpt = 'Z';
+ else if (strcmp(pszArg, "chdir") == 0)
+ chOpt = 'C';
+ else if (strcmp(pszArg, "close") == 0)
+ chOpt = 'c';
+ else if (strcmp(pszArg, "verbose") == 0)
+ chOpt = 'v';
+ else if (strcmp(pszArg, "stdin-pipe") == 0)
+ chOpt = 'I';
+ else
+ {
+ errx(pCtx, 2, "Unknown option: '%s'", pszArg - 2);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ break;
+ }
+ pszArg = "";
+ }
+
+ /*
+ * Deal with the obligatory help and version switches first to get them out of the way.
+ */
+ if (chOpt == 'h')
+ {
+ kmk_redirect_usage(pCtx, 0);
+ rcExit = -1;
+ break;
+ }
+ if (chOpt == 'V')
+ {
+ kbuild_version(argv[0]);
+ rcExit = -1;
+ break;
+ }
+
+ /*
+ * Get option value first, if the option takes one.
+ */
+ if ( chOpt == 'E'
+ || chOpt == 'A'
+ || chOpt == 'D'
+ || chOpt == 'U'
+ || chOpt == 'C'
+ || chOpt == 'c'
+ || chOpt == 'd'
+ || chOpt == 'e')
+ {
+ if (*pszArg != '\0')
+ pszValue = pszArg + (*pszArg == ':' || *pszArg == '=');
+ else if (++iArg < argc)
+ pszValue = argv[iArg];
+ else
+ {
+ errx(pCtx, 2, "syntax error: Option -%c requires a value!", chOpt);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ break;
+ }
+ }
+ else
+ pszValue = NULL;
+
+ /*
+ * Environment switch?
+ */
+ if (chOpt == 'E')
+ {
+ const char *pchEqual = strchr(pszValue, '=');
+#ifdef KBUILD_OS_OS2
+ if ( strncmp(pszValue, TUPLE("BEGINLIBPATH=")) == 0
+ || strncmp(pszValue, TUPLE("ENDLIBPATH=")) == 0
+ || strncmp(pszValue, TUPLE("LIBPATHSTRICT=")) == 0)
+ {
+ ULONG ulVar = *pszValue == 'B' ? BEGIN_LIBPATH
+ : *pszValue == 'E' ? END_LIBPATH
+ : LIBPATHSTRICT;
+ APIRET rc;
+ if (apszSavedLibPaths[ulVar] == NULL)
+ {
+ /* The max length is supposed to be 1024 bytes. */
+ apszSavedLibPaths[ulVar] = calloc(1024, 2);
+ if (apszSavedLibPaths[ulVar])
+ {
+ rc = DosQueryExtLIBPATH(apszSavedLibPaths[ulVar], ulVar);
+ if (rc)
+ {
+ rcExit = errx(pCtx, 9, "DosQueryExtLIBPATH(,%u) failed: %lu", ulVar, rc);
+ free(apszSavedLibPaths[ulVar]);
+ apszSavedLibPaths[ulVar] = NULL;
+ }
+ }
+ else
+ rcExit = errx(pCtx, 9, "out of memory!");
+ }
+ if (rcExit == 0)
+ {
+ rc = DosSetExtLIBPATH(pchEqual + 1, ulVar);
+ if (rc)
+ rcExit = errx(pCtx, 9, "error: DosSetExtLibPath(\"%s\", %.*s (%lu)): %lu",
+ pchEqual, pchEqual - pszValue, pchEqual + 1, ulVar, rc);
+ }
+ continue;
+ }
+#endif /* KBUILD_OS_OS2 */
+
+ /* We differ from kSubmit here and use putenv sematics. */
+ if (pchEqual)
+ {
+ if (pchEqual[1] != '\0')
+ rcExit = kBuiltinOptEnvSet(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ else
+ {
+ char *pszCopy = strdup(pszValue);
+ if (pszCopy)
+ {
+ pszCopy[pchEqual - pszValue] = '\0';
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszCopy);
+ free(pszCopy);
+ }
+ else
+ rcExit = errx(pCtx, 1, "out of memory!");
+ }
+ continue;
+ }
+ /* Simple unset. */
+ chOpt = 'U';
+ }
+
+ /*
+ * Append or prepend value to and environment variable.
+ */
+ if (chOpt == 'A' || chOpt == 'D')
+ {
+#ifdef KBUILD_OS_OS2
+ if ( strcmp(pszValue, "BEGINLIBPATH") == 0
+ || strcmp(pszValue, "ENDLIBPATH") == 0
+ || strcmp(pszValue, "LIBPATHSTRICT") == 0)
+ rcExit = errx(pCtx, 2, "error: '%s' cannot currently be appended or prepended to. Please use -E/--set for now.", pszValue);
+ else
+#endif
+ if (chOpt == 'A')
+ rcExit = kBuiltinOptEnvAppend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ else
+ rcExit = kBuiltinOptEnvPrepend(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ continue;
+ }
+
+ /*
+ * Unset environment variable.
+ */
+ if (chOpt == 'U')
+ {
+#ifdef KBUILD_OS_OS2
+ if ( strcmp(pszValue, "BEGINLIBPATH") == 0
+ || strcmp(pszValue, "ENDLIBPATH") == 0
+ || strcmp(pszValue, "LIBPATHSTRICT") == 0)
+ rcExit = errx(pCtx, 2, "error: '%s' cannot be unset, only set to an empty value using -E/--set.", pszValue);
+ else
+#endif
+ rcExit = kBuiltinOptEnvUnset(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity, pszValue);
+ continue;
+ }
+
+ /*
+ * Zap environment switch?
+ */
+ if (chOpt == 'Z') /* (no -i option here, as it's reserved for stdin) */
+ {
+ rcExit = kBuiltinOptEnvZap(pCtx, &papszEnvVars, &cEnvVars, &cAllocatedEnvVars, cVerbosity);
+ continue;
+ }
+
+ /*
+ * Change directory switch?
+ */
+ if (chOpt == 'C')
+ {
+ if (pszSavedCwd == NULL)
+ pszSavedCwd = strdup(szCwd);
+ if (pszSavedCwd)
+ rcExit = kBuiltinOptChDir(pCtx, szCwd, cbCwdBuf, pszValue);
+ else
+ rcExit = err(pCtx, 9, "out of memory!");
+ continue;
+ }
+
+
+ /*
+ * Verbose operation switch?
+ */
+ if (chOpt == 'v')
+ {
+ cVerbosity++;
+ continue;
+ }
+
+ /*
+ * Executable image other than the first argument following '--'.
+ */
+ if (chOpt == 'e')
+ {
+ pszExecutable = pszValue;
+ continue;
+ }
+
+ /*
+ * Okay, it is some file descriptor operation. Make sure we've got room for it.
+ */
+ if (cOrders + 1 < K_ELEMENTS(aOrders))
+ {
+ aOrders[cOrders].fdTarget = -1;
+ aOrders[cOrders].fdSource = -1;
+ aOrders[cOrders].fOpen = 0;
+ aOrders[cOrders].fRemoveOnFailure = 0;
+ aOrders[cOrders].pszFilename = NULL;
+ aOrders[cOrders].fdOtherPipeEnd = -1;
+#ifndef USE_POSIX_SPAWN
+ aOrders[cOrders].fdSaved = -1;
+#endif
+ }
+ else
+ {
+ rcExit = errx(pCtx, 2, "error: too many file actions (max: %d)", K_ELEMENTS(aOrders));
+ break;
+ }
+
+ if (chOpt == 'c')
+ {
+ /*
+ * Close the specified file descriptor (no stderr/out/in aliases).
+ */
+ char *pszTmp;
+ fd = (int)strtol(pszValue, &pszTmp, 0);
+ if (pszTmp == pszValue || *pszTmp != '\0')
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", pszValue);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, pszValue);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
+#endif
+ else
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Close;
+ aOrders[cOrders].fdTarget = fd;
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_addclose(&FileActions, fd);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_addclose(%d) failed: %s", fd, strerror(rcExit));
+#endif
+ }
+ }
+ else if (chOpt == 'd')
+ {
+ /*
+ * Duplicate file handle. Value is fdTarget=fdSource
+ */
+ char *pszEqual;
+ fd = (int)strtol(pszValue, &pszEqual, 0);
+ if (pszEqual == pszValue)
+ rcExit = errx(pCtx, 2, "error: failed to convert target descriptor of '-d %s' to a number", pszValue);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative target descriptor %d ('-d %s')", fd, pszValue);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: target %d is not a standard descriptor number", fd);
+#endif
+ else if (*pszEqual != '=')
+ rcExit = errx(pCtx, 2, "syntax error: expected '=' to follow target descriptor: '-d %s'", pszValue);
+ else
+ {
+ char *pszEnd;
+ int fdSource = (int)strtol(++pszEqual, &pszEnd, 0);
+ if (pszEnd == pszEqual || *pszEnd != '\0')
+ rcExit = errx(pCtx, 2, "error: failed to convert source descriptor of '-d %s' to a number", pszValue);
+ else if (fdSource < 0)
+ rcExit = errx(pCtx, 2, "error: negative source descriptor %d ('-d %s')", fdSource, pszValue);
+ else
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Dup;
+ aOrders[cOrders].fdTarget = fd;
+ aOrders[cOrders].fdSource = fdSource;
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdSource, fd);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(%d) failed: %s",
+ fdSource, fd, strerror(rcExit));
+#endif
+ }
+ }
+ }
+ else if (chOpt == 'I')
+ {
+ /*
+ * Replace stdin with the read end of an anonymous pipe.
+ */
+ int aFds[2] = { -1, -1 };
+ rcExit = kRedirectCreateStdInPipeWithoutConflict(pCtx, aFds, cOrders, aOrders, 0 /*fdTarget*/);
+ if (rcExit == 0)
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Dup;
+ aOrders[cOrders].fdTarget = 0;
+ aOrders[cOrders].fdSource = aFds[0];
+ aOrders[cOrders].fdOtherPipeEnd = aFds[1];
+ cOrders++;
+#ifdef USE_POSIX_SPAWN
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, aFds[0], 0);
+ if (rcExit != 0)
+ rcExit = errx(pCtx, 2, "posix_spawn_file_actions_adddup2(0) failed: %s", strerror(rcExit));
+#endif
+ }
+ }
+ else
+ {
+ /*
+ * Open file as a given file descriptor.
+ */
+ int fdOpened;
+ int fOpen;
+
+ /* mode */
+ switch (chOpt)
+ {
+ case 'r':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_RDONLY;
+ break;
+
+ case 'w':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR | O_CREAT | O_TRUNC;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_WRONLY | O_CREAT | O_TRUNC;
+ aOrders[cOrders].fRemoveOnFailure = 1;
+ break;
+
+ case 'a':
+ chOpt = *pszArg++;
+ if (chOpt == '+')
+ {
+ fOpen = O_RDWR | O_CREAT | O_APPEND;
+ chOpt = *pszArg++;
+ }
+ else
+ fOpen = O_WRONLY | O_CREAT | O_APPEND;
+ break;
+
+ case 'i': /* make sure stdin is read-only. */
+ fOpen = O_RDONLY;
+ break;
+
+ case '+':
+ rcExit = errx(pCtx, 2, "syntax error: Unexpected '+' in '%s'", argv[iArg]);
+ continue;
+
+ default:
+ fOpen = O_RDWR | O_CREAT | O_TRUNC;
+ aOrders[cOrders].fRemoveOnFailure = 1;
+ break;
+ }
+
+ /* binary / text modifiers */
+ switch (chOpt)
+ {
+ case 'b':
+ chOpt = *pszArg++;
+ default:
+#ifdef O_BINARY
+ fOpen |= O_BINARY;
+#elif defined(_O_BINARY)
+ fOpen |= _O_BINARY;
+#endif
+ break;
+
+ case 't':
+#ifdef O_TEXT
+ fOpen |= O_TEXT;
+#elif defined(_O_TEXT)
+ fOpen |= _O_TEXT;
+#endif
+ chOpt = *pszArg++;
+ break;
+
+ }
+
+ /* convert to file descriptor number */
+ switch (chOpt)
+ {
+ case 'i':
+ fd = 0;
+ break;
+
+ case 'o':
+ fd = 1;
+ break;
+
+ case 'e':
+ fd = 2;
+ break;
+
+ case '0':
+ if (*pszArg == '\0')
+ {
+ fd = 0;
+ break;
+ }
+ /* fall thru */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ pszValue = pszArg - 1;
+ fd = (int)strtol(pszValue, &pszArg, 0);
+ if (pszArg == pszValue)
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' to a number", argv[iArg]);
+ else if (fd < 0)
+ rcExit = errx(pCtx, 2, "error: negative fd %d (%s)", fd, argv[iArg]);
+#ifdef ONLY_TARGET_STANDARD_HANDLES
+ else if (fd > 2)
+ rcExit = errx(pCtx, 2, "error: %d is not a standard descriptor number", fd);
+#endif
+ else
+ break;
+ continue;
+
+ /*
+ * Invalid argument.
+ */
+ default:
+ rcExit = errx(pCtx, 2, "error: failed to convert '%s' ('%s') to a file descriptor", pszArg, argv[iArg]);
+ continue;
+ }
+
+ /*
+ * Check for the filename.
+ */
+ if (*pszArg != '\0')
+ {
+ if (*pszArg != ':' && *pszArg != '=')
+ {
+ rcExit = errx(pCtx, 2, "syntax error: characters following the file descriptor: '%s' ('%s')",
+ pszArg, argv[iArg]);
+ break;
+ }
+ pszArg++;
+ }
+ else if (++iArg < argc)
+ pszArg = argv[iArg];
+ else
+ {
+ rcExit = errx(pCtx, 2, "syntax error: missing filename argument.");
+ break;
+ }
+
+ /*
+ * Open the file. We could've used posix_spawn_file_actions_addopen here,
+ * but that means complicated error reporting. So, since we need to do
+ * this for windows anyway, just do it the same way everywhere.
+ */
+ fdOpened = kRedirectOpenWithoutConflict(pCtx, pszArg, fOpen, 0666, cOrders, aOrders,
+ aOrders[cOrders].fRemoveOnFailure, fd);
+ if (fdOpened >= 0)
+ {
+ aOrders[cOrders].enmOrder = kRedirectOrder_Open;
+ aOrders[cOrders].fdTarget = fd;
+ aOrders[cOrders].fdSource = fdOpened;
+ aOrders[cOrders].fOpen = fOpen;
+ aOrders[cOrders].pszFilename = pszArg;
+ cOrders++;
+
+#ifdef USE_POSIX_SPAWN
+ if (fdOpened != fd)
+ {
+ rcExit = posix_spawn_file_actions_adddup2(&FileActions, fdOpened, fd);
+ if (rcExit != 0)
+ rcExit = err(pCtx, 9, "posix_spawn_file_actions_adddup2(,%d [%s], %d) failed: %s",
+ fdOpened, fd, pszArg, strerror(rcExit));
+ }
+#endif
+ }
+ else
+ rcExit = 9;
+ }
+ }
+ else
+ {
+ errx(pCtx, 2, "syntax error: Invalid argument '%s'.", argv[iArg]);
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ }
+ }
+ if (!pszExecutable)
+ pszExecutable = argv[iArg];
+
+ /*
+ * Make sure there's something to execute.
+ */
+ if (rcExit == 0 && iArg < argc)
+ {
+ /*
+ * Do the spawning in a separate function (main is far to large as it is by now).
+ */
+ rcExit = kRedirectDoSpawn(pCtx, pszExecutable, argc - iArg, &argv[iArg], fWatcomBrainDamage,
+ papszEnvVars,
+ szCwd, pszSavedCwd,
+#ifdef USE_POSIX_SPAWN
+ cOrders, aOrders, &FileActions, cVerbosity,
+#else
+ cOrders, aOrders, cVerbosity,
+#endif
+#ifdef KMK
+ pPidSpawned,
+#endif
+ &fChildExitCode);
+ }
+ else if (rcExit == 0)
+ {
+ errx(pCtx, 2, "syntax error: nothing to execute!");
+ rcExit = kmk_redirect_usage(pCtx, 1);
+ }
+ /* Help and version sets rcExit to -1. Change it to zero. */
+ else if (rcExit == -1)
+ rcExit = 0;
+
+ /*
+ * Cleanup.
+ */
+ kBuiltinOptEnvCleanup(&papszEnvVars, cEnvVars, &cAllocatedEnvVars);
+ if (pszSavedCwd)
+ free(pszSavedCwd);
+ kRedirectCleanupFdOrders(cOrders, aOrders, rcExit != 0 && !fChildExitCode);
+#ifdef USE_POSIX_SPAWN
+ posix_spawn_file_actions_destroy(&FileActions);
+#endif
+#ifdef KBUILD_OS_OS2
+ for (ulLibPath = 0; ulLibPath < K_ELEMENTS(apszSavedLibPaths); ulLibPath++)
+ if (apszSavedLibPaths[ulLibPath] != NULL)
+ {
+ APIRET rc = DosSetExtLIBPATH(apszSavedLibPaths[ulLibPath], ulLibPath);
+ if (rc != 0)
+ warnx(pCtx, "DosSetExtLIBPATH('%s',%u) failed with %u when restoring the original values!",
+ apszSavedLibPaths[ulLibPath], ulLibPath, rc);
+ free(apszSavedLibPaths[ulLibPath]);
+ }
+#endif
+
+ return rcExit;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_redirect", NULL };
+ return kmk_builtin_redirect(argc, argv, envp, &Ctx, NULL, NULL);
+}
+#endif
+
+
diff --git a/src/kmk/kmkbuiltin/rm.c b/src/kmk/kmkbuiltin/rm.c
new file mode 100644
index 0000000..5753b3e
--- /dev/null
+++ b/src/kmk/kmkbuiltin/rm.c
@@ -0,0 +1,844 @@
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1990, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)rm.c 8.5 (Berkeley) 4/18/94";
+#endif /* not lint */
+#include <sys/cdefs.h>
+/*__FBSDID("$FreeBSD: src/bin/rm/rm.c,v 1.47 2004/04/06 20:06:50 markm Exp $");*/
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include <sys/stat.h>
+#if !defined(_MSC_VER) && !defined(__HAIKU__)
+# include <sys/param.h>
+# ifndef __gnu_hurd__
+# include <sys/mount.h>
+# endif
+#endif
+
+#include "err.h"
+#include <errno.h>
+#include <fcntl.h>
+#include "fts.h"
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifndef __HAIKU__
+# include <sysexits.h>
+#endif
+#include <unistd.h>
+#include <ctype.h>
+#include "getopt_r.h"
+#ifdef __HAIKU__
+# include "haikufakes.h"
+#endif
+#ifdef __NetBSD__
+# include <util.h>
+# define fflagstostr(flags) flags_to_string(flags, "")
+#endif
+#ifdef KBUILD_OS_WINDOWS
+# ifdef _MSC_VER
+# include "mscfakes.h"
+# endif
+# include "nt/ntunlink.h"
+ /* Use the special unlink implementation to do rmdir too. */
+# undef rmdir
+# define rmdir(a_pszPath) birdUnlinkForced(a_pszPath)
+#endif
+#if defined(__OS2__) || defined(_MSC_VER)
+# include <direct.h>
+# include <limits.h>
+#endif
+#include "kmkbuiltin.h"
+#include "kbuild_protection.h"
+#include "k/kDefs.h" /* for K_OS */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#if defined(__EMX__) || defined(KBUILD_OS_WINDOWS)
+# define IS_SLASH(ch) ( (ch) == '/' || (ch) == '\\' )
+# define HAVE_DOS_PATHS 1
+# define DEFAULT_PROTECTION_DEPTH 1
+#else
+# define IS_SLASH(ch) ( (ch) == '/' )
+# undef HAVE_DOS_PATHS
+# define DEFAULT_PROTECTION_DEPTH 2
+#endif
+
+#ifdef __EMX__
+#undef S_IFWHT
+#undef S_ISWHT
+#endif
+#ifndef S_IFWHT
+#define S_IFWHT 0
+#define S_ISWHT(s) 0
+#define undelete(s) (-1)
+#endif
+
+#if 1
+#define CUR_LINE_H2(x) "[line " #x "]"
+#define CUR_LINE_H1(x) CUR_LINE_H2(x)
+#define CUR_LINE() CUR_LINE_H1(__LINE__)
+#else
+# define CUR_LINE()
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RMINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
+#ifdef KBUILD_OS_WINDOWS
+ int fUseNtDeleteFile;
+#endif
+ uid_t uid;
+ KBUILDPROTECTION g_ProtData;
+} RMINSTANCE;
+typedef RMINSTANCE *PRMINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 261 },
+ { "version", no_argument, 0, 262 },
+ { "disable-protection", no_argument, 0, 263 },
+ { "enable-protection", no_argument, 0, 264 },
+ { "enable-full-protection", no_argument, 0, 265 },
+ { "disable-full-protection", no_argument, 0, 266 },
+ { "protection-depth", required_argument, 0, 267 },
+#ifdef KBUILD_OS_WINDOWS
+ { "nt-delete-file", no_argument, 0, 268 },
+#endif
+ { 0, 0, 0, 0 },
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+extern void bsd_strmode(mode_t mode, char *p); /* strmode.c */
+
+static int check(PRMINSTANCE, char *, char *, struct stat *);
+static void checkdot(PRMINSTANCE, char **);
+static int rm_file(PRMINSTANCE, char **);
+static int rm_overwrite(PRMINSTANCE, char *, struct stat *);
+static int rm_tree(PRMINSTANCE, char **);
+static int usage(PKMKBUILTINCTX, int);
+
+
+
+/*
+ * rm --
+ * This rm is different from historic rm's, but is expected to match
+ * POSIX 1003.2 behavior. The most visible difference is that -f
+ * has two specific effects now, ignore non-existent files and force
+ * file removal.
+ */
+int
+kmk_builtin_rm(int argc, char *argv[], char **envp, PKMKBUILTINCTX pCtx)
+{
+ RMINSTANCE This;
+ struct getopt_state_r gos;
+ int ch, rflag;
+
+ /* Init global instance data */
+ This.pCtx = pCtx;
+ This.dflag = 0;
+ This.eval = 0;
+ This.fflag = 0;
+ This.iflag = 0;
+ This.Pflag = 0;
+ This.vflag = 0;
+ This.Wflag = 0;
+ This.stdin_ok = 0;
+#ifdef KBUILD_OS_WINDOWS
+ This.fUseNtDeleteFile = 0;
+#endif
+ This.uid = 0;
+ kBuildProtectionInit(&This.g_ProtData, pCtx);
+
+ rflag = 0;
+ getopt_initialize_r(&gos, argc, argv, "dfiPRvW", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch (ch) {
+ case 'd':
+ This.dflag = 1;
+ break;
+ case 'f':
+ This.fflag = 1;
+ This.iflag = 0;
+ break;
+ case 'i':
+ This.fflag = 0;
+ This.iflag = 1;
+ break;
+ case 'P':
+ This.Pflag = 1;
+ break;
+ case 'R':
+#if 0
+ case 'r': /* Compatibility. */
+#endif
+ rflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+#ifdef FTS_WHITEOUT
+ case 'W':
+ This.Wflag = 1;
+ break;
+#endif
+ case 261:
+ kBuildProtectionTerm(&This.g_ProtData);
+ usage(pCtx, 0);
+ return 0;
+ case 262:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return kbuild_version(argv[0]);
+ case 263:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case 264:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE);
+ break;
+ case 265:
+ kBuildProtectionEnable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case 266:
+ kBuildProtectionDisable(&This.g_ProtData, KBUILDPROTECTIONTYPE_FULL);
+ break;
+ case 267:
+ if (kBuildProtectionSetDepth(&This.g_ProtData, gos.optarg)) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ return 1;
+ }
+ break;
+#ifdef KBUILD_OS_WINDOWS
+ case 268:
+ This.fUseNtDeleteFile = 1;
+ break;
+#endif
+ case '?':
+ default:
+ kBuildProtectionTerm(&This.g_ProtData);
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc < 1) {
+ kBuildProtectionTerm(&This.g_ProtData);
+ if (This.fflag)
+ return (0);
+ return usage(pCtx, 1);
+ }
+
+ if (!kBuildProtectionScanEnv(&This.g_ProtData, envp, "KMK_RM_")) {
+ checkdot(&This, argv);
+ This.uid = geteuid();
+
+ if (*argv) {
+ This.stdin_ok = isatty(STDIN_FILENO);
+ if (rflag)
+ This.eval |= rm_tree(&This, argv);
+ else
+ This.eval |= rm_file(&This, argv);
+ }
+ } else {
+ This.eval = 1;
+ }
+
+ kBuildProtectionTerm(&This.g_ProtData);
+ return This.eval;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_rm", NULL };
+ return kmk_builtin_rm(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+rm_tree(PRMINSTANCE pThis, char **argv)
+{
+ FTS *fts;
+ FTSENT *p;
+ int needstat;
+ int flags;
+ int rval;
+
+ /*
+ * Check up front before anything is deleted. This will not catch
+ * everything, but we'll check the individual items later.
+ */
+ int i;
+ for (i = 0; argv[i]; i++) {
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE, argv[i])) {
+ return 1;
+ }
+ }
+
+ /*
+ * Remove a file hierarchy. If forcing removal (-f), or interactive
+ * (-i) or can't ask anyway (stdin_ok), don't stat the file.
+ */
+ needstat = !pThis->uid || (!pThis->fflag && !pThis->iflag && pThis->stdin_ok);
+
+ /*
+ * If the -i option is specified, the user can skip on the pre-order
+ * visit. The fts_number field flags skipped directories.
+ */
+#define SKIPPED 1
+
+ flags = FTS_PHYSICAL;
+#ifndef KMK_BUILTIN_STANDALONE
+ flags |= FTS_NOCHDIR; /* Must not change the directory from inside kmk! */
+#endif
+ if (!needstat)
+ flags |= FTS_NOSTAT;
+#ifdef FTS_WHITEOUT
+ if (pThis->Wflag)
+ flags |= FTS_WHITEOUT;
+#endif
+ if (!(fts = fts_open(argv, flags, NULL))) {
+ return err(pThis->pCtx, 1, "fts_open");
+ }
+ while ((p = fts_read(fts)) != NULL) {
+ const char *operation = "chflags";
+ switch (p->fts_info) {
+ case FTS_DNR:
+ if (!pThis->fflag || p->fts_errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "fts: %s: %s" CUR_LINE() "\n",
+ p->fts_path, strerror(p->fts_errno));
+ continue;
+ case FTS_ERR:
+ fts_close(fts);
+ return errx(pThis->pCtx, 1, "fts: %s: %s " CUR_LINE(), p->fts_path, strerror(p->fts_errno));
+ case FTS_NS:
+ /*
+ * Assume that since fts_read() couldn't stat the
+ * file, it can't be unlinked.
+ */
+ if (!needstat)
+ break;
+ if (!pThis->fflag || p->fts_errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "fts: %s: %s " CUR_LINE() "\n",
+ p->fts_path, strerror(p->fts_errno));
+ continue;
+ case FTS_D:
+ /* Pre-order: give user chance to skip. */
+ if (!pThis->fflag && !check(pThis, p->fts_path, p->fts_accpath, p->fts_statp)) {
+ (void)fts_set(fts, p, FTS_SKIP);
+ p->fts_number = SKIPPED;
+ }
+#ifdef UF_APPEND
+ else if (!pThis->uid &&
+ (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ chflags(p->fts_accpath,
+ p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
+ goto err;
+#endif
+ continue;
+ case FTS_DP:
+ /* Post-order: see if user skipped. */
+ if (p->fts_number == SKIPPED)
+ continue;
+ break;
+ default:
+ if (!pThis->fflag && !check(pThis, p->fts_path, p->fts_accpath, p->fts_statp))
+ continue;
+ }
+
+ /*
+ * Protect against deleting root files and directories.
+ */
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_RECURSIVE, p->fts_accpath)) {
+ fts_close(fts);
+ return 1;
+ }
+
+ rval = 0;
+#ifdef UF_APPEND
+ if (!pThis->uid &&
+ (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
+ rval = chflags(p->fts_accpath,
+ p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
+#endif
+ if (rval == 0) {
+ /*
+ * If we can't read or search the directory, may still be
+ * able to remove it. Don't print out the un{read,search}able
+ * message unless the remove fails.
+ */
+ switch (p->fts_info) {
+ case FTS_DP:
+ case FTS_DNR:
+#ifdef KBUILD_OS_WINDOWS
+ if (p->fts_parent->fts_dirfd != NT_FTS_INVALID_HANDLE_VALUE) {
+ rval = birdUnlinkForcedEx(p->fts_parent->fts_dirfd, p->fts_name);
+ } else {
+ rval = birdUnlinkForced(p->fts_accpath);
+ }
+#else
+ rval = rmdir(p->fts_accpath);
+#endif
+ if (rval == 0 || (pThis->fflag && errno == ENOENT)) {
+ if (rval == 0 && pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ if (rval == 0) {
+ extern int dir_cache_deleted_directory(const char *pszDir);
+ dir_cache_deleted_directory(p->fts_accpath);
+ }
+#endif
+ continue;
+ }
+ operation = "rmdir";
+ break;
+
+#ifdef FTS_W
+ case FTS_W:
+ rval = undelete(p->fts_accpath);
+ if (rval == 0 && (pThis->fflag && errno == ENOENT)) {
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+ continue;
+ }
+ operation = "undelete";
+ break;
+#endif
+
+ case FTS_NS:
+ /*
+ * Assume that since fts_read() couldn't stat
+ * the file, it can't be unlinked.
+ */
+ if (pThis->fflag)
+ continue;
+ /* FALLTHROUGH */
+ default:
+ if (pThis->Pflag)
+ if (!rm_overwrite(pThis, p->fts_accpath, NULL))
+ continue;
+#ifdef KBUILD_OS_WINDOWS
+ if (p->fts_parent->fts_dirfd != NT_FTS_INVALID_HANDLE_VALUE) {
+ if (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE) {
+ rval = birdUnlinkForcedFastEx(p->fts_parent->fts_dirfd, p->fts_name);
+ } else { /* NtDeleteFile doesn't work on directory links, so slow symlink deletion: */
+ rval = birdUnlinkForcedEx(p->fts_parent->fts_dirfd, p->fts_name);
+ }
+ } else {
+ if (p->fts_info != FTS_SL && p->fts_info != FTS_SLNONE) {
+ rval = birdUnlinkForcedFast(p->fts_accpath);
+ } else { /* NtDeleteFile doesn't work on directory links, so slow symlink deletion: */
+ rval = birdUnlinkForced(p->fts_accpath);
+ }
+ }
+#else
+ rval = unlink(p->fts_accpath);
+#endif
+
+ if (rval == 0 || (pThis->fflag && errno == ENOENT)) {
+ if (rval == 0 && pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", p->fts_path);
+ continue;
+ }
+ operation = "unlink";
+ break;
+ }
+ }
+#ifdef UF_APPEND
+err:
+#endif
+ pThis->eval = errx(pThis->pCtx, 1, "%s: %s failed: %s " CUR_LINE() "\n", p->fts_path, operation, strerror(errno));
+ }
+ if (errno) {
+ pThis->eval = errx(pThis->pCtx, 1, "fts_read: %s " CUR_LINE() "\n", strerror(errno));
+ }
+ fts_close(fts);
+ return pThis->eval;
+}
+
+static int
+rm_file(PRMINSTANCE pThis, char **argv)
+{
+ struct stat sb;
+ int rval;
+ char *f;
+
+ /*
+ * Check up front before anything is deleted.
+ */
+ int i;
+ for (i = 0; argv[i]; i++) {
+ if (kBuildProtectionEnforce(&pThis->g_ProtData, KBUILDPROTECTIONTYPE_FULL, argv[i]))
+ return 1;
+ }
+
+ /*
+ * Remove a file. POSIX 1003.2 states that, by default, attempting
+ * to remove a directory is an error, so must always stat the file.
+ */
+ while ((f = *argv++) != NULL) {
+ const char *operation = "?";
+ /* Assume if can't stat the file, can't unlink it. */
+ if (lstat(f, &sb)) {
+#ifdef FTS_WHITEOUT
+ if (pThis->Wflag) {
+ sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
+ } else {
+#else
+ {
+#endif
+ if (!pThis->fflag || errno != ENOENT)
+ pThis->eval = errx(pThis->pCtx, 1, "%s: lstat failed: %s " CUR_LINE() "\n",
+ f, strerror(errno));
+ continue;
+ }
+#ifdef FTS_WHITEOUT
+ } else if (pThis->Wflag) {
+ errx(pThis->pCtx, 1, "%s: %s\n", f, strerror(EEXIST));
+ pThis->eval = 1;
+ continue;
+#endif
+ }
+
+ if (S_ISDIR(sb.st_mode) && !pThis->dflag) {
+ pThis->eval = errx(pThis->pCtx, 1, "%s: is a directory\n", f);
+ continue;
+ }
+ if (!pThis->fflag && !S_ISWHT(sb.st_mode) && !check(pThis, f, f, &sb))
+ continue;
+ rval = 0;
+#ifdef UF_APPEND
+ if (!pThis->uid &&
+ (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
+ !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
+ rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
+#endif
+ if (rval == 0) {
+ if (S_ISWHT(sb.st_mode)) {
+ rval = undelete(f);
+ operation = "undelete";
+ } else if (S_ISDIR(sb.st_mode)) {
+ rval = rmdir(f);
+ operation = "rmdir";
+ } else {
+ if (pThis->Pflag)
+ if (!rm_overwrite(pThis, f, &sb))
+ continue;
+#ifndef KBUILD_OS_WINDOWS
+ rval = unlink(f);
+ operation = "unlink";
+#else
+ if (pThis->fUseNtDeleteFile) {
+ rval = birdUnlinkForcedFast(f);
+ operation = "NtDeleteFile";
+ } else {
+ rval = birdUnlinkForced(f);
+ operation = "unlink";
+ }
+#endif
+ }
+ }
+ if (rval && (!pThis->fflag || errno != ENOENT))
+ pThis->eval = errx(pThis->pCtx, 1, "%s: %s failed: %s" CUR_LINE() "\n", f, operation, strerror(errno));
+ if (pThis->vflag && rval == 0)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", f);
+ }
+ return pThis->eval;
+}
+
+/*
+ * rm_overwrite --
+ * Overwrite the file 3 times with varying bit patterns.
+ *
+ * XXX
+ * This is a cheap way to *really* delete files. Note that only regular
+ * files are deleted, directories (and therefore names) will remain.
+ * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
+ * System V file system). In a logging file system, you'll have to have
+ * kernel support.
+ */
+static int
+rm_overwrite(PRMINSTANCE pThis, char *file, struct stat *sbp)
+{
+ struct stat sb;
+#ifdef HAVE_FSTATFS
+ struct statfs fsb;
+#endif
+ off_t len;
+ int bsize, fd, wlen;
+ char *buf = NULL;
+ const char *operation = "lstat";
+ int error;
+
+ fd = -1;
+ if (sbp == NULL) {
+ if (lstat(file, &sb))
+ goto err;
+ sbp = &sb;
+ }
+ if (!S_ISREG(sbp->st_mode))
+ return (1);
+ operation = "open";
+ if ((fd = open(file, O_WRONLY | KMK_OPEN_NO_INHERIT, 0)) == -1)
+ goto err;
+#ifdef HAVE_FSTATFS
+ if (fstatfs(fd, &fsb) == -1)
+ goto err;
+ bsize = MAX(fsb.f_iosize, 1024);
+#elif defined(HAVE_ST_BLKSIZE)
+ bsize = MAX(sb.st_blksize, 1024);
+#else
+ bsize = 1024;
+#endif
+ if ((buf = malloc(bsize)) == NULL) {
+ err(pThis->pCtx, 1, "%s: malloc", file);
+ close(fd);
+ return 1;
+ }
+
+#define PASS(byte) { \
+ operation = "write"; \
+ memset(buf, byte, bsize); \
+ for (len = sbp->st_size; len > 0; len -= wlen) { \
+ wlen = len < bsize ? len : bsize; \
+ if (write(fd, buf, wlen) != wlen) \
+ goto err; \
+ } \
+}
+ PASS(0xff);
+ operation = "fsync/lseek";
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
+ goto err;
+ PASS(0x00);
+ operation = "fsync/lseek";
+ if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
+ goto err;
+ PASS(0xff);
+ if (!fsync(fd) && !close(fd)) {
+ free(buf);
+ return (1);
+ }
+ operation = "fsync/close";
+
+err: pThis->eval = 1;
+ error = errno;
+ if (buf)
+ free(buf);
+ if (fd != -1)
+ close(fd);
+ errx(pThis->pCtx, 1, "%s: %s: %s: %s" CUR_LINE() "\n", operation, pThis->pCtx->pszProgName, file, strerror(error));
+ return (0);
+}
+
+
+static int
+check(PRMINSTANCE pThis, char *path, char *name, struct stat *sp)
+{
+ int ch, first;
+ char modep[15], *flagsp;
+
+ /* Check -i first. */
+ if (pThis->iflag)
+ (void)fprintf(stderr, "%s: remove %s? ", pThis->pCtx->pszProgName, path);
+ else {
+ /*
+ * If it's not a symbolic link and it's unwritable and we're
+ * talking to a terminal, ask. Symbolic links are excluded
+ * because their permissions are meaningless. Check stdin_ok
+ * first because we may not have stat'ed the file.
+ * Also skip this check if the -P option was specified because
+ * we will not be able to overwrite file contents and will
+ * barf later.
+ */
+ if (!pThis->stdin_ok || S_ISLNK(sp->st_mode) || pThis->Pflag ||
+ (!access(name, W_OK) &&
+#ifdef SF_APPEND
+ !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
+ (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !pThis->uid))
+#else
+ 1)
+#endif
+ )
+ return (1);
+ bsd_strmode(sp->st_mode, modep);
+#if defined(SF_APPEND) && K_OS != K_OS_GNU_KFBSD && K_OS != K_OS_GNU_HURD
+ if ((flagsp = fflagstostr(sp->st_flags)) == NULL) {
+ err(pThis->pCtx, 1, "fflagstostr");
+ flagsp = "<bad-fflagstostr>";
+ }
+ (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ user_from_uid(sp->st_uid, 0),
+ group_from_gid(sp->st_gid, 0),
+ *flagsp ? flagsp : "", *flagsp ? " " : "",
+ path);
+ free(flagsp);
+#else
+ (void)flagsp;
+ (void)fprintf(stderr, "override %s%s %d/%d for %s? ",
+ modep + 1, modep[9] == ' ' ? "" : " ",
+ sp->st_uid, sp->st_gid, path);
+#endif
+ }
+ (void)fflush(stderr);
+
+ first = ch = getchar();
+ while (ch != '\n' && ch != EOF)
+ ch = getchar();
+ return (first == 'y' || first == 'Y');
+}
+
+#define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
+static void
+checkdot(PRMINSTANCE pThis, char **argv)
+{
+ char *p, **save, **t;
+ int complained;
+
+ complained = 0;
+ for (t = argv; *t;) {
+#ifdef HAVE_DOS_PATHS
+ const char *tmp = p = *t;
+ while (*tmp) {
+ switch (*tmp) {
+ case '/':
+ case '\\':
+ case ':':
+ p = (char *)tmp + 1;
+ break;
+ }
+ tmp++;
+ }
+#else
+ if ((p = strrchr(*t, '/')) != NULL)
+ ++p;
+ else
+ p = *t;
+#endif
+ if (ISDOT(p)) {
+ if (!complained++)
+ warnx(pThis->pCtx, "\".\" and \"..\" may not be removed\n");
+ pThis->eval = 1;
+ for (save = t; (t[0] = t[1]) != NULL; ++t)
+ continue;
+ t = save;
+ } else
+ ++t;
+ }
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [options] file ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Options:\n"
+ " -f\n"
+ " Attempt to remove files without prompting, regardless of the file\n"
+ " permission. Ignore non-existing files. Overrides previous -i's.\n"
+ " -i\n"
+ " Prompt for each file. Always.\n"
+ " -d\n"
+ " Attempt to remove directories as well as other kinds of files.\n"
+ " -P\n"
+ " Overwrite regular files before deleting; three passes: ff,0,ff\n"
+ " -R\n"
+ " Attempt to remove the file hierachy rooted in each file argument.\n"
+ " This option implies -d and file protection.\n"
+ " -v\n"
+ " Be verbose, show files as they are removed.\n"
+ " -W\n"
+ " Undelete without files.\n"
+ " --disable-protection\n"
+ " Will disable the protection file protection applied with -R.\n"
+ " --enable-protection\n"
+ " Will enable the protection file protection applied with -R.\n"
+ " --enable-full-protection\n"
+ " Will enable the protection file protection for all operations.\n"
+ " --disable-full-protection\n"
+ " Will disable the protection file protection for all operations.\n"
+ " --protection-depth\n"
+ " Number or path indicating the file protection depth. Default: %d\n"
+ "\n"
+ "Environment:\n"
+ " KMK_RM_DISABLE_PROTECTION\n"
+ " Same as --disable-protection. Overrides command line.\n"
+ " KMK_RM_ENABLE_PROTECTION\n"
+ " Same as --enable-protection. Overrides everyone else.\n"
+ " KMK_RM_ENABLE_FULL_PROTECTION\n"
+ " Same as --enable-full-protection. Overrides everyone else.\n"
+ " KMK_RM_DISABLE_FULL_PROTECTION\n"
+ " Same as --disable-full-protection. Overrides command line.\n"
+ " KMK_RM_PROTECTION_DEPTH\n"
+ " Same as --protection-depth. Overrides command line.\n"
+ "\n"
+ "The file protection of the top %d layers of the file hierarchy is there\n"
+ "to try prevent makefiles from doing bad things to your system. This\n"
+ "protection is not bulletproof, but should help prevent you from shooting\n"
+ "yourself in the foot.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ kBuildProtectionDefaultDepth(), kBuildProtectionDefaultDepth());
+ return EX_USAGE;
+}
+
diff --git a/src/kmk/kmkbuiltin/rmdir.c b/src/kmk/kmkbuiltin/rmdir.c
new file mode 100644
index 0000000..b4cd981
--- /dev/null
+++ b/src/kmk/kmkbuiltin/rmdir.c
@@ -0,0 +1,251 @@
+/*-
+ * Copyright (c) 1992, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)rmdir.c 8.3 (Berkeley) 4/2/94";
+#endif /* not lint */
+#endif
+#if 0
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/bin/rmdir/rmdir.c,v 1.20 2005/01/26 06:51:28 ssouhlal Exp $");
+#endif
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define FAKES_NO_GETOPT_H /* bird */
+#include "config.h"
+#include "err.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+#endif
+#include "getopt_r.h"
+#include "kmkbuiltin.h"
+
+#ifdef _MSC_VER
+# include "mscfakes.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct RMDIRINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ int pflag;
+ int vflag;
+ int ignore_fail_on_non_empty;
+ int ignore_fail_on_not_exist;
+} RMDIRINSTANCE;
+typedef RMDIRINSTANCE *PRMDIRINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static struct option long_options[] =
+{
+ { "help", no_argument, 0, 262 },
+ { "ignore-fail-on-non-empty", no_argument, 0, 260 },
+ { "ignore-fail-on-not-exist", no_argument, 0, 261 },
+ { "parents", no_argument, 0, 'p' },
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 263 },
+ { 0, 0, 0, 0 },
+};
+
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS)
+extern int dir_cache_deleted_directory(const char *pszDir);
+#endif
+static int rm_path(PRMDIRINSTANCE, char *);
+static int usage(PKMKBUILTINCTX, int);
+
+
+int
+kmk_builtin_rmdir(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ RMDIRINSTANCE This;
+ struct getopt_state_r gos;
+ int ch, errors;
+
+ /* Initialize global instance. */
+ This.pCtx = pCtx;
+ This.ignore_fail_on_not_exist = 0;
+ This.ignore_fail_on_non_empty = 0;
+ This.vflag = 0;
+ This.pflag = 0;
+
+ getopt_initialize_r(&gos, argc, argv, "pv", long_options, envp, pCtx);
+ while ((ch = getopt_long_r(&gos, NULL)) != -1)
+ switch(ch) {
+ case 'p':
+ This.pflag = 1;
+ break;
+ case 'v':
+ This.vflag = 1;
+ break;
+ case 260:
+ This.ignore_fail_on_non_empty = 1;
+ break;
+ case 261:
+ This.ignore_fail_on_not_exist = 1;
+ break;
+ case 262:
+ usage(pCtx, 0);
+ return 0;
+ case 263:
+ return kbuild_version(argv[0]);
+ case '?':
+ default:
+ return usage(pCtx, 1);
+ }
+ argc -= gos.optind;
+ argv += gos.optind;
+
+ if (argc == 0)
+ return /*usage(stderr)*/0;
+
+ for (errors = 0; *argv; argv++) {
+ if (rmdir(*argv) < 0) {
+ if ( ( !This.ignore_fail_on_non_empty
+ || (errno != ENOTEMPTY && errno != EPERM && errno != EACCES && errno != EINVAL && errno != EEXIST))
+ && ( !This.ignore_fail_on_not_exist
+ || errno != ENOENT)) {
+ warn(pCtx, "rmdir: %s", *argv);
+ errors = 1;
+ continue;
+ }
+ if (!This.ignore_fail_on_not_exist || errno != ENOENT)
+ continue;
+ /* (only ignored doesn't exist errors fall thru) */
+ } else {
+#if !defined(KMK_BUILTIN_STANDALONE) && defined(KBUILD_OS_WINDOWS)
+ dir_cache_deleted_directory(*argv);
+#endif
+ if (This.vflag)
+ kmk_builtin_ctx_printf(pCtx, 0, "%s\n", *argv);
+ }
+ if (This.pflag)
+ errors |= rm_path(&This, *argv);
+ }
+
+ return errors;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_rmdir", NULL };
+ return kmk_builtin_rmdir(argc, argv, envp, &Ctx);
+}
+#endif
+
+static int
+rm_path(PRMDIRINSTANCE pThis, char *path)
+{
+ char *p;
+ const size_t len = strlen(path);
+ p = alloca(len + 1);
+ path = memcpy(p, path, len + 1);
+
+#if defined(_MSC_VER) || defined(__EMX__)
+ p = strchr(path, '\\');
+ while (p) {
+ *p++ = '/';
+ p = strchr(p, '\\');
+ }
+#endif
+
+ p = path + len;
+ while (--p > path && *p == '/')
+ ;
+ *++p = '\0';
+ while ((p = strrchr(path, '/')) != NULL) {
+ /* Delete trailing slashes. */
+ while (--p >= path && *p == '/')
+ ;
+ *++p = '\0';
+ if (p == path)
+ break;
+#if defined(_MSC_VER) || defined(__EMX__)
+ if (p[-1] == ':' && p - 2 == path)
+ break;
+#endif
+
+ if (rmdir(path) < 0) {
+ if ( pThis->ignore_fail_on_non_empty
+ && ( errno == ENOTEMPTY || errno == EPERM || errno == EACCES || errno == EINVAL || errno == EEXIST))
+ break;
+ if (!pThis->ignore_fail_on_not_exist || errno != ENOENT) {
+ warn(pThis->pCtx, "rmdir: %s", path);
+ return (1);
+ }
+ }
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ else {
+ dir_cache_deleted_directory(path);
+ }
+#endif
+ if (pThis->vflag)
+ kmk_builtin_ctx_printf(pThis->pCtx, 0, "%s\n", path);
+ }
+
+ return (0);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s [-pv --ignore-fail-on-non-empty --ignore-fail-on-not-exist] directory ...\n"
+ " or: %s --help\n"
+ " or: %s --version\n",
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
diff --git a/src/kmk/kmkbuiltin/setmode.c b/src/kmk/kmkbuiltin/setmode.c
new file mode 100644
index 0000000..80ed301
--- /dev/null
+++ b/src/kmk/kmkbuiltin/setmode.c
@@ -0,0 +1,506 @@
+/* $NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * 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 <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 3/25/94";
+#else
+__RCSID("$NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+/*#include "namespace.h"*/
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include "mscfakes.h"
+#endif
+
+#ifdef SETMODE_DEBUG
+#include <stdio.h>
+#endif
+
+/*#ifdef __weak_alias
+__weak_alias(getmode,_getmode)
+__weak_alias(setmode,_setmode)
+#endif*/
+
+#define SET_LEN 6 /* initial # of bitcmd struct to malloc */
+#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+ char cmd;
+ char cmd2;
+ mode_t bits;
+} BITCMD;
+
+#define CMD2_CLR 0x01
+#define CMD2_SET 0x02
+#define CMD2_GBITS 0x04
+#define CMD2_OBITS 0x08
+#define CMD2_UBITS 0x10
+
+static BITCMD *addcmd(BITCMD *, int, int, int, u_int);
+static void compress_mode(BITCMD *);
+#ifdef SETMODE_DEBUG
+static void dumpmode(BITCMD *);
+#endif
+
+#ifndef _DIAGASSERT
+# define _DIAGASSERT assert
+#endif
+
+#ifndef S_ISTXT
+# ifdef S_ISVTX
+# define S_ISTXT S_ISVTX
+# else
+# define S_ISTXT 0
+# endif
+#endif /* !S_ISTXT */
+
+extern mode_t g_fUMask; /* Initialize in main() and keep up to date. */
+
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+bsd_getmode(bbox, omode)
+ const void *bbox;
+ mode_t omode;
+{
+ const BITCMD *set;
+ mode_t clrval, newmode, value;
+
+ _DIAGASSERT(bbox != NULL);
+
+ set = (const BITCMD *)bbox;
+ newmode = omode;
+ for (value = 0;; set++)
+ switch(set->cmd) {
+ /*
+ * When copying the user, group or other bits around, we "know"
+ * where the bits are in the mode so that we can do shifts to
+ * copy them around. If we don't use shifts, it gets real
+ * grundgy with lots of single bit checks and bit sets.
+ */
+ case 'u':
+ value = (newmode & S_IRWXU) >> 6;
+ goto common;
+
+ case 'g':
+ value = (newmode & S_IRWXG) >> 3;
+ goto common;
+
+ case 'o':
+ value = newmode & S_IRWXO;
+common: if (set->cmd2 & CMD2_CLR) {
+ clrval =
+ (set->cmd2 & CMD2_SET) ? S_IRWXO : value;
+ if (set->cmd2 & CMD2_UBITS)
+ newmode &= ~((clrval<<6) & set->bits);
+ if (set->cmd2 & CMD2_GBITS)
+ newmode &= ~((clrval<<3) & set->bits);
+ if (set->cmd2 & CMD2_OBITS)
+ newmode &= ~(clrval & set->bits);
+ }
+ if (set->cmd2 & CMD2_SET) {
+ if (set->cmd2 & CMD2_UBITS)
+ newmode |= (value<<6) & set->bits;
+ if (set->cmd2 & CMD2_GBITS)
+ newmode |= (value<<3) & set->bits;
+ if (set->cmd2 & CMD2_OBITS)
+ newmode |= value & set->bits;
+ }
+ break;
+
+ case '+':
+ newmode |= set->bits;
+ break;
+
+ case '-':
+ newmode &= ~set->bits;
+ break;
+
+ case 'X':
+ if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+ newmode |= set->bits;
+ break;
+
+ case '\0':
+ default:
+#ifdef SETMODE_DEBUG
+ (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+ return (newmode);
+ }
+}
+
+#define ADDCMD(a, b, c, d) do { \
+ if (set >= endset) { \
+ BITCMD *newset; \
+ setlen += SET_LEN_INCR; \
+ newset = realloc(saveset, sizeof(BITCMD) * setlen); \
+ if (newset == NULL) { \
+ free(saveset); \
+ return (NULL); \
+ } \
+ set = newset + (set - saveset); \
+ saveset = newset; \
+ endset = newset + (setlen - 2); \
+ } \
+ set = addcmd(set, (a), (b), (c), (d)); \
+} while (/*CONSTCOND*/0)
+
+#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+void *
+bsd_setmode(p)
+ const char *p;
+{
+ int perm, who;
+ char op, *ep;
+ BITCMD *set, *saveset, *endset;
+#ifndef _MSC_VER
+ sigset_t signset, sigoset;
+#endif
+ mode_t mask;
+ int equalopdone = 0; /* pacify gcc */
+ int permXbits, setlen;
+
+ if (!*p)
+ return (NULL);
+
+ /*
+ * Get a copy of the mask for the permissions that are mask relative.
+ * Flip the bits, we want what's not set. Since it's possible that
+ * the caller is opening files inside a signal handler, protect them
+ * as best we can.
+ */
+#ifndef _MSC_VER
+ sigfillset(&signset);
+ (void)sigprocmask(SIG_BLOCK, &signset, &sigoset);
+#endif
+ mask = g_fUMask;
+ assert(mask == umask(g_fUMask));
+ mask = ~mask;
+#ifndef _MSC_VER
+ (void)sigprocmask(SIG_SETMASK, &sigoset, NULL);
+#endif
+
+ setlen = SET_LEN + 2;
+
+ if ((set = malloc((u_int)(sizeof(BITCMD) * setlen))) == NULL)
+ return (NULL);
+ saveset = set;
+ endset = set + (setlen - 2);
+
+ /*
+ * If an absolute number, get it and return; disallow non-octal digits
+ * or illegal bits.
+ */
+ if (isdigit((unsigned char)*p)) {
+ perm = (mode_t)strtol(p, &ep, 8);
+ if (*ep || perm & ~(STANDARD_BITS|S_ISTXT)) {
+ free(saveset);
+ return (NULL);
+ }
+ ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
+ set->cmd = 0;
+ return (saveset);
+ }
+
+ /*
+ * Build list of structures to set/clear/copy bits as described by
+ * each clause of the symbolic mode.
+ */
+ for (;;) {
+ /* First, find out which bits might be modified. */
+ for (who = 0;; ++p) {
+ switch (*p) {
+ case 'a':
+ who |= STANDARD_BITS;
+ break;
+ case 'u':
+ who |= S_ISUID|S_IRWXU;
+ break;
+ case 'g':
+ who |= S_ISGID|S_IRWXG;
+ break;
+ case 'o':
+ who |= S_IRWXO;
+ break;
+ default:
+ goto getop;
+ }
+ }
+
+getop: if ((op = *p++) != '+' && op != '-' && op != '=') {
+ free(saveset);
+ return (NULL);
+ }
+ if (op == '=')
+ equalopdone = 0;
+
+ who &= ~S_ISTXT;
+ for (perm = 0, permXbits = 0;; ++p) {
+ switch (*p) {
+ case 'r':
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ break;
+ case 's':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO))
+ perm |= S_ISUID|S_ISGID;
+ break;
+ case 't':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO)) {
+ who |= S_ISTXT;
+ perm |= S_ISTXT;
+ }
+ break;
+ case 'w':
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ break;
+ case 'X':
+ permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'x':
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'u':
+ case 'g':
+ case 'o':
+ /*
+ * When ever we hit 'u', 'g', or 'o', we have
+ * to flush out any partial mode that we have,
+ * and then do the copying of the mode bits.
+ */
+ if (perm) {
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (op == '=')
+ equalopdone = 1;
+ if (op == '+' && permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ ADDCMD(*p, who, op, mask);
+ break;
+
+ default:
+ /*
+ * Add any permissions that we haven't already
+ * done.
+ */
+ if (perm || (op == '=' && !equalopdone)) {
+ if (op == '=')
+ equalopdone = 1;
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ goto apply;
+ }
+ }
+
+apply: if (!*p)
+ break;
+ if (*p != ',')
+ goto getop;
+ ++p;
+ }
+ set->cmd = 0;
+#ifdef SETMODE_DEBUG
+ (void)printf("Before compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+ (void)printf("After compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ return (saveset);
+}
+
+static BITCMD *
+addcmd(set, op, who, oparg, mask)
+ BITCMD *set;
+ int oparg, who;
+ int op;
+ u_int mask;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ switch (op) {
+ case '=':
+ set->cmd = '-';
+ set->bits = who ? who : STANDARD_BITS;
+ set++;
+
+ op = '+';
+ /* FALLTHROUGH */
+ case '+':
+ case '-':
+ case 'X':
+ set->cmd = op;
+ set->bits = (who ? (mode_t)who : mask) & oparg;
+ break;
+
+ case 'u':
+ case 'g':
+ case 'o':
+ set->cmd = op;
+ if (who) {
+ set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+ ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+ ((who & S_IROTH) ? CMD2_OBITS : 0);
+ set->bits = (mode_t)~0;
+ } else {
+ set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+ set->bits = mask;
+ }
+
+ if (oparg == '+')
+ set->cmd2 |= CMD2_SET;
+ else if (oparg == '-')
+ set->cmd2 |= CMD2_CLR;
+ else if (oparg == '=')
+ set->cmd2 |= CMD2_SET|CMD2_CLR;
+ break;
+ }
+ return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(set)
+ BITCMD *set;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ for (; set->cmd; ++set)
+ (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+ set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+ set->cmd2 & CMD2_CLR ? " CLR" : "",
+ set->cmd2 & CMD2_SET ? " SET" : "",
+ set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+ set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+ set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u',
+ * 'g' and 'o' commands continue to be separate. They could probably be
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(set)
+ BITCMD *set;
+{
+ BITCMD *nset;
+ int setbits, clrbits, Xbits, op;
+
+ _DIAGASSERT(set != NULL);
+
+ for (nset = set;;) {
+ /* Copy over any 'u', 'g' and 'o' commands. */
+ while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+ *set++ = *nset++;
+ if (!op)
+ return;
+ }
+
+ for (setbits = clrbits = Xbits = 0;; nset++) {
+ if ((op = nset->cmd) == '-') {
+ clrbits |= nset->bits;
+ setbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == '+') {
+ setbits |= nset->bits;
+ clrbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == 'X')
+ Xbits |= nset->bits & ~setbits;
+ else
+ break;
+ }
+ if (clrbits) {
+ set->cmd = '-';
+ set->cmd2 = 0;
+ set->bits = clrbits;
+ set++;
+ }
+ if (setbits) {
+ set->cmd = '+';
+ set->cmd2 = 0;
+ set->bits = setbits;
+ set++;
+ }
+ if (Xbits) {
+ set->cmd = 'X';
+ set->cmd2 = 0;
+ set->bits = Xbits;
+ set++;
+ }
+ }
+}
diff --git a/src/kmk/kmkbuiltin/sleep.c b/src/kmk/kmkbuiltin/sleep.c
new file mode 100644
index 0000000..044ed9c
--- /dev/null
+++ b/src/kmk/kmkbuiltin/sleep.c
@@ -0,0 +1,179 @@
+/* $Id: sleep.c 3192 2018-03-26 20:25:56Z bird $ */
+/** @file
+ * kmk_sleep - suspend execution for an interval of time.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#if defined(_MSC_VER)
+# include <Windows.h>
+#else
+# include <unistd.h>
+# include <time.h>
+#endif
+
+#include "err.h"
+#include "../kmkbuiltin.h"
+
+
+static int kmk_builtin_sleep_usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr,
+ "usage: %s <seconds>[s]\n"
+ " or: %s <milliseconds>ms\n"
+ " or: %s <minutes>m\n"
+ " or: %s <hours>h\n"
+ " or: %s <days>d\n"
+ " or: %s --help\n"
+ " or: %s --version\n"
+ "\n"
+ "Only integer values are accepted.\n"
+ ,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName,
+ pCtx->pszProgName, pCtx->pszProgName, pCtx->pszProgName);
+ return 1;
+}
+
+
+int kmk_builtin_sleep(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ long cMsToSleep;
+ long lTmp;
+ unsigned long ulFactor;
+ char *pszInterval;
+ char *pszSuff;
+
+ /*
+ * Parse arguments.
+ */
+ if (argc != 2)
+ return kmk_builtin_sleep_usage(pCtx, 1);
+
+ /* help request */
+ if ( !strcmp(argv[1], "-h")
+ || !strcmp(argv[1], "-?")
+ || !strcmp(argv[1], "-H")
+ || !strcmp(argv[1], "--help"))
+ {
+ kmk_builtin_sleep_usage(pCtx, 0);
+ return 0;
+ }
+
+ /* version request */
+ if ( !strcmp(argv[1], "-V")
+ || !strcmp(argv[1], "--version"))
+ {
+ printf("kmk_sleep - kBuild version %d.%d.%d (r%u)\n"
+ "Copyright (c) 2008-2009 knut st. osmundsen\n",
+ KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH,
+ KBUILD_SVN_REV);
+ return 0;
+ }
+
+ /*
+ * Try convert the argument to a time period.
+ * Allow spaces before, between and after the different parts.
+ */
+ pszInterval = argv[1];
+ while (isspace(*pszInterval))
+ pszInterval++;
+
+ cMsToSleep = strtol(pszInterval, &pszSuff, 0);
+ if (pszSuff == pszInterval)
+ return errx(pCtx, 1, "malformed interval '%s'!\n", pszInterval);
+
+ while (isspace(*pszSuff))
+ pszSuff++;
+
+ if (!*pszSuff)
+ ulFactor = 1000; /* s */
+ else
+ {
+ /* find the suffix length and check that it's only white space following it. */
+ int cchSuff;
+ int i = 1;
+ while (pszSuff[i] && !isspace(pszSuff[i]))
+ i++;
+ cchSuff = i;
+ while (pszSuff[i])
+ {
+ if (!isspace(pszSuff[i]))
+ return errx(pCtx, 1, "malformed interval '%s'!\n", pszInterval);
+ i++;
+ }
+
+ if (cchSuff == 2 && !strncmp (pszSuff, "ms", 2))
+ ulFactor = 1;
+ else if (cchSuff == 1 && *pszSuff == 's')
+ ulFactor = 1000;
+ else if (cchSuff == 1 && *pszSuff == 'm')
+ ulFactor = 60*1000;
+ else if (cchSuff == 1 && *pszSuff == 'h')
+ ulFactor = 60*60*1000;
+ else if (cchSuff == 1 && *pszSuff == 'd')
+ ulFactor = 24*60*60*1000;
+ else
+ return errx(pCtx, 1, "unknown suffix '%.*s'!\n", cchSuff, pszSuff);
+ }
+
+ lTmp = cMsToSleep;
+ cMsToSleep *= ulFactor;
+ if ((cMsToSleep / ulFactor) != (unsigned long)lTmp)
+ return errx(pCtx, 1, "time interval overflow!\n");
+
+ /*
+ * Do the actual sleeping.
+ */
+ if (cMsToSleep > 0)
+ {
+#if defined(_MSC_VER)
+ Sleep(cMsToSleep);
+#else
+ if (cMsToSleep)
+ {
+ struct timespec TimeSpec;
+ TimeSpec.tv_nsec = (cMsToSleep % 1000) * 1000000;
+ TimeSpec.tv_sec = cMsToSleep / 1000;
+ nanosleep(&TimeSpec, NULL);
+ }
+#endif
+ }
+
+ return 0;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_sleep", NULL };
+ return kmk_builtin_sleep(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/kmkbuiltin/solfakes.c b/src/kmk/kmkbuiltin/solfakes.c
new file mode 100644
index 0000000..6996ab4
--- /dev/null
+++ b/src/kmk/kmkbuiltin/solfakes.c
@@ -0,0 +1,99 @@
+/* $Id: solfakes.c 2901 2016-09-09 15:10:24Z bird $ */
+/** @file
+ * Fake Unix stuff for Solaris.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "config.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include "solfakes.h"
+
+
+int asprintf(char **strp, const char *fmt, ...)
+{
+ int rc;
+ va_list va;
+ va_start(va, fmt);
+ rc = vasprintf(strp, fmt, va);
+ va_end(va);
+ return rc;
+}
+
+
+int vasprintf(char **strp, const char *fmt, va_list va)
+{
+ int rc;
+ char *psz;
+ size_t cb = 1024;
+
+ *strp = NULL;
+ for (;;)
+ {
+ va_list va2;
+
+ psz = malloc(cb);
+ if (!psz)
+ return -1;
+
+#ifdef va_copy
+ va_copy(va2, va);
+ rc = vsnprintf(psz, cb, fmt, va2);
+ va_end(va2);
+#else
+ va2 = va;
+ rc = vsnprintf(psz, cb, fmt, va2);
+#endif
+ if (rc < 0 || (size_t)rc < cb)
+ break;
+ cb *= 2;
+ free(psz);
+ }
+
+ *strp = psz;
+ return rc;
+}
+
+
+
+int sol_lchmod(const char *pszPath, mode_t mode)
+{
+ /*
+ * Weed out symbolic links.
+ */
+ struct stat s;
+ if ( !lstat(pszPath, &s)
+ && S_ISLNK(s.st_mode))
+ {
+ errno = -ENOSYS;
+ return -1;
+ }
+
+ return chmod(pszPath, mode);
+}
+
diff --git a/src/kmk/kmkbuiltin/solfakes.h b/src/kmk/kmkbuiltin/solfakes.h
new file mode 100644
index 0000000..30519cc
--- /dev/null
+++ b/src/kmk/kmkbuiltin/solfakes.h
@@ -0,0 +1,51 @@
+/* $Id: solfakes.h 3213 2018-03-30 21:03:28Z bird $ */
+/** @file
+ * Unix fakes for Solaris.
+ */
+
+/*
+ * Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___solfakes_h
+#define ___solfakes_h
+#ifdef __sun__
+
+#include <stdarg.h>
+#include <sys/types.h>
+#ifndef FAKES_NO_GETOPT_H
+# include "getopt.h"
+#endif
+
+#define _PATH_DEVNULL "/dev/null"
+#define ALLPERMS 0000777
+#define lutimes(path, tvs) utimes(path, tvs)
+#define lchmod sol_lchmod
+#define MAX(a,b) ((a) >= (b) ? (a) : (b))
+#ifndef USHRT_MAX
+# define USHRT_MAX 65535
+#endif
+
+int vasprintf(char **strp, const char *fmt, va_list va);
+int asprintf(char **strp, const char *fmt, ...);
+int sol_lchmod(const char *pszPath, mode_t mode);
+
+#endif /* __sun__ */
+#endif /* !___solfakes_h */
+
diff --git a/src/kmk/kmkbuiltin/strlcpy.c b/src/kmk/kmkbuiltin/strlcpy.c
new file mode 100644
index 0000000..e1be568
--- /dev/null
+++ b/src/kmk/kmkbuiltin/strlcpy.c
@@ -0,0 +1,78 @@
+/* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */
+
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $");
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: src/lib/libc/string/strlcpy.c,v 1.7 2003/05/01 19:03:14 nectar Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <sys/types.h>
+#include <string.h>
+
+/* This trick is for bootstrap.gmk. */
+#if defined(__DARWIN_C_LEVEL) && defined(__DARWIN_C_FULL)
+# define SKIP_STRLCPY
+#endif
+#ifndef SKIP_STRLCPY
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
+
+#endif /* !SKIP_STRLCPY */
diff --git a/src/kmk/kmkbuiltin/strmode.c b/src/kmk/kmkbuiltin/strmode.c
new file mode 100644
index 0000000..c2a99de
--- /dev/null
+++ b/src/kmk/kmkbuiltin/strmode.c
@@ -0,0 +1,197 @@
+/* $NetBSD: strmode.c,v 1.16 2004/06/20 22:20:15 jmc Exp $ */
+
+/*-
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*#include <sys/cdefs.h>*/
+#if defined(LIBC_SCCS) && !defined(lint)
+#if 0
+static char sccsid[] = "@(#)strmode.c 8.3 (Berkeley) 8/15/94";
+#else
+__RCSID("$NetBSD: strmode.c,v 1.16 2004/06/20 22:20:15 jmc Exp $");
+#endif
+#endif /* LIBC_SCCS and not lint */
+
+#include "config.h"
+/*#include "namespace.h"*/
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <assert.h>
+#ifndef _MSC_VER
+#include <unistd.h>
+#else
+#include "mscfakes.h"
+#endif
+
+#ifndef _DIAGASSERT
+#define _DIAGASSERT assert
+#endif
+
+void
+bsd_strmode(mode, p)
+ mode_t mode;
+ char *p;
+{
+
+ _DIAGASSERT(p != NULL);
+
+ /* print type */
+ switch (mode & S_IFMT) {
+ case S_IFDIR: /* directory */
+ *p++ = 'd';
+ break;
+ case S_IFCHR: /* character special */
+ *p++ = 'c';
+ break;
+#ifdef S_IFBLK
+ case S_IFBLK: /* block special */
+ *p++ = 'b';
+ break;
+#endif
+ case S_IFREG: /* regular */
+#ifdef S_ARCH2
+ if ((mode & S_ARCH2) != 0) {
+ *p++ = 'A';
+ } else if ((mode & S_ARCH1) != 0) {
+ *p++ = 'a';
+ } else {
+#endif
+ *p++ = '-';
+#ifdef S_ARCH2
+ }
+#endif
+ break;
+#ifdef S_IFLNK
+ case S_IFLNK: /* symbolic link */
+ *p++ = 'l';
+ break;
+#endif
+#ifdef S_IFSOCK
+ case S_IFSOCK: /* socket */
+ *p++ = 's';
+ break;
+#endif
+#ifdef S_IFIFO
+ case S_IFIFO: /* fifo */
+ *p++ = 'p';
+ break;
+#endif
+#ifdef S_IFWHT
+ case S_IFWHT: /* whiteout */
+ *p++ = 'w';
+ break;
+#endif
+#ifdef S_IFDOOR
+ case S_IFDOOR: /* door */
+ *p++ = 'D';
+ break;
+#endif
+ default: /* unknown */
+ *p++ = '?';
+ break;
+ }
+ /* usr */
+ if (mode & S_IRUSR)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWUSR)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+ switch (mode & (S_IXUSR | S_ISUID)) {
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXUSR:
+ *p++ = 'x';
+ break;
+ case S_ISUID:
+ *p++ = 'S';
+ break;
+ case S_IXUSR | S_ISUID:
+ *p++ = 's';
+ break;
+ }
+ /* group */
+ if (mode & S_IRGRP)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWGRP)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+ switch (mode & (S_IXGRP | S_ISGID)) {
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXGRP:
+ *p++ = 'x';
+ break;
+ case S_ISGID:
+ *p++ = 'S';
+ break;
+ case S_IXGRP | S_ISGID:
+ *p++ = 's';
+ break;
+ }
+ /* other */
+ if (mode & S_IROTH)
+ *p++ = 'r';
+ else
+ *p++ = '-';
+ if (mode & S_IWOTH)
+ *p++ = 'w';
+ else
+ *p++ = '-';
+#ifdef S_ISVTX
+ switch (mode & (S_IXOTH | S_ISVTX)) {
+#else
+ switch (mode & (S_IXOTH)) {
+#endif
+ case 0:
+ *p++ = '-';
+ break;
+ case S_IXOTH:
+ *p++ = 'x';
+ break;
+#ifdef S_ISVTX
+ case S_ISVTX:
+ *p++ = 'T';
+ break;
+ case S_IXOTH | S_ISVTX:
+ *p++ = 't';
+ break;
+#endif
+ }
+ *p++ = ' '; /* will be a '+' if ACL's implemented */
+ *p = '\0';
+}
diff --git a/src/kmk/kmkbuiltin/test.c b/src/kmk/kmkbuiltin/test.c
new file mode 100644
index 0000000..259f223
--- /dev/null
+++ b/src/kmk/kmkbuiltin/test.c
@@ -0,0 +1,869 @@
+/* $NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $ */
+
+/*
+ * test(1); version 7-like -- author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+/*#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.33 2007/06/24 18:54:58 christos Exp $");
+#endif*/
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "config.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <ctype.h>
+#include "err.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _MSC_VER
+# include <direct.h>
+# include <io.h>
+# include <process.h>
+# include "mscfakes.h"
+# include "quote_argv.h"
+#else
+# include <unistd.h>
+#endif
+#include <stdarg.h>
+#include <sys/stat.h>
+
+#include "kmkbuiltin.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef __arraycount
+# define __arraycount(a) ( sizeof(a) / sizeof(a[0]) )
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" primary
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+ unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+ binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef";
+ operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+ EOI,
+ FILRD,
+ FILWR,
+ FILEX,
+ FILEXIST,
+ FILREG,
+ FILDIR,
+ FILCDEV,
+ FILBDEV,
+ FILFIFO,
+ FILSOCK,
+ FILSYM,
+ FILGZ,
+ FILTT,
+ FILSUID,
+ FILSGID,
+ FILSTCK,
+ FILNT,
+ FILOT,
+ FILEQ,
+ FILUID,
+ FILGID,
+ STREZ,
+ STRNZ,
+ STREQ,
+ STRNE,
+ STRLT,
+ STRGT,
+ INTEQ,
+ INTNE,
+ INTGE,
+ INTGT,
+ INTLE,
+ INTLT,
+ UNOT,
+ BAND,
+ BOR,
+ LPAREN,
+ RPAREN,
+ OPERAND
+};
+
+enum token_types {
+ UNOP,
+ BINOP,
+ BUNOP,
+ BBINOP,
+ PAREN
+};
+
+struct t_op {
+ const char *op_text;
+ short op_num, op_type;
+};
+
+/** kmk_test instance data. */
+typedef struct TESTINSTANCE
+{
+ PKMKBUILTINCTX pCtx;
+ char **t_wp;
+ struct t_op const *t_wp_op;
+} TESTINSTANCE;
+typedef TESTINSTANCE *PTESTINSTANCE;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static const struct t_op cop[] = {
+ {"!", UNOT, BUNOP},
+ {"(", LPAREN, PAREN},
+ {")", RPAREN, PAREN},
+ {"<", STRLT, BINOP},
+ {"=", STREQ, BINOP},
+ {">", STRGT, BINOP},
+};
+
+static const struct t_op cop2[] = {
+ {"!=", STRNE, BINOP},
+};
+
+static const struct t_op mop3[] = {
+ {"ef", FILEQ, BINOP},
+ {"eq", INTEQ, BINOP},
+ {"ge", INTGE, BINOP},
+ {"gt", INTGT, BINOP},
+ {"le", INTLE, BINOP},
+ {"lt", INTLT, BINOP},
+ {"ne", INTNE, BINOP},
+ {"nt", FILNT, BINOP},
+ {"ot", FILOT, BINOP},
+};
+
+static const struct t_op mop2[] = {
+ {"G", FILGID, UNOP},
+ {"L", FILSYM, UNOP},
+ {"O", FILUID, UNOP},
+ {"S", FILSOCK,UNOP},
+ {"a", BAND, BBINOP},
+ {"b", FILBDEV,UNOP},
+ {"c", FILCDEV,UNOP},
+ {"d", FILDIR, UNOP},
+ {"e", FILEXIST,UNOP},
+ {"f", FILREG, UNOP},
+ {"g", FILSGID,UNOP},
+ {"h", FILSYM, UNOP}, /* for backwards compat */
+ {"k", FILSTCK,UNOP},
+ {"n", STRNZ, UNOP},
+ {"o", BOR, BBINOP},
+ {"p", FILFIFO,UNOP},
+ {"r", FILRD, UNOP},
+ {"s", FILGZ, UNOP},
+ {"t", FILTT, UNOP},
+ {"u", FILSUID,UNOP},
+ {"w", FILWR, UNOP},
+ {"x", FILEX, UNOP},
+ {"z", STREZ, UNOP},
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int syntax(PTESTINSTANCE, const char *, const char *);
+static int oexpr(PTESTINSTANCE, enum token);
+static int aexpr(PTESTINSTANCE, enum token);
+static int nexpr(PTESTINSTANCE, enum token);
+static int primary(PTESTINSTANCE, enum token);
+static int binop(PTESTINSTANCE);
+static int test_access(struct stat *, mode_t);
+static int filstat(char *, enum token);
+static enum token t_lex(PTESTINSTANCE, char *);
+static int isoperand(PTESTINSTANCE);
+static int getn(PTESTINSTANCE, const char *);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+static int usage(PKMKBUILTINCTX, int);
+
+#if !defined(KMK_BUILTIN_STANDALONE) || defined(ELECTRIC_HEAP)
+extern void *xmalloc(unsigned int);
+#else
+extern void *xmalloc(unsigned int sz)
+{
+ void *p = malloc(sz);
+ if (!p) {
+ fprintf(stderr, "kmk_test: malloc(%u) failed\n", sz);
+ exit(1);
+ }
+ return p;
+}
+#endif
+
+
+
+int kmk_builtin_test(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx, char ***ppapszArgvSpawn)
+{
+ TESTINSTANCE This;
+ int res;
+ char **argv_spawn;
+ int i;
+
+ This.pCtx = pCtx;
+ This.t_wp = NULL;
+ This.t_wp_op = NULL;
+
+ /* look for the '--', '--help' and '--version'. */
+ argv_spawn = NULL;
+ for (i = 1; i < argc; i++) {
+ if ( argv[i][0] == '-'
+ && argv[i][1] == '-') {
+ if (argv[i][2] == '\0') {
+ argc = i;
+ argv[i] = NULL;
+
+ /* skip blank arguments (happens inside kmk) */
+ while (argv[++i]) {
+ const char *psz = argv[i];
+ while (isspace(*psz))
+ psz++;
+ if (*psz)
+ break;
+ }
+ argv_spawn = &argv[i];
+ break;
+ }
+ if (!strcmp(argv[i], "--help"))
+ return usage(pCtx, 0);
+ if (!strcmp(argv[i], "--version"))
+ return kbuild_version(argv[0]);
+ }
+ }
+
+ /* are we '['? then check for ']'. */
+ if (strcmp(argv[0], "[") == 0) { /** @todo should skip the path in g_progname */
+ if (strcmp(argv[--argc], "]"))
+ return errx(pCtx, 1, "missing ]");
+ argv[argc] = NULL;
+ }
+
+ /* evaluate the expression */
+ if (argc < 2)
+ res = 1;
+ else {
+ This.t_wp = &argv[1];
+ res = oexpr(&This, t_lex(&This, *This.t_wp));
+ if (res != -42 && *This.t_wp != NULL && *++This.t_wp != NULL)
+ res = syntax(&This, *This.t_wp, "unexpected operator");
+ if (res == -42)
+ return 1; /* don't mix syntax errors with the argv_spawn ignore */
+ res = !res;
+ }
+
+ /* anything to execute on success? */
+ if (argv_spawn) {
+ if (res != 0 || !argv_spawn[0])
+ res = 0; /* ignored */
+ else {
+#ifdef KMK_BUILTIN_STANDALONE
+ /* try exec the specified process */
+# if defined(_MSC_VER)
+ int argc_spawn = 0;
+ while (argv_spawn[argc_spawn])
+ argc_spawn++;
+ if (quote_argv(argc, argv_spawn, 0 /*fWatcomBrainDamage*/, 0/*fFreeOrLeak*/) != -1)
+ {
+ res = _spawnvp(_P_WAIT, argv_spawn[0], argv_spawn);
+ if (res == -1)
+ res = err(pCtx, 1, "_spawnvp(_P_WAIT,%s,..)", argv_spawn[0]);
+ }
+ else
+ res = err(pCtx, 1, "quote_argv: out of memory");
+# else
+ execvp(argv_spawn[0], argv_spawn);
+ res = err(pCtx, 1, "execvp(%s,..)", argv_spawn[0]);
+# endif
+#else /* in kmk */
+ /* let job.c spawn the process, make a job.c style argv_spawn copy. */
+ char *cur, **argv_new;
+ size_t sz = 0;
+ int argc_new = 0;
+ while (argv_spawn[argc_new]) {
+ size_t len = strlen(argv_spawn[argc_new]) + 1;
+ sz += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ argc_new++;
+ }
+
+ argv_new = xmalloc((argc_new + 1) * sizeof(char *));
+ cur = xmalloc(sz);
+ for (i = 0; i < argc_new; i++) {
+ size_t len = strlen(argv_spawn[i]) + 1;
+ argv_new[i] = memcpy(cur, argv_spawn[i], len);
+ cur += (len + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
+ }
+ argv_new[i] = NULL;
+
+ *ppapszArgvSpawn = argv_new;
+ res = 0;
+#endif /* in kmk */
+ }
+ }
+
+ return res;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_test", NULL };
+ return kmk_builtin_test(argc, argv, envp, &Ctx, NULL);
+}
+#endif
+
+static int
+syntax(PTESTINSTANCE pThis, const char *op, const char *msg)
+{
+
+ if (op && *op)
+ errx(pThis->pCtx, 1, "%s: %s", op, msg);
+ else
+ errx(pThis->pCtx, 1, "%s", msg);
+ return -42;
+}
+
+static int
+oexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = aexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BOR) {
+ int res2 = oexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 || res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+aexpr(PTESTINSTANCE pThis, enum token n)
+{
+ int res;
+
+ res = nexpr(pThis, n);
+ if (res == -42 || *pThis->t_wp == NULL)
+ return res;
+ if (t_lex(pThis, *++(pThis->t_wp)) == BAND) {
+ int res2 = aexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res2 != -42 ? res2 && res : res2;
+ }
+ pThis->t_wp--;
+ return res;
+}
+
+static int
+nexpr(PTESTINSTANCE pThis, enum token n)
+{
+ if (n == UNOT) {
+ int res = nexpr(pThis, t_lex(pThis, *++(pThis->t_wp)));
+ return res != -42 ? !res : res;
+ }
+ return primary(pThis, n);
+}
+
+static int
+primary(PTESTINSTANCE pThis, enum token n)
+{
+ enum token nn;
+ int res;
+
+ if (n == EOI)
+ return 0; /* missing expression */
+ if (n == LPAREN) {
+ if ((nn = t_lex(pThis, *++(pThis->t_wp))) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(pThis, nn);
+ if (res != -42 && t_lex(pThis, *++(pThis->t_wp)) != RPAREN)
+ return syntax(pThis, NULL, "closing paren expected");
+ return res;
+ }
+ if (pThis->t_wp_op && pThis->t_wp_op->op_type == UNOP) {
+ /* unary expression */
+ if (*++(pThis->t_wp) == NULL)
+ return syntax(pThis, pThis->t_wp_op->op_text, "argument expected");
+ switch (n) {
+ case STREZ:
+ return strlen(*pThis->t_wp) == 0;
+ case STRNZ:
+ return strlen(*pThis->t_wp) != 0;
+ case FILTT:
+ return isatty(getn(pThis, *pThis->t_wp));
+ default:
+ return filstat(*pThis->t_wp, n);
+ }
+ }
+
+ if (t_lex(pThis, pThis->t_wp[1]), pThis->t_wp_op && pThis->t_wp_op->op_type == BINOP) {
+ return binop(pThis);
+ }
+
+ return strlen(*pThis->t_wp) > 0;
+}
+
+static int
+binop(PTESTINSTANCE pThis)
+{
+ const char *opnd1, *opnd2;
+ struct t_op const *op;
+
+ opnd1 = *pThis->t_wp;
+ (void) t_lex(pThis, *++(pThis->t_wp));
+ op = pThis->t_wp_op;
+
+ if ((opnd2 = *++(pThis->t_wp)) == NULL)
+ return syntax(pThis, op->op_text, "argument expected");
+
+ switch (op->op_num) {
+ case STREQ:
+ return strcmp(opnd1, opnd2) == 0;
+ case STRNE:
+ return strcmp(opnd1, opnd2) != 0;
+ case STRLT:
+ return strcmp(opnd1, opnd2) < 0;
+ case STRGT:
+ return strcmp(opnd1, opnd2) > 0;
+ case INTEQ:
+ return getn(pThis, opnd1) == getn(pThis, opnd2);
+ case INTNE:
+ return getn(pThis, opnd1) != getn(pThis, opnd2);
+ case INTGE:
+ return getn(pThis, opnd1) >= getn(pThis, opnd2);
+ case INTGT:
+ return getn(pThis, opnd1) > getn(pThis, opnd2);
+ case INTLE:
+ return getn(pThis, opnd1) <= getn(pThis, opnd2);
+ case INTLT:
+ return getn(pThis, opnd1) < getn(pThis, opnd2);
+ case FILNT:
+ return newerf(opnd1, opnd2);
+ case FILOT:
+ return olderf(opnd1, opnd2);
+ case FILEQ:
+ return equalf(opnd1, opnd2);
+ default:
+ abort();
+ /* NOTREACHED */
+#ifdef _MSC_VER
+ return -42;
+#endif
+ }
+}
+
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ * True shall indicate only that the write flag is on. The file is not
+ * writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ * True shall indicate that permission to read from file will be granted,
+ * as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ * When a file is to be read or written, the file shall be opened with an
+ * access mode corresponding to the operation to be performed. If file
+ * access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ * * If a process has the appropriate privilege:
+ *
+ * * If read, write, or directory search permission is requested,
+ * access shall be granted.
+ *
+ * * If execute permission is requested, access shall be granted if
+ * execute permission is granted to at least one user by the file
+ * permission bits or by an alternate access control mechanism;
+ * otherwise, access shall be denied.
+ *
+ * * Otherwise:
+ *
+ * * The file permission bits of a file contain read, write, and
+ * execute/search permissions for the file owner class, file group
+ * class, and file other class.
+ *
+ * * Access shall be granted if an alternate access control mechanism
+ * is not enabled and the requested access permission bit is set for
+ * the class (file owner class, file group class, or file other class)
+ * to which the process belongs, or if an alternate access control
+ * mechanism is enabled and it allows the requested access; otherwise,
+ * access shall be denied.
+ *
+ * and when I first read this I thought: surely we can't go about using
+ * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ * On historical BSD systems, test -w directory always returned false
+ * because test tried to open the directory for writing, which always
+ * fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus. They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very
+ * much prefer the original wording in 1003.2-1992. It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ *
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ *
+ * This change is a clarification and is the result of interpretation
+ * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ *
+ * That interpretation can be found here:
+ *
+ * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ *
+ * Not terribly helpful, unfortunately. I wonder who that fence sitter was.
+ *
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD). The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'. This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ *
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ *
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ *
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int
+test_access(struct stat *sp, mode_t stmode)
+{
+#ifdef _MSC_VER
+ /* just pretend to be root for now. */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ return !!(sp->st_mode & stmode);
+#else
+ gid_t *groups;
+ register int n;
+ uid_t euid;
+ int maxgroups;
+
+ /*
+ * I suppose we could use access() if not running as root and if we are
+ * running with ((euid == uid) && (egid == gid)), but we've already
+ * done the stat() so we might as well just test the permissions
+ * directly instead of asking the kernel to do it....
+ */
+ euid = geteuid();
+ if (euid == 0) /* any bit is good enough */
+ stmode = (stmode << 6) | (stmode << 3) | stmode;
+ else if (sp->st_uid == euid)
+ stmode <<= 6;
+ else if (sp->st_gid == getegid())
+ stmode <<= 3;
+ else {
+ /* XXX stolen almost verbatim from ksh93.... */
+ /* on some systems you can be in several groups */
+ if ((maxgroups = getgroups(0, NULL)) <= 0)
+ maxgroups = NGROUPS_MAX; /* pre-POSIX system? */
+ groups = xmalloc((maxgroups + 1) * sizeof(gid_t));
+ n = getgroups(maxgroups, groups);
+ while (--n >= 0) {
+ if (groups[n] == sp->st_gid) {
+ stmode <<= 3;
+ break;
+ }
+ }
+ free(groups);
+ }
+
+ return !!(sp->st_mode & stmode);
+#endif
+}
+
+static int
+filstat(char *nm, enum token mode)
+{
+ struct stat s;
+
+ if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
+ return 0;
+
+ switch (mode) {
+ case FILRD:
+ return test_access(&s, S_IROTH);
+ case FILWR:
+ return test_access(&s, S_IWOTH);
+ case FILEX:
+ return test_access(&s, S_IXOTH);
+ case FILEXIST:
+ return 1; /* the successful lstat()/stat() is good enough */
+ case FILREG:
+ return S_ISREG(s.st_mode);
+ case FILDIR:
+ return S_ISDIR(s.st_mode);
+ case FILCDEV:
+#ifdef S_ISCHR
+ return S_ISCHR(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILBDEV:
+#ifdef S_ISBLK
+ return S_ISBLK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILFIFO:
+#ifdef S_ISFIFO
+ return S_ISFIFO(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSOCK:
+#ifdef S_ISSOCK
+ return S_ISSOCK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSYM:
+#ifdef S_ISLNK
+ return S_ISLNK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSUID:
+ return (s.st_mode & S_ISUID) != 0;
+ case FILSGID:
+ return (s.st_mode & S_ISGID) != 0;
+ case FILSTCK:
+#ifdef S_ISVTX
+ return (s.st_mode & S_ISVTX) != 0;
+#else
+ return 0;
+#endif
+ case FILGZ:
+ return s.st_size > (off_t)0;
+ case FILUID:
+ return s.st_uid == geteuid();
+ case FILGID:
+ return s.st_gid == getegid();
+ default:
+ return 1;
+ }
+}
+
+#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text
+
+static int
+compare1(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+
+ return a[0] - b[0];
+}
+
+static int
+compare2(const void *va, const void *vb)
+{
+ const unsigned char *a = va;
+ const unsigned char *b = VTOC(vb);
+ int z = a[0] - b[0];
+
+ return z ? z : (a[1] - b[1]);
+}
+
+static struct t_op const *
+findop(const char *s)
+{
+ if (s[0] == '-') {
+ if (s[1] == '\0')
+ return NULL;
+ if (s[2] == '\0')
+ return bsearch(s + 1, mop2, __arraycount(mop2), sizeof(*mop2), compare1);
+ else if (s[3] != '\0')
+ return NULL;
+ else
+ return bsearch(s + 1, mop3, __arraycount(mop3), sizeof(*mop3), compare2);
+ } else {
+ if (s[1] == '\0')
+ return bsearch(s, cop, __arraycount(cop), sizeof(*cop), compare1);
+ else if (strcmp(s, cop2[0].op_text) == 0)
+ return cop2;
+ else
+ return NULL;
+ }
+}
+
+static enum token
+t_lex(PTESTINSTANCE pThis, char *s)
+{
+ struct t_op const *op;
+
+ if (s == NULL) {
+ pThis->t_wp_op = NULL;
+ return EOI;
+ }
+
+ if ((op = findop(s)) != NULL) {
+ if (!((op->op_type == UNOP && isoperand(pThis)) ||
+ (op->op_num == LPAREN && *(pThis->t_wp+1) == 0))) {
+ pThis->t_wp_op = op;
+ return op->op_num;
+ }
+ }
+ pThis->t_wp_op = NULL;
+ return OPERAND;
+}
+
+static int
+isoperand(PTESTINSTANCE pThis)
+{
+ struct t_op const *op;
+ char *s, *t;
+
+ if ((s = *(pThis->t_wp+1)) == 0)
+ return 1;
+ if ((t = *(pThis->t_wp+2)) == 0)
+ return 0;
+ if ((op = findop(s)) != NULL)
+ return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0');
+ return 0;
+}
+
+/* atoi with error detection */
+static int
+getn(PTESTINSTANCE pThis, const char *s)
+{
+ char *p;
+ long r;
+
+ errno = 0;
+ r = strtol(s, &p, 10);
+
+ if (errno != 0)
+ return errx(pThis->pCtx, -42, "%s: out of range", s);
+
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p)
+ return errx(pThis->pCtx, -42, "%s: bad number", s);
+
+ return (int) r;
+}
+
+static int
+newerf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime > b2.st_mtime);
+}
+
+static int
+olderf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_mtime < b2.st_mtime);
+}
+
+static int
+equalf(const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (stat(f1, &b1) == 0 &&
+ stat(f2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev &&
+ b1.st_ino == b2.st_ino);
+}
+
+static int
+usage(PKMKBUILTINCTX pCtx, int fIsErr)
+{
+ kmk_builtin_ctx_printf(pCtx, fIsErr, "usage: %s expression [-- <prog> [args]]\n", pCtx->pszProgName);
+ return 0; /* only used in --help. */
+}
+
diff --git a/src/kmk/kmkbuiltin/touch.c b/src/kmk/kmkbuiltin/touch.c
new file mode 100644
index 0000000..046b0d3
--- /dev/null
+++ b/src/kmk/kmkbuiltin/touch.c
@@ -0,0 +1,952 @@
+/* $Id: touch.c 3282 2019-01-05 00:57:52Z bird $ */
+/** @file
+ * kmk_touch - Simple touch implementation.
+ */
+
+/*
+ * Copyright (c) 2017 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(_MSC_VER)
+# include <ctype.h>
+# include <io.h>
+# include <sys/timeb.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <k/kDefs.h>
+#include <k/kTypes.h>
+#include "err.h"
+#include "kbuild_version.h"
+#include "kmkbuiltin.h"
+
+#ifdef _MSC_VER
+# include "nt/ntstat.h"
+# undef FILE_TIMESTAMP_HI_RES
+# define FILE_TIMESTAMP_HI_RES 1
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** The program name to use in message. */
+#ifdef KMK
+# define TOUCH_NAME "kmk_builtin_touch"
+#else
+# define TOUCH_NAME "kmk_touch"
+#endif
+/** Converts a two digit decimal field to a number. */
+#define TWO_CHARS_TO_INT(chHigh, chLow) ( ((unsigned)(chHigh) - (unsigned)'0') * 10 + ((unsigned)(chLow) - (unsigned)'0') )
+/** Checks an alleged digit. */
+#define IS_DIGIT(chDigit, uMax) ( ((unsigned)(chDigit) - (unsigned)'0') <= (unsigned)(uMax) )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef enum KMKTOUCHTARGET
+{
+ kTouchAccessAndModify,
+ kTouchAccessOnly,
+ kTouchModifyOnly
+} KMKTOUCHTARGET;
+
+typedef enum KMKTOUCHACTION
+{
+ kTouchActionCurrent,
+ kTouchActionSet,
+ kTouchActionAdjust
+} KMKTOUCHACTION;
+
+
+typedef struct KMKTOUCHOPTS
+{
+ /** Command execution context. */
+ PKMKBUILTINCTX pCtx;
+ /** What timestamps to modify on the files. */
+ KMKTOUCHTARGET enmWhatToTouch;
+ /** How to update the time. */
+ KMKTOUCHACTION enmAction;
+ /** Whether to create files (K_TRUE) or ignore missing (K_FALSE). */
+ KBOOL fCreate;
+ /** Whether to dereference files. */
+ KBOOL fDereference;
+ /** The new access time value. */
+ struct timeval NewATime;
+ /** The new modified time value. */
+ struct timeval NewMTime;
+
+ /** Number of files. */
+ int cFiles;
+ /** The specified files. */
+ char **papszFiles;
+} KMKTOUCHOPTS;
+typedef KMKTOUCHOPTS *PKMKTOUCHOPTS;
+
+
+static int touch_usage(void)
+{
+ fputs("Usage: " TOUCH_NAME " [options] [MMDDhhmm[YY]] <file1> [.. [fileN]]\n"
+ "\n"
+ "Options:\n"
+ " -A [-][[hh]mm]SS, --adjust=[-][[hh]mm]SS\n"
+ " Adjust timestamps by given delta.\n"
+ " -a, --time=atime, --time=access\n"
+ " Only change the accessed timestamp.\n"
+ " -c, --no-create\n"
+ " Ignore missing files and don't create them. (default create empty file)\n"
+ " -d YYYY-MM-DDThh:mm:SS[.frac][tz], --date=YYYY-MM-DDThh:mm:SS[.frac][tz]\n"
+ " Set the timestamps to the given one.\n"
+ " -f\n"
+ " Ignored for compatbility reasons.\n"
+ " -h, --no-dereference\n"
+ " Don't follow links, touch links. (Not applicable to -r.)\n"
+ " -m, --time=mtime, --time=modify\n"
+ " Only changed the modified timestamp.\n"
+ " -r <file>, --reference=<file>\n"
+ " Take the timestamps from <file>.\n"
+ " -t [[CC]YY]MMDDhhmm[.SS]\n"
+ " Set the timestamps to the given one.\n"
+ "\n"
+ "Note. For compatibility reasons the first file can be taken to be a 8 or 10\n"
+ " character long timestamp if it matches the given pattern and none of\n"
+ " the -A, -d, --date, -r, --reference, or -t options are given. So, use\n"
+ " absolute or relative paths when specifying more than one file.\n"
+ , stdout);
+ return 0;
+}
+
+
+#if K_OS == K_OS_SOLARIS
+/**
+ * Solaris doesn't have lutimes because System V doesn't believe in stuff like file modes on symbolic links.
+ */
+static int lutimes(const char *pszFile, struct timeval aTimes[2])
+{
+ struct stat Stat;
+ if (stat(pszFile, &Stat) != -1)
+ {
+ if (!S_ISLNK(Stat.st_mode))
+ return utimes(pszFile, aTimes);
+ return 0;
+ }
+ return -1;
+}
+#endif
+
+
+/**
+ * Parses adjustment value: [-][[hh]mm]SS
+ */
+static int touch_parse_adjust(PKMKBUILTINCTX pCtx, const char *pszValue, int *piAdjustValue)
+{
+ const char * const pszInValue = pszValue;
+ size_t cchValue = strlen(pszValue);
+ KBOOL fNegative = K_FALSE;
+
+ /* Deal with negativity */
+ if (pszValue[0] == '-')
+ {
+ fNegative = K_TRUE;
+ pszValue++;
+ cchValue--;
+ }
+
+ /* Validate and convert. */
+ *piAdjustValue = 0;
+ switch (cchValue)
+ {
+ case 6:
+ if ( !IS_DIGIT(pszValue[0], 9)
+ || !IS_DIGIT(pszValue[0], 9))
+ return errx(pCtx, 2, "Malformed hour part of -A value: %s", pszInValue);
+ *piAdjustValue = TWO_CHARS_TO_INT(pszValue[0], pszValue[1]) * 60 * 60;
+ /* fall thru */
+ case 4:
+ if ( !IS_DIGIT(pszValue[cchValue - 4], 9) /* don't bother limit to 60 minutes */
+ || !IS_DIGIT(pszValue[cchValue - 3], 9))
+ return errx(pCtx, 2, "Malformed minute part of -A value: %s", pszInValue);
+ *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 4], pszValue[cchValue - 3]) * 60;
+ /* fall thru */
+ case 2:
+ if ( !IS_DIGIT(pszValue[cchValue - 2], 9) /* don't bother limit to 60 seconds */
+ || !IS_DIGIT(pszValue[cchValue - 1], 9))
+ return errx(pCtx, 2, "Malformed second part of -A value: %s", pszInValue);
+ *piAdjustValue += TWO_CHARS_TO_INT(pszValue[cchValue - 2], pszValue[cchValue - 1]);
+ break;
+
+ default:
+ return errx(pCtx, 2, "Invalid -A value (length): %s", pszInValue);
+ }
+
+ /* Apply negativity. */
+ if (fNegative)
+ *piAdjustValue = -*piAdjustValue;
+
+ return 0;
+}
+
+
+/**
+ * Parse -d timestamp: YYYY-MM-DDThh:mm:SS[.frac][tz]
+ */
+static int touch_parse_d_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst)
+{
+ const char * const pszTsIn = pszTs;
+ struct tm ExpTime;
+
+ /*
+ * Validate and parse the timestamp into the tm structure.
+ */
+ memset(&ExpTime, 0, sizeof(ExpTime));
+
+ /* year */
+ if ( !IS_DIGIT(pszTs[0], 9)
+ || !IS_DIGIT(pszTs[1], 9)
+ || !IS_DIGIT(pszTs[2], 9)
+ || !IS_DIGIT(pszTs[3], 9)
+ || pszTs[4] != '-')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to start with 4 digit year followed by a dash",
+ pszTsIn);
+ ExpTime.tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
+ + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
+ - 1900;
+ pszTs += 5;
+
+ /* month */
+ if ( !IS_DIGIT(pszTs[0], 1)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != '-')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit month at position 6 followed by a dash",
+ pszTsIn);
+ ExpTime.tm_mon = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) - 1;
+ pszTs += 3;
+
+ /* day */
+ if ( !IS_DIGIT(pszTs[0], 3)
+ || !IS_DIGIT(pszTs[1], 9)
+ || (pszTs[2] != 'T' && pszTs[2] != 't' && pszTs[2] != ' ') )
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit day of month at position 9 followed by 'T' or space",
+ pszTsIn);
+ ExpTime.tm_mday = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* hour */
+ if ( !IS_DIGIT(pszTs[0], 2)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != ':')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit hour at position 12 followed by colon",
+ pszTsIn);
+ ExpTime.tm_hour = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* minute */
+ if ( !IS_DIGIT(pszTs[0], 5)
+ || !IS_DIGIT(pszTs[1], 9)
+ || pszTs[2] != ':')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit minute at position 15 followed by colon",
+ pszTsIn);
+ ExpTime.tm_min = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 3;
+
+ /* seconds */
+ if ( !IS_DIGIT(pszTs[0], 5)
+ || !IS_DIGIT(pszTs[1], 9))
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to two digit seconds at position 12", pszTsIn);
+ ExpTime.tm_sec = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ pszTs += 2;
+
+ /* fraction */
+ pDst->tv_usec = 0;
+ if (*pszTs == '.' || *pszTs == ',')
+ {
+ int iFactor;
+
+ pszTs++;
+ if (!IS_DIGIT(*pszTs, 9))
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: empty fraction", pszTsIn);
+
+ iFactor = 100000;
+ do
+ {
+ pDst->tv_usec += ((unsigned)*pszTs - (unsigned)'0') * iFactor;
+ iFactor /= 10;
+ pszTs++;
+ } while (IS_DIGIT(*pszTs, 9));
+ }
+
+ /* zulu time indicator */
+ ExpTime.tm_isdst = -1;
+ if (*pszTs == 'Z' || *pszTs == 'z')
+ {
+ ExpTime.tm_isdst = 0;
+ pszTs++;
+ if (*pszTs != '\0')
+ return errx(pCtx, 2,
+ "Malformed timestamp '%s' given to -d: Unexpected character(s) after zulu time indicator at end of timestamp",
+ pszTsIn);
+ }
+ else if (*pszTs != '\0')
+ return errx(pCtx, 2, "Malformed timestamp '%s' given to -d: expected to 'Z' (zulu) or nothing at end of timestamp", pszTsIn);
+
+ /*
+ * Convert to UTC seconds using either timegm or mktime.
+ */
+ ExpTime.tm_yday = -1;
+ ExpTime.tm_wday = -1;
+ if (ExpTime.tm_isdst == 0)
+ {
+#if K_OS == K_OS_SOLARIS || K_OS == K_OS_OS2
+ pDst->tv_sec = mktime(&ExpTime) - timezone; /* best we can do for now */
+#else
+ pDst->tv_sec = timegm(&ExpTime);
+#endif
+ if (pDst->tv_sec == -1)
+ return errx(pCtx, 1, "timegm failed on '%s': %s", pszTs, strerror(errno));
+ }
+ else
+ {
+ pDst->tv_sec = mktime(&ExpTime);
+ if (pDst->tv_sec == -1)
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno));
+ }
+ return 0;
+}
+
+
+/**
+ * Parse -t timestamp: [[CC]YY]MMDDhhmm[.SS]
+ */
+static int touch_parse_ts(PKMKBUILTINCTX pCtx, const char *pszTs, struct timeval *pDst)
+{
+ size_t const cchTs = strlen(pszTs);
+ size_t cchTsNoSec;
+ struct tm ExpTime;
+ struct tm *pExpTime;
+ struct timeval Now;
+ int rc;
+
+ /*
+ * Do some input validations first.
+ */
+ if ((cchTs & 1) && pszTs[cchTs - 3] != '.')
+ return errx(pCtx, 2, "Invalid timestamp given to -t: %s", pszTs);
+ switch (cchTs)
+ {
+ case 8: /* MMDDhhmm */
+ case 8 + 2: /* YYMMDDhhmm */
+ case 8 + 2 + 2: /* CCYYMMDDhhmm */
+ cchTsNoSec = cchTs;
+ break;
+ case 8 + 3: /* MMDDhhmm.SS */
+ case 8 + 3 + 2: /* YYMMDDhhmm.SS */
+ case 8 + 3 + 2 + 2: /* CCYYMMDDhhmm.SS */
+ if (pszTs[cchTs - 3] != '.')
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': missing dot for seconds part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTs - 2], 5)
+ || !IS_DIGIT(pszTs[cchTs - 1], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed seconds part", pszTs);
+ cchTsNoSec = cchTs - 3;
+ break;
+ default:
+ return errx(pCtx, 1, "Invalid timestamp (-t) '%s': wrong length (%d)", pszTs, (int)cchTs);
+ }
+
+ switch (cchTsNoSec)
+ {
+ case 8 + 2 + 2: /* CCYYMMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 12], 9)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 11], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed CC part", pszTs);
+ /* fall thru */
+ case 8 + 2: /* YYMMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 10], 9)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 9], 9))
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed YY part", pszTs);
+ /* fall thru */
+ case 8: /* MMDDhhmm */
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 8], 1)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 7], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed month part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 6], 3)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 5], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed day part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 4], 2)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 3], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed hour part", pszTs);
+ if ( !IS_DIGIT(pszTs[cchTsNoSec - 2], 5)
+ || !IS_DIGIT(pszTs[cchTsNoSec - 1], 9) )
+ return errx(pCtx, 2, "Invalid timestamp (-t) '%s': malformed minute part", pszTs);
+ break;
+ }
+
+ /*
+ * Get the current time and explode it.
+ */
+ rc = gettimeofday(&Now, NULL);
+ if (rc != 0)
+ return errx(pCtx, 1, "gettimeofday failed: %s", strerror(errno));
+
+ pExpTime = localtime_r(&Now.tv_sec, &ExpTime);
+ if (pExpTime == NULL)
+ return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno));
+
+ /*
+ * Do the decoding.
+ */
+ if (cchTs & 1)
+ pExpTime->tm_sec = TWO_CHARS_TO_INT(pszTs[cchTs - 2], pszTs[cchTs - 1]);
+ else
+ pExpTime->tm_sec = 0;
+
+ if (cchTsNoSec == 8 + 2 + 2) /* CCYY */
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]) * 100
+ + TWO_CHARS_TO_INT(pszTs[2], pszTs[3])
+ - 1900;
+ else if (cchTsNoSec == 8 + 2) /* YY */
+ {
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszTs[0], pszTs[1]);
+ if (pExpTime->tm_year < 69)
+ pExpTime->tm_year += 100;
+ }
+
+ pExpTime->tm_mon = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 8], pszTs[cchTsNoSec - 7]) - 1;
+ pExpTime->tm_mday = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 6], pszTs[cchTsNoSec - 5]);
+ pExpTime->tm_hour = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 4], pszTs[cchTsNoSec - 3]);
+ pExpTime->tm_min = TWO_CHARS_TO_INT(pszTs[cchTsNoSec - 2], pszTs[cchTsNoSec - 1]);
+
+ /*
+ * Use mktime to convert to UTC seconds.
+ */
+ pExpTime->tm_isdst = -1;
+ pExpTime->tm_yday = -1;
+ pExpTime->tm_wday = -1;
+ pDst->tv_usec = 0;
+ pDst->tv_sec = mktime(pExpTime);
+ if (pDst->tv_sec != -1)
+ return 0;
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszTs, strerror(errno));
+}
+
+
+/**
+ * Check for old timestamp: MMDDhhmm[YY]
+ */
+static int touch_parse_old_ts(PKMKBUILTINCTX pCtx, const char *pszOldTs, time_t Now, struct timeval *pDst)
+{
+ /*
+ * Check if this is a valid timestamp.
+ */
+ size_t const cchOldTs = strlen(pszOldTs);
+ if ( ( cchOldTs == 8
+ || cchOldTs == 10)
+ && IS_DIGIT(pszOldTs[0], 1)
+ && IS_DIGIT(pszOldTs[1], 9)
+ && IS_DIGIT(pszOldTs[2], 3)
+ && IS_DIGIT(pszOldTs[3], 9)
+ && IS_DIGIT(pszOldTs[4], 2)
+ && IS_DIGIT(pszOldTs[5], 9)
+ && IS_DIGIT(pszOldTs[6], 5)
+ && IS_DIGIT(pszOldTs[7], 9)
+ && ( cchOldTs == 8
+ || ( IS_DIGIT(pszOldTs[8], 9)
+ && IS_DIGIT(pszOldTs[9], 9) )) )
+ {
+ /*
+ * Explode the current time as local.
+ */
+ struct tm ExpTime;
+ struct tm *pExpTime;
+ pExpTime = localtime_r(&Now, &ExpTime);
+ if (pExpTime == NULL)
+ return errx(pCtx, 1, "localtime_r failed: %s", strerror(errno));
+
+ /*
+ * Decode the bits we've got.
+ */
+ pExpTime->tm_mon = TWO_CHARS_TO_INT(pszOldTs[0], pszOldTs[1]) - 1;
+ pExpTime->tm_mday = TWO_CHARS_TO_INT(pszOldTs[2], pszOldTs[3]);
+ pExpTime->tm_hour = TWO_CHARS_TO_INT(pszOldTs[4], pszOldTs[5]);
+ pExpTime->tm_min = TWO_CHARS_TO_INT(pszOldTs[6], pszOldTs[7]);
+ if (cchOldTs == 10)
+ {
+ pExpTime->tm_year = TWO_CHARS_TO_INT(pszOldTs[8], pszOldTs[9]);
+ if (pExpTime->tm_year <= 38) /* up to 2038, 32-bit time_t style logic. */
+ pExpTime->tm_year += 100;
+ }
+
+ /*
+ * Use mktime to convert to UTC seconds.
+ */
+ pExpTime->tm_isdst = -1;
+ pExpTime->tm_yday = -1;
+ pExpTime->tm_wday = -1;
+ pDst->tv_usec = 0;
+ pDst->tv_sec = mktime(pExpTime);
+ if (pDst->tv_sec != -1)
+ return 0;
+ return errx(pCtx, 1, "mktime failed on '%s': %s", pszOldTs, strerror(errno));
+ }
+
+ /* No valid timestamp present. */
+ return -1;
+}
+
+
+/**
+ * Parses the arguments into pThis.
+ *
+ * @returns exit code.
+ * @param pThis Options structure to return the parsed info in.
+ * Caller initalizes this with defaults.
+ * @param cArgs The number of arguments.
+ * @param papszArgs The arguments.
+ * @param pfExit Indicates whether to exit or to start processing
+ * files.
+ */
+static int touch_parse_args(PKMKTOUCHOPTS pThis, int cArgs, char **papszArgs, KBOOL *pfExit)
+{
+ int iAdjustValue = 0;
+ int iArg;
+ int rc;
+
+ *pfExit = K_TRUE;
+ /*
+ * Parse arguments, skipping all files (GNU style).
+ */
+ for (iArg = 1; iArg < cArgs; iArg++)
+ {
+ const char *pszArg = papszArgs[iArg];
+ if (*pszArg == '-')
+ {
+ const char *pszLongValue = NULL;
+ char ch = *++pszArg;
+ pszArg++;
+
+ /*
+ * Deal with long options first, preferably translating them into short ones.
+ */
+ if (ch == '-')
+ {
+ const char *pszLong = pszArg;
+ ch = *pszArg++;
+ if (!ch)
+ {
+ while (++iArg < cArgs)
+ pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg];
+ break; /* '--' */
+ }
+
+ /* Translate long options. */
+ pszArg = "";
+ if (strcmp(pszLong, "adjust") == 0)
+ ch = 'A';
+ else if (strncmp(pszLong, "adjust=", sizeof("adjust=") - 1) == 0)
+ {
+ ch = 'A';
+ pszLongValue = pszArg = pszLong + sizeof("adjust=") - 1;
+ }
+ else if (strcmp(pszLong, "no-create") == 0)
+ ch = 'c';
+ else if (strcmp(pszLong, "date") == 0)
+ ch = 'd';
+ else if (strncmp(pszLong, "date=", sizeof("date=") - 1) == 0)
+ {
+ ch = 'd';
+ pszLongValue = pszArg = pszLong + sizeof("date=") - 1;
+ }
+ else if (strcmp(pszLong, "no-dereference") == 0)
+ ch = 'h';
+ else if (strcmp(pszLong, "reference") == 0)
+ ch = 'r';
+ else if (strncmp(pszLong, "reference=", sizeof("reference=") - 1) == 0)
+ {
+ ch = 'r';
+ pszLongValue = pszArg = pszLong + sizeof("reference=") - 1;
+ }
+ else if (strcmp(pszLong, "time") == 0)
+ ch = 'T';
+ else if (strncmp(pszLong, "time=", sizeof("time=") - 1) == 0)
+ {
+ ch = 'T';
+ pszLongValue = pszArg = pszLong + sizeof("time=") - 1;
+ }
+ else if (strcmp(pszLong, "help") == 0)
+ return touch_usage();
+ else if (strcmp(pszLong, "version") == 0)
+ return kbuild_version(papszArgs[0]);
+ else
+ return errx(pThis->pCtx, 2, "Unknown option: --%s", pszLong);
+ }
+
+ /*
+ * Process short options.
+ */
+ do
+ {
+ /* Some options takes a value. */
+ const char *pszValue;
+ switch (ch)
+ {
+ case 'A':
+ case 'd':
+ case 'r':
+ case 't':
+ case 'T':
+ if (*pszArg || pszLongValue)
+ {
+ pszValue = pszArg;
+ pszArg = "";
+ }
+ else if (iArg + 1 < cArgs)
+ pszValue = papszArgs[++iArg];
+ else
+ return errx(pThis->pCtx, 2, "Option -%c requires a value", ch);
+ break;
+
+ default:
+ pszValue = NULL;
+ break;
+ }
+
+ switch (ch)
+ {
+ /* -A [-][[HH]MM]SS */
+ case 'A':
+ rc = touch_parse_adjust(pThis->pCtx, pszValue, &iAdjustValue);
+ if (rc != 0)
+ return rc;
+ if (pThis->enmAction != kTouchActionSet)
+ {
+ pThis->enmAction = kTouchActionAdjust;
+ pThis->NewATime.tv_sec = iAdjustValue;
+ pThis->NewATime.tv_usec = 0;
+ pThis->NewMTime = pThis->NewATime;
+ }
+ /* else: applied after parsing everything. */
+ break;
+
+ case 'a':
+ pThis->enmWhatToTouch = kTouchAccessOnly;
+ break;
+
+ case 'c':
+ pThis->fCreate = K_FALSE;
+ break;
+
+ case 'd':
+ rc = touch_parse_d_ts(pThis->pCtx, pszValue, &pThis->NewATime);
+ if (rc != 0)
+ return rc;
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewMTime = pThis->NewATime;
+ break;
+
+ case 'f':
+ /* some historical thing, ignored. */
+ break;
+
+ case 'h':
+ pThis->fDereference = K_FALSE;
+ break;
+
+ case 'm':
+ pThis->enmWhatToTouch = kTouchModifyOnly;
+ break;
+
+ case 'r':
+ {
+ struct stat St;
+ if (stat(pszValue, &St) != 0)
+ return errx(pThis->pCtx, 1, "Failed to stat '%s' (-r option): %s", pszValue, strerror(errno));
+
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewATime.tv_sec = St.st_atime;
+ pThis->NewMTime.tv_sec = St.st_mtime;
+#if FILE_TIMESTAMP_HI_RES
+ pThis->NewATime.tv_usec = St.ST_ATIM_NSEC / 1000;
+ pThis->NewMTime.tv_usec = St.ST_MTIM_NSEC / 1000;
+#else
+ pThis->NewATime.tv_usec = 0;
+ pThis->NewMTime.tv_usec = 0;
+#endif
+ break;
+ }
+
+ case 't':
+ rc = touch_parse_ts(pThis->pCtx, pszValue, &pThis->NewATime);
+ if (rc != 0)
+ return rc;
+ pThis->enmAction = kTouchActionSet;
+ pThis->NewMTime = pThis->NewATime;
+ break;
+
+ case 'T':
+ if ( strcmp(pszValue, "atime") == 0
+ || strcmp(pszValue, "access") == 0)
+ pThis->enmWhatToTouch = kTouchAccessOnly;
+ else if ( strcmp(pszValue, "mtime") == 0
+ || strcmp(pszValue, "modify") == 0)
+ pThis->enmWhatToTouch = kTouchModifyOnly;
+ else
+ return errx(pThis->pCtx, 2, "Unknown --time value: %s", pszValue);
+ break;
+
+ case 'V':
+ return kbuild_version(papszArgs[0]);
+
+ default:
+ return errx(pThis->pCtx, 2, "Unknown option: -%c (%c%s)", ch, ch, pszArg);
+ }
+
+ } while ((ch = *pszArg++) != '\0');
+ }
+ else
+ pThis->papszFiles[pThis->cFiles++] = papszArgs[iArg];
+ }
+
+ /*
+ * Allow adjusting specified timestamps too like BSD does.
+ */
+ if ( pThis->enmAction == kTouchActionSet
+ && iAdjustValue != 0)
+ {
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchAccessOnly)
+ pThis->NewATime.tv_sec += iAdjustValue;
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchModifyOnly)
+ pThis->NewMTime.tv_sec += iAdjustValue;
+ }
+
+ /*
+ * Check for old timestamp: MMDDhhmm[YY]
+ */
+ if ( pThis->enmAction == kTouchActionCurrent
+ && pThis->cFiles >= 2)
+ {
+ struct timeval OldTs;
+ rc = touch_parse_old_ts(pThis->pCtx, pThis->papszFiles[0], pThis->NewATime.tv_sec, &OldTs);
+ if (rc == 0)
+ {
+ int iFile;
+
+ pThis->NewATime = OldTs;
+ pThis->NewMTime = OldTs;
+ pThis->enmAction = kTouchActionSet;
+
+ for (iFile = 1; iFile < pThis->cFiles; iFile++)
+ pThis->papszFiles[iFile - 1] = pThis->papszFiles[iFile];
+ pThis->cFiles--;
+ }
+ else if (rc > 0)
+ return rc;
+ }
+
+ /*
+ * Check that we've found at least one file argument.
+ */
+ if (pThis->cFiles > 0)
+ {
+ *pfExit = K_FALSE;
+ return 0;
+ }
+ return errx(pThis->pCtx, 2, "No file specified");
+}
+
+
+/**
+ * Touches one file.
+ *
+ * @returns exit code.
+ * @param pThis The options.
+ * @param pszFile The file to touch.
+ */
+static int touch_process_file(PKMKTOUCHOPTS pThis, const char *pszFile)
+{
+ int fd;
+ int rc;
+ struct stat St;
+ struct timeval aTimes[2];
+
+ /*
+ * Create the file if it doesn't exists. If the --no-create/-c option is
+ * in effect, we silently skip the file if it doesn't already exist.
+ */
+ if (pThis->fDereference)
+ rc = stat(pszFile, &St);
+ else
+ rc = lstat(pszFile, &St);
+ if (rc != 0)
+ {
+ if (errno != ENOENT)
+ return errx(pThis->pCtx, 1, "Failed to stat '%s': %s", pszFile, strerror(errno));
+
+ if (!pThis->fCreate)
+ return 0;
+ fd = open(pszFile, O_WRONLY | O_CREAT | KMK_OPEN_NO_INHERIT, 0666);
+ if (fd == -1)
+ return errx(pThis->pCtx, 1, "Failed to create '%s': %s", pszFile, strerror(errno));
+
+ /* If we're not setting the current time, we may need value stat info
+ on the file, so get it thru the file descriptor before closing it. */
+ if (pThis->enmAction == kTouchActionCurrent)
+ rc = 0;
+ else
+ rc = fstat(fd, &St);
+ if (close(fd) != 0)
+ return errx(pThis->pCtx, 1, "Failed to close '%s' after creation: %s", pszFile, strerror(errno));
+ if (rc != 0)
+ return errx(pThis->pCtx, 1, "Failed to fstat '%s' after creation: %s", pszFile, strerror(errno));
+
+ /* We're done now if we're setting the current time. */
+ if (pThis->enmAction == kTouchActionCurrent)
+ return 0;
+ }
+
+ /*
+ * Create aTimes and call utimes/lutimes.
+ */
+ aTimes[0].tv_sec = St.st_atime;
+ aTimes[1].tv_sec = St.st_mtime;
+#if FILE_TIMESTAMP_HI_RES
+ aTimes[0].tv_usec = St.ST_ATIM_NSEC / 1000;
+ aTimes[1].tv_usec = St.ST_MTIM_NSEC / 1000;
+#else
+ aTimes[0].tv_usec = 0;
+ aTimes[1].tv_usec = 0;
+#endif
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchAccessOnly)
+ {
+ if ( pThis->enmAction == kTouchActionCurrent
+ || pThis->enmAction == kTouchActionSet)
+ aTimes[0] = pThis->NewATime;
+ else
+ aTimes[0].tv_sec += pThis->NewATime.tv_sec;
+ }
+ if ( pThis->enmWhatToTouch == kTouchAccessAndModify
+ || pThis->enmWhatToTouch == kTouchModifyOnly)
+ {
+ if ( pThis->enmAction == kTouchActionCurrent
+ || pThis->enmAction == kTouchActionSet)
+ aTimes[1] = pThis->NewMTime;
+ else
+ aTimes[1].tv_sec += pThis->NewMTime.tv_sec;
+ }
+
+ /*
+ * Try set the times. If we're setting current time, fall back on calling
+ * [l]utimes with a NULL timeval vector since that has slightly different
+ * permissions checks. (Note that we don't do that by default because it
+ * may do more than what we want (st_ctime).)
+ */
+ if (pThis->fDereference)
+ rc = utimes(pszFile, aTimes);
+ else
+ rc = lutimes(pszFile, aTimes);
+ if (rc != 0)
+ {
+ if (pThis->enmAction == kTouchActionCurrent)
+ {
+ if (pThis->fDereference)
+ rc = utimes(pszFile, NULL);
+ else
+ rc = lutimes(pszFile, NULL);
+ }
+ if (rc != 0)
+ rc = errx(pThis->pCtx, 1, "%stimes failed on '%s': %s", pThis->fDereference ? "" : "l", pszFile, strerror(errno));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Actual main function for the touch command.
+ */
+int kmk_builtin_touch(int argc, char **argv, char **envp, PKMKBUILTINCTX pCtx)
+{
+ int rc;
+ KMKTOUCHOPTS This;
+ K_NOREF(envp);
+
+ /*
+ * Initialize options with defaults and parse them.
+ */
+ This.pCtx = pCtx;
+ This.enmWhatToTouch = kTouchAccessAndModify;
+ This.enmAction = kTouchActionCurrent;
+ This.fCreate = K_TRUE;
+ This.fDereference = K_TRUE;
+ This.cFiles = 0;
+ This.papszFiles = (char **)calloc(argc, sizeof(char *));
+ if (This.papszFiles)
+ {
+ rc = gettimeofday(&This.NewATime, NULL);
+ if (rc == 0)
+ {
+ KBOOL fExit;
+ This.NewMTime = This.NewATime;
+
+ rc = touch_parse_args(&This, argc, argv, &fExit);
+ if (rc == 0 && !fExit)
+ {
+ /*
+ * Process the files.
+ */
+ int iFile;
+ for (iFile = 0; iFile < This.cFiles; iFile++)
+ {
+ int rc2 = touch_process_file(&This, This.papszFiles[iFile]);
+ if (rc2 != 0 && rc == 0)
+ rc = rc2;
+ }
+ }
+ }
+ else
+ rc = errx(pCtx, 2, "gettimeofday failed: %s", strerror(errno));
+ free(This.papszFiles);
+ }
+ else
+ rc = errx(pCtx, 2, "calloc failed");
+ return rc;
+}
+
+#ifdef KMK_BUILTIN_STANDALONE
+int main(int argc, char **argv, char **envp)
+{
+ KMKBUILTINCTX Ctx = { "kmk_touch", NULL };
+ return kmk_builtin_touch(argc, argv, envp, &Ctx);
+}
+#endif
+
diff --git a/src/kmk/load.c b/src/kmk/load.c
new file mode 100644
index 0000000..37e7b8e
--- /dev/null
+++ b/src/kmk/load.c
@@ -0,0 +1,267 @@
+/* Loading dynamic objects for GNU Make.
+Copyright (C) 2012-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#if MAKE_LOAD
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <errno.h>
+
+#define SYMBOL_EXTENSION "_gmk_setup"
+
+#include "debug.h"
+#include "filedef.h"
+#include "variable.h"
+
+/* Tru64 V4.0 does not have this flag */
+#ifndef RTLD_GLOBAL
+# define RTLD_GLOBAL 0
+#endif
+
+struct load_list
+ {
+ struct load_list *next;
+ const char *name;
+ void *dlp;
+ };
+
+static struct load_list *loaded_syms = NULL;
+
+static load_func_t
+load_object (const floc *flocp, int noerror, const char *ldname,
+ const char *symname)
+{
+ static void *global_dl = NULL;
+ load_func_t symp;
+
+ if (! global_dl)
+ {
+ global_dl = dlopen (NULL, RTLD_NOW|RTLD_GLOBAL);
+ if (! global_dl)
+ {
+ const char *err = dlerror ();
+ OS (fatal, flocp, _("Failed to open global symbol table: %s"), err);
+ }
+ }
+
+ symp = (load_func_t) dlsym (global_dl, symname);
+ if (! symp)
+ {
+ struct load_list *new;
+ void *dlp = NULL;
+
+ /* If the path has no "/", try the current directory first. */
+ if (! strchr (ldname, '/')
+#ifdef HAVE_DOS_PATHS
+ && ! strchr (ldname, '\\')
+#endif
+ )
+ dlp = dlopen (concat (2, "./", ldname), RTLD_LAZY|RTLD_GLOBAL);
+
+ /* If we haven't opened it yet, try the default search path. */
+ if (! dlp)
+ dlp = dlopen (ldname, RTLD_LAZY|RTLD_GLOBAL);
+
+ /* Still no? Then fail. */
+ if (! dlp)
+ {
+ const char *err = dlerror ();
+ if (noerror)
+ DB (DB_BASIC, ("%s", err));
+ else
+ OS (error, flocp, "%s", err);
+ return NULL;
+ }
+
+ /* Assert that the GPL license symbol is defined. */
+ symp = (load_func_t) dlsym (dlp, "plugin_is_GPL_compatible");
+ if (! symp)
+ OS (fatal, flocp,
+ _("Loaded object %s is not declared to be GPL compatible"),
+ ldname);
+
+ symp = (load_func_t) dlsym (dlp, symname);
+ if (! symp)
+ {
+ const char *err = dlerror ();
+ OSSS (fatal, flocp, _("Failed to load symbol %s from %s: %s"),
+ symname, ldname, err);
+ }
+
+ /* Add this symbol to a trivial lookup table. This is not efficient but
+ it's highly unlikely we'll be loading lots of objects, and we only
+ need it to look them up on unload, if we rebuild them. */
+ new = xmalloc (sizeof (struct load_list));
+ new->name = xstrdup (ldname);
+ new->dlp = dlp;
+ new->next = loaded_syms;
+ loaded_syms = new;
+ }
+
+ return symp;
+}
+
+int
+load_file (const floc *flocp, const char **ldname, int noerror)
+{
+ int nmlen = strlen (*ldname);
+ char *new = alloca (nmlen + CSTRLEN (SYMBOL_EXTENSION) + 1);
+ char *symname = NULL;
+ char *loaded;
+ const char *fp;
+ int r;
+ load_func_t symp;
+
+ /* Break the input into an object file name and a symbol name. If no symbol
+ name was provided, compute one from the object file name. */
+ fp = strchr (*ldname, '(');
+ if (fp)
+ {
+ const char *ep;
+
+ /* There's an open paren, so see if there's a close paren: if so use
+ that as the symbol name. We can't have whitespace: it would have
+ been chopped up before this function is called. */
+ ep = strchr (fp+1, ')');
+ if (ep && ep[1] == '\0')
+ {
+ int l = fp - *ldname;;
+
+ ++fp;
+ if (fp == ep)
+ OS (fatal, flocp, _("Empty symbol name for load: %s"), *ldname);
+
+ /* Make a copy of the ldname part. */
+ memcpy (new, *ldname, l);
+ new[l] = '\0';
+ *ldname = new;
+ nmlen = l;
+
+ /* Make a copy of the symbol name part. */
+ symname = new + l + 1;
+ memcpy (symname, fp, ep - fp);
+ symname[ep - fp] = '\0';
+ }
+ }
+
+ /* Add this name to the string cache so it can be reused later. */
+ *ldname = strcache_add (*ldname);
+
+ /* If this object has been loaded, we're done. */
+ loaded = allocated_variable_expand ("$(.LOADED)");
+ fp = strstr (loaded, *ldname);
+ r = fp && (fp==loaded || fp[-1]==' ') && (fp[nmlen]=='\0' || fp[nmlen]==' ');
+ if (r)
+ goto exit;
+
+ /* If we didn't find a symbol name yet, construct it from the ldname. */
+ if (! symname)
+ {
+ char *p = new;
+
+ fp = strrchr (*ldname, '/');
+#ifdef HAVE_DOS_PATHS
+ if (fp)
+ {
+ const char *fp2 = strchr (fp, '\\');
+
+ if (fp2 > fp)
+ fp = fp2;
+ }
+ else
+ fp = strrchr (*ldname, '\\');
+ /* The (improbable) case of d:foo. */
+ if (fp && *fp && fp[1] == ':')
+ fp++;
+#endif
+ if (!fp)
+ fp = *ldname;
+ else
+ ++fp;
+ while (isalnum (*fp) || *fp == '_')
+ *(p++) = *(fp++);
+ strcpy (p, SYMBOL_EXTENSION);
+ symname = new;
+ }
+
+ DB (DB_VERBOSE, (_("Loading symbol %s from %s\n"), symname, *ldname));
+
+ /* Load it! */
+ symp = load_object (flocp, noerror, *ldname, symname);
+ if (! symp)
+ return 0;
+
+ /* Invoke the symbol. */
+ r = (*symp) (flocp);
+
+ /* If it succeeded, add the load file to the loaded variable. */
+ if (r > 0)
+ {
+ size_t loadlen = strlen (loaded);
+ char *newval = alloca (loadlen + strlen (*ldname) + 2);
+ /* Don't add a space if it's empty. */
+ if (loadlen)
+ {
+ memcpy (newval, loaded, loadlen);
+ newval[loadlen++] = ' ';
+ }
+ strcpy (&newval[loadlen], *ldname);
+ do_variable_definition (flocp, ".LOADED", newval, o_default, f_simple, 0);
+ }
+
+ exit:
+ free (loaded);
+ return r;
+}
+
+void
+unload_file (const char *name)
+{
+ struct load_list *d;
+
+ for (d = loaded_syms; d != NULL; d = d->next)
+ if (streq (d->name, name) && d->dlp)
+ {
+ if (dlclose (d->dlp))
+ perror_with_name ("dlclose", d->name);
+ d->dlp = NULL;
+ break;
+ }
+}
+
+#else
+
+int
+load_file (const floc *flocp, const char **ldname UNUSED, int noerror)
+{
+ if (! noerror)
+ O (fatal, flocp,
+ _("The 'load' operation is not supported on this platform."));
+
+ return 0;
+}
+
+void
+unload_file (const char *name UNUSED)
+{
+ O (fatal, NILF, "INTERNAL: Cannot unload when load is not supported!");
+}
+
+#endif /* MAKE_LOAD */
diff --git a/src/kmk/loadapi.c b/src/kmk/loadapi.c
new file mode 100644
index 0000000..677bc33
--- /dev/null
+++ b/src/kmk/loadapi.c
@@ -0,0 +1,82 @@
+/* API for GNU Make dynamic objects.
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include "filedef.h"
+#include "variable.h"
+#include "dep.h"
+
+/* Allocate a buffer in our context, so we can free it. */
+char *
+gmk_alloc (unsigned int len)
+{
+ return xmalloc (len);
+}
+
+/* Free a buffer returned by gmk_expand(). */
+void
+gmk_free (char *s)
+{
+ free (s);
+}
+
+/* Evaluate a buffer as make syntax.
+ Ideally eval_buffer() will take const char *, but not yet. */
+void
+gmk_eval (const char *buffer, const gmk_floc *gfloc)
+{
+ /* Preserve existing variable buffer context. */
+ char *pbuf;
+ unsigned int plen;
+ char *s;
+ floc fl;
+ floc *flp;
+
+ if (gfloc)
+ {
+ fl.filenm = gfloc->filenm;
+ fl.lineno = gfloc->lineno;
+ fl.offset = 0;
+ flp = &fl;
+ }
+ else
+ flp = NULL;
+
+ install_variable_buffer (&pbuf, &plen);
+
+ s = xstrdup (buffer);
+ eval_buffer (s, flp IF_WITH_VALUE_LENGTH_PARAM (strlen (s) /** @todo suboptimal */));
+ free (s);
+
+ restore_variable_buffer (pbuf, plen);
+}
+
+/* Expand a string and return an allocated buffer.
+ Caller must call gmk_free() with this buffer. */
+char *
+gmk_expand (const char *ref)
+{
+ return allocated_variable_expand (ref);
+}
+
+/* Register a function to be called from makefiles. */
+void
+gmk_add_function (const char *name, gmk_func_ptr func,
+ unsigned int min, unsigned int max, unsigned int flags)
+{
+ define_new_function (reading_file, name, min, max, flags, func);
+}
diff --git a/src/kmk/main.c b/src/kmk/main.c
new file mode 100644
index 0000000..d56af0a
--- /dev/null
+++ b/src/kmk/main.c
@@ -0,0 +1,4482 @@
+/* Argument parsing and main program of GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "os.h"
+#include "filedef.h"
+#include "dep.h"
+#include "variable.h"
+#include "job.h"
+#include "commands.h"
+#include "rule.h"
+#include "debug.h"
+#include "getopt.h"
+#ifdef KMK
+# include "kbuild.h"
+#endif
+#ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+# include "kmkbuiltin.h"
+#endif
+
+#include <assert.h>
+#ifdef _AMIGA
+# include <dos/dos.h>
+# include <proto/dos.h>
+#endif
+#ifdef WINDOWS32
+# include <windows.h>
+# include <io.h>
+# include "pathstuff.h"
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "w32/winchildren.h"
+# endif
+# include "w32err.h"
+#endif
+#ifdef __EMX__
+# include <sys/types.h>
+# include <sys/wait.h>
+#endif
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#ifdef CONFIG_WITH_COMPILER
+# include "kmk_cc_exec.h"
+#endif
+
+#ifdef KMK /* for get_online_cpu_count */
+# if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+# include <sys/sysctl.h>
+# endif
+# ifdef __OS2__
+# define INCL_BASE
+# include <os2.h>
+# endif
+# ifdef __HAIKU__
+# include <OS.h>
+# endif
+#endif /* KMK*/
+
+#if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT)
+# define SET_STACK_SIZE
+#endif
+
+#ifdef SET_STACK_SIZE
+# include <sys/resource.h>
+#endif
+
+#ifdef _AMIGA
+int __stack = 20000; /* Make sure we have 20K of stack space */
+#endif
+#ifdef VMS
+int vms_use_mcr_command = 0;
+int vms_always_use_cmd_file = 0;
+int vms_gnv_shell = 0;
+int vms_legacy_behavior = 0;
+int vms_comma_separator = 0;
+int vms_unix_simulation = 0;
+int vms_report_unix_paths = 0;
+
+/* Evaluates if a VMS environment option is set, only look at first character */
+static int
+get_vms_env_flag (const char *name, int default_value)
+{
+char * value;
+char x;
+
+ value = getenv (name);
+ if (value == NULL)
+ return default_value;
+
+ x = toupper (value[0]);
+ switch (x)
+ {
+ case '1':
+ case 'T':
+ case 'E':
+ return 1;
+ break;
+ case '0':
+ case 'F':
+ case 'D':
+ return 0;
+ }
+}
+#endif
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+void print_variable_stats (void);
+void print_dir_stats (void);
+void print_file_stats (void);
+#endif
+
+#if defined HAVE_WAITPID || defined HAVE_WAIT3
+# define HAVE_WAIT_NOHANG
+#endif
+
+#if !defined(HAVE_UNISTD_H) && !defined(_MSC_VER) /* bird */
+int chdir ();
+#endif
+#ifndef STDC_HEADERS
+# ifndef sun /* Sun has an incorrect decl in a header. */
+void exit (int) __attribute__ ((noreturn));
+# endif
+double atof ();
+#endif
+
+static void clean_jobserver (int status);
+static void print_data_base (void);
+static void print_version (void);
+static void decode_switches (int argc, const char **argv, int env);
+static void decode_env_switches (const char *envar, unsigned int len);
+static struct variable *define_makeflags (int all, int makefile);
+static char *quote_for_env (char *out, const char *in);
+static void initialize_global_hash_tables (void);
+
+
+/* The structure that describes an accepted command switch. */
+
+struct command_switch
+ {
+ int c; /* The switch character. */
+
+ enum /* Type of the value. */
+ {
+ flag, /* Turn int flag on. */
+ flag_off, /* Turn int flag off. */
+ string, /* One string per invocation. */
+ strlist, /* One string per switch. */
+ filename, /* A string containing a file name. */
+ positive_int, /* A positive integer. */
+ floating, /* A floating-point number (double). */
+ ignore /* Ignored. */
+ } type;
+
+ void *value_ptr; /* Pointer to the value-holding variable. */
+
+ unsigned int env:1; /* Can come from MAKEFLAGS. */
+ unsigned int toenv:1; /* Should be put in MAKEFLAGS. */
+ unsigned int no_makefile:1; /* Don't propagate when remaking makefiles. */
+
+ const void *noarg_value; /* Pointer to value used if no arg given. */
+ const void *default_value; /* Pointer to default value. */
+
+ const char *long_name; /* Long option name. */
+ };
+
+/* True if C is a switch value that corresponds to a short option. */
+
+#define short_option(c) ((c) <= CHAR_MAX)
+
+/* The structure used to hold the list of strings given
+ in command switches of a type that takes strlist arguments. */
+
+struct stringlist
+ {
+ const char **list; /* Nil-terminated list of strings. */
+ unsigned int idx; /* Index into above. */
+ unsigned int max; /* Number of pointers allocated. */
+ };
+
+
+/* The recognized command switches. */
+
+/* Nonzero means do extra verification (that may slow things down). */
+
+int verify_flag;
+
+/* Nonzero means do not print commands to be executed (-s). */
+
+int silent_flag;
+
+/* Nonzero means just touch the files
+ that would appear to need remaking (-t) */
+
+int touch_flag;
+
+/* Nonzero means just print what commands would need to be executed,
+ don't actually execute them (-n). */
+
+int just_print_flag;
+
+#ifdef CONFIG_PRETTY_COMMAND_PRINTING
+/* Nonzero means to print commands argument for argument skipping blanks. */
+
+int pretty_command_printing;
+#endif
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+/* Nonzero means to print internal statistics before exiting. */
+
+int print_stats_flag;
+#endif
+
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+/* Minimum number of seconds to report, -1 if disabled. */
+
+int print_time_min = -1;
+static int default_print_time_min = -1;
+static int no_val_print_time_min = 0;
+static big_int make_start_ts = -1;
+int print_time_width = 5;
+#endif
+
+/* Print debugging info (--debug). */
+
+static struct stringlist *db_flags = 0;
+static int debug_flag = 0;
+
+int db_level = 0;
+
+/* Synchronize output (--output-sync). */
+
+char *output_sync_option = 0;
+
+#ifdef WINDOWS32
+/* Suspend make in main for a short time to allow debugger to attach */
+
+int suspend_flag = 0;
+#endif
+
+/* Environment variables override makefile definitions. */
+
+int env_overrides = 0;
+
+/* Nonzero means ignore status codes returned by commands
+ executed to remake files. Just treat them all as successful (-i). */
+
+int ignore_errors_flag = 0;
+
+/* Nonzero means don't remake anything, just print the data base
+ that results from reading the makefile (-p). */
+
+int print_data_base_flag = 0;
+
+/* Nonzero means don't remake anything; just return a nonzero status
+ if the specified targets are not up to date (-q). */
+
+int question_flag = 0;
+
+/* Nonzero means do not use any of the builtin rules (-r) / variables (-R). */
+
+int no_builtin_rules_flag = 0;
+int no_builtin_variables_flag = 0;
+
+/* Nonzero means keep going even if remaking some file fails (-k). */
+
+int keep_going_flag;
+int default_keep_going_flag = 0;
+
+/* Nonzero means check symlink mtimes. */
+
+int check_symlink_flag = 0;
+
+/* Nonzero means print directory before starting and when done (-w). */
+
+int print_directory_flag = 0;
+
+/* Nonzero means ignore print_directory_flag and never print the directory.
+ This is necessary because print_directory_flag is set implicitly. */
+
+int inhibit_print_directory_flag = 0;
+
+/* Nonzero means print version information. */
+
+int print_version_flag = 0;
+
+/* List of makefiles given with -f switches. */
+
+static struct stringlist *makefiles = 0;
+
+/* Size of the stack when we started. */
+
+#ifdef SET_STACK_SIZE
+struct rlimit stack_limit;
+#endif
+
+
+/* Number of job slots for parallelism. */
+
+unsigned int job_slots;
+
+#define INVALID_JOB_SLOTS (-1)
+static unsigned int master_job_slots = 0;
+static int arg_job_slots = INVALID_JOB_SLOTS;
+
+#ifdef KMK
+static int default_job_slots = INVALID_JOB_SLOTS;
+#else
+static const int default_job_slots = INVALID_JOB_SLOTS;
+#endif
+
+/* Value of job_slots that means no limit. */
+
+static const int inf_jobs = 0;
+
+/* Authorization for the jobserver. */
+
+static char *jobserver_auth = NULL;
+
+/* Handle for the mutex used on Windows to synchronize output of our
+ children under -O. */
+
+char *sync_mutex = NULL;
+
+/* Maximum load average at which multiple jobs will be run.
+ Negative values mean unlimited, while zero means limit to
+ zero load (which could be useful to start infinite jobs remotely
+ but one at a time locally). */
+#ifndef NO_FLOAT
+double max_load_average = -1.0;
+double default_load_average = -1.0;
+#else
+int max_load_average = -1;
+int default_load_average = -1;
+#endif
+
+/* List of directories given with -C switches. */
+
+static struct stringlist *directories = 0;
+
+/* List of include directories given with -I switches. */
+
+static struct stringlist *include_directories = 0;
+
+/* List of files given with -o switches. */
+
+static struct stringlist *old_files = 0;
+
+/* List of files given with -W switches. */
+
+static struct stringlist *new_files = 0;
+
+/* List of strings to be eval'd. */
+static struct stringlist *eval_strings = 0;
+
+/* If nonzero, we should just print usage and exit. */
+
+static int print_usage_flag = 0;
+
+/* If nonzero, we should print a warning message
+ for each reference to an undefined variable. */
+
+int warn_undefined_variables_flag;
+
+/* If nonzero, always build all targets, regardless of whether
+ they appear out of date or not. */
+
+static int always_make_set = 0;
+int always_make_flag = 0;
+
+/* If nonzero, we're in the "try to rebuild makefiles" phase. */
+
+int rebuilding_makefiles = 0;
+
+/* Remember the original value of the SHELL variable, from the environment. */
+
+struct variable shell_var;
+
+/* This character introduces a command: it's the first char on the line. */
+
+char cmd_prefix = '\t';
+
+#ifdef KMK
+/* Process priority.
+ 0 = no change;
+ 1 = idle / max nice;
+ 2 = below normal / nice 10;
+ 3 = normal / nice 0;
+ 4 = high / nice -10;
+ 5 = realtime / nice -19; */
+
+int process_priority = 0;
+
+/* Process affinity mask; 0 means any CPU. */
+
+int process_affinity = 0;
+#endif /* KMK */
+
+#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+/* When set, we'll gather expensive statistics like for the heap. */
+
+int make_expensive_statistics = 0;
+#endif
+
+#if defined (WINDOWS32) && defined (CONFIG_NEW_WIN_CHILDREN)
+/* --job-object[=mode]. */
+char *win_job_object_mode = NULL;
+
+/* --job-object-name=name */
+char *win_job_object_name = NULL;
+
+/** --job-object-no-kill. */
+int win_job_object_no_kill = 0;
+#endif
+
+
+/* The usage output. We write it this way to make life easier for the
+ translators, especially those trying to translate to right-to-left
+ languages like Hebrew. */
+
+static const char *const usage[] =
+ {
+ N_("Options:\n"),
+ N_("\
+ -b, -m Ignored for compatibility.\n"),
+ N_("\
+ -B, --always-make Unconditionally make all targets.\n"),
+ N_("\
+ -C DIRECTORY, --directory=DIRECTORY\n\
+ Change to DIRECTORY before doing anything.\n"),
+ N_("\
+ -d Print lots of debugging information.\n"),
+ N_("\
+ --debug[=FLAGS] Print various types of debugging information.\n"),
+ N_("\
+ -e, --environment-overrides\n\
+ Environment variables override makefiles.\n"),
+ N_("\
+ --eval=STRING Evaluate STRING as a makefile statement.\n"),
+ N_("\
+ -f FILE, --file=FILE, --makefile=FILE\n\
+ Read FILE as a makefile.\n"),
+ N_("\
+ -h, --help Print this message and exit.\n"),
+ N_("\
+ -i, --ignore-errors Ignore errors from recipes.\n"),
+ N_("\
+ -I DIRECTORY, --include-dir=DIRECTORY\n\
+ Search DIRECTORY for included makefiles.\n"),
+#ifdef KMK
+ N_("\
+ -j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.\n\
+ The default is the number of active CPUs.\n"),
+#else
+ N_("\
+ -j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.\n"),
+#endif
+ N_("\
+ -k, --keep-going Keep going when some targets can't be made.\n"),
+ N_("\
+ -l [N], --load-average[=N], --max-load[=N]\n\
+ Don't start multiple jobs unless load is below N.\n"),
+ N_("\
+ -L, --check-symlink-times Use the latest mtime between symlinks and target.\n"),
+ N_("\
+ -n, --just-print, --dry-run, --recon\n\
+ Don't actually run any recipe; just print them.\n"),
+ N_("\
+ -o FILE, --old-file=FILE, --assume-old=FILE\n\
+ Consider FILE to be very old and don't remake it.\n"),
+#ifndef KMK
+ N_("\
+ -O[TYPE], --output-sync[=TYPE]\n\
+ Synchronize output of parallel jobs by TYPE.\n"),
+#elif defined(KBUILD_OS_WINDOWS)
+ N_("\
+ -O[TYPE], --output-sync[=TYPE]\n\
+ Synchronize output of parallel jobs by TYPE:\n\
+ none = no synchronization.\n\
+ line = receip line output\n\
+ target = entire receip output (default)\n\
+ recurse = entire recursive invocation\n"),
+#else
+ N_("\
+ -O[TYPE], --output-sync[=TYPE]\n\
+ Synchronize output of parallel jobs by TYPE:\n\
+ none = no synchronization (default).\n\
+ line = receip line output\n\
+ target = entire receip output\n\
+ recurse = entire recursive invocation\n"),
+#endif
+ N_("\
+ -p, --print-data-base Print make's internal database.\n"),
+ N_("\
+ -q, --question Run no recipe; exit status says if up to date.\n"),
+ N_("\
+ -r, --no-builtin-rules Disable the built-in implicit rules.\n"),
+ N_("\
+ -R, --no-builtin-variables Disable the built-in variable settings.\n"),
+ N_("\
+ -s, --silent, --quiet Don't echo recipes.\n"),
+ N_("\
+ -S, --no-keep-going, --stop\n\
+ Turns off -k.\n"),
+ N_("\
+ -t, --touch Touch targets instead of remaking them.\n"),
+ N_("\
+ --trace Print tracing information.\n"),
+ N_("\
+ -v, --version Print the version number of make and exit.\n"),
+ N_("\
+ -w, --print-directory Print the current directory.\n"),
+ N_("\
+ --no-print-directory Turn off -w, even if it was turned on implicitly.\n"),
+ N_("\
+ -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE\n\
+ Consider FILE to be infinitely new.\n"),
+ N_("\
+ --warn-undefined-variables Warn when an undefined variable is referenced.\n"),
+#ifdef KMK
+ N_("\
+ --affinity=mask Sets the CPU affinity on some hosts.\n"),
+ N_("\
+ --priority=1-5 Sets the process priority / nice level:\n\
+ 1 = idle / max nice;\n\
+ 2 = below normal / nice 10;\n\
+ 3 = normal / nice 0;\n\
+ 4 = high / nice -10;\n\
+ 5 = realtime / nice -19;\n"),
+ N_("\
+ --nice Alias for --priority=1\n"),
+#endif /* KMK */
+#ifdef CONFIG_PRETTY_COMMAND_PRINTING
+ N_("\
+ --pretty-command-printing Makes the command echo easier to read.\n"),
+#endif
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+ N_("\
+ --print-stats Print make statistics.\n"),
+#endif
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ N_("\
+ --print-time[=MIN-SEC] Print file build times starting at arg.\n"),
+#endif
+#ifdef CONFIG_WITH_MAKE_STATS
+ N_("\
+ --statistics Gather extra statistics for $(make-stats ).\n"),
+#endif
+#if defined (WINDOWS32) && defined (CONFIG_NEW_WIN_CHILDREN)
+ N_("\
+ --job-object=mode Windows job object mode:\n\
+ login = Per login session (default).\n\
+ root = Root make instance only.\n\
+ each = Each make instances.\n\
+ none = No job objects.\n"),
+ N_("\
+ --job-object-name=name Name of windows job object to open or create.\n\
+ The default name depends on the level.\n"),
+ N_("\
+ --job-object-no-kill Do not kill orphaned child processes when done.\n"),
+#endif
+ NULL
+ };
+
+/* The table of command switches.
+ Order matters here: this is the order MAKEFLAGS will be constructed.
+ So be sure all simple flags (single char, no argument) come first. */
+
+static const struct command_switch switches[] =
+ {
+ { 'b', ignore, 0, 0, 0, 0, 0, 0, 0 },
+ { 'B', flag, &always_make_set, 1, 1, 0, 0, 0, "always-make" },
+ { 'd', flag, &debug_flag, 1, 1, 0, 0, 0, 0 },
+#ifdef WINDOWS32
+ { 'D', flag, &suspend_flag, 1, 1, 0, 0, 0, "suspend-for-debug" },
+#endif
+ { 'e', flag, &env_overrides, 1, 1, 0, 0, 0, "environment-overrides", },
+ { 'h', flag, &print_usage_flag, 0, 0, 0, 0, 0, "help" },
+ { 'i', flag, &ignore_errors_flag, 1, 1, 0, 0, 0, "ignore-errors" },
+ { 'k', flag, &keep_going_flag, 1, 1, 0, 0, &default_keep_going_flag,
+ "keep-going" },
+ { 'L', flag, &check_symlink_flag, 1, 1, 0, 0, 0, "check-symlink-times" },
+ { 'm', ignore, 0, 0, 0, 0, 0, 0, 0 },
+ { 'n', flag, &just_print_flag, 1, 1, 1, 0, 0, "just-print" },
+ { 'p', flag, &print_data_base_flag, 1, 1, 0, 0, 0, "print-data-base" },
+#ifdef CONFIG_PRETTY_COMMAND_PRINTING
+ { CHAR_MAX+50, flag, (char *) &pretty_command_printing, 1, 1, 1, 0, 0,
+ "pretty-command-printing" },
+#endif
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+ { CHAR_MAX+51, flag, (char *) &print_stats_flag, 1, 1, 1, 0, 0,
+ "print-stats" },
+#endif
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ { CHAR_MAX+52, positive_int, (char *) &print_time_min, 1, 1, 0,
+ (char *) &no_val_print_time_min, (char *) &default_print_time_min,
+ "print-time" },
+#endif
+#ifdef KMK
+ { CHAR_MAX+54, positive_int, (char *) &process_priority, 1, 1, 0,
+ (char *) &process_priority, (char *) &process_priority, "priority" },
+ { CHAR_MAX+55, positive_int, (char *) &process_affinity, 1, 1, 0,
+ (char *) &process_affinity, (char *) &process_affinity, "affinity" },
+ { CHAR_MAX+56, flag, (char *) &process_priority, 1, 1, 0, 0, 0, "nice" },
+#endif
+ { 'q', flag, &question_flag, 1, 1, 1, 0, 0, "question" },
+ { 'r', flag, &no_builtin_rules_flag, 1, 1, 0, 0, 0, "no-builtin-rules" },
+ { 'R', flag, &no_builtin_variables_flag, 1, 1, 0, 0, 0,
+ "no-builtin-variables" },
+ { 's', flag, &silent_flag, 1, 1, 0, 0, 0, "silent" },
+ { 'S', flag_off, &keep_going_flag, 1, 1, 0, 0, &default_keep_going_flag,
+ "no-keep-going" },
+#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+ { CHAR_MAX+57, flag, (char *) &make_expensive_statistics, 1, 1, 1, 0, 0,
+ "statistics" },
+#endif
+ { 't', flag, &touch_flag, 1, 1, 1, 0, 0, "touch" },
+ { 'v', flag, &print_version_flag, 1, 1, 0, 0, 0, "version" },
+ { 'w', flag, &print_directory_flag, 1, 1, 0, 0, 0, "print-directory" },
+
+ /* These options take arguments. */
+ { 'C', filename, &directories, 0, 0, 0, 0, 0, "directory" },
+ { 'f', filename, &makefiles, 0, 0, 0, 0, 0, "file" },
+ { 'I', filename, &include_directories, 1, 1, 0, 0, 0,
+ "include-dir" },
+ { 'j', positive_int, &arg_job_slots, 1, 1, 0, &inf_jobs, &default_job_slots,
+ "jobs" },
+#ifndef NO_FLOAT
+ { 'l', floating, &max_load_average, 1, 1, 0, &default_load_average,
+ &default_load_average, "load-average" },
+#else
+ { 'l', positive_int, &max_load_average, 1, 1, 0, &default_load_average,
+ &default_load_average, "load-average" },
+#endif
+ { 'o', filename, &old_files, 0, 0, 0, 0, 0, "old-file" },
+ { 'O', string, &output_sync_option, 1, 1, 0, "target", 0, "output-sync" },
+ { 'W', filename, &new_files, 0, 0, 0, 0, 0, "what-if" },
+
+ /* These are long-style options. */
+ { CHAR_MAX+1, strlist, &db_flags, 1, 1, 0, "basic", 0, "debug" },
+ { CHAR_MAX+2, string, &jobserver_auth, 1, 1, 0, 0, 0, "jobserver-auth" },
+ { CHAR_MAX+3, flag, &trace_flag, 1, 1, 0, 0, 0, "trace" },
+ { CHAR_MAX+4, flag, &inhibit_print_directory_flag, 1, 1, 0, 0, 0,
+ "no-print-directory" },
+ { CHAR_MAX+5, flag, &warn_undefined_variables_flag, 1, 1, 0, 0, 0,
+ "warn-undefined-variables" },
+ { CHAR_MAX+6, strlist, &eval_strings, 1, 0, 0, 0, 0, "eval" },
+ { CHAR_MAX+7, string, &sync_mutex, 1, 1, 0, 0, 0, "sync-mutex" },
+#if defined (WINDOWS32) && defined (CONFIG_NEW_WIN_CHILDREN)
+ { CHAR_MAX+58, string, &win_job_object_mode, 1, 1, 1, 0, 0, "job-object" },
+ { CHAR_MAX+59, string, &win_job_object_name, 1, 1, 1, 0, 0,
+ "job-object-name" },
+ { CHAR_MAX+60, flag, &win_job_object_no_kill, 1, 1, 1, 0, 0,
+ "job-object-no-kill" },
+#endif
+ { 0, 0, 0, 0, 0, 0, 0, 0, 0 }
+ };
+
+/* Secondary long names for options. */
+
+static struct option long_option_aliases[] =
+ {
+ { "quiet", no_argument, 0, 's' },
+ { "stop", no_argument, 0, 'S' },
+ { "new-file", required_argument, 0, 'W' },
+ { "assume-new", required_argument, 0, 'W' },
+ { "assume-old", required_argument, 0, 'o' },
+ { "max-load", optional_argument, 0, 'l' },
+ { "dry-run", no_argument, 0, 'n' },
+ { "recon", no_argument, 0, 'n' },
+ { "makefile", required_argument, 0, 'f' },
+ };
+
+/* List of goal targets. */
+
+#ifndef KMK
+static
+#endif
+struct goaldep *goals, *lastgoal;
+
+/* List of variables which were defined on the command line
+ (or, equivalently, in MAKEFLAGS). */
+
+struct command_variable
+ {
+ struct command_variable *next;
+ struct variable *variable;
+ };
+static struct command_variable *command_variables;
+
+/* The name we were invoked with. */
+
+#ifdef WINDOWS32
+/* On MS-Windows, we chop off the .exe suffix in 'main', so this
+ cannot be 'const'. */
+char *program;
+#else
+const char *program;
+#endif
+
+/* Our current directory before processing any -C options. */
+
+char *directory_before_chdir;
+
+/* Our current directory after processing all -C options. */
+
+char *starting_directory;
+
+/* Value of the MAKELEVEL variable at startup (or 0). */
+
+unsigned int makelevel;
+
+/* Pointer to the value of the .DEFAULT_GOAL special variable.
+ The value will be the name of the goal to remake if the command line
+ does not override it. It can be set by the makefile, or else it's
+ the first target defined in the makefile whose name does not start
+ with '.'. */
+
+struct variable * default_goal_var;
+
+/* Pointer to structure for the file .DEFAULT
+ whose commands are used for any file that has none of its own.
+ This is zero if the makefiles do not define .DEFAULT. */
+
+struct file *default_file;
+
+/* Nonzero if we have seen the magic '.POSIX' target.
+ This turns on pedantic compliance with POSIX.2. */
+
+int posix_pedantic;
+
+/* Nonzero if we have seen the '.SECONDEXPANSION' target.
+ This turns on secondary expansion of prerequisites. */
+
+int second_expansion;
+
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+/* Nonzero if we have seen the '.SECONDTARGETEXPANSION' target.
+ This turns on secondary expansion of targets. */
+
+int second_target_expansion;
+#endif
+
+/* Nonzero if we have seen the '.ONESHELL' target.
+ This causes the entire recipe to be handed to SHELL
+ as a single string, potentially containing newlines. */
+
+int one_shell;
+
+/* One of OUTPUT_SYNC_* if the "--output-sync" option was given. This
+ attempts to synchronize the output of parallel jobs such that the results
+ of each job stay together. */
+
+#ifdef KMK
+int output_sync = OUTPUT_SYNC_TARGET;
+#else
+int output_sync = OUTPUT_SYNC_NONE;
+#endif
+
+/* Nonzero if the "--trace" option was given. */
+
+int trace_flag = 0;
+
+#ifndef CONFIG_WITH_EXTENDED_NOTPARALLEL
+
+/* Nonzero if we have seen the '.NOTPARALLEL' target.
+ This turns off parallel builds for this invocation of make. */
+
+#else /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+/* Negative if we have seen the '.NOTPARALLEL' target with an
+ empty dependency list.
+
+ Zero if no '.NOTPARALLEL' or no file in the dependency list
+ is being executed.
+
+ Positive when a file in the '.NOTPARALLEL' dependency list
+ is in progress, the value is the number of notparallel files
+ in progress (running or queued for running).
+
+ In short, any nonzero value means no more parallel builing. */
+#endif /* CONFIG_WITH_EXTENDED_NOTPARALLEL */
+
+int not_parallel;
+
+/* Nonzero if some rule detected clock skew; we keep track so (a) we only
+ print one warning about it during the run, and (b) we can print a final
+ warning at the end of the run. */
+
+int clock_skew_detected;
+
+/* Map of possible stop characters for searching strings. */
+#ifndef UCHAR_MAX
+# define UCHAR_MAX 255
+#endif
+#ifdef _MSC_VER
+__declspec(align(512)) /* bird: improve cacheline & tlb mojo */
+#endif
+unsigned short stopchar_map[UCHAR_MAX + 1] = {0};
+
+/* If output-sync is enabled we'll collect all the output generated due to
+ options, while reading makefiles, etc. */
+
+struct output make_sync;
+
+#ifdef KMK
+/** Current umask() value. */
+mode_t g_fUMask = 0022;
+#endif
+
+
+/* Mask of signals that are being caught with fatal_error_signal. */
+
+#ifdef POSIX
+sigset_t fatal_signal_set;
+#else
+# ifdef HAVE_SIGSETMASK
+int fatal_signal_mask;
+# endif
+#endif
+
+#if !HAVE_DECL_BSD_SIGNAL && !defined bsd_signal
+# if !defined HAVE_SIGACTION
+# define bsd_signal signal
+# else
+typedef RETSIGTYPE (*bsd_signal_ret_t) (int);
+
+static bsd_signal_ret_t
+bsd_signal (int sig, bsd_signal_ret_t func)
+{
+ struct sigaction act, oact;
+ act.sa_handler = func;
+ act.sa_flags = SA_RESTART;
+ sigemptyset (&act.sa_mask);
+ sigaddset (&act.sa_mask, sig);
+ if (sigaction (sig, &act, &oact) != 0)
+ return SIG_ERR;
+ return oact.sa_handler;
+}
+# endif
+#endif
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+struct alloccache dep_cache;
+struct alloccache goaldep_cache;
+struct alloccache nameseq_cache;
+struct alloccache file_cache;
+struct alloccache commands_cache;
+struct alloccache variable_cache;
+struct alloccache variable_set_cache;
+struct alloccache variable_set_list_cache;
+
+static void
+initialize_global_alloc_caches (void)
+{
+ alloccache_init (&dep_cache, sizeof (struct dep), "dep", NULL, NULL);
+ alloccache_init (&goaldep_cache, sizeof (struct goaldep), "goaldep", NULL, NULL);
+ alloccache_init (&nameseq_cache, sizeof (struct nameseq), "nameseq", NULL, NULL);
+ alloccache_init (&file_cache, sizeof (struct file), "file", NULL, NULL);
+ alloccache_init (&commands_cache, sizeof (struct commands), "commands", NULL, NULL);
+ alloccache_init (&variable_cache, sizeof (struct variable), "variable", NULL, NULL);
+ alloccache_init (&variable_set_cache, sizeof (struct variable_set), "variable_set", NULL, NULL);
+ alloccache_init (&variable_set_list_cache, sizeof (struct variable_set_list), "variable_set_list", NULL, NULL);
+}
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
+static void
+initialize_global_hash_tables (void)
+{
+ init_hash_global_variable_set ();
+ strcache_init ();
+ init_hash_files ();
+ hash_init_directories ();
+ hash_init_function_table ();
+}
+
+/* This character map locate stop chars when parsing GNU makefiles.
+ Each element is true if we should stop parsing on that character. */
+
+static void
+initialize_stopchar_map (void)
+{
+ int i;
+
+ stopchar_map[(int)'\0'] = MAP_NUL;
+ stopchar_map[(int)'#'] = MAP_COMMENT;
+ stopchar_map[(int)';'] = MAP_SEMI;
+ stopchar_map[(int)'='] = MAP_EQUALS;
+ stopchar_map[(int)':'] = MAP_COLON;
+ stopchar_map[(int)'%'] = MAP_PERCENT;
+ stopchar_map[(int)'|'] = MAP_PIPE;
+ stopchar_map[(int)'.'] = MAP_DOT | MAP_USERFUNC;
+ stopchar_map[(int)','] = MAP_COMMA;
+ stopchar_map[(int)'$'] = MAP_VARIABLE;
+
+ stopchar_map[(int)'-'] = MAP_USERFUNC;
+ stopchar_map[(int)'_'] = MAP_USERFUNC;
+
+ stopchar_map[(int)' '] = MAP_BLANK;
+ stopchar_map[(int)'\t'] = MAP_BLANK;
+
+ stopchar_map[(int)'/'] = MAP_DIRSEP;
+#if defined(VMS)
+ stopchar_map[(int)':'] |= MAP_DIRSEP;
+ stopchar_map[(int)']'] |= MAP_DIRSEP;
+ stopchar_map[(int)'>'] |= MAP_DIRSEP;
+#elif defined(HAVE_DOS_PATHS)
+ stopchar_map[(int)'\\'] |= MAP_DIRSEP;
+#endif
+
+ for (i = 1; i <= UCHAR_MAX; ++i)
+ {
+ if (isspace (i) && NONE_SET (stopchar_map[i], MAP_BLANK))
+ /* Don't mark blank characters as newline characters. */
+ stopchar_map[i] |= MAP_NEWLINE;
+ else if (isalnum (i))
+ stopchar_map[i] |= MAP_USERFUNC;
+ }
+}
+
+static const char *
+expand_command_line_file (const char *name)
+{
+ const char *cp;
+ char *expanded = 0;
+
+ if (name[0] == '\0')
+ O (fatal, NILF, _("empty string invalid as file name"));
+
+ if (name[0] == '~')
+ {
+ expanded = tilde_expand (name);
+ if (expanded && expanded[0] != '\0')
+ name = expanded;
+ }
+
+ /* This is also done in parse_file_seq, so this is redundant
+ for names read from makefiles. It is here for names passed
+ on the command line. */
+ while (name[0] == '.' && name[1] == '/')
+ {
+ name += 2;
+ while (name[0] == '/')
+ /* Skip following slashes: ".//foo" is "foo", not "/foo". */
+ ++name;
+ }
+
+ if (name[0] == '\0')
+ {
+ /* Nothing else but one or more "./", maybe plus slashes! */
+ name = "./";
+ }
+
+ cp = strcache_add (name);
+
+ free (expanded);
+
+ return cp;
+}
+
+/* Toggle -d on receipt of SIGUSR1. */
+
+#ifdef SIGUSR1
+static RETSIGTYPE
+debug_signal_handler (int sig UNUSED)
+{
+ db_level = db_level ? DB_NONE : DB_BASIC;
+}
+#endif
+
+static void
+decode_debug_flags (void)
+{
+ const char **pp;
+
+ if (debug_flag)
+ db_level = DB_ALL;
+
+ if (db_flags)
+ for (pp=db_flags->list; *pp; ++pp)
+ {
+ const char *p = *pp;
+
+ while (1)
+ {
+ switch (tolower (p[0]))
+ {
+ case 'a':
+ db_level |= DB_ALL;
+ break;
+ case 'b':
+ db_level |= DB_BASIC;
+ break;
+ case 'i':
+ db_level |= DB_BASIC | DB_IMPLICIT;
+ break;
+ case 'j':
+ db_level |= DB_JOBS;
+ break;
+ case 'm':
+ db_level |= DB_BASIC | DB_MAKEFILES;
+ break;
+ case 'n':
+ db_level = 0;
+ break;
+ case 'v':
+ db_level |= DB_BASIC | DB_VERBOSE;
+ break;
+#ifdef DB_KMK
+ case 'k':
+ db_level |= DB_KMK;
+ break;
+#endif /* DB_KMK */
+ default:
+ OS (fatal, NILF,
+ _("unknown debug level specification '%s'"), p);
+ }
+
+ while (*(++p) != '\0')
+ if (*p == ',' || *p == ' ')
+ {
+ ++p;
+ break;
+ }
+
+ if (*p == '\0')
+ break;
+ }
+ }
+
+ if (db_level)
+ verify_flag = 1;
+
+ if (! db_level)
+ debug_flag = 0;
+}
+
+static void
+decode_output_sync_flags (void)
+{
+#ifdef NO_OUTPUT_SYNC
+ output_sync = OUTPUT_SYNC_NONE;
+#else
+ if (output_sync_option)
+ {
+ if (streq (output_sync_option, "none"))
+ output_sync = OUTPUT_SYNC_NONE;
+ else if (streq (output_sync_option, "line"))
+ output_sync = OUTPUT_SYNC_LINE;
+ else if (streq (output_sync_option, "target"))
+ output_sync = OUTPUT_SYNC_TARGET;
+ else if (streq (output_sync_option, "recurse"))
+ output_sync = OUTPUT_SYNC_RECURSE;
+ else
+ OS (fatal, NILF,
+ _("unknown output-sync type '%s'"), output_sync_option);
+ }
+
+ if (sync_mutex)
+ RECORD_SYNC_MUTEX (sync_mutex);
+#endif
+}
+
+
+#ifdef KMK
+static void
+set_make_priority_and_affinity (void)
+{
+# ifdef WINDOWS32
+ DWORD dwClass, dwPriority;
+
+ if (process_affinity)
+ if (!SetProcessAffinityMask (GetCurrentProcess (), process_affinity))
+ fprintf (stderr, "warning: SetProcessAffinityMask (,%#x) failed with last error %d\n",
+ process_affinity, GetLastError ());
+
+ switch (process_priority)
+ {
+ case 0: return;
+ case 1: dwClass = IDLE_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_IDLE; break;
+ case 2: dwClass = BELOW_NORMAL_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_BELOW_NORMAL; break;
+ case 3: dwClass = NORMAL_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_NORMAL; break;
+ case 4: dwClass = HIGH_PRIORITY_CLASS; dwPriority = 0xffffffff; break;
+ case 5: dwClass = REALTIME_PRIORITY_CLASS; dwPriority = 0xffffffff; break;
+ default: ON (fatal, NILF, _("invalid priority %d\n"), process_priority);
+ }
+ if (!SetPriorityClass (GetCurrentProcess (), dwClass))
+ fprintf (stderr, "warning: SetPriorityClass (,%#x) failed with last error %d\n",
+ dwClass, GetLastError ());
+ if (dwPriority != 0xffffffff
+ && !SetThreadPriority (GetCurrentThread (), dwPriority))
+ fprintf (stderr, "warning: SetThreadPriority (,%#x) failed with last error %d\n",
+ dwPriority, GetLastError ());
+
+#elif defined(__HAIKU__)
+ int32 iNewPriority;
+ status_t error;
+
+ switch (process_priority)
+ {
+ case 0: return;
+ case 1: iNewPriority = B_LOWEST_ACTIVE_PRIORITY; break;
+ case 2: iNewPriority = B_LOW_PRIORITY; break;
+ case 3: iNewPriority = B_NORMAL_PRIORITY; break;
+ case 4: iNewPriority = B_URGENT_DISPLAY_PRIORITY; break;
+ case 5: iNewPriority = B_REAL_TIME_DISPLAY_PRIORITY; break;
+ default: ON (fatal, NILF, _("invalid priority %d\n"), process_priority);
+ }
+ error = set_thread_priority (find_thread (NULL), iNewPriority);
+ if (error != B_OK)
+ fprintf (stderr, "warning: set_thread_priority (,%d) failed: %s\n",
+ iNewPriority, strerror (error));
+
+# else /*#elif HAVE_NICE */
+ int nice_level = 0;
+ switch (process_priority)
+ {
+ case 0: return;
+ case 1: nice_level = 19; break;
+ case 2: nice_level = 10; break;
+ case 3: nice_level = 0; break;
+ case 4: nice_level = -10; break;
+ case 5: nice_level = -19; break;
+ default: ON (fatal, NILF, _("invalid priority %d\n"), process_priority);
+ }
+ errno = 0;
+ if (nice (nice_level) == -1 && errno != 0)
+ fprintf (stderr, "warning: nice (%d) failed: %s\n",
+ nice_level, strerror (errno));
+# endif
+}
+#endif /* KMK */
+
+
+#ifdef WINDOWS32
+
+#ifndef NO_OUTPUT_SYNC
+
+/* This is called from start_job_command when it detects that
+ output_sync option is in effect. The handle to the synchronization
+ mutex is passed, as a string, to sub-makes via the --sync-mutex
+ command-line argument. */
+void
+# ifdef CONFIG_NEW_WIN_CHILDREN
+prepare_mutex_handle_string (const char *mtxname)
+{
+ if (!sync_mutex)
+ {
+ sync_mutex = xstrdup(mtxname);
+ define_makeflags (1, 0);
+ }
+}
+# else
+prepare_mutex_handle_string (sync_handle_t handle)
+{
+ if (!sync_mutex)
+ {
+ /* Prepare the mutex handle string for our children. */
+ /* 2 hex digits per byte + 2 characters for "0x" + null. */
+ sync_mutex = xmalloc ((2 * sizeof (sync_handle_t)) + 2 + 1);
+ sprintf (sync_mutex, "0x%Ix", handle);
+ define_makeflags (1, 0);
+ }
+}
+# endif
+
+#endif /* NO_OUTPUT_SYNC */
+
+# ifndef KMK /* I'd rather have WER collect dumps. */
+/*
+ * HANDLE runtime exceptions by avoiding a requestor on the GUI. Capture
+ * exception and print it to stderr instead.
+ *
+ * If ! DB_VERBOSE, just print a simple message and exit.
+ * If DB_VERBOSE, print a more verbose message.
+ * If compiled for DEBUG, let exception pass through to GUI so that
+ * debuggers can attach.
+ */
+LONG WINAPI
+handle_runtime_exceptions (struct _EXCEPTION_POINTERS *exinfo)
+{
+ PEXCEPTION_RECORD exrec = exinfo->ExceptionRecord;
+ LPSTR cmdline = GetCommandLine ();
+ LPSTR prg = strtok (cmdline, " ");
+ CHAR errmsg[1024];
+#ifdef USE_EVENT_LOG
+ HANDLE hEventSource;
+ LPTSTR lpszStrings[1];
+#endif
+
+ if (! ISDB (DB_VERBOSE))
+ {
+ sprintf (errmsg,
+ _("%s: Interrupt/Exception caught (code = 0x%lx, addr = 0x%p)\n"),
+ prg, exrec->ExceptionCode, exrec->ExceptionAddress);
+ fprintf (stderr, errmsg);
+ exit (255);
+ }
+
+ sprintf (errmsg,
+ _("\nUnhandled exception filter called from program %s\nExceptionCode = %lx\nExceptionFlags = %lx\nExceptionAddress = 0x%p\n"),
+ prg, exrec->ExceptionCode, exrec->ExceptionFlags,
+ exrec->ExceptionAddress);
+
+ if (exrec->ExceptionCode == EXCEPTION_ACCESS_VIOLATION
+ && exrec->NumberParameters >= 2)
+ sprintf (&errmsg[strlen(errmsg)],
+ (exrec->ExceptionInformation[0]
+ ? _("Access violation: write operation at address 0x%p\n")
+ : _("Access violation: read operation at address 0x%p\n")),
+ (PVOID)exrec->ExceptionInformation[1]);
+
+ /* turn this on if we want to put stuff in the event log too */
+#ifdef USE_EVENT_LOG
+ hEventSource = RegisterEventSource (NULL, "GNU Make");
+ lpszStrings[0] = errmsg;
+
+ if (hEventSource != NULL)
+ {
+ ReportEvent (hEventSource, /* handle of event source */
+ EVENTLOG_ERROR_TYPE, /* event type */
+ 0, /* event category */
+ 0, /* event ID */
+ NULL, /* current user's SID */
+ 1, /* strings in lpszStrings */
+ 0, /* no bytes of raw data */
+ lpszStrings, /* array of error strings */
+ NULL); /* no raw data */
+
+ (VOID) DeregisterEventSource (hEventSource);
+ }
+#endif
+
+ /* Write the error to stderr too */
+ fprintf (stderr, errmsg);
+
+#ifdef DEBUG
+ return EXCEPTION_CONTINUE_SEARCH;
+#else
+ exit (255);
+ return (255); /* not reached */
+#endif
+}
+# endif /* !KMK */
+
+/*
+ * On WIN32 systems we don't have the luxury of a /bin directory that
+ * is mapped globally to every drive mounted to the system. Since make could
+ * be invoked from any drive, and we don't want to propagate /bin/sh
+ * to every single drive. Allow ourselves a chance to search for
+ * a value for default shell here (if the default path does not exist).
+ */
+
+int
+find_and_set_default_shell (const char *token)
+{
+ int sh_found = 0;
+ char *atoken = 0;
+ const char *search_token;
+ const char *tokend;
+ PATH_VAR(sh_path);
+ extern const char *default_shell;
+
+ if (!token)
+ search_token = default_shell;
+ else
+ search_token = atoken = xstrdup (token);
+
+ /* If the user explicitly requests the DOS cmd shell, obey that request.
+ However, make sure that's what they really want by requiring the value
+ of SHELL either equal, or have a final path element of, "cmd" or
+ "cmd.exe" case-insensitive. */
+ tokend = search_token + strlen (search_token) - 3;
+ if (((tokend == search_token
+ || (tokend > search_token
+ && (tokend[-1] == '/' || tokend[-1] == '\\')))
+ && !strcasecmp (tokend, "cmd"))
+ || ((tokend - 4 == search_token
+ || (tokend - 4 > search_token
+ && (tokend[-5] == '/' || tokend[-5] == '\\')))
+ && !strcasecmp (tokend - 4, "cmd.exe")))
+ {
+ batch_mode_shell = 1;
+ unixy_shell = 0;
+# if 1 /* bird: sprintf? wtf. */
+ default_shell = unix_slashes (xstrdup (search_token));
+# else
+ sprintf (sh_path, "%s", search_token);
+ default_shell = xstrdup (w32ify (sh_path, 0));
+# endif
+ DB (DB_VERBOSE, (_("find_and_set_shell() setting default_shell = %s\n"),
+ default_shell));
+ sh_found = 1;
+ }
+ else if (!no_default_sh_exe
+ && (token == NULL || !strcmp (search_token, default_shell)))
+ {
+ /* no new information, path already set or known */
+ sh_found = 1;
+ }
+ else if (_access (search_token, 0) == 0)
+ {
+ /* search token path was found */
+# if 1 /* bird: sprintf? wtf. */
+ default_shell = unix_slashes (xstrdup (search_token));
+# else
+ sprintf (sh_path, "%s", search_token);
+ default_shell = xstrdup (w32ify (sh_path, 0));
+# endif
+ DB (DB_VERBOSE, (_("find_and_set_shell() setting default_shell = %s\n"),
+ default_shell));
+ sh_found = 1;
+ }
+ else
+ {
+ char *p;
+ struct variable *v = lookup_variable (STRING_SIZE_TUPLE ("PATH"));
+
+ /* Search Path for shell */
+ if (v && v->value)
+ {
+ char *ep;
+
+ p = v->value;
+ ep = strchr (p, PATH_SEPARATOR_CHAR);
+
+ while (ep && *ep)
+ {
+ *ep = '\0';
+
+# if 1 /* bird: insanity insurance */
+ _snprintf (sh_path, GET_PATH_MAX, "%s/%s", p, search_token);
+# else
+ sprintf (sh_path, "%s/%s", p, search_token);
+# endif
+ if (_access (sh_path, 0) == 0)
+ {
+# if 1 /* bird: we can modify sh_path directly. */
+ default_shell = xstrdup (unix_slashes (sh_path));
+# else
+ default_shell = xstrdup (w32ify (sh_path, 0));
+# endif
+ sh_found = 1;
+ *ep = PATH_SEPARATOR_CHAR;
+
+ /* terminate loop */
+ p += strlen (p);
+ }
+ else
+ {
+ *ep = PATH_SEPARATOR_CHAR;
+ p = ++ep;
+ }
+
+ ep = strchr (p, PATH_SEPARATOR_CHAR);
+ }
+
+ /* be sure to check last element of Path */
+ if (p && *p)
+ {
+# if 1 /* bird: insanity insurance */
+ _snprintf (sh_path, GET_PATH_MAX, "%s/%s", p, search_token);
+# else
+ sprintf (sh_path, "%s/%s", p, search_token);
+# endif
+ if (_access (sh_path, 0) == 0)
+ {
+# if 1 /* bird: we can modify sh_path directly. */
+ default_shell = xstrdup (unix_slashes (sh_path));
+# else
+ default_shell = xstrdup (w32ify (sh_path, 0));
+# endif
+ sh_found = 1;
+ }
+ }
+
+ if (sh_found)
+ DB (DB_VERBOSE,
+ (_("find_and_set_shell() path search set default_shell = %s\n"),
+ default_shell));
+ }
+ }
+
+ /* naive test */
+ if (!unixy_shell && sh_found
+ && (strstr (default_shell, "sh") || strstr (default_shell, "SH")))
+ {
+ unixy_shell = 1;
+ batch_mode_shell = 0;
+ }
+
+#ifdef BATCH_MODE_ONLY_SHELL
+ batch_mode_shell = 1;
+#endif
+
+ free (atoken);
+
+ return (sh_found);
+}
+
+/* bird: */
+#ifdef CONFIG_NEW_WIN32_CTRL_EVENT
+#include <process.h>
+static UINT g_tidMainThread = 0;
+static int volatile g_sigPending = 0; /* lazy bird */
+# ifndef _M_IX86
+static LONG volatile g_lTriggered = 0;
+static CONTEXT g_Ctx;
+# endif
+
+# ifdef _M_IX86
+static __declspec(naked) void dispatch_stub(void)
+{
+ __asm {
+ pushfd
+ pushad
+ cld
+ }
+ fflush(stdout);
+ /*fprintf(stderr, "dbg: raising %s on the main thread (%d)\n", g_sigPending == SIGINT ? "SIGINT" : "SIGBREAK", _getpid());*/
+ raise(g_sigPending);
+ __asm {
+ popad
+ popfd
+ ret
+ }
+}
+# else /* !_M_IX86 */
+static void dispatch_stub(void)
+{
+ fflush(stdout);
+ /*fprintf(stderr, "dbg: raising %s on the main thread (%d)\n", g_sigPending == SIGINT ? "SIGINT" : "SIGBREAK", _getpid());*/
+ raise(g_sigPending);
+
+ SetThreadContext(GetCurrentThread(), &g_Ctx);
+ fprintf(stderr, "fatal error: SetThreadContext failed with last error %d\n", GetLastError());
+ for (;;)
+ exit(131);
+}
+# endif /* !_M_IX86 */
+
+static BOOL WINAPI ctrl_event(DWORD CtrlType)
+{
+ int sig = (CtrlType == CTRL_C_EVENT) ? SIGINT : SIGBREAK;
+ HANDLE hThread;
+ CONTEXT Ctx;
+
+ /*fprintf(stderr, "dbg: ctrl_event sig=%d\n", sig);*/
+#ifndef _M_IX86
+ /* only once. */
+ if (InterlockedExchange(&g_lTriggered, 1))
+ {
+ Sleep(1);
+ return TRUE;
+ }
+#endif
+
+ /* open the main thread and suspend it. */
+ hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, g_tidMainThread);
+ SuspendThread(hThread);
+
+ /* Get the thread context and if we've get a valid Esp, dispatch
+ it on the main thread otherwise raise the signal in the
+ ctrl-event thread (this). */
+ memset(&Ctx, 0, sizeof(Ctx));
+ Ctx.ContextFlags = CONTEXT_FULL;
+ if (GetThreadContext(hThread, &Ctx)
+#ifdef _M_IX86
+ && Ctx.Esp >= 0x1000
+#else
+ && Ctx.Rsp >= 0x1000
+#endif
+ )
+ {
+#ifdef _M_IX86
+ ((uintptr_t *)Ctx.Esp)[-1] = Ctx.Eip;
+ Ctx.Esp -= sizeof(uintptr_t);
+ Ctx.Eip = (uintptr_t)&dispatch_stub;
+#else
+ g_Ctx = Ctx;
+ Ctx.Rsp -= 0x80;
+ Ctx.Rsp &= ~(uintptr_t)0xf;
+ Ctx.Rsp += 8; /* (Stack aligned before call instruction, not after.) */
+ Ctx.Rip = (uintptr_t)&dispatch_stub;
+#endif
+
+ SetThreadContext(hThread, &Ctx);
+ g_sigPending = sig;
+ ResumeThread(hThread);
+ CloseHandle(hThread);
+ }
+ else
+ {
+ fprintf(stderr, "dbg: raising %s on the ctrl-event thread (%d)\n", sig == SIGINT ? "SIGINT" : "SIGBREAK", _getpid());
+ raise(sig);
+ ResumeThread(hThread);
+ CloseHandle(hThread);
+ exit(130);
+ }
+
+ Sleep(1);
+ return TRUE;
+}
+#endif /* CONFIG_NEW_WIN32_CTRL_EVENT */
+
+#endif /* WINDOWS32 */
+
+#ifdef KMK
+/* Determins the number of CPUs that are currently online.
+ This is used to setup the default number of job slots. */
+static int
+get_online_cpu_count(void)
+{
+# ifdef WINDOWS32
+ /* Windows: Count the active CPUs. */
+ int cpus;
+
+ /* Process groups (W7+). */
+ typedef DWORD (WINAPI *PFNGETACTIVEPROCESSORCOUNT)(DWORD);
+ PFNGETACTIVEPROCESSORCOUNT pfnGetActiveProcessorCount;
+ pfnGetActiveProcessorCount = (PFNGETACTIVEPROCESSORCOUNT)GetProcAddress(GetModuleHandleW(L"kernel32.dll"),
+ "GetActiveProcessorCount");
+ if (pfnGetActiveProcessorCount)
+ cpus = pfnGetActiveProcessorCount(ALL_PROCESSOR_GROUPS);
+ /* Legacy (<= Vista). */
+ else
+ {
+ int i;
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ for (i = cpus = 0; i < sizeof(si.dwActiveProcessorMask) * 8; i++)
+ {
+ if (si.dwActiveProcessorMask & 1)
+ cpus++;
+ si.dwActiveProcessorMask >>= 1;
+ }
+ }
+ if (!cpus)
+ cpus = 1;
+# ifndef CONFIG_NEW_WIN_CHILDREN
+ if (cpus > 64)
+ cpus = 64; /* (wait for multiple objects limit) */
+# endif
+ return cpus;
+
+# elif defined(__OS2__)
+ /* OS/2: Count the active CPUs. */
+ int cpus, i, j;
+ MPAFFINITY mp;
+ if (DosQueryThreadAffinity(AFNTY_SYSTEM, &mp))
+ return 1;
+ for (j = cpus = 0; j < sizeof(mp.mask) / sizeof(mp.mask[0]); j++)
+ for (i = 0; i < 32; i++)
+ if (mp.mask[j] & (1UL << i))
+ cpus++;
+ return cpus ? cpus : 1;
+
+# else
+ /* UNIX like systems, try sysconf and sysctl. */
+ int cpus = -1;
+# if defined(CTL_HW)
+ int mib[2];
+ size_t sz;
+# endif
+
+# ifdef _SC_NPROCESSORS_ONLN
+ cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (cpus >= 1)
+ return cpus;
+ cpus = -1;
+# endif
+
+# if defined(CTL_HW)
+# ifdef HW_AVAILCPU
+ sz = sizeof(cpus);
+ mib[0] = CTL_HW;
+ mib[1] = HW_AVAILCPU;
+ if (!sysctl(mib, 2, &cpus, &sz, NULL, 0)
+ && cpus >= 1)
+ return cpus;
+ cpus = -1;
+# endif /* HW_AVAILCPU */
+
+ sz = sizeof(cpus);
+ mib[0] = CTL_HW;
+ mib[1] = HW_NCPU;
+ if (!sysctl(mib, 2, &cpus, &sz, NULL, 0)
+ && cpus >= 1)
+ return cpus;
+ cpus = -1;
+# endif /* CTL_HW */
+
+ /* no idea / failure, just return 1. */
+ return 1;
+# endif
+}
+#endif /* KMK */
+
+#ifdef __MSDOS__
+static void
+msdos_return_to_initial_directory (void)
+{
+ if (directory_before_chdir)
+ chdir (directory_before_chdir);
+}
+#endif /* __MSDOS__ */
+
+static void
+reset_jobserver (void)
+{
+ jobserver_clear ();
+ free (jobserver_auth);
+ jobserver_auth = NULL;
+}
+
+#ifdef _AMIGA
+int
+main (int argc, char **argv)
+#else
+int
+main (int argc, char **argv, char **envp)
+#endif
+{
+ static char *stdin_nm = 0;
+#ifdef CONFIG_WITH_MAKE_STATS
+ unsigned long long uStartTick = CURRENT_CLOCK_TICK();
+#endif
+ int makefile_status = MAKE_SUCCESS;
+ struct goaldep *read_files;
+ PATH_VAR (current_directory);
+ unsigned int restarts = 0;
+ unsigned int syncing = 0;
+ int argv_slots;
+#ifdef WINDOWS32
+ const char *unix_path = NULL;
+ const char *windows32_path = NULL;
+
+# ifndef ELECTRIC_HEAP /* Drop this because it prevents JIT debugging. */
+# ifndef KMK /* Don't want none of this crap. */
+ SetUnhandledExceptionFilter (handle_runtime_exceptions);
+# endif
+# endif /* !ELECTRIC_HEAP */
+
+# ifdef KMK
+ /* Clear the SEM_NOGPFAULTERRORBOX flag so WER will generate dumps when we run
+ under cygwin. To void popups, set WER registry value DontShowUI to 1. */
+ if (getenv("KMK_NO_SET_ERROR_MODE") == NULL)
+ SetErrorMode(SetErrorMode(0) & ~SEM_NOGPFAULTERRORBOX);
+# endif
+
+ /* start off assuming we have no shell */
+ unixy_shell = 0;
+ no_default_sh_exe = 1;
+#endif
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ make_start_ts = nano_timestamp ();
+#endif
+
+ output_init (&make_sync);
+
+ initialize_stopchar_map();
+
+#ifdef SET_STACK_SIZE
+ /* Get rid of any avoidable limit on stack size. */
+ {
+ struct rlimit rlim;
+
+ /* Set the stack limit huge so that alloca does not fail. */
+ if (getrlimit (RLIMIT_STACK, &rlim) == 0
+ && rlim.rlim_cur > 0 && rlim.rlim_cur < rlim.rlim_max)
+ {
+ stack_limit = rlim;
+ rlim.rlim_cur = rlim.rlim_max;
+ setrlimit (RLIMIT_STACK, &rlim);
+ }
+ else
+ stack_limit.rlim_cur = 0;
+ }
+#endif
+
+ /* Needed for OS/2 */
+ initialize_main (&argc, &argv);
+
+#ifdef KMK
+ init_kbuild (argc, argv);
+#endif
+
+#ifdef MAKE_MAINTAINER_MODE
+ /* In maintainer mode we always enable verification. */
+ verify_flag = 1;
+#endif
+
+#if defined (__MSDOS__) && !defined (_POSIX_SOURCE)
+ /* Request the most powerful version of 'system', to
+ make up for the dumb default shell. */
+ __system_flags = (__system_redirect
+ | __system_use_shell
+ | __system_allow_multiple_cmds
+ | __system_allow_long_cmds
+ | __system_handle_null_commands
+ | __system_emulate_chdir);
+
+#endif
+
+ /* Set up gettext/internationalization support. */
+ setlocale (LC_ALL, "");
+ /* The cast to void shuts up compiler warnings on systems that
+ disable NLS. */
+#ifdef LOCALEDIR /* bird */
+ (void)bindtextdomain (PACKAGE, LOCALEDIR);
+ (void)textdomain (PACKAGE);
+#endif
+
+#ifdef POSIX
+ sigemptyset (&fatal_signal_set);
+#define ADD_SIG(sig) sigaddset (&fatal_signal_set, sig)
+#else
+#ifdef HAVE_SIGSETMASK
+ fatal_signal_mask = 0;
+#define ADD_SIG(sig) fatal_signal_mask |= sigmask (sig)
+#else
+#define ADD_SIG(sig) (void)sig
+#endif
+#endif
+
+#define FATAL_SIG(sig) \
+ if (bsd_signal (sig, fatal_error_signal) == SIG_IGN) \
+ bsd_signal (sig, SIG_IGN); \
+ else \
+ ADD_SIG (sig);
+
+#ifdef SIGHUP
+ FATAL_SIG (SIGHUP);
+#endif
+#ifdef SIGQUIT
+ FATAL_SIG (SIGQUIT);
+#endif
+ FATAL_SIG (SIGINT);
+ FATAL_SIG (SIGTERM);
+
+#ifdef __MSDOS__
+ /* Windows 9X delivers FP exceptions in child programs to their
+ parent! We don't want Make to die when a child divides by zero,
+ so we work around that lossage by catching SIGFPE. */
+ FATAL_SIG (SIGFPE);
+#endif
+
+#ifdef SIGDANGER
+ FATAL_SIG (SIGDANGER);
+#endif
+#ifdef SIGXCPU
+ FATAL_SIG (SIGXCPU);
+#endif
+#ifdef SIGXFSZ
+ FATAL_SIG (SIGXFSZ);
+#endif
+
+#ifdef KMK
+ /* Get the incoming umask so we don't have to modify it later to get it. */
+ umask(g_fUMask = umask(0077));
+#endif
+
+#ifdef CONFIG_NEW_WIN32_CTRL_EVENT
+ /* bird: dispatch signals in our own way to try avoid deadlocks. */
+ g_tidMainThread = GetCurrentThreadId ();
+ SetConsoleCtrlHandler (ctrl_event, TRUE);
+#endif /* CONFIG_NEW_WIN32_CTRL_EVENT */
+
+#undef FATAL_SIG
+
+ /* Do not ignore the child-death signal. This must be done before
+ any children could possibly be created; otherwise, the wait
+ functions won't work on systems with the SVR4 ECHILD brain
+ damage, if our invoker is ignoring this signal. */
+
+#ifdef HAVE_WAIT_NOHANG
+# if defined SIGCHLD
+ (void) bsd_signal (SIGCHLD, SIG_DFL);
+# endif
+# if defined SIGCLD && SIGCLD != SIGCHLD
+ (void) bsd_signal (SIGCLD, SIG_DFL);
+# endif
+#endif
+
+ output_init (NULL);
+
+ /* Figure out where this program lives. */
+
+ if (argv[0] == 0)
+ argv[0] = (char *)"";
+ if (argv[0][0] == '\0')
+#ifdef KMK
+ program = "kmk";
+#else
+ program = "make";
+#endif
+ else
+ {
+ program = strrchr (argv[0], '/');
+#if defined(__MSDOS__) || defined(__EMX__)
+ if (program == 0)
+ program = strrchr (argv[0], '\\');
+ else
+ {
+ /* Some weird environments might pass us argv[0] with
+ both kinds of slashes; we must find the rightmost. */
+ char *p = strrchr (argv[0], '\\');
+ if (p && p > program)
+ program = p;
+ }
+ if (program == 0 && argv[0][1] == ':')
+ program = argv[0] + 1;
+#endif
+#ifdef WINDOWS32
+ if (program == 0)
+ {
+ /* Extract program from full path */
+ program = strrchr (argv[0], '\\');
+ if (program)
+ {
+ int argv0_len = strlen (program);
+ if (argv0_len > 4 && streq (&program[argv0_len - 4], ".exe"))
+ /* Remove .exe extension */
+ program[argv0_len - 4] = '\0';
+ }
+ }
+#endif
+#ifdef VMS
+ set_program_name (argv[0]);
+ program = program_name;
+ {
+ const char *shell;
+ char pwdbuf[256];
+ char *pwd;
+ shell = getenv ("SHELL");
+ if (shell != NULL)
+ vms_gnv_shell = 1;
+
+ /* Need to know if CRTL set to report UNIX paths. Use getcwd as
+ it works on all versions of VMS. */
+ pwd = getcwd(pwdbuf, 256);
+ if (pwd[0] == '/')
+ vms_report_unix_paths = 1;
+
+ vms_use_mcr_command = get_vms_env_flag ("GNV$MAKE_USE_MCR", 0);
+
+ vms_always_use_cmd_file = get_vms_env_flag ("GNV$MAKE_USE_CMD_FILE", 0);
+
+ /* Legacy behavior is on VMS is older behavior that needed to be
+ changed to be compatible with standard make behavior.
+ For now only completely disable when running under a Bash shell.
+ TODO: Update VMS built in recipes and macros to not need this
+ behavior, at which time the default may change. */
+ vms_legacy_behavior = get_vms_env_flag ("GNV$MAKE_OLD_VMS",
+ !vms_gnv_shell);
+
+ /* VMS was changed to use a comma separator in the past, but that is
+ incompatible with built in functions that expect space separated
+ lists. Allow this to be selectively turned off. */
+ vms_comma_separator = get_vms_env_flag ("GNV$MAKE_COMMA",
+ vms_legacy_behavior);
+
+ /* Some Posix shell syntax options are incompatible with VMS syntax.
+ VMS requires double quotes for strings and escapes quotes
+ differently. When this option is active, VMS will try
+ to simulate Posix shell simulations instead of using
+ VMS DCL behavior. */
+ vms_unix_simulation = get_vms_env_flag ("GNV$MAKE_SHELL_SIM",
+ !vms_legacy_behavior);
+
+ }
+ if (need_vms_symbol () && !vms_use_mcr_command)
+ create_foreign_command (program_name, argv[0]);
+#else
+ if (program == 0)
+ program = argv[0];
+ else
+ ++program;
+#endif
+ }
+
+ /* Set up to access user data (files). */
+ user_access ();
+
+# ifdef CONFIG_WITH_COMPILER
+ kmk_cc_init ();
+# endif
+#ifdef CONFIG_WITH_ALLOC_CACHES
+ initialize_global_alloc_caches ();
+#endif
+ initialize_global_hash_tables ();
+#ifdef KMK
+ init_kbuild_object ();
+#endif
+
+ /* Figure out where we are. */
+
+#ifdef WINDOWS32
+ if (getcwd_fs (current_directory, GET_PATH_MAX) == 0)
+#else
+ if (getcwd (current_directory, GET_PATH_MAX) == 0)
+#endif
+ {
+#ifdef HAVE_GETCWD
+ perror_with_name ("getcwd", "");
+#else
+ OS (error, NILF, "getwd: %s", current_directory);
+#endif
+ current_directory[0] = '\0';
+ directory_before_chdir = 0;
+ }
+ else
+ directory_before_chdir = xstrdup (current_directory);
+
+#ifdef __MSDOS__
+ /* Make sure we will return to the initial directory, come what may. */
+ atexit (msdos_return_to_initial_directory);
+#endif
+
+ /* Initialize the special variables. */
+ define_variable_cname (".VARIABLES", "", o_default, 0)->special = 1;
+ /* define_variable_cname (".TARGETS", "", o_default, 0)->special = 1; */
+ define_variable_cname (".RECIPEPREFIX", "", o_default, 0)->special = 1;
+ define_variable_cname (".SHELLFLAGS", "-c", o_default, 0);
+ define_variable_cname (".LOADED", "", o_default, 0);
+
+ /* Set up .FEATURES
+ Use a separate variable because define_variable_cname() is a macro and
+ some compilers (MSVC) don't like conditionals in macros. */
+ {
+ const char *features = "target-specific order-only second-expansion"
+ " else-if shortest-stem undefine oneshell"
+#ifndef NO_ARCHIVES
+ " archives"
+#endif
+#ifdef MAKE_JOBSERVER
+ " jobserver"
+#endif
+#ifndef NO_OUTPUT_SYNC
+ " output-sync"
+#endif
+#ifdef MAKE_SYMLINKS
+ " check-symlink"
+#endif
+#ifdef HAVE_GUILE
+ " guile"
+#endif
+#ifdef MAKE_LOAD
+ " load"
+#endif
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ " explicit-multitarget"
+#endif
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ " prepend-assignment"
+#endif
+ ;
+
+ define_variable_cname (".FEATURES", features, o_default, 0);
+ }
+
+#ifdef KMK
+ /* Initialize the default number of jobs to the cpu/core/smt count. */
+ default_job_slots = arg_job_slots = job_slots = get_online_cpu_count ();
+#endif /* KMK */
+
+ /* Configure GNU Guile support */
+ guile_gmake_setup (NILF);
+
+ /* Read in variables from the environment. It is important that this be
+ done before $(MAKE) is figured out so its definitions will not be
+ from the environment. */
+
+#ifndef _AMIGA
+ {
+ unsigned int i;
+
+ for (i = 0; envp[i] != 0; ++i)
+ {
+ struct variable *v;
+ const char *ep = envp[i];
+ /* By default, export all variables culled from the environment. */
+ enum variable_export export = v_export;
+ unsigned int len;
+
+ while (! STOP_SET (*ep, MAP_EQUALS))
+ ++ep;
+
+ /* If there's no equals sign it's a malformed environment. Ignore. */
+ if (*ep == '\0')
+ continue;
+
+#ifdef WINDOWS32
+ if (!unix_path && strneq (envp[i], "PATH=", 5))
+ unix_path = ep+1;
+ else if (!strnicmp (envp[i], "Path=", 5))
+ {
+ if (!windows32_path)
+ windows32_path = ep+1;
+ /* PATH gets defined after the loop exits. */
+ continue;
+ }
+#endif
+
+ /* Length of the variable name, and skip the '='. */
+ len = ep++ - envp[i];
+
+ /* If this is MAKE_RESTARTS, check to see if the "already printed
+ the enter statement" flag is set. */
+ if (len == 13 && strneq (envp[i], "MAKE_RESTARTS", 13))
+ {
+ if (*ep == '-')
+ {
+ OUTPUT_TRACED ();
+ ++ep;
+ }
+ restarts = (unsigned int) atoi (ep);
+ export = v_noexport;
+ }
+
+ v = define_variable (envp[i], len, ep, o_env, 1);
+
+ /* POSIX says the value of SHELL set in the makefile won't change the
+ value of SHELL given to subprocesses. */
+ if (streq (v->name, "SHELL"))
+ {
+#ifndef __MSDOS__
+ export = v_noexport;
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ shell_var.name = xstrdup ("SHELL");
+#else
+ shell_var.name = v->name;
+#endif
+ shell_var.length = 5;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ shell_var.value = xstrdup (ep);
+#else
+ shell_var.value = xstrndup (v->value, v->value_length);
+ shell_var.value_length = v->value_length;
+#endif
+ }
+
+ v->export = export;
+ }
+ }
+#ifdef WINDOWS32
+ /* If we didn't find a correctly spelled PATH we define PATH as
+ * either the first misspelled value or an empty string
+ */
+ if (!unix_path)
+ define_variable_cname ("PATH", windows32_path ? windows32_path : "",
+ o_env, 1)->export = v_export;
+#endif
+#else /* For Amiga, read the ENV: device, ignoring all dirs */
+ {
+ BPTR env, file, old;
+ char buffer[1024];
+ int len;
+ __aligned struct FileInfoBlock fib;
+
+ env = Lock ("ENV:", ACCESS_READ);
+ if (env)
+ {
+ old = CurrentDir (DupLock (env));
+ Examine (env, &fib);
+
+ while (ExNext (env, &fib))
+ {
+ if (fib.fib_DirEntryType < 0) /* File */
+ {
+ /* Define an empty variable. It will be filled in
+ variable_lookup(). Makes startup quite a bit faster. */
+ define_variable (fib.fib_FileName,
+ strlen (fib.fib_FileName),
+ "", o_env, 1)->export = v_export;
+ }
+ }
+ UnLock (env);
+ UnLock (CurrentDir (old));
+ }
+ }
+#endif
+
+ /* Decode the switches. */
+ decode_env_switches (STRING_SIZE_TUPLE ("GNUMAKEFLAGS"));
+
+ /* Clear GNUMAKEFLAGS to avoid duplication. */
+ define_variable_cname ("GNUMAKEFLAGS", "", o_env, 0);
+
+#ifdef KMK
+ decode_env_switches (STRING_SIZE_TUPLE ("KMK_FLAGS"));
+#else /* !KMK */
+ decode_env_switches (STRING_SIZE_TUPLE ("MAKEFLAGS"));
+
+#if 0
+ /* People write things like:
+ MFLAGS="CC=gcc -pipe" "CFLAGS=-g"
+ and we set the -p, -i and -e switches. Doesn't seem quite right. */
+ decode_env_switches (STRING_SIZE_TUPLE ("MFLAGS"));
+#endif
+#endif /* !KMK */
+
+ /* In output sync mode we need to sync any output generated by reading the
+ makefiles, such as in $(info ...) or stderr from $(shell ...) etc. */
+
+ syncing = make_sync.syncout = (output_sync == OUTPUT_SYNC_LINE
+ || output_sync == OUTPUT_SYNC_TARGET);
+ OUTPUT_SET (&make_sync);
+
+ /* Remember the job slots set through the environment vs. command line. */
+ {
+ int env_slots = arg_job_slots;
+ arg_job_slots = INVALID_JOB_SLOTS;
+
+ decode_switches (argc, (const char **)argv, 0);
+ argv_slots = arg_job_slots;
+
+ if (arg_job_slots == INVALID_JOB_SLOTS)
+ arg_job_slots = env_slots;
+ }
+
+ /* Set a variable specifying whether stdout/stdin is hooked to a TTY. */
+#ifdef HAVE_ISATTY
+ if (isatty (fileno (stdout)))
+ if (! lookup_variable (STRING_SIZE_TUPLE ("MAKE_TERMOUT")))
+ {
+ const char *tty = TTYNAME (fileno (stdout));
+ define_variable_cname ("MAKE_TERMOUT", tty ? tty : DEFAULT_TTYNAME,
+ o_default, 0)->export = v_export;
+ }
+ if (isatty (fileno (stderr)))
+ if (! lookup_variable (STRING_SIZE_TUPLE ("MAKE_TERMERR")))
+ {
+ const char *tty = TTYNAME (fileno (stderr));
+ define_variable_cname ("MAKE_TERMERR", tty ? tty : DEFAULT_TTYNAME,
+ o_default, 0)->export = v_export;
+ }
+#endif
+
+ /* Reset in case the switches changed our minds. */
+ syncing = (output_sync == OUTPUT_SYNC_LINE
+ || output_sync == OUTPUT_SYNC_TARGET);
+
+#ifdef KMK
+ set_make_priority_and_affinity ();
+#endif
+
+ if (make_sync.syncout && ! syncing)
+ output_close (&make_sync);
+
+ make_sync.syncout = syncing;
+ OUTPUT_SET (&make_sync);
+
+ /* Figure out the level of recursion. */
+ {
+ struct variable *v = lookup_variable (STRING_SIZE_TUPLE (MAKELEVEL_NAME));
+ if (v && v->value[0] != '\0' && v->value[0] != '-')
+ makelevel = (unsigned int) atoi (v->value);
+ else
+ makelevel = 0;
+ }
+
+#ifdef WINDOWS32
+ if (suspend_flag)
+ {
+ fprintf (stderr, "%s (pid = %ld)\n", argv[0], GetCurrentProcessId ());
+ fprintf (stderr, _("%s is suspending for 30 seconds..."), argv[0]);
+ Sleep (30 * 1000);
+ fprintf (stderr, _("done sleep(30). Continuing.\n"));
+ }
+#endif
+
+ /* Set always_make_flag if -B was given and we've not restarted already. */
+ always_make_flag = always_make_set && (restarts == 0);
+
+ /* Print version information, and exit. */
+ if (print_version_flag)
+ {
+ print_version ();
+ die (MAKE_SUCCESS);
+ }
+
+ if (ISDB (DB_BASIC))
+ print_version ();
+
+#ifndef VMS
+ /* Set the "MAKE_COMMAND" variable to the name we were invoked with.
+ (If it is a relative pathname with a slash, prepend our directory name
+ so the result will run the same program regardless of the current dir.
+ If it is a name with no slash, we can only hope that PATH did not
+ find it in the current directory.) */
+#ifdef WINDOWS32
+ /*
+ * Convert from backslashes to forward slashes for
+ * programs like sh which don't like them. Shouldn't
+ * matter if the path is one way or the other for
+ * CreateProcess().
+ */
+ if (strpbrk (argv[0], "/:\\") || strstr (argv[0], "..")
+ || strneq (argv[0], "//", 2))
+# if 1 /* bird */
+ {
+ PATH_VAR (tmp_path_buf);
+ argv[0] = xstrdup (unix_slashes_resolved (argv[0], tmp_path_buf,
+ GET_PATH_MAX));
+ }
+# else /* bird */
+ //argv[0] = xstrdup (w32ify (argv[0], 1));
+# endif /* bird */
+#else /* WINDOWS32 */
+#if defined (__MSDOS__) || defined (__EMX__)
+ if (strchr (argv[0], '\\'))
+ {
+ char *p;
+
+ argv[0] = xstrdup (argv[0]);
+ for (p = argv[0]; *p; p++)
+ if (*p == '\\')
+ *p = '/';
+ }
+ /* If argv[0] is not in absolute form, prepend the current
+ directory. This can happen when Make is invoked by another DJGPP
+ program that uses a non-absolute name. */
+ if (current_directory[0] != '\0'
+ && argv[0] != 0
+ && (argv[0][0] != '/' && (argv[0][0] == '\0' || argv[0][1] != ':'))
+# ifdef __EMX__
+ /* do not prepend cwd if argv[0] contains no '/', e.g. "make" */
+ && (strchr (argv[0], '/') != 0 || strchr (argv[0], '\\') != 0)
+# endif
+ )
+ argv[0] = xstrdup (concat (3, current_directory, "/", argv[0]));
+#else /* !__MSDOS__ */
+ if (current_directory[0] != '\0'
+ && argv[0] != 0 && argv[0][0] != '/' && strchr (argv[0], '/') != 0
+#ifdef HAVE_DOS_PATHS
+ && (argv[0][0] != '\\' && (!argv[0][0] || argv[0][1] != ':'))
+ && strchr (argv[0], '\\') != 0
+#endif
+ )
+ argv[0] = xstrdup (concat (3, current_directory, "/", argv[0]));
+#endif /* !__MSDOS__ */
+#endif /* WINDOWS32 */
+#endif
+
+ /* We may move, but until we do, here we are. */
+ starting_directory = current_directory;
+
+ /* Set up the job_slots value and the jobserver. This can't be usefully set
+ in the makefile, and we want to verify the authorization is valid before
+ make has a chance to start using it for something else. */
+
+ if (jobserver_auth)
+ {
+ if (argv_slots == INVALID_JOB_SLOTS)
+ {
+ if (jobserver_parse_auth (jobserver_auth))
+ {
+ /* Success! Use the jobserver. */
+ job_slots = 0;
+ goto job_setup_complete;
+ }
+
+ O (error, NILF, _("warning: jobserver unavailable: using -j1. Add '+' to parent make rule."));
+ arg_job_slots = 1;
+ }
+
+ /* The user provided a -j setting on the command line: use it. */
+ else if (!restarts)
+ /* If restarts is >0 we already printed this message. */
+ O (error, NILF,
+ _("warning: -jN forced in submake: disabling jobserver mode."));
+
+ /* We failed to use our parent's jobserver. */
+ reset_jobserver ();
+ job_slots = (unsigned int)arg_job_slots;
+ }
+ else if (arg_job_slots == INVALID_JOB_SLOTS)
+#ifdef KMK
+ job_slots = default_job_slots; /* The default is set to CPU count early in main. */
+#else
+ /* The default is one job at a time. */
+ job_slots = 1;
+#endif
+ else
+ /* Use whatever was provided. */
+ job_slots = (unsigned int)arg_job_slots;
+
+ job_setup_complete:
+
+#if defined (WINDOWS32) && defined(CONFIG_NEW_WIN_CHILDREN)
+ /* Initialize the windows child management. */
+ MkWinChildInit(job_slots);
+#endif
+
+ /* The extra indirection through $(MAKE_COMMAND) is done
+ for hysterical raisins. */
+
+#ifdef VMS
+ if (vms_use_mcr_command)
+ define_variable_cname ("MAKE_COMMAND", vms_command (argv[0]), o_default, 0);
+ else
+ define_variable_cname ("MAKE_COMMAND", program, o_default, 0);
+#else
+ define_variable_cname ("MAKE_COMMAND", argv[0], o_default, 0);
+#endif
+ define_variable_cname ("MAKE", "$(MAKE_COMMAND)", o_default, 1);
+#ifdef KMK
+ (void) define_variable ("KMK", 3, argv[0], o_default, 1);
+#endif
+
+ if (command_variables != 0)
+ {
+ struct command_variable *cv;
+ struct variable *v;
+ unsigned int len = 0;
+ char *value, *p;
+
+ /* Figure out how much space will be taken up by the command-line
+ variable definitions. */
+ for (cv = command_variables; cv != 0; cv = cv->next)
+ {
+ v = cv->variable;
+ len += 2 * strlen (v->name);
+ if (! v->recursive)
+ ++len;
+ ++len;
+ len += 2 * strlen (v->value);
+ ++len;
+ }
+
+ /* Now allocate a buffer big enough and fill it. */
+ p = value = alloca (len);
+ for (cv = command_variables; cv != 0; cv = cv->next)
+ {
+ v = cv->variable;
+ p = quote_for_env (p, v->name);
+ if (! v->recursive)
+ *p++ = ':';
+ *p++ = '=';
+ p = quote_for_env (p, v->value);
+ *p++ = ' ';
+ }
+ p[-1] = '\0'; /* Kill the final space and terminate. */
+
+ /* Define an unchangeable variable with a name that no POSIX.2
+ makefile could validly use for its own variable. */
+ define_variable_cname ("-*-command-variables-*-", value, o_automatic, 0);
+
+ /* Define the variable; this will not override any user definition.
+ Normally a reference to this variable is written into the value of
+ MAKEFLAGS, allowing the user to override this value to affect the
+ exported value of MAKEFLAGS. In POSIX-pedantic mode, we cannot
+ allow the user's setting of MAKEOVERRIDES to affect MAKEFLAGS, so
+ a reference to this hidden variable is written instead. */
+#ifdef KMK
+ define_variable_cname ("KMK_OVERRIDES", "${-*-command-variables-*-}",
+ o_env, 1);
+#else
+ define_variable_cname ("MAKEOVERRIDES", "${-*-command-variables-*-}",
+ o_env, 1);
+#endif
+#ifdef VMS
+ vms_export_dcl_symbol ("MAKEOVERRIDES", "${-*-command-variables-*-}");
+#endif
+ }
+
+ /* If there were -C flags, move ourselves about. */
+ if (directories != 0)
+ {
+ unsigned int i;
+ for (i = 0; directories->list[i] != 0; ++i)
+ {
+ const char *dir = directories->list[i];
+#ifdef WINDOWS32
+ /* WINDOWS32 chdir() doesn't work if the directory has a trailing '/'
+ But allow -C/ just in case someone wants that. */
+ {
+ char *p = (char *)dir + strlen (dir) - 1;
+ while (p > dir && (p[0] == '/' || p[0] == '\\'))
+ --p;
+ p[1] = '\0';
+ }
+#endif
+ if (chdir (dir) < 0)
+ pfatal_with_name (dir);
+ }
+ }
+
+#ifdef KMK
+ /* Check for [Mm]akefile.kup and change directory when found.
+ Makefile.kmk overrides Makefile.kup but not plain Makefile.
+ If no -C arguments were given, fake one to indicate chdir. */
+ if (makefiles == 0)
+ {
+ struct stat st;
+ if (( ( stat ("Makefile.kup", &st) == 0
+ && S_ISREG (st.st_mode) )
+ || ( stat ("makefile.kup", &st) == 0
+ && S_ISREG (st.st_mode) ) )
+ && stat ("Makefile.kmk", &st) < 0
+ && stat ("makefile.kmk", &st) < 0)
+ {
+ static char fake_path[3*16 + 32] = "..";
+ char *cur = &fake_path[2];
+ int up_levels = 1;
+ while (up_levels < 16)
+ {
+ /* File with higher precedence.s */
+ strcpy (cur, "/Makefile.kmk");
+ if (stat (fake_path, &st) == 0)
+ break;
+ strcpy (cur, "/makefile.kmk");
+ if (stat (fake_path, &st) == 0)
+ break;
+
+ /* the .kup files */
+ strcpy (cur, "/Makefile.kup");
+ if ( stat (fake_path, &st) != 0
+ || !S_ISREG (st.st_mode))
+ {
+ strcpy (cur, "/makefile.kup");
+ if ( stat (fake_path, &st) != 0
+ || !S_ISREG (st.st_mode))
+ break;
+ }
+
+ /* ok */
+ strcpy (cur, "/..");
+ cur += 3;
+ up_levels++;
+ }
+
+ if (up_levels >= 16)
+ O (fatal, NILF, _("Makefile.kup recursion is too deep."));
+
+ /* attempt to change to the directory. */
+ *cur = '\0';
+ if (chdir (fake_path) < 0)
+ pfatal_with_name (fake_path);
+
+ /* add the string to the directories. */
+ if (!directories)
+ {
+ directories = xmalloc (sizeof(*directories));
+ directories->list = xmalloc (5 * sizeof (char *));
+ directories->max = 5;
+ directories->idx = 0;
+ }
+ else if (directories->idx == directories->max - 1)
+ {
+ directories->max += 5;
+ directories->list = xrealloc ((void *)directories->list,
+ directories->max * sizeof (char *));
+ }
+ directories->list[directories->idx++] = fake_path;
+ }
+ }
+#endif /* KMK */
+
+#ifdef WINDOWS32
+ /*
+ * THIS BLOCK OF CODE MUST COME AFTER chdir() CALL ABOVE IN ORDER
+ * TO NOT CONFUSE THE DEPENDENCY CHECKING CODE IN implicit.c.
+ *
+ * The functions in dir.c can incorrectly cache information for "."
+ * before we have changed directory and this can cause file
+ * lookups to fail because the current directory (.) was pointing
+ * at the wrong place when it was first evaluated.
+ */
+#ifdef KMK /* this is really a candidate for all platforms... */
+ {
+ extern const char *default_shell;
+ const char *bin = get_kbuild_bin_path();
+ char *newshell;
+ size_t len = strlen (bin);
+ default_shell = newshell = xmalloc (len + sizeof("/kmk_ash.exe"));
+ memcpy (newshell, bin, len);
+ strcpy (newshell + len, "/kmk_ash.exe");
+ no_default_sh_exe = 0;
+ batch_mode_shell = 1;
+ unixy_shell = 1;
+ }
+#else /* !KMK */
+ no_default_sh_exe = !find_and_set_default_shell (NULL);
+#endif /* !KMK */
+#endif /* WINDOWS32 */
+
+ /* Except under -s, always do -w in sub-makes and under -C. */
+ if (!silent_flag && (directories != 0 || makelevel > 0))
+ print_directory_flag = 1;
+
+ /* Let the user disable that with --no-print-directory. */
+ if (inhibit_print_directory_flag)
+ print_directory_flag = 0;
+
+ /* If -R was given, set -r too (doesn't make sense otherwise!) */
+ if (no_builtin_variables_flag)
+ no_builtin_rules_flag = 1;
+
+ /* Construct the list of include directories to search. */
+
+ construct_include_path (include_directories == 0
+ ? 0 : include_directories->list);
+
+ /* If we chdir'ed, figure out where we are now. */
+ if (directories)
+ {
+#ifdef WINDOWS32
+ if (getcwd_fs (current_directory, GET_PATH_MAX) == 0)
+#else
+ if (getcwd (current_directory, GET_PATH_MAX) == 0)
+#endif
+ {
+#ifdef HAVE_GETCWD
+ perror_with_name ("getcwd", "");
+#else
+ OS (error, NILF, "getwd: %s", current_directory);
+#endif
+ starting_directory = 0;
+ }
+ else
+ starting_directory = current_directory;
+ }
+
+ define_variable_cname ("CURDIR", current_directory, o_file, 0);
+
+ /* Read any stdin makefiles into temporary files. */
+
+ if (makefiles != 0)
+ {
+ unsigned int i;
+ for (i = 0; i < makefiles->idx; ++i)
+ if (makefiles->list[i][0] == '-' && makefiles->list[i][1] == '\0')
+ {
+ /* This makefile is standard input. Since we may re-exec
+ and thus re-read the makefiles, we read standard input
+ into a temporary file and read from that. */
+ FILE *outfile;
+ char *template;
+ const char *tmpdir;
+
+ if (stdin_nm)
+ O (fatal, NILF,
+ _("Makefile from standard input specified twice."));
+
+#ifdef VMS
+# define DEFAULT_TMPDIR "/sys$scratch/"
+#else
+# ifdef P_tmpdir
+# define DEFAULT_TMPDIR P_tmpdir
+# else
+# define DEFAULT_TMPDIR "/tmp"
+# endif
+#endif
+#define DEFAULT_TMPFILE "GmXXXXXX"
+
+ if (((tmpdir = getenv ("TMPDIR")) == NULL || *tmpdir == '\0')
+#if defined (__MSDOS__) || defined (WINDOWS32) || defined (__EMX__)
+ /* These are also used commonly on these platforms. */
+ && ((tmpdir = getenv ("TEMP")) == NULL || *tmpdir == '\0')
+ && ((tmpdir = getenv ("TMP")) == NULL || *tmpdir == '\0')
+#endif
+ )
+ tmpdir = DEFAULT_TMPDIR;
+
+ template = alloca (strlen (tmpdir) + CSTRLEN (DEFAULT_TMPFILE) + 2);
+ strcpy (template, tmpdir);
+
+#ifdef HAVE_DOS_PATHS
+ if (strchr ("/\\", template[strlen (template) - 1]) == NULL)
+ strcat (template, "/");
+#else
+# ifndef VMS
+ if (template[strlen (template) - 1] != '/')
+ strcat (template, "/");
+# endif /* !VMS */
+#endif /* !HAVE_DOS_PATHS */
+
+ strcat (template, DEFAULT_TMPFILE);
+ outfile = output_tmpfile (&stdin_nm, template);
+ if (outfile == 0)
+ pfatal_with_name (_("fopen (temporary file)"));
+ while (!feof (stdin) && ! ferror (stdin))
+ {
+ char buf[2048];
+ unsigned int n = fread (buf, 1, sizeof (buf), stdin);
+ if (n > 0 && fwrite (buf, 1, n, outfile) != n)
+ pfatal_with_name (_("fwrite (temporary file)"));
+ }
+ fclose (outfile);
+
+ /* Replace the name that read_all_makefiles will
+ see with the name of the temporary file. */
+ makefiles->list[i] = strcache_add (stdin_nm);
+
+ /* Make sure the temporary file will not be remade. */
+ {
+ struct file *f = enter_file (strcache_add (stdin_nm));
+ f->updated = 1;
+ f->update_status = us_success;
+ f->command_state = cs_finished;
+ /* Can't be intermediate, or it'll be removed too early for
+ make re-exec. */
+ f->intermediate = 0;
+ f->dontcare = 0;
+ }
+ }
+ }
+
+#if !defined(__EMX__) || defined(__KLIBC__) /* Don't use a SIGCHLD handler for good old EMX (bird) */
+#if !defined(HAVE_WAIT_NOHANG) || defined(MAKE_JOBSERVER)
+ /* Set up to handle children dying. This must be done before
+ reading in the makefiles so that 'shell' function calls will work.
+
+ If we don't have a hanging wait we have to fall back to old, broken
+ functionality here and rely on the signal handler and counting
+ children.
+
+ If we're using the jobs pipe we need a signal handler so that SIGCHLD is
+ not ignored; we need it to interrupt the read(2) of the jobserver pipe if
+ we're waiting for a token.
+
+ If none of these are true, we don't need a signal handler at all. */
+ {
+# if defined SIGCHLD
+ bsd_signal (SIGCHLD, child_handler);
+# endif
+# if defined SIGCLD && SIGCLD != SIGCHLD
+ bsd_signal (SIGCLD, child_handler);
+# endif
+ }
+
+#ifdef HAVE_PSELECT
+ /* If we have pselect() then we need to block SIGCHLD so it's deferred. */
+ {
+ sigset_t block;
+ sigemptyset (&block);
+ sigaddset (&block, SIGCHLD);
+ if (sigprocmask (SIG_SETMASK, &block, NULL) < 0)
+ pfatal_with_name ("sigprocmask(SIG_SETMASK, SIGCHLD)");
+ }
+#endif
+
+#endif
+#endif
+
+ /* Let the user send us SIGUSR1 to toggle the -d flag during the run. */
+#ifdef SIGUSR1
+ bsd_signal (SIGUSR1, debug_signal_handler);
+#endif
+
+ /* Define the initial list of suffixes for old-style rules. */
+ set_default_suffixes ();
+
+ /* Define the file rules for the built-in suffix rules. These will later
+ be converted into pattern rules. We used to do this in
+ install_default_implicit_rules, but since that happens after reading
+ makefiles, it results in the built-in pattern rules taking precedence
+ over makefile-specified suffix rules, which is wrong. */
+ install_default_suffix_rules ();
+
+ /* Define some internal and special variables. */
+ define_automatic_variables ();
+
+ /* Set up the MAKEFLAGS and MFLAGS variables for makefiles to see.
+ Initialize it to be exported but allow the makefile to reset it. */
+ define_makeflags (0, 0)->export = v_export;
+
+ /* Define the default variables. */
+ define_default_variables ();
+
+ default_file = enter_file (strcache_add (".DEFAULT"));
+
+ default_goal_var = define_variable_cname (".DEFAULT_GOAL", "", o_file, 0);
+
+ /* Evaluate all strings provided with --eval.
+ Also set up the $(-*-eval-flags-*-) variable. */
+
+ if (eval_strings)
+ {
+ char *p, *value;
+ unsigned int i;
+ unsigned int len = (CSTRLEN ("--eval=") + 1) * eval_strings->idx;
+
+ for (i = 0; i < eval_strings->idx; ++i)
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p = xstrdup (eval_strings->list[i]);
+ len += 2 * strlen (p);
+ eval_buffer (p, NULL);
+#else
+ unsigned int sub_len = strlen(eval_strings->list[i]);
+ p = xstrndup (eval_strings->list[i], sub_len);
+ len += 2 * sub_len;
+ eval_buffer (p, NULL, p + sub_len);
+#endif
+ free (p);
+ }
+
+ p = value = alloca (len);
+ for (i = 0; i < eval_strings->idx; ++i)
+ {
+ strcpy (p, "--eval=");
+ p += CSTRLEN ("--eval=");
+ p = quote_for_env (p, eval_strings->list[i]);
+ *(p++) = ' ';
+ }
+ p[-1] = '\0';
+
+ define_variable_cname ("-*-eval-flags-*-", value, o_automatic, 0);
+ }
+
+ /* Read all the makefiles. */
+
+ read_files = read_all_makefiles (makefiles == 0 ? 0 : makefiles->list);
+
+#ifdef WINDOWS32
+ /* look one last time after reading all Makefiles */
+ if (no_default_sh_exe)
+ no_default_sh_exe = !find_and_set_default_shell (NULL);
+#endif /* WINDOWS32 */
+
+#if defined (__MSDOS__) || defined (__EMX__) || defined (VMS)
+ /* We need to know what kind of shell we will be using. */
+ {
+ extern int _is_unixy_shell (const char *_path);
+ struct variable *shv = lookup_variable (STRING_SIZE_TUPLE ("SHELL"));
+ extern int unixy_shell;
+ extern const char *default_shell;
+
+ if (shv && *shv->value)
+ {
+ char *shell_path = recursively_expand (shv);
+
+ if (shell_path && _is_unixy_shell (shell_path))
+ unixy_shell = 1;
+ else
+ unixy_shell = 0;
+ if (shell_path)
+ default_shell = shell_path;
+ }
+ }
+#endif /* __MSDOS__ || __EMX__ */
+
+ {
+ int old_builtin_rules_flag = no_builtin_rules_flag;
+ int old_builtin_variables_flag = no_builtin_variables_flag;
+
+ /* Decode switches again, for variables set by the makefile. */
+ decode_env_switches (STRING_SIZE_TUPLE ("GNUMAKEFLAGS"));
+
+ /* Clear GNUMAKEFLAGS to avoid duplication. */
+ define_variable_cname ("GNUMAKEFLAGS", "", o_override, 0);
+
+#ifdef KMK
+ decode_env_switches (STRING_SIZE_TUPLE ("KMK_FLAGS"));
+#else /* !KMK */
+ decode_env_switches (STRING_SIZE_TUPLE ("MAKEFLAGS"));
+#if 0
+ decode_env_switches (STRING_SIZE_TUPLE ("MFLAGS"));
+#endif
+#endif /* !KMK */
+
+ /* Reset in case the switches changed our mind. */
+ syncing = (output_sync == OUTPUT_SYNC_LINE
+ || output_sync == OUTPUT_SYNC_TARGET);
+
+ if (make_sync.syncout && ! syncing)
+ output_close (&make_sync);
+
+ make_sync.syncout = syncing;
+ OUTPUT_SET (&make_sync);
+
+ /* If we've disabled builtin rules, get rid of them. */
+ if (no_builtin_rules_flag && ! old_builtin_rules_flag)
+ {
+ if (suffix_file->builtin)
+ {
+ free_dep_chain (suffix_file->deps);
+ suffix_file->deps = 0;
+ }
+ define_variable_cname ("SUFFIXES", "", o_default, 0);
+ }
+
+ /* If we've disabled builtin variables, get rid of them. */
+ if (no_builtin_variables_flag && ! old_builtin_variables_flag)
+ undefine_default_variables ();
+ }
+
+#if defined (__MSDOS__) || defined (__EMX__) || defined (VMS)
+ if (arg_job_slots != 1
+# ifdef __EMX__
+ && _osmode != OS2_MODE /* turn off -j if we are in DOS mode */
+# endif
+ )
+ {
+ O (error, NILF,
+ _("Parallel jobs (-j) are not supported on this platform."));
+ O (error, NILF, _("Resetting to single job (-j1) mode."));
+ arg_job_slots = job_slots = 1;
+ }
+#endif
+
+ /* If we have >1 slot at this point, then we're a top-level make.
+ Set up the jobserver.
+
+ Every make assumes that it always has one job it can run. For the
+ submakes it's the token they were given by their parent. For the top
+ make, we just subtract one from the number the user wants. */
+
+ if (job_slots > 1 && jobserver_setup (job_slots - 1))
+ {
+ /* Fill in the jobserver_auth for our children. */
+ jobserver_auth = jobserver_get_auth ();
+
+ if (jobserver_auth)
+ {
+ /* We're using the jobserver so set job_slots to 0. */
+ master_job_slots = job_slots;
+ job_slots = 0;
+ }
+ }
+
+ /* If we're not using parallel jobs, then we don't need output sync.
+ This is so people can enable output sync in GNUMAKEFLAGS or similar, but
+ not have it take effect unless parallel builds are enabled. */
+ if (syncing && job_slots == 1)
+ {
+ OUTPUT_UNSET ();
+ output_close (&make_sync);
+ syncing = 0;
+ output_sync = OUTPUT_SYNC_NONE;
+ }
+
+#ifndef MAKE_SYMLINKS
+ if (check_symlink_flag)
+ {
+ O (error, NILF, _("Symbolic links not supported: disabling -L."));
+ check_symlink_flag = 0;
+ }
+#endif
+
+ /* Set up MAKEFLAGS and MFLAGS again, so they will be right. */
+
+ define_makeflags (1, 0);
+
+ /* Make each 'struct goaldep' point at the 'struct file' for the file
+ depended on. Also do magic for special targets. */
+
+ snap_deps ();
+
+ /* Convert old-style suffix rules to pattern rules. It is important to
+ do this before installing the built-in pattern rules below, so that
+ makefile-specified suffix rules take precedence over built-in pattern
+ rules. */
+
+ convert_to_pattern ();
+
+ /* Install the default implicit pattern rules.
+ This used to be done before reading the makefiles.
+ But in that case, built-in pattern rules were in the chain
+ before user-defined ones, so they matched first. */
+
+ install_default_implicit_rules ();
+
+ /* Compute implicit rule limits. */
+
+ count_implicit_rule_limits ();
+
+ /* Construct the listings of directories in VPATH lists. */
+
+ build_vpath_lists ();
+
+ /* Mark files given with -o flags as very old and as having been updated
+ already, and files given with -W flags as brand new (time-stamp as far
+ as possible into the future). If restarts is set we'll do -W later. */
+
+ if (old_files != 0)
+ {
+ const char **p;
+ for (p = old_files->list; *p != 0; ++p)
+ {
+ struct file *f = enter_file (*p);
+ f->last_mtime = f->mtime_before_update = OLD_MTIME;
+ f->updated = 1;
+ f->update_status = us_success;
+ f->command_state = cs_finished;
+ }
+ }
+
+ if (!restarts && new_files != 0)
+ {
+ const char **p;
+ for (p = new_files->list; *p != 0; ++p)
+ {
+ struct file *f = enter_file (*p);
+ f->last_mtime = f->mtime_before_update = NEW_MTIME;
+ }
+ }
+
+ /* Initialize the remote job module. */
+ remote_setup ();
+
+ /* Dump any output we've collected. */
+
+ OUTPUT_UNSET ();
+ output_close (&make_sync);
+
+ if (read_files != 0)
+ {
+ /* Update any makefiles if necessary. */
+
+ FILE_TIMESTAMP *makefile_mtimes = 0;
+ unsigned int mm_idx = 0;
+ char **aargv = NULL;
+ const char **nargv;
+ int nargc;
+ enum update_status status;
+
+ DB (DB_BASIC, (_("Updating makefiles....\n")));
+
+ /* Remove any makefiles we don't want to try to update.
+ Also record the current modtimes so we can compare them later. */
+ {
+ register struct goaldep *d, *last;
+ last = 0;
+ d = read_files;
+ while (d != 0)
+ {
+ struct file *f = d->file;
+ if (f->double_colon)
+ for (f = f->double_colon; f != NULL; f = f->prev)
+ {
+ if (f->deps == 0 && f->cmds != 0)
+ {
+ /* This makefile is a :: target with commands, but
+ no dependencies. So, it will always be remade.
+ This might well cause an infinite loop, so don't
+ try to remake it. (This will only happen if
+ your makefiles are written exceptionally
+ stupidly; but if you work for Athena, that's how
+ you write your makefiles.) */
+
+ DB (DB_VERBOSE,
+ (_("Makefile '%s' might loop; not remaking it.\n"),
+ f->name));
+
+ if (last == 0)
+ read_files = d->next;
+ else
+ last->next = d->next;
+
+ /* Free the storage. */
+ free_goaldep (d);
+
+ d = last == 0 ? read_files : last->next;
+
+ break;
+ }
+ }
+
+ if (f == NULL || !f->double_colon)
+ {
+ makefile_mtimes = xrealloc (makefile_mtimes,
+ (mm_idx+1)
+ * sizeof (FILE_TIMESTAMP));
+ makefile_mtimes[mm_idx++] = file_mtime_no_search (d->file);
+ last = d;
+ d = d->next;
+ }
+ }
+ }
+
+ /* Set up 'MAKEFLAGS' specially while remaking makefiles. */
+ define_makeflags (1, 1);
+
+ {
+ int orig_db_level = db_level;
+
+ if (! ISDB (DB_MAKEFILES))
+ db_level = DB_NONE;
+
+ rebuilding_makefiles = 1;
+ status = update_goal_chain (read_files);
+ rebuilding_makefiles = 0;
+
+ db_level = orig_db_level;
+ }
+
+ switch (status)
+ {
+ case us_question:
+ /* The only way this can happen is if the user specified -q and asked
+ for one of the makefiles to be remade as a target on the command
+ line. Since we're not actually updating anything with -q we can
+ treat this as "did nothing". */
+
+ case us_none:
+ /* Did nothing. */
+ break;
+
+ case us_failed:
+ /* Failed to update. Figure out if we care. */
+ {
+ /* Nonzero if any makefile was successfully remade. */
+ int any_remade = 0;
+ /* Nonzero if any makefile we care about failed
+ in updating or could not be found at all. */
+ int any_failed = 0;
+ unsigned int i;
+ struct goaldep *d;
+
+ for (i = 0, d = read_files; d != 0; ++i, d = d->next)
+ {
+ if (d->file->updated)
+ {
+ /* This makefile was updated. */
+ if (d->file->update_status == us_success)
+ {
+ /* It was successfully updated. */
+ any_remade |= (file_mtime_no_search (d->file)
+ != makefile_mtimes[i]);
+ }
+ else if (! (d->flags & RM_DONTCARE))
+ {
+ FILE_TIMESTAMP mtime;
+ /* The update failed and this makefile was not
+ from the MAKEFILES variable, so we care. */
+ OS (error, NILF, _("Failed to remake makefile '%s'."),
+ d->file->name);
+ mtime = file_mtime_no_search (d->file);
+ any_remade |= (mtime != NONEXISTENT_MTIME
+ && mtime != makefile_mtimes[i]);
+ makefile_status = MAKE_FAILURE;
+ }
+ }
+ else
+ /* This makefile was not found at all. */
+ if (! (d->flags & RM_DONTCARE))
+ {
+ const char *dnm = dep_name (d);
+ size_t l = strlen (dnm);
+
+ /* This is a makefile we care about. See how much. */
+ if (d->flags & RM_INCLUDED)
+ /* An included makefile. We don't need to die, but we
+ do want to complain. */
+ error (NILF, l,
+ _("Included makefile '%s' was not found."), dnm);
+ else
+ {
+ /* A normal makefile. We must die later. */
+ error (NILF, l,
+ _("Makefile '%s' was not found"), dnm);
+ any_failed = 1;
+ }
+ }
+ }
+ /* Reset this to empty so we get the right error message below. */
+ read_files = 0;
+
+ if (any_remade)
+ goto re_exec;
+ if (any_failed)
+ die (MAKE_FAILURE);
+ break;
+ }
+
+ case us_success:
+ re_exec:
+ /* Updated successfully. Re-exec ourselves. */
+
+ remove_intermediates (0);
+
+ if (print_data_base_flag)
+ print_data_base ();
+
+ clean_jobserver (0);
+
+ if (makefiles != 0)
+ {
+ /* These names might have changed. */
+ int i, j = 0;
+ for (i = 1; i < argc; ++i)
+ if (strneq (argv[i], "-f", 2)) /* XXX */
+ {
+ if (argv[i][2] == '\0')
+ /* This cast is OK since we never modify argv. */
+ argv[++i] = (char *) makefiles->list[j];
+ else
+ argv[i] = xstrdup (concat (2, "-f", makefiles->list[j]));
+ ++j;
+ }
+ }
+
+ /* Add -o option for the stdin temporary file, if necessary. */
+ nargc = argc;
+ if (stdin_nm)
+ {
+ void *m = xmalloc ((nargc + 2) * sizeof (char *));
+ aargv = m;
+ memcpy (aargv, argv, argc * sizeof (char *));
+ aargv[nargc++] = xstrdup (concat (2, "-o", stdin_nm));
+ aargv[nargc] = 0;
+ nargv = m;
+ }
+ else
+ nargv = (const char**)argv;
+
+ if (directories != 0 && directories->idx > 0)
+ {
+ int bad = 1;
+ if (directory_before_chdir != 0)
+ {
+ if (chdir (directory_before_chdir) < 0)
+ perror_with_name ("chdir", "");
+ else
+ bad = 0;
+ }
+ if (bad)
+ O (fatal, NILF,
+ _("Couldn't change back to original directory."));
+ }
+
+ ++restarts;
+
+ if (ISDB (DB_BASIC))
+ {
+ const char **p;
+ printf (_("Re-executing[%u]:"), restarts);
+ for (p = nargv; *p != 0; ++p)
+ printf (" %s", *p);
+ putchar ('\n');
+ fflush (stdout);
+ }
+
+#ifndef _AMIGA
+ {
+ char **p;
+ for (p = environ; *p != 0; ++p)
+ {
+ if (strneq (*p, MAKELEVEL_NAME "=", MAKELEVEL_LENGTH+1))
+ {
+ *p = alloca (40);
+ sprintf (*p, "%s=%u", MAKELEVEL_NAME, makelevel);
+#ifdef VMS
+ vms_putenv_symbol (*p);
+#endif
+ }
+ else if (strneq (*p, "MAKE_RESTARTS=", CSTRLEN ("MAKE_RESTARTS=")))
+ {
+ *p = alloca (40);
+ sprintf (*p, "MAKE_RESTARTS=%s%u",
+ OUTPUT_IS_TRACED () ? "-" : "", restarts);
+ restarts = 0;
+ }
+ }
+ }
+#else /* AMIGA */
+ {
+ char buffer[256];
+
+ sprintf (buffer, "%u", makelevel);
+ SetVar (MAKELEVEL_NAME, buffer, -1, GVF_GLOBAL_ONLY);
+
+ sprintf (buffer, "%s%u", OUTPUT_IS_TRACED () ? "-" : "", restarts);
+ SetVar ("MAKE_RESTARTS", buffer, -1, GVF_GLOBAL_ONLY);
+ restarts = 0;
+ }
+#endif
+
+ /* If we didn't set the restarts variable yet, add it. */
+ if (restarts)
+ {
+ char *b = alloca (40);
+ sprintf (b, "MAKE_RESTARTS=%s%u",
+ OUTPUT_IS_TRACED () ? "-" : "", restarts);
+ putenv (b);
+ }
+
+ fflush (stdout);
+ fflush (stderr);
+
+#ifdef _AMIGA
+ exec_command (nargv);
+ exit (0);
+#elif defined (__EMX__)
+ {
+ /* It is not possible to use execve() here because this
+ would cause the parent process to be terminated with
+ exit code 0 before the child process has been terminated.
+ Therefore it may be the best solution simply to spawn the
+ child process including all file handles and to wait for its
+ termination. */
+ int pid;
+ int r;
+ pid = child_execute_job (NULL, 1, nargv, environ);
+
+ /* is this loop really necessary? */
+ do {
+ pid = wait (&r);
+ } while (pid <= 0);
+ /* use the exit code of the child process */
+ exit (WIFEXITED(r) ? WEXITSTATUS(r) : EXIT_FAILURE);
+ }
+#else
+#ifdef SET_STACK_SIZE
+ /* Reset limits, if necessary. */
+ if (stack_limit.rlim_cur)
+ setrlimit (RLIMIT_STACK, &stack_limit);
+#endif
+# if !defined(WINDOWS32) || !defined(CONFIG_NEW_WIN_CHILDREN)
+ exec_command ((char **)nargv, environ);
+# else
+ MkWinChildReExecMake ((char **)nargv, environ);
+# endif
+#endif
+ free (aargv);
+ break;
+ }
+
+ /* Free the makefile mtimes. */
+ free (makefile_mtimes);
+ }
+
+ /* Set up 'MAKEFLAGS' again for the normal targets. */
+ define_makeflags (1, 0);
+
+ /* Set always_make_flag if -B was given. */
+ always_make_flag = always_make_set;
+
+ /* If restarts is set we haven't set up -W files yet, so do that now. */
+ if (restarts && new_files != 0)
+ {
+ const char **p;
+ for (p = new_files->list; *p != 0; ++p)
+ {
+ struct file *f = enter_file (*p);
+ f->last_mtime = f->mtime_before_update = NEW_MTIME;
+ }
+ }
+
+ /* If there is a temp file from reading a makefile from stdin, get rid of
+ it now. */
+ if (stdin_nm && unlink (stdin_nm) < 0 && errno != ENOENT)
+ perror_with_name (_("unlink (temporary file): "), stdin_nm);
+
+ /* If there were no command-line goals, use the default. */
+ if (goals == 0)
+ {
+ char *p;
+
+ if (default_goal_var->recursive)
+ p = variable_expand (default_goal_var->value);
+ else
+ {
+ p = variable_buffer_output (variable_buffer, default_goal_var->value,
+ strlen (default_goal_var->value));
+ *p = '\0';
+ p = variable_buffer;
+ }
+
+ if (*p != '\0')
+ {
+ struct file *f = lookup_file (p);
+
+ /* If .DEFAULT_GOAL is a non-existent target, enter it into the
+ table and let the standard logic sort it out. */
+ if (f == 0)
+ {
+ struct nameseq *ns;
+
+ ns = PARSE_SIMPLE_SEQ (&p, struct nameseq);
+ if (ns)
+ {
+ /* .DEFAULT_GOAL should contain one target. */
+ if (ns->next != 0)
+ O (fatal, NILF,
+ _(".DEFAULT_GOAL contains more than one target"));
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ f = enter_file (strcache_add (ns->name));
+#else
+ f = enter_file (ns->name);
+#endif
+
+ ns->name = 0; /* It was reused by enter_file(). */
+ free_ns_chain (ns);
+ }
+ }
+
+ if (f)
+ {
+ goals = alloc_goaldep ();
+ goals->file = f;
+ }
+ }
+ }
+ else
+ lastgoal->next = 0;
+
+
+ if (!goals)
+ {
+ if (read_files == 0)
+ O (fatal, NILF, _("No targets specified and no makefile found"));
+
+ O (fatal, NILF, _("No targets"));
+ }
+
+ /* Update the goals. */
+
+ DB (DB_BASIC, (_("Updating goal targets....\n")));
+
+ {
+ switch (update_goal_chain (goals))
+ {
+ case us_none:
+ /* Nothing happened. */
+ /* FALLTHROUGH */
+ case us_success:
+ /* Keep the previous result. */
+ break;
+ case us_question:
+ /* We are under -q and would run some commands. */
+ makefile_status = MAKE_TROUBLE;
+ break;
+ case us_failed:
+ /* Updating failed. POSIX.2 specifies exit status >1 for this; */
+ makefile_status = MAKE_FAILURE;
+ break;
+ }
+
+ /* If we detected some clock skew, generate one last warning */
+ if (clock_skew_detected)
+ O (error, NILF,
+ _("warning: Clock skew detected. Your build may be incomplete."));
+
+ MAKE_STATS_2(if (uStartTick) printf("main ticks elapsed: %llu\n", (unsigned long long)(CURRENT_CLOCK_TICK() - uStartTick)) );
+ /* Exit. */
+ die (makefile_status);
+ }
+
+ /* NOTREACHED */
+ exit (MAKE_SUCCESS);
+}
+
+/* Parsing of arguments, decoding of switches. */
+
+static char options[1 + sizeof (switches) / sizeof (switches[0]) * 3];
+static struct option long_options[(sizeof (switches) / sizeof (switches[0])) +
+ (sizeof (long_option_aliases) /
+ sizeof (long_option_aliases[0]))];
+
+/* Fill in the string and vector for getopt. */
+static void
+init_switches (void)
+{
+ char *p;
+ unsigned int c;
+ unsigned int i;
+
+ if (options[0] != '\0')
+ /* Already done. */
+ return;
+
+ p = options;
+
+ /* Return switch and non-switch args in order, regardless of
+ POSIXLY_CORRECT. Non-switch args are returned as option 1. */
+ *p++ = '-';
+
+ for (i = 0; switches[i].c != '\0'; ++i)
+ {
+ long_options[i].name = (switches[i].long_name == 0 ? "" :
+ switches[i].long_name);
+ long_options[i].flag = 0;
+ long_options[i].val = switches[i].c;
+ if (short_option (switches[i].c))
+ *p++ = switches[i].c;
+ switch (switches[i].type)
+ {
+ case flag:
+ case flag_off:
+ case ignore:
+ long_options[i].has_arg = no_argument;
+ break;
+
+ case string:
+ case strlist:
+ case filename:
+ case positive_int:
+ case floating:
+ if (short_option (switches[i].c))
+ *p++ = ':';
+ if (switches[i].noarg_value != 0)
+ {
+ if (short_option (switches[i].c))
+ *p++ = ':';
+ long_options[i].has_arg = optional_argument;
+ }
+ else
+ long_options[i].has_arg = required_argument;
+ break;
+ }
+ }
+ *p = '\0';
+ for (c = 0; c < (sizeof (long_option_aliases) /
+ sizeof (long_option_aliases[0]));
+ ++c)
+ long_options[i++] = long_option_aliases[c];
+ long_options[i].name = 0;
+}
+
+
+/* Non-option argument. It might be a variable definition. */
+static void
+handle_non_switch_argument (const char *arg, int env)
+{
+ struct variable *v;
+
+ if (arg[0] == '-' && arg[1] == '\0')
+ /* Ignore plain '-' for compatibility. */
+ return;
+
+#ifdef VMS
+ {
+ /* VMS DCL quoting can result in foo="bar baz" showing up here.
+ Need to remove the double quotes from the value. */
+ char * eq_ptr;
+ char * new_arg;
+ eq_ptr = strchr (arg, '=');
+ if ((eq_ptr != NULL) && (eq_ptr[1] == '"'))
+ {
+ int len;
+ int seg1;
+ int seg2;
+ len = strlen(arg);
+ new_arg = alloca(len);
+ seg1 = eq_ptr - arg + 1;
+ strncpy(new_arg, arg, (seg1));
+ seg2 = len - seg1 - 1;
+ strncpy(&new_arg[seg1], &eq_ptr[2], seg2);
+ new_arg[seg1 + seg2] = 0;
+ if (new_arg[seg1 + seg2 - 1] == '"')
+ new_arg[seg1 + seg2 - 1] = 0;
+ arg = new_arg;
+ }
+ }
+#endif
+ v = try_variable_definition (0, arg IF_WITH_VALUE_LENGTH_PARAM(NULL), o_command, 0);
+ if (v != 0)
+ {
+ /* It is indeed a variable definition. If we don't already have this
+ one, record a pointer to the variable for later use in
+ define_makeflags. */
+ struct command_variable *cv;
+
+ for (cv = command_variables; cv != 0; cv = cv->next)
+ if (cv->variable == v)
+ break;
+
+ if (! cv)
+ {
+ cv = xmalloc (sizeof (*cv));
+ cv->variable = v;
+ cv->next = command_variables;
+ command_variables = cv;
+ }
+ }
+ else if (! env)
+ {
+ /* Not an option or variable definition; it must be a goal
+ target! Enter it as a file and add it to the dep chain of
+ goals. */
+ struct file *f = enter_file (strcache_add (expand_command_line_file (arg)));
+ f->cmd_target = 1;
+
+ if (goals == 0)
+ {
+ goals = alloc_goaldep ();
+ lastgoal = goals;
+ }
+ else
+ {
+ lastgoal->next = alloc_goaldep ();
+ lastgoal = lastgoal->next;
+ }
+
+ lastgoal->file = f;
+
+ {
+ /* Add this target name to the MAKECMDGOALS variable. */
+ struct variable *gv;
+ const char *value;
+
+ gv = lookup_variable (STRING_SIZE_TUPLE ("MAKECMDGOALS"));
+ if (gv == 0)
+ value = f->name;
+ else
+ {
+ /* Paste the old and new values together */
+ unsigned int oldlen, newlen;
+ char *vp;
+
+ oldlen = strlen (gv->value);
+ newlen = strlen (f->name);
+ vp = alloca (oldlen + 1 + newlen + 1);
+ memcpy (vp, gv->value, oldlen);
+ vp[oldlen] = ' ';
+ memcpy (&vp[oldlen + 1], f->name, newlen + 1);
+ value = vp;
+ }
+ define_variable_cname ("MAKECMDGOALS", value, o_default, 0);
+ }
+ }
+}
+
+/* Print a nice usage method. */
+
+static void
+print_usage (int bad)
+{
+ const char *const *cpp;
+ FILE *usageto;
+
+ if (print_version_flag)
+ print_version ();
+
+ usageto = bad ? stderr : stdout;
+
+ fprintf (usageto, _("Usage: %s [options] [target] ...\n"), program);
+
+ for (cpp = usage; *cpp; ++cpp)
+ fputs (_(*cpp), usageto);
+
+#ifdef KMK
+ if (!remote_description || *remote_description == '\0')
+ fprintf (usageto, _("\nThis program is built for %s/%s/%s [" __DATE__ " " __TIME__ "]\n"),
+ KBUILD_HOST, KBUILD_HOST_ARCH, KBUILD_HOST_CPU);
+ else
+ fprintf (usageto, _("\nThis program is built for %s/%s/%s (%s) [" __DATE__ " " __TIME__ "]\n"),
+ KBUILD_HOST, KBUILD_HOST_ARCH, KBUILD_HOST_CPU, remote_description);
+#else /* !KMK */
+ if (!remote_description || *remote_description == '\0')
+ fprintf (usageto, _("\nThis program built for %s\n"), make_host);
+ else
+ fprintf (usageto, _("\nThis program built for %s (%s)\n"),
+ make_host, remote_description);
+#endif /* !KMK */
+
+ fprintf (usageto, _("Report bugs to <bug-make@gnu.org>\n"));
+}
+
+/* Decode switches from ARGC and ARGV.
+ They came from the environment if ENV is nonzero. */
+
+static void
+decode_switches (int argc, const char **argv, int env)
+{
+ int bad = 0;
+ register const struct command_switch *cs;
+ register struct stringlist *sl;
+ register int c;
+
+ /* getopt does most of the parsing for us.
+ First, get its vectors set up. */
+
+ init_switches ();
+
+ /* Let getopt produce error messages for the command line,
+ but not for options from the environment. */
+ opterr = !env;
+ /* Reset getopt's state. */
+ optind = 0;
+
+ while (optind < argc)
+ {
+ const char *coptarg;
+
+ /* Parse the next argument. */
+ c = getopt_long (argc, (char*const*)argv, options, long_options, NULL);
+ coptarg = optarg;
+ if (c == EOF)
+ /* End of arguments, or "--" marker seen. */
+ break;
+ else if (c == 1)
+ /* An argument not starting with a dash. */
+ handle_non_switch_argument (coptarg, env);
+ else if (c == '?')
+ /* Bad option. We will print a usage message and die later.
+ But continue to parse the other options so the user can
+ see all he did wrong. */
+ bad = 1;
+ else
+ for (cs = switches; cs->c != '\0'; ++cs)
+ if (cs->c == c)
+ {
+ /* Whether or not we will actually do anything with
+ this switch. We test this individually inside the
+ switch below rather than just once outside it, so that
+ options which are to be ignored still consume args. */
+ int doit = !env || cs->env;
+
+ switch (cs->type)
+ {
+ default:
+ abort ();
+
+ case ignore:
+ break;
+
+ case flag:
+ case flag_off:
+ if (doit)
+ *(int *) cs->value_ptr = cs->type == flag;
+ break;
+
+ case string:
+ case strlist:
+ case filename:
+ if (!doit)
+ break;
+
+ if (! coptarg)
+ coptarg = xstrdup (cs->noarg_value);
+ else if (*coptarg == '\0')
+ {
+ char opt[2] = "c";
+ const char *op = opt;
+
+ if (short_option (cs->c))
+ opt[0] = cs->c;
+ else
+ op = cs->long_name;
+
+ error (NILF, strlen (op),
+ _("the '%s%s' option requires a non-empty string argument"),
+ short_option (cs->c) ? "-" : "--", op);
+ bad = 1;
+ break;
+ }
+
+ if (cs->type == string)
+ {
+ char **val = (char **)cs->value_ptr;
+ free (*val);
+ *val = xstrdup (coptarg);
+ break;
+ }
+
+ sl = *(struct stringlist **) cs->value_ptr;
+ if (sl == 0)
+ {
+ sl = xmalloc (sizeof (struct stringlist));
+ sl->max = 5;
+ sl->idx = 0;
+ sl->list = xmalloc (5 * sizeof (char *));
+ *(struct stringlist **) cs->value_ptr = sl;
+ }
+ else if (sl->idx == sl->max - 1)
+ {
+ sl->max += 5;
+ /* MSVC erroneously warns without a cast here. */
+ sl->list = xrealloc ((void *)sl->list,
+ sl->max * sizeof (char *));
+ }
+ if (cs->type == filename)
+ sl->list[sl->idx++] = expand_command_line_file (coptarg);
+ else
+ sl->list[sl->idx++] = xstrdup (coptarg);
+ sl->list[sl->idx] = 0;
+ break;
+
+ case positive_int:
+ /* See if we have an option argument; if we do require that
+ it's all digits, not something like "10foo". */
+ if (coptarg == 0 && argc > optind)
+ {
+ const char *cp;
+ for (cp=argv[optind]; ISDIGIT (cp[0]); ++cp)
+ ;
+ if (cp[0] == '\0')
+ coptarg = argv[optind++];
+ }
+
+ if (!doit)
+ break;
+
+ if (coptarg)
+ {
+ int i = atoi (coptarg);
+ const char *cp;
+
+ /* Yes, I realize we're repeating this in some cases. */
+ for (cp = coptarg; ISDIGIT (cp[0]); ++cp)
+ ;
+
+ if (i < 1 || cp[0] != '\0')
+ {
+ error (NILF, 0,
+ _("the '-%c' option requires a positive integer argument"),
+ cs->c);
+ bad = 1;
+ }
+ else
+ *(unsigned int *) cs->value_ptr = i;
+ }
+ else
+ *(unsigned int *) cs->value_ptr
+ = *(unsigned int *) cs->noarg_value;
+ break;
+
+#ifndef NO_FLOAT
+ case floating:
+ if (coptarg == 0 && optind < argc
+ && (ISDIGIT (argv[optind][0]) || argv[optind][0] == '.'))
+ coptarg = argv[optind++];
+
+ if (doit)
+ *(double *) cs->value_ptr
+ = (coptarg != 0 ? atof (coptarg)
+ : *(double *) cs->noarg_value);
+
+ break;
+#endif
+ }
+
+ /* We've found the switch. Stop looking. */
+ break;
+ }
+ }
+
+ /* There are no more options according to getting getopt, but there may
+ be some arguments left. Since we have asked for non-option arguments
+ to be returned in order, this only happens when there is a "--"
+ argument to prevent later arguments from being options. */
+ while (optind < argc)
+ handle_non_switch_argument (argv[optind++], env);
+
+ if (!env && (bad || print_usage_flag))
+ {
+ print_usage (bad);
+ die (bad ? MAKE_FAILURE : MAKE_SUCCESS);
+ }
+
+ /* If there are any options that need to be decoded do it now. */
+ decode_debug_flags ();
+ decode_output_sync_flags ();
+
+#if defined (WINDOWS32) && defined (CONFIG_NEW_WIN_CHILDREN)
+ /* validate the job object mode value . */
+ if (win_job_object_mode == NULL)
+ win_job_object_mode = xstrdup ("login");
+ else if ( strcmp (win_job_object_mode, "none") != 0
+ && strcmp (win_job_object_mode, "login") != 0
+ && strcmp (win_job_object_mode, "root") != 0
+ && strcmp (win_job_object_mode, "each") != 0)
+ OS (fatal, NILF, _("unknown job object mode '%s'"), win_job_object_mode);
+#endif
+}
+
+/* Decode switches from environment variable ENVAR (which is LEN chars long).
+ We do this by chopping the value into a vector of words, prepending a
+ dash to the first word if it lacks one, and passing the vector to
+ decode_switches. */
+
+static void
+decode_env_switches (const char *envar, unsigned int len)
+{
+ char *varref = alloca (2 + len + 2);
+ char *value, *p, *buf;
+ int argc;
+ const char **argv;
+
+ /* Get the variable's value. */
+ varref[0] = '$';
+ varref[1] = '(';
+ memcpy (&varref[2], envar, len);
+ varref[2 + len] = ')';
+ varref[2 + len + 1] = '\0';
+ value = variable_expand (varref);
+
+ /* Skip whitespace, and check for an empty value. */
+ NEXT_TOKEN (value);
+ len = strlen (value);
+ if (len == 0)
+ return;
+
+ /* Allocate a vector that is definitely big enough. */
+ argv = alloca ((1 + len + 1) * sizeof (char *));
+
+ /* getopt will look at the arguments starting at ARGV[1].
+ Prepend a spacer word. */
+ argv[0] = 0;
+ argc = 1;
+
+ /* We need a buffer to copy the value into while we split it into words
+ and unquote it. Set up in case we need to prepend a dash later. */
+ buf = alloca (1 + len + 1);
+ buf[0] = '-';
+ p = buf+1;
+ argv[argc] = p;
+ while (*value != '\0')
+ {
+ if (*value == '\\' && value[1] != '\0')
+ ++value; /* Skip the backslash. */
+ else if (ISBLANK (*value))
+ {
+ /* End of the word. */
+ *p++ = '\0';
+ argv[++argc] = p;
+ do
+ ++value;
+ while (ISBLANK (*value));
+ continue;
+ }
+ *p++ = *value++;
+ }
+ *p = '\0';
+ argv[++argc] = 0;
+ assert (p < buf + len + 2);
+
+ if (argv[1][0] != '-' && strchr (argv[1], '=') == 0)
+ /* The first word doesn't start with a dash and isn't a variable
+ definition, so add a dash. */
+ argv[1] = buf;
+
+ /* Parse those words. */
+ decode_switches (argc, argv, 1);
+}
+
+/* Quote the string IN so that it will be interpreted as a single word with
+ no magic by decode_env_switches; also double dollar signs to avoid
+ variable expansion in make itself. Write the result into OUT, returning
+ the address of the next character to be written.
+ Allocating space for OUT twice the length of IN is always sufficient. */
+
+static char *
+quote_for_env (char *out, const char *in)
+{
+ while (*in != '\0')
+ {
+ if (*in == '$')
+ *out++ = '$';
+ else if (ISBLANK (*in) || *in == '\\')
+ *out++ = '\\';
+ *out++ = *in++;
+ }
+
+ return out;
+}
+
+/* Define the MAKEFLAGS and MFLAGS variables to reflect the settings of the
+ command switches. Include options with args if ALL is nonzero.
+ Don't include options with the 'no_makefile' flag set if MAKEFILE. */
+
+static struct variable *
+define_makeflags (int all, int makefile)
+{
+#ifdef KMK
+ static const char ref[] = "$(KMK_OVERRIDES)";
+#else
+ static /*<- bird*/ const char ref[] = "$(MAKEOVERRIDES)";
+#endif
+ static /*<- bird*/ const char posixref[] = "$(-*-command-variables-*-)";
+ static /*<- bird*/ const char evalref[] = "$(-*-eval-flags-*-)";
+ const struct command_switch *cs;
+ char *flagstring;
+ char *p;
+
+ /* We will construct a linked list of 'struct flag's describing
+ all the flags which need to go in MAKEFLAGS. Then, once we
+ know how many there are and their lengths, we can put them all
+ together in a string. */
+
+ struct flag
+ {
+ struct flag *next;
+ const struct command_switch *cs;
+ const char *arg;
+ };
+ struct flag *flags = 0;
+ struct flag *last = 0;
+ unsigned int flagslen = 0;
+#define ADD_FLAG(ARG, LEN) \
+ do { \
+ struct flag *new = alloca (sizeof (struct flag)); \
+ new->cs = cs; \
+ new->arg = (ARG); \
+ new->next = 0; \
+ if (! flags) \
+ flags = new; \
+ else \
+ last->next = new; \
+ last = new; \
+ if (new->arg == 0) \
+ /* Just a single flag letter: " -x" */ \
+ flagslen += 3; \
+ else \
+ /* " -xfoo", plus space to escape "foo". */ \
+ flagslen += 1 + 1 + 1 + (3 * (LEN)); \
+ if (!short_option (cs->c)) \
+ /* This switch has no single-letter version, so we use the long. */ \
+ flagslen += 2 + strlen (cs->long_name); \
+ } while (0)
+
+ for (cs = switches; cs->c != '\0'; ++cs)
+ if (cs->toenv && (!makefile || !cs->no_makefile))
+ switch (cs->type)
+ {
+ case ignore:
+ break;
+
+ case flag:
+ case flag_off:
+ if ((!*(int *) cs->value_ptr) == (cs->type == flag_off)
+ && (cs->default_value == 0
+ || *(int *) cs->value_ptr != *(int *) cs->default_value))
+ ADD_FLAG (0, 0);
+ break;
+
+ case positive_int:
+ if (all)
+ {
+ if ((cs->default_value != 0
+ && (*(unsigned int *) cs->value_ptr
+ == *(unsigned int *) cs->default_value)))
+ break;
+ else if (cs->noarg_value != 0
+ && (*(unsigned int *) cs->value_ptr ==
+ *(unsigned int *) cs->noarg_value))
+ ADD_FLAG ("", 0); /* Optional value omitted; see below. */
+ else
+ {
+ char *buf = alloca (30);
+ sprintf (buf, "%u", *(unsigned int *) cs->value_ptr);
+ ADD_FLAG (buf, strlen (buf));
+ }
+ }
+ break;
+
+#ifndef NO_FLOAT
+ case floating:
+ if (all)
+ {
+ if (cs->default_value != 0
+ && (*(double *) cs->value_ptr
+ == *(double *) cs->default_value))
+ break;
+ else if (cs->noarg_value != 0
+ && (*(double *) cs->value_ptr
+ == *(double *) cs->noarg_value))
+ ADD_FLAG ("", 0); /* Optional value omitted; see below. */
+ else
+ {
+ char *buf = alloca (100);
+ sprintf (buf, "%g", *(double *) cs->value_ptr);
+ ADD_FLAG (buf, strlen (buf));
+ }
+ }
+ break;
+#endif
+
+ case string:
+ if (all)
+ {
+ p = *((char **)cs->value_ptr);
+ if (p)
+ ADD_FLAG (p, strlen (p));
+ }
+ break;
+
+ case filename:
+ case strlist:
+ if (all)
+ {
+ struct stringlist *sl = *(struct stringlist **) cs->value_ptr;
+ if (sl != 0)
+ {
+ unsigned int i;
+ for (i = 0; i < sl->idx; ++i)
+ ADD_FLAG (sl->list[i], strlen (sl->list[i]));
+ }
+ }
+ break;
+
+ default:
+ abort ();
+ }
+
+#undef ADD_FLAG
+
+ /* Four more for the possible " -- ", plus variable references. */
+ flagslen += 4 + CSTRLEN (posixref) + 1 + CSTRLEN (evalref) + 1;
+
+ /* Construct the value in FLAGSTRING.
+ We allocate enough space for a preceding dash and trailing null. */
+ flagstring = alloca (1 + flagslen + 1);
+ memset (flagstring, '\0', 1 + flagslen + 1);
+ p = flagstring;
+
+ /* Start with a dash, for MFLAGS. */
+ *p++ = '-';
+
+ /* Add simple options as a group. */
+ while (flags != 0 && !flags->arg && short_option (flags->cs->c))
+ {
+ *p++ = flags->cs->c;
+ flags = flags->next;
+ }
+
+ /* Now add more complex flags: ones with options and/or long names. */
+ while (flags)
+ {
+ *p++ = ' ';
+ *p++ = '-';
+
+ /* Add the flag letter or name to the string. */
+ if (short_option (flags->cs->c))
+ *p++ = flags->cs->c;
+ else
+ {
+ /* Long options require a double-dash. */
+ *p++ = '-';
+ strcpy (p, flags->cs->long_name);
+ p += strlen (p);
+ }
+ /* An omitted optional argument has an ARG of "". */
+ if (flags->arg && flags->arg[0] != '\0')
+ {
+ if (!short_option (flags->cs->c))
+ /* Long options require '='. */
+ *p++ = '=';
+ p = quote_for_env (p, flags->arg);
+ }
+ flags = flags->next;
+ }
+
+ /* If no flags at all, get rid of the initial dash. */
+ if (p == &flagstring[1])
+ {
+ flagstring[0] = '\0';
+ p = flagstring;
+ }
+
+#ifdef KMK
+ /* Define MFLAGS before appending variable definitions. Omit an initial
+ empty dash. Since MFLAGS is not parsed for flags, there is no reason to
+ override any makefile redefinition. */
+ define_variable_cname ("MFLAGS",
+ flagstring + (flagstring[0] == '-' && flagstring[1] == ' ' ? 2 : 0),
+ o_env, 1);
+#endif /* !KMK */
+
+ /* Write a reference to -*-eval-flags-*-, which contains all the --eval
+ flag options. */
+ if (eval_strings)
+ {
+ *p++ = ' ';
+ memcpy (p, evalref, CSTRLEN (evalref));
+ p += CSTRLEN (evalref);
+ }
+
+ if (all && command_variables)
+ {
+ /* Write a reference to $(MAKEOVERRIDES), which contains all the
+ command-line variable definitions. Separate the variables from the
+ switches with a "--" arg. */
+
+ strcpy (p, " -- ");
+ p += 4;
+
+ /* Copy in the string. */
+ if (posix_pedantic)
+ {
+ memcpy (p, posixref, CSTRLEN (posixref));
+ p += CSTRLEN (posixref);
+ }
+ else
+ {
+ memcpy (p, ref, CSTRLEN (ref));
+ p += CSTRLEN (ref);
+ }
+ }
+
+ /* If there is a leading dash, omit it. */
+ if (flagstring[0] == '-')
+ ++flagstring;
+
+#ifdef KMK
+ /* Provide simple access to some of the options. */
+ {
+ char val[32];
+ sprintf (val, "%u", job_slots ? job_slots : master_job_slots);
+ define_variable_cname ("KMK_OPTS_JOBS", val, o_default, 1);
+ sprintf (val, "%u", default_job_slots);
+ define_variable_cname ("KMK_OPTS_JOBS_DEFAULT", val, o_default, 1);
+ define_variable_cname ("KMK_OPTS_KEEP_GOING", keep_going_flag ? "1" : "0", o_default, 1);
+ define_variable_cname ("KMK_OPTS_JUST_PRINT", just_print_flag ? "1" : "0", o_default, 1);
+ define_variable_cname ("KMK_OPTS_PRETTY_COMMAND_PRINTING",
+ pretty_command_printing ? "1" : "0", o_default, 1);
+ sprintf (val, "%u", process_priority);
+ define_variable_cname ("KMK_OPTS_PRORITY", val, o_default, 1);
+ sprintf (val, "%u", process_affinity);
+ define_variable_cname ("KMK_OPTS_AFFINITY", val, o_default, 1);
+# if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+ define_variable_cname ("KMK_OPTS_STATISTICS", make_expensive_statistics ? "1" : "0",
+ o_default, 1);
+# endif
+# ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ sprintf (val, "%u", print_time_min);
+ define_variable_cname ("KMK_OPTS_PRINT_TIME", val, o_default, 1);
+# endif
+ }
+#endif
+
+ /* This used to use o_env, but that lost when a makefile defined MAKEFLAGS.
+ Makefiles set MAKEFLAGS to add switches, but we still want to redefine
+ its value with the full set of switches. Then we used o_file, but that
+ lost when users added -e, causing a previous MAKEFLAGS env. var. to take
+ precedence over the new one. Of course, an override or command
+ definition will still take precedence. */
+#ifdef KMK
+ return define_variable_cname ("KMK_FLAGS", flagstring,
+ env_overrides ? o_env_override : o_file, 1);
+#else
+ return define_variable_cname ("MAKEFLAGS", flagstring,
+ env_overrides ? o_env_override : o_file, 1);
+#endif
+}
+
+/* Print version information. */
+
+static void
+print_version (void)
+{
+ static int printed_version = 0;
+
+ const char *precede = print_data_base_flag ? "# " : "";
+
+ if (printed_version)
+ /* Do it only once. */
+ return;
+
+#ifdef KMK
+ printf ("%skmk - kBuild version %d.%d.%d (r%u)\n\
+\n",
+ precede, KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR,
+ KBUILD_VERSION_PATCH, KBUILD_SVN_REV);
+
+ printf("%sBased on GNU Make %s:\n", precede, version_string);
+
+#else /* !KMK */
+ printf ("%sGNU Make %s\n", precede, version_string);
+
+ if (!remote_description || *remote_description == '\0')
+ printf (_("%sBuilt for %s\n"), precede, make_host);
+ else
+ printf (_("%sBuilt for %s (%s)\n"),
+ precede, make_host, remote_description);
+#endif /* !KMK */
+
+ /* Print this untranslated. The coding standards recommend translating the
+ (C) to the copyright symbol, but this string is going to change every
+ year, and none of the rest of it should be translated (including the
+ word "Copyright"), so it hardly seems worth it. */
+
+ printf ("%sCopyright (C) 1988-2016 Free Software Foundation, Inc.\n",
+ precede);
+
+ printf (_("%sLicense GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
+%sThis is free software: you are free to change and redistribute it.\n\
+%sThere is NO WARRANTY, to the extent permitted by law.\n"),
+ precede, precede, precede);
+
+#ifdef KMK
+ printf ("\n\
+%skBuild modifications:\n\
+%s Copyright (c) 2005-2018 knut st. osmundsen.\n\
+\n\
+%skmkbuiltin commands derived from *BSD sources:\n\
+%s Copyright (c) 1983 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994\n\
+%s The Regents of the University of California. All rights reserved.\n\
+%s Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>\n",
+ precede, precede, precede, precede, precede, precede);
+
+# ifdef KBUILD_PATH
+ printf (_("\n\
+%sKBUILD_PATH: '%s' (default '%s')\n\
+%sKBUILD_BIN_PATH: '%s' (default '%s')\n\
+\n"),
+ precede, get_kbuild_path(), KBUILD_PATH,
+ precede, get_kbuild_bin_path(), KBUILD_BIN_PATH);
+# else /* !KBUILD_PATH */
+ printf ("\n\
+%sKBUILD_PATH: '%s'\n\
+%sKBUILD_BIN_PATH: '%s'\n\
+\n",
+ precede, get_kbuild_path(),
+ precede, get_kbuild_bin_path());
+# endif /* !KBUILD_PATH */
+
+ if (!remote_description || *remote_description == '\0')
+ printf (_("%sThis program is a %s build, built for %s/%s/%s [" __DATE__ " " __TIME__ "]\n\n"),
+ precede, KBUILD_TYPE, KBUILD_HOST, KBUILD_HOST_ARCH, KBUILD_HOST_CPU);
+ else
+ printf (_("%sThis program is a %s build, built for %s/%s/%s (%s) [" __DATE__ " " __TIME__ "]\n\n"),
+ precede, KBUILD_TYPE, KBUILD_HOST, KBUILD_HOST_ARCH, KBUILD_HOST_CPU, remote_description);
+
+#endif /* KMK */
+
+ printed_version = 1;
+
+ /* Flush stdout so the user doesn't have to wait to see the
+ version information while make thinks about things. */
+ fflush (stdout);
+}
+
+/* Print a bunch of information about this and that. */
+
+static void
+print_data_base (void)
+{
+ time_t when = time ((time_t *) 0);
+
+ print_version ();
+
+ printf (_("\n# Make data base, printed on %s"), ctime (&when));
+
+ print_variable_data_base ();
+ print_dir_data_base ();
+ print_rule_data_base ();
+ print_file_data_base ();
+ print_vpath_data_base ();
+#ifdef KMK
+ print_kbuild_data_base ();
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ strcache_print_stats ("#");
+#else
+ strcache2_print_stats_all ("#");
+#endif
+#ifdef CONFIG_WITH_ALLOC_CACHES
+ alloccache_print_all ();
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ kmk_cc_print_stats ();
+#endif
+
+ when = time ((time_t *) 0);
+ printf (_("\n# Finished Make data base on %s\n"), ctime (&when));
+}
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+
+static void
+print_stats ()
+{
+ time_t when;
+
+ when = time ((time_t *) 0);
+ printf (_("\n# Make statistics, printed on %s"), ctime (&when));
+
+ /* Allocators: */
+# ifdef CONFIG_WITH_COMPILER
+ kmk_cc_print_stats ();
+# endif
+# ifndef CONFIG_WITH_STRCACHE2
+ strcache_print_stats ("#");
+# else
+ strcache2_print_stats_all ("#");
+# endif
+# ifdef CONFIG_WITH_ALLOC_CACHES
+ alloccache_print_all ();
+# endif
+ print_heap_stats ();
+
+ /* Make stuff: */
+ print_variable_stats ();
+ print_file_stats ();
+ print_dir_stats ();
+# ifdef KMK
+ print_kbuild_define_stats ();
+# endif
+# ifdef CONFIG_WITH_KMK_BUILTIN_STATS
+ kmk_builtin_print_stats (stdout, "# ");
+# endif
+# ifdef CONFIG_WITH_COMPILER
+ kmk_cc_print_stats ();
+# endif
+
+ when = time ((time_t *) 0);
+ printf (_("\n# Finished Make statistics on %s\n"), ctime (&when));
+}
+#endif /* CONFIG_WITH_PRINT_STATS_SWITCH */
+
+static void
+clean_jobserver (int status)
+{
+ /* Sanity: have we written all our jobserver tokens back? If our
+ exit status is 2 that means some kind of syntax error; we might not
+ have written all our tokens so do that now. If tokens are left
+ after any other error code, that's bad. */
+
+ if (jobserver_enabled() && jobserver_tokens)
+ {
+ if (status != 2)
+ ON (error, NILF,
+ "INTERNAL: Exiting with %u jobserver tokens (should be 0)!",
+ jobserver_tokens);
+ else
+ /* Don't write back the "free" token */
+ while (--jobserver_tokens)
+ jobserver_release (0);
+ }
+
+
+ /* Sanity: If we're the master, were all the tokens written back? */
+
+ if (master_job_slots)
+ {
+ /* We didn't write one for ourself, so start at 1. */
+ unsigned int tokens = 1 + jobserver_acquire_all ();
+
+ if (tokens != master_job_slots)
+ ONN (error, NILF,
+ "INTERNAL: Exiting with %u jobserver tokens available; should be %u!",
+ tokens, master_job_slots);
+
+ reset_jobserver ();
+ }
+}
+
+/* Exit with STATUS, cleaning up as necessary. */
+
+void
+#ifdef KMK
+die_with_job_output (int status, struct output *out)
+#else
+die (int status)
+#endif
+{
+ static char dying = 0;
+#ifdef KMK
+ static char need_2nd_error = 0;
+ static char need_2nd_error_output = 0;
+#endif
+
+ if (!dying)
+ {
+ int err;
+
+ dying = 1;
+
+ if (print_version_flag)
+ print_version ();
+
+#ifdef KMK
+ /* Flag 2nd error message. */
+ if (status != 0
+ && ( job_slots_used > 0
+ || print_data_base_flag
+ || print_stats_flag))
+ {
+ need_2nd_error = 1;
+ need_2nd_error_output = job_slots_used >= 2
+ && out != NULL
+ && out != &make_sync;
+ if (need_2nd_error_output)
+ output_metered = 0;
+ }
+#endif /* KMK */
+
+ /* Wait for children to die. */
+ err = (status != 0);
+ while (job_slots_used > 0)
+ reap_children (1, err);
+
+ /* Let the remote job module clean up its state. */
+ remote_cleanup ();
+
+ /* Remove the intermediate files. */
+ remove_intermediates (0);
+
+ if (print_data_base_flag)
+ print_data_base ();
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+ if (print_stats_flag)
+ print_stats ();
+#endif
+ if (verify_flag)
+ verify_file_data_base ();
+
+#ifdef NDEBUG /* bird: Don't waste time on debug sanity checks. */
+ if (print_data_base_flag || db_level)
+#endif
+ verify_file_data_base ();
+
+ clean_jobserver (status);
+
+ if (output_context)
+ {
+ /* die() might be called in a recipe output context due to an
+ $(error ...) function. */
+ output_close (output_context);
+
+ if (output_context != &make_sync)
+ output_close (&make_sync);
+
+ OUTPUT_UNSET ();
+ }
+
+ output_close (NULL);
+
+ /* Try to move back to the original directory. This is essential on
+ MS-DOS (where there is really only one process), and on Unix it
+ puts core files in the original directory instead of the -C
+ directory. Must wait until after remove_intermediates(), or unlinks
+ of relative pathnames fail. */
+ if (directory_before_chdir != 0)
+ {
+ /* If it fails we don't care: shut up GCC. */
+ int _x UNUSED;
+ _x = chdir (directory_before_chdir);
+ }
+
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+ if (print_time_min != -1)
+ {
+ big_int elapsed = nano_timestamp () - make_start_ts;
+ if (elapsed >= print_time_min * BIG_INT_C(1000000000))
+ {
+ char buf[64];
+ format_elapsed_nano (buf, sizeof (buf), elapsed);
+ message (1, strlen (buf), _("%*s"), print_time_width, buf);
+ }
+ }
+#endif
+ }
+
+#ifdef KMK
+ /* The failure might be lost in a -j <lots> run, so mention the
+ failure again before exiting. */
+ if (need_2nd_error != 0)
+ ON (error, NILF, _("*** Exiting with status %d"), status);
+ if (out)
+ {
+ out->dont_truncate = 0;
+ if (need_2nd_error_output && output_metered > 20)
+ output_dump (out);
+ else
+ output_reset (out);
+ output_close (out);
+ }
+#endif
+
+ exit (status);
+}
+
+#ifdef KMK
+void die (int status)
+{
+ die_with_job_output (status, NULL);
+}
+#endif
diff --git a/src/kmk/maintMakefile b/src/kmk/maintMakefile
new file mode 100644
index 0000000..d817efc
--- /dev/null
+++ b/src/kmk/maintMakefile
@@ -0,0 +1,414 @@
+# Maintainer-only makefile segment. This contains things that are relevant
+# only if you have the full copy of the GNU make sources from the Git
+# tree, not a dist copy.
+
+BUGLIST := bug-make@gnu.org
+
+# These are related to my personal setup.
+GPG_FINGERPRINT := 6338B6D4
+
+# SRCROOTDIR is just a handy location to keep source files in
+SRCROOTDIR ?= $(HOME)/src
+
+# Where the gnulib project has been locally cloned
+GNULIBDIR ?= $(SRCROOTDIR)/gnulib
+
+# Where to put the CVS checkout of the GNU web repository
+GNUWEBDIR ?= $(SRCROOTDIR)/gnu-www
+
+# Where to put the CVS checkout of the GNU make web repository
+MAKEWEBDIR ?= $(SRCROOTDIR)/make/make-web
+
+# We like mondo-warnings!
+ifeq ($(KBUILD_TARGET),openbsd) # bird
+AM_CFLAGS += -Wall -Wwrite-strings -Wshadow -Wpointer-arith -Wbad-function-cast
+else
+AM_CFLAGS += -Wall -Wwrite-strings -Wextra -Wdeclaration-after-statement -Wshadow -Wpointer-arith -Wbad-function-cast
+endif
+
+MAKE_MAINTAINER_MODE := -DMAKE_MAINTAINER_MODE
+AM_CPPFLAGS += $(MAKE_MAINTAINER_MODE)
+
+# I want this one but I have to wait for the const cleanup!
+# -Wwrite-strings
+
+# Find the glob source files... this might be dangerous, but we're maintainers!
+globsrc := $(wildcard glob/*.c)
+globhdr := $(wildcard glob/*.h)
+
+TEMPLATES = README README.DOS README.W32 README.OS2 \
+ config.ami configh.dos config.h.W32 config.h-vms
+MTEMPLATES = Makefile.DOS SMakefile
+
+# These are built as a side-effect of the dist rule
+#all-am: $(TEMPLATES) $(MTEMPLATES) build.sh.in
+
+# Create preprocessor output files--GCC specific!
+%.i : %.c
+ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) -E -dD -o $@ $<
+
+# General rule for turning a .template into a regular file.
+#
+$(TEMPLATES) : % : %.template Makefile
+ rm -f $@
+ sed -e 's@%VERSION%@$(VERSION)@g' \
+ -e 's@%PACKAGE%@$(PACKAGE)@g' \
+ $< > $@
+ chmod a-w $@
+
+# Construct Makefiles by adding on dependencies, etc.
+#
+$(MTEMPLATES) : % : %.template .dep_segment Makefile
+ rm -f $@
+ sed -e 's@%VERSION%@$(VERSION)@g' \
+ -e 's@%PROGRAMS%@$(bin_PROGRAMS)@g' \
+ -e 's@%SOURCES%@$(filter-out remote-%,$(make_SOURCES)) remote-$$(REMOTE).c@g' \
+ -e 's@%OBJECTS%@$(filter-out remote-%,$(make_OBJECTS)) remote-$$(REMOTE).o@g' \
+ -e 's@%GLOB_SOURCES%@$(globsrc) $(globhdr)@g' \
+ -e 's@%GLOB_OBJECTS%@$(globsrc:glob/%.c=%.o)@g' \
+ $< > $@
+ echo >>$@; echo '# --------------- DEPENDENCIES' >>$@; echo '#' >>$@; \
+ cat $(word 2,$^) >>$@
+ chmod a-w $@
+
+NMakefile: NMakefile.template .dep_segment Makefile
+ rm -f $@
+ cp $< $@
+ echo >>$@; echo '# --------------- DEPENDENCIES' >>$@; echo '#' >>$@; \
+ sed 's/^\([^ ]*\)\.o:/$$(OUTDIR)\/\1.obj:/' $(word 2,$^) >>$@
+ chmod a-w $@
+
+# Construct build.sh.in
+#
+build.sh.in: build.template Makefile
+ rm -f $@
+ sed -e 's@%objs%@$(patsubst %.o,%.$${OBJEXT},$(filter-out remote-%,$(make_OBJECTS)))@g' \
+ -e 's@%globobjs%@$(patsubst %.c,%.$${OBJEXT},$(globsrc))@g' \
+ $< > $@
+ chmod a-w+x $@
+
+
+# Use automake to build a dependency list file, for "foreign" makefiles like
+# Makefile.DOS.
+#
+# Automake used to have a --generate-deps flag, but it's gone now, so we have
+# to do it ourselves.
+#
+DEP_FILES := $(wildcard $(DEPDIR)/*.Po)
+.dep_segment: Makefile.am maintMakefile $(DEP_FILES)
+ rm -f $@
+ (for f in $(DEPDIR)/*.Po; do \
+ echo ""; \
+ echo "# $$f"; \
+ sed -e '/^[^:]*\.[ch] *:/d' \
+ -e 's, /usr/[^ ]*,,g' \
+ -e 's, $(srcdir)/, ,g' \
+ -e '/^ *\\$$/d' \
+ -e '/^ *$$/d' \
+ < $$f; \
+ done) > $@
+
+# Cleaning
+
+GIT := git
+
+# git-clean: Clean all "ignored" files. Leave untracked files.
+# git-very-clean: Clean all files that aren't stored in source control.
+
+.PHONY: git-clean git-very-clean
+git-clean:
+ -$(GIT) clean -fdX
+git-very-clean: git-clean
+ -$(GIT) clean -fd
+
+
+
+## ---------------------- ##
+## Generating ChangeLog. ##
+## ---------------------- ##
+
+gl2cl-date := 2013-10-10
+gl2cl := $(GNULIBDIR)/build-aux/gitlog-to-changelog
+
+# Rebuild the changelog whenever a new commit is added
+ChangeLog: .check-git-HEAD
+ if test -f '$(gl2cl)'; then \
+ '$(gl2cl)' --since='$(gl2cl-date)' > '$@'; \
+ else \
+ echo "WARNING: $(gl2cl) is not available. No $@ generated."; \
+ fi
+
+.PHONY: .check-git-HEAD
+.check-git-HEAD:
+ sha="`git rev-parse HEAD`"; \
+ [ -f '$@' ] && [ "`cat '$@' 2>/dev/null`" = "$$sha" ] \
+ || echo "$$sha" > '$@'
+
+
+## ---------------- ##
+## Updating files. ##
+## ---------------- ##
+RSYNC = rsync -Lrtvz
+WGET = wget --passive-ftp -np -nv
+ftp-gnu = ftp://ftp.gnu.org/gnu
+
+move_if_change = if test -r $(target) && cmp -s $(target).t $(target); then \
+ echo $(target) is unchanged; rm -f $(target).t; \
+ else \
+ mv -f $(target).t $(target); \
+ fi
+
+# ------------------- #
+# Updating PO files. #
+# ------------------- #
+
+# PO archive mirrors --- Be careful; some might not be fully populated!
+# ftp://ftp.unex.es/pub/gnu-i18n/po/maint/
+# http://translation.sf.net/maint/
+# ftp://tiger.informatik.hu-berlin.de/pub/po/maint/
+
+po_wget_flags = --recursive --level=1 --no-directories --no-check-certificate
+po_repo = http://translationproject.org/latest/$(PACKAGE)
+po_sync = translationproject.org::tp/latest/$(PACKAGE)/
+
+.PHONY: do-po-update po-update
+do-po-update:
+ tmppo="/tmp/po-$(PACKAGE)-$(VERSION).$$$$" \
+ && rm -rf "$$tmppo" \
+ && mkdir "$$tmppo" \
+ && $(RSYNC) $(po_sync) "$$tmppo" \
+ && cp "$$tmppo"/*.po $(top_srcdir)/po \
+ && rm -rf "$$tmppo"
+ cd po && $(MAKE) update-po
+ $(MAKE) po-check
+
+po-update:
+ [ -d "po" ] && $(MAKE) do-po-update
+
+# -------------------------- #
+# Updating GNU build files. #
+# -------------------------- #
+
+# The following pseudo table associates a local directory and a URL
+# with each of the files that belongs to some other package and is
+# regularly updated from the specified URL.
+
+cvs-url = http://savannah.gnu.org/cgi-bin/viewcvs/~checkout~
+git-url = http://git.savannah.gnu.org/cgit
+target = $(patsubst get-%,%,$@)
+
+config-url = $(git-url)/config.git/plain/$(patsubst get-config/%,%,$@)
+get-config/config.guess get-config/config.sub:
+ @echo $(WGET) $(config-url) -O $(target) \
+ && $(WGET) $(config-url) -O $(target).t \
+ && $(move_if_change)
+
+gnulib-url = $(git-url)/gnulib.git/plain/build-aux/$(patsubst get-config/%,%,$@)
+get-config/texinfo.tex:
+ @echo $(WGET) $(gnulib-url) -O $(target) \
+ && $(WGET) $(gnulib-url) -O $(target).t \
+ && $(move_if_change)
+
+gnustandards-url = $(cvs-url)/gnustandards/gnustandards/$(patsubst get-doc/%,%,$@)
+get-doc/make-stds.texi get-doc/fdl.texi:
+ @echo $(WGET) $(gnustandards-url) -O $(target) \
+ && $(WGET) $(gnustandards-url) -O $(target).t \
+ && $(move_if_change)
+
+.PHONY: scm-update
+scm-update: get-config/texinfo.tex get-config/config.guess get-config/config.sub get-doc/make-stds.texi get-doc/fdl.texi
+
+
+# --------------------- #
+# Updating everything. #
+# --------------------- #
+
+.PHONY: update
+update: po-update scm-update
+
+
+# ---------------------------------- #
+# Alternative configuration checks. #
+# ---------------------------------- #
+
+.PHONY: check-alt-config
+check-alt-config: \
+ checkcfg.--disable-job-server \
+ checkcfg.--disable-load \
+ checkcfg.--without-guile \
+ checkcfg.CPPFLAGS^-DNO_OUTPUT_SYNC \
+ checkcfg.CPPFLAGS^-DNO_ARCHIVES
+
+# Trick GNU make so it doesn't run the submake as a recursive make.
+NR_MAKE = $(MAKE)
+
+# Check builds both with build.sh and with make
+checkcfg.%: distdir
+ @echo "Building $@ (output in checkcfg.$*.log)"
+ @exec >'checkcfg.$*.log' 2>&1; \
+ rm -rf $(distdir)/_build \
+ && mkdir $(distdir)/_build \
+ && cd $(distdir)/_build \
+ && echo "Testing configure with $(subst ^,=,$*)" \
+ && ../configure --srcdir=.. $(subst ^,=,$*) \
+ $(AM_DISTCHECK_CONFIGURE_FLAGS) $(DISTCHECK_CONFIGURE_FLAGS) \
+ CFLAGS='$(AM_CFLAGS)' \
+ && ./build.sh \
+ && ./make $(AM_MAKEFLAGS) check \
+ && rm -f *.o make \
+ && $(NR_MAKE) $(AM_MAKEFLAGS) \
+ && ./make $(AM_MAKEFLAGS) check
+
+
+## --------------- ##
+## Sanity checks. ##
+## --------------- ##
+
+# Before we build a distribution be sure we run our local checks
+#distdir: local-check
+
+.PHONY: local-check po-check changelog-check
+
+# Checks that don't require Git. Run 'changelog-check' last as
+# previous test may reveal problems requiring new ChangeLog entries.
+local-check: po-check changelog-check
+
+# copyright-check writable-files
+
+changelog-check:
+ if head $(top_srcdir)/ChangeLog | grep 'Version $(VERSION)' >/dev/null; then \
+ :; \
+ else \
+ echo "$(VERSION) not in ChangeLog" 1>&2; \
+ exit 1; \
+ fi
+
+# Verify that all source files using _() are listed in po/POTFILES.in.
+# Ignore makeint.h; it defines _().
+po-check:
+ if test -f po/POTFILES.in; then \
+ grep '^[^#]' po/POTFILES.in | sort > $@-1; \
+ $(PERL) -wn -e 'if (/\b_\(/) { $$ARGV eq "./makeint.h" || print "$$ARGV\n" and close ARGV }' `find . -name '*.[ch]'` | sed 's,^\./,,' | sort > $@-2; \
+ diff -u $@-1 $@-2 || exit 1; \
+ rm -f $@-1 $@-2; \
+ fi
+
+
+## --------------- ##
+## Generate docs. ##
+## --------------- ##
+
+.PHONY: update-makeweb gendocs
+
+CVS = cvs
+
+makeweb-repo = $(USER)@cvs.sv.gnu.org:/web/make
+gnuweb-repo = :pserver:anonymous@cvs.sv.gnu.org:/web/www
+gnuweb-dir = www/server/standards
+
+# Get the GNU make web page boilerplate etc.
+update-makeweb:
+ [ -d '$(MAKEWEBDIR)' ] || mkdir -p '$(MAKEWEBDIR)'
+ [ -d '$(MAKEWEBDIR)'/CVS ] \
+ && { cd '$(MAKEWEBDIR)' && $(CVS) update; } \
+ || { mkdir -p '$(dir $(MAKEWEBDIR))' && cd '$(dir $(MAKEWEBDIR))' \
+ && $(CVS) -d $(makeweb-repo) co -d '$(notdir $(MAKEWEBDIR))' make; }
+
+# Get the GNU web page boilerplate etc.
+update-gnuweb:
+ [ -d '$(GNUWEBDIR)' ] || mkdir -p '$(GNUWEBDIR)'
+ [ -d '$(GNUWEBDIR)/$(gnuweb-dir)'/CVS ] \
+ && { cd '$(GNUWEBDIR)/$(gnuweb-dir)' && $(CVS) update; } \
+ || { cd '$(GNUWEBDIR)' && $(CVS) -d $(gnuweb-repo) co '$(gnuweb-dir)'; }
+
+gendocs: update-gnuweb update-makeweb
+ cp $(GNULIBDIR)/doc/gendocs_template doc
+ cd doc \
+ && rm -rf doc/manual \
+ && $(GNULIBDIR)/build-aux/gendocs.sh --email '$(BUGLIST)' \
+ make 'GNU Make Manual'
+ find '$(MAKEWEBDIR)'/manual \( -name CVS -prune \) -o \( -name '[!.]*' -type f -exec rm -f '{}' \; \)
+ cp -r doc/manual '$(MAKEWEBDIR)'
+ @echo 'Status of $(MAKEWEBDIR) repo:' && cd '$(MAKEWEBDIR)' \
+ && cvs -q -n update | grep -v '^M ' \
+ && echo '- cvs add <new files>' \
+ && echo '- cvs remove <deleted files>' \
+ && echo '- cvs commit' \
+ && echo '- cvs tag make-$(subst .,-,$(VERSION))'
+
+## ------------------------- ##
+## Make release targets. ##
+## ------------------------- ##
+
+.PHONY: tag-release
+tag-release:
+ case '$(VERSION)' in \
+ (*.*.9*) message=" candidate" ;; \
+ (*) message= ;; \
+ esac; \
+ $(GIT) tag -m "GNU Make release$$message $(VERSION)" -u '$(GPG_FINGERPRINT)' '$(VERSION)'
+
+## ------------------------- ##
+## GNU FTP upload artifacts. ##
+## ------------------------- ##
+
+# This target creates the upload artifacts.
+# Sign it with my key. If you don't have my key/passphrase then sorry,
+# you're SOL! :)
+
+GPG = gpg
+GPGFLAGS = -u $(GPG_FINGERPRINT)
+
+DIST_ARCHIVES_SIG = $(addsuffix .sig,$(DIST_ARCHIVES))
+DIST_ARCHIVES_DIRECTIVE = $(addsuffix .directive.asc,$(DIST_ARCHIVES))
+
+# A simple rule to test signing, etc.
+.PHONY: distsign
+distsign: $(DIST_ARCHIVES_SIG) $(DIST_ARCHIVES_DIRECTIVE)
+
+%.sig : %
+ @echo "Signing file '$<':"
+ $(GPG) $(GPGFLAGS) -o "$@" -b "$<"
+
+%.directive.asc: %
+ @echo "Creating directive file '$@':"
+ @( \
+ echo 'version: 1.1'; \
+ echo 'directory: make'; \
+ echo 'filename: $*'; \
+ echo 'comment: Official upload of GNU make version $(VERSION)'; \
+ ) > "$*.directive"
+ $(GPG) $(GPGFLAGS) -o "$@" --clearsign "$*.directive"
+ @rm -f "$*.directive"
+
+# Upload the artifacts
+
+FTPPUT = ncftpput
+gnu-upload-host = ftp-upload.gnu.org
+gnu-upload-dir = /incoming
+
+
+UPLOADS = upload-alpha upload-ftp
+.PHONY: $(UPLOADS)
+$(UPLOADS): $(DIST_ARCHIVES) $(DIST_ARCHIVES_SIG) $(DIST_ARCHIVES_DIRECTIVE)
+ $(FTPPUT) "$(gnu-upload-host)" "$(gnu-upload-dir)/$(@:upload-%=%)" $^
+
+
+# Rebuild Makefile.in if this file is modifed.
+Makefile.in: maintMakefile
+
+# Copyright (C) 1997-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/make.1 b/src/kmk/make.1
new file mode 100644
index 0000000..d4bd284
--- /dev/null
+++ b/src/kmk/make.1
@@ -0,0 +1,381 @@
+.TH MAKE 1 "28 February 2016" "GNU" "User Commands"
+.SH NAME
+make \- GNU make utility to maintain groups of programs
+.SH SYNOPSIS
+.B make
+[\fIOPTION\fR]... [\fITARGET\fR]...
+.SH DESCRIPTION
+.LP
+The
+.I make
+utility will determine automatically which pieces of a large program need to
+be recompiled, and issue the commands to recompile them. The manual describes
+the GNU implementation of
+.BR make ,
+which was written by Richard Stallman and Roland McGrath, and is currently
+maintained by Paul Smith. Our examples show C programs, since they are very
+common, but you can use
+.B make
+with any programming language whose compiler can be run with a shell command.
+In fact,
+.B make
+is not limited to programs. You can use it to describe any task where some
+files must be updated automatically from others whenever the others change.
+.LP
+To prepare to use
+.BR make ,
+you must write a file called the
+.I makefile
+that describes the relationships among files in your program, and the states
+the commands for updating each file. In a program, typically the executable
+file is updated from object files, which are in turn made by compiling source
+files.
+.LP
+Once a suitable makefile exists, each time you change some source files,
+this simple shell command:
+.sp 1
+.RS
+.B make
+.RE
+.sp 1
+suffices to perform all necessary recompilations.
+The
+.B make
+program uses the makefile description and the last-modification times of the
+files to decide which of the files need to be updated. For each of those
+files, it issues the commands recorded in the makefile.
+.LP
+.B make
+executes commands in the
+.I makefile
+to update one or more target
+.IR names ,
+where
+.I name
+is typically a program.
+If no
+.B \-f
+option is present,
+.B make
+will look for the makefiles
+.IR GNUmakefile ,
+.IR makefile ,
+and
+.IR Makefile ,
+in that order.
+.LP
+Normally you should call your makefile either
+.I makefile
+or
+.IR Makefile .
+(We recommend
+.I Makefile
+because it appears prominently near the beginning of a directory
+listing, right near other important files such as
+.IR README .)
+The first name checked,
+.IR GNUmakefile ,
+is not recommended for most makefiles. You should use this name if you have a
+makefile that is specific to GNU
+.BR make ,
+and will not be understood by other versions of
+.BR make .
+If
+.I makefile
+is '\-', the standard input is read.
+.LP
+.B make
+updates a target if it depends on prerequisite files
+that have been modified since the target was last modified,
+or if the target does not exist.
+.SH OPTIONS
+.sp 1
+.TP 0.5i
+\fB\-b\fR, \fB\-m\fR
+These options are ignored for compatibility with other versions of
+.BR make .
+.TP 0.5i
+\fB\-B\fR, \fB\-\-always\-make\fR
+Unconditionally make all targets.
+.TP 0.5i
+\fB\-C\fR \fIdir\fR, \fB\-\-directory\fR=\fIdir\fR
+Change to directory
+.I dir
+before reading the makefiles or doing anything else.
+If multiple
+.B \-C
+options are specified, each is interpreted relative to the
+previous one:
+.BR "\-C " /
+.BR "\-C " etc
+is equivalent to
+.BR "\-C " /etc.
+This is typically used with recursive invocations of
+.BR make .
+.TP 0.5i
+.B \-d
+Print debugging information in addition to normal processing.
+The debugging information says which files are being considered for
+remaking, which file-times are being compared and with what results,
+which files actually need to be remade, which implicit rules are
+considered and which are applied---everything interesting about how
+.B make
+decides what to do.
+.TP 0.5i
+.BI \-\-debug "[=FLAGS]"
+Print debugging information in addition to normal processing.
+If the
+.I FLAGS
+are omitted, then the behavior is the same as if
+.B \-d
+was specified.
+.I FLAGS
+may be
+.I a
+for all debugging output (same as using
+.BR \-d ),
+.I b
+for basic debugging,
+.I v
+for more verbose basic debugging,
+.I i
+for showing implicit rules,
+.I j
+for details on invocation of commands, and
+.I m
+for debugging while remaking makefiles. Use
+.I n
+to disable all previous debugging flags.
+.TP 0.5i
+\fB\-e\fR, \fB\-\-environment\-overrides\fR
+Give variables taken from the environment precedence
+over variables from makefiles.
+.TP 0.5i
+\fB\-f\fR \fIfile\fR, \fB\-\-file\fR=\fIfile\fR, \fB\-\-makefile\fR=\fIFILE\fR
+Use
+.I file
+as a makefile.
+.TP 0.5i
+\fB\-i\fR, \fB\-\-ignore\-errors\fR
+Ignore all errors in commands executed to remake files.
+.TP 0.5i
+\fB\-I\fR \fIdir\fR, \fB\-\-include\-dir\fR=\fIdir\fR
+Specifies a directory
+.I dir
+to search for included makefiles.
+If several
+.B \-I
+options are used to specify several directories, the directories are
+searched in the order specified.
+Unlike the arguments to other flags of
+.BR make ,
+directories given with
+.B \-I
+flags may come directly after the flag:
+.BI \-I dir
+is allowed, as well as
+.B \-I
+.IR dir .
+This syntax is allowed for compatibility with the C
+preprocessor's
+.B \-I
+flag.
+.TP 0.5i
+\fB\-j\fR [\fIjobs\fR], \fB\-\-jobs\fR[=\fIjobs\fR]
+Specifies the number of
+.I jobs
+(commands) to run simultaneously.
+If there is more than one
+.B \-j
+option, the last one is effective.
+If the
+.B \-j
+option is given without an argument,
+.BR make
+will not limit the number of jobs that can run simultaneously.
+.TP 0.5i
+\fB\-k\fR, \fB\-\-keep\-going\fR
+Continue as much as possible after an error.
+While the target that failed, and those that depend on it, cannot
+be remade, the other dependencies of these targets can be processed
+all the same.
+.TP 0.5i
+\fB\-l\fR [\fIload\fR], \fB\-\-load\-average\fR[=\fIload\fR]
+Specifies that no new jobs (commands) should be started if there are
+others jobs running and the load average is at least
+.I load
+(a floating-point number).
+With no argument, removes a previous load limit.
+.TP 0.5i
+\fB\-L\fR, \fB\-\-check\-symlink\-times\fR
+Use the latest mtime between symlinks and target.
+.TP 0.5i
+\fB\-n\fR, \fB\-\-just\-print\fR, \fB\-\-dry\-run\fR, \fB\-\-recon\fR
+Print the commands that would be executed, but do not execute them (except in
+certain circumstances).
+.TP 0.5i
+\fB\-o\fR \fIfile\fR, \fB\-\-old\-file\fR=\fIfile\fR, \fB\-\-assume\-old\fR=\fIfile\fR
+Do not remake the file
+.I file
+even if it is older than its dependencies, and do not remake anything
+on account of changes in
+.IR file .
+Essentially the file is treated as very old and its rules are ignored.
+.TP 0.5i
+\fB\-O\fR[\fItype\fR], \fB\-\-output\-sync\fR[=\fItype\fR]
+When running multiple jobs in parallel with \fB-j\fR, ensure the output of
+each job is collected together rather than interspersed with output from
+other jobs. If
+.I type
+is not specified or is
+.B target
+the output from the entire recipe for each target is grouped together. If
+.I type
+is
+.B line
+the output from each command line within a recipe is grouped together.
+If
+.I type
+is
+.B recurse
+output from an entire recursive make is grouped together. If
+.I type
+is
+.B none
+output synchronization is disabled.
+.TP 0.5i
+\fB\-p\fR, \fB\-\-print\-data\-base\fR
+Print the data base (rules and variable values) that results from
+reading the makefiles; then execute as usual or as otherwise
+specified.
+This also prints the version information given by the
+.B \-v
+switch (see below).
+To print the data base without trying to remake any files, use
+.IR "make \-p \-f/dev/null" .
+.TP 0.5i
+\fB\-q\fR, \fB\-\-question\fR
+``Question mode''.
+Do not run any commands, or print anything; just return an exit status
+that is zero if the specified targets are already up to date, nonzero
+otherwise.
+.TP 0.5i
+\fB\-r\fR, \fB\-\-no\-builtin\-rules\fR
+Eliminate use of the built\-in implicit rules.
+Also clear out the default list of suffixes for suffix rules.
+.TP 0.5i
+\fB\-R\fR, \fB\-\-no\-builtin\-variables\fR
+Don't define any built\-in variables.
+.TP 0.5i
+\fB\-s\fR, \fB\-\-silent\fR, \fB\-\-quiet\fR
+Silent operation; do not print the commands as they are executed.
+.TP 0.5i
+\fB\-S\fR, \fB\-\-no\-keep\-going\fR, \fB\-\-stop\fR
+Cancel the effect of the
+.B \-k
+option.
+This is never necessary except in a recursive
+.B make
+where
+.B \-k
+might be inherited from the top-level
+.B make
+via MAKEFLAGS or if you set
+.B \-k
+in MAKEFLAGS in your environment.
+.TP 0.5i
+\fB\-t\fR, \fB\-\-touch\fR
+Touch files (mark them up to date without really changing them)
+instead of running their commands.
+This is used to pretend that the commands were done, in order to fool
+future invocations of
+.BR make .
+.TP 0.5i
+.B \-\-trace
+Information about the disposition of each target is printed (why the target is
+being rebuilt and what commands are run to rebuild it).
+.TP 0.5i
+\fB\-v\fR, \fB\-\-version\fR
+Print the version of the
+.B make
+program plus a copyright, a list of authors and a notice that there
+is no warranty.
+.TP 0.5i
+\fB\-w\fR, \fB\-\-print\-directory\fR
+Print a message containing the working directory
+before and after other processing.
+This may be useful for tracking down errors from complicated nests of
+recursive
+.B make
+commands.
+.TP 0.5i
+.B \-\-no\-print\-directory
+Turn off
+.BR \-w ,
+even if it was turned on implicitly.
+.TP 0.5i
+\fB\-W\fR \fIfile\fR, \fB\-\-what\-if\fR=\fIfile\fR, \fB\-\-new\-file\fR=\fIfile\fR, \fB\-\-assume\-new\fR=\fIfile\fR
+Pretend that the target
+.I file
+has just been modified.
+When used with the
+.B \-n
+flag, this shows you what would happen if you were to modify that file.
+Without
+.BR \-n ,
+it is almost the same as running a
+.I touch
+command on the given file before running
+.BR make ,
+except that the modification time is changed only in the imagination of
+.BR make .
+.TP 0.5i
+.B \-\-warn\-undefined\-variables
+Warn when an undefined variable is referenced.
+.SH "EXIT STATUS"
+GNU
+.B make
+exits with a status of zero if all makefiles were successfully parsed
+and no targets that were built failed. A status of one will be returned
+if the
+.B \-q
+flag was used and
+.B make
+determines that a target needs to be rebuilt. A status of two will be
+returned if any errors were encountered.
+.SH "SEE ALSO"
+The full documentation for
+.B make
+is maintained as a Texinfo manual. If the
+.B info
+and
+.B make
+programs are properly installed at your site, the command
+.IP
+.B info make
+.PP
+should give you access to the complete manual.
+.SH BUGS
+See the chapter ``Problems and Bugs'' in
+.IR "The GNU Make Manual" .
+.SH AUTHOR
+This manual page contributed by Dennis Morse of Stanford University.
+Further updates contributed by Mike Frysinger. It has been reworked by Roland
+McGrath. Maintained by Paul Smith.
+.SH "COPYRIGHT"
+Copyright \(co 1992-1993, 1996-2016 Free Software Foundation, Inc.
+This file is part of
+.IR "GNU make" .
+.LP
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+.LP
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+.LP
+You should have received a copy of the GNU General Public License along with
+this program. If not, see
+.IR http://www.gnu.org/licenses/ .
diff --git a/src/kmk/make.lnk b/src/kmk/make.lnk
new file mode 100644
index 0000000..0d983bf
--- /dev/null
+++ b/src/kmk/make.lnk
@@ -0,0 +1,5 @@
+FROM LIB:cres.o "commands.o"+"job.o"+"dir.o"+"file.o"+"misc.o"+"main.o"+"read.o"+"remake.o"+"rule.o"+"implicit.o"+"default.o"+"variable.o"+"expand.o"+"function.o"+"vpath.o"+"version.o"+"ar.o"+"arscan.o"+"signame.o"+"remote-stub.o"+"getopt.o"+"getopt1.o"+"alloca.o"+"amiga.o"+"hash.o"+"strcache.o"+"output.o"
+TO "make.new"
+LIB glob/glob.lib LIB:sc.lib LIB:amiga.lib
+QUIET
+
diff --git a/src/kmk/make_msvc_net2003.sln b/src/kmk/make_msvc_net2003.sln
new file mode 100644
index 0000000..e993896
--- /dev/null
+++ b/src/kmk/make_msvc_net2003.sln
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 8.00
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "make_msvc.net2003", "make_msvc_net2003.vcproj", "{E96B5060-3240-4723-91C9-E64F1C877A04}"
+ ProjectSection(ProjectDependencies) = postProject
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfiguration) = preSolution
+ Debug = Debug
+ Release = Release
+ EndGlobalSection
+ GlobalSection(ProjectConfiguration) = postSolution
+ {E96B5060-3240-4723-91C9-E64F1C877A04}.Debug.ActiveCfg = Debug|Win32
+ {E96B5060-3240-4723-91C9-E64F1C877A04}.Debug.Build.0 = Debug|Win32
+ {E96B5060-3240-4723-91C9-E64F1C877A04}.Release.ActiveCfg = Release|Win32
+ {E96B5060-3240-4723-91C9-E64F1C877A04}.Release.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityAddIns) = postSolution
+ EndGlobalSection
+EndGlobal
diff --git a/src/kmk/make_msvc_net2003.vcproj b/src/kmk/make_msvc_net2003.vcproj
new file mode 100644
index 0000000..bcc2e8b
--- /dev/null
+++ b/src/kmk/make_msvc_net2003.vcproj
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="7.10"
+ Name="make_msvc.net2003"
+ ProjectGUID="{E96B5060-3240-4723-91C9-E64F1C877A04}"
+ Keyword="Win32Proj">
+ <Platforms>
+ <Platform
+ Name="Win32"/>
+ </Platforms>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="Debug"
+ IntermediateDirectory="Debug"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ Optimization="0"
+ AdditionalIncludeDirectories=".;w32/include;glob"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;HAVE_CONFIG_H=1;WINDOWS32=1"
+ MinimalRebuild="TRUE"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ ForceConformanceInForLoopScope="TRUE"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ SuppressStartupBanner="TRUE"
+ Detect64BitPortabilityProblems="FALSE"
+ DebugInformationFormat="4"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile="$(OutDir)/make_msvc.net2003.exe"
+ LinkIncremental="2"
+ GenerateDebugInformation="TRUE"
+ ProgramDatabaseFile="$(OutDir)/make_msvc.net2003.pdb"
+ SubSystem="1"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"
+ Description="Copying config.h.W32 to config.h"
+ CommandLine="if not exist config.h copy config.h.W32 config.h"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="Release"
+ IntermediateDirectory="Release"
+ ConfigurationType="1"
+ CharacterSet="2">
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=".;w32/include;glob"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;HAVE_CONFIG_H=1;WINDOWS32=1"
+ RuntimeLibrary="0"
+ ForceConformanceInForLoopScope="TRUE"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ Detect64BitPortabilityProblems="FALSE"
+ DebugInformationFormat="3"/>
+ <Tool
+ Name="VCCustomBuildTool"/>
+ <Tool
+ Name="VCLinkerTool"
+ OutputFile="$(OutDir)/make_msvc.net2003.exe"
+ LinkIncremental="1"
+ GenerateDebugInformation="TRUE"
+ SubSystem="1"
+ OptimizeReferences="2"
+ EnableCOMDATFolding="2"
+ TargetMachine="1"/>
+ <Tool
+ Name="VCMIDLTool"/>
+ <Tool
+ Name="VCPostBuildEventTool"/>
+ <Tool
+ Name="VCPreBuildEventTool"
+ Description="Copying config.h.W32 to config.h"
+ CommandLine="if not exist config.h copy config.h.W32 config.h"/>
+ <Tool
+ Name="VCPreLinkEventTool"/>
+ <Tool
+ Name="VCResourceCompilerTool"/>
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"/>
+ <Tool
+ Name="VCXMLDataGeneratorTool"/>
+ <Tool
+ Name="VCWebDeploymentTool"/>
+ <Tool
+ Name="VCManagedWrapperGeneratorTool"/>
+ <Tool
+ Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <Filter
+ Name="src"
+ Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
+ UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
+ <File
+ RelativePath=".\ar.c">
+ </File>
+ <File
+ RelativePath=".\arscan.c">
+ </File>
+ <File
+ RelativePath=".\commands.c">
+ </File>
+ <File
+ RelativePath=".\default.c">
+ </File>
+ <File
+ RelativePath=".\dir.c">
+ </File>
+ <File
+ RelativePath=".\expand.c">
+ </File>
+ <File
+ RelativePath=".\file.c">
+ </File>
+ <File
+ RelativePath=".\function.c">
+ </File>
+ <File
+ RelativePath=".\getloadavg.c">
+ </File>
+ <File
+ RelativePath=".\getopt.c">
+ </File>
+ <File
+ RelativePath=".\getopt1.c">
+ </File>
+
+ <File
+ RelativePath=".\guile.c">
+ </File>
+
+ <File
+ RelativePath=".\hash.c">
+ </File>
+ <File
+ RelativePath=".\strcache.c">
+ </File>
+ <File
+ RelativePath=".\implicit.c">
+ </File>
+ <File
+ RelativePath=".\job.c">
+ </File>
+ <File
+ RelativePath=".\load.c">
+ </File>
+ <File
+ RelativePath=".\output.c">
+ </File>
+ <File
+ RelativePath=".\main.c">
+ </File>
+ <File
+ RelativePath=".\misc.c">
+ </File>
+ <File
+ RelativePath=".\read.c">
+ </File>
+ <File
+ RelativePath=".\remake.c">
+ </File>
+ <File
+ RelativePath=".\remote-stub.c">
+ </File>
+ <File
+ RelativePath=".\rule.c">
+ </File>
+ <File
+ RelativePath=".\signame.c">
+ </File>
+ <File
+ RelativePath=".\variable.c">
+ </File>
+ <File
+ RelativePath=".\version.c">
+ </File>
+ <File
+ RelativePath=".\vpath.c">
+ </File>
+ <Filter
+ Name="w32"
+ Filter="">
+ <File
+ RelativePath=".\w32\compat\dirent.c">
+ </File>
+ <File
+ RelativePath=".\w32\compat\posixfcn.c">
+ </File>
+ <File
+ RelativePath=".\w32\subproc\misc.c">
+ <FileConfiguration
+ Name="Debug|Win32">
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)/$(InputName)1.obj"/>
+ </FileConfiguration>
+ <FileConfiguration
+ Name="Release|Win32">
+ <Tool
+ Name="VCCLCompilerTool"
+ ObjectFile="$(IntDir)/$(InputName)1.obj"/>
+ </FileConfiguration>
+ </File>
+ <File
+ RelativePath=".\w32\pathstuff.c">
+ </File>
+ <File
+ RelativePath=".\w32\w32os.c">
+ </File>
+ <File
+ RelativePath=".\w32\subproc\sub_proc.c">
+ </File>
+ <File
+ RelativePath=".\w32\subproc\w32err.c">
+ </File>
+ </Filter>
+ <Filter
+ Name="glob"
+ Filter="">
+ <File
+ RelativePath=".\glob\fnmatch.c">
+ </File>
+ <File
+ RelativePath=".\glob\glob.c">
+ </File>
+ </Filter>
+ </Filter>
+ <Filter
+ Name="include"
+ Filter="h;hpp;hxx;hm;inl;inc;xsd"
+ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}">
+ <File
+ RelativePath=".\commands.h">
+ </File>
+ <File
+ RelativePath=".\config.h">
+ </File>
+ <File
+ RelativePath=".\debug.h">
+ </File>
+ <File
+ RelativePath=".\dep.h">
+ </File>
+ <File
+ RelativePath=".\filedef.h">
+ </File>
+ <File
+ RelativePath=".\getopt.h">
+ </File>
+ <File
+ RelativePath=".\gettext.h">
+ </File>
+ <File
+ RelativePath=".\gmk-default.h">
+ </File>
+ <File
+ RelativePath=".\hash.h">
+ </File>
+ <File
+ RelativePath=".\job.h">
+ </File>
+ <File
+ RelativePath=".\output.h">
+ </File>
+ <File
+ RelativePath=".\makeint.h">
+ </File>
+ <File
+ RelativePath=".\rule.h">
+ </File>
+ <File
+ RelativePath=".\variable.h">
+ </File>
+ <File
+ RelativePath=".\vmsdir.h">
+ </File>
+ <Filter
+ Name="w32"
+ Filter="">
+ <File
+ RelativePath=".\w32\include\dirent.h">
+ </File>
+ <File
+ RelativePath=".\w32\include\pathstuff.h">
+ </File>
+ <File
+ RelativePath=".\w32\subproc\proc.h">
+ </File>
+ <File
+ RelativePath=".\w32\include\sub_proc.h">
+ </File>
+ <File
+ RelativePath=".\w32\include\w32err.h">
+ </File>
+ </Filter>
+ <Filter
+ Name="glob"
+ Filter="">
+ <File
+ RelativePath=".\glob\fnmatch.h">
+ </File>
+ <File
+ RelativePath=".\glob\glob.h">
+ </File>
+ </Filter>
+ </Filter>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/src/kmk/makefile.com b/src/kmk/makefile.com
new file mode 100644
index 0000000..f579695
--- /dev/null
+++ b/src/kmk/makefile.com
@@ -0,0 +1,166 @@
+$!
+$! Makefile.com - builds GNU Make for VMS
+$!
+$! P1 = LIST will provide compiler listings.
+$! P2 = DEBUG will build an image with debug information
+$! P3 = WALL will enable all warning messages (some are suppressed since
+$! one macro intentionally causes an error condition)
+$!
+$! In case of problems with the install you might contact me at
+$! zinser@decus.de (preferred) or zinser@sysdev.deutsche-boerse.com
+$
+$! hb
+$! But don't ask Martin Zinser about the lines, I added/changed.
+$! In case of an error do some cleanup
+$ on error then $ goto cleanup
+$! in case somebody set up her/his own symbol for cc
+$ set symbol/scope=(nolocal,noglobal)
+$!
+$! Just some general constants...
+$!
+$ true = 1
+$ false = 0
+$ tmpnam = "temp_" + f$getjpi("","pid")
+$ tt = tmpnam + ".txt"
+$ tc = tmpnam + ".c"
+$!
+$! Look for the compiler used
+$!
+$ lval = ""
+$ if f$search("SYS$SYSTEM:DECC$COMPILER.EXE").eqs.""
+$ then
+$ if f$trnlnm("SYS").eqs."" then def/nolog sys sys$library:
+$ ccopt = ""
+$ else
+$ ccopt = "/decc/prefix=(all,except=(globfree,glob))"
+$ if f$trnlnm("SYS").eqs.""
+$ then
+$ if f$trnlnm("DECC$LIBRARY_INCLUDE").nes.""
+$ then
+$ define sys decc$library_include:
+$ else
+$ if f$search("SYS$COMMON:[DECC$LIB.REFERENCE]DECC$RTLDEF.DIR").nes."" -
+ then lval = "SYS$COMMON:[DECC$LIB.REFERENCE.DECC$RTLDEF],"
+$ if f$search("SYS$COMMON:[DECC$LIB.REFERENCE]SYS$STARLET_C.DIR").nes."" -
+ then lval = lval+"SYS$COMMON:[DECC$LIB.REFERENCE.SYS$STARLET_C],"
+$ lval=lval+"SYS$LIBRARY:"
+$ define sys 'lval
+$ endif
+$ endif
+$ endif
+$!
+$!
+$ if (p1 .eqs. "LIST")
+$ then
+$ ccopt = ccopt + "/list/show=(expan,inclu)"
+$ endif
+$!
+$! Should we build a debug image
+$!
+$ if (p2.eqs."DEBUG")
+$ then
+$ ccopt = ccopt + "/noopt/debug"
+$ lopt = "/debug"
+$ else
+$ lopt = ""
+$ endif
+$!
+$! Do we want to see all warnings
+$!
+$ if (p3.nes."WALL")
+$ then
+$ gosub check_cc_qual
+$ endif
+$ filelist = "alloca ar arscan commands default dir expand file function " + -
+ "guile hash implicit job load main misc read remake " + -
+ "remote-stub rule output signame variable version " + -
+ "vmsfunctions vmsify vpath vms_progname vms_exit " + -
+ "vms_export_symbol [.glob]glob [.glob]fnmatch getopt1 " + -
+ "getopt strcache"
+$!
+$ copy config.h-vms config.h
+$ n=0
+$ open/write optf make.opt
+$ loop:
+$ cfile = f$elem(n," ",filelist)
+$ if cfile .eqs. " " then goto linkit
+$ write sys$output "Compiling ''cfile'..."
+$ call compileit 'cfile'
+$ n = n + 1
+$ goto loop
+$ linkit:
+$ close optf
+$ link/exe=make make.opt/opt'lopt
+$ goto cleanup
+$
+$ cleanup:
+$ if f$trnlnm("SYS").nes."" then $ deassign sys
+$ if f$trnlnm("OPTF").nes."" then $ close optf
+$ if f$search("make.opt").nes."" then $ del make.opt;*
+$ exit
+$!
+$!-----------------------------------------------------------------------------
+$!
+$! Check if this is a define relating to the properties of the C/C++
+$! compiler
+$!
+$CHECK_CC_QUAL:
+$ open/write tmpc 'tc
+$ ccqual = "/warn=(disable=questcompare)"
+$ write tmpc "#include <stdio.h>"
+$ write tmpc "unsigned int i = 1;"
+$ write tmpc "int main(){"
+$ write tmpc "if (i < 0){printf(""Mission impossible\n"");}}"
+$ close tmpc
+$ gosub cc_qual_check
+$ return
+$!
+$!-----------------------------------------------------------------------------
+$!
+$! Check for properties of C/C++ compiler
+$!
+$CC_QUAL_CHECK:
+$ cc_qual = false
+$ set message/nofac/noident/nosever/notext
+$ cc 'ccqual' 'tmpnam'
+$ if $status then cc_qual = true
+$ set message/fac/ident/sever/text
+$ delete/nolog 'tmpnam'.*;*
+$ if cc_qual then ccopt = ccopt + ccqual
+$ return
+$!-----------------------------------------------------------------------------
+$!
+$ compileit : subroutine
+$ ploc = f$locate("]",p1)
+$! filnam = p1
+$ if ploc .lt. f$length(p1)
+$ then
+$ objdir = f$extract(0, ploc+1, p1)
+$ write optf p1
+$ else
+$ objdir := []
+$ write optf objdir+p1
+$ endif
+$ cc'ccopt'/nested=none/include=([],[.glob])/obj='objdir' -
+ /define=("allocated_variable_expand_for_file=alloc_var_expand_for_file",-
+ "unlink=remove","HAVE_CONFIG_H","VMS") -
+ 'p1'
+$ exit
+$ endsubroutine : compileit
+$!
+$!-----------------------------------------------------------------------------
+$!Copyright (C) 1996-2016 Free Software Foundation, Inc.
+$!This file is part of GNU Make.
+$!
+$!GNU Make is free software; you can redistribute it and/or modify it under
+$!the terms of the GNU General Public License as published by the Free Software
+$!Foundation; either version 3 of the License, or (at your option) any later
+$!version.
+$!
+$!GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+$!WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+$!FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+$!details.
+$!
+$!You should have received a copy of the GNU General Public License along with
+$!this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/makefile.vms b/src/kmk/makefile.vms
new file mode 100644
index 0000000..37702d5
--- /dev/null
+++ b/src/kmk/makefile.vms
@@ -0,0 +1,180 @@
+# -*-Makefile-*- to build GNU make on VMS
+#
+# Copyright (C) 1996-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+# VMS extensions from GNU Make 3.60 imported by
+# Klaus Kämpf (kkaempf@rmi.de)
+# Modified for version 3.78.1 by Hartmut.Becker@compaq.com.
+# Modified for version 3.80 by zinser@decus.de
+# Modified for versions 3.81, 3.99.90 by Hartmut Becker
+
+CC = cc
+CP = copy
+
+%.obj: %.c
+ $(CC) $(CFLAGS)/obj=$@ $<
+#
+# Makefile for GNU Make
+#
+
+ifeq ($(CC),cc)
+cinclude = /nested=none/include=([],[.glob])
+cprefix = /prefix=(all,except=(glob,globfree))
+cwarn = /standard=relaxed/warn=(disable=questcompare)
+CFLAGS = $(defines) $(cinclude)$(cprefix)$(cwarn)
+else
+CFLAGS = $(defines) $(cinclude)
+endif
+#LDFLAGS = /deb
+LDFLAGS =
+
+ifeq ($(CC),cc)
+defines = /define=("unlink=remove","HAVE_CONFIG_H","VMS","allocated_variable_expand_for_file=alloc_var_expand_for_file")
+else
+ifeq ($(ARCH),VAX)
+defines = /define=("HAVE_CONFIG_H","GCC_IS_NATIVE","VAX")
+else
+defines = /define=("HAVE_CONFIG_H","GCC_IS_NATIVE")
+endif
+endif
+
+LOAD_AVG = /define="NO_LDAV"
+
+# If you don't want archive support, comment these out.
+ARCHIVES = ,ar.obj,arscan.obj
+ARCHIVES_SRC = ar.c arscan.c
+
+# If your system needs extra libraries loaded in, define them here.
+# System V probably need -lPW for alloca.
+# if on vax, uncomment the following line
+#LOADLIBES = ,c.opt/opt
+ifeq ($(CC),cc)
+#LOADLIBES =,sys$$library:vaxcrtl.olb/lib
+CRT0 =
+else
+LOADLIBES =,gnu_cc_library:libgcc.olb/lib
+endif
+
+# If your system doesn't have alloca, or the one provided is bad,
+# get it from the Emacs distribution and define these.
+#ALLOCA = ,alloca.obj
+#ALLOCASRC = alloca.c
+
+# If there are remote execution facilities defined,
+# enable them with switches here (see remote-*.c).
+REMOTE =
+
+# Any extra object files your system needs.
+extras = ,signame.obj,remote-stub.obj,vmsfunctions.obj,vmsify.obj
+#,directory.obj
+# as an alternative:
+glob = ,[.glob]glob.obj,[.glob]fnmatch.obj
+getopt = ,getopt.obj,getopt1.obj
+# Directory to install 'make' in.
+bindir = []
+# Directory to install the man page in.
+mandir = []
+# Number to put on the man page filename.
+manext = 1
+
+guile = ,guile.obj
+
+objs = commands.obj,job.obj,output.obj,dir.obj,file.obj,misc.obj,hash.obj,\
+ load.obj,main.obj,read.obj,remake.obj,rule.obj,implicit.obj,\
+ default.obj,variable.obj,expand.obj,function.obj,strcache.obj,\
+ vpath.obj,version.obj,vms_progname.obj,vms_exit.obj,\
+ vms_export_symbol.obj$(guile)$(ARCHIVES)$(extras)$(getopt)$(glob)
+
+srcs = commands.c job.c output.c dir.c file.c misc.c guile.c hash.c \
+ load.c main.c read.c remake.c rule.c implicit.c \
+ default.c variable.c expand.c function.c strcache.c \
+ vpath.c version.c vmsfunctions.c vmsify.c vms_progname.c vms_exit.c \
+ vms_export_symbol.c $(ARCHIVES_SRC) $(ALLOCASRC) \
+ commands.h dep.h filedef.h job.h output.h makeint.h rule.h variable.h
+
+
+.PHONY: all doc
+all: config.h make.exe
+
+doc: make.info make.dvi
+
+
+make.exe: $(objs)
+ $(LD)$(LDFLAGS)/exe=$@ $^$(LOADLIBES)$(CRT0)
+
+.PHONY: clean realclean
+clean:
+ -purge [...]
+ -$(RM) make.exe;,*.obj;
+ -$(RM) [.glob]*.obj;
+
+ar.obj: ar.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h dep.h \
+ [.glob]fnmatch.h
+arscan.obj: arscan.c makeint.h config.h gnumake.h gettext.h
+commands.obj: commands.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h dep.h variable.h job.h output.h commands.h
+default.obj: default.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h variable.h rule.h dep.h job.h output.h commands.h
+dir.obj: dir.c makeint.h config.h gnumake.h gettext.h hash.h filedef.h \
+ dep.h [.glob]glob.h
+expand.obj: expand.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h job.h output.h commands.h variable.h rule.h
+file.obj: file.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h \
+ dep.h job.h output.h commands.h variable.h debug.h
+[.glob]fnmatch.obj: [.glob]fnmatch.c config.h [.glob]fnmatch.h
+function.obj: function.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h variable.h dep.h job.h output.h commands.h debug.h
+getopt.obj: getopt.c config.h gettext.h getopt.h
+getopt1.obj: getopt1.c config.h getopt.h
+[.glob]glob.obj: [.glob]glob.c config.h [.glob]fnmatch.h [.glob]glob.h
+guile.obj: guile.c makeint.h config.h gnumake.h gettext.h
+hash.obj: hash.c makeint.h config.h gnumake.h gettext.h hash.h
+implicit.obj: implicit.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h rule.h dep.h debug.h variable.h job.h output.h commands.h
+job.obj: job.c makeint.h config.h gnumake.h gettext.h job.h output.h debug.h \
+ filedef.h hash.h commands.h variable.h debug.h vmsjobs.c
+load.obj: load.c makeint.h config.h gnumake.h gettext.h
+main.obj: main.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h \
+ dep.h variable.h job.h output.h commands.h rule.h debug.h getopt.h
+misc.obj: misc.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h \
+ dep.h debug.h
+output.obj: output.c makeint.h config.h gnumake.h gettext.h job.h output.h
+read.obj: read.c makeint.h config.h gnumake.h gettext.h [.glob]glob.h \
+ filedef.h hash.h dep.h job.h output.h commands.h variable.h rule.h \
+ debug.h
+remake.obj: remake.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h job.h output.h commands.h dep.h variable.h debug.h
+remote-stub.obj: remote-stub.c makeint.h config.h gnumake.h gettext.h \
+ filedef.h hash.h job.h output.h commands.h
+rule.obj: rule.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h \
+ dep.h job.h output.h commands.h variable.h rule.h
+signame.obj: signame.c makeint.h config.h gnumake.h gettext.h
+strcache.obj: strcache.c makeint.h config.h gnumake.h gettext.h hash.h
+variable.obj: variable.c makeint.h config.h gnumake.h gettext.h filedef.h \
+ hash.h dep.h job.h output.h commands.h variable.h rule.h
+version.obj: version.c config.h
+vmsfunctions.obj: vmsfunctions.c makeint.h config.h gnumake.h gettext.h \
+ debug.h job.h output.h vmsdir.h
+vmsify.obj: vmsify.c
+vpath.obj: vpath.c makeint.h config.h gnumake.h gettext.h filedef.h hash.h \
+ variable.h
+vms_progname.obj: vms_progname.c
+vms_exit.obj: vms_exit.c
+vms_export_symbol.obj: vms_export_symbol.c
+
+config.h: config.h-vms
+ $(CP) $< $@
diff --git a/src/kmk/makeint.h b/src/kmk/makeint.h
new file mode 100644
index 0000000..9924cb9
--- /dev/null
+++ b/src/kmk/makeint.h
@@ -0,0 +1,1195 @@
+/* Miscellaneous global declarations and portability cruft for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* We use <config.h> instead of "config.h" so that a compilation
+ using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+ (which it would do because makeint.h was found in $srcdir). */
+#include <config.h>
+#undef HAVE_CONFIG_H
+#define HAVE_CONFIG_H 1
+
+#include <k/kDefs.h> /* bird */
+
+/* Specify we want GNU source code. This must be defined before any
+ system headers are included. */
+
+#define _GNU_SOURCE 1
+
+/* AIX requires this to be the first thing in the file. */
+#if HAVE_ALLOCA_H
+# include <alloca.h>
+#else
+# ifdef _AIX
+ #pragma alloca
+# else
+# if !defined(__GNUC__) && !defined(WINDOWS32)
+# ifndef alloca /* predefined by HP cc +Olibcalls */
+char *alloca ();
+# endif
+# endif
+# endif
+#endif
+
+/* Disable assert() unless we're a maintainer.
+ Some asserts are compute-intensive. */
+#ifndef MAKE_MAINTAINER_MODE
+# define NDEBUG 1
+#endif
+
+/* Include the externally-visible content.
+ Be sure to use the local one, and not one installed on the system.
+ Define GMK_BUILDING_MAKE for proper selection of dllexport/dllimport
+ declarations for MS-Windows. */
+#ifdef WINDOWS32
+# define GMK_BUILDING_MAKE
+#endif
+#include "gnumake.h"
+
+#ifdef CRAY
+/* This must happen before #include <signal.h> so
+ that the declaration therein is changed. */
+# define signal bsdsignal
+#endif
+
+/* If we're compiling for the dmalloc debugger, turn off string inlining. */
+#if defined(HAVE_DMALLOC_H) && defined(__GNUC__)
+# define __NO_STRING_INLINES
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef HAVE_SYS_TIMEB_H
+/* SCO 3.2 "devsys 4.2" has a prototype for 'ftime' in <time.h> that bombs
+ unless <sys/timeb.h> has been included first. */
+# include <sys/timeb.h>
+#endif
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+#endif
+
+#include <errno.h>
+
+#ifndef errno
+extern int errno;
+#endif
+
+#ifdef __VMS
+/* In strict ANSI mode, VMS compilers should not be defining the
+ VMS macro. Define it here instead of a bulk edit for the correct code.
+ */
+# ifndef VMS
+# define VMS
+# endif
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+/* Ultrix's unistd.h always defines _POSIX_VERSION, but you only get
+ POSIX.1 behavior with 'cc -YPOSIX', which predefines POSIX itself! */
+# if defined (_POSIX_VERSION) && !defined (ultrix) && !defined (VMS)
+# define POSIX 1
+# endif
+#endif
+
+/* Some systems define _POSIX_VERSION but are not really POSIX.1. */
+#if (defined (butterfly) || defined (__arm) || (defined (__mips) && defined (_SYSTYPE_SVR3)) || (defined (sequent) && defined (i386)))
+# undef POSIX
+#endif
+
+#if !defined (POSIX) && defined (_AIX) && defined (_POSIX_SOURCE)
+# define POSIX 1
+#endif
+
+#ifndef RETSIGTYPE
+# define RETSIGTYPE void
+#endif
+
+#ifndef sigmask
+# define sigmask(sig) (1 << ((sig) - 1))
+#endif
+
+#ifndef HAVE_SA_RESTART
+# define SA_RESTART 0
+#endif
+
+#ifdef HAVE_VFORK_H
+# include <vfork.h>
+#endif
+
+#ifdef HAVE_LIMITS_H
+# include <limits.h>
+#endif
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#ifndef PATH_MAX
+# ifndef POSIX
+# define PATH_MAX MAXPATHLEN
+# endif
+#endif
+#ifndef MAXPATHLEN
+# define MAXPATHLEN 1024
+#endif
+
+#ifdef PATH_MAX
+# define GET_PATH_MAX PATH_MAX
+# define PATH_VAR(var) char var[PATH_MAX]
+#else
+# define NEED_GET_PATH_MAX 1
+# define GET_PATH_MAX (get_path_max ())
+# define PATH_VAR(var) char *var = alloca (GET_PATH_MAX)
+unsigned int get_path_max (void);
+#endif
+
+#if defined (KMK) || defined (CONFIG_WITH_VALUE_LENGTH) \
+ || defined (CONFIG_WITH_ALLOC_CACHES) \
+ || defined (CONFIG_WITH_STRCACHE2)
+# ifdef _MSC_VER
+# define MY_INLINE _inline static
+# elif defined(__GNUC__)
+# define MY_INLINE static __inline__
+# else
+# define MY_INLINE static
+# endif
+
+# ifdef __GNUC__
+# define MY_PREDICT_TRUE(expr) __builtin_expect(!!(expr), 1)
+# define MY_PREDICT_FALSE(expr) __builtin_expect(!!(expr), 0)
+# else
+# define MY_PREDICT_TRUE(expr) (expr)
+# define MY_PREDICT_FALSE(expr) (expr)
+# endif
+#endif
+
+#if defined (KMK) || defined (CONFIG_WITH_VALUE_LENGTH) \
+ || defined (CONFIG_WITH_STRCACHE2)
+# ifdef _MSC_VER
+# define MY_DBGBREAK __debugbreak()
+# elif defined(__GNUC__)
+# if defined(__i386__) || defined(__x86_64__)
+# define MY_DBGBREAK __asm__ __volatile__ ("int3")
+# else
+# define MY_DBGBREAK assert(0)
+# endif
+# else
+# define MY_DBGBREAK assert(0)
+# endif
+# ifndef NDEBUG
+# define MY_ASSERT_MSG(expr, printfargs) \
+ do { if (!(expr)) { printf printfargs; MY_DBGBREAK; } } while (0)
+# else
+# define MY_ASSERT_MSG(expr, printfargs) do { } while (0)
+# endif
+#endif
+
+#ifdef KMK
+/** @todo measure performance diff here! */
+# if 0 /* See if this speeds things up (Windows is doing this anyway, so,
+ we might as well try be consistent in speed + features). */
+# define MY_IS_BLANK(ch) ((ch) == ' ' || (ch) == '\t')
+# define MY_IS_BLANK_OR_EOS(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\0')
+# else
+# define MY_IS_BLANK(ch) ISBLANK ((ch))
+# define MY_IS_BLANK_OR_EOS(ch) (ISBLANK ((ch)) || (ch) == '\0')
+# endif
+#endif
+
+#ifdef CONFIG_WITH_MAKE_STATS
+extern long make_stats_allocations;
+extern long make_stats_reallocations;
+extern unsigned long make_stats_allocated;
+extern unsigned long make_stats_ht_lookups;
+extern unsigned long make_stats_ht_collisions;
+
+# ifdef __APPLE__
+# include <malloc/malloc.h>
+# define SIZE_OF_HEAP_BLOCK(ptr) malloc_size(ptr)
+
+# elif defined(__linux__) /* glibc */
+# include <malloc.h>
+# define SIZE_OF_HEAP_BLOCK(ptr) malloc_usable_size(ptr)
+
+# elif defined(_MSC_VER) || defined(__OS2__)
+# define SIZE_OF_HEAP_BLOCK(ptr) _msize(ptr)
+
+# else
+# include <stdlib.h>
+# define SIZE_OF_HEAP_BLOCK(ptr) 0
+#endif
+
+# define MAKE_STATS_3(expr) do { expr; } while (0)
+# define MAKE_STATS_2(expr) do { expr; } while (0)
+# define MAKE_STATS(expr) do { expr; } while (0)
+#else
+# define MAKE_STATS_3(expr) do { } while (0)
+# define MAKE_STATS_2(expr) do { } while (0)
+# define MAKE_STATS(expr) do { } while (0)
+#endif
+
+/* bird - start */
+#ifdef _MSC_VER
+# include <intrin.h>
+# define CURRENT_CLOCK_TICK() __rdtsc()
+#else
+# define CURRENT_CLOCK_TICK() 0
+#endif
+
+#define COMMA ,
+#ifdef CONFIG_WITH_VALUE_LENGTH
+# define IF_WITH_VALUE_LENGTH(a_Expr) a_Expr
+# define IF_WITH_VALUE_LENGTH_PARAM(a_Expr) , a_Expr
+#else
+# define IF_WITH_VALUE_LENGTH(a_Expr)
+# define IF_WITH_VALUE_LENGTH_PARAM(a_Expr)
+#endif
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+# define IF_WITH_ALLOC_CACHES(a_Expr) a_Expr
+# define IF_WITH_ALLOC_CACHES_PARAM(a_Expr) , a_Expr
+#else
+# define IF_WITH_ALLOC_CACHES(a_Expr)
+# define IF_WITH_ALLOC_CACHES_PARAM(a_Expr)
+#endif
+
+#ifdef CONFIG_WITH_COMMANDS_FUNC
+# define IF_WITH_COMMANDS_FUNC(a_Expr) a_Expr
+# define IF_WITH_COMMANDS_FUNC_PARAM(a_Expr) , a_Expr
+#else
+# define IF_WITH_COMMANDS_FUNC(a_Expr)
+# define IF_WITH_COMMANDS_FUNC_PARAM(a_Expr)
+#endif
+
+/* bird - end */
+
+
+#ifndef CHAR_BIT
+# define CHAR_BIT 8
+#endif
+
+#ifndef USHRT_MAX
+# define USHRT_MAX 65535
+#endif
+
+/* Nonzero if the integer type T is signed.
+ Use <= to avoid GCC warnings about always-false expressions. */
+#define INTEGER_TYPE_SIGNED(t) ((t) -1 <= 0)
+
+/* The minimum and maximum values for the integer type T.
+ Use ~ (t) 0, not -1, for portability to 1's complement hosts. */
+#define INTEGER_TYPE_MINIMUM(t) \
+ (! INTEGER_TYPE_SIGNED (t) ? (t) 0 : ~ (t) 0 << (sizeof (t) * CHAR_BIT - 1))
+#define INTEGER_TYPE_MAXIMUM(t) (~ (t) 0 - INTEGER_TYPE_MINIMUM (t))
+
+#ifndef CHAR_MAX
+# define CHAR_MAX INTEGER_TYPE_MAXIMUM (char)
+#endif
+
+#ifdef STAT_MACROS_BROKEN
+# ifdef S_ISREG
+# undef S_ISREG
+# endif
+# ifdef S_ISDIR
+# undef S_ISDIR
+# endif
+#endif /* STAT_MACROS_BROKEN. */
+
+#ifndef S_ISREG
+# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
+#endif
+#ifndef S_ISDIR
+# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR)
+#endif
+
+#ifdef VMS
+# include <fcntl.h>
+# include <types.h>
+# include <unixlib.h>
+# include <unixio.h>
+# include <perror.h>
+/* Needed to use alloca on VMS. */
+# include <builtins.h>
+
+extern int vms_use_mcr_command;
+extern int vms_always_use_cmd_file;
+extern int vms_gnv_shell;
+extern int vms_comma_separator;
+extern int vms_legacy_behavior;
+extern int vms_unix_simulation;
+#endif
+
+#ifndef __attribute__
+/* This feature is available in gcc versions 2.5 and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 5) || __STRICT_ANSI__
+# define __attribute__(x)
+# endif
+/* The __-protected variants of 'format' and 'printf' attributes
+ are accepted by gcc versions 2.6.4 (effectively 2.7) and later. */
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
+# define __format__ format
+# define __printf__ printf
+# endif
+#endif
+#define UNUSED __attribute__ ((unused))
+
+#if defined (STDC_HEADERS) || defined (__GNU_LIBRARY__)
+# include <stdlib.h>
+# include <string.h>
+# define ANSI_STRING 1
+#else /* No standard headers. */
+# ifdef HAVE_STRING_H
+# include <string.h>
+# define ANSI_STRING 1
+# else
+# include <strings.h>
+# endif
+# ifdef HAVE_MEMORY_H
+# include <memory.h>
+# endif
+# ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+# else
+void *malloc (int);
+void *realloc (void *, int);
+void free (void *);
+
+void abort (void) __attribute__ ((noreturn));
+void exit (int) __attribute__ ((noreturn));
+# endif /* HAVE_STDLIB_H. */
+
+#endif /* Standard headers. */
+
+/* These should be in stdlib.h. Make sure we have them. */
+#ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+#endif
+#ifndef EXIT_FAILURE
+# define EXIT_FAILURE 1
+#endif
+
+#ifndef ANSI_STRING
+
+/* SCO Xenix has a buggy macro definition in <string.h>. */
+#undef strerror
+#if !defined(__DECC)
+char *strerror (int errnum);
+#endif
+
+#endif /* !ANSI_STRING. */
+#undef ANSI_STRING
+
+#if HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#if HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#define FILE_TIMESTAMP uintmax_t
+
+#if !defined(HAVE_STRSIGNAL)
+char *strsignal (int signum);
+#endif
+
+/* ISDIGIT offers the following features:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ NOTE! Make relies on this behavior, don't change it!
+ - It's typically faster.
+ POSIX 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
+ only '0' through '9' are digits. Prefer ISDIGIT to isdigit() unless
+ it's important to use the locale's definition of 'digit' even when the
+ host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+/* Test if two strings are equal. Is this worthwhile? Should be profiled. */
+#define streq(a, b) \
+ ((a) == (b) || \
+ (*(a) == *(b) && (*(a) == '\0' || !strcmp ((a) + 1, (b) + 1))))
+
+/* Test if two strings are equal, but match case-insensitively on systems
+ which have case-insensitive filesystems. Should only be used for
+ filenames! */
+#ifdef HAVE_CASE_INSENSITIVE_FS
+# define patheq(a, b) \
+ ((a) == (b) \
+ || (tolower((unsigned char)*(a)) == tolower((unsigned char)*(b)) \
+ && (*(a) == '\0' || !strcasecmp ((a) + 1, (b) + 1))))
+#else
+# define patheq(a, b) streq(a, b)
+#endif
+
+#define strneq(a, b, l) (strncmp ((a), (b), (l)) == 0)
+
+#if (defined(__GNUC__) || defined(ENUM_BITFIELDS)) && !defined(NO_ENUM_BITFIELDS) /* bird: NO_ENUM_BITFIELDS */
+# define ENUM_BITFIELD(bits) :bits
+#else
+# define ENUM_BITFIELD(bits)
+#endif
+
+/* Handle gettext and locales. */
+
+#if HAVE_LOCALE_H
+# include <locale.h>
+#else
+# define setlocale(category, locale)
+#endif
+
+#include <gettext.h>
+
+#define _(msgid) gettext (msgid)
+#define N_(msgid) gettext_noop (msgid)
+#define S_(msg1,msg2,num) ngettext (msg1,msg2,num)
+
+/* This is needed for getcwd() and chdir(), on some W32 systems. */
+#if defined(HAVE_DIRECT_H)
+# include <direct.h>
+#endif
+
+#ifdef WINDOWS32
+# include <fcntl.h>
+# include <malloc.h>
+# define pipe(_p) _pipe((_p), 512, O_BINARY)
+# ifndef CONFIG_NEW_WIN_CHILDREN /* (only used by commands.c) */
+# define kill(_pid,_sig) w32_kill((_pid),(_sig))
+# endif
+/* MSVC and Watcom C don't have ftruncate. */
+# if defined(_MSC_VER) || defined(__WATCOMC__)
+# define ftruncate(_fd,_len) _chsize(_fd,_len)
+# endif
+/* MinGW64 doesn't have _S_ISDIR. */
+# ifndef _S_ISDIR
+# define _S_ISDIR(m) S_ISDIR(m)
+# endif
+
+void sync_Path_environment (void);
+# ifndef CONFIG_NEW_WIN_CHILDREN /* (only used by commands.c) */
+int w32_kill (pid_t pid, int sig);
+# endif
+int find_and_set_default_shell (const char *token);
+
+/* indicates whether or not we have Bourne shell */
+extern int no_default_sh_exe;
+
+/* is default_shell unixy? */
+extern int unixy_shell;
+
+/* We don't have a preferred fixed value for LOCALEDIR. */
+# ifndef LOCALEDIR
+# define LOCALEDIR NULL
+# endif
+
+/* Include only the minimal stuff from windows.h. */
+# define WIN32_LEAN_AND_MEAN
+#endif /* WINDOWS32 */
+
+#define ANY_SET(_v,_m) (((_v)&(_m)) != 0)
+#define NONE_SET(_v,_m) (! ANY_SET ((_v),(_m)))
+
+#define MAP_NUL 0x0001
+#define MAP_BLANK 0x0002
+#define MAP_NEWLINE 0x0004
+#define MAP_COMMENT 0x0008
+#define MAP_SEMI 0x0010
+#define MAP_EQUALS 0x0020
+#define MAP_COLON 0x0040
+#define MAP_PERCENT 0x0080
+#define MAP_PIPE 0x0100
+#define MAP_DOT 0x0200
+#define MAP_COMMA 0x0400
+
+/* These are the valid characters for a user-defined function. */
+#define MAP_USERFUNC 0x2000
+/* This means not only a '$', but skip the variable reference. */
+#define MAP_VARIABLE 0x4000
+/* The set of characters which are directory separators is OS-specific. */
+#define MAP_DIRSEP 0x8000
+
+#ifdef VMS
+# define MAP_VMSCOMMA MAP_COMMA
+#else
+# define MAP_VMSCOMMA 0x0000
+#endif
+
+#define MAP_SPACE (MAP_BLANK|MAP_NEWLINE)
+
+/* Handle other OSs.
+ To overcome an issue parsing paths in a DOS/Windows environment when
+ built in a unix based environment, override the PATH_SEPARATOR_CHAR
+ definition unless being built for Cygwin. */
+#if defined(HAVE_DOS_PATHS) && !defined(__CYGWIN__)
+# undef PATH_SEPARATOR_CHAR
+# define PATH_SEPARATOR_CHAR ';'
+# define MAP_PATHSEP MAP_SEMI
+#elif !defined(PATH_SEPARATOR_CHAR)
+# if defined (VMS)
+# define PATH_SEPARATOR_CHAR (vms_comma_separator ? ',' : ':')
+# define MAP_PATHSEP (vms_comma_separator ? MAP_COMMA : MAP_SEMI)
+# else
+# define PATH_SEPARATOR_CHAR ':'
+# define MAP_PATHSEP MAP_COLON
+# endif
+#elif PATH_SEPARATOR_CHAR == ':'
+# define MAP_PATHSEP MAP_COLON
+#elif PATH_SEPARATOR_CHAR == ';'
+# define MAP_PATHSEP MAP_SEMI
+#elif PATH_SEPARATOR_CHAR == ','
+# define MAP_PATHSEP MAP_COMMA
+#else
+# error "Unknown PATH_SEPARATOR_CHAR"
+#endif
+
+#define STOP_SET(_v,_m) ANY_SET(stopchar_map[(unsigned char)(_v)],(_m))
+
+#define ISBLANK(c) STOP_SET((c),MAP_BLANK)
+#define ISSPACE(c) STOP_SET((c),MAP_SPACE)
+#define NEXT_TOKEN(s) while (ISSPACE (*(s))) ++(s)
+#define END_OF_TOKEN(s) while (! STOP_SET (*(s), MAP_SPACE|MAP_NUL)) ++(s)
+
+#if defined(HAVE_SYS_RESOURCE_H) && defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT)
+# define SET_STACK_SIZE
+#endif
+#ifdef SET_STACK_SIZE
+# include <sys/resource.h>
+extern struct rlimit stack_limit;
+#endif
+
+#include "glob.h" /* bird double quotes */
+
+#define NILF ((floc *)0)
+
+#define CSTRLEN(_s) (sizeof (_s)-1)
+#define STRING_SIZE_TUPLE(_s) (_s), CSTRLEN(_s) /* The number of bytes needed to represent the largest integer as a string. */
+#define INTSTR_LENGTH CSTRLEN ("18446744073709551616")
+
+#define DEFAULT_TTYNAME "true"
+#ifdef HAVE_TTYNAME
+# define TTYNAME(_f) ttyname (_f)
+#else
+# define TTYNAME(_f) DEFAULT_TTYNAME
+#endif
+
+
+
+/* Specify the location of elements read from makefiles. */
+typedef struct
+ {
+ const char *filenm;
+ unsigned long lineno;
+ unsigned long offset;
+ } floc;
+
+#if defined (CONFIG_WITH_MATH) /* bird start */ \
+ || defined (CONFIG_WITH_NANOTS) \
+ || defined (CONFIG_WITH_FILE_SIZE) \
+ || defined (CONFIG_WITH_PRINT_TIME_SWITCH)
+# ifdef _MSC_VER
+typedef __int64 big_int;
+# define BIG_INT_C(c) (c ## LL)
+typedef unsigned __int64 big_uint;
+# define BIG_UINT_C(c) (c ## ULL)
+# else
+# include <stdint.h>
+typedef int64_t big_int;
+# define BIG_INT_C(c) INT64_C(c)
+typedef uint64_t big_uint;
+# define BIG_UINT_C(c) UINT64_C(c)
+# endif
+#endif /* bird end */
+
+const char *concat (unsigned int, ...);
+void message (int prefix, size_t length, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 3, 4)));
+void error (const floc *flocp, size_t length, const char *fmt, ...)
+ __attribute__ ((__format__ (__printf__, 3, 4)));
+void fatal (const floc *flocp, size_t length, const char *fmt, ...)
+ __attribute__ ((noreturn, __format__ (__printf__, 3, 4)));
+
+#define O(_t,_a,_f) _t((_a), 0, (_f))
+#define OS(_t,_a,_f,_s) _t((_a), strlen (_s), (_f), (_s))
+#define OSS(_t,_a,_f,_s1,_s2) _t((_a), strlen (_s1) + strlen (_s2), \
+ (_f), (_s1), (_s2))
+#define OSSS(_t,_a,_f,_s1,_s2,_s3) _t((_a), strlen (_s1) + strlen (_s2) + strlen (_s3), \
+ (_f), (_s1), (_s2), (_s3))
+#define ON(_t,_a,_f,_n) _t((_a), INTSTR_LENGTH, (_f), (_n))
+#define ONN(_t,_a,_f,_n1,_n2) _t((_a), INTSTR_LENGTH*2, (_f), (_n1), (_n2))
+
+#define OSN(_t,_a,_f,_s,_n) _t((_a), strlen (_s) + INTSTR_LENGTH, \
+ (_f), (_s), (_n))
+#define ONS(_t,_a,_f,_n,_s) _t((_a), INTSTR_LENGTH + strlen (_s), \
+ (_f), (_n), (_s))
+
+/* bird: more wrappers */
+#define OSNN(_t,_a,_f,_s,_n1,_n2) _t((_a), strlen (_s) + INTSTR_LENGTH + INTSTR_LENGTH, \
+ (_f), (_s), (_n1), (_n2)) /* bird */
+#define OSNS(_t,_a,_f,_s1,_n,_s2) _t((_a), strlen (_s1) + strlen (_s2) + INTSTR_LENGTH, \
+ (_f), (_s1), (_n), (_s2)) /* bird */
+#define OSSSS(_t,_a,_f,_s1,_s2,_s3,_s4) _t((_a), strlen (_s1) + strlen (_s2) + strlen (_s3) + strlen (_s4), \
+ (_f), (_s1), (_s2), (_s3), (_s4)) /* bird */
+#define OSSN(_t,_a,_f,_s1,_s2,_n) _t((_a), strlen (_s1) + strlen (_s2) + INTSTR_LENGTH, \
+ (_f), (_s1), (_s2), (_n)) /* bird */
+#define OSSNS(_t,_a,_f,_s1,_s2,_n,_s3) _t((_a), strlen (_s1) + strlen (_s2) + strlen (_s3) + INTSTR_LENGTH, \
+ (_f), (_s1), (_s2), (_n), (_s3)) /* bird */
+#define ONNS(_t,_a,_f,_n1,_n2,_s1) _t((_a), INTSTR_LENGTH * 2 + strlen (_s1), \
+ (_f), (_n1), (_n2), (_s1)) /* bird */
+#define ONNNS(_t,_a,_f,_n1,_n2,_n3,_s1) _t((_a), INTSTR_LENGTH * 3 + strlen (_s1), \
+ (_f), (_n1), (_n2), (_n3), (_s1)) /* bird */
+
+#define OUT_OF_MEM() O (fatal, NILF, _("virtual memory exhausted"))
+
+void die (int) __attribute__ ((noreturn));
+struct output;
+void die_with_job_output (int, struct output *) __attribute__ ((noreturn));
+void pfatal_with_name (const char *) __attribute__ ((noreturn));
+void perror_with_name (const char *, const char *);
+#define xstrlen(_s) ((_s)==NULL ? 0 : strlen (_s))
+void *xmalloc (unsigned int);
+void *xcalloc (unsigned int);
+void *xrealloc (void *, unsigned int);
+char *xstrdup (const char *);
+char *xstrndup (const char *, unsigned int);
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+void print_heap_stats (void);
+#endif
+char *find_next_token (const char **, unsigned int *);
+char *next_token (const char *);
+char *end_of_token (const char *);
+#ifdef KMK
+char *find_next_token_eos (const char **ptr, const char *eos, unsigned int *lengthptr);
+char *find_next_file_token (const char **ptr, const char *eos, unsigned int *lengthptr);
+#endif
+#ifndef CONFIG_WITH_VALUE_LENGTH
+void collapse_continuations (char *);
+#else
+char *collapse_continuations (char *, unsigned int);
+#endif
+#ifdef CONFIG_WITH_OPTIMIZATION_HACKS /* memchr is usually compiler intrinsic, thus faster. */
+# define lindex(s, limit, c) ((char *)memchr((s), (c), (limit) - (s)))
+#else
+char *lindex (const char *, const char *, int);
+#endif
+int alpha_compare (const void *, const void *);
+void print_spaces (unsigned int);
+char *find_percent (char *);
+const char *find_percent_cached (const char **);
+
+#ifndef NO_ARCHIVES
+int ar_name (const char *);
+void ar_parse_name (const char *, char **, char **);
+int ar_touch (const char *);
+time_t ar_member_date (const char *);
+
+typedef long int (*ar_member_func_t) (int desc, const char *mem, int truncated,
+ long int hdrpos, long int datapos,
+ long int size, long int date, int uid,
+ int gid, unsigned int mode,
+ const void *arg);
+
+long int ar_scan (const char *archive, ar_member_func_t function, const void *arg);
+int ar_name_equal (const char *name, const char *mem, int truncated);
+#ifndef VMS
+int ar_member_touch (const char *arname, const char *memname);
+#endif
+#endif
+
+int dir_file_exists_p (const char *, const char *);
+int file_exists_p (const char *);
+int file_impossible_p (const char *);
+void file_impossible (const char *);
+const char *dir_name (const char *);
+void print_dir_data_base (void);
+void dir_setup_glob (glob_t *);
+void hash_init_directories (void);
+#if defined (KMK) && defined (KBUILD_OS_WINDOWS)
+int utf16_regular_file_p(const wchar_t *pwszPath);
+#endif
+
+void define_default_variables (void);
+void undefine_default_variables (void);
+void set_default_suffixes (void);
+void install_default_suffix_rules (void);
+void install_default_implicit_rules (void);
+
+void build_vpath_lists (void);
+void construct_vpath_list (char *pattern, char *dirpath);
+const char *vpath_search (const char *file, FILE_TIMESTAMP *mtime_ptr,
+ unsigned int* vpath_index, unsigned int* path_index);
+int gpath_search (const char *file, unsigned int len);
+
+void construct_include_path (const char **arg_dirs);
+
+void user_access (void);
+void make_access (void);
+void child_access (void);
+
+char *strip_whitespace (const char **begpp, const char **endpp);
+
+void show_goal_error (void);
+
+
+#ifdef CONFIG_WITH_ALLOC_CACHES /* bird start */
+/* alloccache (misc.c) */
+
+struct alloccache_free_ent
+{
+ struct alloccache_free_ent *next;
+};
+
+struct alloccache
+{
+ char *free_start;
+ char *free_end;
+ struct alloccache_free_ent *free_head;
+ unsigned int size;
+ unsigned int total_count;
+ unsigned long alloc_count;
+ unsigned long free_count;
+ const char *name;
+ struct alloccache *next;
+ void *grow_arg;
+ void *(*grow_alloc)(void *grow_arg, unsigned int size);
+};
+
+void alloccache_init (struct alloccache *cache, unsigned int size, const char *name,
+ void *(*grow_alloc)(void *grow_arg, unsigned int size), void *grow_arg);
+void alloccache_term (struct alloccache *cache,
+ void (*term_free)(void *term_arg, void *ptr, unsigned int size), void *term_arg);
+void alloccache_join (struct alloccache *cache, struct alloccache *eat);
+void alloccache_print (struct alloccache *cache);
+void alloccache_print_all (void);
+struct alloccache_free_ent *alloccache_alloc_grow (struct alloccache *cache);
+void alloccache_free (struct alloccache *cache, void *item);
+
+/* Allocate an item. */
+MY_INLINE void *
+alloccache_alloc (struct alloccache *cache)
+{
+ struct alloccache_free_ent *f;
+# ifndef CONFIG_WITH_ALLOCCACHE_DEBUG
+ f = cache->free_head;
+ if (f)
+ cache->free_head = f->next;
+ else if (cache->free_start != cache->free_end)
+ {
+ f = (struct alloccache_free_ent *)cache->free_start;
+ cache->free_start += cache->size;
+ }
+ else
+# endif
+ f = alloccache_alloc_grow (cache);
+ MAKE_STATS(cache->alloc_count++;);
+ return f;
+}
+
+/* Allocate a cleared item. */
+MY_INLINE void *
+alloccache_calloc (struct alloccache *cache)
+{
+ void *item = alloccache_alloc (cache);
+ memset (item, '\0', cache->size);
+ return item;
+}
+
+
+/* the alloc caches */
+extern struct alloccache dep_cache;
+extern struct alloccache goaldep_cache;
+extern struct alloccache nameseq_cache;
+extern struct alloccache file_cache;
+extern struct alloccache commands_cache;
+extern struct alloccache variable_cache;
+extern struct alloccache variable_set_cache;
+extern struct alloccache variable_set_list_cache;
+
+#endif /* CONFIG_WITH_ALLOC_CACHES - bird end*/
+
+
+/* String caching */
+void strcache_init (void);
+void strcache_print_stats (const char *prefix);
+#ifndef CONFIG_WITH_STRCACHE2 /* bird */
+int strcache_iscached (const char *str);
+const char *strcache_add (const char *str);
+const char *strcache_add_len (const char *str, unsigned int len);
+#else /* CONFIG_WITH_STRCACHE2 */
+
+# include "strcache2.h"
+extern struct strcache2 file_strcache;
+extern const char *suffixes_strcached;
+
+# define strcache_iscached(str) strcache2_is_cached(&file_strcache, str)
+# define strcache_add(str) strcache2_add_file(&file_strcache, str, strlen (str))
+# define strcache_add_len(str, len) strcache2_add_file(&file_strcache, str, len)
+# define strcache_get_len(str) strcache2_get_len(&file_strcache, str) /* FIXME: replace this and related checks ... */
+
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+/* Guile support */
+int guile_gmake_setup (const floc *flocp);
+
+/* Loadable object support. Sets to the strcached name of the loaded file. */
+typedef int (*load_func_t)(const floc *flocp);
+int load_file (const floc *flocp, const char **filename, int noerror);
+void unload_file (const char *name);
+
+/* We omit these declarations on non-POSIX systems which define _POSIX_VERSION,
+ because such systems often declare them in header files anyway. */
+
+#if !defined (__GNU_LIBRARY__) && !defined (POSIX) && !defined (_POSIX_VERSION) && !defined(WINDOWS32)
+
+long int atol ();
+# ifndef VMS
+long int lseek ();
+# endif
+
+# ifdef HAVE_GETCWD
+# if !defined(VMS) && !defined(__DECC) && !defined(_MSC_VER) /* bird: MSC */
+char *getcwd ();
+# endif
+# else
+char *getwd ();
+# define getcwd(buf, len) getwd (buf)
+# endif
+
+#endif /* Not GNU C library or POSIX. */
+
+#if !HAVE_STRCASECMP
+# if HAVE_STRICMP
+# define strcasecmp stricmp
+# elif HAVE_STRCMPI
+# define strcasecmp strcmpi
+# else
+/* Create our own, in misc.c */
+int strcasecmp (const char *s1, const char *s2);
+# endif
+#endif
+
+#if !HAVE_STRNCASECMP
+# if HAVE_STRNICMP
+# define strncasecmp strnicmp
+# elif HAVE_STRNCMPI
+# define strncasecmp strncmpi
+# else
+/* Create our own, in misc.c */
+int strncasecmp (const char *s1, const char *s2, int n);
+# endif
+#endif
+
+#define OUTPUT_SYNC_NONE 0
+#define OUTPUT_SYNC_LINE 1
+#define OUTPUT_SYNC_TARGET 2
+#define OUTPUT_SYNC_RECURSE 3
+
+#if !defined(_MSC_VER) /* bird */
+/* Non-GNU systems may not declare this in unistd.h. */
+extern char **environ;
+#endif
+
+extern const floc *reading_file;
+extern const floc **expanding_var;
+
+extern unsigned short stopchar_map[];
+
+extern int just_print_flag, silent_flag, ignore_errors_flag, keep_going_flag;
+extern int print_data_base_flag, question_flag, touch_flag, always_make_flag;
+extern int env_overrides, no_builtin_rules_flag, no_builtin_variables_flag;
+extern int print_version_flag, print_directory_flag, check_symlink_flag;
+extern int warn_undefined_variables_flag, trace_flag, posix_pedantic;
+extern int not_parallel, second_expansion, clock_skew_detected;
+extern int rebuilding_makefiles, one_shell, output_sync, verify_flag;
+
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION /* bird start */
+extern int second_target_expansion;
+#endif
+#ifdef CONFIG_PRETTY_COMMAND_PRINTING
+extern int pretty_command_printing;
+#endif
+#ifdef CONFIG_WITH_PRINT_TIME_SWITCH
+extern int print_time_min, print_time_width;
+#endif
+#if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+extern int make_expensive_statistics;
+#endif /* bird end */
+
+extern const char *default_shell;
+
+/* can we run commands via 'sh -c xxx' or must we use batch files? */
+extern int batch_mode_shell;
+
+/* Resetting the command script introduction prefix character. */
+#define RECIPEPREFIX_NAME ".RECIPEPREFIX"
+#define RECIPEPREFIX_DEFAULT '\t'
+extern char cmd_prefix;
+
+extern unsigned int job_slots;
+#ifndef NO_FLOAT
+extern double max_load_average;
+#else
+extern int max_load_average;
+#endif
+
+#ifdef WINDOWS32
+extern char *program;
+#else
+extern const char *program;
+#endif
+
+#ifdef VMS
+const char *vms_command (const char *argv0);
+const char *vms_progname (const char *argv0);
+
+void vms_exit (int);
+# define _exit(foo) vms_exit(foo)
+# define exit(foo) vms_exit(foo)
+
+extern char *program_name;
+
+void
+set_program_name (const char *arv0);
+
+int
+need_vms_symbol (void);
+
+int
+create_foreign_command (const char *command, const char *image);
+
+int
+vms_export_dcl_symbol (const char *name, const char *value);
+
+int
+vms_putenv_symbol (const char *string);
+
+void
+vms_restore_symbol (const char *string);
+
+#endif
+
+void remote_setup (void);
+void remote_cleanup (void);
+int start_remote_job_p (int);
+int start_remote_job (char **, char **, int, int *, int *, int *);
+int remote_status (int *, int *, int *, int);
+void block_remote_children (void);
+void unblock_remote_children (void);
+int remote_kill (int id, int sig);
+void print_variable_data_base (void);
+void print_vpath_data_base (void);
+
+extern char *starting_directory;
+extern unsigned int makelevel;
+extern char *version_string, *remote_description, *make_host;
+
+extern unsigned int commands_started;
+
+extern int handling_fatal_signal;
+
+
+#ifndef MIN
+#define MIN(_a,_b) ((_a)<(_b)?(_a):(_b))
+#endif
+#ifndef MAX
+#define MAX(_a,_b) ((_a)>(_b)?(_a):(_b))
+#endif
+
+
+#define MAKE_SUCCESS 0
+#define MAKE_TROUBLE 1
+#define MAKE_FAILURE 2
+
+/* Set up heap debugging library dmalloc. */
+
+#ifdef HAVE_DMALLOC_H
+#include <dmalloc.h>
+#endif
+
+#ifndef initialize_main
+# ifdef __EMX__
+# define initialize_main(pargc, pargv) \
+ { _wildcard(pargc, pargv); _response(pargc, pargv); }
+# else
+# define initialize_main(pargc, pargv)
+# endif
+#endif
+
+#ifdef __EMX__
+# if !defined chdir
+# define chdir _chdir2
+# endif
+# if !defined getcwd
+# define getcwd _getcwd2
+# endif
+
+/* NO_CHDIR2 causes make not to use _chdir2() and _getcwd2() instead of
+ chdir() and getcwd(). This avoids some error messages for the
+ make testsuite but restricts the drive letter support. */
+# ifdef NO_CHDIR2
+# warning NO_CHDIR2: usage of drive letters restricted
+# undef chdir
+# undef getcwd
+# endif
+#endif
+
+#ifndef initialize_main
+# define initialize_main(pargc, pargv)
+#endif
+
+
+/* Some systems (like Solaris, PTX, etc.) do not support the SA_RESTART flag
+ properly according to POSIX. So, we try to wrap common system calls with
+ checks for EINTR. Note that there are still plenty of system calls that
+ can fail with EINTR but this, reportedly, gets the vast majority of
+ failure cases. If you still experience failures you'll need to either get
+ a system where SA_RESTART works, or you need to avoid -j. */
+
+#define EINTRLOOP(_v,_c) while (((_v)=_c)==-1 && errno==EINTR)
+
+/* While system calls that return integers are pretty consistent about
+ returning -1 on failure and setting errno in that case, functions that
+ return pointers are not always so well behaved. Sometimes they return
+ NULL for expected behavior: one good example is readdir() which returns
+ NULL at the end of the directory--and _doesn't_ reset errno. So, we have
+ to do it ourselves here. */
+
+#define ENULLLOOP(_v,_c) do { errno = 0; (_v) = _c; } \
+ while((_v)==0 && errno==EINTR)
+
+
+#if defined(__EMX__) && defined(CONFIG_WITH_OPTIMIZATION_HACKS) /* bird: saves 40-100ms on libc. */
+static inline void *__my_rawmemchr (const void *__s, int __c);
+#undef strchr
+#define strchr(s, c) \
+ (__extension__ (__builtin_constant_p (c) \
+ ? ((c) == '\0' \
+ ? (char *) __my_rawmemchr ((s), (c)) \
+ : __my_strchr_c ((s), ((c) & 0xff) << 8)) \
+ : __my_strchr_g ((s), (c))))
+static inline char *__my_strchr_c (const char *__s, int __c)
+{
+ register unsigned long int __d0;
+ register char *__res;
+ __asm__ __volatile__
+ ("1:\n\t"
+ "movb (%0),%%al\n\t"
+ "cmpb %%ah,%%al\n\t"
+ "je 2f\n\t"
+ "leal 1(%0),%0\n\t"
+ "testb %%al,%%al\n\t"
+ "jne 1b\n\t"
+ "xorl %0,%0\n"
+ "2:"
+ : "=r" (__res), "=&a" (__d0)
+ : "0" (__s), "1" (__c),
+ "m" ( *(struct { char __x[0xfffffff]; } *)__s)
+ : "cc");
+ return __res;
+}
+
+static inline char *__my_strchr_g (__const char *__s, int __c)
+{
+ register unsigned long int __d0;
+ register char *__res;
+ __asm__ __volatile__
+ ("movb %%al,%%ah\n"
+ "1:\n\t"
+ "movb (%0),%%al\n\t"
+ "cmpb %%ah,%%al\n\t"
+ "je 2f\n\t"
+ "leal 1(%0),%0\n\t"
+ "testb %%al,%%al\n\t"
+ "jne 1b\n\t"
+ "xorl %0,%0\n"
+ "2:"
+ : "=r" (__res), "=&a" (__d0)
+ : "0" (__s), "1" (__c),
+ "m" ( *(struct { char __x[0xfffffff]; } *)__s)
+ : "cc");
+ return __res;
+}
+
+static inline void *__my_rawmemchr (const void *__s, int __c)
+{
+ register unsigned long int __d0;
+ register unsigned char *__res;
+ __asm__ __volatile__
+ ("cld\n\t"
+ "repne; scasb\n\t"
+ : "=D" (__res), "=&c" (__d0)
+ : "a" (__c), "0" (__s), "1" (0xffffffff),
+ "m" ( *(struct { char __x[0xfffffff]; } *)__s)
+ : "cc");
+ return __res - 1;
+}
+
+#undef memchr
+#define memchr(a,b,c) __my_memchr((a),(b),(c))
+static inline void *__my_memchr (__const void *__s, int __c, size_t __n)
+{
+ register unsigned long int __d0;
+ register unsigned char *__res;
+ if (__n == 0)
+ return NULL;
+ __asm__ __volatile__
+ ("repne; scasb\n\t"
+ "je 1f\n\t"
+ "movl $1,%0\n"
+ "1:"
+ : "=D" (__res), "=&c" (__d0)
+ : "a" (__c), "0" (__s), "1" (__n),
+ "m" ( *(struct { __extension__ char __x[__n]; } *)__s)
+ : "cc");
+ return __res - 1;
+}
+
+#endif /* __EMX__ (bird) */
+
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+extern int expr_eval_if_conditionals(const char *line, const floc *flocp);
+extern char *expr_eval_to_string(char *o, const char *expr);
+#endif
+
+#ifdef KMK
+extern char *abspath(const char *name, char *apath);
+extern char *func_breakpoint(char *o, char **argv, const char *funcname);
+# ifdef KBUILD_OS_WINDOWS
+extern void dir_cache_invalid_after_job (void);
+extern void dir_cache_invalid_all (void);
+extern void dir_cache_invalid_all_and_close_dirs (int including_root);
+extern void dir_cache_invalid_missing (void);
+extern int dir_cache_volatile_dir (const char *dir);
+extern int dir_cache_deleted_directory(const char *pszDir);
+# endif
+#endif
+
+#if defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_PRINT_TIME_SWITCH) || defined(CONFIG_WITH_KMK_BUILTIN_STATS)
+/* misc.c */
+extern big_int nano_timestamp (void);
+extern int format_elapsed_nano (char *buf, size_t size, big_int ts);
+#endif
+
+#ifdef KMK
+/* main.c */
+extern mode_t g_fUMask;
+#endif
diff --git a/src/kmk/misc.c b/src/kmk/misc.c
new file mode 100644
index 0000000..dec450a
--- /dev/null
+++ b/src/kmk/misc.c
@@ -0,0 +1,1358 @@
+/* Miscellaneous generic support functions for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "dep.h"
+#include "debug.h"
+
+/* GNU make no longer supports pre-ANSI89 environments. */
+
+#include <stdarg.h>
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+#if defined (CONFIG_WITH_VALUE_LENGTH) || defined (CONFIG_WITH_ALLOC_CACHES)
+# include <assert.h>
+#endif
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+# ifdef __APPLE__
+# include <malloc/malloc.h>
+# endif
+# if defined(__GLIBC__) || defined(HAVE_MALLINFO)
+# include <malloc.h>
+# endif
+#endif
+#if defined (CONFIG_WITH_NANOTS) || defined (CONFIG_WITH_PRINT_TIME_SWITCH)
+# ifdef WINDOWS32
+# include <Windows.h>
+# endif
+#endif
+
+/* All bcopy calls in this file can be replaced by memcpy and save a tick or two. */
+#ifdef CONFIG_WITH_OPTIMIZATION_HACKS
+# undef bcopy
+# if defined(__GNUC__) && defined(CONFIG_WITH_OPTIMIZATION_HACKS)
+# define bcopy(src, dst, size) __builtin_memcpy ((dst), (src), (size))
+# else
+# define bcopy(src, dst, size) memcpy ((dst), (src), (size))
+# endif
+#endif
+
+
+/* Compare strings *S1 and *S2.
+ Return negative if the first is less, positive if it is greater,
+ zero if they are equal. */
+
+int
+alpha_compare (const void *v1, const void *v2)
+{
+ const char *s1 = *((char **)v1);
+ const char *s2 = *((char **)v2);
+
+ if (*s1 != *s2)
+ return *s1 - *s2;
+ return strcmp (s1, s2);
+}
+
+/* Discard each backslash-newline combination from LINE.
+ Backslash-backslash-newline combinations become backslash-newlines.
+ This is done by copying the text at LINE into itself. */
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+void
+collapse_continuations (char *line)
+#else
+char *
+collapse_continuations (char *line, unsigned int linelen)
+#endif
+{
+ char *in, *out, *p;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ in = strchr (line, '\n');
+ if (in == 0)
+ return;
+#else
+ assert (strlen (line) == linelen);
+ in = memchr (line, '\n', linelen);
+ if (in == 0)
+ return line + linelen;
+ if (in == line || in[-1] != '\\')
+ {
+ do
+ {
+ unsigned int off_in = in - line;
+ if (off_in == linelen)
+ return in;
+ in = memchr (in + 1, '\n', linelen - off_in - 1);
+ if (in == 0)
+ return line + linelen;
+ }
+ while (in[-1] != '\\');
+ }
+#endif
+
+ out = in;
+ while (out > line && out[-1] == '\\')
+ --out;
+
+ while (*in != '\0')
+ {
+ /* BS_WRITE gets the number of quoted backslashes at
+ the end just before IN, and BACKSLASH gets nonzero
+ if the next character is quoted. */
+ unsigned int backslash = 0;
+ unsigned int bs_write = 0;
+ for (p = in - 1; p >= line && *p == '\\'; --p)
+ {
+ if (backslash)
+ ++bs_write;
+ backslash = !backslash;
+
+ /* It should be impossible to go back this far without exiting,
+ but if we do, we can't get the right answer. */
+ if (in == out - 1)
+ abort ();
+ }
+
+ /* Output the appropriate number of backslashes. */
+ while (bs_write-- > 0)
+ *out++ = '\\';
+
+ /* Skip the newline. */
+ ++in;
+
+ if (backslash)
+ {
+ /* Backslash/newline handling:
+ In traditional GNU make all trailing whitespace, consecutive
+ backslash/newlines, and any leading non-newline whitespace on the
+ next line is reduced to a single space.
+ In POSIX, each backslash/newline and is replaced by a space. */
+ while (ISBLANK (*in))
+ ++in;
+ if (! posix_pedantic)
+ while (out > line && ISBLANK (out[-1]))
+ --out;
+ *out++ = ' ';
+ }
+ else
+ /* If the newline isn't quoted, put it in the output. */
+ *out++ = '\n';
+
+ /* Now copy the following line to the output.
+ Stop when we find backslashes followed by a newline. */
+ while (*in != '\0')
+ if (*in == '\\')
+ {
+ p = in + 1;
+ while (*p == '\\')
+ ++p;
+ if (*p == '\n')
+ {
+ in = p;
+ break;
+ }
+ while (in < p)
+ *out++ = *in++;
+ }
+ else
+ *out++ = *in++;
+ }
+
+ *out = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ assert (strchr (line, '\0') == out);
+ return out;
+#endif
+}
+
+/* Print N spaces (used in debug for target-depth). */
+
+void
+print_spaces (unsigned int n)
+{
+ while (n-- > 0)
+ putchar (' ');
+}
+
+
+/* Return a string whose contents concatenate the NUM strings provided
+ This string lives in static, re-used memory. */
+
+const char *
+concat (unsigned int num, ...)
+{
+ static unsigned int rlen = 0;
+ static char *result = NULL;
+ unsigned int ri = 0;
+ va_list args;
+
+ va_start (args, num);
+
+ while (num-- > 0)
+ {
+ const char *s = va_arg (args, const char *);
+ unsigned int l = xstrlen (s);
+
+ if (l == 0)
+ continue;
+
+ if (ri + l > rlen)
+ {
+ rlen = ((rlen ? rlen : 60) + l) * 2;
+ result = xrealloc (result, rlen);
+ }
+
+ memcpy (result + ri, s, l);
+ ri += l;
+ }
+
+ va_end (args);
+
+ /* Get some more memory if we don't have enough space for the
+ terminating '\0'. */
+ if (ri == rlen)
+ {
+ rlen = (rlen ? rlen : 60) * 2;
+ result = xrealloc (result, rlen);
+ }
+
+ result[ri] = '\0';
+
+ return result;
+}
+
+
+#ifndef HAVE_STRERROR
+#undef strerror
+char *
+strerror (int errnum)
+{
+ extern int errno, sys_nerr;
+#ifndef __DECC
+ extern char *sys_errlist[];
+#endif
+ static char buf[] = "Unknown error 12345678901234567890";
+
+ if (errno < sys_nerr)
+ return sys_errlist[errnum];
+
+ sprintf (buf, _("Unknown error %d"), errnum);
+ return buf;
+}
+#endif
+
+/* Like malloc but get fatal error if memory is exhausted. */
+/* Don't bother if we're using dmalloc; it provides these for us. */
+
+#if !defined(HAVE_DMALLOC_H) && !defined(ELECTRIC_HEAP) /* bird */
+
+#undef xmalloc
+#undef xcalloc
+#undef xrealloc
+#undef xstrdup
+
+void *
+xmalloc (unsigned int size)
+{
+ /* Make sure we don't allocate 0, for pre-ISO implementations. */
+ void *result = malloc (size ? size : 1);
+ if (result == 0)
+ OUT_OF_MEM();
+
+#ifdef CONFIG_WITH_MAKE_STATS
+ make_stats_allocations++;
+ if (make_expensive_statistics)
+ make_stats_allocated += SIZE_OF_HEAP_BLOCK (result);
+ else
+ make_stats_allocated += size;
+#endif
+ return result;
+}
+
+
+void *
+xcalloc (unsigned int size)
+{
+ /* Make sure we don't allocate 0, for pre-ISO implementations. */
+ void *result = calloc (size ? size : 1, 1);
+ if (result == 0)
+ OUT_OF_MEM();
+
+#ifdef CONFIG_WITH_MAKE_STATS
+ make_stats_allocations++;
+ if (make_expensive_statistics)
+ make_stats_allocated += SIZE_OF_HEAP_BLOCK (result);
+ else
+ make_stats_allocated += size;
+#endif
+ return result;
+}
+
+
+void *
+xrealloc (void *ptr, unsigned int size)
+{
+ void *result;
+#ifdef CONFIG_WITH_MAKE_STATS
+ if (make_expensive_statistics && ptr != NULL)
+ make_stats_allocated -= SIZE_OF_HEAP_BLOCK (ptr);
+ if (ptr)
+ make_stats_reallocations++;
+ else
+ make_stats_allocations++;
+#endif
+
+ /* Some older implementations of realloc() don't conform to ISO. */
+ if (! size)
+ size = 1;
+ result = ptr ? realloc (ptr, size) : malloc (size);
+ if (result == 0)
+ OUT_OF_MEM();
+
+#ifdef CONFIG_WITH_MAKE_STATS
+ if (make_expensive_statistics)
+ make_stats_allocated += SIZE_OF_HEAP_BLOCK (result);
+ else
+ make_stats_allocated += size;
+#endif
+ return result;
+}
+
+
+char *
+xstrdup (const char *ptr)
+{
+ char *result;
+
+#ifdef HAVE_STRDUP
+ result = strdup (ptr);
+#else
+ result = malloc (strlen (ptr) + 1);
+#endif
+
+ if (result == 0)
+ OUT_OF_MEM();
+
+#ifdef CONFIG_WITH_MAKE_STATS
+ make_stats_allocations++;
+ if (make_expensive_statistics)
+ make_stats_allocated += SIZE_OF_HEAP_BLOCK (result);
+ else
+ make_stats_allocated += strlen (ptr) + 1;
+#endif
+#ifdef HAVE_STRDUP
+ return result;
+#else
+ return strcpy (result, ptr);
+#endif
+}
+
+#endif /* HAVE_DMALLOC_H */
+
+char *
+xstrndup (const char *str, unsigned int length)
+{
+ char *result;
+
+#if defined(HAVE_STRNDUP) && !defined(KMK)
+ result = strndup (str, length);
+ if (result == 0)
+ OUT_OF_MEM();
+#else
+ result = xmalloc (length + 1);
+ if (length > 0)
+ strncpy (result, str, length);
+ result[length] = '\0';
+#endif
+
+ return result;
+}
+
+
+#ifndef CONFIG_WITH_OPTIMIZATION_HACKS /* This is really a reimplemntation of
+ memchr, only slower. It's been replaced by a macro in the header file. */
+
+/* Limited INDEX:
+ Search through the string STRING, which ends at LIMIT, for the character C.
+ Returns a pointer to the first occurrence, or nil if none is found.
+ Like INDEX except that the string searched ends where specified
+ instead of at the first null. */
+
+char *
+lindex (const char *s, const char *limit, int c)
+{
+ while (s < limit)
+ if (*s++ == c)
+ return (char *)(s - 1);
+
+ return 0;
+}
+#endif /* CONFIG_WITH_OPTIMIZATION_HACKS */
+
+/* Return the address of the first whitespace or null in the string S. */
+
+char *
+end_of_token (const char *s)
+{
+#if 0 /* @todo def KMK */
+ for (;;)
+ {
+ unsigned char ch0, ch1, ch2, ch3;
+
+ ch0 = *s;
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch0)))
+ return (char *)s;
+ ch1 = s[1];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch1)))
+ return (char *)s + 1;
+ ch2 = s[2];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch2)))
+ return (char *)s + 2;
+ ch3 = s[3];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch3)))
+ return (char *)s + 3;
+
+ s += 4;
+ }
+
+#else
+ END_OF_TOKEN (s);
+ return (char *)s;
+#endif
+}
+
+/* Return the address of the first nonwhitespace or null in the string S. */
+
+char *
+next_token (const char *s)
+{
+#if 0 /* @todo def KMK */
+ for (;;)
+ {
+ unsigned char ch0, ch1, ch2, ch3;
+
+ ch0 = *s;
+ if (MY_PREDICT_FALSE(!MY_IS_BLANK(ch0)))
+ return (char *)s;
+ ch1 = s[1];
+ if (MY_PREDICT_TRUE(!MY_IS_BLANK(ch1)))
+ return (char *)s + 1;
+ ch2 = s[2];
+ if (MY_PREDICT_FALSE(!MY_IS_BLANK(ch2)))
+ return (char *)s + 2;
+ ch3 = s[3];
+ if (MY_PREDICT_TRUE(!MY_IS_BLANK(ch3)))
+ return (char *)s + 3;
+
+ s += 4;
+ }
+
+#else /* !KMK */
+ NEXT_TOKEN (s);
+ return (char *)s;
+#endif /* !KMK */
+}
+
+/* Find the next token in PTR; return the address of it, and store the length
+ of the token into *LENGTHPTR if LENGTHPTR is not nil. Set *PTR to the end
+ of the token, so this function can be called repeatedly in a loop. */
+
+char *
+find_next_token (const char **ptr, unsigned int *lengthptr)
+{
+#ifdef KMK
+ const char *p = *ptr;
+ const char *e;
+
+ /* skip blanks */
+# if 0 /* a moderate version */
+ for (;; p++)
+ {
+ unsigned char ch = *p;
+ if (!MY_IS_BLANK(ch))
+ {
+ if (!ch)
+ return NULL;
+ break;
+ }
+ }
+
+# else /* (too) big unroll */
+ for (;; p += 4)
+ {
+ unsigned char ch0, ch1, ch2, ch3;
+
+ ch0 = *p;
+ if (MY_PREDICT_FALSE(!MY_IS_BLANK(ch0)))
+ {
+ if (!ch0)
+ return NULL;
+ break;
+ }
+ ch1 = p[1];
+ if (MY_PREDICT_TRUE(!MY_IS_BLANK(ch1)))
+ {
+ if (!ch1)
+ return NULL;
+ p += 1;
+ break;
+ }
+ ch2 = p[2];
+ if (MY_PREDICT_FALSE(!MY_IS_BLANK(ch2)))
+ {
+ if (!ch2)
+ return NULL;
+ p += 2;
+ break;
+ }
+ ch3 = p[3];
+ if (MY_PREDICT_TRUE(!MY_IS_BLANK(ch3)))
+ {
+ if (!ch3)
+ return NULL;
+ p += 3;
+ break;
+ }
+ }
+# endif
+
+ /* skip ahead until EOS or blanks. */
+# if 0 /* a moderate version */
+ for (e = p + 1; ; e++)
+ {
+ unsigned char ch = *e;
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch)))
+ break;
+ }
+# else /* (too) big unroll */
+ for (e = p + 1; ; e += 4)
+ {
+ unsigned char ch0, ch1, ch2, ch3;
+
+ ch0 = *e;
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch0)))
+ break;
+ ch1 = e[1];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch1)))
+ {
+ e += 1;
+ break;
+ }
+ ch2 = e[2];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch2)))
+ {
+ e += 2;
+ break;
+ }
+ ch3 = e[3];
+ if (MY_PREDICT_FALSE(MY_IS_BLANK_OR_EOS(ch3)))
+ {
+ e += 3;
+ break;
+ }
+ }
+# endif
+ *ptr = e;
+
+ if (lengthptr != 0)
+ *lengthptr = e - p;
+
+ return (char *)p;
+
+#else
+ const char *p = next_token (*ptr);
+
+ if (*p == '\0')
+ return 0;
+
+ *ptr = end_of_token (p);
+ if (lengthptr != 0)
+ *lengthptr = *ptr - p;
+
+ return (char *)p;
+#endif
+}
+
+#ifdef KMK
+/* Finds the ends of the variable expansion starting at S, stopping at EOS if
+ not found before. */
+static char *find_end_of_variable_expansion (const char *s, char const *eos)
+{
+ char const openparen = s[1];
+ char const closeparen = openparen == '(' ? ')' : '}';
+ int levels = 0;
+
+ assert (s[0] == '$');
+ assert (s[1] == '(' || s[1] == '{');
+
+ s += 2;
+ while (s != eos)
+ {
+ unsigned char ch = *s;
+ if (ch != '\0')
+ {
+ if (ch != closeparen)
+ {
+ if (ch != openparen)
+ { /* likely */ }
+ else
+ levels++;
+ }
+ else if (levels <= 1)
+ break;
+ else
+ levels--;
+ }
+ else
+ break;
+ s++;
+ }
+
+ return (char *)s;
+}
+
+/* Same as find_next_token with two exception:
+ - The string ends at EOS or '\0'.
+ - We keep track of $() and ${}, allowing functions to be used. */
+
+char *
+find_next_token_eos (const char **ptr, const char *eos, unsigned int *lengthptr)
+{
+ const char *p = *ptr;
+ const char *e;
+
+ /* skip blanks */
+ while (p != eos)
+ {
+ unsigned char const ch = *p;
+ unsigned int const map = stopchar_map[ch] & (MAP_NUL | MAP_BLANK);
+ if (map & MAP_BLANK)
+ p++;
+ else if (!(map & MAP_NUL))
+ break;
+ else
+ return NULL;
+ }
+ if (p == eos)
+ return NULL;
+
+ /* skip ahead until EOS or blanks. */
+ e = p;
+ while (e != eos)
+ {
+ unsigned char const ch = *e;
+ unsigned int const map = stopchar_map[ch] & (MAP_NUL | MAP_BLANK | MAP_VARIABLE);
+ if (!map)
+ e++; /* likely */
+ /* Dollar can be escaped by duplication ($$) and when not, they need to
+ be skipped over. */
+ else if (map & MAP_VARIABLE)
+ {
+ e++;
+ if (&e[1] != eos)
+ {
+ unsigned ch2 = *e;
+ if (ch2 == ch)
+ e++; /* escaped */
+ else if (ch == '(' || ch == '}')
+ e = find_end_of_variable_expansion (e - 1, eos);
+ }
+ else
+ break;
+ }
+ else
+ break; /* MAP_NUL or MAP_BLANK */
+ }
+
+ *ptr = e;
+ if (lengthptr != 0)
+ *lengthptr = e - p;
+
+ return (char *)p;
+}
+
+/* Same as find_next_token_eos but takes GNU make quoting into account,
+ but without doing any unquoting like find_char_unquote & parse_file_seq. */
+
+char *
+find_next_file_token (const char **ptr, const char *eos, unsigned int *lengthptr)
+{
+ const char *p = *ptr;
+ const char *e;
+
+ /* skip blanks */
+ while (p != eos)
+ {
+ unsigned char const ch = *p;
+ unsigned int const map = stopchar_map[ch] & (MAP_NUL | MAP_BLANK);
+ if (map & MAP_BLANK)
+ p++;
+ else if (!(map & MAP_NUL))
+ break;
+ else
+ return NULL;
+ }
+ if (p == eos)
+ return NULL;
+
+ /* skip ahead until EOS or blanks. */
+ e = p;
+ while (e != eos)
+ {
+ unsigned char const ch = *e;
+ unsigned int const map = stopchar_map[ch] & (MAP_NUL | MAP_BLANK | MAP_VARIABLE);
+ if (!map)
+ e++; /* likely */
+ /* Dollar can be escaped by duplication ($$) and when not, they need to
+ be skipped over. */
+ else if (map & MAP_VARIABLE)
+ {
+ e++;
+ if (&e[1] != eos)
+ {
+ unsigned ch2 = *e;
+ if (ch2 == ch)
+ e++; /* escaped */
+ else if (ch == '(' || ch == '}')
+ e = find_end_of_variable_expansion (e - 1, eos);
+ }
+ else
+ break;
+ }
+ else if (map & MAP_NUL)
+ break;
+ /* A blank can be escaped using a backslash. */
+ else if (e[-1] != '\\')
+ break;
+ else
+ {
+ int slashes = 1;
+ while (&e[-slashes] != p && e[-slashes - 1] == '\\')
+ slashes++;
+ if (slashes & 1)
+ e++;
+ else
+ break;
+ }
+ }
+
+ *ptr = e;
+ if (lengthptr != 0)
+ *lengthptr = e - p;
+
+ return (char *)p;
+}
+
+#endif /* KMK */
+
+
+/* Copy a chain of 'struct dep'. For 2nd expansion deps, dup the name. */
+
+struct dep *
+copy_dep_chain (const struct dep *d)
+{
+ struct dep *firstnew = 0;
+ struct dep *lastnew = 0;
+
+ while (d != 0)
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ struct dep *c = xmalloc (sizeof (struct dep));
+#else
+ struct dep *c = alloccache_alloc(&dep_cache);
+#endif
+ memcpy (c, d, sizeof (struct dep));
+
+ /** @todo KMK: Check if we need this duplication! */
+ if (c->need_2nd_expansion)
+ c->name = xstrdup (c->name);
+
+ c->next = 0;
+ if (firstnew == 0)
+ firstnew = lastnew = c;
+ else
+ lastnew = lastnew->next = c;
+
+ d = d->next;
+ }
+
+ return firstnew;
+}
+
+/* Free a chain of struct nameseq.
+ For struct dep chains use free_dep_chain. */
+
+void
+free_ns_chain (struct nameseq *ns)
+{
+ while (ns != 0)
+ {
+ struct nameseq *t = ns;
+ ns = ns->next;
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ free_ns (t);
+#else
+ alloccache_free (&nameseq_cache, t);
+#endif
+ }
+}
+
+
+#ifdef CONFIG_WITH_ALLOC_CACHES
+
+void
+free_dep_chain (struct dep *d)
+{
+ while (d != 0)
+ {
+ struct dep *tofree = d;
+ d = d->next;
+ alloccache_free (&dep_cache, tofree);
+ }
+}
+
+void
+free_goal_chain (struct goaldep *g)
+{
+ while (g != 0)
+ {
+ struct goaldep *tofree = g;
+ g = g->next;
+ alloccache_free (&dep_cache, tofree);
+ }
+}
+
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+
+
+#if !HAVE_STRCASECMP && !HAVE_STRICMP && !HAVE_STRCMPI
+/* If we don't have strcasecmp() (from POSIX), or anything that can substitute
+ for it, define our own version. */
+
+int
+strcasecmp (const char *s1, const char *s2)
+{
+ while (1)
+ {
+ int c1 = (int) *(s1++);
+ int c2 = (int) *(s2++);
+
+ if (isalpha (c1))
+ c1 = tolower (c1);
+ if (isalpha (c2))
+ c2 = tolower (c2);
+
+ if (c1 != '\0' && c1 == c2)
+ continue;
+
+ return (c1 - c2);
+ }
+}
+#endif
+
+#if !HAVE_STRNCASECMP && !HAVE_STRNICMP && !HAVE_STRNCMPI
+/* If we don't have strncasecmp() (from POSIX), or anything that can
+ substitute for it, define our own version. */
+
+int
+strncasecmp (const char *s1, const char *s2, int n)
+{
+ while (n-- > 0)
+ {
+ int c1 = (int) *(s1++);
+ int c2 = (int) *(s2++);
+
+ if (isalpha (c1))
+ c1 = tolower (c1);
+ if (isalpha (c2))
+ c2 = tolower (c2);
+
+ if (c1 != '\0' && c1 == c2)
+ continue;
+
+ return (c1 - c2);
+ }
+
+ return 0;
+}
+#endif
+
+#ifdef GETLOADAVG_PRIVILEGED
+
+#ifdef POSIX
+
+/* Hopefully if a system says it's POSIX.1 and has the setuid and setgid
+ functions, they work as POSIX.1 says. Some systems (Alpha OSF/1 1.2,
+ for example) which claim to be POSIX.1 also have the BSD setreuid and
+ setregid functions, but they don't work as in BSD and only the POSIX.1
+ way works. */
+
+#undef HAVE_SETREUID
+#undef HAVE_SETREGID
+
+#else /* Not POSIX. */
+
+/* Some POSIX.1 systems have the seteuid and setegid functions. In a
+ POSIX-like system, they are the best thing to use. However, some
+ non-POSIX systems have them too but they do not work in the POSIX style
+ and we must use setreuid and setregid instead. */
+
+#undef HAVE_SETEUID
+#undef HAVE_SETEGID
+
+#endif /* POSIX. */
+
+#ifndef HAVE_UNISTD_H
+extern int getuid (), getgid (), geteuid (), getegid ();
+extern int setuid (), setgid ();
+#ifdef HAVE_SETEUID
+extern int seteuid ();
+#else
+#ifdef HAVE_SETREUID
+extern int setreuid ();
+#endif /* Have setreuid. */
+#endif /* Have seteuid. */
+#ifdef HAVE_SETEGID
+extern int setegid ();
+#else
+#ifdef HAVE_SETREGID
+extern int setregid ();
+#endif /* Have setregid. */
+#endif /* Have setegid. */
+#endif /* No <unistd.h>. */
+
+/* Keep track of the user and group IDs for user- and make- access. */
+static int user_uid = -1, user_gid = -1, make_uid = -1, make_gid = -1;
+#define access_inited (user_uid != -1)
+static enum { make, user } current_access;
+
+
+/* Under -d, write a message describing the current IDs. */
+
+static void
+log_access (const char *flavor)
+{
+ if (! ISDB (DB_JOBS))
+ return;
+
+ /* All the other debugging messages go to stdout,
+ but we write this one to stderr because it might be
+ run in a child fork whose stdout is piped. */
+
+ fprintf (stderr, _("%s: user %lu (real %lu), group %lu (real %lu)\n"),
+ flavor, (unsigned long) geteuid (), (unsigned long) getuid (),
+ (unsigned long) getegid (), (unsigned long) getgid ());
+ fflush (stderr);
+}
+
+
+static void
+init_access (void)
+{
+#ifndef VMS
+ user_uid = getuid ();
+ user_gid = getgid ();
+
+ make_uid = geteuid ();
+ make_gid = getegid ();
+
+ /* Do these ever fail? */
+ if (user_uid == -1 || user_gid == -1 || make_uid == -1 || make_gid == -1)
+ pfatal_with_name ("get{e}[gu]id");
+
+ log_access (_("Initialized access"));
+
+ current_access = make;
+#endif
+}
+
+#endif /* GETLOADAVG_PRIVILEGED */
+
+/* Give the process appropriate permissions for access to
+ user data (i.e., to stat files, or to spawn a child process). */
+void
+user_access (void)
+{
+#ifdef GETLOADAVG_PRIVILEGED
+
+ if (!access_inited)
+ init_access ();
+
+ if (current_access == user)
+ return;
+
+ /* We are in "make access" mode. This means that the effective user and
+ group IDs are those of make (if it was installed setuid or setgid).
+ We now want to set the effective user and group IDs to the real IDs,
+ which are the IDs of the process that exec'd make. */
+
+#ifdef HAVE_SETEUID
+
+ /* Modern systems have the seteuid/setegid calls which set only the
+ effective IDs, which is ideal. */
+
+ if (seteuid (user_uid) < 0)
+ pfatal_with_name ("user_access: seteuid");
+
+#else /* Not HAVE_SETEUID. */
+
+#ifndef HAVE_SETREUID
+
+ /* System V has only the setuid/setgid calls to set user/group IDs.
+ There is an effective ID, which can be set by setuid/setgid.
+ It can be set (unless you are root) only to either what it already is
+ (returned by geteuid/getegid, now in make_uid/make_gid),
+ the real ID (return by getuid/getgid, now in user_uid/user_gid),
+ or the saved set ID (what the effective ID was before this set-ID
+ executable (make) was exec'd). */
+
+ if (setuid (user_uid) < 0)
+ pfatal_with_name ("user_access: setuid");
+
+#else /* HAVE_SETREUID. */
+
+ /* In 4BSD, the setreuid/setregid calls set both the real and effective IDs.
+ They may be set to themselves or each other. So you have two alternatives
+ at any one time. If you use setuid/setgid, the effective will be set to
+ the real, leaving only one alternative. Using setreuid/setregid, however,
+ you can toggle between your two alternatives by swapping the values in a
+ single setreuid or setregid call. */
+
+ if (setreuid (make_uid, user_uid) < 0)
+ pfatal_with_name ("user_access: setreuid");
+
+#endif /* Not HAVE_SETREUID. */
+#endif /* HAVE_SETEUID. */
+
+#ifdef HAVE_SETEGID
+ if (setegid (user_gid) < 0)
+ pfatal_with_name ("user_access: setegid");
+#else
+#ifndef HAVE_SETREGID
+ if (setgid (user_gid) < 0)
+ pfatal_with_name ("user_access: setgid");
+#else
+ if (setregid (make_gid, user_gid) < 0)
+ pfatal_with_name ("user_access: setregid");
+#endif
+#endif
+
+ current_access = user;
+
+ log_access (_("User access"));
+
+#endif /* GETLOADAVG_PRIVILEGED */
+}
+
+/* Give the process appropriate permissions for access to
+ make data (i.e., the load average). */
+void
+make_access (void)
+{
+#ifdef GETLOADAVG_PRIVILEGED
+
+ if (!access_inited)
+ init_access ();
+
+ if (current_access == make)
+ return;
+
+ /* See comments in user_access, above. */
+
+#ifdef HAVE_SETEUID
+ if (seteuid (make_uid) < 0)
+ pfatal_with_name ("make_access: seteuid");
+#else
+#ifndef HAVE_SETREUID
+ if (setuid (make_uid) < 0)
+ pfatal_with_name ("make_access: setuid");
+#else
+ if (setreuid (user_uid, make_uid) < 0)
+ pfatal_with_name ("make_access: setreuid");
+#endif
+#endif
+
+#ifdef HAVE_SETEGID
+ if (setegid (make_gid) < 0)
+ pfatal_with_name ("make_access: setegid");
+#else
+#ifndef HAVE_SETREGID
+ if (setgid (make_gid) < 0)
+ pfatal_with_name ("make_access: setgid");
+#else
+ if (setregid (user_gid, make_gid) < 0)
+ pfatal_with_name ("make_access: setregid");
+#endif
+#endif
+
+ current_access = make;
+
+ log_access (_("Make access"));
+
+#endif /* GETLOADAVG_PRIVILEGED */
+}
+
+/* Give the process appropriate permissions for a child process.
+ This is like user_access, but you can't get back to make_access. */
+void
+child_access (void)
+{
+#ifdef GETLOADAVG_PRIVILEGED
+
+ if (!access_inited)
+ abort ();
+
+ /* Set both the real and effective UID and GID to the user's.
+ They cannot be changed back to make's. */
+
+#ifndef HAVE_SETREUID
+ if (setuid (user_uid) < 0)
+ pfatal_with_name ("child_access: setuid");
+#else
+ if (setreuid (user_uid, user_uid) < 0)
+ pfatal_with_name ("child_access: setreuid");
+#endif
+
+#ifndef HAVE_SETREGID
+ if (setgid (user_gid) < 0)
+ pfatal_with_name ("child_access: setgid");
+#else
+ if (setregid (user_gid, user_gid) < 0)
+ pfatal_with_name ("child_access: setregid");
+#endif
+
+ log_access (_("Child access"));
+
+#endif /* GETLOADAVG_PRIVILEGED */
+}
+
+#ifdef NEED_GET_PATH_MAX
+unsigned int
+get_path_max (void)
+{
+ static unsigned int value;
+
+ if (value == 0)
+ {
+ long int x = pathconf ("/", _PC_PATH_MAX);
+ if (x > 0)
+ value = x;
+ else
+ return MAXPATHLEN;
+ }
+
+ return value;
+}
+#endif
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+/* Print heap statistics if supported by the platform. */
+void
+print_heap_stats (void)
+{
+ /* Darwin / Mac OS X */
+# ifdef __APPLE__
+ malloc_statistics_t s;
+
+ malloc_zone_statistics (NULL, &s);
+ printf (_("\n# CRT Heap: %u bytes in use, in %u blocks, avg %u bytes/block\n"),
+ (unsigned)s.size_in_use, (unsigned)s.blocks_in_use,
+ s.blocks_in_use ? (unsigned)(s.size_in_use / s.blocks_in_use) : 0);
+ printf (_("# %u bytes max in use (high water mark)\n"),
+ (unsigned)s.max_size_in_use);
+ printf (_("# %u bytes reserved, %u bytes free (estimate)\n"),
+ (unsigned)s.size_allocated,
+ (unsigned)(s.size_allocated - s.size_in_use));
+# endif /* __APPLE__ */
+
+ /* MSC / Windows */
+# ifdef _MSC_VER
+ unsigned int blocks_used = 0;
+ unsigned int bytes_used = 0;
+ unsigned int blocks_avail = 0;
+ unsigned int bytes_avail = 0;
+ _HEAPINFO hinfo;
+
+ memset (&hinfo, '\0', sizeof (hinfo));
+ while (_heapwalk(&hinfo) == _HEAPOK)
+ {
+ if (hinfo._useflag == _USEDENTRY)
+ {
+ blocks_used++;
+ bytes_used += hinfo._size;
+ }
+ else
+ {
+ blocks_avail++;
+ bytes_avail += hinfo._size;
+ }
+ }
+
+ printf (_("\n# CRT Heap: %u bytes in use, in %u blocks, avg %u bytes/block\n"),
+ bytes_used, blocks_used, blocks_used ? bytes_used / blocks_used : 0);
+ printf (_("# %u bytes avail, in %u blocks, avg %u bytes/block\n"),
+ bytes_avail, blocks_avail, blocks_avail ? bytes_avail / blocks_avail : 0);
+# endif /* _MSC_VER */
+
+ /* Darwin Libc sources indicates that something like this may be
+ found in GLIBC, however, it's not in any current one... */
+# if 0 /* ??? */
+ struct mstats m;
+
+ m = mstats();
+ printf (_("\n# CRT Heap: %zu blocks / %zu bytes in use, %zu blocks / %zu bytes free\n"),
+ m.chunks_used, m.bytes_used, m.chunks_free, m.bytes_free);
+ printf (_("# %zu bytes reserved\n"),
+ m.bytes_total);
+# endif /* ??? */
+
+ /* XVID2/XPG mallinfo (displayed per GLIBC documentation). */
+# if defined(__GLIBC__) || defined(HAVE_MALLINFO)
+ struct mallinfo m;
+
+ m = mallinfo();
+ printf (_("\n# CRT Heap: %d bytes in use, %d bytes free\n"),
+ m.uordblks, m.fordblks);
+
+ printf (_("# # free chunks=%d, # fastbin blocks=%d\n"),
+ m.ordblks, m.smblks);
+ printf (_("# # mapped regions=%d, space in mapped regions=%d\n"),
+ m.hblks, m.hblkhd);
+ printf (_("# non-mapped space allocated from system=%d\n"),
+ m.arena);
+ printf (_("# maximum total allocated space=%d\n"),
+ m.usmblks);
+ printf (_("# top-most releasable space=%d\n"),
+ m.keepcost);
+# endif /* __GLIBC__ || HAVE_MALLINFO */
+
+# ifdef CONFIG_WITH_MAKE_STATS
+ printf(_("# %lu malloc calls, %lu realloc calls\n"),
+ make_stats_allocations, make_stats_reallocations);
+ printf(_("# %lu MBs alloc sum, not counting freed, add pinch of salt\n"), /* XXX: better wording */
+ make_stats_allocated / (1024*1024));
+# endif
+
+ /* XXX: windows */
+}
+#endif /* CONFIG_WITH_PRINT_STATS_SWITCH */
+
+#if defined(CONFIG_WITH_PRINT_TIME_SWITCH) || defined(CONFIG_WITH_KMK_BUILTIN_STATS)
+/* Get a nanosecond timestamp, from a monotonic time source if
+ possible. Returns -1 after calling error() on failure. */
+
+big_int
+nano_timestamp (void)
+{
+ big_int ts;
+#if defined (WINDOWS32)
+ static int s_state = -1;
+ static LARGE_INTEGER s_freq;
+
+ if (s_state == -1)
+ s_state = QueryPerformanceFrequency (&s_freq);
+ if (s_state)
+ {
+ LARGE_INTEGER pc;
+ if (!QueryPerformanceCounter (&pc))
+ {
+ s_state = 0;
+ return nano_timestamp ();
+ }
+ ts = (big_int)((long double)pc.QuadPart / (long double)s_freq.QuadPart * 1000000000);
+ }
+ else
+ {
+ /* fall back to low resolution system time. */
+ LARGE_INTEGER bigint;
+ FILETIME ft = {0,0};
+ GetSystemTimeAsFileTime (&ft);
+ bigint.u.LowPart = ft.dwLowDateTime;
+ bigint.u.HighPart = ft.dwLowDateTime;
+ ts = bigint.QuadPart * 100;
+ }
+
+#elif HAVE_GETTIMEOFDAY
+/* FIXME: Linux and others have the realtime clock_* api, detect and use it. */
+ struct timeval tv;
+ if (!gettimeofday (&tv, NULL))
+ ts = (big_int)tv.tv_sec * 1000000000
+ + tv.tv_usec * 1000;
+ else
+ {
+ O (error, NILF, _("gettimeofday failed"));
+ ts = -1;
+ }
+
+#else
+# error "PORTME"
+#endif
+
+ return ts;
+}
+
+/* Formats the elapsed time (nano seconds) in the manner easiest
+ to read, with millisecond percision for larger numbers. */
+
+int
+format_elapsed_nano (char *buf, size_t size, big_int ts)
+{
+ unsigned sz;
+ if (ts < 1000)
+ sz = sprintf (buf, "%uns", (unsigned)ts);
+ else if (ts < 100000)
+ sz = sprintf (buf, "%u.%03uus",
+ (unsigned)(ts / 1000),
+ (unsigned)(ts % 1000));
+ else
+ {
+ ts /= 1000;
+ if (ts < 1000)
+ sz = sprintf (buf, "%uus", (unsigned)ts);
+ else if (ts < 100000)
+ sz = sprintf (buf, "%u.%03ums",
+ (unsigned)(ts / 1000),
+ (unsigned)(ts % 1000));
+ else
+ {
+ ts /= 1000;
+ if (ts < BIG_INT_C(60000))
+ sz = sprintf (buf,
+ "%u.%03us",
+ (unsigned)(ts / 1000),
+ (unsigned)(ts % 1000));
+ else
+ sz = sprintf (buf,
+ "%um%u.%03us",
+ (unsigned)( ts / BIG_INT_C(60000)),
+ (unsigned)((ts % BIG_INT_C(60000)) / 1000),
+ (unsigned)((ts % BIG_INT_C(60000)) % 1000));
+ }
+ }
+ if (sz >= size)
+ ONN (fatal, NILF, _("format_elapsed_nano buffer overflow: %u written, %lu buffer"),
+ sz, (unsigned long)size);
+ return sz;
+}
+#endif /* CONFIG_WITH_PRINT_TIME_SWITCH || defined(CONFIG_WITH_KMK_BUILTIN_STATS) */
+
diff --git a/src/kmk/os.h b/src/kmk/os.h
new file mode 100644
index 0000000..c1a19e1
--- /dev/null
+++ b/src/kmk/os.h
@@ -0,0 +1,84 @@
+/* Declarations for operating system interfaces for GNU Make.
+Copyright (C) 2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* This section provides OS-specific functions to support the jobserver. */
+
+#ifdef MAKE_JOBSERVER
+
+/* Returns 1 if the jobserver is enabled, else 0. */
+unsigned int jobserver_enabled (void);
+
+/* Called in the master instance to set up the jobserver initially. */
+unsigned int jobserver_setup (int job_slots);
+
+/* Called in a child instance to connect to the jobserver. */
+unsigned int jobserver_parse_auth (const char* auth);
+
+/* Returns an allocated buffer used to pass to child instances. */
+char *jobserver_get_auth (void);
+
+/* Clear this instance's jobserver configuration. */
+void jobserver_clear (void);
+
+/* Recover all the jobserver tokens and return the number we got. */
+unsigned int jobserver_acquire_all (void);
+
+/* Release a jobserver token. If it fails and is_fatal is 1, fatal. */
+void jobserver_release (int is_fatal);
+
+/* Notify the jobserver that a child exited. */
+void jobserver_signal (void);
+
+/* Get ready to start a non-recursive child. */
+void jobserver_pre_child (int);
+
+/* Complete starting a non-recursive child. */
+void jobserver_post_child (int);
+
+/* Set up to acquire a new token. */
+void jobserver_pre_acquire (void);
+
+/* Wait until we can acquire a jobserver token.
+ TIMEOUT is 1 if we have other jobs waiting for the load to go down;
+ in this case we won't wait forever, so we can check the load.
+ Returns 1 if we got a token, or 0 if we stopped waiting due to a child
+ exiting or a timeout. */
+unsigned int jobserver_acquire (int timeout);
+
+#else
+
+#define jobserver_enabled() (0)
+#define jobserver_setup(_slots) (0)
+#define jobserver_parse_auth(_auth) (0)
+#define jobserver_get_auth() (NULL)
+#define jobserver_clear() (void)(0)
+#define jobserver_release(_fatal) (void)(0)
+#define jobserver_acquire_all() (0)
+#define jobserver_signal() (void)(0)
+#define jobserver_pre_child(_r) (void)(0)
+#define jobserver_post_child(_r) (void)(0)
+#define jobserver_pre_acquire() (void)(0)
+#define jobserver_acquire(_tmout) (0)
+
+#endif
+
+/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
+#if !defined(VMD) && !defined(WINDOWS32) && !defined(_AMIGA) && !defined(__MSDOS__)
+int get_bad_stdin (void);
+#else
+# define get_bad_stdin() (-1)
+#endif
diff --git a/src/kmk/output.c b/src/kmk/output.c
new file mode 100644
index 0000000..e37680e
--- /dev/null
+++ b/src/kmk/output.c
@@ -0,0 +1,1392 @@
+/* Output to stdout / stderr for GNU make
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "job.h"
+
+/* GNU make no longer supports pre-ANSI89 environments. */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#else
+# include <sys/file.h>
+#endif
+
+#ifdef WINDOWS32
+# include <windows.h>
+# include <io.h>
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "w32/winchildren.h"
+# endif
+#endif /* WINDOWS32 */
+#ifdef KBUILD_OS_WINDOWS
+# include "console.h"
+#endif
+
+struct output *output_context = NULL;
+unsigned int stdio_traced = 0;
+
+#define OUTPUT_NONE (-1)
+
+#define OUTPUT_ISSET(_out) ((_out)->out >= 0 || (_out)->err >= 0)
+
+#ifdef HAVE_FCNTL_H
+# define STREAM_OK(_s) ((fcntl (fileno (_s), F_GETFD) != -1) || (errno != EBADF))
+#else
+# define STREAM_OK(_s) 1
+#endif
+
+
+#if defined(KMK) && !defined(NO_OUTPUT_SYNC)
+/* Non-negative if we're counting output lines.
+
+ This is used by die_with_job_output to decide whether the initial build
+ error needs to be repeated because there was too much output from parallel
+ jobs between it and the actual make termination. */
+int output_metered = -1;
+
+static void meter_output_block (char const *buffer, size_t len)
+{
+ while (len > 0)
+ {
+ char *nl = (char *)memchr (buffer, '\n', len);
+ size_t linelen;
+ if (nl)
+ {
+ linelen = nl - buffer + 1;
+ output_metered++;
+ }
+ else
+ linelen = len;
+ output_metered += linelen / 132;
+
+ /* advance */
+ buffer += linelen;
+ len -= linelen;
+ }
+}
+#endif
+
+
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+# define MEMBUF_MIN_SEG_SIZE 4096
+# define MEMBUF_MAX_SEG_SIZE (512*1024)
+# define MEMBUF_MAX_MOVE_LEN ( MEMBUF_MIN_SEG_SIZE \
+ - offsetof (struct output_segment, runs) \
+ - sizeof (struct output_run))
+# define MEMBUF_MAX_TOTAL ( sizeof (void *) <= 4 \
+ ? (size_t)512*1024 : (size_t)16*1024*1024 )
+
+static void *acquire_semaphore (void);
+static void release_semaphore (void *);
+static int log_working_directory (int);
+
+/* Is make's stdout going to the same place as stderr?
+ Also, did we already sync_init (== -1)? */
+static int combined_output = -1;
+
+/* Helper for membuf_reset and output_reset */
+static membuf_reset (struct output *out)
+{
+ struct output_segment *seg;
+ while ((seg = out->out.head_seg))
+ {
+ out->out.head_seg = seg->next;
+ free (seg);
+ }
+ out->out.tail_seg = NULL;
+ out->out.tail_run = NULL;
+ out->out.head_run = NULL;
+ out->out.left = 0;
+ out->out.total = 0;
+
+ while ((seg = out->err.head_seg))
+ {
+ out->err.head_seg = seg->next;
+ free (seg);
+ }
+ out->err.tail_seg = NULL;
+ out->err.tail_run = NULL;
+ out->err.head_run = NULL;
+ out->err.left = 0;
+ out->err.total = 0;
+
+ out->seqno = 0;
+}
+
+/* Used by die_with_job_output to suppress output when it shouldn't be repeated. */
+void output_reset (struct output *out)
+{
+ if (out && (out->out.total || out->err.total))
+ membuf_reset (out);
+}
+
+/* Internal worker for output_dump and membuf_dump_most. */
+static void membuf_dump (struct output *out)
+{
+ if (out->out.total || out->err.total)
+ {
+ int traced = 0;
+ struct output_run *err_run;
+ struct output_run *out_run;
+ FILE *prevdst;
+
+ /* Try to acquire the semaphore. If it fails, dump the output
+ unsynchronized; still better than silently discarding it.
+ We want to keep this lock for as little time as possible. */
+ void *sem = acquire_semaphore ();
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ int prev_mode_out = _setmode (fileno (stdout), _O_BINARY);
+ int prev_mode_err = _setmode (fileno (stderr), _O_BINARY);
+# endif
+
+# ifndef KMK /* this drives me bananas. */
+ /* Log the working directory for this dump. */
+ if (print_directory_flag && output_sync != OUTPUT_SYNC_RECURSE)
+ traced = log_working_directory (1);
+# endif
+
+ /* Work the out and err sequences in parallel. */
+ out_run = out->out.head_run;
+ err_run = out->err.head_run;
+ prevdst = NULL;
+ while (err_run || out_run)
+ {
+ FILE *dst;
+ const void *src;
+ size_t len;
+ if (out_run && (!err_run || out_run->seqno <= err_run->seqno))
+ {
+ src = out_run + 1;
+ len = out_run->len;
+ dst = stdout;
+ out_run = out_run->next;
+ }
+ else
+ {
+ src = err_run + 1;
+ len = err_run->len;
+ dst = stderr;
+ err_run = err_run->next;
+ }
+ if (dst != prevdst)
+ fflush(prevdst);
+ prevdst = dst;
+#ifdef KMK
+ if (output_metered < 0)
+ { /* likely */ }
+ else
+ meter_output_block (src, len);
+#endif
+# if 0 /* for debugging */
+ while (len > 0)
+ {
+ const char *nl = (const char *)memchr (src, '\n', len);
+ size_t line_len = nl ? nl - (const char *)src + 1 : len;
+ char *tmp = (char *)xmalloc (1 + line_len + 1 + 1);
+ tmp[0] = '{';
+ memcpy (&tmp[1], src, line_len);
+ tmp[1 + line_len] = '}';
+# ifdef KBUILD_OS_WINDOWS
+ maybe_con_fwrite (tmp, 1 + line_len + 1, 1, dst);
+# else
+ fwrite (tmp, 1 + line_len + 1, 1, dst);
+# endif
+ free (tmp);
+ src = (const char *)src + line_len;
+ len -= line_len;
+ }
+#else
+# ifdef KBUILD_OS_WINDOWS
+ maybe_con_fwrite (src, len, 1, dst);
+# else
+ fwrite (src, len, 1, dst);
+# endif
+# endif
+ }
+ if (prevdst)
+ fflush (prevdst);
+
+# ifndef KMK /* this drives me bananas. */
+ if (traced)
+ log_working_directory (0);
+# endif
+
+ /* Exit the critical section. */
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ _setmode (fileno (stdout), prev_mode_out);
+ _setmode (fileno (stderr), prev_mode_err);
+# endif
+ if (sem)
+ release_semaphore (sem);
+
+# ifdef KMK
+ if (!out->dont_truncate)
+ { /* likely */ }
+ else return;
+# endif
+
+ /* Free the segments and reset the state. */
+ membuf_reset (out);
+ }
+ else
+ assert (out->out.head_seg == NULL && out->err.head_seg == NULL);
+}
+
+/* Writes up to LEN bytes to the given segment.
+ Returns how much was actually written. */
+static size_t
+membuf_write_segment (struct output_membuf *membuf, struct output_segment *seg,
+ const char *src, size_t len, unsigned int *pseqno)
+{
+ size_t written = 0;
+ if (seg && membuf->left > 0)
+ {
+ struct output_run *run = membuf->tail_run;
+ char *dst = (char *)(run + 1) + run->len;
+ assert ((uintptr_t)run - (uintptr_t)seg < seg->size);
+
+ /* If the sequence number didn't change, then we can append
+ to the current run without further considerations. */
+ if (run->seqno == *pseqno)
+ written = len;
+ /* If the current run does not end with a newline, don't start a new
+ run till we encounter one. */
+ else if (dst[-1] != '\n')
+ {
+ char const *srcnl = (const char *)memchr (src, '\n', len);
+ written = srcnl ? srcnl - src + 1 : len;
+ }
+ /* Try create a new empty run and append to it. */
+ else
+ {
+ size_t const offnextrun = ( (uintptr_t)dst - (uintptr_t)(seg)
+ + sizeof(void *) - 1)
+ & ~(sizeof(void *) - 1);
+ if (offnextrun > seg->size - sizeof (struct output_run) * 2)
+ return 0; /* need new segment */
+
+ run = run->next = (struct output_run *)((char *)seg + offnextrun);
+ run->next = NULL;
+ run->seqno = ++(*pseqno);
+ run->len = 0;
+ membuf->tail_run = run;
+ membuf->left = seg->size - (offnextrun + sizeof (*run));
+ dst = (char *)(run + 1);
+ written = len;
+ }
+
+ /* Append to the current run. */
+ if (written > membuf->left)
+ written = membuf->left;
+ memcpy (dst, src, written);
+ run->len += written;
+ membuf->left -= written;
+ }
+ return written;
+}
+
+/* Helper for membuf_write_new_segment and membuf_dump_most that figures out
+ now much data needs to be moved from the previous run in order to make it
+ end with a newline. */
+static size_t membuf_calc_move_len (struct output_run *tail_run)
+{
+ size_t to_move = 0;
+ if (tail_run)
+ {
+ const char *data = (const char *)(tail_run + 1);
+ size_t off = tail_run->len;
+ while (off > 0 && data[off - 1] != '\n')
+ off--;
+ to_move = tail_run->len - off;
+ if (to_move >= MEMBUF_MAX_MOVE_LEN)
+ to_move = 0;
+ }
+ return to_move;
+}
+
+/* Allocates a new segment and writes to it.
+ This will take care to make sure the previous run terminates with
+ a newline so that we pass whole lines to fwrite when dumping. */
+static size_t
+membuf_write_new_segment (struct output_membuf *membuf, const char *src,
+ size_t len, unsigned int *pseqno)
+{
+ struct output_run *prev_run = membuf->tail_run;
+ struct output_segment *prev_seg = membuf->tail_seg;
+ size_t const to_move = membuf_calc_move_len (prev_run);
+ struct output_segment *new_seg;
+ size_t written;
+ char *dst;
+
+ /* Figure the the segment size. We start with MEMBUF_MIN_SEG_SIZE and double
+ it each time till we reach MEMBUF_MAX_SEG_SIZE. */
+ size_t const offset_runs = offsetof (struct output_segment, runs);
+ size_t segsize = !prev_seg ? MEMBUF_MIN_SEG_SIZE
+ : prev_seg->size >= MEMBUF_MAX_SEG_SIZE ? MEMBUF_MAX_SEG_SIZE
+ : prev_seg->size * 2;
+ while ( segsize < to_move + len + offset_runs + sizeof (struct output_run) * 2
+ && segsize < MEMBUF_MAX_SEG_SIZE)
+ segsize *= 2;
+
+ /* Allocate the segment and link it and the first run. */
+ new_seg = (struct output_segment *)xmalloc (segsize);
+ new_seg->size = segsize;
+ new_seg->next = NULL;
+ new_seg->runs[0].next = NULL;
+ if (!prev_seg)
+ {
+ membuf->head_seg = new_seg;
+ membuf->head_run = &new_seg->runs[0];
+ }
+ else
+ {
+ prev_seg->next = new_seg;
+ prev_run->next = &new_seg->runs[0];
+ }
+ membuf->tail_seg = new_seg;
+ membuf->tail_run = &new_seg->runs[0];
+ membuf->total += segsize;
+ membuf->left = segsize - sizeof (struct output_run) - offset_runs;
+
+ /* Initialize and write data to the first run. */
+ dst = (char *)&new_seg->runs[0]; /* Try bypass gcc array size cleverness. */
+ dst += sizeof (struct output_run);
+ assert (MEMBUF_MAX_MOVE_LEN < MEMBUF_MIN_SEG_SIZE);
+ if (to_move > 0)
+ {
+ /* Move to_move bytes from the previous run in hope that we'll get a
+ newline to soon. Afterwards call membuf_segment_write to work SRC. */
+ assert (prev_run != NULL);
+ assert (membuf->left >= to_move);
+ prev_run->len -= to_move;
+ new_seg->runs[0].len = to_move;
+ new_seg->runs[0].seqno = prev_run->seqno;
+ memcpy (dst, (const char *)(prev_run + 1) + prev_run->len, to_move);
+ membuf->left -= to_move;
+
+ written = membuf_write_segment (membuf, new_seg, src, len, pseqno);
+ }
+ else
+ {
+ /* Create a run with up to LEN from SRC. */
+ written = len;
+ if (written > membuf->left)
+ written = membuf->left;
+ new_seg->runs[0].len = written;
+ new_seg->runs[0].seqno = ++(*pseqno);
+ memcpy (dst, src, written);
+ membuf->left -= written;
+ }
+ return written;
+}
+
+/* Worker for output_write that will dump most of the output when we hit
+ MEMBUF_MAX_TOTAL on either of the two membuf structures, then free all the
+ output segments. Incomplete lines will be held over to the next buffers
+ and copied into new segments. */
+static void
+membuf_dump_most (struct output *out)
+{
+ size_t out_to_move = membuf_calc_move_len (out->out.tail_run);
+ size_t err_to_move = membuf_calc_move_len (out->err.tail_run);
+ if (!out_to_move && !err_to_move)
+ membuf_dump (out);
+ else
+ {
+ /* Allocate a stack buffer for holding incomplete lines. This should be
+ fine since we're only talking about max 2 * MEMBUF_MAX_MOVE_LEN.
+ The -1 on the sequence numbers, ise because membuf_write_new_segment
+ will increment them before use. */
+ unsigned int out_seqno = out_to_move ? out->out.tail_run->seqno - 1 : 0;
+ unsigned int err_seqno = err_to_move ? out->err.tail_run->seqno - 1 : 0;
+ char *tmp = alloca (out_to_move + err_to_move);
+ if (out_to_move)
+ {
+ out->out.tail_run->len -= out_to_move;
+ memcpy (tmp,
+ (char *)(out->out.tail_run + 1) + out->out.tail_run->len,
+ out_to_move);
+ }
+ if (err_to_move)
+ {
+ out->err.tail_run->len -= err_to_move;
+ memcpy (tmp + out_to_move,
+ (char *)(out->err.tail_run + 1) + out->err.tail_run->len,
+ err_to_move);
+ }
+
+ membuf_dump (out);
+
+ if (out_to_move)
+ {
+ size_t written = membuf_write_new_segment (&out->out, tmp,
+ out_to_move, &out_seqno);
+ assert (written == out_to_move); (void)written;
+ }
+ if (err_to_move)
+ {
+ size_t written = membuf_write_new_segment (&out->err,
+ tmp + out_to_move,
+ err_to_move, &err_seqno);
+ assert (written == err_to_move); (void)written;
+ }
+ }
+}
+
+
+/* write/fwrite like function, binary mode. */
+ssize_t
+output_write_bin (struct output *out, int is_err, const char *src, size_t len)
+{
+ size_t ret = len;
+ if (!out || !out->syncout)
+ {
+ FILE *f = is_err ? stderr : stdout;
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ /* On DOS platforms we need to disable \n -> \r\n converts that is common on
+ standard output/error. Also optimize for console output. */
+ int saved_errno;
+ int fd = fileno (f);
+ int prev_mode = _setmode (fd, _O_BINARY);
+ maybe_con_fwrite (src, len, 1, f);
+ if (fflush (f) == EOF)
+ ret = -1;
+ saved_errno = errno;
+ _setmode (fd, prev_mode);
+ errno = saved_errno;
+# else
+ fwrite (src, len, 1, f);
+ if (fflush (f) == EOF)
+ ret = -1;
+# endif
+ }
+ else
+ {
+ struct output_membuf *membuf = is_err ? &out->err : &out->out;
+ while (len > 0)
+ {
+ size_t runlen = membuf_write_segment (membuf, membuf->tail_seg, src, len, &out->seqno);
+ if (!runlen)
+ {
+ if (membuf->total < MEMBUF_MAX_TOTAL)
+ runlen = membuf_write_new_segment (membuf, src, len, &out->seqno);
+ else
+ membuf_dump_most (out);
+ }
+ /* advance */
+ len -= runlen;
+ src += runlen;
+ }
+ }
+ return ret;
+}
+
+#endif /* CONFIG_WITH_OUTPUT_IN_MEMORY */
+
+/* write/fwrite like function, text mode. */
+ssize_t
+output_write_text (struct output *out, int is_err, const char *src, size_t len)
+{
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ ssize_t ret = len;
+ if (!out || !out->syncout)
+ {
+ /* ASSUME fwrite does the desired conversion. */
+ FILE *f = is_err ? stderr : stdout;
+# ifdef KBUILD_OS_WINDOWS
+ if (maybe_con_fwrite (src, len, 1, f) < 0)
+ ret = -1;
+# else
+ fwrite (src, len, 1, f);
+# endif
+ if (fflush (f) == EOF)
+ ret = -1;
+ }
+ else
+ {
+ /* Work the buffer line by line, replacing each \n with \r\n. */
+ while (len > 0)
+ {
+ const char *nl = memchr ( src, '\n', len);
+ size_t line_len = nl ? nl - src : len;
+ output_write_bin (out, is_err, src, line_len);
+ if (!nl)
+ break;
+ output_write_bin (out, is_err, "\r\n", 2);
+ len -= line_len + 1;
+ src += line_len + 1;
+ }
+ }
+ return ret;
+# else
+ return output_write_bin (out, is_err, src, len);
+# endif
+#else
+ ssize_t ret = len;
+ if (! out || ! out->syncout)
+ {
+ FILE *f = is_err ? stderr : stdout;
+# ifdef KBUILD_OS_WINDOWS
+ maybe_con_fwrite(src, len, 1, f);
+# else
+ fwrite (src, len, 1, f);
+# endif
+ fflush (f);
+ }
+ else
+ {
+ int fd = is_err ? out->err : out->out;
+ int r;
+
+ EINTRLOOP (r, lseek (fd, 0, SEEK_END));
+ while (1)
+ {
+ EINTRLOOP (r, write (fd, src, len));
+ if ((size_t)r == len || r <= 0)
+ break;
+ len -= r;
+ src += r;
+ }
+ }
+ return ret;
+#endif
+}
+
+
+
+/* Write a string to the current STDOUT or STDERR. */
+static void
+_outputs (struct output *out, int is_err, const char *msg)
+{
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ output_write_text (out, is_err, msg, strlen (msg));
+#else /* !CONFIG_WITH_OUTPUT_IN_MEMORY */
+ if (! out || ! out->syncout)
+ {
+ FILE *f = is_err ? stderr : stdout;
+# ifdef KBUILD_OS_WINDOWS
+ maybe_con_fwrite(msg, strlen(msg), 1, f);
+# else
+ fputs (msg, f);
+# endif
+ fflush (f);
+ }
+ else
+ {
+ int fd = is_err ? out->err : out->out;
+ int len = strlen (msg);
+ int r;
+
+ EINTRLOOP (r, lseek (fd, 0, SEEK_END));
+ while (1)
+ {
+ EINTRLOOP (r, write (fd, msg, len));
+ if (r == len || r <= 0)
+ break;
+ len -= r;
+ msg += r;
+ }
+ }
+#endif /* !CONFIG_WITH_OUTPUT_IN_MEMORY */
+}
+
+/* Write a message indicating that we've just entered or
+ left (according to ENTERING) the current directory. */
+
+static int
+log_working_directory (int entering)
+{
+ static char *buf = NULL;
+ static unsigned int len = 0;
+ unsigned int need;
+ const char *fmt;
+ char *p;
+
+ /* Get enough space for the longest possible output. */
+ need = strlen (program) + INTSTR_LENGTH + 2 + 1;
+ if (starting_directory)
+ need += strlen (starting_directory);
+
+ /* Use entire sentences to give the translators a fighting chance. */
+ if (makelevel == 0)
+ if (starting_directory == 0)
+ if (entering)
+ fmt = _("%s: Entering an unknown directory\n");
+ else
+ fmt = _("%s: Leaving an unknown directory\n");
+ else
+ if (entering)
+ fmt = _("%s: Entering directory '%s'\n");
+ else
+ fmt = _("%s: Leaving directory '%s'\n");
+ else
+ if (starting_directory == 0)
+ if (entering)
+ fmt = _("%s[%u]: Entering an unknown directory\n");
+ else
+ fmt = _("%s[%u]: Leaving an unknown directory\n");
+ else
+ if (entering)
+ fmt = _("%s[%u]: Entering directory '%s'\n");
+ else
+ fmt = _("%s[%u]: Leaving directory '%s'\n");
+
+ need += strlen (fmt);
+
+ if (need > len)
+ {
+ buf = xrealloc (buf, need);
+ len = need;
+ }
+
+ p = buf;
+ if (print_data_base_flag)
+ {
+ *(p++) = '#';
+ *(p++) = ' ';
+ }
+
+ if (makelevel == 0)
+ if (starting_directory == 0)
+ sprintf (p, fmt , program);
+ else
+ sprintf (p, fmt, program, starting_directory);
+ else if (starting_directory == 0)
+ sprintf (p, fmt, program, makelevel);
+ else
+ sprintf (p, fmt, program, makelevel, starting_directory);
+
+ _outputs (NULL, 0, buf);
+
+ return 1;
+}
+
+/* Set a file descriptor to be in O_APPEND mode.
+ If it fails, just ignore it. */
+
+static void
+set_append_mode (int fd)
+{
+#if defined(F_GETFL) && defined(F_SETFL) && defined(O_APPEND)
+ int flags = fcntl (fd, F_GETFL, 0);
+ if (flags >= 0)
+ fcntl (fd, F_SETFL, flags | O_APPEND);
+#endif
+}
+
+
+#ifndef NO_OUTPUT_SYNC
+
+/* Semaphore for use in -j mode with output_sync. */
+static sync_handle_t sync_handle = -1;
+
+#define FD_NOT_EMPTY(_f) ((_f) != OUTPUT_NONE && lseek ((_f), 0, SEEK_END) > 0)
+
+/* Set up the sync handle. Disables output_sync on error. */
+static int
+sync_init (void)
+{
+ int combined_output = 0;
+
+#ifdef WINDOWS32
+# ifdef CONFIG_NEW_WIN_CHILDREN
+ if (STREAM_OK (stdout))
+ {
+ if (STREAM_OK (stderr))
+ {
+ char mtxname[256];
+ sync_handle = create_mutex (mtxname, sizeof (mtxname));
+ if (sync_handle != -1)
+ {
+ prepare_mutex_handle_string (mtxname);
+ return same_stream (stdout, stderr);
+ }
+ perror_with_name ("output-sync suppressed: ", "create_mutex");
+ }
+ else
+ perror_with_name ("output-sync suppressed: ", "stderr");
+ }
+ else
+ perror_with_name ("output-sync suppressed: ", "stdout");
+ output_sync = OUTPUT_SYNC_NONE;
+
+# else /* !CONFIG_NEW_WIN_CHILDREN */
+ if ((!STREAM_OK (stdout) && !STREAM_OK (stderr))
+ || (sync_handle = create_mutex ()) == -1)
+ {
+ perror_with_name ("output-sync suppressed: ", "stderr");
+ output_sync = 0;
+ }
+ else
+ {
+ combined_output = same_stream (stdout, stderr);
+ prepare_mutex_handle_string (sync_handle);
+ }
+# endif /* !CONFIG_NEW_WIN_CHILDREN */
+
+#else
+ if (STREAM_OK (stdout))
+ {
+ struct stat stbuf_o, stbuf_e;
+
+ sync_handle = fileno (stdout);
+ combined_output = (fstat (fileno (stdout), &stbuf_o) == 0
+ && fstat (fileno (stderr), &stbuf_e) == 0
+ && stbuf_o.st_dev == stbuf_e.st_dev
+ && stbuf_o.st_ino == stbuf_e.st_ino);
+ }
+ else if (STREAM_OK (stderr))
+ sync_handle = fileno (stderr);
+ else
+ {
+ perror_with_name ("output-sync suppressed: ", "stderr");
+ output_sync = 0;
+ }
+#endif
+
+ return combined_output;
+}
+
+#ifndef CONFIG_WITH_OUTPUT_IN_MEMORY
+/* Support routine for output_sync() */
+static void
+pump_from_tmp (int from, FILE *to)
+{
+# ifdef KMK
+ char buffer[8192];
+# else
+ static char buffer[8192];
+#endif
+
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ int prev_mode;
+
+ /* "from" is opened by open_tmpfd, which does it in binary mode, so
+ we need the mode of "to" to match that. */
+ prev_mode = _setmode (fileno (to), O_BINARY);
+#endif
+
+ if (lseek (from, 0, SEEK_SET) == -1)
+ perror ("lseek()");
+
+ while (1)
+ {
+ int len;
+ EINTRLOOP (len, read (from, buffer, sizeof (buffer)));
+ if (len < 0)
+ perror ("read()");
+ if (len <= 0)
+ break;
+#ifdef KMK
+ if (output_metered < 0)
+ { /* likely */ }
+ else
+ meter_output_block (buffer, len);
+#endif
+ if (fwrite (buffer, len, 1, to) < 1)
+ {
+ perror ("fwrite()");
+ break;
+ }
+ fflush (to);
+ }
+
+# if defined (KBUILD_OS_WINDOWS) || defined (KBUILD_OS_OS2) || defined (KBUILD_OS_DOS)
+ /* Switch "to" back to its original mode, so that log messages by
+ Make have the same EOL format as without --output-sync. */
+ _setmode (fileno (to), prev_mode);
+#endif
+}
+#endif /* CONFIG_WITH_OUTPUT_IN_MEMORY */
+
+/* Obtain the lock for writing output. */
+static void *
+acquire_semaphore (void)
+{
+ static struct flock fl;
+
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 1;
+ if (fcntl (sync_handle, F_SETLKW, &fl) != -1)
+ return &fl;
+#ifdef KBUILD_OS_DARWIN /* F_SETLKW isn't supported on pipes */
+ if (errno != EBADF)
+#endif
+ perror ("fcntl()");
+ return NULL;
+}
+
+/* Release the lock for writing output. */
+static void
+release_semaphore (void *sem)
+{
+ struct flock *flp = (struct flock *)sem;
+ flp->l_type = F_UNLCK;
+ if (fcntl (sync_handle, F_SETLKW, flp) == -1)
+ perror ("fcntl()");
+}
+
+#ifndef CONFIG_WITH_OUTPUT_IN_MEMORY
+
+/* Returns a file descriptor to a temporary file. The file is automatically
+ closed/deleted on exit. Don't use a FILE* stream. */
+int
+output_tmpfd (void)
+{
+ int fd = -1;
+ FILE *tfile = tmpfile ();
+
+ if (! tfile)
+ {
+#ifdef KMK
+ if (output_context && output_context->syncout)
+ output_context->syncout = 0; /* Avoid inifinit recursion. */
+#endif
+ pfatal_with_name ("tmpfile");
+ }
+
+ /* Create a duplicate so we can close the stream. */
+ fd = dup (fileno (tfile));
+ if (fd < 0)
+ {
+#ifdef KMK
+ if (output_context && output_context->syncout)
+ output_context->syncout = 0; /* Avoid inifinit recursion. */
+#endif
+ pfatal_with_name ("dup");
+ }
+
+ fclose (tfile);
+
+ set_append_mode (fd);
+
+ return fd;
+}
+
+/* Adds file descriptors to the child structure to support output_sync; one
+ for stdout and one for stderr as long as they are open. If stdout and
+ stderr share a device they can share a temp file too.
+ Will reset output_sync on error. */
+static void
+setup_tmpfile (struct output *out)
+{
+ /* Is make's stdout going to the same place as stderr? */
+ static int combined_output = -1;
+
+ if (combined_output < 0)
+ {
+#ifdef KMK /* prevent infinite recursion if sync_init() calls perror_with_name. */
+ combined_output = 0;
+#endif
+ combined_output = sync_init ();
+ }
+
+ if (STREAM_OK (stdout))
+ {
+ int fd = output_tmpfd ();
+ if (fd < 0)
+ goto error;
+ CLOSE_ON_EXEC (fd);
+ out->out = fd;
+ }
+
+ if (STREAM_OK (stderr))
+ {
+ if (out->out != OUTPUT_NONE && combined_output)
+ out->err = out->out;
+ else
+ {
+ int fd = output_tmpfd ();
+ if (fd < 0)
+ goto error;
+ CLOSE_ON_EXEC (fd);
+ out->err = fd;
+ }
+ }
+
+ return;
+
+ /* If we failed to create a temp file, disable output sync going forward. */
+ error:
+ output_close (out);
+ output_sync = OUTPUT_SYNC_NONE;
+}
+
+#endif /* CONFIG_WITH_OUTPUT_IN_MEMORY */
+
+/* Synchronize the output of jobs in -j mode to keep the results of
+ each job together. This is done by holding the results in temp files,
+ one for stdout and potentially another for stderr, and only releasing
+ them to "real" stdout/stderr when a semaphore can be obtained. */
+
+void
+output_dump (struct output *out)
+{
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ membuf_dump (out);
+#else
+ int outfd_not_empty = FD_NOT_EMPTY (out->out);
+ int errfd_not_empty = FD_NOT_EMPTY (out->err);
+
+ if (outfd_not_empty || errfd_not_empty)
+ {
+# ifndef KMK /* this drives me bananas. */
+ int traced = 0;
+# endif
+
+ /* Try to acquire the semaphore. If it fails, dump the output
+ unsynchronized; still better than silently discarding it.
+ We want to keep this lock for as little time as possible. */
+ void *sem = acquire_semaphore ();
+
+# ifndef KMK /* this drives me bananas. */
+ /* Log the working directory for this dump. */
+ if (print_directory_flag && output_sync != OUTPUT_SYNC_RECURSE)
+ traced = log_working_directory (1);
+# endif
+
+ if (outfd_not_empty)
+ pump_from_tmp (out->out, stdout);
+ if (errfd_not_empty && out->err != out->out)
+ pump_from_tmp (out->err, stderr);
+
+# ifndef KMK /* this drives me bananas. */
+ if (traced)
+ log_working_directory (0);
+# endif
+
+ /* Exit the critical section. */
+ if (sem)
+ release_semaphore (sem);
+
+# ifdef KMK
+ if (!out->dont_truncate)
+ { /* likely */ }
+ else return;
+# endif
+ /* Truncate and reset the output, in case we use it again. */
+ if (out->out != OUTPUT_NONE)
+ {
+ int e;
+ lseek (out->out, 0, SEEK_SET);
+ EINTRLOOP (e, ftruncate (out->out, 0));
+ }
+ if (out->err != OUTPUT_NONE && out->err != out->out)
+ {
+ int e;
+ lseek (out->err, 0, SEEK_SET);
+ EINTRLOOP (e, ftruncate (out->err, 0));
+ }
+ }
+#endif
+}
+
+# if defined(KMK) && !defined(CONFIG_WITH_OUTPUT_IN_MEMORY)
+/* Used by die_with_job_output to suppress output when it shouldn't be repeated. */
+void output_reset (struct output *out)
+{
+ if (out)
+ {
+ if (out->out != OUTPUT_NONE)
+ {
+ int e;
+ lseek (out->out, 0, SEEK_SET);
+ EINTRLOOP (e, ftruncate (out->out, 0));
+ }
+ if (out->err != OUTPUT_NONE && out->err != out->out)
+ {
+ int e;
+ lseek (out->err, 0, SEEK_SET);
+ EINTRLOOP (e, ftruncate (out->err, 0));
+ }
+ }
+}
+# endif
+#endif /* NO_OUTPUT_SYNC */
+
+
+/* Provide support for temporary files. */
+
+#ifndef HAVE_STDLIB_H
+# ifdef HAVE_MKSTEMP
+int mkstemp (char *template);
+# else
+char *mktemp (char *template);
+# endif
+#endif
+
+FILE *
+output_tmpfile (char **name, const char *template)
+{
+#ifdef HAVE_FDOPEN
+ int fd;
+#endif
+
+#if defined HAVE_MKSTEMP || defined HAVE_MKTEMP
+# define TEMPLATE_LEN strlen (template)
+#else
+# define TEMPLATE_LEN L_tmpnam
+#endif
+ *name = xmalloc (TEMPLATE_LEN + 1);
+ strcpy (*name, template);
+
+#if defined HAVE_MKSTEMP && defined HAVE_FDOPEN
+ /* It's safest to use mkstemp(), if we can. */
+ fd = mkstemp (*name);
+ if (fd == -1)
+ return 0;
+ return fdopen (fd, "w");
+#else
+# ifdef HAVE_MKTEMP
+ (void) mktemp (*name);
+# else
+ (void) tmpnam (*name);
+# endif
+
+# ifdef HAVE_FDOPEN
+ /* Can't use mkstemp(), but guard against a race condition. */
+ EINTRLOOP (fd, open (*name, O_CREAT|O_EXCL|O_WRONLY, 0600));
+ if (fd == -1)
+ return 0;
+ return fdopen (fd, "w");
+# else
+ /* Not secure, but what can we do? */
+ return fopen (*name, "w");
+# endif
+#endif
+}
+
+
+/* This code is stolen from gnulib.
+ If/when we abandon the requirement to work with K&R compilers, we can
+ remove this (and perhaps other parts of GNU make!) and migrate to using
+ gnulib directly.
+
+ This is called only through atexit(), which means die() has already been
+ invoked. So, call exit() here directly. Apparently that works...?
+*/
+
+/* Close standard output, exiting with status 'exit_failure' on failure.
+ If a program writes *anything* to stdout, that program should close
+ stdout and make sure that it succeeds before exiting. Otherwise,
+ suppose that you go to the extreme of checking the return status
+ of every function that does an explicit write to stdout. The last
+ printf can succeed in writing to the internal stream buffer, and yet
+ the fclose(stdout) could still fail (due e.g., to a disk full error)
+ when it tries to write out that buffered data. Thus, you would be
+ left with an incomplete output file and the offending program would
+ exit successfully. Even calling fflush is not always sufficient,
+ since some file systems (NFS and CODA) buffer written/flushed data
+ until an actual close call.
+
+ Besides, it's wasteful to check the return value from every call
+ that writes to stdout -- just let the internal stream state record
+ the failure. That's what the ferror test is checking below.
+
+ It's important to detect such failures and exit nonzero because many
+ tools (most notably 'make' and other build-management systems) depend
+ on being able to detect failure in other tools via their exit status. */
+
+static void
+close_stdout (void)
+{
+ int prev_fail = ferror (stdout);
+ int fclose_fail = fclose (stdout);
+
+ if (prev_fail || fclose_fail)
+ {
+ if (fclose_fail)
+ perror_with_name (_("write error: stdout"), "");
+ else
+ O (error, NILF, _("write error: stdout"));
+ exit (MAKE_TROUBLE);
+ }
+}
+
+
+void
+output_init (struct output *out)
+{
+ if (out)
+ {
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ out->out.head_seg = NULL;
+ out->out.tail_seg = NULL;
+ out->out.head_run = NULL;
+ out->out.tail_run = NULL;
+ out->err.head_seg = NULL;
+ out->err.tail_seg = NULL;
+ out->err.head_run = NULL;
+ out->err.tail_run = NULL;
+ out->err.total = 0;
+ out->out.total = 0;
+ out->seqno = 0;
+#else
+ out->out = out->err = OUTPUT_NONE;
+#endif
+ out->syncout = !!output_sync;
+#ifdef KMK
+ out->dont_truncate = 0;
+#endif
+ return;
+ }
+
+ /* Configure this instance of make. Be sure stdout is line-buffered. */
+
+#ifdef HAVE_SETVBUF
+# ifdef SETVBUF_REVERSED
+ setvbuf (stdout, _IOLBF, xmalloc (BUFSIZ), BUFSIZ);
+# else /* setvbuf not reversed. */
+ /* Some buggy systems lose if we pass 0 instead of allocating ourselves. */
+ setvbuf (stdout, 0, _IOLBF, BUFSIZ);
+# endif /* setvbuf reversed. */
+#elif HAVE_SETLINEBUF
+ setlinebuf (stdout);
+#endif /* setlinebuf missing. */
+
+ /* Force stdout/stderr into append mode. This ensures parallel jobs won't
+ lose output due to overlapping writes. */
+ set_append_mode (fileno (stdout));
+ set_append_mode (fileno (stderr));
+
+#ifdef HAVE_ATEXIT
+ if (STREAM_OK (stdout))
+ atexit (close_stdout);
+#endif
+}
+
+void
+output_close (struct output *out)
+{
+ if (! out)
+ {
+ if (stdio_traced)
+ log_working_directory (0);
+ return;
+ }
+
+#ifndef NO_OUTPUT_SYNC
+ output_dump (out);
+#endif
+
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ assert (out->out.total == 0);
+ assert (out->out.head_seg == NULL);
+ assert (out->err.total == 0);
+ assert (out->err.head_seg == NULL);
+#else
+ if (out->out >= 0)
+ close (out->out);
+ if (out->err >= 0 && out->err != out->out)
+ close (out->err);
+#endif
+
+ output_init (out);
+}
+
+/* We're about to generate output: be sure it's set up. */
+void
+output_start (void)
+{
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ /* If we're syncing output make sure the sempahore (win) is set up. */
+ if (output_context && output_context->syncout)
+ if (combined_output < 0)
+ combined_output = sync_init ();
+#else
+#ifndef NO_OUTPUT_SYNC
+ /* If we're syncing output make sure the temporary file is set up. */
+ if (output_context && output_context->syncout)
+ if (! OUTPUT_ISSET(output_context))
+ setup_tmpfile (output_context);
+#endif
+#endif
+
+#ifndef KMK
+ /* If we're not syncing this output per-line or per-target, make sure we emit
+ the "Entering..." message where appropriate. */
+ if (output_sync == OUTPUT_SYNC_NONE || output_sync == OUTPUT_SYNC_RECURSE)
+#else
+ /* Indiscriminately output "Entering..." and "Leaving..." message for each
+ command line or target is plain annoying! And when there is no recursion
+ it's actually inappropriate. Haven't got a simple way of detecting that,
+ so back to the old behavior for now. [bird] */
+#endif
+ if (! stdio_traced && print_directory_flag)
+ stdio_traced = log_working_directory (1);
+}
+
+void
+outputs (int is_err, const char *msg)
+{
+ if (! msg || *msg == '\0')
+ return;
+
+ output_start ();
+
+ _outputs (output_context, is_err, msg);
+}
+
+
+static struct fmtstring
+ {
+ char *buffer;
+ size_t size;
+ } fmtbuf = { NULL, 0 };
+
+static char *
+get_buffer (size_t need)
+{
+ /* Make sure we have room. NEED includes space for \0. */
+ if (need > fmtbuf.size)
+ {
+ fmtbuf.size += need * 2;
+ fmtbuf.buffer = xrealloc (fmtbuf.buffer, fmtbuf.size);
+ }
+
+ fmtbuf.buffer[need-1] = '\0';
+
+ return fmtbuf.buffer;
+}
+
+/* Print a message on stdout. */
+
+void
+message (int prefix, size_t len, const char *fmt, ...)
+{
+ va_list args;
+ char *p;
+
+ len += strlen (fmt) + strlen (program) + INTSTR_LENGTH + 4 + 1 + 1;
+ p = get_buffer (len);
+
+ if (prefix)
+ {
+ if (makelevel == 0)
+ sprintf (p, "%s: ", program);
+ else
+ sprintf (p, "%s[%u]: ", program, makelevel);
+ p += strlen (p);
+ }
+
+ va_start (args, fmt);
+ vsprintf (p, fmt, args);
+ va_end (args);
+
+ strcat (p, "\n");
+
+ assert (fmtbuf.buffer[len-1] == '\0');
+ outputs (0, fmtbuf.buffer);
+}
+
+/* Print an error message. */
+
+void
+error (const floc *flocp, size_t len, const char *fmt, ...)
+{
+ va_list args;
+ char *p;
+
+ len += (strlen (fmt) + strlen (program)
+ + (flocp && flocp->filenm ? strlen (flocp->filenm) : 0)
+ + INTSTR_LENGTH + 4 + 1 + 1);
+ p = get_buffer (len);
+
+ if (flocp && flocp->filenm)
+ sprintf (p, "%s:%lu: ", flocp->filenm, flocp->lineno + flocp->offset);
+ else if (makelevel == 0)
+ sprintf (p, "%s: ", program);
+ else
+ sprintf (p, "%s[%u]: ", program, makelevel);
+ p += strlen (p);
+
+ va_start (args, fmt);
+ vsprintf (p, fmt, args);
+ va_end (args);
+
+ strcat (p, "\n");
+
+ assert (fmtbuf.buffer[len-1] == '\0');
+ outputs (1, fmtbuf.buffer);
+}
+
+/* Print an error message and exit. */
+
+void
+fatal (const floc *flocp, size_t len, const char *fmt, ...)
+{
+ va_list args;
+ const char *stop = _(". Stop.\n");
+ char *p;
+
+ len += (strlen (fmt) + strlen (program)
+ + (flocp && flocp->filenm ? strlen (flocp->filenm) : 0)
+ + INTSTR_LENGTH + 8 + strlen (stop) + 1);
+ p = get_buffer (len);
+
+ if (flocp && flocp->filenm)
+ sprintf (p, "%s:%lu: *** ", flocp->filenm, flocp->lineno + flocp->offset);
+ else if (makelevel == 0)
+ sprintf (p, "%s: *** ", program);
+ else
+ sprintf (p, "%s[%u]: *** ", program, makelevel);
+ p += strlen (p);
+
+ va_start (args, fmt);
+ vsprintf (p, fmt, args);
+ va_end (args);
+
+ strcat (p, stop);
+
+ assert (fmtbuf.buffer[len-1] == '\0');
+ outputs (1, fmtbuf.buffer);
+
+ die (MAKE_FAILURE);
+}
+
+/* Print an error message from errno. */
+
+void
+perror_with_name (const char *str, const char *name)
+{
+ const char *err = strerror (errno);
+ OSSS (error, NILF, _("%s%s: %s"), str, name, err);
+}
+
+/* Print an error message from errno and exit. */
+
+void
+pfatal_with_name (const char *name)
+{
+ const char *err = strerror (errno);
+ OSS (fatal, NILF, _("%s: %s"), name, err);
+
+ /* NOTREACHED */
+}
diff --git a/src/kmk/output.h b/src/kmk/output.h
new file mode 100644
index 0000000..d34d052
--- /dev/null
+++ b/src/kmk/output.h
@@ -0,0 +1,107 @@
+/* Output to stdout / stderr for GNU make
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef INCLUDED_MAKE_OUTPUT_H
+#define INCLUDED_MAKE_OUTPUT_H
+#include <stdio.h> /* darwin*/
+
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+/* Output run. */
+struct output_run
+{
+ unsigned int seqno; /* For interleaving out/err output. */
+ unsigned int len; /* The length of the output. */
+ struct output_run *next; /* Pointer to the next run. */
+};
+
+/* Output segment. */
+struct output_segment
+{
+ struct output_segment *next;
+ size_t size; /* Segment size, everything included. */
+ struct output_run runs[1];
+};
+
+/* Output memory buffer. */
+struct output_membuf
+{
+ struct output_run *head_run;
+ struct output_run *tail_run; /* Always in tail_seg. */
+ struct output_segment *head_seg;
+ struct output_segment *tail_seg;
+ size_t left; /* Number of bytes that can be appended to
+ the tail_run. */
+ size_t total; /* Total segment allocation size. */
+};
+#endif /* CONFIG_WITH_OUTPUT_IN_MEMORY */
+
+struct output
+ {
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ struct output_membuf out;
+ struct output_membuf err;
+ unsigned int seqno; /* The current run sequence number. */
+#else
+ int out;
+ int err;
+#endif
+ unsigned int syncout:1; /* True if we want to synchronize output. */
+#ifdef KMK
+ unsigned int dont_truncate:1; /* For die_with_child_output repeat. */
+#endif
+ };
+
+extern struct output *output_context;
+extern unsigned int stdio_traced;
+#if defined(KMK) && !defined(NO_OUTPUT_SYNC)
+extern int output_metered;
+#endif
+
+#define OUTPUT_SET(_new) do{ output_context = (_new)->syncout ? (_new) : NULL; }while(0)
+#define OUTPUT_UNSET() do{ output_context = NULL; }while(0)
+
+#define OUTPUT_TRACED() do{ stdio_traced = 1; }while(0)
+#define OUTPUT_IS_TRACED() (!!stdio_traced)
+
+FILE *output_tmpfile (char **, const char *);
+
+/* Initialize and close a child output structure: if NULL do this program's
+ output (this should only be done once). */
+void output_init (struct output *out);
+void output_close (struct output *out);
+
+/* In situations where output may be about to be displayed but we're not
+ sure if we've set it up yet, call this. */
+void output_start (void);
+
+/* Show a message on stdout or stderr. Will start the output if needed. */
+void outputs (int is_err, const char *msg);
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ssize_t output_write_bin (struct output *out, int is_err, const char *src, size_t len);
+#endif
+ssize_t output_write_text (struct output *out, int is_err, const char *src, size_t len);
+
+#ifndef NO_OUTPUT_SYNC
+int output_tmpfd (void);
+/* Dump any child output content to stdout, and reset it. */
+void output_dump (struct output *out);
+# ifdef KMK
+void output_reset (struct output *out);
+# endif
+#endif
+
+#endif /* INLCUDED_MAKE_OUTPUT_H */
+
diff --git a/src/kmk/po/.gitignore b/src/kmk/po/.gitignore
new file mode 100644
index 0000000..d7261f5
--- /dev/null
+++ b/src/kmk/po/.gitignore
@@ -0,0 +1,15 @@
+Makefile.in.in
+Makevars.template
+POTFILES
+Rules-quot
+boldquot.sed
+en@boldquot.header
+en@quot.header
+insert-header.sin
+make.pot
+quot.sed
+remove-potcdate.sin
+remove-potcdate.sed
+stamp-po
+*.gmo
+*.po
diff --git a/src/kmk/po/LINGUAS b/src/kmk/po/LINGUAS
new file mode 100644
index 0000000..d9ba7f4
--- /dev/null
+++ b/src/kmk/po/LINGUAS
@@ -0,0 +1,5 @@
+# Set of available languages: 25 languages
+
+be cs da de es fi fr ga gl he hr id it ja ko lt nl pl pt_BR ru sv tr uk vi zh_CN
+
+# Can't seem to get en@quot and en@boldquot to build properly?
diff --git a/src/kmk/po/Makevars b/src/kmk/po/Makevars
new file mode 100644
index 0000000..ee01884
--- /dev/null
+++ b/src/kmk/po/Makevars
@@ -0,0 +1,59 @@
+# This is a -*-Makefile-*-
+# Copyright (C) 2002-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding
+# package. (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.) Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright. The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Free Software Foundation, Inc.
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+# in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+# understood.
+# - Strings which make invalid assumptions about notation of date, time or
+# money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = bug-make@gnu.org
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used. It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
diff --git a/src/kmk/po/POTFILES.in b/src/kmk/po/POTFILES.in
new file mode 100644
index 0000000..8b0e500
--- /dev/null
+++ b/src/kmk/po/POTFILES.in
@@ -0,0 +1,48 @@
+# List of source files containing translatable strings.
+# Copyright (C) 2000-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+ar.c
+arscan.c
+commands.c
+dir.c
+expand.c
+file.c
+function.c
+getopt.c
+guile.c
+hash.c
+implicit.c
+job.c
+job.h
+kbuild.c
+load.c
+main.c
+misc.c
+output.c
+posixos.c
+read.c
+remake.c
+remote-cstms.c
+rule.c
+signame.c
+strcache.c
+variable.c
+variable.h
+vmsfunctions.c
+vmsjobs.c
+vpath.c
+w32/w32os.c
diff --git a/src/kmk/posixos.c b/src/kmk/posixos.c
new file mode 100644
index 0000000..e032648
--- /dev/null
+++ b/src/kmk/posixos.c
@@ -0,0 +1,480 @@
+/* POSIX-based operating system interface for GNU Make.
+Copyright (C) 2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#if defined(HAVE_PSELECT) && defined(HAVE_SYS_SELECT_H)
+# include <sys/select.h>
+#endif
+
+#include "debug.h"
+#include "job.h"
+#include "os.h"
+
+#ifdef MAKE_JOBSERVER
+
+/* This section provides OS-specific functions to support the jobserver. */
+
+/* These track the state of the jobserver pipe. Passed to child instances. */
+static int job_fds[2] = { -1, -1 };
+
+/* Used to signal read() that a SIGCHLD happened. Always CLOEXEC.
+ If we use pselect() this will never be created and always -1.
+ */
+static int volatile job_rfd = -1; /* bird: added volatile to try ensure atomic update. */
+
+/* Token written to the pipe (could be any character...) */
+static char token = '+';
+
+static int
+make_job_rfd (void)
+{
+#ifdef HAVE_PSELECT
+ /* Pretend we succeeded. */
+ return 0;
+#else
+ /* bird: modified to use local variable and only update job_rfd once, otherwise
+ we're racing the signal handler clearing and closing this. */
+ int new_job_rfd;
+ EINTRLOOP (new_job_rfd, dup (job_fds[0]));
+ if (new_job_rfd >= 0)
+ CLOSE_ON_EXEC (new_job_rfd);
+
+ job_rfd = new_job_rfd;
+ return new_job_rfd;
+#endif
+}
+
+static void
+set_blocking (int fd, int blocking)
+{
+ // If we're not using pselect() don't change the blocking
+#ifdef HAVE_PSELECT
+ int flags;
+ EINTRLOOP (flags, fcntl (fd, F_GETFL));
+ if (flags >= 0)
+ {
+ int r;
+ flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
+ EINTRLOOP (r, fcntl (fd, F_SETFL, flags));
+ if (r < 0)
+ pfatal_with_name ("fcntl(O_NONBLOCK)");
+ }
+#endif
+}
+
+unsigned int
+jobserver_setup (int slots)
+{
+ int r;
+
+ EINTRLOOP (r, pipe (job_fds));
+ if (r < 0)
+ pfatal_with_name (_("creating jobs pipe"));
+
+ if (make_job_rfd () < 0)
+ pfatal_with_name (_("duping jobs pipe"));
+
+ while (slots--)
+ {
+ EINTRLOOP (r, write (job_fds[1], &token, 1));
+ if (r != 1)
+ pfatal_with_name (_("init jobserver pipe"));
+ }
+
+ /* When using pselect() we want the read to be non-blocking. */
+ set_blocking (job_fds[0], 0);
+
+ return 1;
+}
+
+unsigned int
+jobserver_parse_auth (const char *auth)
+{
+ /* Given the command-line parameter, parse it. */
+ if (sscanf (auth, "%d,%d", &job_fds[0], &job_fds[1]) != 2)
+ OS (fatal, NILF,
+ _("internal error: invalid --jobserver-auth string '%s'"), auth);
+
+ DB (DB_JOBS,
+ (_("Jobserver client (fds %d,%d)\n"), job_fds[0], job_fds[1]));
+
+#ifdef HAVE_FCNTL_H
+# define FD_OK(_f) (fcntl ((_f), F_GETFD) != -1)
+#else
+# define FD_OK(_f) 1
+#endif
+
+ /* Make sure our pipeline is valid, and (possibly) create a duplicate pipe,
+ that will be closed in the SIGCHLD handler. If this fails with EBADF,
+ the parent has closed the pipe on us because it didn't think we were a
+ submake. If so, warn and default to -j1. */
+
+ if (!FD_OK (job_fds[0]) || !FD_OK (job_fds[1]) || make_job_rfd () < 0)
+ {
+ if (errno != EBADF)
+ pfatal_with_name (_("jobserver pipeline"));
+
+ job_fds[0] = job_fds[1] = -1;
+
+ return 0;
+ }
+
+ /* When using pselect() we want the read to be non-blocking. */
+ set_blocking (job_fds[0], 0);
+
+ return 1;
+}
+
+char *
+jobserver_get_auth (void)
+{
+ char *auth = xmalloc ((INTSTR_LENGTH * 2) + 2);
+ sprintf (auth, "%d,%d", job_fds[0], job_fds[1]);
+ return auth;
+}
+
+unsigned int
+jobserver_enabled (void)
+{
+ return job_fds[0] >= 0;
+}
+
+void
+jobserver_clear (void)
+{
+ if (job_fds[0] >= 0)
+ close (job_fds[0]);
+ if (job_fds[1] >= 0)
+ close (job_fds[1]);
+ if (job_rfd >= 0)
+ close (job_rfd);
+
+ job_fds[0] = job_fds[1] = job_rfd = -1;
+}
+
+void
+jobserver_release (int is_fatal)
+{
+ int r;
+ EINTRLOOP (r, write (job_fds[1], &token, 1));
+ if (r != 1)
+ {
+ if (is_fatal)
+ pfatal_with_name (_("write jobserver"));
+ perror_with_name ("write", "");
+ }
+}
+
+unsigned int
+jobserver_acquire_all (void)
+{
+ unsigned int tokens = 0;
+
+ /* Use blocking reads to wait for all outstanding jobs. */
+ set_blocking (job_fds[0], 1);
+
+ /* Close the write side, so the read() won't hang forever. */
+ close (job_fds[1]);
+ job_fds[1] = -1;
+
+ while (1)
+ {
+ char intake;
+ int r;
+ EINTRLOOP (r, read (job_fds[0], &intake, 1));
+ if (r != 1)
+ return tokens;
+ ++tokens;
+ }
+}
+
+/* Prepare the jobserver to start a child process. */
+void
+jobserver_pre_child (int recursive)
+{
+ /* If it's not a recursive make, avoid polutting the jobserver pipes. */
+ if (!recursive && job_fds[0] >= 0)
+ {
+ CLOSE_ON_EXEC (job_fds[0]);
+ CLOSE_ON_EXEC (job_fds[1]);
+ }
+}
+
+void
+jobserver_post_child (int recursive)
+{
+#if defined(F_GETFD) && defined(F_SETFD)
+ if (!recursive && job_fds[0] >= 0)
+ {
+ unsigned int i;
+ for (i = 0; i < 2; ++i)
+ {
+ int flags;
+ EINTRLOOP (flags, fcntl (job_fds[i], F_GETFD));
+ if (flags >= 0)
+ {
+ int r;
+ EINTRLOOP (r, fcntl (job_fds[i], F_SETFD, flags & ~FD_CLOEXEC));
+ }
+ }
+ }
+#endif
+}
+
+void
+jobserver_signal (void)
+{
+ if (job_rfd >= 0)
+ {
+ close (job_rfd);
+ job_rfd = -1;
+ }
+}
+
+void
+jobserver_pre_acquire (void)
+{
+ /* Make sure we have a dup'd FD. */
+ if (job_rfd < 0 && job_fds[0] >= 0 && make_job_rfd () < 0)
+ pfatal_with_name (_("duping jobs pipe"));
+}
+
+#ifdef HAVE_PSELECT
+
+/* Use pselect() to atomically wait for both a signal and a file descriptor.
+ It also provides a timeout facility so we don't need to use SIGALRM.
+
+ This method relies on the fact that SIGCHLD will be blocked everywhere,
+ and only unblocked (atomically) within the pselect() call, so we can
+ never miss a SIGCHLD.
+ */
+unsigned int
+jobserver_acquire (int timeout)
+{
+ struct timespec spec;
+ struct timespec *specp = NULL;
+ sigset_t empty;
+
+ sigemptyset (&empty);
+
+ if (timeout)
+ {
+ /* Alarm after one second (is this too granular?) */
+ spec.tv_sec = 1;
+ spec.tv_nsec = 0;
+ specp = &spec;
+ }
+
+ while (1)
+ {
+ fd_set readfds;
+ int r;
+ char intake;
+
+ FD_ZERO (&readfds);
+ FD_SET (job_fds[0], &readfds);
+
+ r = pselect (job_fds[0]+1, &readfds, NULL, NULL, specp, &empty);
+ if (r < 0)
+ switch (errno)
+ {
+ case EINTR:
+ /* SIGCHLD will show up as an EINTR. */
+ return 0;
+
+ case EBADF:
+ /* Someone closed the jobs pipe.
+ That shouldn't happen but if it does we're done. */
+ O (fatal, NILF, _("job server shut down"));
+
+ default:
+ pfatal_with_name (_("pselect jobs pipe"));
+ }
+
+ if (r == 0)
+ /* Timeout. */
+ return 0;
+
+ /* The read FD is ready: read it! This is non-blocking. */
+ EINTRLOOP (r, read (job_fds[0], &intake, 1));
+
+ if (r < 0)
+ {
+ /* Someone sniped our token! Try again. */
+ if (errno == EAGAIN)
+ continue;
+
+ pfatal_with_name (_("read jobs pipe"));
+ }
+
+ /* read() should never return 0: only the master make can reap all the
+ tokens and close the write side...?? */
+ return r > 0;
+ }
+}
+
+#else
+
+/* This method uses a "traditional" UNIX model for waiting on both a signal
+ and a file descriptor. However, it's complex and since we have a SIGCHLD
+ handler installed we need to check ALL system calls for EINTR: painful!
+
+ Read a token. As long as there's no token available we'll block. We
+ enable interruptible system calls before the read(2) so that if we get a
+ SIGCHLD while we're waiting, we'll return with EINTR and we can process the
+ death(s) and return tokens to the free pool.
+
+ Once we return from the read, we immediately reinstate restartable system
+ calls. This allows us to not worry about checking for EINTR on all the
+ other system calls in the program.
+
+ There is one other twist: there is a span between the time reap_children()
+ does its last check for dead children and the time the read(2) call is
+ entered, below, where if a child dies we won't notice. This is extremely
+ serious as it could cause us to deadlock, given the right set of events.
+
+ To avoid this, we do the following: before we reap_children(), we dup(2)
+ the read FD on the jobserver pipe. The read(2) call below uses that new
+ FD. In the signal handler, we close that FD. That way, if a child dies
+ during the section mentioned above, the read(2) will be invoked with an
+ invalid FD and will return immediately with EBADF. */
+
+static RETSIGTYPE
+job_noop (int sig UNUSED)
+{
+}
+
+/* Set the child handler action flags to FLAGS. */
+static void
+set_child_handler_action_flags (int set_handler, int set_alarm)
+{
+ struct sigaction sa;
+
+#ifdef __EMX__
+ /* The child handler must be turned off here. */
+ signal (SIGCHLD, SIG_DFL);
+#endif
+
+ memset (&sa, '\0', sizeof sa);
+ sa.sa_handler = child_handler;
+ sa.sa_flags = set_handler ? 0 : SA_RESTART;
+
+#if defined SIGCHLD
+ if (sigaction (SIGCHLD, &sa, NULL) < 0)
+ pfatal_with_name ("sigaction: SIGCHLD");
+#endif
+
+#if defined SIGCLD && SIGCLD != SIGCHLD
+ if (sigaction (SIGCLD, &sa, NULL) < 0)
+ pfatal_with_name ("sigaction: SIGCLD");
+#endif
+
+#if defined SIGALRM
+ if (set_alarm)
+ {
+ /* If we're about to enter the read(), set an alarm to wake up in a
+ second so we can check if the load has dropped and we can start more
+ work. On the way out, turn off the alarm and set SIG_DFL. */
+ if (set_handler)
+ {
+ sa.sa_handler = job_noop;
+ sa.sa_flags = 0;
+ if (sigaction (SIGALRM, &sa, NULL) < 0)
+ pfatal_with_name ("sigaction: SIGALRM");
+ alarm (1);
+ }
+ else
+ {
+ alarm (0);
+ sa.sa_handler = SIG_DFL;
+ sa.sa_flags = 0;
+ if (sigaction (SIGALRM, &sa, NULL) < 0)
+ pfatal_with_name ("sigaction: SIGALRM");
+ }
+ }
+#endif
+}
+
+unsigned int
+jobserver_acquire (int timeout)
+{
+ char intake;
+ int got_token;
+ int saved_errno;
+
+ /* Set interruptible system calls, and read() for a job token. */
+ set_child_handler_action_flags (1, timeout);
+
+ EINTRLOOP (got_token, read (job_rfd, &intake, 1));
+ saved_errno = errno;
+
+ set_child_handler_action_flags (0, timeout);
+
+ if (got_token == 1)
+ return 1;
+
+ /* If the error _wasn't_ expected (EINTR or EBADF), fatal. Otherwise,
+ go back and reap_children(), and try again. */
+ errno = saved_errno;
+
+ if (errno != EINTR && errno != EBADF)
+ pfatal_with_name (_("read jobs pipe"));
+
+ if (errno == EBADF)
+ DB (DB_JOBS, ("Read returned EBADF.\n"));
+
+ return 0;
+}
+
+#endif
+
+#endif /* MAKE_JOBSERVER */
+
+/* Create a "bad" file descriptor for stdin when parallel jobs are run. */
+int
+get_bad_stdin (void)
+{
+ static int bad_stdin = -1;
+
+ /* Set up a bad standard input that reads from a broken pipe. */
+
+ if (bad_stdin == -1)
+ {
+ /* Make a file descriptor that is the read end of a broken pipe.
+ This will be used for some children's standard inputs. */
+ int pd[2];
+ if (pipe (pd) == 0)
+ {
+ /* Close the write side. */
+ (void) close (pd[1]);
+ /* Save the read side. */
+ bad_stdin = pd[0];
+
+ /* Set the descriptor to close on exec, so it does not litter any
+ child's descriptor table. When it is dup2'd onto descriptor 0,
+ that descriptor will not close on exec. */
+ CLOSE_ON_EXEC (bad_stdin);
+ }
+ }
+
+ return bad_stdin;
+}
diff --git a/src/kmk/prepare_vms.com b/src/kmk/prepare_vms.com
new file mode 100644
index 0000000..04f581f
--- /dev/null
+++ b/src/kmk/prepare_vms.com
@@ -0,0 +1,59 @@
+$!
+$! prepare_vms.com - Build config.h-vms from master on VMS.
+$!
+$! This is used for building off the master instead of a release tarball.
+$!
+$!
+$!
+$! First try ODS-5, Pathworks V6 or UNZIP name.
+$!
+$ config_template = f$search("sys$disk:[]config*h-vms.template")
+$ if config_template .eqs. ""
+$ then
+$!
+$! Try NFS, VMStar, or Pathworks V5 ODS-2 encoded name.
+$!
+$ config_template = f$search("sys$disk:[]config.h-vms*template")
+$ if config_template .eqs. ""
+$ then
+$ write sys$output "Could not find config.h-vms*template!"
+$ exit 44
+$ endif
+$ endif
+$ config_template_file = f$parse(config_template,,,"name")
+$ config_template_type = f$parse(config_template,,,"type")
+$ config_template = "sys$disk:[]" + config_template_file + config_template_type
+$!
+$!
+$! Pull the package and version from configure.ac
+$!
+$ open/read ac_file sys$disk:[]configure.ac
+$ac_read_loop:
+$ read ac_file/end=ac_read_loop_end line_in
+$ key = f$extract(0, 7, line_in)
+$ if key .nes. "AC_INIT" then goto ac_read_loop
+$ package = f$element (1,"[",line_in)
+$ package = f$element (0,"]",package)
+$ version = f$element (2,"[",line_in)
+$ version = f$element (0,"]",version)
+$ac_read_loop_end:
+$ close ac_file
+$!
+$ if (package .eqs. "") .or. (version .eqs. "")
+$ then
+$ write sys$output "Unable to determine package and/or version!"
+$ exit 44
+$ endif
+$!
+$!
+$ outfile = "sys$disk:[]config.h-vms"
+$!
+$! Note the pipe command is close to the length of 255, which is the
+$! maximum token length prior to VMS V8.2:
+$! %DCL-W-TKNOVF, command element is too long - shorten
+$ pipe (write sys$output "sub/%PACKAGE%/''package'/WHOLE/NOTYPE" ;-
+ write sys$output "sub/%VERSION%/''version'/WHOLE/NOTYPE" ;-
+ write sys$output "exit") |-
+ edit/edt 'config_template'/out='outfile'/command=sys$pipe >nla0:
+$!
+$ write sys$output package, ", version: ", version, " prepared for VMS"
diff --git a/src/kmk/prepare_w32.bat b/src/kmk/prepare_w32.bat
new file mode 100644
index 0000000..7591e27
--- /dev/null
+++ b/src/kmk/prepare_w32.bat
@@ -0,0 +1,6 @@
+@echo off
+@echo Windows32 SCM build preparation of config.h.W32 and NMakefile.
+if not exist config.h.W32 copy config.h.W32.template config.h.W32
+if not exist config.h copy config.h.W32 config.h
+if not exist NMakefile copy NMakefile.template NMakefile
+@echo Preparation complete. Run build_w32.bat to compile and link.
diff --git a/src/kmk/read.c b/src/kmk/read.c
new file mode 100644
index 0000000..6ecac0c
--- /dev/null
+++ b/src/kmk/read.c
@@ -0,0 +1,4101 @@
+/* Reading and parsing of makefiles for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#include "debug.h"
+#include "hash.h"
+#ifdef KMK
+# include "kbuild.h"
+#endif
+
+#ifdef WINDOWS32
+#include <windows.h>
+# ifndef _MSC_VER
+# ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+# else
+# include "w32/winchildren.h"
+# endif
+# endif
+#else /* !WINDOWS32 */
+#ifndef _AMIGA
+#ifndef VMS
+#include <pwd.h>
+#else
+struct passwd *getpwnam (char *name);
+#endif
+#endif
+#endif /* !WINDOWS32 */
+
+/* A 'struct ebuffer' controls the origin of the makefile we are currently
+ eval'ing.
+*/
+
+struct ebuffer
+ {
+ char *buffer; /* Start of the current line in the buffer. */
+ char *bufnext; /* Start of the next line in the buffer. */
+ char *bufstart; /* Start of the entire buffer. */
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ char *eol; /* End of the current line in the buffer. */
+#endif
+ unsigned int size; /* Malloc'd size of buffer. */
+ FILE *fp; /* File, or NULL if this is an internal buffer. */
+ floc floc; /* Info on the file in fp (if any). */
+ };
+
+/* Track the modifiers we can have on variable assignments */
+
+struct vmodifiers
+ {
+ unsigned int assign_v:1;
+ unsigned int define_v:1;
+ unsigned int undefine_v:1;
+ unsigned int export_v:1;
+ unsigned int override_v:1;
+ unsigned int private_v:1;
+ };
+
+/* Types of "words" that can be read in a makefile. */
+enum make_word_type
+ {
+ w_bogus, w_eol, w_static, w_variable, w_colon, w_dcolon, w_semicolon,
+ w_varassign
+ };
+
+
+/* A 'struct conditionals' contains the information describing
+ all the active conditionals in a makefile.
+
+ The global variable 'conditionals' contains the conditionals
+ information for the current makefile. It is initialized from
+ the static structure 'toplevel_conditionals' and is later changed
+ to new structures for included makefiles. */
+
+struct conditionals
+ {
+ unsigned int if_cmds; /* Depth of conditional nesting. */
+ unsigned int allocated; /* Elts allocated in following arrays. */
+ char *ignoring; /* Are we ignoring or interpreting?
+ 0=interpreting, 1=not yet interpreted,
+ 2=already interpreted */
+ char *seen_else; /* Have we already seen an 'else'? */
+#ifdef KMK
+ char ignoring_first[8];
+ char seen_else_first[8];
+#endif
+ };
+
+#ifdef KMK
+static struct conditionals toplevel_conditionals =
+{
+ 0,
+ sizeof (toplevel_conditionals.ignoring_first),
+ &toplevel_conditionals.ignoring_first[0],
+ &toplevel_conditionals.seen_else_first[0],
+ "", ""
+};
+#else /* !KMK */
+static struct conditionals toplevel_conditionals;
+#endif /* !KMK */
+static struct conditionals *conditionals = &toplevel_conditionals;
+
+
+/* Default directories to search for include files in */
+
+static const char *default_include_directories[] =
+ {
+#ifndef KMK
+#if defined(WINDOWS32) && !defined(INCLUDEDIR)
+/* This completely up to the user when they install MSVC or other packages.
+ This is defined as a placeholder. */
+# define INCLUDEDIR "."
+#endif
+# ifdef INCLUDEDIR /* bird */
+ INCLUDEDIR,
+# else /* bird */
+ ".", /* bird */
+# endif /* bird */
+#ifndef _AMIGA
+ "/usr/gnu/include",
+ "/usr/local/include",
+ "/usr/include",
+#endif
+#endif /* !KMK */
+ 0
+ };
+
+/* List of directories to search for include files in */
+
+static const char **include_directories;
+
+/* Maximum length of an element of the above. */
+
+static unsigned int max_incl_len;
+
+/* The filename and pointer to line number of the
+ makefile currently being read in. */
+
+const floc *reading_file = 0;
+
+/* The chain of files read by read_all_makefiles. */
+
+static struct goaldep *read_files = 0;
+
+static struct goaldep *eval_makefile (const char *filename, int flags);
+static void eval (struct ebuffer *buffer, int flags);
+
+static long readline (struct ebuffer *ebuf);
+static void do_undefine (char *name, enum variable_origin origin,
+ struct ebuffer *ebuf);
+static struct variable *do_define (char *name IF_WITH_VALUE_LENGTH_PARAM(char *eos),
+ enum variable_origin origin, struct ebuffer *ebuf);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+static int conditional_line (char *line, int len, const floc *flocp);
+#else
+static int conditional_line (char *line, char *eol, int len, const floc *flocp);
+#endif
+static void record_files (struct nameseq *filenames, const char *pattern,
+ const char *pattern_percent, char *depstr,
+ unsigned int cmds_started, char *commands,
+ unsigned int commands_idx, int two_colon,
+ char prefix, const floc *flocp);
+static void record_target_var (struct nameseq *filenames, char *defn,
+ enum variable_origin origin,
+ struct vmodifiers *vmod,
+ const floc *flocp);
+static enum make_word_type get_next_mword (char *buffer, char *delim,
+ char **startp, unsigned int *length);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+static void remove_comments (char *line);
+static char *find_char_unquote (char *string, int map);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+static char *remove_comments (char *line, char *eos);
+static char *find_char_unquote (char *string, int map, unsigned int string_len);
+K_INLINE char *find_char_unquote_0 (char *string, int stop1, int map, char **eosp);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+static char *unescape_char (char *string, int c);
+
+
+/* Compare a word, both length and contents.
+ P must point to the word to be tested, and WLEN must be the length.
+*/
+#define word1eq(s) (wlen == CSTRLEN (s) && strneq (s, p, CSTRLEN (s)))
+
+
+/* Read in all the makefiles and return a chain of targets to rebuild. */
+
+struct goaldep *
+read_all_makefiles (const char **makefiles)
+{
+ unsigned int num_makefiles = 0;
+
+ /* Create *_LIST variables, to hold the makefiles, targets, and variables
+ we will be reading. */
+
+ define_variable_cname ("MAKEFILE_LIST", "", o_file, 0);
+
+ DB (DB_BASIC, (_("Reading makefiles...\n")));
+
+ /* If there's a non-null variable MAKEFILES, its value is a list of
+ files to read first thing. But don't let it prevent reading the
+ default makefiles and don't let the default goal come from there. */
+
+ {
+ char *value;
+ char *name, *p;
+ unsigned int length;
+
+ {
+ /* Turn off --warn-undefined-variables while we expand MAKEFILES. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 0;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ value = allocated_variable_expand ("$(MAKEFILES)");
+#else
+ value = allocated_variable_expand_2 (STRING_SIZE_TUPLE("$(MAKEFILES)"), NULL);
+#endif
+
+ warn_undefined_variables_flag = save;
+ }
+
+ /* Set NAME to the start of next token and LENGTH to its length.
+ MAKEFILES is updated for finding remaining tokens. */
+ p = value;
+
+ while ((name = find_next_token ((const char **)&p, &length)) != 0)
+ {
+ if (*p != '\0')
+ *p++ = '\0';
+ eval_makefile (name, RM_NO_DEFAULT_GOAL|RM_INCLUDED|RM_DONTCARE);
+ }
+
+ free (value);
+ }
+
+ /* Read makefiles specified with -f switches. */
+
+ if (makefiles != 0)
+ while (*makefiles != 0)
+ {
+ struct goaldep *d = eval_makefile (*makefiles, 0);
+
+ if (errno)
+ perror_with_name ("", *makefiles);
+
+ /* Reuse the storage allocated for the read_file. */
+ *makefiles = dep_name (d);
+ ++num_makefiles;
+ ++makefiles;
+ }
+
+ /* If there were no -f switches, try the default names. */
+
+ if (num_makefiles == 0)
+ {
+ static const char *default_makefiles[] =
+#ifdef VMS
+ /* all lower case since readdir() (the vms version) 'lowercasifies' */
+ /* TODO: Above is not always true, this needs more work */
+# ifdef KMK
+ { "makefile.kmk", "makefile.vms", "gnumakefile.", "makefile.", 0 };
+# else
+ { "makefile.vms", "gnumakefile", "makefile", 0 };
+# endif
+#else
+#ifdef _AMIGA
+# ifdef KMK
+ { "Makefile.kmk", "makefile.kmk", "GNUmakefile", "Makefile", "SMakefile", 0 };
+# else
+ { "GNUmakefile", "Makefile", "SMakefile", 0 };
+# endif
+#else /* !Amiga && !VMS */
+#ifdef WINDOWS32
+# ifdef KMK
+ { "Makefile.kmk", "makefile.kmk", "GNUmakefile", "makefile", "Makefile", "makefile.mak", 0 };
+# else
+ { "GNUmakefile", "makefile", "Makefile", "makefile.mak", 0 };
+# endif
+#else /* !Amiga && !VMS && !WINDOWS32 */
+# ifdef KMK
+ { "Makefile.kmk", "makefile.kmk", "GNUmakefile", "makefile", "Makefile", 0 };
+# else
+ { "GNUmakefile", "makefile", "Makefile", 0 };
+# endif
+#endif /* !Amiga && !VMS && !WINDOWS32 */
+#endif /* AMIGA */
+#endif /* VMS */
+ const char **p = default_makefiles;
+ while (*p != 0 && !file_exists_p (*p))
+ ++p;
+
+ if (*p != 0)
+ {
+ eval_makefile (*p, 0);
+ if (errno)
+ perror_with_name ("", *p);
+ }
+ else
+ {
+ /* No default makefile was found. Add the default makefiles to the
+ 'read_files' chain so they will be updated if possible. */
+ struct goaldep *tail = read_files;
+ /* Add them to the tail, after any MAKEFILES variable makefiles. */
+ while (tail != 0 && tail->next != 0)
+ tail = tail->next;
+ for (p = default_makefiles; *p != 0; ++p)
+ {
+ struct goaldep *d = alloc_goaldep ();
+ d->file = enter_file (strcache_add (*p));
+ /* Tell update_goal_chain to bail out as soon as this file is
+ made, and main not to die if we can't make this file. */
+ d->flags = RM_DONTCARE;
+ if (tail == 0)
+ read_files = d;
+ else
+ tail->next = d;
+ tail = d;
+ }
+ if (tail != 0)
+ tail->next = 0;
+ }
+ }
+
+ return read_files;
+}
+
+/* Install a new conditional and return the previous one. */
+
+static struct conditionals *
+install_conditionals (struct conditionals *new)
+{
+ struct conditionals *save = conditionals;
+
+#ifndef KMK
+ memset (new, '\0', sizeof (*new));
+#else /* KMK */
+ new->if_cmds = 0;
+ new->allocated = sizeof (new->ignoring_first);
+ new->ignoring = new->ignoring_first;
+ new->seen_else = new->seen_else_first;
+#endif /* KMK */
+ conditionals = new;
+
+ return save;
+}
+
+/* Free the current conditionals and reinstate a saved one. */
+
+static void
+restore_conditionals (struct conditionals *saved)
+{
+ /* Free any space allocated by conditional_line. */
+#ifdef KMK
+ if (conditionals->allocated > sizeof (conditionals->ignoring_first))
+ {
+#endif
+ free (conditionals->ignoring);
+ free (conditionals->seen_else);
+#ifdef KMK
+ }
+#endif
+
+ /* Restore state. */
+ conditionals = saved;
+}
+
+static struct goaldep *
+eval_makefile (const char *filename, int flags)
+{
+ struct goaldep *deps;
+ struct ebuffer ebuf;
+ const floc *curfile;
+ char *expanded = 0;
+ int makefile_errno;
+
+ ebuf.floc.filenm = filename; /* Use the original file name. */
+ ebuf.floc.lineno = 1;
+ ebuf.floc.offset = 0;
+
+ if (ISDB (DB_VERBOSE))
+ {
+ printf (_("Reading makefile '%s'"), filename);
+ if (flags & RM_NO_DEFAULT_GOAL)
+ printf (_(" (no default goal)"));
+ if (flags & RM_INCLUDED)
+ printf (_(" (search path)"));
+ if (flags & RM_DONTCARE)
+ printf (_(" (don't care)"));
+ if (flags & RM_NO_TILDE)
+ printf (_(" (no ~ expansion)"));
+ puts ("...");
+ }
+
+ /* First, get a stream to read. */
+
+ /* Expand ~ in FILENAME unless it came from 'include',
+ in which case it was already done. */
+ if (!(flags & RM_NO_TILDE) && filename[0] == '~')
+ {
+ expanded = tilde_expand (filename);
+ if (expanded != 0)
+ filename = expanded;
+ }
+
+#ifdef _MSC_VER
+ ENULLLOOP (ebuf.fp, fopen (filename, "rN")); /* N == noinherit */
+#else
+ ENULLLOOP (ebuf.fp, fopen (filename, "r"));
+#endif
+
+ /* Save the error code so we print the right message later. */
+ makefile_errno = errno;
+
+ /* Check for unrecoverable errors: out of mem or FILE slots. */
+ switch (makefile_errno)
+ {
+#ifdef EMFILE
+ case EMFILE:
+#endif
+#ifdef ENFILE
+ case ENFILE:
+#endif
+ case ENOMEM:
+ {
+ const char *err = strerror (makefile_errno);
+ OS (fatal, reading_file, "%s", err);
+ }
+ }
+
+ /* If the makefile wasn't found and it's either a makefile from
+ the 'MAKEFILES' variable or an included makefile,
+ search the included makefile search path for this makefile. */
+ if (ebuf.fp == 0 && (flags & RM_INCLUDED) && *filename != '/')
+ {
+ unsigned int i;
+ for (i = 0; include_directories[i] != 0; ++i)
+ {
+ const char *included = concat (3, include_directories[i],
+ "/", filename);
+#ifdef _MSC_VER
+ ebuf.fp = fopen (included, "rN"); /* N == noinherit */
+#else
+ ebuf.fp = fopen (included, "r");
+#endif
+ if (ebuf.fp)
+ {
+ filename = included;
+ break;
+ }
+ }
+ }
+
+ /* Now we have the final name for this makefile. Enter it into
+ the cache. */
+ filename = strcache_add (filename);
+
+ /* Add FILENAME to the chain of read makefiles. */
+ deps = alloc_goaldep ();
+ deps->next = read_files;
+ read_files = deps;
+#ifndef CONFIG_WITH_STRCACHE2
+ deps->file = lookup_file (filename);
+#else
+ deps->file = lookup_file_cached (filename);
+#endif
+ if (deps->file == 0)
+ deps->file = enter_file (filename);
+ filename = deps->file->name;
+ deps->flags = flags;
+
+ free (expanded);
+
+ /* If the makefile can't be found at all, give up entirely. */
+
+ if (ebuf.fp == 0)
+ {
+ /* If we did some searching, errno has the error from the last
+ attempt, rather from FILENAME itself. Store it in case the
+ caller wants to use it in a message. */
+ errno = makefile_errno;
+ return deps;
+ }
+
+ /* Set close-on-exec to avoid leaking the makefile to children, such as
+ $(shell ...). */
+#ifndef _MSC_VER /* not necessary, see fopen calls above. */
+#ifdef HAVE_FILENO
+ CLOSE_ON_EXEC (fileno (ebuf.fp));
+#endif
+#endif
+
+ /* Add this makefile to the list. */
+ do_variable_definition (&ebuf.floc, "MAKEFILE_LIST", filename, o_file,
+ f_append, 0);
+
+#ifdef CONFIG_WITH_COMPILER
+ /* Execute compiled version if repeatedly evaluating this file.
+ ASSUMES file content is unmodified since compilation. */
+ deps->file->eval_count++;
+ if ( deps->file->evalprog
+# ifdef CONFIG_WITH_COMPILE_EVERYTHING
+ || ( deps->file->eval_count == 1
+# else
+ || ( deps->file->eval_count == 3
+# endif
+ && (deps->file->evalprog = kmk_cc_compile_file_for_eval (ebuf.fp, filename)) != NULL) )
+ {
+ curfile = reading_file;
+ reading_file = &ebuf.floc;
+
+ kmk_exec_eval_file (deps->file->evalprog);
+
+ reading_file = curfile;
+ fclose (ebuf.fp);
+ alloca (0);
+ return 1;
+ }
+#elif defined (CONFIG_WITH_MAKE_STATS)
+ deps->file->eval_count++;
+#endif
+
+#ifdef KMK
+ /* Buffer the entire file or at least 256KB (footer.kmk) of it. */
+ {
+ void *stream_buf = NULL;
+ struct stat st;
+# ifdef KBUILD_OS_WINDOWS
+ if (!birdStatOnFdJustSize(fileno(ebuf.fp), &st.st_size))
+# else
+ if (!fstat (fileno (ebuf.fp), &st))
+# endif
+ {
+ int stream_buf_size = 256*1024;
+ if (st.st_size < stream_buf_size)
+ {
+ if (st.st_size)
+ stream_buf_size = (st.st_size + 0xfff) & ~0xfff;
+ else
+ stream_buf_size = 0x1000;
+ }
+ stream_buf = xmalloc (stream_buf_size);
+ setvbuf (ebuf.fp, stream_buf, _IOFBF, stream_buf_size);
+ }
+#endif
+
+ /* Evaluate the makefile */
+
+ ebuf.size = 200;
+ ebuf.buffer = ebuf.bufnext = ebuf.bufstart = xmalloc (ebuf.size);
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf.eol = NULL;
+#endif
+
+ curfile = reading_file;
+ reading_file = &ebuf.floc;
+
+ eval (&ebuf, !(flags & RM_NO_DEFAULT_GOAL));
+
+ reading_file = curfile;
+
+ fclose (ebuf.fp);
+
+#ifdef KMK
+ if (stream_buf)
+ free (stream_buf);
+ }
+#endif
+ free (ebuf.bufstart);
+ alloca (0);
+
+ errno = 0;
+ return deps;
+}
+
+void
+eval_buffer (char *buffer, const floc *flocp IF_WITH_VALUE_LENGTH_PARAM(char *eos))
+{
+ struct ebuffer ebuf;
+ struct conditionals *saved;
+ struct conditionals new;
+ const floc *curfile;
+
+ /* Evaluate the buffer */
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ ebuf.size = strlen (buffer);
+#else
+ ebuf.size = eos - buffer;
+ ebuf.eol = eos;
+ assert(strchr(buffer, '\0') == eos);
+#endif
+ ebuf.buffer = ebuf.bufnext = ebuf.bufstart = buffer;
+ ebuf.fp = NULL;
+
+ if (flocp)
+ ebuf.floc = *flocp;
+ else if (reading_file)
+ ebuf.floc = *reading_file;
+ else
+ {
+ ebuf.floc.filenm = NULL;
+ ebuf.floc.lineno = 1;
+ ebuf.floc.offset = 0;
+ }
+
+ curfile = reading_file;
+ reading_file = &ebuf.floc;
+
+ saved = install_conditionals (&new);
+
+ eval (&ebuf, 1);
+
+ restore_conditionals (saved);
+
+ reading_file = curfile;
+
+ alloca (0);
+}
+
+/* Check LINE to see if it's a variable assignment or undefine.
+
+ It might use one of the modifiers "export", "override", "private", or it
+ might be one of the conditional tokens like "ifdef", "include", etc.
+
+ If it's not a variable assignment or undefine, VMOD.V_ASSIGN is 0.
+ Returns LINE.
+
+ Returns a pointer to the first non-modifier character, and sets VMOD
+ based on the modifiers found if any, plus V_ASSIGN is 1.
+ */
+static char *
+parse_var_assignment (const char *line, struct vmodifiers *vmod)
+{
+ const char *p;
+ memset (vmod, '\0', sizeof (*vmod));
+
+ /* Find the start of the next token. If there isn't one we're done. */
+ NEXT_TOKEN (line);
+ if (*line == '\0')
+ return (char *)line;
+
+ p = line;
+ while (1)
+ {
+ int wlen;
+ const char *p2;
+ struct variable v;
+
+ p2 = parse_variable_definition (p, &v);
+
+ /* If this is a variable assignment, we're done. */
+ if (p2)
+ break;
+
+ /* It's not a variable; see if it's a modifier. */
+ p2 = end_of_token (p);
+ wlen = p2 - p;
+
+ if (word1eq ("export"))
+ vmod->export_v = 1;
+ else if (word1eq ("override"))
+ vmod->override_v = 1;
+ else if (word1eq ("private"))
+ vmod->private_v = 1;
+ else if (word1eq ("define"))
+ {
+ /* We can't have modifiers after 'define' */
+ vmod->define_v = 1;
+ p = next_token (p2);
+ break;
+ }
+ else if (word1eq ("undefine"))
+ {
+ /* We can't have modifiers after 'undefine' */
+ vmod->undefine_v = 1;
+ p = next_token (p2);
+ break;
+ }
+ else
+ /* Not a variable or modifier: this is not a variable assignment. */
+ return (char *)line;
+
+ /* It was a modifier. Try the next word. */
+ p = next_token (p2);
+ if (*p == '\0')
+ return (char *)line;
+ }
+
+ /* Found a variable assignment or undefine. */
+ vmod->assign_v = 1;
+ return (char *)p;
+}
+
+
+/* Read file FILENAME as a makefile and add its contents to the data base.
+
+ SET_DEFAULT is true if we are allowed to set the default goal. */
+
+static void
+eval (struct ebuffer *ebuf, int set_default)
+{
+ char *collapsed = 0;
+ unsigned int collapsed_length = 0;
+ unsigned int commands_len = 200;
+ char *commands;
+ unsigned int commands_idx = 0;
+ unsigned int cmds_started, tgts_started;
+ int ignoring = 0, in_ignored_define = 0;
+ int no_targets = 0; /* Set when reading a rule without targets. */
+ struct nameseq *filenames = 0;
+ char *depstr = 0;
+ long nlines = 0;
+ int two_colon = 0;
+ char prefix = cmd_prefix;
+ const char *pattern = 0;
+ const char *pattern_percent;
+ floc *fstart;
+ floc fi;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ unsigned int tmp_len;
+#endif
+#ifdef KMK
+ struct kbuild_eval_data *kdata = 0;
+ int krc;
+#endif
+
+#define record_waiting_files() \
+ do \
+ { \
+ if (filenames != 0) \
+ { \
+ fi.lineno = tgts_started; \
+ fi.offset = 0; \
+ record_files (filenames, pattern, pattern_percent, depstr, \
+ cmds_started, commands, commands_idx, two_colon, \
+ prefix, &fi); \
+ filenames = 0; \
+ } \
+ commands_idx = 0; \
+ no_targets = 0; \
+ pattern = 0; \
+ } while (0)
+
+ pattern_percent = 0;
+ cmds_started = tgts_started = 1;
+
+ fstart = &ebuf->floc;
+ fi.filenm = ebuf->floc.filenm;
+
+ /* Loop over lines in the file.
+ The strategy is to accumulate target names in FILENAMES, dependencies
+ in DEPS and commands in COMMANDS. These are used to define a rule
+ when the start of the next rule (or eof) is encountered.
+
+ When you see a "continue" in the loop below, that means we are moving on
+ to the next line. If you see record_waiting_files(), then the statement
+ we are parsing also finishes the previous rule. */
+
+ commands = xmalloc (200);
+
+ while (1)
+ {
+ unsigned int linelen;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ char *eol;
+#endif
+ char *line;
+ unsigned int wlen;
+ char *p;
+ char *p2;
+ struct vmodifiers vmod;
+
+ /* At the top of this loop, we are starting a brand new line. */
+ /* Grab the next line to be evaluated */
+ ebuf->floc.lineno += nlines;
+ nlines = readline (ebuf);
+
+ /* If there is nothing left to eval, we're done. */
+ if (nlines < 0)
+ break;
+
+ line = ebuf->buffer;
+
+ /* If this is the first line, check for a UTF-8 BOM and skip it. */
+ if (ebuf->floc.lineno == 1 && line[0] == (char)0xEF
+ && line[1] == (char)0xBB && line[2] == (char)0xBF)
+ {
+ line += 3;
+ if (ISDB(DB_BASIC))
+ {
+ if (ebuf->floc.filenm)
+ printf (_("Skipping UTF-8 BOM in makefile '%s'\n"),
+ ebuf->floc.filenm);
+ else
+ printf (_("Skipping UTF-8 BOM in makefile buffer\n"));
+ }
+ }
+
+ /* If this line is empty, skip it. */
+ if (line[0] == '\0')
+ continue;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ linelen = strlen (line);
+#else
+ linelen = ebuf->eol - line;
+ assert (strlen (line) == linelen);
+#endif
+
+ /* Check for a shell command line first.
+ If it is not one, we can stop treating cmd_prefix specially. */
+ if (line[0] == cmd_prefix)
+ {
+ if (no_targets)
+ /* Ignore the commands in a rule with no targets. */
+ continue;
+
+ /* If there is no preceding rule line, don't treat this line
+ as a command, even though it begins with a recipe prefix.
+ SunOS 4 make appears to behave this way. */
+
+ if (filenames != 0)
+ {
+ if (ignoring)
+ /* Yep, this is a shell command, and we don't care. */
+ continue;
+
+ if (commands_idx == 0)
+ cmds_started = ebuf->floc.lineno;
+
+ /* Append this command line to the line being accumulated.
+ Skip the initial command prefix character. */
+ if (linelen + commands_idx > commands_len)
+ {
+ commands_len = (linelen + commands_idx) * 2;
+ commands = xrealloc (commands, commands_len);
+ }
+ memcpy (&commands[commands_idx], line + 1, linelen - 1);
+ commands_idx += linelen - 1;
+ commands[commands_idx++] = '\n';
+ continue;
+ }
+ }
+
+ /* This line is not a shell command line. Don't worry about whitespace.
+ Get more space if we need it; we don't need to preserve the current
+ contents of the buffer. */
+
+ if (collapsed_length < linelen+1)
+ {
+ collapsed_length = linelen+1;
+ free (collapsed);
+ /* Don't need xrealloc: we don't need to preserve the content. */
+ collapsed = xmalloc (collapsed_length);
+ }
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ strcpy (collapsed, line);
+ /* Collapse continuation lines. */
+ collapse_continuations (collapsed);
+ remove_comments (collapsed);
+#else
+ memcpy (collapsed, line, linelen + 1);
+ /* Collapse continuation lines. */
+ eol = collapse_continuations (collapsed, linelen);
+ assert (strchr (collapsed, '\0') == eol);
+ eol = remove_comments (collapsed, eol);
+ assert (strchr (collapsed, '\0') == eol);
+#endif
+
+ /* Get rid if starting space (including formfeed, vtab, etc.) */
+ p = collapsed;
+ NEXT_TOKEN (p);
+
+ /* See if this is a variable assignment. We need to do this early, to
+ allow variables with names like 'ifdef', 'export', 'private', etc. */
+ p = parse_var_assignment (p, &vmod);
+ if (vmod.assign_v)
+ {
+ struct variable *v;
+ enum variable_origin origin = vmod.override_v ? o_override : o_file;
+
+ /* If we're ignoring then we're done now. */
+ if (ignoring)
+ {
+ if (vmod.define_v)
+ in_ignored_define = 1;
+ continue;
+ }
+
+ /* Variable assignment ends the previous rule. */
+ record_waiting_files ();
+
+ if (vmod.undefine_v)
+ {
+ do_undefine (p, origin, ebuf);
+ continue;
+ }
+ else if (vmod.define_v)
+ v = do_define (p IF_WITH_VALUE_LENGTH_PARAM(NULL), origin, ebuf);
+ else
+ v = try_variable_definition (fstart, p IF_WITH_VALUE_LENGTH_PARAM(NULL), origin, 0);
+
+ assert (v != NULL);
+
+ if (vmod.export_v)
+ v->export = v_export;
+ if (vmod.private_v)
+ v->private_var = 1;
+
+ /* This line has been dealt with. */
+ continue;
+ }
+
+ /* If this line is completely empty, ignore it. */
+ if (*p == '\0')
+ continue;
+
+ p2 = end_of_token (p);
+ wlen = p2 - p;
+ NEXT_TOKEN (p2);
+
+ /* If we're in an ignored define, skip this line (but maybe get out). */
+ if (in_ignored_define)
+ {
+ /* See if this is an endef line (plus optional comment). */
+ if (word1eq ("endef") && STOP_SET (*p2, MAP_COMMENT|MAP_NUL))
+ in_ignored_define = 0;
+
+ continue;
+ }
+
+ /* Check for conditional state changes. */
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ int i = conditional_line (p, wlen, fstart);
+#else
+ int i = conditional_line (p, eol, wlen, fstart);
+#endif
+ if (i != -2)
+ {
+ if (i == -1)
+ O (fatal, fstart, _("invalid syntax in conditional"));
+
+ ignoring = i;
+ continue;
+ }
+ }
+
+ /* Nothing to see here... move along. */
+ if (ignoring)
+ continue;
+
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ if (word1eq ("local"))
+ {
+ if (*p2 == '\0')
+ O (error, fstart, _("empty `local' directive"));
+
+ if (strneq (p2, "define", 6)
+ && (ISBLANK (p2[6]) || p2[6] == '\0'))
+ {
+ if (ignoring)
+ in_ignored_define = 1;
+ else
+ {
+ p2 = next_token (p2 + 6);
+ if (*p2 == '\0')
+ O (fatal, fstart, _("empty variable name"));
+
+ /* Let the variable name be the whole rest of the line,
+ with trailing blanks stripped (comments have already been
+ removed), so it could be a complex variable/function
+ reference that might contain blanks. */
+ p = strchr (p2, '\0');
+ while (ISBLANK (p[-1]))
+ --p;
+ do_define (p2 IF_WITH_VALUE_LENGTH_PARAM(p), o_local, ebuf);
+ }
+ }
+ else if (!ignoring
+ && !try_variable_definition (fstart, p2 IF_WITH_VALUE_LENGTH_PARAM(eol), o_local, 0))
+ O (error, fstart, _("invalid `local' directive"));
+
+ continue;
+ }
+#endif /* CONFIG_WITH_LOCAL_VARIABLES */
+
+#ifdef KMK
+ /* Check for the kBuild language extensions. */
+ if ( wlen > sizeof("kBuild-")
+ && strneq (p, "kBuild-", sizeof("kBuild-") - 1))
+ {
+ krc = eval_kbuild_read_hook (&kdata, fstart, p, wlen, p2, eol, ignoring);
+ if (krc != 42)
+ {
+ if (krc != 0)
+ ON (error, fstart, _("krc=%d"), krc);
+ continue;
+ }
+ }
+#endif /* KMK */
+
+ /* Manage the "export" keyword used outside of variable assignment
+ as well as "unexport". */
+ if (word1eq ("export") || word1eq ("unexport"))
+ {
+ int exporting = *p == 'u' ? 0 : 1;
+
+ /* Export/unexport ends the previous rule. */
+ record_waiting_files ();
+
+ /* (un)export by itself causes everything to be (un)exported. */
+ if (*p2 == '\0')
+ export_all_variables = exporting;
+ else
+ {
+ unsigned int l;
+ const char *cp;
+ char *ap;
+
+ /* Expand the line so we can use indirect and constructed
+ variable names in an (un)export command. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ cp = ap = allocated_variable_expand (p2);
+#else
+ unsigned int buf_len;
+ cp = ap = allocated_variable_expand_3 (p2, eol - p2, NULL, &buf_len);
+#endif
+
+ for (p = find_next_token (&cp, &l); p != 0;
+ p = find_next_token (&cp, &l))
+ {
+ struct variable *v = lookup_variable (p, l);
+ if (v == 0)
+ v = define_variable_global (p, l, "", o_file, 0, fstart);
+ v->export = exporting ? v_export : v_noexport;
+ }
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (ap);
+#else
+ recycle_variable_buffer (ap, buf_len);
+#endif
+ }
+ continue;
+ }
+
+ /* Handle the special syntax for vpath. */
+ if (word1eq ("vpath"))
+ {
+ const char *cp;
+ char *vpat;
+ unsigned int l;
+
+ /* vpath ends the previous rule. */
+ record_waiting_files ();
+
+ cp = variable_expand (p2);
+ p = find_next_token (&cp, &l);
+ if (p != 0)
+ {
+ vpat = xstrndup (p, l);
+ p = find_next_token (&cp, &l);
+ /* No searchpath means remove all previous
+ selective VPATH's with the same pattern. */
+ }
+ else
+ /* No pattern means remove all previous selective VPATH's. */
+ vpat = 0;
+ construct_vpath_list (vpat, p);
+ free (vpat);
+
+ continue;
+ }
+
+#ifdef CONFIG_WITH_INCLUDEDEP
+ assert (strchr (p2, '\0') == eol);
+ if (word1eq ("includedep") || word1eq ("includedep-queue") || word1eq ("includedep-flush"))
+ {
+ /* We have found an `includedep' line specifying one or more dep files
+ to be read at this point. This include variation does no
+ globbing and do not support multiple names. It's trying to save
+ time by being dead simple as well as ignoring errors. */
+ enum incdep_op op = p[wlen - 1] == 'p'
+ ? incdep_read_it
+ : p[wlen - 1] == 'e'
+ ? incdep_queue : incdep_flush;
+ char *free_me = NULL;
+ unsigned int buf_len;
+ char *name = p2;
+
+ if (memchr (name, '$', eol - name))
+ {
+ unsigned int name_len;
+ free_me = name = allocated_variable_expand_3 (name, eol - name, &name_len, &buf_len);
+ eol = name + name_len;
+ while (ISSPACE (*name))
+ ++name;
+ }
+
+ while (eol > name && ISSPACE (eol[-1]))
+ --eol;
+
+ *eol = '\0';
+ eval_include_dep (name, fstart, op);
+
+ if (free_me)
+ recycle_variable_buffer (free_me, buf_len);
+ continue;
+ }
+#endif /* CONFIG_WITH_INCLUDEDEP */
+
+ /* Handle include and variants. */
+ if (word1eq ("include") || word1eq ("-include") || word1eq ("sinclude"))
+ {
+ /* We have found an 'include' line specifying a nested
+ makefile to be read at this point. */
+ struct conditionals *save;
+ struct conditionals new_conditionals;
+ struct nameseq *files;
+ /* "-include" (vs "include") says no error if the file does not
+ exist. "sinclude" is an alias for this from SGI. */
+ int noerror = (p[0] != 'i');
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ unsigned int buf_len;
+#endif
+
+ /* Include ends the previous rule. */
+ record_waiting_files ();
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p = allocated_variable_expand (p2);
+#else
+ p = allocated_variable_expand_3 (p2, eol - p2, NULL, &buf_len);
+#endif
+
+
+ /* If no filenames, it's a no-op. */
+ if (*p == '\0')
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (p);
+#else
+ recycle_variable_buffer (p, buf_len);
+#endif
+ continue;
+ }
+
+ /* Parse the list of file names. Don't expand archive references! */
+ p2 = p;
+ files = PARSE_FILE_SEQ (&p2, struct nameseq, MAP_NUL, NULL,
+ PARSEFS_NOAR);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (p);
+#else
+ recycle_variable_buffer (p, buf_len);
+#endif
+
+ /* Save the state of conditionals and start
+ the included makefile with a clean slate. */
+ save = install_conditionals (&new_conditionals);
+
+ /* Record the rules that are waiting so they will determine
+ the default goal before those in the included makefile. */
+ record_waiting_files ();
+
+ /* Read each included makefile. */
+ while (files != 0)
+ {
+ struct nameseq *next = files->next;
+ int flags = (RM_INCLUDED | RM_NO_TILDE
+ | (noerror ? RM_DONTCARE : 0)
+ | (set_default ? 0 : RM_NO_DEFAULT_GOAL));
+
+ struct goaldep *d = eval_makefile (files->name, flags);
+
+ if (errno)
+ {
+ d->error = (unsigned short)errno;
+ d->floc = *fstart;
+ }
+
+ free_ns (files);
+ files = next;
+ }
+
+ /* Restore conditional state. */
+ restore_conditionals (save);
+
+ continue;
+ }
+
+ /* Handle the load operations. */
+ if (word1eq ("load") || word1eq ("-load"))
+ {
+ /* A 'load' line specifies a dynamic object to load. */
+ struct nameseq *files;
+ int noerror = (p[0] == '-');
+
+ /* Load ends the previous rule. */
+ record_waiting_files ();
+
+ p = allocated_variable_expand (p2);
+
+ /* If no filenames, it's a no-op. */
+ if (*p == '\0')
+ {
+ free (p);
+ continue;
+ }
+
+ /* Parse the list of file names.
+ Don't expand archive references or strip "./" */
+ p2 = p;
+ files = PARSE_FILE_SEQ (&p2, struct nameseq, MAP_NUL, NULL,
+ PARSEFS_NOAR);
+ free (p);
+
+ /* Load each file. */
+ while (files != 0)
+ {
+ struct nameseq *next = files->next;
+ const char *name = files->name;
+ struct goaldep *deps;
+ int r;
+
+ /* Load the file. 0 means failure. */
+ r = load_file (&ebuf->floc, &name, noerror);
+ if (! r && ! noerror)
+ OS (fatal, &ebuf->floc, _("%s: failed to load"), name);
+
+ free_ns (files);
+ files = next;
+
+ /* Return of -1 means a special load: don't rebuild it. */
+ if (r == -1)
+ continue;
+
+ /* It succeeded, so add it to the list "to be rebuilt". */
+ deps = alloc_goaldep ();
+ deps->next = read_files;
+ read_files = deps;
+ deps->file = lookup_file (name);
+ if (deps->file == 0)
+ deps->file = enter_file (name);
+ deps->file->loaded = 1;
+ }
+
+ continue;
+ }
+
+ /* This line starts with a tab but was not caught above because there
+ was no preceding target, and the line might have been usable as a
+ variable definition. But now we know it is definitely lossage. */
+ if (line[0] == cmd_prefix)
+ O (fatal, fstart, _("recipe commences before first target"));
+
+ /* This line describes some target files. This is complicated by
+ the existence of target-specific variables, because we can't
+ expand the entire line until we know if we have one or not. So
+ we expand the line word by word until we find the first ':',
+ then check to see if it's a target-specific variable.
+
+ In this algorithm, 'lb_next' will point to the beginning of the
+ unexpanded parts of the input buffer, while 'p2' points to the
+ parts of the expanded buffer we haven't searched yet. */
+
+ {
+ enum make_word_type wtype;
+ char *cmdleft, *semip, *lb_next;
+ unsigned int plen = 0;
+ char *colonp;
+ const char *end, *beg; /* Helpers for whitespace stripping. */
+
+ /* Record the previous rule. */
+
+ record_waiting_files ();
+ tgts_started = fstart->lineno;
+
+ /* Search the line for an unquoted ; that is not after an
+ unquoted #. */
+ cmdleft = find_char_unquote (line, MAP_SEMI|MAP_COMMENT|MAP_VARIABLE IF_WITH_VALUE_LENGTH_PARAM(ebuf->eol - line));
+ if (cmdleft != 0 && *cmdleft == '#')
+ {
+ /* We found a comment before a semicolon. */
+ *cmdleft = '\0';
+ cmdleft = 0;
+ }
+ else if (cmdleft != 0)
+ /* Found one. Cut the line short there before expanding it. */
+ *(cmdleft++) = '\0';
+ semip = cmdleft;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ collapse_continuations (line);
+#else
+ collapse_continuations (line, strlen (line)); /**@todo fix this */
+#endif
+
+ /* We can't expand the entire line, since if it's a per-target
+ variable we don't want to expand it. So, walk from the
+ beginning, expanding as we go, and looking for "interesting"
+ chars. The first word is always expandable. */
+ wtype = get_next_mword (line, NULL, &lb_next, &wlen);
+ switch (wtype)
+ {
+ case w_eol:
+ if (cmdleft != 0)
+ O (fatal, fstart, _("missing rule before recipe"));
+ /* This line contained something but turned out to be nothing
+ but whitespace (a comment?). */
+ continue;
+
+ case w_colon:
+ case w_dcolon:
+ /* We accept and ignore rules without targets for
+ compatibility with SunOS 4 make. */
+ no_targets = 1;
+ continue;
+
+ default:
+ break;
+ }
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p2 = variable_expand_string (NULL, lb_next, wlen);
+#else
+ p2 = variable_expand_string_2 (NULL, lb_next, wlen, &eol);
+ assert (strchr (p2, '\0') == eol);
+#endif
+
+ while (1)
+ {
+ lb_next += wlen;
+ if (cmdleft == 0)
+ {
+ /* Look for a semicolon in the expanded line. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ cmdleft = find_char_unquote (p2, MAP_SEMI);
+#else
+ cmdleft = find_char_unquote_0 (p2, ';', MAP_SEMI, &eol);
+#endif
+
+ if (cmdleft != 0)
+ {
+ unsigned long p2_off = p2 - variable_buffer;
+ unsigned long cmd_off = cmdleft - variable_buffer;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *pend = p2 + strlen (p2);
+#endif
+
+ /* Append any remnants of lb, then cut the line short
+ at the semicolon. */
+ *cmdleft = '\0';
+
+ /* One school of thought says that you shouldn't expand
+ here, but merely copy, since now you're beyond a ";"
+ and into a command script. However, the old parser
+ expanded the whole line, so we continue that for
+ backwards-compatibility. Also, it wouldn't be
+ entirely consistent, since we do an unconditional
+ expand below once we know we don't have a
+ target-specific variable. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ (void)variable_expand_string (pend, lb_next, (long)-1);
+ lb_next += strlen (lb_next);
+#else
+ tmp_len = strlen (lb_next);
+ variable_expand_string_2 (eol, lb_next, tmp_len, &eol);
+ lb_next += tmp_len;
+#endif
+ p2 = variable_buffer + p2_off;
+ cmdleft = variable_buffer + cmd_off + 1;
+ }
+ }
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ colonp = find_char_unquote (p2, MAP_COLON);
+#else
+ colonp = find_char_unquote_0 (p2, ':', MAP_COLON, &eol);
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* The drive spec brain-damage strikes again... */
+ /* Note that the only separators of targets in this context
+ are whitespace and a left paren. If others are possible,
+ they should be added to the string in the call to index. */
+ while (colonp && (colonp[1] == '/' || colonp[1] == '\\') &&
+ colonp > p2 && isalpha ((unsigned char)colonp[-1]) &&
+ (colonp == p2 + 1 || strchr (" \t(", colonp[-2]) != 0))
+# ifndef CONFIG_WITH_VALUE_LENGTH
+ colonp = find_char_unquote (colonp + 1, MAP_COLON);
+# else
+ colonp = find_char_unquote_0 (colonp + 1, ':', MAP_COLON, &eol);
+# endif
+#endif
+ if (colonp != 0)
+ break;
+
+ wtype = get_next_mword (lb_next, NULL, &lb_next, &wlen);
+ if (wtype == w_eol)
+ break;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p2 += strlen (p2);
+ *(p2++) = ' ';
+ p2 = variable_expand_string (p2, lb_next, wlen);
+#else
+ *(eol++) = ' ';
+ p2 = variable_expand_string_2 (eol, lb_next, wlen, &eol);
+#endif
+ /* We don't need to worry about cmdleft here, because if it was
+ found in the variable_buffer the entire buffer has already
+ been expanded... we'll never get here. */
+ }
+
+ p2 = next_token (variable_buffer);
+
+ /* If the word we're looking at is EOL, see if there's _anything_
+ on the line. If not, a variable expanded to nothing, so ignore
+ it. If so, we can't parse this line so punt. */
+ if (wtype == w_eol)
+ {
+ if (*p2 == '\0')
+ continue;
+
+ /* There's no need to be ivory-tower about this: check for
+ one of the most common bugs found in makefiles... */
+ if (cmd_prefix == '\t' && strneq (line, " ", 8))
+ O (fatal, fstart, _("missing separator (did you mean TAB instead of 8 spaces?)"));
+ else
+ O (fatal, fstart, _("missing separator"));
+ }
+
+ /* Make the colon the end-of-string so we know where to stop
+ looking for targets. Start there again once we're done. */
+ *colonp = '\0';
+ filenames = PARSE_SIMPLE_SEQ (&p2, struct nameseq);
+ *colonp = ':';
+ p2 = colonp;
+
+ if (!filenames)
+ {
+ /* We accept and ignore rules without targets for
+ compatibility with SunOS 4 make. */
+ no_targets = 1;
+ continue;
+ }
+ /* This should never be possible; we handled it above. */
+ assert (*p2 != '\0');
+ ++p2;
+
+ /* Is this a one-colon or two-colon entry? */
+ two_colon = *p2 == ':';
+ if (two_colon)
+ p2++;
+
+ /* Test to see if it's a target-specific variable. Copy the rest
+ of the buffer over, possibly temporarily (we'll expand it later
+ if it's not a target-specific variable). PLEN saves the length
+ of the unparsed section of p2, for later. */
+ if (*lb_next != '\0')
+ {
+ unsigned int l = p2 - variable_buffer;
+ plen = strlen (p2);
+ variable_buffer_output (p2+plen, lb_next, strlen (lb_next)+1);
+ p2 = variable_buffer + l;
+ }
+
+ p2 = parse_var_assignment (p2, &vmod);
+ if (vmod.assign_v)
+ {
+ /* If there was a semicolon found, add it back, plus anything
+ after it. */
+ if (semip)
+ {
+ unsigned int l = p2 - variable_buffer;
+ *(--semip) = ';';
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ collapse_continuations (semip);
+#else
+ collapse_continuations (semip, strlen(semip)); /** @todo fix this */
+#endif
+ variable_buffer_output (p2 + strlen (p2),
+ semip, strlen (semip)+1);
+ p2 = variable_buffer + l;
+ }
+ record_target_var (filenames, p2,
+ vmod.override_v ? o_override : o_file,
+ &vmod, fstart);
+ filenames = 0;
+ continue;
+ }
+
+ /* This is a normal target, _not_ a target-specific variable.
+ Unquote any = in the dependency list. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ find_char_unquote (lb_next, MAP_EQUALS);
+#else
+ {
+ char *tmp_eos = strchr(lb_next, '\0'); /** @todo see if we can optimize this away... */
+ find_char_unquote_0 (lb_next, '=', MAP_EQUALS, &tmp_eos);
+ }
+#endif
+
+ /* Remember the command prefix for this target. */
+ prefix = cmd_prefix;
+
+ /* We have some targets, so don't ignore the following commands. */
+ no_targets = 0;
+
+ /* Expand the dependencies, etc. */
+ if (*lb_next != '\0')
+ {
+ unsigned int l = p2 - variable_buffer;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ (void) variable_expand_string (p2 + plen, lb_next, (long)-1);
+#else
+ char *eos;
+ (void) variable_expand_string_2 (p2 + plen, lb_next, (long)-1, &eos);
+#endif
+ p2 = variable_buffer + l;
+
+ /* Look for a semicolon in the expanded line. */
+ if (cmdleft == 0)
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ cmdleft = find_char_unquote (p2, MAP_SEMI);
+#else
+ cmdleft = find_char_unquote_0 (p2, ';', MAP_SEMI, &eos);
+#endif
+ if (cmdleft != 0)
+ *(cmdleft++) = '\0';
+ }
+ }
+
+ /* Is this a static pattern rule: 'target: %targ: %dep; ...'? */
+ p = strchr (p2, ':');
+ while (p != 0 && p[-1] == '\\')
+ {
+ char *q = &p[-1];
+ int backslash = 0;
+ while (*q-- == '\\')
+ backslash = !backslash;
+ if (backslash)
+ p = strchr (p + 1, ':');
+ else
+ break;
+ }
+#ifdef _AMIGA
+ /* Here, the situation is quite complicated. Let's have a look
+ at a couple of targets:
+
+ install: dev:make
+
+ dev:make: make
+
+ dev:make:: xyz
+
+ The rule is that it's only a target, if there are TWO :'s
+ OR a space around the :.
+ */
+ if (p && !(ISSPACE (p[1]) || !p[1] || ISSPACE (p[-1])))
+ p = 0;
+#endif
+#ifdef HAVE_DOS_PATHS
+ {
+ int check_again;
+ do {
+ check_again = 0;
+ /* For DOS-style paths, skip a "C:\..." or a "C:/..." */
+ if (p != 0 && (p[1] == '\\' || p[1] == '/') &&
+ isalpha ((unsigned char)p[-1]) &&
+ (p == p2 + 1 || strchr (" \t:(", p[-2]) != 0)) {
+ p = strchr (p + 1, ':');
+ check_again = 1;
+ }
+ } while (check_again);
+ }
+#endif
+ if (p != 0)
+ {
+ struct nameseq *target;
+ target = PARSE_FILE_SEQ (&p2, struct nameseq, MAP_COLON, NULL,
+ PARSEFS_NOGLOB);
+ ++p2;
+ if (target == 0)
+ O (fatal, fstart, _("missing target pattern"));
+ else if (target->next != 0)
+ OS (fatal, fstart, _("multiple target patterns (target '%s')"), target->name); /* bird added target */
+ pattern_percent = find_percent_cached (&target->name);
+ pattern = target->name;
+ if (pattern_percent == 0)
+ OS (fatal, fstart, _("target pattern contains no '%%' (target '%s')"), target->name); /* bird added target */
+ free_ns (target);
+ }
+ else
+ pattern = 0;
+
+ /* Strip leading and trailing whitespaces. */
+ beg = p2;
+ end = beg + strlen (beg) - 1;
+ strip_whitespace (&beg, &end);
+
+ /* Put all the prerequisites here; they'll be parsed later. */
+ if (beg <= end && *beg != '\0')
+ depstr = xstrndup (beg, end - beg + 1);
+ else
+ depstr = 0;
+
+ commands_idx = 0;
+ if (cmdleft != 0)
+ {
+ /* Semicolon means rest of line is a command. */
+ unsigned int l = strlen (cmdleft);
+
+ cmds_started = fstart->lineno;
+
+ /* Add this command line to the buffer. */
+ if (l + 2 > commands_len)
+ {
+ commands_len = (l + 2) * 2;
+ commands = xrealloc (commands, commands_len);
+ }
+ memcpy (commands, cmdleft, l);
+ commands_idx += l;
+ commands[commands_idx++] = '\n';
+ }
+
+ /* Determine if this target should be made default. We used to do
+ this in record_files() but because of the delayed target recording
+ and because preprocessor directives are legal in target's commands
+ it is too late. Consider this fragment for example:
+
+ foo:
+
+ ifeq ($(.DEFAULT_GOAL),foo)
+ ...
+ endif
+
+ Because the target is not recorded until after ifeq directive is
+ evaluated the .DEFAULT_GOAL does not contain foo yet as one
+ would expect. Because of this we have to move the logic here. */
+
+ if (set_default && default_goal_var->value[0] == '\0')
+ {
+ struct dep *d;
+ struct nameseq *t = filenames;
+
+ for (; t != 0; t = t->next)
+ {
+ int reject = 0;
+ const char *name = t->name;
+
+ /* We have nothing to do if this is an implicit rule. */
+ if (strchr (name, '%') != 0)
+ break;
+
+ /* See if this target's name does not start with a '.',
+ unless it contains a slash. */
+ if (*name == '.' && strchr (name, '/') == 0
+#ifdef HAVE_DOS_PATHS
+ && strchr (name, '\\') == 0
+#endif
+ )
+ continue;
+
+
+ /* If this file is a suffix, don't let it be
+ the default goal file. */
+ for (d = suffix_file->deps; d != 0; d = d->next)
+ {
+ register struct dep *d2;
+ if (*dep_name (d) != '.' && streq (name, dep_name (d)))
+ {
+ reject = 1;
+ break;
+ }
+ for (d2 = suffix_file->deps; d2 != 0; d2 = d2->next)
+ {
+#ifndef CONFIG_WITH_STRCACHE2
+ unsigned int l = strlen (dep_name (d2));
+#else
+ unsigned int l = strcache2_get_len (&file_strcache, dep_name (d2));
+#endif
+ if (!strneq (name, dep_name (d2), l))
+ continue;
+ if (streq (name + l, dep_name (d)))
+ {
+ reject = 1;
+ break;
+ }
+ }
+
+ if (reject)
+ break;
+ }
+
+ if (!reject)
+ {
+ define_variable_global (".DEFAULT_GOAL", 13, t->name,
+ o_file, 0, NILF);
+ break;
+ }
+ }
+ }
+
+ continue;
+ }
+
+ /* We get here except in the case that we just read a rule line.
+ Record now the last rule we read, so following spurious
+ commands are properly diagnosed. */
+ record_waiting_files ();
+ }
+
+#undef word1eq
+
+ if (conditionals->if_cmds)
+ O (fatal, fstart, _("missing 'endif'"));
+#ifdef KMK
+
+ if (kdata != NULL)
+ O (fatal, fstart, _("missing `kBuild-endef-*'"));
+#endif
+
+ /* At eof, record the last rule. */
+ record_waiting_files ();
+
+ free (collapsed);
+ free (commands);
+}
+
+
+/* Remove comments from LINE.
+ This is done by copying the text at LINE onto itself. */
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+static void
+remove_comments (char *line)
+#else
+static char *
+remove_comments (char *line, char *eos)
+#endif
+{
+ char *comment;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ comment = find_char_unquote (line, MAP_COMMENT);
+
+ if (comment != 0)
+ /* Cut off the line at the #. */
+ *comment = '\0';
+#else
+ comment = find_char_unquote_0 (line, '#', MAP_COMMENT, &eos);
+ if (comment)
+ {
+ /* Cut off the line at the #. */
+ *comment = '\0';
+ return comment;
+ }
+ return eos;
+#endif
+}
+
+/* Execute a 'undefine' directive.
+ The undefine line has already been read, and NAME is the name of
+ the variable to be undefined. */
+
+static void
+do_undefine (char *name, enum variable_origin origin, struct ebuffer *ebuf)
+{
+ char *p, *var;
+
+ /* Expand the variable name and find the beginning (NAME) and end. */
+ var = allocated_variable_expand (name);
+ name = next_token (var);
+ if (*name == '\0')
+ O (fatal, &ebuf->floc, _("empty variable name"));
+ p = name + strlen (name) - 1;
+ while (p > name && ISBLANK (*p))
+ --p;
+ p[1] = '\0';
+
+ undefine_variable_global (name, p - name + 1, origin);
+ free (var);
+}
+
+/* Execute a 'define' directive.
+ The first line has already been read, and NAME is the name of
+ the variable to be defined. The following lines remain to be read. */
+
+static struct variable *
+do_define (char *name IF_WITH_VALUE_LENGTH_PARAM(char *eos),
+ enum variable_origin origin, struct ebuffer *ebuf)
+{
+ struct variable *v;
+ struct variable var;
+ floc defstart;
+ int nlevels = 1;
+ unsigned int length = 100;
+ char *definition = xmalloc (length);
+ unsigned int idx = 0;
+ char *p, *n;
+
+ defstart = ebuf->floc;
+
+ p = parse_variable_definition (name, &var);
+ if (p == NULL)
+ /* No assignment token, so assume recursive. */
+ var.flavor = f_recursive;
+ else
+ {
+ if (var.value[0] != '\0')
+ O (error, &defstart, _("extraneous text after 'define' directive"));
+
+ /* Chop the string before the assignment token to get the name. */
+#ifndef CONFIG_WITH_STRCACHE2
+ var.name[var.length] = '\0';
+#else
+ assert (!strcache2_is_cached (&variable_strcache, var.name));
+ ((char *)var.name)[var.length] = '\0';
+#endif
+ }
+
+ /* Expand the variable name and find the beginning (NAME) and end. */
+ n = allocated_variable_expand (name);
+ name = next_token (n);
+ if (name[0] == '\0')
+ O (fatal, &defstart, _("empty variable name"));
+ p = name + strlen (name) - 1;
+ while (p > name && ISBLANK (*p))
+ --p;
+ p[1] = '\0';
+
+ /* Now read the value of the variable. */
+ while (1)
+ {
+ unsigned int len;
+ char *line;
+ long nlines = readline (ebuf);
+
+ /* If there is nothing left to be eval'd, there's no 'endef'!! */
+ if (nlines < 0)
+ O (fatal, &defstart, _("missing 'endef', unterminated 'define'"));
+
+ ebuf->floc.lineno += nlines;
+ line = ebuf->buffer;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ collapse_continuations (line);
+#else
+ ebuf->eol = collapse_continuations (line, ebuf->eol - line);
+#endif
+
+ /* If the line doesn't begin with a tab, test to see if it introduces
+ another define, or ends one. Stop if we find an 'endef' */
+ if (line[0] != cmd_prefix)
+ {
+ p = next_token (line);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ len = strlen (p);
+#else
+ len = ebuf->eol - p;
+ assert (len == strlen (p));
+#endif
+
+ /* If this is another 'define', increment the level count. */
+ if ((len == 6 || (len > 6 && ISBLANK (p[6])))
+ && strneq (p, "define", 6))
+ ++nlevels;
+
+ /* If this is an 'endef', decrement the count. If it's now 0,
+ we've found the last one. */
+ else if ((len == 5 || (len > 5 && ISBLANK (p[5])))
+ && strneq (p, "endef", 5))
+ {
+ p += 5;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ remove_comments (p);
+#else
+ ebuf->eol = remove_comments (p, ebuf->eol);
+#endif
+ if (*(next_token (p)) != '\0')
+ O (error, &ebuf->floc,
+ _("extraneous text after 'endef' directive"));
+
+ if (--nlevels == 0)
+ break;
+ }
+ }
+
+ /* Add this line to the variable definition. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ len = strlen (line);
+#else
+ len = ebuf->eol - line;
+ assert (len == strlen (line));
+#endif
+ if (idx + len + 1 > length)
+ {
+ length = (idx + len) * 2;
+ definition = xrealloc (definition, length + 1);
+ }
+
+ memcpy (&definition[idx], line, len);
+ idx += len;
+ /* Separate lines with a newline. */
+ definition[idx++] = '\n';
+ }
+
+ /* We've got what we need; define the variable. */
+ if (idx == 0)
+ definition[0] = '\0';
+ else
+ definition[idx - 1] = '\0';
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ v = do_variable_definition (&defstart, name,
+ definition, origin, var.flavor, 0);
+#else
+ v = do_variable_definition_2 (&defstart, name, definition,
+ idx ? idx - 1 : idx, var.flavor == f_simple,
+ 0 /* free_value */, origin, var.flavor,
+ 0 /*target_var*/);
+#endif
+ free (definition);
+ free (n);
+ return (v);
+}
+
+/* Interpret conditional commands "ifdef", "ifndef", "ifeq",
+ "ifneq", "if1of", "ifn1of", "else" and "endif".
+ LINE is the input line, with the command as its first word.
+
+ FILENAME and LINENO are the filename and line number in the
+ current makefile. They are used for error messages.
+
+ Value is -2 if the line is not a conditional at all,
+ -1 if the line is an invalid conditional,
+ 0 if following text should be interpreted,
+ 1 if following text should be ignored. */
+
+static int
+conditional_line (char *line IF_WITH_VALUE_LENGTH_PARAM(char *eol), int len, const floc *flocp)
+{
+ const char *cmdname;
+ enum { c_ifdef, c_ifndef, c_ifeq, c_ifneq,
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+ c_if1of, c_ifn1of,
+#endif
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+ c_ifcond,
+#endif
+ c_else, c_endif
+ } cmdtype;
+ unsigned int i;
+ unsigned int o;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ assert (strchr (line, '\0') == eol);
+#endif
+
+ /* Compare a word, both length and contents. */
+#define word1eq(s) (len == CSTRLEN (s) && strneq (s, line, CSTRLEN (s)))
+#define chkword(s, t) if (word1eq (s)) { cmdtype = (t); cmdname = (s); }
+
+ /* Make sure this line is a conditional. */
+ chkword ("ifdef", c_ifdef)
+ else chkword ("ifndef", c_ifndef)
+ else chkword ("ifeq", c_ifeq)
+ else chkword ("ifneq", c_ifneq)
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+ else chkword ("if1of", c_if1of)
+ else chkword ("ifn1of", c_ifn1of)
+#endif
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+ else chkword ("if", c_ifcond)
+#endif
+ else chkword ("else", c_else)
+ else chkword ("endif", c_endif)
+ else
+ return -2;
+
+ /* Found one: skip past it and any whitespace after it. */
+ line += len;
+ NEXT_TOKEN (line);
+
+#define EXTRATEXT() OS (error, flocp, _("extraneous text after '%s' directive"), cmdname)
+#define EXTRACMD() OS (fatal, flocp, _("extraneous '%s'"), cmdname)
+
+ /* An 'endif' cannot contain extra text, and reduces the if-depth by 1 */
+ if (cmdtype == c_endif)
+ {
+ if (*line != '\0')
+ EXTRATEXT ();
+
+ if (!conditionals->if_cmds)
+ EXTRACMD ();
+
+ --conditionals->if_cmds;
+
+ goto DONE;
+ }
+
+ /* An 'else' statement can either be simple, or it can have another
+ conditional after it. */
+ if (cmdtype == c_else)
+ {
+ const char *p;
+
+ if (!conditionals->if_cmds)
+ EXTRACMD ();
+
+ o = conditionals->if_cmds - 1;
+
+ if (conditionals->seen_else[o])
+ O (fatal, flocp, _("only one 'else' per conditional"));
+
+ /* Change the state of ignorance. */
+ switch (conditionals->ignoring[o])
+ {
+ case 0:
+ /* We've just been interpreting. Never do it again. */
+ conditionals->ignoring[o] = 2;
+ break;
+ case 1:
+ /* We've never interpreted yet. Maybe this time! */
+ conditionals->ignoring[o] = 0;
+ break;
+ }
+
+ /* It's a simple 'else'. */
+ if (*line == '\0')
+ {
+ conditionals->seen_else[o] = 1;
+ goto DONE;
+ }
+
+ /* The 'else' has extra text. That text must be another conditional
+ and cannot be an 'else' or 'endif'. */
+
+ /* Find the length of the next word. */
+ for (p = line+1; ! STOP_SET (*p, MAP_SPACE|MAP_NUL); ++p)
+ ;
+ len = p - line;
+
+ /* If it's 'else' or 'endif' or an illegal conditional, fail. */
+ if (word1eq ("else") || word1eq ("endif")
+ || conditional_line (line IF_WITH_VALUE_LENGTH_PARAM(eol), len, flocp) < 0)
+ EXTRATEXT ();
+ else
+ {
+ /* conditional_line() created a new level of conditional.
+ Raise it back to this level. */
+ if (conditionals->ignoring[o] < 2)
+ conditionals->ignoring[o] = conditionals->ignoring[o+1];
+ --conditionals->if_cmds;
+ }
+
+ goto DONE;
+ }
+
+#ifndef KMK
+ if (conditionals->allocated == 0)
+ {
+ conditionals->allocated = 5;
+ conditionals->ignoring = xmalloc (conditionals->allocated);
+ conditionals->seen_else = xmalloc (conditionals->allocated);
+ }
+#endif
+
+ o = conditionals->if_cmds++;
+ if (conditionals->if_cmds > conditionals->allocated)
+ {
+#ifdef KMK
+ if (conditionals->allocated <= sizeof (conditionals->ignoring_first))
+ {
+ assert (conditionals->allocated == sizeof (conditionals->ignoring_first));
+ conditionals->allocated += 16;
+ conditionals->ignoring = xmalloc (conditionals->allocated);
+ memcpy (conditionals->ignoring, conditionals->ignoring_first,
+ sizeof (conditionals->ignoring_first));
+ conditionals->seen_else = xmalloc (conditionals->allocated);
+ memcpy (conditionals->seen_else, conditionals->seen_else_first,
+ sizeof (conditionals->seen_else_first));
+ }
+ else
+ {
+ conditionals->allocated *= 2;
+#else /* !KMK */
+ conditionals->allocated += 5;
+#endif /* !KMK */
+ conditionals->ignoring = xrealloc (conditionals->ignoring,
+ conditionals->allocated);
+ conditionals->seen_else = xrealloc (conditionals->seen_else,
+ conditionals->allocated);
+#ifdef KMK
+ }
+#endif
+ }
+
+ /* Record that we have seen an 'if...' but no 'else' so far. */
+ conditionals->seen_else[o] = 0;
+
+ /* Search through the stack to see if we're already ignoring. */
+ for (i = 0; i < o; ++i)
+ if (conditionals->ignoring[i])
+ {
+ /* We are already ignoring, so just push a level to match the next
+ "else" or "endif", and keep ignoring. We don't want to expand
+ variables in the condition. */
+ conditionals->ignoring[o] = 1;
+ return 1;
+ }
+
+ if (cmdtype == c_ifdef || cmdtype == c_ifndef)
+ {
+ char *var;
+ struct variable *v;
+ char *p;
+
+ /* Expand the thing we're looking up, so we can use indirect and
+ constructed variable names. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ var = allocated_variable_expand (line);
+#else
+ var = variable_expand_string_2 (NULL, line, eol - line, &p);
+#endif
+
+ /* Make sure there's only one variable name to test. */
+ p = end_of_token (var);
+ i = p - var;
+ NEXT_TOKEN (p);
+ if (*p != '\0')
+ return -1;
+
+ var[i] = '\0';
+ v = lookup_variable (var, i);
+
+ conditionals->ignoring[o] =
+ ((v != 0 && *v->value != '\0') == (cmdtype == c_ifndef));
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (var);
+#endif
+ }
+#ifdef CONFIG_WITH_IF_CONDITIONALS
+ else if (cmdtype == c_ifcond)
+ {
+ int rval = expr_eval_if_conditionals (line, flocp);
+ if (rval == -1)
+ return rval;
+ conditionals->ignoring[o] = rval;
+ }
+#endif
+ else
+ {
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+ /* "ifeq", "ifneq", "if1of" or "ifn1of". */
+#else
+ /* "ifeq" or "ifneq". */
+#endif
+ char *s1, *s2;
+ unsigned int l;
+ char termin = *line == '(' ? ',' : *line;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ char *s1_end, *s2_end;
+#endif
+
+ if (termin != ',' && termin != '"' && termin != '\'')
+ return -1;
+
+ s1 = ++line;
+ /* Find the end of the first string. */
+ if (termin == ',')
+ {
+ int count = 0;
+ for (; *line != '\0'; ++line)
+ if (*line == '(')
+ ++count;
+ else if (*line == ')')
+ --count;
+ else if (*line == ',' && count <= 0)
+ break;
+ }
+ else
+ while (*line != '\0' && *line != termin)
+ ++line;
+
+ if (*line == '\0')
+ return -1;
+
+ if (termin == ',')
+ {
+ /* Strip blanks after the first string. */
+ char *p = line++;
+ while (ISBLANK (p[-1]))
+ --p;
+ *p = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ l = p - s1;
+#endif
+ }
+ else
+ {
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ l = line - s1;
+#endif
+ *line++ = '\0';
+ }
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ s2 = variable_expand (s1);
+ /* We must allocate a new copy of the expanded string because
+ variable_expand re-uses the same buffer. */
+ l = strlen (s2);
+ s1 = alloca (l + 1);
+ memcpy (s1, s2, l + 1);
+#else
+ s1 = variable_expand_string_2 (NULL, s1, l, &s1_end);
+#endif
+
+ if (termin != ',')
+ /* Find the start of the second string. */
+ NEXT_TOKEN (line);
+
+ termin = termin == ',' ? ')' : *line;
+ if (termin != ')' && termin != '"' && termin != '\'')
+ return -1;
+
+ /* Find the end of the second string. */
+ if (termin == ')')
+ {
+ int count = 0;
+ s2 = next_token (line);
+ for (line = s2; *line != '\0'; ++line)
+ {
+ if (*line == '(')
+ ++count;
+ else if (*line == ')')
+ {
+ if (count <= 0)
+ break;
+ else
+ --count;
+ }
+ }
+ }
+ else
+ {
+ ++line;
+ s2 = line;
+ while (*line != '\0' && *line != termin)
+ ++line;
+ }
+
+ if (*line == '\0')
+ return -1;
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ l = line - s2;
+#endif
+ *(line++) = '\0';
+ NEXT_TOKEN (line);
+ if (*line != '\0')
+ EXTRATEXT ();
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ s2 = variable_expand (s2);
+#else
+ s2 = variable_expand_string_2 (s1_end + 1, s2, l, &s2_end);
+ if (s2 != s1_end + 1)
+ s1 += s2 - s1_end - 1; /* the variable buffer was reallocated */
+#endif
+#ifdef CONFIG_WITH_SET_CONDITIONALS
+ if (cmdtype == c_if1of || cmdtype == c_ifn1of)
+ {
+ const char *s1_cur;
+ unsigned int s1_len;
+ const char *s1_iterator = s1;
+
+ conditionals->ignoring[o] = (cmdtype == c_if1of); /* if not found */
+ while ((s1_cur = find_next_token (&s1_iterator, &s1_len)) != 0)
+ {
+ const char *s2_cur;
+ unsigned int s2_len;
+ const char *s2_iterator = s2;
+ while ((s2_cur = find_next_token (&s2_iterator, &s2_len)) != 0)
+ if (s2_len == s1_len
+ && strneq (s2_cur, s1_cur, s1_len) )
+ {
+ conditionals->ignoring[o] = (cmdtype != c_if1of); /* found */
+ break;
+ }
+ }
+ }
+ else
+ conditionals->ignoring[o] = (streq (s1, s2) == (cmdtype == c_ifneq));
+#else
+ conditionals->ignoring[o] = (streq (s1, s2) == (cmdtype == c_ifneq));
+#endif
+ }
+
+ DONE:
+ /* Search through the stack to see if we're ignoring. */
+ for (i = 0; i < conditionals->if_cmds; ++i)
+ if (conditionals->ignoring[i])
+ return 1;
+ return 0;
+}
+
+/* Record target-specific variable values for files FILENAMES.
+ TWO_COLON is nonzero if a double colon was used.
+
+ The links of FILENAMES are freed, and so are any names in it
+ that are not incorporated into other data structures.
+
+ If the target is a pattern, add the variable to the pattern-specific
+ variable value list. */
+
+static void
+record_target_var (struct nameseq *filenames, char *defn,
+ enum variable_origin origin, struct vmodifiers *vmod,
+ const floc *flocp)
+{
+ struct nameseq *nextf;
+ struct variable_set_list *global;
+
+ global = current_variable_set_list;
+
+ /* If the variable is an append version, store that but treat it as a
+ normal recursive variable. */
+
+ for (; filenames != 0; filenames = nextf)
+ {
+ struct variable *v;
+ const char *name = filenames->name;
+ const char *percent;
+ struct pattern_var *p;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ const char *fname;
+#endif
+
+ nextf = filenames->next;
+ free_ns (filenames);
+
+ /* If it's a pattern target, then add it to the pattern-specific
+ variable list. */
+ percent = find_percent_cached (&name);
+ if (percent)
+ {
+ /* Get a reference for this pattern-specific variable struct. */
+ p = create_pattern_var (name, percent);
+ p->variable.fileinfo = *flocp;
+ /* I don't think this can fail since we already determined it was a
+ variable definition. */
+ v = assign_variable_definition (&p->variable, defn IF_WITH_VALUE_LENGTH_PARAM(NULL));
+ assert (v != 0);
+
+ v->origin = origin;
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (v->flavor == f_simple)
+ v->value = allocated_variable_expand (v->value);
+ else
+ v->value = xstrdup (v->value);
+#else
+ v->value_length = strlen (v->value);
+ if (v->flavor == f_simple)
+ v->value = allocated_variable_expand_2 (v->value, v->value_length, &v->value_length);
+ else
+ v->value = (char *)memcpy (xmalloc (v->value_length + 1), v->value, v->value_length + 1);
+ v->value_alloc_len = v->value_length + 1;
+#endif
+ }
+ else
+ {
+ struct file *f;
+
+ /* Get a file reference for this file, and initialize it.
+ We don't want to just call enter_file() because that allocates a
+ new entry if the file is a double-colon, which we don't want in
+ this situation. */
+#ifndef CONFIG_WITH_STRCACHE2
+ f = lookup_file (name);
+ if (!f)
+ f = enter_file (strcache_add (name));
+#else /* CONFIG_WITH_STRCACHE2 */
+ /* XXX: this is probably already a cached string. */
+ fname = strcache_add (name);
+ f = lookup_file_cached (fname);
+ if (!f)
+ f = enter_file (fname);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ else if (f->double_colon)
+ f = f->double_colon;
+
+ initialize_file_variables (f, 1);
+
+ current_variable_set_list = f->variables;
+ v = try_variable_definition (flocp, defn IF_WITH_VALUE_LENGTH_PARAM(NULL), origin, 1);
+ if (!v)
+ O (fatal, flocp, _("Malformed target-specific variable definition"));
+ current_variable_set_list = global;
+ }
+
+ /* Set up the variable to be *-specific. */
+ v->per_target = 1;
+ v->private_var = vmod->private_v;
+ v->export = vmod->export_v ? v_export : v_default;
+
+ /* If it's not an override, check to see if there was a command-line
+ setting. If so, reset the value. */
+ if (v->origin != o_override)
+ {
+ struct variable *gv;
+#ifndef CONFIG_WITH_STRCACHE2
+ int len = strlen (v->name);
+#else
+ int len = !percent
+ ? strcache2_get_len (&variable_strcache, v->name)
+ : strlen(v->name);
+#endif
+
+ gv = lookup_variable (v->name, len);
+ if (gv && v != gv
+ && (gv->origin == o_env_override || gv->origin == o_command))
+ {
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ assert (!v->rdonly_val); /* paranoia */
+#endif
+ free (v->value);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ v->value = xstrdup (gv->value);
+#else
+ v->value = xstrndup (gv->value, gv->value_length);
+ v->value_length = gv->value_length;
+#endif
+ v->origin = gv->origin;
+ v->recursive = gv->recursive;
+ v->append = 0;
+ VARIABLE_CHANGED (v);
+ }
+ }
+ }
+}
+
+/* Record a description line for files FILENAMES,
+ with dependencies DEPS, commands to execute described
+ by COMMANDS and COMMANDS_IDX, coming from FILENAME:COMMANDS_STARTED.
+ TWO_COLON is nonzero if a double colon was used.
+ If not nil, PATTERN is the '%' pattern to make this
+ a static pattern rule, and PATTERN_PERCENT is a pointer
+ to the '%' within it.
+
+ The links of FILENAMES are freed, and so are any names in it
+ that are not incorporated into other data structures. */
+
+static void
+record_files (struct nameseq *filenames, const char *pattern,
+ const char *pattern_percent, char *depstr,
+ unsigned int cmds_started, char *commands,
+ unsigned int commands_idx, int two_colon,
+ char prefix, const floc *flocp)
+{
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ struct file *prev_file = 0;
+ enum multitarget_mode { m_unsettled, m_no, m_yes, m_yes_maybe }
+ multi_mode = !two_colon && !pattern ? m_unsettled : m_no;
+#endif
+ struct commands *cmds;
+ struct dep *deps;
+ const char *implicit_percent;
+ const char *name;
+
+ /* If we've already snapped deps, that means we're in an eval being
+ resolved after the makefiles have been read in. We can't add more rules
+ at this time, since they won't get snapped and we'll get core dumps.
+ See Savannah bug # 12124. */
+ if (snapped_deps)
+ O (fatal, flocp, _("prerequisites cannot be defined in recipes"));
+
+ /* Determine if this is a pattern rule or not. */
+ name = filenames->name;
+ implicit_percent = find_percent_cached (&name);
+
+ /* If there's a recipe, set up a struct for it. */
+ if (commands_idx > 0)
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ cmds = xmalloc (sizeof (struct commands));
+#else
+ cmds = alloccache_alloc (&commands_cache);
+#endif
+ cmds->fileinfo.filenm = flocp->filenm;
+ cmds->fileinfo.lineno = cmds_started;
+ cmds->fileinfo.offset = 0;
+ cmds->commands = xstrndup (commands, commands_idx);
+ cmds->command_lines = 0;
+ cmds->recipe_prefix = prefix;
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ cmds->refs = 0;
+#endif
+ }
+ else
+ cmds = 0;
+
+ /* If there's a prereq string then parse it--unless it's eligible for 2nd
+ expansion: if so, snap_deps() will do it. */
+ if (depstr == 0)
+ deps = 0;
+ else
+ {
+ depstr = unescape_char (depstr, ':');
+ if (second_expansion && strchr (depstr, '$'))
+ {
+ deps = alloc_dep ();
+ deps->name = depstr;
+ deps->need_2nd_expansion = 1;
+ deps->staticpattern = pattern != 0;
+ }
+ else
+ {
+ deps = split_prereqs (depstr);
+ free (depstr);
+
+ /* We'll enter static pattern prereqs later when we have the stem.
+ We don't want to enter pattern rules at all so that we don't
+ think that they ought to exist (make manual "Implicit Rule Search
+ Algorithm", item 5c). */
+ if (! pattern && ! implicit_percent)
+ deps = enter_prereqs (deps, NULL);
+ }
+ }
+
+ /* For implicit rules, _all_ the targets must have a pattern. That means we
+ can test the first one to see if we're working with an implicit rule; if
+ so we handle it specially. */
+
+ if (implicit_percent)
+ {
+ struct nameseq *nextf;
+ const char **targets, **target_pats;
+ unsigned int c;
+
+ if (pattern != 0)
+ O (fatal, flocp, _("mixed implicit and static pattern rules"));
+
+ /* Count the targets to create an array of target names.
+ We already have the first one. */
+ nextf = filenames->next;
+ free_ns (filenames);
+ filenames = nextf;
+
+ for (c = 1; nextf; ++c, nextf = nextf->next)
+ ;
+ targets = xmalloc (c * sizeof (const char *));
+ target_pats = xmalloc (c * sizeof (const char *));
+
+ targets[0] = name;
+ target_pats[0] = implicit_percent;
+
+ c = 1;
+ while (filenames)
+ {
+ name = filenames->name;
+ implicit_percent = find_percent_cached (&name);
+
+ if (implicit_percent == 0)
+ O (fatal, flocp, _("mixed implicit and normal rules"));
+
+ targets[c] = name;
+ target_pats[c] = implicit_percent;
+ ++c;
+
+ nextf = filenames->next;
+ free_ns (filenames);
+ filenames = nextf;
+ }
+
+ create_pattern_rule (targets, target_pats, c, two_colon, deps, cmds, 1);
+
+ return;
+ }
+
+
+ /* Walk through each target and create it in the database.
+ We already set up the first target, above. */
+ while (1)
+ {
+ struct nameseq *nextf = filenames->next;
+ struct file *f;
+ struct dep *this = 0;
+
+ free_ns (filenames);
+
+ /* Check for special targets. Do it here instead of, say, snap_deps()
+ so that we can immediately use the value. */
+ if (streq (name, ".POSIX"))
+ {
+ posix_pedantic = 1;
+ define_variable_cname (".SHELLFLAGS", "-ec", o_default, 0);
+ /* These default values are based on IEEE Std 1003.1-2008. */
+ define_variable_cname ("ARFLAGS", "-rv", o_default, 0);
+ define_variable_cname ("CC", "c99", o_default, 0);
+ define_variable_cname ("CFLAGS", "-O", o_default, 0);
+ define_variable_cname ("FC", "fort77", o_default, 0);
+ define_variable_cname ("FFLAGS", "-O 1", o_default, 0);
+ define_variable_cname ("SCCSGETFLAGS", "-s", o_default, 0);
+ }
+ else if (streq (name, ".SECONDEXPANSION"))
+ second_expansion = 1;
+#ifdef CONFIG_WITH_2ND_TARGET_EXPANSION
+ else if (streq (name, ".SECONDTARGETEXPANSION"))
+ second_target_expansion = 1;
+#endif
+#if !defined (__MSDOS__) && !defined (__EMX__)
+ else if (streq (name, ".ONESHELL"))
+ one_shell = 1;
+#endif
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* Check for the explicit multitarget mode operators. For this to be
+ identified as an explicit multiple target rule, the first + or +|
+ operator *must* appear between the first two files. If not found as
+ the 2nd file or if found as the 1st file, the rule will be rejected
+ as a potential multiple first target rule. For the subsequent files
+ the operator is only required to switch between maybe and non-maybe
+ mode:
+ `primary + 2nd 3rd +| 4th-maybe + 5th-for-sure: deps; cmds'
+
+ The whole idea of the maybe-updated files is this:
+ timestamp +| maybe.h: src1.c src2.c
+ grep goes-into-maybe.h $* > timestamp
+ cmp timestamp maybe.h || cp -f timestamp maybe.h
+
+ This is implemented in remake.c where we don't consider the mtime of
+ the maybe-updated targets. */
+ if (multi_mode != m_no && name[0] == '+'
+ && (name[1] == '\0' || (name[1] == '|' && name[2] == '\0')))
+ {
+ if (!prev_file)
+ multi_mode = m_no; /* first */
+ else
+ {
+ if (multi_mode == m_unsettled)
+ {
+ prev_file->multi_head = prev_file;
+
+ /* Only the primary file needs the dependencies. */
+ if (deps)
+ {
+ free_dep_chain (deps);
+ deps = NULL;
+ }
+ }
+ multi_mode = name[1] == '\0' ? m_yes : m_yes_maybe;
+ goto l_next;
+ }
+ }
+ else if (multi_mode == m_unsettled && prev_file)
+ multi_mode = m_no;
+#endif
+
+ /* If this is a static pattern rule:
+ 'targets: target%pattern: prereq%pattern; recipe',
+ make sure the pattern matches this target name. */
+ if (pattern && !pattern_matches (pattern, pattern_percent, name))
+ OS (error, flocp,
+ _("target '%s' doesn't match the target pattern"), name);
+ else if (deps)
+ /* If there are multiple targets, copy the chain DEPS for all but the
+ last one. It is not safe for the same deps to go in more than one
+ place in the database. */
+ this = nextf != 0 ? copy_dep_chain (deps) : deps;
+
+ /* Find or create an entry in the file database for this target. */
+ if (!two_colon)
+ {
+ /* Single-colon. Combine this rule with the file's existing record,
+ if any. */
+#ifndef KMK
+ f = enter_file (strcache_add (name));
+#else /* KMK - the name is already in the cache, don't waste time. */
+ f = enter_file (name);
+#endif
+ if (f->double_colon)
+ OS (fatal, flocp,
+ _("target file '%s' has both : and :: entries"), f->name);
+
+ /* If CMDS == F->CMDS, this target was listed in this rule
+ more than once. Just give a warning since this is harmless. */
+ if (cmds != 0 && cmds == f->cmds)
+ OS (error, flocp,
+ _("target '%s' given more than once in the same rule"),
+ f->name);
+
+ /* Check for two single-colon entries both with commands.
+ Check is_target so that we don't lose on files such as .c.o
+ whose commands were preinitialized. */
+ else if (cmds != 0 && f->cmds != 0 && f->is_target)
+ {
+ size_t l = strlen (f->name);
+ error (&cmds->fileinfo, l,
+ _("warning: overriding recipe for target '%s'"),
+ f->name);
+ error (&f->cmds->fileinfo, l,
+ _("warning: ignoring old recipe for target '%s'"),
+ f->name);
+ }
+
+ /* Defining .DEFAULT with no deps or cmds clears it. */
+ if (f == default_file && this == 0 && cmds == 0)
+ f->cmds = 0;
+ if (cmds != 0)
+ f->cmds = cmds;
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* If this is an explicit multi target rule, add it to the
+ target chain and set the multi_maybe flag according to
+ the current mode. */
+
+ if (multi_mode >= m_yes)
+ {
+ f->multi_maybe = multi_mode == m_yes_maybe;
+ prev_file->multi_next = f;
+ assert (prev_file->multi_head != 0);
+ f->multi_head = prev_file->multi_head;
+
+ if (f == suffix_file)
+ O (error, flocp,
+ _(".SUFFIXES encountered in an explicit multi target rule"));
+ }
+ prev_file = f;
+#endif
+
+ /* Defining .SUFFIXES with no dependencies clears out the list of
+ suffixes. */
+ if (f == suffix_file && this == 0)
+ {
+ free_dep_chain (f->deps);
+ f->deps = 0;
+ }
+ }
+ else
+ {
+ /* Double-colon. Make a new record even if there already is one. */
+#ifndef CONFIG_WITH_STRCACHE2
+ f = lookup_file (name);
+#else /* CONFIG_WITH_STRCACHE2 - the name is already in the cache, don't waste time. */
+ f = lookup_file_cached (name);
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+ /* Check for both : and :: rules. Check is_target so we don't lose
+ on default suffix rules or makefiles. */
+ if (f != 0 && f->is_target && !f->double_colon)
+ OS (fatal, flocp,
+ _("target file '%s' has both : and :: entries"), f->name);
+
+#ifndef KMK
+ f = enter_file (strcache_add (name));
+#else /* KMK - the name is already in the cache, don't waste time. */
+ f = enter_file (name);
+#endif
+ /* If there was an existing entry and it was a double-colon entry,
+ enter_file will have returned a new one, making it the prev
+ pointer of the old one, and setting its double_colon pointer to
+ the first one. */
+ if (f->double_colon == 0)
+ /* This is the first entry for this name, so we must set its
+ double_colon pointer to itself. */
+ f->double_colon = f;
+
+ f->cmds = cmds;
+ }
+
+ f->is_target = 1;
+
+ /* If this is a static pattern rule, set the stem to the part of its
+ name that matched the '%' in the pattern, so you can use $* in the
+ commands. If we didn't do it before, enter the prereqs now. */
+ if (pattern)
+ {
+ static const char *percent = "%";
+ char *buffer = variable_expand ("");
+ const size_t buffer_offset = buffer - variable_buffer; /* bird */
+ char *o = patsubst_expand_pat (buffer, name, pattern, percent,
+ pattern_percent+1, percent+1);
+ buffer = variable_buffer + buffer_offset; /* bird - variable_buffer may have been reallocated. */
+ f->stem = strcache_add_len (buffer, o - buffer);
+ if (this)
+ {
+ if (! this->need_2nd_expansion)
+ this = enter_prereqs (this, f->stem);
+ else
+ this->stem = f->stem;
+ }
+ }
+
+ /* Add the dependencies to this file entry. */
+ if (this != 0)
+ {
+ /* Add the file's old deps and the new ones in THIS together. */
+ if (f->deps == 0)
+ f->deps = this;
+ else if (cmds != 0)
+ {
+ struct dep *d = this;
+
+ /* If this rule has commands, put these deps first. */
+ while (d->next != 0)
+ d = d->next;
+
+ d->next = f->deps;
+ f->deps = this;
+ }
+ else
+ {
+ struct dep *d = f->deps;
+
+ /* A rule without commands: put its prereqs at the end. */
+ while (d->next != 0)
+ d = d->next;
+
+ d->next = this;
+ }
+ }
+
+ name = f->name;
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+l_next:
+#endif
+ /* All done! Set up for the next one. */
+ if (nextf == 0)
+ break;
+
+ filenames = nextf;
+
+ /* Reduce escaped percents. If there are any unescaped it's an error */
+ name = filenames->name;
+ if (find_percent_cached (&name))
+ O (error, flocp,
+ _("*** mixed implicit and normal rules: deprecated syntax"));
+ }
+}
+
+/* Search STRING for an unquoted STOPCHAR or blank (if BLANK is nonzero).
+ Backslashes quote STOPCHAR, blanks if BLANK is nonzero, and backslash.
+ Quoting backslashes are removed from STRING by compacting it into
+ itself. Returns a pointer to the first unquoted STOPCHAR if there is
+ one, or nil if there are none. STOPCHARs inside variable references are
+ ignored if IGNOREVARS is true.
+
+ STOPCHAR _cannot_ be '$' if IGNOREVARS is true. */
+
+static char *
+find_char_unquote (char *string, int map IF_WITH_VALUE_LENGTH_PARAM(unsigned int string_len))
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ unsigned int string_len = 0;
+#endif
+ char *p = string;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ assert (string_len == 0 || string_len == strlen (string));
+#endif
+
+ /* Always stop on NUL. */
+ map |= MAP_NUL;
+
+ while (1)
+ {
+ while (! STOP_SET (*p, map))
+ ++p;
+
+ if (*p == '\0')
+ break;
+
+ /* If we stopped due to a variable reference, skip over its contents. */
+ if (STOP_SET (*p, MAP_VARIABLE))
+ {
+ char openparen = p[1];
+
+ /* Check if '$' is the last character in the string. */
+ if (openparen == '\0')
+ break;
+
+ p += 2;
+
+ /* Skip the contents of a non-quoted, multi-char variable ref. */
+ if (openparen == '(' || openparen == '{')
+ {
+ unsigned int pcount = 1;
+ char closeparen = (openparen == '(' ? ')' : '}');
+ char ch; /* bird */
+
+ while ((ch = *p))
+ {
+ if (ch == openparen)
+ ++pcount;
+ else if (ch == closeparen)
+ if (--pcount == 0)
+ {
+ ++p;
+ break;
+ }
+ ++p;
+ }
+ }
+
+ /* Skipped the variable reference: look for STOPCHARS again. */
+ continue;
+ }
+
+ if (p > string && p[-1] == '\\')
+ {
+ /* Search for more backslashes. */
+ int i = -2;
+ while (&p[i] >= string && p[i] == '\\')
+ --i;
+ ++i;
+ /* Only compute the length if really needed. */
+ if (string_len == 0)
+ string_len = strlen (string);
+ /* The number of backslashes is now -I.
+ Copy P over itself to swallow half of them. */
+ memmove (&p[i], &p[i/2], (string_len - (p - string)) - (i/2) + 1);
+ p += i/2;
+ if (i % 2 == 0)
+ /* All the backslashes quoted each other; the STOPCHAR was
+ unquoted. */
+ return p;
+
+ /* The STOPCHAR was quoted by a backslash. Look for another. */
+ }
+ else
+ /* No backslash in sight. */
+ return p;
+ }
+
+ /* Never hit a STOPCHAR or blank (with BLANK nonzero). */
+ return 0;
+}
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+/* Special case version of find_char_unquote that only takes stop character.
+ This is so common that it makes a lot of sense to specialize this. */
+
+K_INLINE char *
+find_char_unquote_0 (char *string, int stop1, int map, char **eosp)
+{
+ unsigned int string_len = *eosp - string;
+ char *p;
+
+ assert (strlen (string) == string_len);
+ assert (!(map & MAP_VARIABLE) && map != 0);
+ assert ((stopchar_map[(unsigned char)stop1] & map) == map);
+
+ p = (char *)memchr (string, stop1, string_len);
+ if (!p)
+ return NULL;
+ if (p <= string || p[-1] != '\\')
+ return p;
+
+ p = find_char_unquote (string, map, string_len);
+ *eosp = memchr (string, '\0', string_len);
+ return p;
+}
+#endif
+
+/* Unescape a character in a string. The string is compressed onto itself. */
+
+static char *
+unescape_char (char *string, int c)
+{
+ char *p = string;
+ char *s = string;
+
+ while (*s != '\0')
+ {
+ if (*s == '\\')
+ {
+ char *e = s;
+ int l;
+
+ /* We found a backslash. See if it's escaping our character. */
+ while (*e == '\\')
+ ++e;
+ l = e - s;
+
+ if (*e != c || l%2 == 0)
+ {
+ /* It's not; just take it all without unescaping. */
+ memmove (p, s, l);
+ p += l;
+
+ // If we hit the end of the string, we're done
+ if (*e == '\0')
+ break;
+ }
+ else if (l > 1)
+ {
+ /* It is, and there's >1 backslash. Take half of them. */
+ l /= 2;
+ memmove (p, s, l);
+ p += l;
+ }
+
+ s = e;
+ }
+
+ *(p++) = *(s++);
+ }
+
+ *p = '\0';
+ return string;
+}
+
+/* Search PATTERN for an unquoted % and handle quoting. */
+
+char *
+find_percent (char *pattern)
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ return find_char_unquote (pattern, MAP_PERCENT);
+#else
+ char *eos = strchr (pattern, '\0');
+ return find_char_unquote_0 (pattern, '%', MAP_PERCENT, &eos);
+#endif
+}
+
+/* Search STRING for an unquoted % and handle quoting. Returns a pointer to
+ the % or NULL if no % was found.
+ This version is used with strings in the string cache: if there's a need to
+ modify the string a new version will be added to the string cache and
+ *STRING will be set to that. */
+
+const char *
+find_percent_cached (const char **string)
+{
+ const char *p = *string;
+ char *new = 0;
+ int slen = 0;
+
+ /* If the first char is a % return now. This lets us avoid extra tests
+ inside the loop. */
+ if (*p == '%')
+ return p;
+
+ while (1)
+ {
+ while (! STOP_SET (*p, MAP_PERCENT|MAP_NUL))
+ ++p;
+
+ if (*p == '\0')
+ break;
+
+ /* See if this % is escaped with a backslash; if not we're done. */
+ if (p[-1] != '\\')
+ break;
+
+ {
+ /* Search for more backslashes. */
+ char *pv;
+ int i = -2;
+
+ while (&p[i] >= *string && p[i] == '\\')
+ --i;
+ ++i;
+
+ /* At this point we know we'll need to allocate a new string.
+ Make a copy if we haven't yet done so. */
+ if (! new)
+ {
+ slen = strlen (*string);
+ new = alloca (slen + 1);
+ memcpy (new, *string, slen + 1);
+ p = new + (p - *string);
+ *string = new;
+ }
+
+ /* At this point *string, p, and new all point into the same string.
+ Get a non-const version of p so we can modify new. */
+ pv = new + (p - *string);
+
+ /* The number of backslashes is now -I.
+ Copy P over itself to swallow half of them. */
+ memmove (&pv[i], &pv[i/2], (slen - (pv - new)) - (i/2) + 1);
+ p += i/2;
+
+ /* If the backslashes quoted each other; the % was unquoted. */
+ if (i % 2 == 0)
+ break;
+ }
+ }
+
+ /* If we had to change STRING, add it to the strcache. */
+ if (new)
+ {
+ *string = strcache_add (*string);
+ p = *string + (p - new);
+ }
+
+ /* If we didn't find a %, return NULL. Otherwise return a ptr to it. */
+ return (*p == '\0') ? NULL : p;
+}
+
+/* Find the next line of text in an eval buffer, combining continuation lines
+ into one line.
+ Return the number of actual lines read (> 1 if continuation lines).
+ Returns -1 if there's nothing left in the buffer.
+
+ After this function, ebuf->buffer points to the first character of the
+ line we just found.
+ */
+
+/* Read a line of text from a STRING.
+ Since we aren't really reading from a file, don't bother with linenumbers.
+ */
+
+static long
+readstring (struct ebuffer *ebuf)
+{
+ char *eol;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ char *end;
+#endif
+
+ /* If there is nothing left in this buffer, return 0. */
+ if (ebuf->bufnext >= ebuf->bufstart + ebuf->size)
+ return -1;
+
+ /* Set up a new starting point for the buffer, and find the end of the
+ next logical line (taking into account backslash/newline pairs). */
+
+ eol = ebuf->buffer = ebuf->bufnext;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ end = ebuf->bufstart + ebuf->size;
+#endif
+
+ while (1)
+ {
+ int backslash = 0;
+ const char *bol = eol;
+ const char *p;
+
+ /* Find the next newline. At EOS, stop. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p = eol = strchr (eol , '\n');
+#else
+ p = (char *)memchr (eol, '\n', end - eol);
+ assert (!memchr (eol, '\0', p != 0 ? p - eol : end - eol));
+ eol = (char *)p;
+#endif
+ if (!eol)
+ {
+ ebuf->bufnext = ebuf->bufstart + ebuf->size + 1;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = end;
+#endif
+ return 0;
+ }
+
+ /* Found a newline; if it's escaped continue; else we're done. */
+ while (p > bol && *(--p) == '\\')
+ backslash = !backslash;
+ if (!backslash)
+ break;
+ ++eol;
+ }
+
+ /* Overwrite the newline char. */
+ *eol = '\0';
+ ebuf->bufnext = eol+1;
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = eol;
+#endif
+
+ return 0;
+}
+
+static long
+readline (struct ebuffer *ebuf)
+{
+ char *p;
+ char *end;
+ char *start;
+ long nlines = 0;
+
+ /* The behaviors between string and stream buffers are different enough to
+ warrant different functions. Do the Right Thing. */
+
+ if (!ebuf->fp)
+ return readstring (ebuf);
+
+ /* When reading from a file, we always start over at the beginning of the
+ buffer for each new line. */
+
+ p = start = ebuf->bufstart;
+ end = p + ebuf->size;
+ *p = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = p;
+#endif
+
+ while (fgets (p, end - p, ebuf->fp) != 0)
+ {
+ char *p2;
+ unsigned long len;
+ int backslash;
+
+ len = strlen (p);
+ if (len == 0)
+ {
+ /* This only happens when the first thing on the line is a '\0'.
+ It is a pretty hopeless case, but (wonder of wonders) Athena
+ lossage strikes again! (xmkmf puts NULs in its makefiles.)
+ There is nothing really to be done; we synthesize a newline so
+ the following line doesn't appear to be part of this line. */
+ O (error, &ebuf->floc,
+ _("warning: NUL character seen; rest of line ignored"));
+ p[0] = '\n';
+ len = 1;
+ }
+
+ /* Jump past the text we just read. */
+ p += len;
+
+ /* If the last char isn't a newline, the whole line didn't fit into the
+ buffer. Get some more buffer and try again. */
+ if (p[-1] != '\n')
+ goto more_buffer;
+
+ /* We got a newline, so add one to the count of lines. */
+ ++nlines;
+
+#if !defined(WINDOWS32) && !defined(__MSDOS__) && !defined(__EMX__)
+ /* Check to see if the line was really ended with CRLF; if so ignore
+ the CR. */
+ if ((p - start) > 1 && p[-2] == '\r')
+ {
+ --p;
+ memmove (p-1, p, strlen (p) + 1);
+ }
+#endif
+
+ backslash = 0;
+ for (p2 = p - 2; p2 >= start; --p2)
+ {
+ if (*p2 != '\\')
+ break;
+ backslash = !backslash;
+ }
+
+ if (!backslash)
+ {
+ p[-1] = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = p - 1;
+#endif
+ break;
+ }
+
+ /* It was a backslash/newline combo. If we have more space, read
+ another line. */
+ if (end - p >= 80)
+ {
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = p;
+#endif
+ continue;
+ }
+
+ /* We need more space at the end of our buffer, so realloc it.
+ Make sure to preserve the current offset of p. */
+ more_buffer:
+ {
+ unsigned long off = p - start;
+ ebuf->size *= 2;
+ start = ebuf->buffer = ebuf->bufstart = xrealloc (start, ebuf->size);
+ p = start + off;
+ end = start + ebuf->size;
+ *p = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ebuf->eol = p;
+#endif
+ }
+ }
+
+ if (ferror (ebuf->fp))
+ pfatal_with_name (ebuf->floc.filenm);
+
+ /* If we found some lines, return how many.
+ If we didn't, but we did find _something_, that indicates we read the last
+ line of a file with no final newline; return 1.
+ If we read nothing, we're at EOF; return -1. */
+
+ return nlines ? nlines : p == ebuf->bufstart ? -1 : 1;
+}
+
+/* Parse the next "makefile word" from the input buffer, and return info
+ about it.
+
+ A "makefile word" is one of:
+
+ w_bogus Should never happen
+ w_eol End of input
+ w_static A static word; cannot be expanded
+ w_variable A word containing one or more variables/functions
+ w_colon A colon
+ w_dcolon A double-colon
+ w_semicolon A semicolon
+ w_varassign A variable assignment operator (=, :=, ::=, +=, >=, ?=, or !=)
+
+ Note that this function is only used when reading certain parts of the
+ makefile. Don't use it where special rules hold sway (RHS of a variable,
+ in a command list, etc.) */
+
+static enum make_word_type
+get_next_mword (char *buffer, char *delim, char **startp, unsigned int *length)
+{
+ enum make_word_type wtype = w_bogus;
+ char *p = buffer, *beg;
+ char c;
+
+ /* Skip any leading whitespace. */
+ while (ISBLANK (*p))
+ ++p;
+
+ beg = p;
+ c = *(p++);
+ switch (c)
+ {
+ case '\0':
+ wtype = w_eol;
+ break;
+
+ case ';':
+ wtype = w_semicolon;
+ break;
+
+ case '=':
+ wtype = w_varassign;
+ break;
+
+ case ':':
+ wtype = w_colon;
+ switch (*p)
+ {
+ case ':':
+ ++p;
+ if (p[1] != '=')
+ wtype = w_dcolon;
+ else
+ {
+ wtype = w_varassign;
+ ++p;
+ }
+ break;
+
+ case '=':
+ ++p;
+ wtype = w_varassign;
+ break;
+ }
+ break;
+
+ case '+':
+ case '?':
+ case '!':
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ case '>':
+#endif
+ if (*p == '=')
+ {
+ ++p;
+ wtype = w_varassign;
+ break;
+ }
+ /* fall thru */
+
+ default:
+ if (delim && strchr (delim, c))
+ wtype = w_static;
+ break;
+ }
+
+ /* Did we find something? If so, return now. */
+ if (wtype != w_bogus)
+ goto done;
+
+ /* This is some non-operator word. A word consists of the longest
+ string of characters that doesn't contain whitespace, one of [:=#],
+ or [?+!]=, or one of the chars in the DELIM string. */
+
+ /* We start out assuming a static word; if we see a variable we'll
+ adjust our assumptions then. */
+ wtype = w_static;
+
+ /* We already found the first value of "c", above. */
+ while (1)
+ {
+ char closeparen;
+ int count;
+
+ switch (c)
+ {
+ case '\0':
+ case ' ':
+ case '\t':
+ case '=':
+ goto done_word;
+
+ case ':':
+#ifdef HAVE_DOS_PATHS
+ /* A word CAN include a colon in its drive spec. The drive
+ spec is allowed either at the beginning of a word, or as part
+ of the archive member name, like in "libfoo.a(d:/foo/bar.o)". */
+ if (!(p - beg >= 2
+ && (*p == '/' || *p == '\\') && isalpha ((unsigned char)p[-2])
+ && (p - beg == 2 || p[-3] == '(')))
+#endif
+ goto done_word;
+
+ case '$':
+ c = *(p++);
+ if (c == '$')
+ break;
+ if (c == '\0')
+ goto done_word;
+
+ /* This is a variable reference, so note that it's expandable.
+ Then read it to the matching close paren. */
+ wtype = w_variable;
+
+ if (c == '(')
+ closeparen = ')';
+ else if (c == '{')
+ closeparen = '}';
+ else
+ /* This is a single-letter variable reference. */
+ break;
+
+ for (count=0; *p != '\0'; ++p)
+ {
+ if (*p == c)
+ ++count;
+ else if (*p == closeparen && --count < 0)
+ {
+ ++p;
+ break;
+ }
+ }
+ break;
+
+ case '?':
+ case '+':
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ case '>':
+#endif
+ if (*p == '=')
+ goto done_word;
+ break;
+
+ case '\\':
+ switch (*p)
+ {
+ case ':':
+ case ';':
+ case '=':
+ case '\\':
+ ++p;
+ break;
+ }
+ break;
+
+ default:
+ if (delim && strchr (delim, c))
+ goto done_word;
+ break;
+ }
+
+ c = *(p++);
+ }
+ done_word:
+ --p;
+
+ done:
+ if (startp)
+ *startp = beg;
+ if (length)
+ *length = p - beg;
+ return wtype;
+}
+
+/* Construct the list of include directories
+ from the arguments and the default list. */
+
+void
+construct_include_path (const char **arg_dirs)
+{
+#ifdef VAXC /* just don't ask ... */
+ stat_t stbuf;
+#else
+ struct stat stbuf;
+#endif
+ const char **dirs;
+ const char **cpp;
+ unsigned int idx;
+
+ /* Compute the number of pointers we need in the table. */
+ idx = sizeof (default_include_directories) / sizeof (const char *);
+ if (arg_dirs)
+ for (cpp = arg_dirs; *cpp != 0; ++cpp)
+ ++idx;
+
+#ifdef __MSDOS__
+ /* Add one for $DJDIR. */
+ ++idx;
+#endif
+#ifdef KMK
+ /* Add one for the kBuild directory. */
+ ++idx;
+#endif
+
+ dirs = xmalloc (idx * sizeof (const char *));
+
+ idx = 0;
+ max_incl_len = 0;
+
+ /* First consider any dirs specified with -I switches.
+ Ignore any that don't exist. Remember the maximum string length. */
+
+ if (arg_dirs)
+ while (*arg_dirs != 0)
+ {
+ const char *dir = *(arg_dirs++);
+ char *expanded = 0;
+ int e;
+
+ if (dir[0] == '~')
+ {
+ expanded = tilde_expand (dir);
+ if (expanded != 0)
+ dir = expanded;
+ }
+
+ EINTRLOOP (e, stat (dir, &stbuf));
+ if (e == 0 && S_ISDIR (stbuf.st_mode))
+ {
+ unsigned int len = strlen (dir);
+ /* If dir name is written with trailing slashes, discard them. */
+ while (len > 1 && dir[len - 1] == '/')
+ --len;
+ if (len > max_incl_len)
+ max_incl_len = len;
+ dirs[idx++] = strcache_add_len (dir, len);
+ }
+
+ free (expanded);
+ }
+
+ /* Now add the standard default dirs at the end. */
+
+#ifdef __MSDOS__
+ {
+ /* The environment variable $DJDIR holds the root of the DJGPP directory
+ tree; add ${DJDIR}/include. */
+ struct variable *djdir = lookup_variable ("DJDIR", 5);
+
+ if (djdir)
+ {
+ unsigned int len = strlen (djdir->value) + 8;
+ char *defdir = alloca (len + 1);
+
+ strcat (strcpy (defdir, djdir->value), "/include");
+ dirs[idx++] = strcache_add (defdir);
+
+ if (len > max_incl_len)
+ max_incl_len = len;
+ }
+ }
+#endif
+#ifdef KMK
+ /* Add $(KBUILD_PATH). */
+ {
+ size_t len = strlen (get_kbuild_path ());
+ dirs[idx++] = strcache_add_len (get_kbuild_path (), len);
+ if (len > max_incl_len)
+ max_incl_len = len;
+ }
+#endif
+
+ for (cpp = default_include_directories; *cpp != 0; ++cpp)
+ {
+ int e;
+
+ EINTRLOOP (e, stat (*cpp, &stbuf));
+ if (e == 0 && S_ISDIR (stbuf.st_mode))
+ {
+ unsigned int len = strlen (*cpp);
+ /* If dir name is written with trailing slashes, discard them. */
+ while (len > 1 && (*cpp)[len - 1] == '/')
+ --len;
+ if (len > max_incl_len)
+ max_incl_len = len;
+ dirs[idx++] = strcache_add_len (*cpp, len);
+ }
+ }
+
+ dirs[idx] = 0;
+
+ /* Now add each dir to the .INCLUDE_DIRS variable. */
+
+ for (cpp = dirs; *cpp != 0; ++cpp)
+ do_variable_definition (NILF, ".INCLUDE_DIRS", *cpp,
+ o_default, f_append, 0);
+
+ include_directories = dirs;
+}
+
+/* Expand ~ or ~USER at the beginning of NAME.
+ Return a newly malloc'd string or 0. */
+
+char *
+tilde_expand (const char *name)
+{
+#ifndef VMS
+ if (name[1] == '/' || name[1] == '\0')
+ {
+ char *home_dir;
+ int is_variable;
+
+ {
+ /* Turn off --warn-undefined-variables while we expand HOME. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 0;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ home_dir = allocated_variable_expand ("$(HOME)");
+#else
+ home_dir = allocated_variable_expand_2 (STRING_SIZE_TUPLE("$(HOME)"), NULL);
+#endif
+
+ warn_undefined_variables_flag = save;
+ }
+
+ is_variable = home_dir[0] != '\0';
+ if (!is_variable)
+ {
+ free (home_dir);
+ home_dir = getenv ("HOME");
+ }
+# if !defined(_AMIGA) && !defined(WINDOWS32)
+ if (home_dir == 0 || home_dir[0] == '\0')
+ {
+ char *logname = getlogin ();
+ home_dir = 0;
+ if (logname != 0)
+ {
+ struct passwd *p = getpwnam (logname);
+ if (p != 0)
+ home_dir = p->pw_dir;
+ }
+ }
+# endif /* !AMIGA && !WINDOWS32 */
+ if (home_dir != 0)
+ {
+ char *new = xstrdup (concat (2, home_dir, name + 1));
+ if (is_variable)
+ free (home_dir);
+ return new;
+ }
+ }
+# if !defined(_AMIGA) && !defined(WINDOWS32)
+ else
+ {
+ struct passwd *pwent;
+ char *userend = strchr (name + 1, '/');
+ if (userend != 0)
+ *userend = '\0';
+ pwent = getpwnam (name + 1);
+ if (pwent != 0)
+ {
+ if (userend == 0)
+ return xstrdup (pwent->pw_dir);
+ else
+ return xstrdup (concat (3, pwent->pw_dir, "/", userend + 1));
+ }
+ else if (userend != 0)
+ *userend = '/';
+ }
+# endif /* !AMIGA && !WINDOWS32 */
+#endif /* !VMS */
+ return 0;
+}
+
+/* Parse a string into a sequence of filenames represented as a chain of
+ struct nameseq's and return that chain. Optionally expand the strings via
+ glob().
+
+ The string is passed as STRINGP, the address of a string pointer.
+ The string pointer is updated to point at the first character
+ not parsed, which either is a null char or equals STOPCHAR.
+
+ SIZE is how big to construct chain elements.
+ This is useful if we want them actually to be other structures
+ that have room for additional info.
+
+ PREFIX, if non-null, is added to the beginning of each filename.
+
+ FLAGS allows one or more of the following bitflags to be set:
+ PARSEFS_NOSTRIP - Do no strip './'s off the beginning
+ PARSEFS_NOAR - Do not check filenames for archive references
+ PARSEFS_NOGLOB - Do not expand globbing characters
+ PARSEFS_EXISTS - Only return globbed files that actually exist
+ (cannot also set NOGLOB)
+ PARSEFS_NOCACHE - Do not add filenames to the strcache (caller frees)
+ */
+
+void *
+parse_file_seq (char **stringp, unsigned int size, int stopmap,
+ const char *prefix, int flags
+ IF_WITH_ALLOC_CACHES_PARAM(struct alloccache *alloc_cache) )
+{
+ /* tmp points to tmpbuf after the prefix, if any.
+ tp is the end of the buffer. */
+ static char *tmpbuf = NULL;
+
+ int cachep = NONE_SET (flags, PARSEFS_NOCACHE);
+
+ struct nameseq *new = 0;
+ struct nameseq **newp = &new;
+#ifndef CONFIG_WITH_ALLOC_CACHES
+#define NEWELT(_n) do { \
+ const char *__n = (_n); \
+ *newp = xcalloc (size); \
+ (*newp)->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \
+ newp = &(*newp)->next; \
+ } while(0)
+#else
+# define NEWELT(_n) do { \
+ const char *__n = (_n); \
+ *newp = alloccache_calloc (alloc_cache); \
+ (*newp)->name = (cachep ? strcache_add (__n) : xstrdup (__n)); \
+ newp = &(*newp)->next; \
+ } while(0)
+#endif
+
+ char *p;
+ glob_t gl;
+ char *tp;
+
+ /* Always stop on NUL. */
+ stopmap |= MAP_NUL;
+
+ if (size < sizeof (struct nameseq))
+ size = sizeof (struct nameseq);
+
+ if (NONE_SET (flags, PARSEFS_NOGLOB))
+ dir_setup_glob (&gl);
+
+ /* Get enough temporary space to construct the largest possible target. */
+ {
+ static int tmpbuf_len = 0;
+ int l = strlen (*stringp) + 1;
+ if (l > tmpbuf_len)
+ {
+ tmpbuf = xrealloc (tmpbuf, l);
+ tmpbuf_len = l;
+ }
+ }
+ tp = tmpbuf;
+
+ /* Parse STRING. P will always point to the end of the parsed content. */
+ p = *stringp;
+ while (1)
+ {
+ const char *name;
+ const char **nlist = 0;
+ char *tildep = 0;
+ int globme = 1;
+#ifndef NO_ARCHIVES
+ char *arname = 0;
+ char *memname = 0;
+#endif
+ char *s;
+ int nlen;
+ int i;
+
+ /* Skip whitespace; at the end of the string or STOPCHAR we're done. */
+ NEXT_TOKEN (p);
+ if (STOP_SET (*p, stopmap))
+ break;
+
+ /* There are names left, so find the end of the next name.
+ Throughout this iteration S points to the start. */
+ s = p;
+ p = find_char_unquote (p, stopmap|MAP_VMSCOMMA|MAP_BLANK IF_WITH_VALUE_LENGTH_PARAM(0));
+#ifdef VMS
+ /* convert comma separated list to space separated */
+ if (p && *p == ',')
+ *p =' ';
+#endif
+#ifdef _AMIGA
+ if (p && STOP_SET (*p, stopmap & MAP_COLON)
+ && !(ISSPACE (p[1]) || !p[1] || ISSPACE (p[-1])))
+ p = find_char_unquote (p+1, stopmap|MAP_VMSCOMMA|MAP_BLANK);
+#endif
+#ifdef HAVE_DOS_PATHS
+ /* For DOS paths, skip a "C:\..." or a "C:/..." until we find the
+ first colon which isn't followed by a slash or a backslash.
+ Note that tokens separated by spaces should be treated as separate
+ tokens since make doesn't allow path names with spaces */
+ if (stopmap | MAP_COLON)
+ while (p != 0 && !ISSPACE (*p) &&
+ (p[1] == '\\' || p[1] == '/') && isalpha ((unsigned char)p[-1]))
+ p = find_char_unquote (p + 1, stopmap|MAP_VMSCOMMA|MAP_BLANK IF_WITH_VALUE_LENGTH_PARAM(0));
+#endif
+ if (p == 0)
+ p = s + strlen (s);
+
+ /* Strip leading "this directory" references. */
+ if (NONE_SET (flags, PARSEFS_NOSTRIP))
+#ifdef VMS
+ /* Skip leading '[]'s. should only be one set or bug somwhere else */
+ if (p - s > 2 && s[0] == '[' && s[1] == ']')
+ s += 2;
+ /* Skip leading '<>'s. should only be one set or bug somwhere else */
+ if (p - s > 2 && s[0] == '<' && s[1] == '>')
+ s += 2;
+#endif
+ /* Skip leading './'s. */
+ while (p - s > 2 && s[0] == '.' && s[1] == '/')
+ {
+ /* Skip "./" and all following slashes. */
+ s += 2;
+ while (*s == '/')
+ ++s;
+ }
+
+ /* Extract the filename just found, and skip it.
+ Set NAME to the string, and NLEN to its length. */
+
+ if (s == p)
+ {
+ /* The name was stripped to empty ("./"). */
+#if defined(_AMIGA)
+ /* PDS-- This cannot be right!! */
+ tp[0] = '\0';
+ nlen = 0;
+#else
+ tp[0] = '.';
+ tp[1] = '/';
+ tp[2] = '\0';
+ nlen = 2;
+#endif
+ }
+ else
+ {
+#ifdef VMS
+/* VMS filenames can have a ':' in them but they have to be '\'ed but we need
+ * to remove this '\' before we can use the filename.
+ * xstrdup called because S may be read-only string constant.
+ */
+ char *n = tp;
+ while (s < p)
+ {
+ if (s[0] == '\\' && s[1] == ':')
+ ++s;
+ *(n++) = *(s++);
+ }
+ n[0] = '\0';
+ nlen = strlen (tp);
+#else
+ nlen = p - s;
+ memcpy (tp, s, nlen);
+ tp[nlen] = '\0';
+#endif
+ }
+
+ /* At this point, TP points to the element and NLEN is its length. */
+
+#ifndef NO_ARCHIVES
+ /* If this is the start of an archive group that isn't complete, set up
+ to add the archive prefix for future files. A file list like:
+ "libf.a(x.o y.o z.o)" needs to be expanded as:
+ "libf.a(x.o) libf.a(y.o) libf.a(z.o)"
+
+ TP == TMP means we're not already in an archive group. Ignore
+ something starting with '(', as that cannot actually be an
+ archive-member reference (and treating it as such results in an empty
+ file name, which causes much lossage). Also if it ends in ")" then
+ it's a complete reference so we don't need to treat it specially.
+
+ Finally, note that archive groups must end with ')' as the last
+ character, so ensure there's some word ending like that before
+ considering this an archive group. */
+ if (NONE_SET (flags, PARSEFS_NOAR)
+ && tp == tmpbuf && tp[0] != '(' && tp[nlen-1] != ')')
+ {
+ char *n = strchr (tp, '(');
+ if (n)
+ {
+ /* This looks like the first element in an open archive group.
+ A valid group MUST have ')' as the last character. */
+ const char *e = p;
+ do
+ {
+ const char *o = e;
+ NEXT_TOKEN (e);
+ /* Find the end of this word. We don't want to unquote and
+ we don't care about quoting since we're looking for the
+ last char in the word. */
+ while (! STOP_SET (*e, stopmap|MAP_BLANK|MAP_VMSCOMMA))
+ ++e;
+ /* If we didn't move, we're done now. */
+ if (e == o)
+ break;
+ if (e[-1] == ')')
+ {
+ /* Found the end, so this is the first element in an
+ open archive group. It looks like "lib(mem".
+ Reset TP past the open paren. */
+ nlen -= (n + 1) - tp;
+ tp = n + 1;
+
+ /* We can stop looking now. */
+ break;
+ }
+ }
+ while (*e != '\0');
+
+ /* If we have just "lib(", part of something like "lib( a b)",
+ go to the next item. */
+ if (! nlen)
+ continue;
+ }
+ }
+
+ /* If we are inside an archive group, make sure it has an end. */
+ if (tp > tmpbuf)
+ {
+ if (tp[nlen-1] == ')')
+ {
+ /* This is the natural end; reset TP. */
+ tp = tmpbuf;
+
+ /* This is just ")", something like "lib(a b )": skip it. */
+ if (nlen == 1)
+ continue;
+ }
+ else
+ {
+ /* Not the end, so add a "fake" end. */
+ tp[nlen++] = ')';
+ tp[nlen] = '\0';
+ }
+ }
+#endif
+
+ /* If we're not globbing we're done: add it to the end of the chain.
+ Go to the next item in the string. */
+ if (ANY_SET (flags, PARSEFS_NOGLOB))
+ {
+ NEWELT (concat (2, prefix, tmpbuf));
+ continue;
+ }
+
+ /* If we get here we know we're doing glob expansion.
+ TP is a string in tmpbuf. NLEN is no longer used.
+ We may need to do more work: after this NAME will be set. */
+ name = tmpbuf;
+
+ /* Expand tilde if applicable. */
+ if (tmpbuf[0] == '~')
+ {
+ tildep = tilde_expand (tmpbuf);
+ if (tildep != 0)
+ name = tildep;
+ }
+
+#ifndef NO_ARCHIVES
+ /* If NAME is an archive member reference replace it with the archive
+ file name, and save the member name in MEMNAME. We will glob on the
+ archive name and then reattach MEMNAME later. */
+ if (NONE_SET (flags, PARSEFS_NOAR) && ar_name (name))
+ {
+ ar_parse_name (name, &arname, &memname);
+ name = arname;
+ }
+#endif /* !NO_ARCHIVES */
+
+ /* glob() is expensive: don't call it unless we need to. */
+ if (NONE_SET (flags, PARSEFS_EXISTS) && strpbrk (name, "?*[") == NULL)
+ {
+ globme = 0;
+ i = 1;
+ nlist = &name;
+ }
+ else
+ switch (glob (name, GLOB_NOSORT|GLOB_ALTDIRFUNC, NULL, &gl))
+ {
+ case GLOB_NOSPACE:
+ OUT_OF_MEM();
+
+ case 0:
+ /* Success. */
+ i = gl.gl_pathc;
+ nlist = (const char **)gl.gl_pathv;
+ break;
+
+ case GLOB_NOMATCH:
+ /* If we want only existing items, skip this one. */
+ if (ANY_SET (flags, PARSEFS_EXISTS))
+ {
+ i = 0;
+ break;
+ }
+ /* FALLTHROUGH */
+
+ default:
+ /* By default keep this name. */
+ i = 1;
+ nlist = &name;
+ break;
+ }
+
+ /* For each matched element, add it to the list. */
+ while (i-- > 0)
+#ifndef NO_ARCHIVES
+ if (memname != 0)
+ {
+ /* Try to glob on MEMNAME within the archive. */
+ struct nameseq *found = ar_glob (nlist[i], memname, size);
+ if (! found)
+ /* No matches. Use MEMNAME as-is. */
+ NEWELT (concat (5, prefix, nlist[i], "(", memname, ")"));
+ else
+ {
+ /* We got a chain of items. Attach them. */
+ if (*newp)
+ (*newp)->next = found;
+ else
+ *newp = found;
+
+ /* Find and set the new end. Massage names if necessary. */
+ while (1)
+ {
+ if (! cachep)
+ found->name = xstrdup (concat (2, prefix, name));
+ else if (prefix)
+ found->name = strcache_add (concat (2, prefix, name));
+
+ if (found->next == 0)
+ break;
+
+ found = found->next;
+ }
+ newp = &found->next;
+ }
+ }
+ else
+#endif /* !NO_ARCHIVES */
+ NEWELT (concat (2, prefix, nlist[i]));
+
+ if (globme)
+ globfree (&gl);
+
+#ifndef NO_ARCHIVES
+ free (arname);
+#endif
+
+ free (tildep);
+ }
+
+ *stringp = p;
+ return new;
+}
+
diff --git a/src/kmk/remake.c b/src/kmk/remake.c
new file mode 100644
index 0000000..2551816
--- /dev/null
+++ b/src/kmk/remake.c
@@ -0,0 +1,2124 @@
+/* Basic dependency engine for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "job.h"
+#include "commands.h"
+#include "dep.h"
+#include "variable.h"
+#include "debug.h"
+
+#include <assert.h>
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#include <sys/file.h>
+#endif
+
+#ifdef VMS
+#include <starlet.h>
+#endif
+#ifdef WINDOWS32
+#include <io.h>
+#endif
+
+
+/* The test for circular dependencies is based on the 'updating' bit in
+ 'struct file'. However, double colon targets have separate 'struct
+ file's; make sure we always use the base of the double colon chain. */
+
+#define start_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\
+ ->updating = 1)
+#define finish_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\
+ ->updating = 0)
+#define is_updating(_f) (((_f)->double_colon ? (_f)->double_colon : (_f))\
+ ->updating)
+
+
+/* Incremented when a command is started (under -n, when one would be). */
+unsigned int commands_started = 0;
+
+/* Set to the goal dependency. Mostly needed for remaking makefiles. */
+static struct goaldep *goal_list;
+static struct dep *goal_dep;
+
+/* Current value for pruning the scan of the goal chain.
+ All files start with considered == 0. */
+static unsigned int considered = 0;
+
+static enum update_status update_file (struct file *file, unsigned int depth);
+static enum update_status update_file_1 (struct file *file, unsigned int depth);
+static enum update_status check_dep (struct file *file, unsigned int depth,
+ FILE_TIMESTAMP this_mtime, int *must_make);
+static enum update_status touch_file (struct file *file);
+static void remake_file (struct file *file);
+static FILE_TIMESTAMP name_mtime (const char *name);
+static const char *library_search (const char *lib, FILE_TIMESTAMP *mtime_ptr);
+
+#ifdef CONFIG_WITH_DOT_MUST_MAKE
+static int call_must_make_target_var (struct file *file, unsigned int depth);
+#endif
+#ifdef CONFIG_WITH_DOT_IS_CHANGED
+static int call_is_changed_target_var (struct file *file);
+#endif
+
+
+/* Remake all the goals in the 'struct dep' chain GOALS. Return -1 if nothing
+ was done, 0 if all goals were updated successfully, or 1 if a goal failed.
+
+ If rebuilding_makefiles is nonzero, these goals are makefiles, so -t, -q,
+ and -n should be disabled for them unless they were also command-line
+ targets, and we should only make one goal at a time and return as soon as
+ one goal whose 'changed' member is nonzero is successfully made. */
+
+enum update_status
+update_goal_chain (struct goaldep *goaldeps)
+{
+ int t = touch_flag, q = question_flag, n = just_print_flag;
+ enum update_status status = us_none;
+
+ /* Duplicate the chain so we can remove things from it. */
+
+ struct dep *goals = copy_dep_chain ((struct dep *)goaldeps);
+
+ goal_list = rebuilding_makefiles ? goaldeps : NULL;
+
+#define MTIME(file) (rebuilding_makefiles ? file_mtime_no_search (file) \
+ : file_mtime (file))
+
+ /* Start a fresh batch of consideration. */
+ ++considered;
+
+ /* Update all the goals until they are all finished. */
+
+ while (goals != 0)
+ {
+ register struct dep *g, *lastgoal;
+
+ /* Start jobs that are waiting for the load to go down. */
+
+ start_waiting_jobs ();
+
+ /* Wait for a child to die. */
+
+ reap_children (1, 0);
+
+ lastgoal = 0;
+ g = goals;
+ while (g != 0)
+ {
+ /* Iterate over all double-colon entries for this file. */
+ struct file *file;
+ int stop = 0, any_not_updated = 0;
+
+ goal_dep = g;
+
+ for (file = g->file->double_colon ? g->file->double_colon : g->file;
+ file != NULL;
+ file = file->prev)
+ {
+ unsigned int ocommands_started;
+ enum update_status fail;
+
+ file->dontcare = ANY_SET (g->flags, RM_DONTCARE);
+
+ check_renamed (file);
+ if (rebuilding_makefiles)
+ {
+ if (file->cmd_target)
+ {
+ touch_flag = t;
+ question_flag = q;
+ just_print_flag = n;
+ }
+ else
+ touch_flag = question_flag = just_print_flag = 0;
+ }
+
+ /* Save the old value of 'commands_started' so we can compare
+ later. It will be incremented when any commands are
+ actually run. */
+ ocommands_started = commands_started;
+
+ fail = update_file (file, rebuilding_makefiles ? 1 : 0);
+ check_renamed (file);
+
+ /* Set the goal's 'changed' flag if any commands were started
+ by calling update_file above. We check this flag below to
+ decide when to give an "up to date" diagnostic. */
+ if (commands_started > ocommands_started)
+ g->changed = 1;
+
+ stop = 0;
+ if ((fail || file->updated) && status < us_question)
+ {
+ /* We updated this goal. Update STATUS and decide whether
+ to stop. */
+ if (file->update_status)
+ {
+ /* Updating failed, or -q triggered. The STATUS value
+ tells our caller which. */
+ status = file->update_status;
+ /* If -q just triggered, stop immediately. It doesn't
+ matter how much more we run, since we already know
+ the answer to return. */
+ stop = (question_flag && !keep_going_flag
+ && !rebuilding_makefiles);
+ }
+ else
+ {
+ FILE_TIMESTAMP mtime = MTIME (file);
+ check_renamed (file);
+
+ if (file->updated && g->changed &&
+ mtime != file->mtime_before_update)
+ {
+ /* Updating was done. If this is a makefile and
+ just_print_flag or question_flag is set (meaning
+ -n or -q was given and this file was specified
+ as a command-line target), don't change STATUS.
+ If STATUS is changed, we will get re-exec'd, and
+ enter an infinite loop. */
+ if (!rebuilding_makefiles
+ || (!just_print_flag && !question_flag))
+ status = us_success;
+ if (rebuilding_makefiles && file->dontcare)
+ /* This is a default makefile; stop remaking. */
+ stop = 1;
+ }
+ }
+ }
+
+ /* Keep track if any double-colon entry is not finished.
+ When they are all finished, the goal is finished. */
+ any_not_updated |= !file->updated;
+
+ file->dontcare = 0;
+
+ if (stop)
+ break;
+ }
+
+ /* Reset FILE since it is null at the end of the loop. */
+ file = g->file;
+
+ if (stop || !any_not_updated)
+ {
+ /* If we have found nothing whatever to do for the goal,
+ print a message saying nothing needs doing. */
+
+ if (!rebuilding_makefiles
+ /* If the update_status is success, we updated successfully
+ or not at all. G->changed will have been set above if
+ any commands were actually started for this goal. */
+ && file->update_status == us_success && !g->changed
+ /* Never give a message under -s or -q. */
+ && !silent_flag && !question_flag)
+ OS (message, 1, ((file->phony || file->cmds == 0)
+ ? _("Nothing to be done for '%s'.")
+ : _("'%s' is up to date.")),
+ file->name);
+
+ /* This goal is finished. Remove it from the chain. */
+ if (lastgoal == 0)
+ goals = g->next;
+ else
+ lastgoal->next = g->next;
+
+ /* Free the storage. */
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ free (g);
+#else
+ free_dep (g);
+#endif
+
+ g = lastgoal == 0 ? goals : lastgoal->next;
+
+ if (stop)
+ break;
+ }
+ else
+ {
+ lastgoal = g;
+ g = g->next;
+ }
+ }
+
+ /* If we reached the end of the dependency graph update CONSIDERED
+ for the next pass. */
+ if (g == 0)
+ ++considered;
+ }
+
+ if (rebuilding_makefiles)
+ {
+ touch_flag = t;
+ question_flag = q;
+ just_print_flag = n;
+ }
+
+ return status;
+}
+
+/* If we're rebuilding an included makefile that failed, and we care
+ about errors, show an error message the first time. */
+
+void
+show_goal_error (void)
+{
+ struct goaldep *goal;
+
+ if ((goal_dep->flags & (RM_INCLUDED|RM_DONTCARE)) != RM_INCLUDED)
+ return;
+
+ for (goal = goal_list; goal; goal = goal->next)
+ if (goal_dep->file == goal->file)
+ {
+ if (goal->error)
+ {
+ OSS (error, &goal->floc, "%s: %s",
+ goal->file->name, strerror ((int)goal->error));
+ goal->error = 0;
+ }
+ return;
+ }
+}
+
+/* If FILE is not up to date, execute the commands for it.
+ Return 0 if successful, non-0 if unsuccessful;
+ but with some flag settings, just call 'exit' if unsuccessful.
+
+ DEPTH is the depth in recursions of this function.
+ We increment it during the consideration of our dependencies,
+ then decrement it again after finding out whether this file
+ is out of date.
+
+ If there are multiple double-colon entries for FILE,
+ each is considered in turn. */
+
+static enum update_status
+update_file (struct file *file, unsigned int depth)
+{
+ enum update_status status = us_success;
+ struct file *f;
+
+ f = file->double_colon ? file->double_colon : file;
+
+ /* Prune the dependency graph: if we've already been here on _this_
+ pass through the dependency graph, we don't have to go any further.
+ We won't reap_children until we start the next pass, so no state
+ change is possible below here until then. */
+ if (f->considered == considered)
+ {
+ /* Check for the case where a target has been tried and failed but
+ the diagnostics haven't been issued. If we need the diagnostics
+ then we will have to continue. */
+ if (!(f->updated && f->update_status > us_none
+ && !f->dontcare && f->no_diag))
+ {
+ DBF (DB_VERBOSE, _("Pruning file '%s'.\n"));
+ return f->command_state == cs_finished ? f->update_status : us_success;
+ }
+ }
+
+ /* This loop runs until we start commands for a double colon rule, or until
+ the chain is exhausted. */
+ for (; f != 0; f = f->prev)
+ {
+ enum update_status new;
+
+ f->considered = considered;
+
+ new = update_file_1 (f, depth);
+ check_renamed (f);
+
+ /* Clean up any alloca() used during the update. */
+ alloca (0);
+
+ /* If we got an error, don't bother with double_colon etc. */
+ if (new && !keep_going_flag)
+ return new;
+
+ if (f->command_state == cs_running
+ || f->command_state == cs_deps_running)
+ /* Don't run other :: rules for this target until
+ this rule is finished. */
+ return us_success;
+
+ if (new > status)
+ status = new;
+ }
+
+ /* Process the remaining rules in the double colon chain so they're marked
+ considered. Start their prerequisites, too. */
+ if (file->double_colon)
+ for (; f != 0 ; f = f->prev)
+ {
+ struct dep *d;
+
+ f->considered = considered;
+
+ for (d = f->deps; d != 0; d = d->next)
+ {
+ enum update_status new = update_file (d->file, depth + 1);
+ if (new > status)
+ status = new;
+ }
+ }
+
+ return status;
+}
+
+/* Show a message stating the target failed to build. */
+
+static void
+complain (struct file *file)
+{
+ /* If this file has no_diag set then it means we tried to update it
+ before in the dontcare mode and failed. The target that actually
+ failed is not necessarily this file but could be one of its direct
+ or indirect dependencies. So traverse this file's dependencies and
+ find the one that actually caused the failure. */
+
+ struct dep *d;
+
+ for (d = file->deps; d != 0; d = d->next)
+ {
+ if (d->file->updated && d->file->update_status > us_none && file->no_diag)
+ {
+ complain (d->file);
+ break;
+ }
+ }
+
+ if (d == 0)
+ {
+ show_goal_error ();
+
+ /* Didn't find any dependencies to complain about. */
+
+#ifdef KMK
+ /* jokes */
+ if (!keep_going_flag && file->parent == 0)
+ {
+ const char *msg_joke = 0;
+ extern struct dep *goals;
+
+ /* classics */
+ if (!strcmp (file->name, "fire")
+ || !strcmp (file->name, "Fire"))
+ msg_joke = "No matches.\n";
+ else if (!strcmp (file->name, "love")
+ || !strcmp (file->name, "Love")
+ || !strcmp (file->name, "peace")
+ || !strcmp (file->name, "Peace"))
+ msg_joke = "Not war.\n";
+ else if (!strcmp (file->name, "war"))
+ msg_joke = "Don't know how to make war.\n";
+
+ /* http://xkcd.com/149/ - GNU Make bug #23273. */
+ else if (( !strcmp (file->name, "me")
+ && goals != 0
+ && !strcmp (dep_name(goals), "me")
+ && goals->next != 0
+ && !strcmp (dep_name(goals->next), "a")
+ && goals->next->next != 0)
+ || !strncmp (file->name, "me a ", 5))
+ msg_joke =
+# ifdef HAVE_UNISTD_H
+ getuid () == 0 ? "Okay.\n" :
+# endif
+ "What? Make it yourself!\n";
+ if (msg_joke)
+ {
+ fputs (msg_joke, stderr);
+ die (2);
+ }
+ }
+#endif /* KMK */
+
+ if (file->parent)
+ {
+ size_t l = strlen (file->name) + strlen (file->parent->name) + 4;
+ const char *m = _("%sNo rule to make target '%s', needed by '%s'%s");
+
+ if (!keep_going_flag)
+ fatal (NILF, l, m, "", file->name, file->parent->name, "");
+
+ error (NILF, l, m, "*** ", file->name, file->parent->name, ".");
+ }
+ else
+ {
+ size_t l = strlen (file->name) + 4;
+ const char *m = _("%sNo rule to make target '%s'%s");
+
+ if (!keep_going_flag)
+ fatal (NILF, l, m, "", file->name, "");
+
+ error (NILF, l, m, "*** ", file->name, ".");
+ }
+
+ file->no_diag = 0;
+ }
+}
+
+/* Consider a single 'struct file' and update it as appropriate.
+ Return 0 on success, or non-0 on failure. */
+
+static enum update_status
+update_file_1 (struct file *file, unsigned int depth)
+{
+ enum update_status dep_status = us_success;
+ FILE_TIMESTAMP this_mtime;
+ int noexist, must_make, deps_changed;
+ struct file *ofile;
+ struct dep *d, *ad;
+ struct dep amake;
+ int running = 0;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ struct file *org_file;
+ struct file *req_file;
+ struct file *f2, *f3;
+
+ /* Secondary target expansion leaves renamed files around, skip any rename
+ files to simplify multi target handling. */
+
+ check_renamed(file);
+
+ /* Remember the request file and find the primary multi target file. Always
+ work on the primary file in a multi target recipe. */
+
+ req_file = file;
+ org_file = file;
+ if (file->multi_head != NULL)
+ {
+ if (file->multi_head == file)
+ DBS (DB_VERBOSE, (_("Considering target file '%s' (multi head).\n"), file->name));
+ else
+ {
+ org_file = file = file->multi_head;
+ DBS (DB_VERBOSE, (_("Considering target file '%s' -> multi head '%s'.\n"),
+ req_file->name, file->name));
+ assert (file->multi_head == file);
+ }
+ }
+ else
+#endif /* CONFIG_WITH_EXPLICIT_MULTITARGET */
+ DBF (DB_VERBOSE, _("Considering target file '%s'.\n"));
+
+ if (file->updated)
+ {
+ if (file->update_status > us_none)
+ {
+ DBF (DB_VERBOSE,
+ _("Recently tried and failed to update file '%s'.\n"));
+
+ /* If the file we tried to make is marked no_diag then no message
+ was printed about it when it failed during the makefile rebuild.
+ If we're trying to build it again in the normal rebuild, print a
+ message now. */
+ if (file->no_diag && !file->dontcare)
+ complain (file);
+
+ return file->update_status;
+ }
+
+ DBF (DB_VERBOSE, _("File '%s' was considered already.\n"));
+ return 0;
+ }
+
+ switch (file->command_state)
+ {
+ case cs_not_started:
+ case cs_deps_running:
+ break;
+ case cs_running:
+ DBF (DB_VERBOSE, _("Still updating file '%s'.\n"));
+ return 0;
+ case cs_finished:
+ DBF (DB_VERBOSE, _("Finished updating file '%s'.\n"));
+ return file->update_status;
+ default:
+ abort ();
+ }
+
+ /* Determine whether the diagnostics will be issued should this update
+ fail. */
+ file->no_diag = file->dontcare;
+
+ ++depth;
+
+ /* Notice recursive update of the same file. */
+ start_updating (file);
+
+ /* We might change file if we find a different one via vpath;
+ remember this one to turn off updating. */
+ ofile = file;
+
+ /* Looking at the file's modtime beforehand allows the possibility
+ that its name may be changed by a VPATH search, and thus it may
+ not need an implicit rule. If this were not done, the file
+ might get implicit commands that apply to its initial name, only
+ to have that name replaced with another found by VPATH search.
+
+ For multi target files check the other files and use the time
+ of the oldest / non-existing file. */
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ this_mtime = file_mtime (file);
+ check_renamed (file);
+ f3 = file;
+ for (f2 = file->multi_next;
+ f2 != NULL && this_mtime != NONEXISTENT_MTIME;
+ f2 = f2->multi_next)
+ if (!f2->multi_maybe)
+ {
+ FILE_TIMESTAMP second_mtime = file_mtime (f2);
+ if (second_mtime < this_mtime)
+ {
+ this_mtime = second_mtime;
+ f3 = f2;
+ }
+ }
+ /** @todo this isn't sufficient, need to introduce a truly optional type and
+ * make |+ ignore mtime. let's hope that doesn't break too much... */
+ /* If the requested file doesn't exist, always do a remake in the
+ hope that it is recreated even if it's "maybe" target. */
+ else if (f2 == req_file && file_mtime (f2) == NONEXISTENT_MTIME)
+ {
+ this_mtime = NONEXISTENT_MTIME;
+ f3 = f2;
+ break;
+ }
+ check_renamed (f3);
+ noexist = this_mtime == NONEXISTENT_MTIME;
+ if (noexist)
+ DBS (DB_BASIC, (_("File '%s' does not exist.\n"), f3->name));
+#else /* !CONFIG_WITH_EXPLICIT_MULTITARGET */
+ this_mtime = file_mtime (file);
+ check_renamed (file);
+ noexist = this_mtime == NONEXISTENT_MTIME;
+ if (noexist)
+ DBF (DB_BASIC, _("File '%s' does not exist.\n"));
+#endif /* !CONFIG_WITH_EXPLICIT_MULTITARGET */
+ else if (ORDINARY_MTIME_MIN <= this_mtime && this_mtime <= ORDINARY_MTIME_MAX
+ && file->low_resolution_time)
+ {
+ /* Avoid spurious rebuilds due to low resolution time stamps. */
+ int ns = FILE_TIMESTAMP_NS (this_mtime);
+ if (ns != 0)
+ OS (error, NILF,
+ _("*** Warning: .LOW_RESOLUTION_TIME file '%s' has a high resolution time stamp"),
+ file->name);
+ this_mtime += FILE_TIMESTAMPS_PER_S - 1 - ns;
+ }
+
+ must_make = noexist;
+
+ /* If file was specified as a target with no commands,
+ come up with some default commands. */
+
+ if (!file->phony && file->cmds == 0 && !file->tried_implicit)
+ {
+ if (try_implicit_rule (file, depth))
+ DBF (DB_IMPLICIT, _("Found an implicit rule for '%s'.\n"));
+ else
+ DBF (DB_IMPLICIT, _("No implicit rule found for '%s'.\n"));
+ file->tried_implicit = 1;
+ }
+ if (file->cmds == 0 && !file->is_target
+ && default_file != 0 && default_file->cmds != 0)
+ {
+ DBF (DB_IMPLICIT, _("Using default recipe for '%s'.\n"));
+ file->cmds = default_file->cmds;
+ }
+
+ /* Update all non-intermediate files we depend on, if necessary, and see
+ whether any of them is more recent than this file. We need to walk our
+ deps, AND the deps of any also_make targets to ensure everything happens
+ in the correct order.
+
+ bird: For explicit multitarget rules we must iterate all the output
+ files to get the correct picture. The special .MUST_MAKE
+ target variable call is also done from this context. */
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ assert (file == org_file);
+ for (f2 = file; f2; file = f2 = f2->multi_next)
+ {
+#endif
+ amake.file = file;
+ amake.next = file->also_make;
+ ad = &amake;
+ while (ad)
+ {
+ struct dep *lastd = 0;
+
+ /* Find the deps we're scanning */
+ d = ad->file->deps;
+ ad = ad->next;
+
+ while (d)
+ {
+ enum update_status new;
+ FILE_TIMESTAMP mtime;
+ int maybe_make;
+ int dontcare = 0;
+
+ check_renamed (d->file);
+
+ mtime = file_mtime (d->file);
+ check_renamed (d->file);
+
+ if (is_updating (d->file))
+ {
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* silently ignore the order-only dep hack. */
+ if (file->multi_maybe && d->file == org_file)
+ {
+ lastd = d;
+ d = d->next;
+ continue;
+ }
+#endif
+
+ OSS (error, NILF, _("Circular %s <- %s dependency dropped."),
+ file->name, d->file->name);
+ /* We cannot free D here because our the caller will still have
+ a reference to it when we were called recursively via
+ check_dep below. */
+ if (lastd == 0)
+ file->deps = d->next;
+ else
+ lastd->next = d->next;
+ d = d->next;
+ continue;
+ }
+
+ d->file->parent = file;
+ maybe_make = must_make;
+
+ /* Inherit dontcare flag from our parent. */
+ if (rebuilding_makefiles)
+ {
+ dontcare = d->file->dontcare;
+ d->file->dontcare = file->dontcare;
+ }
+
+ new = check_dep (d->file, depth, this_mtime, &maybe_make);
+ if (new > dep_status)
+ dep_status = new;
+
+ /* Restore original dontcare flag. */
+ if (rebuilding_makefiles)
+ d->file->dontcare = dontcare;
+
+ if (! d->ignore_mtime)
+ must_make = maybe_make;
+
+ check_renamed (d->file);
+
+ {
+ register struct file *f = d->file;
+ if (f->double_colon)
+ f = f->double_colon;
+ do
+ {
+ running |= (f->command_state == cs_running
+ || f->command_state == cs_deps_running);
+ f = f->prev;
+ }
+ while (f != 0);
+ }
+
+ if (dep_status && !keep_going_flag)
+ break;
+
+ if (!running)
+ /* The prereq is considered changed if the timestamp has changed
+ while it was built, OR it doesn't exist. */
+ d->changed = ((file_mtime (d->file) != mtime)
+ || (mtime == NONEXISTENT_MTIME));
+
+ lastd = d;
+ d = d->next;
+ }
+ }
+
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (dep_status != 0 && !keep_going_flag)
+ break;
+ }
+ file = org_file;
+#endif
+
+#ifdef CONFIG_WITH_DOT_MUST_MAKE
+ /* Check with the .MUST_MAKE target variable if it's
+ not already decided to make the file. */
+
+# ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (!must_make)
+ for (f2 = org_file; f2 && !must_make; f2 = f2->multi_next)
+ must_make = call_must_make_target_var (f2, depth);
+# else
+ if (!must_make)
+ must_make = call_must_make_target_var (file, depth);
+# endif
+#endif /* CONFIG_WITH_DOT_MUST_MAKE */
+
+ /* Now we know whether this target needs updating.
+ If it does, update all the intermediate files we depend on. */
+
+ if (must_make || always_make_flag)
+ {
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ for (file = f2 = org_file; f2; file = f2 = f2->multi_next)
+#endif
+ for (d = file->deps; d != 0; d = d->next)
+ if (d->file->intermediate)
+ {
+ enum update_status new;
+ int dontcare = 0;
+
+ FILE_TIMESTAMP mtime = file_mtime (d->file);
+ check_renamed (d->file);
+ d->file->parent = file;
+
+ /* Inherit dontcare flag from our parent. */
+ if (rebuilding_makefiles)
+ {
+ dontcare = d->file->dontcare;
+ d->file->dontcare = file->dontcare;
+ }
+
+ /* We may have already considered this file, when we didn't know
+ we'd need to update it. Force update_file() to consider it and
+ not prune it. */
+ d->file->considered = 0;
+
+ new = update_file (d->file, depth);
+ if (new > dep_status)
+ dep_status = new;
+
+ /* Restore original dontcare flag. */
+ if (rebuilding_makefiles)
+ d->file->dontcare = dontcare;
+
+ check_renamed (d->file);
+
+ {
+ register struct file *f = d->file;
+ if (f->double_colon)
+ f = f->double_colon;
+ do
+ {
+ running |= (f->command_state == cs_running
+ || f->command_state == cs_deps_running);
+ f = f->prev;
+ }
+ while (f != 0);
+ }
+
+ if (dep_status && !keep_going_flag)
+ break;
+
+ if (!running)
+ d->changed = ((file->phony && file->cmds != 0)
+ || file_mtime (d->file) != mtime);
+ }
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ file = org_file;
+#endif
+ }
+
+ finish_updating (file);
+ finish_updating (ofile);
+
+ DBF (DB_VERBOSE, _("Finished prerequisites of target file '%s'.\n"));
+
+ if (running)
+ {
+ set_command_state (file, cs_deps_running);
+ --depth;
+ DBF (DB_VERBOSE, _("The prerequisites of '%s' are being made.\n"));
+ return 0;
+ }
+
+ /* If any dependency failed, give up now. */
+
+ if (dep_status)
+ {
+ /* I'm not sure if we can't just assign dep_status... */
+ file->update_status = dep_status == us_none ? us_failed : dep_status;
+ notice_finished_file (file);
+
+ --depth;
+
+ DBF (DB_VERBOSE, _("Giving up on target file '%s'.\n"));
+
+ if (depth == 0 && keep_going_flag
+ && !just_print_flag && !question_flag)
+ OS (error, NILF,
+ _("Target '%s' not remade because of errors."), file->name);
+
+ return dep_status;
+ }
+
+ if (file->command_state == cs_deps_running)
+ /* The commands for some deps were running on the last iteration, but
+ they have finished now. Reset the command_state to not_started to
+ simplify later bookkeeping. It is important that we do this only
+ when the prior state was cs_deps_running, because that prior state
+ was definitely propagated to FILE's also_make's by set_command_state
+ (called above), but in another state an also_make may have
+ independently changed to finished state, and we would confuse that
+ file's bookkeeping (updated, but not_started is bogus state). */
+ set_command_state (file, cs_not_started);
+
+ /* Now record which prerequisites are more
+ recent than this file, so we can define $?. */
+
+ deps_changed = 0;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ for (file = f2 = org_file; f2; file = f2 = f2->multi_next)
+#endif
+ for (d = file->deps; d != 0; d = d->next)
+ {
+ FILE_TIMESTAMP d_mtime = file_mtime (d->file);
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (d->file == file && file->multi_maybe)
+ continue;
+#endif
+ check_renamed (d->file);
+
+ if (! d->ignore_mtime)
+ {
+#if 1
+ /* %%% In version 4, remove this code completely to
+ implement not remaking deps if their deps are newer
+ than their parents. */
+ if (d_mtime == NONEXISTENT_MTIME && !d->file->intermediate)
+ /* We must remake if this dep does not
+ exist and is not intermediate. */
+ must_make = 1;
+#endif
+
+ /* Set DEPS_CHANGED if this dep actually changed. */
+ deps_changed |= d->changed;
+ }
+
+ /* Set D->changed if either this dep actually changed,
+ or its dependent, FILE, is older or does not exist. */
+ d->changed |= noexist || d_mtime > this_mtime;
+
+ if (!noexist && ISDB (DB_BASIC|DB_VERBOSE))
+ {
+ const char *fmt = 0;
+
+ if (d->ignore_mtime)
+ {
+ if (ISDB (DB_VERBOSE))
+ fmt = _("Prerequisite '%s' is order-only for target '%s'.\n");
+ }
+ else if (d_mtime == NONEXISTENT_MTIME)
+ {
+ if (ISDB (DB_BASIC))
+ fmt = _("Prerequisite '%s' of target '%s' does not exist.\n");
+ }
+ else if (d->changed)
+ {
+ if (ISDB (DB_BASIC))
+ fmt = _("Prerequisite '%s' is newer than target '%s'.\n");
+ }
+ else if (ISDB (DB_VERBOSE))
+ fmt = _("Prerequisite '%s' is older than target '%s'.\n");
+
+ if (fmt)
+ {
+ print_spaces (depth);
+ printf (fmt, dep_name (d), file->name);
+ fflush (stdout);
+ }
+ }
+ }
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ file = org_file;
+#endif
+
+ /* Here depth returns to the value it had when we were called. */
+ depth--;
+
+ if (file->double_colon && file->deps == 0)
+ {
+ must_make = 1;
+ DBF (DB_BASIC,
+ _("Target '%s' is double-colon and has no prerequisites.\n"));
+ }
+ else if (!noexist && file->is_target && !deps_changed && file->cmds == 0
+ && !always_make_flag)
+ {
+ must_make = 0;
+ DBF (DB_VERBOSE,
+ _("No recipe for '%s' and no prerequisites actually changed.\n"));
+ }
+ else if (!must_make && file->cmds != 0 && always_make_flag)
+ {
+ must_make = 1;
+ DBF (DB_VERBOSE, _("Making '%s' due to always-make flag.\n"));
+ }
+
+ if (!must_make)
+ {
+ if (ISDB (DB_VERBOSE))
+ {
+ print_spaces (depth);
+ printf (_("No need to remake target '%s'"), file->name);
+ if (!streq (file->name, file->hname))
+ printf (_("; using VPATH name '%s'"), file->hname);
+ puts (".");
+ fflush (stdout);
+ }
+
+ notice_finished_file (file);
+
+ /* Since we don't need to remake the file, convert it to use the
+ VPATH filename if we found one. hfile will be either the
+ local name if no VPATH or the VPATH name if one was found. */
+
+ while (file)
+ {
+ file->name = file->hname;
+ file = file->prev;
+ }
+
+ return 0;
+ }
+
+ DBF (DB_BASIC, _("Must remake target '%s'.\n"));
+
+ /* It needs to be remade. If it's VPATH and not reset via GPATH, toss the
+ VPATH. */
+ if (!streq (file->name, file->hname))
+ {
+ DB (DB_BASIC, (_(" Ignoring VPATH name '%s'.\n"), file->hname));
+ file->ignore_vpath = 1;
+ }
+
+ /* Now, take appropriate actions to remake the file. */
+ remake_file (file);
+
+ if (file->command_state != cs_finished)
+ {
+ DBF (DB_VERBOSE, _("Recipe of '%s' is being run.\n"));
+ return 0;
+ }
+
+ switch (file->update_status)
+ {
+ case us_failed:
+ DBF (DB_BASIC, _("Failed to remake target file '%s'.\n"));
+ break;
+ case us_success:
+ DBF (DB_BASIC, _("Successfully remade target file '%s'.\n"));
+ break;
+ case us_question:
+ DBF (DB_BASIC, _("Target file '%s' needs to be remade under -q.\n"));
+ break;
+ case us_none:
+ break;
+ }
+
+ file->updated = 1;
+ return file->update_status;
+}
+
+#ifdef CONFIG_WITH_DOT_MUST_MAKE
+/* Consider the .MUST_MAKE target variable if present.
+
+ Returns 1 if must remake, 0 if not.
+
+ The deal is that .MUST_MAKE returns non-zero if it thinks the target needs
+ updating. We have to initialize file variables (for the sake of pattern
+ vars) and set the most important file variables before calling (expanding)
+ the .MUST_MAKE variable.
+
+ The file variables keeping the dependency lists, $+, $^, $? and $| are not
+ available at this point because $? depends on things happening after we've
+ decided to make the file. So, to keep things simple all 4 of them are
+ undefined in this call. */
+static int
+call_must_make_target_var (struct file *file, unsigned int depth)
+{
+ struct variable *var;
+ unsigned char ch;
+ const char *str;
+
+ if (file->variables)
+ {
+ var = lookup_variable_in_set (".MUST_MAKE", sizeof (".MUST_MAKE") - 1,
+ file->variables->set);
+ if (var)
+ {
+ initialize_file_variables (file, 0);
+ set_file_variables (file, 1 /* called early, no dep lists please */);
+
+ str = variable_expand_for_file_2 (NULL,
+ var->value, var->value_length,
+ file, NULL);
+
+ /* Stripped string should be non-zero. */
+
+ ch = *str;
+ while (ISSPACE (ch))
+ ch = *++str;
+
+ if (ch != '\0')
+ {
+ if (ISDB (DB_BASIC))
+ {
+ print_spaces (depth);
+ printf (_(".MUST_MAKE returned `%s' for target `%s'.\n"),
+ str, file->name);
+ }
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+#endif /* CONFIG_WITH_DOT_MUST_MAKE */
+
+#ifdef CONFIG_WITH_DOT_IS_CHANGED
+/* Consider the .IS_CHANGED target variable if present.
+
+ Returns 1 if the file should be considered modified, 0 if not.
+
+ The calling context and restrictions are the same as for .MUST_MAKE.
+ Make uses the information from this 'function' for determining whether to
+ make a file which lists this as a prerequisite. So, this is the feature you
+ use to check MD5 sums, file sizes, time stamps and the like with data from
+ a previous run.
+
+ FIXME: Would be nice to know which file is currently being considered.
+ FIXME: Is currently not invoked for intermediate files. */
+static int
+call_is_changed_target_var (struct file *file)
+{
+ struct variable *var;
+ unsigned char ch;
+ const char *str;
+
+ if (file->variables)
+ {
+ var = lookup_variable_in_set (".IS_CHANGED", sizeof (".IS_CHANGED") - 1,
+ file->variables->set);
+ if (var)
+ {
+ initialize_file_variables (file, 0);
+ set_file_variables (file, 1 /* called early, no dep lists please */);
+
+ str = variable_expand_for_file_2 (NULL,
+ var->value, var->value_length,
+ file, NULL);
+
+ /* stripped string should be non-zero. */
+ do
+ ch = *str++;
+ while (ISSPACE (ch));
+
+ return (ch != '\0');
+ }
+ }
+ return 0;
+}
+#endif /* CONFIG_WITH_DOT_IS_CHANGED */
+
+/* Set FILE's 'updated' flag and re-check its mtime and the mtime's of all
+ files listed in its 'also_make' member. Under -t, this function also
+ touches FILE.
+
+ On return, FILE->update_status will no longer be us_none if it was. */
+
+void
+notice_finished_file (struct file *file)
+{
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ struct file *f2;
+#endif
+ struct dep *d;
+ int ran = file->command_state == cs_running;
+ int touched = 0;
+ DB (DB_JOBS, (_("notice_finished_file - entering: file=%p `%s' update_status=%d command_state=%d\n"), /* bird */
+ (void *) file, file->name, file->update_status, file->command_state));
+
+ file->command_state = cs_finished;
+ file->updated = 1;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (file->multi_head)
+ {
+ assert (file == file->multi_head);
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ {
+ f2->command_state = cs_finished;
+ f2->updated = 1;
+ }
+ }
+#endif
+
+#ifdef CONFIG_WITH_EXTENDED_NOTPARALLEL
+ /* update not_parallel if the file was flagged for that. */
+ if ( ran
+ && (file->command_flags & (COMMANDS_NOTPARALLEL | COMMANDS_NO_COMMANDS))
+ == COMMANDS_NOTPARALLEL)
+ {
+ DB (DB_KMK, (_("not_parallel %d -> %d (file=%p `%s') [notice_finished_file]\n"), not_parallel,
+ not_parallel - 1, (void *) file, file->name));
+ assert(not_parallel >= 1);
+ --not_parallel;
+ }
+#endif
+
+ if (touch_flag
+ /* The update status will be:
+ us_success if 0 or more commands (+ or ${MAKE}) were run and won;
+ us_none if this target was not remade;
+ >us_none if some commands were run and lost.
+ We touch the target if it has commands which either were not run
+ or won when they ran (i.e. status is 0). */
+ && file->update_status == us_success)
+ {
+ if (file->cmds != 0 && file->cmds->any_recurse)
+ {
+ /* If all the command lines were recursive,
+ we don't want to do the touching. */
+ unsigned int i;
+ for (i = 0; i < file->cmds->ncommand_lines; ++i)
+ if (!(file->cmds->lines_flags[i] & COMMANDS_RECURSE))
+ goto have_nonrecursing;
+ }
+ else
+ {
+ have_nonrecursing:
+ if (file->phony)
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ {
+ file->update_status = us_success;
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ f2->update_status = us_success;
+ }
+#else
+ file->update_status = us_success;
+#endif
+ /* According to POSIX, -t doesn't affect targets with no cmds. */
+ else if (file->cmds != 0)
+ {
+ /* Should set file's modification date and do nothing else. */
+ file->update_status = touch_file (file);
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ {
+ /* figure out this, touch if it exist ignore otherwise? */
+ }
+#endif
+
+ /* Pretend we ran a real touch command, to suppress the
+ "'foo' is up to date" message. */
+ commands_started++;
+
+ /* Request for the timestamp to be updated (and distributed
+ to the double-colon entries). Simply setting ran=1 would
+ almost have done the trick, but messes up with the also_make
+ updating logic below. */
+ touched = 1;
+ }
+ }
+ }
+
+ if (file->mtime_before_update == UNKNOWN_MTIME)
+ file->mtime_before_update = file->last_mtime;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ if (f2->mtime_before_update == UNKNOWN_MTIME)
+ f2->mtime_before_update = f2->last_mtime;
+#endif
+
+ if ((ran && !file->phony) || touched)
+ {
+ int i = 0;
+
+ /* If -n, -t, or -q and all the commands are recursive, we ran them so
+ really check the target's mtime again. Otherwise, assume the target
+ would have been updated. */
+
+ if ((question_flag || just_print_flag || touch_flag) && file->cmds)
+ {
+ for (i = file->cmds->ncommand_lines; i > 0; --i)
+ if (! (file->cmds->lines_flags[i-1] & COMMANDS_RECURSE))
+ break;
+ }
+
+ /* If there were no commands at all, it's always new. */
+
+ else if (file->is_target && file->cmds == 0)
+ i = 1;
+
+ file->last_mtime = i == 0 ? UNKNOWN_MTIME : NEW_MTIME;
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ file->last_mtime = i == 0 ? UNKNOWN_MTIME : NEW_MTIME; /*??*/
+#endif
+ }
+
+ if (file->double_colon)
+ {
+ /* If this is a double colon rule and it is the last one to be
+ updated, propagate the change of modification time to all the
+ double-colon entries for this file.
+
+ We do it on the last update because it is important to handle
+ individual entries as separate rules with separate timestamps
+ while they are treated as targets and then as one rule with the
+ unified timestamp when they are considered as a prerequisite
+ of some target. */
+
+ struct file *f;
+ FILE_TIMESTAMP max_mtime = file->last_mtime;
+
+ /* Check that all rules were updated and at the same time find
+ the max timestamp. We assume UNKNOWN_MTIME is newer then
+ any other value. */
+ for (f = file->double_colon; f != 0 && f->updated; f = f->prev)
+ if (max_mtime != UNKNOWN_MTIME
+ && (f->last_mtime == UNKNOWN_MTIME || f->last_mtime > max_mtime))
+ max_mtime = f->last_mtime;
+
+ if (f == 0)
+ for (f = file->double_colon; f != 0; f = f->prev)
+ f->last_mtime = max_mtime;
+ }
+
+ if (ran && file->update_status != us_none)
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ {
+#endif
+ /* We actually tried to update FILE, which has
+ updated its also_make's as well (if it worked).
+ If it didn't work, it wouldn't work again for them.
+ So mark them as updated with the same status. */
+ for (d = file->also_make; d != 0; d = d->next)
+ {
+ d->file->command_state = cs_finished;
+ d->file->updated = 1;
+ d->file->update_status = file->update_status;
+
+ if (ran && !d->file->phony)
+ /* Fetch the new modification time.
+ We do this instead of just invalidating the cached time
+ so that a vpath_search can happen. Otherwise, it would
+ never be done because the target is already updated. */
+ f_mtime (d->file, 0);
+ }
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ /* Same as above but for explicit multi target rules. */
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ {
+ f2->update_status = file->update_status;
+ if (!f2->phony)
+ f_mtime (f2, 0);
+ }
+ }
+#endif
+ else if (file->update_status == us_none)
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ {
+ /* Nothing was done for FILE, but it needed nothing done.
+ So mark it now as "succeeded". */
+ file->update_status = 0;
+ if (file->multi_head)
+ for (f2 = file->multi_next; f2 != 0; f2 = f2->multi_next)
+ f2->update_status = 0;
+ }
+#else
+ /* Nothing was done for FILE, but it needed nothing done.
+ So mark it now as "succeeded". */
+ file->update_status = us_success;
+#endif
+
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ /* We're done with this command, so free the memory held by the chopped
+ command lines. Saves heap for the compilers & linkers. */
+ if (file->cmds && file->cmds->command_lines)
+ free_chopped_commands (file->cmds);
+#endif
+}
+
+/* Check whether another file (whose mtime is THIS_MTIME) needs updating on
+ account of a dependency which is file FILE. If it does, store 1 in
+ *MUST_MAKE_PTR. In the process, update any non-intermediate files that
+ FILE depends on (including FILE itself). Return nonzero if any updating
+ failed. */
+
+static enum update_status
+check_dep (struct file *file, unsigned int depth,
+ FILE_TIMESTAMP this_mtime, int *must_make_ptr)
+{
+ struct file *ofile;
+ struct dep *d;
+ enum update_status dep_status = us_success;
+
+ ++depth;
+ start_updating (file);
+
+ /* We might change file if we find a different one via vpath;
+ remember this one to turn off updating. */
+ ofile = file;
+
+ if (file->phony || !file->intermediate)
+ {
+ /* If this is a non-intermediate file, update it and record whether it
+ is newer than THIS_MTIME. */
+ FILE_TIMESTAMP mtime;
+ dep_status = update_file (file, depth);
+ check_renamed (file);
+ mtime = file_mtime (file);
+ check_renamed (file);
+ if (mtime == NONEXISTENT_MTIME || mtime > this_mtime)
+ *must_make_ptr = 1;
+#ifdef CONFIG_WITH_DOT_IS_CHANGED
+ else if ( *must_make_ptr == 0
+ && call_is_changed_target_var (file))
+ *must_make_ptr = 1;
+#endif /* CONFIG_WITH_DOT_IS_CHANGED */
+ }
+ else
+ {
+ /* FILE is an intermediate file. */
+ FILE_TIMESTAMP mtime;
+
+ if (!file->phony && file->cmds == 0 && !file->tried_implicit)
+ {
+ if (try_implicit_rule (file, depth))
+ DBF (DB_IMPLICIT, _("Found an implicit rule for '%s'.\n"));
+ else
+ DBF (DB_IMPLICIT, _("No implicit rule found for '%s'.\n"));
+ file->tried_implicit = 1;
+ }
+ if (file->cmds == 0 && !file->is_target
+ && default_file != 0 && default_file->cmds != 0)
+ {
+ DBF (DB_IMPLICIT, _("Using default commands for '%s'.\n"));
+ file->cmds = default_file->cmds;
+ }
+
+ check_renamed (file);
+ mtime = file_mtime (file);
+ check_renamed (file);
+ if (mtime != NONEXISTENT_MTIME && mtime > this_mtime)
+ /* If the intermediate file actually exists and is newer, then we
+ should remake from it. */
+ *must_make_ptr = 1;
+ else
+ {
+ /* Otherwise, update all non-intermediate files we depend on, if
+ necessary, and see whether any of them is more recent than the
+ file on whose behalf we are checking. */
+ struct dep *ld;
+ int deps_running = 0;
+
+ /* If this target is not running, set it's state so that we check it
+ fresh. It could be it was checked as part of an order-only
+ prerequisite and so wasn't rebuilt then, but should be now. */
+ if (file->command_state != cs_running)
+ {
+ /* If the target was waiting for a dependency it has to be
+ reconsidered, as that dependency might have finished. */
+ if (file->command_state == cs_deps_running)
+ file->considered = 0;
+
+ set_command_state (file, cs_not_started);
+ }
+
+ ld = 0;
+ d = file->deps;
+ while (d != 0)
+ {
+ enum update_status new;
+ int maybe_make;
+
+ if (is_updating (d->file))
+ {
+ OSS (error, NILF, _("Circular %s <- %s dependency dropped."),
+ file->name, d->file->name);
+ if (ld == 0)
+ {
+ file->deps = d->next;
+ free_dep (d);
+ d = file->deps;
+ }
+ else
+ {
+ ld->next = d->next;
+ free_dep (d);
+ d = ld->next;
+ }
+ continue;
+ }
+
+ d->file->parent = file;
+ maybe_make = *must_make_ptr;
+ new = check_dep (d->file, depth, this_mtime, &maybe_make);
+ if (new > dep_status)
+ dep_status = new;
+
+ if (! d->ignore_mtime)
+ *must_make_ptr = maybe_make;
+ check_renamed (d->file);
+ if (dep_status && !keep_going_flag)
+ break;
+
+ if (d->file->command_state == cs_running
+ || d->file->command_state == cs_deps_running)
+ deps_running = 1;
+
+ ld = d;
+ d = d->next;
+ }
+
+ if (deps_running)
+ /* Record that some of FILE's deps are still being made.
+ This tells the upper levels to wait on processing it until the
+ commands are finished. */
+ set_command_state (file, cs_deps_running);
+ }
+ }
+
+ finish_updating (file);
+ finish_updating (ofile);
+
+ return dep_status;
+}
+
+/* Touch FILE. Return us_success if successful, us_failed if not. */
+
+#define TOUCH_ERROR(call) do{ perror_with_name ((call), file->name); \
+ return us_failed; }while(0)
+
+static enum update_status
+touch_file (struct file *file)
+{
+ if (!silent_flag)
+ OS (message, 0, "touch %s", file->name);
+
+ /* Print-only (-n) takes precedence over touch (-t). */
+ if (just_print_flag)
+ return us_success;
+
+#ifndef NO_ARCHIVES
+ if (ar_name (file->name))
+ return ar_touch (file->name) ? us_failed : us_success;
+ else
+#endif
+ {
+ int fd;
+
+ EINTRLOOP (fd, open (file->name, O_RDWR | O_CREAT, 0666));
+ if (fd < 0)
+ TOUCH_ERROR ("touch: open: ");
+ else
+ {
+ struct stat statbuf;
+ char buf = 'x';
+ int e;
+
+ EINTRLOOP (e, fstat (fd, &statbuf));
+ if (e < 0)
+ TOUCH_ERROR ("touch: fstat: ");
+ /* Rewrite character 0 same as it already is. */
+ EINTRLOOP (e, read (fd, &buf, 1));
+ if (e < 0)
+ TOUCH_ERROR ("touch: read: ");
+ {
+ off_t o;
+ EINTRLOOP (o, lseek (fd, 0L, 0));
+ if (o < 0L)
+ TOUCH_ERROR ("touch: lseek: ");
+ }
+ EINTRLOOP (e, write (fd, &buf, 1));
+ if (e < 0)
+ TOUCH_ERROR ("touch: write: ");
+
+ /* If file length was 0, we just changed it, so change it back. */
+ if (statbuf.st_size == 0)
+ {
+ (void) close (fd);
+ EINTRLOOP (fd, open (file->name, O_RDWR | O_TRUNC, 0666));
+ if (fd < 0)
+ TOUCH_ERROR ("touch: open: ");
+ }
+ (void) close (fd);
+ }
+ }
+
+ return us_success;
+}
+
+/* Having checked and updated the dependencies of FILE,
+ do whatever is appropriate to remake FILE itself.
+ Return the status from executing FILE's commands. */
+
+static void
+remake_file (struct file *file)
+{
+#ifdef CONFIG_WITH_EXPLICIT_MULTITARGET
+ assert(file->multi_head == NULL || file->multi_head == file);
+#endif
+
+ if (file->cmds == 0)
+ {
+ if (file->phony)
+ /* Phony target. Pretend it succeeded. */
+ file->update_status = us_success;
+ else if (file->is_target)
+ /* This is a nonexistent target file we cannot make.
+ Pretend it was successfully remade. */
+ file->update_status = us_success;
+ else
+ {
+ /* This is a dependency file we cannot remake. Fail. */
+ if (!rebuilding_makefiles || !file->dontcare)
+ complain (file);
+ file->update_status = us_failed;
+ }
+ }
+ else
+ {
+ chop_commands (file->cmds);
+
+ /* The normal case: start some commands. */
+ if (!touch_flag || file->cmds->any_recurse)
+ {
+ execute_file_commands (file);
+ return;
+ }
+
+ /* This tells notice_finished_file it is ok to touch the file. */
+ file->update_status = us_success;
+ }
+
+ /* This does the touching under -t. */
+ notice_finished_file (file);
+}
+
+/* Return the mtime of a file, given a 'struct file'.
+ Caches the time in the struct file to avoid excess stat calls.
+
+ If the file is not found, and SEARCH is nonzero, VPATH searching and
+ replacement is done. If that fails, a library (-lLIBNAME) is tried and
+ the library's actual name (/lib/libLIBNAME.a, etc.) is substituted into
+ FILE. */
+
+FILE_TIMESTAMP
+f_mtime (struct file *file, int search)
+{
+ FILE_TIMESTAMP mtime;
+ int propagate_timestamp;
+
+ /* File's mtime is not known; must get it from the system. */
+
+#ifndef NO_ARCHIVES
+ if (ar_name (file->name))
+ {
+ /* This file is an archive-member reference. */
+
+ char *arname, *memname;
+ struct file *arfile;
+ time_t member_date;
+
+ /* Find the archive's name. */
+ ar_parse_name (file->name, &arname, &memname);
+
+ /* Find the modification time of the archive itself.
+ Also allow for its name to be changed via VPATH search. */
+ arfile = lookup_file (arname);
+ if (arfile == 0)
+ arfile = enter_file (strcache_add (arname));
+ mtime = f_mtime (arfile, search);
+ check_renamed (arfile);
+ if (search && strcmp (arfile->hname, arname))
+ {
+ /* The archive's name has changed.
+ Change the archive-member reference accordingly. */
+
+ char *name;
+ unsigned int arlen, memlen;
+
+ arlen = strlen (arfile->hname);
+ memlen = strlen (memname);
+
+ name = alloca (arlen + 1 + memlen + 2);
+ memcpy (name, arfile->hname, arlen);
+ name[arlen] = '(';
+ memcpy (name + arlen + 1, memname, memlen);
+ name[arlen + 1 + memlen] = ')';
+ name[arlen + 1 + memlen + 1] = '\0';
+
+ /* If the archive was found with GPATH, make the change permanent;
+ otherwise defer it until later. */
+ if (arfile->name == arfile->hname)
+ rename_file (file, strcache_add (name));
+ else
+ rehash_file (file, strcache_add (name));
+ check_renamed (file);
+ }
+
+ free (arname);
+
+ file->low_resolution_time = 1;
+
+ if (mtime == NONEXISTENT_MTIME)
+ /* The archive doesn't exist, so its members don't exist either. */
+ return NONEXISTENT_MTIME;
+
+ member_date = ar_member_date (file->hname);
+ mtime = (member_date == (time_t) -1
+ ? NONEXISTENT_MTIME
+ : file_timestamp_cons (file->hname, member_date, 0));
+ }
+ else
+#endif
+ {
+ mtime = name_mtime (file->name);
+
+ if (mtime == NONEXISTENT_MTIME && search && !file->ignore_vpath)
+ {
+ /* If name_mtime failed, search VPATH. */
+ const char *name = vpath_search (file->name, &mtime, NULL, NULL);
+ if (name
+ /* Last resort, is it a library (-lxxx)? */
+ || (file->name[0] == '-' && file->name[1] == 'l'
+ && (name = library_search (file->name, &mtime)) != 0))
+ {
+ int name_len;
+
+ if (mtime != UNKNOWN_MTIME)
+ /* vpath_search and library_search store UNKNOWN_MTIME
+ if they didn't need to do a stat call for their work. */
+ file->last_mtime = mtime;
+
+ /* If we found it in VPATH, see if it's in GPATH too; if so,
+ change the name right now; if not, defer until after the
+ dependencies are updated. */
+#ifndef VMS
+ name_len = strlen (name) - strlen (file->name) - 1;
+#else
+ name_len = strlen (name) - strlen (file->name);
+ if (name[name_len - 1] == '/')
+ name_len--;
+#endif
+ if (gpath_search (name, name_len))
+ {
+ rename_file (file, name);
+ check_renamed (file);
+ return file_mtime (file);
+ }
+
+ rehash_file (file, name);
+ check_renamed (file);
+ /* If the result of a vpath search is -o or -W, preserve it.
+ Otherwise, find the mtime of the resulting file. */
+ if (mtime != OLD_MTIME && mtime != NEW_MTIME)
+ mtime = name_mtime (name);
+ }
+ }
+ }
+
+ /* Files can have bogus timestamps that nothing newly made will be
+ "newer" than. Updating their dependents could just result in loops.
+ So notify the user of the anomaly with a warning.
+
+ We only need to do this once, for now. */
+
+ if (!clock_skew_detected
+ && mtime != NONEXISTENT_MTIME && mtime != NEW_MTIME
+ && !file->updated)
+ {
+ static FILE_TIMESTAMP adjusted_now;
+
+ FILE_TIMESTAMP adjusted_mtime = mtime;
+
+#if defined(WINDOWS32) || defined(__MSDOS__)
+ /* Experimentation has shown that FAT filesystems can set file times
+ up to 3 seconds into the future! Play it safe. */
+
+#define FAT_ADJ_OFFSET (FILE_TIMESTAMP) 3
+
+ FILE_TIMESTAMP adjustment = FAT_ADJ_OFFSET << FILE_TIMESTAMP_LO_BITS;
+ if (ORDINARY_MTIME_MIN + adjustment <= adjusted_mtime)
+ adjusted_mtime -= adjustment;
+#elif defined(__EMX__)
+ /* FAT filesystems round time to the nearest even second!
+ Allow for any file (NTFS or FAT) to perhaps suffer from this
+ brain damage. */
+ FILE_TIMESTAMP adjustment = (((FILE_TIMESTAMP_S (adjusted_mtime) & 1) == 0
+ && FILE_TIMESTAMP_NS (adjusted_mtime) == 0)
+ ? (FILE_TIMESTAMP) 1 << FILE_TIMESTAMP_LO_BITS
+ : 0);
+#endif
+
+ /* If the file's time appears to be in the future, update our
+ concept of the present and try once more. */
+ if (adjusted_now < adjusted_mtime)
+ {
+ int resolution;
+ FILE_TIMESTAMP now = file_timestamp_now (&resolution);
+ adjusted_now = now + (resolution - 1);
+ if (adjusted_now < adjusted_mtime)
+ {
+#ifdef NO_FLOAT
+ OS (error, NILF,
+ _("Warning: File '%s' has modification time in the future"),
+ file->name);
+#else
+ double from_now =
+ (FILE_TIMESTAMP_S (mtime) - FILE_TIMESTAMP_S (now)
+ + ((FILE_TIMESTAMP_NS (mtime) - FILE_TIMESTAMP_NS (now))
+ / 1e9));
+ char from_now_string[100];
+
+ if (from_now >= 99 && from_now <= ULONG_MAX)
+ sprintf (from_now_string, "%lu", (unsigned long) from_now);
+ else
+ sprintf (from_now_string, "%.2g", from_now);
+ OSS (error, NILF,
+ _("Warning: File '%s' has modification time %s s in the future"),
+ file->name, from_now_string);
+#endif
+ clock_skew_detected = 1;
+ }
+ }
+ }
+
+ /* Store the mtime into all the entries for this file for which it is safe
+ to do so: avoid propagating timestamps to double-colon rules that haven't
+ been examined so they're run or not based on the pre-update timestamp. */
+ if (file->double_colon)
+ file = file->double_colon;
+
+ propagate_timestamp = file->updated;
+ do
+ {
+ /* If this file is not implicit but it is intermediate then it was
+ made so by the .INTERMEDIATE target. If this file has never
+ been built by us but was found now, it existed before make
+ started. So, turn off the intermediate bit so make doesn't
+ delete it, since it didn't create it. */
+ if (mtime != NONEXISTENT_MTIME && file->command_state == cs_not_started
+ && !file->tried_implicit && file->intermediate)
+ file->intermediate = 0;
+
+ if (file->updated == propagate_timestamp)
+ file->last_mtime = mtime;
+ file = file->prev;
+ }
+ while (file != 0);
+
+ return mtime;
+}
+
+
+/* Return the mtime of the file or archive-member reference NAME. */
+
+/* First, we check with stat(). If the file does not exist, then we return
+ NONEXISTENT_MTIME. If it does, and the symlink check flag is set, then
+ examine each indirection of the symlink and find the newest mtime.
+ This causes one duplicate stat() when -L is being used, but the code is
+ much cleaner. */
+
+static FILE_TIMESTAMP
+name_mtime (const char *name)
+{
+ FILE_TIMESTAMP mtime;
+ struct stat st;
+ int e;
+
+#if defined(KMK) && defined(KBUILD_OS_WINDOWS)
+ extern int stat_only_mtime(const char *pszPath, struct stat *pStat);
+ e = stat_only_mtime (name, &st);
+#else
+ EINTRLOOP (e, stat (name, &st));
+#endif
+ if (e == 0)
+ mtime = FILE_TIMESTAMP_STAT_MODTIME (name, st);
+ else if (errno == ENOENT || errno == ENOTDIR)
+ mtime = NONEXISTENT_MTIME;
+ else
+ {
+ perror_with_name ("stat: ", name);
+ return NONEXISTENT_MTIME;
+ }
+
+ /* If we get here we either found it, or it doesn't exist.
+ If it doesn't exist see if we can use a symlink mtime instead. */
+
+#ifdef MAKE_SYMLINKS
+#ifndef S_ISLNK
+# define S_ISLNK(_m) (((_m)&S_IFMT)==S_IFLNK)
+#endif
+ if (check_symlink_flag)
+ {
+ PATH_VAR (lpath);
+
+ /* Check each symbolic link segment (if any). Find the latest mtime
+ amongst all of them (and the target file of course).
+ Note that we have already successfully dereferenced all the links
+ above. So, if we run into any error trying to lstat(), or
+ readlink(), or whatever, something bizarre-o happened. Just give up
+ and use whatever mtime we've already computed at that point. */
+ strcpy (lpath, name);
+ while (1)
+ {
+ FILE_TIMESTAMP ltime;
+ PATH_VAR (lbuf);
+ long llen;
+ char *p;
+
+ EINTRLOOP (e, lstat (lpath, &st));
+ if (e)
+ {
+ /* Just take what we have so far. */
+ if (errno != ENOENT && errno != ENOTDIR)
+ perror_with_name ("lstat: ", lpath);
+ break;
+ }
+
+ /* If this is not a symlink, we're done (we started with the real
+ file's mtime so we don't need to test it again). */
+ if (!S_ISLNK (st.st_mode))
+ break;
+
+ /* If this mtime is newer than what we had, keep the new one. */
+ ltime = FILE_TIMESTAMP_STAT_MODTIME (lpath, st);
+ if (ltime > mtime)
+ mtime = ltime;
+
+ /* Set up to check the file pointed to by this link. */
+ EINTRLOOP (llen, readlink (lpath, lbuf, GET_PATH_MAX));
+ if (llen < 0)
+ {
+ /* Eh? Just take what we have. */
+ perror_with_name ("readlink: ", lpath);
+ break;
+ }
+ lbuf[llen] = '\0';
+
+ /* If the target is fully-qualified or the source is just a
+ filename, then the new path is the target. Otherwise it's the
+ source directory plus the target. */
+ if (lbuf[0] == '/' || (p = strrchr (lpath, '/')) == NULL)
+ strcpy (lpath, lbuf);
+ else if ((p - lpath) + llen + 2 > GET_PATH_MAX)
+ /* Eh? Path too long! Again, just go with what we have. */
+ break;
+ else
+ /* Create the next step in the symlink chain. */
+ strcpy (p+1, lbuf);
+ }
+ }
+#endif
+
+ return mtime;
+}
+
+
+/* Search for a library file specified as -lLIBNAME, searching for a
+ suitable library file in the system library directories and the VPATH
+ directories. */
+
+static const char *
+library_search (const char *lib, FILE_TIMESTAMP *mtime_ptr)
+{
+ static const char *dirs[] =
+ {
+#ifdef KMK
+ ".",
+#else /* !KMK */
+#ifndef _AMIGA
+ "/lib",
+ "/usr/lib",
+#endif
+#if defined(WINDOWS32) && !defined(LIBDIR)
+/*
+ * This is completely up to the user at product install time. Just define
+ * a placeholder.
+ */
+#define LIBDIR "."
+#endif
+# ifdef LIBDIR /* bird */
+ LIBDIR, /* Defined by configuration. */
+# else /* bird */
+ ".", /* bird */
+# endif /* bird */
+#endif /* !KMK */
+ 0
+ };
+
+ const char *file = 0;
+ char *libpatterns;
+ FILE_TIMESTAMP mtime;
+
+ /* Loop variables for the libpatterns value. */
+ char *p;
+ const char *p2;
+ unsigned int len;
+ unsigned int liblen;
+
+ /* Information about the earliest (in the vpath sequence) match. */
+ unsigned int best_vpath = 0, best_path = 0;
+
+ const char **dp;
+
+ libpatterns = xstrdup (variable_expand ("$(.LIBPATTERNS)"));
+
+ /* Skip the '-l'. */
+ lib += 2;
+ liblen = strlen (lib);
+
+ /* Loop through all the patterns in .LIBPATTERNS, and search on each one.
+ To implement the linker-compatible behavior we have to search through
+ all entries in .LIBPATTERNS and choose the "earliest" one. */
+ p2 = libpatterns;
+ while ((p = find_next_token (&p2, &len)) != 0)
+ {
+ static char *buf = NULL;
+ static unsigned int buflen = 0;
+ static int libdir_maxlen = -1;
+ static unsigned int std_dirs = 0;
+ char *libbuf = variable_expand ("");
+ const size_t libbuf_offset = libbuf - variable_buffer; /* bird */
+
+ /* Expand the pattern using LIB as a replacement. */
+ {
+ char c = p[len];
+ char *p3, *p4;
+
+ p[len] = '\0';
+ p3 = find_percent (p);
+ if (!p3)
+ {
+ /* Give a warning if there is no pattern. */
+ OS (error, NILF,
+ _(".LIBPATTERNS element '%s' is not a pattern"), p);
+ p[len] = c;
+ continue;
+ }
+ p4 = variable_buffer_output (libbuf, p, p3-p);
+ p4 = variable_buffer_output (p4, lib, liblen);
+ p4 = variable_buffer_output (p4, p3+1, len - (p3-p));
+ p[len] = c;
+ libbuf = variable_buffer + libbuf_offset; /* bird - variable_buffer may have been reallocated. UPSTREAM */
+ }
+
+ /* Look first for 'libNAME.a' in the current directory. */
+ mtime = name_mtime (libbuf);
+ if (mtime != NONEXISTENT_MTIME)
+ {
+ if (mtime_ptr != 0)
+ *mtime_ptr = mtime;
+ file = strcache_add (libbuf);
+ /* This by definition will have the best index, so stop now. */
+ break;
+ }
+
+ /* Now try VPATH search on that. */
+
+ {
+ unsigned int vpath_index, path_index;
+ const char* f = vpath_search (libbuf, mtime_ptr ? &mtime : NULL,
+ &vpath_index, &path_index);
+ if (f)
+ {
+ /* If we have a better match, record it. */
+ if (file == 0 ||
+ vpath_index < best_vpath ||
+ (vpath_index == best_vpath && path_index < best_path))
+ {
+ file = f;
+ best_vpath = vpath_index;
+ best_path = path_index;
+
+ if (mtime_ptr != 0)
+ *mtime_ptr = mtime;
+ }
+ }
+ }
+
+ /* Now try the standard set of directories. */
+
+ if (!buflen)
+ {
+ for (dp = dirs; *dp != 0; ++dp)
+ {
+ int l = strlen (*dp);
+ if (l > libdir_maxlen)
+ libdir_maxlen = l;
+ std_dirs++;
+ }
+ buflen = strlen (libbuf);
+ buf = xmalloc (libdir_maxlen + buflen + 2);
+ }
+ else if (buflen < strlen (libbuf))
+ {
+ buflen = strlen (libbuf);
+ buf = xrealloc (buf, libdir_maxlen + buflen + 2);
+ }
+
+ {
+ /* Use the last std_dirs index for standard directories. This
+ was it will always be greater than the VPATH index. */
+ unsigned int vpath_index = ~((unsigned int)0) - std_dirs;
+
+ for (dp = dirs; *dp != 0; ++dp)
+ {
+ sprintf (buf, "%s/%s", *dp, libbuf);
+ mtime = name_mtime (buf);
+ if (mtime != NONEXISTENT_MTIME)
+ {
+ if (file == 0 || vpath_index < best_vpath)
+ {
+ file = strcache_add (buf);
+ best_vpath = vpath_index;
+
+ if (mtime_ptr != 0)
+ *mtime_ptr = mtime;
+ }
+ }
+
+ vpath_index++;
+ }
+ }
+
+ }
+
+ free (libpatterns);
+ return file;
+}
diff --git a/src/kmk/remote-cstms.c b/src/kmk/remote-cstms.c
new file mode 100644
index 0000000..7c36b9d
--- /dev/null
+++ b/src/kmk/remote-cstms.c
@@ -0,0 +1,300 @@
+/* GNU Make remote job exportation interface to the Customs daemon.
+ THIS CODE IS NOT SUPPORTED BY THE GNU PROJECT.
+ Please do not send bug reports or questions about it to
+ the Make maintainers.
+
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "commands.h"
+#include "job.h"
+#include "debug.h"
+
+#include <sys/time.h>
+#include <netdb.h>
+
+#include "customs.h"
+
+char *remote_description = "Customs";
+
+/* File name of the Customs 'export' client command.
+ A full path name can be used to avoid some path-searching overhead. */
+#define EXPORT_COMMAND "/usr/local/bin/export"
+
+/* ExportPermit gotten by start_remote_job_p, and used by start_remote_job. */
+static ExportPermit permit;
+
+/* Normalized path name of the current directory. */
+static char *normalized_cwd;
+
+/* Call once at startup even if no commands are run. */
+
+void
+remote_setup (void)
+{
+}
+
+/* Called before exit. */
+
+void
+remote_cleanup (void)
+{
+}
+
+/* Return nonzero if the next job should be done remotely. */
+
+int
+start_remote_job_p (int first_p)
+{
+ static int inited = 0;
+ int status;
+ int njobs;
+
+ if (!inited)
+ {
+ /* Allow the user to turn off job exportation (useful while he is
+ debugging Customs, for example). */
+ if (getenv ("GNU_MAKE_NO_CUSTOMS") != 0)
+ {
+ inited = -1;
+ return 0;
+ }
+
+ /* For secure Customs, make is installed setuid root and
+ Customs requires a privileged source port be used. */
+ make_access ();
+
+ if (ISDB (DB_JOBS))
+ Rpc_Debug (1);
+
+ /* Ping the daemon once to see if it is there. */
+ inited = Customs_Ping () == RPC_SUCCESS ? 1 : -1;
+
+ /* Return to normal user access. */
+ user_access ();
+
+ if (starting_directory == 0)
+ /* main couldn't figure it out. */
+ inited = -1;
+ else
+ {
+ /* Normalize the current directory path name to something
+ that should work on all machines exported to. */
+
+ normalized_cwd = xmalloc (GET_PATH_MAX);
+ strcpy (normalized_cwd, starting_directory);
+ if (Customs_NormPath (normalized_cwd, GET_PATH_MAX) < 0)
+ /* Path normalization failure means using Customs
+ won't work, but it's not really an error. */
+ inited = -1;
+ }
+ }
+
+ if (inited < 0)
+ return 0;
+
+ njobs = job_slots_used;
+ if (!first_p)
+ njobs -= 1; /* correction for being called from reap_children() */
+
+ /* the first job should run locally, or, if the -l flag is given, we use
+ that as clue as to how many local jobs should be scheduled locally */
+ if (max_load_average < 0 && njobs == 0 || njobs < max_load_average)
+ return 0;
+
+ status = Customs_Host (EXPORT_SAME, &permit);
+ if (status != RPC_SUCCESS)
+ {
+ DB (DB_JOBS, (_("Customs won't export: %s\n"),
+ Rpc_ErrorMessage (status)));
+ return 0;
+ }
+
+ return !CUSTOMS_FAIL (&permit.addr);
+}
+
+/* Start a remote job running the command in ARGV, with environment from
+ ENVP. It gets standard input from STDIN_FD. On failure, return
+ nonzero. On success, return zero, and set *USED_STDIN to nonzero if it
+ will actually use STDIN_FD, zero if not, set *ID_PTR to a unique
+ identification, and set *IS_REMOTE to nonzero if the job is remote, zero
+ if it is local (meaning *ID_PTR is a process ID). */
+
+int
+start_remote_job (char **argv, char **envp, int stdin_fd,
+ int *is_remote, int *id_ptr, int *used_stdin)
+{
+ char waybill[MAX_DATA_SIZE], msg[128];
+ struct hostent *host;
+ struct timeval timeout;
+ struct sockaddr_in sin;
+ int len;
+ int retsock, retport, sock;
+ Rpc_Stat status;
+ int pid;
+
+ /* Create the return socket. */
+ retsock = Rpc_UdpCreate (True, 0);
+ if (retsock < 0)
+ {
+ O (error, NILF, "exporting: Couldn't create return socket.");
+ return 1;
+ }
+
+ /* Get the return socket's port number. */
+ len = sizeof (sin);
+ if (getsockname (retsock, (struct sockaddr *) &sin, &len) < 0)
+ {
+ (void) close (retsock);
+ perror_with_name ("exporting: ", "getsockname");
+ return 1;
+ }
+ retport = sin.sin_port;
+
+ /* Create the TCP socket for talking to the remote child. */
+ sock = Rpc_TcpCreate (False, 0);
+
+ /* Create a WayBill to give to the server. */
+ len = Customs_MakeWayBill (&permit, normalized_cwd, argv[0], argv,
+ envp, retport, waybill);
+
+ /* Modify the waybill as if the remote child had done 'child_access ()'. */
+ {
+ WayBill *wb = (WayBill *) waybill;
+ wb->ruid = wb->euid;
+ wb->rgid = wb->egid;
+ }
+
+ /* Send the request to the server, timing out in 20 seconds. */
+ timeout.tv_usec = 0;
+ timeout.tv_sec = 20;
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons (Customs_Port ());
+ sin.sin_addr = permit.addr;
+ status = Rpc_Call (sock, &sin, (Rpc_Proc) CUSTOMS_IMPORT,
+ len, (Rpc_Opaque) waybill,
+ sizeof (msg), (Rpc_Opaque) msg,
+ 1, &timeout);
+
+ host = gethostbyaddr ((char *)&permit.addr, sizeof(permit.addr), AF_INET);
+
+ {
+ const char *hnm = host ? host->h_name : inet_ntoa (permit.addr);
+ size_t hlen = strlen (hnm);
+
+ if (status != RPC_SUCCESS)
+ {
+ const char *err = Rpc_ErrorMessage (status);
+ (void) close (retsock);
+ (void) close (sock);
+ error (NILF, hlen + strlen (err),
+ "exporting to %s: %s", hnm, err);
+ return 1;
+ }
+ else if (msg[0] != 'O' || msg[1] != 'k' || msg[2] != '\0')
+ {
+ (void) close (retsock);
+ (void) close (sock);
+ error (NILF, hlen + strlen (msg), "exporting to %s: %s", hnm, msg);
+ return 1;
+ }
+ else
+ {
+ error (NILF, hlen + INTSTR_LENGTH,
+ "*** exported to %s (id %u)", hnm, permit.id);
+ }
+
+ fflush (stdout);
+ fflush (stderr);
+ }
+
+ pid = vfork ();
+ if (pid < 0)
+ {
+ /* The fork failed! */
+ perror_with_name ("fork", "");
+ return 1;
+ }
+ else if (pid == 0)
+ {
+ /* Child side. Run 'export' to handle the connection. */
+ static char sock_buf[20], retsock_buf[20], id_buf[20];
+ static char *new_argv[6] =
+ { EXPORT_COMMAND, "-id", sock_buf, retsock_buf, id_buf, 0 };
+
+ /* Set up the arguments. */
+ (void) sprintf (sock_buf, "%d", sock);
+ (void) sprintf (retsock_buf, "%d", retsock);
+ (void) sprintf (id_buf, "%x", permit.id);
+
+ /* Get the right stdin. */
+ if (stdin_fd != 0)
+ (void) dup2 (stdin_fd, 0);
+
+ /* Unblock signals in the child. */
+ unblock_sigs ();
+
+ /* Run the command. */
+ exec_command (new_argv, envp);
+ }
+
+ /* Parent side. Return the 'export' process's ID. */
+ (void) close (retsock);
+ (void) close (sock);
+ *is_remote = 0;
+ *id_ptr = pid;
+ *used_stdin = 1;
+ return 0;
+}
+
+/* Get the status of a dead remote child. Block waiting for one to die
+ if BLOCK is nonzero. Set *EXIT_CODE_PTR to the exit status, *SIGNAL_PTR
+ to the termination signal or zero if it exited normally, and *COREDUMP_PTR
+ nonzero if it dumped core. Return the ID of the child that died,
+ 0 if we would have to block and !BLOCK, or < 0 if there were none. */
+
+int
+remote_status (int *exit_code_ptr, int *signal_ptr, int *coredump_ptr,
+ int block)
+{
+ return -1;
+}
+
+/* Block asynchronous notification of remote child death.
+ If this notification is done by raising the child termination
+ signal, do not block that signal. */
+void
+block_remote_children (void)
+{
+ return;
+}
+
+/* Restore asynchronous notification of remote child death.
+ If this is done by raising the child termination signal,
+ do not unblock that signal. */
+void
+unblock_remote_children (void)
+{
+ return;
+}
+
+/* Send signal SIG to child ID. Return 0 if successful, -1 if not. */
+int
+remote_kill (int id, int sig)
+{
+ return -1;
+}
diff --git a/src/kmk/remote-stub.c b/src/kmk/remote-stub.c
new file mode 100644
index 0000000..8e31a20
--- /dev/null
+++ b/src/kmk/remote-stub.c
@@ -0,0 +1,99 @@
+/* Template for the remote job exportation interface to GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "job.h"
+#include "commands.h"
+
+
+char *remote_description = 0;
+
+/* Call once at startup even if no commands are run. */
+
+void
+remote_setup (void)
+{
+}
+
+/* Called before exit. */
+
+void
+remote_cleanup (void)
+{
+}
+
+/* Return nonzero if the next job should be done remotely. */
+
+int
+start_remote_job_p (int first_p UNUSED)
+{
+ return 0;
+}
+
+/* Start a remote job running the command in ARGV,
+ with environment from ENVP. It gets standard input from STDIN_FD. On
+ failure, return nonzero. On success, return zero, and set *USED_STDIN
+ to nonzero if it will actually use STDIN_FD, zero if not, set *ID_PTR to
+ a unique identification, and set *IS_REMOTE to zero if the job is local,
+ nonzero if it is remote (meaning *ID_PTR is a process ID). */
+
+int
+start_remote_job (char **argv UNUSED, char **envp UNUSED, int stdin_fd UNUSED,
+ int *is_remote UNUSED, int *id_ptr UNUSED,
+ int *used_stdin UNUSED)
+{
+ return -1;
+}
+
+/* Get the status of a dead remote child. Block waiting for one to die
+ if BLOCK is nonzero. Set *EXIT_CODE_PTR to the exit status, *SIGNAL_PTR
+ to the termination signal or zero if it exited normally, and *COREDUMP_PTR
+ nonzero if it dumped core. Return the ID of the child that died,
+ 0 if we would have to block and !BLOCK, or < 0 if there were none. */
+
+int
+remote_status (int *exit_code_ptr UNUSED, int *signal_ptr UNUSED,
+ int *coredump_ptr UNUSED, int block UNUSED)
+{
+ errno = ECHILD;
+ return -1;
+}
+
+/* Block asynchronous notification of remote child death.
+ If this notification is done by raising the child termination
+ signal, do not block that signal. */
+void
+block_remote_children (void)
+{
+ return;
+}
+
+/* Restore asynchronous notification of remote child death.
+ If this is done by raising the child termination signal,
+ do not unblock that signal. */
+void
+unblock_remote_children (void)
+{
+ return;
+}
+
+/* Send signal SIG to child ID. Return 0 if successful, -1 if not. */
+int
+remote_kill (int id UNUSED, int sig UNUSED)
+{
+ return -1;
+}
diff --git a/src/kmk/rule.c b/src/kmk/rule.c
new file mode 100644
index 0000000..5e4f5d9
--- /dev/null
+++ b/src/kmk/rule.c
@@ -0,0 +1,546 @@
+/* Pattern and suffix rule internals for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+
+static void freerule (struct rule *rule, struct rule *lastrule);
+
+/* Chain of all pattern rules. */
+
+struct rule *pattern_rules;
+
+/* Pointer to last rule in the chain, so we can add onto the end. */
+
+struct rule *last_pattern_rule;
+
+/* Number of rules in the chain. */
+
+unsigned int num_pattern_rules;
+
+/* Maximum number of target patterns of any pattern rule. */
+
+unsigned int max_pattern_targets;
+
+/* Maximum number of dependencies of any pattern rule. */
+
+unsigned int max_pattern_deps;
+
+/* Maximum length of the name of a dependencies of any pattern rule. */
+
+unsigned int max_pattern_dep_length;
+
+/* Pointer to structure for the file .SUFFIXES
+ whose dependencies are the suffixes to be searched. */
+
+struct file *suffix_file;
+
+/* Maximum length of a suffix. */
+
+unsigned int maxsuffix;
+
+/* Compute the maximum dependency length and maximum number of
+ dependencies of all implicit rules. Also sets the subdir
+ flag for a rule when appropriate, possibly removing the rule
+ completely when appropriate. */
+
+void
+count_implicit_rule_limits (void)
+{
+ char *name;
+ int namelen;
+ struct rule *rule;
+
+ num_pattern_rules = max_pattern_targets = max_pattern_deps = 0;
+ max_pattern_dep_length = 0;
+
+ name = 0;
+ namelen = 0;
+ rule = pattern_rules;
+ while (rule != 0)
+ {
+ unsigned int ndeps = 0;
+ struct dep *dep;
+ struct rule *next = rule->next;
+
+ ++num_pattern_rules;
+
+ if (rule->num > max_pattern_targets)
+ max_pattern_targets = rule->num;
+
+ for (dep = rule->deps; dep != 0; dep = dep->next)
+ {
+ const char *dname = dep_name (dep);
+ unsigned int len = strlen (dname);
+
+#ifdef VMS
+ const char *p = strrchr (dname, ']');
+ const char *p2;
+ if (p == 0)
+ p = strrchr (dname, ':');
+ p2 = p != 0 ? strchr (dname, '%') : 0;
+#else
+ const char *p = strrchr (dname, '/');
+ const char *p2 = p != 0 ? strchr (dname, '%') : 0;
+#endif
+ ndeps++;
+
+ if (len > max_pattern_dep_length)
+ max_pattern_dep_length = len;
+
+ if (p != 0 && p2 > p)
+ {
+ /* There is a slash before the % in the dep name.
+ Extract the directory name. */
+ if (p == dname)
+ ++p;
+ if (p - dname > namelen)
+ {
+ namelen = p - dname;
+ name = xrealloc (name, namelen + 1);
+ }
+ memcpy (name, dname, p - dname);
+ name[p - dname] = '\0';
+
+ /* In the deps of an implicit rule the 'changed' flag
+ actually indicates that the dependency is in a
+ nonexistent subdirectory. */
+
+ dep->changed = !dir_file_exists_p (name, "");
+ }
+ else
+ /* This dependency does not reside in a subdirectory. */
+ dep->changed = 0;
+ }
+
+ if (ndeps > max_pattern_deps)
+ max_pattern_deps = ndeps;
+
+ rule = next;
+ }
+
+ free (name);
+}
+
+/* Create a pattern rule from a suffix rule.
+ TARGET is the target suffix; SOURCE is the source suffix.
+ CMDS are the commands.
+ If TARGET is nil, it means the target pattern should be '(%.o)'.
+ If SOURCE is nil, it means there should be no deps. */
+
+static void
+convert_suffix_rule (const char *target, const char *source,
+ struct commands *cmds)
+{
+ const char **names, **percents;
+ struct dep *deps;
+
+ names = xmalloc (sizeof (const char *));
+ percents = xmalloc (sizeof (const char *));
+
+ if (target == 0)
+ {
+ /* Special case: TARGET being nil means we are defining a '.X.a' suffix
+ rule; the target pattern is always '(%.o)'. */
+#ifdef VMS
+ *names = strcache_add_len ("(%.obj)", 7);
+#else
+ *names = strcache_add_len ("(%.o)", 5);
+#endif
+ *percents = *names + 1;
+ }
+ else
+ {
+ /* Construct the target name. */
+ unsigned int len = strlen (target);
+ char *p = alloca (1 + len + 1);
+ p[0] = '%';
+ memcpy (p + 1, target, len + 1);
+ *names = strcache_add_len (p, len + 1);
+ *percents = *names;
+ }
+
+ if (source == 0)
+ deps = 0;
+ else
+ {
+ /* Construct the dependency name. */
+ unsigned int len = strlen (source);
+ char *p = alloca (1 + len + 1);
+ p[0] = '%';
+ memcpy (p + 1, source, len + 1);
+ deps = alloc_dep ();
+ deps->name = strcache_add_len (p, len + 1);
+ }
+
+ create_pattern_rule (names, percents, 1, 0, deps, cmds, 0);
+}
+
+/* Convert old-style suffix rules to pattern rules.
+ All rules for the suffixes on the .SUFFIXES list are converted and added to
+ the chain of pattern rules. */
+
+void
+convert_to_pattern (void)
+{
+ struct dep *d, *d2;
+ char *rulename;
+
+ /* We will compute every potential suffix rule (.x.y) from the list of
+ suffixes in the .SUFFIXES target's dependencies and see if it exists.
+ First find the longest of the suffixes. */
+
+ maxsuffix = 0;
+ for (d = suffix_file->deps; d != 0; d = d->next)
+ {
+ unsigned int l = strlen (dep_name (d));
+ if (l > maxsuffix)
+ maxsuffix = l;
+ }
+
+ /* Space to construct the suffix rule target name. */
+ rulename = alloca ((maxsuffix * 2) + 1);
+
+ for (d = suffix_file->deps; d != 0; d = d->next)
+ {
+ unsigned int slen;
+
+ /* Make a rule that is just the suffix, with no deps or commands.
+ This rule exists solely to disqualify match-anything rules. */
+ convert_suffix_rule (dep_name (d), 0, 0);
+
+ if (d->file->cmds != 0)
+ /* Record a pattern for this suffix's null-suffix rule. */
+ convert_suffix_rule ("", dep_name (d), d->file->cmds);
+
+ /* Add every other suffix to this one and see if it exists as a
+ two-suffix rule. */
+ slen = strlen (dep_name (d));
+ memcpy (rulename, dep_name (d), slen);
+
+ for (d2 = suffix_file->deps; d2 != 0; d2 = d2->next)
+ {
+ struct file *f;
+ unsigned int s2len;
+
+ s2len = strlen (dep_name (d2));
+
+ /* Can't build something from itself. */
+ if (slen == s2len && streq (dep_name (d), dep_name (d2)))
+ continue;
+
+ memcpy (rulename + slen, dep_name (d2), s2len + 1);
+ f = lookup_file (rulename);
+ if (f == 0 || f->cmds == 0)
+ continue;
+
+ if (s2len == 2 && rulename[slen] == '.' && rulename[slen + 1] == 'a')
+ /* A suffix rule '.X.a:' generates the pattern rule '(%.o): %.X'.
+ It also generates a normal '%.a: %.X' rule below. */
+ convert_suffix_rule (NULL, /* Indicates '(%.o)'. */
+ dep_name (d),
+ f->cmds);
+
+ /* The suffix rule '.X.Y:' is converted
+ to the pattern rule '%.Y: %.X'. */
+ convert_suffix_rule (dep_name (d2), dep_name (d), f->cmds);
+ }
+ }
+}
+
+
+/* Install the pattern rule RULE (whose fields have been filled in) at the end
+ of the list (so that any rules previously defined will take precedence).
+ If this rule duplicates a previous one (identical target and dependencies),
+ the old one is replaced if OVERRIDE is nonzero, otherwise this new one is
+ thrown out. When an old rule is replaced, the new one is put at the end of
+ the list. Return nonzero if RULE is used; zero if not. */
+
+static int
+new_pattern_rule (struct rule *rule, int override)
+{
+ struct rule *r, *lastrule;
+ unsigned int i, j;
+
+ rule->in_use = 0;
+ rule->terminal = 0;
+
+ rule->next = 0;
+
+ /* Search for an identical rule. */
+ lastrule = 0;
+ for (r = pattern_rules; r != 0; lastrule = r, r = r->next)
+ for (i = 0; i < rule->num; ++i)
+ {
+ for (j = 0; j < r->num; ++j)
+ if (!streq (rule->targets[i], r->targets[j]))
+ break;
+ /* If all the targets matched... */
+ if (j == r->num)
+ {
+ struct dep *d, *d2;
+ for (d = rule->deps, d2 = r->deps;
+ d != 0 && d2 != 0; d = d->next, d2 = d2->next)
+ if (!streq (dep_name (d), dep_name (d2)))
+ break;
+ if (d == 0 && d2 == 0)
+ {
+ /* All the dependencies matched. */
+ if (override)
+ {
+ /* Remove the old rule. */
+ freerule (r, lastrule);
+ /* Install the new one. */
+ if (pattern_rules == 0)
+ pattern_rules = rule;
+ else
+ last_pattern_rule->next = rule;
+ last_pattern_rule = rule;
+
+ /* We got one. Stop looking. */
+ goto matched;
+ }
+ else
+ {
+ /* The old rule stays intact. Destroy the new one. */
+ freerule (rule, (struct rule *) 0);
+ return 0;
+ }
+ }
+ }
+ }
+
+ matched:;
+
+ if (r == 0)
+ {
+ /* There was no rule to replace. */
+ if (pattern_rules == 0)
+ pattern_rules = rule;
+ else
+ last_pattern_rule->next = rule;
+ last_pattern_rule = rule;
+ }
+
+ return 1;
+}
+
+
+/* Install an implicit pattern rule based on the three text strings
+ in the structure P points to. These strings come from one of
+ the arrays of default implicit pattern rules.
+ TERMINAL specifies what the 'terminal' field of the rule should be. */
+
+void
+install_pattern_rule (struct pspec *p, int terminal)
+{
+ struct rule *r;
+ const char *ptr;
+
+ r = xmalloc (sizeof (struct rule));
+
+ r->num = 1;
+ r->targets = xmalloc (sizeof (const char *));
+ r->suffixes = xmalloc (sizeof (const char *));
+ r->lens = xmalloc (sizeof (unsigned int));
+
+ r->lens[0] = strlen (p->target);
+ r->targets[0] = p->target;
+ r->suffixes[0] = find_percent_cached (&r->targets[0]);
+ assert (r->suffixes[0] != NULL);
+ ++r->suffixes[0];
+
+ ptr = p->dep;
+ r->deps = PARSE_SIMPLE_SEQ ((char **)&ptr, struct dep);
+
+ if (new_pattern_rule (r, 0))
+ {
+ r->terminal = terminal;
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ r->cmds = xmalloc (sizeof (struct commands));
+#else
+ r->cmds = alloccache_alloc (&commands_cache);
+#endif
+ r->cmds->fileinfo.filenm = 0;
+ r->cmds->fileinfo.lineno = 0;
+ r->cmds->fileinfo.offset = 0;
+ /* These will all be string literals, but we malloc space for them
+ anyway because somebody might want to free them later. */
+ r->cmds->commands = xstrdup (p->commands);
+ r->cmds->command_lines = 0;
+ r->cmds->recipe_prefix = RECIPEPREFIX_DEFAULT;
+
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ r->cmds->refs = 1000;
+#endif
+ }
+}
+
+
+/* Free all the storage used in RULE and take it out of the
+ pattern_rules chain. LASTRULE is the rule whose next pointer
+ points to RULE. */
+
+static void
+freerule (struct rule *rule, struct rule *lastrule)
+{
+ struct rule *next = rule->next;
+
+ free_dep_chain (rule->deps);
+
+ /* MSVC erroneously warns without a cast here. */
+ free ((void *)rule->targets);
+ free ((void *)rule->suffixes);
+ free (rule->lens);
+
+ /* We can't free the storage for the commands because there
+ are ways that they could be in more than one place:
+ * If the commands came from a suffix rule, they could also be in
+ the 'struct file's for other suffix rules or plain targets given
+ on the same makefile line.
+ * If two suffixes that together make a two-suffix rule were each
+ given twice in the .SUFFIXES list, and in the proper order, two
+ identical pattern rules would be created and the second one would
+ be discarded here, but both would contain the same 'struct commands'
+ pointer from the 'struct file' for the suffix rule. */
+
+ free (rule);
+
+ if (pattern_rules == rule)
+ if (lastrule != 0)
+ abort ();
+ else
+ pattern_rules = next;
+ else if (lastrule != 0)
+ lastrule->next = next;
+ if (last_pattern_rule == rule)
+ last_pattern_rule = lastrule;
+}
+
+/* Create a new pattern rule with the targets in the nil-terminated array
+ TARGETS. TARGET_PERCENTS is an array of pointers to the % in each element
+ of TARGETS. N is the number of items in the array (not counting the nil
+ element). The new rule has dependencies DEPS and commands from COMMANDS.
+ It is a terminal rule if TERMINAL is nonzero. This rule overrides
+ identical rules with different commands if OVERRIDE is nonzero.
+
+ The storage for TARGETS and its elements and TARGET_PERCENTS is used and
+ must not be freed until the rule is destroyed. */
+
+void
+create_pattern_rule (const char **targets, const char **target_percents,
+ unsigned int n, int terminal, struct dep *deps,
+ struct commands *commands, int override)
+{
+ unsigned int i;
+ struct rule *r = xmalloc (sizeof (struct rule));
+
+ r->num = n;
+ r->cmds = commands;
+ r->deps = deps;
+ r->targets = targets;
+ r->suffixes = target_percents;
+ r->lens = xmalloc (n * sizeof (unsigned int));
+
+ for (i = 0; i < n; ++i)
+ {
+ r->lens[i] = strlen (targets[i]);
+ assert (r->suffixes[i] != NULL);
+ ++r->suffixes[i];
+ }
+
+ if (new_pattern_rule (r, override))
+ r->terminal = terminal;
+#ifdef CONFIG_WITH_MEMORY_OPTIMIZATIONS
+ if (commands != NULL)
+ commands->refs = 1000;
+#endif
+}
+
+/* Print the data base of rules. */
+
+static void /* Useful to call from gdb. */
+print_rule (struct rule *r)
+{
+ unsigned int i;
+
+ for (i = 0; i < r->num; ++i)
+ {
+ fputs (r->targets[i], stdout);
+ putchar ((i + 1 == r->num) ? ':' : ' ');
+ }
+ if (r->terminal)
+ putchar (':');
+
+ print_prereqs (r->deps);
+
+ if (r->cmds != 0)
+ print_commands (r->cmds);
+}
+
+void
+print_rule_data_base (void)
+{
+ unsigned int rules, terminal;
+ struct rule *r;
+
+ puts (_("\n# Implicit Rules"));
+
+ rules = terminal = 0;
+ for (r = pattern_rules; r != 0; r = r->next)
+ {
+ ++rules;
+
+ putchar ('\n');
+ print_rule (r);
+
+ if (r->terminal)
+ ++terminal;
+ }
+
+ if (rules == 0)
+ puts (_("\n# No implicit rules."));
+ else
+ {
+ printf (_("\n# %u implicit rules, %u"), rules, terminal);
+#ifndef NO_FLOAT
+ printf (" (%.1f%%)", (double) terminal / (double) rules * 100.0);
+#else
+ {
+ int f = (terminal * 1000 + 5) / rules;
+ printf (" (%d.%d%%)", f/10, f%10);
+ }
+#endif
+ puts (_(" terminal."));
+ }
+
+ if (num_pattern_rules != rules)
+ {
+ /* This can happen if a fatal error was detected while reading the
+ makefiles and thus count_implicit_rule_limits wasn't called yet. */
+ if (num_pattern_rules != 0)
+ ONN (fatal, NILF, _("BUG: num_pattern_rules is wrong! %u != %u"),
+ num_pattern_rules, rules);
+ }
+}
diff --git a/src/kmk/rule.h b/src/kmk/rule.h
new file mode 100644
index 0000000..9156b8e
--- /dev/null
+++ b/src/kmk/rule.h
@@ -0,0 +1,58 @@
+/* Definitions for using pattern rules in GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Structure used for pattern (implicit) rules. */
+
+struct rule
+ {
+ struct rule *next;
+ const char **targets; /* Targets of the rule. */
+ unsigned int *lens; /* Lengths of each target. */
+ const char **suffixes; /* Suffixes (after '%') of each target. */
+ struct dep *deps; /* Dependencies of the rule. */
+ struct commands *cmds; /* Commands to execute. */
+ unsigned short num; /* Number of targets. */
+ char terminal; /* If terminal (double-colon). */
+ char in_use; /* If in use by a parent pattern_search. */
+ };
+
+/* For calling install_pattern_rule. */
+struct pspec
+ {
+ const char *target, *dep, *commands;
+ };
+
+
+extern struct rule *pattern_rules;
+extern struct rule *last_pattern_rule;
+extern unsigned int num_pattern_rules;
+
+extern unsigned int max_pattern_deps;
+extern unsigned int max_pattern_targets;
+extern unsigned int max_pattern_dep_length;
+
+extern struct file *suffix_file;
+extern unsigned int maxsuffix;
+
+
+void count_implicit_rule_limits (void);
+void convert_to_pattern (void);
+void install_pattern_rule (struct pspec *p, int terminal);
+void create_pattern_rule (const char **targets, const char **target_percents,
+ unsigned int num, int terminal, struct dep *deps,
+ struct commands *commands, int override);
+void print_rule_data_base (void);
diff --git a/src/kmk/signame.c b/src/kmk/signame.c
new file mode 100644
index 0000000..55646e9
--- /dev/null
+++ b/src/kmk/signame.c
@@ -0,0 +1,254 @@
+/* Convert between signal names and numbers.
+Copyright (C) 1990-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+/* If the system provides strsignal, we don't need it. */
+
+#if !HAVE_STRSIGNAL
+
+/* If the system provides sys_siglist, we'll use that.
+ Otherwise create our own.
+ */
+
+#if !HAVE_DECL_SYS_SIGLIST
+
+/* Some systems do not define NSIG in <signal.h>. */
+#ifndef NSIG
+#ifdef _NSIG
+#define NSIG _NSIG
+#else
+#define NSIG 32
+#endif
+#endif
+
+/* There is too much variation in Sys V signal numbers and names, so
+ we must initialize them at runtime. */
+
+static const char *undoc;
+
+static const char *sys_siglist[NSIG];
+
+/* Table of abbreviations for signals. Note: A given number can
+ appear more than once with different abbreviations. */
+#define SIG_TABLE_SIZE (NSIG*2)
+
+typedef struct
+ {
+ int number;
+ const char *abbrev;
+ } num_abbrev;
+
+static num_abbrev sig_table[SIG_TABLE_SIZE];
+
+/* Number of elements of sig_table used. */
+static int sig_table_nelts = 0;
+
+/* Enter signal number NUMBER into the tables with ABBREV and NAME. */
+
+static void
+init_sig (int number, const char *abbrev, const char *name)
+{
+ /* If this value is ever greater than NSIG it seems like it'd be a bug in
+ the system headers, but... better safe than sorry. We know, for
+ example, that this isn't always true on VMS. */
+
+ if (number >= 0 && number < NSIG)
+ sys_siglist[number] = name;
+
+ if (sig_table_nelts < SIG_TABLE_SIZE)
+ {
+ sig_table[sig_table_nelts].number = number;
+ sig_table[sig_table_nelts++].abbrev = abbrev;
+ }
+}
+
+static int
+signame_init (void)
+{
+ int i;
+
+ undoc = xstrdup (_("unknown signal"));
+
+ /* Initialize signal names. */
+ for (i = 0; i < NSIG; i++)
+ sys_siglist[i] = undoc;
+
+ /* Initialize signal names. */
+#if defined (SIGHUP)
+ init_sig (SIGHUP, "HUP", _("Hangup"));
+#endif
+#if defined (SIGINT)
+ init_sig (SIGINT, "INT", _("Interrupt"));
+#endif
+#if defined (SIGQUIT)
+ init_sig (SIGQUIT, "QUIT", _("Quit"));
+#endif
+#if defined (SIGILL)
+ init_sig (SIGILL, "ILL", _("Illegal Instruction"));
+#endif
+#if defined (SIGTRAP)
+ init_sig (SIGTRAP, "TRAP", _("Trace/breakpoint trap"));
+#endif
+ /* If SIGIOT == SIGABRT, we want to print it as SIGABRT because
+ SIGABRT is in ANSI and POSIX.1 and SIGIOT isn't. */
+#if defined (SIGABRT)
+ init_sig (SIGABRT, "ABRT", _("Aborted"));
+#endif
+#if defined (SIGIOT)
+ init_sig (SIGIOT, "IOT", _("IOT trap"));
+#endif
+#if defined (SIGEMT)
+ init_sig (SIGEMT, "EMT", _("EMT trap"));
+#endif
+#if defined (SIGFPE)
+ init_sig (SIGFPE, "FPE", _("Floating point exception"));
+#endif
+#if defined (SIGKILL)
+ init_sig (SIGKILL, "KILL", _("Killed"));
+#endif
+#if defined (SIGBUS)
+ init_sig (SIGBUS, "BUS", _("Bus error"));
+#endif
+#if defined (SIGSEGV)
+ init_sig (SIGSEGV, "SEGV", _("Segmentation fault"));
+#endif
+#if defined (SIGSYS)
+ init_sig (SIGSYS, "SYS", _("Bad system call"));
+#endif
+#if defined (SIGPIPE)
+ init_sig (SIGPIPE, "PIPE", _("Broken pipe"));
+#endif
+#if defined (SIGALRM)
+ init_sig (SIGALRM, "ALRM", _("Alarm clock"));
+#endif
+#if defined (SIGTERM)
+ init_sig (SIGTERM, "TERM", _("Terminated"));
+#endif
+#if defined (SIGUSR1)
+ init_sig (SIGUSR1, "USR1", _("User defined signal 1"));
+#endif
+#if defined (SIGUSR2)
+ init_sig (SIGUSR2, "USR2", _("User defined signal 2"));
+#endif
+ /* If SIGCLD == SIGCHLD, we want to print it as SIGCHLD because that
+ is what is in POSIX.1. */
+#if defined (SIGCHLD)
+ init_sig (SIGCHLD, "CHLD", _("Child exited"));
+#endif
+#if defined (SIGCLD)
+ init_sig (SIGCLD, "CLD", _("Child exited"));
+#endif
+#if defined (SIGPWR)
+ init_sig (SIGPWR, "PWR", _("Power failure"));
+#endif
+#if defined (SIGTSTP)
+ init_sig (SIGTSTP, "TSTP", _("Stopped"));
+#endif
+#if defined (SIGTTIN)
+ init_sig (SIGTTIN, "TTIN", _("Stopped (tty input)"));
+#endif
+#if defined (SIGTTOU)
+ init_sig (SIGTTOU, "TTOU", _("Stopped (tty output)"));
+#endif
+#if defined (SIGSTOP)
+ init_sig (SIGSTOP, "STOP", _("Stopped (signal)"));
+#endif
+#if defined (SIGXCPU)
+ init_sig (SIGXCPU, "XCPU", _("CPU time limit exceeded"));
+#endif
+#if defined (SIGXFSZ)
+ init_sig (SIGXFSZ, "XFSZ", _("File size limit exceeded"));
+#endif
+#if defined (SIGVTALRM)
+ init_sig (SIGVTALRM, "VTALRM", _("Virtual timer expired"));
+#endif
+#if defined (SIGPROF)
+ init_sig (SIGPROF, "PROF", _("Profiling timer expired"));
+#endif
+#if defined (SIGWINCH)
+ /* "Window size changed" might be more accurate, but even if that
+ is all that it means now, perhaps in the future it will be
+ extended to cover other kinds of window changes. */
+ init_sig (SIGWINCH, "WINCH", _("Window changed"));
+#endif
+#if defined (SIGCONT)
+ init_sig (SIGCONT, "CONT", _("Continued"));
+#endif
+#if defined (SIGURG)
+ init_sig (SIGURG, "URG", _("Urgent I/O condition"));
+#endif
+#if defined (SIGIO)
+ /* "I/O pending" has also been suggested. A disadvantage is that signal
+ only happens when the process has asked for it, not every time I/O is
+ pending. Another disadvantage is the confusion from giving it a
+ different name than under Unix. */
+ init_sig (SIGIO, "IO", _("I/O possible"));
+#endif
+#if defined (SIGWIND)
+ init_sig (SIGWIND, "WIND", _("SIGWIND"));
+#endif
+#if defined (SIGPHONE)
+ init_sig (SIGPHONE, "PHONE", _("SIGPHONE"));
+#endif
+#if defined (SIGPOLL)
+ init_sig (SIGPOLL, "POLL", _("I/O possible"));
+#endif
+#if defined (SIGLOST)
+ init_sig (SIGLOST, "LOST", _("Resource lost"));
+#endif
+#if defined (SIGDANGER)
+ init_sig (SIGDANGER, "DANGER", _("Danger signal"));
+#endif
+#if defined (SIGINFO)
+ init_sig (SIGINFO, "INFO", _("Information request"));
+#endif
+#if defined (SIGNOFP)
+ init_sig (SIGNOFP, "NOFP", _("Floating point co-processor not available"));
+#endif
+
+ return 1;
+}
+
+#endif /* HAVE_DECL_SYS_SIGLIST */
+
+
+char *
+strsignal (int sig)
+{
+ static char buf[] = "Signal 12345678901234567890";
+
+#if ! HAVE_DECL_SYS_SIGLIST
+# if HAVE_DECL__SYS_SIGLIST
+# define sys_siglist _sys_siglist
+# elif HAVE_DECL___SYS_SIGLIST
+# define sys_siglist __sys_siglist
+# else
+ static char sig_initted = 0;
+
+ if (!sig_initted)
+ sig_initted = signame_init ();
+# endif
+#endif
+
+ if (sig > 0 && sig < NSIG)
+ return (char *) sys_siglist[sig];
+
+ sprintf (buf, "Signal %d", sig);
+ return buf;
+}
+
+#endif /* HAVE_STRSIGNAL */
diff --git a/src/kmk/strcache.c b/src/kmk/strcache.c
new file mode 100644
index 0000000..6f5c500
--- /dev/null
+++ b/src/kmk/strcache.c
@@ -0,0 +1,368 @@
+/* Constant string caching for GNU Make.
+Copyright (C) 2006-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#ifndef CONFIG_WITH_STRCACHE2
+
+#include <stddef.h>
+#include <assert.h>
+
+#include "hash.h"
+
+/* A string cached here will never be freed, so we don't need to worry about
+ reference counting. We just store the string, and then remember it in a
+ hash so it can be looked up again. */
+
+typedef unsigned short int sc_buflen_t;
+
+struct strcache {
+ struct strcache *next; /* The next block of strings. Must be first! */
+ sc_buflen_t end; /* Offset to the beginning of free space. */
+ sc_buflen_t bytesfree; /* Free space left in this buffer. */
+ sc_buflen_t count; /* # of strings in this buffer (for stats). */
+ char buffer[1]; /* The buffer comes after this. */
+};
+
+/* The size (in bytes) of each cache buffer.
+ Try to pick something that will map well into the heap.
+ This must be able to be represented by a short int (<=65535). */
+#define CACHE_BUFFER_BASE (8192)
+#define CACHE_BUFFER_ALLOC(_s) ((_s) - (2 * sizeof (size_t)))
+#define CACHE_BUFFER_OFFSET (offsetof (struct strcache, buffer))
+#define CACHE_BUFFER_SIZE(_s) (CACHE_BUFFER_ALLOC(_s) - CACHE_BUFFER_OFFSET)
+#define BUFSIZE CACHE_BUFFER_SIZE (CACHE_BUFFER_BASE)
+
+static struct strcache *strcache = NULL;
+static struct strcache *fullcache = NULL;
+
+static unsigned long total_buffers = 0;
+static unsigned long total_strings = 0;
+static unsigned long total_size = 0;
+
+/* Add a new buffer to the cache. Add it at the front to reduce search time.
+ This can also increase the overhead, since it's less likely that older
+ buffers will be filled in. However, GNU make has so many smaller strings
+ that this doesn't seem to be much of an issue in practice.
+ */
+static struct strcache *
+new_cache (struct strcache **head, sc_buflen_t buflen)
+{
+ struct strcache *new = xmalloc (buflen + CACHE_BUFFER_OFFSET);
+ new->end = 0;
+ new->count = 0;
+ new->bytesfree = buflen;
+
+ new->next = *head;
+ *head = new;
+
+ ++total_buffers;
+ return new;
+}
+
+static const char *
+copy_string (struct strcache *sp, const char *str, unsigned int len)
+{
+ /* Add the string to this cache. */
+ char *res = &sp->buffer[sp->end];
+
+ memmove (res, str, len);
+ res[len++] = '\0';
+ sp->end += len;
+ sp->bytesfree -= len;
+ ++sp->count;
+
+ return res;
+}
+
+static const char *
+add_string (const char *str, unsigned int len)
+{
+ const char *res;
+ struct strcache *sp;
+ struct strcache **spp = &strcache;
+ /* We need space for the nul char. */
+ unsigned int sz = len + 1;
+
+ ++total_strings;
+ total_size += sz;
+
+ /* If the string we want is too large to fit into a single buffer, then
+ no existing cache is large enough. Add it directly to the fullcache. */
+ if (sz > BUFSIZE)
+ {
+ sp = new_cache (&fullcache, sz);
+ return copy_string (sp, str, len);
+ }
+
+ /* Find the first cache with enough free space. */
+ for (; *spp != NULL; spp = &(*spp)->next)
+ if ((*spp)->bytesfree > sz)
+ break;
+ sp = *spp;
+
+ /* If nothing is big enough, make a new cache at the front. */
+ if (sp == NULL)
+ {
+ sp = new_cache (&strcache, BUFSIZE);
+ spp = &strcache;
+ }
+
+ /* Add the string to this cache. */
+ res = copy_string (sp, str, len);
+
+ /* If the amount free in this cache is less than the average string size,
+ consider it full and move it to the full list. */
+ if (total_strings > 20 && sp->bytesfree < (total_size / total_strings) + 1)
+ {
+ *spp = sp->next;
+ sp->next = fullcache;
+ fullcache = sp;
+ }
+
+ return res;
+}
+
+/* For strings too large for the strcache, we just save them in a list. */
+struct hugestring {
+ struct hugestring *next; /* The next string. */
+ char buffer[1]; /* The string. */
+};
+
+static struct hugestring *hugestrings = NULL;
+
+static const char *
+add_hugestring (const char *str, unsigned int len)
+{
+ struct hugestring *new = xmalloc (sizeof (struct hugestring) + len);
+ memcpy (new->buffer, str, len);
+ new->buffer[len] = '\0';
+
+ new->next = hugestrings;
+ hugestrings = new;
+
+ return new->buffer;
+}
+
+/* Hash table of strings in the cache. */
+
+static unsigned long
+str_hash_1 (const void *key)
+{
+ return_ISTRING_HASH_1 ((const char *) key);
+}
+
+static unsigned long
+str_hash_2 (const void *key)
+{
+ return_ISTRING_HASH_2 ((const char *) key);
+}
+
+static int
+str_hash_cmp (const void *x, const void *y)
+{
+ return_ISTRING_COMPARE ((const char *) x, (const char *) y);
+}
+
+static struct hash_table strings;
+static unsigned long total_adds = 0;
+
+static const char *
+add_hash (const char *str, unsigned int len)
+{
+ char *const *slot;
+ const char *key;
+
+ /* If it's too large for the string cache, just copy it.
+ We don't bother trying to match these. */
+ if (len > USHRT_MAX - 1)
+ return add_hugestring (str, len);
+
+ /* Look up the string in the hash. If it's there, return it. */
+ slot = (char *const *) hash_find_slot (&strings, str);
+ key = *slot;
+
+ /* Count the total number of add operations we performed. */
+ ++total_adds;
+
+ if (!HASH_VACANT (key))
+ return key;
+
+ /* Not there yet so add it to a buffer, then into the hash table. */
+ key = add_string (str, len);
+ hash_insert_at (&strings, key, slot);
+ return key;
+}
+
+/* Returns true if the string is in the cache; false if not. */
+int
+strcache_iscached (const char *str)
+{
+ struct strcache *sp;
+
+ for (sp = strcache; sp != 0; sp = sp->next)
+ if (str >= sp->buffer && str < sp->buffer + sp->end)
+ return 1;
+ for (sp = fullcache; sp != 0; sp = sp->next)
+ if (str >= sp->buffer && str < sp->buffer + sp->end)
+ return 1;
+
+ {
+ struct hugestring *hp;
+ for (hp = hugestrings; hp != 0; hp = hp->next)
+ if (str == hp->buffer)
+ return 1;
+ }
+
+ return 0;
+}
+
+/* If the string is already in the cache, return a pointer to the cached
+ version. If not, add it then return a pointer to the cached version.
+ Note we do NOT take control of the string passed in. */
+const char *
+strcache_add (const char *str)
+{
+ return add_hash (str, strlen (str));
+}
+
+const char *
+strcache_add_len (const char *str, unsigned int len)
+{
+ /* If we're not given a nul-terminated string we have to create one, because
+ the hashing functions expect it. */
+ if (str[len] != '\0')
+ {
+ char *key = alloca (len + 1);
+ memcpy (key, str, len);
+ key[len] = '\0';
+ str = key;
+ }
+
+ return add_hash (str, len);
+}
+
+void
+strcache_init (void)
+{
+ hash_init (&strings, 8000, str_hash_1, str_hash_2, str_hash_cmp);
+}
+
+
+/* Generate some stats output. */
+
+void
+strcache_print_stats (const char *prefix)
+{
+ const struct strcache *sp;
+ unsigned long numbuffs = 0, fullbuffs = 0;
+ unsigned long totfree = 0, maxfree = 0, minfree = BUFSIZE;
+
+ if (! strcache)
+ {
+ printf (_("\n%s No strcache buffers\n"), prefix);
+ return;
+ }
+
+ /* Count the first buffer separately since it's not full. */
+ for (sp = strcache->next; sp != NULL; sp = sp->next)
+ {
+ sc_buflen_t bf = sp->bytesfree;
+
+ totfree += bf;
+ maxfree = (bf > maxfree ? bf : maxfree);
+ minfree = (bf < minfree ? bf : minfree);
+
+ ++numbuffs;
+ }
+ for (sp = fullcache; sp != NULL; sp = sp->next)
+ {
+ sc_buflen_t bf = sp->bytesfree;
+
+ totfree += bf;
+ maxfree = (bf > maxfree ? bf : maxfree);
+ minfree = (bf < minfree ? bf : minfree);
+
+ ++numbuffs;
+ ++fullbuffs;
+ }
+
+ /* Make sure we didn't lose any buffers. */
+ assert (total_buffers == numbuffs + 1);
+
+ printf (_("\n%s strcache buffers: %lu (%lu) / strings = %lu / storage = %lu B / avg = %lu B\n"),
+ prefix, numbuffs + 1, fullbuffs, total_strings, total_size,
+ (total_size / total_strings));
+
+ printf (_("%s current buf: size = %hu B / used = %hu B / count = %hu / avg = %hu B\n"),
+ prefix, (sc_buflen_t)BUFSIZE, strcache->end, strcache->count,
+ (strcache->end / strcache->count));
+
+ if (numbuffs)
+ {
+ /* Show information about non-current buffers. */
+ unsigned long sz = total_size - strcache->end;
+ unsigned long cnt = total_strings - strcache->count;
+ sc_buflen_t avgfree = totfree / numbuffs;
+
+ printf (_("%s other used: total = %lu B / count = %lu / avg = %lu B\n"),
+ prefix, sz, cnt, sz / cnt);
+
+ printf (_("%s other free: total = %lu B / max = %lu B / min = %lu B / avg = %hu B\n"),
+ prefix, totfree, maxfree, minfree, avgfree);
+ }
+
+ printf (_("\n%s strcache performance: lookups = %lu / hit rate = %lu%%\n"),
+ prefix, total_adds, (long unsigned)(100.0 * (total_adds - total_strings) / total_adds));
+ fputs (_("# hash-table stats:\n# "), stdout);
+ hash_print_stats (&strings, stdout);
+}
+
+#else /* CONFIG_WITH_STRCACHE2 */
+
+#include "strcache2.h"
+
+const char *suffixes_strcached;
+
+/* The file string cache. */
+struct strcache2 file_strcache;
+
+void strcache_init (void)
+{
+ strcache2_init(&file_strcache,
+ "file", /* name */
+ 131072, /* hash size */
+ 0, /* default segment size*/
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ 1, /* case insensitive */
+#else
+ 0, /* case insensitive */
+#endif
+ 0); /* thread safe */
+
+ /* .SUFFIXES is referenced in several loops, keep the added pointer in a
+ global var so these can be optimized. */
+
+ suffixes_strcached = strcache_add_len (".SUFFIXES", sizeof (".SUFFIXES")-1);
+}
+
+void
+strcache_print_stats (const char *prefix)
+{
+ strcache2_print_stats (&file_strcache, prefix);
+}
+
+#endif /* CONFIG_WITH_STRCACHE2 */
+
diff --git a/src/kmk/strcache2.c b/src/kmk/strcache2.c
new file mode 100644
index 0000000..c2bad20
--- /dev/null
+++ b/src/kmk/strcache2.c
@@ -0,0 +1,1327 @@
+/* $Id: strcache2.c 3140 2018-03-14 21:28:10Z bird $ */
+/** @file
+ * strcache2 - New string cache.
+ */
+
+/*
+ * Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "makeint.h"
+#include "strcache2.h"
+
+#include <assert.h>
+
+#include "debug.h"
+
+#ifdef _MSC_VER
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef signed char int8_t;
+typedef signed short int16_t;
+typedef signed int int32_t;
+#else
+# include <stdint.h>
+#endif
+
+#ifdef WINDOWS32
+# include <io.h>
+# include <process.h>
+# include <Windows.h>
+# define PARSE_IN_WORKER
+#endif
+
+#ifdef __OS2__
+# include <sys/fmutex.h>
+#endif
+
+#ifdef HAVE_PTHREAD
+# include <pthread.h>
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/* The default size of a memory segment (1MB). */
+#define STRCACHE2_SEG_SIZE (1024U*1024U)
+/* The default hash table shift (hash size give as a power of two). */
+#define STRCACHE2_HASH_SHIFT 16
+/** Does the modding / masking of a hash number into an index. */
+#ifdef STRCACHE2_USE_MASK
+# define STRCACHE2_MOD_IT(cache, hash) ((hash) & (cache)->hash_mask)
+#else
+# define STRCACHE2_MOD_IT(cache, hash) ((hash) % (cache)->hash_div)
+#endif
+
+# if ( defined(__amd64__) || defined(__x86_64__) || defined(__AMD64__) || defined(_M_X64) || defined(__amd64) \
+ || defined(__i386__) || defined(__x86__) || defined(__X86__) || defined(_M_IX86) || defined(__i386)) \
+ && !defined(GCC_ADDRESS_SANITIZER)
+# define strcache2_get_unaligned_16bits(ptr) ( *((const uint16_t *)(ptr)))
+# else
+ /* (endian doesn't matter) */
+# define strcache2_get_unaligned_16bits(ptr) ( (((const uint8_t *)(ptr))[0] << 8) \
+ | (((const uint8_t *)(ptr))[1]) )
+# endif
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+/* List of initialized string caches. */
+static struct strcache2 *strcache_head;
+
+
+#ifndef STRCACHE2_USE_MASK
+/** Finds the closest primary number for power of two value (or something else
+ * useful if not support). */
+MY_INLINE unsigned int strcache2_find_prime(unsigned int shift)
+{
+ switch (shift)
+ {
+ case 5: return 31;
+ case 6: return 61;
+ case 7: return 127;
+ case 8: return 251;
+ case 9: return 509;
+ case 10: return 1021;
+ case 11: return 2039;
+ case 12: return 4093;
+ case 13: return 8191;
+ case 14: return 16381;
+ case 15: return 32749;
+ case 16: return 65521;
+ case 17: return 131063;
+ case 18: return 262139;
+ case 19: return 524269;
+ case 20: return 1048573;
+ case 21: return 2097143;
+ case 22: return 4194301;
+ case 23: return 8388593;
+
+ default:
+ assert (0);
+ return (1 << shift) - 1;
+ }
+}
+#endif
+
+/* The following is a bit experiment. It produces longer chains, i.e. worse
+ distribution of the strings in the table, however the actual make
+ performances is better (<time). The explanation is probably that the
+ collisions only really increase for entries that aren't looked up that
+ much and that it actually improoves the situation for those that is. Or
+ that we spend so much less time hashing that it makes up (and more) for
+ the pentalty we suffer from the longer chains and worse distribution.
+
+ XXX: Check how this works out with different path lengths. I suspect it
+ might depend on the length of PATH_ROOT and the depth of the files
+ in the project as well. If it does, this might make matters worse
+ for some and better for others which isn't very cool... */
+
+#if 0
+# define BIG_HASH_SIZE 32 /* kinda fast */
+# define BIG_HASH_HEAD 16
+# define BIG_HASH_TAIL 12
+#elif 0
+# define BIG_HASH_SIZE 68 /* kinda safe */
+# define BIG_HASH_HEAD 24
+# define BIG_HASH_TAIL 24
+#elif 0
+# define BIG_HASH_SIZE 128 /* safe */
+# define BIG_HASH_HEAD 32
+# define BIG_HASH_TAIL 32
+#endif
+
+#ifdef BIG_HASH_SIZE
+/* long string: hash head and tail, drop the middle. */
+MY_INLINE unsigned int
+strcache2_case_sensitive_hash_big (const char *str, unsigned int len)
+{
+ uint32_t hash = len;
+ uint32_t tmp;
+ unsigned int head;
+
+ /* head BIG_HASH_HEAD bytes */
+ head = (BIG_HASH_HEAD >> 2);
+ while (head-- > 0)
+ {
+ hash += strcache2_get_unaligned_16bits (str);
+ tmp = (strcache2_get_unaligned_16bits (str + 2) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ str += 2 * sizeof (uint16_t);
+ hash += hash >> 11;
+ }
+
+ /* tail BIG_HASH_TAIL bytes (minus the odd ones) */
+ str += (len - BIG_HASH_HEAD - BIG_HASH_TAIL) & ~3U;
+ head = (BIG_HASH_TAIL >> 2);
+ while (head-- > 0)
+ {
+ hash += strcache2_get_unaligned_16bits (str);
+ tmp = (strcache2_get_unaligned_16bits (str + 2) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ str += 2 * sizeof (uint16_t);
+ hash += hash >> 11;
+ }
+
+ /* force "avalanching" of final 127 bits. */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+}
+#endif /* BIG_HASH_SIZE */
+
+MY_INLINE unsigned int
+strcache2_case_sensitive_hash (const char *str, unsigned int len)
+{
+#if 1
+ /* Paul Hsieh hash SuperFast function:
+ http://www.azillionmonkeys.com/qed/hash.html
+
+ This performs very good and as a sligtly better distribution than
+ STRING_N_HASH_1 on a typical kBuild run.
+
+ It is also 37% faster than return_STRING_N_HASH_1 when running the
+ two 100 times over typical kBuild strings that end up here (did a
+ fprintf here and built kBuild). Compiler was 32-bit gcc 4.0.1, darwin,
+ with -O2.
+
+ FIXME: A path for well aligned data should be added to speed up
+ execution on alignment sensitive systems. */
+ unsigned int rem;
+ uint32_t hash;
+ uint32_t tmp;
+
+ assert (sizeof (uint8_t) == sizeof (char));
+
+# ifdef BIG_HASH_SIZE
+ /* long string? */
+# if 0 /*BIG_HASH_SIZE > 128*/
+ if (MY_PREDICT_FALSE(len >= BIG_HASH_SIZE))
+# else
+ if (len >= BIG_HASH_SIZE)
+# endif
+ return strcache2_case_sensitive_hash_big (str, len);
+# endif
+
+ /* short string: main loop, walking on 2 x uint16_t */
+ hash = len;
+ rem = len & 3;
+ len >>= 2;
+ while (len > 0)
+ {
+ hash += strcache2_get_unaligned_16bits (str);
+ tmp = (strcache2_get_unaligned_16bits (str + 2) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ str += 2 * sizeof (uint16_t);
+ hash += hash >> 11;
+ len--;
+ }
+
+ /* the remainder */
+ switch (rem)
+ {
+ case 3:
+ hash += strcache2_get_unaligned_16bits (str);
+ hash ^= hash << 16;
+ hash ^= str[sizeof (uint16_t)] << 18;
+ hash += hash >> 11;
+ break;
+ case 2:
+ hash += strcache2_get_unaligned_16bits (str);
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ break;
+ case 1:
+ hash += *str;
+ hash ^= hash << 10;
+ hash += hash >> 1;
+ break;
+ }
+
+ /* force "avalanching" of final 127 bits. */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+
+#elif 1
+ /* Note! This implementation is 18% faster than return_STRING_N_HASH_1
+ when running the two 100 times over typical kBuild strings that
+ end up here (did a fprintf here and built kBuild).
+ Compiler was 32-bit gcc 4.0.1, darwin, with -O2. */
+
+ unsigned int hash = 0;
+ if (MY_PREDICT_TRUE(len >= 2))
+ {
+ unsigned int ch0 = *str++;
+ hash = 0;
+ len--;
+ while (len >= 2)
+ {
+ unsigned int ch1 = *str++;
+ hash += ch0 << (ch1 & 0xf);
+
+ ch0 = *str++;
+ hash += ch1 << (ch0 & 0xf);
+
+ len -= 2;
+ }
+ if (len == 1)
+ {
+ unsigned ch1 = *str;
+ hash += ch0 << (ch1 & 0xf);
+
+ hash += ch1;
+ }
+ else
+ hash += ch0;
+ }
+ else if (len)
+ {
+ hash = *str;
+ hash += hash << (hash & 0xf);
+ }
+ else
+ hash = 0;
+ return hash;
+
+#elif 1
+# if 0
+ /* This is SDBM. This specific form/unroll was benchmarked to be 28% faster
+ than return_STRING_N_HASH_1. (Now the weird thing is that putting the (ch)
+ first in the assignment made it noticably slower.)
+
+ However, it is noticably slower in practice, most likely because of more
+ collisions. Hrmpf. */
+
+# define UPDATE_HASH(ch) hash = (hash << 6) + (hash << 16) - hash + (ch)
+ unsigned int hash = 0;
+
+# else
+ /* This is DJB2. This specific form/unroll was benchmarked to be 27%
+ fast than return_STRING_N_HASH_1.
+
+ Ditto. */
+
+# define UPDATE_HASH(ch) hash = (hash << 5) + hash + (ch)
+ unsigned int hash = 5381;
+# endif
+
+
+ while (len >= 4)
+ {
+ UPDATE_HASH (str[0]);
+ UPDATE_HASH (str[1]);
+ UPDATE_HASH (str[2]);
+ UPDATE_HASH (str[3]);
+ str += 4;
+ len -= 4;
+ }
+ switch (len)
+ {
+ default:
+ case 0:
+ return hash;
+ case 1:
+ UPDATE_HASH (str[0]);
+ return hash;
+ case 2:
+ UPDATE_HASH (str[0]);
+ UPDATE_HASH (str[1]);
+ return hash;
+ case 3:
+ UPDATE_HASH (str[0]);
+ UPDATE_HASH (str[1]);
+ UPDATE_HASH (str[2]);
+ return hash;
+ }
+#endif
+}
+
+MY_INLINE unsigned int
+strcache2_case_insensitive_hash (const char *str, unsigned int len)
+{
+ unsigned int hash = 0;
+ if (MY_PREDICT_TRUE(len >= 2))
+ {
+ unsigned int ch0 = *str++;
+ ch0 = tolower (ch0);
+ hash = 0;
+ len--;
+ while (len >= 2)
+ {
+ unsigned int ch1 = *str++;
+ ch1 = tolower (ch1);
+ hash += ch0 << (ch1 & 0xf);
+
+ ch0 = *str++;
+ ch0 = tolower (ch0);
+ hash += ch1 << (ch0 & 0xf);
+
+ len -= 2;
+ }
+ if (len == 1)
+ {
+ unsigned ch1 = *str;
+ ch1 = tolower (ch1);
+ hash += ch0 << (ch1 & 0xf);
+
+ hash += ch1;
+ }
+ else
+ hash += ch0;
+ }
+ else if (len)
+ {
+ hash = *str;
+ hash += hash << (hash & 0xf);
+ }
+ else
+ hash = 0;
+ return hash;
+}
+
+#if 0
+MY_INLINE int
+strcache2_memcmp_inline_short (const char *xs, const char *ys, unsigned int length)
+{
+ if (length <= 8)
+ {
+ /* short string compare - ~50% of the kBuild calls. */
+ assert ( !((size_t)ys & 3) );
+ if (!((size_t)xs & 3))
+ {
+ /* aligned */
+ int result;
+ switch (length)
+ {
+ default: /* memcmp for longer strings */
+ return memcmp (xs, ys, length);
+ case 8:
+ result = *(int32_t*)(xs + 4) - *(int32_t*)(ys + 4);
+ result |= *(int32_t*)xs - *(int32_t*)ys;
+ return result;
+ case 7:
+ result = xs[6] - ys[6];
+ result |= xs[5] - ys[5];
+ result |= xs[4] - ys[4];
+ result |= *(int32_t*)xs - *(int32_t*)ys;
+ return result;
+ case 6:
+ result = xs[5] - ys[5];
+ result |= xs[4] - ys[4];
+ result |= *(int32_t*)xs - *(int32_t*)ys;
+ return result;
+ case 5:
+ result = xs[4] - ys[4];
+ result |= *(int32_t*)xs - *(int32_t*)ys;
+ return result;
+ case 4:
+ return *(int32_t*)xs - *(int32_t*)ys;
+ case 3:
+ result = xs[2] - ys[2];
+ result |= xs[1] - ys[1];
+ result |= xs[0] - ys[0];
+ return result;
+ case 2:
+ result = xs[1] - ys[1];
+ result |= xs[0] - ys[0];
+ return result;
+ case 1:
+ return *xs - *ys;
+ case 0:
+ return 0;
+ }
+ }
+ else
+ {
+ /* unaligned */
+ int result = 0;
+ switch (length)
+ {
+ case 8: result |= xs[7] - ys[7];
+ case 7: result |= xs[6] - ys[6];
+ case 6: result |= xs[5] - ys[5];
+ case 5: result |= xs[4] - ys[4];
+ case 4: result |= xs[3] - ys[3];
+ case 3: result |= xs[2] - ys[2];
+ case 2: result |= xs[1] - ys[1];
+ case 1: result |= xs[0] - ys[0];
+ case 0:
+ return result;
+ }
+ }
+ }
+
+ /* memcmp for longer strings */
+ return memcmp (xs, ys, length);
+}
+#endif
+
+MY_INLINE int
+strcache2_memcmp_inlined (const char *xs, const char *ys, unsigned int length)
+{
+#ifndef ELECTRIC_HEAP
+ assert ( !((size_t)ys & 3) );
+#endif
+ if (!((size_t)xs & 3))
+ {
+ /* aligned */
+ int result;
+ unsigned reminder = length & 7;
+ length >>= 3;
+ while (length-- > 0)
+ {
+ result = *(int32_t*)xs - *(int32_t*)ys;
+ result |= *(int32_t*)(xs + 4) - *(int32_t*)(ys + 4);
+ if (MY_PREDICT_FALSE(result))
+ return result;
+ xs += 8;
+ ys += 8;
+ }
+ switch (reminder)
+ {
+ case 7:
+ result = *(int32_t*)xs - *(int32_t*)ys;
+ result |= xs[6] - ys[6];
+ result |= xs[5] - ys[5];
+ result |= xs[4] - ys[4];
+ return result;
+ case 6:
+ result = *(int32_t*)xs - *(int32_t*)ys;
+ result |= xs[5] - ys[5];
+ result |= xs[4] - ys[4];
+ return result;
+ case 5:
+ result = *(int32_t*)xs - *(int32_t*)ys;
+ result |= xs[4] - ys[4];
+ return result;
+ case 4:
+ return *(int32_t*)xs - *(int32_t*)ys;
+ case 3:
+ result = xs[2] - ys[2];
+ result |= xs[1] - ys[1];
+ result |= xs[0] - ys[0];
+ return result;
+ case 2:
+ result = xs[1] - ys[1];
+ result |= xs[0] - ys[0];
+ return result;
+ case 1:
+ return *xs - *ys;
+ default:
+ case 0:
+ return 0;
+ }
+ }
+ else
+ {
+ /* unaligned */
+ int result;
+ unsigned reminder = length & 7;
+ length >>= 3;
+ while (length-- > 0)
+ {
+#if defined(__i386__) || defined(__x86_64__)
+ result = ( ((int32_t)xs[3] << 24)
+ | ((int32_t)xs[2] << 16)
+ | ((int32_t)xs[1] << 8)
+ | xs[0] )
+ - *(int32_t*)ys;
+ result |= ( ((int32_t)xs[7] << 24)
+ | ((int32_t)xs[6] << 16)
+ | ((int32_t)xs[5] << 8)
+ | xs[4] )
+ - *(int32_t*)(ys + 4);
+#else
+ result = xs[3] - ys[3];
+ result |= xs[2] - ys[2];
+ result |= xs[1] - ys[1];
+ result |= xs[0] - ys[0];
+ result |= xs[7] - ys[7];
+ result |= xs[6] - ys[6];
+ result |= xs[5] - ys[5];
+ result |= xs[4] - ys[4];
+#endif
+ if (MY_PREDICT_FALSE(result))
+ return result;
+ xs += 8;
+ ys += 8;
+ }
+
+ result = 0;
+ switch (reminder)
+ {
+ case 7: result |= xs[6] - ys[6]; /* fall thru */
+ case 6: result |= xs[5] - ys[5]; /* fall thru */
+ case 5: result |= xs[4] - ys[4]; /* fall thru */
+ case 4: result |= xs[3] - ys[3]; /* fall thru */
+ case 3: result |= xs[2] - ys[2]; /* fall thru */
+ case 2: result |= xs[1] - ys[1]; /* fall thru */
+ case 1: result |= xs[0] - ys[0]; /* fall thru */
+ return result;
+ default:
+ case 0:
+ return 0;
+ }
+ }
+}
+
+MY_INLINE int
+strcache2_is_equal (struct strcache2 *cache, struct strcache2_entry const *entry,
+ const char *str, unsigned int length, unsigned int hash)
+{
+ assert (!cache->case_insensitive);
+
+ /* the simple stuff first. */
+ if ( entry->hash != hash
+ || entry->length != length)
+ return 0;
+
+#if 0
+ return memcmp (str, entry + 1, length) == 0;
+#elif 1
+ return strcache2_memcmp_inlined (str, (const char *)(entry + 1), length) == 0;
+#else
+ return strcache2_memcmp_inline_short (str, (const char *)(entry + 1), length) == 0;
+#endif
+}
+
+#if defined(HAVE_CASE_INSENSITIVE_FS)
+MY_INLINE int
+strcache2_is_iequal (struct strcache2 *cache, struct strcache2_entry const *entry,
+ const char *str, unsigned int length, unsigned int hash)
+{
+ assert (cache->case_insensitive);
+
+ /* the simple stuff first. */
+ if ( entry->hash != hash
+ || entry->length != length)
+ return 0;
+
+# if defined(_MSC_VER) || defined(__OS2__)
+ return _memicmp (entry + 1, str, length) == 0;
+# else
+ return strncasecmp ((const char *)(entry + 1), str, length) == 0;
+# endif
+}
+#endif /* HAVE_CASE_INSENSITIVE_FS */
+
+static void
+strcache2_rehash (struct strcache2 *cache)
+{
+ unsigned int src = cache->hash_size;
+ struct strcache2_entry **src_tab = cache->hash_tab;
+ struct strcache2_entry **dst_tab;
+#ifndef STRCACHE2_USE_MASK
+ unsigned int hash_shift;
+#endif
+
+ /* Allocate a new hash table twice the size of the current. */
+ cache->hash_size <<= 1;
+#ifdef STRCACHE2_USE_MASK
+ cache->hash_mask <<= 1;
+ cache->hash_mask |= 1;
+#else
+ for (hash_shift = 1; (1U << hash_shift) < cache->hash_size; hash_shift++)
+ /* nothing */;
+ cache->hash_div = strcache2_find_prime (hash_shift);
+#endif
+ cache->rehash_count <<= 1;
+ cache->hash_tab = dst_tab = (struct strcache2_entry **)
+ xmalloc (cache->hash_size * sizeof (struct strcache2_entry *));
+ memset (dst_tab, '\0', cache->hash_size * sizeof (struct strcache2_entry *));
+
+ /* Copy the entries from the old to the new hash table. */
+ cache->collision_count = 0;
+ while (src-- > 0)
+ {
+ struct strcache2_entry *entry = src_tab[src];
+ while (entry)
+ {
+ struct strcache2_entry *next = entry->next;
+ unsigned int dst = STRCACHE2_MOD_IT (cache, entry->hash);
+ if ((entry->next = dst_tab[dst]) != 0)
+ cache->collision_count++;
+ dst_tab[dst] = entry;
+
+ entry = next;
+ }
+ }
+
+ /* That's it, just free the old table and we're done. */
+ free (src_tab);
+}
+
+static struct strcache2_seg *
+strcache2_new_seg (struct strcache2 *cache, unsigned int minlen)
+{
+ struct strcache2_seg *seg;
+ size_t size;
+ size_t off;
+
+ size = cache->def_seg_size;
+ if (size < (size_t)minlen + sizeof (struct strcache2_seg) + STRCACHE2_ENTRY_ALIGNMENT)
+ {
+ size = (size_t)minlen * 2;
+ size = (size + 0xfff) & ~(size_t)0xfff;
+ }
+
+ seg = xmalloc (size);
+ seg->start = (char *)(seg + 1);
+ seg->size = size - sizeof (struct strcache2_seg);
+ off = (size_t)seg->start & (STRCACHE2_ENTRY_ALIGNMENT - 1);
+ if (off)
+ {
+ off = STRCACHE2_ENTRY_ALIGNMENT - off;
+ seg->start += off;
+ seg->size -= off;
+ }
+ assert (seg->size > minlen);
+ seg->cursor = seg->start;
+ seg->avail = seg->size;
+
+ seg->next = cache->seg_head;
+ cache->seg_head = seg;
+
+ return seg;
+}
+
+/* Internal worker that enters a new string into the cache. */
+static const char *
+strcache2_enter_string (struct strcache2 *cache, unsigned int idx,
+ const char *str, unsigned int length,
+ unsigned int hash)
+{
+ struct strcache2_entry *entry;
+ struct strcache2_seg *seg;
+ unsigned int size;
+ char *str_copy;
+
+ /* Allocate space for the string. */
+
+ size = length + 1 + sizeof (struct strcache2_entry);
+ size = (size + STRCACHE2_ENTRY_ALIGNMENT - 1) & ~(STRCACHE2_ENTRY_ALIGNMENT - 1U);
+
+ seg = cache->seg_head;
+ if (MY_PREDICT_FALSE(seg->avail < size))
+ {
+ do
+ seg = seg->next;
+ while (seg && seg->avail < size);
+ if (!seg)
+ seg = strcache2_new_seg (cache, size);
+ }
+
+ entry = (struct strcache2_entry *) seg->cursor;
+ assert (!((size_t)entry & (STRCACHE2_ENTRY_ALIGNMENT - 1)));
+ seg->cursor += size;
+ seg->avail -= size;
+
+ /* Setup the entry, copy the string and insert it into the hash table. */
+
+ entry->user = NULL;
+ entry->length = length;
+ entry->hash = hash;
+ str_copy = (char *) memcpy (entry + 1, str, length);
+ str_copy[length] = '\0';
+
+ if ((entry->next = cache->hash_tab[idx]) != 0)
+ cache->collision_count++;
+ cache->hash_tab[idx] = entry;
+ cache->count++;
+ if (cache->count >= cache->rehash_count)
+ strcache2_rehash (cache);
+
+ return str_copy;
+}
+
+/* The public add string interface. */
+const char *
+strcache2_add (struct strcache2 *cache, const char *str, unsigned int length)
+{
+ struct strcache2_entry const *entry;
+ unsigned int hash = strcache2_case_sensitive_hash (str, length);
+ unsigned int idx;
+
+ assert (!cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. If not found, enter the string at IDX. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+/* The public add string interface for prehashed strings.
+ Use strcache2_hash_str to calculate the hash of a string. */
+const char *
+strcache2_add_hashed (struct strcache2 *cache, const char *str,
+ unsigned int length, unsigned int hash)
+{
+ struct strcache2_entry const *entry;
+ unsigned int idx;
+#ifndef NDEBUG
+ unsigned correct_hash;
+
+ assert (!cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+ correct_hash = strcache2_case_sensitive_hash (str, length);
+ MY_ASSERT_MSG (hash == correct_hash, ("%#x != %#x\n", hash, correct_hash));
+#endif /* NDEBUG */
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. If not found, enter the string at IDX. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+/* The public lookup (case sensitive) string interface. */
+const char *
+strcache2_lookup (struct strcache2 *cache, const char *str, unsigned int length)
+{
+ struct strcache2_entry const *entry;
+ unsigned int hash = strcache2_case_sensitive_hash (str, length);
+ unsigned int idx;
+
+ assert (!cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return NULL;
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return NULL;
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return NULL;
+ if (strcache2_is_equal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+#if defined(HAVE_CASE_INSENSITIVE_FS)
+
+/* The public add string interface for case insensitive strings. */
+const char *
+strcache2_iadd (struct strcache2 *cache, const char *str, unsigned int length)
+{
+ struct strcache2_entry const *entry;
+ unsigned int hash = strcache2_case_insensitive_hash (str, length);
+ unsigned int idx;
+
+ assert (cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. If not found, enter the string at IDX. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+/* The public add string interface for prehashed case insensitive strings.
+ Use strcache2_hash_istr to calculate the hash of a string. */
+const char *
+strcache2_iadd_hashed (struct strcache2 *cache, const char *str,
+ unsigned int length, unsigned int hash)
+{
+ struct strcache2_entry const *entry;
+ unsigned int idx;
+#ifndef NDEBUG
+ unsigned correct_hash;
+
+ assert (cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+ correct_hash = strcache2_case_insensitive_hash (str, length);
+ MY_ASSERT_MSG (hash == correct_hash, ("%#x != %#x\n", hash, correct_hash));
+#endif /* NDEBUG */
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. If not found, enter the string at IDX. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return strcache2_enter_string (cache, idx, str, length, hash);
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+/* The public lookup (case insensitive) string interface. */
+const char *
+strcache2_ilookup (struct strcache2 *cache, const char *str, unsigned int length)
+{
+ struct strcache2_entry const *entry;
+ unsigned int hash = strcache2_case_insensitive_hash (str, length);
+ unsigned int idx;
+
+ assert (cache->case_insensitive);
+ assert (!memchr (str, '\0', length));
+
+ MAKE_STATS (cache->lookup_count++);
+
+ /* Lookup the entry in the hash table, hoping for an
+ early match. */
+ idx = STRCACHE2_MOD_IT (cache, hash);
+ entry = cache->hash_tab[idx];
+ if (!entry)
+ return NULL;
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_1st_count++);
+
+ entry = entry->next;
+ if (!entry)
+ return NULL;
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_2nd_count++);
+
+ /* Loop the rest. */
+ for (;;)
+ {
+ entry = entry->next;
+ if (!entry)
+ return NULL;
+ if (strcache2_is_iequal (cache, entry, str, length, hash))
+ return (const char *)(entry + 1);
+ MAKE_STATS (cache->collision_3rd_count++);
+ }
+ /* not reached */
+}
+
+#endif /* HAVE_CASE_INSENSITIVE_FS */
+
+/* Is the given string cached? returns 1 if it is, 0 if it isn't. */
+int
+strcache2_is_cached (struct strcache2 *cache, const char *str)
+{
+ /* Check mandatory alignment first. */
+ if (!((size_t)str & (sizeof (void *) - 1)))
+ {
+ /* Check the segment list and consider the question answered if the
+ string is within one of them. (Could check it more thoroughly...) */
+ struct strcache2_seg const *seg;
+ for (seg = cache->seg_head; seg; seg = seg->next)
+ if ((size_t)(str - seg->start) < seg->size)
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Verify the integrity of the specified string, returning 0 if OK. */
+int
+strcache2_verify_entry (struct strcache2 *cache, const char *str)
+{
+ struct strcache2_entry const *entry;
+ unsigned int hash;
+ unsigned int length;
+ const char *end;
+
+ entry = (struct strcache2_entry const *)str - 1;
+ if ((size_t)entry & (STRCACHE2_ENTRY_ALIGNMENT - 1))
+ {
+ fprintf (stderr,
+ "strcache2[%s]: missaligned entry %p\nstring: %p=%s\n",
+ cache->name, (void *)entry, (void *)str, str);
+ return -1;
+ }
+
+ end = memchr (str, '\0', entry->length + 1);
+ length = end - str;
+ if (length != entry->length)
+ {
+ fprintf (stderr,
+ "strcache2[%s]: corrupt entry %p, length: %u, expected %u;\nstring: %s\n",
+ cache->name, (void *)entry, length, entry->length, str);
+ return -1;
+ }
+
+ hash = cache->case_insensitive
+ ? strcache2_case_insensitive_hash (str, entry->length)
+ : strcache2_case_sensitive_hash (str, entry->length);
+ if (hash != entry->hash)
+ {
+ fprintf (stderr,
+ "strcache2[%s]: corrupt entry %p, hash: %x, expected %x;\nstring: %s\n",
+ cache->name, (void *)entry, hash, entry->hash, str);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Calculates the case sensitive hash values of the string.
+ The first hash is returned, the other is put at HASH2P. */
+unsigned int strcache2_hash_str (const char *str, unsigned int length, unsigned int *hash2p)
+{
+ *hash2p = 1;
+ return strcache2_case_sensitive_hash (str, length);
+}
+
+/* Calculates the case insensitive hash values of the string.
+ The first hash is returned, the other is put at HASH2P. */
+unsigned int strcache2_hash_istr (const char *str, unsigned int length, unsigned int *hash2p)
+{
+ *hash2p = 1;
+ return strcache2_case_insensitive_hash (str, length);
+}
+
+
+
+/* Initalizes a new cache. */
+void
+strcache2_init (struct strcache2 *cache, const char *name, unsigned int size,
+ unsigned int def_seg_size, int case_insensitive, int thread_safe)
+{
+ unsigned hash_shift;
+ assert (!thread_safe);
+
+ /* calc the size as a power of two */
+ if (!size)
+ hash_shift = STRCACHE2_HASH_SHIFT;
+ else
+ {
+ assert (size <= (~0U / 2 + 1));
+ for (hash_shift = 8; (1U << hash_shift) < size; hash_shift++)
+ /* nothing */;
+ }
+
+ /* adjust the default segment size */
+ if (!def_seg_size)
+ def_seg_size = STRCACHE2_SEG_SIZE;
+ else if (def_seg_size < sizeof (struct strcache2_seg) * 10)
+ def_seg_size = sizeof (struct strcache2_seg) * 10;
+ else if ((def_seg_size & 0xfff) < 0xf00)
+ def_seg_size = ((def_seg_size + 0xfff) & ~0xfffU);
+
+
+ /* init the structure. */
+ cache->case_insensitive = case_insensitive;
+#ifdef STRCACHE2_USE_MASK
+ cache->hash_mask = (1U << hash_shift) - 1U;
+#else
+ cache->hash_div = strcache2_find_prime(hash_shift);
+#endif
+ cache->count = 0;
+ cache->collision_count = 0;
+ cache->lookup_count = 0;
+ cache->collision_1st_count = 0;
+ cache->collision_2nd_count = 0;
+ cache->collision_3rd_count = 0;
+ cache->rehash_count = (1U << hash_shift) / 4 * 3; /* rehash at 75% */
+ cache->init_size = 1U << hash_shift;
+ cache->hash_size = 1U << hash_shift;
+ cache->def_seg_size = def_seg_size;
+ cache->lock = NULL;
+ cache->name = name;
+
+ /* allocate the hash table and first segment. */
+ cache->hash_tab = (struct strcache2_entry **)
+ xmalloc (cache->init_size * sizeof (struct strcache2_entry *));
+ memset (cache->hash_tab, '\0', cache->init_size * sizeof (struct strcache2_entry *));
+ strcache2_new_seg (cache, 0);
+
+ /* link it */
+ cache->next = strcache_head;
+ strcache_head = cache;
+}
+
+
+/* Terminates a string cache, freeing all memory and other
+ associated resources. */
+void
+strcache2_term (struct strcache2 *cache)
+{
+ /* unlink it */
+ if (strcache_head == cache)
+ strcache_head = cache->next;
+ else
+ {
+ struct strcache2 *prev = strcache_head;
+ while (prev->next != cache)
+ prev = prev->next;
+ assert (prev);
+ prev->next = cache->next;
+ }
+
+ /* free the memory segments */
+ do
+ {
+ void *free_it = cache->seg_head;
+ cache->seg_head = cache->seg_head->next;
+ free (free_it);
+ }
+ while (cache->seg_head);
+
+ /* free the hash and clear the structure. */
+ free (cache->hash_tab);
+ memset (cache, '\0', sizeof (struct strcache2));
+}
+
+/* Print statistics a string cache. */
+void
+strcache2_print_stats (struct strcache2 *cache, const char *prefix)
+{
+ unsigned int seg_count = 0;
+ unsigned long seg_total_bytes = 0;
+ unsigned long seg_avg_bytes;
+ unsigned long seg_avail_bytes = 0;
+ unsigned long seg_max_bytes = 0;
+ struct strcache2_seg *seg;
+ unsigned int str_count = 0;
+ unsigned long str_total_len = 0;
+ unsigned int str_avg_len;
+ unsigned int str_min_len = ~0U;
+ unsigned int str_max_len = 0;
+ unsigned int idx;
+ unsigned int rehashes;
+ unsigned int chain_depths[32];
+
+ printf (_("\n%s strcache2: %s\n"), prefix, cache->name);
+
+ /* Segment statistics. */
+ for (seg = cache->seg_head; seg; seg = seg->next)
+ {
+ seg_count++;
+ seg_total_bytes += seg->size;
+ seg_avail_bytes += seg->avail;
+ if (seg->size > seg_max_bytes)
+ seg_max_bytes = seg->size;
+ }
+ seg_avg_bytes = seg_total_bytes / seg_count;
+ printf (_("%s %u segments: total = %lu / max = %lu / avg = %lu / def = %u avail = %lu\n"),
+ prefix, seg_count, seg_total_bytes, seg_max_bytes, seg_avg_bytes,
+ cache->def_seg_size, seg_avail_bytes);
+
+ /* String statistics. */
+ memset (chain_depths, '\0', sizeof (chain_depths));
+ idx = cache->hash_size;
+ while (idx-- > 0)
+ {
+ struct strcache2_entry const *entry = cache->hash_tab[idx];
+ unsigned int depth = 0;
+ for (; entry != 0; entry = entry->next, depth++)
+ {
+ unsigned int length = entry->length;
+ str_total_len += length;
+ if (length > str_max_len)
+ str_max_len = length;
+ if (length < str_min_len)
+ str_min_len = length;
+ str_count++;
+ }
+ chain_depths[depth >= 32 ? 31 : depth]++;
+ }
+ str_avg_len = cache->count ? str_total_len / cache->count : 0;
+ printf (_("%s %u strings: total len = %lu / max = %u / avg = %u / min = %u\n"),
+ prefix, cache->count, str_total_len, str_max_len, str_avg_len, str_min_len);
+ if (str_count != cache->count)
+ printf (_("%s string count mismatch! cache->count=%u, actual count is %u\n"), prefix,
+ cache->count, str_count);
+
+ /* Hash statistics. */
+ idx = cache->init_size;
+ rehashes = 0;
+ while (idx < cache->hash_size)
+ {
+ idx *= 2;
+ rehashes++;
+ }
+
+#ifdef STRCACHE2_USE_MASK
+ printf (_("%s hash size = %u mask = %#x rehashed %u times"),
+ prefix, cache->hash_size, cache->hash_mask, rehashes);
+#else
+ printf (_("%s hash size = %u div = %#x rehashed %u times"),
+ prefix, cache->hash_size, cache->hash_div, rehashes);
+#endif
+ if (cache->lookup_count)
+ printf (_("%s lookups = %lu\n"
+ "%s hash collisions 1st = %lu (%u%%) 2nd = %lu (%u%%) 3rd = %lu (%u%%)"),
+ prefix, cache->lookup_count,
+ prefix,
+ cache->collision_1st_count, (unsigned int)((100.0 * cache->collision_1st_count) / cache->lookup_count),
+ cache->collision_2nd_count, (unsigned int)((100.0 * cache->collision_2nd_count) / cache->lookup_count),
+ cache->collision_3rd_count, (unsigned int)((100.0 * cache->collision_3rd_count) / cache->lookup_count));
+ printf (_("\n%s hash insert collisions = %u (%u%%)\n"),
+ prefix, cache->collision_count,(unsigned int)((100.0 * cache->collision_count) / cache->count));
+ printf (_("%s %5u (%u%%) empty hash table slots\n"),
+ prefix, chain_depths[0], (unsigned int)((100.0 * chain_depths[0]) / cache->hash_size));
+ printf (_("%s %5u (%u%%) occupied hash table slots\n"),
+ prefix, chain_depths[1], (unsigned int)((100.0 * chain_depths[1]) / cache->hash_size));
+ for (idx = 2; idx < 32; idx++)
+ {
+ unsigned strs_at_this_depth = chain_depths[idx];
+ unsigned i;
+ for (i = idx + 1; i < 32; i++)
+ strs_at_this_depth += chain_depths[i];
+ if (strs_at_this_depth)
+ printf (_("%s %5u (%2u%%) with %u string%s chained on; %5u (%2u%%) strings at depth %u.\n"),
+ prefix, chain_depths[idx], (unsigned int)((100.0 * chain_depths[idx]) / (cache->count - cache->collision_count)),
+ idx - 1, idx == 2 ? " " : "s",
+ strs_at_this_depth, (unsigned int)((100.0 * strs_at_this_depth) / cache->count), idx - 1);
+ }
+}
+
+/* Print statistics for all string caches. */
+void
+strcache2_print_stats_all (const char *prefix)
+{
+ struct strcache2 *cur;
+ for (cur = strcache_head; cur; cur = cur->next)
+ strcache2_print_stats (cur, prefix);
+}
+
diff --git a/src/kmk/strcache2.h b/src/kmk/strcache2.h
new file mode 100644
index 0000000..8ef8650
--- /dev/null
+++ b/src/kmk/strcache2.h
@@ -0,0 +1,182 @@
+/* $Id: strcache2.h 2413 2010-09-11 17:43:04Z bird $ */
+/** @file
+ * strcache - New string cache.
+ */
+
+/*
+ * Copyright (c) 2006-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef ___strcache2_h
+#define ___strcache2_h
+
+#ifndef CHAR_BIT
+# error "include after make.h!"
+#endif
+
+#define STRCACHE2_USE_MASK 1
+
+/* string cache memory segment. */
+struct strcache2_seg
+{
+ struct strcache2_seg *next; /* The next cache segment. */
+ char *start; /* The first byte in the segment. */
+ size_t size; /* The size of the segment. */
+ size_t avail; /* The number of available bytes. */
+ char *cursor; /* Allocation cursor. */
+};
+
+/* string cache hash table entry. */
+struct strcache2_entry
+{
+ struct strcache2_entry *next; /* Collision chain. */
+ void *user;
+ unsigned int hash;
+ unsigned int length;
+};
+
+/* The entry alignment, cacheline size if it's known & sensible.
+
+ On x86/AMD64 we assume a 64-byte cacheline size. As it is difficult to
+ guess other right now, these default 16 chars as that shouldn't cause
+ much trouble, even if it not the most optimial value. Override, or modify
+ for other platforms. */
+#ifndef STRCACHE2_ENTRY_ALIGN_SHIFT
+# if defined (__i386__) || defined(__x86_64__)
+# define STRCACHE2_ENTRY_ALIGN_SHIFT 6
+# else
+# define STRCACHE2_ENTRY_ALIGN_SHIFT 4
+# endif
+#endif
+#define STRCACHE2_ENTRY_ALIGNMENT (1 << STRCACHE2_ENTRY_ALIGN_SHIFT)
+
+
+struct strcache2
+{
+ struct strcache2_entry **hash_tab; /* The hash table. */
+ int case_insensitive; /* case insensitive or not. */
+#ifdef STRCACHE2_USE_MASK
+ unsigned int hash_mask; /* The AND mask matching hash_size.*/
+#else
+ unsigned int hash_div; /* The number (prime) to mod by. */
+#endif
+ unsigned long lookup_count; /* The number of lookups. */
+ unsigned long collision_1st_count; /* The number of 1st level collisions. */
+ unsigned long collision_2nd_count; /* The number of 2nd level collisions. */
+ unsigned long collision_3rd_count; /* The number of 3rd level collisions. */
+ unsigned int count; /* Number entries in the cache. */
+ unsigned int collision_count; /* Number of entries in chains. */
+ unsigned int rehash_count; /* When to rehash the table. */
+ unsigned int init_size; /* The initial hash table size. */
+ unsigned int hash_size; /* The hash table size. */
+ unsigned int def_seg_size; /* The default segment size. */
+ void *lock; /* The lock handle. */
+ struct strcache2_seg *seg_head; /* The memory segment list. */
+ struct strcache2 *next; /* The next string cache. */
+ const char *name; /* Cache name. */
+};
+
+
+void strcache2_init (struct strcache2 *cache, const char *name, unsigned int size,
+ unsigned int def_seg_size, int case_insensitive, int thread_safe);
+void strcache2_term (struct strcache2 *cache);
+void strcache2_print_stats (struct strcache2 *cache, const char *prefix);
+void strcache2_print_stats_all (const char *prefix);
+const char *strcache2_add (struct strcache2 *cache, const char *str, unsigned int length);
+const char *strcache2_iadd (struct strcache2 *cache, const char *str, unsigned int length);
+const char *strcache2_add_hashed (struct strcache2 *cache, const char *str,
+ unsigned int length, unsigned int hash);
+const char *strcache2_iadd_hashed (struct strcache2 *cache, const char *str,
+ unsigned int length, unsigned int hash);
+const char *strcache2_lookup (struct strcache2 *cache, const char *str, unsigned int length);
+const char *strcache2_ilookup (struct strcache2 *cache, const char *str, unsigned int length);
+#ifdef HAVE_CASE_INSENSITIVE_FS
+# define strcache2_add_file strcache2_iadd
+# define strcache2_add_hashed_file strcache2_iadd_hashed
+# define strcache2_lookup_file strcache2_ilookup
+#else
+# define strcache2_add_file strcache2_add
+# define strcache2_add_hashed_file strcache2_add_hashed
+# define strcache2_lookup_file strcache2_lookup
+#endif
+int strcache2_is_cached (struct strcache2 *cache, const char *str);
+int strcache2_verify_entry (struct strcache2 *cache, const char *str);
+unsigned int strcache2_get_hash2_fallback (struct strcache2 *cache, const char *str);
+unsigned int strcache2_hash_str (const char *str, unsigned int length, unsigned int *hash2p);
+unsigned int strcache2_hash_istr (const char *str, unsigned int length, unsigned int *hash2p);
+
+/* Get the hash table entry pointer. */
+MY_INLINE struct strcache2_entry const *
+strcache2_get_entry (struct strcache2 *cache, const char *str)
+{
+#ifndef NDEBUG
+ strcache2_verify_entry (cache, str);
+#endif
+ return (struct strcache2_entry const *)str - 1;
+}
+
+/* Get the string length. */
+MY_INLINE unsigned int
+strcache2_get_len (struct strcache2 *cache, const char *str)
+{
+ return strcache2_get_entry (cache, str)->length;
+}
+
+/* Get the first hash value for the string. */
+MY_INLINE unsigned int
+strcache2_get_hash (struct strcache2 *cache, const char *str)
+{
+ return strcache2_get_entry (cache, str)->hash;
+}
+
+/* Calc the pointer hash value for the string.
+
+ This takes the string address, shift out the bits that are always zero
+ due to alignment, and then returns the unsigned integer value of it.
+
+ The results from using this is generally better than for any of the
+ other hash values. It is also sligtly faster code as it does not
+ involve any memory accesses, just a right SHIFT and an optional AND. */
+MY_INLINE unsigned int
+strcache2_calc_ptr_hash (struct strcache2 *cache, const char *str)
+{
+ (void)cache;
+ return (size_t)str >> STRCACHE2_ENTRY_ALIGN_SHIFT;
+}
+
+/* Get the user value for the string. */
+MY_INLINE void *
+strcache2_get_user_val (struct strcache2 *cache, const char *str)
+{
+ return strcache2_get_entry (cache, str)->user;
+}
+
+/* Get the user value for the string. */
+MY_INLINE void
+strcache2_set_user_val (struct strcache2 *cache, const char *str, void *value)
+{
+ struct strcache2_entry *entry = (struct strcache2_entry *)str - 1;
+#ifndef NDEBUG
+ strcache2_verify_entry (cache, str);
+#endif
+ entry->user = value;
+}
+
+#endif
+
diff --git a/src/kmk/subproc.bat b/src/kmk/subproc.bat
new file mode 100644
index 0000000..685ed9d
--- /dev/null
+++ b/src/kmk/subproc.bat
@@ -0,0 +1,24 @@
+@echo off
+rem Copyright (C) 1996-2016 Free Software Foundation, Inc.
+rem This file is part of GNU Make.
+rem
+rem GNU Make is free software; you can redistribute it and/or modify it under
+rem the terms of the GNU General Public License as published by the Free
+rem Software Foundation; either version 3 of the License, or (at your option)
+rem any later version.
+rem
+rem GNU Make is distributed in the hope that it will be useful, but WITHOUT
+rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for.
+rem more details.
+rem
+rem You should have received a copy of the GNU General Public License along
+rem with this program. If not, see <http://www.gnu.org/licenses/>.
+
+cd w32\subproc
+set MAKE=%2
+set MAKEFILE=%1
+if x%2 == x set MAKE=nmake
+%MAKE% /f %MAKEFILE%
+if ERRORLEVEL 1 exit /B
+cd ..\..
diff --git a/src/kmk/testcase-2ndtargetexp.kmk b/src/kmk/testcase-2ndtargetexp.kmk
new file mode 100644
index 0000000..1e19a8b
--- /dev/null
+++ b/src/kmk/testcase-2ndtargetexp.kmk
@@ -0,0 +1,68 @@
+# $Id: testcase-2ndtargetexp.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the 2nd target expansion feature.
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+#
+# Enable it.
+#
+.SECONDTARGETEXPANSION:
+
+
+#
+# This is expanded immediately.
+#
+foo1 = foo1
+$(foo1):
+ $(if $(eq $@,foo1),$(ECHO) "foo1 works",$(ECHO) "foo1 is busted @=$@"; exit 1)
+
+# Mostly for making sure the ifeq test works below.
+flush_command_recoding := 1 # see record_waiting_files() in read.c
+ifeq ($(strip $(commands foo1)),)
+$(error No commands for foo1: $(commands foo1))
+endif
+
+
+#
+# This is expanded in the 2nd round.
+#
+$$(foo2):
+ $(if $(eq $@,foo2),$(ECHO) "foo2 works",$(ECHO) "foo2 is busted @=$@"; exit 1)
+
+# Check that a $(foo2) file exists.
+flush_command_recoding := 1 # see record_waiting_files() in read.c
+# $ (info $$(foo2) commands: $(commands $$(foo2)))
+ifeq ($(strip $(commands $$(foo2))),)
+$(error No commands for $$(foo2): $(commands $$(foo2)))
+endif
+
+
+all_recursive: foo1 foo2
+ $(ECHO) "2nd target expansion passes smoke testing"
+
+# define this last
+foo2 = foo2
+
diff --git a/src/kmk/testcase-assignments.kmk b/src/kmk/testcase-assignments.kmk
new file mode 100644
index 0000000..1ef2431
--- /dev/null
+++ b/src/kmk/testcase-assignments.kmk
@@ -0,0 +1,29 @@
+# $Id: testcase-assignments.kmk 3154 2018-03-15 23:35:33Z bird $
+# Testcase for weird various assignment operators and parsing.
+
+
+
+SIMPLE1 := simple1
+ifneq ($(SIMPLE1),simple1)
+ $(error simple1 assignment no 1 failed: SIMPLE1=$(SIMPLE1))
+endif
+
+SIMPLE2 := simple2-$(SIMPLE1)
+ifneq ($(SIMPLE2),simple2-simple1)
+ $(error simple assignment no 2 failed: $(SIMPLE2))
+endif
+
+$(SIMPLE1)-3 := simple3-$(SIMPLE1)
+ifneq ($(simple1-3),simple3-simple1)
+ $(error simple assignment no 3 failed: $($(SIMPLE1)-3))
+endif
+
+$(subst 1,4,$(SIMPLE1)) := simple4
+ifneq ($(simple4),simple4)
+ $(error simple assignment no 4 failed: simple4=$(simple4)) # (Including an equal inside the error call here.)
+endif
+
+all:
+ @echo okay
+
+
diff --git a/src/kmk/testcase-if1of.kmk b/src/kmk/testcase-if1of.kmk
new file mode 100644
index 0000000..dc878ba
--- /dev/null
+++ b/src/kmk/testcase-if1of.kmk
@@ -0,0 +1,80 @@
+# $Id: testcase-if1of.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the if1of and ifn1of conditionals.
+#
+
+#
+# Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+# the basics.
+if1of (asdf,asdf)
+else
+ $(error busted)
+endif
+ifn1of (asdf,asdf)
+ $(error busted)
+endif
+
+# larger sets.
+if1of (1,2 3 4 5 6 7 8 9 0)
+ $(error busted)
+endif
+if1of (1,12 3 4 5 6 7 8 9 0)
+ $(error busted)
+endif
+if1of (1,2 31 4 5 6 7 8 9 0)
+ $(error busted)
+endif
+ifn1of (1,1 2 3 4 5 6 7 8 9 0)
+ $(error busted)
+endif
+ifn1of (8,1 2 3 4 5 6 7 8 9 0)
+ $(error busted)
+endif
+ifn1of (asdf,asdf)
+ $(error busted)
+endif
+ifn1of (asdf,asdf asdf)
+ $(error busted)
+endif
+
+# any in set 1 match any in set 2.
+if1of (1 3 5 7 9, 2 4 6 8)
+ $(error busted)
+endif
+ifn1of (1 2 3 4 5, 2 4 6 8)
+ $(error busted)
+endif
+
+# real life.
+ifn1of (win linux, linux)
+ $(error busted)
+endif
+ifn1of (win.x86, win.amd64 linux.x86 darwin.x86 win.x86 os2.x86)
+ $(error busted)
+endif
+
+
+all_recursive:
+ $(ECHO) "if1of and ifn1of work fine"
+
diff --git a/src/kmk/testcase-ifeq-escape.kmk b/src/kmk/testcase-ifeq-escape.kmk
new file mode 100644
index 0000000..500b812
--- /dev/null
+++ b/src/kmk/testcase-ifeq-escape.kmk
@@ -0,0 +1,18 @@
+# $Id: testcase-ifeq-escape.kmk 3154 2018-03-15 23:35:33Z bird $
+# Testcase for weird 'ifeq' and funny escapes.
+
+ifeq "1 \
+ \
+ \
+ \
+ \
+ " \
+"1 "
+$(info info: ifeq -> equal)
+else
+$(error info: ifeq -> not equal - wrong)
+endif
+
+all:
+ @echo okay
+
diff --git a/src/kmk/testcase-includedep-esc-sub.kmk b/src/kmk/testcase-includedep-esc-sub.kmk
new file mode 100644
index 0000000..fe132d5
--- /dev/null
+++ b/src/kmk/testcase-includedep-esc-sub.kmk
@@ -0,0 +1,113 @@
+# $Id: testcase-includedep-esc-sub.kmk 3318 2020-04-01 07:05:32Z bird $
+## @file
+# kBuild - testcase for the includdep directive with filename escaping, helper
+# file that gets included.
+#
+
+#
+# Copyright (c) 2007-2020 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+testcase-includedep-esc-sub.kmk=included
+
+# spaces
+/phoney/file-with-two-spaces\ \ .c: \
+ /phoney/header-file-with-two-spaces\ \ .h
+
+
+#
+# From example-spaces.kmk
+#
+/phoney-dep-ignore:
+
+phoney\ space\ \ 1: /phoney-dep\ space\ \ 1
+
+phoney\ colon\:\ 2: /phoney-dep\ colon\:\ 2 \
+
+phoney\ hash\#\ 3 : /phoney-dep\ hash\#\ 3
+
+phoney\ dollar$$\ 4 : /phoney-dep\ dollar$$\ 4 \
+
+phoney\ slash-space\\\ 5: /phoney-dep\ slash-space\\\ 5
+
+phoney\ slash-hash\\\#\ 6: /phoney-dep\ slash-hash\\\#\ 6 \
+
+phoney\ slash-slash-hash\\\\\#\ 7: /phoney-dep\ slash-slash-hash\\\\\#\ 7
+
+phoney\ equal\=\ 8: /phoney-dep\ equal\=\ 8
+
+phoney\ semI\;\ 9: /phoney-dep\ semi\;\ 9
+
+# Note! The percent is only escaped on the target side!
+phoney\ percent\%\ 10: /phoney-dep\ percent%\ 10 \
+
+# Note! The pipe is only escaped on the dependency list side!
+phoney\ pipe|\ 11: /phoney-dep\ pipe\|\ 11
+
+phoney\ plus+\ 12: \
+ /phoney-dep\ plus+\ 12
+
+phoney\ trailing-slash13\\: /phoney-dep\ trailing-slash13\ \
+
+phoney\ trailing-slash13b\\: /phoney-dep\ trailing-slash13b\ \
+ \
+ \
+ \
+
+phoney\ trailing-slash14\\: \
+ /phoney-dep\ trailing-slash14\\ \
+ /phoney-dep-ignore
+
+phoney\ 15-trailing-space\ : /phoney-dep\ 15-trailing-space\ /phoney-dep-ignore
+
+# Note! No stripping spaces! Trailing space here that gets stripped instead of escaped.
+phoney\ 16-no-trailing-space\\: /phoney-dep\ 16-no-trailing-space\
+
+phoney\ 17-3x-escaped-newlines\ becomes-single-space: /phoney-dep\ 17-3x-escaped-newlines\ \
+\
+\
+ becomes-single-space
+
+# Note! Must have a trailing space or comment.
+phoney\ 18-3x-escaped-trailing-spaces-no-newline\ \ \\: \
+ /phoney-dep\ 18-3x-escaped-trailing-spaces-no-newline\ \ \
+
+phoney\ 19-target-trailing-space-with-padding\ : /phoney-dep\ 19-target-trailing-space-with-padding\ /phoney-dep-ignore
+
+phoney\ 20-target-trailing-space-with-newline-padding\ \
+\
+: /phoney-dep\ 20-target-trailing-space-with-newline-padding\ /phoney-dep-ignore
+
+phoney\ 21-target-trailing-space-with-newline-padding-and-tail\ \
+\
+ \
+ \
+my-tail-21: /phoney-dep\ 21-target-trailing-space-with-newline-padding-and-tail\ \
+\
+\
+my-tail-21
+
+
+all-trailing-slashes1: /phoney-dep\ trailing-slash13\ \
+
+all-trailing-slashes2: /phoney-dep\ trailing-slash13b\ \
+ \
+ \
+ \
+
diff --git a/src/kmk/testcase-includedep-esc.kmk b/src/kmk/testcase-includedep-esc.kmk
new file mode 100644
index 0000000..0f08b79
--- /dev/null
+++ b/src/kmk/testcase-includedep-esc.kmk
@@ -0,0 +1,133 @@
+# $Id: testcase-includedep-esc.kmk 3318 2020-04-01 07:05:32Z bird $
+## @file
+# kBuild - testcase for the includedep directive with filename escaping.
+#
+
+#
+# Copyright (c) 2008-2020 knut st. osmundsen <bird-kBuild-spamxx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+all_recursive:
+ $(ECHO) "includedep works fine"
+
+# Include before header to avoid secondary expansion in the noraml
+# include scenario.
+ifndef USE_NORMAL_INCLUDE
+includedep testcase-includedep-esc-sub.kmk
+else
+include testcase-includedep-esc-sub.kmk
+endif
+
+# We need the header for variables for special characters.
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+
+ifneq ("$(deps-all /phoney/file-with-two-spaces .c)","/phoney/header-file-with-two-spaces .h")
+$(error /phoney/file-with-two-spaces .c: $(deps-all /phoney/file-with-two-spaces .c))
+endif
+
+ifneq ("$(deps-all phoney space 1)","/phoney-dep space 1")
+$(error phoney space 1: $(deps-all phoney space 1))
+endif
+
+ifneq ("$(deps-all phoney colon: 2)","/phoney-dep colon: 2")
+$(error phoney colon: 2: $(deps-all phoney colon: 2))
+endif
+
+ifneq ("$(deps-all phoney hash$(HASH) 3)","/phoney-dep hash$(HASH) 3")
+$(error phoney hash$(HASH) 3: $(deps-all phoney hash$(HASH) 3))
+endif
+
+ifeq ("$(deps-all phoney dollar$$ 4)","$/phoney-dep dollar$(DOLLAR) 4")
+$(error phoney dollar$$ 4: $(deps-all phoney dollar$$ 4))
+endif
+
+ifneq ("$(deps-all phoney slash-space\ 5)","/phoney-dep slash-space\ 5")
+$(error phoney slash-space\ 5: $(deps-all phoney slash-space\ 5))
+endif
+
+ifneq ("$(deps-all phoney slash-hash\$(HASH) 6)","/phoney-dep slash-hash\$(HASH) 6")
+$(error phoney slash-hash\$(HASH) 6: $(deps-all phoney slash-hash\$(HASH) 6))
+endif
+
+ifneq ("$(deps-all phoney slash-slash-hash\\$(HASH) 7)","/phoney-dep slash-slash-hash\\$(HASH) 7")
+$(error phoney slash-slash-hash\\$(HASH) 7: $(deps-all phoney slash-slash-hash\\$(HASH) 7))
+endif
+
+ifneq ("$(deps-all phoney equal= 8)","/phoney-dep equal= 8")
+$(error phoney equal= 8: $(deps-all phoney equal= 8))
+endif
+
+ifneq ("$(deps-all phoney semI; 9)","/phoney-dep semi; 9")
+$(error phoney semI; 9: $(deps-all phoney semI; 9))
+endif
+
+ifneq ("$(deps-all phoney percent% 10)","/phoney-dep percent% 10")
+$(error phoney percent% 10: $(deps-all phoney percent% 10))
+endif
+
+ifneq ("$(deps-all phoney pipe| 11)","/phoney-dep pipe| 11")
+$(error phoney pipe| 11: $(deps-all phoney pipe| 11))
+endif
+
+ifneq ("$(deps-all phoney plus+ 12)","/phoney-dep plus+ 12")
+$(error phoney plus+ 12: $(deps-all phoney plus+ 12))
+endif
+
+ifneq ("$(deps-all phoney trailing-slash13\)","/phoney-dep trailing-slash13\")
+$(error phoney trailing-slash13\: $(deps-all phoney trailing-slash13\))
+endif
+
+ifneq ("$(deps-all phoney trailing-slash13b\)","/phoney-dep trailing-slash13b\")
+$(error phoney trailing-slash13b\: $(deps-all phoney trailing-slash13b\))
+endif
+
+ifneq ("$(deps-all phoney trailing-slash14\)","/phoney-dep trailing-slash14\ /phoney-dep-ignore")
+$(error phoney trailing-slash14\: $(deps-all phoney trailing-slash14\))
+endif
+
+ifneq ("$(deps-all phoney 15-trailing-space )","/phoney-dep 15-trailing-space /phoney-dep-ignore")
+$(error phoney 15-trailing-space : $(deps-all phoney 15-trailing-space ))
+endif
+
+ifneq ("$(deps-all phoney 16-no-trailing-space\)","/phoney-dep 16-no-trailing-space\")
+$(error phoney 16-no-trailing-space\: $(deps-all phoney 16-no-trailing-space\))
+endif
+
+ifneq ("$(deps-all phoney 17-3x-escaped-newlines becomes-single-space)","/phoney-dep 17-3x-escaped-newlines becomes-single-space")
+$(error phoney 17-3x-escaped-newlines becomes-single-space: $(deps-all phoney 17-3x-escaped-newlines becomes-single-space))
+endif
+
+ifneq ("$(deps-all phoney 18-3x-escaped-trailing-spaces-no-newline \)","/phoney-dep 18-3x-escaped-trailing-spaces-no-newline \")
+$(error phoney 18-3x-escaped-trailing-spaces-no-newline \: $(deps-all phoney 18-3x-escaped-trailing-spaces-no-newline \))
+endif
+
+ifneq ("$(deps-all phoney 19-target-trailing-space-with-padding )","/phoney-dep 19-target-trailing-space-with-padding /phoney-dep-ignore")
+$(error phoney 19-target-trailing-space-with-padding : $(deps-all phoney 19-target-trailing-space-with-padding ))
+endif
+
+ifneq ("$(deps-all phoney 20-target-trailing-space-with-newline-padding )","/phoney-dep 20-target-trailing-space-with-newline-padding /phoney-dep-ignore")
+$(error phoney 20-target-trailing-space-with-newline-padding : $(deps-all phoney 20-target-trailing-space-with-newline-padding ))
+endif
+
+ifneq ("$(deps-all phoney 21-target-trailing-space-with-newline-padding-and-tail my-tail-21)","/phoney-dep 21-target-trailing-space-with-newline-padding-and-tail my-tail-21")
+$(error 21-target-trailing-space-with-newline-padding-and-tail my-tail-21: $(deps-all 21-target-trailing-space-with-newline-padding-and-tail my-tail-21))
+endif
+
diff --git a/src/kmk/testcase-includedep-sub.kmk b/src/kmk/testcase-includedep-sub.kmk
new file mode 100644
index 0000000..b5bf546
--- /dev/null
+++ b/src/kmk/testcase-includedep-sub.kmk
@@ -0,0 +1,28 @@
+# $Id: testcase-includedep-sub.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the includdep directive, helper file
+# that gets included all the time.
+#
+
+#
+# Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+testcase-includedep-sub.kmk=included
+
diff --git a/src/kmk/testcase-includedep.kmk b/src/kmk/testcase-includedep.kmk
new file mode 100644
index 0000000..685f527
--- /dev/null
+++ b/src/kmk/testcase-includedep.kmk
@@ -0,0 +1,90 @@
+# $Id: testcase-includedep.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the includedep directive.
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is defined at the start of the testcase.)
+endif
+
+
+foo = testcase-includedep-sub
+includedep $(foo).kmk
+ifneq ($(testcase-includedep-sub.kmk),included)
+$(error The first test failed.)
+endif
+testcase-includedep-sub.kmk :=
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is persistent and does not want to be undefed.)
+endif
+
+
+foo = includedep
+includedep testcase-$(foo)-sub.kmk
+ifneq ($(testcase-includedep-sub.kmk),included)
+$(error The second test failed.)
+endif
+testcase-includedep-sub.kmk :=
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is persistent and does not want to be undefed.)
+endif
+
+
+foo = kmk
+includedep testcase-includedep-sub.$(foo)
+ifneq ($(testcase-includedep-sub.kmk),included)
+$(error The thrid test failed.)
+endif
+testcase-includedep-sub.kmk :=
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is persistent and does not want to be undefed.)
+endif
+
+
+includedep testcase-includedep-sub.kmk
+ifneq ($(testcase-includedep-sub.kmk),included)
+$(error The forth test failed.)
+endif
+testcase-includedep-sub.kmk :=
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is persistent and does not want to be undefed.)
+endif
+
+
+foo = asdf
+includedep testcase-$(foo)-sub.kmk
+ifeq ($(testcase-includedep-sub.kmk),included)
+$(error The fifth test failed.)
+endif
+testcase-includedep-sub.kmk :=
+ifdef testcase-includedep-sub.kmk
+$(error testcase-includedep-sub.kmk is persistent and does not want to be undefed.)
+endif
+
+
+all_recursive:
+ $(ECHO) "includedep works fine"
+
diff --git a/src/kmk/testcase-kBuild-define.kmk b/src/kmk/testcase-kBuild-define.kmk
new file mode 100644
index 0000000..1074e72
--- /dev/null
+++ b/src/kmk/testcase-kBuild-define.kmk
@@ -0,0 +1,141 @@
+# $Id: testcase-kBuild-define.kmk 2720 2014-01-01 22:59:50Z bird $
+## @file
+# kBuild - testcase for the kBuild-define-* directives.
+#
+
+#
+# Copyright (c) 2011-2013 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+#DEPTH = ../..
+#include $(PATH_KBUILD)/header.kmk
+
+##
+# Test if $($1) == $2 and raises an error if it isn't.
+#
+# @param 1 Something to apply '$' to.
+# @param 2 The expected value.
+TEST_EQ = $(if-expr "$($1)" == "$2",,$(error $1 is '$($1)' not '$2'))
+
+if 0
+# object definition syntax:
+# kobject <type> <name> [extends <object> [by <||>]] [object specific args...]
+# kendobj [<type> [name]]
+kobject kb-target MyTarget
+.TOOL = GCC
+.SOURCES = file.c
+kendobj
+else
+# Target definition.
+# kBuild-define-target <name> [extends <target> [by <||>]] [using <template>]
+# kBuild-endef-target [name]
+kBuild-define-target MyTarget
+_TOOL = GCC
+_SOURCES = file.c
+kBuild-endef-target
+endif
+
+if 0
+# accesses an already defined object.
+# syntax:
+# kaccess <type> <name>
+# kendacc [<type> [name]]
+kaccess kb-target MyTarget
+.SOURCES += file2.c
+kendacc
+else
+#kBuild-access-target MyTarget
+#_SOURCES += file2.c
+#kBuild-endacc-target
+endif
+
+
+# Referencing an object variable, the object must exist.
+# syntax: [<type>@<name>].<property>
+[target@MyTarget]_SOURCES += file3.c
+$(info [target@MyTarget]_SOURCES is $([target@MyTarget]_SOURCES))
+
+
+# Test #1
+kBuild-define-target BaseTarget using DUMMY
+_SOURCES = BaseTargetSource.c
+kBuild-endef-target BaseTarget
+$(if-expr "$([target@BaseTarget]_SOURCES)" == "BaseTargetSource.c",,$(error [target@BaseTarget]_SOURCES is '$([target@BaseTarget]_SOURCES)' not 'BaseTargetSource.c'))
+$(if-expr "$(BaseTarget_SOURCES)" == "BaseTargetSource.c",,$(error BaseTarget's _SOURCES wasn't set correctly in the global space))
+
+$(if-expr "$([target@BaseTarget]_TEMPLATE)" == "DUMMY",,$(error [target@BaseTarget]_TEMPLATE is '$([target@BaseTarget]_TEMPLATE)' not 'DUMMY'))
+$(if-expr "$(BaseTarget_TEMPLATE)" == "DUMMY",,$(error BaseTarget's _TEMPLATE wasn't set correctly in the global space))
+
+# Test #2
+kBuild-define-target TargetWithLocals
+local _LOCAL_PROP = no global alias
+kBuild-endef-target
+$(if-expr "$([target@TargetWithLocals]_LOCAL_PROP)" == "no global alias",,$(error [target@TargetWithLocals]_LOCAL_PROP is '$([target@TargetWithLocals]_LOCAL_PROP)' not 'no global alias'))
+$(if-expr "$(TargetWithLocals_LOCAL_PROP)" == "",,$(error TargetWithLocals_LOCAL_PROP's local property 'LOCAL_PROP' was exposed globally.))
+
+# Test #3
+kBuild-define-target OutsideMod
+_SOURCES = file3.c
+_OTHER = inside-value
+kBuild-endef-target
+[target@OutsideMod]_SOURCES += file4.c
+[target@OutsideMod]_SOURCES <= file2.c
+[target@OutsideMod]_OTHER = outside-value
+$(if-expr "$([target@OutsideMod]_SOURCES)" == "file2.c file3.c file4.c",,$(error [target@OutsideMod]_SOURCES is '$([target@OutsideMod]_SOURCES)' not 'file2.c file3.c file4.c'))
+$(if-expr "$(OutsideMod_SOURCES)" == "file2.c file3.c file4.c",,$(error OutsideMod_SOURCES is '$(OutsideMod_SOURCES)' not 'file2.c file3.c file4.c'))
+
+$(if-expr "$([target@OutsideMod]_OTHER)" == "outside-value",,$(error [target@OutsideMod]_OTHER is '$([target@OutsideMod]_OTHER)' not 'outside-value'))
+$(if-expr "$(OutsideMod_OTHER)" == "outside-value",,$(error OutsideMod_OTHER is '$(OutsideMod_OTHER)' not 'outside-value'))
+
+# Test #4
+kBuild-define-target SpecialBase
+_SOURCES = file1.c file2.c
+_DEFS.win.x86 = XXX YYY
+_DEFS.win.amd64 = $(filter-out YYY,$([@self]_DEFS.win.x86))
+# Unnecessary use of [@self].
+[@self]_LIBS = MyLib
+kBuild-endef-target
+
+kBuild-define-target SpecialChild extending SpecialBase
+_SOURCES = file1-child.c $(filter-out file1.c,$([@super]_SOURCES))
+# Rare use of [@super].
+[@super]_SET_BY_CHILD = 42
+kBuild-endef-target
+
+$(call TEST_EQ,[target@SpecialBase]_LIBS,MyLib)
+$(call TEST_EQ,SpecialBase_LIBS,MyLib)
+
+$(call TEST_EQ,[target@SpecialBase]_SET_BY_CHILD,42)
+$(call TEST_EQ,SpecialBase_SET_BY_CHILD,42)
+$(call TEST_EQ,[target@SpecialChild]_SET_BY_CHILD,42)
+#$(call TEST_EQ,SpecialChild_SET_BY_CHILD,42) ## @todo
+
+$(call TEST_EQ,[target@SpecialBase]_DEFS.win.x86,XXX YYY)
+$(call TEST_EQ,[target@SpecialBase]_DEFS.win.amd64,XXX)
+$(call TEST_EQ,SpecialBase_DEFS.win.amd64,XXX)
+$(call TEST_EQ,[target@SpecialChild]_DEFS.win.x86,XXX YYY)
+$(call TEST_EQ,[target@SpecialChild]_DEFS.win.amd64,XXX)
+#$(call TEST_EQ,SpecialChild_DEFS.win.amd64,XXX) ## @todo
+
+$(call TEST_EQ,[target@SpecialChild]_SOURCES,file1-child.c file2.c)
+$(call TEST_EQ,SpecialChild_SOURCES,file1-child.c file2.c)
+
+all_recursive:
+ @kmk_echo "kBuild-define-xxxx works fine"
+
diff --git a/src/kmk/testcase-lazy-deps-vars.kmk b/src/kmk/testcase-lazy-deps-vars.kmk
new file mode 100644
index 0000000..75b6b9b
--- /dev/null
+++ b/src/kmk/testcase-lazy-deps-vars.kmk
@@ -0,0 +1,72 @@
+# $Id: testcase-lazy-deps-vars.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the lazy dependency lists.
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+ifneq ($(not 1),)
+ $(error The 'not' function is missing)
+endif
+ifneq ($(eq 1,1),1)
+ $(error The 'eq' function is missing)
+endif
+
+
+all: simple_1
+
+
+simple_1: variable.c variable.h variable.c variable.c variable.h function.c | variable.h read.c
+ @$(ECHO) "testcase-lazy-deps-vars.kmk::$@: TESTING..."
+ @$(ECHO) "pluss: $+"
+ $(if $(eq $+,variable.c variable.h variable.c variable.c variable.h function.c),,exit 1)
+ $(if $(eq $(deps-all $@,1),variable.c),,exit 1)
+ $(if $(eq $(deps-all $@,2),variable.h),,exit 2)
+ $(if $(eq $(deps-all $@,3),variable.c),,exit 3)
+ $(if $(eq $(deps-all $@,4),variable.c),,exit 4)
+ $(if $(eq $(deps-all $@,5),variable.h),,exit 5)
+ $(if $(eq $(deps-all $@,6),function.c),,exit 6)
+ $(if $(eq $(deps-all $@,7),),,exit 7)
+
+ @$(ECHO) "caret: $^"
+ $(if $(eq $^,variable.c variable.h function.c),,exit 1)
+ $(if $(eq $(deps $@,1),variable.c),,exit 1)
+ $(if $(eq $(deps $@,2),variable.h),,exit 2)
+ $(if $(eq $(deps $@,3),function.c),,exit 3)
+ $(if $(eq $(deps $@,4),),,exit 4)
+
+ @$(ECHO) "qmark: $?"
+ $(if $(eq $?,variable.c variable.h function.c),,exit 1)
+ $(if $(eq $(deps-newer $@,1),variable.c),,exit 1)
+ $(if $(eq $(deps-newer $@,2),variable.h),,exit 2)
+ $(if $(eq $(deps-newer $@,3),function.c),,exit 3)
+ $(if $(eq $(deps-newer $@,4),),,exit 4)
+
+ @$(ECHO) " bar: $|"
+ $(if $(eq $|,read.c),,exit 1)
+ $(if $(eq $(deps-oo $@,1),read.c),,exit 1)
+ $(if $(eq $(deps-oo $@,2),),,exit 2)
+
+ @$(ECHO) "testcase-lazy-deps-vars.kmk::simple_1: SUCCESS"
+
diff --git a/src/kmk/testcase-libpath.kmk b/src/kmk/testcase-libpath.kmk
new file mode 100644
index 0000000..c5c0efc
--- /dev/null
+++ b/src/kmk/testcase-libpath.kmk
@@ -0,0 +1,20 @@
+
+all:
+ @kmk_builtin_echo ""
+ @kmk_builtin_echo "Warning: This testcase requires manual inspection and is intended for OS/2 only."
+ @kmk_builtin_echo ""
+ @kmk_builtin_echo "getting:"
+ @kmk_builtin_echo "libpath BEGINLIBPATH: $(libpath BEGINLIBPATH)"
+ @kmk_builtin_echo "libpath LIBPATH: $(libpath LIBPATH)"
+ @kmk_builtin_echo "libpath ENDLIBPATH: $(libpath ENDLIBPATH)"
+ @kmk_builtin_echo "libpath LIBPATHSTRICT: $(libpath LIBPATHSTRICT)"
+ @kmk_builtin_echo "setting:"
+ @kmk_builtin_echo "libpath ENDLIBPATH,\foobar: $(libpath ENDLIBPATH,\foobar) -> $(libpath ENDLIBPATH)"
+ @kmk_builtin_echo "libpath ENDLIBPATH,: $(libpath ENDLIBPATH,) -> $(libpath ENDLIBPATH)"
+ @kmk_builtin_echo "libpath BEGINLIBPATH,\qwerty: $(libpath BEGINLIBPATH,\qwerty) -> $(libpath BEGINLIBPATH)"
+ @kmk_builtin_echo "libpath BEGINLIBPATH,: $(libpath BEGINLIBPATH,) -> $(libpath BEGINLIBPATH)"
+ @kmk_builtin_echo "libpath LIBPATHSTRICT,T: $(libpath LIBPATHSTRICT,T) -> $(libpath LIBPATHSTRICT)"
+ @kmk_builtin_echo "libpath LIBPATHSTRICT,qwerty: $(libpath LIBPATHSTRICT,qwerty) -> $(libpath LIBPATHSTRICT)"
+ @kmk_builtin_echo "libpath LIBPATHSTRICT,F: $(libpath LIBPATHSTRICT,F) -> $(libpath LIBPATHSTRICT)"
+ @kmk_builtin_echo "libpath LIBPATHSTRICT,: $(libpath LIBPATHSTRICT,) -> $(libpath LIBPATHSTRICT)"
+
diff --git a/src/kmk/testcase-local.kmk b/src/kmk/testcase-local.kmk
new file mode 100644
index 0000000..710f0e9
--- /dev/null
+++ b/src/kmk/testcase-local.kmk
@@ -0,0 +1,127 @@
+#
+# local variables:
+# o The keyword will make sure the variable is defined in
+# current variable context instead of the global one.
+# o Local variables are readable by children but not writable,
+# writes goes to the globle space (a sideeffect / feature).
+# o Local variables hides global and parent variables.
+#
+
+
+# global variable.
+var_exists1 = 1
+
+
+
+##
+# A simple define that is $(eval)uated.
+define def_test1
+
+# check that the existing variable is accessible.
+ifneq ($(var_exists1),1)
+ $(error var_exists1=$(var_exists1) (def_test1/1))
+endif
+
+# Quick check with a couple of local variables.
+local var_local1 = 2
+ifneq ($(var_local1),2)
+ $(error var_local1=$(var_local1) (def_test1/2))
+endif
+local var_local2 = 3
+ifneq ($(var_local2),3)
+ $(error var_local2=$(var_local2) (def_test1/3))
+endif
+
+# var_local1 and var_local2 should remain unchanged, var_local3 shouldn't exist.
+$(evalctx $(value def_test2))
+
+ifneq ($(var_local1),2)
+ $(error var_local1=$(var_local1) (def_test1/4))
+endif
+ifneq ($(var_local2),3)
+ $(error var_local2=$(var_local2) (def_test1/5))
+endif
+ifneq ($(var_local3),)
+ $(error var_local3=$(var_local3) (def_test1/6))
+endif
+
+endef # def_test1
+
+
+
+##
+# Called by def_test1, this checks that the locals of def_test1
+# are accessible and can be hidden by another local variable
+# or updated if assigned to.
+define def_test2
+
+# check that the existing variables are accessible, including the def_test1 ones.
+ifneq ($(var_exists1),1)
+ $(error var_exists1=$(var_exists1) (def_test2/1))
+endif
+ifneq ($(var_local1),2)
+ $(error var_local1=$(var_local1) (def_test2/2))
+endif
+ifneq ($(var_local2),3)
+ $(error var_local2=$(var_local2) (def_test2/3))
+endif
+
+# Make a local var_local1 that hides the one in def_test1.
+local var_local1 = 20
+ifneq ($(var_local1),20)
+ $(error var_local1=$(var_local1) (def_test2/4))
+endif
+
+# FEATURE: Update the var_local2 variable, this should be visible in the global space and not the local.
+var_local2 = 30
+ifneq ($(var_local2),3)
+ $(error var_local2=$(var_local2) (def_test2/5))
+endif
+
+# create a new local variable that isn't accessible from def_test1.
+local var_local3 = 4
+ifneq ($(var_local3),4)
+ $(error var_local3=$(var_local3) (def_test2/6))
+endif
+
+endef # def_test2
+
+
+
+#
+# The test body
+#
+
+# None of the local variables should exist.
+ifneq ($(var_local1),)
+ $(error var_local1=$(var_local1))
+endif
+ifneq ($(var_local2),)
+ $(error var_local2=$(var_local2))
+endif
+ifneq ($(var_local3),)
+ $(error var_local3=$(var_local3))
+endif
+
+# Evaluate the function in a local context.
+$(evalctx $(value def_test1))
+
+# FEATURE: see var_local2 = 30 in def_test2.
+ifneq ($(var_local2),30)
+ $(error var_local2=$(var_local2))
+endif
+
+# None of the other local variables should exist.
+ifneq ($(var_local1),)
+ $(error var_local1=$(var_local1))
+endif
+ifneq ($(var_local3),)
+ $(error var_local3=$(var_local3))
+endif
+
+
+
+# dummy
+all:
+ echo local variables works.
+
diff --git a/src/kmk/testcase-math.kmk b/src/kmk/testcase-math.kmk
new file mode 100644
index 0000000..bd4fb7e
--- /dev/null
+++ b/src/kmk/testcase-math.kmk
@@ -0,0 +1,98 @@
+# $Id: testcase-math.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the math functions.
+#
+
+#
+# Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+ifneq ($(not 1),)
+ $(error The 'not' function is missing)
+endif
+ifneq ($(eq 1,1),1)
+ $(error The 'eq' function is missing)
+endif
+
+
+ASSERT_TRUE = $(if $(not $(1)),$(error failure: '$(1)' isn't true))
+ASSERT_FALSE = $(if $(1) ,$(error failure: '$(1)' isn't false))
+
+$(call ASSERT_TRUE, $(int-eq 0x0, 0))
+$(call ASSERT_FALSE,$(int-eq 1, 0))
+$(call ASSERT_FALSE,$(int-eq 1123123123, 9898787987))
+$(call ASSERT_TRUE, $(int-eq 1234567890, 1234567890))
+$(call ASSERT_TRUE, $(int-eq 0x1c, 28))
+$(call ASSERT_TRUE, $(int-eq 1c, 28))
+$(call ASSERT_TRUE, $(int-ne 0x123, -0x123))
+$(call ASSERT_TRUE, $(int-ne 123, -0x123))
+$(call ASSERT_FALSE,$(int-ne 0x100, 256))
+$(call ASSERT_FALSE,$(int-ne 0x0, 0))
+$(call ASSERT_FALSE,$(int-ne 0x1c, 28))
+$(call ASSERT_TRUE, $(int-le 0, 0))
+$(call ASSERT_TRUE, $(int-le -0, 0))
+$(call ASSERT_FALSE,$(int-le 5, 1))
+$(call ASSERT_FALSE,$(int-lt 5, 1))
+$(call ASSERT_FALSE,$(int-lt 5, 5))
+$(call ASSERT_TRUE, $(int-lt 9, 10))
+$(call ASSERT_TRUE, $(int-lt -9, -8))
+$(call ASSERT_TRUE, $(int-ge 0, 0))
+$(call ASSERT_TRUE, $(int-ge -0, 0))
+$(call ASSERT_TRUE, $(int-ge 1, 0))
+$(call ASSERT_TRUE, $(int-ge -55, -55))
+$(call ASSERT_TRUE, $(int-ge 512, 400))
+$(call ASSERT_TRUE, $(int-ge -18, -19))
+$(call ASSERT_FALSE,$(int-ge -19, -18))
+$(call ASSERT_FALSE,$(int-ge 15, 20))
+$(call ASSERT_FALSE,$(int-gt 15, 20))
+$(call ASSERT_FALSE,$(int-gt 15, 15))
+$(call ASSERT_TRUE, $(int-gt 20, 15))
+
+ASSERT2 = $(if $(not $(int-eq $(1),$(2))),$(error failure: '$(1)' -ne '$(2)'))
+$(call ASSERT2,$(int-add 1, 1),0x2)
+$(call ASSERT2,$(int-add 1, 1, 1, 1, 1, 1, 1),7)
+$(call ASSERT2,$(int-add 1, -1),0)
+$(call ASSERT2,$(int-sub 1, -1),2)
+$(call ASSERT2,$(int-sub 1, 5),-4)
+$(call ASSERT2,$(int-mul 0x10, 0x20),0x200)
+$(call ASSERT2,$(int-mul 0x20, 0x10),0x200)
+$(call ASSERT2,$(int-mul 4, 7),28)
+$(call ASSERT2,$(int-mul 2, 2, 2, 2, 2, 4, 1, 1, 1, 1),128)
+$(call ASSERT2,$(int-div 0x1000, 0x100),0x10)
+$(call ASSERT2,$(int-div 999, 10),99)
+$(call ASSERT2,$(int-div 4096, 4,2,2,2,2),64)
+#$(call ASSERT2,$(int-div 0x1230023213, 0),0x0)
+$(call ASSERT2,$(int-mod 19, 10),9)
+$(call ASSERT2,$(int-mod 9, 10),9)
+$(call ASSERT2,$(int-mod 30, 10),0)
+$(call ASSERT2,$(int-not 0),-1)
+$(call ASSERT2,$(int-and 1, 1),1)
+$(call ASSERT2,$(int-and 0x123123214, 0xfff),0x214)
+$(call ASSERT2,$(int-and 0x123123214, 0xf0f, 0xf),4)
+$(call ASSERT2,$(int-or 1, 1, 1, 2, 2),3)
+$(call ASSERT2,$(int-xor 1, 1, 2, 2),0)
+$(call ASSERT2,$(int-xor 1, 2, 4),7)
+
+
+all_recursive:
+ $(ECHO) The math works. 6 * 7 = $(int-mul 6,7)
+
diff --git a/src/kmk/testcase-root.kmk b/src/kmk/testcase-root.kmk
new file mode 100644
index 0000000..92de5c5
--- /dev/null
+++ b/src/kmk/testcase-root.kmk
@@ -0,0 +1,30 @@
+#
+# The $(root ...) and $(notroot ) functions.
+#
+
+
+
+x := $(root /a)
+y := $(notroot /a)
+ifneq ($x,/)
+ $(error x=$x)
+endif
+ifneq ($y,a)
+ $(error y=$y)
+endif
+
+x := $(root /a /b /)
+y := $(notroot /a /b /)
+ifneq ($x,/ / /)
+ $(error x=$x)
+endif
+ifneq ($y,a b .)
+ $(error y=$y)
+endif
+
+
+# dummy
+all:
+ echo The root and notroot functions works.
+
+
diff --git a/src/kmk/testcase-stack.kmk b/src/kmk/testcase-stack.kmk
new file mode 100644
index 0000000..e610319
--- /dev/null
+++ b/src/kmk/testcase-stack.kmk
@@ -0,0 +1,86 @@
+# $Id: testcase-stack.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the functions.
+#
+
+#
+# Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+ifneq ($(not 1),)
+ $(error The 'not' function is missing)
+endif
+ifneq ($(eq 1,1),1)
+ $(error The 'eq' function is missing)
+endif
+
+ASSERT1 = $(if $(not $(eq $(STACK1),$(1))),$(error failure: STACK1:='$(STACK1)' expected='$(1)'))
+$(call stack-push,STACK1,1)
+$(call ASSERT,1)
+$(call stack-push,STACK1,2)
+$(call ASSERT,1 2)
+$(call stack-push,STACK1,3)
+$(call ASSERT,1 2 3)
+$(call stack-push,STACK1,4)
+$(call ASSERT,1 2 3 4)
+$(call stack-push,STACK1,5)
+$(call ASSERT,1 2 3 4 5)
+$(call stack-popv,STACK1)
+$(call ASSERT,1 2 3 4)
+$(call stack-push,STACK1,5)
+$(call ASSERT,1 2 3 4 5)
+$(call stack-popv,STACK1)
+$(call ASSERT,1 2 3 4)
+$(call stack-popv,STACK1)
+$(call ASSERT,1 2 3)
+$(call stack-push,STACK1,4)
+$(call ASSERT,1 2 3 4)
+$(call stack-push,STACK1,5)
+$(call ASSERT,1 2 3 4 5)
+top := $(call stack-top,STACK1)
+$(if $(not $(eq $(top),5)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='5'))
+$(call ASSERT,1 2 3 4 5)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),5)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='5'))
+$(call ASSERT,1 2 3 4)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),4)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='4'))
+$(call ASSERT,1 2 3)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),3)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='3'))
+$(call ASSERT,1 2)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),2)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='2'))
+$(call ASSERT,1)
+top := $(call stack-top,STACK1)
+$(if $(not $(eq $(top),1)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='1'))
+$(call ASSERT,1)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),1)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected='1'))
+$(call ASSERT,)
+top := $(call stack-pop,STACK1)
+$(if $(not $(eq $(top),)),$(error failure STACK1:='$(STACK1)' top:='$(top)' expected=''))
+$(call ASSERT,)
+
+all_recursive:
+ $(ECHO) The stack works.$(STACK1)
+
diff --git a/src/kmk/testcase-which.kmk b/src/kmk/testcase-which.kmk
new file mode 100644
index 0000000..9ff82c7
--- /dev/null
+++ b/src/kmk/testcase-which.kmk
@@ -0,0 +1,5 @@
+
+all:
+ @echo which kmk: $(which kmk)
+ @echo which kmk: $(which kmk kmk kmk )
+
diff --git a/src/kmk/testcase-xargs.kmk b/src/kmk/testcase-xargs.kmk
new file mode 100644
index 0000000..ee6b5a8
--- /dev/null
+++ b/src/kmk/testcase-xargs.kmk
@@ -0,0 +1,59 @@
+# $Id: testcase-xargs.kmk 2413 2010-09-11 17:43:04Z bird $
+## @file
+# kBuild - testcase for the xargs function.
+# Requires manual inspection of the output.
+#
+
+#
+# Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+DEPTH = ../..
+include $(PATH_KBUILD)/header.kmk
+
+ifneq ($(not 1),)
+ $(error The 'not' function is missing)
+endif
+ifneq ($(eq 1,1),1)
+ $(error The 'eq' function is missing)
+endif
+
+
+ASSERT_TRUE = $(if $(not $(1)),$(error failure: '$(1)' isn't true))
+ASSERT_FALSE = $(if $(1) ,$(error failure: '$(1)' isn't false))
+
+# 94 bytes
+ONEARG = abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_xxxxxxxxxxxx
+ITERATIONS := 0 1 2 3 4 5 6 7 8 9
+ITERATIONS := $(foreach i, 0 1 2 3 4 5 6 7 8 9,$(addprefix $(i),$(ITERATIONS)))
+ITERATIONS := $(foreach i, 0 1 2 3 4 5 6 7 8 9,$(addprefix $(i),$(ITERATIONS)))
+ITERATIONS := $(foreach i, 0 1 2 3 4 5 6 7 8 9,$(addprefix $(i),$(ITERATIONS)))
+ITERATIONS := $(foreach i, 0 1 2 3 4 5 6 7 8 9,$(addprefix $(i),$(ITERATIONS)))
+
+# add a 5 bytes sequence number and a space, then duplicate it 10000 times:
+# 100 bytes * 10000 = 1,000,000 bytes.
+REALLY_LONG := $(foreach i,$(ITERATIONS),$(i)$(ONEARG))
+
+
+#$(call ASSERT_TRUE, $(xargs $(ECHO) 1:, $(ECHO) 2:, $(ECHO) 3:, asdf asdf asdf asdf asdf asdf asdf adf asdf asdf))
+
+all_recursive:
+ $(xargs @$(ECHO_EXT) 1:, @$(ECHO_EXT) 2:, @$(ECHO_EXT) 3:, $(REALLY_LONG))
+ $(ECHO) done
+
diff --git a/src/kmk/testcase/testcase-export.kmk b/src/kmk/testcase/testcase-export.kmk
new file mode 100644
index 0000000..5d0c4b4
--- /dev/null
+++ b/src/kmk/testcase/testcase-export.kmk
@@ -0,0 +1,48 @@
+
+
+var0 = value0
+var1 = value1
+var2 = value2
+var3 = value3
+var4 = value4
+var5 = value5
+var6 = value6
+var7 = value7
+var8 = value8
+var9 = value9
+
+varname1 = var1
+varname2 = var2
+varname3 = var3
+varname4 = var4
+varname5 = var5
+varname5 = var5
+varname6 = var6
+varname7 = var7
+varname8 = var8
+varname9 = var9
+
+export var0 var8 $(varname1) $(subst foo,var, foo2 )
+export $(foreach x, 3 4 \
+,$(subst \
+odd(, \
+ parenthesis parsing behaviour), dont-mind-this-type{ except if you put a ${dollar} in front of it, \
+$(varname$(x)) \
+)\
+)
+
+export ${foreach x, 5 \
+,${subst \
+odd{, \
+ parenthesis parsing behaviour}, dont-mind-this-type( \
+ except if you put a $(dollar) in front of it; two dollars $$(does do the trick though, \
+${varname$x}} \
+}\
+}
+
+export $ ${varname6}
+export $(varname7$)
+
+all:
+ kmk_ash -c "export | kmk_sed '/var/!d'
+
diff --git a/src/kmk/tests/.gitignore b/src/kmk/tests/.gitignore
new file mode 100644
index 0000000..a30a689
--- /dev/null
+++ b/src/kmk/tests/.gitignore
@@ -0,0 +1,2 @@
+config-flags.pm
+work
diff --git a/src/kmk/tests/COPYING b/src/kmk/tests/COPYING
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/src/kmk/tests/COPYING
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/src/kmk/tests/ChangeLog.1 b/src/kmk/tests/ChangeLog.1
new file mode 100644
index 0000000..684af03
--- /dev/null
+++ b/src/kmk/tests/ChangeLog.1
@@ -0,0 +1,1429 @@
+2013-10-09 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/patspecific_vars: Typo fixes.
+
+2013-10-05 Paul Smith <psmith@gnu.org>
+
+ * test_driver.pl (run_all_tests): Rewrite to be more clear.
+ * scripts/features/jobserver: Avoid using $ENV{HOME} as it doesn't
+ exist everywhere.
+ * scripts/features/default_names: End with 1;
+
+ * scripts/features/loadapi: Use new calling signatures. Verify
+ the NOEXPAND flag works. Test with all valid function name
+ characters.
+
+2013-09-29 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/SHELL: Solaris /bin/sh can't handle options in
+ multiple words; skip that test.
+ * scripts/targets/ONESHELL: Ditto.
+
+ * scripts/variables/GNUMAKEFLAGS: Verify that GNUMAKEFLAGS is
+ cleared and options are not duplicated.
+
+2013-09-23 Paul Smith <psmith@gnu.org>
+
+ * scripts/options/print-directory: Rename dash-w to
+ print-directory to avoid conflicts with dash-W on case-insensitive
+ filesystems.
+
+2013-09-22 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/se_implicit: Verify that order-only tokens
+ inside second expansion are parsed correctly.
+ Test for Savannah bug #31155.
+
+ * run_make_tests.pl (set_more_defaults): If we can't find
+ gnumake.h based on the make program we might be running from a
+ remote build directory. Parse the Makefile for the right path.
+
+ Fix some test issues on Solaris.
+
+ * scripts/features/archives: Determine what output ar gives when
+ adding and replacing objects and compare with that.
+ * scripts/features/escape: Solaris /bin/sh doesn't properly handle
+ backslashes inside single quotes, so don't rely on it.
+ * scripts/features/output-sync: false(1) gives different exit
+ codes on different systems; use "exit 1" instead.
+ * scripts/features/parallelism: Increase the timeout for slower systems.
+
+2013-09-21 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/archives: Some versions of ar (MacOSX) generate
+ different output when creating archives. Run it and verify the
+ real output.
+ * scripts/features/default_names: MacOSX is, like Windows,
+ case-preserving / case-insensitive. Redo the test to avoid
+ checking for "UNIX".
+ * test_driver.pl (attach_default_output): Don't dup stdout into
+ stderr. Reported by Denis Excoffier <bug-tar@Denis-Excoffier.org>
+
+ * scripts/features/se_explicit: Fix a test that behaves
+ differently with/without archive capability enabled.
+ * scripts/features/output-sync: Don't test output-sync if it's not
+ enabled. We also skip it if parallelism is not enabled, although
+ strictly speaking some of the output-sync tests are valid even
+ without parallelism.
+ * scripts/features/jobserver: Move some tests that require the
+ jobserver from features/parallelism to a separate suite. Only run
+ this if jobserver mode is enabled.
+
+ * scripts/features/output-sync: Test shell functions writing to
+ stderr in recipes: ensure it's captured via output-sync. Test
+ output generated while reading makefiles and make sure it's
+ captured via output-sync. Make sure that fatal errors dump the
+ output so it's not lost.
+
+ * scripts/options/dash-w: Add a test for -w flag.
+
+2013-09-15 Paul Smith <psmith@gnu.org>
+
+ * scripts/misc/fopen-fail: Check for failure on infinite recursion.
+ * run_make_tests.pl (run_make_test): Allow the answer string to be
+ undef, which means that we shouldn't compare it at all. Only the
+ exit code matters in this case.
+ * test_driver.pl (compare_output): Ditto.
+ Test for Savannah bug #27374.
+
+ * scripts/features/parallelism: Test broken jobserver on recursion.
+ Test for Savannah bug #39934.
+
+ * scripts/options/eval: Verify --eval during restart.
+ Test for Savannah bug #39203.
+
+2013-09-14 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync: Verify -Orecurse properly.
+
+2013-09-12 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync: Modify for output sync behavior.
+ * scripts/variables/MAKE_RESTARTS: Ditto.
+ * scripts/variables/MAKEFLAGS: Remove mode for --trace.
+ * scripts/variables/GNUMAKEFLAGS: Ditto.
+
+2013-07-22 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/rule_glob: Add tests for wildcards in rules.
+ Test for Savannah bug #39310.
+
+2013-07-09 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/se_implicit: Add a test for SE rules depending
+ on other SE rules to be built.
+
+2013-05-26 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/archives: Test for Savannah bug #38442.
+
+ * scripts/misc/bs-nl: Test for Savannah bug #39035.
+ Add a test for Savannah bug #38945.
+
+2013-05-22 Paul Smith <psmith@gnu.org>
+
+ * scripts/options/dash-n: Fix results after MAKEFLAGS fixes.
+ * scripts/variables/MAKEFLAGS: Ditto.
+ * scripts/variables/GNUMAKEFLAGS: Ditto.
+
+2013-05-14 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/loadapi: Add plugin_is_GPL_compatible symbol.
+ * scripts/features/load: Ditto.
+
+2013-05-13 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync (output_sync_set): Update for new
+ --trace behavior.
+
+2013-05-05 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync (output_sync_set): Remove
+ extraneous enter/leave lines, which are no longer printed.
+ Add tests for syncing command line printing.
+ (output_sync_set): Rename options: "job"->"line"; "make"->"recurse"
+
+2013-05-04 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/loadapi: Use the new alloc functions.
+
+ * scripts/features/output-sync (output_sync_set): New test for
+ ordered recursive output for -Ojob / -Otarget.
+
+2013-05-03 Eli Zaretskii <eliz@gnu.org>
+
+ * scripts/features/load: Fix signatures of testload_gmk_setup and
+ explicit_setup, to bring them in line with the documentation.
+
+2013-04-28 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync (output_sync_set): Add tests for
+ the per-job syntax mode.
+ (output_sync_set): Test improved error message location.
+
+2013-04-15 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync (output_sync_set): New arg syntax.
+
+2013-04-14 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/output-sync: Rewrite to be more reliable.
+
+ * test_driver.pl (_run_command): Don't set SIGALRM until after we
+ start the child. Print errors to the top-level output, which will
+ be stderr.
+ (attach_default_output): Use a list of file handles as the stack.
+ (detach_default_output): Ditto.
+
+ * scripts/features/output-sync: Add a test for output-sync.
+
+2013-02-25 Paul Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (valid_option): Support the -srcdir flag.
+ (set_more_defaults): Set up $srcdir if it's not set yet.
+
+ * scripts/functions/guile: Verify gmk-eval doesn't expand twice.
+ * scripts/features/load: Rework to test just the load capability.
+ * scripts/features/loadapi: New set of tests for the load API.
+
+2013-01-19 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/load: Test loaded files with and without "./"
+ prefix. Add tests for automatically rebuilding loaded files if
+ they are out of date or non-existent.
+
+2013-01-13 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/archives: Add a check targets that have parens,
+ but are not archives. See Savannah bug #37878.
+
+ * scripts/options/dash-n: Verify -n is preserved after recursive /
+ re-exec. See Savannah bug #38051.
+
+2013-01-12 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Change rule so it doesn't depend
+ on invocation order, etc.
+
+2012-10-29 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/load: New test suite for the "load" directive.
+
+2012-09-09 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/file: Get errors in the C locale, not the
+ current locale. Fixes Savannah bug #35764.
+
+ * scripts/features/escape: Check that backslashes before
+ non-special characters are not removed.
+
+ * scripts/features/utf8: New test for UTF-8 support.
+ See Savannah bug #36529.
+
+ * scripts/targets/POSIX: Add tests for default macro values as
+ specified by IEEE Std 1003.1-2008. See Savannah bug #37069.
+
+2012-03-04 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/se_explicit: Test $(x:%=%) format in secondary
+ expansion prerequisite lists. See Savannah bug #16545.
+
+ * scripts/features/escape: Test escaped ":" in prerequisite lists.
+ See Savannah bug #12126.
+
+ * scripts/variables/private: Test appending private variables in
+ pattern-specific target rules. See Savannah bug #35468.
+
+2012-03-03 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/SHELL: Ensure .SHELLFLAGS works with options
+ separated by whitespace.
+
+ * scripts/targets/ONESHELL: Try .ONESHELL in combination with
+ whitespace-separated options in .SHELLFLAGS. See Savannah bug #35397.
+
+ * scripts/functions/filter-out: Add filter tests and test escape
+ operations. See Savannah bug #35410.
+
+ * guile.supp: Suppress valgrind errors from Guile
+ * run_make_tests.pl: Use the Guile suppression file.
+
+ * scripts/misc/bs-nl: Check for POSIX and non-POSIX
+ backslash/newline handling. Addresses Savannah bug #16670.
+
+2012-01-29 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/flavors: Add tests for ::=
+ * scripts/variables/define: Ditto
+
+ * scripts/functions/file: Test the new $(file ...) function.
+
+2012-01-12 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/guile: New regression tests for Guile support.
+
+2011-12-10 Paul Smith <psmith@gnu.org>
+
+ * scripts/targets/SECONDARY: Add prereq statements to ensure rules
+ are printed in the right order for test #9
+
+2011-11-14 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/double_colon: Check double-colon with escaped
+ filenames. See Savannah bug #33399.
+
+2011-09-18 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: On re-exec make sure we preserve
+ the value of MAKEFLAGS when necessary. See Savannah bug #33873.
+
+ * scripts/features/vpath3: Verify handling of -lfoo libraries
+ found via vpath vs. the standard directory search.
+ See Savannah bug #32511.
+
+2011-09-12 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/call: Verify that using export in a $(call ...)
+ context creates a global variable. See Savannah bug #32498.
+
+2011-09-02 Paul Smith <psmith@gnu.org>
+
+ * scripts/options/dash-n: Verify that in "-n -t", the -n takes
+ priority. Patch from Michael Witten <mfwitten@gmail.com>.
+
+2011-08-29 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/varnesting: Test resetting of variables while
+ expanding them. See Savannah patch #7534
+
+2011-06-12 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/archives: Check archives with whitespace at the
+ beginning, end, and extra in the middle.
+ Another test for Savannah bug #30612.
+
+2011-05-07 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/private: Ensure we skip private variables when
+ appending. Test for Savannah bug #32872.
+
+ * scripts/functions/wildcard: Verify wildcard used to test for
+ file existence/non-existence.
+
+2011-05-02 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/sort: Add a test for Savannah bug #33125.
+
+2011-04-17 David A. Wheeler <dwheeler@dwheeler.com>
+
+ * scripts/features/shell_assignment: Regression for "!=" feature
+
+2010-11-06 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Fix known-good output for BS/NL changes.
+ * scripts/functions/call: Ditto.
+ * scripts/variables/special: Ditto.
+
+ * scripts/misc/bs-nl: New test suite for backslash/newline testing.
+
+2010-08-29 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/errors: Add new error message to output text.
+ * scripts/variables/SHELL: Ditto.
+ * scripts/targets/POSIX: Ditto.
+ * scripts/options/dash-k: Ditto.
+ * scripts/features/vpathplus: Ditto.
+ * scripts/features/patternrules: Ditto.
+ * scripts/features/parallelism: Ditto.
+
+2010-08-13 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/archives: New regression tests for archive
+ support. Test for fix to Savannah bug #30612.
+
+ * run_make_tests.pl (set_more_defaults): Set a %FEATURES hash to
+ the features available in $(.FEATURES).
+
+2010-08-10 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/reinvoke: Ensure command line variable settings
+ are preserved across make re-exec. Tests Savannah bug #30723.
+
+2010-07-28 Paul Smith <psmith@gnu.org>
+
+ * scripts/targets/POSIX: Compatibility issues with Solaris (and
+ Tru64?); "false" returns different exit codes, and set -x shows
+ output with extra whitespace. Run the commands by hand first to
+ find out what the real shell would do, then compare what make does.
+ * scripts/variables/SHELL: Ditto.
+
+2010-07-12 Paul Smith <psmith@gnu.org>
+
+ * test_driver.pl: Add a new $perl_name containing the path to Perl.
+ * run_make_tests.pl (run_make_test): Replace the special string
+ #PERL# in a makefile etc. with the path the Perl executable so
+ makefiles can use it.
+
+ * scripts/targets/ONESHELL: Add a new set of regression tests for
+ the .ONESHELL feature.
+
+2010-07-06 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/SHELL: Test the new .SHELLFLAGS variable.
+
+ * scripts/targets/POSIX: New file. Test the .POSIX special target.
+ Verify that enabling .POSIX changes the shell flags to set -e.
+
+2010-07-01 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/recursion: Add a space to separate command-line
+ args. Fixes Savannah bug #29968.
+
+2009-11-12 Boris Kolpackov <boris@codesynthesis.com>
+
+ * scripts/features/vpath3: Test for the new library search
+ behavior.
+
+2009-10-06 Boris Kolpackov <boris@codesynthesis.com>
+
+ * scripts/features/se_explicit: Enable the test for now fixed
+ Savannah bug 25780.
+
+2009-10-06 Boris Kolpackov <boris@codesynthesis.com>
+
+ * scripts/variables/undefine: Tests for the new undefine feature.
+
+2009-10-03 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Test for open Savannah bug #26846.
+
+ * scripts/variables/MAKE: Rewrite for new run_make_test() format.
+
+ * scripts/variables/MAKEFLAGS: Created.
+ Add test for Savannah bug #2216 (still open).
+
+ * scripts/features/include: Test for Savannah bug #102 (still open).
+
+2009-09-30 Boris Kolpackov <boris@codesynthesis.com>
+
+ * scripts/features/include: Add diagnostics issuing tests for
+ cases where targets have been updated and failed with the
+ dontcare flag. Savannah bugs #15110, #25493, #12686, #17740.
+
+2009-09-28 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/shell: Add regression test for Savannah bug
+ #20513 (still open).
+
+ * scripts/features/se_explicit: Add regression tests for Savannah
+ bug #25780 (still open).
+
+ * run_make_tests.pl (valid_option): Add a new flag, -all([-_]?tests)?
+ that runs tests we know will fail. This allows us to add
+ regression tests to the test suite for bugs that haven't been
+ fixed yet.
+
+2009-09-28 Boris Kolpackov <boris@codesynthesis.com>
+
+ * scripts/features/patspecific_vars: Add a test for the shortest
+ stem first order.
+
+ * scripts/features/patternrules: Add a test for the shortest stem
+ first order.
+
+2009-09-24 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/se_implicit: Add a test for order-only
+ secondary expansion prerequisites.
+
+2009-09-23 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/patternrules: Test that we can remove pattern
+ rules, both single and multiple prerequisites. Savannah bug #18622.
+
+ * scripts/features/echoing: Rework for run_make_test().
+
+2009-06-14 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/vpath: Verify we don't get bogus circular
+ dependency warnings if we choose a different file via vpath during
+ update. Savannah bug #13529.
+
+2009-06-13 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/MAKEFILES: Verify that MAKEFILES included
+ files (and files included by them) don't set the default goal.
+ Savannah bug #13401.
+
+ * scripts/functions/wildcard: Test that wildcards with
+ non-existent glob matchers return empty.
+
+2009-06-09 Paul Smith <psmith@gnu.org>
+
+ * scripts/options/dash-B: Test the $? works correctly with -B.
+ Savannah bug #17825.
+
+ * scripts/features/patternrules: Test that dependencies of
+ "also_make" targets are created properly. Savannah bug #19108.
+
+ * test_driver.pl (compare_output): Create a "run" file for failed
+ tests containing the command that was run.
+ (get_runfile): New function.
+
+ * run_make_tests.pl (valid_option): Enhanced support for valgrind:
+ allow memcheck and massif tools.
+
+ * scripts/features/patternrules: Have to comment out a line in the
+ first test due to backing out a change that broke the implicit
+ rule search algorithm. Savannah bug #17752.
+ * scripts/misc/general4: Remove a test that is redundant with
+ patternrules.
+
+ * scripts/features/parallelism: Add a test for re-exec with
+ jobserver master override. Savannah bug #18124.
+
+2009-06-08 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Add a test for continued target
+ vars after a semicolon. Savannah bug #17521.
+
+2009-06-07 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/se_explicit: Make sure we catch defining
+ prereqs during snap_deps(). Savannah bug #24622.
+
+ * scripts/variables/automatic: Check prereq ordering when the
+ target with the recipe has no prereqs. Savannah bug #21198.
+
+ * scripts/variables/LIBPATTERNS: Add a new set of test for
+ $(.LIBPATTERNS) (previously untested!)
+
+2009-06-04 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/SHELL: The export target-specific SHELL test
+ has an incorrect known-good-value.
+
+ * scripts/misc/general4: Check for whitespace (ffeed, vtab, etc.)
+
+ * scripts/features/se_explicit: Add tests for Savannah bug #24588.
+
+2009-05-31 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/DEFAULT_GOAL: Add tests for Savannah bug #25697.
+
+ * scripts/features/targetvars: Add tests of overrides for Savannah
+ bug #26207.
+ * scripts/features/patspecific_vars: Ditto.
+
+ * scripts/features/patternrules: Add a test for Savannah bug #26593.
+
+2009-05-30 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/flavors: Update with new variable flavor tests.
+ * scripts/variables/define: Create a new set of tests for
+ define/endef and move those aspects of the flavors suite here.
+
+2009-05-25 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Ditto.
+
+ * scripts/features/export: Test new variable parsing abilities.
+
+2009-02-23 Ramon Garcia <ramon.garcia.f@gmail.com>
+
+ * scripts/variables/private: Create a new suite of tests for 'private'.
+
+2007-11-04 Paul Smith <psmith@gnu.org>
+
+ * scripts/functions/eval: Update error message for command -> recipe.
+
+ * test_driver.pl (compare_output): Allow the answer to be a regex,
+ if surrounded by '/'.
+ * scripts/misc/close_stdout: Use a regex for the answer, since
+ sometimes the error will have a description and sometimes it won't.
+
+2007-09-10 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/special: Add tests for .RECIPEPREFIX variable.
+
+2007-08-15 Paul Smith <psmith@gnu.org>
+
+ These test cases were contributed by
+ Icarus Sparry <savannah@icarus.freeuk.com> and J. David Bryan for
+ Savannah bugs #3330 and #15919.
+
+ * scripts/targets/SECONDARY: Add tests for Savannah bugs 3330 and
+ 15919.
+
+ * scripts/features/parallelism: Add tests for wrong answer/hang
+ combining INTERMEDIATE, order-only prereqs, and parallelism.
+ See Savannah bugs 3330 and 15919.
+
+2007-07-13 Paul Smith <psmith@gnu.org>
+
+ Install a timeout so tests can never loop infinitely.
+ Original idea and patch for a single-test version provided by
+ Icarus Sparry <savannah@icarus.freeuk.com>
+
+ * test_driver.pl (_run_command): New function: this is called by
+ other functions to actually run a command. Before we run it,
+ install a SIGALRM handler and set up a timer to go off in the
+ future (default is 5s; this can be overridden by individual tests).
+ (run_command): Call it.
+ (run_command_with_output): Call it.
+
+ * run_make_tests.pl (run_make_with_options): Override the default
+ timeout if the caller requests it.
+ (run_make_test): Pass any timeout override to run_make_with_options.
+
+ * scripts/features/parallelism: Increase the timeout for long tests.
+ * scripts/options/dash-l: Ditto.
+
+2006-10-01 Paul Smith <psmith@paulandlesley.org>
+
+ * run_make_tests.pl (set_more_defaults): Remove setting of LANG in
+ ENV here. This doesn't always work.
+ * test_driver.pl (toplevel): Set LC_ALL to 'C' in the make
+ environment. Fixes Savannah bug #16698.
+
+2006-09-30 Paul Smith <psmith@paulandlesley.org>
+
+ * scripts/variables/automatic: Add back the test for bug #8154.
+
+2006-04-01 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/realpath: Don't run tests with multiple
+ initial slashes on Windows: those paths mean something different.
+
+2006-03-19 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Test that the jobserver is
+ properly managed when we have to re-exec the master instance of
+ make.
+
+2006-03-17 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/statipattrules: Add tests for bug #16053.
+
+2006-03-09 Paul Smith <psmith@gnu.org>
+
+ * scripts/features/escape: Use "pre:" not "p:" to avoid conflicts
+ with DOS drive letters. Fixes Savannah bug #15947.
+
+ * test_driver.pl (run_each_test): Set the status properly even
+ when a test fails to execute. Fixes Savannah bug #15942.
+
+ * scripts/functions/foreach: Use a different environment variable
+ other than PATH to avoid differences with Windows platforms.
+ Fixes Savannah bug #15938.
+
+2006-03-05 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (set_more_defaults): Add CYGWIN_NT as a port
+ type W32. Fixed Savannah bug #15937.
+
+ * scripts/features/default_names: Don't call error() when the test
+ fails. Fixes Savannah bug #15941.
+
+2006-02-17 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Test a complex construction which
+ guarantees that we have to merge variable lists of different
+ sizes. Tests for Savannah bug #15757.
+
+2006-02-15 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/error: Make sure filename/lineno information
+ is related to where the error is expanded, not where it's set.
+ * scripts/functions/warning: Ditto.
+ * scripts/functions/foreach: Check for different error conditions.
+ * scripts/functions/word: Ditto.
+ * scripts/variables/negative: Test some variable reference failure
+ conditions.
+ * scripts/options/warn-undefined-variables: Test the
+ --warn-undefined-variables flag.
+
+2006-02-09 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (set_more_defaults): Update valgrind support
+ for newer versions.
+ * test_driver.pl (toplevel): Skip all hidden files/directories (ones
+ beginning with ".").
+
+ * scripts/functions/andor: Tests for $(and ...) and $(or ...)
+ functions.
+
+2006-02-08 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/parallelism: Add a test for bug #15641.
+
+2006-02-06 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-W: Add a test for bug #15341.
+
+2006-01-03 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/automatic: Add a test for bug #8154.
+
+ * README: Update to reflect the current state of the test suite.
+
+2005-12-12 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism, scripts/functions/wildcard,
+ scripts/targets/FORCE, scripts/targets/PHONY,
+ scripts/targets/SILENT: Use the default setting for
+ $delete_command. Fixes bug #15085.
+
+ * run_make_tests.pl (get_this_pwd) [VMS]: Use -no_ask with delete_file.
+
+2005-12-11 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/misc/general4: Test implicit rules with '$' in the
+ prereq list & prereq patterns.
+ * scripts/features/se_implicit: Add in .SECONDEXPANSION settings.
+
+2005-12-09 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/patternrules: Add a test for bug #13022.
+
+2005-12-07 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/double_colon: Add a test for bug #14334.
+
+2005-11-17 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/functions/flavor: Add a test for the flavor function.
+
+2005-11-14 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/variables/INCLUDE_DIRS: Add a test for the .INCLUDE_DIRS
+ special variable.
+
+2005-10-24 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/misc/general4: Test '$$' in prerequisites list.
+ * scripts/features/statipattrules: Rewrite to use run_make_test().
+ Add various static pattern info.
+ * scripts/features/se_statpat: Enable .SECONDEXPANSION target.
+ * scripts/features/se_explicit: Add tests for handling '$$' in
+ prerequisite lists with and without setting .SECONDEXPANSION.
+ * scripts/features/order_only: Convert to run_make_test().
+ * run_make_tests.pl (set_more_defaults): If we can't get the value
+ of $(MAKE) from make, then fatal immediately.
+
+2005-08-31 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (get_this_pwd): Require the POSIX module (in
+ an eval to trap errors) and if it exists, use POSIX::getcwd to
+ find the working directory. If it doesn't exist, go back to the
+ previous methods. This tries to be more accurate on Windows
+ systems.
+
+2005-08-29 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/abspath: Add some text to the error messages
+ to get a better idea of what's wrong. Make warnings instead of
+ errors.
+
+ * scripts/features/patspecific_vars: Don't use "test", which is
+ UNIX specific. Print the values and let the test script match
+ them.
+
+2005-08-25 Paul Smith <psmith@gnu.org>
+
+ * scripts/variables/SHELL: Use a /./ prefix instead of //: the
+ former works better with non-UNIX environments. Fixes Savannah
+ bug #14129.
+
+2005-08-13 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/functions/wildcard: Wrap calls to $(wildcard) with
+ $(sort) so that the resulting order is no longer filesystem-
+ dependent.
+
+2005-08-10 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/statipattrules: Add a test for Savannah bug #13881.
+
+2005-08-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Add a test for a bug reported by
+ Michael Matz (matz@suse.de) in which make exits without waiting
+ for all its children in some situations during parallel builds.
+
+2005-07-08 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl: Reset the environment to a clean value every
+ time before we invoke make. I'm suspicious that the environment
+ isn't handled the same way in Windows as it is in UNIX, and some
+ variables are leaking out beyond the tests they are intended for.
+ Create an %extraENV hash tests can set to add more env. vars.
+ * tests/scripts/features/export: Change to use %extraENV.
+ * tests/scripts/functions/eval: Ditto.
+ * tests/scripts/functions/origin: Ditto.
+ * tests/scripts/options/dash-e: Ditto.
+ * tests/scripts/variables/SHELL: Ditto.
+
+2005-06-27 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-W: Use 'echo >>' instead of touch to update
+ files.
+ * scripts/features/reinvoke: Rewrite to be safer on systems with
+ subsecond timestamps.
+ * scripts/features/patternrules: False exits with different error
+ codes on different systems (for example, Linux => 1, Solaris => 255).
+
+ * scripts/options/dash-W: Set the timestamp to foo.x in the future,
+ to be sure it will be considered updated when it's remade.
+
+2005-06-26 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/shell: New test suite for the shell function.
+
+2005-06-25 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/include: Test include/-include/sinclude with no
+ arguments. Tests fix for Savannah bug #1761.
+
+ * scripts/misc/general3: Implement comprehensive testing of
+ backslash-newline behavior in command scripts: various types of
+ quoting, fast path / slow path, etc.
+ Tests fix for Savannah bug #1332.
+
+ * scripts/options/symlinks: Test symlinks to non-existent files.
+ Tests fix for Savannah bug #13280.
+
+ * scripts/misc/general3: Test semicolons in variable references.
+ Tests fix for Savannah bug #1454.
+
+ * scripts/variables/MAKE_RESTARTS: New file: test the
+ MAKE_RESTARTS variable.
+ * scripts/options/dash-B: Test re-exec doesn't loop infinitely.
+ Tests fix for Savannah bug #7566.
+ * scripts/options/dash-W: New file: test the -W flag, including
+ re-exec infinite looping.
+
+2005-06-12 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/misc/close_stdout: Add a test for Savannah bug #1328.
+ This test only works on systems that have /dev/full (e.g., Linux).
+
+2005-06-09 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/foreach: Add a test for Savannah bug #11913.
+
+2005-05-31 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/include: Add a test for Savannah bug #13216.
+ * scripts/features/patternrules: Add a test for Savannah bug #13218.
+
+2005-05-13 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/conditionals: Add tests for the new if... else
+ if... endif syntax.
+
+2005-05-03 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/DEFAULT_GOAL: Rename DEFAULT_TARGET to
+ DEFAULT_GOAL.
+
+2005-05-02 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Add a test for exporting recursive
+ variables containing $(shell) calls. Rewrite this script to use
+ run_make_test() everywhere.
+
+2005-04-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/targets/SECONDARY: Add a test for Savannah bug #12331.
+
+2005-03-15 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/variables/automatic: Add a test for Savannah bug #12320.
+
+2005-03-10 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/patternrules: Add a test for Savannah bug #12267.
+
+2005-03-09 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/variables/DEFAULT_TARGET: Add a test for Savannah
+ bug #12266.
+
+2005-03-04 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/patternrules: Add a test for Savannah bug #12202.
+
+2005-03-03 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/se_implicit: Add a test for stem
+ termination bug. Add a test for stem triple-expansion bug.
+
+ * scripts/features/se_statpat: Add a test for stem
+ triple-expansion bug.
+
+ * scripts/features/statipattrules: Change test #4 to reflect
+ new way empty prerequisite list is handled.
+
+
+2005-03-01 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/statipattrules: Add a test for
+ Savannah bug #12180.
+
+2005-02-28 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-q: Add a test for Savannah bug # 7144.
+
+ * scripts/options/symlinks: New file to test checking of symlink
+ timestamps. Can't use filename dash-L because it conflicts with
+ dash-l on case-insensitive filesystems.
+
+ * scripts/variables/MAKEFILE_LIST, scripts/variables/MFILE_LIST:
+ Rename MAKEFILE_LIST test to MFILE_LIST, for systems that need 8.3
+ unique filenames.
+
+2005-02-28 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/variables/DEFAULT_TARGET: Test the .DEFAULT_TARGET
+ special variable.
+
+2005-02-27 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/se_explicit: Test the second expansion in
+ explicit rules.
+ * scripts/features/se_implicit: Test the second expansion in
+ implicit rules.
+ * scripts/features/se_statpat: Test the second expansion in
+ static pattern rules.
+ * scripts/variables/automatic: Fix to work with the second
+ expansion.
+
+ * scripts/misc/general4: Add a test for bug #12091.
+
+2005-02-27 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/eval: Check that eval of targets within
+ command scripts fails. See Savannah bug # 12124.
+
+2005-02-26 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (compare_output): If a basic comparison of the
+ log and answer doesn't match, try harder: change all backslashes
+ to slashes and all CRLF to LF. This helps on DOS/Windows systems.
+
+2005-02-09 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/recursion: Test command line variable settings:
+ only one instance of a given variable should be provided.
+
+2004-11-30 Boris Kolpackov <boris@kolpackov.net>
+
+ * tests/scripts/functions/abspath: New file: test `abspath'
+ built-in function.
+
+ * tests/scripts/functions/realpath: New file: test `realpath'
+ built-in function.
+
+2004-11-28 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-C [WINDOWS32]: Add a test for bug #10252;
+ this doesn't really test anything useful in UNIX but...
+
+ * scripts/variables/SHELL: New file: test proper handling of SHELL
+ according to POSIX rules. Fixes bug #1276.
+
+2004-10-21 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/functions/word: Test $(firstword ) and $(lastword ).
+
+2004-10-05 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/patspecific_vars: Test simple/recursive
+ variable expansion.
+
+2004-09-28 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/include: Test dontcare flag inheritance
+ when rebuilding makefiles.
+
+2004-09-27 Boris Kolpackov <boris@kolpackov.net>
+
+ * scripts/features/patspecific_vars: Test exported variables.
+
+2004-09-22 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (run_make_test): Don't add newlines to the
+ makestring or answer if they are completely empty.
+
+ * scripts/features/patternrules: Rename from implicit_prereq_eval.
+
+ * scripts/test_template: Rework the template.
+
+2004-09-21 Boris Kolpackov <boris@kolpackov.net>
+
+ * run_make_tests.pl: Change `#!/usr/local/bin/perl' to be
+ `#!/usr/bin/env perl'.
+
+ * scripts/features/implicit_prereq_eval: Test implicit rule
+ prerequisite evaluation code.
+
+2004-09-21 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (run_make_test): Enhance to allow the make
+ string to be undef: in that case it reuses the previous make
+ string. Allows multiple tests on the same makefile.
+
+ * scripts/variables/flavors: Add some tests for prefix characters
+ interacting with define/endef variables.
+
+2004-09-20 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/substitution: Rewrite to use run_make_test()
+ interface, and add test for substitution failures reported by
+ Markus Mauhart <qwe123@chello.at>.
+
+2004-03-22 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (run_each_test, toplevel, compare_output): Change
+ to track both the testing categories _AND_ the number of
+ individual tests, and report both sets of numbers.
+
+2004-02-21 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/origin: Set our own environment variable
+ rather than relying on $HOME.
+
+2004-01-21 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/conditionals: Test arguments to ifn?def which
+ contain whitespace (such as a function that is evaluated). Bug
+ #7257.
+
+2004-01-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/order_only: Test order-only prerequisites in
+ pattern rules (patch #2349).
+
+2003-11-02 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/if: Test if on conditionals with trailing
+ whitespace--bug #5798.
+
+ * scripts/functions/eval: Test eval in a non-file context--bug #6195.
+
+2003-04-19 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/patspecific_vars: Test multiple patterns
+ matching the same target--Bug #1405.
+
+2003-04-09 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (set_more_defaults): A new $port_type of
+ 'OS/2' for (surprise!) OS/2. Also choose a wait time of 2 seconds
+ for OS/2.
+
+2003-03-28 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/targets/SECONDARY: Test the "global" .SECONDARY (with
+ not prerequisites)--Bug #2515.
+
+2003-01-30 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Test very long target-specific
+ variable definition lines (longer than the default make buffer
+ length). Tests patch # 1022.
+
+ * scripts/functions/eval: Test very recursive $(eval ...) calls
+ with simple variable expansion (bug #2238).
+
+ * scripts/functions/word: Test error handling for word and
+ wordlist functions (bug #2407).
+
+2003-01-22 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/call: Test recursive argument masking (bug
+ #1744).
+
+2002-10-25 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/eval: Test using $(eval ...) inside
+ conditionals (Bug #1516).
+
+2002-10-14 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-t: Add a test for handling -t on targets
+ with no commands (Bug #1418).
+
+2002-10-13 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Add a test for exporting
+ target-specific vars (Bug #1391).
+
+2002-10-05 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/automatic: Add tests for $$(@), $${@}, $${@D},
+ and $${@F}.
+
+2002-09-23 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/escape: Test handling of escaped comment
+ characters in targets and prerequisites.
+
+2002-09-18 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/export: Test export/unexport of multiple
+ variables in a single command.
+
+2002-09-17 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Tests for Bug #940: test
+ target-specific and pattern-specific variables in conjunction with
+ double-colon targets.
+
+2002-09-10 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (compare_output): Match the new format for time
+ skew error messages.
+
+ * scripts/features/export: Created. Add tests for export/unexport
+ capabilities, including exporting/unexporting expanded variables.
+
+ * scripts/features/conditionals: Add a test for expanded variables
+ in ifdef conditionals.
+
+2002-09-04 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/reinvoke: Change touch/sleep combos to utouch
+ invocations.
+ * scripts/features/vpathgpath: Ditto.
+ * scripts/features/vpathplus: Ditto.
+ * scripts/options/dash-n: Ditto.
+ * scripts/targets/INTERMEDIATE: Ditto.
+ * scripts/targets/SECONDARY: Ditto.
+
+ * scripts/options/dash-t: Added a test for the -t bug fixed by
+ Henning Makholm. This test was also contributed by Henning.
+
+ * scripts/misc/general4: Add a test suite for obscure algorithmic
+ features of make. First test: make sure creation subdirectories
+ as prerequisites of targets works properly.
+
+ * scripts/misc/version: Remove this bogus test.
+
+2002-08-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/misc/general3: Add a test for makefiles that don't end
+ in newlines.
+
+ * scripts/variables/special: Create tests for the special
+ variables (.VARIABLES and .TARGETS). Comment out .TARGETS test
+ for now as it's not yet supported.
+
+2002-08-01 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-B: Add a test for the new -B option.
+
+2002-07-11 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (valid_option): Add support for Valgrind. Use
+ -valgrind option to the test suite.
+ (set_more_defaults): Set up the file descriptor to capture
+ Valgrind output. We have to unset its close-on-exec flag; we
+ hardcode the value for F_SETFD (2) rather than load it; hopefully
+ this will help us avoid breaking the Windows/DOS test suite.
+
+2002-07-10 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/automatic: Add some tests for $$@, $$(@D), and
+ $$(@F).
+
+ * test_driver.pl (utouch): Create a new function that creates a
+ file with a specific timestamp offset. Use of this function will
+ let us avoid lots of annoying sleep() invocations in the tests
+ just to get proper timestamping, which will make the tests run a
+ lot faster. So far it's only used in the automatic test suite.
+
+2002-07-09 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/automatic: Create a test for automatic variables.
+
+2002-07-08 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/order_only: Test new order-only prerequisites.
+
+2002-07-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/eval: Test new function.
+ * scripts/functions/value: Test new function.
+ * scripts/variables/MAKEFILE_LIST: Test new variable.
+
+2002-04-28 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/call: New test: transitive closure
+ implementation using $(call ...) to test variable recursion.
+
+2002-04-21 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (compare_dir_tree): Ignore CVS and RCS
+ directories in the script directories.
+
+2001-05-02 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/flavors: Test define/endef scripts where only
+ one of the command lines is quiet.
+
+2000-06-22 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-q: New file; test the -q option. Includes
+ a test for PR/1780.
+
+2000-06-21 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Added a test for PR/1709: allowing
+ semicolons in target-specific variable values.
+
+2000-06-19 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/addsuffix: Test for an empty final argument.
+ Actually this bug might have happened for any function, but this
+ one was handy.
+
+2000-06-17 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * scripts/options/general: If parallel jobs are not supported,
+ expect a warning message from Make.
+
+2000-06-15 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * scripts/options/general: Don't try -jN with N != 1 if parallel
+ jobs are not supported.
+
+2000-05-24 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/general: Test general option processing (PR/1716).
+
+2000-04-11 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/strip: Test empty value to strip (PR/1689).
+
+2000-04-08 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * scripts/features/reinvoke: Sleep before updating the target
+ files in the first test, to ensure its time stamp really gets
+ newer; otherwise Make might re-exec more than once.
+
+2000-04-07 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * scripts/features/double_colon: Don't run the parallel tests if
+ parallel jobs aren't supported.
+
+2000-04-04 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/word: wordlist doesn't swap arguments anymore.
+
+2000-03-27 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/statipattrules: Test that static pattern rules
+ whose prerequisite patterns resolve to empty strings throw an
+ error (instead of dumping core). Fixes PR/1670.
+
+ * scripts/features/reinvoke: Make more robust by touching "b"
+ first, to ensure it's not newer than "a".
+ Reported by Marco Franzen <Marco.Franzen@Thyron.com>.
+ * scripts/options/dash-n: Ditto.
+
+ * scripts/functions/call: Whoops. The fix to PR/1527 caused
+ recursive invocations of $(call ...) to break. I can't come up
+ with any way to get both working at the same time, so I backed out
+ the fix to 1527 and added a test case for recursive calls. This
+ also tests the fix for PR/1610.
+
+ * scripts/features/double_colon: Test that circular dependencies
+ in double-colon rule sets are detected correctly (PR/1671).
+
+2000-03-26 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/targets/INTERMEDIATE: Test that make doesn't remove
+ .INTERMEDIATE files when given on the command line (PR/1669).
+
+2000-03-08 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-k: Add a test for error detection by
+ multiple targets depending on the same prerequisite with -k.
+ For PR/1634.
+
+2000-02-07 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/escape: Add a test for backslash-escaped spaces
+ in a target name (PR/1586).
+
+2000-02-04 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/patspecific_vars: Add a test for pattern-specific
+ target variables inherited from the parent target (PR/1407).
+
+2000-02-02 Paul D. Smith <psmith@gnu.org>
+
+ * run_make_tests.pl (set_more_defaults): Hard-code the LANG to C
+ to make sure sorting order, etc. is predictable.
+ Reported by Andreas Jaeger <aj@suse.de>.
+
+ * run_make_tests.pl (set_more_defaults): Set the $wtime variable
+ depending on the OS. Eli Zaretskii <eliz@is.elta.co.il> reports
+ this seems to need to be *4* on DOS/Windows, not just 2. Keep it
+ 1 for other systems.
+ * scripts/features/vpathplus (touchfiles): Use the $wtime value
+ instead of hardcoding 2.
+ * scripts/targets/SECONDARY: Ditto.
+ * scripts/targets/INTERMEDIATE: Ditto.
+
+2000-01-27 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (toplevel): Don't try to run test scripts which
+ are really directories.
+
+2000-01-23 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/include: Remove a check; the fix caused more
+ problems than the error, so I removed it and removed the test for
+ it.
+
+2000-01-11 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/call: Add a test for PR/1517 and PR/1527: make
+ sure $(call ...) doesn't eval its arguments and that you can
+ invoke foreach from it without looping forever.
+
+1999-12-15 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/targets/INTERMEDIATE: Add a test for PR/1423: make sure
+ .INTERMEDIATE settings on files don't disable them as implicit
+ intermediate possibilities.
+
+1999-12-01 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/double_colon: Add a test for PR/1476: Try
+ double-colon rules as non-goal targets and during parallel builds
+ to make sure they're handled serially.
+
+1999-11-17 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/functions/if: Add a test for PR/1429: put some text
+ after an if-statement to make sure it works.
+
+ * scripts/features/targetvars: Add a test for PR/1380: handling +=
+ in target-specific variable definitions correctly.
+
+1999-10-15 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/variables/MAKEFILES: This was really broken: it didn't
+ test anything at all, really. Rewrote it, plus added a test for
+ PR/1394.
+
+1999-10-13 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/options/dash-n: Add a test for PR/1379: "-n doesn't
+ behave properly when used with recursive targets".
+
+1999-10-08 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/targetvars: Add a check for PR/1378:
+ "Target-specific vars don't inherit correctly"
+
+1999-09-29 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (get_osname): Change $fancy_file_names to
+ $short_filenames and reverse the logic.
+ (run_each_test): Change test of non-existent $port_host to use
+ $short_filenames--problem reported by Eli Zaretskii.
+
+1999-09-23 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/parallelism: Add a check to ensure that the
+ jobserver works when we re-invoke. Also cleaned up the tests a
+ little, reducing the number of rules we use so the test won't need
+ as many "sleep" commands.
+
+1999-09-16 Paul D. Smith <psmith@gnu.org>
+
+ * scripts/features/reinvoke: Remove invocations of "touch" in
+ makefiles. See the comments on the touch function rewrite below.
+ Note that UNIX touch behaves the same way if the file already
+ exists: it sets the time to the _local_ time. We don't want
+ this. This is probably a good tip for makefile writers in
+ general, actually... where practical.
+ * scripts/options/dash-l: Ditto.
+ * scripts/options/dash-n: Ditto.
+
+ * test_driver.pl (run_each_test): In retrospect, I don't like the
+ .lN/.bN/.dN postfix required by DOS. So, for non-DOS systems I
+ changed it back to use .log, .base, and .diff.
+
+ * run_make_tests.pl (set_more_defaults): Move the check for the
+ make pathname to here from set_defaults (that's too early since it
+ happens before the command line processing).
+ Create a new variable $port_type, calculated from $osname, to
+ specify what kind of system we're running on. We should integrate
+ the VOS stuff here, too.
+ (valid_option): Comment out the workdir/-work stuff so people
+ won't be fooled into thinking it works... someone needs to fix
+ this, though!
+
+ * scripts/functions/origin: Use $port_type instead of $osname.
+ * scripts/functions/foreach: Ditto.
+ * scripts/features/default_names: Ditto.
+
+1999-09-15 Paul D. Smith <psmith@gnu.org>
+
+ * test_driver.pl (touch): Rewrite this function. Previously it
+ used to use utime() to hard-set the time based on the current
+ local clock, or, if the file didn't exist, it merely created it.
+ This mirrors exactly what real UNIX touch does, but it fails badly
+ on networked filesystems where the FS server clock is skewed from
+ the local clock: normally modifying a file causes it to get a mod
+ time based on the _server's_ clock. Hard-setting it based on the
+ _local_ clock causes gratuitous errors and makes the tests
+ unreliable except on local filesystems. The new function will
+ simply modify the file, allowing the filesystem to set the mod
+ time as it sees fit.
+
+ * scripts/features/parallelism: The second test output could
+ change depending on how fast some scripts completed; use "sleep"
+ to force the order we want.
+
+ * test_driver.pl (toplevel): A bug in Perl 5.000 to Perl 5.004
+ means that "%ENV = ();" doesn't do the right thing. This worked
+ in Perl 4 and was fixed in Perl 5.004_01, but use a loop to delete
+ the environment rather than require specific versions.
+
+ * run_make_tests.pl (set_more_defaults): Don't use Perl 5 s///
+ modifier "s", so the tests will run with Perl 4.
+ (set_more_defaults): Set $pure_log to empty if there's no -logfile
+ option in PURIFYOPTIONS.
+ (setup_for_test): Don't remove any logs unless $pure_log is set.
+
+1999-09-15 Eli Zaretskii <eliz@is.elta.co.il>
+
+ * scripts/features/reinvoke: Put the SHELL definition in the right
+ test makefile.
+
+1999-09-15 Paul D. Smith <psmith@gnu.org>
+
+ ChangeLog file for the test suite created.
+
+
+Copyright (C) 1992-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/tests/NEWS b/src/kmk/tests/NEWS
new file mode 100644
index 0000000..f55e4ba
--- /dev/null
+++ b/src/kmk/tests/NEWS
@@ -0,0 +1,178 @@
+Changes from 0.4.9 to 3.78 (Sep 6, 1999):
+
+ Lots of new tests. Renamed to follow the GNU make scheme. Also
+ added some support for using Purify with make.
+
+ Rob Tulloh contributed some changes to get the test suite running on
+ NT; I tweaked them a bit (hopefully I didn't break anything!) Note
+ that NT doesn't grok the self-exec funkiness that Unix shells use,
+ so instead I broke that out into a separate shell script
+ "run_make_tests" that invokes perl with the (renamed) script
+ run_make_tests.pl.
+
+ Eli Zaretski contributed changes to get the test suite running on
+ DOS with DJGPP. I also meddled in these somewhat.
+
+ If you're on DOS or NT you should run "perl.exe run_make_tests.pl ..."
+ If you're on Unix, you can continue to run "./run_make_tests ..." as
+ before.
+
+Changes from 0.4.8 to 0.4.9 (May 14, 1998):
+
+ Release by Paul D. Smith <psmith@baynetworks.com>; I'm the one to
+ blame for problems in this version :).
+
+ Add some perl to test_driver.pl to strip out GNU make clock skew
+ warning messages from the output before comparing it to the
+ known-good output.
+
+ A new test for escaped :'s in filenames (someone on VMS found this
+ didn't work anymore in 3.77): scripts/features/escape.
+
+Changes from 0.4.7 to 0.4.8 (May 14, 1998):
+
+ Release by Paul D. Smith <psmith@baynetworks.com>; I'm the one to
+ blame for problems in this version :).
+
+ New tests for features to be included in GNU make 3.77.
+
+Changes from 0.4.6 to 0.4.7 (August 18, 1997):
+
+ Release by Paul D. Smith <psmith@baynetworks.com>; I'm the one to
+ blame for problems in this version :).
+
+ Reworked some tests to make sure they all work with both perl4 and perl5.
+
+ Work around a bug in perl 5.004 which doesn't clean the environment
+ correctly in all cases (fixed in at least 5.004_02).
+
+ Updated functions/strip to test for newline stripping.
+
+ Keep a $PURIFYOPTIONS env variable if present.
+
+Changes from 0.4.5 to 0.4.6 (April 07, 1997):
+
+ Release by Paul D. Smith <psmith@baynetworks.com>; I'm the one to
+ blame for problems in this version :).
+
+ Updated to work with GNU make 3.76 (and pretests).
+
+ Added new tests and updated existing ones. Note that the new tests
+ weren't tested with perl 4, however I think they should work.
+
+ Ignore any tests whose filenames end in "~", so that Emacs backup
+ files aren't run.
+
+Changes from 0.4.4 to 0.4.5 (April 29, 1995):
+
+ Updated to be compatible with perl 5.001 as well as 4.036.
+
+ Note: the test suite still won't work on 14-char filesystems
+ (sorry, Kaveh), but I will get to it.
+
+ Also, some tests and stuff still haven't made it in because I
+ haven't had time to write the test scripts for them. But they,
+ too, will get in eventually. Contributions of scripts (i.e., tests
+ that I can just drop in) are particularly welcome and will be
+ incorporated immediately.
+
+Changes from 0.4.3 to 0.4.4 (March 1995):
+
+ Updated for changes in make 3.72.12, and to ignore CVS directories
+ (thanks go to Jim Meyering for the patches for this).
+
+ Fixed uname call to not make a mess on BSD/OS 2.0 (whose uname -a
+ is very verbose). Let me know if this doesn't work correctly on
+ your system.
+
+ Changed to display test name while it is running, not just when it
+ finishes.
+
+ Note: the test suite still won't work on 14-char filesystems
+ (sorry, Kaveh), but I will get to it.
+
+ Also, some tests and stuff still haven't made it in because I
+ haven't had time to write the test scripts for them. But they,
+ too, will get in eventually.
+
+Changes from 0.4 to 0.4.3 (October 1994):
+
+ Fixed bugs (like dependencies on environment variables).
+
+ Caught up with changes in make.
+
+ The load_limit test should now silently ignore a failure due to
+ make not being able to read /dev/kmem.
+
+ Reorganized tests into subdirs and renamed lots of things so that
+ those poor souls who still have to deal with 14-char filename
+ limits won't hate me any more. Thanks very much to Kaveh R. Ghazi
+ <ghazi@noc.rutgers.edu> for helping me with the implementation and
+ testing of these changes, and for putting up with all my whining
+ about it...
+
+ Added a $| = 1 so that systems that don't seem to automatically
+ flush their output for some reason will still print all the
+ output. I'd hate for someone to miss out on the smiley that
+ you're supposed to get when all the tests pass... :-)
+
+Changes from 0.3 to 0.4 (August 1993):
+
+ Lost in the mists of time (and my hurry to get it out before I
+ left my job).
+
+Changes from 0.2 to 0.3 (9-30-92):
+
+ Several tests fixed to match the fact that MAKELEVEL > 0 or -C now
+ imply -w.
+
+ parallel_execution test fixed to not use double colon rules any
+ more since their behavior has changed.
+
+ errors_in_commands test fixed to handle different error messages
+ and return codes from rm.
+
+ Several tests fixed to handle -make_path with a relative path
+ and/or a name other than "make" for make.
+
+ dash-e-option test fixed to use $PATH instead of $USER (since the
+ latter does not exist on some System V systems). This also
+ removes the dependency on getlogin (which fails under certain
+ weird conditions).
+
+ test_driver_core changed so that you can give a test name like
+ scripts/errors_in_commands and it will be handled correctly (handy
+ if you have a shell with filename completion).
+
+Changes from 0.1 to 0.2 (5-4-92):
+
+ README corrected to require perl 4.019, not 4.010.
+
+ -make_path replaces -old.
+
+ errors_in_commands test updated for change in format introduced in
+ make 3.62.6.
+
+ test_driver_core now uses a better way of figuring what OS it is
+ running on (thanks to meyering@cs.utexas.edu (Jim Meyering) for
+ suggesting this, as well as discovering the hard way that the old
+ way (testing for /mnt) fails on his machine).
+
+ Some new tests were added.
+
+
+-------------------------------------------------------------------------------
+Copyright (C) 1992-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/src/kmk/tests/README b/src/kmk/tests/README
new file mode 100644
index 0000000..0213159
--- /dev/null
+++ b/src/kmk/tests/README
@@ -0,0 +1,102 @@
+The test suite was originally written by Steve McGee and Chris Arthur.
+It is covered by the GNU General Public License (Version 2), described
+in the file COPYING. It has been maintained as part of GNU make proper
+since GNU make 3.78.
+
+This entire test suite, including all test files, are copyright and
+distributed under the following terms:
+
+ -----------------------------------------------------------------------------
+ Copyright (C) 1992-2016 Free Software Foundation, Inc.
+ This file is part of GNU Make.
+
+ GNU Make is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3 of the License, or (at your option) any later
+ version.
+
+ GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see <http://www.gnu.org/licenses/>.
+ -----------------------------------------------------------------------------
+
+The test suite requires Perl. These days, you should have at least Perl
+5.004 (available from ftp.gnu.org, and portable to many machines). It
+used to work with Perl 4.036 but official support for Perl 4.x was
+abandoned a long time ago, due to lack of testbeds, as well as interest.
+
+The test suite assumes that the first "diff" it finds on your PATH is
+GNU diff, but that only matters if a test fails.
+
+To run the test suite on a UNIX system, use "perl ./run_make_tests"
+(or just "./run_make_tests" if you have a perl on your PATH).
+
+To run the test suite on Windows NT or DOS systems, use
+"perl.exe ./run_make-tests.pl".
+
+By default, the test engine picks up the first executable called "make"
+that it finds in your path. You may use the -make_path option (i.e.,
+"perl run_make_tests -make_path /usr/local/src/make-3.78/make") if
+you want to run a particular copy. This now works correctly with
+relative paths and when make is called something other than "make" (like
+"gmake").
+
+Tests cannot end with a "~" character, as the test suite will ignore any
+that do (I was tired of having it run my Emacs backup files as tests :))
+
+Also, sometimes the tests may behave strangely on networked
+filesystems. You can use mkshadow to create a copy of the test suite in
+/tmp or similar, and try again. If the error disappears, it's an issue
+with your network or file server, not GNU make (I believe). This
+shouldn't happen very often anymore: I've done a lot of work on the
+tests to reduce the impacts of this situation.
+
+The options/dash-l test will not really test anything if the copy of
+make you are using can't obtain the system load. Some systems require
+make to be setgid sys or kmem for this; if you don't want to install
+make just to test it, make it setgid to kmem or whatever group /dev/kmem
+is (i.e., "chgrp kmem make;chmod g+s make" as root). In any case, the
+options/dash-l test should no longer *fail* because make can't read
+/dev/kmem.
+
+A directory named "work" will be created when the tests are run which
+will contain any makefiles and "diff" files of tests that fail so that
+you may look at them afterward to see the output of make and the
+expected result.
+
+There is a -help option which will give you more information about the
+other possible options for the test suite.
+
+
+Open Issues
+-----------
+
+The test suite has a number of problems which should be addressed. One
+VERY serious one is that there is no real documentation. You just have
+to see the existing tests. Use the newer tests: many of the tests
+haven't been updated to use the latest/greatest test methods. See the
+ChangeLog in the tests directory for pointers.
+
+The second serious problem is that it's not parallelizable: it scribbles
+all over its installation directory and so can only test one make at a
+time. The third serious problem is that it's not relocatable: the only
+way it works when you build out of the source tree is to create
+symlinks, which doesn't work on every system and is bogus to boot. The
+fourth serious problem is that it doesn't create its own sandbox when
+running tests, so that if a test forgets to clean up after itself that
+can impact future tests.
+
+
+Bugs
+----
+
+Any complaints/suggestions/bugs/etc. for the test suite itself (as
+opposed to problems in make that the suite finds) should be handled the
+same way as normal GNU make bugs/problems (see the README for GNU make).
+
+
+ Paul D. Smith
+ Chris Arthur
diff --git a/src/kmk/tests/config-flags.pm.in b/src/kmk/tests/config-flags.pm.in
new file mode 100644
index 0000000..29ba146
--- /dev/null
+++ b/src/kmk/tests/config-flags.pm.in
@@ -0,0 +1,19 @@
+# This is a -*-perl-*- script
+#
+# Set variables that were defined by configure, in case we need them
+# during the tests.
+
+%CONFIG_FLAGS = (
+ AM_LDFLAGS => '@AM_LDFLAGS@',
+ AR => '@AR@',
+ CC => '@CC@',
+ CFLAGS => '@CFLAGS@',
+ CPP => '@CPP@',
+ CPPFLAGS => '@CPPFLAGS@',
+ GUILE_CFLAGS => '@GUILE_CFLAGS@',
+ GUILE_LIBS => '@GUILE_LIBS@',
+ LDFLAGS => '@LDFLAGS@',
+ LIBS => '@LIBS@'
+);
+
+1;
diff --git a/src/kmk/tests/config_flags_pm.com b/src/kmk/tests/config_flags_pm.com
new file mode 100644
index 0000000..a4271b6
--- /dev/null
+++ b/src/kmk/tests/config_flags_pm.com
@@ -0,0 +1,53 @@
+$!
+$! config_flags_pm.com - Build config-flags.pm on VMS.
+$!
+$! Just good enough to run the self tests for now.
+$!
+$! Copyright (C) 2014-2016 Free Software Foundation, Inc.
+$! This file is part of GNU Make.
+$!
+$! GNU Make is free software; you can redistribute it and/or modify it under
+$! the terms of the GNU General Public License as published by the Free Software
+$! Foundation; either version 3 of the License, or (at your option) any later
+$! version.
+$!
+$! GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+$! WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+$! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+$! details.
+$!
+$! You should have received a copy of the GNU General Public License along with
+$! this program. If not, see <http://www.gnu.org/licenses/>.
+$!
+$!
+$ open/read cfpm_in config-flags.pm.in
+$!
+$ outfile = "sys$disk:[]config-flags.pm"
+$!
+$ cflags = "/include=([],[.glob]"
+$!
+$ create 'outfile'
+$ open/append cfpm 'outfile'
+$!
+$cfpm_read_loop:
+$ read cfpm_in/end=cfpm_read_loop_end line_in
+$ line_in_len = f$length(line_in)
+$ if f$locate("@", line_in) .lt. line_in_len
+$ then
+$ part1 = f$element(0, "@", line_in)
+$ key = f$element(1, "@", line_in)
+$ part2 = f$element(2, "@", line_in)
+$ value = ""
+$ if key .eqs. "CC" then value = "CC"
+$ if key .eqs. "CPP" then value = "CPP"
+$ if key .eqs. "CFLAGS" then value = cflags
+$ if key .eqs. "GUILE_CFLAGS" then value = cflags
+$ write cfpm part1, value, part2
+$ goto cfpm_read_loop
+$ endif
+$ write cfpm line_in
+$ goto cfpm_read_loop
+$cfpm_read_loop_end:
+$ close cfpm_in
+$ close cfpm
+$!
diff --git a/src/kmk/tests/guile.supp b/src/kmk/tests/guile.supp
new file mode 100644
index 0000000..9e9b01b
--- /dev/null
+++ b/src/kmk/tests/guile.supp
@@ -0,0 +1,31 @@
+# Guile valgrind suppression file
+# Created with Guile 1.8.7
+
+# --- Garbage collection
+{
+ guilegc
+ Memcheck:Cond
+ ...
+ fun:scm_gc_for_newcell
+}
+{
+ guilegc
+ Memcheck:Value4
+ ...
+ fun:scm_gc_for_newcell
+}
+{
+ guilegc
+ Memcheck:Value8
+ ...
+ fun:scm_gc_for_newcell
+}
+
+
+# -- scm_alloc_struct
+{
+ guileheap
+ Memcheck:Leak
+ ...
+ fun:scm_alloc_struct
+}
diff --git a/src/kmk/tests/mkshadow b/src/kmk/tests/mkshadow
new file mode 100755
index 0000000..23c7ab0
--- /dev/null
+++ b/src/kmk/tests/mkshadow
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Simple script to make a "shadow" test directory, using symbolic links.
+# Typically you'd put the shadow in /tmp or another local disk
+#
+# Copyright (C) 1992-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+case "$1" in
+ "") echo 'Usage: mkshadow <destdir>'; exit 1 ;;
+esac
+
+dest="$1"
+
+if [ ! -d "$dest" ]; then
+ echo "Destination directory '$dest' must exist!"
+ exit 1
+fi
+
+if [ ! -f run_make_tests ]; then
+ echo "The current directory doesn't appear to contain the test suite!"
+ exit 1
+fi
+
+suite=`pwd | sed 's%^/tmp_mnt%%'`
+name=`basename "$suite"`
+
+files=`echo *`
+
+set -e
+
+mkdir "$dest/$name"
+cd "$dest/$name"
+
+ln -s "$suite" .testdir
+
+for f in $files; do
+ ln -s .testdir/$f .
+done
+
+rm -rf work
+
+echo "Shadow test suite created in '$dest/$name'."
+exit 0
diff --git a/src/kmk/tests/run_make_tests b/src/kmk/tests/run_make_tests
new file mode 100755
index 0000000..b68b784
--- /dev/null
+++ b/src/kmk/tests/run_make_tests
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec perl $0.pl ${1+"$@"}
diff --git a/src/kmk/tests/run_make_tests.com b/src/kmk/tests/run_make_tests.com
new file mode 100644
index 0000000..de79a91
--- /dev/null
+++ b/src/kmk/tests/run_make_tests.com
@@ -0,0 +1,272 @@
+$! Test_make.com
+$!
+$! This is a wrapper for the GNU make perl test programs on VMS.
+$!
+$! Parameter "-help" for description on how to use described below.
+$!
+$! Copyright (C) 2014-2016 Free Software Foundation, Inc.
+$! This file is part of GNU Make.
+$!
+$! GNU Make is free software; you can redistribute it and/or modify it under
+$! the terms of the GNU General Public License as published by the Free Software
+$! Foundation; either version 3 of the License, or (at your option) any later
+$! version.
+$!
+$! GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+$! WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+$! FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+$! details.
+$!
+$! You should have received a copy of the GNU General Public License along with
+$! this program. If not, see <http://www.gnu.org/licenses/>.
+$!
+$!
+$! Allow more than 8 paramters with using commas as a delimiter.
+$!
+$ params = "''p1',''p2',''p3',''p4',''p5',''p6',''p7',''p8'"
+$!
+$ test_flags = ",verbose,detail,keep,usage,help,debug,"
+$ test_flags_len = f$length(test_flags)
+$ verbose_flag = ""
+$ detail_flag = ""
+$ keep_flag = ""
+$ usage_flag = ""
+$ help_flag = ""
+$ debug_flag = ""
+$!
+$ ignored_options = "profile,make,srcdir,valgrind,memcheck,massif,"
+$ ignored_option_len = f$length(ignored_options)
+$!
+$ testname = ""
+$ make :== $bin:make.exe"
+$!
+$ i = 0
+$param_loop:
+$ param = f$element(i, ",", params)
+$ i = i + 1
+$ if param .eqs. "" then goto param_loop
+$ if param .eqs. "," then goto param_loop_end
+$ param_len = f$length(param)
+$ if f$locate("/", param) .lt. param_len
+$ then
+$ if testname .nes. ""
+$ then
+$ write sys$output "Only the last test name specified will be run!"
+$ endif
+$ testname = param
+$ goto param_loop
+$ endif
+$ lc_param = f$edit(param,"LOWERCASE") - "-"
+$ if f$locate(",''lc_param',", ignored_options) .lt. ignored_option_len
+$ then
+$ write sys$output "parameter ''param' is ignored on VMS for now."
+$ goto param_loop
+$ endif
+$ if f$locate(",''lc_param',", test_flags) .lt. test_flags_len
+$ then
+$ 'lc_param'_flag = "-" + lc_param
+$ goto param_loop
+$ endif
+$ write sys$output "parameter ''param' is not known to VMS."
+$ goto param_loop
+$!
+$param_loop_end:
+$!
+$no_gnv = 1
+$no_perl = 1
+$!
+$! Find GNV 2.1.3 + manditory updates
+$! If properly updated, the GNV$GNU logical name is present.
+$! Updated GNV utilities have a gnv$ prefix on them.
+$ gnv_root = f$trnlnm("GNV$GNU", "LNM$SYSTEM_TABLE")
+$ if gnv_root .nes. ""
+$ then
+$ no_gnv = 0
+$ ! Check for update ar utility.
+$ new_ar = "gnv$gnu:[usr.bin]gnv$ar.exe"
+$ if f$search(new_ar) .nes. ""
+$ then
+$ ! See if a new port of ar exists.
+$ ar :== $'new_ar'
+$ else
+$ ! Fall back to legacy GNV AR wrapper.
+$ old_ar = "gnv$gnu:[bin]ar.exe"
+$ if f$search(old_ar) .nes. ""
+$ then
+$ ar :== $'old_ar'
+$ else
+$ no_gnv = 1
+$ endif
+$ endif
+$ ! Check for updated bash
+$ if no_gnv .eq. 0
+$ then
+$ new_bash = "gnv$gnu:[bin]gnv$bash.exe"
+$ if f$search(new_bash) .nes. ""
+$ then
+$ bash :== $'new_bash'
+$ sh :== $'new_bash'
+$ else
+$ no_gnv = 1
+$ endif
+$ endif
+$ ! Check for updated coreutils
+$ if no_gnv .eq. 0
+$ then
+$ new_cat = "gnv$gnu:[bin]gnv$cat.exe"
+$ if f$search(new_cat) .nes. ""
+$ then
+$ cat :== $'new_cat'
+$ cp :== $gnv$gnu:[bin]gnv$cp.exe
+$ echo :== $gnv$gnu:[bin]gnv$echo.exe
+$ false :== $gnv$gnu:[bin]gnv$false.exe
+$ true :== $gnv$gnu:[bin]gnv$true.exe
+$ touch :== $gnv$gnu:[bin]gnv$touch.exe
+$ mkdir :== $gnv$gnu:[bin]gnv$mkdir.exe
+$ rm :== $gnv$gnu:[bin]gnv$rm.exe
+$ sleep :== $gnv$gnu:[bin]gnv$sleep.exe
+$ else
+$ no_gnv = 1
+$ endif
+$ endif
+$ ! Check for updated diff utility.
+$ if no_gnv .eq. 0
+$ then
+$ new_diff = "gnv$gnu:[usr.bin]gnv$diff.exe"
+$ if f$search(new_diff) .nes. ""
+$ then
+$ ! See if a new port of diff exists.
+$ diff :== $'new_diff'
+$ else
+$ ! Fall back to legacy GNV diff
+$ old_diff = "gnv$gnu:[bin]diff.exe"
+$ if f$search(old_diff) .nes. ""
+$ then
+$ diff :== $'old_diff'
+$ else
+$ no_gnv = 1
+$ endif
+$ endif
+$ endif
+$ endif
+$!
+$if no_gnv
+$then
+$ write sys$output "Could not find an up to date GNV installed!"
+$ help_flag = 1
+$endif
+$!
+$! Find perl 5.18.1 or later.
+$!
+$! look in perl_root:[000000]perl_setup.com
+$ perl_root = f$trnlnm("perl_root")
+$ ! This works with known perl installed from PCSI kits.
+$ if perl_root .nes. ""
+$ then
+$ perl_ver = f$element(1, ".", perl_root)
+$ if f$locate("-", perl_ver) .lt. f$length(perl_ver)
+$ then
+$ no_perl = 0
+$ endif
+$ endif
+$ if no_perl
+$ then
+$! look for sys$common:[perl-*]perl_setup.com
+$ perl_setup = f$search("sys$common:[perl-*]perl_setup.com")
+$ if perl_setup .eqs. ""
+$ then
+$ if gnv_root .nes. ""
+$ then
+$ gnv_device = f$parse(gnv_root,,,"DEVICE")
+$ perl_templ = "[vms$common.perl-*]perl_setup.com"
+$ perl_search = f$parse(perl_templ, gnv_device)
+$ perl_setup = f$search(perl_search)
+$ endif
+$ endif
+$ if perl_setup .nes. ""
+$ then
+$ @'perl_setup'
+$ no_perl = 0
+$ endif
+$ endif
+$!
+$ if no_perl
+$ then
+$ write sys$output "Could not find an up to date Perl installed!"
+$ help_flag = "-help"
+$ endif
+$!
+$!
+$ if help_flag .nes. ""
+$ then
+$ type sys$input
+$DECK
+This is a test script wrapper for the run_make_tests.pl script.
+
+This wrapper makes sure that the DCL symbols and logical names needed to
+run the perl script are in place.
+
+The test wrapper currently requires that the DCL symbols be global symbols.
+Those symbols will be left behind after the procedure is run.
+
+The PERL_ROOT will be set to a compatible perl if such a perl is found and
+is not the default PERL_ROOT:. This setting will persist after the test.
+
+This wrapper should be run with the default set to the base directory
+of the make source.
+
+The HELP parameter will bring up this text and then run the help script
+for the Perl wrapper. Not all options for the perl script have been
+implemented, such as valgrind or specifying the make path or source path.
+
+Running the wrapper script requires:
+ Perl 5.18 or later.
+ PCSI kits available from http://sourceforge.net/projects/vmsperlkit/files/
+
+ GNV 2.1.3 or later. GNV 3.0.1 has not tested with this script.
+ Bash 4.2.47 or later.
+ Coreutils 8.21 or later.
+ http://sourceforge.net/projects/gnv/files/
+ Read before installing:
+ http://sourceforge.net/p/gnv/wiki/InstallingGNVPackages/
+ As updates for other GNV components get posted, those updates should
+ be used.
+
+$EOD
+$ endif
+$!
+$ if no_gnv .or. no_perl then exit 44
+$!
+$!
+$ default = f$environment("DEFAULT")
+$ default_dev = f$element(0, ":", default) + ":"
+$ this = f$environment("PROCEDURE")
+$ on error then goto all_error
+$ set default 'default_dev''f$parse(this,,,"DIRECTORY")'
+$!
+$! Need to make sure that the config-flags.pm exists.
+$ if f$search("config-flags.pm") .eqs. ""
+$ then
+$ @config_flags_pm.com
+$ endif
+$ define/user bin 'default_dev'[-],gnv$gnu:[bin]
+$ define/user decc$filename_unix_noversion enable
+$ define/user decc$filename_unix_report enable
+$ define/user decc$readdir_dropdotnotype enable
+$ flags = ""
+$ if verbose_flag .nes. "" then flags = verbose_flag
+$ if detail_flag .nes. "" then flags = flags + " " + detail_flag
+$ if keep_flag .nes. "" then flags = flags + " " + keep_flag
+$ if usage_flag .nes. "" then flags = flags + " " + usage_flag
+$ if help_flag .nes. "" then flags = flags + " " + help_flag
+$ if debug_flag .nes. "" then flags = flags + " " + debug_flag
+$ flags = f$edit(flags, "TRIM, COMPRESS")
+$ if testname .nes. ""
+$ then
+$ perl run_make_tests.pl "''testname'" 'flags'
+$ else
+$ perl run_make_tests.pl 'flags'
+$ endif
+$all_error:
+$ set default 'default'
+$!
diff --git a/src/kmk/tests/run_make_tests.pl b/src/kmk/tests/run_make_tests.pl
new file mode 100755
index 0000000..dfd1dda
--- /dev/null
+++ b/src/kmk/tests/run_make_tests.pl
@@ -0,0 +1,507 @@
+#!/usr/bin/env perl
+# -*-perl-*-
+
+# Test driver for the Make test suite
+
+# Usage: run_make_tests [testname]
+# [-debug]
+# [-help]
+# [-verbose]
+# [-keep]
+# [-make <make prog>]
+# (and others)
+
+# Copyright (C) 1992-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+%FEATURES = ();
+
+$valgrind = 0; # invoke make with valgrind
+$valgrind_args = '';
+$memcheck_args = '--num-callers=15 --tool=memcheck --leak-check=full --suppressions=guile.supp';
+$massif_args = '--num-callers=15 --tool=massif --alloc-fn=xmalloc --alloc-fn=xcalloc --alloc-fn=xrealloc --alloc-fn=xstrdup --alloc-fn=xstrndup';
+$pure_log = undef;
+
+# The location of the GNU make source directory
+$srcdir = '';
+
+$command_string = '';
+
+$all_tests = 0;
+
+# rmdir broken in some Perls on VMS.
+if ($^O eq 'VMS')
+{
+ require VMS::Filespec;
+ VMS::Filespec->import();
+
+ sub vms_rmdir {
+ my $vms_file = vmspath($_[0]);
+ $vms_file = fileify($vms_file);
+ my $ret = unlink(vmsify($vms_file));
+ return $ret
+ };
+
+ *CORE::GLOBAL::rmdir = \&vms_rmdir;
+}
+
+require "test_driver.pl";
+require "config-flags.pm";
+
+# Some target systems might not have the POSIX module...
+$has_POSIX = eval { require "POSIX.pm" };
+
+#$SIG{INT} = sub { print STDERR "Caught a signal!\n"; die @_; };
+
+sub valid_option
+{
+ local($option) = @_;
+
+ if ($option =~ /^-make([-_]?path)?$/i) {
+ $make_path = shift @argv;
+ if (!-f $make_path) {
+ print "$option $make_path: Not found.\n";
+ exit 0;
+ }
+ return 1;
+ }
+
+ if ($option =~ /^-srcdir$/i) {
+ $srcdir = shift @argv;
+ if (! -f "$srcdir/gnumake.h") {
+ print "$option $srcdir: Not a valid GNU make source directory.\n";
+ exit 0;
+ }
+ return 1;
+ }
+
+ if ($option =~ /^-all([-_]?tests)?$/i) {
+ $all_tests = 1;
+ return 1;
+ }
+
+ if ($option =~ /^-(valgrind|memcheck)$/i) {
+ $valgrind = 1;
+ $valgrind_args = $memcheck_args;
+ return 1;
+ }
+
+ if ($option =~ /^-massif$/i) {
+ $valgrind = 1;
+ $valgrind_args = $massif_args;
+ return 1;
+ }
+
+# This doesn't work--it _should_! Someone badly needs to fix this.
+#
+# elsif ($option =~ /^-work([-_]?dir)?$/)
+# {
+# $workdir = shift @argv;
+# return 1;
+# }
+
+ return 0;
+}
+
+
+# This is an "all-in-one" function. Arguments are as follows:
+#
+# [0] (string): The makefile to be tested. undef means use the last one.
+# [1] (string): Arguments to pass to make.
+# [2] (string): Answer we should get back.
+# [3] (integer): Exit code we expect. A missing code means 0 (success)
+
+$old_makefile = undef;
+
+sub subst_make_string
+{
+ local $_ = shift;
+ $makefile and s/#MAKEFILE#/$makefile/g;
+ s/#MAKEPATH#/$mkpath/g;
+ s/#MAKE#/$make_name/g;
+ s/#PERL#/$perl_name/g;
+ s/#PWD#/$pwd/g;
+ return $_;
+}
+
+sub run_make_test
+{
+ local ($makestring, $options, $answer, $err_code, $timeout) = @_;
+ my @call = caller;
+
+ # If the user specified a makefile string, create a new makefile to contain
+ # it. If the first value is not defined, use the last one (if there is
+ # one).
+
+ if (! defined $makestring) {
+ defined $old_makefile
+ || die "run_make_test(undef) invoked before run_make_test('...')\n";
+ $makefile = $old_makefile;
+ } else {
+ if (! defined($makefile)) {
+ $makefile = &get_tmpfile();
+ }
+
+ # Make sure it ends in a newline and substitute any special tokens.
+ $makestring && $makestring !~ /\n$/s and $makestring .= "\n";
+ $makestring = subst_make_string($makestring);
+
+ # Populate the makefile!
+ open(MAKEFILE, "> $makefile") || die "Failed to open $makefile: $!\n";
+ print MAKEFILE $makestring;
+ close(MAKEFILE) || die "Failed to write $makefile: $!\n";
+ }
+
+ # Do the same processing on $answer as we did on $makestring.
+ if (defined $answer) {
+ $answer && $answer !~ /\n$/s and $answer .= "\n";
+ $answer = subst_make_string($answer);
+ }
+
+ run_make_with_options($makefile, $options, &get_logfile(0),
+ $err_code, $timeout, @call);
+ &compare_output($answer, &get_logfile(1));
+
+ $old_makefile = $makefile;
+ $makefile = undef;
+}
+
+# The old-fashioned way...
+sub run_make_with_options {
+ my ($filename,$options,$logname,$expected_code,$timeout,@call) = @_;
+ @call = caller unless @call;
+ local($code);
+ local($command) = $make_path;
+
+ $expected_code = 0 unless defined($expected_code);
+
+ # Reset to reflect this one test.
+ $test_passed = 1;
+
+ if ($filename) {
+ $command .= " -f $filename";
+ }
+
+ if ($options) {
+ if ($^O eq 'VMS') {
+ # Try to make sure arguments are properly quoted.
+ # This does not handle all cases.
+
+ # VMS uses double quotes instead of single quotes.
+ $options =~ s/\'/\"/g;
+
+ # If the leading quote is inside non-whitespace, then the
+ # quote must be doubled, because it will be enclosed in another
+ # set of quotes.
+ $options =~ s/(\S)(\".*\")/$1\"$2\"/g;
+
+ # Options must be quoted to preserve case if not already quoted.
+ $options =~ s/(\S+)/\"$1\"/g;
+
+ # Special fixup for embedded quotes.
+ $options =~ s/(\"\".+)\"(\s+)\"(.+\"\")/$1$2$3/g;
+
+ $options =~ s/(\A)(?:\"\")(.+)(?:\"\")/$1\"$2\"/g;
+
+ # Special fixup for misc/general4 test.
+ $options =~ s/""\@echo" "cc""/\@echo cc"/;
+ $options =~ s/"\@echo link"""/\@echo link"/;
+
+ # Remove shell escapes expected to be removed by bash
+ if ($options !~ /path=pre/) {
+ $options =~ s/\\//g;
+ }
+
+ # special fixup for options/eval
+ $options =~ s/"--eval=\$\(info" "eval/"--eval=\$\(info eval/;
+
+ print ("Options fixup = -$options-\n") if $debug;
+ }
+ $command .= " $options";
+ }
+
+ $command_string = "";
+ if (@call) {
+ $command_string = "#$call[1]:$call[2]\n";
+ }
+ $command_string .= "$command\n";
+
+ if ($valgrind) {
+ print VALGRIND "\n\nExecuting: $command\n";
+ }
+
+
+ {
+ my $old_timeout = $test_timeout;
+ $timeout and $test_timeout = $timeout;
+
+ # If valgrind is enabled, turn off the timeout check
+ $valgrind and $test_timeout = 0;
+
+ $code = &run_command_with_output($logname,$command);
+ $test_timeout = $old_timeout;
+ }
+
+ # Check to see if we have Purify errors. If so, keep the logfile.
+ # For this to work you need to build with the Purify flag -exit-status=yes
+
+ if ($pure_log && -f $pure_log) {
+ if ($code & 0x7000) {
+ $code &= ~0x7000;
+
+ # If we have a purify log, save it
+ $tn = $pure_testname . ($num_of_logfiles ? ".$num_of_logfiles" : "");
+ print("Renaming purify log file to $tn\n") if $debug;
+ rename($pure_log, "$tn")
+ || die "Can't rename $log to $tn: $!\n";
+ ++$purify_errors;
+ } else {
+ unlink($pure_log);
+ }
+ }
+
+ if ($code != $expected_code) {
+ print "Error running $make_path (expected $expected_code; got $code): $command\n";
+ $test_passed = 0;
+ $runf = &get_runfile;
+ &create_file (&get_runfile, $command_string);
+ # If it's a SIGINT, stop here
+ if ($code & 127) {
+ print STDERR "\nCaught signal ".($code & 127)."!\n";
+ ($code & 127) == 2 and exit($code);
+ }
+ return 0;
+ }
+
+ if ($profile & $vos) {
+ system "add_profile $make_path";
+ }
+
+ return 1;
+}
+
+sub print_usage
+{
+ &print_standard_usage ("run_make_tests",
+ "[-make MAKE_PATHNAME] [-srcdir SRCDIR] [-memcheck] [-massif]",);
+}
+
+sub print_help
+{
+ &print_standard_help (
+ "-make",
+ "\tYou may specify the pathname of the copy of make to run.",
+ "-srcdir",
+ "\tSpecify the make source directory.",
+ "-valgrind",
+ "-memcheck",
+ "\tRun the test suite under valgrind's memcheck tool.",
+ "\tChange the default valgrind args with the VALGRIND_ARGS env var.",
+ "-massif",
+ "\tRun the test suite under valgrind's massif toool.",
+ "\tChange the default valgrind args with the VALGRIND_ARGS env var."
+ );
+}
+
+sub get_this_pwd {
+ $delete_command = 'rm -f';
+ if ($has_POSIX) {
+ $__pwd = POSIX::getcwd();
+ } elsif ($vos) {
+ $delete_command = "delete_file -no_ask";
+ $__pwd = `++(current_dir)`;
+ } else {
+ # No idea... just try using pwd as a last resort.
+ chop ($__pwd = `pwd`);
+ }
+
+ return $__pwd;
+}
+
+sub set_defaults
+{
+ # $profile = 1;
+ $testee = "GNU make";
+ $make_path = "make";
+ $tmpfilesuffix = "mk";
+ $pwd = &get_this_pwd;
+}
+
+sub set_more_defaults
+{
+ local($string);
+ local($index);
+
+ # find the type of the port. We do this up front to have a single
+ # point of change if it needs to be tweaked.
+ #
+ # This is probably not specific enough.
+ #
+ if ($osname =~ /Windows/i || $osname =~ /MINGW32/i || $osname =~ /CYGWIN_NT/i) {
+ $port_type = 'W32';
+ }
+ # Bleah, the osname is so variable on DOS. This kind of bites.
+ # Well, as far as I can tell if we check for some text at the
+ # beginning of the line with either no spaces or a single space, then
+ # a D, then either "OS", "os", or "ev" and a space. That should
+ # match and be pretty specific.
+ elsif ($osname =~ /^([^ ]*|[^ ]* [^ ]*)D(OS|os|ev) /) {
+ $port_type = 'DOS';
+ }
+ # Check for OS/2
+ elsif ($osname =~ m%OS/2%) {
+ $port_type = 'OS/2';
+ }
+
+ # VMS has a GNV Unix mode or a DCL mode.
+ # The SHELL environment variable should not be defined in VMS-DCL mode.
+ elsif ($osname eq 'VMS' && !defined $ENV{"SHELL"}) {
+ $port_type = 'VMS-DCL';
+ }
+ # Everything else, right now, is UNIX. Note that we should integrate
+ # the VOS support into this as well and get rid of $vos; we'll do
+ # that next time.
+ else {
+ $port_type = 'UNIX';
+ }
+
+ # On DOS/Windows system the filesystem apparently can't track
+ # timestamps with second granularity (!!). Change the sleep time
+ # needed to force a file to be considered "old".
+ $wtime = $port_type eq 'UNIX' ? 1 : $port_type eq 'OS/2' ? 2 : 4;
+
+ print "Port type: $port_type\n" if $debug;
+ print "Make path: $make_path\n" if $debug;
+ print "fs type : case insensitive\n" if $debug && $case_insensitive_fs;
+
+ # Find the full pathname of Make. For DOS systems this is more
+ # complicated, so we ask make itself.
+ if ($osname eq 'VMS') {
+ $port_type = 'VMS-DCL' unless defined $ENV{"SHELL"};
+ # On VMS pre-setup make to be found with simply 'make'.
+ $make_path = 'make';
+ } else {
+ my $mk = `sh -c 'echo "all:;\@echo \\\$(MAKE)" | $make_path -f-'`;
+ chop $mk;
+ $mk or die "FATAL ERROR: Cannot determine the value of \$(MAKE):\n
+'echo \"all:;\@echo \\\$(MAKE)\" | $make_path -f-' failed!\n";
+ $make_path = $mk;
+ }
+ print "Make\t= '$make_path'\n" if $debug;
+
+ my $redir2 = '2> /dev/null';
+ $redir2 = '' if os_name eq 'VMS';
+ $string = `$make_path -v -f /dev/null $redir2`;
+
+ $string =~ /^(GNU Make [^,\n]*)/;
+ $testee_version = "$1\n";
+
+ my $redir = '2>&1';
+ $redir = '' if os_name eq 'VMS';
+ $string = `sh -c "$make_path -f /dev/null $redir"`;
+ if ($string =~ /(.*): \*\*\* No targets\. Stop\./) {
+ $make_name = $1;
+ }
+ else {
+ $make_path =~ /^(?:.*$pathsep)?(.+)$/;
+ $make_name = $1;
+ }
+
+ # prepend pwd if this is a relative path (ie, does not
+ # start with a slash, but contains one). Thanks for the
+ # clue, Roland.
+
+ if (index ($make_path, ":") != 1 && index ($make_path, "/") > 0)
+ {
+ $mkpath = "$pwd$pathsep$make_path";
+ }
+ else
+ {
+ $mkpath = $make_path;
+ }
+
+ # If srcdir wasn't provided on the command line, see if the
+ # location of the make program gives us a clue. Don't fail if not;
+ # we'll assume it's been installed into /usr/include or wherever.
+ if (! $srcdir) {
+ $make_path =~ /^(.*$pathsep)?/;
+ my $d = $1 || '../';
+ -f "${d}gnumake.h" and $srcdir = $d;
+ }
+
+ # Not with the make program, so see if we can get it out of the makefile
+ if (! $srcdir && open(MF, "< ../Makefile")) {
+ local $/ = undef;
+ $_ = <MF>;
+ close(MF);
+ /^abs_srcdir\s*=\s*(.*?)\s*$/m;
+ -f "$1/gnumake.h" and $srcdir = $1;
+ }
+
+ # Get Purify log info--if any.
+
+ if (exists $ENV{PURIFYOPTIONS}
+ && $ENV{PURIFYOPTIONS} =~ /.*-logfile=([^ ]+)/) {
+ $pure_log = $1 || '';
+ $pure_log =~ s/%v/$make_name/;
+ $purify_errors = 0;
+ }
+
+ $string = `sh -c "$make_path -j 2 -f /dev/null $redir"`;
+ if ($string =~ /not supported/) {
+ $parallel_jobs = 0;
+ }
+ else {
+ $parallel_jobs = 1;
+ }
+
+ %FEATURES = map { $_ => 1 } split /\s+/, `sh -c "echo '\\\$(info \\\$(.FEATURES))' | $make_path -f- 2>/dev/null"`;
+
+ # Set up for valgrind, if requested.
+
+ $make_command = $make_path;
+
+ if ($valgrind) {
+ my $args = $valgrind_args;
+ open(VALGRIND, "> valgrind.out")
+ || die "Cannot open valgrind.out: $!\n";
+ # -q --leak-check=yes
+ exists $ENV{VALGRIND_ARGS} and $args = $ENV{VALGRIND_ARGS};
+ $make_path = "valgrind --log-fd=".fileno(VALGRIND)." $args $make_path";
+ # F_SETFD is 2
+ fcntl(VALGRIND, 2, 0) or die "fcntl(setfd) failed: $!\n";
+ system("echo Starting on `date` 1>&".fileno(VALGRIND));
+ print "Enabled valgrind support.\n";
+ }
+}
+
+sub setup_for_test
+{
+ $makefile = &get_tmpfile;
+ if (-f $makefile) {
+ unlink $makefile;
+ }
+
+ # Get rid of any Purify logs.
+ if ($pure_log) {
+ ($pure_testname = $testname) =~ tr,/,_,;
+ $pure_testname = "$pure_log.$pure_testname";
+ system("rm -f $pure_testname*");
+ print("Purify testfiles are: $pure_testname*\n") if $debug;
+ }
+}
+
+exit !&toplevel;
diff --git a/src/kmk/tests/scripts/features/archives b/src/kmk/tests/scripts/features/archives
new file mode 100644
index 0000000..a064dd4
--- /dev/null
+++ b/src/kmk/tests/scripts/features/archives
@@ -0,0 +1,213 @@
+# -*-mode: perl-*-
+
+$description = "Test GNU make's archive management features.";
+
+$details = "\
+This only works on systems that support it.";
+
+# If this instance of make doesn't support archives, skip it
+exists $FEATURES{archives} or return -1;
+
+# Create some .o files to work with
+if ($osname eq 'VMS') {
+ use Cwd;
+ my $pwd = getcwd;
+ # VMS AR needs real object files at this time.
+ foreach $afile ('a1', 'a2', 'a3') {
+ # Use non-standard extension to prevent implicit rules from recreating
+ # objects when the test tampers with the timestamp.
+ 1 while unlink "$afile.c1";
+ 1 while unlink "$afile.o";
+ open (MYFILE, ">$afile.c1");
+ print MYFILE "int $afile(void) {return 1;}\n";
+ close MYFILE;
+ system("cc $afile.c1 /object=$afile.o");
+ }
+} else {
+ utouch(-60, qw(a1.o a2.o a3.o));
+}
+
+my $ar = $CONFIG_FLAGS{AR};
+
+# Fallback if configure did not find AR, such as VMS
+# which does not run configure.
+$ar = 'ar' if $ar eq '';
+
+my $redir = '2>&1';
+$redir = '' if $osname eq 'VMS';
+
+my $arflags = 'rv';
+my $arvar = "AR=$ar";
+
+# Newer versions of binutils can be built with --enable-deterministic-archives
+# which forces all timestamps (among other things) to always be 0, defeating
+# GNU make's archive support. See if ar supports the U option to disable it.
+unlink('libxx.a');
+$_ = `$ar U$arflags libxx.a a1.o $redir`;
+if ($? == 0) {
+ $arflags = 'Urv';
+ $arvar = "$arvar ARFLAGS=$arflags";
+}
+
+# Some versions of ar print different things on creation. Find out.
+unlink('libxx.a');
+my $created = `$ar $arflags libxx.a a1.o $redir`;
+
+# Some versions of ar print different things on add. Find out.
+my $add = `$ar $arflags libxx.a a2.o $redir`;
+$add =~ s/a2\.o/#OBJECT#/g;
+
+# Some versions of ar print different things on replacement. Find out.
+my $repl = `$ar $arflags libxx.a a2.o $redir`;
+$repl =~ s/a2\.o/#OBJECT#/g;
+
+unlink('libxx.a');
+
+# Very simple
+my $answer = "$ar $arflags libxx.a a1.o\n$created";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a1.o';
+}
+run_make_test('all: libxx.a(a1.o)', $arvar, $answer);
+
+# Multiple .o's. Add a new one to the existing library
+($_ = $add) =~ s/#OBJECT#/a2.o/g;
+
+$answer = "$ar $arflags libxx.a a2.o\n$_";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a2.o';
+}
+run_make_test('all: libxx.a(a1.o a2.o)', $arvar, $answer);
+
+# Touch one of the .o's so it's rebuilt
+if ($port_type eq 'VMS-DCL') {
+ # utouch is not changing what VMS library compare is testing for.
+ # So do a real change by regenerating the file.
+ 1 while unlink('a1.o');
+ # Later time stamp than last insertion.
+ sleep(2);
+ system('cc a1.c1 /object=a1.o');
+ # Next insertion will have a later timestamp.
+ sleep(2);
+} else {
+ utouch(-40, 'a1.o');
+}
+
+($_ = $repl) =~ s/#OBJECT#/a1.o/g;
+$answer = "$ar $arflags libxx.a a1.o\n$_";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a1.o';
+}
+run_make_test(undef, $arvar, $answer);
+
+# Use wildcards
+$answer = "#MAKE#: Nothing to be done for 'all'.\n";
+run_make_test('all: libxx.a(*.o)', $arvar, $answer);
+
+# Touch one of the .o's so it's rebuilt
+if ($port_type eq 'VMS-DCL') {
+ # utouch is not changing what VMS library compare is testing for.
+ # So do a real change by regenerating the file.
+ 1 while unlink('a1.o');
+ # Make timestamp later than last insertion.
+ sleep(2);
+ system('cc a1.c1 /object=a1.o');
+} else {
+ utouch(-30, 'a1.o');
+}
+($_ = $repl) =~ s/#OBJECT#/a1.o/g;
+$answer = "$ar $arflags libxx.a a1.o\n$_";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a1.o';
+}
+run_make_test(undef, $arvar, $answer);
+
+# Use both wildcards and simple names
+if ($port_type eq 'VMS-DCL') {
+ # utouch is not changing what VMS library compare is testing for.
+ # So do a real change by regenerating the file.
+ 1 while unlink('a2.o');
+ sleep(2);
+ system('cc a2.c1 /object=a2.o');
+} else {
+ utouch(-50, 'a2.o');
+}
+($_ = $add) =~ s/#OBJECT#/a3.o/g;
+$_ .= "$ar $arflags libxx.a a2.o\n";
+($_ .= $repl) =~ s/#OBJECT#/a2.o/g;
+$answer = "$ar $arflags libxx.a a3.o\n$_";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a3.o';
+}
+
+run_make_test('all: libxx.a(a3.o *.o)', $arvar, $answer);
+
+# Check whitespace handling
+if ($port_type eq 'VMS-DCL') {
+ # utouch is not changing what VMS library compare is testing for.
+ # So do a real change by regenerating the file.
+ 1 while unlink('a2.o');
+ sleep(2);
+ system('cc a2.c1 /object=a2.o');
+} else {
+ utouch(-40, 'a2.o');
+}
+($_ = $repl) =~ s/#OBJECT#/a2.o/g;
+$answer = "$ar $arflags libxx.a a2.o\n$_";
+if ($port_type eq 'VMS-DCL') {
+ $answer = 'library /replace libxx.a a2.o';
+}
+run_make_test('all: libxx.a( a3.o *.o )', $arvar, $answer);
+
+rmfiles(qw(a1.c1 a2.c1 a3.c1 a1.o a2.o a3.o libxx.a));
+
+# Check non-archive targets
+# See Savannah bug #37878
+$mk_string = q!
+all: foo(bar).baz
+foo(bar).baz: ; @echo '$@'
+!;
+
+if ($port_type eq 'VMS-DCL') {
+ $mk_string =~ s/echo/write sys\$\$output/;
+ $mk_string =~ s/\'/\"/g;
+}
+run_make_test($mk_string, $arvar, "foo(bar).baz\n");
+
+# Check renaming of archive targets.
+# See Savannah bug #38442
+
+mkdir('artest', 0777);
+touch('foo.vhd');
+$mk_string = q!
+DIR = artest
+vpath % $(DIR)
+default: lib(foo)
+(%): %.vhd ; @cd $(DIR) && touch $(*F) && $(AR) $(ARFLAGS) $@ $(*F) >/dev/null 2>&1 && rm $(*F)
+.PHONY: default
+!;
+if ($port_type eq 'VMS-DCL') {
+ $mk_string =~ s#= artest#= sys\$\$disk:\[.artest\]#;
+ $mk_string =~ s#lib\(foo\)#lib.tlb\(foo\)#;
+ $mk_string =~ s#; \@cd#; pipe SET DEFAULT#;
+ $mk_string =~
+ s#touch \$\(\*F\)#touch \$\(\*F\) && library/create/text sys\$\$disk:\$\@#;
+ $mk_string =~
+ s#library#if f\$\$search(\"\$\@\") \.eqs\. \"\" then library#;
+ # VMS needs special handling for null extension
+ $mk_string =~ s#\@ \$\(\*F\)#\@ \$\(\*F\)\.#;
+ $mk_string =~ s#>/dev/null 2>&1 ##;
+}
+run_make_test($mk_string, $arvar, "");
+
+run_make_test(undef, $arvar, "#MAKE#: Nothing to be done for 'default'.\n");
+
+unlink('foo.vhd');
+if ($osname eq 'VMS') {
+ remove_directory_tree("$pwd/artest");
+} else {
+ remove_directory_tree('artest');
+}
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/comments b/src/kmk/tests/scripts/features/comments
new file mode 100644
index 0000000..9257955
--- /dev/null
+++ b/src/kmk/tests/scripts/features/comments
@@ -0,0 +1,35 @@
+$description = "The following test creates a makefile to test comments\n"
+ ."and comment continuation to the next line using a \n"
+ ."backslash within makefiles.";
+
+$details = "To test comments within a makefile, a semi-colon was placed \n"
+ ."after a comment was started. This should not be reported as\n"
+ ."an error since it is within a comment. We then continue the \n"
+ ."comment to the next line using a backslash. To test whether\n"
+ ."the comment really continued, we place an echo command with some\n"
+ ."text on the line which should never execute since it should be \n"
+ ."within a comment\n";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<\EOF;
+# Test comment vs semicolon parsing and line continuation
+target: # this ; is just a comment \
+ @echo This is within a comment.
+ @echo There should be no errors for this makefile.
+EOF
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "There should be no errors for this makefile.\n";
+
+# COMPARE RESULTS
+
+&compare_output($answer,&get_logfile(1))
diff --git a/src/kmk/tests/scripts/features/conditionals b/src/kmk/tests/scripts/features/conditionals
new file mode 100644
index 0000000..78344b9
--- /dev/null
+++ b/src/kmk/tests/scripts/features/conditionals
@@ -0,0 +1,162 @@
+# -*-perl-*-
+$description = "Check GNU make conditionals.";
+
+$details = "Attempt various different flavors of GNU make conditionals.";
+
+run_make_test('
+arg1 = first
+arg2 = second
+arg3 = third
+arg4 = cc
+arg5 = second
+
+all:
+ifeq ($(arg1),$(arg2))
+ @echo arg1 equals arg2
+else
+ @echo arg1 NOT equal arg2
+endif
+
+ifeq \'$(arg2)\' "$(arg5)"
+ @echo arg2 equals arg5
+else
+ @echo arg2 NOT equal arg5
+endif
+
+ifneq \'$(arg3)\' \'$(arg4)\'
+ @echo arg3 NOT equal arg4
+else
+ @echo arg3 equal arg4
+endif
+
+ifndef undefined
+ @echo variable is undefined
+else
+ @echo variable undefined is defined
+endif
+ifdef arg4
+ @echo arg4 is defined
+else
+ @echo arg4 is NOT defined
+endif',
+ '',
+ 'arg1 NOT equal arg2
+arg2 equals arg5
+arg3 NOT equal arg4
+variable is undefined
+arg4 is defined');
+
+
+# Test expansion of variables inside ifdef.
+
+run_make_test('
+foo = 1
+
+FOO = foo
+F = f
+
+DEF = no
+DEF2 = no
+
+ifdef $(FOO)
+DEF = yes
+endif
+
+ifdef $(F)oo
+DEF2 = yes
+endif
+
+
+DEF3 = no
+FUNC = $1
+ifdef $(call FUNC,DEF)3
+ DEF3 = yes
+endif
+
+all:; @echo DEF=$(DEF) DEF2=$(DEF2) DEF3=$(DEF3)',
+ '',
+ 'DEF=yes DEF2=yes DEF3=yes');
+
+
+# Test all the different "else if..." constructs
+
+run_make_test('
+arg1 = first
+arg2 = second
+arg3 = third
+arg4 = cc
+arg5 = fifth
+
+result =
+
+ifeq ($(arg1),$(arg2))
+ result += arg1 equals arg2
+else ifeq \'$(arg2)\' "$(arg5)"
+ result += arg2 equals arg5
+else ifneq \'$(arg3)\' \'$(arg3)\'
+ result += arg3 NOT equal arg4
+else ifndef arg5
+ result += variable is undefined
+else ifdef undefined
+ result += arg4 is defined
+else
+ result += success
+endif
+
+
+all: ; @echo $(result)',
+ '',
+ 'success');
+
+
+# Test some random "else if..." construct nesting
+
+run_make_test('
+arg1 = first
+arg2 = second
+arg3 = third
+arg4 = cc
+arg5 = second
+
+ifeq ($(arg1),$(arg2))
+ $(info failed 1)
+else ifeq \'$(arg2)\' "$(arg2)"
+ ifdef undefined
+ $(info failed 2)
+ else
+ $(info success)
+ endif
+else ifneq \'$(arg3)\' \'$(arg3)\'
+ $(info failed 3)
+else ifdef arg5
+ $(info failed 4)
+else ifdef undefined
+ $(info failed 5)
+else
+ $(info failed 6)
+endif
+
+.PHONY: all
+all: ; @:',
+ '',
+ 'success');
+
+# SV 47960 : ensure variable assignments in non-taken legs don't cause problems
+run_make_test('
+ifneq ($(FOO),yes)
+target:
+else
+BAR = bar
+target:
+endif
+ @echo one
+',
+ '', "one\n");
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/default_names b/src/kmk/tests/scripts/features/default_names
new file mode 100644
index 0000000..2e83880
--- /dev/null
+++ b/src/kmk/tests/scripts/features/default_names
@@ -0,0 +1,44 @@
+# -*-perl-*-
+
+$description = "This script tests to make sure that Make looks for
+default makefiles in the correct order (GNUmakefile,makefile,Makefile)";
+
+# Create a makefile called "GNUmakefile"
+$makefile = "GNUmakefile";
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE "FIRST: ; \@echo It chose GNUmakefile\n";
+close(MAKEFILE);
+
+# Create another makefile called "makefile"
+open(MAKEFILE,"> makefile");
+print MAKEFILE "SECOND: ; \@echo It chose makefile\n";
+close(MAKEFILE);
+
+# DOS/WIN32/MacOSX platforms are case-insensitive / case-preserving, so
+# Makefile is the same file as makefile. Just test what we can here.
+
+my $case_sensitive = 0;
+if (! -f 'Makefile') {
+ # Create another makefile called "Makefile"
+ $case_sensitive = 1;
+ open(MAKEFILE,"> Makefile");
+ print MAKEFILE "THIRD: ; \@echo It chose Makefile\n";
+ close(MAKEFILE);
+}
+
+run_make_with_options("","",&get_logfile);
+compare_output("It chose GNUmakefile\n",&get_logfile(1));
+unlink($makefile);
+
+run_make_with_options("","",&get_logfile);
+compare_output("It chose makefile\n",&get_logfile(1));
+unlink("makefile");
+
+if ($case_sensitive) {
+ run_make_with_options("","",&get_logfile);
+ compare_output("It chose Makefile\n",&get_logfile(1));
+ unlink("Makefile");
+}
+
+1;
diff --git a/src/kmk/tests/scripts/features/double_colon b/src/kmk/tests/scripts/features/double_colon
new file mode 100644
index 0000000..58f126f
--- /dev/null
+++ b/src/kmk/tests/scripts/features/double_colon
@@ -0,0 +1,220 @@
+# -*-perl-*-
+$description = "Test handling of double-colon rules.";
+
+$details = "\
+We test these features:
+
+ - Multiple commands for the same (double-colon) target
+ - Different prerequisites for targets: only out-of-date
+ ones are rebuilt.
+ - Double-colon targets that aren't the goal target.
+
+Then we do the same thing for parallel builds: double-colon
+targets should always be built serially.";
+
+# The Contents of the MAKEFILE ...
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+
+all: baz
+
+foo:: f1.h ; @echo foo FIRST
+foo:: f2.h ; @echo foo SECOND
+
+bar:: ; @echo aaa; sleep 1; echo aaa done
+bar:: ; @echo bbb
+
+baz:: ; @echo aaa
+baz:: ; @echo bbb
+
+biz:: ; @echo aaa
+biz:: two ; @echo bbb
+
+two: ; @echo two
+
+f1.h f2.h: ; @echo $@
+
+d :: ; @echo ok
+d :: d ; @echo oops
+
+EOF
+
+close(MAKEFILE);
+
+# TEST 0: A simple double-colon rule that isn't the goal target.
+
+&run_make_with_options($makefile, "all", &get_logfile, 0);
+$answer = "aaa\nbbb\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 1: As above, in parallel
+
+if ($parallel_jobs) {
+ &run_make_with_options($makefile, "-j10 all", &get_logfile, 0);
+ $answer = "aaa\nbbb\n";
+ &compare_output($answer, &get_logfile(1));
+}
+
+# TEST 2: A simple double-colon rule that is the goal target
+
+&run_make_with_options($makefile, "bar", &get_logfile, 0);
+$answer = "aaa\naaa done\nbbb\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 3: As above, in parallel
+
+if ($parallel_jobs) {
+ &run_make_with_options($makefile, "-j10 bar", &get_logfile, 0);
+ $answer = "aaa\naaa done\nbbb\n";
+ &compare_output($answer, &get_logfile(1));
+}
+
+# TEST 4: Each double-colon rule is supposed to be run individually
+
+&utouch(-5, 'f2.h');
+&touch('foo');
+
+&run_make_with_options($makefile, "foo", &get_logfile, 0);
+$answer = "f1.h\nfoo FIRST\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 5: Again, in parallel.
+
+if ($parallel_jobs) {
+ &run_make_with_options($makefile, "-j10 foo", &get_logfile, 0);
+ $answer = "f1.h\nfoo FIRST\n";
+ &compare_output($answer, &get_logfile(1));
+}
+
+# TEST 6: Each double-colon rule is supposed to be run individually
+
+&utouch(-5, 'f1.h');
+unlink('f2.h');
+&touch('foo');
+
+&run_make_with_options($makefile, "foo", &get_logfile, 0);
+$answer = "f2.h\nfoo SECOND\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 7: Again, in parallel.
+
+if ($parallel_jobs) {
+ &run_make_with_options($makefile, "-j10 foo", &get_logfile, 0);
+ $answer = "f2.h\nfoo SECOND\n";
+ &compare_output($answer, &get_logfile(1));
+}
+
+# TEST 8: Test circular dependency check; PR/1671
+
+&run_make_with_options($makefile, "d", &get_logfile, 0);
+$answer = "ok\n$make_name: Circular d <- d dependency dropped.\noops\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 8: I don't grok why this is different than the above, but it is...
+#
+# Hmm... further testing indicates this might be timing-dependent?
+#
+#if ($parallel_jobs) {
+# &run_make_with_options($makefile, "-j10 biz", &get_logfile, 0);
+# $answer = "aaa\ntwo\nbbb\n";
+# &compare_output($answer, &get_logfile(1));
+#}
+
+unlink('foo','f1.h','f2.h');
+
+
+# TEST 9: make sure all rules in s double colon family get executed
+# (Savannah bug #14334).
+#
+
+&touch('one');
+&touch('two');
+
+run_make_test('
+.PHONY: all
+all: result
+
+result:: one
+ @echo $^ >>$@
+ @echo $^
+
+result:: two
+ @echo $^ >>$@
+ @echo $^
+
+',
+'',
+'one
+two');
+
+unlink('result','one','two');
+
+# TEST 10: SV 33399 : check for proper backslash handling
+
+run_make_test('
+a\ xb :: ; @echo one
+a\ xb :: ; @echo two
+',
+ '', "one\ntwo\n");
+
+# Test 11: SV 44742 : All double-colon rules should be run in parallel build.
+
+run_make_test('result :: 01
+ @echo update
+ @touch $@
+result :: 02
+ @echo update
+ @touch $@
+result :: 03
+ @echo update
+ @touch $@
+result :: 04
+ @echo update
+ @touch $@
+result :: 05
+ @echo update
+ @touch $@
+01 02 03 04 05:
+ @touch 01 02 03 04 05
+',
+ '-j10 result', "update\nupdate\nupdate\nupdate\nupdate\n");
+
+unlink('result', '01', '02', '03', '04', '05');
+
+# Test 12: SV 44742 : Double-colon rules with parallelism
+
+run_make_test('
+root: all
+ echo root
+all::
+ echo all_one
+all:: 3
+ echo all_two
+%:
+ sleep $*
+',
+ '-rs -j2 1 2 root', "all_one\nall_two\nroot\n");
+
+# SV 47995 : Parallel double-colon rules with FORCE
+
+run_make_test('
+all:: ; @echo one
+
+all:: joe ; @echo four
+
+joe: FORCE ; touch joe-is-forced
+
+FORCE:
+',
+ '-j5', "one\ntouch joe-is-forced\nfour\n");
+
+unlink('joe-is-forced');
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/echoing b/src/kmk/tests/scripts/features/echoing
new file mode 100644
index 0000000..40debf5
--- /dev/null
+++ b/src/kmk/tests/scripts/features/echoing
@@ -0,0 +1,64 @@
+# -*-perl-*-
+$description = "The following test creates a makefile to test command
+echoing. It tests that when a command line starts with
+a '\@', the echoing of that line is suppressed. It also
+tests the -n option which tells make to ONLY echo the
+commands and no execution happens. In this case, even
+the commands with '\@' are printed. Lastly, it tests the
+-s flag which tells make to prevent all echoing, as if
+all commands started with a '\@'.";
+
+$details = "This test is similar to the 'clean' test except that a '\@' has
+been placed in front of the delete command line. Four tests
+are run here. First, make is run normally and the first echo
+command should be executed. In this case there is no '\@' so
+we should expect make to display the command AND display the
+echoed message. Secondly, make is run with the clean target,
+but since there is a '\@' at the beginning of the command, we
+expect no output; just the deletion of a file which we check
+for. Third, we give the clean target again except this time
+we give make the -n option. We now expect the command to be
+displayed but not to be executed. In this case we need only
+to check the output since an error message would be displayed
+if it actually tried to run the delete command again and the
+file didn't exist. Lastly, we run the first test again with
+the -s option and check that make did not echo the echo
+command before printing the message.\n";
+
+$example = "EXAMPLE_FILE";
+
+touch($example);
+
+# TEST #1
+# -------
+
+run_make_test("
+all:
+\techo This makefile did not clean the dir... good
+clean:
+\t\@$delete_command $example\n",
+ '', 'echo This makefile did not clean the dir... good
+This makefile did not clean the dir... good');
+
+# TEST #2
+# -------
+
+run_make_test(undef, 'clean', '');
+if (-f $example) {
+ $test_passed = 0;
+ unlink($example);
+}
+
+# TEST #3
+# -------
+
+run_make_test(undef, '-n clean', "$delete_command $example\n");
+
+
+# TEST #4
+# -------
+
+run_make_test(undef, '-s', "This makefile did not clean the dir... good\n");
+
+
+1;
diff --git a/src/kmk/tests/scripts/features/errors b/src/kmk/tests/scripts/features/errors
new file mode 100644
index 0000000..ebd4383
--- /dev/null
+++ b/src/kmk/tests/scripts/features/errors
@@ -0,0 +1,107 @@
+# -*-perl-*-
+
+$description = "The following tests the -i option and the '-' in front of \n"
+ ."commands to test that make ignores errors in these commands\n"
+ ."and continues processing.";
+
+$details = "This test runs two makes. The first runs on a target with a \n"
+ ."command that has a '-' in front of it (and a command that is \n"
+ ."intended to fail) and then a delete command after that is \n"
+ ."intended to succeed. If make ignores the failure of the first\n"
+ ."command as it is supposed to, then the second command should \n"
+ ."delete a file and this is what we check for. The second make\n"
+ ."that is run in this test is identical except that the make \n"
+ ."command is given with the -i option instead of the '-' in \n"
+ ."front of the command. They should run the same. ";
+
+if ($vos)
+{
+ $rm_command = "delete_file";
+}
+else
+{
+ $rm_command = "rm";
+}
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "clean:\n"
+ ."\t-$rm_command cleanit\n"
+ ."\t$rm_command foo\n"
+ ."clean2: \n"
+ ."\t$rm_command cleanit\n"
+ ."\t$rm_command foo\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch("foo");
+
+unlink("cleanit");
+$cleanit_error = `sh -c "$rm_command cleanit 2>&1"`;
+chomp $cleanit_error;
+$delete_error_code = $? >> 8;
+
+# TEST #1
+# -------
+
+$answer = "$rm_command cleanit
+$cleanit_error
+$make_name: [$makefile:2: clean] Error $delete_error_code (ignored)
+$rm_command foo\n";
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+# If make acted as planned, it should ignore the error from the first
+# command in the target and execute the second which deletes the file "foo"
+# This file, therefore, should not exist if the test PASSES.
+if (-f "foo") {
+ $test_passed = 0;
+}
+
+# The output for this on VOS is too hard to replicate, so we only check it
+# on unix.
+if (!$vos)
+{
+ &compare_output($answer,&get_logfile(1));
+}
+
+
+&touch("foo");
+
+# TEST #2
+# -------
+
+$answer = "$rm_command cleanit
+$cleanit_error
+$make_name: [$makefile:5: clean2] Error $delete_error_code (ignored)
+$rm_command foo\n";
+
+&run_make_with_options($makefile,"clean2 -i",&get_logfile);
+
+if (-f "foo") {
+ $test_passed = 0;
+}
+
+if (!$vos) {
+ &compare_output($answer,&get_logfile(1));
+}
+
+# Test that error line offset works
+
+run_make_test(q!
+all:
+ @echo hi
+ @echo there
+ @exit 1
+!,
+ '', "hi\nthere\n#MAKE#: *** [#MAKEFILE#:5: all] Error 1", 512);
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/escape b/src/kmk/tests/scripts/features/escape
new file mode 100644
index 0000000..5fcb023
--- /dev/null
+++ b/src/kmk/tests/scripts/features/escape
@@ -0,0 +1,74 @@
+# -*-perl-*-
+$description = "Test various types of escaping in makefiles.";
+
+$details = "\
+Make sure that escaping of ':' works in target names.
+Make sure escaping of whitespace works in target names.
+Make sure that escaping of '#' works.
+Make sure that backslash before non-special characters are kept.";
+
+
+# TEST 1
+
+run_make_test('
+$(path)foo : ; @echo "touch ($@)"
+
+foo\ bar: ; @echo "touch ($@)"
+
+sharp: foo\#bar.ext
+foo\#bar.ext: ; @echo "foo#bar.ext = ($@)"',
+ '',
+ 'touch (foo)');
+
+# TEST 2: This one should fail, since the ":" is unquoted.
+
+run_make_test(undef,
+ 'path=pre:',
+ "#MAKEFILE#:2: *** target pattern contains no '%' (target 'foo'). Stop.",
+ 512);
+
+# TEST 3: This one should work, since we escape the ":".
+
+run_make_test(undef,
+ "'path=pre\\:'",
+ 'touch (pre:foo)');
+
+# TEST 4: This one should fail, since the escape char is escaped.
+
+run_make_test(undef,
+ "'path=pre\\\\:'",
+ "#MAKEFILE#:2: *** target pattern contains no '%' (target 'foo'). Stop.",
+ 512);
+
+# TEST 5: This one should work
+
+run_make_test(undef,
+ "'foo bar'",
+ 'touch (foo bar)');
+
+# TEST 6: Test escaped comments
+
+run_make_test(undef,
+ 'sharp',
+ 'foo#bar.ext = (foo#bar.ext)');
+
+# Test escaped colons in prerequisites
+# Quoting of backslashes in q!! is kind of messy.
+# Solaris sh does not properly handle backslashes even in '' so just
+# check the output make prints, not what the shell interprets.
+run_make_test(q!
+foo: foo\\:bar foo\\\\\\:bar foo\\\\\\\\\\:bar
+foo foo\\:bar foo\\\\\\:bar foo\\\\\\\\\\:bar: ; : '$@'
+!,
+ '', ": 'foo:bar'\n: 'foo\\:bar'\n: 'foo\\\\:bar'\n: 'foo'\n");
+
+# Test backslash before non-special chars: should be kept as-is
+
+run_make_test(q!
+all: ..\foo
+.DEFAULT: ; : '$@'
+!,
+ '', ": '..\\foo'\n");
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/export b/src/kmk/tests/scripts/features/export
new file mode 100644
index 0000000..81bff0c
--- /dev/null
+++ b/src/kmk/tests/scripts/features/export
@@ -0,0 +1,186 @@
+# -*-perl-*-
+$description = "Check GNU make export/unexport commands.";
+
+$details = "";
+
+# The test driver cleans out our environment for us so we don't have to worry
+# about that here.
+
+&run_make_test('
+FOO = foo
+BAR = bar
+BOZ = boz
+
+export BAZ = baz
+export BOZ
+
+BITZ = bitz
+BOTZ = botz
+
+export BITZ BOTZ
+unexport BOTZ
+
+ifdef EXPORT_ALL
+export
+endif
+
+ifdef UNEXPORT_ALL
+unexport
+endif
+
+ifdef EXPORT_ALL_PSEUDO
+.EXPORT_ALL_VARIABLES:
+endif
+
+all:
+ @echo "FOO=$(FOO) BAR=$(BAR) BAZ=$(BAZ) BOZ=$(BOZ) BITZ=$(BITZ) BOTZ=$(BOTZ)"
+ @echo "FOO=$$FOO BAR=$$BAR BAZ=$$BAZ BOZ=$$BOZ BITZ=$$BITZ BOTZ=$$BOTZ"
+',
+ '', "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO= BAR= BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 1: make sure vars inherited from the parent are exported
+
+$extraENV{FOO} = 1;
+
+&run_make_test(undef, '', "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO=foo BAR= BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 2: global export. Explicit unexport takes precedence.
+
+run_make_test(undef, "EXPORT_ALL=1" ,
+ "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 3: global unexport. Explicit export takes precedence.
+
+&run_make_test(undef, "UNEXPORT_ALL=1",
+ "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO= BAR= BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 4: both: in the above makefile the unexport comes last so that rules.
+
+&run_make_test(undef, "EXPORT_ALL=1 UNEXPORT_ALL=1",
+ "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO= BAR= BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 5: test the pseudo target.
+
+&run_make_test(undef, "EXPORT_ALL_PSEUDO=1",
+ "FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=botz
+FOO=foo BAR=bar BAZ=baz BOZ=boz BITZ=bitz BOTZ=\n");
+
+# TEST 6: Test the expansion of variables inside export
+
+&run_make_test('
+foo = f-ok
+bar = b-ok
+
+FOO = foo
+F = f
+
+BAR = bar
+B = b
+
+export $(FOO)
+export $(B)ar
+
+all:
+ @echo foo=$(foo) bar=$(bar)
+ @echo foo=$$foo bar=$$bar
+',
+ "", "foo=f-ok bar=b-ok\nfoo=f-ok bar=b-ok\n");
+
+# TEST 7: Test the expansion of variables inside unexport
+
+&run_make_test('
+foo = f-ok
+bar = b-ok
+
+FOO = foo
+F = f
+
+BAR = bar
+B = b
+
+export foo bar
+
+unexport $(FOO)
+unexport $(B)ar
+
+all:
+ @echo foo=$(foo) bar=$(bar)
+ @echo foo=$$foo bar=$$bar
+',
+ '', "foo=f-ok bar=b-ok\nfoo= bar=\n");
+
+# TEST 7: Test exporting multiple variables on the same line
+
+&run_make_test('
+A = a
+B = b
+C = c
+D = d
+E = e
+F = f
+G = g
+H = h
+I = i
+J = j
+
+SOME = A B C
+
+export F G H I J
+
+export D E $(SOME)
+
+all: ; @echo A=$$A B=$$B C=$$C D=$$D E=$$E F=$$F G=$$G H=$$H I=$$I J=$$J
+',
+ '', "A=a B=b C=c D=d E=e F=f G=g H=h I=i J=j\n");
+
+# TEST 8: Test unexporting multiple variables on the same line
+
+@extraENV{qw(A B C D E F G H I J)} = qw(1 2 3 4 5 6 7 8 9 10);
+
+&run_make_test('
+A = a
+B = b
+C = c
+D = d
+E = e
+F = f
+G = g
+H = h
+I = i
+J = j
+
+SOME = A B C
+
+unexport F G H I J
+
+unexport D E $(SOME)
+
+all: ; @echo A=$$A B=$$B C=$$C D=$$D E=$$E F=$$F G=$$G H=$$H I=$$I J=$$J
+',
+ '', "A= B= C= D= E= F= G= H= I= J=\n");
+
+# TEST 9: Check setting a variable named "export"
+
+&run_make_test('
+export = 123
+export export
+export export = 456
+a: ; @echo "\$$(export)=$(export) / \$$export=$$export"
+',
+ '', "\$(export)=456 / \$export=456\n");
+
+# TEST 9: Check "export" as a target
+
+&run_make_test('
+a: export
+export: ; @echo "$@"
+',
+ '', "export\n");
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/ifcond b/src/kmk/tests/scripts/features/ifcond
new file mode 100644
index 0000000..b492e77
--- /dev/null
+++ b/src/kmk/tests/scripts/features/ifcond
@@ -0,0 +1,950 @@
+# $Id: ifcond 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# if conditionals.
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the if conditionals";
+
+$details = "...";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(if-expr 1+1,1,0),1)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - A more comprehensive, yet a bit large, test.
+ # ------------------------------------------------------
+ run_make_test('
+
+#
+# Note! The testcase are ordered by ascending operator precedence
+# with the exception of equal and not-equal because these
+# are kind of useful for performing tests on non-logical ops.
+#
+
+.PHONY: all
+all: ; @:
+
+#
+# Parenthesis
+#
+$(info unary operators: ( and ))
+if (1)
+else
+$(error )
+endif
+
+if ((((1))))
+else
+$(error )
+endif
+
+
+#
+# Equal and Not Equal w/ some fundamental bits thrown in.
+#
+$(info binary operators: == and !=)
+
+if 1 == 1
+else
+$(error )
+endif
+
+if 2 == 3
+$(error )
+else
+endif
+
+if 2 != 3
+else
+$(error )
+endif
+
+if a != b
+else
+$(error )
+endif
+
+if asdf == asdf
+else
+$(error )
+endif
+
+if "asdf" == asdf
+else
+$(error )
+endif
+
+if \'asdf\' == asdf
+else
+$(error )
+endif
+
+if \'asdf\' == "asdf"
+else
+$(error )
+endif
+
+if \'asdf\' == \'asdf\'
+else
+$(error )
+endif
+
+if "asdf" == "asdf"
+else
+$(error )
+endif
+
+if 0x1 == 1
+else
+$(error )
+endif
+
+if 0xfff == 4095
+else
+$(error )
+endif
+
+if 0xfff == 4095
+else
+$(error )
+endif
+
+if 0d10 == 10
+else
+$(error )
+endif
+
+if 0d10 == 10
+else
+$(error )
+endif
+
+if 0xa == 012
+else
+$(error )
+endif
+
+if 0b1110 == 016
+else
+$(error )
+endif
+
+
+#
+# Logical OR
+#
+$(info binary operator: ||)
+if 1
+else
+$(error busted)
+endif
+
+if 1 || 1
+else
+$(error )
+endif
+
+if 0 || 0
+$(error )
+else
+endif
+
+if 1 || 0
+else
+$(error )
+endif
+
+if 0 || 1
+else
+$(error )
+endif
+
+if 0 || 0 || 0 || 0 || 0 || 0 || 0
+$(error )
+else
+endif
+
+if 0 || 0 || 0 || 1 || 0 || 0 || 0
+else
+$(error )
+endif
+
+if "asdf" || 0
+else
+$(error )
+endif
+
+if 0 || "asdf"
+else
+$(error )
+endif
+
+if \'asdf\' || 0
+else
+$(error )
+endif
+
+if "" || 0
+$(error )
+endif
+if "" || 1
+else
+$(error )
+endif
+if \'\' || 0
+$(error )
+endif
+if \'\' || 1
+else
+$(error )
+endif
+
+if "" || \'\'
+$(error )
+endif
+if "1" || \'\'
+else
+$(error )
+endif
+if "1" || \'1\'
+else
+$(error )
+endif
+if "" || \'1\'
+else
+$(error )
+endif
+
+
+#
+# Logical AND
+#
+$(info binary operator: &&)
+if 1 && 1
+else
+$(error )
+endif
+if 1 && 0
+$(error )
+endif
+if 1234 && 0
+$(error )
+endif
+if 123434 && 0 && 123435 && 1
+$(error )
+endif
+
+if "" && 1
+$(error )
+endif
+if ("asdf" && 1) != 1
+$(error )
+endif
+if "1" && \'asdf\'
+else
+$(error )
+endif
+if "1" && \'asdf\' && 0
+$(error )
+endif
+
+if 0 || 1 && 0
+$(error )
+endif
+
+
+#
+# Bitwise OR
+#
+$(info binary operator: |)
+if 1 | 0
+else
+$(error )
+endif
+if 1 | 1
+else
+$(error )
+endif
+if 11234 | 343423
+else
+$(error )
+endif
+if (1|2)!=3
+$(error )
+endif
+if 1|2 != 3
+else
+$(error )
+endif
+if (1|2|4|8)!=0xf
+$(error )
+endif
+
+
+#
+# Bitwise XOR
+#
+$(info binary operator: ^)
+if 1 ^ 1
+$(error )
+endif
+
+if (2 ^ 1) != 3
+$(error )
+endif
+
+if 7 != (2 ^ 1 ^ 4)
+$(error )
+endif
+
+if (2 ^ 1 | 2) != 3
+$(error )
+endif
+
+
+#
+# Bitwise AND
+#
+$(info binary operator: &)
+if (4097 & 1) != 1
+$(error )
+endif
+if (0xfff & 0x0f0) != 0xf0
+$(error )
+endif
+if (0x1e3 & 0x100 | 3) != 0x103
+$(error )
+endif
+
+
+#
+# Greater than
+#
+$(info binary operator: >)
+if 1 > 0
+else
+$(error )
+endif
+
+if 1024 > 1023
+else
+$(error )
+endif
+
+if 999 > 1023
+$(error )
+endif
+
+if (5 > 4 | 2) != 3
+$(error )
+endif
+
+if (1 & 8 > 4) != 1
+$(error )
+endif
+
+if (8 > 4 ^ 16) != 17
+$(error )
+endif
+
+if "b" > \'a\'
+else
+$(error )
+endif
+if "abcdef" > \'ffdas\'
+$(error )
+endif
+if abcdef > ffdas
+$(error )
+endif
+
+
+#
+# Greater or equal than
+#
+$(info binary operator: >=)
+if 20 > 0
+else
+$(error )
+endif
+
+if 20 >= 20
+else
+$(error )
+endif
+
+if 19 >= 20
+$(error )
+endif
+
+if (1 & 8 >= 4) != 1
+$(error )
+endif
+
+if "x" >= \'x\'
+else
+$(error )
+endif
+if "abdc" >= \'abcd\'
+else
+$(error )
+endif
+if "ffdaaa" >= \'ffdasd\'
+$(error )
+endif
+if asdf >= asdf
+else
+$(error )
+endif
+
+
+#
+# Less than
+#
+if 1 < 1
+$(error )
+endif
+if -123 < -134
+$(error )
+endif
+if 123 <= 7777
+else
+$(error )
+endif
+
+if "b" < \'a\'
+$(error )
+endif
+if b < a
+$(error )
+endif
+if \'foobar\' < \'a$\'
+$(error )
+endif
+if hhhh < ggggg
+$(error )
+endif
+if qwerty < qwerty0
+else
+$(error )
+endif
+
+
+#
+# Less or equal than
+#
+$(info binary operator: >>)
+if 1 <= 0
+$(error )
+endif
+if 1 <= 1
+else
+$(error )
+endif
+if 123 <= 123 != 1
+$(error )
+endif
+if 560 <= 456
+$(error )
+endif
+
+if "a" <= \'a\'
+else
+$(error )
+endif
+if "abcdef" <= \'abcdef\'
+else
+$(error )
+endif
+if q12345z6 <= q12345z
+$(error )
+endif
+if QWERTY <= ABCDE
+$(error )
+endif
+
+
+#
+# Shift right
+#
+$(info binary operator: >>)
+if 1 >> 0 != 1
+$(error )
+endif
+if 1024 >> 2 != 256
+$(error )
+endif
+if 102435 >> 4 > 1234 != 1
+$(error )
+endif
+
+
+#
+# Shift left
+#
+$(info binary operator: <<)
+if 1 << 0 != 1
+$(error )
+endif
+if 1 << 1 != 2
+$(error )
+endif
+if 1 << 4 != 16
+$(error )
+endif
+if 1 << 10 != 1024
+$(error )
+endif
+if 34 << 10 != 0x8800
+$(error )
+endif
+if 1099511627776 << 21 != 2305843009213693952
+$(error )
+endif
+if 1 << 61 != 2305843009213693952
+$(error )
+endif
+
+if 2 << 60 > 123434323 != 1
+$(error )
+endif
+
+
+#
+# Subtraction
+#
+$(info binary operator: -)
+if 1-1 != 0
+$(error )
+endif
+if 1023-511 != 512
+$(error )
+endif
+if 4 - 3 << 3 != 8
+$(error )
+endif
+
+
+#
+# Addition
+#
+$(info binary operator: +)
+if 1+1 != 2
+$(error )
+endif
+if 1234+1000 != 2234
+$(error )
+endif
+if 2 + 2 << 4 != 64
+$(error )
+endif
+
+
+#
+# Modulus
+#
+$(info binary operator: %)
+if 0%2 != 0
+$(error )
+endif
+if 10%7 != 3
+$(error )
+endif
+if 10 + 100%70 - 3 != 37
+$(error )
+endif
+
+
+#
+# Division
+#
+$(info binary operator: /)
+if 0/1 != 0
+$(error )
+endif
+if 1000/2 != 500
+$(error )
+endif
+if 1000/2 + 4 != 504
+$(error )
+endif
+if 5 + 1000/4 != 255
+$(error )
+endif
+
+
+#
+# Multiplication
+#
+$(info binary operator: *)
+if 1*1 != 1
+$(error )
+endif
+if 10*10 != 100
+$(error )
+endif
+if 1024*64 != 65536
+$(error )
+endif
+if 10*10 - 10 != 90
+$(error )
+endif
+if 1000 - 10*10 != 900
+$(error )
+endif
+
+
+#
+# Logical NOT
+#
+$(info unary operator: !)
+if !1
+$(error )
+endif
+
+if !42 == 0
+else
+$(error )
+endif
+
+if !0 == 1
+else
+$(error )
+endif
+
+if !!0 == 0
+else
+$(error )
+endif
+
+if !0 * 123 != 123
+$(error )
+endif
+if !!!0 * 512 != 512
+$(error )
+endif
+
+
+#
+# Bitwise NOT
+#
+$(info unary operator: ~)
+if ~0xfff != 0xfffffffffffff000
+$(error )
+endif
+
+
+#
+# Pluss
+#
+$(info unary operator: +)
+if +2 != 2
+$(error )
+endif
+if 1++++++++++++2134 != 2135
+$(error )
+endif
+
+
+#
+# Minus (negation)
+#
+$(info unary operator: -)
+if --2 != 2
+$(error )
+endif
+
+if 1 - -2 != 3
+$(error )
+endif
+
+
+#
+# target
+#
+trg_deps_only: foobar
+trg_with_cmds: foobar
+ echo $@
+
+$(info unary operator: target) # This flushes stuff in read.c
+
+if target trg_with_cmds
+else
+$(error target trg_with_cmds)
+endif
+if target(trg_deps_only)
+$(error target trg_deps_only)
+endif
+if target ( foobar )
+$(error target foobar)
+endif
+
+
+#
+# defined
+#
+$(info unary operator: defined)
+var_defined := 1
+var_not_defined :=
+
+if defined var_defined
+else
+$(error )
+endif
+if defined(var_defined)
+else
+$(error )
+endif
+if defined (var_defined)
+else
+$(error )
+endif
+if !defined(var_defined)
+$(error )
+endif
+if defined (var_not_defined)
+$(error )
+endif
+
+
+#
+# bool
+#
+$(info unary operator: bool)
+if bool("Asdf") != 1
+$(error )
+endif
+if bool("") != 0
+$(error )
+endif
+
+
+#
+# bool
+#
+$(info unary operator: num)
+if num("1234") != 1235 - 1
+$(error )
+endif
+if num(\'1234\') != 1233 + 1
+$(error )
+endif
+
+
+#
+# str
+#
+$(info unary operator: str)
+if str(a < b) != 1
+$(error )
+endif
+if str(a < b) != \'1\'
+$(error )
+endif
+if str( 1 ) != "1"
+$(error )
+endif
+if str( 1 ) != "1"
+$(error )
+endif
+if str( num(0x1000) ) != "4096"
+$(error )
+endif
+if str(0x1000) != 0x1000
+$(error )
+endif
+
+
+
+#
+# Quick check of $(if-expr ) and $(expr ).
+#
+$(info $$(if-expr ,,))
+ifeq ($(if-expr 0 || 2,42,500),42)
+else
+$(error )
+endif
+ifeq ($(if-expr 5+3 == 231,42,500),42)
+$(error )
+endif
+
+$(info $$(expr ))
+ifeq ($(expr 5+3),8)
+else
+$(error expr:$(expr 5+3) expected 8)
+endif
+ifeq ($(expr 25*25),625)
+else
+$(error expr:$(expr 25*25) expected 625)
+endif
+ifeq ($(expr 100/3),3)
+$(error )
+endif
+',
+'',
+'unary operators: ( and )
+binary operators: == and !=
+binary operator: ||
+binary operator: &&
+binary operator: |
+binary operator: ^
+binary operator: &
+binary operator: >
+binary operator: >=
+binary operator: >>
+binary operator: >>
+binary operator: <<
+binary operator: -
+binary operator: +
+binary operator: %
+binary operator: /
+binary operator: *
+unary operator: !
+unary operator: ~
+unary operator: +
+unary operator: -
+unary operator: target
+unary operator: defined
+unary operator: bool
+unary operator: num
+unary operator: str
+$(if-expr ,,)
+$(expr )
+');
+
+}
+
+
+ # TEST #2 - A bug.
+ # ------------------------------------------------------
+ run_make_test('
+.PHONY: all
+all: ; @:
+
+#
+# Assert sanity first on simple strings.
+#
+if abcd != "abcd"
+$(error )
+endif
+
+if \'abcd\' != abcd
+$(error )
+endif
+
+if abcd != abcd
+$(error )
+endif
+
+
+#
+# String by reference, start with a few simple cases.
+#
+STR1 = abcd
+
+if "$(STR1)" != "abcd"
+$(error )
+endif
+
+if \'$(STR1)\' == "abcd" # not expanded.
+$(error )
+endif
+
+if \'$(STR1)\' != \'$(STR1)\'
+$(error )
+endif
+
+if "$(STR1)" != "$(STR1)"
+$(error )
+endif
+
+#
+# Now for the kmk 0.1.4 bug...
+#
+if $(STR1) != "$(STR1)"
+$(error )
+endif
+
+if "$(STR1)" != $(STR1)
+$(error )
+endif
+
+if $(STR1) != $(STR1)
+$(error )
+endif
+
+#
+# And some extra for good measure.
+#
+STR2 = STR
+NUM1 = 1
+
+if $($(STR2)$(NUM1)) != "abcd"
+$(error )
+endif
+
+if "abcd" != $($(STR2)$(NUM1))
+$(error )
+endif
+
+if "abcd" != $(${STR2}$(NUM1))
+$(error )
+endif
+
+if "abcd" != ${$(STR2)$(NUM1)}
+$(error )
+endif
+
+if "abcd" != ${${STR2}${NUM1}}
+$(error )
+endif
+
+if ${${STR2}${NUM1}} != \'abcd\'
+$(error )
+endif
+
+if "${${STR2}${NUM1}}" != \'abcd\'
+$(error )
+endif
+
+
+',
+'',
+'');
+
+
+
+# Indicate that we're done.
+1;
+
+
diff --git a/src/kmk/tests/scripts/features/include b/src/kmk/tests/scripts/features/include
new file mode 100644
index 0000000..f78563f
--- /dev/null
+++ b/src/kmk/tests/scripts/features/include
@@ -0,0 +1,243 @@
+# -*-mode: perl; rm-trailing-spaces: nil-*-
+
+$description = "Test various forms of the GNU make 'include' command.";
+
+$details = "\
+Test include, -include, sinclude and various regressions involving them.
+Test extra whitespace at the end of the include, multiple -includes and
+sincludes (should not give an error) and make sure that errors are reported
+for targets that were also -included.";
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+
+# The contents of the Makefile ...
+
+print MAKEFILE <<EOF;
+\#Extra space at the end of the following file name
+include $makefile2
+all: ; \@echo There should be no errors for this makefile.
+
+-include nonexistent.mk
+-include nonexistent.mk
+sinclude nonexistent.mk
+sinclude nonexistent-2.mk
+-include makeit.mk
+sinclude makeit.mk
+
+error: makeit.mk
+EOF
+
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+
+print MAKEFILE "ANOTHER: ; \@echo This is another included makefile\n";
+
+close(MAKEFILE);
+
+# Create the answer to what should be produced by this Makefile
+&run_make_with_options($makefile, "all", &get_logfile);
+$answer = "There should be no errors for this makefile.\n";
+&compare_output($answer, &get_logfile(1));
+
+&run_make_with_options($makefile, "ANOTHER", &get_logfile);
+$answer = "This is another included makefile\n";
+&compare_output($answer, &get_logfile(1));
+
+$makefile = undef;
+
+# Try to build the "error" target; this will fail since we don't know
+# how to create makeit.mk, but we should also get a message (even though
+# the -include suppressed it during the makefile read phase, we should
+# see one during the makefile run phase).
+
+run_make_test
+ ('
+-include foo.mk
+error: foo.mk ; @echo $@
+',
+ '',
+ "#MAKE#: *** No rule to make target 'foo.mk', needed by 'error'. Stop.\n",
+ 512
+ );
+
+# Make sure that target-specific variables don't impact things. This could
+# happen because a file record is created when a target-specific variable is
+# set.
+
+run_make_test
+ ('
+bar.mk: foo := baz
+-include bar.mk
+hello: ; @echo hello
+',
+ '',
+ "hello\n"
+ );
+
+
+# Test inheritance of dontcare flag when rebuilding makefiles.
+#
+run_make_test('
+.PHONY: all
+all: ; @:
+
+-include foo
+
+foo: bar; @:
+', '', '');
+
+
+# Make sure that we don't die when the command fails but we dontcare.
+# (Savannah bug #13216).
+#
+run_make_test('
+.PHONY: all
+all:; @:
+
+-include foo
+
+foo: bar; @:
+
+bar:; @exit 1
+', '', '');
+
+# Check include, sinclude, -include with no filenames.
+# (Savannah bug #1761).
+
+run_make_test('
+.PHONY: all
+all:; @:
+include
+-include
+sinclude', '', '');
+
+
+# Test that the diagnostics is issued even if the target has been
+# tried before with the dontcare flag (direct dependency case).
+#
+run_make_test('
+-include foo
+
+all: bar
+
+foo: baz
+bar: baz
+',
+'',
+"#MAKE#: *** No rule to make target 'baz', needed by 'bar'. Stop.\n",
+512);
+
+# Test that the diagnostics is issued even if the target has been
+# tried before with the dontcare flag (indirect dependency case).
+#
+run_make_test('
+-include foo
+
+all: bar
+
+foo: baz
+bar: baz
+baz: end
+',
+'',
+"#MAKE#: *** No rule to make target 'end', needed by 'baz'. Stop.\n",
+512);
+
+# Test that the diagnostics is issued even if the target has been
+# tried before with the dontcare flag (include/-include case).
+#
+run_make_test('
+include bar
+-include foo
+
+all:
+
+foo: baz
+bar: baz
+baz: end
+',
+'',
+"#MAKEFILE#:2: bar: No such file or directory
+#MAKE#: *** No rule to make target 'end', needed by 'baz'. Stop.\n",
+512);
+
+# Test include of make-able file doesn't show an error (Savannah #102)
+run_make_test(q!
+.PHONY: default
+default:; @echo DONE
+
+inc1:; echo > $@
+include inc1
+include inc2
+inc2:; echo > $@
+!,
+ '', "echo > inc2\necho > inc1\nDONE\n");
+
+rmfiles('inc1', 'inc2');
+
+# Test include of non-make-able file does show an error (Savannah #102)
+run_make_test(q!
+.PHONY: default
+default:; @echo DONE
+
+inc1:; echo > $@
+include inc1
+include inc2
+!,
+ '', "#MAKEFILE#:7: inc2: No such file or directory\n#MAKE#: *** No rule to make target 'inc2'. Stop.\n", 512);
+
+rmfiles('inc1');
+
+# Include same file multiple times
+
+run_make_test(q!
+default:; @echo DEFAULT
+include inc1
+inc1:; echo > $@
+include inc1
+!,
+ '', "echo > inc1\nDEFAULT\n");
+
+rmfiles('inc1');
+
+# Included file has a prerequisite that fails to build
+
+run_make_test(q!
+default:; @echo DEFAULT
+include inc1
+inc1: foo; echo > $@
+foo:; exit 1
+!,
+ '', "exit 1\n#MAKEFILE#:3: inc1: No such file or directory\n#MAKE#: *** [#MAKEFILE#:5: foo] Error 1\n", 512);
+
+rmfiles('inc1');
+
+# Included file has a prerequisite we don't know how to build
+
+run_make_test(q!
+default:; @echo DEFAULT
+include inc1
+inc1: foo; echo > $@
+!,
+ '', "#MAKEFILE#:3: inc1: No such file or directory\n#MAKE#: *** No rule to make target 'foo', needed by 'inc1'. Stop.\n", 512);
+
+rmfiles('inc1');
+
+# include a directory
+
+if ($all_tests) {
+ # Test that include of a rebuild-able file doesn't show a warning
+ # Savannah bug #102
+ run_make_test(q!
+include foo
+foo: ; @echo foo = bar > $@
+!,
+ '', "#MAKE#: 'foo' is up to date.\n");
+ rmfiles('foo');
+}
+
+1;
diff --git a/src/kmk/tests/scripts/features/jobserver b/src/kmk/tests/scripts/features/jobserver
new file mode 100644
index 0000000..7da4a65
--- /dev/null
+++ b/src/kmk/tests/scripts/features/jobserver
@@ -0,0 +1,107 @@
+# -*-perl-*-
+
+$description = "Test jobserver.";
+
+$details = "These tests are ones that specifically are different when the
+jobserver feature is available. Most -j tests are the same whether or not
+jobserver is available, and those appear in the 'parallelism' test suite.";
+
+exists $FEATURES{'jobserver'} or return -1;
+
+if (!$parallel_jobs) {
+ return -1;
+}
+
+# Shorthand
+my $np = '--no-print-directory';
+
+# Simple test of MAKEFLAGS settings
+run_make_test(q!
+SHOW = $(patsubst --jobserver-auth=%,--jobserver-auth=<auth>,$(MAKEFLAGS))
+recurse: ; @echo $@: "/$(SHOW)/"; $(MAKE) -f #MAKEFILE# all
+all:;@echo $@: "/$(SHOW)/"
+!,
+ "-j2 $np", "recurse: /-j2 --jobserver-auth=<auth> $np/\nall: /-j2 --jobserver-auth=<auth> $np/\n");
+
+# Setting parallelism with the environment
+# Command line should take precedence over the environment
+$extraENV{MAKEFLAGS} = "-j2 $np";
+run_make_test(q!
+SHOW = $(patsubst --jobserver-auth=%,--jobserver-auth=<auth>,$(MAKEFLAGS))
+recurse: ; @echo $@: "/$(SHOW)/"; $(MAKE) -f #MAKEFILE# all
+all:;@echo $@: "/$(SHOW)/"
+!,
+ '', "recurse: /-j2 --jobserver-auth=<auth> $np/\nall: /-j2 --jobserver-auth=<auth> $np/\n");
+delete $extraENV{MAKEFLAGS};
+
+# Test override of -jN
+$extraENV{MAKEFLAGS} = "-j9 $np";
+run_make_test(q!
+SHOW = $(patsubst --jobserver-auth=%,--jobserver-auth=<auth>,$(MAKEFLAGS))
+recurse: ; @echo $@: "/$(SHOW)/"; $(MAKE) -j3 -f #MAKEFILE# recurse2
+recurse2: ; @echo $@: "/$(SHOW)/"; $(MAKE) -f #MAKEFILE# all
+all:;@echo $@: "/$(SHOW)/"
+!,
+ "-j2 $np", "recurse: /-j2 --jobserver-auth=<auth> $np/\n#MAKE#[1]: warning: -jN forced in submake: disabling jobserver mode.\nrecurse2: /-j3 --jobserver-auth=<auth> $np/\nall: /-j3 --jobserver-auth=<auth> $np/\n");
+delete $extraENV{MAKEFLAGS};
+
+# Test override of -jN with -j
+run_make_test(q!
+SHOW = $(patsubst --jobserver-auth=%,--jobserver-auth=<auth>,$(MAKEFLAGS))
+recurse: ; @echo $@: "/$(SHOW)/"; $(MAKE) -j -f #MAKEFILE# recurse2
+recurse2: ; @echo $@: "/$(SHOW)/"; $(MAKE) -f #MAKEFILE# all
+all:;@echo $@: "/$(SHOW)/"
+!,
+ "-j2 $np", "recurse: /-j2 --jobserver-auth=<auth> $np/\n#MAKE#[1]: warning: -jN forced in submake: disabling jobserver mode.\nrecurse2: /-j $np/\nall: /-j $np/\n");
+
+# Don't put --jobserver-auth into a re-exec'd MAKEFLAGS.
+# We can't test this directly because there's no way a makefile can
+# show the value of MAKEFLAGS we were re-exec'd with. We can intuit it
+# by looking for "disabling jobserver mode" warnings; we should only
+# get one from the original invocation and none from the re-exec.
+# See Savannah bug #18124
+
+unlink('inc.mk');
+
+run_make_test(q!
+-include inc.mk
+recur:
+# @echo 'MAKEFLAGS = $(MAKEFLAGS)'
+ @rm -f inc.mk
+ @$(MAKE) -j2 -f #MAKEFILE# all
+all:
+# @echo 'MAKEFLAGS = $(MAKEFLAGS)'
+ @echo $@
+inc.mk:
+# @echo 'MAKEFLAGS = $(MAKEFLAGS)'
+ @echo 'FOO = bar' > $@
+!,
+ "$np -j2", "#MAKE#[1]: warning: -jN forced in submake: disabling jobserver mode.\nall\n");
+
+unlink('inc.mk');
+
+# Test recursion when make doesn't think it exists.
+# See Savannah bug #39934
+# Or Red Hat bug https://bugzilla.redhat.com/show_bug.cgi?id=885474
+
+open(MAKEFILE,"> Makefile2");
+print MAKEFILE '
+vpath %.c ../
+foo:
+';
+close(MAKEFILE);
+
+run_make_test(q!
+default: ; @ #MAKEPATH# -f Makefile2
+!,
+ "-j2 $np",
+"#MAKE#[1]: warning: jobserver unavailable: using -j1. Add '+' to parent make rule.
+#MAKE#[1]: Nothing to be done for 'foo'.");
+
+rmfiles('Makefile2');
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/load b/src/kmk/tests/scripts/features/load
new file mode 100644
index 0000000..2e9318d
--- /dev/null
+++ b/src/kmk/tests/scripts/features/load
@@ -0,0 +1,110 @@
+# -*-perl-*-
+$description = "Test the load operator.";
+
+$details = "Test dynamic loading of modules.";
+
+# Don't do anything if this system doesn't support "load"
+exists $FEATURES{load} or return -1;
+
+# First build a shared object
+# Provide both a default and non-default load symbol
+
+unlink(qw(testload.c testload.so));
+
+open(my $F, '> testload.c') or die "open: testload.c: $!\n";
+print $F <<'EOF' ;
+#include <string.h>
+#include <stdio.h>
+
+#include "gnumake.h"
+
+int plugin_is_GPL_compatible;
+
+int
+testload_gmk_setup (gmk_floc *pos)
+{
+ (void)pos;
+ gmk_eval ("TESTLOAD = implicit", 0);
+ return 1;
+}
+
+int
+explicit_setup (gmk_floc *pos)
+{
+ (void)pos;
+ gmk_eval ("TESTLOAD = explicit", 0);
+ return 1;
+}
+EOF
+close($F) or die "close: testload.c: $!\n";
+
+# Make sure we can compile
+# CONFIG_FLAGS are loaded from config-flags.pm and set by configure
+
+my $sobuild = "$CONFIG_FLAGS{CC} ".($srcdir? "-I$srcdir":'')." $CONFIG_FLAGS{CPPFLAGS} $CONFIG_FLAGS{CFLAGS} -shared -fPIC $CONFIG_FLAGS{LDFLAGS} -o testload.so testload.c";
+
+my $clog = `$sobuild 2>&1`;
+if ($? != 0) {
+ $verbose and print "Failed to build testload.so:\n$sobuild\n$_";
+ return -1;
+}
+
+# TEST 1
+run_make_test(q!
+PRE := $(.LOADED)
+load testload.so
+POST := $(.LOADED)
+all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD)
+!,
+ '--warn-undefined-variables', "pre= post=testload.so implicit\n");
+
+# TEST 2
+# Load using an explicit function
+run_make_test(q!
+PRE := $(.LOADED)
+load ./testload.so(explicit_setup)
+POST := $(.LOADED)
+all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD)
+!,
+ '', "pre= post=testload.so explicit\n");
+
+# TEST 4
+# Check multiple loads
+run_make_test(q!
+PRE := $(.LOADED)
+load ./testload.so
+load testload.so(explicit_setup)
+POST := $(.LOADED)
+all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD)
+!,
+ '', "pre= post=testload.so implicit\n");
+
+# TEST 5
+# Check auto-rebuild of loaded file that's out of date
+utouch(-10, 'testload.so');
+touch('testload.c');
+
+run_make_test(q!
+PRE := $(.LOADED)
+load ./testload.so
+POST := $(.LOADED)
+all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD)
+testload.so: testload.c ; @echo "rebuilding $@"; !.$sobuild,
+ '', "rebuilding testload.so\npre= post=testload.so implicit\n");
+
+# TEST 5
+# Check auto-rebuild of loaded file when it doesn't exist
+unlink('testload.so');
+
+run_make_test(q!
+PRE := $(.LOADED)
+-load ./testload.so(explicit_setup)
+POST := $(.LOADED)
+all: ; @echo pre=$(PRE) post=$(POST) $(TESTLOAD)
+%.so: %.c ; @echo "rebuilding $@"; !.$sobuild,
+ '', "rebuilding testload.so\npre= post=testload.so explicit\n");
+
+unlink(qw(testload.c testload.so)) unless $keep;
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/loadapi b/src/kmk/tests/scripts/features/loadapi
new file mode 100644
index 0000000..8c824c0
--- /dev/null
+++ b/src/kmk/tests/scripts/features/loadapi
@@ -0,0 +1,116 @@
+# -*-perl-*-
+$description = "Test the shared object load API.";
+
+$details = "Verify the different aspects of the shared object API.";
+
+# Don't do anything if this system doesn't support "load"
+exists $FEATURES{load} or return -1;
+
+# First build a shared object
+# Provide both a default and non-default load symbol
+
+unlink(qw(testapi.c testapi.so));
+
+open(my $F, '> testapi.c') or die "open: testapi.c: $!\n";
+print $F <<'EOF' ;
+#include <string.h>
+#include <stdio.h>
+
+#include "gnumake.h"
+
+int plugin_is_GPL_compatible;
+
+static char *
+test_eval (const char *buf)
+{
+ gmk_eval (buf, 0);
+ return NULL;
+}
+
+static char *
+test_expand (const char *val)
+{
+ return gmk_expand (val);
+}
+
+static char *
+test_noexpand (const char *val)
+{
+ char *str = gmk_alloc (strlen (val) + 1);
+ strcpy (str, val);
+ return str;
+}
+
+static char *
+func_test (const char *funcname, unsigned int argc, char **argv)
+{
+ char *mem;
+
+ if (strcmp (funcname, "test-expand") == 0)
+ return test_expand (argv[0]);
+
+ if (strcmp (funcname, "test-eval") == 0)
+ return test_eval (argv[0]);
+
+ if (strcmp (funcname, "test-noexpand") == 0)
+ return test_noexpand (argv[0]);
+
+ mem = gmk_alloc (sizeof ("unknown"));
+ strcpy (mem, "unknown");
+ return mem;
+}
+
+int
+testapi_gmk_setup ()
+{
+ gmk_add_function ("test-expand", func_test, 1, 1, GMK_FUNC_DEFAULT);
+ gmk_add_function ("test-noexpand", func_test, 1, 1, GMK_FUNC_NOEXPAND);
+ gmk_add_function ("test-eval", func_test, 1, 1, GMK_FUNC_DEFAULT);
+ gmk_add_function ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.", func_test, 0, 0, 0);
+ return 1;
+}
+EOF
+close($F) or die "close: testapi.c: $!\n";
+
+my $sobuild = "$CONFIG_FLAGS{CC} ".($srcdir? "-I$srcdir":'')." $CONFIG_FLAGS{CPPFLAGS} $CONFIG_FLAGS{CFLAGS} -shared -fPIC $CONFIG_FLAGS{LDFLAGS} -o testapi.so testapi.c";
+
+my $clog = `$sobuild 2>&1`;
+if ($? != 0) {
+ $verbose and print "Failed to build testapi.so:\n$sobuild\n$_";
+ return -1;
+}
+
+# TEST 1
+# Check the gmk_expand() function
+run_make_test(q!
+EXPAND = expansion
+all: ; @echo $(test-expand $$(EXPAND))
+load testapi.so
+!,
+ '', "expansion\n");
+
+# TEST 2
+# Check the eval operation. Prove that the argument is expanded only once
+run_make_test(q!
+load testapi.so
+TEST = bye
+ASSIGN = VAR = $(TEST) $(shell echo there)
+$(test-eval $(value ASSIGN))
+TEST = hi
+all:;@echo '$(VAR)'
+!,
+ '', "hi there\n");
+
+# TEST 2
+# Check the no-expand capability
+run_make_test(q!
+load testapi.so
+TEST = hi
+all:;@echo '$(test-noexpand $(TEST))'
+!,
+ '', "\$(TEST)\n");
+
+unlink(qw(testapi.c testapi.so)) unless $keep;
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/mult_rules b/src/kmk/tests/scripts/features/mult_rules
new file mode 100644
index 0000000..e706e17
--- /dev/null
+++ b/src/kmk/tests/scripts/features/mult_rules
@@ -0,0 +1,78 @@
+$description = "\
+The following test creates a makefile to test the presence
+of multiple rules for one target. One file can be the
+target of several rules if at most one rule has commands;
+the other rules can only have dependencies.";
+
+$details = "\
+The makefile created in this test contains two hardcoded rules
+for foo.o and bar.o. It then gives another multiple target rule
+with the same names as above but adding more dependencies.
+Additionally, another variable extradeps is listed as a
+dependency but is defined to be null. It can however be defined
+on the make command line as extradeps=extra.h which adds yet
+another dependency to the targets.";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<EOF;
+objects = foo.o bar.o
+foo.o : defs.h
+bar.o : defs.h test.h
+extradeps =
+\$(objects) : config.h \$(extradeps)
+\t\@echo EXTRA EXTRA
+EOF
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch("defs.h","test.h","config.h");
+
+if ($vos)
+{
+ $error_code = 3307;
+}
+else
+{
+ $error_code = 512;
+}
+
+&run_make_with_options($makefile,
+ "extradeps=extra.h",
+ &get_logfile,
+ $error_code);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "$make_name: *** No rule to make target 'extra.h', needed by 'foo.o'. Stop.\n";
+
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #2
+# -------
+
+&touch("extra.h");
+
+&run_make_with_options($makefile,
+ "extradeps=extra.h",
+ &get_logfile,
+ 0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "EXTRA EXTRA\n";
+
+&compare_output($answer,&get_logfile(1));
+
+unlink("defs.h","test.h","config.h","extra.h");
+
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/features/mult_targets b/src/kmk/tests/scripts/features/mult_targets
new file mode 100644
index 0000000..c8ff418
--- /dev/null
+++ b/src/kmk/tests/scripts/features/mult_targets
@@ -0,0 +1,46 @@
+$description = "The following test creates a makefile to test that a \n "
+ ."rule with multiple targets is equivalent to writing \n"
+ ."many rules, each with one target, and all identical aside\n"
+ ."from that.";
+
+$details = "A makefile is created with one rule and two targets. Make \n"
+ ."is called twice, once for each target, and the output which \n"
+ ."contains the target name with \$@ is looked at for the changes.\n"
+ ."This test also tests the substitute function by replacing \n"
+ ."the word output with nothing in the target name giving either\n"
+ ."an output of \"I am little\" or \"I am big\"";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "bigoutput littleoutput: test.h\n";
+print MAKEFILE "\t\@echo I am \$(subst output,,\$@)\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch("test.h");
+
+&run_make_with_options($makefile,"bigoutput",&get_logfile);
+
+
+# Create the answer to what should be produced by this Makefile
+$answer = "I am big\n";
+
+&compare_output($answer,&get_logfile(1));
+
+&run_make_with_options($makefile,"littleoutput",&get_logfile);
+$answer = "I am little\n";
+&compare_output($answer,&get_logfile(1));
+
+unlink "test.h";
+
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/features/order_only b/src/kmk/tests/scripts/features/order_only
new file mode 100644
index 0000000..4ebdc2b
--- /dev/null
+++ b/src/kmk/tests/scripts/features/order_only
@@ -0,0 +1,118 @@
+# -*-perl-*-
+$description = "Test order-only prerequisites.";
+
+$details = "\
+Create makefiles with various combinations of normal and order-only
+prerequisites and ensure they behave properly. Test the \$| variable.";
+
+# TEST #0 -- Basics
+
+run_make_test('
+%r: | baz ; @echo $< $^ $|
+bar: foo
+foo:;@:
+baz:;@:',
+ '', "foo foo baz\n");
+
+# TEST #1 -- First try: the order-only prereqs need to be built.
+
+run_make_test(q!
+foo: bar | baz
+ @echo '$$^ = $^'
+ @echo '$$| = $|'
+ touch $@
+
+.PHONY: baz
+
+bar baz:
+ touch $@!,
+ '', "touch bar\ntouch baz\n\$^ = bar\n\$| = baz\ntouch foo\n");
+
+
+# TEST #2 -- now we do it again: baz is PHONY but foo should _NOT_ be updated
+
+run_make_test(undef, '', "touch baz\n");
+
+unlink(qw(foo bar baz));
+
+# TEST #3 -- Make sure the order-only prereq was promoted to normal.
+
+run_make_test(q!
+foo: bar | baz
+ @echo '$$^ = $^'
+ @echo '$$| = $|'
+ touch $@
+
+foo: baz
+
+.PHONY: baz
+
+bar baz:
+ touch $@!,
+ '', "touch bar\ntouch baz\n\$^ = bar baz\n\$| = \ntouch foo\n");
+
+
+# TEST #4 -- now we do it again
+
+run_make_test(undef, '', "touch baz\n\$^ = bar baz\n\$| = \ntouch foo\n");
+
+unlink(qw(foo bar baz));
+
+# Test empty normal prereqs
+
+# TEST #5 -- make sure the parser was correct.
+
+run_make_test(q!
+foo:| baz
+ @echo '$$^ = $^'
+ @echo '$$| = $|'
+ touch $@
+
+.PHONY: baz
+
+baz:
+ touch $@!,
+ '', "touch baz\n\$^ = \n\$| = baz\ntouch foo\n");
+
+# TEST #6 -- now we do it again: this time foo won't be built
+
+run_make_test(undef, '', "touch baz\n");
+
+unlink(qw(foo baz));
+
+# Test order-only in pattern rules
+
+# TEST #7 -- make sure the parser was correct.
+
+run_make_test(q!
+%.w : %.x | baz
+ @echo '$$^ = $^'
+ @echo '$$| = $|'
+ touch $@
+
+all: foo.w
+
+.PHONY: baz
+foo.x baz:
+ touch $@!,
+ '',
+ "touch foo.x\ntouch baz\n\$^ = foo.x\n\$| = baz\ntouch foo.w\n");
+
+# TEST #8 -- now we do it again: this time foo.w won't be built
+
+run_make_test(undef, '', "touch baz\n");
+
+unlink(qw(foo.w foo.x baz));
+
+# TEST #9 -- make sure that $< is set correctly in the face of order-only
+# prerequisites in pattern rules.
+
+run_make_test('
+%r: | baz ; @echo $< $^ $|
+bar: foo
+foo:;@:
+baz:;@:',
+ '', "foo foo baz\n");
+
+
+1;
diff --git a/src/kmk/tests/scripts/features/output-sync b/src/kmk/tests/scripts/features/output-sync
new file mode 100644
index 0000000..7237e65
--- /dev/null
+++ b/src/kmk/tests/scripts/features/output-sync
@@ -0,0 +1,349 @@
+# -*-perl-*-
+
+$description = "Test --output-sync (-O) option.";
+
+$details = "Test the synchronization of output from parallel jobs.";
+
+# If we don't have output sync support, never mind.
+exists $FEATURES{'output-sync'} or return -1;
+
+# Output sync can't be tested without parallelization
+$parallel_jobs or return -1;
+
+
+if ($vos) {
+ $sleep_command = "sleep -seconds";
+}
+else {
+ $sleep_command = "sleep";
+}
+
+# The following subdirectories with Makefiles are used in several
+# of the following tests. The model is:
+# foo/Makefile - has a "foo" target that waits for the bar target
+# bar/Makefile - has a "bar" target that runs immediately
+# - has a "baz" target that waits for the foo target
+#
+# So, you start the two sub-makes in parallel and first the "bar" target is
+# built, followed by "foo", followed by "baz". The trick is that first each
+# target prints a "start" statement, then waits (if appropriate), then prints
+# an end statement. Thus we can tell if the -O flag is working, since
+# otherwise these statements would be mixed together.
+
+@syncfiles = ();
+
+sub output_sync_clean {
+ rmfiles('foo/Makefile', 'bar/Makefile', @syncfiles);
+ rmdir('foo');
+ rmdir('bar');
+}
+
+# We synchronize the different jobs by having them wait for a sentinel file to
+# be created, instead of relying on a certain amount of time passing.
+# Unfortunately in this test we have to sleep after we see the sync file,
+# since we also want to make the obtaining of the write synchronization lock
+# reliable. If things are too fast, then sometimes a different job will steal
+# the output sync lock and the output is mis-ordered from what we expect.
+sub output_sync_wait {
+ return "while [ ! -f ../mksync.$_[0] ]; do :; done; rm -f ../mksync.$_[0].wait; $sleep_command 1";
+}
+sub output_sync_set {
+ return "date > ../mksync.$_[0]";
+}
+
+@syncfiles = qw(mksync.foo mksync.foo_start mksync.bar mksync.bar_start);
+
+$tmout = 30;
+
+output_sync_clean();
+mkdir('foo', 0777);
+mkdir('bar', 0777);
+
+$set_foo = output_sync_set('foo');
+$set_bar = output_sync_set('bar');
+$set_foo_start = output_sync_set('foo_start');
+$set_bar_start = output_sync_set('bar_start');
+
+$wait_foo = output_sync_wait('foo');
+$wait_bar = output_sync_wait('bar');
+$wait_foo_start = output_sync_set('foo_start');
+$wait_bar_start = output_sync_set('bar_start');
+
+open(MAKEFILE,"> foo/Makefile");
+print MAKEFILE <<EOF;
+all: foo
+
+foo: foo-base ; \@$set_foo
+
+foo-base:
+\t\@echo foo: start
+\t\@$wait_bar
+\t\@echo foo: end
+
+foo-job: foo-job-base ; \@$set_foo
+
+foo-job-base:
+\t\@$wait_bar_start
+\t\@echo foo: start
+\t\@$set_foo_start
+\t\@$wait_bar
+\t\@echo foo: end
+
+foo-fail:
+\t\@echo foo-fail: start
+\t\@$wait_bar
+\t\@echo foo-fail: end
+\t\@exit 1
+EOF
+close(MAKEFILE);
+
+open(MAKEFILE,"> bar/Makefile");
+print MAKEFILE <<EOF;
+all: bar baz
+
+bar: bar-base ; \@$set_bar
+bar-base:
+\t\@echo bar: start
+\t\@echo bar: end
+
+bar-job: bar-job-base ; \@$set_bar
+
+bar-job-base:
+\t\@echo bar: start
+\t\@$set_bar_start
+\t\@$wait_foo_start
+\t\@echo bar: end
+
+baz: baz-base
+baz-base:
+\t\@echo baz: start
+\t\@$wait_foo
+\t\@echo baz: end
+EOF
+close(MAKEFILE);
+
+# Test per-make synchronization.
+unlink(@syncfiles);
+run_make_test(qq!
+all: make-foo make-bar
+
+make-foo: ; \$(MAKE) -C foo
+
+make-bar: ; \$(MAKE) -C bar!,
+ '-j -Orecurse',
+"#MAKEPATH# -C foo
+#MAKE#[1]: Entering directory '#PWD#/foo'
+foo: start
+foo: end
+#MAKE#[1]: Leaving directory '#PWD#/foo'
+#MAKEPATH# -C bar
+#MAKE#[1]: Entering directory '#PWD#/bar'
+bar: start
+bar: end
+baz: start
+baz: end
+#MAKE#[1]: Leaving directory '#PWD#/bar'\n", 0, $tmout);
+
+# Test per-target synchronization.
+# Note we have to sleep again here after starting the foo makefile before
+# starting the bar makefile, otherwise the "entering/leaving" messages for the
+# submakes might be ordered differently than we expect.
+
+unlink(@syncfiles);
+run_make_test(qq!
+x=1
+\$xMAKEFLAGS += --no-print-directory
+
+all: make-foo make-bar
+
+make-foo: ; \$(MAKE) -C foo
+
+make-bar: ; $sleep_command 1 ; \$(MAKE) -C bar!,
+ '-j --output-sync=target',
+"#MAKEPATH# -C foo
+$sleep_command 1 ; #MAKEPATH# -C bar
+#MAKE#[1]: Entering directory '#PWD#/bar'
+bar: start
+bar: end
+#MAKE#[1]: Leaving directory '#PWD#/bar'
+#MAKE#[1]: Entering directory '#PWD#/foo'
+foo: start
+foo: end
+#MAKE#[1]: Leaving directory '#PWD#/foo'
+#MAKE#[1]: Entering directory '#PWD#/bar'
+baz: start
+baz: end
+#MAKE#[1]: Leaving directory '#PWD#/bar'\n", 0, $tmout);
+
+# Rerun but this time suppress the directory tracking
+unlink(@syncfiles);
+run_make_test(undef, '-j --output-sync=target x=',
+ "#MAKEPATH# -C foo
+$sleep_command 1 ; #MAKEPATH# -C bar
+bar: start
+bar: end
+foo: start
+foo: end
+baz: start
+baz: end\n", 0, $tmout);
+
+# Test that messages from make itself are enclosed with
+# "Entering/Leaving directory" messages.
+unlink(@syncfiles);
+run_make_test(qq!
+all: make-foo-fail make-bar-bar
+
+make-foo-fail: ; \$(MAKE) -C foo foo-fail
+
+make-bar-bar: ; $sleep_command 1 ; \$(MAKE) -C bar bar!,
+ '-j -O',
+"#MAKEPATH# -C foo foo-fail
+$sleep_command 1 ; #MAKEPATH# -C bar bar
+#MAKE#[1]: Entering directory '#PWD#/bar'
+bar: start
+bar: end
+#MAKE#[1]: Leaving directory '#PWD#/bar'
+#MAKE#[1]: Entering directory '#PWD#/foo'
+foo-fail: start
+foo-fail: end
+#MAKE#[1]: *** [Makefile:23: foo-fail] Error 1
+#MAKE#[1]: Leaving directory '#PWD#/foo'
+#MAKE#: *** [#MAKEFILE#:4: make-foo-fail] Error 2\n",
+512);
+
+# Test the per-job synchronization.
+# For this we'll have bar-job:
+# print start, invoke bar-start, wait for foo-start, print end, print-bar-end
+# And foo-job:
+# wait for bar-start, print foo-start, wait for bar-end, print end
+
+unlink(@syncfiles);
+run_make_test(qq!
+all: make-foo make-bar
+
+make-foo: ; \$(MAKE) -C foo foo-job
+
+make-bar: ; $sleep_command 1 ; \$(MAKE) -C bar bar-job!,
+ '-j --output-sync=line',
+"#MAKEPATH# -C foo foo-job
+$sleep_command 1 ; #MAKEPATH# -C bar bar-job
+#MAKE#[1]: Entering directory '#PWD#/foo'
+foo: start
+#MAKE#[1]: Leaving directory '#PWD#/foo'
+#MAKE#[1]: Entering directory '#PWD#/bar'
+bar: start
+#MAKE#[1]: Leaving directory '#PWD#/bar'
+#MAKE#[1]: Entering directory '#PWD#/bar'
+bar: end
+#MAKE#[1]: Leaving directory '#PWD#/bar'
+#MAKE#[1]: Entering directory '#PWD#/foo'
+foo: end
+#MAKE#[1]: Leaving directory '#PWD#/foo'\n", 0, $tmout);
+
+
+# Remove temporary directories and contents.
+output_sync_clean();
+
+# Ensure recursion doesn't mis-order or double-print output
+run_make_test(qq!
+all:
+\t\@echo foo
+\t\@+echo bar
+!,
+ '-j -Oline', "foo\nbar\n");
+
+run_make_test(undef, '-j -Otarget', "foo\nbar\n");
+
+# Ensure when make writes out command it's not misordered
+run_make_test(qq!
+all:
+\t\@echo foobar
+\ttrue
+!,
+ '-j -Oline', "foobar\ntrue\n");
+
+run_make_test(undef, '-j -Otarget', "foobar\ntrue\n");
+
+# Ensure that shell functions inside recipes write stderr to the sync file
+run_make_test(q!
+all: ; @: $(shell echo foo 1>&2)
+!,
+ '-w -Oline', "#MAKE#: Entering directory '#PWD#'\nfoo\n#MAKE#: Leaving directory '#PWD#'\n");
+
+# Ensure that output generated while parsing makefiles is synced
+# when appropriate.
+run_make_test(q!
+$(shell echo foo 1>&2)
+all: ; echo bar
+!,
+ '-s -w -Otarget', "#MAKE#: Entering directory '#PWD#'\nfoo\n#MAKE#: Leaving directory '#PWD#'\n#MAKE#: Entering directory '#PWD#'\nbar\n#MAKE#: Leaving directory '#PWD#'\n");
+
+# Test recursion
+$m1 = get_tmpfile();
+$m2 = get_tmpfile();
+
+open(M1, "> $m1");
+print M1 <<'EOF';
+$(shell echo d1 stderr 1>&2)
+$(info d1 stdout)
+all:; @:
+EOF
+close(M1);
+
+open(M2, "> $m2");
+print M2 <<'EOF';
+$(shell echo d2 stderr 1>&2)
+$(info d2 stdout)
+all:; @:
+# Force an ordering on the output
+$(shell sleep 1)
+EOF
+close(M2);
+
+run_make_test(qq!
+all: t1 t2
+t1: ; \@\$(MAKE) -f $m1
+t2: ; \@\$(MAKE) -f $m2
+!,
+ "-j -Oline", "#MAKE#[1]: Entering directory '#PWD#'\nd1 stderr\nd1 stdout\n#MAKE#[1]: Leaving directory '#PWD#'\n#MAKE#[1]: Entering directory '#PWD#'\nd2 stderr\nd2 stdout\n#MAKE#[1]: Leaving directory '#PWD#'\n");
+
+rmfiles($m1, $m2);
+
+# Ensure that output generated while parsing makefiles is synced
+# when appropriate.
+$m1 = get_tmpfile();
+
+open(M1, "> $m1");
+print M1 <<'EOF';
+$(shell echo d1 stderr 1>&2)
+$(info d1 stdout)
+$(error d1 failed)
+all:; @:
+EOF
+close(M1);
+
+run_make_test(qq!
+all: t1
+t1: ; -\@\$(MAKE) -f $m1
+!,
+ "-j -Oline", "#MAKE#[1]: Entering directory '#PWD#'\nd1 stderr\nd1 stdout\n$m1:3: *** d1 failed. Stop.\n#MAKE#[1]: Leaving directory '#PWD#'\n#MAKE#: [#MAKEFILE#:3: t1] Error 2 (ignored)\n");
+
+rmfiles($m1);
+
+# Test $(error ...) functions in recipes
+
+run_make_test(q!
+foo: $(OBJS) ; echo $(or $(filter %.o,$^),$(error fail))
+!,
+ '-O', "#MAKEFILE#:2: *** fail. Stop.\n", 512);
+
+# SV 47365: Make sure exec failure error messages are shown
+# Is "127" not always the same everywhere? We may have to detect it?
+
+run_make_test(q!
+all:: ; @./foo bar baz
+!,
+ '-O', "#MAKE#: ./foo: Command not found\n#MAKE#: *** [#MAKEFILE#:2: all] Error 127\n", 512);
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/override b/src/kmk/tests/scripts/features/override
new file mode 100644
index 0000000..fff6c4e
--- /dev/null
+++ b/src/kmk/tests/scripts/features/override
@@ -0,0 +1,45 @@
+# -*-perl-*-
+
+$description = "Test the override directive on variable assignments.";
+
+$details = "";
+
+# TEST 0: Basic override
+
+run_make_test('
+X = start
+override recur = $(X)
+override simple := $(X)
+X = end
+all: ; @echo "$(recur) $(simple)"
+',
+ 'recur=I simple=J', "end start\n");
+
+# TEST 1: Override with append
+
+run_make_test('
+X += X1
+override X += X2
+override Y += Y1
+Y += Y2
+all: ; @echo "$(X) $(Y)"
+',
+ '', "X1 X2 Y1\n");
+
+# TEST 2: Override with append to the command line
+
+run_make_test(undef, 'X=C Y=C', "C X2 C Y1\n");
+
+# Test override of define/endef
+
+run_make_test('
+override define foo
+@echo First comes the definition.
+@echo Then comes the override.
+endef
+all: ; $(foo)
+',
+ 'foo=Hello', "First comes the definition.\nThen comes the override.\n");
+
+
+1;
diff --git a/src/kmk/tests/scripts/features/parallelism b/src/kmk/tests/scripts/features/parallelism
new file mode 100644
index 0000000..ee3846d
--- /dev/null
+++ b/src/kmk/tests/scripts/features/parallelism
@@ -0,0 +1,231 @@
+# -*-perl-*-
+
+$description = "Test parallelism (-j) option.";
+
+
+$details = "This test creates a makefile with two double-colon default
+rules. The first rule has a series of sleep and echo commands
+intended to run in series. The second and third have just an
+echo statement. When make is called in this test, it is given
+the -j option with a value of 4. This tells make that it may
+start up to four jobs simultaneously. In this case, since the
+first command is a sleep command, the output of the second
+and third commands will appear before the first if indeed
+make is running all of these commands in parallel.";
+
+if (!$parallel_jobs) {
+ return -1;
+}
+
+if ($vos) {
+ $sleep_command = "sleep -seconds";
+}
+else {
+ $sleep_command = "sleep";
+}
+
+
+run_make_test("
+all : def_1 def_2 def_3
+def_1 : ; \@echo ONE; $sleep_command 3 ; echo TWO
+def_2 : ; \@$sleep_command 2 ; echo THREE
+def_3 : ; \@$sleep_command 1 ; echo FOUR",
+ '-j4', "ONE\nFOUR\nTHREE\nTWO");
+
+# Test parallelism with included files. Here we sleep/echo while
+# building the included files, to test that they are being built in
+# parallel.
+run_make_test("
+all: 1 2; \@echo success
+-include 1.inc 2.inc
+1.inc: ; \@echo ONE.inc; $sleep_command 2; echo TWO.inc; echo '1: ; \@echo ONE; $sleep_command 2; echo TWO' > \$\@
+2.inc: ; \@$sleep_command 1; echo THREE.inc; echo '2: ; \@$sleep_command 1; echo THREE' > \$\@",
+ "-j4",
+ "ONE.inc\nTHREE.inc\nTWO.inc\nONE\nTHREE\nTWO\nsuccess\n", 0, 7);
+
+rmfiles(qw(1.inc 2.inc));
+
+
+# Test parallelism with included files--this time recurse first and make
+# sure the jobserver works.
+run_make_test("
+recurse: ; \@\$(MAKE) --no-print-directory -f #MAKEFILE# INC=yes all
+all: 1 2; \@echo success
+
+INC = no
+ifeq (\$(INC),yes)
+-include 1.inc 2.inc
+endif
+
+1.inc: ; \@echo ONE.inc; $sleep_command 2; echo TWO.inc; echo '1: ; \@echo ONE; $sleep_command 2; echo TWO' > \$\@
+2.inc: ; \@$sleep_command 1; echo THREE.inc; echo '2: ; \@$sleep_command 1; echo THREE' > \$\@",
+ "-j4",
+ "ONE.inc\nTHREE.inc\nTWO.inc\nONE\nTHREE\nTWO\nsuccess\n", 0, 7);
+
+rmfiles(qw(1.inc 2.inc));
+
+# Grant Taylor reports a problem where tokens can be lost (not written back
+# to the pipe when they should be): this happened when there is a $(shell ...)
+# function in an exported recursive variable. I added some code to check
+# for this situation and print a message if it occurred. This test used
+# to trigger this code when I added it but no longer does after the fix.
+# We have to increase the timeout from the default (5s) on this test.
+
+run_make_test("
+export HI = \$(shell \$(\$\@.CMD))
+first.CMD = echo hi
+second.CMD = $sleep_command 4; echo hi
+
+.PHONY: all first second
+all: first second
+
+first second: ; \@echo \$\@; $sleep_command 1; echo \$\@",
+ '-j2', "first\nfirst\nsecond\nsecond", 0, 7);
+
+# Michael Matz <matz@suse.de> reported a bug where if make is running in
+# parallel without -k and two jobs die in a row, but not too close to each
+# other, then make will quit without waiting for the rest of the jobs to die.
+
+run_make_test("
+.PHONY: all fail.1 fail.2 fail.3 ok
+all: fail.1 ok fail.2 fail.3
+
+fail.1 fail.2 fail.3:
+ \@$sleep_command \$(patsubst fail.%,%,\$\@)
+ \@echo Fail
+ \@exit 1
+
+ok:
+ \@$sleep_command 4
+ \@echo Ok done",
+ '-rR -j5', (!$is_kmk) ? "Fail
+#MAKE#: *** [#MAKEFILE#:8: fail.1] Error 1
+#MAKE#: *** Waiting for unfinished jobs....
+Fail
+#MAKE#: *** [#MAKEFILE#:8: fail.2] Error 1
+Fail
+#MAKE#: *** [#MAKEFILE#:8: fail.3] Error 1
+Ok done" : 'Fail
+#MAKE#: *** [fail.1] Error 1
+The failing command:
+@exit 1
+#MAKE#: *** Waiting for unfinished jobs....
+Fail
+#MAKE#: *** [fail.2] Error 1
+The failing command:
+@exit 1
+Fail
+#MAKE#: *** [fail.3] Error 1
+The failing command:
+@exit 1
+Ok done
+#MAKE#: *** Exiting with status 2',
+ 512);
+
+
+# Test for Savannah bug #15641.
+#
+run_make_test('
+.PHONY: all
+all:; @:
+
+-include foo.d
+
+foo.d: comp
+ @echo building $@
+
+comp: mod_a.o mod_b.o; @:
+
+mod_a.o mod_b.o:
+ @exit 1
+', '-j2', '');
+
+
+# TEST #9 -- Savannah bugs 3330 and 15919
+# In earlier versions of make this will either give the wrong answer, or hang.
+
+utouch(-10, 'target');
+run_make_test('target: intermed ; touch $@
+
+.INTERMEDIATE: intermed
+intermed: | phony ; touch $@
+
+.PHONY: phony
+phony: ; : phony', '-rR -j', ': phony');
+rmfiles('target');
+
+# TEST #11: Make sure -jN from MAKEFLAGS is processed even when we re-exec
+# See Savannah bug #33873
+
+$extraENV{MAKEFLAGS} = '-j4';
+
+run_make_test(q!
+things = thing1 thing2
+all: $(things)
+thing1:; @sleep 1; echo '$@ start'; sleep 2; echo '$@ end'
+thing2:; @echo '$@ start'; sleep 2; echo '$@ end'
+-include inc.mk
+inc.mk: ; @touch $@
+!,
+ '', "thing2 start\nthing1 start\nthing2 end\nthing1 end\n");
+
+delete $extraENV{MAKEFLAGS};
+rmfiles('inc.mk');
+
+# Ensure intermediate/secondary files are not pruned incorrectly.
+# See Savannah bug #30653
+
+utouch(-15, 'file2');
+utouch(-10, 'file4');
+utouch(-5, 'file1');
+
+run_make_test(q!
+.INTERMEDIATE: file3
+file4: file3 ; @mv -f $< $@
+file3: file2 ; touch $@
+file2: file1 ; @touch $@
+!,
+ '--no-print-directory -j2', "touch file3");
+
+rmfiles('file1', 'file2', 'file3', 'file4');
+
+# Make sure that all jobserver FDs are closed if we need to re-exec the
+# master copy.
+#
+# First, find the "default" file descriptors we normally use
+# Then make sure they're still used.
+#
+# Right now we don't have a way to run a makefile and capture the output
+# without checking it, so we can't really write this test.
+
+# run_make_test('
+# submake: ; @$(MAKE) --no-print-directory -f #MAKEFILE# fdprint 5>output
+
+# dependfile: ; @echo FOO=bar > $@
+
+# INCL := true
+
+# FOO=foo
+# ifeq ($(INCL),true)
+# -include dependfile
+# endif
+
+# fdprint: ; @echo $(filter --jobserver%,$(MAKEFLAGS))
+
+# recurse: ; @$(MAKE) --no-print-directory -f #MAKEFILE# submake INCL=true',
+# '-j2 INCL=false fdprint',
+# 'bar');
+
+# rmfiles(qw(dependfile output));
+
+
+# # Do it again, this time where the include is done by the non-master make.
+# run_make_test(undef, '-j2 recurse INCL=false', 'bar');
+
+# rmfiles(qw(dependfile output));
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/patspecific_vars b/src/kmk/tests/scripts/features/patspecific_vars
new file mode 100644
index 0000000..bbeda64
--- /dev/null
+++ b/src/kmk/tests/scripts/features/patspecific_vars
@@ -0,0 +1,148 @@
+# -*-perl-*-
+$description = "Test pattern-specific variable settings.";
+
+$details = "\
+Create a makefile containing various flavors of pattern-specific variable
+settings, override and non-override, and using various variable expansion
+rules, semicolon interference, etc.";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+all: one.x two.x three.x
+FOO = foo
+BAR = bar
+BAZ = baz
+one.x: override FOO = one
+%.x: BAR = two
+t%.x: BAR = four
+thr% : override BAZ = three
+one.x two.x three.x: ; @echo $@: $(FOO) $(BAR) $(BAZ)
+four.x: baz ; @echo $@: $(FOO) $(BAR) $(BAZ)
+baz: ; @echo $@: $(FOO) $(BAR) $(BAZ)
+
+# test matching multiple patterns
+a%: AAA = aaa
+%b: BBB = ccc
+a%: BBB += ddd
+%b: AAA ?= xxx
+%b: AAA += bbb
+.PHONY: ab
+ab: ; @echo $(AAA); echo $(BBB)
+EOF
+
+close(MAKEFILE);
+
+
+# TEST #1 -- basics
+
+&run_make_with_options($makefile, "-j1", &get_logfile);
+$answer = "one.x: one two baz\ntwo.x: foo four baz\nthree.x: foo four three\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #2 -- try the override feature
+
+&run_make_with_options($makefile, "-j1 BAZ=five", &get_logfile);
+$answer = "one.x: one two five\ntwo.x: foo four five\nthree.x: foo four three\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #3 -- make sure patterns are inherited properly
+
+&run_make_with_options($makefile, "-j1 four.x", &get_logfile);
+$answer = "baz: foo two baz\nfour.x: foo two baz\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #4 -- test multiple patterns matching the same target
+
+&run_make_with_options($makefile, "-j1 ab", &get_logfile);
+$answer = "aaa bbb\nccc ddd\n";
+&compare_output($answer,&get_logfile(1));
+
+# TEST #5 -- test pattern-specific exported variables
+#
+run_make_test('
+/%: export foo := foo
+
+/bar:
+ @echo $(foo) $$foo
+', '-j1', 'foo foo');
+
+
+# TEST #6 -- test expansion of pattern-specific simple variables
+#
+run_make_test('
+.PHONY: all
+
+all: inherit := good $$t
+all: bar baz
+
+b%: pattern := good $$t
+
+global := original $$t
+
+
+# normal target
+#
+ifdef rec
+bar: a = global: $(global) pattern: $(pattern) inherit: $(inherit)
+else
+bar: a := global: $(global) pattern: $(pattern) inherit: $(inherit)
+endif
+
+bar: ; @echo \'normal: $a;\'
+
+
+# pattern target
+#
+ifdef rec
+%z: a = global: $(global) pattern: $(pattern) inherit: $(inherit)
+else
+%z: a := global: $(global) pattern: $(pattern) inherit: $(inherit)
+endif
+
+%z: ; @echo \'pattern: $a;\'
+
+
+global := new $$t
+',
+'-j1',
+'normal: global: original $t pattern: inherit: ;
+pattern: global: original $t pattern: inherit: ;');
+
+
+# TEST #7 -- test expansion of pattern-specific recursive variables
+#
+run_make_test(undef, # reuse previous makefile
+'-j1 rec=1',
+'normal: global: new $t pattern: good $t inherit: good $t;
+pattern: global: new $t pattern: good $t inherit: good $t;');
+
+# TEST #8: override in pattern-specific variables
+
+run_make_test('
+a%: override FOO += f1
+a%: FOO += f2
+ab: ; @echo "$(FOO)"
+',
+ '', "f1\n");
+
+run_make_test(undef, 'FOO=C', "C f1\n");
+
+# TEST #9: Test shortest stem selection in pattern-specific variables.
+
+run_make_test('
+%-mt.x: x := two
+%.x: x := one
+
+all: foo.x foo-mt.x
+
+foo.x: ;@echo $x
+foo-mt.x: ;@echo $x
+',
+'',
+"one\ntwo");
+
+1;
diff --git a/src/kmk/tests/scripts/features/patternrules b/src/kmk/tests/scripts/features/patternrules
new file mode 100644
index 0000000..9aa4f62
--- /dev/null
+++ b/src/kmk/tests/scripts/features/patternrules
@@ -0,0 +1,232 @@
+# -*-perl-*-
+
+$description = "Test pattern rules.";
+
+$details = "";
+
+use Cwd;
+
+$dir = cwd;
+$dir =~ s,.*/([^/]+)$,../$1,;
+
+
+# TEST #0: Make sure that multiple patterns where the same target
+# can be built are searched even if the first one fails
+# to match properly.
+#
+
+run_make_test(q!
+.PHONY: all
+
+all: case.1 case.2 case.3
+
+# We can't have this, due to "Implicit Rule Search Algorithm" step 5c
+#xxx: void
+
+# 1 - existing file
+%.1: void
+ @exit 1
+%.1: #MAKEFILE#
+ @exit 0
+
+# 2 - phony
+%.2: void
+ @exit 1
+%.2: 2.phony
+ @exit 0
+.PHONY: 2.phony
+
+# 3 - implicit-phony
+%.3: void
+ @exit 1
+%.3: 3.implicit-phony
+ @exit 0
+
+3.implicit-phony:
+!, '', '');
+
+# TEST #1: make sure files that are built via implicit rules are marked
+# as targets (Savannah bug #12202).
+#
+run_make_test('
+TARGETS := foo foo.out
+
+.PHONY: all foo.in
+
+all: $(TARGETS)
+
+%: %.in
+ @echo $@
+
+%.out: %
+ @echo $@
+
+foo.in: ; @:
+
+',
+'',
+'foo
+foo.out');
+
+
+# TEST #2: make sure intermediate files that also happened to be
+# prerequisites are not removed (Savannah bug #12267).
+#
+run_make_test('
+$(dir)/foo.o:
+
+$(dir)/foo.y:
+ @echo $@
+
+%.c: %.y
+ touch $@
+
+%.o: %.c
+ @echo $@
+
+.PHONY: install
+install: $(dir)/foo.c
+
+',
+"dir=$dir",
+"$dir/foo.y
+touch $dir/foo.c
+$dir/foo.o");
+
+unlink("$dir/foo.c");
+
+
+# TEST #3: make sure precious flag is set properly for targets
+# that are built via implicit rules (Savannah bug #13218).
+#
+run_make_test('
+.DELETE_ON_ERROR:
+
+.PRECIOUS: %.bar
+
+%.bar:; @touch $@ && exit 1
+
+$(dir)/foo.bar:
+
+',
+"dir=$dir",
+(!$is_kmk) ?
+"#MAKE#: *** [#MAKEFILE#:6: $dir/foo.bar] Error 1":
+"#MAKE#: *** [$dir/foo.bar] Error 1" . '
+The failing command:
+ @touch $@ && exit 1',
+512);
+
+unlink("$dir/foo.bar");
+
+
+# TEST #4: make sure targets of a matched implicit pattern rule are
+# never considered intermediate (Savannah bug #13022).
+#
+run_make_test('
+.PHONY: all
+all: foo.c foo.o
+
+%.h %.c: %.in
+ touch $*.h
+ touch $*.c
+
+%.o: %.c %.h
+ echo $+ >$@
+
+%.o: %.c
+ @echo wrong rule
+
+foo.in:
+ touch $@
+
+',
+'-j1',
+'touch foo.in
+touch foo.h
+touch foo.c
+echo foo.c foo.h >foo.o');
+
+unlink('foo.in', 'foo.h', 'foo.c', 'foo.o');
+
+# TEST #5: make sure both prefix and suffix patterns work with multiple
+# target patterns (Savannah bug #26593).
+#
+run_make_test('
+all: foo.s1 foo.s2 p1.foo p2.foo
+
+p1.% p2.%: %.orig
+ @echo $@
+%.s1 %.s2: %.orig
+ @echo $@
+
+.PHONY: foo.orig
+',
+ '', "foo.s1\np1.foo\n");
+
+# TEST 6: Make sure that non-target files are still eligible to be created
+# as part of implicit rule chaining. Savannah bug #17752.
+
+run_make_test(q!
+BIN = xyz
+COPY = $(BIN).cp
+SRC = $(BIN).c
+allbroken: $(COPY) $(BIN) ; @echo ok
+$(SRC): ; @echo 'main(){}' > $@
+%.cp: % ; @cp $< $@
+% : %.c ; @cp $< $@
+clean: ; @rm -rf $(SRC) $(COPY) $(BIN)
+!,
+ '', "ok\n");
+
+unlink(qw(xyz xyz.cp xyz.c));
+
+# TEST 7: Make sure that all prereqs of all "also_make" targets get created
+# before any of the things that depend on any of them. Savannah bug #19108.
+
+run_make_test(q!
+final: x ; @echo $@
+x: x.t1 x.t2 ; @echo $@
+x.t2: dep
+dep: ; @echo $@
+%.t1 %.t2: ; @echo $*.t1 ; echo $*.t2
+!,
+ '', "dep\nx.t1\nx.t2\nx\nfinal\n");
+
+
+# TEST 8: Verify we can remove pattern rules. Savannah bug #18622.
+
+my @f = (qw(foo.w foo.ch));
+touch(@f);
+
+run_make_test(q!
+CWEAVE := :
+
+# Disable builtin rules
+%.tex : %.w
+%.tex : %.w %.ch
+!,
+ 'foo.tex',
+ "#MAKE#: *** No rule to make target 'foo.tex'. Stop.", 512);
+
+unlink(@f);
+
+# TEST #9: Test shortest stem selection in pattern rules.
+
+run_make_test('
+%.x: ;@echo one
+%-mt.x: ;@echo two
+
+all: foo.x foo-mt.x
+',
+'',
+"one\ntwo");
+
+1;
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/features/quoting b/src/kmk/tests/scripts/features/quoting
new file mode 100644
index 0000000..916681c
--- /dev/null
+++ b/src/kmk/tests/scripts/features/quoting
@@ -0,0 +1,32 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test using \n" .
+ "quotes within makefiles.";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<'EOM';
+SHELL = /bin/sh
+TEXFONTS = NICEFONT
+DEFINES = -DDEFAULT_TFM_PATH=\".:$(TEXFONTS)\"
+test: ; @"echo" 'DEFINES = $(DEFINES)'
+EOM
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+
+# Create the answer to what should be produced by this Makefile
+$answer = 'DEFINES = -DDEFAULT_TFM_PATH=\".:NICEFONT\"' . "\n";
+
+# COMPARE RESULTS
+
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/features/recursion b/src/kmk/tests/scripts/features/recursion
new file mode 100644
index 0000000..862b6c4
--- /dev/null
+++ b/src/kmk/tests/scripts/features/recursion
@@ -0,0 +1,55 @@
+# -*-perl-*-
+$description = "Test recursion.";
+
+$details = "DETAILS";
+
+# Test some basic recursion.
+run_make_test('
+all:
+ $(MAKE) -f #MAKEFILE# foo
+foo:
+ @echo $(MAKE)
+ @echo MAKELEVEL = $('. (!$is_kmk ? 'MAKELEVEL' : 'KMK_LEVEL') .')
+ $(MAKE) -f #MAKEFILE# last
+last:
+ @echo $(MAKE)
+ @echo MAKELEVEL = $('. (!$is_kmk ? 'MAKELEVEL' : 'KMK_LEVEL') .')
+ @echo THE END
+',
+ ('CFLAGS=-O -w' . ($parallel_jobs ? ' -j 2' : '')),
+ ($vos
+ ? "#MAKE#: Entering directory '#PWD#'
+make 'CFLAGS=-O' -f #MAKEFILE# foo
+make CFLAGS=-O
+MAKELEVEL = 0
+make 'CFLAGS=-O' -f #MAKEFILE# last
+make CFLAGS=-O
+MAKELEVEL = 0
+THE END
+#MAKE#: Leaving directory '#PWD#'"
+ : "#MAKE#: Entering directory '#PWD#'
+#MAKEPATH# -f #MAKEFILE# foo
+#MAKE#[1]: Entering directory '#PWD#'
+#MAKEPATH#
+MAKELEVEL = 1
+#MAKEPATH# -f #MAKEFILE# last
+#MAKE#[2]: Entering directory '#PWD#'
+#MAKEPATH#
+MAKELEVEL = 2
+THE END
+#MAKE#[2]: Leaving directory '#PWD#'
+#MAKE#[1]: Leaving directory '#PWD#'
+#MAKE#: Leaving directory '#PWD#'"));
+
+
+# Test command line overrides.
+run_make_test('
+recur: all ; @$(MAKE) --no-print-directory -f #MAKEFILE# a=AA all
+all: ; @echo "MAKEOVERRIDES = $('. (!$is_kmk ? 'MAKEOVERRIDES' : 'KMK_OVERRIDES') .')"
+',
+ 'a=ZZ',
+ 'MAKEOVERRIDES = a=ZZ
+MAKEOVERRIDES = a=AA
+');
+
+1;
diff --git a/src/kmk/tests/scripts/features/reinvoke b/src/kmk/tests/scripts/features/reinvoke
new file mode 100644
index 0000000..eb1a349
--- /dev/null
+++ b/src/kmk/tests/scripts/features/reinvoke
@@ -0,0 +1,80 @@
+# -*-mode: perl-*-
+
+$description = "Test GNU make's auto-reinvocation feature.";
+
+$details = "\
+If the makefile or one it includes can be rebuilt then it is, and make
+is reinvoked. We create a rule to rebuild the makefile from a temp
+file, then touch the temp file to make it newer than the makefile.";
+
+$omkfile = $makefile;
+
+&utouch(-600, 'incl.mk');
+# For some reason if we don't do this then the test fails for systems
+# with sub-second timestamps, maybe + NFS? Not sure.
+&utouch(-1, 'incl-1.mk');
+
+run_make_test('
+all: ; @echo running rules.
+
+#MAKEFILE# incl.mk: incl-1.mk
+ @echo rebuilding $@
+ @echo >> $@
+
+include incl.mk',
+ '', "rebuilding incl.mk\nrunning rules.\n");
+
+# Make sure updating the makefile itself also works
+
+&utouch(-600, $omkfile);
+
+run_make_test(undef, '', "rebuilding #MAKEFILE#\nrunning rules.\n");
+
+&rmfiles('incl.mk', 'incl-1.mk');
+
+
+# In this test we create an included file that's out-of-date, but then
+# the rule doesn't update it. Make shouldn't re-exec.
+
+&utouch(-600, 'b','a');
+#&utouch(-10, 'a');
+&touch('c');
+
+run_make_test('
+SHELL = /bin/sh
+
+all: ; @echo hello
+
+a : b ; echo >> $@
+
+b : c ; [ -f $@ ] || echo >> $@
+
+c: ; echo >> $@
+
+include $(F)',
+ 'F=a', "[ -f b ] || echo >> b\nhello\n");
+
+# Now try with the file we're not updating being the actual file we're
+# including: this and the previous one test different parts of the code.
+
+run_make_test(undef, 'F=b', "[ -f b ] || echo >> b\nhello\n")
+
+&rmfiles('a','b','c');
+
+# Ensure command line variables are preserved properly across re-exec
+# Tests for Savannah bug #30723
+
+run_make_test('
+ifdef RECURSE
+-include foo30723
+endif
+recurse: ; @$(MAKE) -f $(MAKEFILE_LIST) RECURSE=1 test
+test: ; @echo F.O=$(F.O)
+foo30723: ; @touch $@
+',
+ '--no-print-directory F.O=bar', "F.O=bar\n");
+
+unlink('foo30723');
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/rule_glob b/src/kmk/tests/scripts/features/rule_glob
new file mode 100644
index 0000000..2d377e7
--- /dev/null
+++ b/src/kmk/tests/scripts/features/rule_glob
@@ -0,0 +1,37 @@
+# -*-perl-*-
+
+$description = "Test globbing in targets and prerequisites.";
+
+$details = "";
+
+touch(qw(a.one a.two a.three));
+
+# Test wildcards in regular targets and prerequisites
+run_make_test(q{
+.PHONY: all a.one a.two a.three
+all: a.one* a.t[a-z0-9]o a.th[!q]ee
+a.o[Nn][Ee] a.t*: ; @echo $@
+},
+ '', "a.one\na.two\na.three");
+
+# Test wildcards in pattern targets and prerequisites
+run_make_test(q{
+.PHONY: all
+all: a.four
+%.four : %.t* ; @echo $@: $(sort $^)
+},
+ '', "a.four: a.three a.two");
+
+# Test wildcards in second expansion targets and prerequisites
+run_make_test(q{
+.PHONY: all
+all: a.four
+.SECONDEXPANSION:
+%.four : $$(sort %.t*) ; @echo $@: $(sort $^)
+},
+ '', "a.four: a.three a.two");
+
+unlink(qw(a.one a.two a.three));
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/se_explicit b/src/kmk/tests/scripts/features/se_explicit
new file mode 100644
index 0000000..ab7c26f
--- /dev/null
+++ b/src/kmk/tests/scripts/features/se_explicit
@@ -0,0 +1,169 @@
+# -*-perl-*-
+$description = "Test second expansion in ordinary rules.";
+
+$details = "";
+
+# TEST #0: Test handing of '$' in prerequisites with and without second
+# expansion.
+# bird: Modified this test to use ${PRE} instead of $(PRE) as it failes
+# when make is built with NO_ARCHIVES defined.
+
+# If we don't support archives then the prerequisite is different
+my $prereq = exists $FEATURES{'archives'} ? '$' : '$(PRE)';
+
+run_make_test(q!
+ifdef SE
+ .SECONDEXPANSION:
+endif
+foo$$bar: bar$$baz bar$$biz ; @echo '$@ : $^'
+PRE = one two
+bar$$baz: $${PRE}
+baraz: $${PRE}
+PRE = three four
+.DEFAULT: ; @echo '$@'
+!,
+ '',
+ "$prereq\nbar\$biz\nfoo\$bar : bar\$baz bar\$biz");
+
+run_make_test(undef, 'SE=1', "three\nfour\nbariz\nfoo\$bar : baraz bariz");
+
+# TEST #1: automatic variables.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+foo: bar baz
+
+foo: biz | buz
+
+foo: $$@.1 \
+ $$<.2 \
+ $$(addsuffix .3,$$^) \
+ $$(addsuffix .4,$$+) \
+ $$|.5 \
+ $$*.6
+
+!,
+'-j1',
+'bar
+baz
+biz
+buz
+foo.1
+bar.2
+bar.3
+baz.3
+biz.3
+bar.4
+baz.4
+biz.4
+buz.5
+.6
+');
+
+
+# Test #2: target/pattern -specific variables.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+foo.x: $$a $$b
+
+foo.x: a := bar
+
+%.x: b := baz
+!,
+'',
+'bar
+baz
+');
+
+
+# Test #3: order of prerequisites.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+all: foo bar baz
+
+# Subtest #1
+foo: foo.1; @:
+foo: foo.2
+foo: foo.3
+
+# Subtest #2
+bar: bar.2
+bar: bar.1; @:
+bar: bar.3
+
+# Subtest #3
+baz: baz.1
+baz: baz.2
+baz: ; @:
+!,
+'-j1',
+'foo.1
+foo.2
+foo.3
+bar.1
+bar.2
+bar.3
+baz.1
+baz.2
+');
+
+# TEST #4: eval in a context where there is no reading_file
+run_make_test(q!
+.SECONDEXPANSION:
+all : $$(eval $$(info test))
+!,
+ '', "test\n#MAKE#: Nothing to be done for 'all'.\n");
+
+# TEST #5: (NEGATIVE) catch eval in a prereq list trying to create new
+# target/prereq relationships.
+
+run_make_test(q!
+.SECONDEXPANSION:
+proj1.exe : proj1.o $$(eval $$(test))
+define test
+proj1.o : proj1.c
+proj1.c: proj1.h
+endef
+!,
+ '', "#MAKE#: *** prerequisites cannot be defined in recipes. Stop.\n", 512);
+
+
+# Automatic $$+ variable expansion issue. Savannah bug #25780
+run_make_test(q!
+all : foo foo
+.SECONDEXPANSION:
+all : $$+ ; @echo '$+'
+foo : ;
+!,
+ '', "foo foo foo foo\n");
+
+
+# Automatic $$+ variable expansion issue. Savannah bug #25780
+run_make_test(q!
+all : bar bar
+bar : ;
+q%x : ;
+.SECONDEXPANSION:
+a%l: q1x $$+ q2x ; @echo '$+'
+!,
+ '', "q1x bar bar q2x bar bar\n");
+
+
+# Allow patsubst shorthand in second expansion context.
+# Requires the colon to be quoted. Savannah bug #16545
+run_make_test(q!
+.PHONY: foo.bar
+.SECONDEXPANSION:
+foo: $$(@\\:%=%.bar); @echo '$^'
+!,
+ '', "foo.bar\n");
+
+1;
diff --git a/src/kmk/tests/scripts/features/se_implicit b/src/kmk/tests/scripts/features/se_implicit
new file mode 100644
index 0000000..e40270f
--- /dev/null
+++ b/src/kmk/tests/scripts/features/se_implicit
@@ -0,0 +1,260 @@
+# -*-perl-*-
+$description = "Test second expansion in ordinary rules.";
+
+$details = "";
+
+use Cwd;
+
+$dir = cwd;
+$dir =~ s,.*/([^/]+)$,../$1,;
+
+
+# Test #1: automatic variables.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+foo.a: bar baz
+
+foo.a: biz | buz
+
+foo.%: 1.$$@ \
+ 2.$$< \
+ $$(addprefix 3.,$$^) \
+ $$(addprefix 4.,$$+) \
+ 5.$$| \
+ 6.$$*
+ @:
+
+1.foo.a \
+2.bar \
+3.bar \
+3.baz \
+3.biz \
+4.bar \
+4.baz \
+4.biz \
+5.buz \
+6.a:
+ @echo '$@'
+
+!,
+'-j1',
+'1.foo.a
+2.bar
+3.bar
+3.baz
+3.biz
+4.bar
+4.baz
+4.biz
+5.buz
+6.a
+bar
+baz
+biz
+buz
+');
+
+
+# Test #2: target/pattern -specific variables.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+foo.x:
+
+foo.%: $$(%_a) $$(%_b) bar
+ @:
+
+foo.x: x_a := bar
+
+%.x: x_b := baz
+
+bar baz: ; @echo '$@'
+!,
+ '', "bar\nbaz\n");
+
+
+# Test #3: order of prerequisites.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+all: foo bar baz
+
+
+# Subtest #1
+#
+%oo: %oo.1; @:
+
+foo: foo.2
+
+foo: foo.3
+
+foo.1: ; @echo '$@'
+
+
+# Subtest #2
+#
+bar: bar.2
+
+%ar: %ar.1; @:
+
+bar: bar.3
+
+bar.1: ; @echo '$@'
+
+
+# Subtest #3
+#
+baz: baz.1
+
+baz: baz.2
+
+%az: ; @:
+!,
+ '-j1',
+'foo.1
+foo.2
+foo.3
+bar.1
+bar.2
+bar.3
+baz.1
+baz.2
+');
+
+
+# Test #4: stem splitting logic.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+$(dir)/tmp/bar.o:
+
+$(dir)/tmp/foo/bar.c: ; @echo '$@'
+$(dir)/tmp/bar/bar.c: ; @echo '$@'
+foo.h: ; @echo '$@'
+
+%.o: $$(addsuffix /%.c,foo bar) foo.h
+ @echo '$@: {$<} $^'
+!,
+ "dir=$dir", "$dir/tmp/foo/bar.c
+$dir/tmp/bar/bar.c
+foo.h
+$dir/tmp/bar.o: {$dir/tmp/foo/bar.c} $dir/tmp/foo/bar.c $dir/tmp/bar/bar.c foo.h
+");
+
+
+# Test #5: stem splitting logic and order-only prerequisites.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+$(dir)/tmp/foo.o: $(dir)/tmp/foo.c
+$(dir)/tmp/foo.c: ; @echo '$@'
+bar.h: ; @echo '$@'
+
+%.o: %.c|bar.h
+ @echo '$@: {$<} {$|} $^'
+
+!,
+ "dir=$dir", "$dir/tmp/foo.c
+bar.h
+$dir/tmp/foo.o: {$dir/tmp/foo.c} {bar.h} $dir/tmp/foo.c
+");
+
+
+# Test #6: lack of implicit prerequisites.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+foo.o: foo.c
+foo.c: ; @echo '$@'
+
+%.o:
+ @echo '$@: {$<} $^'
+!,
+ '', "foo.c\nfoo.o: {foo.c} foo.c\n");
+
+
+# Test #7: Test stem from the middle of the name.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+foobarbaz:
+
+foo%baz: % $$*.1
+ @echo '$*'
+
+bar bar.1:
+ @echo '$@'
+!,
+ '', "bar\nbar.1\nbar\n");
+
+
+# Test #8: Make sure stem triple-expansion does not happen.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+foo$$bar:
+
+f%r: % $$*.1
+ @echo '$*'
+
+oo$$ba oo$$ba.1:
+ @echo '$@'
+!,
+ '', 'oo$ba
+oo$ba.1
+oo$ba
+');
+
+# Test #9: Check the value of $^
+run_make_test(q!
+.SECONDEXPANSION:
+
+%.so: | $$(extra) ; @echo $^
+
+foo.so: extra := foo.o
+foo.so:
+foo.o:
+!,
+ '', "\n");
+
+# Test #10: Test second expansion with second expansion prerequisites
+# Ensures pattern_search() recurses with SE prereqs.
+touch('a');
+run_make_test(q!
+.SECONDEXPANSION:
+sim_base_rgg := just_a_name
+sim_base_src := a
+sim_base_f := a a a
+sim_%.f: $${sim_$$*_f}
+ echo $@
+sim_%.src: $${sim_$$*_src}
+ echo $@
+sim_%: \
+ $$(if $$(sim_$$*_src),sim_%.src) \
+ $$(if $$(sim_$$*_f),sim_%.f) \
+ $$(if $$(sim_$$*_rgg),$$(sim_$$*_rgg).s)
+ echo $@
+!,
+ '-s sim_base', "#MAKE#: *** No rule to make target 'sim_base'. Stop.", 512);
+
+unlink('a');
+
+# Ensure that order-only tokens embedded in second expansions are parsed
+run_make_test(q!
+.SECONDEXPANSION:
+PREREQS=p1|p2
+P2=p2
+all : foo bar
+f%o: $$(PREREQS) ; @echo '$@' from '$^' and '$|'
+b%r: p1|$$(P2) ; @echo '$@' from '$^' and '$|'
+p% : ; : $@
+!,
+ "", ": p1\n: p2\nfoo from p1 and p2\nbar from p1 and p2\n");
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/se_statpat b/src/kmk/tests/scripts/features/se_statpat
new file mode 100644
index 0000000..828afa2
--- /dev/null
+++ b/src/kmk/tests/scripts/features/se_statpat
@@ -0,0 +1,109 @@
+# -*-perl-*-
+$description = "Test second expansion in static pattern rules.";
+
+$details = "";
+
+# Test #1: automatic variables.
+#
+# bird: Had to add -j1 here earlier...
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+foo.a foo.b: foo.%: bar.% baz.%
+foo.a foo.b: foo.%: biz.% | buz.%
+
+foo.a foo.b: foo.%: $$@.1 \
+ $$<.2 \
+ $$(addsuffix .3,$$^) \
+ $$(addsuffix .4,$$+) \
+ $$|.5 \
+ $$*.6
+!,
+ '', 'bar.a
+baz.a
+biz.a
+buz.a
+foo.a.1
+bar.a.2
+bar.a.3
+baz.a.3
+biz.a.3
+bar.a.4
+baz.a.4
+biz.a.4
+buz.a.5
+a.6
+');
+
+
+# Test #2: target/pattern -specific variables.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+foo.x foo.y: foo.%: $$(%_a) $$($$*_b)
+
+foo.x: x_a := bar
+
+%.x: x_b := baz
+!,
+ '', "bar\nbaz\n");
+
+
+# Test #3: order of prerequisites.
+#
+# bird: Had to add -j1 here earlier...
+run_make_test(q!
+.SECONDEXPANSION:
+.DEFAULT: ; @echo '$@'
+
+all: foo.a bar.a baz.a
+
+# Subtest #1
+foo.a foo.b: foo.%: foo.%.1; @:
+foo.a foo.b: foo.%: foo.%.2
+foo.a foo.b: foo.%: foo.%.3
+
+
+# Subtest #2
+bar.a bar.b: bar.%: bar.%.2
+bar.a bar.b: bar.%: bar.%.1; @:
+bar.a bar.b: bar.%: bar.%.3
+
+
+# Subtest #3
+baz.a baz.b: baz.%: baz.%.1
+baz.a baz.b: baz.%: baz.%.2
+baz.a baz.b: ; @:
+!,
+ '', 'foo.a.1
+foo.a.2
+foo.a.3
+bar.a.1
+bar.a.2
+bar.a.3
+baz.a.1
+baz.a.2
+');
+
+
+# Test #4: Make sure stem triple-expansion does not happen.
+#
+run_make_test(q!
+.SECONDEXPANSION:
+foo$$bar: f%r: % $$*.1
+ @echo '$*'
+
+oo$$ba oo$$ba.1:
+ @echo '$@'
+!,
+ '', 'oo$ba
+oo$ba.1
+oo$ba
+');
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/shell_assignment b/src/kmk/tests/scripts/features/shell_assignment
new file mode 100644
index 0000000..686e4bd
--- /dev/null
+++ b/src/kmk/tests/scripts/features/shell_assignment
@@ -0,0 +1,65 @@
+# -*-perl-*-
+
+$description = "Test BSD-style shell assignments (VAR != VAL) for variables.";
+
+$details = "";
+
+# TEST 0: Basic shell assignment (!=).
+
+run_make_test('
+.POSIX:
+
+demo1!=printf \' 1 2 3\n4\n\n5 \n \n 6\n\n\n\n\'
+demo2 != printf \'7 8\n \'
+demo3 != printf \'$$(demo2)\'
+demo4 != printf \' 2 3 \n\'
+demo5 != printf \' 2 3 \n\n\'
+all: ; @echo "<$(demo1)> <$(demo2)> <$(demo3)> <$(demo4)> <${demo5}>"
+',
+ '', "< 1 2 3 4 5 6 > <7 8 > <7 8 > < 2 3 > < 2 3 >\n");
+
+# TEST 1: Handle '#' the same way as BSD make
+
+run_make_test('
+foo1!=echo bar#baz
+hash != printf \'\043\'
+foo2!= echo "bar$(hash)baz"
+
+all: ; @echo "<$(foo1)> <$(hash)> <$(foo2)>"
+',
+ '', "<bar> <#> <bar#baz>\n");
+
+# TEST 2: shell assignment variables (from !=) should be recursive.
+# Note that variables are re-evaluated later, so the shell can output
+# a value like $(XYZZY) as part of !=. The $(XYZZY) will be EVALUATED
+# when the value containing it is evaluated. On the negative side, this
+# means if you don't want this, you need to escape dollar signs as $$.
+# On the positive side, it means that shell programs can output macros
+# that are then evaluated as they are traditionally evaluated.. and that
+# you can use traditional macro evaluation semantics to implement !=.
+
+run_make_test('
+XYZZY = fiddle-dee-dee
+dollar = $$
+VAR3 != printf \'%s\' \'$(dollar)(XYZZY)\'
+
+all: ; @echo "<$(VAR3)>"
+',
+ '', "<fiddle-dee-dee>\n");
+
+
+# TEST 3: Overrides invoke shell anyway; they just don't store the result
+# in a way that is visible.
+
+run_make_test('
+
+override != echo abc > ,abc ; cat ,abc
+
+all: ; @echo "<$(override)>" ; cat ,abc
+',
+ 'override=xyz', "<xyz>\nabc\n");
+
+unlink(',abc');
+
+
+1;
diff --git a/src/kmk/tests/scripts/features/statipattrules b/src/kmk/tests/scripts/features/statipattrules
new file mode 100644
index 0000000..6b3c565
--- /dev/null
+++ b/src/kmk/tests/scripts/features/statipattrules
@@ -0,0 +1,111 @@
+# -*-perl-*-
+$description = "Test handling of static pattern rules.";
+
+$details = "\
+The makefile created in this test has three targets. The
+filter command is used to get those target names ending in
+.o and statically creates a compile command with the target
+name and the target name with .c. It also does the same thing
+for another target filtered with .elc and creates a command
+to emacs a .el file";
+
+&touch('bar.c', 'lose.c');
+
+# TEST #0
+# -------
+
+run_make_test('
+files = foo.elc bar.o lose.o
+
+$(filter %.o,$(files)): %.o: %.c ; @echo CC -c $(CFLAGS) $< -o $@
+
+$(filter %.elc,$(files)): %.elc: %.el ; @echo emacs $<
+',
+ '',
+ 'CC -c bar.c -o bar.o');
+
+# TEST #1
+# -------
+
+run_make_test(undef, 'lose.o', 'CC -c lose.c -o lose.o');
+
+
+# TEST #2
+# -------
+&touch("foo.el");
+
+run_make_test(undef, 'foo.elc', 'emacs foo.el');
+
+# Clean up after the first tests.
+unlink('foo.el', 'bar.c', 'lose.c');
+
+
+# TEST #3 -- PR/1670: don't core dump on invalid static pattern rules
+# -------
+
+run_make_test('
+.DEFAULT: ; @echo $@
+foo: foo%: % %.x % % % y.% % ; @echo $@
+',
+ '-j1', ".x\ny.\nfoo");
+
+
+# TEST #4 -- bug #12180: core dump on a stat pattern rule with an empty
+# prerequisite list.
+run_make_test('
+foo.x bar.x: %.x : ; @echo $@
+
+',
+ '', 'foo.x');
+
+
+# TEST #5 -- bug #13881: double colon static pattern rule does not
+# substitute %.
+run_make_test('
+foo.bar:: %.bar: %.baz
+foo.baz: ;@:
+',
+ '', '');
+
+
+# TEST #6: make sure the second stem does not overwrite the first
+# perprerequisite's stem (Savannah bug #16053).
+#
+run_make_test('
+all.foo.bar: %.foo.bar: %.one
+
+all.foo.bar: %.bar: %.two
+
+all.foo.bar:
+ @echo $*
+ @echo $^
+
+.DEFAULT:;@:
+',
+'',
+'all.foo
+all.one all.foo.two');
+
+
+# TEST #7: make sure the second stem does not overwrite the first
+# perprerequisite's stem when second expansion is enabled
+# (Savannah bug #16053).
+#
+run_make_test('
+.SECONDEXPANSION:
+
+all.foo.bar: %.foo.bar: %.one $$*-one
+
+all.foo.bar: %.bar: %.two $$*-two
+
+all.foo.bar:
+ @echo $*
+ @echo $^
+
+.DEFAULT:;@:
+',
+'',
+'all.foo
+all.one all-one all.foo.two all.foo-two');
+
+1;
diff --git a/src/kmk/tests/scripts/features/targetvars b/src/kmk/tests/scripts/features/targetvars
new file mode 100644
index 0000000..a9b8dbe
--- /dev/null
+++ b/src/kmk/tests/scripts/features/targetvars
@@ -0,0 +1,273 @@
+# -*-perl-*-
+$description = "Test target-specific variable settings.";
+
+$details = "\
+Create a makefile containing various flavors of target-specific variable
+values, override and non-override, and using various variable expansion
+rules, semicolon interference, etc.";
+
+run_make_test('
+SHELL = /bin/sh
+export FOO = foo
+export BAR = bar
+one: override FOO = one
+one two: ; @echo $(FOO) $(BAR)
+two: BAR = two
+three: ; BAR=1000
+ @echo $(FOO) $(BAR)
+# Some things that shouldn not be target vars
+funk : override
+funk : override adelic
+adelic override : ; echo $@
+# Test per-target recursive variables
+four:FOO=x
+four:VAR$(FOO)=ok
+four: ; @echo "$(FOO) $(VAR$(FOO)) $(VAR) $(VARx)"
+five:FOO=x
+five six : VAR$(FOO)=good
+five six: ;@echo "$(FOO) $(VAR$(FOO)) $(VAR) $(VARx) $(VARfoo)"
+# Test per-target variable inheritance
+seven: eight
+seven eight: ; @echo $@: $(FOO) $(BAR)
+seven: BAR = seven
+seven: FOO = seven
+eight: BAR = eight
+# Test the export keyword with per-target variables
+nine: ; @echo $(FOO) $(BAR) $$FOO $$BAR
+nine: FOO = wallace
+nine-a: export BAZ = baz
+nine-a: ; @echo $$BAZ
+# Test = escaping
+EQ = =
+ten: one$(EQ)two
+ten: one $(EQ) two
+ten one$(EQ)two $(EQ):;@echo $@
+.PHONY: one two three four five six seven eight nine ten $(EQ) one$(EQ)two
+# Test target-specific vars with pattern/suffix rules
+QVAR = qvar
+RVAR = =
+%.q : ; @echo $(QVAR) $(RVAR)
+foo.q : RVAR += rvar
+# Target-specific vars with multiple LHS pattern rules
+%.r %.s %.t: ; @echo $(QVAR) $(RVAR) $(SVAR) $(TVAR)
+foo.r : RVAR += rvar
+foo.t : TVAR := $(QVAR)
+',
+ "one two three", "one bar\nfoo two\nBAR=1000\nfoo bar\n");
+
+# TEST #2
+
+run_make_test(undef, "one two FOO=1 BAR=2", "one 2\n1 2\n");
+
+# TEST #3
+
+run_make_test(undef, "four", "x ok ok\n");
+
+# TEST #4
+
+run_make_test(undef, "seven", "eight: seven eight\nseven: seven seven\n");
+
+# TEST #5
+
+run_make_test(undef, "nine", "wallace bar wallace bar\n");
+
+# TEST #5-a
+
+run_make_test(undef, "nine-a", "baz\n");
+
+# TEST #6
+
+run_make_test(undef, "ten", "one=two\none bar\n=\nfoo two\nten\n");
+
+# TEST #6
+
+run_make_test(undef, "foo.q bar.q", "qvar = rvar\nqvar =\n");
+
+# TEST #7
+
+run_make_test(undef, "foo.t bar.s", "qvar = qvar\nqvar =\n");
+
+
+# TEST #8
+# For PR/1378: Target-specific vars don't inherit correctly
+
+run_make_test('
+foo: FOO = foo
+bar: BAR = bar
+foo: bar
+bar: baz
+baz: ; @echo $(FOO) $(BAR)
+', "", "foo bar\n");
+
+# TEST #9
+# For PR/1380: Using += assignment in target-specific variables sometimes fails
+# Also PR/1831
+
+run_make_test('
+.PHONY: all one
+all: FOO += baz
+all: one; @echo $(FOO)
+
+FOO = bar
+
+one: FOO += biz
+one: FOO += boz
+one: ; @echo $(FOO)
+',
+ '', "bar baz biz boz\nbar baz\n");
+
+# Test #10
+
+run_make_test(undef, 'one', "bar biz boz\n");
+
+# Test #11
+# PR/1709: Test semicolons in target-specific variable values
+
+run_make_test('
+foo : FOO = ; ok
+foo : ; @echo "$(FOO)"
+',
+ '', "; ok\n");
+
+# Test #12
+# PR/2020: More hassles with += target-specific vars. I _really_ think
+# I nailed it this time :-/.
+
+run_make_test('
+.PHONY: a
+
+BLAH := foo
+COMMAND = echo $(BLAH)
+
+a: ; @$(COMMAND)
+
+a: BLAH := bar
+a: COMMAND += snafu $(BLAH)
+',
+ '', "bar snafu bar\n");
+
+# Test #13
+# Test double-colon rules with target-specific variable values
+
+run_make_test('
+W = bad
+X = bad
+foo: W = ok
+foo:: ; @echo $(W) $(X) $(Y) $(Z)
+foo:: ; @echo $(W) $(X) $(Y) $(Z)
+foo: X = ok
+
+Y = foo
+bar: foo
+bar: Y = bar
+
+Z = nopat
+ifdef PATTERN
+ fo% : Z = pat
+endif
+',
+ 'foo', "ok ok foo nopat\nok ok foo nopat\n");
+
+# Test #14
+# Test double-colon rules with target-specific variable values and
+# inheritance
+
+run_make_test(undef, 'bar', "ok ok bar nopat\nok ok bar nopat\n");
+
+# Test #15
+# Test double-colon rules with pattern-specific variable values
+
+run_make_test(undef, 'foo PATTERN=yes', "ok ok foo pat\nok ok foo pat\n");
+
+# Test #16
+# Test target-specific variables with very long command line
+# (> make default buffer length)
+
+run_make_test('
+base_metals_fmd_reports.sun5 base_metals_fmd_reports CreateRealPositions CreateMarginFunds deals_changed_since : BUILD_OBJ=$(shell if [ -f "build_information.generate" ]; then echo "$(OBJ_DIR)/build_information.o"; else echo "no build information"; fi )
+
+deals_changed_since: ; @echo $(BUILD_OBJ)
+',
+ '', "no build information\n");
+
+# TEST #17
+
+# Test a merge of set_lists for files, where one list is much longer
+# than the other. See Savannah bug #15757.
+
+mkdir('t1', 0777);
+touch('t1/rules.mk');
+
+run_make_test('
+VPATH = t1
+include rules.mk
+.PHONY: all
+all: foo.x
+foo.x : rules.mk ; @echo MYVAR=$(MYVAR) FOOVAR=$(FOOVAR) ALLVAR=$(ALLVAR)
+all: ALLVAR = xxx
+foo.x: FOOVAR = bar
+rules.mk : MYVAR = foo
+.INTERMEDIATE: foo.x rules.mk
+',
+ '-I t1', 'MYVAR= FOOVAR=bar ALLVAR=xxx');
+
+rmfiles('t1/rules.mk');
+rmdir('t1');
+
+# TEST #18
+
+# Test appending to a simple variable containing a "$": avoid a
+# double-expansion. See Savannah bug #15913.
+
+run_make_test('
+VAR := $$FOO
+foo: VAR += BAR
+foo: ; @echo '."'".'$(VAR)'."'".'
+',
+ '', '$FOO BAR');
+
+# TEST #19: Override with append variables
+
+run_make_test('
+a: override FOO += f1
+a: FOO += f2
+a: ; @echo "$(FOO)"
+',
+ '', "f1\n");
+
+run_make_test(undef, 'FOO=C', "C f1\n");
+
+# TEST #19: Conditional variables with command-line settings
+
+run_make_test('
+a: FOO ?= f1
+a: ; @echo "$(FOO)"
+',
+ '', "f1\n");
+
+run_make_test(undef, 'FOO=C', "C\n");
+
+# TEST #20: Check for continuation after semicolons
+
+run_make_test(q!
+a: A = 'hello;\
+world'
+a: ; @echo $(A)
+!,
+ '', "hello; world\n");
+
+# TEST #19: Test define/endef variables as target-specific vars
+
+# run_make_test('
+# define b
+# @echo global
+# endef
+# a: define b
+# @echo local
+# endef
+
+# a: ; $(b)
+# ',
+# '', "local\n");
+
+1;
diff --git a/src/kmk/tests/scripts/features/utf8 b/src/kmk/tests/scripts/features/utf8
new file mode 100644
index 0000000..54bc471
--- /dev/null
+++ b/src/kmk/tests/scripts/features/utf8
@@ -0,0 +1,11 @@
+# -*-perl-*-
+
+$description = "Test support for UTF-8.";
+
+$details = "";
+
+# Verify that the UTF-8 BOM is ignored.
+run_make_test("\xEF\xBB\xBFall: ; \@echo \$\@\n", '', "all");
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/features/varnesting b/src/kmk/tests/scripts/features/varnesting
new file mode 100644
index 0000000..d8f3ffb
--- /dev/null
+++ b/src/kmk/tests/scripts/features/varnesting
@@ -0,0 +1,35 @@
+# -*-perl-*-
+$description = "Test recursive variables";
+
+$details = "";
+
+run_make_test('
+x = variable1
+variable2 := Hello
+y = $(subst 1,2,$(x))
+z = y
+a := $($($(z)))
+all:
+ @echo $(a)
+',
+ '', "Hello\n");
+
+# This tests resetting the value of a variable while expanding it.
+# You may only see problems with this if you're using valgrind or
+# some other memory checker that poisons freed memory.
+# See Savannah patch #7534
+
+run_make_test('
+VARIABLE = $(eval VARIABLE := echo hi)$(VARIABLE)
+wololo:
+ @$(VARIABLE)
+',
+ '', "hi\n");
+
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/features/vpath b/src/kmk/tests/scripts/features/vpath
new file mode 100644
index 0000000..530d9e5
--- /dev/null
+++ b/src/kmk/tests/scripts/features/vpath
@@ -0,0 +1,82 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test the \n"
+ ."vpath directive which allows you to specify a search \n"
+ ."path for a particular class of filenames, those that\n"
+ ."match a particular pattern.";
+
+$details = "This tests the vpath directive by specifying search directories\n"
+ ."for one class of filenames with the form: vpath pattern directories"
+ ."\nIn this test, we specify the working directory for all files\n"
+ ."that end in c or h. We also test the variables $@ (which gives\n"
+ ."target name) and $^ (which is a list of all dependencies \n"
+ ."including the directories in which they were found). It also\n"
+ ."uses the function firstword used to extract just the first\n"
+ ."dependency from the entire list.";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "vpath %.c foo\n";
+print MAKEFILE "vpath %.c $workdir\n";
+print MAKEFILE "vpath %.h $workdir\n";
+print MAKEFILE "objects = main.o kbd.o commands.o display.o insert.o\n";
+print MAKEFILE "edit: \$(objects)\n";
+print MAKEFILE "\t\@echo cc -o \$@ \$^\n";
+print MAKEFILE "main.o : main.c defs.h\n";
+print MAKEFILE "\t\@echo cc -c \$(firstword \$^)\n";
+print MAKEFILE "kbd.o : kbd.c defs.h command.h\n";
+print MAKEFILE "\t\@echo cc -c kbd.c\n";
+print MAKEFILE "commands.o : command.c defs.h command.h\n";
+print MAKEFILE "\t\@echo cc -c commands.c\n";
+print MAKEFILE "display.o : display.c defs.h buffer.h\n";
+print MAKEFILE "\t\@echo cc -c display.c\n";
+print MAKEFILE "insert.o : insert.c defs.h buffer.h\n";
+print MAKEFILE "\t\@echo cc -c insert.c\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+@files_to_touch = ("$workdir${pathsep}main.c","$workdir${pathsep}defs.h",
+ "$workdir${pathsep}kbd.c","$workdir${pathsep}command.h",
+ "$workdir${pathsep}commands.c","$workdir${pathsep}display.c",
+ "$workdir${pathsep}buffer.h","$workdir${pathsep}insert.c",
+ "$workdir${pathsep}command.c");
+
+&touch(@files_to_touch);
+
+# kmk: this requires -j1 because of ordering.
+&run_make_with_options($makefile,"-j1",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "cc -c $workdir${pathsep}main.c\ncc -c kbd.c\ncc -c commands.c\n"
+ ."cc -c display.c\n"
+ ."cc -c insert.c\ncc -o edit main.o kbd.o commands.o display.o "
+ ."insert.o\n";
+
+if (&compare_output($answer,&get_logfile(1)))
+{
+ unlink @files_to_touch;
+}
+
+# TEST 2: after vpath lookup ensure we don't get incorrect circular dependency
+# warnings due to change of struct file ptr. Savannah bug #13529.
+
+mkdir('vpath-d', 0777);
+
+run_make_test(q!
+vpath %.te vpath-d/
+.SECONDARY:
+default: vpath-d/a vpath-d/b
+vpath-d/a: fail.te
+vpath-d/b : fail.te
+vpath-d/fail.te:
+!,
+ '', "#MAKE#: Nothing to be done for 'default'.\n");
+
+rmdir('vpath-d');
+
+1;
diff --git a/src/kmk/tests/scripts/features/vpath2 b/src/kmk/tests/scripts/features/vpath2
new file mode 100644
index 0000000..7e970a7
--- /dev/null
+++ b/src/kmk/tests/scripts/features/vpath2
@@ -0,0 +1,45 @@
+$description = "This is part 2 in a series to test the vpath directive\n"
+ ."It tests the three forms of the directive:\n"
+ ." vpath pattern directive\n"
+ ." vpath pattern (clears path associated with pattern)\n"
+ ." vpath (clears all paths specified with vpath)\n";
+
+$details = "This test simply adds many search paths using various vpath\n"
+ ."directive forms and clears them afterwards. It has a simple\n"
+ ."rule to print a message at the end to confirm that the makefile\n"
+ ."ran with no errors.\n";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "VPATH = $workdir:$sourcedir\n";
+print MAKEFILE "vpath %.c foo\n";
+print MAKEFILE "vpath %.c $workdir\n";
+print MAKEFILE "vpath %.c $sourcedir\n";
+print MAKEFILE "vpath %.h $workdir\n";
+print MAKEFILE "vpath %.c\n";
+print MAKEFILE "vpath\n";
+print MAKEFILE "all:\n";
+print MAKEFILE "\t\@echo ALL IS WELL\n";
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "ALL IS WELL\n";
+
+&compare_output($answer,&get_logfile(1));
+
+1;
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/features/vpath3 b/src/kmk/tests/scripts/features/vpath3
new file mode 100644
index 0000000..839fb72
--- /dev/null
+++ b/src/kmk/tests/scripts/features/vpath3
@@ -0,0 +1,41 @@
+# -*-perl-*-
+
+$description = "Test the interaction of the -lfoo feature and vpath";
+$details = "";
+
+my @dirs_to_make = qw(a1 b1 a2 b2 b3);
+for my $d (@dirs_to_make) {
+ mkdir($d, 0777);
+}
+
+my @files_to_touch = ("a1${pathsep}lib1.a",
+ "a1${pathsep}libc.a",
+ "b1${pathsep}lib1.so",
+ "a2${pathsep}lib2.a",
+ "b2${pathsep}lib2.so",
+ "lib3.a",
+ "b3${pathsep}lib3.so");
+&touch(@files_to_touch);
+
+my $answer = "a1${pathsep}lib1.a a1${pathsep}libc.a " .
+ "a2${pathsep}lib2.a lib3.a\n";
+if ($port_type eq 'VMS-DCL') {
+ $answer =~ s/ /,/g;
+}
+
+run_make_test('
+vpath %.h b3
+vpath %.a a1
+vpath %.so b1
+vpath % a2 b2
+vpath % b3
+all: -l1 -lc -l2 -l3; @echo $^
+',
+ '', $answer);
+
+unlink(@files_to_touch);
+for my $d (@dirs_to_make) {
+ rmdir($d);
+}
+
+1;
diff --git a/src/kmk/tests/scripts/features/vpathgpath b/src/kmk/tests/scripts/features/vpathgpath
new file mode 100644
index 0000000..5e6217b
--- /dev/null
+++ b/src/kmk/tests/scripts/features/vpathgpath
@@ -0,0 +1,66 @@
+# -*-perl-*-
+$description = "Tests VPATH+/GPATH functionality.";
+
+$details = "";
+
+$VP = "$workdir$pathsep";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "VPATH = $VP\n";
+
+print MAKEFILE <<'EOMAKE';
+
+GPATH = $(VPATH)
+
+.SUFFIXES: .a .b .c .d
+.PHONY: general rename notarget intermediate
+
+%.a:
+%.b:
+%.c:
+%.d:
+
+%.a : %.b ; cat $^ > $@
+%.b : %.c ; cat $^ > $@
+%.c :: %.d ; cat $^ > $@
+
+# General testing info:
+
+general: foo.b
+foo.b: foo.c bar.c
+
+EOMAKE
+
+close(MAKEFILE);
+
+@touchedfiles = ();
+
+$off = -500;
+
+sub touchfiles {
+ foreach (@_) {
+ ($f = $_) =~ s,VP/,$VP,g;
+ &utouch($off, $f);
+ $off += 10;
+ push(@touchedfiles, $f);
+ }
+}
+
+# Run the general-case test
+
+&touchfiles("VP/foo.d", "VP/bar.d", "VP/foo.c", "VP/bar.c", "foo.b", "bar.d");
+
+&run_make_with_options($makefile,"general",&get_logfile());
+
+push(@touchedfiles, "bar.c");
+
+$answer = "$make_name: Nothing to be done for 'general'.\n";
+
+&compare_output($answer,&get_logfile(1));
+
+unlink(@touchedfiles) unless $keep;
+
+1;
diff --git a/src/kmk/tests/scripts/features/vpathplus b/src/kmk/tests/scripts/features/vpathplus
new file mode 100644
index 0000000..9ade3f0
--- /dev/null
+++ b/src/kmk/tests/scripts/features/vpathplus
@@ -0,0 +1,132 @@
+# -*-perl-*-
+$description = "Tests the new VPATH+ functionality added in 3.76.";
+
+$details = "";
+
+$VP = "$workdir$pathsep";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "VPATH = $VP\n";
+
+print MAKEFILE <<'EOMAKE';
+
+SHELL = /bin/sh
+
+.SUFFIXES: .a .b .c .d
+.PHONY: general rename notarget intermediate
+
+%.a:
+%.b:
+%.c:
+%.d:
+
+%.a : %.b
+ cat $^ > $@
+%.b : %.c
+ cat $^ > $@ 2>/dev/null || exit 1
+%.c :: %.d
+ cat $^ > $@
+
+# General testing info:
+
+general: foo.b
+foo.b: foo.c bar.c
+
+# Rename testing info:
+
+rename: $(VPATH)/foo.c foo.d
+
+# Target not made testing info:
+
+notarget: notarget.b
+notarget.c: notarget.d
+ -@echo "not creating $@ from $^"
+
+# Intermediate files:
+
+intermediate: inter.a
+
+EOMAKE
+
+close(MAKEFILE);
+
+@touchedfiles = ();
+
+$off = -500;
+
+sub touchfiles {
+ foreach (@_) {
+ &utouch($off, $_);
+ $off += 10;
+ push(@touchedfiles, $_);
+ }
+}
+
+# Run the general-case test
+
+&touchfiles("$VP/foo.d", "$VP/bar.d", "$VP/foo.c", "$VP/bar.c", "foo.b", "bar.d");
+
+&run_make_with_options($makefile,"general",&get_logfile);
+
+push(@touchedfiles, "bar.c");
+
+$answer = "cat bar.d > bar.c
+cat ${VP}foo.c bar.c > foo.b 2>/dev/null || exit 1
+";
+&compare_output($answer,&get_logfile(1));
+
+# Test rules that don't make the target correctly
+
+&touchfiles("$VP/notarget.c", "notarget.b", "notarget.d");
+
+&run_make_with_options($makefile,"notarget",&get_logfile,512);
+
+$answer = "not creating notarget.c from notarget.d
+cat notarget.c > notarget.b 2>/dev/null || exit 1
+$make_name: *** [$makefile:16: notarget.b] Error 1
+";
+
+&compare_output($answer,&get_logfile(1));
+
+# Test intermediate file handling (part 1)
+
+&touchfiles("$VP/inter.d");
+
+&run_make_with_options($makefile,"intermediate",&get_logfile);
+
+push(@touchedfiles, "inter.a", "inter.b");
+
+$answer = "cat ${VP}inter.d > inter.c
+cat inter.c > inter.b 2>/dev/null || exit 1
+cat inter.b > inter.a
+rm inter.b inter.c
+";
+&compare_output($answer,&get_logfile(1));
+
+# Test intermediate file handling (part 2)
+
+&utouch(-20, "inter.a");
+&utouch(-10, "$VP/inter.b");
+&touch("$VP/inter.d");
+
+push(@touchedfiles, "$VP/inter.b", "$VP/inter.d");
+
+&run_make_with_options($makefile,"intermediate",&get_logfile);
+
+$answer = "cat ${VP}inter.d > inter.c
+cat inter.c > inter.b 2>/dev/null || exit 1
+cat inter.b > inter.a
+rm inter.c
+";
+&compare_output($answer,&get_logfile(1));
+
+unlink @touchedfiles unless $keep;
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/abspath b/src/kmk/tests/scripts/functions/abspath
new file mode 100644
index 0000000..84c30ab
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/abspath
@@ -0,0 +1,81 @@
+# -*-perl-*-
+$description = "Test the abspath functions.";
+
+$details = "";
+
+run_make_test('
+ifneq ($(realpath $(abspath .)),$(CURDIR))
+ $(warning .: abs="$(abspath .)" real="$(realpath $(abspath .))" curdir="$(CURDIR)")
+endif
+
+ifneq ($(realpath $(abspath ./)),$(CURDIR))
+ $(warning ./: abs="$(abspath ./)" real="$(realpath $(abspath ./))" curdir="$(CURDIR)")
+endif
+
+ifneq ($(realpath $(abspath .///)),$(CURDIR))
+ $(warning .///: abs="$(abspath .///)" real="$(realpath $(abspath .///))" curdir="$(CURDIR)")
+endif
+
+ifneq ($(abspath /),/)
+ $(warning /: abspath="$(abspath /)")
+endif
+
+ifneq ($(abspath ///),/)
+ $(warning ///: abspath="$(abspath ///)")
+endif
+
+ifneq ($(abspath /.),/)
+ $(warning /.: abspath="$(abspath /.)")
+endif
+
+ifneq ($(abspath ///.),/)
+ $(warning ///.: abspath="$(abspath ///.)")
+endif
+
+ifneq ($(abspath /./),/)
+ $(warning /./: abspath="$(abspath /./)")
+endif
+
+ifneq ($(abspath /.///),/)
+ $(warning /.///: abspath="$(abspath /.///)")
+endif
+
+ifneq ($(abspath /..),/)
+ $(warning /..: abspath="$(abspath /..)")
+endif
+
+ifneq ($(abspath ///..),/)
+ $(warning ///..: abspath="$(abspath ///..)")
+endif
+
+ifneq ($(abspath /../),/)
+ $(warning /../: abspath="$(abspath /../)")
+endif
+
+ifneq ($(abspath /..///),/)
+ $(warning /..///: abspath="$(abspath /..///)")
+endif
+
+
+ifneq ($(abspath /foo/bar/..),/foo)
+ $(warning /foo/bar/..: abspath="$(abspath /foo/bar/..)")
+endif
+
+ifneq ($(abspath /foo/bar/../../../baz),/baz)
+ $(warning /foo/bar/../../../baz: abspath="$(abspath /foo/bar/../../../baz)")
+endif
+
+ifneq ($(abspath /foo/bar/../ /..),/foo /)
+ $(warning /foo/bar/../ /..: abspath="$(abspath /foo/bar/../ /..)")
+endif
+
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/functions/addprefix b/src/kmk/tests/scripts/functions/addprefix
new file mode 100644
index 0000000..1845552
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/addprefix
@@ -0,0 +1,44 @@
+$description = "The following test creates a makefile to test the addprefix "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(addprefix src${pathsep},a.b.z.foo hacks) \n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "src${pathsep}a.b.z.foo src${pathsep}hacks\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/addsuffix b/src/kmk/tests/scripts/functions/addsuffix
new file mode 100644
index 0000000..da4fbb7
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/addsuffix
@@ -0,0 +1,36 @@
+# -*-perl-*-
+$description = "Test the addsuffix function.";
+
+$details = "";
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<EOMAKE;
+string := \$(addsuffix .c,src${pathsep}a.b.z.foo hacks)
+one: ; \@echo \$(string)
+
+two: ; \@echo \$(addsuffix foo,)
+EOMAKE
+
+close(MAKEFILE);
+
+
+# TEST 0
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "src${pathsep}a.b.z.foo.c hacks.c\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST 1
+
+&run_make_with_options($makefile, "two", &get_logfile);
+$answer = "\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/functions/andor b/src/kmk/tests/scripts/functions/andor
new file mode 100644
index 0000000..62e0c2e
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/andor
@@ -0,0 +1,50 @@
+# -*-perl-*-
+$description = "Test the and & or functions.\n";
+
+$details = "Try various uses of and & or to ensure they all give the correct
+results.\n";
+
+# TEST #0
+# For $(and ...), it will either be empty or the last value
+run_make_test('
+NEQ = $(subst $1,,$2)
+f =
+t = true
+
+all:
+ @echo 1 $(and ,$t)
+ @echo 2 $(and $t)
+ @echo 3 $(and $t,)
+ @echo 4 $(and z,true,$f,false)
+ @echo 5 $(and $t,$f,$(info bad short-circuit))
+ @echo 6 $(and $(call NEQ,a,b),true)
+ @echo 7 $(and $(call NEQ,a,a),true)
+ @echo 8 $(and z,true,fal,se) hi
+ @echo 9 $(and ,true,fal,se)there
+ @echo 10 $(and $(e) ,$t)',
+ '',
+ "1\n2 true\n3\n4\n5\n6 true\n7\n8 se hi\n9 there\n10\n");
+
+# TEST #1
+# For $(or ...), it will either be empty or the first true value
+run_make_test('
+NEQ = $(subst $1,,$2)
+f =
+t = true
+
+all:
+ @echo 1 $(or , )
+ @echo 2 $(or $t)
+ @echo 3 $(or ,$t)
+ @echo 4 $(or z,true,$f,false)
+ @echo 5 $(or $t,$(info bad short-circuit))
+ @echo 6 $(or $(info short-circuit),$t)
+ @echo 7 $(or $(call NEQ,a,b),true)
+ @echo 8 $(or $(call NEQ,a,a),true)
+ @echo 9 $(or z,true,fal,se) hi
+ @echo 10 $(or ,true,fal,se)there
+ @echo 11 $(or $(e) ,$f)',
+ '',
+ "short-circuit\n1\n2 true\n3 true\n4 z\n5 true\n6 true\n7 b\n8 true\n9 z hi\n10 truethere\n11\n");
+
+1;
diff --git a/src/kmk/tests/scripts/functions/basename b/src/kmk/tests/scripts/functions/basename
new file mode 100644
index 0000000..08f2ea5
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/basename
@@ -0,0 +1,44 @@
+$description = "The following test creates a makefile to test the suffix "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(basename src${pathsep}a.b.z.foo.c src${pathsep}hacks src.bar${pathsep}a.b.z.foo.c src.bar${pathsep}hacks hacks) \n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "src${pathsep}a.b.z.foo src${pathsep}hacks src.bar${pathsep}a.b.z.foo src.bar${pathsep}hacks hacks\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/call b/src/kmk/tests/scripts/functions/call
new file mode 100644
index 0000000..dc1a623
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/call
@@ -0,0 +1,92 @@
+# -*-perl-*-
+$description = "Test the call function.\n";
+
+$details = "Try various uses of call and ensure they all give the correct
+results.\n";
+
+run_make_test(q!
+# Simple, just reverse two things
+#
+reverse = $2 $1
+
+# A complex 'map' function, using recursive 'call'.
+#
+map = $(foreach a,$2,$(call $1,$a))
+
+# Test using a builtin; this is silly as it's simpler to do without call
+#
+my-notdir = $(call notdir,$(1))
+
+# Test using non-expanded builtins
+#
+my-foreach = $(foreach $(1),$(2),$(3))
+my-if = $(if $(1),$(2),$(3))
+
+# Test recursive invocations of call with different arguments
+#
+one = $(1) $(2) $(3)
+two = $(call one,$(1),foo,$(2))
+
+# Test recursion on the user-defined function. As a special case make
+# won't error due to this.
+# Implement transitive closure using $(call ...)
+#
+DEP_foo = bar baz quux
+DEP_baz = quux blarp
+rest = $(wordlist 2,$(words ${1}),${1})
+tclose = $(if $1,$(firstword $1)\
+ $(call tclose,$(sort ${DEP_$(firstword $1)} $(call rest,$1))))
+
+all: ; @echo '$(call reverse,bar,foo)'; \
+ echo '$(call map,origin,MAKE reverse map)'; \
+ echo '$(call my-notdir,a/b c/d e/f)'; \
+ echo '$(call my-foreach)'; \
+ echo '$(call my-foreach,a,,,)'; \
+ echo '$(call my-if,a,b,c)'; \
+ echo '$(call two,bar,baz)'; \
+ echo '$(call tclose,foo)';
+!,
+ "", "foo bar\ndefault file file\nb d f\n\n\nb\nbar foo baz\nfoo bar baz blarp quux \n");
+
+# These won't work because call expands all its arguments first, before
+# passing them on, then marks them as resolved/simple, so they're not
+# expanded again by the function.
+#
+# echo '$(call my-foreach,a,x y z,$$(a)$$(a))'; \
+# echo '$(call my-if,,$$(info don't print this),$$(info do print this))'
+#
+# $answer = "xx yy zz\ndo print this\n";
+
+# TEST eclipsing of arguments when invoking sub-calls
+
+run_make_test(q!
+all = $1 $2 $3 $4 $5 $6 $7 $8 $9
+
+level1 = $(call all,$1,$2,$3,$4,$5)
+level2 = $(call level1,$1,$2,$3)
+level3 = $(call level2,$1,$2,$3,$4,$5)
+
+all:
+ @echo $(call all,1,2,3,4,5,6,7,8,9,10,11)
+ @echo $(call level1,1,2,3,4,5,6,7,8)
+ @echo $(call level2,1,2,3,4,5,6,7,8)
+ @echo $(call level3,1,2,3,4,5,6,7,8)
+!,
+ "", "1 2 3 4 5 6 7 8 9\n1 2 3 4 5\n1 2 3\n1 2 3\n");
+
+# Ensure that variables are defined in global scope even in a $(call ...)
+
+delete $ENV{X123};
+
+run_make_test('
+tst = $(eval export X123)
+$(call tst)
+all: ; @echo "$${X123-not set}"
+',
+ '', "\n");
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/dir b/src/kmk/tests/scripts/functions/dir
new file mode 100644
index 0000000..f48fb8c
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/dir
@@ -0,0 +1,44 @@
+$description = "The following test creates a makefile to test the dir "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(dir src${pathsep}foo.c hacks) \n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "src${pathsep} .${pathsep}\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/error b/src/kmk/tests/scripts/functions/error
new file mode 100644
index 0000000..998afe4
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/error
@@ -0,0 +1,71 @@
+# -*-Perl-*-
+
+$description = "\
+The following test creates a makefile to test the error function.";
+
+$details = "";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE 'err = $(error Error found!)
+
+ifdef ERROR1
+$(error error is $(ERROR1))
+endif
+
+ifdef ERROR2
+$(error error is $(ERROR2))
+endif
+
+ifdef ERROR3
+all: some; @echo $(error error is $(ERROR3))
+endif
+
+ifdef ERROR4
+all: some; @echo error is $(ERROR4)
+ @echo $(error error is $(ERROR4))
+endif
+
+some: ; @echo Some stuff
+
+testvar: ; @: $(err)
+';
+
+close(MAKEFILE);
+
+# Test #1
+
+&run_make_with_options($makefile, "ERROR1=yes", &get_logfile, 512);
+$answer = "$makefile:4: *** error is yes. Stop.\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #2
+
+&run_make_with_options($makefile, "ERROR2=no", &get_logfile, 512);
+$answer = "$makefile:8: *** error is no. Stop.\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #3
+
+&run_make_with_options($makefile, "ERROR3=maybe", &get_logfile, 512);
+$answer = "Some stuff\n$makefile:12: *** error is maybe. Stop.\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #4
+
+&run_make_with_options($makefile, "ERROR4=definitely", &get_logfile, 512);
+$answer = "Some stuff\n$makefile:17: *** error is definitely. Stop.\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #5
+
+&run_make_with_options($makefile, "testvar", &get_logfile, 512);
+$answer = "$makefile:22: *** Error found!. Stop.\n";
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/eval b/src/kmk/tests/scripts/functions/eval
new file mode 100644
index 0000000..90513bd
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/eval
@@ -0,0 +1,169 @@
+# -*-perl-*-
+
+$description = "Test the eval function.";
+
+$details = "This is a test of the eval function in GNU make.
+This function will evaluate inline makefile syntax and incorporate the
+results into its internal database.\n";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+define Y
+ all:: ; @echo $AA
+ A = B
+endef
+
+X = $(eval $(value Y))
+
+$(eval $(shell echo A = A))
+$(eval $(Y))
+$(eval A = C)
+$(eval $(X))
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile, "", &get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "AA\nBA\n";
+
+&compare_output($answer,&get_logfile(1));
+
+# Test to make sure defining variables when we have extra scope pushed works
+# as expected.
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile2");
+
+print MAKEFILE <<'EOF';
+VARS = A B
+
+VARSET = $(1) = $(2)
+
+$(foreach v,$(VARS),$(eval $(call VARSET,$v,$v)))
+
+all: ; @echo A = $(A) B = $(B)
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile2, "", &get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "A = A B = B\n";
+
+&compare_output($answer,&get_logfile(1));
+
+# Test to make sure eval'ing inside conditionals works properly
+
+$makefile3 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile3");
+
+print MAKEFILE <<'EOF';
+FOO = foo
+
+all:: ; @echo it
+
+define Y
+ all:: ; @echo worked
+endef
+
+ifdef BAR
+$(eval $(Y))
+endif
+
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile3, "", &get_logfile);
+$answer = "it\n";
+&compare_output($answer,&get_logfile(1));
+
+&run_make_with_options($makefile3, "BAR=1", &get_logfile);
+$answer = "it\nworked\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST very recursive invocation of eval
+
+$makefile3 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile3");
+
+print MAKEFILE <<'EOF';
+..9 := 0 1 2 3 4 5 6 7 8 9
+rev=$(eval res:=)$(foreach word,$1,$(eval res:=${word} ${res}))${res}
+a:=$(call rev,${..9})
+all: ; @echo '[$(a)]'
+
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile3, "", &get_logfile);
+$answer = "[ 9 8 7 6 5 4 3 2 1 0 ]\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST eval with no filename context.
+# The trick here is that because EVAR is taken from the environment, it must
+# be evaluated before every command is invoked. Make sure that works, when
+# we have no file context for reading_file (bug # 6195)
+
+$makefile4 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile4");
+
+print MAKEFILE <<'EOF';
+EVAR = $(eval FOBAR = 1)
+all: ; @echo "OK"
+
+EOF
+
+close(MAKEFILE);
+
+$extraENV{EVAR} = '1';
+&run_make_with_options($makefile4, "", &get_logfile);
+$answer = "OK\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# Clean out previous information to allow new run_make_test() interface.
+# If we ever convert all the above to run_make_test() we can remove this line.
+$makefile = undef;
+
+# Test handling of backslashes in strings to be evaled.
+
+run_make_test('
+define FOO
+all: ; @echo hello \
+world
+endef
+$(eval $(FOO))
+', '', 'hello world');
+
+run_make_test('
+define FOO
+all: ; @echo '."'".'he\llo'."'".'
+ @echo world
+endef
+$(eval $(FOO))
+', '', 'he\llo
+world');
+
+
+# We don't allow new target/prerequisite relationships to be defined within a
+# command script, because these are evaluated after snap_deps() and that
+# causes lots of problems (like core dumps!)
+# See Savannah bug # 12124.
+
+run_make_test('deps: ; $(eval deps: foo)', '',
+ '#MAKEFILE#:1: *** prerequisites cannot be defined in recipes. Stop.',
+ 512);
+
+1;
diff --git a/src/kmk/tests/scripts/functions/evalcall b/src/kmk/tests/scripts/functions/evalcall
new file mode 100644
index 0000000..f0213c2
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/evalcall
@@ -0,0 +1,119 @@
+# $Id: evalcall 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(evalcall var,argN...)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(evalcall ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+FUNC = local .RETURN = $2 $1
+ifneq ($(evalcall FUNC,a,b),b a)
+$(error sub-test 0 failed: $(evalcall FUNC,a,b))
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+
+FUNC = local .RETURN = $2 $1
+ifneq ($(evalcall FUNC,a,b),b a)
+$(error sub-test 0 failed)
+endif
+
+ADD = local .RETURN = $(expr $1 + $2)
+ifneq ($(evalcall ADD,1,2),3)
+$(error sub-test 1 failed)
+endif
+
+define POP
+local words := $(words $($1))
+local .RETURN := $(word $(words), $($1))
+$1 := $(wordlist 1, $(expr $(words) - 1), $($1))
+endef
+stack-var = a b c d
+ifneq ($(evalcall POP,stack-var),d)
+$(error sub-test 2d failed)
+endif
+ifneq ($(evalcall POP,stack-var),c)
+$(error sub-test 2c failed)
+endif
+ifneq ($(evalcall POP,stack-var),b)
+$(error sub-test 2b failed)
+endif
+ifneq ($(evalcall POP,stack-var),a)
+$(error sub-test 2a failed)
+endif
+
+
+# Negative tests:
+
+.RETURN = $2 $1
+FUNC =
+ifneq ($(evalcall FUNC,a,b),)
+$(error sub-test 10 failed)
+endif
+
+.RETURN =
+FUNC = .RETURN = $2 $1
+ifneq ($(evalcall FUNC,a,b),)
+$(error sub-test 11 failed)
+endif
+
+
+# Test .ARGC:
+
+FUNC = local .RETURN = $(.ARGC)
+ifneq ($(evalcall FUNC,a,b),2)
+$(error sub-test 20 failed)
+endif
+ifneq ($(evalcall FUNC),0)
+$(error sub-test 21 failed)
+endif
+ifneq ($(evalcall FUNC,aasdfasdf),1)
+$(error sub-test 22 failed)
+endif
+
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/expr b/src/kmk/tests/scripts/functions/expr
new file mode 100644
index 0000000..a68205f
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/expr
@@ -0,0 +1,74 @@
+# $Id: expr 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(expr expr)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the \$(expr ) function";
+
+$details = "Much of the basic testing is taken care of by features/ifcond.
+We only make sure \$(expr ) works here).";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(expr 1+1),2)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - basics, the $(expr test checks the rest).
+ # ---------------------------------------------------
+ run_make_test('
+ifneq ($(expr 1==1),1)
+$(error sub-test 0 failed)
+endif
+ifneq ($(expr 1!=1),0)
+$(error sub-test 1 failed)
+endif
+ifneq ($(expr 2*2),4)
+$(error sub-test 1 failed)
+endif
+ifneq ($(expr 25*25),625)
+$(error sub-test 1 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+}
+
+
+
+# Indicate that we're done.
+1;
+
+
diff --git a/src/kmk/tests/scripts/functions/file b/src/kmk/tests/scripts/functions/file
new file mode 100644
index 0000000..904db79
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/file
@@ -0,0 +1,161 @@
+# -*-perl-*-
+
+$description = 'Test the $(file ...) function.';
+
+# Test > and >>
+run_make_test(q!
+define A
+a
+b
+endef
+B = c d
+$(file >file.out,$(A))
+$(foreach L,$(B),$(file >> file.out,$L))
+x:;@echo hi; cat file.out
+!,
+ '', "hi\na\nb\nc\nd");
+
+unlink('file.out');
+
+# Test >> to a non-existent file
+run_make_test(q!
+define A
+a
+b
+endef
+$(file >> file.out,$(A))
+x:;@cat file.out
+!,
+ '', "a\nb");
+
+unlink('file.out');
+
+# Test > with no content
+run_make_test(q!
+$(file >4touch)
+.PHONY:x
+x:;@cat 4touch
+!,
+ '', '');
+
+# Test >> with no content
+run_make_test(q!
+$(file >>4touch)
+.PHONY:x
+x:;@cat 4touch
+!,
+ '', '');
+unlink('4touch');
+
+# Test > to a read-only file
+touch('file.out');
+chmod(0444, 'file.out');
+
+# Find the error that will be printed
+# This seems complicated, but we need the message from the C locale
+my $loc = undef;
+if ($has_POSIX) {
+ $loc = POSIX::setlocale(POSIX::LC_MESSAGES);
+ POSIX::setlocale(POSIX::LC_MESSAGES, 'C');
+}
+my $e;
+open(my $F, '>', 'file.out') and die "Opened read-only file!\n";
+$e = "$!";
+$loc and POSIX::setlocale(POSIX::LC_MESSAGES, $loc);
+
+run_make_test(q!
+define A
+a
+b
+endef
+$(file > file.out,$(A))
+x:;@cat file.out
+!,
+ '', "#MAKEFILE#:6: *** open: file.out: $e. Stop.",
+ 512);
+
+unlink('file.out');
+
+# Use variables for operator and filename
+run_make_test(q!
+define A
+a
+b
+endef
+OP = >
+FN = file.out
+$(file $(OP) $(FN),$(A))
+x:;@cat file.out
+!,
+ '', "a\nb");
+
+unlink('file.out');
+
+# Don't add newlines if one already exists
+run_make_test(q!
+define A
+a
+b
+
+endef
+$(file >file.out,$(A))
+x:;@cat file.out
+!,
+ '', "a\nb");
+
+unlink('file.out');
+
+# Empty text
+run_make_test(q!
+$(file >file.out,)
+$(file >>file.out,)
+x:;@cat file.out
+!,
+ '', "\n\n");
+
+unlink('file.out');
+
+# Reading files
+run_make_test(q!
+$(file >file.out,A = foo)
+X1 := $(file <file.out)
+$(file >>file.out,B = bar)
+$(eval $(file <file.out))
+
+x:;@echo '$(X1)'; echo '$(A)'; echo '$(B)'
+!,
+ '', "A = foo\nfoo\nbar\n");
+
+unlink('file.out');
+
+# Reading from non-existent file
+run_make_test(q!
+X1 := $(file <file.out)
+x:;@echo '$(X1)';
+!,
+ '', "\n");
+
+# Extra arguments in read mode
+run_make_test(q!
+X1 := $(file <file.out,foo)
+x:;@echo '$(X1)';
+!,
+ '', "#MAKEFILE#:2: *** file: too many arguments. Stop.\n", 512);
+
+
+# Missing filename
+run_make_test('$(file >)', '',
+ "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512);
+
+run_make_test('$(file >>)', '',
+ "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512);
+
+run_make_test('$(file <)', '',
+ "#MAKEFILE#:1: *** file: missing filename. Stop.\n", 512);
+
+# Bad call
+
+run_make_test('$(file foo)', '',
+ "#MAKEFILE#:1: *** file: invalid file operation: foo. Stop.\n", 512);
+
+1;
diff --git a/src/kmk/tests/scripts/functions/filter-out b/src/kmk/tests/scripts/functions/filter-out
new file mode 100644
index 0000000..1fe4819
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/filter-out
@@ -0,0 +1,42 @@
+# -*-perl-*-
+
+$description = "Test the filter and filter-out functions.";
+
+$details = "The makefile created in this test has two variables. The
+filter-out function is first used to discard names ending in
+.o with a single simple pattern. The second filter-out function
+augments the simple pattern with three literal names, which are
+also added to the text argument. This tests an internal hash table
+which is only used if there are multiple literals present in both
+the pattern and text arguments. The result of both filter-out
+functions is the same single .elc name.\n";
+
+# Basic test -- filter
+run_make_test(q!
+files1 := $(filter %.o, foo.elc bar.o lose.o)
+files2 := $(filter %.o foo.i, foo.i bar.i lose.i foo.elc bar.o lose.o)
+all: ; @echo '$(files1) $(files2)'
+!,
+ '', "bar.o lose.o foo.i bar.o lose.o\n");
+
+# Basic test -- filter-out
+run_make_test(q!
+files1 := $(filter-out %.o, foo.elc bar.o lose.o)
+files2 := $(filter-out foo.i bar.i lose.i %.o, foo.i bar.i lose.i foo.elc bar.o lose.o)
+all: ; @echo '$(files1) $(files2)'
+!,
+ '', "foo.elc foo.elc\n");
+
+# Escaped patterns
+run_make_test(q!all:;@echo '$(filter foo\%bar,foo%bar fooXbar)'!,
+ '', "foo%bar\n");
+
+run_make_test(q!all:;@echo '$(filter foo\%\%\\\\\%\%bar,foo%%\\%%bar fooX\\Ybar)'!,
+ '', "foo%%\\%%bar\n");
+
+run_make_test(q!
+X = $(filter foo\\\\\%bar,foo\%bar foo\Xbar)
+all:;@echo '$(X)'!,
+ '', "foo\\%bar\n");
+
+1;
diff --git a/src/kmk/tests/scripts/functions/findstring b/src/kmk/tests/scripts/functions/findstring
new file mode 100644
index 0000000..48abede
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/findstring
@@ -0,0 +1,47 @@
+$description = "The following test creates a makefile to test the findstring "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(findstring port, reporter)\n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,
+ "",
+ &get_logfile,
+ 0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "port\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/flavor b/src/kmk/tests/scripts/functions/flavor
new file mode 100644
index 0000000..80d6be7
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/flavor
@@ -0,0 +1,44 @@
+# -*-perl-*-
+$description = "Test the flavor function.";
+
+$details = "";
+
+
+# Test #1: Test general logic.
+#
+run_make_test('
+s := s
+r = r
+
+$(info u $(flavor u))
+$(info s $(flavor s))
+$(info r $(flavor r))
+
+ra += ra
+rc ?= rc
+
+$(info ra $(flavor ra))
+$(info rc $(flavor rc))
+
+s += s
+r += r
+
+$(info s $(flavor s))
+$(info r $(flavor r))
+
+
+.PHONY: all
+all:;@:
+',
+'',
+'u undefined
+s simple
+r recursive
+ra recursive
+rc recursive
+s simple
+r recursive');
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/functions/for b/src/kmk/tests/scripts/functions/for
new file mode 100644
index 0000000..0152395
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/for
@@ -0,0 +1,69 @@
+# $Id: for 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(for init,condition,next,body)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(for ) loop function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(for local i=0, $i <= 10, local i := $(expr $i + 1),$i),0 1 2 3 4 5 6 7 8 9 10)
+$(error sub-test 0 failed:$(for local i=0, $i <= 10, local i := $(expr $i + 1),$i))
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(for local i=0, $i <= 10, local i := $(expr $i + 1),$i),0 1 2 3 4 5 6 7 8 9 10)
+$(error sub-test 0 failed)
+endif
+ifneq (.$(for local i=0, $i <= 3, local i := $(expr $i + 1), $i ).,. 0 1 2 3 .)
+$(error sub-test 1 failed)
+endif
+ifneq (.$(foreach i,0 1 2 3, $i ).,. 0 1 2 3 .)
+$(error sub-test 1b failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/foreach b/src/kmk/tests/scripts/functions/foreach
new file mode 100644
index 0000000..88ef0a7
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/foreach
@@ -0,0 +1,97 @@
+# -*-perl-*-
+# $Id$
+
+$description = "Test the foreach function.";
+
+$details = "This is a test of the foreach function in gnu make.
+This function starts with a space separated list of
+names and a variable. Each name in the list is subsituted
+into the variable and the given text evaluated. The general
+form of the command is $(foreach var,$list,$text). Several
+types of foreach loops are tested\n";
+
+
+# TEST 0
+
+# Set an environment variable that we can test in the makefile.
+# kmk: CC isn't a default.
+$extraENV{FOOFOO} = 'foo foo';
+$CC_origin = $is_kmk ? "undefined" : "default";
+
+run_make_test("space = ' '".'
+null :=
+auto_var = udef space CC null FOOFOO MAKE foo CFLAGS WHITE @ <
+foo = bletch null @ garf
+av = $(foreach var, $(auto_var), $(origin $(var)) )
+override WHITE := BLACK
+for_var = $(addsuffix .c,foo $(null) $(foo) $(space) $(av) )
+fe = $(foreach var2, $(for_var),$(subst .c,.o, $(var2) ) )
+all: auto for2
+auto : ; @echo $(av)
+for2: ; @echo $(fe)',
+ '-j1 -e WHITE=WHITE CFLAGS=',
+ "undefined file ". $CC_origin ." file environment default file command line override automatic automatic
+foo.o bletch.o null.o @.o garf.o .o .o undefined.o file.o ". $CC_origin .".o file.o environment.o default.o file.o command.o line.o override.o automatic.o automatic.o");
+
+delete $extraENV{FOOFOO};
+
+# TEST 1: Test that foreach variables take precedence over global
+# variables in a global scope (like inside an eval). Tests bug #11913
+
+run_make_test('
+.PHONY: all target
+all: target
+
+x := BAD
+
+define mktarget
+target: x := $(x)
+target: ; @echo "$(x)"
+endef
+
+x := GLOBAL
+
+$(foreach x,FOREACH,$(eval $(value mktarget)))',
+ '',
+ 'FOREACH');
+
+# Allow variable names with trailing space
+run_make_test(q!
+$(foreach \
+ a \
+, b c d \
+, $(info $a))
+all:;@:
+!,
+ "", "b\nc\nd\n");
+
+# Allow empty variable names. We still expand the body.
+
+run_make_test('
+x = $(foreach ,1 2 3,a)
+y := $x
+
+all: ; @echo $y',
+ '', "a a a\n");
+
+# Check some error conditions.
+
+run_make_test('
+x = $(foreach )
+y = $x
+
+all: ; @echo $y',
+ '',
+ "#MAKEFILE#:2: *** insufficient number of arguments (1) to function 'foreach'. Stop.",
+ 512);
+
+run_make_test('
+x = $(foreach x,y)
+y := $x
+
+all: ; @echo $y',
+ '',
+ "#MAKEFILE#:2: *** insufficient number of arguments (2) to function 'foreach'. Stop.",
+ 512);
+
+1;
diff --git a/src/kmk/tests/scripts/functions/guile b/src/kmk/tests/scripts/functions/guile
new file mode 100644
index 0000000..c63bec9
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/guile
@@ -0,0 +1,99 @@
+# -*-perl-*-
+
+$description = 'Test the $(guile ...) function.';
+
+$details = 'This only works on systems that support it.';
+
+# If this instance of make doesn't support GNU Guile, skip it
+# This detects if guile is loaded using the "load" directive
+# $makefile = get_tmpfile();
+# open(MAKEFILE, "> $makefile") || die "Failed to open $makefile: $!\n";
+# print MAKEFILE q!
+# -load guile
+# all: ; @echo $(filter guile,$(.LOADED))
+# !;
+# close(MAKEFILE) || die "Failed to write $makefile: $!\n";
+# $cmd = subst_make_string("#MAKEPATH# -f $makefile");
+# $log = get_logfile(0);
+# $code = run_command_with_output($log, $cmd);
+# read_file_into_string ($log) eq "guile\n" and $FEATURES{guile} = 1;
+
+# If we don't have Guile support, never mind.
+exists $FEATURES{guile} or return -1;
+
+# Verify simple data type conversions
+# Currently we don't support vectors:
+# echo '$(guile (vector 1 2 3))'; \
+run_make_test(q!
+x:;@echo '$(guile #f)'; \
+ echo '$(guile #t)'; \
+ echo '$(guile #\c)'; \
+ echo '$(guile 1234)'; \
+ echo '$(guile 'foo)'; \
+ echo '$(guile "bar")'; \
+ echo '$(guile (cons 'a 'b))'; \
+ echo '$(guile '(a b (c . d) 1 (2) 3))'
+!,
+ '', "\n#t\nc\n1234\nfoo\nbar\na b\na b c d 1 2 3");
+
+# Verify the gmk-expand function
+run_make_test(q!
+VAR = $(guile (gmk-expand "$(shell echo hi)"))
+x:;@echo '$(VAR)'
+!,
+ '', "hi");
+
+# Verify the gmk-eval function
+# Prove that the string is expanded only once (by eval)
+run_make_test(q!
+TEST = bye
+EVAL = VAR = $(TEST) $(shell echo there)
+$(guile (gmk-eval "$(value EVAL)"))
+TEST = hi
+x:;@echo '$(VAR)'
+!,
+ '', "hi there");
+
+# Verify the gmk-eval function with a list
+run_make_test(q!
+$(guile (gmk-eval '(VAR = 1 (2) () 3)))
+x:;@echo '$(VAR)'
+!,
+ '', "1 2 3");
+
+# Verify the gmk-var function
+run_make_test(q!
+VALUE = hi $(shell echo there)
+VAR = $(guile (gmk-var "VALUE"))
+x:;@echo '$(VAR)'
+!,
+ '', "hi there");
+
+# Verify the gmk-var function with a symbol
+run_make_test(q!
+VALUE = hi $(shell echo there)
+VAR = $(guile (gmk-var 'VALUE))
+x:;@echo '$(VAR)'
+!,
+ '', "hi there");
+
+# Write a Guile program using define and run it
+run_make_test(q!
+# Define the "fib" function in Guile
+define fib
+;; A procedure for counting the n:th Fibonacci number
+;; See SICP, p. 37
+(define (fib n)
+ (cond ((= n 0) 0)
+ ((= n 1) 1)
+ (else (+ (fib (- n 1))
+ (fib (- n 2))))))
+endef
+$(guile $(fib))
+
+# Now run it
+x:;@echo $(guile (fib $(FIB)))
+!,
+ 'FIB=10', "55");
+
+1;
diff --git a/src/kmk/tests/scripts/functions/if b/src/kmk/tests/scripts/functions/if
new file mode 100644
index 0000000..8604e4f
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/if
@@ -0,0 +1,33 @@
+# -*-perl-*-
+$description = "Test the if function.\n";
+
+$details = "Try various uses of if and ensure they all give the correct
+results.\n";
+
+open(MAKEFILE, "> $makefile");
+
+print MAKEFILE <<EOMAKE;
+NEQ = \$(subst \$1,,\$2)
+e =
+
+all:
+\t\@echo 1 \$(if ,true,false)
+\t\@echo 2 \$(if ,true,)
+\t\@echo 3 \$(if ,true)
+\t\@echo 4 \$(if z,true,false)
+\t\@echo 5 \$(if z,true,\$(shell echo hi))
+\t\@echo 6 \$(if ,\$(shell echo hi),false)
+\t\@echo 7 \$(if \$(call NEQ,a,b),true,false)
+\t\@echo 8 \$(if \$(call NEQ,a,a),true,false)
+\t\@echo 9 \$(if z,true,fal,se) hi
+\t\@echo 10 \$(if ,true,fal,se)there
+\t\@echo 11 \$(if \$(e) ,true,false)
+EOMAKE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "1 false\n2\n3\n4 true\n5 true\n6 false\n7 true\n8 false\n9 true hi\n10 fal,sethere\n11 false\n";
+&compare_output($answer, &get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/functions/if-expr b/src/kmk/tests/scripts/functions/if-expr
new file mode 100644
index 0000000..764522d
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/if-expr
@@ -0,0 +1,84 @@
+# $Id: if-expr 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(if-expr expr, if-expand, else-expand)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the \$(if-expr ) function";
+
+$details = "A few simple tests, nothing spectacular. More comprehensive testing
+is preformed by functions/expr and features/ifcond.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(if-expr 1+1,1,0),1)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - basics, the $(expr test checks the rest).
+ # ---------------------------------------------------
+ run_make_test('
+IF-EXPAND = 7
+ELSE-EXPAND = -7
+ifneq ($(if-expr 1==1,$(IF-EXPAND),$(ELSE-EXPAND)),7)
+$(error sub-test 0 failed)
+endif
+ifneq ($(if-expr 1!=1,$(IF-EXPAND),$(ELSE-EXPAND)),-7)
+$(error sub-test 1 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+
+ # TEST #2 - Checks that the optional 3 argument can be omitted.
+ # -------------------------------------------------------------
+ run_make_test('
+ifneq ($(if-expr 1==1,true),true)
+$(error sub-test 0 failed)
+endif
+ifneq ($(if-expr 2==1,true),)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/insert b/src/kmk/tests/scripts/functions/insert
new file mode 100644
index 0000000..6a597c6
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/insert
@@ -0,0 +1,106 @@
+# $Id: insert 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(insert in, str[, n[, length[, pad]]])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(insert ) function";
+
+$details = "Testing edges and some simple stuff.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(insert a,b),ab)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(insert a,b,1),ab)
+$(error sub-test 0 failed)
+endif
+ifneq ($(insert a,b,2),ba)
+$(error sub-test 1 failed)
+endif
+ifneq ($(insert a,b,3),b a)
+$(error sub-test 2 failed)
+endif
+ifneq ($(insert a,b,0),ba)
+$(error sub-test 3 failed)
+endif
+ifneq ($(insert a,b,-1),ab)
+$(error sub-test 4 failed)
+endif
+ifneq ($(insert a,b,-2),ab)
+$(error sub-test 5 failed)
+endif
+ifneq ($(insert a,b,-10),ab)
+$(error sub-test 6 failed)
+endif
+
+ifneq ($(insert a,b,-10,0),b)
+$(error sub-test 10 failed)
+endif
+ifneq ($(insert aAAA,b,4,1),b a)
+$(error sub-test 11 failed)
+endif
+ifneq ($(insert a,bBbBbBb,4,4),bBba BbBb)
+$(error sub-test 12 failed)
+endif
+
+ifneq ($(insert a,bBbBbBb,4,4,z),bBbazzzBbBb)
+$(error sub-test 20 failed)
+endif
+ifneq ($(insert a,bBbBbBb,4,4,xy),bBbaxyxBbBb)
+$(error sub-test 21 failed)
+endif
+ifneq ($(insert a,bBbBbBb,4,4,xyz),bBbaxyzBbBb)
+$(error sub-test 22 failed)
+endif
+ifneq ($(insert a,bBbBbBb,4,4,xyzXYZ),bBbaxyzBbBb)
+$(error sub-test 23 failed)
+endif
+ifneq ($(insert a,bBbBbBb,4,4,),bBba BbBb)
+$(error sub-test 24 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/intersects b/src/kmk/tests/scripts/functions/intersects
new file mode 100644
index 0000000..8d136fb
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/intersects
@@ -0,0 +1,94 @@
+# $Id: intersects 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(intersects set-a,set-b)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(intersecs ) predicate function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(intersects a b c d e f, a),1)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(intersects a b c d e f, f),1)
+$(error sub-test 0 failed)
+endif
+ifneq ($(intersects a b c d e f, f),1)
+$(error sub-test 1 failed)
+endif
+ifneq ($(intersects a b c d e f, d),1)
+$(error sub-test 2 failed)
+endif
+ifneq ($(intersects b c d e f, a),)
+$(error sub-test 3 failed)
+endif
+ifneq ($(intersects a b c d e f, a b c d e f),1)
+$(error sub-test 4 failed)
+endif
+ifneq ($(intersects a b c d e f, f e d c b a),1)
+$(error sub-test 5 failed)
+endif
+ifneq ($(intersects f e d c b a, a b c d e f),1)
+$(error sub-test 6 failed)
+endif
+
+SET-A = make foo bar
+SET-B = $(SET-A)
+ifeq ($(intersects $(SET-A),$(SET-B)),)
+$(error sub-test 7 failed)
+endif
+SET-B = foo
+ifeq ($(intersects $(SET-A),$(SET-B)),)
+$(error sub-test 8 failed)
+endif
+SET-B = foobar
+ifneq ($(intersects $(SET-A),$(SET-B)),)
+$(error sub-test 9 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/join b/src/kmk/tests/scripts/functions/join
new file mode 100644
index 0000000..302c307
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/join
@@ -0,0 +1,44 @@
+$description = "The following test creates a makefile to test the join "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(join a b c,foo hacks .pl1) \n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "afoo bhacks c.pl1\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/lastpos b/src/kmk/tests/scripts/functions/lastpos
new file mode 100644
index 0000000..248db2b
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/lastpos
@@ -0,0 +1,118 @@
+# $Id: lastpos 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(lastpos needle, haystack[, start])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(lastpos ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(lastpos b,abc),2)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(lastpos t,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 0 failed)
+endif
+ifneq ($(lastpos tu,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 1 failed)
+endif
+ifneq ($(lastpos tuv,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 2 failed)
+endif
+ifneq ($(lastpos tuvw,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 3 failed)
+endif
+ifneq ($(lastpos tuvwx,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 4 failed)
+endif
+ifneq ($(lastpos tuvwxy,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 5 failed)
+endif
+ifneq ($(lastpos tuvwxyz,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 6 failed)
+endif
+ifneq ($(lastpos tuvwxyz!,abcdefghijklmnopqrstuvwxyz),0)
+$(error sub-test 7 failed)
+endif
+
+ifneq ($(lastpos a,ababababab),9)
+$(error sub-test 10 failed)
+endif
+ifneq ($(lastpos a,ababababab,8),7)
+$(error sub-test 11 failed)
+endif
+ifneq ($(lastpos a,ababababab,7),7)
+$(error sub-test 12 failed)
+endif
+ifneq ($(lastpos a,ababababab,4),3)
+$(error sub-test 13 failed)
+endif
+ifneq ($(lastpos a,ababababab,3),3)
+$(error sub-test 14 failed)
+endif
+ifneq ($(lastpos a,ababababab,2),1)
+$(error sub-test 15 failed)
+endif
+ifneq ($(lastpos a,ababababab,1),1)
+$(error sub-test 16 failed)
+endif
+ifneq ($(lastpos a,ababababab,-1),9)
+$(error sub-test 17 failed)
+endif
+ifneq ($(lastpos a,ababababab,-2),9)
+$(error sub-test 18 failed)
+endif
+ifneq ($(lastpos a,ababababab,-10),1)
+$(error sub-test 19 failed)
+endif
+ifneq ($(lastpos a,ababababab,-11),0)
+$(error sub-test 20 failed)
+endif
+
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/length b/src/kmk/tests/scripts/functions/length
new file mode 100644
index 0000000..c8ea34d
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/length
@@ -0,0 +1,71 @@
+# $Id: length 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(length text)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(length ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(length abcd),4)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(length asdf),4)
+$(error sub-test 0 failed)
+endif
+ifneq ($(length a),1)
+$(error sub-test 1 failed)
+endif
+ifneq ($(length 0123456789),10)
+$(error sub-test 2 failed)
+endif
+ifneq ($(length 0123456789 ),11)
+$(error sub-test 3 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/length-var b/src/kmk/tests/scripts/functions/length-var
new file mode 100644
index 0000000..0583713
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/length-var
@@ -0,0 +1,75 @@
+# $Id: length-var 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(length-var var)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(length-var ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(length-var non-existing-variable),0)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+VAR0 := asdf
+ifneq ($(length-var VAR0),4)
+$(error sub-test 0 failed)
+endif
+VAR1 = a
+ifneq ($(length-var VAR1),1)
+$(error sub-test 1 failed)
+endif
+VAR2 = 0123456789
+ifneq ($(length-var VAR2),10)
+$(error sub-test 2 failed)
+endif
+VAR2 = $(VAR1) $(VAR0)
+ifneq ($(length-var VAR2),15)
+$(error sub-test 3 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/notdir b/src/kmk/tests/scripts/functions/notdir
new file mode 100644
index 0000000..4ed8f9c
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/notdir
@@ -0,0 +1,44 @@
+$description = "The following test creates a makefile to test the notdir "
+ ."function.";
+
+$details = "";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := \$(notdir ${pathsep}src${pathsep}foo.c hacks) \n"
+ ."all: \n"
+ ."\t\@echo \$(string) \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "foo.c hacks\n";
+
+# COMPARE RESULTS
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/origin b/src/kmk/tests/scripts/functions/origin
new file mode 100644
index 0000000..7a6a9fa
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/origin
@@ -0,0 +1,54 @@
+# -*-perl-*-
+
+$description = "Test the origin function.";
+
+$details = "This is a test of the origin function in gnu make.
+This function will report on where a variable was
+defined per the following list:
+
+'undefined' never defined
+'default' default definition
+'environment' environment var without -e
+'environment override' environment var with -e
+'file' defined in makefile
+'command line' defined on the command line
+'override' defined by override in makefile
+'automatic' Automatic variable\n";
+
+# kmk: CC isn't a default.
+$CC_origin = $is_kmk ? "undefined" : "default";
+
+# Set an environment variable
+$extraENV{MAKETEST} = 1;
+
+run_make_test('
+foo := bletch garf
+auto_var = undefined CC MAKETEST MAKE foo CFLAGS WHITE @
+av = $(foreach var, $(auto_var), $(origin $(var)) )
+override WHITE := BLACK
+all: auto
+ @echo $(origin undefined)
+ @echo $(origin CC)
+ @echo $(origin MAKETEST)
+ @echo $(origin MAKE)
+ @echo $(origin foo)
+ @echo $(origin CFLAGS)
+ @echo $(origin WHITE)
+ @echo $(origin @)
+auto :
+ @echo $(av)',
+ '-e WHITE=WHITE CFLAGS=',
+ 'undefined '. $CC_origin .' environment default file command line override automatic
+undefined
+'. $CC_origin .'
+environment
+default
+file
+command line
+override
+automatic');
+
+# Reset an environment variable
+delete $extraENV{MAKETEST};
+
+1;
diff --git a/src/kmk/tests/scripts/functions/pos b/src/kmk/tests/scripts/functions/pos
new file mode 100644
index 0000000..bdc3d40
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/pos
@@ -0,0 +1,118 @@
+# $Id: pos 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(pos needle, haystack[, start])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(pos ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(pos b,abc),2)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(pos t,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 0 failed)
+endif
+ifneq ($(pos tu,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 1 failed)
+endif
+ifneq ($(pos tuv,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 2 failed)
+endif
+ifneq ($(pos tuvw,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 3 failed)
+endif
+ifneq ($(pos tuvwx,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 4 failed)
+endif
+ifneq ($(pos tuvwxy,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 5 failed)
+endif
+ifneq ($(pos tuvwxyz,abcdefghijklmnopqrstuvwxyz),20)
+$(error sub-test 6 failed)
+endif
+ifneq ($(pos tuvwxyz!,abcdefghijklmnopqrstuvwxyz),0)
+$(error sub-test 7 failed)
+endif
+
+ifneq ($(pos a,ababababab),1)
+$(error sub-test 10 failed)
+endif
+ifneq ($(pos a,ababababab,2),3)
+$(error sub-test 11 failed)
+endif
+ifneq ($(pos a,ababababab,3),3)
+$(error sub-test 12 failed)
+endif
+ifneq ($(pos a,ababababab,8),9)
+$(error sub-test 13 failed)
+endif
+ifneq ($(pos a,ababababab,8),9)
+$(error sub-test 14 failed)
+endif
+ifneq ($(pos a,ababababab,9),9)
+$(error sub-test 15 failed)
+endif
+ifneq ($(pos a,ababababab,10),0)
+$(error sub-test 16 failed)
+endif
+ifneq ($(pos a,ababababab,-1),0)
+$(error sub-test 17 failed)
+endif
+ifneq ($(pos a,ababababab,-2),9)
+$(error sub-test 18 failed)
+endif
+ifneq ($(pos a,ababababab,-10),1)
+$(error sub-test 19 failed)
+endif
+ifneq ($(pos a,ababababab,-11),0)
+$(error sub-test 20 failed)
+endif
+
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/printf b/src/kmk/tests/scripts/functions/printf
new file mode 100644
index 0000000..cb20168
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/printf
@@ -0,0 +1,80 @@
+# $Id: printf 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(printf fmt[,args...])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(printf ) function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(printf abcd),abcd)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(printf %s,abcde),abcde)
+$(error sub-test 0 failed)
+endif
+ifneq ($(printf %.4s,abcde),abcd)
+$(error sub-test 1 failed)
+endif
+ifneq ($(printf %.8s,abc),abc)
+$(error sub-test 2 failed)
+endif
+ifneq ($(printf %.2s%.3s,abc,zde),abzde)
+$(error sub-test 3 failed)
+endif
+define HASH
+#
+endef
+ifneq ($(printf %$(HASH)x,127),0x7f)
+$(error sub-test 4 failed)
+endif
+ifneq ($(printf %$(HASH)X,127),0X7F)
+$(error sub-test 5 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/realpath b/src/kmk/tests/scripts/functions/realpath
new file mode 100644
index 0000000..9b503b4
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/realpath
@@ -0,0 +1,82 @@
+# -*-perl-*-
+$description = "Test the realpath functions.";
+
+$details = "";
+
+run_make_test('
+ifneq ($(realpath .),$(CURDIR))
+ $(error )
+endif
+
+ifneq ($(realpath ./),$(CURDIR))
+ $(error )
+endif
+
+ifneq ($(realpath .///),$(CURDIR))
+ $(error )
+endif
+
+ifneq ($(realpath /),/)
+ $(error )
+endif
+
+ifneq ($(realpath /.),/)
+ $(error )
+endif
+
+ifneq ($(realpath /./),/)
+ $(error )
+endif
+
+ifneq ($(realpath /.///),/)
+ $(error )
+endif
+
+ifneq ($(realpath /..),/)
+ $(error )
+endif
+
+ifneq ($(realpath /../),/)
+ $(error )
+endif
+
+ifneq ($(realpath /..///),/)
+ $(error )
+endif
+
+ifneq ($(realpath . /..),$(CURDIR) /)
+ $(error )
+endif
+
+.PHONY: all
+all: ; @:
+',
+ '',
+ '');
+
+# On Windows platforms, "//" means something special. So, don't do these
+# tests there.
+
+if ($port_type ne 'W32') {
+ run_make_test('
+ifneq ($(realpath ///),/)
+ $(error )
+endif
+
+ifneq ($(realpath ///.),/)
+ $(error )
+endif
+
+ifneq ($(realpath ///..),/)
+ $(error )
+endif
+
+.PHONY: all
+all: ; @:',
+ '',
+ '');
+}
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/functions/root b/src/kmk/tests/scripts/functions/root
new file mode 100644
index 0000000..46abbd1
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/root
@@ -0,0 +1,172 @@
+# $Id: root 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(root path...)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(path ) function";
+
+$details = "Testing edges and some simple stuff.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(root /a),/)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(root /asdf/asdf/adsf),/)
+$(error sub-test 0 failed:$(root /asdf/asdf/adsf))
+endif
+ifneq ($(root asdf/asdf/adsf),)
+$(error sub-test 1 failed)
+endif
+ifneq ($(root asdf/asdf/adsf/),)
+$(error sub-test 2 failed)
+endif
+ifneq ($(root asdf/asdf/adsf/),)
+$(error sub-test 3 failed)
+endif
+ifneq ($(root a),)
+$(error sub-test 4 failed)
+endif
+ifneq ($(root ),)
+$(error sub-test 5 failed)
+endif
+ifneq ($(root //a),//)
+$(error sub-test 6 failed)
+endif
+ifneq ($(root /a),/)
+$(error sub-test 7 failed)
+endif
+ifneq ($(root ///a),///)
+$(error sub-test 8 failed)
+endif
+ifneq ($(root /a /b /c d /e),/ / / /)
+$(error sub-test 9 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+
+ # TEST #2 - DOS PATH stuff.
+ # ------------------------
+ if ($port_type eq 'W32' || $port_type eq 'OS/2' || $port_type eq 'DOS') {
+ run_make_test('
+ifneq ($(root D:),D:)
+$(error sub-test 0 failed)
+endif
+ifneq ($(root D:/),D:/)
+$(error sub-test 1 failed)
+endif
+ifneq ($(root D:\\),D:\\)
+$(error sub-test 2 failed)
+endif
+ifneq ($(root D:\\\\),D:\\\\)
+$(error sub-test 3 failed)
+endif
+ifneq ($(root D:\\\\a),D:\\\\)
+$(error sub-test 4 failed)
+endif
+ifneq ($(root D:\\/a),D:\\/)
+$(error sub-test 5 failed)
+endif
+ifneq ($(root a:\\\\//asdf/asdf\\asdf),a:\\\\//)
+$(error sub-test 6 failed)
+endif
+ifneq ($(root z://\\\\asdf/asdf\\asdf),z://\\\\)
+$(error sub-test 7 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+ }
+
+ # TEST #3 - UNC PATH stuff.
+ # ------------------------
+ if ($port_type eq 'W32' || $port_type eq 'OS/2') {
+ run_make_test('
+ifneq ($(root //./),//./)
+$(error sub-test 0 failed)
+endif
+ifneq ($(root \\\\.\\),\\\\.\\)
+$(error sub-test 1 failed)
+endif
+ifneq ($(root \\\\\\.\\),\\\\\\)
+$(error sub-test 2 failed)
+endif
+ifneq ($(root ///.\\),///)
+$(error sub-test 3 failed)
+endif
+ifneq ($(root /\\.\\),/\\.\\)
+$(error sub-test 4 failed)
+endif
+ifneq ($(root \\/.\\),\\/.\\)
+$(error sub-test 5 failed)
+endif
+ifneq ($(root //srv/),//srv/)
+$(error sub-test 6 failed)
+endif
+ifneq ($(root //srv),)
+$(error sub-test 7 failed)
+endif
+ifneq ($(root //srv/share),//srv/share)
+$(error sub-test 8 failed)
+endif
+ifneq ($(root //srv/share/),//srv/share/)
+$(error sub-test 9 failed)
+endif
+ifneq ($(root //srv/share/asdf),//srv/share/)
+$(error sub-test 10 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+ }
+
+}
+
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/select b/src/kmk/tests/scripts/functions/select
new file mode 100644
index 0000000..843ff2e
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/select
@@ -0,0 +1,96 @@
+# $Id: select 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(select when1-cond, when1-body[,whenN-cond, whenN-body])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(select ) conditional function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(select 0,failed,1,success),success)
+$(error sub-test 0 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+
+ifneq ($(select 0,failed,1-1,failed2,otherwise,success),success)
+$(error sub-test 0 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2,otherwise:,success),success)
+$(error sub-test 1 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2, otherwise:,success),success)
+$(error sub-test 2 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2, otherwise: ,success),success)
+$(error sub-test 3 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2, otherwise : ,success),success)
+$(error sub-test 4 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2, default: ,success),success)
+$(error sub-test 5 failed)
+endif
+ifneq ($(select 0,failed,1-1,failed2,default,success),success)
+$(error sub-test 6 failed)
+endif
+
+ifneq ($(select 0,failed),)
+$(error sub-test 10 failed)
+endif
+ifneq ($(select 1,works),works)
+$(error sub-test 11 failed)
+endif
+ifneq ($(select 0,failed,1,success,1,failed3,otherwise,failed4),success)
+$(error sub-test 12 failed)
+endif
+
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/shell b/src/kmk/tests/scripts/functions/shell
new file mode 100644
index 0000000..809c77f
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/shell
@@ -0,0 +1,60 @@
+# -*-perl-*-
+
+$description = 'Test the $(shell ...) function.';
+
+$details = '';
+
+# Test standard shell
+run_make_test('.PHONY: all
+OUT := $(shell echo hi)
+all: ; @echo $(OUT)
+ ','','hi');
+
+# Test shells inside rules.
+run_make_test('.PHONY: all
+all: ; @echo $(shell echo hi)
+ ','','hi');
+
+# Verify .SHELLSTATUS
+run_make_test('.PHONY: all
+PRE := $(.SHELLSTATUS)
+$(shell exit 0)
+OK := $(.SHELLSTATUS)
+$(shell exit 1)
+BAD := $(.SHELLSTATUS)
+all: ; @echo PRE=$(PRE) OK=$(OK) BAD=$(BAD)
+ ','','PRE= OK=0 BAD=1');
+
+
+# Test unescaped comment characters in shells. Savannah bug #20513
+if ($all_tests) {
+ run_make_test(q!
+FOO := $(shell echo '#')
+foo: ; echo '$(FOO)'
+!,
+ '', "#\n");
+}
+
+# Test shells inside exported environment variables.
+# This is the test that fails if we try to put make exported variables into
+# the environment for a $(shell ...) call.
+run_make_test('
+export HI = $(shell echo hi)
+.PHONY: all
+all: ; @echo $$HI
+ ','','hi');
+
+# Test shell errors in recipes including offset
+run_make_test('
+all:
+ @echo hi
+ $(shell ./basdfdfsed there)
+ @echo there
+',
+ '', "#MAKE#: ./basdfdfsed: Command not found\nhi\nthere\n");
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/sort b/src/kmk/tests/scripts/functions/sort
new file mode 100644
index 0000000..e6e1343
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/sort
@@ -0,0 +1,51 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to verify
+the ability of make to sort lists of object. Sort
+will also remove any duplicate entries. This will also
+be tested.";
+
+$details = "The make file is built with a list of object in a random order
+and includes some duplicates. Make should sort all of the elements
+remove all duplicates\n";
+
+run_make_test('
+foo := moon_light days
+foo1:= jazz
+bar := captured
+bar2 = boy end, has rise A midnight
+bar3:= $(foo)
+s1 := _by
+s2 := _and_a
+t1 := $(addsuffix $(s1), $(bar) )
+t2 := $(addsuffix $(s2), $(foo1) )
+t3 := $(t2) $(t2) $(t2) $(t2) $(t2) $(t2) $(t2) $(t2) $(t2) $(t2)
+t4 := $(t3) $(t3) $(t3) $(t3) $(t3) $(t3) $(t3) $(t3) $(t3) $(t3)
+t5 := $(t4) $(t4) $(t4) $(t4) $(t4) $(t4) $(t4) $(t4) $(t4) $(t4)
+t6 := $(t5) $(t5) $(t5) $(t5) $(t5) $(t5) $(t5) $(t5) $(t5) $(t5)
+t7 := $(t6) $(t6) $(t6)
+p1 := $(addprefix $(foo1), $(s2) )
+blank:=
+all:
+ @echo $(sort $(bar2) $(foo) $(addsuffix $(s1), $(bar) ) $(t2) $(bar2) $(bar3))
+ @echo $(sort $(blank) $(foo) $(bar2) $(t1) $(p1) )
+ @echo $(sort $(foo) $(bar2) $(t1) $(t4) $(t5) $(t7) $(t6) )
+',
+ '', 'A boy captured_by days end, has jazz_and_a midnight moon_light rise
+A boy captured_by days end, has jazz_and_a midnight moon_light rise
+A boy captured_by days end, has jazz_and_a midnight moon_light rise
+');
+
+
+# Test with non-space/tab whitespace. Note that you can't see the
+# original bug except using valgrind.
+
+run_make_test("FOO = a b\tc\rd\fe \f \f \f \f \ff
+all: ; \@echo \$(words \$(sort \$(FOO)))\n",
+ '', "6\n");
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/strip b/src/kmk/tests/scripts/functions/strip
new file mode 100644
index 0000000..8222433
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/strip
@@ -0,0 +1,57 @@
+# -*-perl-*-
+$description = "The following test creates a makefile to verify
+the ability of make to strip white space from lists of object.\n";
+
+
+$details = "The make file is built with a list of objects that contain white space
+These are then run through the strip command to remove it. This is then
+verified by echoing the result.\n";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<'EOMAKE';
+TEST1 := "Is this TERMINAL fun? What makes you believe is this terminal fun? JAPAN is a WONDERFUL planet -- I wonder if we will ever reach their level of COMPARATIVE SHOPPING..."
+E :=
+TEST2 := $E try this and this $E
+
+define TEST3
+
+and these test out
+
+
+some
+blank lines
+
+
+
+endef
+
+.PHONY: all
+all:
+ @echo '$(strip $(TEST1) )'
+ @echo '$(strip $(TEST2) )'
+ @echo '$(strip $(TEST3) )'
+
+space: ; @echo '$(strip ) $(strip )'
+
+EOMAKE
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile);
+$answer = "\"Is this TERMINAL fun? What makes you believe is this terminal fun? JAPAN is a WONDERFUL planet -- I wonder if we will ever reach their level of COMPARATIVE SHOPPING...\"
+try this and this
+and these test out some blank lines
+";
+&compare_output($answer,&get_logfile(1));
+
+
+&run_make_with_options($makefile,"space",&get_logfile);
+$answer = " \n";
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/functions/substitution b/src/kmk/tests/scripts/functions/substitution
new file mode 100644
index 0000000..0d2f6a2
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/substitution
@@ -0,0 +1,38 @@
+# -*-perl-*-
+
+$description = "Test the subst and patsubst functions";
+
+$details = "";
+
+# Generic patsubst test: test both the function and variable form.
+
+run_make_test('
+foo := a.o b.o c.o
+bar := $(foo:.o=.c)
+bar2:= $(foo:%.o=%.c)
+bar3:= $(patsubst %.c,%.o,x.c.c bar.c)
+all:;@echo $(bar); echo $(bar2); echo $(bar3)',
+'',
+'a.c b.c c.c
+a.c b.c c.c
+x.c.o bar.o');
+
+# Patsubst without '%'--shouldn't match because the whole word has to match
+# in patsubst. Based on a bug report by Markus Mauhart <qwe123@chello.at>
+
+run_make_test('all:;@echo $(patsubst Foo,Repl,FooFoo)', '', 'FooFoo');
+
+# Variable subst where a pattern matches multiple times in a single word.
+# Based on a bug report by Markus Mauhart <qwe123@chello.at>
+
+run_make_test('
+A := fooBARfooBARfoo
+all:;@echo $(A:fooBARfoo=REPL)', '', 'fooBARREPL');
+
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/substr b/src/kmk/tests/scripts/functions/substr
new file mode 100644
index 0000000..bf0eba9
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/substr
@@ -0,0 +1,125 @@
+# $Id: substr 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(substr str, start[, length[, pad]])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(substr ) function";
+
+$details = "A few simple tests and edge cases.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(substr asdf,4),f)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,20),tuvwxyz)
+$(error sub-test 0 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,20,1),t)
+$(error sub-test 1 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,20,2),tu)
+$(error sub-test 2 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,20,0),)
+$(error sub-test 3 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-1),z)
+$(error sub-test 4 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-2),yz)
+$(error sub-test 5 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-2,2),yz)
+$(error sub-test 6 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-2,3),yz)
+$(error sub-test 7 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-2,5,XYZ),yzXYZ)
+$(error sub-test 8 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-25,1),b)
+$(error sub-test 9 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-26,1),a)
+$(error sub-test 10 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-27,1),)
+$(error sub-test 11 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-27,2),a)
+$(error sub-test 12 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-27,3),ab)
+$(error sub-test 13 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-27,3,_),_ab)
+$(error sub-test 14 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-28,4,.^),.^ab)
+$(error sub-test 15 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-50,4),)
+$(error sub-test 16 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,-50,4,.^),.^.^)
+$(error sub-test 17 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,27,4,.^),.^.^)
+$(error sub-test 18 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,28,3,.^),.^.)
+$(error sub-test 19 failed)
+endif
+SP := $(subst ., ,.)
+ifneq (.$(substr abcdefghijklmnopqrstuvwxyz,100,3, ).,. .)
+$(error sub-test 20 failed)
+endif
+ifneq ($(substr abcdefghijklmnopqrstuvwxyz,100,3, ),$(SP)$(SP)$(SP))
+$(error sub-test 21 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/suffix b/src/kmk/tests/scripts/functions/suffix
new file mode 100644
index 0000000..0c4f919
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/suffix
@@ -0,0 +1,57 @@
+$description = "The following test creates a makefile to test the suffix\n"
+ ."function. \n";
+
+$details = "The suffix function will return the string following the last _._\n"
+ ."the list provided. It will provide all of the unique suffixes found\n"
+ ."in the list. The long strings are sorted to remove duplicates.\n";
+
+# IF YOU NEED >1 MAKEFILE FOR THIS TEST, USE &get_tmpfile; TO GET
+# THE NAME OF THE MAKEFILE. THIS INSURES CONSISTENCY AND KEEPS TRACK OF
+# HOW MANY MAKEFILES EXIST FOR EASY DELETION AT THE END.
+# EXAMPLE: $makefile2 = &get_tmpfile;
+
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "string := word.pl general_test2.pl1 FORCE.pl word.pl3 generic_test.perl /tmp.c/bar foo.baz/bar.c MAKEFILES_variable.c\n"
+ ."string2 := \$(string) \$(string) \$(string) \$(string) \$(string) \$(string) \$(string)\n"
+ ."string3 := \$(string2) \$(string2) \$(string2) \$(string2) \$(string2) \$(string2) \$(string2)\n"
+ ."string4 := \$(string3) \$(string3) \$(string3) \$(string3) \$(string3) \$(string3) \$(string3)\n"
+ ."all: \n"
+ ."\t\@echo \$(suffix \$(string)) \n"
+ ."\t\@echo \$(sort \$(suffix \$(string4))) \n"
+ ."\t\@echo \$(suffix \$(string) a.out) \n"
+ ."\t\@echo \$(sort \$(suffix \$(string3))) \n";
+
+
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+
+# COMPARE RESULTS
+$answer = ".pl .pl1 .pl .pl3 .perl .c .c\n"
+ .".c .perl .pl .pl1 .pl3\n"
+ .".pl .pl1 .pl .pl3 .perl .c .c .out\n"
+ .".c .perl .pl .pl1 .pl3\n";
+
+# In this call to compare output, you should use the call &get_logfile(1)
+# to send the name of the last logfile created. You may also use
+# the special call &get_logfile(1) which returns the same as &get_logfile(1).
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/functions/translate b/src/kmk/tests/scripts/functions/translate
new file mode 100644
index 0000000..2dcf83f
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/translate
@@ -0,0 +1,76 @@
+# $Id: translate 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(translate string, from-set[, to-set[, pad-char]])
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(translate ) function";
+
+$details = "A few simple tests and edge cases.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,abcde,01234),01234fghijklmnopqrstuvwxyz)
+$(error sub-test 0 failed)
+endif
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,abcde,01234),01234fghijklmnopqrstuvwxyz)
+$(error sub-test 0 failed)
+endif
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,abcde,,.),.....fghijklmnopqrstuvwxyz)
+$(error sub-test 1 failed)
+endif
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,abcde,),fghijklmnopqrstuvwxyz)
+$(error sub-test 2 failed)
+endif
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,abcde,x1),x1fghijklmnopqrstuvwxyz)
+$(error sub-test 3 failed)
+endif
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,bfh),acdegijklmnopqrstuvwxyz)
+$(error sub-test 4 failed)
+endif
+ifneq ($(translate abcdefghijklmnopqrstuvwxyz,z,Z),abcdefghijklmnopqrstuvwxyZ)
+$(error sub-test 5 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+# Indicate that we're done.
+1;
+
diff --git a/src/kmk/tests/scripts/functions/value b/src/kmk/tests/scripts/functions/value
new file mode 100644
index 0000000..8e1a6f0
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/value
@@ -0,0 +1,30 @@
+# -*-perl-*-
+
+$description = "Test the value function.";
+
+$details = "This is a test of the value function in GNU make.
+This function will evaluate to the value of the named variable with no
+further expansion performed on it.\n";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+export FOO = foo
+
+recurse = FOO = $FOO
+static := FOO = $(value FOO)
+
+all: ; @echo $(recurse) $(value recurse) $(static) $(value static)
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile, "", &get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "FOO = OO FOO = foo FOO = foo FOO = foo\n";
+
+
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/functions/warning b/src/kmk/tests/scripts/functions/warning
new file mode 100644
index 0000000..16eb83b
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/warning
@@ -0,0 +1,83 @@
+# -*-Perl-*-
+
+$description = "\
+The following test creates a makefile to test the warning function.";
+
+$details = "";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+ifdef WARNING1
+$(warning warning is $(WARNING1))
+endif
+
+ifdef WARNING2
+$(warning warning is $(WARNING2))
+endif
+
+ifdef WARNING3
+all: some; @echo hi $(warning warning is $(WARNING3))
+endif
+
+ifdef WARNING4
+all: some; @echo hi
+ @echo there $(warning warning is $(WARNING4))
+endif
+
+some: ; @echo Some stuff
+
+EOF
+
+close(MAKEFILE);
+
+# Test #1
+
+&run_make_with_options($makefile, "WARNING1=yes", &get_logfile, 0);
+$answer = "$makefile:2: warning is yes\nSome stuff\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #2
+
+&run_make_with_options($makefile, "WARNING2=no", &get_logfile, 0);
+$answer = "$makefile:6: warning is no\nSome stuff\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #3
+
+&run_make_with_options($makefile, "WARNING3=maybe", &get_logfile, 0);
+$answer = "Some stuff\n$makefile:10: warning is maybe\nhi\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test #4
+
+&run_make_with_options($makefile, "WARNING4=definitely", &get_logfile, 0);
+$answer = "Some stuff\n$makefile:15: warning is definitely\nhi\nthere\n";
+&compare_output($answer,&get_logfile(1));
+
+# Test linenumber offset
+
+run_make_test(q!
+all: one two
+ $(warning in $@ line 3)
+ @true
+ $(warning in $@ line 5)
+
+one two:
+ $(warning in $@ line 8)
+ @true
+ $(warning in $@ line 10)
+!,
+ '', "#MAKEFILE#:8: in one line 8
+#MAKEFILE#:10: in one line 10
+#MAKEFILE#:8: in two line 8
+#MAKEFILE#:10: in two line 10
+#MAKEFILE#:3: in all line 3
+#MAKEFILE#:5: in all line 5\n");
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/functions/while b/src/kmk/tests/scripts/functions/while
new file mode 100644
index 0000000..c0e6481
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/while
@@ -0,0 +1,73 @@
+# $Id: while 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# $(while condition,body)
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the $(while ) loop function";
+
+$details = "A few simple tests, nothing spectacular.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check that the feature is present.
+ # --------------------------------------------
+ run_make_test('
+VAR := 0
+OUTPUT := $(while $(VAR)==0,$(eval VAR=1)works)
+ifneq ($(OUTPUT),works)
+$(error sub-test 0 failed:$(OUTPUT))
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+
+ # TEST #1 - the real test.
+ # ------------------------
+ run_make_test('
+VAR := 0
+OUTPUT := $(while $(VAR) < 3,$(eval VAR:=$(expr $(VAR) + 1))$(VAR))
+ifneq ($(OUTPUT),1 2 3)
+$(error sub-test 0 failed:$(OUTPUT))
+endif
+
+ifneq (.$(while 0,asdfasdfadsf).,..)
+$(error sub-test 1 failed)
+endif
+
+.PHONY: all
+all: ; @:
+',
+'',
+'');
+}
+
+
+
+# Indicate that we're done.
+1;
+
+
+
diff --git a/src/kmk/tests/scripts/functions/wildcard b/src/kmk/tests/scripts/functions/wildcard
new file mode 100644
index 0000000..bcd84ad
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/wildcard
@@ -0,0 +1,103 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test wildcard
+expansions and the ability to put a command on the same
+line as the target name separated by a semi-colon.";
+
+$details = "\
+This test creates 4 files by the names of 1.example,
+two.example and 3.example. We execute three tests. The first
+executes the print1 target which tests the '*' wildcard by
+echoing all filenames by the name of '*.example'. The second
+test echo's all files which match '?.example' and
+[a-z0-9].example. Lastly we clean up all of the files using
+the '*' wildcard as in the first test";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<EOM;
+.PHONY: print1 print2 clean
+print1: ;\@echo \$(sort \$(wildcard example.*))
+print2:
+\t\@echo \$(sort \$(wildcard example.?))
+\t\@echo \$(sort \$(wildcard example.[a-z0-9]))
+\t\@echo \$(sort \$(wildcard example.[!A-Za-z_\\!]))
+clean:
+\t$delete_command \$(sort \$(wildcard example.*))
+EOM
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch("example.1");
+&touch("example.two");
+&touch("example.3");
+&touch("example.for");
+&touch("example._");
+
+# TEST #1
+# -------
+
+$answer = "example.1 example.3 example._ example.for example.two\n";
+
+&run_make_with_options($makefile,"print1",&get_logfile);
+
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #2
+# -------
+
+$answer = "example.1 example.3 example._\n"
+ ."example.1 example.3\n"
+ ."example.1 example.3\n";
+
+&run_make_with_options($makefile,"print2",&get_logfile);
+
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #3
+# -------
+
+$answer = "$delete_command example.1 example.3 example._ example.for example.two";
+if ($vos)
+{
+ $answer .= " \n";
+}
+else
+{
+ $answer .= "\n";
+}
+
+&run_make_with_options($makefile,"clean",&get_logfile);
+
+if ((-f "example.1")||(-f "example.two")||(-f "example.3")||(-f "example.for")) {
+ $test_passed = 0;
+}
+
+&compare_output($answer,&get_logfile(1));
+
+# TEST #4: Verify that failed wildcards don't return the pattern
+
+run_make_test(q!
+all: ; @echo $(wildcard xz--y*.7)
+!,
+ '', "\n");
+
+# TEST #5: wildcard used to verify file existence
+
+touch('xxx.yyy');
+
+run_make_test(q!exists: ; @echo file=$(wildcard xxx.yyy)!,
+ '', "file=xxx.yyy\n");
+
+unlink('xxx.yyy');
+
+run_make_test(q!exists: ; @echo file=$(wildcard xxx.yyy)!,
+ '', "file=\n");
+
+1;
diff --git a/src/kmk/tests/scripts/functions/word b/src/kmk/tests/scripts/functions/word
new file mode 100644
index 0000000..4dcc940
--- /dev/null
+++ b/src/kmk/tests/scripts/functions/word
@@ -0,0 +1,167 @@
+# -*-perl-*-
+$description = "\
+Test the word, words, wordlist, firstword, and lastword functions.\n";
+
+$details = "\
+Produce a variable with a large number of words in it,
+determine the number of words, and then read each one back.\n";
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE <<'EOF';
+string := word.pl general_test2.pl FORCE.pl word.pl generic_test.perl MAKEFILES_variable.pl
+string2 := $(string) $(string) $(string) $(string) $(string) $(string) $(string)
+string3 := $(string2) $(string2) $(string2) $(string2) $(string2) $(string2) $(string2)
+string4 := $(string3) $(string3) $(string3) $(string3) $(string3) $(string3) $(string3)
+all:
+ @echo $(words $(string))
+ @echo $(words $(string4))
+ @echo $(word 1, $(string))
+ @echo $(word 100, $(string))
+ @echo $(word 1, $(string))
+ @echo $(word 1000, $(string3))
+ @echo $(wordlist 3, 4, $(string))
+ @echo $(wordlist 4, 3, $(string))
+ @echo $(wordlist 1, 6, $(string))
+ @echo $(wordlist 5, 7, $(string))
+ @echo $(wordlist 100, 110, $(string))
+ @echo $(wordlist 7, 10, $(string2))
+EOF
+close(MAKEFILE);
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "6\n"
+ ."2058\n"
+ ."word.pl\n"
+ ."\n"
+ ."word.pl\n"
+ ."\n"
+ ."FORCE.pl word.pl\n"
+ ."\n"
+ ."word.pl general_test2.pl FORCE.pl word.pl generic_test.perl MAKEFILES_variable.pl\n"
+ ."generic_test.perl MAKEFILES_variable.pl\n"
+ ."\n"
+ ."word.pl general_test2.pl FORCE.pl word.pl\n";
+&compare_output($answer, &get_logfile(1));
+
+
+# Test error conditions
+
+run_make_test('FOO = foo bar biz baz
+
+word-e1: ; @echo $(word ,$(FOO))
+word-e2: ; @echo $(word abc ,$(FOO))
+word-e3: ; @echo $(word 1a,$(FOO))
+
+wordlist-e1: ; @echo $(wordlist ,,$(FOO))
+wordlist-e2: ; @echo $(wordlist abc ,,$(FOO))
+wordlist-e3: ; @echo $(wordlist 1, 12a ,$(FOO))',
+ 'word-e1',
+ "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'word-e2',
+ "#MAKEFILE#:4: *** non-numeric first argument to 'word' function: 'abc '. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'word-e3',
+ "#MAKEFILE#:5: *** non-numeric first argument to 'word' function: '1a'. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e1',
+ "#MAKEFILE#:7: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e2',
+ "#MAKEFILE#:8: *** non-numeric first argument to 'wordlist' function: 'abc '. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e3',
+ "#MAKEFILE#:9: *** non-numeric second argument to 'wordlist' function: ' 12a '. Stop.",
+ 512);
+
+# Test error conditions again, but this time in a variable reference
+
+run_make_test('FOO = foo bar biz baz
+
+W = $(word $x,$(FOO))
+WL = $(wordlist $s,$e,$(FOO))
+
+word-e: ; @echo $(W)
+wordlist-e: ; @echo $(WL)',
+ 'word-e x=',
+ "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: ''. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'word-e x=abc',
+ "#MAKEFILE#:3: *** non-numeric first argument to 'word' function: 'abc'. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'word-e x=0',
+ "#MAKEFILE#:3: *** first argument to 'word' function must be greater than 0. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e s= e=',
+ "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: ''. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e s=abc e=',
+ "#MAKEFILE#:4: *** non-numeric first argument to 'wordlist' function: 'abc'. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e s=4 e=12a',
+ "#MAKEFILE#:4: *** non-numeric second argument to 'wordlist' function: '12a'. Stop.",
+ 512);
+
+run_make_test(undef,
+ 'wordlist-e s=0 e=12',
+ "#MAKEFILE#:4: *** invalid first argument to 'wordlist' function: '0'. Stop.",
+ 512);
+
+
+# TEST #8 -- test $(firstword )
+#
+run_make_test('
+void :=
+list := $(void) foo bar baz #
+
+a := $(word 1,$(list))
+b := $(firstword $(list))
+
+.PHONY: all
+
+all:
+ @test "$a" = "$b" && echo $a
+',
+'',
+'foo');
+
+
+# TEST #9 -- test $(lastword )
+#
+run_make_test('
+void :=
+list := $(void) foo bar baz #
+
+a := $(word $(words $(list)),$(list))
+b := $(lastword $(list))
+
+.PHONY: all
+
+all:
+ @test "$a" = "$b" && echo $a
+',
+'',
+'baz');
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/misc/bs-nl b/src/kmk/tests/scripts/misc/bs-nl
new file mode 100644
index 0000000..fc323ce
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/bs-nl
@@ -0,0 +1,227 @@
+# -*-perl-*-
+$description = "Test backslash-newline handling.";
+
+$details = "";
+
+# TEST #1
+# -------
+
+# Backslash-newlines in recipes
+
+# These are basic backslash-newlines with no tricks
+run_make_test("fast:;\@echo fa\\\nst\n",
+ '', 'fast');
+
+run_make_test("slow:;\@: no-op; echo sl\\\now\n",
+ '', 'slow');
+
+run_make_test("dquote:;\@echo \"dqu\\\note\"\n",
+ '', 'dquote');
+
+run_make_test("squote:;\@echo 'squ\\\note'\n",
+ '', "squ\\\note");
+
+# Ensure that a leading prefix character is omitted
+run_make_test("fast:;\@echo fa\\\n\tst\n",
+ '', 'fast');
+
+run_make_test("slow:;\@: no-op; echo sl\\\n\tow\n",
+ '', 'slow');
+
+run_make_test("dquote:;\@echo \"dqu\\\n\tote\"\n",
+ '', 'dquote');
+
+run_make_test("squote:;\@echo 'squ\\\n\tote'\n",
+ '', "squ\\\note");
+
+# Ensure that ONLY the leading prefix character is omitted
+run_make_test("fast:;\@echo fa\\\n\t st\n",
+ '', 'fa st');
+
+run_make_test("slow:;\@: no-op; echo sl\\\n\t\tow\n",
+ '', "sl ow");
+
+run_make_test("dquote:;\@echo \"dqu\\\n\t ote\"\n",
+ '', 'dqu ote');
+
+run_make_test("squote:;\@echo 'squ\\\n\t\t ote'\n",
+ '', "squ\\\n\t ote");
+
+# Backslash-newlines in variable values
+
+# Simple
+run_make_test(q!
+var = he\
+llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# Condense trailing space
+run_make_test(q!
+var = he \
+llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# Remove leading space
+run_make_test(q!
+var = he\
+ llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# Multiple bs/nl condensed
+run_make_test(q!
+var = he\
+\
+\
+ llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# POSIX: Preserve trailing space
+run_make_test(q!
+.POSIX:
+x = y
+var = he \
+llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# POSIX: One space per bs-nl
+run_make_test(q!
+.POSIX:
+x = y
+var = he\
+\
+\
+ llo
+var:;@echo '|$(var)|'!,
+ '', "|he llo|");
+
+# Savannah #39035: handle whitespace in call
+run_make_test(q!
+f = echo $(1)
+t:; @$(call f,"a \
+ b"); \
+ $(call f,"a \
+ b")
+!,
+ '', "a b\na b\n");
+
+# Savannah #38945: handle backslash CRLF
+# We need our own makefile so we can set binmode
+my $m1 = get_tmpfile();
+open(MAKEFILE, "> $m1");
+binmode(MAKEFILE);
+print MAKEFILE "FOO = foo \\\r\n";
+close(MAKEFILE);
+
+my $m2 = get_tmpfile();
+open(MAKEFILE, "> $m2");
+print MAKEFILE "include $m1\ndefine BAR\nall: ; \@echo \$(FOO) bar\nendef\n\$(eval \$(BAR))\n";
+close(MAKEFILE);
+
+run_make_with_options($m2, '', get_logfile());
+compare_output("foo bar\n", get_logfile(1));
+
+# Test different types of whitespace, and bsnl inside functions
+
+sub xlate
+{
+ $_ = $_[0];
+ s/\\r/\r/g;
+ s/\\t/\t/g;
+ s/\\f/\f/g;
+ s/\\v/\v/g;
+ s/\\n/\n/g;
+ return $_;
+}
+
+run_make_test(xlate(q!
+$(foreach\r a \t , b\t c \r ,$(info $a \r ) )
+all:;@:
+!),
+ '', "b \r \nc \r \n");
+
+run_make_test(xlate(q!
+all:;@:$(foreach\r a \t , b\t c \r ,$(info $a \r ) )
+!),
+ '', "b \r \nc \r \n");
+
+run_make_test(xlate(q!
+$(foreach \
+\r a \t\
+ , b\t \
+ c \r ,$(info \
+ $a \r ) \
+ )
+all:;@:
+!),
+ '', "b \r \nc \r \n");
+
+run_make_test(xlate(q!
+all:;@:$(foreach \
+\r a \t\
+ , b\t \
+ c \r ,$(info \
+ $a \r ) \
+ )
+!),
+ '', "b \r \nc \r \n");
+
+run_make_test(xlate(q!
+define FOO
+$(foreach
+\r a \t
+ , b\t
+ c \r ,$(info
+ $a \r )
+ )
+endef
+$(FOO)
+all:;@:
+!),
+ '', "b \r \nc \r \n");
+
+run_make_test(xlate(q!
+define FOO
+$(foreach
+\r a \t
+ , b\t
+ c \r ,$(info
+ $a \r )
+ )
+endef
+all:;@:$(FOO)
+!),
+ '', "b \r \nc \r \n");
+
+# Test variables in recipes that expand to multiple lines
+
+run_make_test(q!
+define var
+
+echo foo
+
+
+echo bar
+endef
+all:;$(var)
+!,
+ '', "echo foo\nfoo\necho bar\nbar\n");
+
+run_make_test(q!
+define var
+
+echo foo
+
+@
+
+echo bar
+endef
+all:;$(var)
+!,
+ '', "echo foo\nfoo\necho bar\nbar\n");
+
+1;
diff --git a/src/kmk/tests/scripts/misc/close_stdout b/src/kmk/tests/scripts/misc/close_stdout
new file mode 100644
index 0000000..18606c3
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/close_stdout
@@ -0,0 +1,9 @@
+# -*-perl-*-
+
+$description = "Make sure make exits with an error if stdout is full.";
+
+if (-e '/dev/full') {
+ run_make_test('', '-v > /dev/full', '/^#MAKE#: write error/', 256);
+}
+
+1;
diff --git a/src/kmk/tests/scripts/misc/fopen-fail b/src/kmk/tests/scripts/misc/fopen-fail
new file mode 100644
index 0000000..2ec9810
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/fopen-fail
@@ -0,0 +1,18 @@
+# -*-perl-*-
+
+$description = "Make sure make exits with an error if fopen fails.";
+
+# Recurse infinitely until we run out of open files, and ensure we
+# fail with a non-zero exit code. Don't bother to test the output
+# since it's hard to know what it will be, exactly.
+# See Savannah bug #27374.
+
+# Use a longer-than-normal timeout: some systems have more FDs available?
+# We also set ulimit -n 512 in check-regression in Makefile.am, which see.
+# See Savannah bug #42390.
+run_make_test(q!
+include $(lastword $(MAKEFILE_LIST))
+!,
+ '', undef, 512, 300);
+
+1;
diff --git a/src/kmk/tests/scripts/misc/general1 b/src/kmk/tests/scripts/misc/general1
new file mode 100644
index 0000000..352fc6a
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/general1
@@ -0,0 +1,51 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test the
+simple functionality of make. It mimics the
+rebuilding of a product with dependencies.
+It also tests the simple definition of VPATH.";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<EOF;
+VPATH = $workdir
+edit: main.o kbd.o commands.o display.o \\
+ insert.o
+\t\@echo cc -o edit main.o kbd.o commands.o display.o \\
+ insert.o
+main.o : main.c defs.h
+\t\@echo cc -c main.c
+kbd.o : kbd.c defs.h command.h
+\t\@echo cc -c kbd.c
+commands.o : command.c defs.h command.h
+\t\@echo cc -c commands.c
+display.o : display.c defs.h buffer.h
+\t\@echo cc -c display.c
+insert.o : insert.c defs.h buffer.h
+\t\@echo cc -c insert.c
+EOF
+
+close(MAKEFILE);
+
+
+@files_to_touch = ("$workdir${pathsep}main.c","$workdir${pathsep}defs.h",
+ "$workdir${pathsep}kbd.c","$workdir${pathsep}command.h",
+ "$workdir${pathsep}commands.c","$workdir${pathsep}display.c",
+ "$workdir${pathsep}buffer.h","$workdir${pathsep}insert.c",
+ "$workdir${pathsep}command.c");
+
+&touch(@files_to_touch);
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "cc -c main.c\ncc -c kbd.c\ncc -c commands.c\ncc -c display.c
+cc -c insert.c\ncc -o edit main.o kbd.o commands.o display.o insert.o\n";
+
+# COMPARE RESULTS
+
+if (&compare_output($answer,&get_logfile(1))) {
+ unlink @files_to_touch;
+}
+
+1;
diff --git a/src/kmk/tests/scripts/misc/general2 b/src/kmk/tests/scripts/misc/general2
new file mode 100644
index 0000000..d4e008a
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/general2
@@ -0,0 +1,50 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test the
+simple functionality of make. It is the same as
+general_test1 except that this one tests the
+definition of a variable to hold the object filenames.";
+
+open(MAKEFILE,"> $makefile");
+
+# The contents of the Makefile ...
+
+print MAKEFILE <<EOF;
+VPATH = $workdir
+objects = main.o kbd.o commands.o display.o insert.o
+edit: \$(objects)
+\t\@echo cc -o edit \$(objects)
+main.o : main.c defs.h
+\t\@echo cc -c main.c
+kbd.o : kbd.c defs.h command.h
+\t\@echo cc -c kbd.c
+commands.o : command.c defs.h command.h
+\t\@echo cc -c commands.c
+display.o : display.c defs.h buffer.h
+\t\@echo cc -c display.c
+insert.o : insert.c defs.h buffer.h
+\t\@echo cc -c insert.c
+EOF
+
+close(MAKEFILE);
+
+
+@files_to_touch = ("$workdir${pathsep}main.c","$workdir${pathsep}defs.h",
+ "$workdir${pathsep}kbd.c","$workdir${pathsep}command.h",
+ "$workdir${pathsep}commands.c","$workdir${pathsep}display.c",
+ "$workdir${pathsep}buffer.h","$workdir${pathsep}insert.c",
+ "$workdir${pathsep}command.c");
+
+&touch(@files_to_touch);
+
+&run_make_with_options($makefile,"-j1",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "cc -c main.c\ncc -c kbd.c\ncc -c commands.c\ncc -c display.c
+cc -c insert.c\ncc -o edit main.o kbd.o commands.o display.o insert.o\n";
+
+if (&compare_output($answer,&get_logfile(1))) {
+ unlink @files_to_touch;
+}
+
+1;
diff --git a/src/kmk/tests/scripts/misc/general3 b/src/kmk/tests/scripts/misc/general3
new file mode 100644
index 0000000..7bbff1c
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/general3
@@ -0,0 +1,315 @@
+# -*-perl-*-
+
+$description = "\
+This tests random features of the parser that need to be supported, and
+which have either broken at some point in the past or seem likely to
+break.";
+
+run_make_test("
+# We want to allow both empty commands _and_ commands that resolve to empty.
+EMPTY =
+
+.PHONY: all a1 a2 a3 a4
+all: a1 a2 a3 a4
+
+a1:;
+a2:
+\t
+a3:;\$(EMPTY)
+a4:
+\t\$(EMPTY)
+
+\# Non-empty lines that expand to nothing should also be ignored.
+STR = \# Some spaces
+TAB = \t \# A TAB and some spaces
+
+\$(STR)
+
+\$(STR) \$(TAB)",
+ '', "#MAKE#: Nothing to be done for 'all'.");
+
+# TEST 2
+
+# Make sure files without trailing newlines are handled properly.
+# Have to use the old style invocation to test this.
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+print MAKEFILE "all:;\@echo FOO = \$(FOO)\nFOO = foo";
+close(MAKEFILE);
+
+&run_make_with_options($makefile2,"",&get_logfile);
+$answer = "FOO = foo\n";
+&compare_output($answer,&get_logfile(1));
+
+# TEST 3
+
+# Check semicolons in variable references
+
+run_make_test('
+$(if true,$(info true; true))
+all: ; @:
+',
+ '', 'true; true');
+
+# TEST 4
+
+# Check that backslashes in command scripts are handled according to POSIX.
+# Checks Savannah bug # 1332.
+
+# Test the fastpath / no quotes
+run_make_test('
+all:
+ @echo foo\
+bar
+ @echo foo\
+ bar
+ @echo foo\
+ bar
+ @echo foo\
+ bar
+ @echo foo \
+bar
+ @echo foo \
+ bar
+ @echo foo \
+ bar
+ @echo foo \
+ bar
+',
+ '', 'foobar
+foobar
+foo bar
+foo bar
+foo bar
+foo bar
+foo bar
+foo bar');
+
+# Test the fastpath / single quotes
+run_make_test("
+all:
+ \@echo 'foo\\
+bar'
+ \@echo 'foo\\
+ bar'
+ \@echo 'foo\\
+ bar'
+ \@echo 'foo\\
+ bar'
+ \@echo 'foo \\
+bar'
+ \@echo 'foo \\
+ bar'
+ \@echo 'foo \\
+ bar'
+ \@echo 'foo \\
+ bar'
+",
+ '', 'foo\
+bar
+foo\
+bar
+foo\
+ bar
+foo\
+ bar
+foo \
+bar
+foo \
+bar
+foo \
+ bar
+foo \
+ bar');
+
+# Test the fastpath / double quotes
+run_make_test('
+all:
+ @echo "foo\
+bar"
+ @echo "foo\
+ bar"
+ @echo "foo\
+ bar"
+ @echo "foo\
+ bar"
+ @echo "foo \
+bar"
+ @echo "foo \
+ bar"
+ @echo "foo \
+ bar"
+ @echo "foo \
+ bar"
+',
+ '', 'foobar
+foobar
+foo bar
+foo bar
+foo bar
+foo bar
+foo bar
+foo bar');
+
+# Test the slow path / no quotes
+run_make_test('
+all:
+ @echo hi; echo foo\
+bar
+ @echo hi; echo foo\
+ bar
+ @echo hi; echo foo\
+ bar
+ @echo hi; echo foo\
+ bar
+ @echo hi; echo foo \
+bar
+ @echo hi; echo foo \
+ bar
+ @echo hi; echo foo \
+ bar
+ @echo hi; echo foo \
+ bar
+',
+ '', 'hi
+foobar
+hi
+foobar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar');
+
+# Test the slow path / no quotes. This time we put the slow path
+# determination _after_ the backslash-newline handling.
+run_make_test('
+all:
+ @echo foo\
+bar; echo hi
+ @echo foo\
+ bar; echo hi
+ @echo foo\
+ bar; echo hi
+ @echo foo\
+ bar; echo hi
+ @echo foo \
+bar; echo hi
+ @echo foo \
+ bar; echo hi
+ @echo foo \
+ bar; echo hi
+ @echo foo \
+ bar; echo hi
+',
+ '', 'foobar
+hi
+foobar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi');
+
+# Test the slow path / single quotes
+run_make_test("
+all:
+ \@echo hi; echo 'foo\\
+bar'
+ \@echo hi; echo 'foo\\
+ bar'
+ \@echo hi; echo 'foo\\
+ bar'
+ \@echo hi; echo 'foo\\
+ bar'
+ \@echo hi; echo 'foo \\
+bar'
+ \@echo hi; echo 'foo \\
+ bar'
+ \@echo hi; echo 'foo \\
+ bar'
+ \@echo hi; echo 'foo \\
+ bar'
+",
+ '', 'hi
+foo\
+bar
+hi
+foo\
+bar
+hi
+foo\
+ bar
+hi
+foo\
+ bar
+hi
+foo \
+bar
+hi
+foo \
+bar
+hi
+foo \
+ bar
+hi
+foo \
+ bar');
+
+# Test the slow path / double quotes
+run_make_test('
+all:
+ @echo hi; echo "foo\
+bar"
+ @echo hi; echo "foo\
+ bar"
+ @echo hi; echo "foo\
+ bar"
+ @echo hi; echo "foo\
+ bar"
+ @echo hi; echo "foo \
+bar"
+ @echo hi; echo "foo \
+ bar"
+ @echo hi; echo "foo \
+ bar"
+ @echo hi; echo "foo \
+ bar"
+',
+ '', 'hi
+foobar
+hi
+foobar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar
+hi
+foo bar');
+
+run_make_test('x:;@-exit 1', '', "#MAKE#: [#MAKEFILE#:1: x] Error 1 (ignored)\n");
+
+1;
diff --git a/src/kmk/tests/scripts/misc/general4 b/src/kmk/tests/scripts/misc/general4
new file mode 100644
index 0000000..dd9475c
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/general4
@@ -0,0 +1,85 @@
+# -*-perl-*-
+
+$description = "\
+This tests random features of make's algorithms, often somewhat obscure,
+which have either broken at some point in the past or seem likely to
+break.";
+
+run_make_test('
+# Make sure that subdirectories built as prerequisites are actually handled
+# properly.
+
+all: dir/subdir/file.a
+
+dir/subdir: ; @echo mkdir -p dir/subdir
+
+dir/subdir/file.b: dir/subdir ; @echo touch dir/subdir/file.b
+
+dir/subdir/%.a: dir/subdir/%.b ; @echo cp $< $@',
+ '', "mkdir -p dir/subdir\ntouch dir/subdir/file.b\ncp dir/subdir/file.b dir/subdir/file.a\n");
+
+# Test implicit rules
+# kmk: No default implicit rules.
+
+&touch('foo.c');
+run_make_test('foo: foo.o',
+ 'CC="@echo cc" OUTPUT_OPTION=',
+ !$is_kmk ? 'cc -c foo.c
+cc foo.o -o foo' :
+"#MAKE#: *** No rule to make target `foo.o', needed by `foo'. Stop.",
+!$is_kmk ? 0 : 512);
+unlink('foo.c');
+
+
+# Test implicit rules with '$' in the name (see se_implicit)
+
+run_make_test(q!
+%.foo : baz$$bar ; @echo 'done $<'
+%.foo : bar$$baz ; @echo 'done $<'
+test.foo:
+baz$$bar bar$$baz: ; @echo '$@'
+!,
+ '',
+ "baz\$bar\ndone baz\$bar");
+
+
+# Test implicit rules with '$' in the name (see se_implicit)
+# Use the '$' in the pattern.
+
+run_make_test(q!
+%.foo : %$$bar ; @echo 'done $<'
+test.foo:
+test$$bar: ; @echo '$@'
+!,
+ '',
+ "test\$bar\ndone test\$bar");
+
+# Make sure that subdirectories built as prerequisites are actually handled
+# properly... this time with '$'
+
+run_make_test(q!
+
+all: dir/subdir/file.$$a
+
+dir/subdir: ; @echo mkdir -p '$@'
+
+dir/subdir/file.$$b: dir/subdir ; @echo touch '$@'
+
+dir/subdir/%.$$a: dir/subdir/%.$$b ; @echo 'cp $< $@'
+!,
+ '', "mkdir -p dir/subdir\ntouch dir/subdir/file.\$b\ncp dir/subdir/file.\$b dir/subdir/file.\$a\n");
+
+# Test odd whitespace at the beginning of a line
+
+run_make_test("
+all:
+ \f
+
+ \\
+ \f \\
+ \013 \\
+all: ; \@echo hi
+",
+ '', "hi\n");
+
+1;
diff --git a/src/kmk/tests/scripts/misc/utf8 b/src/kmk/tests/scripts/misc/utf8
new file mode 100644
index 0000000..2adcd07
--- /dev/null
+++ b/src/kmk/tests/scripts/misc/utf8
@@ -0,0 +1,14 @@
+# -*-perl-*-
+$description = "Test utf8 handling.";
+
+$details = "";
+
+# Variable names containing UTF8 characters
+run_make_test("
+\xe2\x96\xaa := hello
+\$(info \$(\xe2\x96\xaa))
+all:
+",
+ '', "hello\n#MAKE#: Nothing to be done for 'all'.");
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-B b/src/kmk/tests/scripts/options/dash-B
new file mode 100644
index 0000000..4c4c4cf
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-B
@@ -0,0 +1,87 @@
+# -*-perl-*-
+
+$description = "Test make -B (always remake) option.\n";
+
+$details = "\
+Construct a simple makefile that builds a target.
+Invoke make once, so it builds everything. Invoke it again and verify
+that nothing is built. Then invoke it with -B and verify that everything
+is built again.";
+
+&touch('bar.x');
+
+run_make_test('
+.SUFFIXES:
+
+.PHONY: all
+all: foo
+
+foo: bar.x
+ @echo cp $< $@
+ @echo "" > $@
+',
+ '', 'cp bar.x foo');
+
+run_make_test(undef, '', "#MAKE#: Nothing to be done for 'all'.");
+run_make_test(undef, '-B', 'cp bar.x foo');
+
+# Put the timestamp for foo into the future; it should still be remade.
+
+utouch(1000, 'foo');
+run_make_test(undef, '', "#MAKE#: Nothing to be done for 'all'.");
+run_make_test(undef, '-B', 'cp bar.x foo');
+
+# Clean up
+
+rmfiles('bar.x', 'foo');
+
+# Test -B with the re-exec feature: we don't want to re-exec forever
+# Savannah bug # 7566
+
+run_make_test('
+all: ; @:
+$(info MAKE_RESTARTS=$(MAKE_RESTARTS))
+include foo.x
+foo.x: ; @touch $@
+',
+ '-B', 'MAKE_RESTARTS=
+MAKE_RESTARTS=1');
+
+rmfiles('foo.x');
+
+# Test -B with the re-exec feature: we DO want -B in the "normal" part of the
+# makefile.
+
+&touch('blah.x');
+
+run_make_test('
+all: blah.x ; @echo $@
+$(info MAKE_RESTARTS=$(MAKE_RESTARTS))
+include foo.x
+foo.x: ; @touch $@
+blah.x: ; @echo $@
+',
+ '-B', 'MAKE_RESTARTS=
+MAKE_RESTARTS=1
+blah.x
+all');
+
+rmfiles('foo.x', 'blah.x');
+
+# Test that $? is set properly with -B; all prerequisites will be newer!
+
+utouch(-10, 'x.b');
+touch('x.a');
+
+run_make_test(q!
+x.a: x.b ; @echo $?
+!,
+ '-B', "x.b\n");
+
+unlink(qw(x.a x.b));
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/options/dash-C b/src/kmk/tests/scripts/options/dash-C
new file mode 100644
index 0000000..42d0a8b
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-C
@@ -0,0 +1,71 @@
+# -*-perl-*-
+
+$description = "Test the -C option to GNU make.";
+
+$details = "\
+This test is similar to the clean test except that this test creates the file
+to delete in the work directory instead of the current directory. Make is
+called from another directory using the -C workdir option so that it can both
+find the makefile and the file to delete in the work directory.";
+
+$example = $workdir . $pathsep . "EXAMPLE";
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE <<EOF;
+all: ; \@echo This makefile did not clean the dir ... good
+clean: ; $delete_command EXAMPLE\$(ext)
+EOF
+close(MAKEFILE);
+
+# TEST #1
+# -------
+&touch($example);
+
+&run_make_with_options("${testname}.mk",
+ "-C $workdir clean",
+ &get_logfile);
+
+chdir $workdir;
+$wpath = &get_this_pwd;
+chdir $pwd;
+
+if (-f $example) {
+ $test_passed = 0;
+}
+
+# Create the answer to what should be produced by this Makefile
+$answer = "$make_name: Entering directory '$wpath'\n"
+ . "$delete_command EXAMPLE\n"
+ . "$make_name: Leaving directory '$wpath'\n";
+
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #2
+# -------
+# Do it again with trailing "/"; this should work the same
+
+$example .= "slash";
+
+&touch($example);
+
+&run_make_with_options("${testname}.mk",
+ "-C $workdir/ clean ext=slash",
+ &get_logfile);
+
+chdir $workdir;
+$wpath = &get_this_pwd;
+chdir $pwd;
+
+if (-f $example) {
+ $test_passed = 0;
+}
+
+# Create the answer to what should be produced by this Makefile
+$answer = "$make_name: Entering directory '$wpath'\n"
+ . "$delete_command EXAMPLEslash\n"
+ . "$make_name: Leaving directory '$wpath'\n";
+
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-I b/src/kmk/tests/scripts/options/dash-I
new file mode 100644
index 0000000..d47a8d8
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-I
@@ -0,0 +1,59 @@
+# -*-perl-*-
+
+$description ="The following test creates a makefile to test the -I option.";
+
+$details = "\
+This test tests the -I option by including a filename in
+another directory and giving make that directory name
+under -I in the command line. Without this option, the make
+would fail to find the included file. It also checks to make
+sure that the -I option gets passed to recursive makes.";
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+$mf2 = substr ($makefile2, index ($makefile2, $pathsep) + 1);
+print MAKEFILE <<EOF;
+include $mf2
+all:
+\t\@echo There should be no errors for this makefile.
+EOF
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+
+print MAKEFILE <<EOF;
+ANOTHER:
+\t\@echo This is another included makefile
+recurse:
+\t\$(MAKE) ANOTHER -f $makefile
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,"-I $workdir all",&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "There should be no errors for this makefile.\n";
+&compare_output($answer,&get_logfile(1));
+
+
+$answer = "This is another included makefile\n";
+&run_make_with_options($makefile,"-I $workdir ANOTHER",&get_logfile);
+&compare_output($answer,&get_logfile(1));
+
+
+$answer = "$mkpath ANOTHER -f $makefile
+${make_name}[1]: Entering directory '$pwd'
+This is another included makefile
+${make_name}[1]: Leaving directory '$pwd'\n";
+
+&run_make_with_options($makefile,"-I $workdir recurse",&get_logfile);
+&compare_output($answer,&get_logfile(1));
diff --git a/src/kmk/tests/scripts/options/dash-W b/src/kmk/tests/scripts/options/dash-W
new file mode 100644
index 0000000..857b1cc
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-W
@@ -0,0 +1,91 @@
+# -*-perl-*-
+
+$description = "Test make -W (what if) option.\n";
+
+# Basic build
+
+run_make_test('
+a.x: b.x
+a.x b.x: ; echo >> $@
+',
+ '', "echo >> b.x\necho >> a.x");
+
+# Run it again: nothing should happen
+
+run_make_test(undef, '', "#MAKE#: 'a.x' is up to date.");
+
+# Now run it with -W b.x: should rebuild a.x
+
+run_make_test(undef, '-W b.x', 'echo >> a.x');
+
+# Put the timestamp for a.x into the future; it should still be remade.
+
+utouch(1000, 'a.x');
+run_make_test(undef, '', "#MAKE#: 'a.x' is up to date.");
+run_make_test(undef, '-W b.x', 'echo >> a.x');
+
+# Clean up
+
+rmfiles('a.x', 'b.x');
+
+# Test -W with the re-exec feature: we don't want to re-exec forever
+# Savannah bug # 7566
+
+# First set it up with a normal build
+
+run_make_test('
+all: baz.x ; @:
+include foo.x
+foo.x: bar.x
+ @echo "\$$(info restarts=\$$(MAKE_RESTARTS))" > $@
+ @echo "touch $@"
+bar.x: ; echo >> $@
+baz.x: bar.x ; @echo "touch $@"
+',
+ '', 'echo >> bar.x
+touch foo.x
+restarts=1
+touch baz.x');
+
+# Now run with -W bar.x
+
+# Tweak foo.x's timestamp so the update will change it.
+&utouch(1000, 'foo.x');
+
+run_make_test(undef, '-W bar.x', "restarts=\ntouch foo.x\nrestarts=1\ntouch baz.x");
+
+rmfiles('foo.x', 'bar.x');
+
+# Test -W on vpath-found files: it should take effect.
+# Savannah bug # 15341
+
+mkdir('x-dir', 0777);
+utouch(-20, 'x-dir/x');
+touch('y');
+
+run_make_test('
+y: x ; @echo cp $< $@
+',
+ '-W x-dir/x VPATH=x-dir',
+ 'cp x-dir/x y');
+
+# Make sure ./ stripping doesn't interfere with the match.
+
+run_make_test('
+y: x ; @echo cp $< $@
+',
+ '-W ./x-dir/x VPATH=x-dir',
+ 'cp x-dir/x y');
+
+run_make_test(undef,
+ '-W x-dir/x VPATH=./x-dir',
+ 'cp ./x-dir/x y');
+
+unlink(qw(y x-dir/x));
+rmdir('x-dir');
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/options/dash-e b/src/kmk/tests/scripts/options/dash-e
new file mode 100644
index 0000000..17c3fc8
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-e
@@ -0,0 +1,24 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to ...";
+
+$details = "";
+
+$extraENV{GOOGLE} = 'boggle';
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+GOOGLE = bazzle
+all:; @echo "$(GOOGLE)"
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile, '-e' ,&get_logfile);
+
+$answer = "boggle\n";
+
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-f b/src/kmk/tests/scripts/options/dash-f
new file mode 100644
index 0000000..3aa4746
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-f
@@ -0,0 +1,85 @@
+$description = "The following test tests that if you specify greater \n"
+ ."than one '-f makefilename' on the command line, \n"
+ ."that make concatenates them. This test creates three \n"
+ ."makefiles and specifies all of them with the -f option \n"
+ ."on the command line. To make sure they were concatenated, \n"
+ ."we then call make with the rules from the concatenated \n"
+ ."makefiles one at a time. Finally, it calls all three \n"
+ ."rules in one call to make and checks that the output\n"
+ ."is in the correct order.";
+
+$makefile2 = &get_tmpfile;
+$makefile3 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "all: \n";
+print MAKEFILE "\t\@echo This is the output from the original makefile\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+# Create a second makefile
+open(MAKEFILE,"> $makefile2");
+print MAKEFILE "TWO: \n";
+print MAKEFILE "\t\@echo This is the output from makefile 2\n";
+close(MAKEFILE);
+
+# Create a third makefile
+open(MAKEFILE,"> $makefile3");
+print MAKEFILE "THREE: \n";
+print MAKEFILE "\t\@echo This is the output from makefile 3\n";
+close(MAKEFILE);
+
+
+# Create the answer to what should be produced by this Makefile
+$answer = "This is the output from the original makefile\n";
+
+# Run make to catch the default rule
+&run_make_with_options($makefile,"-f $makefile2 -f $makefile3",&get_logfile,0);
+
+&compare_output($answer,&get_logfile(1));
+
+
+# Run Make again with the rule from the second makefile: TWO
+$answer = "This is the output from makefile 2\n";
+
+&run_make_with_options($makefile,"-f $makefile2 -f $makefile3 TWO",&get_logfile,0);
+
+&compare_output($answer,&get_logfile(1));
+
+
+# Run Make again with the rule from the third makefile: THREE
+
+$answer = "This is the output from makefile 3\n";
+&run_make_with_options($makefile,
+ "-f $makefile2 -f $makefile3 THREE",
+ &get_logfile,
+ 0);
+&compare_output($answer,&get_logfile(1));
+
+
+# Run Make again with ALL three rules in the order 2 1 3 to make sure
+# that all rules are executed in the proper order
+
+$answer = "This is the output from makefile 2\n";
+$answer .= "This is the output from the original makefile\n";
+$answer .= "This is the output from makefile 3\n";
+&run_make_with_options($makefile,
+ "-f $makefile2 -f $makefile3 TWO all THREE",
+ &get_logfile,
+ 0);
+&compare_output($answer,&get_logfile(1));
+
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/options/dash-k b/src/kmk/tests/scripts/options/dash-k
new file mode 100644
index 0000000..e2bb7c9
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-k
@@ -0,0 +1,114 @@
+# -*-perl-*-
+
+$description = "Test the make -k (don't stop on error) option.\n";
+
+$details = "\
+The makefile created in this test is a simulation of building
+a small product. However, the trick to this one is that one
+of the dependencies of the main target does not exist.
+Without the -k option, make would fail immediately and not
+build any part of the target. What we are looking for here,
+is that make builds the rest of the dependencies even though
+it knows that at the end it will fail to rebuild the main target.";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<EOF;
+VPATH = $workdir
+edit: main.o kbd.o commands.o display.o
+\t\@echo cc -o edit main.o kbd.o commands.o display.o
+
+main.o : main.c defs.h
+\t\@echo cc -c main.c
+
+kbd.o : kbd.c defs.h command.h
+\t\@echo cc -c kbd.c
+
+commands.o : command.c defs.h command.h
+\t\@echo cc -c commands.c
+
+display.o : display.c defs.h buffer.h
+\t\@echo cc -c display.c
+EOF
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+@files_to_touch = ("$workdir${pathsep}main.c","$workdir${pathsep}defs.h",
+ "$workdir${pathsep}command.h",
+ "$workdir${pathsep}commands.c","$workdir${pathsep}display.c",
+ "$workdir${pathsep}buffer.h",
+ "$workdir${pathsep}command.c");
+
+&touch(@files_to_touch);
+
+if ($vos) {
+ $error_code = 3307;
+}
+else {
+ $error_code = 512;
+}
+
+&run_make_with_options($makefile, "-j1 -k", &get_logfile, $error_code);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "cc -c main.c
+$make_name: *** No rule to make target 'kbd.c', needed by 'kbd.o'.
+cc -c commands.c
+cc -c display.c
+$make_name: Target 'edit' not remade because of errors.\n";
+
+# COMPARE RESULTS
+
+&compare_output($answer, &get_logfile(1));
+
+unlink(@files_to_touch) unless $keep;
+
+
+# TEST 1: Make sure that top-level targets that depend on targets that
+# previously failed to build, aren't attempted. Regression for PR/1634.
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+print MAKEFILE <<'EOF';
+.SUFFIXES:
+
+all: exe1 exe2; @echo making $@
+
+exe1 exe2: lib; @echo cp $^ $@
+
+lib: foo.o; @echo cp $^ $@
+
+foo.o: ; exit 1
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile2, "-k", &get_logfile, $error_code);
+
+$answer = "exit 1
+$make_name: *** [$makefile2:9: foo.o] Error 1
+$make_name: Target 'all' not remade because of errors.\n";
+
+&compare_output($answer, &get_logfile(1));
+
+# TEST -- make sure we keep the error code if we can't create an included
+# makefile.
+
+run_make_test('all: ; @echo hi
+include ifile
+ifile: no-such-file; @false
+',
+ '-k',
+ "#MAKEFILE#:2: ifile: No such file or directory
+#MAKE#: *** No rule to make target 'no-such-file', needed by 'ifile'.
+#MAKE#: Failed to remake makefile 'ifile'.
+hi\n",
+ 512);
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-l b/src/kmk/tests/scripts/options/dash-l
new file mode 100644
index 0000000..0b0f196
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-l
@@ -0,0 +1,56 @@
+# -*-perl-*-
+# Date: Tue, 11 Aug 1992 09:34:26 -0400
+# From: pds@lemming.webo.dg.com (Paul D. Smith)
+
+$description = "Test load balancing (-l) option.";
+
+$details = "\
+This test creates a makefile where all depends on three rules
+which contain the same body. Each rule checks for the existence
+of a temporary file; if it exists an error is generated. If it
+doesn't exist then it is created, the rule sleeps, then deletes
+the temp file again. Thus if any of the rules are run in
+parallel the test will fail. When make is called in this test,
+it is given the -l option with a value of 0.0001. This ensures
+that the load will be above this number and make will therefore
+decide that it cannot run more than one job even though -j 4 was
+also specified on the command line.";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE <<'EOF';
+SHELL = /bin/sh
+
+define test
+if [ ! -f test-file ]; then \
+ echo >> test-file; sleep 2; rm -f test-file; \
+else \
+ echo $@ FAILED; \
+fi
+endef
+
+all : ONE TWO THREE
+ONE : ; @$(test)
+TWO : ; @$(test)
+THREE : ; @$(test)
+EOF
+
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+$mkoptions = "-l 0.0001";
+$mkoptions .= " -j 4" if ($parallel_jobs);
+
+# We have to wait longer than the default (5s).
+&run_make_with_options($makefile, $mkoptions, &get_logfile, 0, 8);
+
+$slurp = &read_file_into_string (&get_logfile(1));
+if ($slurp !~ /cannot enforce load limit/) {
+ &compare_output("", &get_logfile(1));
+}
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-n b/src/kmk/tests/scripts/options/dash-n
new file mode 100644
index 0000000..02ae4a9
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-n
@@ -0,0 +1,100 @@
+# -*-perl-*-
+$description = "Test the -n option.\n";
+
+$details = "Try various uses of -n and ensure they all give the correct results.\n";
+
+touch('orig');
+
+run_make_test(q!
+final: intermediate ; echo >> $@
+intermediate: orig ; echo >> $@
+!,
+ '', "echo >> intermediate\necho >> final\n");
+
+# TEST 1
+
+run_make_test(undef, '-Worig -n', "echo >> intermediate\necho >> final\n");
+
+rmfiles(qw(orig intermediate final));
+
+# We consider the actual updated timestamp of targets with all
+# recursive commands, even with -n. Switching this to the new model
+# is non-trivial because we use a trick below to change the log content
+# before we compare it ...
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+
+print MAKEFILE <<'EOF';
+.SUFFIXES:
+BAR = # nothing
+FOO = +$(BAR)
+a: b; echo > $@
+b: c; $(FOO)
+EOF
+
+close(MAKEFILE);
+
+&utouch(-20, 'b');
+&utouch(-10, 'a');
+&touch('c');
+
+# TEST 2
+
+&run_make_with_options($makefile2, "", &get_logfile);
+$answer = "$make_name: 'a' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 3
+
+&run_make_with_options($makefile2, "-n", &get_logfile);
+$answer = "$make_name: 'a' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST 4
+
+unlink(qw(a b));
+
+&run_make_with_options($makefile2, "-t -n", &get_logfile);
+
+open(DASH_N_LOG, ">>" . &get_logfile(1));
+print DASH_N_LOG "a exists but should not!\n" if -e 'a';
+print DASH_N_LOG "b exists but should not!\n" if -e 'b';
+close(DASH_N_LOG);
+
+&compare_output("touch b\ntouch a\n", &get_logfile(1));
+
+# CLEANUP
+
+unlink(qw(a b c));
+
+# Ensure -n continues to be included with recursive/re-execed make
+# See Savannah bug #38051
+
+$topmake = &get_tmpfile;
+$submake = &get_tmpfile;
+
+open(MAKEFILE, "> $topmake");
+print MAKEFILE <<"EOF";
+foo: ; \@\$(MAKE) -f "$submake" bar
+EOF
+close(MAKEFILE);
+
+
+# The bar target should print what would happen, but not actually run
+open(MAKEFILE, "> $submake");
+print MAKEFILE <<'EOF';
+inc: ; touch $@
+-include inc
+bar: ; @echo $(strip $(MAKEFLAGS))
+EOF
+close(MAKEFILE);
+
+&run_make_with_options($topmake, '-n --no-print-directory', &get_logfile);
+$answer = "$make_command -f \"$submake\" bar\ntouch inc\necho n --no-print-directory\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('inc');
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-q b/src/kmk/tests/scripts/options/dash-q
new file mode 100644
index 0000000..e67b55d
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-q
@@ -0,0 +1,86 @@
+# -*-perl-*-
+$description = "Test the -q option.\n";
+
+$details = "Try various uses of -q and ensure they all give the correct results.\n";
+
+# TEST 0
+
+run_make_test(qq!
+one:
+two: ;
+three: ; :
+four: ; \$(.XY)
+five: ; \\
+ \$(.XY)
+six: ; \\
+ \$(.XY)
+\t\$(.XY)
+seven: ; \\
+ \$(.XY)
+\t: foo
+\t\$(.XY)
+!,
+ '-q one', '');
+
+# TEST 1
+
+run_make_test(undef, '-q two', '');
+
+# TEST 2
+
+run_make_test(undef, '-q three', '', 256);
+
+# TEST 3
+
+run_make_test(undef, '-q four', '');
+
+# TEST 4
+
+run_make_test(undef, '-q five', '');
+
+# TEST 5
+
+run_make_test(undef, '-q six', '');
+
+# TEST 6
+
+run_make_test(undef, '-q seven', '', 256);
+
+# TEST 7 : Savannah bug # 7144
+
+run_make_test('
+one:: ; @echo one
+one:: ; @echo two
+',
+ '-q', '', 256);
+
+# TEST 7 : Savannah bug # 42249
+# Make sure we exit with 1 even for prerequisite updates
+run_make_test('
+build-stamp: ; echo $@
+build-arch: build-stamp
+build-x: build-arch
+build-y: build-x
+',
+ '-q build-y', '', 256);
+
+# TEST 8
+# Make sure we exit with 2 on error even with -q
+run_make_test('
+build-stamp: ; echo $@
+build-arch: build-stamp-2
+build-x: build-arch
+build-y: build-x
+',
+ '-q build-y', "#MAKE#: *** No rule to make target 'build-stamp-2', needed by 'build-arch'. Stop.\n", 512);
+
+# TEST 9 : Savannah bug # 47151
+# Make sure we exit with 1 when invoking a recursive make
+run_make_test('
+foo: bar ; echo foo
+bar: ; @$(MAKE) -f #MAKEFILE# baz
+baz: ; echo baz
+',
+ '-q foo', '', 256);
+
+1;
diff --git a/src/kmk/tests/scripts/options/dash-t b/src/kmk/tests/scripts/options/dash-t
new file mode 100644
index 0000000..ec27d7a
--- /dev/null
+++ b/src/kmk/tests/scripts/options/dash-t
@@ -0,0 +1,58 @@
+# -*-perl-*-
+
+$description = "Test the -t option.\n";
+
+$details = "Look out for regressions of prior bugs related to -t.\n";
+# That means, nobody has even tried to make the tests below comprehensive
+
+# TEST 0
+# bug reported by Henning Makholm <henning@makholm.net> on 2001-11-03:
+# make 3.79.1 touches only interm-[ab] but reports final-[a] as
+# 'up to date' without touching them.
+# The 'obvious' fix didn't work for double-colon rules, so pay special
+# attention to them.
+
+open(MAKEFILE, "> $makefile");
+print MAKEFILE <<'EOMAKE';
+final-a: interm-a ; echo >> $@
+final-b: interm-b ; echo >> $@
+interm-a:: orig1-a ; echo >> $@
+interm-a:: orig2-a ; echo >> $@
+interm-b:: orig1-b ; echo >> $@
+interm-b:: orig2-b ; echo >> $@
+EOMAKE
+close(MAKEFILE);
+
+&utouch(-30, 'orig1-a','orig2-b');
+&utouch(-20, 'interm-a','interm-b');
+&utouch(-10, 'final-a','final-b');
+&touch('orig2-a','orig1-b');
+
+&run_make_with_options($makefile, "-t final-a final-b", &get_logfile);
+$answer = "touch interm-a\ntouch final-a\ntouch interm-b\ntouch final-b\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('orig1-a', 'orig2-a', 'interm-a', 'final-a');
+unlink('orig1-b', 'orig2-b', 'interm-b', 'final-b');
+
+# TEST 1
+# -t should not touch files with no commands.
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+print MAKEFILE <<'EOMAKE';
+
+PHOOEY: xxx
+xxx: ; @:
+
+EOMAKE
+close(MAKEFILE);
+
+&run_make_with_options($makefile2, "-t", &get_logfile);
+$answer = "touch xxx\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('xxx');
+
+1;
diff --git a/src/kmk/tests/scripts/options/eval b/src/kmk/tests/scripts/options/eval
new file mode 100644
index 0000000..0f82409
--- /dev/null
+++ b/src/kmk/tests/scripts/options/eval
@@ -0,0 +1,29 @@
+# -*-perl-*-
+
+$description = "Test the --eval option.";
+
+$details = "Verify that --eval options take effect,
+and are passed to sub-makes.";
+
+# Verify that --eval is evaluated first
+run_make_test(q!
+BAR = bar
+all: ; @echo all
+recurse: ; @$(MAKE) -f #MAKEFILE# && echo recurse!,
+ '--eval=\$\(info\ eval\) FOO=\$\(BAR\)', "eval\nall");
+
+# Make sure that --eval is handled correctly during recursion
+run_make_test(undef, '--no-print-directory --eval=\$\(info\ eval\) recurse',
+ "eval\neval\nall\nrecurse");
+
+# Make sure that --eval is handled correctly during restarting
+run_make_test(q!
+all: ; @echo $@
+-include gen.mk
+gen.mk: ; @echo > $@
+!,
+ '--eval=\$\(info\ eval\)', "eval\neval\nall");
+
+unlink('gen.mk');
+
+1;
diff --git a/src/kmk/tests/scripts/options/general b/src/kmk/tests/scripts/options/general
new file mode 100644
index 0000000..d35bb35
--- /dev/null
+++ b/src/kmk/tests/scripts/options/general
@@ -0,0 +1,35 @@
+# -*-perl-*-
+$description = "Test generic option processing.\n";
+
+open(MAKEFILE, "> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "foo 1foo: ; \@echo \$\@\n";
+
+close(MAKEFILE);
+
+# TEST 0
+
+&run_make_with_options($makefile, "-j 1foo", &get_logfile);
+if (!$parallel_jobs) {
+ $answer = "$make_name: Parallel jobs (-j) are not supported on this platform.\n$make_name: Resetting to single job (-j1) mode.\n1foo\n";
+}
+else {
+ $answer = "1foo\n";
+}
+
+# TEST 1
+
+# This test prints the usage string; I don't really know a good way to
+# test it. I guess I could invoke make with a known-bad option to see
+# what the usage looks like, then compare it to what I get here... :(
+
+# If I were always on UNIX, I could invoke it with 2>/dev/null, then
+# just check the error code.
+
+&run_make_with_options($makefile, "-j1foo 2>/dev/null", &get_logfile, 512);
+$answer = "";
+&compare_output($answer, &get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/options/print-directory b/src/kmk/tests/scripts/options/print-directory
new file mode 100644
index 0000000..db762b2
--- /dev/null
+++ b/src/kmk/tests/scripts/options/print-directory
@@ -0,0 +1,33 @@
+# -*-perl-*-
+
+$description = "Test the -w option to GNU make.";
+
+# Simple test without -w
+run_make_test(q!
+all: ; @echo hi
+!,
+ "", "hi\n");
+
+# Simple test with -w
+run_make_test(undef, "-w",
+ "#MAKE#: Entering directory '#PWD#'\nhi\n#MAKE#: Leaving directory '#PWD#'\n");
+
+# Test makefile rebuild to ensure no enter/leave
+run_make_test(q!
+include foo
+all: ;@:
+foo: ; touch foo
+!,
+ "", "touch foo\n");
+unlink('foo');
+
+# Test makefile rebuild with -w
+run_make_test(q!
+include foo
+all: ;@:
+foo: ; touch foo
+!,
+ "-w", "#MAKE#: Entering directory '#PWD#'\ntouch foo\n#MAKE#: Leaving directory '#PWD#'\n");
+unlink('foo');
+
+1;
diff --git a/src/kmk/tests/scripts/options/symlinks b/src/kmk/tests/scripts/options/symlinks
new file mode 100644
index 0000000..a1bfce0
--- /dev/null
+++ b/src/kmk/tests/scripts/options/symlinks
@@ -0,0 +1,68 @@
+# -*-perl-*-
+
+$description = "Test the -L option.";
+
+$details = "Verify that symlink handling with and without -L works properly.";
+
+# Only run these tests if the system sypports symlinks
+
+# Apparently the Windows port of Perl reports that it does support symlinks
+# (in that the symlink() function doesn't fail) but it really doesn't, so
+# check for it explicitly.
+
+if ($port_type eq 'W32' || !( eval { symlink("",""); 1 })) {
+ # This test is N/A
+ -1;
+} else {
+
+ # Set up a symlink sym -> dep
+ # We'll make both dep and targ older than sym
+ $pwd =~ m%/([^/]+)$%;
+ $dirnm = $1;
+ &utouch(-10, 'dep');
+ &utouch(-5, 'targ');
+ symlink("../$dirnm/dep", 'sym');
+
+ # Without -L, nothing should happen
+ # With -L, it should update targ
+ run_make_test('targ: sym ; @echo make $@ from $<', '',
+ "#MAKE#: 'targ' is up to date.");
+ run_make_test(undef, '-L', "make targ from sym");
+
+ # Now update dep; in all cases targ should be out of date.
+ &touch('dep');
+ run_make_test(undef, '', "make targ from sym");
+ run_make_test(undef, '-L', "make targ from sym");
+
+ # Now update targ; in all cases targ should be up to date.
+ &touch('targ');
+ run_make_test(undef, '', "#MAKE#: 'targ' is up to date.");
+ run_make_test(undef, '-L', "#MAKE#: 'targ' is up to date.");
+
+ # Add in a new link between sym and dep. Be sure it's newer than targ.
+ sleep(1);
+ rename('dep', 'dep1');
+ symlink('dep1', 'dep');
+
+ # Without -L, nothing should happen
+ # With -L, it should update targ
+ run_make_test(undef, '', "#MAKE#: 'targ' is up to date.");
+ run_make_test(undef, '-L', "make targ from sym");
+
+ rmfiles('targ', 'dep', 'sym', 'dep1');
+
+ # Check handling when symlinks point to non-existent files. Without -L we
+ # should get an error: with -L we should use the timestamp of the symlink.
+
+ symlink("../$dirname/dep", 'sym');
+ run_make_test('targ: sym ; @echo make $@ from $<', '',
+ "#MAKE#: *** No rule to make target 'sym', needed by 'targ'. Stop.", 512);
+
+ run_make_test('targ: sym ; @echo make $@ from $<', '-L',
+ 'make targ from sym');
+
+
+ rmfiles('targ', 'sym');
+
+ 1;
+}
diff --git a/src/kmk/tests/scripts/options/warn-undefined-variables b/src/kmk/tests/scripts/options/warn-undefined-variables
new file mode 100644
index 0000000..ce15507
--- /dev/null
+++ b/src/kmk/tests/scripts/options/warn-undefined-variables
@@ -0,0 +1,25 @@
+# -*-perl-*-
+
+$description = "Test the --warn-undefined-variables option.";
+
+$details = "Verify that warnings are printed for referencing undefined variables.";
+
+# Without --warn-undefined-variables, nothing should happen
+run_make_test('
+EMPTY =
+EREF = $(EMPTY)
+UREF = $(UNDEFINED)
+
+SEREF := $(EREF)
+SUREF := $(UREF)
+
+all: ; @echo ref $(EREF) $(UREF)',
+ '', 'ref');
+
+# With --warn-undefined-variables, it should warn me
+run_make_test(undef, '--warn-undefined-variables',
+ "#MAKEFILE#:7: warning: undefined variable 'UNDEFINED'
+#MAKEFILE#:9: warning: undefined variable 'UNDEFINED'
+ref");
+
+1;
diff --git a/src/kmk/tests/scripts/targets/DEFAULT b/src/kmk/tests/scripts/targets/DEFAULT
new file mode 100644
index 0000000..f3d5148
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/DEFAULT
@@ -0,0 +1,53 @@
+$description = "The following test creates a makefile to override part\n"
+ ."of one Makefile with Another Makefile with the .DEFAULT\n"
+ ."rule.";
+
+$details = "This tests the use of the .DEFAULT special target to say that \n"
+ ."to remake any target that cannot be made fram the information\n"
+ ."in the containing makefile, make should look in another makefile\n"
+ ."This test gives this makefile the target bar which is not \n"
+ ."defined here but passes the target bar on to another makefile\n"
+ ."which does have the target bar defined.\n";
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "foo:\n";
+print MAKEFILE "\t\@echo Executing rule FOO\n\n";
+print MAKEFILE ".DEFAULT:\n";
+print MAKEFILE "\t\@\$(MAKE) -f $makefile2 \$\@ \n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+
+print MAKEFILE "bar:\n";
+print MAKEFILE "\t\@echo Executing rule BAR\n\n";
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile,'bar',&get_logfile);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "${make_name}[1]: Entering directory '$pwd'\n"
+ . "Executing rule BAR\n"
+ . "${make_name}[1]: Leaving directory '$pwd'\n";
+
+# COMPARE RESULTS
+
+&compare_output($answer,&get_logfile(1));
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/targets/DELETE_ON_ERROR b/src/kmk/tests/scripts/targets/DELETE_ON_ERROR
new file mode 100644
index 0000000..f0d9f9b
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/DELETE_ON_ERROR
@@ -0,0 +1,22 @@
+#! -*-perl-*-
+
+$description = "Test the behaviour of the .DELETE_ON_ERROR target.";
+
+$details = "";
+
+run_make_test('
+.DELETE_ON_ERROR:
+all: ; exit 1 > $@
+',
+ '', "exit 1 > all\n#MAKE#: *** [#MAKEFILE#:3: all] Error 1\n#MAKE#: *** Deleting file 'all'", 512);
+
+run_make_test('
+.DELETE_ON_ERROR:
+all: foo.x ;
+%.x : %.q ; echo > $@
+%.q : ; exit 1 > $@
+',
+ '', "exit 1 > foo.q\n#MAKE#: *** [#MAKEFILE#:5: foo.q] Error 1\n#MAKE#: *** Deleting file 'foo.q'", 512);
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/targets/FORCE b/src/kmk/tests/scripts/targets/FORCE
new file mode 100644
index 0000000..eb8f251
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/FORCE
@@ -0,0 +1,40 @@
+# -*-perl-*-
+
+$description = "The following tests rules without Commands or Dependencies.";
+
+$details = "If the rule ...\n";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE ".IGNORE :\n";
+print MAKEFILE "clean: FORCE\n";
+print MAKEFILE "\t$delete_command clean\n";
+print MAKEFILE "FORCE:\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+
+# Create a file named "clean". This is the same name as the target clean
+# and tricks the target into thinking that it is up to date. (Unless you
+# use the .PHONY target.
+&touch("clean");
+
+$answer = "$delete_command clean\n";
+&run_make_with_options($makefile,"clean",&get_logfile);
+
+&compare_output($answer,&get_logfile(1));
+
+1;
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/targets/INTERMEDIATE b/src/kmk/tests/scripts/targets/INTERMEDIATE
new file mode 100644
index 0000000..e2f08bf
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/INTERMEDIATE
@@ -0,0 +1,112 @@
+# -*-perl-*-
+
+$description = "Test the behaviour of the .INTERMEDIATE target.";
+
+$details = "\
+Test the behavior of the .INTERMEDIATE special target.
+Create a makefile where a file would not normally be considered
+intermediate, then specify it as .INTERMEDIATE. Build and ensure it's
+deleted properly. Rebuild to ensure that it's not created if it doesn't
+exist but doesn't need to be built. Change the original and ensure
+that the intermediate file and the ultimate target are both rebuilt, and
+that the intermediate file is again deleted.
+
+Try this with implicit rules and explicit rules: both should work.\n";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+
+.INTERMEDIATE: foo.e bar.e
+
+# Implicit rule test
+%.d : %.e ; cp $< $@
+%.e : %.f ; cp $< $@
+
+foo.d: foo.e
+
+# Explicit rule test
+foo.c: foo.e bar.e; cat $^ > $@
+EOF
+
+close(MAKEFILE);
+
+# TEST #0
+
+&utouch(-20, 'foo.f', 'bar.f');
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "cp foo.f foo.e\ncp foo.e foo.d\nrm foo.e\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #1
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "$make_name: 'foo.d' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #2
+
+&utouch(-10, 'foo.d');
+&touch('foo.f');
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "cp foo.f foo.e\ncp foo.e foo.d\nrm foo.e\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #3
+# kmk+fast: differs because of different hashing.
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "cp foo.f foo.e\ncp bar.f bar.e\ncat foo.e bar.e > foo.c\n"
+ . (!$is_kmk && !$is_fast ? "rm bar.e foo.e\n" : "rm foo.e bar.e\n");
+&compare_output($answer, &get_logfile(1));
+
+# TEST #4
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "$make_name: 'foo.c' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #5
+# kmk+fast: differs because of different hashing.
+
+&utouch(-10, 'foo.c');
+&touch('foo.f');
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "cp foo.f foo.e\ncp bar.f bar.e\ncat foo.e bar.e > foo.c\n"
+ . (!$is_kmk && !$is_fast ? "rm bar.e foo.e\n" : "rm foo.e bar.e\n");
+&compare_output($answer, &get_logfile(1));
+
+# TEST #6 -- added for PR/1669: don't remove files mentioned on the cmd line.
+
+&run_make_with_options($makefile,'foo.e',&get_logfile);
+$answer = "cp foo.f foo.e\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('foo.f', 'foo.e', 'foo.d', 'foo.c', 'bar.f', 'bar.e', 'bar.d', 'bar.c');
+
+# TEST #7 -- added for PR/1423
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+
+print MAKEFILE <<'EOF';
+all: foo
+foo.a: ; touch $@
+%: %.a ; touch $@
+.INTERMEDIATE: foo.a
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile2, '-R', &get_logfile);
+$answer = "touch foo.a\ntouch foo\nrm foo.a\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('foo');
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/targets/ONESHELL b/src/kmk/tests/scripts/targets/ONESHELL
new file mode 100644
index 0000000..87713da
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/ONESHELL
@@ -0,0 +1,88 @@
+# -*-perl-*-
+
+$description = "Test the behaviour of the .ONESHELL target.";
+
+$details = "";
+
+# Some shells (*shakes fist at Solaris*) cannot handle multiple flags in
+# separate arguments.
+my $t = `/bin/sh -e -c true 2>/dev/null`;
+my $multi_ok = $? == 0;
+
+# Simple
+
+run_make_test(q!
+.ONESHELL:
+all:
+ a=$$$$
+ [ 0"$$a" -eq "$$$$" ] || echo fail
+!,
+ '', 'a=$$
+[ 0"$a" -eq "$$" ] || echo fail
+');
+
+# Simple but use multi-word SHELLFLAGS
+
+if ($multi_ok) {
+ run_make_test(q!
+.ONESHELL:
+.SHELLFLAGS = -e -c
+all:
+ a=$$$$
+ [ 0"$$a" -eq "$$$$" ] || echo fail
+!,
+ '', 'a=$$
+[ 0"$a" -eq "$$" ] || echo fail
+');
+}
+
+# Again, but this time with inner prefix chars
+
+run_make_test(q!
+.ONESHELL:
+all:
+ a=$$$$
+ @-+ [ 0"$$a" -eq "$$$$" ] || echo fail
+!,
+ '', 'a=$$
+[ 0"$a" -eq "$$" ] || echo fail
+');
+
+# This time with outer prefix chars
+
+run_make_test(q!
+.ONESHELL:
+all:
+ @a=$$$$
+ [ 0"$$a" -eq "$$$$" ] || echo fail
+!,
+ '', '');
+
+
+# This time with outer and inner prefix chars
+
+run_make_test(q!
+.ONESHELL:
+all:
+ @a=$$$$
+ -@ +[ 0"$$a" -eq "$$$$" ] || echo fail
+!,
+ '', '');
+
+
+# Now try using a different interpreter
+
+run_make_test(q!
+.RECIPEPREFIX = >
+.ONESHELL:
+SHELL = #PERL#
+.SHELLFLAGS = -e
+all:
+> @$$a=5
+> +7;
+> @y=qw(a b c);
+>print "a = $$a, y = (@y)\n";
+!,
+ '', "a = 12, y = (a b c)\n");
+
+1;
diff --git a/src/kmk/tests/scripts/targets/PHONY b/src/kmk/tests/scripts/targets/PHONY
new file mode 100644
index 0000000..c8e2110
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/PHONY
@@ -0,0 +1,54 @@
+# -*-perl-*-
+
+$description = "The following tests the use of a PHONY target. It makes\n"
+ ."sure that the rules under a target get executed even if\n"
+ ."a filename of the same name of the target exists in the\n"
+ ."directory.\n";
+
+$details = "This makefile in this test declares the target clean to be a \n"
+ ."PHONY target. We then create a file named \"clean\" in the \n"
+ ."directory. Although this file exists, the rule under the target\n"
+ ."clean should still execute because of it's phony status.";
+
+$example = "EXAMPLE_FILE";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE ".PHONY : clean \n";
+print MAKEFILE "all: \n";
+print MAKEFILE "\t\@echo This makefile did not clean the dir ... good\n";
+print MAKEFILE "clean: \n";
+print MAKEFILE "\t$delete_command $example clean\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch($example);
+
+# Create a file named "clean". This is the same name as the target clean
+# and tricks the target into thinking that it is up to date. (Unless you
+# use the .PHONY target.
+&touch("clean");
+
+$answer = "$delete_command $example clean\n";
+&run_make_with_options($makefile,"clean",&get_logfile);
+
+if (-f $example) {
+ $test_passed = 0;
+}
+
+&compare_output($answer,&get_logfile(1));
+
+1;
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/targets/POSIX b/src/kmk/tests/scripts/targets/POSIX
new file mode 100644
index 0000000..5c3c7f8
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/POSIX
@@ -0,0 +1,56 @@
+# -*-perl-*-
+
+$description = "Test the behaviour of the .POSIX target.";
+
+$details = "";
+
+
+# Ensure turning on .POSIX enables the -e flag for the shell
+# We can't assume the exit value of "false" because on different systems it's
+# different.
+
+my $script = 'false; true';
+my $flags = '-ec';
+my $out = `/bin/sh $flags '$script' 2>&1`;
+my $err = $? >> 8;
+run_make_test(qq!
+.POSIX:
+all: ; \@$script
+!,
+ '', "#MAKE#: *** [#MAKEFILE#:3: all] Error $err\n", 512);
+
+# User settings must override .POSIX
+$flags = '-xc';
+$out = `/bin/sh $flags '$script' 2>&1`;
+run_make_test(qq!
+.SHELLFLAGS = $flags
+.POSIX:
+all: ; \@$script
+!,
+ '', $out);
+
+# Test the default value of various POSIX-specific variables
+my %POSIX = (AR => 'ar', ARFLAGS => '-rv',
+ YACC => 'yacc', YFLAGS => '',
+ LEX => 'lex', LFLAGS => '',
+ LDFLAGS => '',
+ CC => 'c99', CFLAGS => '-O',
+ FC => 'fort77', FFLAGS => '-O 1',
+ GET => 'get', GFLAGS => '',
+ SCCSFLAGS => '', SCCSGETFLAGS => '-s');
+my $make = join('', map { "\t\@echo '$_=\$($_)'\n" } sort keys %POSIX);
+my $r = join('', map { "$_=$POSIX{$_}\n"} sort keys %POSIX);
+run_make_test(qq!
+.POSIX:
+all:
+$make
+!,
+ '', $r);
+
+# Make sure that local settings take precedence
+%extraENV = map { $_ => "xx-$_" } keys %POSIX;
+$r = join('', map { "$_=xx-$_\n"} sort keys %POSIX);
+run_make_test(undef, '', $r);
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/targets/SECONDARY b/src/kmk/tests/scripts/targets/SECONDARY
new file mode 100644
index 0000000..447c275
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/SECONDARY
@@ -0,0 +1,190 @@
+#! -*-perl-*-
+
+$description = "Test the behaviour of the .SECONDARY target.";
+
+$details = "\
+Test the behavior of the .SECONDARY special target.
+Create a makefile where a file would not normally be considered
+intermediate, then specify it as .SECONDARY. Build and note that it's
+not automatically deleted. Delete the file. Rebuild to ensure that
+it's not created if it doesn't exist but doesn't need to be built.
+Change the original and ensure that the secondary file and the ultimate
+target are both rebuilt, and that the secondary file is not deleted.
+
+Try this with implicit rules and explicit rules: both should work.\n";
+
+open(MAKEFILE,"> $makefile");
+
+print MAKEFILE <<'EOF';
+
+.SECONDARY: foo.e
+
+# Implicit rule test
+%.d : %.e ; cp $< $@
+%.e : %.f ; cp $< $@
+
+foo.d: foo.e
+
+# Explicit rule test
+foo.c: foo.e ; cp $< $@
+EOF
+
+close(MAKEFILE);
+
+# TEST #1
+
+&utouch(-20, 'foo.f');
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "cp foo.f foo.e\ncp foo.e foo.d\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #2
+
+unlink('foo.e');
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "$make_name: 'foo.d' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #3
+
+&utouch(-10, 'foo.d');
+&touch('foo.f');
+
+&run_make_with_options($makefile,'foo.d',&get_logfile);
+$answer = "cp foo.f foo.e\ncp foo.e foo.d\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #4
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "cp foo.e foo.c\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #5
+
+unlink('foo.e');
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "$make_name: 'foo.c' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #6
+
+&utouch(-10, 'foo.c');
+&touch('foo.f');
+
+&run_make_with_options($makefile,'foo.c',&get_logfile);
+$answer = "cp foo.f foo.e\ncp foo.e foo.c\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('foo.f', 'foo.e', 'foo.d', 'foo.c');
+
+# TEST #7 -- test the "global" .SECONDARY, with no targets.
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+
+print MAKEFILE <<'EOF';
+.SECONDARY:
+
+final: intermediate
+intermediate: source
+
+final intermediate source:
+ echo $< > $@
+EOF
+
+close(MAKEFILE);
+
+&utouch(-10, 'source');
+touch('final');
+
+&run_make_with_options($makefile2, '', &get_logfile);
+$answer = "$make_name: 'final' is up to date.\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink('source', 'final', 'intermediate');
+
+
+# TEST #8 -- test the "global" .SECONDARY, with .PHONY.
+
+touch('version2');
+run_make_test('
+.PHONY: version
+.SECONDARY:
+version2: version ; @echo GOOD
+all: version2',
+ 'all', 'GOOD');
+
+unlink('version2');
+
+# TEST #9 -- Savannah bug #15919
+# The original fix for this bug caused a new bug, shown here.
+
+touch(qw(1.a 2.a));
+
+run_make_test('
+%.c : %.b ; cp $< $@
+%.b : %.a ; cp $< $@
+all : 1.c 2.c
+2.a: 1.c', '-rR -j',
+'cp 1.a 1.b
+cp 1.b 1.c
+cp 2.a 2.b
+cp 2.b 2.c
+rm 1.b 2.b');
+
+unlink(qw(1.a 2.a 1.c 2.c));
+
+# TEST #10 -- Savannah bug #15919
+touch('test.0');
+run_make_test('
+.SECONDARY : test.1 test.2 test.3
+
+test : test.4
+
+%.4 : %.int %.3 ; touch $@
+
+%.int : %.3 %.2 ; touch $@
+
+%.3 : | %.2 ; touch $@
+
+%.2 : %.1 ; touch $@
+
+%.1 : %.0 ; touch $@', '-rR -j 2',
+'touch test.1
+touch test.2
+touch test.3
+touch test.int
+touch test.4
+rm test.int');
+
+# After a touch of test.0 it should give the same output, except we don't need
+# to rebuild test.3 (order-only)
+sleep(1);
+touch('test.0');
+run_make_test(undef, '-rR -j 2',
+'touch test.1
+touch test.2
+touch test.int
+touch test.4
+rm test.int');
+
+# With both test.0 and test.3 updated it should still build everything except
+# test.3
+sleep(1);
+touch('test.0', 'test.3');
+run_make_test(undef, '-rR -j 2',
+'touch test.1
+touch test.2
+touch test.int
+touch test.4
+rm test.int');
+
+unlink(qw(test.0 test.1 test.2 test.3 test.4));
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/targets/SILENT b/src/kmk/tests/scripts/targets/SILENT
new file mode 100644
index 0000000..4bb0a0f
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/SILENT
@@ -0,0 +1,42 @@
+# -*-perl-*-
+
+$description = "The following tests the special target .SILENT. By simply\n"
+ ."mentioning this as a target, it tells make not to print\n"
+ ."commands before executing them.";
+
+$details = "This test is the same as the clean test except that it should\n"
+ ."not echo its command before deleting the specified file.\n";
+
+$example = "EXAMPLE_FILE";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE ".SILENT : clean\n";
+print MAKEFILE "clean: \n";
+print MAKEFILE "\t$delete_command EXAMPLE_FILE\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch($example);
+
+$answer = "";
+&run_make_with_options($makefile,"clean",&get_logfile,0);
+if (-f $example) {
+ $test_passed = 0;
+}
+&compare_output($answer,&get_logfile(1));
+
+1;
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/targets/clean b/src/kmk/tests/scripts/targets/clean
new file mode 100644
index 0000000..b32c976
--- /dev/null
+++ b/src/kmk/tests/scripts/targets/clean
@@ -0,0 +1,50 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to delete a \n"
+ ."file in the directory. It tests to see if make will \n"
+ ."NOT execute the command unless the rule is given in \n"
+ ."the make command line.";
+
+$example = "EXAMPLE_FILE";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+print MAKEFILE "all: \n";
+print MAKEFILE "\t\@echo This makefile did not clean the dir... good\n";
+print MAKEFILE "clean: \n";
+print MAKEFILE "\t$delete_command EXAMPLE_FILE\n";
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+&touch($example);
+
+
+&run_make_with_options($makefile,"",&get_logfile,0);
+
+# Create the answer to what should be produced by this Makefile
+$answer = "This makefile did not clean the dir... good\n";
+
+&compare_output($answer,&get_logfile(1)) || &error ("abort");
+
+
+$answer = "$delete_command $example\n";
+&run_make_with_options($makefile,"clean",&get_logfile,0);
+if (-f $example) {
+ $test_passed = 0;
+}
+&compare_output($answer,&get_logfile(1)) || &error ("abort");
+
+1;
+
+
+
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/test_template b/src/kmk/tests/scripts/test_template
new file mode 100644
index 0000000..3fd3f95
--- /dev/null
+++ b/src/kmk/tests/scripts/test_template
@@ -0,0 +1,29 @@
+# -*-perl-*-
+
+$description = "<FILL IN SHORT DESCRIPTION HERE>";
+$details = "<FILL IN DETAILS OF HOW YOU TEST WHAT YOU SAY YOU ARE TESTING>";
+
+# Run a make test. See the documentation of run_make_test() in
+# run_make_tests.pl, but briefly the first argument is a string with the
+# contents of a makefile to be tested, the second is a string containing the
+# arguments to be passed to the make invocation, the third is a string
+# containing the expected output. The fourth is the expected exit code for
+# make. If not specified, it's assumed that the make program should succeed
+# (exit with 0).
+
+run_make_test('Your test makefile goes here',
+ 'Arguments to pass to make go here',
+ 'Expected output from the invocation goes here');
+
+# There are various special tokens, options, etc. See the full documentation
+# in run_make_tests.pl.
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/variables/CURDIR b/src/kmk/tests/scripts/variables/CURDIR
new file mode 100644
index 0000000..ee7cacb
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/CURDIR
@@ -0,0 +1,20 @@
+# -*-perl-*-
+
+$description = "This tests the CURDIR varaible.";
+
+$details = "Echo CURDIR both with and without -C. Also ensure overrides work.";
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE "all: ; \@echo \$(CURDIR)\n";
+close(MAKEFILE);
+
+
+# TEST #1
+# -------
+
+&run_make_with_options($makefile,"",&get_logfile);
+$answer = "$pwd\n";
+&compare_output($answer,&get_logfile(1));
+
+
+1;
diff --git a/src/kmk/tests/scripts/variables/DEFAULT_GOAL b/src/kmk/tests/scripts/variables/DEFAULT_GOAL
new file mode 100644
index 0000000..8188ce7
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/DEFAULT_GOAL
@@ -0,0 +1,87 @@
+# -*-perl-*-
+$description = "Test the .DEFAULT_GOAL special variable.";
+
+$details = "";
+
+
+# Test #1: basic logic.
+#
+run_make_test('
+# Basics.
+#
+foo: ; @:
+
+ifneq ($(.DEFAULT_GOAL),foo)
+$(error )
+endif
+
+# Reset to empty.
+#
+.DEFAULT_GOAL :=
+
+bar: ; @:
+
+ifneq ($(.DEFAULT_GOAL),bar)
+$(error )
+endif
+
+# Change to a different goal.
+#
+
+.DEFAULT_GOAL := baz
+
+baz: ; @echo $@
+',
+'',
+'baz');
+
+
+# Test #2: unknown goal.
+#
+run_make_test('
+.DEFAULT_GOAL = foo
+',
+'',
+"#MAKE#: *** No rule to make target 'foo'. Stop.",
+512);
+
+
+# Test #3: more than one goal.
+#
+run_make_test('
+.DEFAULT_GOAL := foo bar
+',
+'',
+'#MAKE#: *** .DEFAULT_GOAL contains more than one target. Stop.',
+512);
+
+
+# Test #4: Savannah bug #12226.
+#
+run_make_test('
+define rule
+foo: ; @echo $$@
+endef
+
+define make-rule
+$(eval $(rule))
+endef
+
+$(call make-rule)
+
+',
+'',
+'foo');
+
+# TEST #5: .DEFAULT_GOAL containing just whitespace (Savannah bug #25697)
+
+run_make_test('
+N =
+.DEFAULT_GOAL = $N $N # Just whitespace
+
+foo: ; @echo "boo"
+',
+ '', "#MAKE#: *** No targets. Stop.\n", 512);
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/variables/GNUMAKEFLAGS b/src/kmk/tests/scripts/variables/GNUMAKEFLAGS
new file mode 100644
index 0000000..6e50794
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/GNUMAKEFLAGS
@@ -0,0 +1,42 @@
+# -*-perl-*-
+
+$description = "Test proper behavior of GNUMAKEFLAGS";
+
+# Accept flags from GNUMAKEFLAGS as well as MAKEFLAGS
+# Results always go in MAKEFLAGS
+
+$extraENV{'GNUMAKEFLAGS'} = '-e -r -R';
+
+run_make_test(q!
+all: ; @echo $(MAKEFLAGS)
+!,
+ '', 'erR');
+
+# Long arguments mean everything is prefixed with "-"
+
+$extraENV{'GNUMAKEFLAGS'} = '--no-print-directory -e -r -R --trace';
+
+run_make_test(q!
+all: ; @echo $(MAKEFLAGS)
+!,
+ '', "#MAKEFILE#:2: target 'all' does not exist
+echo erR --trace --no-print-directory
+erR --trace --no-print-directory");
+
+# Verify that re-exec / recursion doesn't duplicate flags from GNUMAKEFLAGS
+
+unlink('x.mk');
+
+$extraENV{GNUMAKEFLAGS} = '-Itst/bad';
+
+run_make_test(q!
+recurse: ; @echo $@; echo MAKEFLAGS = $$MAKEFLAGS; echo GNUMAKEFLAGS = $$GNUMAKEFLAGS; #MAKEPATH# -f #MAKEFILE# all
+all: ; @echo $@; echo MAKEFLAGS = $$MAKEFLAGS; echo GNUMAKEFLAGS = $$GNUMAKEFLAGS
+-include x.mk
+x.mk: ; @echo $@; echo MAKEFLAGS = $$MAKEFLAGS; echo GNUMAKEFLAGS = $$GNUMAKEFLAGS; echo > $@
+!,
+ "", "x.mk\nMAKEFLAGS = -Itst/bad\nGNUMAKEFLAGS =\nrecurse\nMAKEFLAGS = -Itst/bad\nGNUMAKEFLAGS =\n#MAKE#[1]: Entering directory '#PWD#'\nall\nMAKEFLAGS = w -Itst/bad\nGNUMAKEFLAGS =\n#MAKE#[1]: Leaving directory '#PWD#'\n");
+
+unlink('x.mk');
+
+1;
diff --git a/src/kmk/tests/scripts/variables/INCLUDE_DIRS b/src/kmk/tests/scripts/variables/INCLUDE_DIRS
new file mode 100644
index 0000000..c9662e9
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/INCLUDE_DIRS
@@ -0,0 +1,46 @@
+# -*-perl-*-
+$description = "Test the .INCLUDE_DIRS special variable.";
+
+$details = "";
+
+use Cwd;
+
+$dir = cwd;
+$dir =~ s,.*/([^/]+)$,../$1,;
+
+# Test #1: The content of .INCLUDE_DIRS depends on the platform for which
+# make was built. What we know for sure is that it shouldn't be
+# empty.
+#
+run_make_test('
+ifeq ($(.INCLUDE_DIRS),)
+$(warning .INCLUDE_DIRS is empty)
+endif
+
+.PHONY: all
+all:;@:
+',
+'',
+'');
+
+
+# Test #2: Make sure -I paths end up in .INCLUDE_DIRS.
+#
+run_make_test('
+ifeq ($(dir),)
+$(warning dir is empty)
+endif
+
+ifeq ($(filter $(dir),$(.INCLUDE_DIRS)),)
+$(warning .INCLUDE_DIRS does not contain $(dir))
+endif
+
+.PHONY: all
+all:;@:
+',
+"-I$dir dir=$dir",
+'');
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/scripts/variables/LIBPATTERNS b/src/kmk/tests/scripts/variables/LIBPATTERNS
new file mode 100644
index 0000000..9182954
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/LIBPATTERNS
@@ -0,0 +1,38 @@
+# -*-perl-*-
+
+$description = "Test .LIBPATTERNS special variable.";
+
+$details = "";
+
+# TEST 0: basics
+
+touch('mtest_foo.a');
+
+run_make_test('
+.LIBPATTERNS = mtest_%.a
+all: -lfoo ; @echo "build $@ from $<"
+',
+ '', "build all from mtest_foo.a\n");
+
+# TEST 1: Handle elements that are not patterns.
+
+run_make_test('
+.LIBPATTERNS = mtest_foo.a mtest_%.a
+all: -lfoo ; @echo "build $@ from $<"
+',
+ '', "#MAKE#: .LIBPATTERNS element 'mtest_foo.a' is not a pattern
+build all from mtest_foo.a\n");
+
+# TEST 2: target-specific override
+
+# Uncomment this when we add support, see Savannah bug #25703
+# run_make_test('
+# .LIBPATTERNS = mbad_%.a
+# all: .LIBPATTERNS += mtest_%.a
+# all: -lfoo ; @echo "build $@ from $<"
+# ',
+# '', "build all from mtest_foo.a\n");
+
+unlink('mtest_foo.a');
+
+1;
diff --git a/src/kmk/tests/scripts/variables/MAKE b/src/kmk/tests/scripts/variables/MAKE
new file mode 100644
index 0000000..dc62160
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKE
@@ -0,0 +1,24 @@
+# -*-perl-*-
+
+$description = "Test proper behavior of the MAKE variable";
+
+$details = "DETAILS";
+
+run_make_test(q!
+TMP := $(MAKE)
+MAKE := $(subst X=$(X),,$(MAKE))
+all:
+ @echo $(TMP)
+ $(MAKE) -f #MAKEFILE# foo
+
+foo:
+ @echo $(MAKE)
+!,
+ '',
+ "#MAKEPATH#\n#MAKEPATH# -f #MAKEFILE# foo\n"
+ . "#MAKE#[1]: Entering directory '#PWD#'\n"
+ . "#MAKEPATH#\n#MAKE#[1]: Leaving directory '#PWD#'\n");
+
+rmfiles("foo");
+
+1;
diff --git a/src/kmk/tests/scripts/variables/MAKECMDGOALS b/src/kmk/tests/scripts/variables/MAKECMDGOALS
new file mode 100644
index 0000000..879283b
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKECMDGOALS
@@ -0,0 +1,52 @@
+# -*-perl-*-
+
+$description = "Test the MAKECMDGOALS variable.";
+
+$details = "\
+We construct a makefile with various targets, all of which print out
+\$(MAKECMDGOALS), then call it different ways.";
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE "\
+.DEFAULT all:
+ \@echo \$(MAKECMDGOALS)
+";
+close(MAKEFILE);
+
+# TEST #1
+
+&run_make_with_options($makefile,
+ "",
+ &get_logfile,
+ 0);
+$answer = "\n";
+&compare_output($answer,&get_logfile(1));
+
+# TEST #2
+
+&run_make_with_options($makefile,
+ "all",
+ &get_logfile,
+ 0);
+$answer = "all\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# TEST #3
+
+&run_make_with_options($makefile,
+ "foo bar baz yaz",
+ &get_logfile,
+ 0);
+$answer = "foo bar baz yaz\nfoo bar baz yaz\nfoo bar baz yaz\nfoo bar baz yaz\n";
+&compare_output($answer,&get_logfile(1));
+
+
+# This tells the test driver that the perl test script executed properly.
+1;
+
+
+
+
+
+
diff --git a/src/kmk/tests/scripts/variables/MAKEFILES b/src/kmk/tests/scripts/variables/MAKEFILES
new file mode 100644
index 0000000..b23da8e
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKEFILES
@@ -0,0 +1,53 @@
+# -*-perl-*-
+
+$description = "Test the MAKEFILES variable.";
+
+$makefile2 = &get_tmpfile;
+$makefile3 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE 'all: ; @echo DEFAULT RULE: M2=$(M2) M3=$(M3)', "\n";
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+print MAKEFILE <<EOF;
+M2 = m2
+NDEF: ; \@echo RULE FROM MAKEFILE 2
+EOF
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile3");
+print MAKEFILE <<EOF;
+M3 = m3
+NDEF3: ; \@echo RULE FROM MAKEFILE 3
+EOF
+close(MAKEFILE);
+
+
+&run_make_with_options($makefile, "MAKEFILES='$makefile2 $makefile3'",
+ &get_logfile);
+$answer = "DEFAULT RULE: M2=m2 M3=m3\n";
+&compare_output($answer,&get_logfile(1));
+
+# TEST 2: Verify that included makefiles don't set the default goal.
+# See Savannah bug #13401.
+
+create_file('xx-inc.mk', '
+include_goal: ; @echo $@
+include xx-ind.mk
+');
+
+create_file('xx-ind.mk', '
+indirect_goal: ; @echo $@
+');
+
+run_make_test(q!
+top: ; @echo $@
+!,
+ 'MAKEFILES=xx-inc.mk', "top\n");
+
+unlink(qw(xx-inc.mk xx-ind.mk));
+
+1;
diff --git a/src/kmk/tests/scripts/variables/MAKEFILE_LIST b/src/kmk/tests/scripts/variables/MAKEFILE_LIST
new file mode 100644
index 0000000..076e42d
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKEFILE_LIST
@@ -0,0 +1,30 @@
+# -*-perl-*-
+
+$description = "Test the MAKEFILE_LIST variable.";
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE <<EOF;
+m1 := \$(MAKEFILE_LIST)
+include $makefile2
+m3 := \$(MAKEFILE_LIST)
+
+all:
+\t\@echo \$(m1)
+\t\@echo \$(m2)
+\t\@echo \$(m3)
+EOF
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+print MAKEFILE "m2 := \$(MAKEFILE_LIST)\n";
+close(MAKEFILE);
+
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "$makefile\n$makefile $makefile2\n$makefile $makefile2\n";
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/variables/MAKEFLAGS b/src/kmk/tests/scripts/variables/MAKEFLAGS
new file mode 100644
index 0000000..0fac74a
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKEFLAGS
@@ -0,0 +1,45 @@
+# -*-perl-*-
+
+$description = "Test proper behavior of MAKEFLAGS";
+
+$details = "DETAILS";
+
+# Normal flags aren't prefixed with "-"
+run_make_test(q!
+all: ; @echo $(MAKEFLAGS)
+!,
+ '-e -r -R', 'erR');
+
+# Long arguments mean everything is prefixed with "-"
+run_make_test(q!
+all: ; @echo $(MAKEFLAGS)
+!,
+ '--no-print-directory -e -r -R --trace', "#MAKEFILE#:2: target 'all' does not exist
+echo erR --trace --no-print-directory
+erR --trace --no-print-directory");
+
+
+# Recursive invocations of make should accumulate MAKEFLAGS values.
+# Savannah bug #2216
+run_make_test(q!
+MSG = Fails
+all:
+ @echo '$@: MAKEFLAGS=$(MAKEFLAGS)'
+ @MSG=Works $(MAKE) -e -f #MAKEFILE# jump
+jump:
+ @echo '$@ $(MSG): MAKEFLAGS=$(MAKEFLAGS)'
+ @$(MAKE) -f #MAKEFILE# print
+print:
+ @echo '$@ $(MSG): MAKEFLAGS=$(MAKEFLAGS)'
+.PHONY: all jump print
+!,
+ '--no-print-directory',
+ 'all: MAKEFLAGS= --no-print-directory
+jump Works: MAKEFLAGS=e --no-print-directory
+print Works: MAKEFLAGS=e --no-print-directory');
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/variables/MAKELEVEL b/src/kmk/tests/scripts/variables/MAKELEVEL
new file mode 100644
index 0000000..0db3a68
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKELEVEL
@@ -0,0 +1,45 @@
+# -*-perl-*-
+
+$description = "The following test creates a makefile to test
+makelevels in Make. It prints \$(MAKELEVEL) and then
+prints the environment variable MAKELEVEL";
+
+open(MAKEFILE,"> $makefile");
+
+# The Contents of the MAKEFILE ...
+
+if (!$is_kmk) {
+ print MAKEFILE <<EOF;
+all:
+\t\@echo MAKELEVEL is \$(MAKELEVEL)
+\techo \$\$MAKELEVEL
+EOF
+} else {
+ print MAKEFILE <<EOF;
+all:
+\t\@echo KMK_LEVEL is \$(KMK_LEVEL)
+\techo \$\$KMK_LEVEL
+EOF
+}
+
+# END of Contents of MAKEFILE
+
+close(MAKEFILE);
+
+# RUN MAKE
+
+&run_make_with_options($makefile,"",&get_logfile);
+
+# SET ANSWER
+
+if (!$is_kmk) {
+ $answer = "MAKELEVEL is 0\necho \$MAKELEVEL\n1\n";
+} else {
+ $answer = "KMK_LEVEL is 0\necho \$KMK_LEVEL\n1\n";
+}
+
+# COMPARE RESULTS
+
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/variables/MAKE_RESTARTS b/src/kmk/tests/scripts/variables/MAKE_RESTARTS
new file mode 100644
index 0000000..01bf55e
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MAKE_RESTARTS
@@ -0,0 +1,61 @@
+# -*-perl-*-
+
+$description = "Test the MAKE_RESTARTS variable.";
+
+# Test basic capability
+
+run_make_test('
+all: ; @:
+$(info MAKE_RESTARTS=$(MAKE_RESTARTS))
+include foo.x
+foo.x: ; @touch $@
+',
+ '', 'MAKE_RESTARTS=
+MAKE_RESTARTS=1');
+
+rmfiles('foo.x');
+
+# Test multiple restarts
+
+run_make_test('
+all: ; @:
+$(info MAKE_RESTARTS=$(MAKE_RESTARTS))
+include foo.x
+foo.x: ; @echo "include bar.x" > $@
+bar.x: ; @touch $@
+',
+ '', 'MAKE_RESTARTS=
+MAKE_RESTARTS=1
+MAKE_RESTARTS=2');
+
+rmfiles('foo.x', 'bar.x');
+
+# Test multiple restarts and make sure the variable is cleaned up
+
+run_make_test('
+recurse:
+ @echo recurse MAKE_RESTARTS=$$MAKE_RESTARTS
+ @$(MAKE) -f #MAKEFILE# all
+all:
+ @echo all MAKE_RESTARTS=$$MAKE_RESTARTS
+$(info MAKE_RESTARTS=$(MAKE_RESTARTS))
+include foo.x
+foo.x: ; @echo "include bar.x" > $@
+bar.x: ; @touch $@
+',
+ '', "MAKE_RESTARTS=
+MAKE_RESTARTS=1
+MAKE_RESTARTS=2
+recurse MAKE_RESTARTS=
+#MAKE#[1]: Entering directory '#PWD#'
+MAKE_RESTARTS=
+all MAKE_RESTARTS=
+#MAKE#[1]: Leaving directory '#PWD#'");
+
+rmfiles('foo.x', 'bar.x');
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/variables/MFILE_LIST b/src/kmk/tests/scripts/variables/MFILE_LIST
new file mode 100644
index 0000000..076e42d
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/MFILE_LIST
@@ -0,0 +1,30 @@
+# -*-perl-*-
+
+$description = "Test the MAKEFILE_LIST variable.";
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE,"> $makefile");
+print MAKEFILE <<EOF;
+m1 := \$(MAKEFILE_LIST)
+include $makefile2
+m3 := \$(MAKEFILE_LIST)
+
+all:
+\t\@echo \$(m1)
+\t\@echo \$(m2)
+\t\@echo \$(m3)
+EOF
+close(MAKEFILE);
+
+
+open(MAKEFILE,"> $makefile2");
+print MAKEFILE "m2 := \$(MAKEFILE_LIST)\n";
+close(MAKEFILE);
+
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "$makefile\n$makefile $makefile2\n$makefile $makefile2\n";
+&compare_output($answer,&get_logfile(1));
+
+1;
diff --git a/src/kmk/tests/scripts/variables/SHELL b/src/kmk/tests/scripts/variables/SHELL
new file mode 100644
index 0000000..9d56796
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/SHELL
@@ -0,0 +1,103 @@
+# -*-perl-*-
+
+$description = "Test proper handling of SHELL.";
+
+# Find the default value when SHELL is not set. On UNIX it will be /bin/sh,
+# but on other platforms who knows?
+resetENV();
+delete $ENV{SHELL};
+$mshell = `echo 'all:;\@echo \$(SHELL)' | $make_path -f-`;
+chop $mshell;
+
+# According to POSIX, the value of SHELL in the environment has no impact on
+# the value in the makefile.
+# Note %extraENV takes precedence over the default value for the shell.
+
+$extraENV{SHELL} = '/dev/null';
+run_make_test('all:;@echo "$(SHELL)"', '', $mshell);
+
+# According to POSIX, any value of SHELL set in the makefile should _NOT_ be
+# exported to the subshell! I wanted to set SHELL to be $^X (perl) in the
+# makefile, but make runs $(SHELL) -c 'commandline' and that doesn't work at
+# all when $(SHELL) is perl :-/. So, we just add an extra initial /./ which
+# works well on UNIX and seems to work OK on at least some non-UNIX systems.
+
+$extraENV{SHELL} = $mshell;
+
+run_make_test("SHELL := /./$mshell\n".'
+all:;@echo "$(SHELL) $$SHELL"
+', '', "/./$mshell $mshell");
+
+# As a GNU make extension, if make's SHELL variable is explicitly exported,
+# then we really _DO_ export it.
+
+$extraENV{SHELL} = $mshell;
+
+run_make_test("export SHELL := /./$mshell\n".'
+all:;@echo "$(SHELL) $$SHELL"
+', '', "/./$mshell /./$mshell");
+
+
+# Test out setting of SHELL, both exported and not, as a target-specific
+# variable.
+
+$extraENV{SHELL} = $mshell;
+
+run_make_test("all: SHELL := /./$mshell\n".'
+all:;@echo "$(SHELL) $$SHELL"
+', '', "/./$mshell $mshell");
+
+$extraENV{SHELL} = $mshell;
+
+# bird: This was wrong at some point, see Savannah bug #24655. Was first fixed in kBuild.
+run_make_test("
+SHELL := /././$mshell
+one: two
+two: export SHELL := /./$mshell\n".'
+one two:;@echo "$@: $(SHELL) $$SHELL"
+', '', "two: /./$mshell /./$mshell\none: /././$mshell $mshell\n");
+
+# Test .SHELLFLAGS
+
+# We don't know the output here: on Solaris for example, every line printed
+# by the shell in -x mode has a trailing space (!!)
+my $script = 'true; true';
+my $flags = '-xc';
+my $out = `/bin/sh $flags '$script' 2>&1`;
+
+run_make_test(qq!
+.SHELLFLAGS = $flags
+all: ; \@$script
+!,
+ '', $out);
+
+# Do it again but add spaces to SHELLFLAGS
+
+# Some shells (*shakes fist at Solaris*) cannot handle multiple flags in
+# separate arguments.
+my $t = `/bin/sh -e -c true 2>/dev/null`;
+my $multi_ok = $? == 0;
+
+if ($multi_ok) {
+ $flags = '-x -c';
+ run_make_test(qq!
+.SHELLFLAGS = $flags
+all: ; \@$script
+!,
+ '', $out);
+}
+
+# We can't just use "false" because on different systems it provides a
+# different exit code--once again Solaris: false exits with 255 not 1
+$script = 'true; false; true';
+$flags = '-xec';
+$out = `/bin/sh $flags '$script' 2>&1`;
+my $err = $? >> 8;
+
+run_make_test(qq!
+.SHELLFLAGS = $flags
+all: ; \@$script
+!,
+ '', "$out#MAKE#: *** [#MAKEFILE#:3: all] Error $err\n", 512);
+
+1;
diff --git a/src/kmk/tests/scripts/variables/automatic b/src/kmk/tests/scripts/variables/automatic
new file mode 100644
index 0000000..2304fa0
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/automatic
@@ -0,0 +1,122 @@
+# -*-perl-*-
+
+$description = "Test automatic variable setting.";
+
+$details = "";
+
+use Cwd;
+
+$dir = cwd;
+$dir =~ s,.*/([^/]+)$,../$1,;
+
+open(MAKEFILE, "> $makefile");
+print MAKEFILE "dir = $dir\n";
+print MAKEFILE <<'EOF';
+.SUFFIXES:
+.SUFFIXES: .x .y .z
+$(dir)/foo.x : baz.z $(dir)/bar.y baz.z
+ @echo '$$@ = $@, $$(@D) = $(@D), $$(@F) = $(@F)'
+ @echo '$$* = $*, $$(*D) = $(*D), $$(*F) = $(*F)'
+ @echo '$$< = $<, $$(<D) = $(<D), $$(<F) = $(<F)'
+ @echo '$$^ = $^, $$(^D) = $(^D), $$(^F) = $(^F)'
+ @echo '$$+ = $+, $$(+D) = $(+D), $$(+F) = $(+F)'
+ @echo '$$? = $?, $$(?D) = $(?D), $$(?F) = $(?F)'
+ touch $@
+
+$(dir)/bar.y baz.z : ; touch $@
+EOF
+close(MAKEFILE);
+
+# TEST #0 -- simple test
+# -------
+
+# Touch these into the past
+&utouch(-10, qw(foo.x baz.z));
+
+&run_make_with_options($makefile, "", &get_logfile);
+$answer = "touch $dir/bar.y
+\$\@ = $dir/foo.x, \$(\@D) = $dir, \$(\@F) = foo.x
+\$* = $dir/foo, \$(*D) = $dir, \$(*F) = foo
+\$< = baz.z, \$(<D) = ., \$(<F) = baz.z
+\$^ = baz.z $dir/bar.y, \$(^D) = . $dir, \$(^F) = baz.z bar.y
+\$+ = baz.z $dir/bar.y baz.z, \$(+D) = . $dir ., \$(+F) = baz.z bar.y baz.z
+\$? = $dir/bar.y, \$(?D) = $dir, \$(?F) = bar.y
+touch $dir/foo.x\n";
+&compare_output($answer, &get_logfile(1));
+
+unlink(qw(foo.x bar.y baz.z));
+
+# TEST #1 -- test the SysV emulation of $$@ etc.
+# -------
+
+$makefile2 = &get_tmpfile;
+
+open(MAKEFILE, "> $makefile2");
+print MAKEFILE "dir = $dir\n";
+print MAKEFILE <<'EOF';
+.SECONDEXPANSION:
+.SUFFIXES:
+.DEFAULT: ; @echo '$@'
+
+$(dir)/foo $(dir)/bar: $@.x $$@.x $$$@.x $$$$@.x $$(@D).x $$(@F).x
+
+$(dir)/x.z $(dir)/y.z: $(dir)/%.z : $@.% $$@.% $$$@.% $$$$@.% $$(@D).% $$(@F).%
+
+$(dir)/biz: $$(@).x $${@}.x $${@D}.x $${@F}.x
+EOF
+
+close(MAKEFILE);
+
+&run_make_with_options($makefile2, "-j1 $dir/foo $dir/bar", &get_logfile);
+$answer = ".x\n$dir/foo.x\nx\n\$@.x\n$dir.x\nfoo.x\n$dir/bar.x\nbar.x\n";
+&compare_output($answer, &get_logfile(1));
+
+&run_make_with_options($makefile2, "-j1 $dir/x.z $dir/y.z", &get_logfile);
+$answer = ".x\n$dir/x.z.x\nx\n\$@.x\n$dir.x\nx.z.x\n.y\n$dir/y.z.y\n\y\n\$@.y\n$dir.y\ny.z.y\n";
+&compare_output($answer, &get_logfile(1));
+
+&run_make_with_options($makefile2, "-j1 $dir/biz", &get_logfile);
+$answer = "$dir/biz.x\n$dir.x\nbiz.x\n";
+&compare_output($answer, &get_logfile(1));
+
+# TEST #2 -- test for Savannah bug #12320.
+#
+run_make_test('
+.SUFFIXES: .b .src
+
+mbr.b: mbr.src
+ @echo $*
+
+mbr.src: ; @:',
+ '',
+ 'mbr');
+
+# TEST #3 -- test for Savannah bug #8154
+# Make sure that nonexistent prerequisites are listed in $?, since they are
+# considered reasons for the target to be rebuilt.
+#
+# See also Savannah bugs #16002 and #16051.
+
+touch('foo');
+
+run_make_test('
+foo: bar ; @echo "\$$? = $?"
+bar: ;',
+ '',
+ '$? = bar');
+
+unlink('foo');
+
+# TEST #4: ensure prereq ordering is correct when the commmand target has none
+# See Savannah bug #21198
+
+run_make_test('
+all : A B
+all : ; @echo $@ -- $^ -- $<
+all : C D
+all : E F
+A B C D E F G H : ; @:
+',
+ '', "all -- A B C D E F -- A\n");
+
+1;
diff --git a/src/kmk/tests/scripts/variables/define b/src/kmk/tests/scripts/variables/define
new file mode 100644
index 0000000..7324cbc
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/define
@@ -0,0 +1,282 @@
+# -*-perl-*-
+
+$description = "Test define/endef variable assignments.";
+
+$details = "";
+
+# TEST 0: old-style basic define/endef
+
+run_make_test('
+define multi
+@echo hi
+echo there
+endef
+
+all: ; $(multi)
+',
+ '', "hi\necho there\nthere\n");
+
+# TEST 1: Various new-style define/endef
+
+run_make_test('
+FOO = foo
+
+define multi =
+echo hi
+@echo $(FOO)
+endef # this is the end
+
+define simple :=
+@echo $(FOO)
+endef
+
+define posix ::=
+@echo $(FOO)
+endef
+
+append = @echo a
+
+define append +=
+
+@echo b
+endef
+
+define cond ?= # this is a conditional
+@echo first
+endef
+
+define cond ?=
+@echo second
+endef
+
+FOO = there
+
+all: ; $(multi)
+ $(simple)
+ $(posix)
+ $(append)
+ $(cond)
+',
+ '', "echo hi\nhi\nthere\nfoo\nfoo\na\nb\nfirst\n");
+
+# TEST 1a: Various new-style define/endef, with no spaces
+
+run_make_test('
+FOO = foo
+
+define multi=
+echo hi
+@echo $(FOO)
+endef # this is the end
+
+define simple:=
+@echo $(FOO)
+endef
+
+define posix::=
+@echo $(FOO)
+endef
+
+append = @echo a
+
+define append+=
+
+@echo b
+endef
+
+define cond?= # this is a conditional
+@echo first
+endef
+
+define cond?=
+@echo second
+endef
+
+FOO = there
+
+all: ; $(multi)
+ $(simple)
+ $(posix)
+ $(append)
+ $(cond)
+',
+ '', "echo hi\nhi\nthere\nfoo\nfoo\na\nb\nfirst\n");
+
+# TEST 2: define in true section of conditional (containing conditional)
+
+run_make_test('
+FOO = foo
+NAME = def
+def =
+ifdef BOGUS
+ define $(subst e,e,$(NAME)) =
+ ifeq (1,1)
+ FOO = bar
+ endif
+ endef
+endif
+
+$(eval $(def))
+all: ; @echo $(FOO)
+',
+ 'BOGUS=1', "bar\n");
+
+# TEST 3: define in false section of conditional (containing conditional)
+
+run_make_test(undef, '', "foo\n");
+
+# TEST 4: nested define (supported?)
+
+run_make_test('
+define outer
+ define inner
+ A = B
+ endef
+endef
+
+$(eval $(outer))
+
+outer: ; @echo $(inner)
+',
+ '', "A = B\n");
+
+# TEST 5: NEGATIVE: Missing variable name
+
+run_make_test('
+NAME =
+define $(NAME) =
+ouch
+endef
+all: ; @echo ouch
+',
+ '', "#MAKEFILE#:3: *** empty variable name. Stop.\n", 512);
+
+# TEST 6: NEGATIVE: extra text after define
+
+run_make_test('
+NAME =
+define NAME = $(NAME)
+ouch
+endef
+all: ; @echo ok
+',
+ '', "#MAKEFILE#:3: extraneous text after 'define' directive\nok\n");
+
+# TEST 7: NEGATIVE: extra text after endef
+
+run_make_test('
+NAME =
+define NAME =
+ouch
+endef $(NAME)
+all: ; @echo ok
+',
+ '', "#MAKEFILE#:5: extraneous text after 'endef' directive\nok\n");
+
+# TEST 8: NEGATIVE: missing endef
+
+run_make_test('
+NAME =
+all: ; @echo ok
+define NAME =
+ouch
+endef$(NAME)
+',
+ '', "#MAKEFILE#:4: *** missing 'endef', unterminated 'define'. Stop.\n", 512);
+
+# -------------------------
+# Make sure that prefix characters apply properly to define/endef values.
+#
+# There's a bit of oddness here if you try to use a variable to hold the
+# prefix character for a define. Even though something like this:
+#
+# define foo
+# echo bar
+# endef
+#
+# all: ; $(V)$(foo)
+#
+# (where V=@) can be seen by the user to be obviously different than this:
+#
+# define foo
+# $(V)echo bar
+# endef
+#
+# all: ; $(foo)
+#
+# and the user thinks it should behave the same as when the "@" is literal
+# instead of in a variable, that can't happen because by the time make
+# expands the variables for the command line and sees it begins with a "@" it
+# can't know anymore whether the prefix character came before the variable
+# reference or was included in the first line of the variable reference.
+
+# TEST #5
+# -------
+
+run_make_test('
+define FOO
+$(V1)echo hello
+$(V2)echo world
+endef
+all: ; @$(FOO)
+', '', 'hello
+world');
+
+# TEST #6
+# -------
+
+run_make_test(undef, 'V1=@ V2=@', 'hello
+world');
+
+# TEST #7
+# -------
+
+run_make_test('
+define FOO
+$(V1)echo hello
+$(V2)echo world
+endef
+all: ; $(FOO)
+', 'V1=@', 'hello
+echo world
+world');
+
+# TEST #8
+# -------
+
+run_make_test(undef, 'V2=@', 'echo hello
+hello
+world');
+
+# TEST #9
+# -------
+
+run_make_test(undef, 'V1=@ V2=@', 'hello
+world');
+
+# TEST #10
+# -------
+# Test the basics; a "@" internally to the variable applies to only one line.
+# A "@" before the variable applies to the entire variable.
+
+run_make_test('
+define FOO
+@echo hello
+echo world
+endef
+define BAR
+echo hello
+echo world
+endef
+
+all: foo bar
+foo: ; $(FOO)
+bar: ; @$(BAR)
+', '', 'hello
+echo world
+world
+hello
+world
+');
+
+1;
diff --git a/src/kmk/tests/scripts/variables/flavors b/src/kmk/tests/scripts/variables/flavors
new file mode 100644
index 0000000..ba133ea
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/flavors
@@ -0,0 +1,96 @@
+# -*-perl-*-
+
+$description = "Test various flavors of make variable setting.";
+
+$details = "";
+
+# TEST 0: Recursive
+
+run_make_test('
+ugh = Goodbye
+foo = $(bar)
+bar = ${ugh}
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Hello\n");
+
+# TEST 1: Simple
+
+run_make_test('
+bar = Goodbye
+foo := $(bar)
+bar = ${ugh}
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Goodbye\n");
+
+# TEST 2: Append to recursive
+
+run_make_test('
+foo = Hello
+ugh = Goodbye
+foo += $(bar)
+bar = ${ugh}
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Hello Hello\n");
+
+# TEST 3: Append to simple
+
+run_make_test('
+foo := Hello
+ugh = Goodbye
+bar = ${ugh}
+foo += $(bar)
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Hello Goodbye\n");
+
+# TEST 4: Conditional pre-set
+
+run_make_test('
+foo = Hello
+ugh = Goodbye
+bar = ${ugh}
+foo ?= $(bar)
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Hello\n");
+
+# TEST 5: Conditional unset
+
+run_make_test('
+ugh = Goodbye
+bar = ${ugh}
+foo ?= $(bar)
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Hello\n");
+
+# TEST 6: Simple using POSIX syntax
+run_make_test('
+bar = Goodbye
+foo ::= $(bar)
+bar = ${ugh}
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Goodbye\n");
+
+# TEST 7: POSIX syntax no spaces
+run_make_test('
+bar = Goodbye
+foo::=$(bar)
+bar = ${ugh}
+ugh = Hello
+all: ; @echo $(foo)
+',
+ '', "Goodbye\n");
+
+1;
diff --git a/src/kmk/tests/scripts/variables/must_make b/src/kmk/tests/scripts/variables/must_make
new file mode 100644
index 0000000..83a8275
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/must_make
@@ -0,0 +1,81 @@
+# $Id: must_make 2413 2010-09-11 17:43:04Z bird $ -*-perl-*-
+## @file
+# .MUST_MAKE target variable.
+#
+
+#
+# Copyright (c) 2008-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+$description = "Tests the .MUST_MAKE target variable";
+
+$details = "The .MUST_MAKE target variable is expanded when make is deciding
+whether a file needs to be made or not. If it returns a non-empty string,
+when stripped, it will force the file to be made. If it returns an empty
+string GNU make decides the normal way. Note that .MUST_MAKE does NOT have
+to be expanded if make already knows the file needs building. Also, note
+that for multi target rules it may be invoked for each file.";
+
+if ($is_kmk) {
+
+ # TEST #0 - check to see that it gets called and is made.
+ # -------------------------------------------------------
+ &touch('foobar.1');
+ run_make_test('
+
+foobar.1: .MUST_MAKE = $(info mustmake:{@=$@,<=$<})FORCE
+foobar.1: ;touch $@
+',
+'',
+'mustmake:{@=foobar.1,<=}
+touch foobar.1'
+);
+ unlink('foobar.1');
+
+ # TEST #1 - check to see that it gets called and isn't made.
+ # ----------------------------------------------------------
+ &touch('foobar.1');
+ run_make_test('
+
+foobar.1: .MUST_MAKE = $(info mustmake:{@=$@,<=$<})
+foobar.1: ;touch $@
+',
+'',
+'mustmake:{@=foobar.1,<=}
+#MAKE#: `foobar.1\' is up to date.'
+);
+ unlink('foobar.1');
+
+ # TEST #2 - check to see that it doesn't get called unnecessary.
+ # --------------------------------------------------------------
+ run_make_test('
+foobar.1: .MUST_MAKE = $(info mustmake:{@=$@,<=$})FORCE
+foobar.1: ;@echo making $@
+',
+'',
+'making foobar.1');
+
+}
+
+
+
+# Indicate that we're done.
+1;
+
+
diff --git a/src/kmk/tests/scripts/variables/negative b/src/kmk/tests/scripts/variables/negative
new file mode 100644
index 0000000..16a72b8
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/negative
@@ -0,0 +1,46 @@
+# -*-perl-*-
+
+$description = "Run some negative tests (things that should fail).";
+
+# TEST #0
+# Check that non-terminated variable references are detected (and
+# reported using the best filename/lineno info
+run_make_test('
+foo = bar
+x = $(foo
+y = $x
+
+all: ; @echo $y
+',
+ '', '#MAKEFILE#:3: *** unterminated variable reference. Stop.',
+ 512);
+
+# TEST #1
+# Bogus variable value passed on the command line.
+run_make_test(undef,
+ 'x=\$\(other',
+ '#MAKEFILE#:4: *** unterminated variable reference. Stop.',
+ 512);
+
+# TEST #2
+# Again, but this time while reading the makefile.
+run_make_test('
+foo = bar
+x = $(foo
+y = $x
+
+z := $y
+
+all: ; @echo $y
+',
+ '', '#MAKEFILE#:3: *** unterminated variable reference. Stop.',
+ 512);
+
+# TEST #3
+# Bogus variable value passed on the command line.
+run_make_test(undef,
+ 'x=\$\(other',
+ '#MAKEFILE#:4: *** unterminated variable reference. Stop.',
+ 512);
+
+1;
diff --git a/src/kmk/tests/scripts/variables/private b/src/kmk/tests/scripts/variables/private
new file mode 100644
index 0000000..8967ffb
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/private
@@ -0,0 +1,122 @@
+# -*-perl-*-
+
+$description = "Test 'private' variables.";
+
+$details = "";
+
+# 1: Simple verification that private variables are not inherited
+&run_make_test('
+a:
+F = g
+a: F = a
+b: private F = b
+
+a b c: ; @echo $@: F=$(F)
+a: b
+b: c
+',
+ '', "c: F=a\nb: F=b\na: F=a\n");
+
+# 2: Again, but this time we start with "b" so "a"'s variable is not in scope
+&run_make_test(undef, 'b', "c: F=g\nb: F=b\n");
+
+# 3: Verification with pattern-specific variables
+&run_make_test('
+t.a:
+
+F1 = g
+F2 = g
+%.a: private F1 = a
+%.a: F2 = a
+
+t.a t.b: ; @echo $@: F1=$(F1) / F2=$(F2)
+t.a: t.b
+',
+ '', "t.b: F1=g / F2=a\nt.a: F1=a / F2=a\n");
+
+# 4: Test private global variables
+&run_make_test('
+a:
+private F = g
+G := $(F)
+a:
+b: F = b
+
+a b: ; @echo $@: F=$(F) / G=$(G)
+a: b
+',
+ '', "b: F=b / G=g\na: F= / G=g\n");
+
+# 5: Multiple conditions on the same variable. Test export.
+delete $ENV{'_X'};
+&run_make_test('
+_X = x
+a: export override private _X = a
+a: ; @echo _X=$(_X) / _X=$$_X
+',
+ '', "_X=a / _X=a");
+
+# 6: Test override.
+&run_make_test(undef, '_X=c', "_X=a / _X=a\n");
+
+# 7: Ensure keywords still work as targets
+&run_make_test('
+a: export override private foo bar
+foo bar export override private: ; @echo $@
+',
+ '', "export\noverride\nprivate\nfoo\nbar\n");
+
+# 8: Ensure keywords still work as variables
+&run_make_test('
+private = g
+a: private = a
+a: b
+a b: ; @echo $@=$(private)
+',
+ '', "b=a\na=a\n");
+
+# 9: make sure private suppresses inheritance
+run_make_test(q!
+DEFS = FOO
+all: bar1
+bar1: private DEFS += 1
+bar3: private DEFS += 3
+bar1: bar2
+bar2: bar3
+bar1 bar2 bar3: ; @echo '$@: $(DEFS)'
+!,
+ '', "bar3: FOO 3\nbar2: FOO\nbar1: FOO 1\n");
+
+# 10: Test append with pattern-specific variables and private
+
+run_make_test(q!
+IA = global
+PA = global
+PS = global
+S = global
+PS = global
+SV = global
+b%: IA += b%
+b%: private PA += b%
+b%: private PS = b%
+bar: all
+bar: IA += bar
+bar: private PA += bar
+bar: private PS = bar
+a%: IA += a%
+a%: private PA += a%
+a%: private PS = a%
+all: IA += all
+all: private PA += all
+all: private PS = all
+
+bar all: ; @echo '$@: IA=$(IA)'; echo '$@: PA=$(PA)'; echo '$@: PS=$(PS)'
+!,
+ '', "all: IA=global b% bar a% all
+all: PA=global a% all
+all: PS=all
+bar: IA=global b% bar
+bar: PA=global b% bar
+bar: PS=bar\n");
+
+1;
diff --git a/src/kmk/tests/scripts/variables/special b/src/kmk/tests/scripts/variables/special
new file mode 100644
index 0000000..7e8a64f
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/special
@@ -0,0 +1,150 @@
+# -*-perl-*-
+
+$description = "Test special GNU make variables.";
+
+$details = "";
+
+&run_make_test('
+
+X1 := $(sort $(filter FOO BAR,$(.VARIABLES)))
+
+FOO := foo
+
+X2 := $(sort $(filter FOO BAR,$(.VARIABLES)))
+
+BAR := bar
+
+all: ; @echo X1 = $(X1); echo X2 = $(X2); echo LAST = $(sort $(filter FOO BAR,$(.VARIABLES)))
+',
+ '', "X1 =\nX2 = FOO\nLAST = BAR FOO\n");
+
+# SV 45728: Test that undefining a variable is reflected properly
+
+&run_make_test('
+FOO := foo
+BAR := bar
+$(info one: $(sort $(filter FOO BAR BAZ,$(.VARIABLES))))
+undefine BAR
+BAZ := baz
+$(info two: $(sort $(filter FOO BAR BAZ,$(.VARIABLES))))
+all:;@:
+',
+ '', "one: BAR FOO\ntwo: BAZ FOO\n");
+
+# $makefile2 = &get_tmpfile;
+# open(MAKEFILE, "> $makefile2");
+
+# print MAKEFILE <<'EOF';
+
+# X1 := $(sort $(.TARGETS))
+
+# all: foo
+# @echo X1 = $(X1)
+# @echo X2 = $(X2)
+# @echo LAST = $(sort $(.TARGETS))
+
+# X2 := $(sort $(.TARGETS))
+
+# foo:
+
+# EOF
+
+# close(MAKEFILE);
+
+# # TEST #2
+# # -------
+
+# &run_make_with_options($makefile2, "", &get_logfile);
+# $answer = "X1 =\nX2 = all\nLAST = all foo\n";
+# &compare_output($answer, &get_logfile(1));
+
+# Test the .RECIPEPREFIX variable
+# kmk: This test isn't -j1 safe, haven't bother looking into why yet.
+&run_make_test('
+define foo
+: foo-one\
+foo-two
+: foo-three
+ : foo-four
+endef
+
+orig: ; : orig-one
+ : orig-two \
+orig-three \
+ orig-four \
+ orig-five \\\\
+ : orig-six
+ $(foo)
+
+.RECIPEPREFIX = >
+test: ; : test-one
+>: test-two \
+test-three \
+>test-four \
+> test-five \\\\
+>: test-six
+>$(foo)
+
+.RECIPEPREFIX =
+reset: ; : reset-one
+ : reset-two \
+reset-three \
+ reset-four \
+ reset-five \\\\
+ : reset-six
+ $(foo)
+',
+ '-j1 orig test reset',
+ ': orig-one
+: orig-two \
+orig-three \
+orig-four \
+ orig-five \\\\
+: orig-six
+: foo-one foo-two
+: foo-three
+: foo-four
+: test-one
+: test-two \
+test-three \
+test-four \
+ test-five \\\\
+: test-six
+: foo-one foo-two
+: foo-three
+: foo-four
+: reset-one
+: reset-two \
+reset-three \
+reset-four \
+ reset-five \\\\
+: reset-six
+: foo-one foo-two
+: foo-three
+: foo-four');
+
+# Test that the "did you mean TAB" message is printed properly
+
+run_make_test(q!
+$x.
+!,
+ '', '#MAKEFILE#:2: *** missing separator. Stop.', 512);
+
+run_make_test(q!
+foo:
+ bar
+!,
+ '', '#MAKEFILE#:3: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.', 512);
+
+run_make_test(q!
+.RECIPEPREFIX = :
+foo:
+ bar
+!,
+ '', '#MAKEFILE#:4: *** missing separator. Stop.', 512);
+
+1;
+
+### Local Variables:
+### eval: (setq whitespace-action (delq 'auto-cleanup whitespace-action))
+### End:
diff --git a/src/kmk/tests/scripts/variables/undefine b/src/kmk/tests/scripts/variables/undefine
new file mode 100644
index 0000000..38707b8
--- /dev/null
+++ b/src/kmk/tests/scripts/variables/undefine
@@ -0,0 +1,73 @@
+# -*-perl-*-
+
+$description = "Test variable undefine.";
+
+$details = "";
+
+# TEST 0: basic undefine functionality
+
+run_make_test('
+a = a
+b := b
+define c
+c
+endef
+
+$(info $(flavor a) $(flavor b) $(flavor c))
+
+n := b
+
+undefine a
+undefine $n
+undefine c
+
+$(info $(flavor a) $(flavor b) $(flavor c))
+
+
+all: ;@:
+',
+'', "recursive simple recursive\nundefined undefined undefined");
+
+
+# TEST 1: override
+
+run_make_test('
+undefine a
+override undefine b
+
+$(info $(flavor a) $(flavor b))
+
+
+all: ;@:
+',
+'a=a b=b', "recursive undefined");
+
+1;
+
+# TEST 2: undefine in eval (make sure we undefine from the global var set)
+
+run_make_test('
+define undef
+$(eval undefine $$1)
+endef
+
+a := a
+$(call undef,a)
+$(info $(flavor a))
+
+
+all: ;@:
+',
+'', "undefined");
+
+
+# TEST 3: Missing variable name
+
+run_make_test('
+a =
+undefine $a
+all: ;@echo ouch
+',
+'', "#MAKEFILE#:3: *** empty variable name. Stop.\n", 512);
+
+1;
diff --git a/src/kmk/tests/scripts/vms/library b/src/kmk/tests/scripts/vms/library
new file mode 100644
index 0000000..9a64951
--- /dev/null
+++ b/src/kmk/tests/scripts/vms/library
@@ -0,0 +1,73 @@
+# -*-mode: perl-*-
+
+$description = "Test GNU make's VMS Library management features.";
+
+$details = "\
+This only works on VMS systems.";
+
+return -1 if $osname ne 'VMS';
+
+# Help library
+$mk_string = "help : help.hlb(file1.hlp)\n\n" .
+"file1.hlp :\n" .
+"\t\@pipe open/write xxx file1.hlp ; write xxx \"1 help\" ; close xxx\n";
+
+my $answer = "library /replace help.hlb file1.hlp";
+
+run_make_test($mk_string,
+ '', $answer);
+
+unlink('help.hlb');
+unlink('file1.hlp');
+
+#Text library
+$mk_string = "text : text.tlb(file1.txt)\n\n" .
+"file1.txt :\n" .
+"\t\@pipe open/write xxx file1.txt ; write xxx \"text file\" ; close xxx\n";
+
+my $answer = "library /replace text.tlb file1.txt";
+
+run_make_test($mk_string,
+ '', $answer);
+
+unlink('text.tlb');
+unlink('file1.txt');
+
+
+#Macro library
+$mk_string = "macro : macro.mlb(file1.mar)\n\n" .
+"file1.mar :\n" .
+"\t\pipe open/write xxx file1.mar ; " .
+"write xxx \".macro a b\" ; write xxx \".endm\" ; close xxx\n";
+
+my $answer = "library /replace macro.mlb file1.mar";
+
+run_make_test($mk_string,
+ '', $answer);
+
+unlink('macro.mlb');
+unlink('file1.mar');
+
+$mk_string =
+"all:imagelib.olb(file2.exe)\n" .
+"file2.exe : file2.obj file2.opt\n" .
+"\t\@link /share=\$\@ \$\*,\$\*/opt\n\n" .
+"file2.opt :\n" .
+"\t\@pipe open/write xxx file2.opt ; " .
+"write xxx \"CASE_SENSITIVE=YES\" ; close xxx\n" .
+"file2.c :\n" .
+"\t\@pipe open/write xxx file2.c ; write xxx \"file2(){}\" ; close xxx\n";
+
+my $answer = "library /replace imagelib.olb file2.exe";
+
+run_make_test($mk_string,
+ '', $answer);
+
+unlink('imagelib.olb');
+unlink('file2.c');
+unlink('file2.obj');
+unlink('file2.exe');
+unlink('file2.opt');
+
+# This tells the test driver that the perl test script executed properly.
+1;
diff --git a/src/kmk/tests/test_driver.pl b/src/kmk/tests/test_driver.pl
new file mode 100644
index 0000000..799a65d
--- /dev/null
+++ b/src/kmk/tests/test_driver.pl
@@ -0,0 +1,1498 @@
+#!/usr/bin/perl
+# -*-perl-*-
+#
+# Modification history:
+# Written 91-12-02 through 92-01-01 by Stephen McGee.
+# Modified 92-02-11 through 92-02-22 by Chris Arthur to further generalize.
+#
+# Copyright (C) 1991-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+# Test driver routines used by a number of test suites, including
+# those for SCS, make, roll_dir, and scan_deps (?).
+#
+# this routine controls the whole mess; each test suite sets up a few
+# variables and then calls &toplevel, which does all the real work.
+
+# $Id$
+
+
+# The number of test categories we've run
+$categories_run = 0;
+# The number of test categroies that have passed
+$categories_passed = 0;
+# The total number of individual tests that have been run
+$total_tests_run = 0;
+# The total number of individual tests that have passed
+$total_tests_passed = 0;
+# The number of tests in this category that have been run
+$tests_run = 0;
+# The number of tests in this category that have passed
+$tests_passed = 0;
+
+
+# Yeesh. This whole test environment is such a hack!
+$test_passed = 1;
+
+# Timeout in seconds. If the test takes longer than this we'll fail it.
+$test_timeout = 5;
+$test_timeout = 10 if $^O eq 'VMS';
+
+# Path to Perl
+$perl_name = $^X;
+
+# %makeENV is the cleaned-out environment.
+%makeENV = ();
+
+# %extraENV are any extra environment variables the tests might want to set.
+# These are RESET AFTER EVERY TEST!
+%extraENV = ();
+
+sub vms_get_process_logicals {
+ # Sorry for the long note here, but to keep this test running on
+ # VMS, it is needed to be understood.
+ #
+ # Perl on VMS by default maps the %ENV array to the system wide logical
+ # name table.
+ #
+ # This is a very large dynamically changing table.
+ # On Linux, this would be the equivalent of a table that contained
+ # every mount point, temporary pipe, and symbolic link on every
+ # file system. You normally do not have permission to clear or replace it,
+ # and if you did, the results would be catastrophic.
+ #
+ # On VMS, added/changed %ENV items show up in the process logical
+ # name table. So to track changes, a copy of it needs to be captured.
+
+ my $raw_output = `show log/process/access_mode=supervisor`;
+ my @raw_output_lines = split('\n',$raw_output);
+ my %log_hash;
+ foreach my $line (@raw_output_lines) {
+ if ($line =~ /^\s+"([A-Za-z\$_]+)"\s+=\s+"(.+)"$/) {
+ $log_hash{$1} = $2;
+ }
+ }
+ return \%log_hash
+}
+
+# %origENV is the caller's original environment
+if ($^O ne 'VMS') {
+ %origENV = %ENV;
+} else {
+ my $proc_env = vms_get_process_logicals;
+ %origENV = %{$proc_env};
+}
+
+sub resetENV
+{
+ # We used to say "%ENV = ();" but this doesn't work in Perl 5.000
+ # through Perl 5.004. It was fixed in Perl 5.004_01, but we don't
+ # want to require that here, so just delete each one individually.
+
+ if ($^O ne 'VMS') {
+ foreach $v (keys %ENV) {
+ delete $ENV{$v};
+ }
+
+ %ENV = %makeENV;
+ } else {
+ my $proc_env = vms_get_process_logicals();
+ my %delta = %{$proc_env};
+ foreach my $v (keys %delta) {
+ if (exists $origENV{$v}) {
+ if ($origENV{$v} ne $delta{$v}) {
+ $ENV{$v} = $origENV{$v};
+ }
+ } else {
+ delete $ENV{$v};
+ }
+ }
+ }
+
+ foreach $v (keys %extraENV) {
+ $ENV{$v} = $extraENV{$v};
+ delete $extraENV{$v};
+ }
+}
+
+sub toplevel
+{
+ # Pull in benign variables from the user's environment
+
+ foreach (# UNIX-specific things
+ 'TZ', 'TMPDIR', 'HOME', 'USER', 'LOGNAME', 'PATH',
+ 'LD_LIBRARY_PATH',
+ # Purify things
+ 'PURIFYOPTIONS',
+ # Windows NT-specific stuff
+ 'Path', 'SystemRoot',
+ # DJGPP-specific stuff
+ 'DJDIR', 'DJGPP', 'SHELL', 'COMSPEC', 'HOSTNAME', 'LFN',
+ 'FNCASE', '387', 'EMU387', 'GROUP'
+ ) {
+ $makeENV{$_} = $ENV{$_} if $ENV{$_};
+ }
+
+ # Make sure our compares are not foiled by locale differences
+
+ $makeENV{LC_ALL} = 'C';
+
+ # Replace the environment with the new one
+ #
+ %origENV = %ENV unless $^O eq 'VMS';
+
+ resetENV();
+
+ $| = 1; # unbuffered output
+
+ $debug = 0; # debug flag
+ $profile = 0; # profiling flag
+ $verbose = 0; # verbose mode flag
+ $detail = 0; # detailed verbosity
+ $keep = 0; # keep temp files around
+ $workdir = "work"; # The directory where the test will start running
+ $scriptdir = "scripts"; # The directory where we find the test scripts
+ $tmpfilesuffix = "t"; # the suffix used on tmpfiles
+ $default_output_stack_level = 0; # used by attach_default_output, etc.
+ $default_input_stack_level = 0; # used by attach_default_input, etc.
+ $cwd = "."; # don't we wish we knew
+ $cwdslash = ""; # $cwd . $pathsep, but "" rather than "./"
+ $is_kmk = 0; # kmk flag.
+ $is_fast = 0; # kmk_fgmake flag.
+
+ &get_osname; # sets $osname, $vos, $pathsep, $short_filenames,
+ # and $case_insensitive_fs
+
+ &set_defaults; # suite-defined
+
+ &parse_command_line (@ARGV);
+
+ print "OS name = '$osname'\n" if $debug;
+
+ $workpath = "$cwdslash$workdir";
+ $scriptpath = "$cwdslash$scriptdir";
+
+ &set_more_defaults; # suite-defined
+
+ &print_banner;
+
+ if ($osname eq 'VMS' && $cwdslash eq "")
+ {
+ # Porting this script to VMS revealed a small bug in opendir() not
+ # handling search lists correctly when the directory only exists in
+ # one of the logical_devices. Need to find the first directory in
+ # the search list, as that is where things will be written to.
+ my @dirs = split("/", $pwd);
+
+ my $logical_device = $ENV{$dirs[1]};
+ if ($logical_device =~ /([A-Za-z0-9_]+):(:?.+:)+/)
+ {
+ # A search list was found. Grab the first logical device
+ # and use it instead of the search list.
+ $dirs[1]=$1;
+ my $lcl_pwd = join('/', @dirs);
+ $workpath = $lcl_pwd . '/' . $workdir
+ }
+ }
+
+ if (-d $workpath)
+ {
+ print "Clearing $workpath...\n";
+ &remove_directory_tree("$workpath/")
+ || &error ("Couldn't wipe out $workpath\n");
+ }
+ else
+ {
+ mkdir ($workpath, 0777) || &error ("Couldn't mkdir $workpath: $!\n");
+ }
+
+ if (!-d $scriptpath)
+ {
+ &error ("Failed to find $scriptpath containing perl test scripts.\n");
+ }
+
+ if (@TESTS)
+ {
+ print "Making work dirs...\n";
+ foreach $test (@TESTS)
+ {
+ if ($test =~ /^([^\/]+)\//)
+ {
+ $dir = $1;
+ push (@rmdirs, $dir);
+ -d "$workpath/$dir"
+ || mkdir ("$workpath/$dir", 0777)
+ || &error ("Couldn't mkdir $workpath/$dir: $!\n");
+ }
+ }
+ }
+ else
+ {
+ print "Finding tests...\n";
+ opendir (SCRIPTDIR, $scriptpath)
+ || &error ("Couldn't opendir $scriptpath: $!\n");
+ @dirs = grep (!/^(\..*|CVS|RCS)$/, readdir (SCRIPTDIR) );
+ closedir (SCRIPTDIR);
+ foreach $dir (@dirs)
+ {
+ next if ($dir =~ /^(\..*|CVS|RCS)$/ || ! -d "$scriptpath/$dir");
+ push (@rmdirs, $dir);
+ # VMS can have overlayed file systems, so directories may repeat.
+ next if -d "$workpath/$dir";
+ mkdir ("$workpath/$dir", 0777)
+ || &error ("Couldn't mkdir $workpath/$dir: $!\n");
+ opendir (SCRIPTDIR, "$scriptpath/$dir")
+ || &error ("Couldn't opendir $scriptpath/$dir: $!\n");
+ @files = grep (!/^(\..*|CVS|RCS|.*~)$/, readdir (SCRIPTDIR) );
+ closedir (SCRIPTDIR);
+ foreach $test (@files)
+ {
+ -d $test and next;
+ push (@TESTS, "$dir/$test");
+ }
+ }
+ }
+
+ if (@TESTS == 0)
+ {
+ &error ("\nNo tests in $scriptpath, and none were specified.\n");
+ }
+
+ print "\n";
+
+ run_all_tests();
+
+ foreach $dir (@rmdirs)
+ {
+ rmdir ("$workpath/$dir");
+ }
+
+ $| = 1;
+
+ $categories_failed = $categories_run - $categories_passed;
+ $total_tests_failed = $total_tests_run - $total_tests_passed;
+
+ if ($total_tests_failed)
+ {
+ print "\n$total_tests_failed Test";
+ print "s" unless $total_tests_failed == 1;
+ print " in $categories_failed Categor";
+ print ($categories_failed == 1 ? "y" : "ies");
+ print " Failed (See .$diffext* files in $workdir dir for details) :-(\n\n";
+ return 0;
+ }
+ else
+ {
+ print "\n$total_tests_passed Test";
+ print "s" unless $total_tests_passed == 1;
+ print " in $categories_passed Categor";
+ print ($categories_passed == 1 ? "y" : "ies");
+ print " Complete ... No Failures :-)\n\n";
+ return 1;
+ }
+}
+
+sub get_osname
+{
+ # Set up an initial value. In perl5 we can do it the easy way.
+ $osname = defined($^O) ? $^O : '';
+
+ if ($osname eq 'VMS')
+ {
+ $vos = 0;
+ $pathsep = "/";
+ return;
+ }
+
+ # Find a path to Perl
+
+ # See if the filesystem supports long file names with multiple
+ # dots. DOS doesn't.
+ $short_filenames = 0;
+ (open (TOUCHFD, "> fancy.file.name") && close (TOUCHFD))
+ || ($short_filenames = 1);
+ unlink ("fancy.file.name") || ($short_filenames = 1);
+
+ if (! $short_filenames) {
+ # Thanks go to meyering@cs.utexas.edu (Jim Meyering) for suggesting a
+ # better way of doing this. (We used to test for existence of a /mnt
+ # dir, but that apparently fails on an SGI Indigo (whatever that is).)
+ # Because perl on VOS translates /'s to >'s, we need to test for
+ # VOSness rather than testing for Unixness (ie, try > instead of /).
+
+ mkdir (".ostest", 0777) || &error ("Couldn't create .ostest: $!\n", 1);
+ open (TOUCHFD, "> .ostest>ick") && close (TOUCHFD);
+ chdir (".ostest") || &error ("Couldn't chdir to .ostest: $!\n", 1);
+ }
+
+ if (! $short_filenames && -f "ick")
+ {
+ $osname = "vos";
+ $vos = 1;
+ $pathsep = ">";
+ }
+ else
+ {
+ # the following is regrettably knarly, but it seems to be the only way
+ # to not get ugly error messages if uname can't be found.
+ # Hmmm, BSD/OS 2.0's uname -a is excessively verbose. Let's try it
+ # with switches first.
+ eval "chop (\$osname = `sh -c 'uname -nmsr 2>&1'`)";
+ if ($osname =~ /not found/i)
+ {
+ $osname = "(something posixy with no uname)";
+ }
+ elsif ($@ ne "" || $?)
+ {
+ eval "chop (\$osname = `sh -c 'uname -a 2>&1'`)";
+ if ($@ ne "" || $?)
+ {
+ $osname = "(something posixy)";
+ }
+ }
+ $vos = 0;
+ $pathsep = "/";
+ }
+
+ if (! $short_filenames) {
+ chdir ("..") || &error ("Couldn't chdir to ..: $!\n", 1);
+ unlink (".ostest>ick");
+ rmdir (".ostest") || &error ("Couldn't rmdir .ostest: $!\n", 1);
+ }
+
+ # Check for case insensitive file system (bird)
+ # The deal is that the 2nd unlink will fail because the first one
+ # will already have removed the file if the fs ignore case.
+ $case_insensitive_fs = 0;
+ my $testfile1 = $short_filenames ? "CaseFs.rmt" : "CaseInSensitiveFs.check";
+ my $testfile2 = $short_filenames ? "casEfS.rmt" : "casEiNsensitivEfS.Check";
+ (open (TOUCHFD, "> $testfile1") && close (TOUCHFD))
+ || &error ("Couldn't create $testfile1: $!\n", 1);
+ (open (TOUCHFD, "> $testfile2") && close (TOUCHFD))
+ || &error ("Couldn't create $testfile2: $!\n", 1);
+ unlink ($testfile1) || &error ("Couldn't unlink $testfile1: $!\n", 1);
+ unlink ($testfile2) || ($case_insensitive_fs = 1);
+}
+
+sub parse_command_line
+{
+ @argv = @_;
+
+ # use @ARGV if no args were passed in
+
+ if (@argv == 0)
+ {
+ @argv = @ARGV;
+ }
+
+ # look at each option; if we don't recognize it, maybe the suite-specific
+ # command line parsing code will...
+
+ while (@argv)
+ {
+ $option = shift @argv;
+ if ($option =~ /^-debug$/i)
+ {
+ print "\nDEBUG ON\n";
+ $debug = 1;
+ }
+ elsif ($option =~ /^-usage$/i)
+ {
+ &print_usage;
+ exit 0;
+ }
+ elsif ($option =~ /^-(h|help)$/i)
+ {
+ &print_help;
+ exit 0;
+ }
+ elsif ($option =~ /^-profile$/i)
+ {
+ $profile = 1;
+ }
+ elsif ($option =~ /^-verbose$/i)
+ {
+ $verbose = 1;
+ }
+ elsif ($option =~ /^-detail$/i)
+ {
+ $detail = 1;
+ $verbose = 1;
+ }
+ elsif ($option =~ /^-keep$/i)
+ {
+ $keep = 1;
+ }
+ elsif ($option =~ /^-kmk/i)
+ {
+ $is_kmk = 1;
+ }
+ elsif ($option =~ /^-fast/i)
+ {
+ $is_fast = 1;
+ }
+ elsif (&valid_option($option))
+ {
+ # The suite-defined subroutine takes care of the option
+ }
+ elsif ($option =~ /^-/)
+ {
+ print "Invalid option: $option\n";
+ &print_usage;
+ exit 0;
+ }
+ else # must be the name of a test
+ {
+ $option =~ s/\.pl$//;
+ push(@TESTS,$option);
+ }
+ }
+}
+
+sub max
+{
+ local($num) = shift @_;
+ local($newnum);
+
+ while (@_)
+ {
+ $newnum = shift @_;
+ if ($newnum > $num)
+ {
+ $num = $newnum;
+ }
+ }
+
+ return $num;
+}
+
+sub print_centered
+{
+ local($width, $string) = @_;
+ local($pad);
+
+ if (length ($string))
+ {
+ $pad = " " x ( ($width - length ($string) + 1) / 2);
+ print "$pad$string";
+ }
+}
+
+sub print_banner
+{
+ local($info);
+ local($line);
+ local($len);
+
+ $info = "Running tests for $testee on $osname\n"; # $testee is suite-defined
+ $len = &max (length ($line), length ($testee_version),
+ length ($banner_info), 73) + 5;
+ $line = ("-" x $len) . "\n";
+ if ($len < 78)
+ {
+ $len = 78;
+ }
+
+ &print_centered ($len, $line);
+ &print_centered ($len, $info);
+ &print_centered ($len, $testee_version); # suite-defined
+ &print_centered ($len, $banner_info); # suite-defined
+ &print_centered ($len, $line);
+ print "\n";
+}
+
+sub run_all_tests
+{
+ $categories_run = 0;
+
+ $lasttest = '';
+ foreach $testname (sort @TESTS) {
+ # Skip duplicates on VMS caused by logical name search lists.
+ next if $testname eq $lasttest;
+ $lasttest = $testname;
+ $suite_passed = 1; # reset by test on failure
+ $num_of_logfiles = 0;
+ $num_of_tmpfiles = 0;
+ $description = "";
+ $details = "";
+ $old_makefile = undef;
+ $testname =~ s/^$scriptpath$pathsep//;
+ $perl_testname = "$scriptpath$pathsep$testname";
+ $testname =~ s/(\.pl|\.perl)$//;
+ $testpath = "$workpath$pathsep$testname";
+ # Leave enough space in the extensions to append a number, even
+ # though it needs to fit into 8+3 limits.
+ if ($short_filenames) {
+ $logext = 'l';
+ $diffext = 'd';
+ $baseext = 'b';
+ $runext = 'r';
+ $extext = '';
+ } else {
+ $logext = 'log';
+ $diffext = 'diff';
+ $baseext = 'base';
+ $runext = 'run';
+ $extext = '.';
+ }
+ $extext = '_' if $^O eq 'VMS';
+ $log_filename = "$testpath.$logext";
+ $diff_filename = "$testpath.$diffext";
+ $base_filename = "$testpath.$baseext";
+ $run_filename = "$testpath.$runext";
+ $tmp_filename = "$testpath.$tmpfilesuffix";
+
+ setup_for_test();
+
+ $output = "........................................................ ";
+
+ substr($output,0,length($testname)) = "$testname ";
+
+ print $output;
+
+ $tests_run = 0;
+ $tests_passed = 0;
+
+ # Run the test!
+ $code = do $perl_testname;
+
+ ++$categories_run;
+ $total_tests_run += $tests_run;
+ $total_tests_passed += $tests_passed;
+
+ # How did it go?
+ if (!defined($code)) {
+ # Failed to parse or called die
+ if (length ($@)) {
+ warn "\n*** Test died ($testname): $@\n";
+ } else {
+ warn "\n*** Couldn't parse $perl_testname\n";
+ }
+ $status = "FAILED ($tests_passed/$tests_run passed)";
+ }
+
+ elsif ($code == -1) {
+ # Skipped... not supported
+ $status = "N/A";
+ --$categories_run;
+ }
+
+ elsif ($code != 1) {
+ # Bad result... this shouldn't really happen. Usually means that
+ # the suite forgot to end with "1;".
+ warn "\n*** Test returned $code\n";
+ $status = "FAILED ($tests_passed/$tests_run passed)";
+ }
+
+ elsif ($tests_run == 0) {
+ # Nothing was done!!
+ $status = "FAILED (no tests found!)";
+ }
+
+ elsif ($tests_run > $tests_passed) {
+ # Lose!
+ $status = "FAILED ($tests_passed/$tests_run passed)";
+ }
+
+ else {
+ # Win!
+ ++$categories_passed;
+ $status = "ok ($tests_passed passed)";
+
+ # Clean up
+ for ($i = $num_of_tmpfiles; $i; $i--) {
+ rmfiles($tmp_filename . num_suffix($i));
+ }
+ for ($i = $num_of_logfiles ? $num_of_logfiles : 1; $i; $i--) {
+ rmfiles($log_filename . num_suffix($i));
+ rmfiles($base_filename . num_suffix($i));
+ }
+ }
+
+ # If the verbose option has been specified, then a short description
+ # of each test is printed before displaying the results of each test
+ # describing WHAT is being tested.
+
+ if ($verbose) {
+ if ($detail) {
+ print "\nWHAT IS BEING TESTED\n";
+ print "--------------------";
+ }
+ print "\n\n$description\n\n";
+ }
+
+ # If the detail option has been specified, then the details of HOW
+ # the test is testing what it says it is testing in the verbose output
+ # will be displayed here before the results of the test are displayed.
+
+ if ($detail) {
+ print "\nHOW IT IS TESTED\n";
+ print "----------------";
+ print "\n\n$details\n\n";
+ }
+
+ print "$status\n";
+ }
+}
+
+# If the keep flag is not set, this subroutine deletes all filenames that
+# are sent to it.
+
+sub rmfiles
+{
+ local(@files) = @_;
+
+ if (!$keep)
+ {
+ return (unlink @files);
+ }
+
+ return 1;
+}
+
+sub print_standard_usage
+{
+ local($plname,@moreusage) = @_;
+ local($line);
+
+ print "usage:\t$plname [testname] [-verbose] [-detail] [-keep]\n";
+ print "\t\t\t[-profile] [-usage] [-help] [-debug]\n";
+ foreach (@moreusage) {
+ print "\t\t\t$_\n";
+ }
+}
+
+sub print_standard_help
+{
+ local(@morehelp) = @_;
+ local($line);
+ local($tline);
+ local($t) = " ";
+
+ $line = "Test Driver For $testee";
+ print "$line\n";
+ $line = "=" x length ($line);
+ print "$line\n";
+
+ &print_usage;
+
+ print "\ntestname\n"
+ . "${t}You may, if you wish, run only ONE test if you know the name\n"
+ . "${t}of that test and specify this name anywhere on the command\n"
+ . "${t}line. Otherwise ALL existing tests in the scripts directory\n"
+ . "${t}will be run.\n"
+ . "-verbose\n"
+ . "${t}If this option is given, a description of every test is\n"
+ . "${t}displayed before the test is run. (Not all tests may have\n"
+ . "${t}descriptions at this time)\n"
+ . "-detail\n"
+ . "${t}If this option is given, a detailed description of every\n"
+ . "${t}test is displayed before the test is run. (Not all tests\n"
+ . "${t}have descriptions at this time)\n"
+ . "-profile\n"
+ . "${t}If this option is given, then the profile file\n"
+ . "${t}is added to other profiles every time $testee is run.\n"
+ . "${t}This option only works on VOS at this time.\n"
+ . "-keep\n"
+ . "${t}You may give this option if you DO NOT want ANY\n"
+ . "${t}of the files generated by the tests to be deleted. \n"
+ . "${t}Without this option, all files generated by the test will\n"
+ . "${t}be deleted IF THE TEST PASSES.\n"
+ . "-debug\n"
+ . "${t}Use this option if you would like to see all of the system\n"
+ . "${t}calls issued and their return status while running the tests\n"
+ . "${t}This can be helpful if you're having a problem adding a test\n"
+ . "${t}to the suite, or if the test fails!\n";
+
+ foreach $line (@morehelp)
+ {
+ $tline = $line;
+ if (substr ($tline, 0, 1) eq "\t")
+ {
+ substr ($tline, 0, 1) = $t;
+ }
+ print "$tline\n";
+ }
+}
+
+#######################################################################
+########### Generic Test Driver Subroutines ###########
+#######################################################################
+
+sub get_caller
+{
+ local($depth);
+ local($package);
+ local($filename);
+ local($linenum);
+
+ $depth = defined ($_[0]) ? $_[0] : 1;
+ ($package, $filename, $linenum) = caller ($depth + 1);
+ return "$filename: $linenum";
+}
+
+sub error
+{
+ local($message) = $_[0];
+ local($caller) = &get_caller (1);
+
+ if (defined ($_[1]))
+ {
+ $caller = &get_caller ($_[1] + 1) . " -> $caller";
+ }
+
+ die "$caller: $message";
+}
+
+sub compare_output
+{
+ local($answer,$logfile) = @_;
+ local($slurp, $answer_matched) = ('', 0);
+
+ ++$tests_run;
+
+ if (! defined $answer) {
+ print "Ignoring output ........ " if $debug;
+ $answer_matched = 1;
+ } else {
+ print "Comparing Output ........ " if $debug;
+
+ $slurp = &read_file_into_string ($logfile);
+
+ # For make, get rid of any time skew error before comparing--too bad this
+ # has to go into the "generic" driver code :-/
+ $slurp =~ s/^.*modification time .*in the future.*\n//gm;
+ $slurp =~ s/^.*Clock skew detected.*\n//gm;
+
+ if ($slurp eq $answer) {
+ $answer_matched = 1;
+ } else {
+ # See if it is a slash or CRLF problem
+ local ($answer_mod, $slurp_mod) = ($answer, $slurp);
+
+ $answer_mod =~ tr,\\,/,;
+ $answer_mod =~ s,\r\n,\n,gs;
+
+ $slurp_mod =~ tr,\\,/,;
+ $slurp_mod =~ s,\r\n,\n,gs;
+
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ if ($^O eq 'VMS') {
+
+ # VMS has extra blank lines in output sometimes.
+ # Ticket #41760
+ if (!$answer_matched) {
+ $slurp_mod =~ s/\n\n+/\n/gm;
+ $slurp_mod =~ s/\A\n+//g;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS adding a "Waiting for unfinished jobs..."
+ # Remove it for now to see what else is going on.
+ if (!$answer_matched) {
+ $slurp_mod =~ s/^.+\*\*\* Waiting for unfinished jobs.+$//m;
+ $slurp_mod =~ s/\n\n/\n/gm;
+ $slurp_mod =~ s/^\n+//gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS wants target device to exist or generates an error,
+ # Some test tagets look like VMS devices and trip this.
+ if (!$answer_matched) {
+ $slurp_mod =~ s/^.+\: no such device or address.*$//gim;
+ $slurp_mod =~ s/\n\n/\n/gm;
+ $slurp_mod =~ s/^\n+//gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS error message has a different case
+ if (!$answer_matched) {
+ $slurp_mod =~ s/no such file /No such file /gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS is putting comas instead of spaces in output
+ if (!$answer_matched) {
+ $slurp_mod =~ s/,/ /gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS Is sometimes adding extra leading spaces to output?
+ if (!$answer_matched) {
+ my $slurp_mod = $slurp_mod;
+ $slurp_mod =~ s/^ +//gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS port not handling POSIX encoded child status
+ # Translate error case it for now.
+ if (!$answer_matched) {
+ $slurp_mod =~ s/0x1035a00a/1/gim;
+ $answer_matched = 1 if $slurp_mod =~ /\Q$answer_mod\E/i;
+
+ }
+ if (!$answer_matched) {
+ $slurp_mod =~ s/0x1035a012/2/gim;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # Tests are using a UNIX null command, temp hack
+ # until this can be handled by the VMS port.
+ # ticket # 41761
+ if (!$answer_matched) {
+ $slurp_mod =~ s/^.+DCL-W-NOCOMD.*$//gim;
+ $slurp_mod =~ s/\n\n+/\n/gm;
+ $slurp_mod =~ s/^\n+//gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+ # Tests are using exit 0;
+ # this generates a warning that should stop the make, but does not
+ if (!$answer_matched) {
+ $slurp_mod =~ s/^.+NONAME-W-NOMSG.*$//gim;
+ $slurp_mod =~ s/\n\n+/\n/gm;
+ $slurp_mod =~ s/^\n+//gm;
+ $answer_matched = ($slurp_mod eq $answer_mod);
+ }
+
+ # VMS is sometimes adding single quotes to output?
+ if (!$answer_matched) {
+ my $noq_slurp_mod = $slurp_mod;
+ $noq_slurp_mod =~ s/\'//gm;
+ $answer_matched = ($noq_slurp_mod eq $answer_mod);
+
+ # And missing an extra space in output
+ if (!$answer_matched) {
+ $noq_answer_mod = $answer_mod;
+ $noq_answer_mod =~ s/\h\h+/ /gm;
+ $answer_matched = ($noq_slurp_mod eq $noq_answer_mod);
+ }
+
+ # VMS adding ; to end of some lines.
+ if (!$answer_matched) {
+ $noq_slurp_mod =~ s/;\n/\n/gm;
+ $answer_matched = ($noq_slurp_mod eq $noq_answer_mod);
+ }
+
+ # VMS adding trailing space to end of some quoted lines.
+ if (!$answer_matched) {
+ $noq_slurp_mod =~ s/\h+\n/\n/gm;
+ $answer_matched = ($noq_slurp_mod eq $noq_answer_mod);
+ }
+
+ # And VMS missing leading blank line
+ if (!$answer_matched) {
+ $noq_answer_mod =~ s/\A\n//g;
+ $answer_matched = ($noq_slurp_mod eq $noq_answer_mod);
+ }
+
+ # Unix double quotes showing up as single quotes on VMS.
+ if (!$answer_matched) {
+ $noq_answer_mod =~ s/\"//g;
+ $answer_matched = ($noq_slurp_mod eq $noq_answer_mod);
+ }
+ }
+ }
+
+ # If it still doesn't match, see if the answer might be a regex.
+ if (!$answer_matched && $answer =~ m,^/(.+)/$,) {
+ $answer_matched = ($slurp =~ /$1/);
+ if (!$answer_matched && $answer_mod =~ m,^/(.+)/$,) {
+ $answer_matched = ($slurp_mod =~ /$1/);
+ }
+ }
+ }
+ }
+
+ if ($answer_matched && $test_passed)
+ {
+ print "ok\n" if $debug;
+ ++$tests_passed;
+ return 1;
+ }
+
+ if (! $answer_matched) {
+ print "DIFFERENT OUTPUT\n" if $debug;
+
+ &create_file (&get_basefile, $answer);
+ &create_file (&get_runfile, $command_string);
+
+ print "\nCreating Difference File ...\n" if $debug;
+
+ # Create the difference file
+
+ local($command) = "diff -c " . &get_basefile . " " . $logfile;
+ &run_command_with_output(&get_difffile,$command);
+ }
+
+ return 0;
+}
+
+sub read_file_into_string
+{
+ local($filename) = @_;
+ local($oldslash) = $/;
+
+ undef $/;
+
+ open (RFISFILE, $filename) || return "";
+ local ($slurp) = <RFISFILE>;
+ close (RFISFILE);
+
+ $/ = $oldslash;
+
+ return $slurp;
+}
+
+my @OUTSTACK = ();
+my @ERRSTACK = ();
+
+sub attach_default_output
+{
+ local ($filename) = @_;
+ local ($code);
+
+ if ($vos)
+ {
+ $code = system "++attach_default_output_hack $filename";
+ $code == -2 || &error ("adoh death\n", 1);
+ return 1;
+ }
+
+ my $dup = undef;
+ open($dup, '>&', STDOUT) or error("ado: $! duping STDOUT\n", 1);
+ push @OUTSTACK, $dup;
+
+ $dup = undef;
+ open($dup, '>&', STDERR) or error("ado: $! duping STDERR\n", 1);
+ push @ERRSTACK, $dup;
+
+ open(STDOUT, '>', $filename) or error("ado: $filename: $!\n", 1);
+ open(STDERR, ">&STDOUT") or error("ado: $filename: $!\n", 1);
+}
+
+# close the current stdout/stderr, and restore the previous ones from
+# the "stack."
+
+sub detach_default_output
+{
+ local ($code);
+
+ if ($vos)
+ {
+ $code = system "++detach_default_output_hack";
+ $code == -2 || &error ("ddoh death\n", 1);
+ return 1;
+ }
+
+ @OUTSTACK or error("default output stack has flown under!\n", 1);
+
+ close(STDOUT);
+ close(STDERR) unless $^O eq 'VMS';
+
+
+ open (STDOUT, '>&', pop @OUTSTACK) or error("ddo: $! duping STDOUT\n", 1);
+ open (STDERR, '>&', pop @ERRSTACK) or error("ddo: $! duping STDERR\n", 1);
+}
+
+# This runs a command without any debugging info.
+sub _run_command
+{
+ my $code;
+
+ # We reset this before every invocation. On Windows I think there is only
+ # one environment, not one per process, so I think that variables set in
+ # test scripts might leak into subsequent tests if this isn't reset--???
+ resetENV();
+
+ eval {
+ if ($^O eq 'VMS') {
+ local $SIG{ALRM} = sub {
+ my $e = $ERRSTACK[0];
+ print $e "\nTest timed out after $test_timeout seconds\n";
+ die "timeout\n"; };
+# alarm $test_timeout;
+ system(@_);
+ my $severity = ${^CHILD_ERROR_NATIVE} & 7;
+ $code = 0;
+ if (($severity & 1) == 0) {
+ $code = 512;
+ }
+
+ # Get the vms status.
+ my $vms_code = ${^CHILD_ERROR_NATIVE};
+
+ # Remove the print status bit
+ $vms_code &= ~0x10000000;
+
+ # Posix code translation.
+ if (($vms_code & 0xFFFFF000) == 0x35a000) {
+ $code = (($vms_code & 0xFFF) >> 3) * 256;
+ }
+ } else {
+ my $pid = fork();
+ if (! $pid) {
+ exec(@_) or die "Cannot execute $_[0]\n";
+ }
+ local $SIG{ALRM} = sub { my $e = $ERRSTACK[0]; print $e "\nTest timed out after $test_timeout seconds\n"; die "timeout\n"; };
+ alarm $test_timeout;
+ waitpid($pid, 0) > 0 or die "No such pid: $pid\n";
+ $code = $?;
+ }
+ alarm 0;
+ };
+ if ($@) {
+ # The eval failed. If it wasn't SIGALRM then die.
+ $@ eq "timeout\n" or die "Command failed: $@";
+
+ # Timed out. Resend the alarm to our process group to kill the children.
+ $SIG{ALRM} = 'IGNORE';
+ kill -14, $$;
+ $code = 14;
+ }
+
+ return $code;
+}
+
+# run one command (passed as a list of arg 0 - n), returning 0 on success
+# and nonzero on failure.
+
+sub run_command
+{
+ print "\nrun_command: @_\n" if $debug;
+ my $code = _run_command(@_);
+ print "run_command returned $code.\n" if $debug;
+ print "vms status = ${^CHILD_ERROR_NATIVE}\n" if $debug and $^O eq 'VMS';
+ return $code;
+}
+
+# run one command (passed as a list of arg 0 - n, with arg 0 being the
+# second arg to this routine), returning 0 on success and non-zero on failure.
+# The first arg to this routine is a filename to connect to the stdout
+# & stderr of the child process.
+
+sub run_command_with_output
+{
+ my $filename = shift;
+
+ print "\nrun_command_with_output($filename,$runname): @_\n" if $debug;
+ &attach_default_output ($filename);
+ my $code = eval { _run_command(@_) };
+ my $err = $@;
+ &detach_default_output;
+
+ $err and die $err;
+
+ print "run_command_with_output returned $code.\n" if $debug;
+ print "vms status = ${^CHILD_ERROR_NATIVE}\n" if $debug and $^O eq 'VMS';
+ return $code;
+}
+
+# performs the equivalent of an "rm -rf" on the first argument. Like
+# rm, if the path ends in /, leaves the (now empty) directory; otherwise
+# deletes it, too.
+
+sub remove_directory_tree
+{
+ local ($targetdir) = @_;
+ local ($nuketop) = 1;
+ local ($ch);
+
+ $ch = substr ($targetdir, length ($targetdir) - 1);
+ if ($ch eq "/" || $ch eq $pathsep)
+ {
+ $targetdir = substr ($targetdir, 0, length ($targetdir) - 1);
+ $nuketop = 0;
+ }
+
+ if (! -e $targetdir)
+ {
+ return 1;
+ }
+
+ &remove_directory_tree_inner ("RDT00", $targetdir) || return 0;
+ if ($nuketop)
+ {
+ rmdir $targetdir || return 0;
+ }
+
+ return 1;
+}
+
+sub remove_directory_tree_inner
+{
+ local ($dirhandle, $targetdir) = @_;
+ local ($object);
+ local ($subdirhandle);
+
+ opendir ($dirhandle, $targetdir) || return 0;
+ $subdirhandle = $dirhandle;
+ $subdirhandle++;
+ while ($object = readdir ($dirhandle))
+ {
+ if ($object =~ /^(\.\.?|CVS|RCS)$/)
+ {
+ next;
+ }
+
+ $object = "$targetdir$pathsep$object";
+ lstat ($object);
+
+ if (-d _ && &remove_directory_tree_inner ($subdirhandle, $object))
+ {
+ rmdir $object || return 0;
+ }
+ else
+ {
+ if ($^O ne 'VMS')
+ {
+ unlink $object || return 0;
+ }
+ else
+ {
+ # VMS can have multiple versions of a file.
+ 1 while unlink $object;
+ }
+ }
+ }
+ closedir ($dirhandle);
+ return 1;
+}
+
+# We used to use this behavior for this function:
+#
+#sub touch
+#{
+# local (@filenames) = @_;
+# local ($now) = time;
+# local ($file);
+#
+# foreach $file (@filenames)
+# {
+# utime ($now, $now, $file)
+# || (open (TOUCHFD, ">> $file") && close (TOUCHFD))
+# || &error ("Couldn't touch $file: $!\n", 1);
+# }
+# return 1;
+#}
+#
+# But this behaves badly on networked filesystems where the time is
+# skewed, because it sets the time of the file based on the _local_
+# host. Normally when you modify a file, it's the _remote_ host that
+# determines the modtime, based on _its_ clock. So, instead, now we open
+# the file and write something into it to force the remote host to set
+# the modtime correctly according to its clock.
+#
+
+sub touch
+{
+ local ($file);
+
+ foreach $file (@_) {
+ (open(T, ">> $file") && print(T "\n") && close(T))
+ || &error("Couldn't touch $file: $!\n", 1);
+ }
+}
+
+# Touch with a time offset. To DTRT, call touch() then use stat() to get the
+# access/mod time for each file and apply the offset.
+
+sub utouch
+{
+ local ($off) = shift;
+ local ($file);
+
+ &touch(@_);
+
+ local (@s) = stat($_[0]);
+
+ utime($s[8]+$off, $s[9]+$off, @_);
+}
+
+# open a file, write some stuff to it, and close it.
+
+sub create_file
+{
+ local ($filename, @lines) = @_;
+
+ open (CF, "> $filename") || &error ("Couldn't open $filename: $!\n", 1);
+ foreach $line (@lines)
+ {
+ print CF $line;
+ }
+ close (CF);
+}
+
+# create a directory tree described by an associative array, wherein each
+# key is a relative pathname (using slashes) and its associated value is
+# one of:
+# DIR indicates a directory
+# FILE:contents indicates a file, which should contain contents +\n
+# LINK:target indicates a symlink, pointing to $basedir/target
+# The first argument is the dir under which the structure will be created
+# (the dir will be made and/or cleaned if necessary); the second argument
+# is the associative array.
+
+sub create_dir_tree
+{
+ local ($basedir, %dirtree) = @_;
+ local ($path);
+
+ &remove_directory_tree ("$basedir");
+ mkdir ($basedir, 0777) || &error ("Couldn't mkdir $basedir: $!\n", 1);
+
+ foreach $path (sort keys (%dirtree))
+ {
+ if ($dirtree {$path} =~ /^DIR$/)
+ {
+ mkdir ("$basedir/$path", 0777)
+ || &error ("Couldn't mkdir $basedir/$path: $!\n", 1);
+ }
+ elsif ($dirtree {$path} =~ /^FILE:(.*)$/)
+ {
+ &create_file ("$basedir/$path", $1 . "\n");
+ }
+ elsif ($dirtree {$path} =~ /^LINK:(.*)$/)
+ {
+ symlink ("$basedir/$1", "$basedir/$path")
+ || &error ("Couldn't symlink $basedir/$path -> $basedir/$1: $!\n", 1);
+ }
+ else
+ {
+ &error ("Bogus dirtree type: \"$dirtree{$path}\"\n", 1);
+ }
+ }
+ if ($just_setup_tree)
+ {
+ die "Tree is setup...\n";
+ }
+}
+
+# compare a directory tree with an associative array in the format used
+# by create_dir_tree, above.
+# The first argument is the dir under which the structure should be found;
+# the second argument is the associative array.
+
+sub compare_dir_tree
+{
+ local ($basedir, %dirtree) = @_;
+ local ($path);
+ local ($i);
+ local ($bogus) = 0;
+ local ($contents);
+ local ($target);
+ local ($fulltarget);
+ local ($found);
+ local (@files);
+ local (@allfiles);
+
+ opendir (DIR, $basedir) || &error ("Couldn't open $basedir: $!\n", 1);
+ @allfiles = grep (!/^(\.\.?|CVS|RCS)$/, readdir (DIR) );
+ closedir (DIR);
+ if ($debug)
+ {
+ print "dirtree: (%dirtree)\n$basedir: (@allfiles)\n";
+ }
+
+ foreach $path (sort keys (%dirtree))
+ {
+ if ($debug)
+ {
+ print "Checking $path ($dirtree{$path}).\n";
+ }
+
+ $found = 0;
+ foreach $i (0 .. $#allfiles)
+ {
+ if ($allfiles[$i] eq $path)
+ {
+ splice (@allfiles, $i, 1); # delete it
+ if ($debug)
+ {
+ print " Zapped $path; files now (@allfiles).\n";
+ }
+ lstat ("$basedir/$path");
+ $found = 1;
+ last;
+ }
+ }
+
+ if (!$found)
+ {
+ print "compare_dir_tree: $path does not exist.\n";
+ $bogus = 1;
+ next;
+ }
+
+ if ($dirtree {$path} =~ /^DIR$/)
+ {
+ if (-d _ && opendir (DIR, "$basedir/$path") )
+ {
+ @files = readdir (DIR);
+ closedir (DIR);
+ @files = grep (!/^(\.\.?|CVS|RCS)$/ && ($_ = "$path/$_"), @files);
+ push (@allfiles, @files);
+ if ($debug)
+ {
+ print " Read in $path; new files (@files).\n";
+ }
+ }
+ else
+ {
+ print "compare_dir_tree: $path is not a dir.\n";
+ $bogus = 1;
+ }
+ }
+ elsif ($dirtree {$path} =~ /^FILE:(.*)$/)
+ {
+ if (-l _ || !-f _)
+ {
+ print "compare_dir_tree: $path is not a file.\n";
+ $bogus = 1;
+ next;
+ }
+
+ if ($1 ne "*")
+ {
+ $contents = &read_file_into_string ("$basedir/$path");
+ if ($contents ne "$1\n")
+ {
+ print "compare_dir_tree: $path contains wrong stuff."
+ . " Is:\n$contentsShould be:\n$1\n";
+ $bogus = 1;
+ }
+ }
+ }
+ elsif ($dirtree {$path} =~ /^LINK:(.*)$/)
+ {
+ $target = $1;
+ if (!-l _)
+ {
+ print "compare_dir_tree: $path is not a link.\n";
+ $bogus = 1;
+ next;
+ }
+
+ $contents = readlink ("$basedir/$path");
+ $contents =~ tr/>/\//;
+ $fulltarget = "$basedir/$target";
+ $fulltarget =~ tr/>/\//;
+ if (!($contents =~ /$fulltarget$/))
+ {
+ if ($debug)
+ {
+ $target = $fulltarget;
+ }
+ print "compare_dir_tree: $path should be link to $target, "
+ . "not $contents.\n";
+ $bogus = 1;
+ }
+ }
+ else
+ {
+ &error ("Bogus dirtree type: \"$dirtree{$path}\"\n", 1);
+ }
+ }
+
+ if ($debug)
+ {
+ print "leftovers: (@allfiles).\n";
+ }
+
+ foreach $file (@allfiles)
+ {
+ print "compare_dir_tree: $file should not exist.\n";
+ $bogus = 1;
+ }
+
+ return !$bogus;
+}
+
+# this subroutine generates the numeric suffix used to keep tmp filenames,
+# log filenames, etc., unique. If the number passed in is 1, then a null
+# string is returned; otherwise, we return ".n", where n + 1 is the number
+# we were given.
+
+sub num_suffix
+{
+ local($num) = @_;
+
+ if (--$num > 0) {
+ return "$extext$num";
+ }
+
+ return "";
+}
+
+# This subroutine returns a log filename with a number appended to
+# the end corresponding to how many logfiles have been created in the
+# current running test. An optional parameter may be passed (0 or 1).
+# If a 1 is passed, then it does NOT increment the logfile counter
+# and returns the name of the latest logfile. If either no parameter
+# is passed at all or a 0 is passed, then the logfile counter is
+# incremented and the new name is returned.
+
+sub get_logfile
+{
+ local($no_increment) = @_;
+
+ $num_of_logfiles += !$no_increment;
+
+ return ($log_filename . &num_suffix ($num_of_logfiles));
+}
+
+# This subroutine returns a base (answer) filename with a number
+# appended to the end corresponding to how many logfiles (and thus
+# base files) have been created in the current running test.
+# NO PARAMETERS ARE PASSED TO THIS SUBROUTINE.
+
+sub get_basefile
+{
+ return ($base_filename . &num_suffix ($num_of_logfiles));
+}
+
+# This subroutine returns a difference filename with a number appended
+# to the end corresponding to how many logfiles (and thus diff files)
+# have been created in the current running test.
+
+sub get_difffile
+{
+ return ($diff_filename . &num_suffix ($num_of_logfiles));
+}
+
+# This subroutine returns a command filename with a number appended
+# to the end corresponding to how many logfiles (and thus command files)
+# have been created in the current running test.
+
+sub get_runfile
+{
+ return ($run_filename . &num_suffix ($num_of_logfiles));
+}
+
+# just like logfile, only a generic tmp filename for use by the test.
+# they are automatically cleaned up unless -keep was used, or the test fails.
+# Pass an argument of 1 to return the same filename as the previous call.
+
+sub get_tmpfile
+{
+ local($no_increment) = @_;
+
+ $num_of_tmpfiles += !$no_increment;
+
+ return ($tmp_filename . &num_suffix ($num_of_tmpfiles));
+}
+
+1;
diff --git a/src/kmk/variable.c b/src/kmk/variable.c
new file mode 100644
index 0000000..c79601d
--- /dev/null
+++ b/src/kmk/variable.c
@@ -0,0 +1,3475 @@
+/* Internals of variables for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <assert.h>
+
+#include "filedef.h"
+#include "dep.h"
+#include "job.h"
+#include "commands.h"
+#include "variable.h"
+#include "rule.h"
+#ifdef WINDOWS32
+#include "pathstuff.h"
+#endif
+#include "hash.h"
+#ifdef KMK
+# include "kbuild.h"
+# ifdef WINDOWS32
+# include <Windows.h>
+# else
+# include <sys/utsname.h>
+# endif
+#endif
+#ifdef CONFIG_WITH_STRCACHE2
+# include <stddef.h>
+#endif
+#ifdef CONFIG_WITH_COMPILER
+# include "kmk_cc_exec.h"
+#endif
+
+#ifdef KMK
+/** Gets the real variable if alias. For use when looking up variables. */
+# define RESOLVE_ALIAS_VARIABLE(v) \
+ do { \
+ if ((v) != NULL && (v)->alias) \
+ { \
+ (v) = (struct variable *)(v)->value; \
+ assert ((v)->aliased); \
+ assert (!(v)->alias); \
+ } \
+ } while (0)
+#endif
+
+#ifdef KMK
+/* Incremented every time a variable is modified, so that target_environment
+ knows when to regenerate the table of exported global variables. */
+static size_t global_variable_generation = 0;
+#endif
+
+/* Incremented every time we add or remove a global variable. */
+static unsigned long variable_changenum;
+
+/* Chain of all pattern-specific variables. */
+
+static struct pattern_var *pattern_vars;
+
+/* Pointer to the last struct in the pack of a specific size, from 1 to 255.*/
+
+static struct pattern_var *last_pattern_vars[256];
+
+/* Create a new pattern-specific variable struct. The new variable is
+ inserted into the PATTERN_VARS list in the shortest patterns first
+ order to support the shortest stem matching (the variables are
+ matched in the reverse order so the ones with the longest pattern
+ will be considered first). Variables with the same pattern length
+ are inserted in the definition order. */
+
+struct pattern_var *
+create_pattern_var (const char *target, const char *suffix)
+{
+ register unsigned int len = strlen (target);
+ register struct pattern_var *p = xmalloc (sizeof (struct pattern_var));
+
+ if (pattern_vars != 0)
+ {
+ if (len < 256 && last_pattern_vars[len] != 0)
+ {
+ p->next = last_pattern_vars[len]->next;
+ last_pattern_vars[len]->next = p;
+ }
+ else
+ {
+ /* Find the position where we can insert this variable. */
+ register struct pattern_var **v;
+
+ for (v = &pattern_vars; ; v = &(*v)->next)
+ {
+ /* Insert at the end of the pack so that patterns with the
+ same length appear in the order they were defined .*/
+
+ if (*v == 0 || (*v)->len > len)
+ {
+ p->next = *v;
+ *v = p;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ pattern_vars = p;
+ p->next = 0;
+ }
+
+ p->target = target;
+ p->len = len;
+ p->suffix = suffix + 1;
+
+ if (len < 256)
+ last_pattern_vars[len] = p;
+
+ return p;
+}
+
+/* Look up a target in the pattern-specific variable list. */
+
+static struct pattern_var *
+lookup_pattern_var (struct pattern_var *start, const char *target)
+{
+ struct pattern_var *p;
+ unsigned int targlen = strlen (target);
+
+ for (p = start ? start->next : pattern_vars; p != 0; p = p->next)
+ {
+ const char *stem;
+ unsigned int stemlen;
+
+ if (p->len > targlen)
+ /* It can't possibly match. */
+ continue;
+
+ /* From the lengths of the filename and the pattern parts,
+ find the stem: the part of the filename that matches the %. */
+ stem = target + (p->suffix - p->target - 1);
+ stemlen = targlen - p->len + 1;
+
+ /* Compare the text in the pattern before the stem, if any. */
+ if (stem > target && !strneq (p->target, target, stem - target))
+ continue;
+
+ /* Compare the text in the pattern after the stem, if any.
+ We could test simply using streq, but this way we compare the
+ first two characters immediately. This saves time in the very
+ common case where the first character matches because it is a
+ period. */
+ if (*p->suffix == stem[stemlen]
+ && (*p->suffix == '\0' || streq (&p->suffix[1], &stem[stemlen+1])))
+ break;
+ }
+
+ return p;
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+struct strcache2 variable_strcache;
+#endif
+
+/* Hash table of all global variable definitions. */
+
+#ifndef CONFIG_WITH_STRCACHE2
+static unsigned long
+variable_hash_1 (const void *keyv)
+{
+ struct variable const *key = (struct variable const *) keyv;
+ return_STRING_N_HASH_1 (key->name, key->length);
+}
+
+static unsigned long
+variable_hash_2 (const void *keyv)
+{
+ struct variable const *key = (struct variable const *) keyv;
+ return_STRING_N_HASH_2 (key->name, key->length);
+}
+
+static int
+variable_hash_cmp (const void *xv, const void *yv)
+{
+ struct variable const *x = (struct variable const *) xv;
+ struct variable const *y = (struct variable const *) yv;
+ int result = x->length - y->length;
+ if (result)
+ return result;
+
+ return_STRING_N_COMPARE (x->name, y->name, x->length);
+}
+#endif /* !CONFIG_WITH_STRCACHE2 */
+
+#ifndef VARIABLE_BUCKETS
+# ifdef KMK /* Move to Makefile.kmk? (insanely high, but wtf, it gets the collitions down) */
+# define VARIABLE_BUCKETS 65535
+# else /*!KMK*/
+#define VARIABLE_BUCKETS 523
+# endif /*!KMK*/
+#endif
+#ifndef PERFILE_VARIABLE_BUCKETS
+# ifdef KMK /* Move to Makefile.kmk? */
+# define PERFILE_VARIABLE_BUCKETS 127
+# else
+#define PERFILE_VARIABLE_BUCKETS 23
+# endif
+#endif
+#ifndef SMALL_SCOPE_VARIABLE_BUCKETS
+# ifdef KMK /* Move to Makefile.kmk? */
+# define SMALL_SCOPE_VARIABLE_BUCKETS 63
+# else
+#define SMALL_SCOPE_VARIABLE_BUCKETS 13
+# endif
+#endif
+#ifndef ENVIRONMENT_VARIABLE_BUCKETS /* added by bird. */
+# define ENVIRONMENT_VARIABLE_BUCKETS 256
+#endif
+
+
+#ifdef KMK /* Drop the 'static' */
+struct variable_set global_variable_set;
+struct variable_set_list global_setlist
+#else
+static struct variable_set global_variable_set;
+static struct variable_set_list global_setlist
+#endif
+ = { 0, &global_variable_set, 0 };
+struct variable_set_list *current_variable_set_list = &global_setlist;
+
+/* Implement variables. */
+
+void
+init_hash_global_variable_set (void)
+{
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&global_variable_set.table, VARIABLE_BUCKETS,
+ variable_hash_1, variable_hash_2, variable_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ strcache2_init (&variable_strcache, "variable", 262144, 0, 0, 0);
+ hash_init_strcached (&global_variable_set.table, VARIABLE_BUCKETS,
+ &variable_strcache, offsetof (struct variable, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+/* Define variable named NAME with value VALUE in SET. VALUE is copied.
+ LENGTH is the length of NAME, which does not need to be null-terminated.
+ ORIGIN specifies the origin of the variable (makefile, command line
+ or environment).
+ If RECURSIVE is nonzero a flag is set in the variable saying
+ that it should be recursively re-expanded. */
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+struct variable *
+define_variable_in_set (const char *name, unsigned int length,
+ const char *value, unsigned int value_len,
+ int duplicate_value, enum variable_origin origin,
+ int recursive, struct variable_set *set,
+ const floc *flocp)
+#else
+struct variable *
+define_variable_in_set (const char *name, unsigned int length,
+ const char *value, enum variable_origin origin,
+ int recursive, struct variable_set *set,
+ const floc *flocp)
+#endif
+{
+ struct variable *v;
+ struct variable **var_slot;
+ struct variable var_key;
+
+#ifdef KMK
+ if (set == NULL || set == &global_variable_set)
+ global_variable_generation++;
+#endif
+
+ if (env_overrides && origin == o_env)
+ origin = o_env_override;
+
+#ifndef KMK
+ if (set == NULL)
+ set = &global_variable_set;
+#else /* KMK */
+ /* Intercept kBuild object variable definitions. */
+ if (name[0] == '[' && length > 3)
+ {
+ v = try_define_kbuild_object_variable_via_accessor (name, length,
+ value, value_len, duplicate_value,
+ origin, recursive, flocp);
+ if (v != VAR_NOT_KBUILD_ACCESSOR)
+ return v;
+ }
+ if (set == NULL)
+ {
+ if (g_pTopKbEvalData)
+ return define_kbuild_object_variable_in_top_obj (name, length,
+ value, value_len, duplicate_value,
+ origin, recursive, flocp);
+ set = &global_variable_set;
+ }
+#endif /* KMK */
+
+#ifndef CONFIG_WITH_STRCACHE2
+ var_key.name = (char *) name;
+ var_key.length = length;
+ var_slot = (struct variable **) hash_find_slot (&set->table, &var_key);
+ v = *var_slot;
+
+#ifdef VMS
+ /* VMS does not populate envp[] with DCL symbols and logical names which
+ historically are mapped to environent variables.
+ If the variable is not yet defined, then we need to check if getenv()
+ can find it. Do not do this for origin == o_env to avoid infinte
+ recursion */
+ if (HASH_VACANT (v) && (origin != o_env))
+ {
+ struct variable * vms_variable;
+ char * vname = alloca (length + 1);
+ char * vvalue;
+
+ strncpy (vname, name, length);
+ vvalue = getenv(vname);
+
+ /* Values starting with '$' are probably foreign commands.
+ We want to treat them as Shell aliases and not look them up here */
+ if ((vvalue != NULL) && (vvalue[0] != '$'))
+ {
+ vms_variable = lookup_variable(name, length);
+ /* Refresh the slot */
+ var_slot = (struct variable **) hash_find_slot (&set->table,
+ &var_key);
+ v = *var_slot;
+ }
+ }
+#endif
+
+ /* if (env_overrides && origin == o_env)
+ origin = o_env_override; - bird moved this up */
+
+#else /* CONFIG_WITH_STRCACHE2 */
+ name = strcache2_add (&variable_strcache, name, length);
+ if ( set != &global_variable_set
+ || !(v = strcache2_get_user_val (&variable_strcache, name)))
+ {
+ var_key.name = name;
+ var_key.length = length;
+ var_slot = (struct variable **) hash_find_slot_strcached (&set->table, &var_key);
+ v = *var_slot;
+ }
+ else
+ {
+ assert (!v || (v->name == name && !HASH_VACANT (v)));
+ var_slot = 0;
+ }
+#endif /* CONFIG_WITH_STRCACHE2 */
+ if (! HASH_VACANT (v))
+ {
+#ifdef KMK
+ RESOLVE_ALIAS_VARIABLE(v);
+#endif
+ if (env_overrides && v->origin == o_env)
+ /* V came from in the environment. Since it was defined
+ before the switches were parsed, it wasn't affected by -e. */
+ v->origin = o_env_override;
+
+ /* A variable of this name is already defined.
+ If the old definition is from a stronger source
+ than this one, don't redefine it. */
+ if ((int) origin >= (int) v->origin)
+ {
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ if (value_len == ~0U)
+ value_len = strlen (value);
+ else
+ assert (value_len == strlen (value));
+ if (!duplicate_value || duplicate_value == -1)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v->value != 0 && !v->rdonly_val)
+ free (v->value);
+ v->rdonly_val = duplicate_value == -1;
+ v->value = (char *) value;
+ v->value_alloc_len = 0;
+# else
+ if (v->value != 0)
+ free (v->value);
+ v->value = (char *) value;
+ v->value_alloc_len = value_len + 1;
+# endif
+ }
+ else
+ {
+ if (v->value_alloc_len <= value_len)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v->rdonly_val)
+ v->rdonly_val = 0;
+ else
+# endif
+ free (v->value);
+ v->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (value_len + 1);
+ v->value = xmalloc (v->value_alloc_len);
+ MAKE_STATS_2(v->reallocs++);
+ }
+ memcpy (v->value, value, value_len + 1);
+ }
+ v->value_length = value_len;
+#else /* !CONFIG_WITH_VALUE_LENGTH */
+ free (v->value);
+ v->value = xstrdup (value);
+#endif /* !CONFIG_WITH_VALUE_LENGTH */
+ if (flocp != 0)
+ v->fileinfo = *flocp;
+ else
+ v->fileinfo.filenm = 0;
+ v->origin = origin;
+ v->recursive = recursive;
+ VARIABLE_CHANGED (v);
+ }
+ return v;
+ }
+
+ /* Create a new variable definition and add it to the hash table. */
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ v = xmalloc (sizeof (struct variable));
+#else
+ v = alloccache_alloc (&variable_cache);
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ v->name = xstrndup (name, length);
+#else
+ v->name = name; /* already cached. */
+#endif
+ v->length = length;
+ hash_insert_at (&set->table, v, var_slot);
+ if (set == &global_variable_set)
+ ++variable_changenum;
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ if (value_len == ~0U)
+ value_len = strlen (value);
+ else
+ assert (value_len == strlen (value));
+ v->value_length = value_len;
+ if (!duplicate_value || duplicate_value == -1)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ v->rdonly_val = duplicate_value == -1;
+ v->value_alloc_len = v->rdonly_val ? 0 : value_len + 1;
+# endif
+ v->value = (char *)value;
+ }
+ else
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ v->rdonly_val = 0;
+# endif
+ v->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (value_len + 1);
+ v->value = xmalloc (v->value_alloc_len);
+ memcpy (v->value, value, value_len + 1);
+ }
+#else /* !CONFIG_WITH_VALUE_LENGTH */
+ v->value = xstrdup (value);
+#endif /* !CONFIG_WITH_VALUE_LENGTH */
+ if (flocp != 0)
+ v->fileinfo = *flocp;
+ else
+ v->fileinfo.filenm = 0;
+ v->origin = origin;
+ v->recursive = recursive;
+ v->special = 0;
+ v->expanding = 0;
+ v->exp_count = 0;
+ v->per_target = 0;
+ v->append = 0;
+ v->private_var = 0;
+#ifdef KMK
+ v->alias = 0;
+ v->aliased = 0;
+#endif
+ v->export = v_default;
+#ifdef CONFIG_WITH_COMPILER
+ v->recursive_without_dollar = 0;
+ v->evalprog = 0;
+ v->expandprog = 0;
+ v->evalval_count = 0;
+ v->expand_count = 0;
+#else
+ MAKE_STATS_2(v->expand_count = 0);
+ MAKE_STATS_2(v->evalval_count = 0);
+#endif
+ MAKE_STATS_2(v->changes = 0);
+ MAKE_STATS_2(v->reallocs = 0);
+ MAKE_STATS_2(v->references = 0);
+ MAKE_STATS_2(v->cTicksEvalVal = 0);
+
+ v->exportable = 1;
+ if (*name != '_' && (*name < 'A' || *name > 'Z')
+ && (*name < 'a' || *name > 'z'))
+ v->exportable = 0;
+ else
+ {
+ for (++name; *name != '\0'; ++name)
+ if (*name != '_' && (*name < 'a' || *name > 'z')
+ && (*name < 'A' || *name > 'Z') && !ISDIGIT(*name))
+ break;
+
+ if (*name != '\0')
+ v->exportable = 0;
+ }
+
+#ifdef CONFIG_WITH_STRCACHE2
+ /* If it's the global set, remember the variable. */
+ if (set == &global_variable_set)
+ strcache2_set_user_val (&variable_strcache, v->name, v);
+#endif
+ return v;
+}
+
+
+/* Undefine variable named NAME in SET. LENGTH is the length of NAME, which
+ does not need to be null-terminated. ORIGIN specifies the origin of the
+ variable (makefile, command line or environment). */
+
+static void
+free_variable_name_and_value (const void *item)
+{
+ struct variable *v = (struct variable *) item;
+#ifndef CONFIG_WITH_STRCACHE2
+ free (v->name);
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ if (v->evalprog || v->expandprog)
+ kmk_cc_variable_deleted (v);
+#endif
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (!v->rdonly_val)
+#endif
+ free (v->value);
+}
+
+void
+free_variable_set (struct variable_set_list *list)
+{
+ hash_map (&list->set->table, free_variable_name_and_value);
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ hash_free (&list->set->table, 1);
+ free (list->set);
+ free (list);
+#else
+ hash_free_cached (&list->set->table, 1, &variable_cache);
+ alloccache_free (&variable_set_cache, list->set);
+ alloccache_free (&variable_set_list_cache, list);
+#endif
+}
+
+void
+undefine_variable_in_set (const char *name, unsigned int length,
+ enum variable_origin origin,
+ struct variable_set *set)
+{
+ struct variable *v;
+ struct variable **var_slot;
+ struct variable var_key;
+
+ if (set == NULL)
+ set = &global_variable_set;
+
+#ifndef CONFIG_WITH_STRCACHE2
+ var_key.name = (char *) name;
+ var_key.length = length;
+ var_slot = (struct variable **) hash_find_slot (&set->table, &var_key);
+#else
+ var_key.name = strcache2_lookup(&variable_strcache, name, length);
+ if (!var_key.name)
+ return;
+ var_key.length = length;
+ var_slot = (struct variable **) hash_find_slot_strcached (&set->table, &var_key);
+#endif
+#ifdef KMK
+ if (set == &global_variable_set)
+ global_variable_generation++;
+#endif
+
+ if (env_overrides && origin == o_env)
+ origin = o_env_override;
+
+ v = *var_slot;
+ if (! HASH_VACANT (v))
+ {
+#ifdef KMK
+ if (v->aliased || v->alias)
+ {
+ if (v->aliased)
+ OS (error, NULL, _("Cannot undefine the aliased variable '%s'"), v->name);
+ else
+ OS (error, NULL, _("Cannot undefine the variable alias '%s'"), v->name);
+ return;
+ }
+#endif
+
+ if (env_overrides && v->origin == o_env)
+ /* V came from in the environment. Since it was defined
+ before the switches were parsed, it wasn't affected by -e. */
+ v->origin = o_env_override;
+
+ /* Undefine only if this undefinition is from an equal or stronger
+ source than the variable definition. */
+ if ((int) origin >= (int) v->origin)
+ {
+ hash_delete_at (&set->table, var_slot);
+#ifdef CONFIG_WITH_STRCACHE2
+ if (set == &global_variable_set)
+ strcache2_set_user_val (&variable_strcache, v->name, NULL);
+#endif
+ free_variable_name_and_value (v);
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ free (v);
+#else
+ alloccache_free (&variable_cache, v);
+#endif
+ if (set == &global_variable_set)
+ ++variable_changenum;
+ }
+ }
+}
+
+#ifdef KMK
+/* Define variable named NAME as an alias of the variable TARGET.
+ SET defaults to the global set if NULL. FLOCP is just for completeness. */
+
+struct variable *
+define_variable_alias_in_set (const char *name, unsigned int length,
+ struct variable *target, enum variable_origin origin,
+ struct variable_set *set, const floc *flocp)
+{
+ struct variable *v;
+ struct variable **var_slot;
+
+#ifdef KMK
+ if (set == NULL || set == &global_variable_set)
+ global_variable_generation++;
+#endif
+
+ /* Look it up the hash table slot for it. */
+ name = strcache2_add (&variable_strcache, name, length);
+ if ( set != &global_variable_set
+ || !(v = strcache2_get_user_val (&variable_strcache, name)))
+ {
+ struct variable var_key;
+
+ var_key.name = name;
+ var_key.length = length;
+ var_slot = (struct variable **) hash_find_slot_strcached (&set->table, &var_key);
+ v = *var_slot;
+ }
+ else
+ {
+ assert (!v || (v->name == name && !HASH_VACANT (v)));
+ var_slot = 0;
+ }
+ if (! HASH_VACANT (v))
+ {
+ /* A variable of this name is already defined.
+ If the old definition is from a stronger source
+ than this one, don't redefine it. */
+
+ if (env_overrides && v->origin == o_env)
+ /* V came from in the environment. Since it was defined
+ before the switches were parsed, it wasn't affected by -e. */
+ v->origin = o_env_override;
+
+ if ((int) origin < (int) v->origin)
+ return v;
+
+ if (v->value != 0 && !v->rdonly_val)
+ free (v->value);
+ VARIABLE_CHANGED (v);
+ }
+ else
+ {
+ /* Create a new variable definition and add it to the hash table. */
+ v = alloccache_alloc (&variable_cache);
+ v->name = name; /* already cached. */
+ v->length = length;
+ hash_insert_at (&set->table, v, var_slot);
+ v->special = 0;
+ v->expanding = 0;
+ v->exp_count = 0;
+ v->per_target = 0;
+ v->append = 0;
+ v->private_var = 0;
+ v->aliased = 0;
+ v->export = v_default;
+#ifdef CONFIG_WITH_COMPILER
+ v->recursive_without_dollar = 0;
+ v->evalprog = 0;
+ v->expandprog = 0;
+ v->evalval_count = 0;
+ v->expand_count = 0;
+#else
+ MAKE_STATS_2(v->expand_count = 0);
+ MAKE_STATS_2(v->evalval_count = 0);
+#endif
+ MAKE_STATS_2(v->changes = 0);
+ MAKE_STATS_2(v->reallocs = 0);
+ MAKE_STATS_2(v->references = 0);
+ MAKE_STATS_2(v->cTicksEvalVal = 0);
+ v->exportable = 1;
+ if (*name != '_' && (*name < 'A' || *name > 'Z')
+ && (*name < 'a' || *name > 'z'))
+ v->exportable = 0;
+ else
+ {
+ for (++name; *name != '\0'; ++name)
+ if (*name != '_' && (*name < 'a' || *name > 'z')
+ && (*name < 'A' || *name > 'Z') && !ISDIGIT(*name))
+ break;
+
+ if (*name != '\0')
+ v->exportable = 0;
+ }
+
+ /* If it's the global set, remember the variable. */
+ if (set == &global_variable_set)
+ strcache2_set_user_val (&variable_strcache, v->name, v);
+ }
+
+ /* Common variable setup. */
+ v->alias = 1;
+ v->rdonly_val = 1;
+ v->value = (char *)target;
+ v->value_length = sizeof(*target); /* Non-zero to provoke trouble. */
+ v->value_alloc_len = sizeof(*target);
+ if (flocp != 0)
+ v->fileinfo = *flocp;
+ else
+ v->fileinfo.filenm = 0;
+ v->origin = origin;
+ v->recursive = 0;
+
+ /* Mark the target as aliased. */
+ target->aliased = 1;
+
+ return v;
+}
+#endif /* KMK */
+
+/* If the variable passed in is "special", handle its special nature.
+ Currently there are two such variables, both used for introspection:
+ .VARIABLES expands to a list of all the variables defined in this instance
+ of make.
+ .TARGETS expands to a list of all the targets defined in this
+ instance of make.
+ Returns the variable reference passed in. */
+
+#define EXPANSION_INCREMENT(_l) ((((_l) / 500) + 1) * 500)
+
+static struct variable *
+lookup_special_var (struct variable *var)
+{
+ static unsigned long last_changenum = 0;
+
+
+ /* This one actually turns out to be very hard, due to the way the parser
+ records targets. The way it works is that target information is collected
+ internally until make knows the target is completely specified. It unitl
+ it sees that some new construct (a new target or variable) is defined that
+ it knows the previous one is done. In short, this means that if you do
+ this:
+
+ all:
+
+ TARGS := $(.TARGETS)
+
+ then $(TARGS) won't contain "all", because it's not until after the
+ variable is created that the previous target is completed.
+
+ Changing this would be a major pain. I think a less complex way to do it
+ would be to pre-define the target files as soon as the first line is
+ parsed, then come back and do the rest of the definition as now. That
+ would allow $(.TARGETS) to be correct without a major change to the way
+ the parser works.
+
+ if (streq (var->name, ".TARGETS"))
+ var->value = build_target_list (var->value);
+ else
+ */
+
+ if (variable_changenum != last_changenum && streq (var->name, ".VARIABLES"))
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ unsigned long max = EXPANSION_INCREMENT (strlen (var->value));
+#else
+ unsigned long max = EXPANSION_INCREMENT (var->value_length);
+#endif
+ unsigned long len;
+ char *p;
+ struct variable **vp = (struct variable **) global_variable_set.table.ht_vec;
+ struct variable **end = &vp[global_variable_set.table.ht_size];
+
+ /* Make sure we have at least MAX bytes in the allocated buffer. */
+ var->value = xrealloc (var->value, max);
+ MAKE_STATS_2(var->reallocs++);
+
+ /* Walk through the hash of variables, constructing a list of names. */
+ p = var->value;
+ len = 0;
+ for (; vp < end; ++vp)
+ if (!HASH_VACANT (*vp))
+ {
+ struct variable *v = *vp;
+ int l = v->length;
+
+ len += l + 1;
+ if (len > max)
+ {
+ unsigned long off = p - var->value;
+
+ max += EXPANSION_INCREMENT (l + 1);
+ var->value = xrealloc (var->value, max);
+ p = &var->value[off];
+ MAKE_STATS_2(var->reallocs++);
+ }
+
+ memcpy (p, v->name, l);
+ p += l;
+ *(p++) = ' ';
+ }
+ *(p-1) = '\0';
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ var->value_length = p - var->value - 1;
+ var->value_alloc_len = max;
+#endif
+ VARIABLE_CHANGED (var);
+
+ /* Remember the current variable change number. */
+ last_changenum = variable_changenum;
+ }
+
+ return var;
+}
+
+
+#if 0 /*FIX THIS - def KMK*/ /* bird: speed */
+MY_INLINE struct variable *
+lookup_cached_variable (const char *name)
+{
+ const struct variable_set_list *setlist = current_variable_set_list;
+ struct hash_table *ht;
+ unsigned int hash_1;
+ unsigned int hash_2;
+ unsigned int idx;
+ struct variable *v;
+
+ /* first set, first entry, both unrolled. */
+
+ if (setlist->set == &global_variable_set)
+ {
+ v = (struct variable *) strcache2_get_user_val (&variable_strcache, name);
+ if (MY_PREDICT_TRUE (v))
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+ assert (setlist->next == 0);
+ return 0;
+ }
+
+ hash_1 = strcache2_calc_ptr_hash (&variable_strcache, name);
+ ht = &setlist->set->table;
+ MAKE_STATS (ht->ht_lookups++);
+ idx = hash_1 & (ht->ht_size - 1);
+ v = ht->ht_vec[idx];
+ if (v != 0)
+ {
+ if ( (void *)v != hash_deleted_item
+ && v->name == name)
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+
+ /* the rest of the loop */
+ hash_2 = strcache2_get_hash (&variable_strcache, name) | 1;
+ for (;;)
+ {
+ idx += hash_2;
+ idx &= (ht->ht_size - 1);
+ v = (struct variable *) ht->ht_vec[idx];
+ MAKE_STATS (ht->ht_collisions++); /* there are hardly any deletions, so don't bother with not counting deleted clashes. */
+
+ if (v == 0)
+ break;
+ if ( (void *)v != hash_deleted_item
+ && v->name == name)
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+ } /* inner collision loop */
+ }
+ else
+ hash_2 = strcache2_get_hash (&variable_strcache, name) | 1;
+
+
+ /* The other sets, if any. */
+
+ setlist = setlist->next;
+ while (setlist)
+ {
+ if (setlist->set == &global_variable_set)
+ {
+ v = (struct variable *) strcache2_get_user_val (&variable_strcache, name);
+ if (MY_PREDICT_TRUE (v))
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+ assert (setlist->next == 0);
+ return 0;
+ }
+
+ /* first iteration unrolled */
+ ht = &setlist->set->table;
+ MAKE_STATS (ht->ht_lookups++);
+ idx = hash_1 & (ht->ht_size - 1);
+ v = ht->ht_vec[idx];
+ if (v != 0)
+ {
+ if ( (void *)v != hash_deleted_item
+ && v->name == name)
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+
+ /* the rest of the loop */
+ for (;;)
+ {
+ idx += hash_2;
+ idx &= (ht->ht_size - 1);
+ v = (struct variable *) ht->ht_vec[idx];
+ MAKE_STATS (ht->ht_collisions++); /* see reason above */
+
+ if (v == 0)
+ break;
+ if ( (void *)v != hash_deleted_item
+ && v->name == name)
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+ } /* inner collision loop */
+ }
+
+ /* next */
+ setlist = setlist->next;
+ }
+
+ return 0;
+}
+
+# ifndef NDEBUG
+struct variable *
+lookup_variable_for_assert (const char *name, unsigned int length)
+{
+ const struct variable_set_list *setlist;
+ struct variable var_key;
+ var_key.name = name;
+ var_key.length = length;
+
+ for (setlist = current_variable_set_list;
+ setlist != 0; setlist = setlist->next)
+ {
+ struct variable *v;
+ v = (struct variable *) hash_find_item_strcached (&setlist->set->table, &var_key);
+ if (v)
+ return MY_PREDICT_FALSE (v->special) ? lookup_special_var (v) : v;
+ }
+ return 0;
+}
+# endif /* !NDEBUG */
+#endif /* KMK - need for speed */
+
+/* Lookup a variable whose name is a string starting at NAME
+ and with LENGTH chars. NAME need not be null-terminated.
+ Returns address of the 'struct variable' containing all info
+ on the variable, or nil if no such variable is defined. */
+
+struct variable *
+lookup_variable (const char *name, unsigned int length)
+{
+#if 1 /*FIX THIS - ndef KMK*/
+ const struct variable_set_list *setlist;
+ struct variable var_key;
+#else /* KMK */
+ struct variable *v;
+#endif /* KMK */
+ int is_parent = 0;
+#ifdef CONFIG_WITH_STRCACHE2
+ const char *cached_name;
+#endif
+
+# ifdef KMK
+ /* Check for kBuild-define- local variable accesses and handle these first. */
+ if (length > 3 && name[0] == '[')
+ {
+ struct variable *v = lookup_kbuild_object_variable_accessor(name, length);
+ if (v != VAR_NOT_KBUILD_ACCESSOR)
+ {
+ MAKE_STATS_2 (v->references++);
+ return v;
+ }
+ }
+# endif
+
+#ifdef CONFIG_WITH_STRCACHE2
+ /* lookup the name in the string case, if it's not there it won't
+ be in any of the sets either. */
+ cached_name = strcache2_lookup (&variable_strcache, name, length);
+ if (!cached_name)
+ return NULL;
+ name = cached_name;
+#endif /* CONFIG_WITH_STRCACHE2 */
+#if 1 /*FIX THIS - ndef KMK */
+
+ var_key.name = (char *) name;
+ var_key.length = length;
+
+ for (setlist = current_variable_set_list;
+ setlist != 0; setlist = setlist->next)
+ {
+ const struct variable_set *set = setlist->set;
+ struct variable *v;
+
+# ifndef CONFIG_WITH_STRCACHE2
+ v = (struct variable *) hash_find_item ((struct hash_table *) &set->table, &var_key);
+# else /* CONFIG_WITH_STRCACHE2 */
+ v = (struct variable *) hash_find_item_strcached ((struct hash_table *) &set->table, &var_key);
+# endif /* CONFIG_WITH_STRCACHE2 */
+ if (v && (!is_parent || !v->private_var))
+ {
+# ifdef KMK
+ RESOLVE_ALIAS_VARIABLE(v);
+# endif
+ MAKE_STATS_2 (v->references++);
+ return v->special ? lookup_special_var (v) : v;
+ }
+
+ is_parent |= setlist->next_is_parent;
+ }
+
+#else /* KMK - need for speed */
+
+ v = lookup_cached_variable (name);
+ assert (lookup_variable_for_assert(name, length) == v);
+#ifdef VMS
+ if (v)
+#endif
+ return v;
+#endif /* KMK - need for speed */
+#ifdef VMS
+ /* VMS does not populate envp[] with DCL symbols and logical names which
+ historically are mapped to enviroment varables and returned by getenv() */
+ {
+ char *vname = alloca (length + 1);
+ char *value;
+ strncpy (vname, name, length);
+ vname[length] = 0;
+ value = getenv (vname);
+ if (value != 0)
+ {
+ char *sptr;
+ int scnt;
+
+ sptr = value;
+ scnt = 0;
+
+ while ((sptr = strchr (sptr, '$')))
+ {
+ scnt++;
+ sptr++;
+ }
+
+ if (scnt > 0)
+ {
+ char *nvalue;
+ char *nptr;
+
+ nvalue = alloca (strlen (value) + scnt + 1);
+ sptr = value;
+ nptr = nvalue;
+
+ while (*sptr)
+ {
+ if (*sptr == '$')
+ {
+ *nptr++ = '$';
+ *nptr++ = '$';
+ }
+ else
+ {
+ *nptr++ = *sptr;
+ }
+ sptr++;
+ }
+
+ *nptr = '\0';
+ return define_variable (vname, length, nvalue, o_env, 1);
+
+ }
+
+ return define_variable (vname, length, value, o_env, 1);
+ }
+ }
+#endif /* VMS */
+
+ return 0;
+}
+
+#ifdef CONFIG_WITH_STRCACHE2
+/* Alternative version of lookup_variable that takes a name that's already in
+ the variable string cache. */
+struct variable *
+lookup_variable_strcached (const char *name)
+{
+ struct variable *v;
+#if 1 /*FIX THIS - ndef KMK*/
+ const struct variable_set_list *setlist;
+ struct variable var_key;
+#endif /* KMK */
+ int is_parent = 0;
+
+#ifndef NDEBUG
+ strcache2_verify_entry (&variable_strcache, name);
+#endif
+
+#ifdef KMK
+ /* Check for kBuild-define- local variable accesses and handle these first. */
+ if (strcache2_get_len(&variable_strcache, name) > 3 && name[0] == '[')
+ {
+ v = lookup_kbuild_object_variable_accessor(name, strcache2_get_len(&variable_strcache, name));
+ if (v != VAR_NOT_KBUILD_ACCESSOR)
+ {
+ MAKE_STATS_2 (v->references++);
+ return v;
+ }
+ }
+#endif
+
+#if 1 /*FIX THIS - ndef KMK */
+
+ var_key.name = (char *) name;
+ var_key.length = strcache2_get_len(&variable_strcache, name);
+
+ for (setlist = current_variable_set_list;
+ setlist != 0; setlist = setlist->next)
+ {
+ const struct variable_set *set = setlist->set;
+
+ v = (struct variable *) hash_find_item_strcached ((struct hash_table *) &set->table, &var_key);
+ if (v && (!is_parent || !v->private_var))
+ {
+# ifdef KMK
+ RESOLVE_ALIAS_VARIABLE(v);
+# endif
+ MAKE_STATS_2 (v->references++);
+ return v->special ? lookup_special_var (v) : v;
+ }
+
+ is_parent |= setlist->next_is_parent;
+ }
+
+#else /* KMK - need for speed */
+
+ v = lookup_cached_variable (name);
+ assert (lookup_variable_for_assert(name, length) == v);
+#ifdef VMS
+ if (v)
+#endif
+ return v;
+#endif /* KMK - need for speed */
+#ifdef VMS
+# error "Port me (split out the relevant code from lookup_varaible and call it)"
+#endif
+ return 0;
+}
+#endif
+
+
+/* Lookup a variable whose name is a string starting at NAME
+ and with LENGTH chars in set SET. NAME need not be null-terminated.
+ Returns address of the 'struct variable' containing all info
+ on the variable, or nil if no such variable is defined. */
+
+struct variable *
+lookup_variable_in_set (const char *name, unsigned int length,
+ const struct variable_set *set)
+{
+ struct variable var_key;
+#ifndef CONFIG_WITH_STRCACHE2
+ var_key.name = (char *) name;
+ var_key.length = length;
+
+ return (struct variable *) hash_find_item ((struct hash_table *) &set->table, &var_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ const char *cached_name;
+ struct variable *v;
+
+# ifdef KMK
+ /* Check for kBuild-define- local variable accesses and handle these first. */
+ if (length > 3 && name[0] == '[' && set == &global_variable_set)
+ {
+ v = lookup_kbuild_object_variable_accessor(name, length);
+ if (v != VAR_NOT_KBUILD_ACCESSOR)
+ {
+ RESOLVE_ALIAS_VARIABLE(v);
+ MAKE_STATS_2 (v->references++);
+ return v;
+ }
+ }
+# endif
+
+ /* lookup the name in the string case, if it's not there it won't
+ be in any of the sets either. Optimize lookups in the global set. */
+ cached_name = strcache2_lookup(&variable_strcache, name, length);
+ if (!cached_name)
+ return NULL;
+
+ if (set == &global_variable_set)
+ {
+ v = strcache2_get_user_val (&variable_strcache, cached_name);
+ assert (!v || v->name == cached_name);
+ }
+ else
+ {
+ var_key.name = cached_name;
+ var_key.length = length;
+
+ v = (struct variable *) hash_find_item_strcached (
+ (struct hash_table *) &set->table, &var_key);
+ }
+# ifdef KMK
+ RESOLVE_ALIAS_VARIABLE(v);
+# endif
+ MAKE_STATS_2 (if (v) v->references++);
+ return v;
+#endif /* CONFIG_WITH_STRCACHE2 */
+}
+
+/* Initialize FILE's variable set list. If FILE already has a variable set
+ list, the topmost variable set is left intact, but the the rest of the
+ chain is replaced with FILE->parent's setlist. If FILE is a double-colon
+ rule, then we will use the "root" double-colon target's variable set as the
+ parent of FILE's variable set.
+
+ If we're READING a makefile, don't do the pattern variable search now,
+ since the pattern variable might not have been defined yet. */
+
+void
+initialize_file_variables (struct file *file, int reading)
+{
+ struct variable_set_list *l = file->variables;
+
+ if (l == 0)
+ {
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ l = (struct variable_set_list *)
+ xmalloc (sizeof (struct variable_set_list));
+ l->set = xmalloc (sizeof (struct variable_set));
+#else /* CONFIG_WITH_ALLOC_CACHES */
+ l = (struct variable_set_list *)
+ alloccache_alloc (&variable_set_list_cache);
+ l->set = (struct variable_set *)
+ alloccache_alloc (&variable_set_cache);
+#endif /* CONFIG_WITH_ALLOC_CACHES */
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&l->set->table, PERFILE_VARIABLE_BUCKETS,
+ variable_hash_1, variable_hash_2, variable_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&l->set->table, PERFILE_VARIABLE_BUCKETS,
+ &variable_strcache, offsetof (struct variable, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+ file->variables = l;
+ }
+
+ /* If this is a double-colon, then our "parent" is the "root" target for
+ this double-colon rule. Since that rule has the same name, parent,
+ etc. we can just use its variables as the "next" for ours. */
+
+ if (file->double_colon && file->double_colon != file)
+ {
+ initialize_file_variables (file->double_colon, reading);
+ l->next = file->double_colon->variables;
+ l->next_is_parent = 0;
+ return;
+ }
+
+ if (file->parent == 0)
+ l->next = &global_setlist;
+ else
+ {
+ initialize_file_variables (file->parent, reading);
+ l->next = file->parent->variables;
+ }
+ l->next_is_parent = 1;
+
+ /* If we're not reading makefiles and we haven't looked yet, see if
+ we can find pattern variables for this target. */
+
+ if (!reading && !file->pat_searched)
+ {
+ struct pattern_var *p;
+
+ p = lookup_pattern_var (0, file->name);
+ if (p != 0)
+ {
+ struct variable_set_list *global = current_variable_set_list;
+
+ /* We found at least one. Set up a new variable set to accumulate
+ all the pattern variables that match this target. */
+
+ file->pat_variables = create_new_variable_set ();
+ current_variable_set_list = file->pat_variables;
+
+ do
+ {
+ /* We found one, so insert it into the set. */
+
+ struct variable *v;
+
+ if (p->variable.flavor == f_simple)
+ {
+ v = define_variable_loc (
+ p->variable.name, strlen (p->variable.name),
+ p->variable.value, p->variable.origin,
+ 0, &p->variable.fileinfo);
+
+ v->flavor = f_simple;
+ }
+ else
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ v = do_variable_definition (
+ &p->variable.fileinfo, p->variable.name,
+ p->variable.value, p->variable.origin,
+ p->variable.flavor, 1);
+#else
+ v = do_variable_definition_2 (
+ &p->variable.fileinfo, p->variable.name,
+ p->variable.value, p->variable.value_length, 0, 0,
+ p->variable.origin, p->variable.flavor, 1);
+#endif
+ }
+
+ /* Also mark it as a per-target and copy export status. */
+ v->per_target = p->variable.per_target;
+ v->export = p->variable.export;
+ v->private_var = p->variable.private_var;
+ }
+ while ((p = lookup_pattern_var (p, file->name)) != 0);
+
+ current_variable_set_list = global;
+ }
+ file->pat_searched = 1;
+ }
+
+ /* If we have a pattern variable match, set it up. */
+
+ if (file->pat_variables != 0)
+ {
+ file->pat_variables->next = l->next;
+ file->pat_variables->next_is_parent = l->next_is_parent;
+ l->next = file->pat_variables;
+ l->next_is_parent = 0;
+ }
+}
+
+/* Pop the top set off the current variable set list,
+ and free all its storage. */
+
+struct variable_set_list *
+create_new_variable_set (void)
+{
+ register struct variable_set_list *setlist;
+ register struct variable_set *set;
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ set = xmalloc (sizeof (struct variable_set));
+#else
+ set = (struct variable_set *) alloccache_alloc (&variable_set_cache);
+#endif
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&set->table, SMALL_SCOPE_VARIABLE_BUCKETS,
+ variable_hash_1, variable_hash_2, variable_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&set->table, SMALL_SCOPE_VARIABLE_BUCKETS,
+ &variable_strcache, offsetof (struct variable, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ setlist = (struct variable_set_list *)
+ xmalloc (sizeof (struct variable_set_list));
+#else
+ setlist = (struct variable_set_list *)
+ alloccache_alloc (&variable_set_list_cache);
+#endif
+ setlist->set = set;
+ setlist->next = current_variable_set_list;
+ setlist->next_is_parent = 0;
+
+ return setlist;
+}
+
+/* Create a new variable set and push it on the current setlist.
+ If we're pushing a global scope (that is, the current scope is the global
+ scope) then we need to "push" it the other way: file variable sets point
+ directly to the global_setlist so we need to replace that with the new one.
+ */
+
+struct variable_set_list *
+push_new_variable_scope (void)
+{
+ current_variable_set_list = create_new_variable_set ();
+ if (current_variable_set_list->next == &global_setlist)
+ {
+ /* It was the global, so instead of new -> &global we want to replace
+ &global with the new one and have &global -> new, with current still
+ pointing to &global */
+ struct variable_set *set = current_variable_set_list->set;
+ current_variable_set_list->set = global_setlist.set;
+ global_setlist.set = set;
+ current_variable_set_list->next = global_setlist.next;
+ global_setlist.next = current_variable_set_list;
+ current_variable_set_list = &global_setlist;
+ }
+ return (current_variable_set_list);
+}
+
+void
+pop_variable_scope (void)
+{
+ struct variable_set_list *setlist;
+ struct variable_set *set;
+
+ /* Can't call this if there's no scope to pop! */
+ assert (current_variable_set_list->next != NULL);
+
+ if (current_variable_set_list != &global_setlist)
+ {
+ /* We're not pointing to the global setlist, so pop this one. */
+ setlist = current_variable_set_list;
+ set = setlist->set;
+ current_variable_set_list = setlist->next;
+ }
+ else
+ {
+ /* This set is the one in the global_setlist, but there is another global
+ set beyond that. We want to copy that set to global_setlist, then
+ delete what used to be in global_setlist. */
+ setlist = global_setlist.next;
+ set = global_setlist.set;
+ global_setlist.set = setlist->set;
+ global_setlist.next = setlist->next;
+ global_setlist.next_is_parent = setlist->next_is_parent;
+ }
+
+ /* Free the one we no longer need. */
+#ifndef CONFIG_WITH_ALLOC_CACHES
+ free (setlist);
+ hash_map (&set->table, free_variable_name_and_value);
+ hash_free (&set->table, 1);
+ free (set);
+#else
+ alloccache_free (&variable_set_list_cache, setlist);
+ hash_map (&set->table, free_variable_name_and_value);
+ hash_free_cached (&set->table, 1, &variable_cache);
+ alloccache_free (&variable_set_cache, set);
+#endif
+}
+
+/* Merge FROM_SET into TO_SET, freeing unused storage in FROM_SET. */
+
+static void
+merge_variable_sets (struct variable_set *to_set,
+ struct variable_set *from_set)
+{
+ struct variable **from_var_slot = (struct variable **) from_set->table.ht_vec;
+ struct variable **from_var_end = from_var_slot + from_set->table.ht_size;
+
+ int inc = to_set == &global_variable_set ? 1 : 0;
+
+ for ( ; from_var_slot < from_var_end; from_var_slot++)
+ if (! HASH_VACANT (*from_var_slot))
+ {
+ struct variable *from_var = *from_var_slot;
+ struct variable **to_var_slot
+#ifndef CONFIG_WITH_STRCACHE2
+ = (struct variable **) hash_find_slot (&to_set->table, *from_var_slot);
+#else /* CONFIG_WITH_STRCACHE2 */
+ = (struct variable **) hash_find_slot_strcached (&to_set->table,
+ *from_var_slot);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ if (HASH_VACANT (*to_var_slot))
+ {
+ hash_insert_at (&to_set->table, from_var, to_var_slot);
+ variable_changenum += inc;
+ }
+ else
+ {
+ /* GKM FIXME: delete in from_set->table */
+#ifdef KMK
+ if (from_var->aliased)
+ OS (fatal, NULL, ("Attempting to delete aliased variable '%s'"), from_var->name);
+ if (from_var->alias)
+ OS (fatal, NULL, ("Attempting to delete variable aliased '%s'"), from_var->name);
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ if (from_var->evalprog || from_var->expandprog)
+ kmk_cc_variable_deleted (from_var);
+#endif
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (!from_var->rdonly_val)
+#endif
+ free (from_var->value);
+ free (from_var);
+ }
+ }
+}
+
+/* Merge SETLIST1 into SETLIST0, freeing unused storage in SETLIST1. */
+
+void
+merge_variable_set_lists (struct variable_set_list **setlist0,
+ struct variable_set_list *setlist1)
+{
+ struct variable_set_list *to = *setlist0;
+ struct variable_set_list *last0 = 0;
+
+ /* If there's nothing to merge, stop now. */
+ if (!setlist1)
+ return;
+
+ /* This loop relies on the fact that all setlists terminate with the global
+ setlist (before NULL). If that's not true, arguably we SHOULD die. */
+ if (to)
+ while (setlist1 != &global_setlist && to != &global_setlist)
+ {
+ struct variable_set_list *from = setlist1;
+ setlist1 = setlist1->next;
+
+ merge_variable_sets (to->set, from->set);
+
+ last0 = to;
+ to = to->next;
+ }
+
+ if (setlist1 != &global_setlist)
+ {
+ if (last0 == 0)
+ *setlist0 = setlist1;
+ else
+ last0->next = setlist1;
+ }
+}
+
+#if defined(KMK) && !defined(WINDOWS32)
+/* Parses out the next number from the uname release level string. Fast
+ forwards to the end of the string when encountering some non-conforming
+ chars. */
+
+static unsigned long parse_release_number (const char **ppsz)
+{
+ unsigned long ul;
+ char *psz = (char *)*ppsz;
+ if (ISDIGIT (*psz))
+ {
+ ul = strtoul (psz, &psz, 10);
+ if (psz != NULL && *psz == '.')
+ psz++;
+ else
+ psz = strchr (*ppsz, '\0');
+ *ppsz = psz;
+ }
+ else
+ ul = 0;
+ return ul;
+}
+#endif
+
+/* Define the automatic variables, and record the addresses
+ of their structures so we can change their values quickly. */
+
+void
+define_automatic_variables (void)
+{
+ struct variable *v;
+#ifndef KMK
+ char buf[200];
+#else
+ char buf[1024];
+ const char *val;
+ struct variable *envvar1;
+ struct variable *envvar2;
+# ifndef WINDOWS32
+ struct utsname uts;
+# endif
+ unsigned long ulMajor = 0, ulMinor = 0, ulPatch = 0, ul4th = 0;
+#endif
+
+ sprintf (buf, "%u", makelevel);
+ define_variable_cname (MAKELEVEL_NAME, buf, o_env, 0);
+
+ sprintf (buf, "%s%s%s",
+ version_string,
+ (remote_description == 0 || remote_description[0] == '\0')
+ ? "" : "-",
+ (remote_description == 0 || remote_description[0] == '\0')
+ ? "" : remote_description);
+#ifndef KMK
+ define_variable_cname ("MAKE_VERSION", buf, o_default, 0);
+ define_variable_cname ("MAKE_HOST", make_host, o_default, 0);
+#else /* KMK */
+
+ /* Define KMK_VERSION to indicate kMk. */
+ define_variable_cname ("KMK_VERSION", buf, o_default, 0);
+
+ /* Define KBUILD_VERSION* */
+ sprintf (buf, "%d", KBUILD_VERSION_MAJOR);
+ define_variable_cname ("KBUILD_VERSION_MAJOR", buf, o_default, 0);
+ sprintf (buf, "%d", KBUILD_VERSION_MINOR);
+ define_variable_cname ("KBUILD_VERSION_MINOR", buf, o_default, 0);
+ sprintf (buf, "%d", KBUILD_VERSION_PATCH);
+ define_variable_cname ("KBUILD_VERSION_PATCH", buf, o_default, 0);
+ sprintf (buf, "%d", KBUILD_SVN_REV);
+ define_variable_cname ("KBUILD_KMK_REVISION", buf, o_default, 0);
+
+ sprintf (buf, "%d.%d.%d-r%d", KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR,
+ KBUILD_VERSION_PATCH, KBUILD_SVN_REV);
+ define_variable_cname ("KBUILD_VERSION", buf, o_default, 0);
+
+ /* The host defaults. The BUILD_* stuff will be replaced by KBUILD_* soon. */
+ envvar1 = lookup_variable (STRING_SIZE_TUPLE ("KBUILD_HOST"));
+ envvar2 = lookup_variable (STRING_SIZE_TUPLE ("BUILD_PLATFORM"));
+ val = envvar1 ? envvar1->value : envvar2 ? envvar2->value : KBUILD_HOST;
+ if (envvar1 && envvar2 && strcmp (envvar1->value, envvar2->value))
+ OS (error, NULL, _("KBUILD_HOST and BUILD_PLATFORM differs, using KBUILD_HOST=%s."), val);
+ if (!envvar1)
+ define_variable_cname ("KBUILD_HOST", val, o_default, 0);
+ if (!envvar2)
+ define_variable_cname ("BUILD_PLATFORM", "$(KBUILD_HOST)", o_default, 1);
+
+ envvar1 = lookup_variable (STRING_SIZE_TUPLE ("KBUILD_HOST_ARCH"));
+ envvar2 = lookup_variable (STRING_SIZE_TUPLE ("BUILD_PLATFORM_ARCH"));
+ val = envvar1 ? envvar1->value : envvar2 ? envvar2->value : KBUILD_HOST_ARCH;
+ if (envvar1 && envvar2 && strcmp (envvar1->value, envvar2->value))
+ OS (error, NULL, _("KBUILD_HOST_ARCH and BUILD_PLATFORM_ARCH differs, using KBUILD_HOST_ARCH=%s."), val);
+ if (!envvar1)
+ define_variable_cname ("KBUILD_HOST_ARCH", val, o_default, 0);
+ if (!envvar2)
+ define_variable_cname ("BUILD_PLATFORM_ARCH", "$(KBUILD_HOST_ARCH)", o_default, 1);
+
+ envvar1 = lookup_variable (STRING_SIZE_TUPLE ("KBUILD_HOST_CPU"));
+ envvar2 = lookup_variable (STRING_SIZE_TUPLE ("BUILD_PLATFORM_CPU"));
+ val = envvar1 ? envvar1->value : envvar2 ? envvar2->value : KBUILD_HOST_CPU;
+ if (envvar1 && envvar2 && strcmp (envvar1->value, envvar2->value))
+ OS (error, NULL, _("KBUILD_HOST_CPU and BUILD_PLATFORM_CPU differs, using KBUILD_HOST_CPU=%s."), val);
+ if (!envvar1)
+ define_variable_cname ("KBUILD_HOST_CPU", val, o_default, 0);
+ if (!envvar2)
+ define_variable_cname ("BUILD_PLATFORM_CPU", "$(KBUILD_HOST_CPU)", o_default, 1);
+
+ /* The host kernel version. */
+# if defined(WINDOWS32)
+ {
+ OSVERSIONINFOEXW oix;
+ NTSTATUS (WINAPI *pfnRtlGetVersion)(OSVERSIONINFOEXW *);
+ *(FARPROC *)&pfnRtlGetVersion = GetProcAddress (GetModuleHandleW (L"NTDLL.DLL"),
+ "RtlGetVersion"); /* GetVersionEx lies */
+ memset (&oix, '\0', sizeof (oix));
+ oix.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEXW);
+ if (!pfnRtlGetVersion || pfnRtlGetVersion (&oix) < 0)
+ {
+ memset (&oix, '\0', sizeof (oix));
+ oix.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEXW);
+ if (!GetVersionExW((LPOSVERSIONINFOW)&oix))
+ {
+ memset (&oix, '\0', sizeof (oix));
+ oix.dwOSVersionInfoSize = sizeof (OSVERSIONINFOW);
+ GetVersionExW ((LPOSVERSIONINFOW)&oix);
+ }
+ }
+ if (oix.dwPlatformId == VER_PLATFORM_WIN32_NT)
+ {
+ ulMajor = oix.dwMajorVersion;
+ ulMinor = oix.dwMinorVersion;
+ ulPatch = oix.wServicePackMajor;
+ ul4th = oix.wServicePackMinor;
+ }
+ else
+ {
+ ulMajor = oix.dwPlatformId == 1 ? 0 /*Win95/98/ME*/
+ : oix.dwPlatformId == 3 ? 1 /*WinCE*/
+ : 2; /*??*/
+ ulMinor = oix.dwMajorVersion;
+ ulPatch = oix.dwMinorVersion;
+ ul4th = oix.wServicePackMajor;
+ }
+ oix.dwBuildNumber &= 0x3fffffff;
+ sprintf (buf, "%lu", oix.dwBuildNumber);
+ define_variable_cname ("KBUILD_HOST_VERSION_BUILD", buf, o_default, 0);
+
+ sprintf (buf, "%lu.%lu.%lu.%lu.%lu", ulMajor, ulMinor, ulPatch, ul4th, oix.dwBuildNumber);
+ define_variable_cname ("KBUILD_HOST_VERSION", buf, o_default, 0);
+ }
+# else
+ memset (&uts, 0, sizeof(uts));
+ uname (&uts);
+ val = uts.release;
+ ulMajor = parse_release_number (&val);
+ ulMinor = parse_release_number (&val);
+ ulPatch = parse_release_number (&val);
+ ul4th = parse_release_number (&val);
+
+ define_variable_cname ("KBUILD_HOST_UNAME_SYSNAME", uts.sysname, o_default, 0);
+ define_variable_cname ("KBUILD_HOST_UNAME_RELEASE", uts.release, o_default, 0);
+ define_variable_cname ("KBUILD_HOST_UNAME_VERSION", uts.version, o_default, 0);
+ define_variable_cname ("KBUILD_HOST_UNAME_MACHINE", uts.machine, o_default, 0);
+ define_variable_cname ("KBUILD_HOST_UNAME_NODENAME", uts.nodename, o_default, 0);
+
+ sprintf (buf, "%lu.%lu.%lu.%lu", ulMajor, ulMinor, ulPatch, ul4th);
+ define_variable_cname ("KBUILD_HOST_VERSION", buf, o_default, 0);
+# endif
+
+ sprintf (buf, "%lu", ulMajor);
+ define_variable_cname ("KBUILD_HOST_VERSION_MAJOR", buf, o_default, 0);
+
+ sprintf (buf, "%lu", ulMinor);
+ define_variable_cname ("KBUILD_HOST_VERSION_MINOR", buf, o_default, 0);
+
+ sprintf (buf, "%lu", ulPatch);
+ define_variable_cname ("KBUILD_HOST_VERSION_PATCH", buf, o_default, 0);
+
+ /* The kBuild locations. */
+ define_variable_cname ("KBUILD_PATH", get_kbuild_path (), o_default, 0);
+ define_variable_cname ("KBUILD_BIN_PATH", get_kbuild_bin_path (), o_default, 0);
+
+ define_variable_cname ("PATH_KBUILD", "$(KBUILD_PATH)", o_default, 1);
+ define_variable_cname ("PATH_KBUILD_BIN", "$(KBUILD_BIN_PATH)", o_default, 1);
+
+ /* Define KMK_FEATURES to indicate various working KMK features. */
+# if defined (CONFIG_WITH_RSORT) \
+ && defined (CONFIG_WITH_ABSPATHEX) \
+ && defined (CONFIG_WITH_TOUPPER_TOLOWER) \
+ && defined (CONFIG_WITH_DEFINED) \
+ && defined (CONFIG_WITH_VALUE_LENGTH) \
+ && defined (CONFIG_WITH_COMPARE) \
+ && defined (CONFIG_WITH_STACK) \
+ && defined (CONFIG_WITH_MATH) \
+ && defined (CONFIG_WITH_XARGS) \
+ && defined (CONFIG_WITH_EXPLICIT_MULTITARGET) \
+ && defined (CONFIG_WITH_DOT_MUST_MAKE) \
+ && defined (CONFIG_WITH_PREPEND_ASSIGNMENT) \
+ && defined (CONFIG_WITH_SET_CONDITIONALS) \
+ && defined (CONFIG_WITH_DATE) \
+ && defined (CONFIG_WITH_FILE_SIZE) \
+ && defined (CONFIG_WITH_WHERE_FUNCTION) \
+ && defined (CONFIG_WITH_WHICH) \
+ && defined (CONFIG_WITH_EVALPLUS) \
+ && (defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)) \
+ && defined (CONFIG_WITH_COMMANDS_FUNC) \
+ && defined (CONFIG_WITH_PRINTF) \
+ && defined (CONFIG_WITH_LOOP_FUNCTIONS) \
+ && defined (CONFIG_WITH_ROOT_FUNC) \
+ && defined (CONFIG_WITH_STRING_FUNCTIONS) \
+ && defined (CONFIG_WITH_DEFINED_FUNCTIONS) \
+ && defined (KMK_HELPERS)
+ define_variable_cname ("KMK_FEATURES",
+ "append-dash-n abspath includedep-queue install-hard-linking umask quote versort"
+ " kBuild-define"
+ " rsort"
+ " abspathex"
+ " toupper tolower"
+ " defined"
+ " comp-vars comp-cmds comp-cmds-ex"
+ " stack"
+ " math-int"
+ " xargs"
+ " explicit-multitarget"
+ " dot-must-make"
+ " prepend-assignment"
+ " set-conditionals intersects"
+ " date"
+ " file-size"
+ " expr if-expr select"
+ " where"
+ " which"
+ " evalctx evalval evalvalctx evalcall evalcall2 eval-opt-var"
+ " make-stats"
+ " commands"
+ " printf"
+ " for while"
+ " root"
+ " length insert pos lastpos substr translate"
+ " kb-src-tool kb-obj-base kb-obj-suff kb-src-prop kb-src-one kb-exp-tmpl"
+ " firstdefined lastdefined"
+ , o_default, 0);
+# else /* MSC can't deal with strings mixed with #if/#endif, thus the slow way. */
+# error "All features should be enabled by default!"
+ strcpy (buf, "append-dash-n abspath includedep-queue install-hard-linking umask quote versort"
+ " kBuild-define");
+# if defined (CONFIG_WITH_RSORT)
+ strcat (buf, " rsort");
+# endif
+# if defined (CONFIG_WITH_ABSPATHEX)
+ strcat (buf, " abspathex");
+# endif
+# if defined (CONFIG_WITH_TOUPPER_TOLOWER)
+ strcat (buf, " toupper tolower");
+# endif
+# if defined (CONFIG_WITH_DEFINED)
+ strcat (buf, " defined");
+# endif
+# if defined (CONFIG_WITH_VALUE_LENGTH) && defined(CONFIG_WITH_COMPARE)
+ strcat (buf, " comp-vars comp-cmds comp-cmds-ex");
+# endif
+# if defined (CONFIG_WITH_STACK)
+ strcat (buf, " stack");
+# endif
+# if defined (CONFIG_WITH_MATH)
+ strcat (buf, " math-int");
+# endif
+# if defined (CONFIG_WITH_XARGS)
+ strcat (buf, " xargs");
+# endif
+# if defined (CONFIG_WITH_EXPLICIT_MULTITARGET)
+ strcat (buf, " explicit-multitarget");
+# endif
+# if defined (CONFIG_WITH_DOT_MUST_MAKE)
+ strcat (buf, " dot-must-make");
+# endif
+# if defined (CONFIG_WITH_PREPEND_ASSIGNMENT)
+ strcat (buf, " prepend-assignment");
+# endif
+# if defined (CONFIG_WITH_SET_CONDITIONALS)
+ strcat (buf, " set-conditionals intersects");
+# endif
+# if defined (CONFIG_WITH_DATE)
+ strcat (buf, " date");
+# endif
+# if defined (CONFIG_WITH_FILE_SIZE)
+ strcat (buf, " file-size");
+# endif
+# if defined (CONFIG_WITH_IF_CONDITIONALS)
+ strcat (buf, " expr if-expr select");
+# endif
+# if defined (CONFIG_WITH_WHERE_FUNCTION)
+ strcat (buf, " where");
+# endif
+# if defined (CONFIG_WITH_WHICH)
+ strcat (buf, " which");
+# endif
+# if defined (CONFIG_WITH_EVALPLUS)
+ strcat (buf, " evalctx evalval evalvalctx evalcall evalcall2 eval-opt-var");
+# endif
+# if defined (CONFIG_WITH_MAKE_STATS) || defined (CONFIG_WITH_MINIMAL_STATS)
+ strcat (buf, " make-stats");
+# endif
+# if defined (CONFIG_WITH_COMMANDS_FUNC)
+ strcat (buf, " commands");
+# endif
+# if defined (CONFIG_WITH_PRINTF)
+ strcat (buf, " printf");
+# endif
+# if defined (CONFIG_WITH_LOOP_FUNCTIONS)
+ strcat (buf, " for while");
+# endif
+# if defined (CONFIG_WITH_ROOT_FUNC)
+ strcat (buf, " root");
+# endif
+# if defined (CONFIG_WITH_STRING_FUNCTIONS)
+ strcat (buf, " length insert pos lastpos substr translate");
+# endif
+# if defined (CONFIG_WITH_DEFINED_FUNCTIONS)
+ strcat (buf, " firstdefined lastdefined");
+# endif
+# if defined (KMK_HELPERS)
+ strcat (buf, " kb-src-tool kb-obj-base kb-obj-suff kb-src-prop kb-src-one kb-exp-tmpl");
+# endif
+ define_variable_cname ("KMK_FEATURES", buf, o_default, 0);
+# endif
+
+#endif /* KMK */
+
+#ifdef CONFIG_WITH_KMK_BUILTIN
+ /* The supported kMk Builtin commands. */
+ define_variable_cname ("KMK_BUILTIN", "append cat chmod cp cmp echo expr install kDepIDB ln md5sum mkdir mv printf rm rmdir sleep test", o_default, 0);
+#endif
+
+#ifdef __MSDOS__
+ /* Allow to specify a special shell just for Make,
+ and use $COMSPEC as the default $SHELL when appropriate. */
+ {
+ static char shell_str[] = "SHELL";
+ const int shlen = sizeof (shell_str) - 1;
+ struct variable *mshp = lookup_variable ("MAKESHELL", 9);
+ struct variable *comp = lookup_variable ("COMSPEC", 7);
+
+ /* $(MAKESHELL) overrides $(SHELL) even if -e is in effect. */
+ if (mshp)
+ (void) define_variable (shell_str, shlen,
+ mshp->value, o_env_override, 0);
+ else if (comp)
+ {
+ /* $(COMSPEC) shouldn't override $(SHELL). */
+ struct variable *shp = lookup_variable (shell_str, shlen);
+
+ if (!shp)
+ (void) define_variable (shell_str, shlen, comp->value, o_env, 0);
+ }
+ }
+#elif defined(__EMX__)
+ {
+ static char shell_str[] = "SHELL";
+ const int shlen = sizeof (shell_str) - 1;
+ struct variable *shell = lookup_variable (shell_str, shlen);
+ struct variable *replace = lookup_variable ("MAKESHELL", 9);
+
+ /* if $MAKESHELL is defined in the environment assume o_env_override */
+ if (replace && *replace->value && replace->origin == o_env)
+ replace->origin = o_env_override;
+
+ /* if $MAKESHELL is not defined use $SHELL but only if the variable
+ did not come from the environment */
+ if (!replace || !*replace->value)
+ if (shell && *shell->value && (shell->origin == o_env
+ || shell->origin == o_env_override))
+ {
+ /* overwrite whatever we got from the environment */
+ free (shell->value);
+ shell->value = xstrdup (default_shell);
+ shell->origin = o_default;
+ }
+
+ /* Some people do not like cmd to be used as the default
+ if $SHELL is not defined in the Makefile.
+ With -DNO_CMD_DEFAULT you can turn off this behaviour */
+# ifndef NO_CMD_DEFAULT
+ /* otherwise use $COMSPEC */
+ if (!replace || !*replace->value)
+ replace = lookup_variable ("COMSPEC", 7);
+
+ /* otherwise use $OS2_SHELL */
+ if (!replace || !*replace->value)
+ replace = lookup_variable ("OS2_SHELL", 9);
+# else
+# warning NO_CMD_DEFAULT: GNU make will not use CMD.EXE as default shell
+# endif
+
+ if (replace && *replace->value)
+ /* overwrite $SHELL */
+ (void) define_variable (shell_str, shlen, replace->value,
+ replace->origin, 0);
+ else
+ /* provide a definition if there is none */
+ (void) define_variable (shell_str, shlen, default_shell,
+ o_default, 0);
+ }
+
+#endif
+
+ /* This won't override any definition, but it will provide one if there
+ isn't one there. */
+ v = define_variable_cname ("SHELL", default_shell, o_default, 0);
+#ifdef __MSDOS__
+ v->export = v_export; /* Export always SHELL. */
+#endif
+
+ /* On MSDOS we do use SHELL from environment, since it isn't a standard
+ environment variable on MSDOS, so whoever sets it, does that on purpose.
+ On OS/2 we do not use SHELL from environment but we have already handled
+ that problem above. */
+#if !defined(__MSDOS__) && !defined(__EMX__)
+ /* Don't let SHELL come from the environment. */
+ if (*v->value == '\0' || v->origin == o_env || v->origin == o_env_override)
+ {
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v->rdonly_val)
+ v->rdonly_val = 0;
+ else
+# endif
+ free (v->value);
+ v->origin = o_file;
+ v->value = xstrdup (default_shell);
+# ifdef CONFIG_WITH_VALUE_LENGTH
+ v->value_length = strlen (v->value);
+ v->value_alloc_len = v->value_length + 1;
+# endif
+ }
+#endif
+
+ /* Make sure MAKEFILES gets exported if it is set. */
+ v = define_variable_cname ("MAKEFILES", "", o_default, 0);
+ v->export = v_ifset;
+
+ /* Define the magic D and F variables in terms of
+ the automatic variables they are variations of. */
+
+#if defined(__MSDOS__) || defined(WINDOWS32)
+ /* For consistency, remove the trailing backslash as well as slash. */
+ define_variable_cname ("@D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $@)))",
+ o_automatic, 1);
+ define_variable_cname ("%D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $%)))",
+ o_automatic, 1);
+ define_variable_cname ("*D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $*)))",
+ o_automatic, 1);
+ define_variable_cname ("<D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $<)))",
+ o_automatic, 1);
+ define_variable_cname ("?D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $?)))",
+ o_automatic, 1);
+ define_variable_cname ("^D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $^)))",
+ o_automatic, 1);
+ define_variable_cname ("+D", "$(patsubst %/,%,$(patsubst %\\,%,$(dir $+)))",
+ o_automatic, 1);
+#else /* not __MSDOS__, not WINDOWS32 */
+ define_variable_cname ("@D", "$(patsubst %/,%,$(dir $@))", o_automatic, 1);
+ define_variable_cname ("%D", "$(patsubst %/,%,$(dir $%))", o_automatic, 1);
+ define_variable_cname ("*D", "$(patsubst %/,%,$(dir $*))", o_automatic, 1);
+ define_variable_cname ("<D", "$(patsubst %/,%,$(dir $<))", o_automatic, 1);
+ define_variable_cname ("?D", "$(patsubst %/,%,$(dir $?))", o_automatic, 1);
+ define_variable_cname ("^D", "$(patsubst %/,%,$(dir $^))", o_automatic, 1);
+ define_variable_cname ("+D", "$(patsubst %/,%,$(dir $+))", o_automatic, 1);
+#endif
+ define_variable_cname ("@F", "$(notdir $@)", o_automatic, 1);
+ define_variable_cname ("%F", "$(notdir $%)", o_automatic, 1);
+ define_variable_cname ("*F", "$(notdir $*)", o_automatic, 1);
+ define_variable_cname ("<F", "$(notdir $<)", o_automatic, 1);
+ define_variable_cname ("?F", "$(notdir $?)", o_automatic, 1);
+ define_variable_cname ("^F", "$(notdir $^)", o_automatic, 1);
+ define_variable_cname ("+F", "$(notdir $+)", o_automatic, 1);
+#ifdef CONFIG_WITH_LAZY_DEPS_VARS
+ define_variable ("^", 1, "$(deps $@)", o_automatic, 1);
+ define_variable ("+", 1, "$(deps-all $@)", o_automatic, 1);
+ define_variable ("?", 1, "$(deps-newer $@)", o_automatic, 1);
+ define_variable ("|", 1, "$(deps-oo $@)", o_automatic, 1);
+#endif /* CONFIG_WITH_LAZY_DEPS_VARS */
+}
+
+int export_all_variables;
+
+#ifdef KMK
+/* Cached table containing the exports of the global_variable_set. When
+ there are many global variables, it can be so expensive to construct the
+ child environment that we have a majority of job slot idle. */
+static size_t global_variable_set_exports_generation = ~(size_t)0;
+static struct hash_table global_variable_set_exports;
+
+static void update_global_variable_set_exports(void)
+{
+ struct variable **v_slot;
+ struct variable **v_end;
+
+ /* Re-initialize the table. */
+ if (global_variable_set_exports_generation != ~(size_t)0)
+ hash_free (&global_variable_set_exports, 0);
+ hash_init_strcached (&global_variable_set_exports, ENVIRONMENT_VARIABLE_BUCKETS,
+ &variable_strcache, offsetof (struct variable, name));
+
+ /* do pretty much the same as target_environment. */
+ v_slot = (struct variable **) global_variable_set.table.ht_vec;
+ v_end = v_slot + global_variable_set.table.ht_size;
+ for ( ; v_slot < v_end; v_slot++)
+ if (! HASH_VACANT (*v_slot))
+ {
+ struct variable **new_slot;
+ struct variable *v = *v_slot;
+
+ switch (v->export)
+ {
+ case v_default:
+ if (v->origin == o_default || v->origin == o_automatic)
+ /* Only export default variables by explicit request. */
+ continue;
+
+ /* The variable doesn't have a name that can be exported. */
+ if (! v->exportable)
+ continue;
+
+ if (! export_all_variables
+ && v->origin != o_command
+ && v->origin != o_env && v->origin != o_env_override)
+ continue;
+ break;
+
+ case v_export:
+ break;
+
+ case v_noexport:
+ {
+ /* If this is the SHELL variable and it's not exported,
+ then add the value from our original environment, if
+ the original environment defined a value for SHELL. */
+ extern struct variable shell_var;
+ if (streq (v->name, "SHELL") && shell_var.value)
+ {
+ v = &shell_var;
+ break;
+ }
+ continue;
+ }
+
+ case v_ifset:
+ if (v->origin == o_default)
+ continue;
+ break;
+ }
+
+ assert (strcache2_is_cached (&variable_strcache, v->name));
+ new_slot = (struct variable **) hash_find_slot_strcached (&global_variable_set_exports, v);
+ if (HASH_VACANT (*new_slot))
+ hash_insert_at (&global_variable_set_exports, v, new_slot);
+ }
+
+ /* done */
+ global_variable_set_exports_generation = global_variable_generation;
+}
+
+#endif
+
+/* Create a new environment for FILE's commands.
+ If FILE is nil, this is for the 'shell' function.
+ The child's MAKELEVEL variable is incremented. */
+
+char **
+target_environment (struct file *file)
+{
+ struct variable_set_list *set_list;
+ register struct variable_set_list *s;
+ struct hash_table table;
+ struct variable **v_slot;
+ struct variable **v_end;
+ struct variable makelevel_key;
+ char **result_0;
+ char **result;
+#ifdef CONFIG_WITH_STRCACHE2
+ const char *cached_name;
+#endif
+
+#ifdef KMK
+ if (global_variable_set_exports_generation != global_variable_generation)
+ update_global_variable_set_exports();
+#endif
+
+ if (file == 0)
+ set_list = current_variable_set_list;
+ else
+ set_list = file->variables;
+
+#ifndef CONFIG_WITH_STRCACHE2
+ hash_init (&table, ENVIRONMENT_VARIABLE_BUCKETS,
+ variable_hash_1, variable_hash_2, variable_hash_cmp);
+#else /* CONFIG_WITH_STRCACHE2 */
+ hash_init_strcached (&table, ENVIRONMENT_VARIABLE_BUCKETS,
+ &variable_strcache, offsetof (struct variable, name));
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+ /* Run through all the variable sets in the list,
+ accumulating variables in TABLE. */
+ for (s = set_list; s != 0; s = s->next)
+ {
+ struct variable_set *set = s->set;
+#ifdef KMK
+ if (set == &global_variable_set)
+ {
+ assert(s->next == NULL);
+ break;
+ }
+#endif
+ v_slot = (struct variable **) set->table.ht_vec;
+ v_end = v_slot + set->table.ht_size;
+ for ( ; v_slot < v_end; v_slot++)
+ if (! HASH_VACANT (*v_slot))
+ {
+ struct variable **new_slot;
+ struct variable *v = *v_slot;
+
+ /* If this is a per-target variable and it hasn't been touched
+ already then look up the global version and take its export
+ value. */
+ if (v->per_target && v->export == v_default)
+ {
+ struct variable *gv;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ gv = lookup_variable_in_set (v->name, strlen (v->name),
+ &global_variable_set);
+#else
+ assert ((int)strlen(v->name) == v->length);
+ gv = lookup_variable_in_set (v->name, v->length,
+ &global_variable_set);
+#endif
+ if (gv)
+ v->export = gv->export;
+ }
+
+ switch (v->export)
+ {
+ case v_default:
+ if (v->origin == o_default || v->origin == o_automatic)
+ /* Only export default variables by explicit request. */
+ continue;
+
+ /* The variable doesn't have a name that can be exported. */
+ if (! v->exportable)
+ continue;
+
+ if (! export_all_variables
+ && v->origin != o_command
+ && v->origin != o_env && v->origin != o_env_override)
+ continue;
+ break;
+
+ case v_export:
+ break;
+
+ case v_noexport:
+ {
+ /* If this is the SHELL variable and it's not exported,
+ then add the value from our original environment, if
+ the original environment defined a value for SHELL. */
+ if (streq (v->name, "SHELL") && shell_var.value)
+ {
+ v = &shell_var;
+ break;
+ }
+ continue;
+ }
+
+ case v_ifset:
+ if (v->origin == o_default)
+ continue;
+ break;
+ }
+
+#ifndef CONFIG_WITH_STRCACHE2
+ new_slot = (struct variable **) hash_find_slot (&table, v);
+#else /* CONFIG_WITH_STRCACHE2 */
+ assert (strcache2_is_cached (&variable_strcache, v->name));
+ new_slot = (struct variable **) hash_find_slot_strcached (&table, v);
+#endif /* CONFIG_WITH_STRCACHE2 */
+ if (HASH_VACANT (*new_slot))
+ hash_insert_at (&table, v, new_slot);
+ }
+ }
+
+#ifdef KMK
+ /* Add the global exports to table. */
+ v_slot = (struct variable **) global_variable_set_exports.ht_vec;
+ v_end = v_slot + global_variable_set_exports.ht_size;
+ for ( ; v_slot < v_end; v_slot++)
+ if (! HASH_VACANT (*v_slot))
+ {
+ struct variable **new_slot;
+ struct variable *v = *v_slot;
+ assert (strcache2_is_cached (&variable_strcache, v->name));
+ new_slot = (struct variable **) hash_find_slot_strcached (&table, v);
+ if (HASH_VACANT (*new_slot))
+ hash_insert_at (&table, v, new_slot);
+ }
+#endif
+
+#ifndef CONFIG_WITH_STRCACHE2
+ makelevel_key.name = (char *)MAKELEVEL_NAME;
+ makelevel_key.length = MAKELEVEL_LENGTH;
+ hash_delete (&table, &makelevel_key);
+#else /* CONFIG_WITH_STRCACHE2 */
+ /* lookup the name in the string case, if it's not there it won't
+ be in any of the sets either. */
+ cached_name = strcache2_lookup (&variable_strcache,
+ MAKELEVEL_NAME, MAKELEVEL_LENGTH);
+ if (cached_name)
+ {
+ makelevel_key.name = cached_name;
+ makelevel_key.length = MAKELEVEL_LENGTH;
+ hash_delete_strcached (&table, &makelevel_key);
+ }
+#endif /* CONFIG_WITH_STRCACHE2 */
+
+ result = result_0 = xmalloc ((table.ht_fill + 2) * sizeof (char *));
+
+ v_slot = (struct variable **) table.ht_vec;
+ v_end = v_slot + table.ht_size;
+ for ( ; v_slot < v_end; v_slot++)
+ if (! HASH_VACANT (*v_slot))
+ {
+ struct variable *v = *v_slot;
+
+ /* If V is recursively expanded and didn't come from the environment,
+ expand its value. If it came from the environment, it should
+ go back into the environment unchanged. */
+ if (v->recursive
+ && v->origin != o_env && v->origin != o_env_override)
+ {
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *value = recursively_expand_for_file (v, file);
+#else
+ char *value = recursively_expand_for_file (v, file, NULL);
+#endif
+#ifdef WINDOWS32
+ if (strcmp (v->name, "Path") == 0 ||
+ strcmp (v->name, "PATH") == 0)
+ convert_Path_to_windows32 (value, ';');
+#endif
+ *result++ = xstrdup (concat (3, v->name, "=", value));
+ free (value);
+ }
+ else
+ {
+#ifdef WINDOWS32
+ if (strcmp (v->name, "Path") == 0 ||
+ strcmp (v->name, "PATH") == 0)
+ convert_Path_to_windows32 (v->value, ';');
+#endif
+ *result++ = xstrdup (concat (3, v->name, "=", v->value));
+ }
+ }
+
+ *result = xmalloc (100);
+ sprintf (*result, "%s=%u", MAKELEVEL_NAME, makelevel + 1);
+ *++result = 0;
+
+ hash_free (&table, 0);
+
+ return result_0;
+}
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+/* Worker function for do_variable_definition_append() and
+ append_expanded_string_to_variable().
+ The APPEND argument indicates whether it's an append or prepend operation. */
+void append_string_to_variable (struct variable *v, const char *value, unsigned int value_len, int append)
+{
+ /* The previous definition of the variable was recursive.
+ The new value is the unexpanded old and new values. */
+ unsigned int new_value_len = value_len + (v->value_length != 0 ? 1 + v->value_length : 0);
+ int done_1st_prepend_copy = 0;
+#ifdef KMK
+ assert (!v->alias);
+#endif
+
+ /* Drop empty strings. Use $(NO_SUCH_VARIABLE) if a space is wanted. */
+ if (!value_len)
+ return;
+
+ /* adjust the size. */
+ if (v->value_alloc_len <= new_value_len + 1)
+ {
+ if (v->value_alloc_len < 256)
+ v->value_alloc_len = 256;
+ else
+ v->value_alloc_len *= 2;
+ if (v->value_alloc_len < new_value_len + 1)
+ v->value_alloc_len = VAR_ALIGN_VALUE_ALLOC (new_value_len + 1 + value_len /*future*/ );
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if ((append || !v->value_length) && !v->rdonly_val)
+# else
+ if (append || !v->value_length)
+# endif
+ v->value = xrealloc (v->value, v->value_alloc_len);
+ else
+ {
+ /* avoid the extra memcpy the xrealloc may have to do */
+ char *new_buf = xmalloc (v->value_alloc_len);
+ memcpy (&new_buf[value_len + 1], v->value, v->value_length + 1);
+ done_1st_prepend_copy = 1;
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ if (v->rdonly_val)
+ v->rdonly_val = 0;
+ else
+# endif
+ free (v->value);
+ v->value = new_buf;
+ }
+ MAKE_STATS_2(v->reallocs++);
+ }
+
+ /* insert the new bits */
+ if (v->value_length != 0)
+ {
+ if (append)
+ {
+ v->value[v->value_length] = ' ';
+ memcpy (&v->value[v->value_length + 1], value, value_len + 1);
+ }
+ else
+ {
+ if (!done_1st_prepend_copy)
+ memmove (&v->value[value_len + 1], v->value, v->value_length + 1);
+ v->value[value_len] = ' ';
+ memcpy (v->value, value, value_len);
+ }
+ }
+ else
+ memcpy (v->value, value, value_len + 1);
+ v->value_length = new_value_len;
+ VARIABLE_CHANGED (v);
+}
+
+struct variable *
+do_variable_definition_append (const floc *flocp, struct variable *v,
+ const char *value, unsigned int value_len,
+ int simple_value, enum variable_origin origin,
+ int append)
+{
+ if (env_overrides && origin == o_env)
+ origin = o_env_override;
+
+ if (env_overrides && v->origin == o_env)
+ /* V came from in the environment. Since it was defined
+ before the switches were parsed, it wasn't affected by -e. */
+ v->origin = o_env_override;
+
+ /* A variable of this name is already defined.
+ If the old definition is from a stronger source
+ than this one, don't redefine it. */
+ if ((int) origin < (int) v->origin)
+ return v;
+ v->origin = origin;
+
+ /* location */
+ if (flocp != 0)
+ v->fileinfo = *flocp;
+
+ /* The juicy bits, append the specified value to the variable
+ This is a heavily exercised code path in kBuild. */
+ if (value_len == ~0U)
+ value_len = strlen (value);
+ if (v->recursive || simple_value)
+ append_string_to_variable (v, value, value_len, append);
+ else
+ /* The previous definition of the variable was simple.
+ The new value comes from the old value, which was expanded
+ when it was set; and from the expanded new value. */
+ append_expanded_string_to_variable (v, value, value_len, append);
+
+ /* update the variable */
+ return v;
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+static struct variable *
+set_special_var (struct variable *var)
+{
+ if (streq (var->name, RECIPEPREFIX_NAME))
+ {
+ /* The user is resetting the command introduction prefix. This has to
+ happen immediately, so that subsequent rules are interpreted
+ properly. */
+ cmd_prefix = var->value[0]=='\0' ? RECIPEPREFIX_DEFAULT : var->value[0];
+ }
+
+ return var;
+}
+
+/* Given a string, shell-execute it and return a malloc'ed string of the
+ * result. This removes only ONE newline (if any) at the end, for maximum
+ * compatibility with the *BSD makes. If it fails, returns NULL. */
+
+static char *
+shell_result (const char *p)
+{
+ char *buf;
+ unsigned int len;
+ char *args[2];
+ char *result;
+
+ install_variable_buffer (&buf, &len);
+
+ args[0] = (char *) p;
+ args[1] = NULL;
+ variable_buffer_output (func_shell_base (variable_buffer, args, 0), "\0", 1);
+ result = strdup (variable_buffer);
+
+ restore_variable_buffer (buf, len);
+ return result;
+}
+
+/* Given a variable, a value, and a flavor, define the variable.
+ See the try_variable_definition() function for details on the parameters. */
+
+struct variable *
+#ifndef CONFIG_WITH_VALUE_LENGTH
+do_variable_definition (const floc *flocp, const char *varname,
+ const char *value, enum variable_origin origin,
+ enum variable_flavor flavor, int target_var)
+#else /* CONFIG_WITH_VALUE_LENGTH */
+do_variable_definition_2 (const floc *flocp,
+ const char *varname, const char *value,
+ unsigned int value_len, int simple_value,
+ char *free_value,
+ enum variable_origin origin,
+ enum variable_flavor flavor,
+ int target_var)
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+{
+ const char *p;
+ char *alloc_value = NULL;
+ struct variable *v;
+ int append = 0;
+ int conditional = 0;
+ const size_t varname_len = strlen (varname); /* bird */
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ if (value_len == ~0U)
+ value_len = strlen (value);
+ else
+ assert (value_len == strlen (value));
+#endif
+
+ /* Calculate the variable's new value in VALUE. */
+
+ switch (flavor)
+ {
+ default:
+ case f_bogus:
+ /* Should not be possible. */
+ abort ();
+ case f_simple:
+ /* A simple variable definition "var := value". Expand the value.
+ We have to allocate memory since otherwise it'll clobber the
+ variable buffer, and we may still need that if we're looking at a
+ target-specific variable. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ p = alloc_value = allocated_variable_expand (value);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ if (!simple_value)
+ p = alloc_value = allocated_variable_expand_2 (value, value_len, &value_len);
+ else
+ {
+ if (value_len == ~0U)
+ value_len = strlen (value);
+ if (!free_value)
+ p = alloc_value = xstrndup (value, value_len);
+ else
+ {
+ assert (value == free_value);
+ p = alloc_value = free_value;
+ free_value = 0;
+ }
+ }
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+ break;
+ case f_shell:
+ {
+ /* A shell definition "var != value". Expand value, pass it to
+ the shell, and store the result in recursively-expanded var. */
+ char *q = allocated_variable_expand (value);
+ p = alloc_value = shell_result (q);
+ free (q);
+ flavor = f_recursive;
+ break;
+ }
+ case f_conditional:
+ /* A conditional variable definition "var ?= value".
+ The value is set IFF the variable is not defined yet. */
+ v = lookup_variable (varname, varname_len);
+ if (v)
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ return v->special ? set_special_var (v) : v;
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ {
+ if (free_value)
+ free (free_value);
+ return v->special ? set_special_var (v) : v;
+ }
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+ conditional = 1;
+ flavor = f_recursive;
+ /* FALLTHROUGH */
+ case f_recursive:
+ /* A recursive variable definition "var = value".
+ The value is used verbatim. */
+ p = value;
+ break;
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ case f_append:
+ case f_prepend:
+ {
+ const enum variable_flavor org_flavor = flavor;
+#else
+ case f_append:
+ {
+#endif
+
+ /* If we have += but we're in a target variable context, we want to
+ append only with other variables in the context of this target. */
+ if (target_var)
+ {
+ append = 1;
+ v = lookup_variable_in_set (varname, varname_len,
+ current_variable_set_list->set);
+
+ /* Don't append from the global set if a previous non-appending
+ target-specific variable definition exists. */
+ if (v && !v->append)
+ append = 0;
+ }
+#ifdef KMK
+ else if ( g_pTopKbEvalData
+ || ( varname_len > 3
+ && varname[0] == '['
+ && is_kbuild_object_variable_accessor (varname, varname_len)) )
+ {
+ v = kbuild_object_variable_pre_append (varname, varname_len,
+ value, value_len, simple_value,
+ origin, org_flavor == f_append, flocp);
+ if (free_value)
+ free (free_value);
+ return v;
+ }
+#endif
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ /* If 'local', restrict it to the current variable context. */
+ else if (origin == o_local)
+ v = lookup_variable_in_set (varname, varname_len,
+ current_variable_set_list->set);
+#endif
+ else
+ v = lookup_variable (varname, varname_len);
+
+ if (v == 0)
+ {
+ /* There was no old value.
+ This becomes a normal recursive definition. */
+ p = value;
+ flavor = f_recursive;
+ }
+ else
+ {
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ v->append = append;
+ v = do_variable_definition_append (flocp, v, value, value_len,
+ simple_value, origin,
+# ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ org_flavor == f_append);
+# else
+ 1);
+# endif
+ if (free_value)
+ free (free_value);
+ return v;
+#else /* !CONFIG_WITH_VALUE_LENGTH */
+
+ /* Paste the old and new values together in VALUE. */
+
+ unsigned int oldlen, vallen;
+ const char *val;
+ char *tp = NULL;
+
+ val = value;
+ if (v->recursive)
+ /* The previous definition of the variable was recursive.
+ The new value is the unexpanded old and new values. */
+ flavor = f_recursive;
+ else
+ /* The previous definition of the variable was simple.
+ The new value comes from the old value, which was expanded
+ when it was set; and from the expanded new value. Allocate
+ memory for the expansion as we may still need the rest of the
+ buffer if we're looking at a target-specific variable. */
+ val = tp = allocated_variable_expand (val);
+
+ oldlen = strlen (v->value);
+ vallen = strlen (val);
+ p = alloc_value = xmalloc (oldlen + 1 + vallen + 1);
+# ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ if (org_flavor == f_prepend)
+ {
+ memcpy (alloc_value, val, vallen);
+ alloc_value[oldlen] = ' ';
+ memcpy (&alloc_value[oldlen + 1], v->value, oldlen + 1);
+ }
+ else
+# endif /* CONFIG_WITH_PREPEND_ASSIGNMENT */
+ {
+ memcpy (alloc_value, v->value, oldlen);
+ alloc_value[oldlen] = ' ';
+ memcpy (&alloc_value[oldlen + 1], val, vallen + 1);
+ }
+
+ free (tp);
+#endif /* !CONFIG_WITH_VALUE_LENGTH */
+ }
+ }
+ }
+
+#ifdef __MSDOS__
+ /* Many Unix Makefiles include a line saying "SHELL=/bin/sh", but
+ non-Unix systems don't conform to this default configuration (in
+ fact, most of them don't even have '/bin'). On the other hand,
+ $SHELL in the environment, if set, points to the real pathname of
+ the shell.
+ Therefore, we generally won't let lines like "SHELL=/bin/sh" from
+ the Makefile override $SHELL from the environment. But first, we
+ look for the basename of the shell in the directory where SHELL=
+ points, and along the $PATH; if it is found in any of these places,
+ we define $SHELL to be the actual pathname of the shell. Thus, if
+ you have bash.exe installed as d:/unix/bash.exe, and d:/unix is on
+ your $PATH, then SHELL=/usr/local/bin/bash will have the effect of
+ defining SHELL to be "d:/unix/bash.exe". */
+ if ((origin == o_file || origin == o_override)
+ && strcmp (varname, "SHELL") == 0)
+ {
+ PATH_VAR (shellpath);
+ extern char * __dosexec_find_on_path (const char *, char *[], char *);
+
+ /* See if we can find "/bin/sh.exe", "/bin/sh.com", etc. */
+ if (__dosexec_find_on_path (p, NULL, shellpath))
+ {
+ char *tp;
+
+ for (tp = shellpath; *tp; tp++)
+ if (*tp == '\\')
+ *tp = '/';
+
+ v = define_variable_loc (varname, varname_len,
+ shellpath, origin, flavor == f_recursive,
+ flocp);
+ }
+ else
+ {
+ const char *shellbase, *bslash;
+ struct variable *pathv = lookup_variable ("PATH", 4);
+ char *path_string;
+ char *fake_env[2];
+ size_t pathlen = 0;
+
+ shellbase = strrchr (p, '/');
+ bslash = strrchr (p, '\\');
+ if (!shellbase || bslash > shellbase)
+ shellbase = bslash;
+ if (!shellbase && p[1] == ':')
+ shellbase = p + 1;
+ if (shellbase)
+ shellbase++;
+ else
+ shellbase = p;
+
+ /* Search for the basename of the shell (with standard
+ executable extensions) along the $PATH. */
+ if (pathv)
+ pathlen = strlen (pathv->value);
+ path_string = xmalloc (5 + pathlen + 2 + 1);
+ /* On MSDOS, current directory is considered as part of $PATH. */
+ sprintf (path_string, "PATH=.;%s", pathv ? pathv->value : "");
+ fake_env[0] = path_string;
+ fake_env[1] = 0;
+ if (__dosexec_find_on_path (shellbase, fake_env, shellpath))
+ {
+ char *tp;
+
+ for (tp = shellpath; *tp; tp++)
+ if (*tp == '\\')
+ *tp = '/';
+
+ v = define_variable_loc (varname, varname_len,
+ shellpath, origin,
+ flavor == f_recursive, flocp);
+ }
+ else
+ v = lookup_variable (varname, varname_len);
+
+ free (path_string);
+ }
+ }
+ else
+#endif /* __MSDOS__ */
+#ifdef WINDOWS32
+ if ( varname_len == sizeof("SHELL") - 1 /* bird */
+ && (origin == o_file || origin == o_override || origin == o_command)
+ && streq (varname, "SHELL"))
+ {
+ extern const char *default_shell;
+
+ /* Call shell locator function. If it returns TRUE, then
+ set no_default_sh_exe to indicate sh was found and
+ set new value for SHELL variable. */
+
+ if (find_and_set_default_shell (p))
+ {
+ v = define_variable_in_set (varname, varname_len, default_shell,
+# ifdef CONFIG_WITH_VALUE_LENGTH
+ ~0U, 1 /* duplicate_value */,
+# endif
+ origin, flavor == f_recursive,
+ (target_var
+ ? current_variable_set_list->set
+ : NULL),
+ flocp);
+ no_default_sh_exe = 0;
+ }
+ else
+ {
+ char *tp = alloc_value;
+
+ alloc_value = allocated_variable_expand (p);
+
+ if (find_and_set_default_shell (alloc_value))
+ {
+ v = define_variable_in_set (varname, varname_len, p,
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ ~0U, 1 /* duplicate_value */,
+#endif
+ origin, flavor == f_recursive,
+ (target_var
+ ? current_variable_set_list->set
+ : NULL),
+ flocp);
+ no_default_sh_exe = 0;
+ }
+ else
+ v = lookup_variable (varname, varname_len);
+
+ free (tp);
+ }
+ }
+ else
+#endif
+
+ /* If we are defining variables inside an $(eval ...), we might have a
+ different variable context pushed, not the global context (maybe we're
+ inside a $(call ...) or something. Since this function is only ever
+ invoked in places where we want to define globally visible variables,
+ make sure we define this variable in the global set. */
+
+ v = define_variable_in_set (varname, varname_len, p,
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ value_len, !alloc_value,
+#endif
+ origin, flavor == f_recursive,
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ (target_var || origin == o_local
+#else
+ (target_var
+#endif
+ ? current_variable_set_list->set : NULL),
+ flocp);
+ v->append = append;
+ v->conditional = conditional;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ free (alloc_value);
+#else
+ if (free_value)
+ free (free_value);
+#endif
+
+ return v->special ? set_special_var (v) : v;
+}
+
+/* Parse P (a null-terminated string) as a variable definition.
+
+ If it is not a variable definition, return NULL and the contents of *VAR
+ are undefined, except NAME is set to the first non-space character or NIL.
+
+ If it is a variable definition, return a pointer to the char after the
+ assignment token and set the following fields (only) of *VAR:
+ name : name of the variable (ALWAYS SET) (NOT NUL-TERMINATED!)
+ length : length of the variable name
+ value : value of the variable (nul-terminated)
+ flavor : flavor of the variable
+ Other values in *VAR are unchanged.
+ */
+
+char *
+parse_variable_definition (const char *p, struct variable *var)
+{
+ int wspace = 0;
+ const char *e = NULL;
+
+/** @todo merge 4.2.1: parse_variable_definition does more now */
+ NEXT_TOKEN (p);
+ var->name = (char *)p;
+ var->length = 0;
+
+ while (1)
+ {
+ int c = *p++;
+
+ /* If we find a comment or EOS, it's not a variable definition. */
+ if (STOP_SET (c, MAP_COMMENT|MAP_NUL))
+ return NULL;
+
+ if (c == '$')
+ {
+ /* This begins a variable expansion reference. Make sure we don't
+ treat chars inside the reference as assignment tokens. */
+ char closeparen;
+ unsigned int count;
+
+ c = *p++;
+ if (c == '(')
+ closeparen = ')';
+ else if (c == '{')
+ closeparen = '}';
+ else if (c == '\0')
+ return NULL;
+ else
+ /* '$$' or '$X'. Either way, nothing special to do here. */
+ continue;
+
+ /* P now points past the opening paren or brace.
+ Count parens or braces until it is matched. */
+ for (count = 1; *p != '\0'; ++p)
+ {
+ if (*p == closeparen && --count == 0)
+ {
+ ++p;
+ break;
+ }
+ if (*p == c)
+ ++count;
+ }
+ continue;
+ }
+
+ /* If we find whitespace skip it, and remember we found it. */
+ if (ISBLANK (c))
+ {
+ wspace = 1;
+ e = p - 1;
+ NEXT_TOKEN (p);
+ c = *p;
+ if (c == '\0')
+ return NULL;
+ ++p;
+ }
+
+
+ if (c == '=')
+ {
+ var->flavor = f_recursive;
+ if (! e)
+ e = p - 1;
+ break;
+ }
+
+ /* Match assignment variants (:=, +=, ?=, !=) */
+ if (*p == '=')
+ {
+ switch (c)
+ {
+ case ':':
+ var->flavor = f_simple;
+ break;
+ case '+':
+ var->flavor = f_append;
+ break;
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ case '<':
+ var->flavor = f_prepend;
+ break;
+#endif
+ case '?':
+ var->flavor = f_conditional;
+ break;
+ case '!':
+ var->flavor = f_shell;
+ break;
+ default:
+ /* If we skipped whitespace, non-assignments means no var. */
+ if (wspace)
+ return NULL;
+
+ /* Might be assignment, or might be $= or #=. Check. */
+ continue;
+ }
+ if (! e)
+ e = p - 1;
+ ++p;
+ break;
+ }
+
+ /* Check for POSIX ::= syntax */
+ if (c == ':')
+ {
+ /* A colon other than :=/::= is not a variable defn. */
+ if (*p != ':' || p[1] != '=')
+ return NULL;
+
+ /* POSIX allows ::= to be the same as GNU make's := */
+ var->flavor = f_simple;
+ if (! e)
+ e = p - 1;
+ p += 2;
+ break;
+ }
+
+ /* If we skipped whitespace, non-assignments means no var. */
+ if (wspace)
+ return NULL;
+ }
+
+ var->length = e - var->name;
+ var->value = next_token (p);
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ var->value_alloc_len = ~(unsigned int)0;
+ var->value_length = -1;
+# ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ var->rdonly_val = 0;
+# endif
+#endif
+ return (char *)p;
+}
+
+/* Try to interpret LINE (a null-terminated string) as a variable definition.
+
+ If LINE was recognized as a variable definition, a pointer to its 'struct
+ variable' is returned. If LINE is not a variable definition, NULL is
+ returned. */
+
+struct variable *
+assign_variable_definition (struct variable *v, const char *line IF_WITH_VALUE_LENGTH_PARAM(char *eos))
+{
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ char *name;
+#endif
+
+ if (!parse_variable_definition (line, v))
+ return NULL;
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ if (eos)
+ {
+ v->value_length = eos - v->value;
+ assert (strchr (v->value, '\0') == eos);
+ }
+#endif
+
+ /* Expand the name, so "$(foo)bar = baz" works. */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ name = alloca (v->length + 1);
+ memcpy (name, v->name, v->length);
+ name[v->length] = '\0';
+ v->name = allocated_variable_expand (name);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+ v->name = allocated_variable_expand_2 (v->name, v->length, NULL);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+ if (v->name[0] == '\0')
+ O (fatal, &v->fileinfo, _("empty variable name"));
+
+ return v;
+}
+
+/* Try to interpret LINE (a null-terminated string) as a variable definition.
+
+ ORIGIN may be o_file, o_override, o_env, o_env_override, o_local,
+ or o_command specifying that the variable definition comes
+ from a makefile, an override directive, the environment with
+ or without the -e switch, or the command line.
+
+ See the comments for assign_variable_definition().
+
+ If LINE was recognized as a variable definition, a pointer to its 'struct
+ variable' is returned. If LINE is not a variable definition, NULL is
+ returned. */
+
+struct variable *
+try_variable_definition (const floc *flocp, const char *line
+ IF_WITH_VALUE_LENGTH_PARAM(char *eos),
+ enum variable_origin origin, int target_var)
+{
+ struct variable v;
+ struct variable *vp;
+
+ if (flocp != 0)
+ v.fileinfo = *flocp;
+ else
+ v.fileinfo.filenm = 0;
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+ if (!assign_variable_definition (&v, line))
+ return 0;
+
+ vp = do_variable_definition (flocp, v.name, v.value,
+ origin, v.flavor, target_var);
+#else
+ if (!assign_variable_definition (&v, line, eos))
+ return 0;
+
+ vp = do_variable_definition_2 (flocp, v.name, v.value, v.value_length,
+ 0, NULL, origin, v.flavor, target_var);
+#endif
+
+#ifndef CONFIG_WITH_STRCACHE2
+ free (v.name);
+#else
+ free ((char *)v.name);
+#endif
+
+ return vp;
+}
+
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+static unsigned long var_stats_evalvals, var_stats_evalvaled;
+static unsigned long var_stats_expands, var_stats_expanded;
+#endif
+#ifdef CONFIG_WITH_COMPILER
+static unsigned long var_stats_expandprogs, var_stats_evalprogs;
+#endif
+#ifdef CONFIG_WITH_MAKE_STATS
+static unsigned long var_stats_changes, var_stats_changed;
+static unsigned long var_stats_reallocs, var_stats_realloced;
+static unsigned long var_stats_references, var_stats_referenced;
+static unsigned long var_stats_val_len, var_stats_val_alloc_len;
+static unsigned long var_stats_val_rdonly_len;
+#endif
+
+/* Print information for variable V, prefixing it with PREFIX. */
+
+static void
+print_variable (const void *item, void *arg)
+{
+ const struct variable *v = item;
+ const char *prefix = arg;
+ const char *origin;
+#ifdef KMK
+ const struct variable *alias = v;
+ RESOLVE_ALIAS_VARIABLE(v);
+#endif
+
+ switch (v->origin)
+ {
+ case o_automatic:
+ origin = _("automatic");
+ break;
+ case o_default:
+ origin = _("default");
+ break;
+ case o_env:
+ origin = _("environment");
+ break;
+ case o_file:
+ origin = _("makefile");
+ break;
+ case o_env_override:
+ origin = _("environment under -e");
+ break;
+ case o_command:
+ origin = _("command line");
+ break;
+ case o_override:
+ origin = _("'override' directive");
+ break;
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ case o_local:
+ origin = _("`local' directive");
+ break;
+#endif
+ case o_invalid:
+ default:
+ abort ();
+ }
+ fputs ("# ", stdout);
+ fputs (origin, stdout);
+ if (v->private_var)
+ fputs (" private", stdout);
+#ifndef KMK
+ if (v->fileinfo.filenm)
+ printf (_(" (from '%s', line %lu)"),
+ v->fileinfo.filenm, v->fileinfo.lineno + v->fileinfo.offset);
+#else /* KMK */
+ if (alias->fileinfo.filenm)
+ printf (_(" (from '%s', line %lu)"),
+ alias->fileinfo.filenm, alias->fileinfo.lineno);
+ if (alias->aliased)
+ fputs (" aliased", stdout);
+ if (alias->alias)
+ printf (_(", alias for '%s'"), v->name);
+#endif /* KMK */
+
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ if (v->evalval_count != 0)
+ {
+# ifdef CONFIG_WITH_MAKE_STATS
+ printf (_(", %u evalvals (%llu ticks)"), v->evalval_count, v->cTicksEvalVal);
+# else
+ printf (_(", %u evalvals"), v->evalval_count);
+# endif
+ var_stats_evalvaled++;
+ }
+ var_stats_evalvals += v->evalval_count;
+
+ if (v->expand_count != 0)
+ {
+ printf (_(", %u expands"), v->expand_count);
+ var_stats_expanded++;
+ }
+ var_stats_expands += v->expand_count;
+
+# ifdef CONFIG_WITH_COMPILER
+ if (v->evalprog != 0)
+ {
+ printf (_(", evalprog"));
+ var_stats_evalprogs++;
+ }
+ if (v->expandprog != 0)
+ {
+ printf (_(", expandprog"));
+ var_stats_expandprogs++;
+ }
+# endif
+#endif
+
+#ifdef CONFIG_WITH_MAKE_STATS
+ if (v->changes != 0)
+ {
+ printf (_(", %u changes"), v->changes);
+ var_stats_changed++;
+ }
+ var_stats_changes += v->changes;
+
+ if (v->reallocs != 0)
+ {
+ printf (_(", %u reallocs"), v->reallocs);
+ var_stats_realloced++;
+ }
+ var_stats_reallocs += v->reallocs;
+
+ if (v->references != 0)
+ {
+ printf (_(", %u references"), v->references);
+ var_stats_referenced++;
+ }
+ var_stats_references += v->references;
+
+ var_stats_val_len += v->value_length;
+ if (v->value_alloc_len)
+ var_stats_val_alloc_len += v->value_alloc_len;
+ else
+ var_stats_val_rdonly_len += v->value_length;
+ assert (v->value_length == strlen (v->value));
+ /*assert (v->rdonly_val ? !v->value_alloc_len : v->value_alloc_len > v->value_length); - FIXME */
+#endif /* CONFIG_WITH_MAKE_STATS */
+ putchar ('\n');
+ fputs (prefix, stdout);
+
+ /* Is this a 'define'? */
+ if (v->recursive && strchr (v->value, '\n') != 0)
+#ifndef KMK /** @todo language feature for aliases */
+ printf ("define %s\n%s\nendef\n", v->name, v->value);
+#else
+ printf ("define %s\n%s\nendef\n", alias->name, v->value);
+#endif
+ else
+ {
+ char *p;
+
+#ifndef KMK /** @todo language feature for aliases */
+ printf ("%s %s= ", v->name, v->recursive ? v->append ? "+" : "" : ":");
+#else
+ printf ("%s %s= ", alias->name, v->recursive ? v->append ? "+" : "" : ":");
+#endif
+
+ /* Check if the value is just whitespace. */
+ p = next_token (v->value);
+ if (p != v->value && *p == '\0')
+ /* All whitespace. */
+ printf ("$(subst ,,%s)", v->value);
+ else if (v->recursive)
+ fputs (v->value, stdout);
+ else
+ /* Double up dollar signs. */
+ for (p = v->value; *p != '\0'; ++p)
+ {
+ if (*p == '$')
+ putchar ('$');
+ putchar (*p);
+ }
+ putchar ('\n');
+ }
+}
+
+
+static void
+print_auto_variable (const void *item, void *arg)
+{
+ const struct variable *v = item;
+
+ if (v->origin == o_automatic)
+ print_variable (item, arg);
+}
+
+
+static void
+print_noauto_variable (const void *item, void *arg)
+{
+ const struct variable *v = item;
+
+ if (v->origin != o_automatic)
+ print_variable (item, arg);
+}
+
+
+/* Print all the variables in SET. PREFIX is printed before
+ the actual variable definitions (everything else is comments). */
+
+#ifndef KMK
+static
+#endif
+void
+print_variable_set (struct variable_set *set, const char *prefix, int pauto)
+{
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ var_stats_expands = var_stats_expanded = var_stats_evalvals
+ = var_stats_evalvaled = 0;
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ var_stats_expandprogs = var_stats_evalprogs = 0;
+#endif
+#ifdef CONFIG_WITH_MAKE_STATS
+ var_stats_changes = var_stats_changed = var_stats_reallocs
+ = var_stats_realloced = var_stats_references = var_stats_referenced
+ = var_stats_val_len = var_stats_val_alloc_len
+ = var_stats_val_rdonly_len = 0;
+#endif
+
+ hash_map_arg (&set->table, (pauto ? print_auto_variable : print_variable),
+ (void *)prefix);
+
+ if (set->table.ht_fill)
+ {
+#ifdef CONFIG_WITH_MAKE_STATS
+ unsigned long fragmentation;
+
+ fragmentation = var_stats_val_alloc_len - (var_stats_val_len - var_stats_val_rdonly_len);
+ printf(_("# variable set value stats:\n\
+# strings %7lu bytes, readonly %6lu bytes\n"),
+ var_stats_val_len, var_stats_val_rdonly_len);
+
+ if (var_stats_val_alloc_len)
+ printf(_("# allocated %7lu bytes, fragmentation %6lu bytes (%u%%)\n"),
+ var_stats_val_alloc_len, fragmentation,
+ (unsigned int)((100.0 * fragmentation) / var_stats_val_alloc_len));
+
+ if (var_stats_changed)
+ printf(_("# changed %5lu (%2u%%), changes %6lu\n"),
+ var_stats_changed,
+ (unsigned int)((100.0 * var_stats_changed) / set->table.ht_fill),
+ var_stats_changes);
+
+ if (var_stats_realloced)
+ printf(_("# reallocated %5lu (%2u%%), reallocations %6lu\n"),
+ var_stats_realloced,
+ (unsigned int)((100.0 * var_stats_realloced) / set->table.ht_fill),
+ var_stats_reallocs);
+
+ if (var_stats_referenced)
+ printf(_("# referenced %5lu (%2u%%), references %6lu\n"),
+ var_stats_referenced,
+ (unsigned int)((100.0 * var_stats_referenced) / set->table.ht_fill),
+ var_stats_references);
+#endif
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ if (var_stats_evalvals)
+ printf(_("# evalvaled %5lu (%2u%%), evalval calls %6lu\n"),
+ var_stats_evalvaled,
+ (unsigned int)((100.0 * var_stats_evalvaled) / set->table.ht_fill),
+ var_stats_evalvals);
+ if (var_stats_expands)
+ printf(_("# expanded %5lu (%2u%%), expands %6lu\n"),
+ var_stats_expanded,
+ (unsigned int)((100.0 * var_stats_expanded) / set->table.ht_fill),
+ var_stats_expands);
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ if (var_stats_expandprogs || var_stats_evalprogs)
+ printf(_("# eval progs %5lu (%2u%%), expand progs %6lu (%2u%%)\n"),
+ var_stats_evalprogs,
+ (unsigned int)((100.0 * var_stats_evalprogs) / set->table.ht_fill),
+ var_stats_expandprogs,
+ (unsigned int)((100.0 * var_stats_expandprogs) / set->table.ht_fill));
+#endif
+ }
+
+ fputs (_("# variable set hash-table stats:\n"), stdout);
+ fputs ("# ", stdout);
+ hash_print_stats (&set->table, stdout);
+ putc ('\n', stdout);
+}
+
+/* Print the data base of variables. */
+
+void
+print_variable_data_base (void)
+{
+ puts (_("\n# Variables\n"));
+
+ print_variable_set (&global_variable_set, "", 0);
+
+ puts (_("\n# Pattern-specific Variable Values"));
+
+ {
+ struct pattern_var *p;
+ unsigned int rules = 0;
+
+ for (p = pattern_vars; p != 0; p = p->next)
+ {
+ ++rules;
+ printf ("\n%s :\n", p->target);
+ print_variable (&p->variable, (void *)"# ");
+ }
+
+ if (rules == 0)
+ puts (_("\n# No pattern-specific variable values."));
+ else
+ printf (_("\n# %u pattern-specific variable values"), rules);
+ }
+
+#ifdef CONFIG_WITH_STRCACHE2
+ strcache2_print_stats (&variable_strcache, "# ");
+#endif
+}
+
+#ifdef CONFIG_WITH_PRINT_STATS_SWITCH
+void
+print_variable_stats (void)
+{
+ fputs (_("\n# Global variable hash-table stats:\n# "), stdout);
+ hash_print_stats (&global_variable_set.table, stdout);
+ fputs ("\n", stdout);
+}
+#endif
+
+/* Print all the local variables of FILE. */
+
+void
+print_file_variables (const struct file *file)
+{
+ if (file->variables != 0)
+ print_variable_set (file->variables->set, "# ", 1);
+}
+
+void
+print_target_variables (const struct file *file)
+{
+ if (file->variables != 0)
+ {
+ int l = strlen (file->name);
+ char *t = alloca (l + 3);
+
+ strcpy (t, file->name);
+ t[l] = ':';
+ t[l+1] = ' ';
+ t[l+2] = '\0';
+
+ hash_map_arg (&file->variables->set->table, print_noauto_variable, t);
+ }
+}
+
+#ifdef WINDOWS32
+void
+sync_Path_environment (void)
+{
+ char *path = allocated_variable_expand ("$(PATH)");
+ static char *environ_path = NULL;
+
+ if (!path)
+ return;
+
+ /* If done this before, free the previous entry before allocating new one. */
+ free (environ_path);
+
+ /* Create something WINDOWS32 world can grok. */
+ convert_Path_to_windows32 (path, ';');
+ environ_path = xstrdup (concat (3, "PATH", "=", path));
+ putenv (environ_path);
+ free (path);
+}
+#endif
diff --git a/src/kmk/variable.h b/src/kmk/variable.h
new file mode 100644
index 0000000..db13b92
--- /dev/null
+++ b/src/kmk/variable.h
@@ -0,0 +1,551 @@
+/* Definitions for using variables in GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "hash.h"
+#ifdef CONFIG_WITH_COMPILER
+# include "kmk_cc_exec.h"
+#endif
+
+/* Codes in a variable definition saying where the definition came from.
+ Increasing numeric values signify less-overridable definitions. */
+enum variable_origin
+ {
+ o_default, /* Variable from the default set. */
+ o_env, /* Variable from environment. */
+ o_file, /* Variable given in a makefile. */
+ o_env_override, /* Variable from environment, if -e. */
+ o_command, /* Variable given by user. */
+ o_override, /* Variable from an 'override' directive. */
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ o_local, /* Variable from an 'local' directive. */
+#endif
+ o_automatic, /* Automatic variable -- cannot be set. */
+ o_invalid /* Core dump time. */
+ };
+
+enum variable_flavor
+ {
+ f_bogus, /* Bogus (error) */
+ f_simple, /* Simple definition (:= or ::=) */
+ f_recursive, /* Recursive definition (=) */
+ f_append, /* Appending definition (+=) */
+#ifdef CONFIG_WITH_PREPEND_ASSIGNMENT
+ f_prepend, /* Prepending definition (>=) */
+#endif
+ f_conditional, /* Conditional definition (?=) */
+ f_shell /* Shell assignment (!=) */
+ };
+
+/* Structure that represents one variable definition.
+ Each bucket of the hash table is a chain of these,
+ chained through 'next'. */
+
+#define EXP_COUNT_BITS 15 /* This gets all the bitfields into 32 bits */
+#define EXP_COUNT_MAX ((1<<EXP_COUNT_BITS)-1)
+#ifdef CONFIG_WITH_VALUE_LENGTH
+# define VAR_ALIGN_VALUE_ALLOC(len) ( ((len) + (unsigned int)15) & ~(unsigned int)15 )
+#endif
+
+struct variable
+ {
+#ifndef CONFIG_WITH_STRCACHE2
+ char *name; /* Variable name. */
+#else
+ const char *name; /* Variable name (in varaible_strcache). */
+#endif
+ char *value; /* Variable value. */
+ floc fileinfo; /* Where the variable was defined. */
+ int length; /* strlen (name) */
+#ifdef CONFIG_WITH_VALUE_LENGTH
+ unsigned int value_length; /* The length of the value. */
+ unsigned int value_alloc_len; /* The amount of memory we've actually allocated. */
+ /* FIXME: make lengths unsigned! */
+#endif
+ unsigned int recursive:1; /* Gets recursively re-evaluated. */
+ unsigned int append:1; /* Nonzero if an appending target-specific
+ variable. */
+ unsigned int conditional:1; /* Nonzero if set with a ?=. */
+ unsigned int per_target:1; /* Nonzero if a target-specific variable. */
+ unsigned int special:1; /* Nonzero if this is a special variable. */
+ unsigned int exportable:1; /* Nonzero if the variable _could_ be
+ exported. */
+ unsigned int expanding:1; /* Nonzero if currently being expanded. */
+ unsigned int private_var:1; /* Nonzero avoids inheritance of this
+ target-specific variable. */
+ unsigned int exp_count:EXP_COUNT_BITS;
+ /* If >1, allow this many self-referential
+ expansions. */
+#ifdef CONFIG_WITH_RDONLY_VARIABLE_VALUE
+ unsigned int rdonly_val:1; /* VALUE is read only (strcache/const). */
+#endif
+#ifdef KMK
+ unsigned int alias:1; /* Nonzero if alias. VALUE points to the real variable. */
+ unsigned int aliased:1; /* Nonzero if aliased. Cannot be undefined. */
+#endif
+ enum variable_flavor
+ flavor ENUM_BITFIELD (3); /* Variable flavor. */
+ enum variable_origin
+#ifdef CONFIG_WITH_LOCAL_VARIABLES
+ origin ENUM_BITFIELD (4); /* Variable origin. */
+#else
+ origin ENUM_BITFIELD (3); /* Variable origin. */
+#endif
+ enum variable_export
+ {
+ v_export, /* Export this variable. */
+ v_noexport, /* Don't export this variable. */
+ v_ifset, /* Export it if it has a non-default value. */
+ v_default /* Decide in target_environment. */
+ } export ENUM_BITFIELD (2);
+#ifdef CONFIG_WITH_COMPILER
+ int recursive_without_dollar : 2; /* 0 if undetermined, 1 if value has no '$' chars, -1 if it has. */
+#endif
+#ifdef CONFIG_WITH_MAKE_STATS
+ unsigned int changes; /* Variable modification count. */
+ unsigned int reallocs; /* Realloc on value count. */
+ unsigned int references; /* Lookup count. */
+ unsigned long long cTicksEvalVal; /* Number of ticks spend in cEvalVal. */
+#endif
+#if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS)
+ unsigned int evalval_count; /* Times used with $(evalval ) or $(evalctx ) since last change. */
+ unsigned int expand_count; /* Times expanded since last change (not to be confused with exp_count). */
+#endif
+#ifdef CONFIG_WITH_COMPILER
+ struct kmk_cc_evalprog *evalprog; /* Pointer to evalval/evalctx "program". */
+ struct kmk_cc_expandprog *expandprog; /* Pointer to variable expand "program". */
+#endif
+ };
+
+/* Update statistics and invalidates optimizations when a variable changes. */
+#ifdef CONFIG_WITH_COMPILER
+# define VARIABLE_CHANGED(v) \
+ do { \
+ MAKE_STATS_2((v)->changes++); \
+ if ((v)->evalprog || (v)->expandprog) kmk_cc_variable_changed(v); \
+ (v)->expand_count = 0; \
+ (v)->evalval_count = 0; \
+ (v)->recursive_without_dollar = 0; \
+ } while (0)
+#else
+# define VARIABLE_CHANGED(v) MAKE_STATS_2((v)->changes++)
+#endif
+
+/* Macro that avoids a lot of CONFIG_WITH_COMPILER checks when
+ accessing recursive_without_dollar. */
+#ifdef CONFIG_WITH_COMPILER
+# define IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(v) ((v)->recursive_without_dollar > 0)
+#else
+# define IS_VARIABLE_RECURSIVE_WITHOUT_DOLLAR(v) 0
+#endif
+
+
+
+/* Structure that represents a variable set. */
+
+struct variable_set
+ {
+ struct hash_table table; /* Hash table of variables. */
+ };
+
+/* Structure that represents a list of variable sets. */
+
+struct variable_set_list
+ {
+ struct variable_set_list *next; /* Link in the chain. */
+ struct variable_set *set; /* Variable set. */
+ int next_is_parent; /* True if next is a parent target. */
+ };
+
+/* Structure used for pattern-specific variables. */
+
+struct pattern_var
+ {
+ struct pattern_var *next;
+ const char *suffix;
+ const char *target;
+ unsigned int len;
+ struct variable variable;
+ };
+
+extern char *variable_buffer;
+extern struct variable_set_list *current_variable_set_list;
+extern struct variable *default_goal_var;
+extern struct variable shell_var;
+
+#ifdef KMK
+extern struct variable_set global_variable_set;
+extern struct variable_set_list global_setlist;
+extern unsigned int variable_buffer_length;
+# define VARIABLE_BUFFER_ZONE 5
+#endif
+
+/* expand.c */
+#ifndef KMK
+char *
+variable_buffer_output (char *ptr, const char *string, unsigned int length);
+#else /* KMK */
+# include <k/kDefs.h>
+/* Subroutine of variable_expand and friends:
+ The text to add is LENGTH chars starting at STRING to the variable_buffer.
+ The text is added to the buffer at PTR, and the updated pointer into
+ the buffer is returned as the value. Thus, the value returned by
+ each call to variable_buffer_output should be the first argument to
+ the following call. */
+
+K_INLINE char *variable_buffer_output (char *ptr, const char *string, unsigned int length)
+{
+ register unsigned int newlen = length + (ptr - variable_buffer);
+
+ if ((newlen + VARIABLE_BUFFER_ZONE) > variable_buffer_length)
+ {
+ unsigned int offset = ptr - variable_buffer;
+ variable_buffer_length = variable_buffer_length <= 1024
+ ? 2048 : variable_buffer_length * 4;
+ if (variable_buffer_length < newlen + 100)
+ variable_buffer_length = (newlen + 100 + 1023) & ~1023U;
+ variable_buffer = xrealloc (variable_buffer, variable_buffer_length);
+ ptr = variable_buffer + offset;
+ }
+
+# ifndef _MSC_VER
+ switch (length)
+ {
+ case 4: ptr[3] = string[3]; /* fall thru */
+ case 3: ptr[2] = string[2]; /* fall thru */
+ case 2: ptr[1] = string[1]; /* fall thru */
+ case 1: ptr[0] = string[0]; /* fall thru */
+ case 0:
+ break;
+ default:
+ memcpy (ptr, string, length);
+ break;
+ }
+# else
+ memcpy (ptr, string, length);
+# endif
+ return ptr + length;
+}
+#endif /* KMK */
+
+char *variable_expand (const char *line);
+char *variable_expand_for_file (const char *line, struct file *file);
+#if defined (CONFIG_WITH_VALUE_LENGTH) || defined (CONFIG_WITH_COMMANDS_FUNC)
+char *variable_expand_for_file_2 (char *o, const char *line, unsigned int lenght,
+ struct file *file, unsigned int *value_lenp);
+#endif
+char *allocated_variable_expand_for_file (const char *line, struct file *file);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+#define allocated_variable_expand(line) \
+ allocated_variable_expand_for_file (line, (struct file *) 0)
+#else /* CONFIG_WITH_VALUE_LENGTH */
+# define allocated_variable_expand(line) \
+ allocated_variable_expand_2 (line, -1, NULL)
+char *allocated_variable_expand_2 (const char *line, unsigned int length, unsigned int *value_lenp);
+char *allocated_variable_expand_3 (const char *line, unsigned int length,
+ unsigned int *value_lenp, unsigned int *buffer_lengthp);
+void recycle_variable_buffer (char *buffer, unsigned int length);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+char *expand_argument (const char *str, const char *end);
+#ifndef CONFIG_WITH_VALUE_LENGTH
+char *
+variable_expand_string (char *line, const char *string, long length);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+# include <k/kDefs.h>
+char *
+variable_expand_string_2 (char *line, const char *string, long length, char **eol);
+K_INLINE char *variable_expand_string (char *line, const char *string, long length)
+{
+ char *ignored;
+ return variable_expand_string_2 (line, string, length, &ignored);
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+void install_variable_buffer (char **bufp, unsigned int *lenp);
+void restore_variable_buffer (char *buf, unsigned int len);
+char *install_variable_buffer_with_hint (char **bufp, unsigned int *lenp, unsigned int size_hint); /* bird */
+char *ensure_variable_buffer_space (char *ptr, unsigned int size); /* bird */
+#ifdef CONFIG_WITH_VALUE_LENGTH
+void append_expanded_string_to_variable (struct variable *v, const char *value,
+ unsigned int value_len, int append);
+#endif
+
+/* function.c */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+int handle_function (char **op, const char **stringp);
+#else
+int handle_function (char **op, const char **stringp, const char *nameend, const char *eol);
+#endif
+#ifdef CONFIG_WITH_COMPILER
+typedef char *(*make_function_ptr_t) (char *, char **, const char *);
+make_function_ptr_t lookup_function_for_compiler (const char *name, unsigned int len,
+ unsigned char *minargsp, unsigned char *maxargsp,
+ char *expargsp, const char **funcnamep);
+#endif
+int pattern_matches (const char *pattern, const char *percent, const char *str);
+char *subst_expand (char *o, const char *text, const char *subst,
+ const char *replace, unsigned int slen, unsigned int rlen,
+ int by_word);
+char *patsubst_expand_pat (char *o, const char *text, const char *pattern,
+ const char *replace, const char *pattern_percent,
+ const char *replace_percent);
+char *patsubst_expand (char *o, const char *text, char *pattern, char *replace);
+char *func_shell_base (char *o, char **argv, int trim_newlines);
+void shell_completed (int exit_code, int exit_sig);
+
+#ifdef CONFIG_WITH_COMMANDS_FUNC /* for append.c */
+char *func_commands (char *o, char **argv, const char *funcname);
+#endif
+
+#if defined (CONFIG_WITH_VALUE_LENGTH)
+/* Avoid calling handle_function for every variable, do the
+ basic checks in variable_expand_string_2. */
+extern char func_char_map[256];
+# define MAX_FUNCTION_LENGTH 14
+# define MIN_FUNCTION_LENGTH 2
+K_INLINE const char *may_be_function_name (const char *name, const char *eos)
+{
+ unsigned char ch;
+ unsigned int len = name - eos;
+
+ /* Minimum length is MIN + whitespace. Check this directly.
+ ASSUMES: MIN_FUNCTION_LENGTH == 2 */
+
+ if (MY_PREDICT_TRUE(len < MIN_FUNCTION_LENGTH + 1
+ || !func_char_map[(int)(name[0])]
+ || !func_char_map[(int)(name[1])]))
+ return 0;
+ if (MY_PREDICT_TRUE(!func_char_map[ch = name[2]]))
+ return ISSPACE (ch) ? name + 2 : 0;
+
+ name += 3;
+ if (len > MAX_FUNCTION_LENGTH)
+ len = MAX_FUNCTION_LENGTH - 3;
+ else if (len == 3)
+ len -= 3;
+ if (!len)
+ return 0;
+
+ /* Loop over the remaining possiblities. */
+
+ while (func_char_map[ch = *name])
+ {
+ if (!len--)
+ return 0;
+ name++;
+ }
+ if (ch == '\0' || ISBLANK (ch))
+ return name;
+ return 0;
+}
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+
+/* expand.c */
+#ifndef CONFIG_WITH_VALUE_LENGTH
+char *recursively_expand_for_file (struct variable *v, struct file *file);
+#define recursively_expand(v) recursively_expand_for_file (v, NULL)
+#else
+char *recursively_expand_for_file (struct variable *v, struct file *file,
+ unsigned int *value_lenp);
+# define recursively_expand(v) recursively_expand_for_file (v, NULL, NULL)
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+#ifdef CONFIG_WITH_COMPILER
+char *reference_recursive_variable (char *o, struct variable *v);
+#endif
+
+/* variable.c */
+struct variable_set_list *create_new_variable_set (void);
+void free_variable_set (struct variable_set_list *);
+struct variable_set_list *push_new_variable_scope (void);
+void pop_variable_scope (void);
+void define_automatic_variables (void);
+void initialize_file_variables (struct file *file, int reading);
+void print_file_variables (const struct file *file);
+void print_target_variables (const struct file *file);
+void merge_variable_set_lists (struct variable_set_list **to_list,
+ struct variable_set_list *from_list);
+#ifdef KMK
+void print_variable_set (struct variable_set *set, const char *prefix, int pauto);
+#endif
+
+#ifndef CONFIG_WITH_VALUE_LENGTH
+struct variable *do_variable_definition (const floc *flocp,
+ const char *name, const char *value,
+ enum variable_origin origin,
+ enum variable_flavor flavor,
+ int target_var);
+#else /* CONFIG_WITH_VALUE_LENGTH */
+# define do_variable_definition(flocp, varname, value, origin, flavor, target_var) \
+ do_variable_definition_2 ((flocp), (varname), (value), ~0U, 0, NULL, \
+ (origin), (flavor), (target_var))
+struct variable *do_variable_definition_2 (const floc *flocp,
+ const char *varname,
+ const char *value,
+ unsigned int value_len,
+ int simple_value, char *free_value,
+ enum variable_origin origin,
+ enum variable_flavor flavor,
+ int target_var);
+#endif /* CONFIG_WITH_VALUE_LENGTH */
+char *parse_variable_definition (const char *line,
+ struct variable *v);
+struct variable *assign_variable_definition (struct variable *v, const char *line IF_WITH_VALUE_LENGTH_PARAM(char *eos));
+struct variable *try_variable_definition (const floc *flocp, const char *line
+ IF_WITH_VALUE_LENGTH_PARAM(char *eos),
+ enum variable_origin origin,
+ int target_var);
+void init_hash_global_variable_set (void);
+void hash_init_function_table (void);
+void define_new_function(const floc *flocp, const char *name,
+ unsigned int min, unsigned int max, unsigned int flags,
+ gmk_func_ptr func);
+struct variable *lookup_variable (const char *name, unsigned int length);
+struct variable *lookup_variable_in_set (const char *name, unsigned int length,
+ const struct variable_set *set);
+#ifdef CONFIG_WITH_STRCACHE2
+struct variable *lookup_variable_strcached (const char *name);
+#endif
+
+#ifdef CONFIG_WITH_VALUE_LENGTH
+void append_string_to_variable (struct variable *v, const char *value,
+ unsigned int value_len, int append);
+struct variable * do_variable_definition_append (const floc *flocp, struct variable *v,
+ const char *value, unsigned int value_len,
+ int simple_value, enum variable_origin origin,
+ int append);
+
+struct variable *define_variable_in_set (const char *name, unsigned int length,
+ const char *value,
+ unsigned int value_length,
+ int duplicate_value,
+ enum variable_origin origin,
+ int recursive,
+ struct variable_set *set,
+ const floc *flocp);
+
+/* Define a variable in the current variable set. */
+
+#define define_variable(n,l,v,o,r) \
+ define_variable_in_set((n),(l),(v),~0U,1,(o),(r),\
+ current_variable_set_list->set,NILF)
+
+#define define_variable_vl(n,l,v,vl,dv,o,r) \
+ define_variable_in_set((n),(l),(v),(vl),(dv),(o),(r),\
+ current_variable_set_list->set,NILF)
+
+/* Define a variable with a constant name in the current variable set. */
+
+#define define_variable_cname(n,v,o,r) \
+ define_variable_in_set((n),(sizeof (n) - 1),(v),~0U,1,(o),(r),\
+ current_variable_set_list->set,NILF)
+
+/* Define a variable with a location in the current variable set. */
+
+#define define_variable_loc(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),~0U,1,(o),(r),\
+ current_variable_set_list->set,(f))
+
+/* Define a variable with a location in the global variable set. */
+
+#define define_variable_global(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),~0U,1,(o),(r),NULL,(f))
+
+#define define_variable_vl_global(n,l,v,vl,dv,o,r,f) \
+ define_variable_in_set((n),(l),(v),(vl),(dv),(o),(r),NULL,(f))
+
+/* Define a variable in FILE's variable set. */
+
+#define define_variable_for_file(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),~0U,1,(o),(r),(f)->variables->set,NILF)
+
+#else /* !CONFIG_WITH_VALUE_LENGTH */
+
+struct variable *define_variable_in_set (const char *name, unsigned int length,
+ const char *value,
+ enum variable_origin origin,
+ int recursive,
+ struct variable_set *set,
+ const floc *flocp);
+
+/* Define a variable in the current variable set. */
+
+#define define_variable(n,l,v,o,r) \
+ define_variable_in_set((n),(l),(v),(o),(r),\
+ current_variable_set_list->set,NILF) /* force merge conflict */
+
+/* Define a variable with a constant name in the current variable set. */
+
+#define define_variable_cname(n,v,o,r) \
+ define_variable_in_set((n),(sizeof (n) - 1),(v),(o),(r),\
+ current_variable_set_list->set,NILF) /* force merge conflict */
+
+/* Define a variable with a location in the current variable set. */
+
+#define define_variable_loc(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),(o),(r),\
+ current_variable_set_list->set,(f)) /* force merge conflict */
+
+/* Define a variable with a location in the global variable set. */
+
+#define define_variable_global(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),(o),(r),NULL,(f)) /* force merge conflict */
+
+/* Define a variable in FILE's variable set. */
+
+#define define_variable_for_file(n,l,v,o,r,f) \
+ define_variable_in_set((n),(l),(v),(o),(r),(f)->variables->set,NILF) /* force merge conflict */
+
+#endif /* !CONFIG_WITH_VALUE_LENGTH */
+
+void undefine_variable_in_set (const char *name, unsigned int length,
+ enum variable_origin origin,
+ struct variable_set *set);
+
+/* Remove variable from the current variable set. */
+
+#define undefine_variable_global(n,l,o) \
+ undefine_variable_in_set((n),(l),(o),NULL)
+
+#ifdef KMK
+struct variable *
+define_variable_alias_in_set (const char *name, unsigned int length,
+ struct variable *target, enum variable_origin origin,
+ struct variable_set *set, const floc *flocp);
+#endif
+
+/* Warn that NAME is an undefined variable. */
+
+#define warn_undefined(n,l) do{\
+ if (warn_undefined_variables_flag) \
+ error (reading_file, (l), \
+ _("warning: undefined variable '%.*s'"), \
+ (int)(l), (n)); \
+ }while(0)
+
+char **target_environment (struct file *file);
+
+struct pattern_var *create_pattern_var (const char *target,
+ const char *suffix);
+
+extern int export_all_variables;
+#ifdef CONFIG_WITH_STRCACHE2
+extern struct strcache2 variable_strcache;
+#endif
+
+#ifdef KMK
+# define MAKELEVEL_NAME "KMK_LEVEL"
+#else
+#define MAKELEVEL_NAME "MAKELEVEL"
+#endif
+#define MAKELEVEL_LENGTH (CSTRLEN (MAKELEVEL_NAME))
diff --git a/src/kmk/version.c b/src/kmk/version.c
new file mode 100644
index 0000000..e6d822d
--- /dev/null
+++ b/src/kmk/version.c
@@ -0,0 +1,33 @@
+/* Record version and build host architecture for GNU make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* We use <config.h> instead of "config.h" so that a compilation
+ using -I. -I$srcdir will use ./config.h rather than $srcdir/config.h
+ (which it would do because makeint.h was found in $srcdir). */
+#include <config.h>
+
+#ifndef MAKE_HOST
+# define MAKE_HOST "unknown"
+#endif
+
+const char *version_string = VERSION;
+const char *make_host = MAKE_HOST;
+
+/*
+ Local variables:
+ version-control: never
+ End:
+ */
diff --git a/src/kmk/vms_exit.c b/src/kmk/vms_exit.c
new file mode 100644
index 0000000..b08d84d
--- /dev/null
+++ b/src/kmk/vms_exit.c
@@ -0,0 +1,95 @@
+/* vms_exit.c
+ *
+ * Wrapper for the VMS exit() command to tranlate UNIX codes to be
+ * encoded for POSIX, but also have VMS severity levels.
+ * The posix_exit() variant only sets a severity level for status code 1.
+ *
+ * Author: John E. Malmberg
+ */
+
+/* Copyright (C) 2014-2016 Free Software Foundation, Inc.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Per copyright assignment agreement with the Free Software Foundation
+ this software may be available under under other license agreements
+ and copyrights. */
+
+#include <makeint.h>
+
+#include <stsdef.h>
+void
+decc$exit (int status);
+#ifndef C_FACILITY_NO
+# define C_FACILITY_NO 0x350000
+#endif
+
+/* Lowest legal non-success VMS exit code is 8 */
+/* GNU make only defines codes 0, 1, 2 */
+/* So assume any exit code > 8 is a VMS exit code */
+
+#ifndef MAX_EXPECTED_EXIT_CODE
+# define MAX_EXPECTED_EXIT_CODE 7
+#endif
+
+/* Build a Posix Exit with VMS severity */
+void
+vms_exit (int status)
+{
+ int vms_status;
+ /* Fake the __posix_exit with severity added */
+ /* Undocumented correct way to do this. */
+ vms_status = 0;
+
+ /* The default DECC definition is not compatible with doing a POSIX_EXIT */
+ /* So fix it. */
+ if (status == EXIT_FAILURE)
+ status = MAKE_FAILURE;
+
+ /* Trivial case exit success */
+ if (status == 0)
+ decc$exit (STS$K_SUCCESS);
+
+ /* Is this a VMS status then just take it */
+ if (status > MAX_EXPECTED_EXIT_CODE)
+ {
+ /* Make sure that the message inhibit is set since message has */
+ /* already been displayed. */
+ vms_status = status | STS$M_INHIB_MSG;
+ decc$exit (vms_status);
+ }
+
+ /* Unix status codes are limited to 1 byte, so anything larger */
+ /* is a probably a VMS exit code and needs to be passed through */
+ /* A lower value can be set for a macro. */
+ /* Status 0 is always passed through as it is converted to SS$_NORMAL */
+ /* Always set the message inhibit bit */
+ vms_status = C_FACILITY_NO | 0xA000 | STS$M_INHIB_MSG;
+ vms_status |= (status << 3);
+
+ /* STS$K_ERROR is for status that stops makefile that a simple */
+ /* Rerun of the makefile will not fix. */
+
+ if (status == MAKE_FAILURE)
+ vms_status |= STS$K_ERROR;
+ else if (status == MAKE_TROUBLE)
+ {
+ /* Make trouble is for when make was told to do nothing and */
+ /* found that a target was not up to date. Since a second */
+ /* of make will produce the same condition, this is of */
+ /* Error severity */
+ vms_status |= STS$K_ERROR;
+ }
+ decc$exit (vms_status);
+}
diff --git a/src/kmk/vms_export_symbol.c b/src/kmk/vms_export_symbol.c
new file mode 100644
index 0000000..954f3f4
--- /dev/null
+++ b/src/kmk/vms_export_symbol.c
@@ -0,0 +1,527 @@
+/* File: vms_export_symbol.c
+ *
+ * Some programs need special environment variables deported as DCL
+ * DCL symbols.
+ */
+
+/* Copyright (C) 2014-2016 Free Software Foundation, Inc.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Per copyright assignment agreement with the Free Software Foundation
+ this software may be available under under other license agreements
+ and copyrights. */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <descrip.h>
+#include <stsdef.h>
+#include <ssdef.h>
+#include <unixlib.h>
+#include <libclidef.h>
+
+#pragma member_alignment save
+#pragma nomember_alignment longword
+struct item_list_3
+{
+ unsigned short len;
+ unsigned short code;
+ void * bufadr;
+ unsigned short * retlen;
+};
+
+
+#pragma member_alignment
+
+int
+LIB$GET_SYMBOL (const struct dsc$descriptor_s * symbol,
+ struct dsc$descriptor_s * value,
+ unsigned short * value_len,
+ const unsigned long * table);
+
+int
+LIB$SET_SYMBOL (const struct dsc$descriptor_s * symbol,
+ const struct dsc$descriptor_s * value,
+ const unsigned long * table);
+
+int
+LIB$DELETE_SYMBOL (const struct dsc$descriptor_s * symbol,
+ const unsigned long * table);
+
+#define MAX_DCL_SYMBOL_LEN (255)
+#if __CRTL_VER >= 70302000 && !defined(__VAX)
+# define MAX_DCL_SYMBOL_VALUE (8192)
+#else
+# define MAX_DCL_SYMBOL_VALUE (1024)
+#endif
+
+struct dcl_symbol
+{
+ struct dcl_symbol * link;
+ struct dsc$descriptor_s name_desc;
+ struct dsc$descriptor_s value_desc;
+ char name[MAX_DCL_SYMBOL_LEN + 1]; /* + 1 byte for null terminator */
+ char value[MAX_DCL_SYMBOL_VALUE +1]; /* + 1 byte for null terminator */
+ char pad[3]; /* Pad structure to longword allignment */
+};
+
+static struct dcl_symbol * vms_dcl_symbol_head = NULL;
+
+/* Restore symbol state to original condition. */
+static unsigned long
+clear_dcl_symbol (struct dcl_symbol * symbol)
+{
+
+ const unsigned long symtbl = LIB$K_CLI_LOCAL_SYM;
+ int status;
+
+ if (symbol->value_desc.dsc$w_length == (unsigned short)-1)
+ status = LIB$DELETE_SYMBOL (&symbol->name_desc, &symtbl);
+ else
+ status = LIB$SET_SYMBOL (&symbol->name_desc,
+ &symbol->value_desc, &symtbl);
+ return status;
+}
+
+
+/* Restore all exported symbols to their original conditions */
+static void
+clear_exported_symbols (void)
+{
+
+ struct dcl_symbol * symbol;
+
+ symbol = vms_dcl_symbol_head;
+
+ /* Walk the list of symbols. This is done durring exit,
+ * so no need to free memory.
+ */
+ while (symbol != NULL)
+ {
+ clear_dcl_symbol (symbol);
+ symbol = symbol->link;
+ }
+
+}
+
+
+/* Restore the symbol back to the original value
+ * symbol name is either a plain name or of the form "symbol=name" where
+ * the name portion is ignored.
+ */
+void
+vms_restore_symbol (const char * string)
+{
+
+ struct dcl_symbol * symbol;
+ char name[MAX_DCL_SYMBOL_LEN + 1];
+ int status;
+ char * value;
+ int name_len;
+
+ symbol = vms_dcl_symbol_head;
+
+ /* Isolate the name from the value */
+ value = strchr (string, '=');
+ if (value != NULL)
+ {
+ /* Copy the name from the string */
+ name_len = (value - string);
+ }
+ else
+ name_len = strlen (string);
+
+ if (name_len > MAX_DCL_SYMBOL_LEN)
+ name_len = MAX_DCL_SYMBOL_LEN;
+
+ strncpy (name, string, name_len);
+ name[name_len] = 0;
+
+ /* Walk the list of symbols. The saved symbol is not freed
+ * symbols are likely to be overwritten multiple times, so this
+ * saves time in saving them each time.
+ */
+ while (symbol != NULL)
+ {
+ int result;
+ result = strcmp (symbol->name, name);
+ if (result == 0)
+ {
+ clear_dcl_symbol (symbol);
+ break;
+ }
+ symbol = symbol->link;
+ }
+}
+
+int
+vms_export_dcl_symbol (const char * name, const char * value)
+{
+
+ struct dcl_symbol * symbol;
+ struct dcl_symbol * next;
+ struct dcl_symbol * link;
+ int found;
+ const unsigned long symtbl = LIB$K_CLI_LOCAL_SYM;
+ struct dsc$descriptor_s value_desc;
+ int string_len;
+ int status;
+ char new_value[MAX_DCL_SYMBOL_VALUE + 1];
+ char * dollarp;
+
+ next = vms_dcl_symbol_head;
+ link = vms_dcl_symbol_head;
+
+ /* Is symbol already exported? */
+ found = 0;
+ while ((found == 0) && (link != NULL))
+ {
+ int x;
+ found = !strncasecmp (link->name, name, MAX_DCL_SYMBOL_LEN);
+ if (found)
+ symbol = link;
+ next = link;
+ link = link->link;
+ }
+
+ /* New symbol, set it up */
+ if (found == 0)
+ {
+ symbol = malloc (sizeof (struct dcl_symbol));
+ if (symbol == NULL)
+ return SS$_INSFMEM;
+
+ /* Construct the symbol descriptor, used for both saving
+ * the old symbol and creating the new symbol.
+ */
+ symbol->name_desc.dsc$w_length = strlen (name);
+ if (symbol->name_desc.dsc$w_length > MAX_DCL_SYMBOL_LEN)
+ symbol->name_desc.dsc$w_length = MAX_DCL_SYMBOL_LEN;
+
+ strncpy (symbol->name, name, symbol->name_desc.dsc$w_length);
+ symbol->name[symbol->name_desc.dsc$w_length] = 0;
+ symbol->name_desc.dsc$a_pointer = symbol->name;
+ symbol->name_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ symbol->name_desc.dsc$b_class = DSC$K_CLASS_S;
+
+ /* construct the value descriptor, used only for saving
+ * the old symbol.
+ */
+ symbol->value_desc.dsc$a_pointer = symbol->value;
+ symbol->value_desc.dsc$w_length = MAX_DCL_SYMBOL_VALUE;
+ symbol->value_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ symbol->value_desc.dsc$b_class = DSC$K_CLASS_S;
+ }
+
+ if (found == 0)
+ {
+ unsigned long old_symtbl;
+ unsigned short value_len;
+
+ /* Look up the symbol */
+ status = LIB$GET_SYMBOL (&symbol->name_desc, &symbol->value_desc,
+ &value_len, &old_symtbl);
+ if (!$VMS_STATUS_SUCCESS (status))
+ value_len = (unsigned short)-1;
+ else if (old_symtbl != symtbl)
+ value_len = (unsigned short)-1;
+
+ symbol->value_desc.dsc$w_length = value_len;
+
+ /* Store it away */
+ if (value_len != (unsigned short) -1)
+ symbol->value[value_len] = 0;
+
+ /* Make sure atexit scheduled */
+ if (vms_dcl_symbol_head == NULL)
+ {
+ vms_dcl_symbol_head = symbol;
+ atexit (clear_exported_symbols);
+ }
+ else
+ {
+ /* Extend the chain */
+ next->link = symbol;
+ }
+ }
+
+ /* Create or replace a symbol */
+ value_desc.dsc$a_pointer = new_value;
+ string_len = strlen (value);
+ if (string_len > MAX_DCL_SYMBOL_VALUE)
+ string_len = MAX_DCL_SYMBOL_VALUE;
+
+ strncpy (new_value, value, string_len);
+ new_value[string_len] = 0;
+
+ /* Special handling for GNU Make. GNU Make doubles the dollar signs
+ * in environment variables read in from getenv(). Make exports symbols
+ * with the dollar signs already doubled. So all $$ must be converted
+ * back to $.
+ * If the first $ is not doubled, then do not convert at all.
+ */
+ dollarp = strchr (new_value, '$');
+ while (dollarp && dollarp[1] == '$')
+ {
+ int left;
+ dollarp++;
+ left = string_len - (dollarp - new_value - 1);
+ string_len--;
+ if (left > 0)
+ {
+ memmove (dollarp, &dollarp[1], left);
+ dollarp = strchr (&dollarp[1], '$');
+ }
+ else
+ {
+ /* Ended with $$, simple case */
+ dollarp[1] = 0;
+ break;
+ }
+ }
+ value_desc.dsc$w_length = string_len;
+ value_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ value_desc.dsc$b_class = DSC$K_CLASS_S;
+ status = LIB$SET_SYMBOL (&symbol->name_desc, &value_desc, &symtbl);
+ return status;
+}
+
+/* export a DCL symbol using a string in the same syntax as putenv */
+int
+vms_putenv_symbol (const char * string)
+{
+
+ char name[MAX_DCL_SYMBOL_LEN + 1];
+ int status;
+ char * value;
+ int name_len;
+
+ /* Isolate the name from the value */
+ value = strchr (string, '=');
+ if (value == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Copy the name from the string */
+ name_len = (value - string);
+ if (name_len > MAX_DCL_SYMBOL_LEN)
+ name_len = MAX_DCL_SYMBOL_LEN;
+
+ strncpy (name, string, name_len);
+ name[name_len] = 0;
+
+ /* Skip past the "=" */
+ value++;
+
+ /* Export the symbol */
+ status = vms_export_dcl_symbol (name, value);
+
+ /* Convert the error to Unix format */
+ if (!$VMS_STATUS_SUCCESS (status))
+ {
+ errno = EVMSERR;
+ vaxc$errno = status;
+ return -1;
+ }
+ return 0;
+}
+
+#if __CRTL_VER >= 70301000
+# define transpath_parm transpath
+#else
+static char transpath[MAX_DCL_SYMBOL_VALUE];
+#endif
+
+/* Helper callback routine for converting Unix paths to VMS */
+static int
+to_vms_action (char * vms_spec, int flag, char * transpath_parm)
+{
+ strncpy (transpath, vms_spec, MAX_DCL_SYMBOL_VALUE - 1);
+ transpath[MAX_DCL_SYMBOL_VALUE - 1] = 0;
+ return 0;
+}
+
+#ifdef __DECC
+# pragma message save
+ /* Undocumented extra parameter use triggers a ptrmismatch warning */
+# pragma message disable ptrmismatch
+#endif
+
+/* Create a foreign command only visible to children */
+int
+create_foreign_command (const char * command, const char * image)
+{
+ char vms_command[MAX_DCL_SYMBOL_VALUE + 1];
+ int status;
+
+ vms_command[0] = '$';
+ vms_command[1] = 0;
+ if (image[0] == '/')
+ {
+#if __CRTL_VER >= 70301000
+ /* Current decc$to_vms is reentrant */
+ decc$to_vms (image, to_vms_action, 0, 1, &vms_command[1]);
+#else
+ /* Older decc$to_vms is not reentrant */
+ decc$to_vms (image, to_vms_action, 0, 1);
+ strncpy (&vms_command[1], transpath, MAX_DCL_SYMBOL_VALUE - 1);
+ vms_command[MAX_DCL_SYMBOL_VALUE] = 0;
+#endif
+ }
+ else
+ {
+ strncpy (&vms_command[1], image, MAX_DCL_SYMBOL_VALUE - 1);
+ vms_command[MAX_DCL_SYMBOL_VALUE] = 0;
+ }
+ status = vms_export_dcl_symbol (command, vms_command);
+
+ return status;
+}
+#ifdef __DECC
+# pragma message restore
+#endif
+
+
+#ifdef DEBUG
+
+int
+main(int argc, char ** argv, char **env)
+{
+
+ char value[MAX_DCL_SYMBOL_VALUE +1];
+ int status = 0;
+ int putenv_status;
+ int vms_status;
+ struct dsc$descriptor_s name_desc;
+ struct dsc$descriptor_s value_desc;
+ const unsigned long symtbl = LIB$K_CLI_LOCAL_SYM;
+ unsigned short value_len;
+ unsigned long old_symtbl;
+ int result;
+ const char * vms_command = "vms_export_symbol";
+ const char * vms_image = "test_image.exe";
+ const char * vms_symbol1 = "test_symbol1";
+ const char * value1 = "test_value1";
+ const char * vms_symbol2 = "test_symbol2";
+ const char * putenv_string = "test_symbol2=value2";
+ const char * value2 = "value2";
+
+ /* Test creating a foreign command */
+ vms_status = create_foreign_command (vms_command, vms_image);
+ if (!$VMS_STATUS_SUCCESS (vms_status))
+ {
+ printf("Create foreign command failed: %d\n", vms_status);
+ status = 1;
+ }
+
+ name_desc.dsc$a_pointer = (char *)vms_command;
+ name_desc.dsc$w_length = strlen (vms_command);
+ name_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ name_desc.dsc$b_class = DSC$K_CLASS_S;
+
+ value_desc.dsc$a_pointer = value;
+ value_desc.dsc$w_length = MAX_DCL_SYMBOL_VALUE;
+ value_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ value_desc.dsc$b_class = DSC$K_CLASS_S;
+
+ vms_status = LIB$GET_SYMBOL (&name_desc, &value_desc,
+ &value_len, &old_symtbl);
+ if (!$VMS_STATUS_SUCCESS (vms_status))
+ {
+ printf ("lib$get_symbol for command failed: %d\n", vms_status);
+ status = 1;
+ }
+
+ value[value_len] = 0;
+ result = strncasecmp (&value[1], vms_image, value_len - 1);
+ if (result != 0)
+ {
+ printf ("create_foreign_command failed! expected '%s', got '%s'\n",
+ vms_image, &value[1]);
+ status = 1;
+ }
+
+ /* Test exporting a symbol */
+ vms_status = vms_export_dcl_symbol (vms_symbol1, value1);
+ if (!$VMS_STATUS_SUCCESS (vms_status))
+ {
+ printf ("vms_export_dcl_symbol for command failed: %d\n", vms_status);
+ status = 1;
+ }
+
+ name_desc.dsc$a_pointer = (char *)vms_symbol1;
+ name_desc.dsc$w_length = strlen (vms_symbol1);
+ vms_status = LIB$GET_SYMBOL(&name_desc, &value_desc,
+ &value_len, &old_symtbl);
+ if (!$VMS_STATUS_SUCCESS(vms_status))
+ {
+ printf ("lib$get_symbol for command failed: %d\n", vms_status);
+ status = 1;
+ }
+
+ value[value_len] = 0;
+ result = strncmp (value, value1, value_len);
+ if (result != 0)
+ {
+ printf ("vms_export_dcl_symbol failed! expected '%s', got '%s'\n",
+ value1, value);
+ status = 1;
+ }
+
+ /* Test putenv for DCL symbols */
+ putenv_status = vms_putenv_symbol (putenv_string);
+ if (putenv_status != 0)
+ {
+ perror ("vms_putenv_symbol");
+ status = 1;
+ }
+
+ name_desc.dsc$a_pointer = (char *)vms_symbol2;
+ name_desc.dsc$w_length = strlen(vms_symbol2);
+ vms_status = LIB$GET_SYMBOL (&name_desc, &value_desc,
+ &value_len, &old_symtbl);
+ if (!$VMS_STATUS_SUCCESS (vms_status))
+ {
+ printf ("lib$get_symbol for command failed: %d\n", vms_status);
+ status = 1;
+ }
+
+ value[value_len] = 0;
+ result = strncmp (value, value2, value_len);
+ if (result != 0)
+ {
+ printf ("vms_putenv_symbol failed! expected '%s', got '%s'\n",
+ value2, value);
+ status = 1;
+ }
+
+ vms_restore_symbol (putenv_string);
+ vms_status = LIB$GET_SYMBOL (&name_desc, &value_desc,
+ &value_len, &old_symtbl);
+ if ($VMS_STATUS_SUCCESS (vms_status))
+ {
+ printf ("lib$get_symbol for command succeeded, should have failed\n");
+ status = 1;
+ }
+
+ exit (status);
+}
+
+#endif
diff --git a/src/kmk/vms_export_symbol_test.com b/src/kmk/vms_export_symbol_test.com
new file mode 100644
index 0000000..4345f44
--- /dev/null
+++ b/src/kmk/vms_export_symbol_test.com
@@ -0,0 +1,37 @@
+$! VMS_EXPORT_SYMBOL_TEST.COM
+$!
+$! Verify the VMS_EXPORT_SYMBOL.C module
+$!
+$! 22-May-2014 J. Malmberg
+$!
+$!=========================================================================
+$!
+$ cc/names=(as_is)/define=(DEBUG=1,_POSIX_EXIT=1) vms_export_symbol.c
+$!
+$ link vms_export_symbol
+$!
+$ delete vms_export_symbol.obj;*
+$!
+$! Need a foreign command to test.
+$ vms_export_symbol := $sys$disk:[]vms_export_symbol.exe
+$ save_export_symbol = vms_export_symbol
+$!
+$ vms_export_symbol
+$ if $severity .ne. 1
+$ then
+$ write sys$output "Test program failed!";
+$ endif
+$!
+$ if vms_export_symbol .nes. save_export_symbol
+$ then
+$ write sys$output "Test failed to restore foreign command!"
+$ endif
+$ if f$type(test_export_symbol) .nes. ""
+$ then
+$ write sys$output "Test failed to clear exported symbol!"
+$ endif
+$ if f$type(test_putenv_symbol) .nes. ""
+$ then
+$ write sys$output "Test failed to clear putenv exported symbol!"
+$ endif
+$!
diff --git a/src/kmk/vms_progname.c b/src/kmk/vms_progname.c
new file mode 100644
index 0000000..0665a54
--- /dev/null
+++ b/src/kmk/vms_progname.c
@@ -0,0 +1,463 @@
+/* File: vms_progname.c
+ *
+ * This module provides a fixup of the program name.
+ *
+ * This module is designed to be a plug in replacement for the
+ * progname module used by many GNU utilities with a few enhancements
+ * needed for GNU Make.
+ *
+ * It does not support the HAVE_DECL_PROGRAM_INVOCATION_* macros at this
+ * time.
+ *
+ * Make sure that the program_name string is set as close as possible to
+ * what the original command was given.
+ *
+ * When run from DCL, The argv[0] element is initialized with an absolute
+ * path name. The decc$ feature logical names can control the format
+ * of this pathname. In some cases it causes the UNIX format name to be
+ * formatted incorrectly.
+ *
+ * This DCL provided name is usually incompatible with what is expected to
+ * be provided by Unix programs and needs to be replaced.
+ *
+ * When run from an exec() call, the argv[0] element is initialized by the
+ * program. This name is compatible with what is expected to be provided
+ * by Unix programs and should be passed through unchanged.
+ *
+ * The DCL provided name can be detected because it always contains the
+ * device name.
+ *
+ * DCL examples:
+ * devname:[dir]program.exe;1 Normal VMS - remove path and .EXE;n
+ * devname:[dir]facility$program.exe;1 Facility also needs removal.
+ * /devname/dir/program.exe
+ * /DISK$VOLUME/dir/program.exe.1 Bug version should not be there.
+ * /DISK$VOLUME/dir/program. Bug Period should not be there.
+ *
+ */
+
+/* Copyright (C) 2014-2016 Free Software Foundation, Inc.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+/* Per copyright assignment agreement with the Free Software Foundation
+ this software may be available under under other license agreements
+ and copyrights. */
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include <descrip.h>
+#include <dvidef.h>
+#include <efndef.h>
+#include <fscndef.h>
+#include <stsdef.h>
+
+#ifdef USE_PROGNAME_H
+# include "progname.h"
+#endif
+
+#pragma member_alignment save
+#pragma nomember_alignment longword
+struct item_list_3
+{
+ unsigned short len;
+ unsigned short code;
+ void * bufadr;
+ unsigned short * retlen;
+};
+
+struct filescan_itmlst_2
+{
+ unsigned short length;
+ unsigned short itmcode;
+ char * component;
+};
+
+#pragma member_alignment
+
+int
+SYS$GETDVIW (unsigned long efn,
+ unsigned short chan,
+ const struct dsc$descriptor_s * devnam,
+ const struct item_list_3 * itmlst,
+ void * iosb,
+ void (* astadr)(unsigned long),
+ unsigned long astprm,
+ void * nullarg);
+
+int
+SYS$FILESCAN (const struct dsc$descriptor_s * srcstr,
+ struct filescan_itmlst_2 * valuelist,
+ unsigned long * fldflags,
+ struct dsc$descriptor_s *auxout,
+ unsigned short * retlen);
+
+/* String containing name the program is called with.
+ To be initialized by main(). */
+
+const char *program_name = NULL;
+
+static int internal_need_vms_symbol = 0;
+
+static char vms_new_nam[256];
+
+int
+need_vms_symbol (void)
+{
+ return internal_need_vms_symbol;
+}
+
+
+void
+set_program_name (const char *argv0)
+{
+ int status;
+ int result;
+
+#ifdef DEBUG
+ printf ("original argv0 = %s\n", argv0);
+#endif
+
+ /* Posix requires non-NULL argv[0] */
+ if (argv0 == NULL)
+ {
+ fputs ("A NULL argv[0] was passed through an exec system call.\n",
+ stderr);
+ abort ();
+ }
+
+ program_name = argv0;
+ result = 0;
+ internal_need_vms_symbol = 0;
+
+ /* If the path name starts with a /, then it is an absolute path */
+ /* that may have been generated by the CRTL instead of the command name */
+ /* If it is the device name between the slashes, then this was likely */
+ /* from the run command and needs to be fixed up. */
+ /* If the DECC$POSIX_COMPLIANT_PATHNAMES is set to 2, then it is the */
+ /* DISK$VOLUME that will be present, and it will still need to be fixed. */
+ if (argv0[0] == '/')
+ {
+ char * nextslash;
+ int length;
+ struct item_list_3 itemlist[3];
+ unsigned short dvi_iosb[4];
+ char alldevnam[64];
+ unsigned short alldevnam_len;
+ struct dsc$descriptor_s devname_dsc;
+ char diskvolnam[256];
+ unsigned short diskvolnam_len;
+
+ internal_need_vms_symbol = 1;
+
+ /* Get some information about the disk */
+ /*--------------------------------------*/
+ itemlist[0].len = (sizeof alldevnam) - 1;
+ itemlist[0].code = DVI$_ALLDEVNAM;
+ itemlist[0].bufadr = alldevnam;
+ itemlist[0].retlen = &alldevnam_len;
+ itemlist[1].len = (sizeof diskvolnam) - 1 - 5;
+ itemlist[1].code = DVI$_VOLNAM;
+ itemlist[1].bufadr = &diskvolnam[5];
+ itemlist[1].retlen = &diskvolnam_len;
+ itemlist[2].len = 0;
+ itemlist[2].code = 0;
+
+ /* Add the prefix for the volume name. */
+ /* SYS$GETDVI will append the volume name to this */
+ strcpy (diskvolnam, "DISK$");
+
+ nextslash = strchr (&argv0[1], '/');
+ if (nextslash != NULL)
+ {
+ length = nextslash - argv0 - 1;
+
+ /* Cast needed for HP C compiler diagnostic */
+ devname_dsc.dsc$a_pointer = (char *)&argv0[1];
+ devname_dsc.dsc$w_length = length;
+ devname_dsc.dsc$b_dtype = DSC$K_DTYPE_T;
+ devname_dsc.dsc$b_class = DSC$K_CLASS_S;
+
+ status = SYS$GETDVIW (EFN$C_ENF, 0, &devname_dsc, itemlist,
+ dvi_iosb, NULL, 0, 0);
+ if (!$VMS_STATUS_SUCCESS (status))
+ {
+ /* If the sys$getdviw fails, then this path was passed by */
+ /* An exec() program and not from DCL, so do nothing */
+ /* An example is "/tmp/program" where tmp: does not exist */
+#ifdef DEBUG
+ printf ("sys$getdviw failed with status %d\n", status);
+#endif
+ result = 0;
+ }
+ else if (!$VMS_STATUS_SUCCESS (dvi_iosb[0]))
+ {
+#ifdef DEBUG
+ printf ("sys$getdviw failed with iosb %d\n", dvi_iosb[0]);
+#endif
+ result = 0;
+ }
+ else
+ {
+ char * devnam;
+ int devnam_len;
+ char argv_dev[64];
+
+ /* Null terminate the returned alldevnam */
+ alldevnam[alldevnam_len] = 0;
+ devnam = alldevnam;
+ devnam_len = alldevnam_len;
+
+ /* Need to skip past any leading underscore */
+ if (devnam[0] == '_')
+ {
+ devnam++;
+ devnam_len--;
+ }
+
+ /* And remove the trailing colon */
+ if (devnam[devnam_len - 1] == ':')
+ {
+ devnam_len--;
+ devnam[devnam_len] = 0;
+ }
+
+ /* Null terminate the returned volnam */
+ diskvolnam_len += 5;
+ diskvolnam[diskvolnam_len] = 0;
+
+ /* Check first for normal CRTL behavior */
+ if (devnam_len == length)
+ {
+ strncpy (vms_new_nam, &argv0[1], length);
+ vms_new_nam[length] = 0;
+ result = (strcasecmp (devnam, vms_new_nam) == 0);
+ }
+
+ /* If we have not got a match, check for POSIX Compliant */
+ /* behavior. To be more accurate, we could also check */
+ /* to see if that feature is active. */
+ if ((result == 0) && (diskvolnam_len == length))
+ {
+ strncpy (vms_new_nam, &argv0[1], length);
+ vms_new_nam[length] = 0;
+ result = (strcasecmp (diskvolnam, vms_new_nam) == 0);
+ }
+ }
+ }
+ }
+ else
+ {
+ /* The path did not start with a slash, so it could be VMS format */
+ /* If it is vms format, it has a volume/device in it as it must */
+ /* be an absolute path */
+ struct dsc$descriptor_s path_desc;
+ int status;
+ unsigned long field_flags;
+ struct filescan_itmlst_2 item_list[5];
+ char * volume;
+ char * name;
+ int name_len;
+ char * ext;
+
+ path_desc.dsc$a_pointer = (char *)argv0; /* cast ok */
+ path_desc.dsc$w_length = strlen (argv0);
+ path_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ path_desc.dsc$b_class = DSC$K_CLASS_S;
+
+ /* Don't actually need to initialize anything buf itmcode */
+ /* I just do not like uninitialized input values */
+
+ /* Sanity check, this must be the same length as input */
+ item_list[0].itmcode = FSCN$_FILESPEC;
+ item_list[0].length = 0;
+ item_list[0].component = NULL;
+
+ /* If the device is present, then it if a VMS spec */
+ item_list[1].itmcode = FSCN$_DEVICE;
+ item_list[1].length = 0;
+ item_list[1].component = NULL;
+
+ /* we need the program name and type */
+ item_list[2].itmcode = FSCN$_NAME;
+ item_list[2].length = 0;
+ item_list[2].component = NULL;
+
+ item_list[3].itmcode = FSCN$_TYPE;
+ item_list[3].length = 0;
+ item_list[3].component = NULL;
+
+ /* End the list */
+ item_list[4].itmcode = 0;
+ item_list[4].length = 0;
+ item_list[4].component = NULL;
+
+ status = SYS$FILESCAN ((const struct dsc$descriptor_s *)&path_desc,
+ item_list, &field_flags, NULL, NULL);
+
+
+ if ($VMS_STATUS_SUCCESS (status) &&
+ (item_list[0].length == path_desc.dsc$w_length) &&
+ (item_list[1].length != 0))
+ {
+
+ char * dollar;
+ int keep_ext;
+ int i;
+
+ /* We need the filescan to be successful, */
+ /* same length as input, and a volume to be present */
+ internal_need_vms_symbol = 1;
+
+ /* We will assume that we only get to this path on a version */
+ /* of VMS that does not support the EFS character set */
+
+ /* There may be a xxx$ prefix on the image name. Linux */
+ /* programs do not handle that well, so strip the prefix */
+ name = item_list[2].component;
+ name_len = item_list[2].length;
+ dollar = strrchr (name, '$');
+ if (dollar != NULL)
+ {
+ dollar++;
+ name_len = name_len - (dollar - name);
+ name = dollar;
+ }
+
+ strncpy (vms_new_nam, name, name_len);
+ vms_new_nam[name_len] = 0;
+
+ /* Commit to using the new name */
+ program_name = vms_new_nam;
+
+ /* We only keep the extension if it is not ".exe" */
+ keep_ext = 0;
+ ext = item_list[3].component;
+
+ if (item_list[3].length != 1)
+ {
+ keep_ext = 1;
+ if (item_list[3].length == 4)
+ {
+ if ((ext[1] == 'e' || ext[1] == 'E') &&
+ (ext[2] == 'x' || ext[2] == 'X') &&
+ (ext[3] == 'e' || ext[3] == 'E'))
+ keep_ext = 0;
+ }
+ }
+
+ if (keep_ext == 1)
+ strncpy (&vms_new_nam[name_len], ext, item_list[3].length);
+ }
+ }
+
+ if (result)
+ {
+ char * lastslash;
+ char * dollar;
+ char * dotexe;
+ char * lastdot;
+ char * extension;
+
+ /* This means it is probably the name from a DCL command */
+ /* Find the last slash which separates the file from the */
+ /* path. */
+ lastslash = strrchr (argv0, '/');
+
+ if (lastslash != NULL) {
+ int i;
+
+ lastslash++;
+
+ /* There may be a xxx$ prefix on the image name. Linux */
+ /* programs do not handle that well, so strip the prefix */
+ dollar = strrchr (lastslash, '$');
+
+ if (dollar != NULL) {
+ dollar++;
+ lastslash = dollar;
+ }
+
+ strcpy (vms_new_nam, lastslash);
+
+ /* In UNIX mode + EFS character set, there should not be a */
+ /* version present, as it is not possible when parsing to */
+ /* tell if it is a version or part of the UNIX filename as */
+ /* UNIX programs use numeric extensions for many reasons. */
+
+ lastdot = strrchr (vms_new_nam, '.');
+ if (lastdot != NULL) {
+ int i;
+
+ i = 1;
+ while (isdigit (lastdot[i])) {
+ i++;
+ }
+ if (lastdot[i] == 0) {
+ *lastdot = 0;
+ }
+ }
+
+ /* Find the .exe on the name (case insenstive) and toss it */
+ dotexe = strrchr (vms_new_nam, '.');
+ if (dotexe != NULL) {
+ if ((dotexe[1] == 'e' || dotexe[1] == 'E') &&
+ (dotexe[2] == 'x' || dotexe[2] == 'X') &&
+ (dotexe[3] == 'e' || dotexe[3] == 'E') &&
+ (dotexe[4] == 0)) {
+
+ *dotexe = 0;
+ } else {
+ /* Also need to handle a null extension because of a */
+ /* CRTL bug. */
+ if (dotexe[1] == 0) {
+ *dotexe = 0;
+ }
+ }
+ }
+
+ /* Commit to new name */
+ program_name = vms_new_nam;
+
+ } else {
+ /* There is no way that the code should ever get here */
+ /* As we already verified that the '/' was present */
+ fprintf (stderr, "Sanity failure somewhere we lost a '/'\n");
+ }
+ }
+}
+
+#ifdef DEBUG
+
+int
+main (int argc, char ** argv, char **env)
+{
+
+ char command[1024];
+
+ set_program_name (argv[0]);
+
+ printf ("modified argv[0] = %s\n", program_name);
+
+ return 0;
+}
+#endif
diff --git a/src/kmk/vmsdir.h b/src/kmk/vmsdir.h
new file mode 100644
index 0000000..814b89d
--- /dev/null
+++ b/src/kmk/vmsdir.h
@@ -0,0 +1,76 @@
+/* dirent.h for vms
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef VMSDIR_H
+#define VMSDIR_H
+
+#include <rms.h>
+
+#define MAXNAMLEN 255
+
+#ifndef __DECC
+#if !defined (__GNUC__) && !defined (__ALPHA)
+typedef unsigned long u_long;
+typedef unsigned short u_short;
+#endif
+#endif
+
+struct direct
+{
+ off_t d_off;
+ u_long d_fileno;
+ u_short d_reclen;
+ u_short d_namlen;
+ char d_name[MAXNAMLEN + 1];
+};
+
+#undef DIRSIZ
+#define DIRSIZ(dp) \
+ (((sizeof (struct direct) \
+ - (MAXNAMLEN+1) \
+ + ((dp)->d_namlen+1)) \
+ + 3) & ~3)
+
+#define d_ino d_fileno /* compatibility */
+
+
+/*
+ * Definitions for library routines operating on directories.
+ */
+
+typedef struct DIR
+{
+ struct direct dir;
+ char d_result[MAXNAMLEN + 1];
+#if defined (__ALPHA) || defined (__DECC)
+ struct FAB fab;
+#else
+ struct fabdef fab;
+#endif
+} DIR;
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+#define rewinddir(dirp) seekdir((dirp), (long)0)
+
+DIR *opendir ();
+struct direct *readdir (DIR *dfd);
+int closedir (DIR *dfd);
+const char *vmsify (const char *name, int type);
+
+#endif /* VMSDIR_H */
diff --git a/src/kmk/vmsfunctions.c b/src/kmk/vmsfunctions.c
new file mode 100644
index 0000000..e422d48
--- /dev/null
+++ b/src/kmk/vmsfunctions.c
@@ -0,0 +1,226 @@
+/* VMS functions
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "debug.h"
+#include "job.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#ifdef __DECC
+#include <starlet.h>
+#endif
+
+#include <rms.h>
+#include "vmsdir.h"
+
+#ifdef HAVE_VMSDIR_H
+
+DIR *
+opendir (char *dspec)
+{
+ struct DIR *dir = xcalloc (sizeof (struct DIR));
+ struct NAM *dnam = xmalloc (sizeof (struct NAM));
+ struct FAB *dfab = &dir->fab;
+ char *searchspec = xmalloc (MAXNAMLEN + 1);
+
+ *dfab = cc$rms_fab;
+ *dnam = cc$rms_nam;
+ sprintf (searchspec, "%s*.*;", dspec);
+
+ dfab->fab$l_fna = searchspec;
+ dfab->fab$b_fns = strlen (searchspec);
+ dfab->fab$l_nam = dnam;
+
+ *dnam = cc$rms_nam;
+ dnam->nam$l_esa = searchspec;
+ dnam->nam$b_ess = MAXNAMLEN;
+
+ if (! (sys$parse (dfab) & 1))
+ {
+ free (dir);
+ free (dnam);
+ free (searchspec);
+ return (NULL);
+ }
+
+ return dir;
+}
+
+#define uppercasify(str) \
+ do \
+ { \
+ char *tmp; \
+ for (tmp = (str); *tmp != '\0'; tmp++) \
+ if (islower ((unsigned char)*tmp)) \
+ *tmp = toupper ((unsigned char)*tmp); \
+ } \
+ while (0)
+
+struct direct *
+readdir (DIR *dir)
+{
+ struct FAB *dfab = &dir->fab;
+ struct NAM *dnam = (struct NAM *)(dfab->fab$l_nam);
+ struct direct *dentry = &dir->dir;
+ int i;
+
+ memset (dentry, 0, sizeof *dentry);
+
+ dnam->nam$l_rsa = dir->d_result;
+ dnam->nam$b_rss = MAXNAMLEN;
+
+ DB (DB_VERBOSE, ("."));
+
+ if (!((i = sys$search (dfab)) & 1))
+ {
+ DB (DB_VERBOSE, (_("sys$search() failed with %d\n"), i));
+ return (NULL);
+ }
+
+ dentry->d_off = 0;
+ if (dnam->nam$w_fid == 0)
+ dentry->d_fileno = 1;
+ else
+ dentry->d_fileno = dnam->nam$w_fid[0] + (dnam->nam$w_fid[1] << 16);
+
+ dentry->d_reclen = sizeof (struct direct);
+ dentry->d_namlen = dnam->nam$b_name + dnam->nam$b_type;
+ strncpy (dentry->d_name, dnam->nam$l_name, dentry->d_namlen);
+ dentry->d_name[dentry->d_namlen] = '\0';
+
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ uppercasify (dentry->d_name);
+#endif
+
+ return (dentry);
+}
+
+int
+closedir (DIR *dir)
+{
+ if (dir != NULL)
+ {
+ struct FAB *dfab = &dir->fab;
+ struct NAM *dnam = (struct NAM *)(dfab->fab$l_nam);
+ if (dnam != NULL)
+ free (dnam->nam$l_esa);
+ free (dnam);
+ free (dir);
+ }
+
+ return 0;
+}
+#endif /* compiled for OpenVMS prior to V7.x */
+
+/* Argv0 will be a full vms file specification, like
+ node$dka100:[utils.gnumake]make.exe;47
+ prefix it with "mcr " to make it a vms command, executable for DCL. */
+const char *
+vms_command(const char* argv0)
+{
+ size_t l = strlen(argv0) + 1;
+ char* s = xmalloc(l + 4);
+ memcpy(s, "mcr ", 4);
+ memcpy(s+4, argv0, l);
+ return s;
+}
+
+/* Argv0 aka argv[0] will be a full vms file specification, like
+ node$dka100:[utils.gnumake]make.exe;47, set up by the CRTL.
+ The vms progname should be ^^^^, the file name without
+ file type .exe and ;version.
+ Use sys$parse to get the name part of the file specification. That is
+ in the above example, pick up "make" and return a copy of that string.
+ If something goes wrong in sys$parse (unlikely, this is a VMS/CRTL supplied
+ file specification) or if there is an empty name part (not easy to produce,
+ but it is possible) just return "make".
+ Somes notes ...
+ NAM[L]$M_SYNCHK requests a syntax check, only.
+ NAM is for ODS2 names (shorter parts, output usually converted to UPPERCASE).
+ NAML is for ODS2/ODS5 names (longer parts, output unchanged).
+ NAM$M_NO_SHORT_UPCASE may not be available for older versions of VMS.
+ NAML is not available on older versions of VMS (NAML$C_BID not defined).
+ argv[0] on older versions of VMS (no extended parse style and no
+ CRTL feature DECC$ARGV_PARSE_STYLE) is always in lowercase. */
+const char *
+vms_progname(const char* argv0)
+{
+ int status;
+ static struct FAB fab;
+ char *progname;
+ const char *fallback = "make";
+
+#ifdef NAML$C_BID
+ static char esa[NAML$C_MAXRSS];
+ static struct NAML naml;
+#else
+ static char esa[NAM$C_MAXRSS];
+ static struct NAM nam;
+#endif
+
+ fab = cc$rms_fab;
+ fab.fab$l_fna = (char*)argv0;
+ fab.fab$b_fns = strlen(argv0);
+
+#ifdef NAML$C_BID
+ fab.fab$l_naml = &naml;
+ naml = cc$rms_naml;
+ naml.naml$l_long_expand = esa;
+ naml.naml$l_long_expand_alloc = NAML$C_MAXRSS;
+ naml.naml$b_nop = NAML$M_SYNCHK;
+ naml.naml$l_input_flags = NAML$M_NO_SHORT_OUTPUT;
+#else
+ fab.fab$l_nam = &nam;
+ nam = cc$rms_nam;
+ nam.nam$l_esa = esa;
+ nam.nam$b_ess = NAM$C_MAXRSS;
+# ifdef NAM$M_NO_SHORT_UPCASE
+ nam.nam$b_nop = NAM$M_SYNCHK | NAM$M_NO_SHORT_UPCASE;
+# else
+ nam.nam$b_nop = NAM$M_SYNCHK;
+# endif
+#endif
+
+ status = sys$parse(&fab);
+ if (!(status & 1))
+ return fallback;
+
+#ifdef NAML$C_BID
+ if (naml.naml$l_long_name_size == 0)
+ return fallback;
+ progname = xmalloc(naml.naml$l_long_name_size + 1);
+ memcpy(progname, naml.naml$l_long_name, naml.naml$l_long_name_size);
+ progname[naml.naml$l_long_name_size] = '\0';
+#else
+ if (nam.nam$b_name == 0)
+ return fallback;
+ progname = xmalloc(nam.nam$b_name + 1);
+# ifdef NAM$M_NO_SHORT_UPCASE
+ memcpy(progname, nam.nam$l_name, nam.nam$b_name);
+# else
+ {
+ int i;
+ for (i = 0; i < nam.nam$b_name; i++)
+ progname[i] = tolower(nam.nam$l_name[i]);
+ }
+# endif
+ progname[nam.nam$b_name] = '\0';
+#endif
+
+ return progname;
+}
diff --git a/src/kmk/vmsify.c b/src/kmk/vmsify.c
new file mode 100644
index 0000000..e504a09
--- /dev/null
+++ b/src/kmk/vmsify.c
@@ -0,0 +1,1005 @@
+/* vmsify.c -- Module for vms <-> unix file name conversion
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Klaus Kämpf (kkaempf@progis.de)
+ of proGIS Software, Aachen, Germany */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "makeint.h"
+
+#if VMS
+#include <unixlib.h>
+#include <stdlib.h>
+#include <jpidef.h>
+#include <descrip.h>
+#include <uaidef.h>
+#include <ssdef.h>
+#include <starlet.h>
+#include <lib$routines.h>
+/* Initialize a string descriptor (struct dsc$descriptor_s) for an
+ arbitrary string. ADDR is a pointer to the first character
+ of the string, and LEN is the length of the string. */
+
+#define INIT_DSC_S(dsc, addr, len) do { \
+ (dsc).dsc$b_dtype = DSC$K_DTYPE_T; \
+ (dsc).dsc$b_class = DSC$K_CLASS_S; \
+ (dsc).dsc$w_length = (len); \
+ (dsc).dsc$a_pointer = (addr); \
+} while (0)
+
+/* Initialize a string descriptor (struct dsc$descriptor_s) for a
+ NUL-terminated string. S is a pointer to the string; the length
+ is determined by calling strlen(). */
+
+#define INIT_DSC_CSTRING(dsc, s) INIT_DSC_S(dsc, s, strlen(s))
+#endif
+
+/*
+ copy 'from' to 'to' up to but not including 'upto'
+ return 0 if eos on from
+ return 1 if upto found
+
+ return 'to' at last char + 1
+ return 'from' at match + 1 or eos if no match
+
+ if as_dir == 1, change all '.' to '_'
+ else change all '.' but the last to '_'
+*/
+
+static int
+copyto (char **to, const char **from, char upto, int as_dir)
+{
+ const char *s;
+
+ s = strrchr (*from, '.');
+
+ while (**from)
+ {
+ if (**from == upto)
+ {
+ do
+ {
+ (*from)++;
+ }
+ while (**from == upto);
+ return 1;
+ }
+ if (**from == '.')
+ {
+ if ((as_dir == 1)
+ || (*from != s))
+ **to = '_';
+ else
+ **to = '.';
+ }
+ else
+ {
+#ifdef HAVE_CASE_INSENSITIVE_FS
+ if (isupper ((unsigned char)**from))
+ **to = tolower ((unsigned char)**from);
+ else
+#endif
+ **to = **from;
+ }
+ (*to)++;
+ (*from)++;
+ }
+
+ return 0;
+}
+
+
+/*
+ get translation of logical name
+
+*/
+
+static char *
+trnlog (const char *name)
+{
+ int stat;
+ static char reslt[1024];
+ $DESCRIPTOR (reslt_dsc, reslt);
+ short resltlen;
+ struct dsc$descriptor_s name_dsc;
+ char *s;
+
+ /*
+ * the string isn't changed, but there is no string descriptor with
+ * "const char *dsc$a_pointer"
+ */
+ INIT_DSC_CSTRING (name_dsc, (char *)name);
+
+ stat = lib$sys_trnlog (&name_dsc, &resltlen, &reslt_dsc);
+
+ if ((stat&1) == 0)
+ {
+ return "";
+ }
+ if (stat == SS$_NOTRAN)
+ {
+ return "";
+ }
+ reslt[resltlen] = '\0';
+
+ s = xmalloc (resltlen+1);
+ strcpy (s, reslt);
+ return s;
+}
+
+static char *
+showall (char *s)
+{
+ static char t[512];
+ char *pt;
+
+ pt = t;
+ if (strchr (s, '\\') == 0)
+ return s;
+ while (*s)
+ {
+ if (*s == '\\')
+ {
+ *pt++ = *s;
+ }
+ *pt++ = *s++;
+ }
+ return pt;
+}
+
+
+enum namestate { N_START, N_DEVICE, N_OPEN, N_DOT, N_CLOSED, N_DONE };
+
+/*
+ convert unix style name to vms style
+ type = 0 -> name is a full name (directory and filename part)
+ type = 1 -> name is a directory
+ type = 2 -> name is a filename without directory
+
+ The following conversions are applied
+ (0) (1) (2)
+ input full name dir name file name
+
+1 ./ <cwd> [] <current directory>.dir
+2 ../ <home of cwd> <home of cwd> <home of cwd>.dir
+
+3 // <dev of cwd>: <dev of cwd>:[000000] <dev of cwd>:000000.dir
+4 //a a: a: a:
+5 //a/ a: a: a:000000.dir
+
+9 / [000000] [000000] 000000.dir
+10 /a [000000]a [a] [000000]a
+11 /a/ [a] [a] [000000]a.dir
+12 /a/b [a]b [a.b] [a]b
+13 /a/b/ [a.b] [a.b] [a]b.dir
+14 /a/b/c [a.b]c [a.b.c] [a.b]c
+15 /a/b/c/ [a.b.c] [a.b.c] [a.b]c.dir
+
+16 a a [.a] a
+17 a/ [.a] [.a] a.dir
+18 a/b [.a]b [.a.b] [.a]b
+19 a/b/ [.a.b] [.a.b] [.a]b.dir
+20 a/b/c [.a.b]c [.a.b.c] [.a.b]c
+21 a/b/c/ [.a.b.c] [.a.b.c] [.a.b]c.dir
+
+22 a.b.c a_b.c [.a_b_c] a_b_c.dir
+
+23 [x][y]z [x.y]z [x.y]z [x.y]z
+24 [x][.y]z [x.y]z [x.y]z [x.y]z
+
+25 filenames with '$' are left unchanged if they contain no '/'
+25 filenames with ':' are left unchanged
+26 filenames with a single pair of '[' ']' are left unchanged
+
+ The input string is not written to. The result is also const because
+ it's a static buffer; we don't want to change it.
+*/
+
+const char *
+vmsify (const char *name, int type)
+{
+/* max 255 device
+ max 39 directory
+ max 39 filename
+ max 39 filetype
+ max 5 version
+*/
+/* todo: VMSMAXPATHLEN is defined for ODS2 names: it needs to be adjusted. */
+#define VMSMAXPATHLEN 512
+
+ enum namestate nstate;
+ static char vmsname[VMSMAXPATHLEN+1];
+ const char *fptr;
+ const char *t;
+ char *vptr;
+ int as_dir;
+ int count;
+ const char *s;
+ const char *s1;
+ const char *s2;
+
+ if (name == 0)
+ return 0;
+ fptr = name;
+ vptr = vmsname;
+ nstate = N_START;
+
+ /* case 25a */
+ t = strpbrk (name, "$:");
+
+ if (t != 0)
+ {
+// const char *s1;
+// const char *s2;
+
+ if (type == 1)
+ {
+ s1 = strchr (t+1, '[');
+ s2 = strchr (t+1, ']');
+ }
+
+ if (*t == '$')
+ {
+ if (strchr (name, '/') == 0)
+ {
+ strcpy (vmsname, name);
+ if ((type == 1) && (s1 != 0) && (s2 == 0))
+ strcat (vmsname, "]");
+ return vmsname;
+ }
+ }
+ else
+ {
+ strcpy (vmsname, name);
+ if ((type == 1) && (s1 != 0) && (s2 == 0))
+ strcat (vmsname, "]");
+ return vmsname;
+ }
+ }
+
+ /* case 26 */
+ t = strchr (name, '[');
+
+ if (t != 0)
+ {
+// const char *s;
+// const char *s1 = strchr (t+1, '[');
+ s1 = strchr (t+1, '[');
+ if (s1 == 0)
+ {
+ strcpy (vmsname, name);
+ if ((type == 1) && (strchr (t+1, ']') == 0))
+ strcat (vmsname, "]");
+ return vmsname;
+ }
+ s1--;
+ if (*s1 != ']')
+ {
+ strcpy (vmsname, name);
+ return vmsname; /* not ][, keep unchanged */
+ }
+
+ /* we have ][ */
+
+ s = name;
+
+ /* s -> starting char
+ s1 -> ending ']' */
+ do
+ {
+ strncpy (vptr, s, s1-s); /* copy up to but not including ']' */
+ vptr += s1-s;
+ if (*s1 == 0)
+ break;
+ s = s1 + 1; /* s -> char behind ']' */
+ if (*s != '[') /* was '][' ? */
+ break; /* no, last ] found, exit */
+ s++;
+ if (*s != '.')
+ *vptr++ = '.';
+ s1 = strchr (s, ']');
+ if (s1 == 0) /* no closing ] */
+ s1 = s + strlen (s);
+ }
+ while (1);
+
+ *vptr++ = ']';
+
+ fptr = s;
+
+ }
+ else /* no [ in name */
+ {
+ int state = 0;
+ int rooted = 1; /* flag if logical is rooted, else insert [000000] */
+
+ do
+ {
+ switch (state)
+ {
+ case 0: /* start of loop */
+ if (*fptr == '/')
+ {
+ fptr++;
+ state = 1;
+ }
+ else if (*fptr == '.')
+ {
+ fptr++;
+ state = 10;
+ }
+ else
+ state = 2;
+ break;
+
+ case 1: /* '/' at start */
+ if (*fptr == '/')
+ {
+ fptr++;
+ state = 3;
+ }
+ else
+ state = 4;
+ break;
+
+ case 2: /* no '/' at start */
+ {
+ const char *s = strchr (fptr, '/');
+ if (s == 0) /* no '/' (16) */
+ {
+ if (type == 1)
+ {
+ strcpy (vptr, "[.");
+ vptr += 2;
+ }
+ copyto (&vptr, &fptr, 0, (type==1));
+ if (type == 1)
+ *vptr++ = ']';
+ state = -1;
+ }
+ else /* found '/' (17..21) */
+ {
+ if ((type == 2)
+ && (*(s+1) == 0)) /* 17(2) */
+ {
+ copyto (&vptr, &fptr, '/', 1);
+ state = 7;
+ }
+ else
+ {
+ strcpy (vptr, "[.");
+ vptr += 2;
+ copyto (&vptr, &fptr, '/', 1);
+ nstate = N_OPEN;
+ state = 9;
+ }
+ }
+ break;
+ }
+
+ case 3: /* '//' at start */
+ {
+// const char *s;
+// const char *s1;
+ char *vp;
+ while (*fptr == '/') /* collapse all '/' */
+ fptr++;
+ if (*fptr == 0) /* just // */
+ {
+ char cwdbuf[VMSMAXPATHLEN+1];
+
+ s1 = getcwd(cwdbuf, VMSMAXPATHLEN);
+ if (s1 == 0)
+ {
+ vmsname[0] = '\0';
+ return vmsname; /* FIXME, err getcwd */
+ }
+ s = strchr (s1, ':');
+ if (s == 0)
+ {
+ vmsname[0] = '\0';
+ return vmsname; /* FIXME, err no device */
+ }
+ strncpy (vptr, s1, s-s1+1);
+ vptr += s-s1+1;
+ state = -1;
+ break;
+ }
+
+ s = vptr;
+
+ if (copyto (&vptr, &fptr, '/', 1) == 0) /* copy device part */
+ {
+ *vptr++ = ':';
+ state = -1;
+ break;
+ }
+ *vptr = ':';
+ nstate = N_DEVICE;
+ if (*fptr == 0) /* just '//a/' */
+ {
+ strcpy (vptr+1, "[000000]");
+ vptr += 9;
+ state = -1;
+ break;
+ }
+ *vptr = 0;
+ /* check logical for [000000] insertion */
+ vp = trnlog (s);
+ if (*vp != '\0')
+ { /* found translation */
+ for (;;) /* loop over all nested logicals */
+ {
+ char *vp2 = vp + strlen (vp) - 1;
+ if (*vp2 == ':') /* translation ends in ':' */
+ {
+ vp2 = trnlog (vp);
+ free (vp);
+ if (*vp2 == 0)
+ {
+ rooted = 0;
+ break;
+ }
+ vp = vp2;
+ continue; /* next iteration */
+ }
+ if (*vp2 == ']') /* translation ends in ']' */
+ {
+ if (*(vp2-1) == '.') /* ends in '.]' */
+ {
+ if (strncmp (fptr, "000000", 6) != 0)
+ rooted = 0;
+ }
+ else
+ {
+ strcpy (vmsname, s1);
+ vp = strchr (vmsname, ']');
+ *vp = '.';
+ nstate = N_DOT;
+ vptr = vp;
+ }
+ }
+ break;
+ }
+ free (vp);
+ }
+ else
+ rooted = 0;
+
+ if (*vptr == 0)
+ {
+ nstate = N_DEVICE;
+ *vptr++ = ':';
+ }
+ else
+ vptr++;
+
+ if (rooted == 0)
+ {
+ nstate = N_DOT;
+ strcpy (vptr, "[000000.");
+ vptr += 8;
+ vp = vptr-1;
+ }
+ else
+ vp = 0;
+
+ /* vp-> '.' after 000000 or NULL */
+
+ s = strchr (fptr, '/');
+ if (s == 0)
+ { /* no next '/' */
+ if (*(vptr-1) == '.')
+ *(vptr-1) = ']';
+ else if (rooted == 0)
+ *vptr++ = ']';
+ copyto (&vptr, &fptr, 0, (type == 1));
+ state = -1;
+ break;
+ }
+ else
+ {
+ while (*(s+1) == '/') /* skip multiple '/' */
+ s++;
+ }
+
+ if ((rooted != 0)
+ && (*(vptr-1) != '.'))
+ {
+ *vptr++ = '[';
+ nstate = N_DOT;
+ }
+ else
+ if ((nstate == N_DOT)
+ && (vp != 0)
+ && (*(s+1) == 0))
+ {
+ if (type == 2)
+ {
+ *vp = ']';
+ nstate = N_CLOSED;
+ }
+ }
+ state = 9;
+ break;
+ }
+ case 4: /* single '/' at start (9..15) */
+ if (*fptr == 0)
+ state = 5;
+ else
+ state = 6;
+ break;
+
+ case 5: /* just '/' at start (9) */
+ if (type != 2)
+ {
+ *vptr++ = '[';
+ nstate = N_OPEN;
+ }
+ strcpy (vptr, "000000");
+ vptr += 6;
+ if (type == 2)
+ state = 7;
+ else
+ state = 8;
+ break;
+
+ case 6: /* chars following '/' at start 10..15 */
+ {
+ const char *s;
+ *vptr++ = '[';
+ nstate = N_OPEN;
+ s = strchr (fptr, '/');
+ if (s == 0) /* 10 */
+ {
+ if (type != 1)
+ {
+ strcpy (vptr, "000000]");
+ vptr += 7;
+ }
+ copyto (&vptr, &fptr, 0, (type == 1));
+ if (type == 1)
+ {
+ *vptr++ = ']';
+ }
+ state = -1;
+ }
+ else /* 11..15 */
+ {
+ if ( (type == 2)
+ && (*(s+1) == 0)) /* 11(2) */
+ {
+ strcpy (vptr, "000000]");
+ nstate = N_CLOSED;
+ vptr += 7;
+ }
+ copyto (&vptr, &fptr, '/', (*(vptr-1) != ']'));
+ state = 9;
+ }
+ break;
+ }
+
+ case 7: /* add '.dir' and exit */
+ if ((nstate == N_OPEN)
+ || (nstate == N_DOT))
+ {
+ char *vp = vptr-1;
+ while (vp > vmsname)
+ {
+ if (*vp == ']')
+ {
+ break;
+ }
+ if (*vp == '.')
+ {
+ *vp = ']';
+ break;
+ }
+ vp--;
+ }
+ }
+ strcpy (vptr, ".dir");
+ vptr += 4;
+ state = -1;
+ break;
+
+ case 8: /* add ']' and exit */
+ *vptr++ = ']';
+ state = -1;
+ break;
+
+ case 9: /* 17..21, fptr -> 1st '/' + 1 */
+ {
+ const char *s;
+ if (*fptr == 0)
+ {
+ if (type == 2)
+ {
+ state = 7;
+ }
+ else
+ state = 8;
+ break;
+ }
+ s = strchr (fptr, '/');
+ if (s == 0)
+ {
+ if (type != 1)
+ {
+ if (nstate == N_OPEN)
+ {
+ *vptr++ = ']';
+ nstate = N_CLOSED;
+ }
+ as_dir = 0;
+ }
+ else
+ {
+ if (nstate == N_OPEN)
+ {
+ *vptr++ = '.';
+ nstate = N_DOT;
+ }
+ as_dir = 1;
+ }
+ }
+ else
+ {
+ while (*(s+1) == '/')
+ s++;
+ if ( (type == 2)
+ && (*(s+1) == 0)) /* 19(2), 21(2)*/
+ {
+ if (nstate != N_CLOSED)
+ {
+ *vptr++ = ']';
+ nstate = N_CLOSED;
+ }
+ as_dir = 1;
+ }
+ else
+ {
+ if (nstate == N_OPEN)
+ {
+ *vptr++ = '.';
+ nstate = N_DOT;
+ }
+ as_dir = 1;
+ }
+ }
+ if ( (*fptr == '.') /* check for '..' or '../' */
+ && (*(fptr+1) == '.')
+ && ((*(fptr+2) == '/')
+ || (*(fptr+2) == 0)) )
+ {
+ char *vp;
+ fptr += 2;
+ if (*fptr == '/')
+ {
+ do
+ {
+ fptr++;
+ }
+ while (*fptr == '/');
+ }
+ else if (*fptr == 0)
+ type = 1;
+ vptr--; /* vptr -> '.' or ']' */
+ vp = vptr;
+ for (;;)
+ {
+ vp--;
+ if (*vp == '.') /* one back */
+ {
+ vptr = vp;
+ nstate = N_OPEN;
+ break;
+ }
+ if (*vp == '[') /* top level reached */
+ {
+ if (*fptr == 0)
+ {
+ strcpy (vp, "[000000]");
+ vptr = vp + 8;
+ nstate = N_CLOSED;
+ s = 0;
+ break;
+ }
+ else
+ {
+ vptr = vp+1;
+ nstate = N_OPEN;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ copyto (&vptr, &fptr, '/', as_dir);
+ if (nstate == N_DOT)
+ nstate = N_OPEN;
+ }
+ if (s == 0)
+ { /* 18,20 */
+ if (type == 1)
+ *vptr++ = ']';
+ state = -1;
+ }
+ else
+ {
+ if (*(s+1) == 0)
+ {
+ if (type == 2) /* 19,21 */
+ {
+ state = 7;
+ }
+ else
+ {
+ *vptr++ = ']';
+ state = -1;
+ }
+ }
+ }
+ break;
+ }
+
+ case 10: /* 1,2 first is '.' */
+ if (*fptr == '.')
+ {
+ fptr++;
+ state = 11;
+ }
+ else
+ state = 12;
+ break;
+
+ case 11: /* 2, '..' at start */
+ count = 1;
+ if (*fptr != 0)
+ {
+ if (*fptr != '/') /* got ..xxx */
+ {
+ strcpy (vmsname, name);
+ return vmsname;
+ }
+ do /* got ../ */
+ {
+ fptr++;
+ while (*fptr == '/') fptr++;
+ if (*fptr != '.')
+ break;
+ if (*(fptr+1) != '.')
+ break;
+ fptr += 2;
+ if ((*fptr == 0)
+ || (*fptr == '/'))
+ count++;
+ }
+ while (*fptr == '/');
+ }
+ { /* got '..' or '../' */
+ char *vp;
+ char cwdbuf[VMSMAXPATHLEN+1];
+
+ vp = getcwd(cwdbuf, VMSMAXPATHLEN);
+ if (vp == 0)
+ {
+ vmsname[0] = '\0';
+ return vmsname; /* FIXME, err getcwd */
+ }
+ strcpy (vptr, vp);
+ vp = strchr (vptr, ']');
+ if (vp != 0)
+ {
+ nstate = N_OPEN;
+ while (vp > vptr)
+ {
+ vp--;
+ if (*vp == '[')
+ {
+ vp++;
+ strcpy (vp, "000000]");
+ state = -1;
+ break;
+ }
+ else if (*vp == '.')
+ {
+ if (--count == 0)
+ {
+ if (*fptr == 0) /* had '..' or '../' */
+ {
+ *vp++ = ']';
+ state = -1;
+ }
+ else /* had '../xxx' */
+ {
+ state = 9;
+ }
+ *vp = '\0';
+ break;
+ }
+ }
+ }
+ }
+ vptr += strlen (vptr);
+ }
+ break;
+
+ case 12: /* 1, '.' at start */
+ if (*fptr != 0)
+ {
+ if (*fptr != '/')
+ {
+ strcpy (vmsname, name);
+ return vmsname;
+ }
+ while (*fptr == '/')
+ fptr++;
+ }
+
+ {
+ char *vp;
+ char cwdbuf[VMSMAXPATHLEN+1];
+
+ vp = getcwd(cwdbuf, VMSMAXPATHLEN);
+ if (vp == 0)
+ {
+ vmsname[0] = '\0';
+ return vmsname; /*FIXME, err getcwd */
+ }
+ strcpy (vptr, vp);
+ }
+ if (*fptr == 0)
+ {
+ state = -1;
+ break;
+ }
+ else
+ {
+ char *vp = strchr (vptr, ']');
+ if (vp == 0)
+ {
+ state = -1;
+ break;
+ }
+ *vp = '\0';
+ nstate = N_OPEN;
+ vptr += strlen (vptr);
+ state = 9;
+ }
+ break;
+ }
+
+ }
+ while (state > 0);
+
+
+ }
+
+
+ /* directory conversion done
+ fptr -> filename part of input string
+ vptr -> free space in vmsname
+ */
+
+ *vptr++ = 0;
+
+ return vmsname;
+}
+
+
+
+/*
+ convert from vms-style to unix-style
+
+ dev:[dir1.dir2] //dev/dir1/dir2/
+*/
+
+const char *
+unixify (const char *name)
+{
+ static char piece[512];
+ const char *s;
+ char *p;
+
+ if (strchr (name, '/') != 0) /* already in unix style */
+ {
+ strcpy (piece, name);
+ return piece;
+ }
+
+ p = piece;
+ *p = 0;
+
+ /* device part */
+
+ s = strchr (name, ':');
+
+ if (s != 0)
+ {
+ int l = s - name;
+ *p++ = '/';
+ *p++ = '/';
+ strncpy (p, name, l);
+ p += l;
+ }
+
+ /* directory part */
+
+ *p++ = '/';
+ s = strchr (name, '[');
+
+ if (s != 0)
+ {
+ s++;
+ switch (*s)
+ {
+ case ']': /* [] */
+ strcat (p, "./");
+ break;
+ case '-': /* [- */
+ strcat (p, "../");
+ break;
+ case '.':
+ strcat (p, "./"); /* [. */
+ break;
+ default:
+ s--;
+ break;
+ }
+ s++;
+ while (*s)
+ {
+ if (*s == '.')
+ *p++ = '/';
+ else
+ *p++ = *s;
+ s++;
+ if (*s == ']')
+ {
+ s++;
+ break;
+ }
+ }
+ if (*s != 0) /* more after ']' ?? */
+ {
+ if (*(p-1) != '/')
+ *p++ = '/';
+ strcpy (p, s); /* copy it anyway */
+ }
+ }
+
+ else /* no '[' anywhere */
+
+ {
+ *p++ = 0;
+ }
+
+ /* force end with '/' */
+
+ if (*(p-1) != '/')
+ *p++ = '/';
+ *p = 0;
+
+ return piece;
+}
+
+/* EOF */
diff --git a/src/kmk/vmsjobs.c b/src/kmk/vmsjobs.c
new file mode 100644
index 0000000..f45c8a8
--- /dev/null
+++ b/src/kmk/vmsjobs.c
@@ -0,0 +1,1468 @@
+/* --------------- Moved here from job.c ---------------
+ This file must be #included in job.c, as it accesses static functions.
+
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <string.h>
+#include <descrip.h>
+#include <clidef.h>
+
+/* TODO - VMS specific header file conditionally included in makeint.h */
+
+#include <stsdef.h>
+#include <ssdef.h>
+void
+decc$exit (int status);
+
+/* Lowest legal non-success VMS exit code is 8 */
+/* GNU make only defines codes 0, 1, 2 */
+/* So assume any exit code > 8 is a VMS exit code */
+
+#ifndef MAX_EXPECTED_EXIT_CODE
+# define MAX_EXPECTED_EXIT_CODE 7
+#endif
+
+
+#if __CRTL_VER >= 70302000 && !defined(__VAX)
+# define MAX_DCL_LINE_LENGTH 4095
+# define MAX_DCL_CMD_LINE_LENGTH 8192
+#else
+# define MAX_DCL_LINE_LENGTH 255
+# define MAX_DCL_CMD_LINE_LENGTH 1024
+#endif
+#define MAX_DCL_TOKEN_LENGTH 255
+#define MAX_DCL_TOKENS 127
+
+enum auto_pipe { nopipe, add_pipe, dcl_pipe };
+
+char *vmsify (char *name, int type);
+
+static int vms_jobsefnmask = 0;
+
+/* returns whether path is assumed to be a unix like shell. */
+int
+_is_unixy_shell (const char *path)
+{
+ return vms_gnv_shell;
+}
+
+#define VMS_GETMSG_MAX 256
+static char vms_strsignal_text[VMS_GETMSG_MAX + 2];
+
+char *
+vms_strsignal (int status)
+{
+ if (status <= MAX_EXPECTED_EXIT_CODE)
+ sprintf (vms_strsignal_text, "lib$spawn returned %x", status);
+ else
+ {
+ int vms_status;
+ unsigned short * msg_len;
+ unsigned char out[4];
+ vms_status = SYS$GETMSG (status, &msg_len,
+ vms_strsignal_text, 7, *out);
+ }
+
+ return vms_strsignal_text;
+}
+
+
+/* Wait for nchildren children to terminate */
+static void
+vmsWaitForChildren (int *status)
+{
+ while (1)
+ {
+ if (!vms_jobsefnmask)
+ {
+ *status = 0;
+ return;
+ }
+
+ *status = sys$wflor (32, vms_jobsefnmask);
+ }
+ return;
+}
+
+static int ctrlYPressed= 0;
+/* This is called at main or AST level. It is at AST level for DONTWAITFORCHILD
+ and at main level otherwise. In any case it is called when a child process
+ terminated. At AST level it won't get interrupted by anything except a
+ inner mode level AST.
+*/
+static int
+vmsHandleChildTerm (struct child *child)
+{
+ int exit_code;
+ register struct child *lastc, *c;
+ int child_failed;
+
+ /* The child efn is 0 when a built-in or null command is executed
+ successfully with out actually creating a child.
+ */
+ if (child->efn > 0)
+ {
+ vms_jobsefnmask &= ~(1 << (child->efn - 32));
+
+ lib$free_ef (&child->efn);
+ }
+ if (child->comname)
+ {
+ if (!ISDB (DB_JOBS) && !ctrlYPressed)
+ unlink (child->comname);
+ free (child->comname);
+ }
+
+ (void) sigblock (fatal_signal_mask);
+
+ /* First check to see if this is a POSIX exit status and handle */
+ if ((child->cstatus & VMS_POSIX_EXIT_MASK) == VMS_POSIX_EXIT_MASK)
+ {
+ exit_code = (child->cstatus >> 3) & 255;
+ if (exit_code != MAKE_SUCCESS)
+ child_failed = 1;
+ }
+ else
+ {
+ child_failed = !$VMS_STATUS_SUCCESS (child->cstatus);
+ if (child_failed)
+ exit_code = child->cstatus;
+ }
+
+ /* Search for a child matching the deceased one. */
+ lastc = 0;
+#if defined(RECURSIVEJOBS)
+ /* I've had problems with recursive stuff and process handling */
+ for (c = children; c != 0 && c != child; lastc = c, c = c->next)
+ ;
+#else
+ c = child;
+#endif
+
+ if ($VMS_STATUS_SUCCESS (child->vms_launch_status))
+ {
+ /* Convert VMS success status to 0 for UNIX code to be happy */
+ child->vms_launch_status = 0;
+ }
+
+ /* Set the state flag to say the commands have finished. */
+ c->file->command_state = cs_finished;
+ notice_finished_file (c->file);
+
+ (void) sigsetmask (sigblock (0) & ~(fatal_signal_mask));
+
+ return 1;
+}
+
+/* VMS:
+ Spawn a process executing the command in ARGV and return its pid. */
+
+/* local helpers to make ctrl+c and ctrl+y working, see below */
+#include <iodef.h>
+#include <libclidef.h>
+#include <ssdef.h>
+
+static int ctrlMask= LIB$M_CLI_CTRLY;
+static int oldCtrlMask;
+static int setupYAstTried= 0;
+static unsigned short int chan= 0;
+
+static void
+reEnableAst(void)
+{
+ lib$enable_ctrl (&oldCtrlMask,0);
+}
+
+static int
+astYHandler (void)
+{
+ struct child *c;
+ for (c = children; c != 0; c = c->next)
+ sys$delprc (&c->pid, 0, 0);
+ ctrlYPressed= 1;
+ kill (getpid(),SIGQUIT);
+ return SS$_NORMAL;
+}
+
+static void
+tryToSetupYAst(void)
+{
+ $DESCRIPTOR(inputDsc,"SYS$COMMAND");
+ int status;
+ struct {
+ short int status, count;
+ int dvi;
+ } iosb;
+ unsigned short int loc_chan;
+
+ setupYAstTried++;
+
+ if (chan)
+ loc_chan= chan;
+ else
+ {
+ status= sys$assign(&inputDsc,&loc_chan,0,0);
+ if (!(status&SS$_NORMAL))
+ {
+ lib$signal(status);
+ return;
+ }
+ }
+ status= sys$qiow (0, loc_chan, IO$_SETMODE|IO$M_CTRLYAST,&iosb,0,0,
+ astYHandler,0,0,0,0,0);
+ if (status==SS$_NORMAL)
+ status= iosb.status;
+ if (status!=SS$_NORMAL)
+ {
+ if (!chan)
+ sys$dassgn(loc_chan);
+ if (status!=SS$_ILLIOFUNC && status!=SS$_NOPRIV)
+ lib$signal(status);
+ return;
+ }
+
+ /* called from AST handler ? */
+ if (setupYAstTried>1)
+ return;
+ if (atexit(reEnableAst))
+ fprintf (stderr,
+ _("-warning, you may have to re-enable CTRL-Y handling from DCL.\n"));
+ status= lib$disable_ctrl (&ctrlMask, &oldCtrlMask);
+ if (!(status&SS$_NORMAL))
+ {
+ lib$signal(status);
+ return;
+ }
+ if (!chan)
+ chan = loc_chan;
+}
+
+/* Check if a token is too long */
+#define INC_TOKEN_LEN_OR_RETURN(x) {token->length++; \
+ if (token->length >= MAX_DCL_TOKEN_LENGTH) \
+ { token->cmd_errno = ERANGE; return x; }}
+
+#define INC_TOKEN_LEN_OR_BREAK {token->length++; \
+ if (token->length >= MAX_DCL_TOKEN_LENGTH) \
+ { token->cmd_errno = ERANGE; break; }}
+
+#define ADD_TOKEN_LEN_OR_RETURN(add_len, x) {token->length += add_len; \
+ if (token->length >= MAX_DCL_TOKEN_LENGTH) \
+ { token->cmd_errno = ERANGE; return x; }}
+
+/* Check if we are out of space for more tokens */
+#define V_NEXT_TOKEN { if (cmd_tkn_index < MAX_DCL_TOKENS) \
+ cmd_tokens[++cmd_tkn_index] = NULL; \
+ else { token.cmd_errno = E2BIG; break; } \
+ token.length = 0;}
+
+
+#define UPDATE_TOKEN {cmd_tokens[cmd_tkn_index] = strdup(token.text); \
+ V_NEXT_TOKEN;}
+
+#define EOS_ERROR(x) { if (*x == 0) { token->cmd_errno = ERANGE; break; }}
+
+struct token_info
+ {
+ char *text; /* Parsed text */
+ int length; /* Length of parsed text */
+ char *src; /* Pointer to source text */
+ int cmd_errno; /* Error status of parse */
+ int use_cmd_file; /* Force use of a command file */
+ };
+
+
+/* Extract a Posix single quoted string from input line */
+static char *
+posix_parse_sq (struct token_info *token)
+{
+ /* A Posix quoted string with no expansion unless in a string
+ Unix simulation means no lexical functions present.
+ */
+ char * q;
+ char * p;
+ q = token->text;
+ p = token->src;
+
+ *q++ = '"';
+ p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+
+ while (*p != '\'' && (token->length < MAX_DCL_TOKEN_LENGTH))
+ {
+ EOS_ERROR (p);
+ if (*p == '"')
+ {
+ /* Embedded double quotes need to be doubled */
+ *q++ = '"';
+ INC_TOKEN_LEN_OR_BREAK;
+ *q = '"';
+ }
+ else
+ *q = *p;
+
+ q++;
+ p++;
+ INC_TOKEN_LEN_OR_BREAK;
+ }
+ *q++ = '"';
+ p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+ *q = 0;
+ return p;
+}
+
+/* Extract a Posix double quoted string from input line */
+static char *
+posix_parse_dq (struct token_info *token)
+{
+ /* Unix mode: Any imbedded \" becomes doubled.
+ \t is tab, \\, \$ leading character stripped.
+ $ character replaced with \' unless escaped.
+ */
+ char * q;
+ char * p;
+ q = token->text;
+ p = token->src;
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+ while (*p != 0)
+ {
+ if (*p == '\\')
+ {
+ switch(p[1])
+ {
+ case 't': /* Convert tabs */
+ *q = '\t';
+ p++;
+ break;
+ case '\\': /* Just remove leading backslash */
+ case '$':
+ p++;
+ *q = *p;
+ break;
+ case '"':
+ p++;
+ *q = *p;
+ *q++ = '"';
+ INC_TOKEN_LEN_OR_BREAK;
+ default: /* Pass through unchanged */
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_BREAK;
+ }
+ INC_TOKEN_LEN_OR_BREAK;
+ }
+ else if (*p == '$' && isalpha (p[1]))
+ {
+ /* A symbol we should be able to substitute */
+ *q++ = '\'';
+ INC_TOKEN_LEN_OR_BREAK;
+ *q = '\'';
+ INC_TOKEN_LEN_OR_BREAK;
+ token->use_cmd_file = 1;
+ }
+ else
+ {
+ *q = *p;
+ INC_TOKEN_LEN_OR_BREAK;
+ if (*p == '"')
+ {
+ p++;
+ q++;
+ break;
+ }
+ }
+ p++;
+ q++;
+ }
+ *q = 0;
+ return p;
+}
+
+/* Extract a VMS quoted string or substitution string from input line */
+static char *
+vms_parse_quotes (struct token_info *token)
+{
+ /* VMS mode, the \' means that a symbol substitution is starting
+ so while you might think you can just copy until the next
+ \'. Unfortunately the substitution can be a lexical function
+ which can contain embedded strings and lexical functions.
+ Messy, so both types need to be handled together.
+ */
+ char * q;
+ char * p;
+ q = token->text;
+ p = token->src;
+ int parse_level[MAX_DCL_TOKENS + 1];
+ int nest = 0;
+
+ parse_level[0] = *p;
+ if (parse_level[0] == '\'')
+ token->use_cmd_file = 1;
+
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+
+
+ /* Copy everything until after the next single quote at nest == 0 */
+ while (token->length < MAX_DCL_TOKEN_LENGTH)
+ {
+ EOS_ERROR (p);
+ *q = *p;
+ INC_TOKEN_LEN_OR_BREAK;
+ if ((*p == parse_level[nest]) && (p[1] != '"'))
+ {
+ if (nest == 0)
+ {
+ *q++ = *p++;
+ break;
+ }
+ nest--;
+ }
+ else
+ {
+ switch(*p)
+ {
+ case '\\':
+ /* Handle continuation on to next line */
+ if (p[1] != '\n')
+ break;
+ p++;
+ p++;
+ *q = *p;
+ break;
+ case '(':
+ /* Parenthesis only in single quote level */
+ if (parse_level[nest] == '\'')
+ {
+ nest++;
+ parse_level[nest] == ')';
+ }
+ break;
+ case '"':
+ /* Double quotes only in parenthesis */
+ if (parse_level[nest] == ')')
+ {
+ nest++;
+ parse_level[nest] == '"';
+ }
+ break;
+ case '\'':
+ /* Symbol substitution ony in double quotes */
+ if ((p[1] == '\'') && (parse_level[nest] == '"'))
+ {
+ nest++;
+ parse_level[nest] == '\'';
+ *p++ = *q++;
+ token->use_cmd_file = 1;
+ INC_TOKEN_LEN_OR_BREAK;
+ break;
+ }
+ *q = *p;
+ }
+ }
+ p++;
+ q++;
+ /* Pass through doubled double quotes */
+ if ((*p == '"') && (p[1] == '"') && (parse_level[nest] == '"'))
+ {
+ *p++ = *q++;
+ INC_TOKEN_LEN_OR_BREAK;
+ *p++ = *q++;
+ INC_TOKEN_LEN_OR_BREAK;
+ }
+ }
+ *q = 0;
+ return p;
+}
+
+/* Extract a $ string from the input line */
+static char *
+posix_parse_dollar (struct token_info *token)
+{
+ /* $foo becomes 'foo' */
+ char * q;
+ char * p;
+ q = token->text;
+ p = token->src;
+ token->use_cmd_file = 1;
+
+ p++;
+ *q++ = '\'';
+ INC_TOKEN_LEN_OR_RETURN (p);
+
+ while ((isalnum (*p)) || (*p == '_'))
+ {
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_BREAK;
+ }
+ *q++ = '\'';
+ while (1)
+ {
+ INC_TOKEN_LEN_OR_BREAK;
+ break;
+ }
+ *q = 0;
+ return p;
+}
+
+const char *vms_filechars = "0123456789abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[]<>:/_-.$";
+
+/* Simple text copy */
+static char *
+parse_text (struct token_info *token, int assignment_hack)
+{
+ char * q;
+ char * p;
+ int str_len;
+ q = token->text;
+ p = token->src;
+
+ /* If assignment hack, then this text needs to be double quoted. */
+ if (vms_unix_simulation && (assignment_hack == 2))
+ {
+ *q++ = '"';
+ INC_TOKEN_LEN_OR_RETURN (p);
+ }
+
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+
+ while (*p != 0)
+ {
+ str_len = strspn (p, vms_filechars);
+ if (str_len == 0)
+ {
+ /* Pass through backslash escapes in Unix simulation
+ probably will not work anyway.
+ All any character after a ^ otherwise to support EFS.
+ */
+ if (vms_unix_simulation && (p[0] == '\\') && (p[1] != 0))
+ str_len = 2;
+ else if ((p[0] == '^') && (p[1] != 0))
+ str_len = 2;
+ else if (!vms_unix_simulation && (p[0] == ';'))
+ str_len = 1;
+
+ if (str_len == 0)
+ {
+ /* If assignment hack, then this needs to be double quoted. */
+ if (vms_unix_simulation && (assignment_hack == 2))
+ {
+ *q++ = '"';
+ INC_TOKEN_LEN_OR_RETURN (p);
+ }
+ *q = 0;
+ return p;
+ }
+ }
+ if (str_len > 0)
+ {
+ ADD_TOKEN_LEN_OR_RETURN (str_len, p);
+ strncpy (q, p, str_len);
+ p += str_len;
+ q += str_len;
+ *q = 0;
+ }
+ }
+ /* If assignment hack, then this text needs to be double quoted. */
+ if (vms_unix_simulation && (assignment_hack == 2))
+ {
+ *q++ = '"';
+ INC_TOKEN_LEN_OR_RETURN (p);
+ }
+ return p;
+}
+
+/* single character copy */
+static char *
+parse_char (struct token_info *token, int count)
+{
+ char * q;
+ char * p;
+ q = token->text;
+ p = token->src;
+
+ while (count > 0)
+ {
+ *q++ = *p++;
+ INC_TOKEN_LEN_OR_RETURN (p);
+ count--;
+ }
+ *q = 0;
+ return p;
+}
+
+/* Build a command string from the collected tokens
+ and process built-ins now
+*/
+static struct dsc$descriptor_s *
+build_vms_cmd (char **cmd_tokens,
+ enum auto_pipe use_pipe_cmd,
+ int append_token)
+{
+ struct dsc$descriptor_s *cmd_dsc;
+ int cmd_tkn_index;
+ char * cmd;
+ int cmd_len;
+ int semicolon_seen;
+
+ cmd_tkn_index = 0;
+ cmd_dsc = xmalloc (sizeof (struct dsc$descriptor_s));
+
+ /* Empty command? */
+ if (cmd_tokens[0] == NULL)
+ {
+ cmd_dsc->dsc$a_pointer = NULL;
+ cmd_dsc->dsc$w_length = 0;
+ return cmd_dsc;
+ }
+
+ /* Max DCL command + 1 extra token and trailing space */
+ cmd = xmalloc (MAX_DCL_CMD_LINE_LENGTH + 256);
+
+ cmd[0] = '$';
+ cmd[1] = 0;
+ cmd_len = 1;
+
+ /* Handle real or auto-pipe */
+ if (use_pipe_cmd == add_pipe)
+ {
+ /* We need to auto convert to a pipe command */
+ strcat (cmd, "pipe ");
+ cmd_len += 5;
+ }
+
+ semicolon_seen = 0;
+ while (cmd_tokens[cmd_tkn_index] != NULL)
+ {
+
+ /* Check for buffer overflow */
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+
+ /* Eliminate double ';' */
+ if (semicolon_seen && (cmd_tokens[cmd_tkn_index][0] == ';'))
+ {
+ semicolon_seen = 0;
+ free (cmd_tokens[cmd_tkn_index++]);
+ if (cmd_tokens[cmd_tkn_index] == NULL)
+ break;
+ }
+
+ /* Special handling for CD built-in */
+ if (strncmp (cmd_tokens[cmd_tkn_index], "builtin_cd", 11) == 0)
+ {
+ int result;
+ semicolon_seen = 0;
+ free (cmd_tokens[cmd_tkn_index]);
+ cmd_tkn_index++;
+ if (cmd_tokens[cmd_tkn_index] == NULL)
+ break;
+ DB(DB_JOBS, (_("BUILTIN CD %s\n"), cmd_tokens[cmd_tkn_index]));
+
+ /* TODO: chdir fails with some valid syntaxes */
+ result = chdir (cmd_tokens[cmd_tkn_index]);
+ if (result != 0)
+ {
+ /* TODO: Handle failure better */
+ free (cmd);
+ while (cmd_tokens[cmd_tkn_index] == NULL)
+ free (cmd_tokens[cmd_tkn_index++]);
+ cmd_dsc->dsc$w_length = -1;
+ cmd_dsc->dsc$a_pointer = NULL;
+ return cmd_dsc;
+ }
+ }
+ else if (strncmp (cmd_tokens[cmd_tkn_index], "exit", 5) == 0)
+ {
+ /* Copy the exit command */
+ semicolon_seen = 0;
+ strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
+ cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
+ free (cmd_tokens[cmd_tkn_index++]);
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+
+ /* Optional whitespace */
+ if (isspace (cmd_tokens[cmd_tkn_index][0]))
+ {
+ strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
+ cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
+ free (cmd_tokens[cmd_tkn_index++]);
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+ }
+
+ /* There should be a status, but it is optional */
+ if (cmd_tokens[cmd_tkn_index][0] == ';')
+ continue;
+
+ /* If Unix simulation, add '((' */
+ if (vms_unix_simulation)
+ {
+ strcpy (&cmd[cmd_len], "((");
+ cmd_len += 2;
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+ }
+
+ /* Add the parameter */
+ strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
+ cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
+ free (cmd_tokens[cmd_tkn_index++]);
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+
+ /* Add " * 8) .and. %x7f8) .or. %x1035a002" */
+ if (vms_unix_simulation)
+ {
+ const char *end_str = " * 8) .and. %x7f8) .or. %x1035a002";
+ strcpy (&cmd[cmd_len], end_str);
+ cmd_len += strlen (end_str);
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+ }
+ continue;
+ }
+
+ /* auto pipe needs spaces before semicolon */
+ if (use_pipe_cmd == add_pipe)
+ if (cmd_tokens[cmd_tkn_index][0] == ';')
+ {
+ cmd[cmd_len++] = ' ';
+ semicolon_seen = 1;
+ if (cmd_len > MAX_DCL_CMD_LINE_LENGTH)
+ {
+ errno = E2BIG;
+ break;
+ }
+ }
+ else
+ {
+ char ch;
+ ch = cmd_tokens[cmd_tkn_index][0];
+ if (!(ch == ' ' || ch == '\t'))
+ semicolon_seen = 0;
+ }
+
+ strcpy (&cmd[cmd_len], cmd_tokens[cmd_tkn_index]);
+ cmd_len += strlen (cmd_tokens[cmd_tkn_index]);
+
+ free (cmd_tokens[cmd_tkn_index++]);
+
+ /* Skip the append tokens if they exist */
+ if (cmd_tkn_index == append_token)
+ {
+ free (cmd_tokens[cmd_tkn_index++]);
+ if (isspace (cmd_tokens[cmd_tkn_index][0]))
+ free (cmd_tokens[cmd_tkn_index++]);
+ free (cmd_tokens[cmd_tkn_index++]);
+ }
+ }
+
+ cmd[cmd_len] = 0;
+ cmd_dsc->dsc$w_length = cmd_len;
+ cmd_dsc->dsc$a_pointer = cmd;
+ cmd_dsc->dsc$b_dtype = DSC$K_DTYPE_T;
+ cmd_dsc->dsc$b_class = DSC$K_CLASS_S;
+
+ return cmd_dsc;
+}
+
+int
+child_execute_job (struct child *child, char *argv)
+{
+ int i;
+
+ static struct dsc$descriptor_s *cmd_dsc;
+ static struct dsc$descriptor_s pnamedsc;
+ int spflags = CLI$M_NOWAIT;
+ int status;
+ int comnamelen;
+ char procname[100];
+
+ char *p;
+ char *cmd_tokens[(MAX_DCL_TOKENS * 2) + 1]; /* whitespace does not count */
+ char token_str[MAX_DCL_TOKEN_LENGTH + 1];
+ struct token_info token;
+ int cmd_tkn_index;
+ int paren_level = 0;
+ enum auto_pipe use_pipe_cmd = nopipe;
+ int append_token = -1;
+ char *append_file = NULL;
+ int unix_echo_cmd = 0; /* Special handle Unix echo command */
+ int assignment_hack = 0; /* Handle x=y command as piped command */
+
+ /* Parse IO redirection. */
+
+ child->comname = NULL;
+
+ DB (DB_JOBS, ("child_execute_job (%s)\n", argv));
+
+ while (isspace ((unsigned char)*argv))
+ argv++;
+
+ if (*argv == 0)
+ {
+ /* Only a built-in or a null command - Still need to run term AST */
+ child->cstatus = VMS_POSIX_EXIT_MASK;
+ child->vms_launch_status = SS$_NORMAL;
+ /* TODO what is this "magic number" */
+ child->pid = 270163; /* Special built-in */
+ child->efn = 0;
+ vmsHandleChildTerm (child);
+ return 1;
+ }
+
+ sprintf (procname, "GMAKE_%05x", getpid () & 0xfffff);
+ pnamedsc.dsc$w_length = strlen (procname);
+ pnamedsc.dsc$a_pointer = procname;
+ pnamedsc.dsc$b_dtype = DSC$K_DTYPE_T;
+ pnamedsc.dsc$b_class = DSC$K_CLASS_S;
+
+ /* Old */
+ /* Handle comments and redirection.
+ For ONESHELL, the redirection must be on the first line. Any other
+ redirection token is handled by DCL, that is, the pipe command with
+ redirection can be used, but it should not be used on the first line
+ for ONESHELL. */
+
+ /* VMS parser notes:
+ 1. A token is any of DCL verbs, qualifiers, parameters, or punctuation.
+ 2. Only MAX_DCL_TOKENS per line in both one line or command file mode.
+ 3. Each token limited to MAC_DCL_TOKEN_LENGTH
+ 4. If the line to DCL is greater than MAX_DCL_LINE_LENGTH then a
+ command file must be used.
+ 5. Currently a command file must be used symbol substitution is to
+ be performed.
+ 6. Currently limiting command files to 2 * MAX_DCL_TOKENS.
+
+ Build both a command file token list and command line token list
+ until it is determined that the command line limits are exceeded.
+ */
+
+ cmd_tkn_index = 0;
+ cmd_tokens[cmd_tkn_index] = NULL;
+ p = argv;
+
+ token.text = token_str;
+ token.length = 0;
+ token.cmd_errno = 0;
+ token.use_cmd_file = 0;
+
+ while (*p != 0)
+ {
+ /* We can not build this command so give up */
+ if (token.cmd_errno != 0)
+ break;
+
+ token.src = p;
+
+ switch (*p)
+ {
+ case '\'':
+ if (vms_unix_simulation || unix_echo_cmd)
+ {
+ p = posix_parse_sq (&token);
+ UPDATE_TOKEN;
+ break;
+ }
+
+ /* VMS mode, the \' means that a symbol substitution is starting
+ so while you might think you can just copy until the next
+ \'. Unfortunately the substitution can be a lexical function
+ which can contain embedded strings and lexical functions.
+ Messy.
+ */
+ p = vms_parse_quotes (&token);
+ UPDATE_TOKEN;
+ break;
+ case '"':
+ if (vms_unix_simulation)
+ {
+ p = posix_parse_dq (&token);
+ UPDATE_TOKEN;
+ break;
+ }
+
+ /* VMS quoted string, can contain lexical functions with
+ quoted strings and nested lexical functions.
+ */
+ p = vms_parse_quotes (&token);
+ UPDATE_TOKEN;
+ break;
+
+ case '$':
+ if (vms_unix_simulation)
+ {
+ p = posix_parse_dollar (&token);
+ UPDATE_TOKEN;
+ break;
+ }
+
+ /* Otherwise nothing special */
+ p = parse_text (&token, 0);
+ UPDATE_TOKEN;
+ break;
+ case '\\':
+ if (p[1] == '\n')
+ {
+ /* Line continuation, remove it */
+ p += 2;
+ break;
+ }
+
+ /* Ordinary character otherwise */
+ if (assignment_hack != 0)
+ assignment_hack++;
+ if (assignment_hack > 2)
+ {
+ assignment_hack = 0; /* Reset */
+ if (use_pipe_cmd == nopipe) /* force pipe use */
+ use_pipe_cmd = add_pipe;
+ token_str[0] = ';'; /* add ; token */
+ token_str[1] = 0;
+ UPDATE_TOKEN;
+ }
+ p = parse_text (&token, assignment_hack);
+ UPDATE_TOKEN;
+ break;
+ case '!':
+ case '#':
+ /* Unix '#' is VMS '!' which comments out the rest of the line.
+ Historically the rest of the line has been skipped.
+ Not quite the right thing to do, as the f$verify lexical
+ function works in comments. But this helps keep the line
+ lengths short.
+ */
+ unix_echo_cmd = 0;
+ while (*p != '\n' && *p != 0)
+ p++;
+ break;
+ case '(':
+ /* Subshell, equation, or lexical function argument start */
+ p = parse_char (&token, 1);
+ UPDATE_TOKEN;
+ paren_level++;
+ break;
+ case ')':
+ /* Close out a paren level */
+ p = parse_char (&token, 1);
+ UPDATE_TOKEN;
+ paren_level--;
+ /* TODO: Should we diagnose if paren_level goes negative? */
+ break;
+ case '&':
+ if (isalpha (p[1]) && !vms_unix_simulation)
+ {
+ /* VMS symbol substitution */
+ p = parse_text (&token, 0);
+ token.use_cmd_file = 1;
+ UPDATE_TOKEN;
+ break;
+ }
+ if (use_pipe_cmd == nopipe)
+ use_pipe_cmd = add_pipe;
+ if (p[1] != '&')
+ p = parse_char (&token, 1);
+ else
+ p = parse_char (&token, 2);
+ UPDATE_TOKEN;
+ break;
+ case '|':
+ if (use_pipe_cmd == nopipe)
+ use_pipe_cmd = add_pipe;
+ if (p[1] != '|')
+ p = parse_char (&token, 1);
+ else
+ p = parse_char (&token, 2);
+ UPDATE_TOKEN;
+ break;
+ case ';':
+ /* Separator - convert to a pipe command. */
+ unix_echo_cmd = 0;
+ case '<':
+ if (use_pipe_cmd == nopipe)
+ use_pipe_cmd = add_pipe;
+ p = parse_char (&token, 1);
+ UPDATE_TOKEN;
+ break;
+ case '>':
+ if (use_pipe_cmd == nopipe)
+ use_pipe_cmd = add_pipe;
+ if (p[1] == '>')
+ {
+ /* Parsing would have been simple until support for the >>
+ append redirect was added.
+ Implementation needs:
+ * if not exist output file create empty
+ * open/append gnv$make_temp??? output_file
+ * define/user sys$output gnv$make_temp???
+ ** And all this done before the command previously tokenized.
+ * command previously tokenized
+ * close gnv$make_temp???
+ */
+ p = parse_char (&token, 2);
+ append_token = cmd_tkn_index;
+ token.use_cmd_file = 1;
+ }
+ else
+ p = parse_char (&token, 1);
+ UPDATE_TOKEN;
+ break;
+ case '/':
+ /* Unix path or VMS option start, read until non-path symbol */
+ if (assignment_hack != 0)
+ assignment_hack++;
+ if (assignment_hack > 2)
+ {
+ assignment_hack = 0; /* Reset */
+ if (use_pipe_cmd == nopipe) /* force pipe use */
+ use_pipe_cmd = add_pipe;
+ token_str[0] = ';'; /* add ; token */
+ token_str[1] = 0;
+ UPDATE_TOKEN;
+ }
+ p = parse_text (&token, assignment_hack);
+ UPDATE_TOKEN;
+ break;
+ case ':':
+ if ((p[1] == 0) || isspace (p[1]))
+ {
+ /* Unix Null command - treat as comment until next command */
+ unix_echo_cmd = 0;
+ p++;
+ while (*p != 0)
+ {
+ if (*p == ';')
+ {
+ /* Remove Null command from pipeline */
+ p++;
+ break;
+ }
+ p++;
+ }
+ break;
+ }
+
+ /* String assignment */
+ /* := :== or : */
+ if (p[1] != '=')
+ p = parse_char (&token, 1);
+ else if (p[2] != '=')
+ p = parse_char (&token, 2);
+ else
+ p = parse_char (&token, 3);
+ UPDATE_TOKEN;
+ break;
+ case '=':
+ /* = or == */
+ /* If this is not an echo statement, this could be a shell
+ assignment. VMS requires the target to be quoted if it
+ is not a macro substitution */
+ if (!unix_echo_cmd && vms_unix_simulation && (assignment_hack == 0))
+ assignment_hack = 1;
+ if (p[1] != '=')
+ p = parse_char (&token, 1);
+ else
+ p = parse_char (&token, 2);
+ UPDATE_TOKEN;
+ break;
+ case '+':
+ case '-':
+ case '*':
+ p = parse_char (&token, 1);
+ UPDATE_TOKEN;
+ break;
+ case '.':
+ /* .xxx. operation, VMS does not require the trailing . */
+ p = parse_text (&token, 0);
+ UPDATE_TOKEN;
+ break;
+ default:
+ /* Skip repetitive whitespace */
+ if (isspace (*p))
+ {
+ p = parse_char (&token, 1);
+
+ /* Force to a space or a tab */
+ if ((token_str[0] != ' ') ||
+ (token_str[0] != '\t'))
+ token_str[0] = ' ';
+ UPDATE_TOKEN;
+
+ while (isspace (*p))
+ p++;
+ if (assignment_hack != 0)
+ assignment_hack++;
+ break;
+ }
+
+ if (assignment_hack != 0)
+ assignment_hack++;
+ if (assignment_hack > 2)
+ {
+ assignment_hack = 0; /* Reset */
+ if (use_pipe_cmd == nopipe) /* force pipe use */
+ use_pipe_cmd = add_pipe;
+ token_str[0] = ';'; /* add ; token */
+ token_str[1] = 0;
+ UPDATE_TOKEN;
+ }
+ p = parse_text (&token, assignment_hack);
+ if (strncasecmp (token.text, "echo", 4) == 0)
+ unix_echo_cmd = 1;
+ else if (strncasecmp (token.text, "pipe", 4) == 0)
+ use_pipe_cmd = dcl_pipe;
+ UPDATE_TOKEN;
+ break;
+ }
+ }
+
+ /* End up here with a list of tokens to build a command line.
+ Deal with errors detected during parsing.
+ */
+ if (token.cmd_errno != 0)
+ {
+ while (cmd_tokens[cmd_tkn_index] == NULL)
+ free (cmd_tokens[cmd_tkn_index++]);
+ child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3);
+ child->vms_launch_status = SS$_ABORT;
+ /* TODO what is this "magic number" */
+ child->pid = 270163; /* Special built-in */
+ child->efn = 0;
+ errno = token.cmd_errno;
+ return 0;
+ }
+
+ /* Save any redirection to append file */
+ if (append_token != -1)
+ {
+ int file_token;
+ char * lastdot;
+ char * lastdir;
+ char * raw_append_file;
+ file_token = append_token;
+ file_token++;
+ if (isspace (cmd_tokens[file_token][0]))
+ file_token++;
+ raw_append_file = vmsify (cmd_tokens[file_token], 0);
+ /* VMS DCL needs a trailing dot if null file extension */
+ lastdot = strrchr(raw_append_file, '.');
+ lastdir = strrchr(raw_append_file, ']');
+ if (lastdir == NULL)
+ lastdir = strrchr(raw_append_file, '>');
+ if (lastdir == NULL)
+ lastdir = strrchr(raw_append_file, ':');
+ if ((lastdot == NULL) || (lastdot > lastdir))
+ {
+ append_file = xmalloc (strlen (raw_append_file) + 1);
+ strcpy (append_file, raw_append_file);
+ strcat (append_file, ".");
+ }
+ else
+ append_file = strdup(raw_append_file);
+ }
+
+ cmd_dsc = build_vms_cmd (cmd_tokens, use_pipe_cmd, append_token);
+ if (cmd_dsc->dsc$a_pointer == NULL)
+ {
+ if (cmd_dsc->dsc$w_length < 0)
+ {
+ free (cmd_dsc);
+ child->cstatus = VMS_POSIX_EXIT_MASK | (MAKE_TROUBLE << 3);
+ child->vms_launch_status = SS$_ABORT;
+ /* TODO what is this "magic number" */
+ child->pid = 270163; /* Special built-in */
+ child->efn = 0;
+ return 0;
+ }
+
+ /* Only a built-in or a null command - Still need to run term AST */
+ free (cmd_dsc);
+ child->cstatus = VMS_POSIX_EXIT_MASK;
+ child->vms_launch_status = SS$_NORMAL;
+ /* TODO what is this "magic number" */
+ child->pid = 270163; /* Special built-in */
+ child->efn = 0;
+ vmsHandleChildTerm (child);
+ return 1;
+ }
+
+ if (cmd_dsc->dsc$w_length > MAX_DCL_LINE_LENGTH)
+ token.use_cmd_file = 1;
+
+ DB(DB_JOBS, (_("DCL: %s\n"), cmd_dsc->dsc$a_pointer));
+
+ /* Enforce the creation of a command file if "vms_always_use_cmd_file" is
+ non-zero.
+ Further, this way DCL reads the input stream and therefore does
+ 'forced' symbol substitution, which it doesn't do for one-liners when
+ they are 'lib$spawn'ed.
+
+ Otherwise the behavior is:
+
+ Create a *.com file if either the command is too long for
+ lib$spawn, or if a redirect appending to a file is desired, or
+ symbol substitition.
+ */
+
+ if (vms_always_use_cmd_file || token.use_cmd_file)
+ {
+ FILE *outfile;
+ int cmd_len;
+
+ outfile = output_tmpfile (&child->comname,
+ "sys$scratch:gnv$make_cmdXXXXXX.com");
+ /* 012345678901234567890 */
+ if (outfile == 0)
+ pfatal_with_name (_("fopen (temporary file)"));
+ comnamelen = strlen (child->comname);
+
+ /* The whole DCL "script" is executed as one action, and it behaves as
+ any DCL "script", that is errors stop it but warnings do not. Usually
+ the command on the last line, defines the exit code. However, with
+ redirections there is a prolog and possibly an epilog to implement
+ the redirection. Both are part of the script which is actually
+ executed. So if the redirection encounters an error in the prolog,
+ the user actions will not run; if in the epilog, the user actions
+ ran, but output is not captured. In both error cases, the error of
+ redirection is passed back and not the exit code of the actions. The
+ user should be able to enable DCL "script" verification with "set
+ verify". However, the prolog and epilog commands are not shown. Also,
+ if output redirection is used, the verification output is redirected
+ into that file as well. */
+ fprintf (outfile, "$ gnv$$make_verify = \"''f$verify(0)'\"\n");
+ fprintf (outfile, "$ gnv$$make_pid = f$getjpi(\"\",\"pid\")\n");
+ fprintf (outfile, "$ on error then $ goto gnv$$make_error\n");
+
+ /* Handle append redirection */
+ if (append_file != NULL)
+ {
+ /* If file does not exist, create it */
+ fprintf (outfile,
+ "$ gnv$$make_al = \"gnv$$make_append''gnv$$make_pid'\"\n");
+ fprintf (outfile,
+ "$ if f$search(\"%s\") .eqs. \"\" then create %s\n",
+ append_file, append_file);
+
+ fprintf (outfile,
+ "$ open/append 'gnv$$make_al' %s\n", append_file);
+
+ /* define sys$output to that file */
+ fprintf (outfile,
+ "$ define/user sys$output 'gnv$$make_al'\n");
+ DB (DB_JOBS, (_("Append output to %s\n"), append_file));
+ free(append_file);
+ }
+
+ fprintf (outfile, "$ gnv$$make_verify = f$verify(gnv$$make_verify)\n");
+
+ /* TODO:
+ Only for ONESHELL there will be several commands separated by
+ '\n'. But there can always be multiple continuation lines.
+ */
+
+ fprintf (outfile, "%s\n", cmd_dsc->dsc$a_pointer);
+ fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
+ fprintf (outfile, "$ goto gnv$$make_exit\n");
+
+ /* Exit and clean up */
+ fprintf (outfile, "$ gnv$$make_error: ! 'f$verify(0)\n");
+ fprintf (outfile, "$ gnv$$make_status_2 = $status\n");
+
+ if (append_token != -1)
+ {
+ fprintf (outfile, "$ deassign sys$output\n");
+ fprintf (outfile, "$ close 'gnv$$make_al'\n");
+
+ DB (DB_JOBS,
+ (_("Append %.*s and cleanup\n"), comnamelen-3, child->comname));
+ }
+ fprintf (outfile, "$ gnv$$make_exit: ! 'f$verify(0)\n");
+ fprintf (outfile,
+ "$ exit 'gnv$$make_status_2' + (0*f$verify(gnv$$make_verify))\n");
+
+ fclose (outfile);
+
+ free (cmd_dsc->dsc$a_pointer);
+ cmd_dsc->dsc$a_pointer = xmalloc (256 + 4);
+ sprintf (cmd_dsc->dsc$a_pointer, "$ @%s", child->comname);
+ cmd_dsc->dsc$w_length = strlen (cmd_dsc->dsc$a_pointer);
+
+ DB (DB_JOBS, (_("Executing %s instead\n"), child->comname));
+ }
+
+ child->efn = 0;
+ while (child->efn < 32 || child->efn > 63)
+ {
+ status = LIB$GET_EF ((unsigned long *)&child->efn);
+ if (!$VMS_STATUS_SUCCESS (status))
+ {
+ if (child->comname)
+ {
+ if (!ISDB (DB_JOBS))
+ unlink (child->comname);
+ free (child->comname);
+ }
+ return 0;
+ }
+ }
+
+ SYS$CLREF (child->efn);
+
+ vms_jobsefnmask |= (1 << (child->efn - 32));
+
+ /* Export the child environment into DCL symbols */
+ if (child->environment != 0)
+ {
+ char **ep = child->environment;
+ while (*ep != 0)
+ {
+ vms_putenv_symbol (*ep);
+ *ep++;
+ }
+ }
+
+ /*
+ LIB$SPAWN [command-string]
+ [,input-file]
+ [,output-file]
+ [,flags]
+ [,process-name]
+ [,process-id] [,completion-status-address] [,byte-integer-event-flag-num]
+ [,AST-address] [,varying-AST-argument]
+ [,prompt-string] [,cli] [,table]
+ */
+
+#ifndef DONTWAITFORCHILD
+ /*
+ * Code to make ctrl+c and ctrl+y working.
+ * The problem starts with the synchronous case where after lib$spawn is
+ * called any input will go to the child. But with input re-directed,
+ * both control characters won't make it to any of the programs, neither
+ * the spawning nor to the spawned one. Hence the caller needs to spawn
+ * with CLI$M_NOWAIT to NOT give up the input focus. A sys$waitfr
+ * has to follow to simulate the wanted synchronous behaviour.
+ * The next problem is ctrl+y which isn't caught by the crtl and
+ * therefore isn't converted to SIGQUIT (for a signal handler which is
+ * already established). The only way to catch ctrl+y, is an AST
+ * assigned to the input channel. But ctrl+y handling of DCL needs to be
+ * disabled, otherwise it will handle it. Not to mention the previous
+ * ctrl+y handling of DCL needs to be re-established before make exits.
+ * One more: At the time of LIB$SPAWN signals are blocked. SIGQUIT will
+ * make it to the signal handler after the child "normally" terminates.
+ * This isn't enough. It seems reasonable for simple command lines like
+ * a 'cc foobar.c' spawned in a subprocess but it is unacceptable for
+ * spawning make. Therefore we need to abort the process in the AST.
+ *
+ * Prior to the spawn it is checked if an AST is already set up for
+ * ctrl+y, if not one is set up for a channel to SYS$COMMAND. In general
+ * this will work except if make is run in a batch environment, but there
+ * nobody can press ctrl+y. During the setup the DCL handling of ctrl+y
+ * is disabled and an exit handler is established to re-enable it.
+ * If the user interrupts with ctrl+y, the assigned AST will fire, force
+ * an abort to the subprocess and signal SIGQUIT, which will be caught by
+ * the already established handler and will bring us back to common code.
+ * After the spawn (now /nowait) a sys$waitfr simulates the /wait and
+ * enables the ctrl+y be delivered to this code. And the ctrl+c too,
+ * which the crtl converts to SIGINT and which is caught by the common
+ * signal handler. Because signals were blocked before entering this code
+ * sys$waitfr will always complete and the SIGQUIT will be processed after
+ * it (after termination of the current block, somewhere in common code).
+ * And SIGINT too will be delayed. That is ctrl+c can only abort when the
+ * current command completes. Anyway it's better than nothing :-)
+ */
+
+ if (!setupYAstTried)
+ tryToSetupYAst();
+ child->vms_launch_status = lib$spawn (cmd_dsc, /* cmd-string */
+ NULL, /* input-file */
+ NULL, /* output-file */
+ &spflags, /* flags */
+ &pnamedsc, /* proc name */
+ &child->pid, &child->cstatus, &child->efn,
+ 0, 0,
+ 0, 0, 0);
+
+ status = child->vms_launch_status;
+ if ($VMS_STATUS_SUCCESS (status))
+ {
+ status = sys$waitfr (child->efn);
+ vmsHandleChildTerm (child);
+ }
+#else
+ child->vms_launch_status = lib$spawn (cmd_dsc,
+ NULL,
+ NULL,
+ &spflags,
+ &pnamedsc,
+ &child->pid, &child->cstatus, &child->efn,
+ vmsHandleChildTerm, child,
+ 0, 0, 0);
+ status = child->vms_launch_status;
+#endif
+
+ /* Free the pointer if not a command file */
+ if (!vms_always_use_cmd_file && !token.use_cmd_file)
+ free (cmd_dsc->dsc$a_pointer);
+ free (cmd_dsc);
+
+ if (!$VMS_STATUS_SUCCESS (status))
+ {
+ switch (status)
+ {
+ case SS$_EXQUOTA:
+ errno = EPROCLIM;
+ break;
+ default:
+ errno = EFAIL;
+ }
+ }
+
+ /* Restore the VMS symbols that were changed */
+ if (child->environment != 0)
+ {
+ char **ep = child->environment;
+ while (*ep != 0)
+ {
+ vms_restore_symbol (*ep);
+ *ep++;
+ }
+ }
+
+ return (status & 1);
+}
diff --git a/src/kmk/vpath.c b/src/kmk/vpath.c
new file mode 100644
index 0000000..ed70208
--- /dev/null
+++ b/src/kmk/vpath.c
@@ -0,0 +1,647 @@
+/* Implementation of pattern-matching file search paths for GNU Make.
+Copyright (C) 1988-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include "filedef.h"
+#include "variable.h"
+#ifdef WINDOWS32
+#include "pathstuff.h"
+#endif
+
+
+/* Structure used to represent a selective VPATH searchpath. */
+
+struct vpath
+ {
+ struct vpath *next; /* Pointer to next struct in the linked list. */
+ const char *pattern;/* The pattern to match. */
+ const char *percent;/* Pointer into 'pattern' where the '%' is. */
+ unsigned int patlen;/* Length of the pattern. */
+ const char **searchpath; /* Null-terminated list of directories. */
+ unsigned int maxlen;/* Maximum length of any entry in the list. */
+ };
+
+/* Linked-list of all selective VPATHs. */
+
+static struct vpath *vpaths;
+
+/* Structure for the general VPATH given in the variable. */
+
+static struct vpath *general_vpath;
+
+/* Structure for GPATH given in the variable. */
+
+static struct vpath *gpaths;
+
+
+/* Reverse the chain of selective VPATH lists so they will be searched in the
+ order given in the makefiles and construct the list from the VPATH
+ variable. */
+
+void
+build_vpath_lists (void)
+{
+ register struct vpath *new = 0;
+ register struct vpath *old, *nexto;
+ register char *p;
+#ifdef CONFIG_WITH_OPTIMIZATION_HACKS
+ char expr[64];
+#endif
+
+ /* Reverse the chain. */
+ for (old = vpaths; old != 0; old = nexto)
+ {
+ nexto = old->next;
+ old->next = new;
+ new = old;
+ }
+
+ vpaths = new;
+
+ /* If there is a VPATH variable with a nonnull value, construct the
+ general VPATH list from it. We use variable_expand rather than just
+ calling lookup_variable so that it will be recursively expanded. */
+
+ {
+ /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 0;
+#ifdef CONFIG_WITH_OPTIMIZATION_HACKS
+ p = variable_expand (strcpy (expr, "$(strip $(VPATH))"));
+#else
+ p = variable_expand ("$(strip $(VPATH))");
+#endif
+
+ warn_undefined_variables_flag = save;
+ }
+
+ if (*p != '\0')
+ {
+ /* Save the list of vpaths. */
+ struct vpath *save_vpaths = vpaths;
+ char gp[] = "%";
+
+ /* Empty 'vpaths' so the new one will have no next, and 'vpaths'
+ will still be nil if P contains no existing directories. */
+ vpaths = 0;
+
+ /* Parse P. */
+ construct_vpath_list (gp, p);
+
+ /* Store the created path as the general path,
+ and restore the old list of vpaths. */
+ general_vpath = vpaths;
+ vpaths = save_vpaths;
+ }
+
+ /* If there is a GPATH variable with a nonnull value, construct the
+ GPATH list from it. We use variable_expand rather than just
+ calling lookup_variable so that it will be recursively expanded. */
+
+ {
+ /* Turn off --warn-undefined-variables while we expand SHELL and IFS. */
+ int save = warn_undefined_variables_flag;
+ warn_undefined_variables_flag = 0;
+
+#ifdef CONFIG_WITH_OPTIMIZATION_HACKS
+ p = variable_expand (strcpy (expr, "$(strip $(GPATH))"));
+#else
+ p = variable_expand ("$(strip $(GPATH))");
+#endif
+
+ warn_undefined_variables_flag = save;
+ }
+
+ if (*p != '\0')
+ {
+ /* Save the list of vpaths. */
+ struct vpath *save_vpaths = vpaths;
+ char gp[] = "%";
+
+ /* Empty 'vpaths' so the new one will have no next, and 'vpaths'
+ will still be nil if P contains no existing directories. */
+ vpaths = 0;
+
+ /* Parse P. */
+ construct_vpath_list (gp, p);
+
+ /* Store the created path as the GPATH,
+ and restore the old list of vpaths. */
+ gpaths = vpaths;
+ vpaths = save_vpaths;
+ }
+}
+
+/* Construct the VPATH listing for the PATTERN and DIRPATH given.
+
+ This function is called to generate selective VPATH lists and also for
+ the general VPATH list (which is in fact just a selective VPATH that
+ is applied to everything). The returned pointer is either put in the
+ linked list of all selective VPATH lists or in the GENERAL_VPATH
+ variable.
+
+ If DIRPATH is nil, remove all previous listings with the same
+ pattern. If PATTERN is nil, remove all VPATH listings. Existing
+ and readable directories that are not "." given in the DIRPATH
+ separated by the path element separator (defined in makeint.h) are
+ loaded into the directory hash table if they are not there already
+ and put in the VPATH searchpath for the given pattern with trailing
+ slashes stripped off if present (and if the directory is not the
+ root, "/"). The length of the longest entry in the list is put in
+ the structure as well. The new entry will be at the head of the
+ VPATHS chain. */
+
+void
+construct_vpath_list (char *pattern, char *dirpath)
+{
+ unsigned int elem;
+ char *p;
+ const char **vpath;
+ unsigned int maxvpath;
+ unsigned int maxelem;
+ const char *percent = NULL;
+
+ if (pattern != 0)
+ percent = find_percent (pattern);
+
+ if (dirpath == 0)
+ {
+ /* Remove matching listings. */
+ struct vpath *path, *lastpath;
+
+ lastpath = 0;
+ path = vpaths;
+ while (path != 0)
+ {
+ struct vpath *next = path->next;
+
+ if (pattern == 0
+ || (((percent == 0 && path->percent == 0)
+ || (percent - pattern == path->percent - path->pattern))
+ && streq (pattern, path->pattern)))
+ {
+ /* Remove it from the linked list. */
+ if (lastpath == 0)
+ vpaths = path->next;
+ else
+ lastpath->next = next;
+
+ /* Free its unused storage. */
+ /* MSVC erroneously warns without a cast here. */
+ free ((void *)path->searchpath);
+ free (path);
+ }
+ else
+ lastpath = path;
+
+ path = next;
+ }
+
+ return;
+ }
+
+#ifdef WINDOWS32
+ convert_vpath_to_windows32 (dirpath, ';');
+#endif
+
+ /* Skip over any initial separators and blanks. */
+ while (STOP_SET (*dirpath, MAP_BLANK|MAP_PATHSEP))
+ ++dirpath;
+
+ /* Figure out the maximum number of VPATH entries and put it in
+ MAXELEM. We start with 2, one before the first separator and one
+ nil (the list terminator) and increment our estimated number for
+ each separator or blank we find. */
+ maxelem = 2;
+ p = dirpath;
+ while (*p != '\0')
+ if (STOP_SET (*p++, MAP_BLANK|MAP_PATHSEP))
+ ++maxelem;
+
+ vpath = xmalloc (maxelem * sizeof (const char *));
+ maxvpath = 0;
+
+ elem = 0;
+ p = dirpath;
+ while (*p != '\0')
+ {
+ char *v;
+ unsigned int len;
+
+ /* Find the end of this entry. */
+ v = p;
+ while (*p != '\0'
+#if defined(HAVE_DOS_PATHS) && (PATH_SEPARATOR_CHAR == ':')
+ /* Platforms whose PATH_SEPARATOR_CHAR is ':' and which
+ also define HAVE_DOS_PATHS would like us to recognize
+ colons after the drive letter in the likes of
+ "D:/foo/bar:C:/xyzzy". */
+ && (*p != PATH_SEPARATOR_CHAR
+ || (p == v + 1 && (p[1] == '/' || p[1] == '\\')))
+#else
+ && *p != PATH_SEPARATOR_CHAR
+#endif
+ && !ISBLANK (*p))
+ ++p;
+
+ len = p - v;
+ /* Make sure there's no trailing slash,
+ but still allow "/" as a directory. */
+#if defined(__MSDOS__) || defined(__EMX__) || defined(HAVE_DOS_PATHS)
+ /* We need also to leave alone a trailing slash in "d:/". */
+ if (len > 3 || (len > 1 && v[1] != ':'))
+#endif
+ if (len > 1 && p[-1] == '/')
+ --len;
+
+ /* Put the directory on the vpath list. */
+ if (len > 1 || *v != '.')
+ {
+ vpath[elem++] = dir_name (strcache_add_len (v, len));
+ if (len > maxvpath)
+ maxvpath = len;
+ }
+
+ /* Skip over separators and blanks between entries. */
+ while (STOP_SET (*p, MAP_BLANK|MAP_PATHSEP))
+ ++p;
+ }
+
+ if (elem > 0)
+ {
+ struct vpath *path;
+ /* ELEM is now incremented one element past the last
+ entry, to where the nil-pointer terminator goes.
+ Usually this is maxelem - 1. If not, shrink down. */
+ if (elem < (maxelem - 1))
+ vpath = xrealloc ((void *)vpath, (elem+1) * sizeof (const char *)); /* bird: Weird cast for msc. */
+
+ /* Put the nil-pointer terminator on the end of the VPATH list. */
+ vpath[elem] = NULL;
+
+ /* Construct the vpath structure and put it into the linked list. */
+ path = xmalloc (sizeof (struct vpath));
+ path->searchpath = vpath;
+ path->maxlen = maxvpath;
+ path->next = vpaths;
+ vpaths = path;
+
+ /* Set up the members. */
+ path->pattern = strcache_add (pattern);
+ path->patlen = strlen (pattern);
+ path->percent = percent ? path->pattern + (percent - pattern) : 0;
+ }
+ else
+ /* There were no entries, so free whatever space we allocated. */
+ /* MSVC erroneously warns without a cast here. */
+ free ((void *)vpath);
+}
+
+/* Search the GPATH list for a pathname string that matches the one passed
+ in. If it is found, return 1. Otherwise we return 0. */
+
+int
+gpath_search (const char *file, unsigned int len)
+{
+ if (gpaths && (len <= gpaths->maxlen))
+ {
+ const char **gp;
+ for (gp = gpaths->searchpath; *gp != NULL; ++gp)
+ if (strneq (*gp, file, len) && (*gp)[len] == '\0')
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Search the given VPATH list for a directory where the name pointed to by
+ FILE exists. If it is found, we return a cached name of the existing file
+ and set *MTIME_PTR (if MTIME_PTR is not NULL) to its modtime (or zero if no
+ stat call was done). Also set the matching directory index in PATH_INDEX
+ if it is not NULL. Otherwise we return NULL. */
+
+static const char *
+selective_vpath_search (struct vpath *path, const char *file,
+ FILE_TIMESTAMP *mtime_ptr, unsigned int* path_index)
+{
+ int not_target;
+ char *name;
+ const char *n;
+ const char *filename;
+ const char **vpath = path->searchpath;
+ unsigned int maxvpath = path->maxlen;
+ unsigned int i;
+ unsigned int flen, name_dplen;
+ int exists = 0;
+
+ /* Find out if *FILE is a target.
+ If and only if it is NOT a target, we will accept prospective
+ files that don't exist but are mentioned in a makefile. */
+ {
+ struct file *f = lookup_file (file);
+ not_target = f == 0 || !f->is_target;
+ }
+
+ flen = strlen (file);
+
+ /* Split *FILE into a directory prefix and a name-within-directory.
+ NAME_DPLEN gets the length of the prefix; FILENAME gets the pointer to
+ the name-within-directory and FLEN is its length. */
+
+ n = strrchr (file, '/');
+#ifdef HAVE_DOS_PATHS
+ /* We need the rightmost slash or backslash. */
+ {
+ const char *bslash = strrchr (file, '\\');
+ if (!n || bslash > n)
+ n = bslash;
+ }
+#endif
+ name_dplen = n != 0 ? n - file : 0;
+ filename = name_dplen > 0 ? n + 1 : file;
+ if (name_dplen > 0)
+ flen -= name_dplen + 1;
+
+ /* Get enough space for the biggest VPATH entry, a slash, the directory
+ prefix that came with FILE, another slash (although this one may not
+ always be necessary), the filename, and a null terminator. */
+ name = alloca (maxvpath + 1 + name_dplen + 1 + flen + 1);
+
+ /* Try each VPATH entry. */
+ for (i = 0; vpath[i] != 0; ++i)
+ {
+ int exists_in_cache = 0;
+ char *p = name;
+ unsigned int vlen = strlen (vpath[i]);
+
+ /* Put the next VPATH entry into NAME at P and increment P past it. */
+ memcpy (p, vpath[i], vlen);
+ p += vlen;
+
+ /* Add the directory prefix already in *FILE. */
+ if (name_dplen > 0)
+ {
+#ifndef VMS
+ *p++ = '/';
+#else
+ /* VMS: if this is not in VMS format, treat as Unix format */
+ if ((*p != ':') && (*p != ']') && (*p != '>'))
+ *p++ = '/';
+#endif
+ memcpy (p, file, name_dplen);
+ p += name_dplen;
+ }
+
+#ifdef HAVE_DOS_PATHS
+ /* Cause the next if to treat backslash and slash alike. */
+ if (p != name && p[-1] == '\\' )
+ p[-1] = '/';
+#endif
+ /* Now add the name-within-directory at the end of NAME. */
+#ifndef VMS
+ if (p != name && p[-1] != '/')
+ {
+ *p = '/';
+ memcpy (p + 1, filename, flen + 1);
+ }
+ else
+#else
+ /* VMS use a slash if no directory terminator present */
+ if (p != name && p[-1] != '/' && p[-1] != ':' &&
+ p[-1] != '>' && p[-1] != ']')
+ {
+ *p = '/';
+ memcpy (p + 1, filename, flen + 1);
+ }
+ else
+#endif
+ memcpy (p, filename, flen + 1);
+
+ /* Check if the file is mentioned in a makefile. If *FILE is not
+ a target, that is enough for us to decide this file exists.
+ If *FILE is a target, then the file must be mentioned in the
+ makefile also as a target to be chosen.
+
+ The restriction that *FILE must not be a target for a
+ makefile-mentioned file to be chosen was added by an
+ inadequately commented change in July 1990; I am not sure off
+ hand what problem it fixes.
+
+ In December 1993 I loosened this restriction to allow a file
+ to be chosen if it is mentioned as a target in a makefile. This
+ seem logical.
+
+ Special handling for -W / -o: make sure we preserve the special
+ values here. Actually this whole thing is a little bogus: I think
+ we should ditch the name/hname thing and look into the renamed
+ capability that already exists for files: that is, have a new struct
+ file* entry for the VPATH-found file, and set the renamed field if
+ we use it.
+ */
+ {
+ struct file *f = lookup_file (name);
+ if (f != 0)
+ {
+ exists = not_target || f->is_target;
+ if (exists && mtime_ptr
+ && (f->last_mtime == OLD_MTIME || f->last_mtime == NEW_MTIME))
+ {
+ *mtime_ptr = f->last_mtime;
+ mtime_ptr = 0;
+ }
+ }
+ }
+
+ if (!exists)
+ {
+ /* That file wasn't mentioned in the makefile.
+ See if it actually exists. */
+
+#ifdef VMS
+ /* For VMS syntax just use the original vpath */
+ if (*p != '/')
+ exists_in_cache = exists = dir_file_exists_p (vpath[i], filename);
+ else
+#endif
+ {
+ /* Clobber a null into the name at the last slash.
+ Now NAME is the name of the directory to look in. */
+ *p = '\0';
+ /* We know the directory is in the hash table now because either
+ construct_vpath_list or the code just above put it there.
+ Does the file we seek exist in it? */
+ exists_in_cache = exists = dir_file_exists_p (name, filename);
+ }
+ }
+
+ if (exists)
+ {
+ /* The file is in the directory cache.
+ Now check that it actually exists in the filesystem.
+ The cache may be out of date. When vpath thinks a file
+ exists, but stat fails for it, confusion results in the
+ higher levels. */
+
+ struct stat st;
+
+#ifndef VMS
+ /* Put the slash back in NAME. */
+ *p = '/';
+#else
+ /* If the slash was removed, put it back */
+ if (*p == 0)
+ *p = '/';
+#endif
+
+ if (exists_in_cache) /* Makefile-mentioned file need not exist. */
+ {
+ int e;
+
+ EINTRLOOP (e, stat (name, &st)); /* Does it really exist? */
+ if (e != 0)
+ {
+ exists = 0;
+ continue;
+ }
+
+ /* Store the modtime into *MTIME_PTR for the caller. */
+ if (mtime_ptr != 0)
+ {
+ *mtime_ptr = FILE_TIMESTAMP_STAT_MODTIME (name, st);
+ mtime_ptr = 0;
+ }
+ }
+
+ /* We have found a file.
+ If we get here and mtime_ptr hasn't been set, record
+ UNKNOWN_MTIME to indicate this. */
+ if (mtime_ptr != 0)
+ *mtime_ptr = UNKNOWN_MTIME;
+
+ /* Store the name we found and return it. */
+
+ if (path_index)
+ *path_index = i;
+
+ return strcache_add_len (name, (p + 1 - name) + flen);
+ }
+ }
+
+ return 0;
+}
+
+
+/* Search the VPATH list whose pattern matches FILE for a directory where FILE
+ exists. If it is found, return the cached name of an existing file, and
+ set *MTIME_PTR (if MTIME_PTR is not NULL) to its modtime (or zero if no
+ stat call was done). Also set the matching directory index in VPATH_INDEX
+ and PATH_INDEX if they are not NULL. Otherwise we return 0. */
+
+const char *
+vpath_search (const char *file, FILE_TIMESTAMP *mtime_ptr,
+ unsigned int* vpath_index, unsigned int* path_index)
+{
+ struct vpath *v;
+
+ /* If there are no VPATH entries or FILENAME starts at the root,
+ there is nothing we can do. */
+
+ if (file[0] == '/'
+#ifdef HAVE_DOS_PATHS
+ || file[0] == '\\' || file[1] == ':'
+#endif
+ || (vpaths == 0 && general_vpath == 0))
+ return 0;
+
+ if (vpath_index)
+ {
+ *vpath_index = 0;
+ *path_index = 0;
+ }
+
+ for (v = vpaths; v != 0; v = v->next)
+ {
+ if (pattern_matches (v->pattern, v->percent, file))
+ {
+ const char *p = selective_vpath_search (
+ v, file, mtime_ptr, path_index);
+ if (p)
+ return p;
+ }
+
+ if (vpath_index)
+ ++*vpath_index;
+ }
+
+
+ if (general_vpath != 0)
+ {
+ const char *p = selective_vpath_search (
+ general_vpath, file, mtime_ptr, path_index);
+ if (p)
+ return p;
+ }
+
+ return 0;
+}
+
+
+
+
+/* Print the data base of VPATH search paths. */
+
+void
+print_vpath_data_base (void)
+{
+ unsigned int nvpaths;
+ struct vpath *v;
+
+ puts (_("\n# VPATH Search Paths\n"));
+
+ nvpaths = 0;
+ for (v = vpaths; v != 0; v = v->next)
+ {
+ register unsigned int i;
+
+ ++nvpaths;
+
+ printf ("vpath %s ", v->pattern);
+
+ for (i = 0; v->searchpath[i] != 0; ++i)
+ printf ("%s%c", v->searchpath[i],
+ v->searchpath[i + 1] == 0 ? '\n' : PATH_SEPARATOR_CHAR);
+ }
+
+ if (vpaths == 0)
+ puts (_("# No 'vpath' search paths."));
+ else
+ printf (_("\n# %u 'vpath' search paths.\n"), nvpaths);
+
+ if (general_vpath == 0)
+ puts (_("\n# No general ('VPATH' variable) search path."));
+ else
+ {
+ const char **path = general_vpath->searchpath;
+ unsigned int i;
+
+ fputs (_("\n# General ('VPATH' variable) search path:\n# "), stdout);
+
+ for (i = 0; path[i] != 0; ++i)
+ printf ("%s%c", path[i],
+ path[i + 1] == 0 ? '\n' : PATH_SEPARATOR_CHAR);
+ }
+}
diff --git a/src/kmk/w32/Makefile.am b/src/kmk/w32/Makefile.am
new file mode 100644
index 0000000..5527f77
--- /dev/null
+++ b/src/kmk/w32/Makefile.am
@@ -0,0 +1,26 @@
+# Makefile.am to create libw32.a for mingw32 host.
+# Copyright (C) 1997-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+AUTOMAKE_OPTIONS = subdir-objects
+
+noinst_LIBRARIES = libw32.a
+
+libw32_a_SOURCES = subproc/misc.c subproc/sub_proc.c subproc/w32err.c \
+ compat/posixfcn.c pathstuff.c w32os.c
+
+libw32_a_CPPFLAGS = -I$(srcdir)/include -I$(srcdir)/subproc -I$(top_srcdir) \
+ -I$(top_srcdir)/glob
diff --git a/src/kmk/w32/Makefile.kup b/src/kmk/w32/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kmk/w32/Makefile.kup
diff --git a/src/kmk/w32/compat/Makefile.kup b/src/kmk/w32/compat/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kmk/w32/compat/Makefile.kup
diff --git a/src/kmk/w32/compat/dirent.c b/src/kmk/w32/compat/dirent.c
new file mode 100644
index 0000000..9f992ec
--- /dev/null
+++ b/src/kmk/w32/compat/dirent.c
@@ -0,0 +1,212 @@
+/* Directory entry code for Window platforms.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include <config.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef KMK_PRF
+# include <stdio.h>
+#endif
+#include "dirent.h"
+
+
+DIR*
+opendir(const char* pDirName)
+{
+ struct stat sb;
+ DIR* pDir;
+ char* pEndDirName;
+ int nBufferLen;
+
+ /* sanity checks */
+ if (!pDirName) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (stat(pDirName, &sb) != 0) {
+ errno = ENOENT;
+ return NULL;
+ }
+ if ((sb.st_mode & S_IFMT) != S_IFDIR) {
+ errno = ENOTDIR;
+ return NULL;
+ }
+
+ /* allocate a DIR structure to return */
+ pDir = (DIR *) malloc(sizeof (DIR));
+
+ if (!pDir)
+ return NULL;
+
+ /* input directory name length */
+ nBufferLen = strlen(pDirName);
+
+ /* copy input directory name to DIR buffer */
+ strcpy(pDir->dir_pDirectoryName, pDirName);
+
+ /* point to end of the copied directory name */
+ pEndDirName = &pDir->dir_pDirectoryName[nBufferLen - 1];
+
+ /* if directory name did not end in '/' or '\', add '/' */
+ if ((*pEndDirName != '/') && (*pEndDirName != '\\')) {
+ pEndDirName++;
+ *pEndDirName = '/';
+ }
+
+ /* now append the wildcard character to the buffer */
+ pEndDirName++;
+ *pEndDirName = '*';
+ pEndDirName++;
+ *pEndDirName = '\0';
+
+ /* other values defaulted */
+ pDir->dir_nNumFiles = 0;
+ pDir->dir_hDirHandle = INVALID_HANDLE_VALUE;
+ pDir->dir_ulCookie = __DIRENT_COOKIE;
+
+#ifdef KMK_PRF
+ fprintf(stderr, "opendir(%s) -> %p\n", pDirName, pDir);
+#endif
+ return pDir;
+}
+
+void
+closedir(DIR *pDir)
+{
+ /* got a valid pointer? */
+ if (!pDir) {
+ errno = EINVAL;
+ return;
+ }
+
+ /* sanity check that this is a DIR pointer */
+ if (pDir->dir_ulCookie != __DIRENT_COOKIE) {
+ errno = EINVAL;
+ return;
+ }
+
+ /* close the WINDOWS32 directory handle */
+ if (pDir->dir_hDirHandle != INVALID_HANDLE_VALUE)
+ FindClose(pDir->dir_hDirHandle);
+
+ free(pDir);
+
+ return;
+}
+
+struct dirent *
+readdir(DIR* pDir)
+{
+ WIN32_FIND_DATA wfdFindData;
+
+ if (!pDir) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* sanity check that this is a DIR pointer */
+ if (pDir->dir_ulCookie != __DIRENT_COOKIE) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (pDir->dir_nNumFiles == 0) {
+ pDir->dir_hDirHandle = FindFirstFile(pDir->dir_pDirectoryName, &wfdFindData);
+ if (pDir->dir_hDirHandle == INVALID_HANDLE_VALUE)
+ return NULL;
+ } else if (!FindNextFile(pDir->dir_hDirHandle, &wfdFindData))
+ return NULL;
+
+ /* bump count for next call to readdir() or telldir() */
+ pDir->dir_nNumFiles++;
+
+ /* fill in struct dirent values */
+ pDir->dir_sdReturn.d_ino = (ino_t)-1;
+ strcpy(pDir->dir_sdReturn.d_name, wfdFindData.cFileName);
+
+ return &pDir->dir_sdReturn;
+}
+
+void
+rewinddir(DIR* pDir)
+{
+ if (!pDir) {
+ errno = EINVAL;
+ return;
+ }
+
+ /* sanity check that this is a DIR pointer */
+ if (pDir->dir_ulCookie != __DIRENT_COOKIE) {
+ errno = EINVAL;
+ return;
+ }
+
+ /* close the WINDOWS32 directory handle */
+ if (pDir->dir_hDirHandle != INVALID_HANDLE_VALUE)
+ if (!FindClose(pDir->dir_hDirHandle))
+ errno = EBADF;
+
+ /* reset members which control readdir() */
+ pDir->dir_hDirHandle = INVALID_HANDLE_VALUE;
+ pDir->dir_nNumFiles = 0;
+
+ return;
+}
+
+int
+telldir(DIR* pDir)
+{
+ if (!pDir) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* sanity check that this is a DIR pointer */
+ if (pDir->dir_ulCookie != __DIRENT_COOKIE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* return number of times readdir() called */
+ return pDir->dir_nNumFiles;
+}
+
+void
+seekdir(DIR* pDir, long nPosition)
+{
+ if (!pDir)
+ return;
+
+ /* sanity check that this is a DIR pointer */
+ if (pDir->dir_ulCookie != __DIRENT_COOKIE)
+ return;
+
+ /* go back to beginning of directory */
+ rewinddir(pDir);
+
+ /* loop until we have found position we care about */
+ for (--nPosition; nPosition && readdir(pDir); nPosition--);
+
+ /* flag invalid nPosition value */
+ if (nPosition)
+ errno = EINVAL;
+
+ return;
+}
diff --git a/src/kmk/w32/compat/posixfcn.c b/src/kmk/w32/compat/posixfcn.c
new file mode 100644
index 0000000..a537ca2
--- /dev/null
+++ b/src/kmk/w32/compat/posixfcn.c
@@ -0,0 +1,516 @@
+/* Replacements for Posix functions and Posix functionality for MS-Windows.
+
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <string.h>
+#include <io.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <windows.h>
+
+#include "dlfcn.h"
+
+#include "makeint.h"
+#include "job.h"
+
+#ifndef NO_OUTPUT_SYNC
+/* Support for OUTPUT_SYNC and related functionality. */
+
+/* Emulation of fcntl that supports only F_GETFD and F_SETLKW. */
+int
+fcntl (intptr_t fd, int cmd, ...)
+{
+ va_list ap;
+
+ va_start (ap, cmd);
+
+ switch (cmd)
+ {
+ case F_GETFD:
+ va_end (ap);
+ /* Could have used GetHandleInformation, but that isn't
+ supported on Windows 9X. */
+ if (_get_osfhandle (fd) == -1)
+ return -1;
+ return 0;
+ case F_SETLKW:
+ {
+ void *buf = va_arg (ap, void *);
+ struct flock *fl = (struct flock *)buf;
+ HANDLE hmutex = (HANDLE)fd;
+ static struct flock last_fl;
+ short last_type = last_fl.l_type;
+
+ va_end (ap);
+
+ if (hmutex == INVALID_HANDLE_VALUE || !hmutex)
+ return -1;
+
+ last_fl = *fl;
+
+ switch (fl->l_type)
+ {
+
+ case F_WRLCK:
+ {
+ DWORD result;
+
+ if (last_type == F_WRLCK)
+ {
+ /* Don't call WaitForSingleObject if we already
+ own the mutex, because doing so will require
+ us to call ReleaseMutex an equal number of
+ times, before the mutex is actually
+ released. */
+ return 0;
+ }
+
+ result = WaitForSingleObject (hmutex, INFINITE);
+ switch (result)
+ {
+ case WAIT_OBJECT_0:
+ /* We don't care if the mutex owner crashed or
+ exited. */
+ case WAIT_ABANDONED:
+ return 0;
+ case WAIT_FAILED:
+ case WAIT_TIMEOUT: /* cannot happen, really */
+ {
+ DWORD err = GetLastError ();
+
+ /* Invalidate the last command. */
+ memset (&last_fl, 0, sizeof (last_fl));
+
+ switch (err)
+ {
+ case ERROR_INVALID_HANDLE:
+ case ERROR_INVALID_FUNCTION:
+ errno = EINVAL;
+ return -1;
+ default:
+ errno = EDEADLOCK;
+ return -1;
+ }
+ }
+ }
+ }
+ case F_UNLCK:
+ {
+ /* FIXME: Perhaps we should call ReleaseMutex
+ repatedly until it errors out, to make sure the
+ mutext is released even if we somehow managed to
+ to take ownership multiple times? */
+ BOOL status = ReleaseMutex (hmutex);
+
+ if (status)
+ return 0;
+ else
+ {
+ DWORD err = GetLastError ();
+
+ if (err == ERROR_NOT_OWNER)
+ errno = EPERM;
+ else
+ {
+ memset (&last_fl, 0, sizeof (last_fl));
+ errno = EINVAL;
+ }
+ return -1;
+ }
+ }
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+ }
+ default:
+ errno = ENOSYS;
+ va_end (ap);
+ return -1;
+ }
+}
+
+static intptr_t mutex_handle = -1;
+
+/* Record in a static variable the mutex handle we were requested to
+ use. That nameless mutex was created by the top-level Make, and
+ its handle was passed to us via inheritance. The value of that
+ handle is passed via the command-line arguments, so that we know
+ which handle to use. */
+void
+record_sync_mutex (const char *str)
+{
+#ifdef CONFIG_NEW_WIN_CHILDREN
+ HANDLE hmtx = OpenMutexA(SYNCHRONIZE, FALSE /*fInheritable*/, str);
+ if (hmtx)
+ mutex_handle = (intptr_t)hmtx;
+ else
+ {
+ mutex_handle = -1;
+ errno = ENOENT;
+ }
+#else
+ char *endp;
+ intptr_t hmutex = strtol (str, &endp, 16);
+
+ if (*endp == '\0')
+ mutex_handle = hmutex;
+ else
+ {
+ mutex_handle = -1;
+ errno = EINVAL;
+ }
+#endif
+}
+
+/* Create a new mutex or reuse one created by our parent. */
+intptr_t
+#ifdef CONFIG_NEW_WIN_CHILDREN
+create_mutex (char *mtxname, size_t size)
+#else
+create_mutex (void)
+#endif
+{
+#ifndef CONFIG_NEW_WIN_CHILDREN
+ SECURITY_ATTRIBUTES secattr;
+#endif
+ intptr_t hmutex = -1;
+
+ /* If we have a mutex handle passed from the parent Make, just use
+ that. */
+ if (mutex_handle > 0)
+ {
+#ifdef CONFIG_NEW_WIN_CHILDREN
+ mtxname[0] = '\0';
+#endif
+ return mutex_handle;
+ }
+
+#ifdef CONFIG_NEW_WIN_CHILDREN
+ /* We're the top-level Make. Child Make processes will open our mutex, since
+ children does not inherit any handles other than the three standard ones. */
+ snprintf(mtxname, size, "Make-output-%u-%u-%u", GetCurrentProcessId(),
+ GetCurrentThreadId(), GetTickCount());
+ hmutex = (intptr_t)CreateMutexA (NULL, FALSE /*Locked*/, mtxname);
+#else
+ /* We are the top-level Make, and we want the handle to be inherited
+ by our child processes. */
+ secattr.nLength = sizeof (secattr);
+ secattr.lpSecurityDescriptor = NULL; /* use default security descriptor */
+ secattr.bInheritHandle = TRUE;
+
+ hmutex = (intptr_t)CreateMutex (&secattr, FALSE, NULL);
+#endif
+ if (!hmutex)
+ {
+ DWORD err = GetLastError ();
+
+ fprintf (stderr, "CreateMutex: error %lu\n", err);
+ errno = ENOLCK;
+ hmutex = -1;
+ }
+
+ mutex_handle = hmutex;
+ return hmutex;
+}
+
+/* Return non-zero if F1 and F2 are 2 streams representing the same
+ file or pipe or device. */
+int
+same_stream (FILE *f1, FILE *f2)
+{
+ HANDLE fh1 = (HANDLE)_get_osfhandle (fileno (f1));
+ HANDLE fh2 = (HANDLE)_get_osfhandle (fileno (f2));
+
+ /* Invalid file descriptors get treated as different streams. */
+ if (fh1 && fh1 != INVALID_HANDLE_VALUE
+ && fh2 && fh2 != INVALID_HANDLE_VALUE)
+ {
+ if (fh1 == fh2)
+ return 1;
+ else
+ {
+ DWORD ftyp1 = GetFileType (fh1), ftyp2 = GetFileType (fh2);
+
+ if (ftyp1 != ftyp2
+ || ftyp1 == FILE_TYPE_UNKNOWN || ftyp2 == FILE_TYPE_UNKNOWN)
+ return 0;
+ else if (ftyp1 == FILE_TYPE_CHAR)
+ {
+ /* For character devices, check if they both refer to a
+ console. This loses if both handles refer to the
+ null device (FIXME!), but in that case we don't care
+ in the context of Make. */
+ DWORD conmode1, conmode2;
+
+ /* Each process on Windows can have at most 1 console,
+ so if both handles are for the console device, they
+ are the same. We also compare the console mode to
+ distinguish between stdin and stdout/stderr. */
+ if (GetConsoleMode (fh1, &conmode1)
+ && GetConsoleMode (fh2, &conmode2)
+ && conmode1 == conmode2)
+ return 1;
+ }
+ else
+ {
+ /* For disk files and pipes, compare their unique
+ attributes. */
+ BY_HANDLE_FILE_INFORMATION bhfi1, bhfi2;
+
+ /* Pipes get zero in the volume serial number, but do
+ appear to have meaningful information in file index
+ attributes. We test file attributes as well, for a
+ good measure. */
+ if (GetFileInformationByHandle (fh1, &bhfi1)
+ && GetFileInformationByHandle (fh2, &bhfi2))
+ return (bhfi1.dwVolumeSerialNumber == bhfi2.dwVolumeSerialNumber
+ && bhfi1.nFileIndexLow == bhfi2.nFileIndexLow
+ && bhfi1.nFileIndexHigh == bhfi2.nFileIndexHigh
+ && bhfi1.dwFileAttributes == bhfi2.dwFileAttributes);
+ }
+ }
+ }
+ return 0;
+}
+
+/* A replacement for tmpfile, since the MSVCRT implementation creates
+ the file in the root directory of the current drive, which might
+ not be writable by our user. Most of the code borrowed from
+ create_batch_file, see job.c. */
+FILE *
+tmpfile (void)
+{
+ char temp_path[MAXPATHLEN];
+ unsigned path_size = GetTempPath (sizeof temp_path, temp_path);
+ int path_is_dot = 0;
+ /* The following variable is static so we won't try to reuse a name
+ that was generated a little while ago, because that file might
+ not be on disk yet, since we use FILE_ATTRIBUTE_TEMPORARY below,
+ which tells the OS it doesn't need to flush the cache to disk.
+ If the file is not yet on disk, we might think the name is
+ available, while it really isn't. This happens in parallel
+ builds, where Make doesn't wait for one job to finish before it
+ launches the next one. */
+ static unsigned uniq = 0;
+ static int second_loop = 0;
+ const char base[] = "gmake_tmpf";
+ const unsigned sizemax = sizeof base - 1 + 4 + 10 + 10;
+ unsigned pid = GetCurrentProcessId ();
+
+ if (path_size == 0)
+ {
+ path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
+ path_is_dot = 1;
+ }
+
+ ++uniq;
+ if (uniq >= 0x10000 && !second_loop)
+ {
+ /* If we already had 64K batch files in this
+ process, make a second loop through the numbers,
+ looking for free slots, i.e. files that were
+ deleted in the meantime. */
+ second_loop = 1;
+ uniq = 1;
+ }
+ while (path_size > 0 &&
+ path_size + sizemax < sizeof temp_path &&
+ !(uniq >= 0x10000 && second_loop))
+ {
+ HANDLE h;
+
+ sprintf (temp_path + path_size,
+ "%s%s%u-%x.tmp",
+ temp_path[path_size - 1] == '\\' ? "" : "\\",
+ base, pid, uniq);
+ h = CreateFile (temp_path, /* file name */
+ GENERIC_READ | GENERIC_WRITE | DELETE, /* desired access */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share mode */
+ NULL, /* default security attributes */
+ CREATE_NEW, /* creation disposition */
+ FILE_ATTRIBUTE_NORMAL | /* flags and attributes */
+ FILE_ATTRIBUTE_TEMPORARY |
+ FILE_FLAG_DELETE_ON_CLOSE,
+ NULL); /* no template file */
+
+ if (h == INVALID_HANDLE_VALUE)
+ {
+ const DWORD er = GetLastError ();
+
+ if (er == ERROR_FILE_EXISTS || er == ERROR_ALREADY_EXISTS)
+ {
+ ++uniq;
+ if (uniq == 0x10000 && !second_loop)
+ {
+ second_loop = 1;
+ uniq = 1;
+ }
+ }
+
+ /* The temporary path is not guaranteed to exist, or might
+ not be writable by user. Use the current directory as
+ fallback. */
+ else if (path_is_dot == 0)
+ {
+ path_size = GetCurrentDirectory (sizeof temp_path, temp_path);
+ path_is_dot = 1;
+ }
+
+ else
+ {
+ errno = EACCES;
+ break;
+ }
+ }
+ else
+ {
+ int fd = _open_osfhandle ((intptr_t)h, 0);
+
+ return _fdopen (fd, "w+b");
+ }
+ }
+
+ if (uniq >= 0x10000)
+ errno = EEXIST;
+ return NULL;
+}
+
+#endif /* !NO_OUTPUT_SYNC */
+
+#if MAKE_LOAD
+
+/* Support for dynamic loading of objects. */
+
+
+static DWORD last_err;
+
+void *
+dlopen (const char *file, int mode)
+{
+ char dllfn[MAX_PATH], *p;
+ HANDLE dllhandle;
+
+ if ((mode & ~(RTLD_LAZY | RTLD_NOW | RTLD_GLOBAL)) != 0)
+ {
+ errno = EINVAL;
+ last_err = ERROR_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ if (!file)
+ dllhandle = GetModuleHandle (NULL);
+ else
+ {
+ /* MSDN says to be sure to use backslashes in the DLL file name. */
+ strcpy (dllfn, file);
+ for (p = dllfn; *p; p++)
+ if (*p == '/')
+ *p = '\\';
+
+ dllhandle = LoadLibrary (dllfn);
+ }
+ if (!dllhandle)
+ last_err = GetLastError ();
+
+ return dllhandle;
+}
+
+char *
+dlerror (void)
+{
+ static char errbuf[1024];
+ DWORD ret;
+
+ if (!last_err)
+ return NULL;
+
+ ret = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM
+ | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, last_err, 0, errbuf, sizeof (errbuf), NULL);
+ while (ret > 0 && (errbuf[ret - 1] == '\n' || errbuf[ret - 1] == '\r'))
+ --ret;
+
+ errbuf[ret] = '\0';
+ if (!ret)
+ sprintf (errbuf, "Error code %lu", last_err);
+
+ last_err = 0;
+ return errbuf;
+}
+
+void *
+dlsym (void *handle, const char *name)
+{
+ FARPROC addr = NULL;
+
+ if (!handle || handle == INVALID_HANDLE_VALUE)
+ {
+ last_err = ERROR_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ addr = GetProcAddress (handle, name);
+ if (!addr)
+ last_err = GetLastError ();
+
+ return (void *)addr;
+}
+
+int
+dlclose (void *handle)
+{
+ if (!handle || handle == INVALID_HANDLE_VALUE)
+ return -1;
+ if (!FreeLibrary (handle))
+ return -1;
+
+ return 0;
+}
+
+
+#endif /* MAKE_LOAD */
+
+
+/* MS runtime's isatty returns non-zero for any character device,
+ including the null device, which is not what we want. */
+int
+isatty (int fd)
+{
+ HANDLE fh = (HANDLE) _get_osfhandle (fd);
+ DWORD con_mode;
+
+ if (fh == INVALID_HANDLE_VALUE)
+ {
+ errno = EBADF;
+ return 0;
+ }
+ if (GetConsoleMode (fh, &con_mode))
+ return 1;
+
+ errno = ENOTTY;
+ return 0;
+}
+
+char *
+ttyname (int fd)
+{
+ /* This "knows" that Make only asks about stdout and stderr. A more
+ sophisticated implementation should test whether FD is open for
+ input or output. We can do that by looking at the mode returned
+ by GetConsoleMode. */
+ return "CONOUT$";
+}
diff --git a/src/kmk/w32/imagecache.c b/src/kmk/w32/imagecache.c
new file mode 100644
index 0000000..2b26dac
--- /dev/null
+++ b/src/kmk/w32/imagecache.c
@@ -0,0 +1,219 @@
+/* $Id: imagecache.c 3195 2018-03-27 18:09:23Z bird $ */
+/** @file
+ * kBuild specific executable image cache for Windows.
+ */
+
+/*
+ * Copyright (c) 2012 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* No GNU coding style here! */
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include "makeint.h"
+
+#include <Windows.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct EXECCACHEENTRY
+{
+ /** The name hash value. */
+ unsigned uHash;
+ /** The name length. */
+ unsigned cwcName;
+ /** Pointer to the next name with the same hash. */
+ struct EXECCACHEENTRY *pNext;
+ /** When it was last referenced. */
+ unsigned uLastRef;
+ /** The module handle, LOAD_LIBRARY_AS_DATAFILE. */
+ HMODULE hmod1;
+ /** The module handle, DONT_RESOLVE_DLL_REFERENCES. */
+ HMODULE hmod2;
+ /** The executable path. */
+ wchar_t wszName[1];
+} EXECCACHEENTRY;
+typedef EXECCACHEENTRY *PEXECCACHEENTRY;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Critical section serializing all access. */
+static CRITICAL_SECTION g_CritSect;
+/** Set if initialized. */
+static int volatile g_fInitialized = 0;
+/** The number of cached images. */
+static unsigned g_cCached;
+/** Used noting when entries was last used.
+ * Increased on each kmk_cache_exec_image call. */
+static unsigned g_uNow;
+
+/** The size of the hash table. */
+#define EXECCACHE_HASHTAB_SIZE 128
+/** The hash table. */
+static PEXECCACHEENTRY g_apHashTab[EXECCACHE_HASHTAB_SIZE];
+
+
+/** A sleepy approach to do-once. */
+static void kmk_cache_lazy_init(void)
+{
+ if (_InterlockedCompareExchange(&g_fInitialized, -1, 0) == 0)
+ {
+ InitializeCriticalSection(&g_CritSect);
+ _InterlockedExchange(&g_fInitialized, 1);
+ }
+ else
+ while (g_fInitialized != 1)
+ Sleep(1);
+}
+
+
+/* sdbm:
+ This algorithm was created for sdbm (a public-domain reimplementation of
+ ndbm) database library. it was found to do well in scrambling bits,
+ causing better distribution of the keys and fewer splits. it also happens
+ to be a good general hashing function with good distribution. the actual
+ function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
+ is the faster version used in gawk. [there is even a faster, duff-device
+ version] the magic constant 65599 was picked out of thin air while
+ experimenting with different constants, and turns out to be a prime.
+ this is one of the algorithms used in berkeley db (see sleepycat) and
+ elsewhere. */
+
+static unsigned execcache_calc_hash(const wchar_t *pwsz, size_t *pcch)
+{
+ wchar_t const * const pwszStart = pwsz;
+ unsigned hash = 0;
+ int ch;
+
+ while ((ch = *pwsz++) != L'\0')
+ hash = ch + (hash << 6) + (hash << 16) - hash;
+
+ *pcch = (size_t)(pwsz - pwszStart - 1);
+ return hash;
+}
+
+/**
+ * Caches two memory mappings of the specified image so that it isn't flushed
+ * from the kernel's cache mananger.
+ *
+ * Not sure exactly how much this actually helps, but whatever...
+ *
+ * @param pwszExec The executable.
+ */
+extern void kmk_cache_exec_image_w(const wchar_t *pwszExec)
+{
+ /*
+ * Prepare name lookup and to lazy init.
+ */
+ size_t cwcName;
+ const unsigned uHash = execcache_calc_hash(pwszExec, &cwcName);
+ PEXECCACHEENTRY *ppCur = &g_apHashTab[uHash % EXECCACHE_HASHTAB_SIZE];
+ PEXECCACHEENTRY pCur;
+
+ if (g_fInitialized != 1)
+ kmk_cache_lazy_init();
+
+ /*
+ * Do the lookup.
+ */
+ EnterCriticalSection(&g_CritSect);
+ pCur = *ppCur;
+ while (pCur)
+ {
+ if ( pCur->uHash == uHash
+ && pCur->cwcName == cwcName
+ && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t)))
+ {
+ pCur->uLastRef = ++g_uNow;
+ LeaveCriticalSection(&g_CritSect);
+ return;
+ }
+ ppCur = &pCur->pNext;
+ pCur = pCur->pNext;
+ }
+ LeaveCriticalSection(&g_CritSect);
+
+ /*
+ * Not found, create a new entry.
+ */
+ pCur = xmalloc(sizeof(*pCur) + cwcName * sizeof(wchar_t));
+ pCur->uHash = uHash;
+ pCur->cwcName = (unsigned)cwcName;
+ pCur->pNext = NULL;
+ pCur->uLastRef = ++g_uNow;
+ memcpy(pCur->wszName, pwszExec, (cwcName + 1) * sizeof(wchar_t));
+ pCur->hmod1 = LoadLibraryExW(pwszExec, NULL, LOAD_LIBRARY_AS_DATAFILE);
+ if (pCur->hmod1 != NULL)
+ pCur->hmod2 = LoadLibraryExW(pwszExec, NULL, DONT_RESOLVE_DLL_REFERENCES);
+ else
+ pCur->hmod2 = NULL;
+
+ /*
+ * Insert it.
+ * Take into account that we might've been racing other threads,
+ * fortunately we don't evict anything from the cache.
+ */
+ EnterCriticalSection(&g_CritSect);
+ if (*ppCur != NULL)
+ {
+ /* Find new end of chain and check for duplicate. */
+ PEXECCACHEENTRY pCur2 = *ppCur;
+ while (pCur2)
+ {
+ if ( pCur->uHash == uHash
+ && pCur->cwcName == cwcName
+ && !memcmp(pCur->wszName, pwszExec, cwcName * sizeof(wchar_t)))
+ break;
+ ppCur = &pCur->pNext;
+ pCur = pCur->pNext;
+ }
+
+ }
+ if (*ppCur == NULL)
+ {
+ *ppCur = pCur;
+ g_cCached++;
+ LeaveCriticalSection(&g_CritSect);
+ }
+ else
+ {
+ LeaveCriticalSection(&g_CritSect);
+
+ if (pCur->hmod1 != NULL)
+ FreeLibrary(pCur->hmod1);
+ if (pCur->hmod2 != NULL)
+ FreeLibrary(pCur->hmod2);
+ free(pCur);
+ }
+}
+
+extern void kmk_cache_exec_image_a(const char *pszExec)
+{
+ wchar_t wszExec[260];
+ int cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszExec, strlen(pszExec) + 1, wszExec, 260);
+ if (cwc > 0)
+ kmk_cache_exec_image_w(wszExec);
+}
+
diff --git a/src/kmk/w32/include/dirent.h b/src/kmk/w32/include/dirent.h
new file mode 100644
index 0000000..c349e85
--- /dev/null
+++ b/src/kmk/w32/include/dirent.h
@@ -0,0 +1,66 @@
+/* Windows version of dirent.h
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _DIRENT_H
+#define _DIRENT_H
+
+#ifdef KMK
+# include <windows.h>
+# include "nt/ntdir.h"
+
+#else /* !KMK */
+
+#ifdef __MINGW32__
+# include <windows.h>
+# include_next <dirent.h>
+#else
+
+#include <stdlib.h>
+#include <windows.h>
+#include <limits.h>
+#include <sys/types.h>
+
+#ifndef NAME_MAX
+#define NAME_MAX 255
+#endif
+
+#define __DIRENT_COOKIE 0xfefeabab
+
+
+struct dirent
+{
+ ino_t d_ino; /* unused - no equivalent on WINDOWS32 */
+ char d_name[NAME_MAX+1];
+};
+
+typedef struct dir_struct {
+ ULONG dir_ulCookie;
+ HANDLE dir_hDirHandle;
+ DWORD dir_nNumFiles;
+ char dir_pDirectoryName[NAME_MAX+1];
+ struct dirent dir_sdReturn;
+} DIR;
+
+DIR *opendir(const char *);
+struct dirent *readdir(DIR *);
+void rewinddir(DIR *);
+void closedir(DIR *);
+int telldir(DIR *);
+void seekdir(DIR *, long);
+
+#endif /* !__MINGW32__ */
+#endif /* !KMK */
+#endif
diff --git a/src/kmk/w32/include/dlfcn.h b/src/kmk/w32/include/dlfcn.h
new file mode 100644
index 0000000..5a2ae28
--- /dev/null
+++ b/src/kmk/w32/include/dlfcn.h
@@ -0,0 +1,29 @@
+/* dlfcn.h replacement for MS-Windows build.
+Copyright (C) 2013-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef DLFCN_H
+#define DLFCN_H
+
+#define RTLD_LAZY 1
+#define RTLD_NOW 2
+#define RTLD_GLOBAL 4
+
+extern void *dlopen (const char *, int);
+extern void *dlsym (void *, const char *);
+extern char *dlerror (void);
+extern int dlclose (void *);
+
+#endif /* DLFCN_H */
diff --git a/src/kmk/w32/include/pathstuff.h b/src/kmk/w32/include/pathstuff.h
new file mode 100644
index 0000000..3f839ec
--- /dev/null
+++ b/src/kmk/w32/include/pathstuff.h
@@ -0,0 +1,30 @@
+/* Definitions for Windows path manipulation.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _PATHSTUFF_H
+#define _PATHSTUFF_H
+
+char *convert_Path_to_windows32(char *Path, char to_delim);
+char *convert_vpath_to_windows32(char *Path, char to_delim);
+#if 1
+char *unix_slashes(char *filename); /* bird */
+char *unix_slashes_resolved(const char *src, char *dst, unsigned len); /* bird */
+#else
+char *w32ify(const char *filename, int resolve);
+#endif
+char *getcwd_fs(char *buf, int len);
+
+#endif
diff --git a/src/kmk/w32/include/sub_proc.h b/src/kmk/w32/include/sub_proc.h
new file mode 100644
index 0000000..51e3830
--- /dev/null
+++ b/src/kmk/w32/include/sub_proc.h
@@ -0,0 +1,72 @@
+/* Definitions for Windows process invocation.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SUB_PROC_H
+#define SUB_PROC_H
+#ifdef CONFIG_NEW_WIN_CHILDREN
+# error "Just checking..."
+#endif
+
+/*
+ * Component Name:
+ *
+ * $Date$
+ *
+ * $Source$
+ *
+ * $Id$
+ */
+
+#define EXTERN_DECL(entry, args) extern entry args
+#define VOID_DECL void
+
+EXTERN_DECL(HANDLE process_init, (VOID_DECL));
+EXTERN_DECL(HANDLE process_init_fd, (HANDLE stdinh, HANDLE stdouth,
+ HANDLE stderrh));
+EXTERN_DECL(long process_begin, (HANDLE proc, char **argv, char **envp,
+ char *exec_path, char *as_user));
+EXTERN_DECL(long process_pipe_io, (HANDLE proc, char *stdin_data,
+ int stdin_data_len));
+#ifndef KMK /* unused */
+EXTERN_DECL(long process_file_io, (HANDLE proc));
+#endif
+EXTERN_DECL(void process_cleanup, (HANDLE proc));
+EXTERN_DECL(HANDLE process_wait_for_any, (int block, DWORD* pdwWaitStatus));
+EXTERN_DECL(void process_register, (HANDLE proc));
+EXTERN_DECL(HANDLE process_easy, (char** argv, char** env,
+ int outfd, int errfd));
+EXTERN_DECL(BOOL process_kill, (HANDLE proc, int signal));
+EXTERN_DECL(int process_used_slots, (VOID_DECL));
+EXTERN_DECL(DWORD process_set_handles, (HANDLE *handles));
+
+#ifdef KMK
+EXTERN_DECL(int process_kmk_register_submit, (HANDLE hEvent, intptr_t clue, pid_t *pPid));
+EXTERN_DECL(int process_kmk_register_redirect, (HANDLE hProcess, pid_t *pPid));
+#endif
+
+/* support routines */
+EXTERN_DECL(long process_errno, (HANDLE proc));
+EXTERN_DECL(long process_last_err, (HANDLE proc));
+EXTERN_DECL(long process_exit_code, (HANDLE proc));
+EXTERN_DECL(long process_signal, (HANDLE proc));
+EXTERN_DECL(char * process_outbuf, (HANDLE proc));
+EXTERN_DECL(char * process_errbuf, (HANDLE proc));
+EXTERN_DECL(int process_outcnt, (HANDLE proc));
+EXTERN_DECL(int process_errcnt, (HANDLE proc));
+EXTERN_DECL(void process_pipes, (HANDLE proc, int pipes[3]));
+EXTERN_DECL(void process_noinherit, (int fildes));
+
+#endif
diff --git a/src/kmk/w32/include/w32err.h b/src/kmk/w32/include/w32err.h
new file mode 100644
index 0000000..b4292f2
--- /dev/null
+++ b/src/kmk/w32/include/w32err.h
@@ -0,0 +1,26 @@
+/* Definitions for Windows error handling.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _W32ERR_H_
+#define _W32ERR_H_
+
+#ifndef EXTERN_DECL
+#define EXTERN_DECL(entry, args) entry args
+#endif
+
+EXTERN_DECL(const char * map_windows32_error_to_string, (DWORD error));
+
+#endif /* !_W32ERR_H */
diff --git a/src/kmk/w32/pathstuff.c b/src/kmk/w32/pathstuff.c
new file mode 100644
index 0000000..f80cbf1
--- /dev/null
+++ b/src/kmk/w32/pathstuff.c
@@ -0,0 +1,321 @@
+/* Path conversion for Windows pathnames.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+#include <string.h>
+#include <stdlib.h>
+#include "pathstuff.h"
+#if 1 /* bird */
+# include "nt_fullpath.h"
+# include <assert.h>
+#endif
+
+/*
+ * Convert delimiter separated vpath to Canonical format.
+ */
+char *
+convert_vpath_to_windows32(char *Path, char to_delim)
+{
+ char *etok; /* token separator for old Path */
+
+ /*
+ * Convert all spaces to delimiters. Note that pathnames which
+ * contain blanks get trounced here. Use 8.3 format as a workaround.
+ */
+ for (etok = Path; etok && *etok; etok++)
+ if (ISBLANK ((unsigned char) *etok))
+ *etok = to_delim;
+
+ return (convert_Path_to_windows32(Path, to_delim));
+}
+
+/*
+ * Convert delimiter separated path to Canonical format.
+ */
+char *
+convert_Path_to_windows32(char *Path, char to_delim)
+{
+ char *etok; /* token separator for old Path */
+ char *p; /* points to element of old Path */
+
+ /* is this a multi-element Path ? */
+ /* FIXME: Perhaps use ":;\"" in strpbrk to convert all quotes to
+ delimiters as well, as a way to handle quoted directories in
+ PATH? */
+ for (p = Path, etok = strpbrk(p, ":;");
+ etok;
+ etok = strpbrk(p, ":;"))
+ if ((etok - p) == 1) {
+ if (*(etok - 1) == ';' ||
+ *(etok - 1) == ':') {
+ etok[-1] = to_delim;
+ etok[0] = to_delim;
+ p = ++etok;
+ continue; /* ignore empty bucket */
+ } else if (!isalpha ((unsigned char) *p)) {
+ /* found one to count, handle things like '.' */
+ *etok = to_delim;
+ p = ++etok;
+ } else if ((*etok == ':') && (etok = strpbrk(etok+1, ":;"))) {
+ /* found one to count, handle drive letter */
+ *etok = to_delim;
+ p = ++etok;
+ } else
+ /* all finished, force abort */
+ p += strlen(p);
+ } else if (*p == '"') { /* a quoted directory */
+ for (p++; *p && *p != '"'; p++) /* skip quoted part */
+ ;
+ etok = strpbrk(p, ":;"); /* find next delimiter */
+ if (etok) {
+ *etok = to_delim;
+ p = ++etok;
+ } else
+ p += strlen(p);
+ } else {
+ /* found another one, no drive letter */
+ *etok = to_delim;
+ p = ++etok;
+ }
+
+ return Path;
+}
+
+/*
+ * Convert to forward slashes directly (w32ify(filename, 0)).
+ */
+char *unix_slashes(char *filename) /* bird */
+{
+ char *slash = filename ;
+ while ((slash = strchr(slash, '\\')) != NULL)
+ *slash++ = '/';
+ return filename;
+}
+
+/*
+ * Resolve and convert to forward slashes directly (w32ify(filename, 1)).
+ * Returns if out of buffer space.
+ */
+char *unix_slashes_resolved(const char *src, char *dst, unsigned len)
+{
+ assert(len >= FILENAME_MAX);
+ *dst = '\0'; /** @todo nt_fullpath_cached needs to return some indication of overflow. */
+#if 1
+ nt_fullpath_cached(src, dst, len);
+#else
+ _fullpath(dst, src, len);
+#endif
+
+ return unix_slashes(dst);
+}
+
+#if 0 /* bird: replaced by unix_slashes and unix_slahes_resolved. */
+/*
+ * Convert to forward slashes. Resolve to full pathname optionally
+ */
+char *
+w32ify(const char *filename, int resolve)
+{
+ static char w32_path[FILENAME_MAX];
+#if 1 /* bird */
+
+ if (resolve) {
+ nt_fullpath_cached(filename, w32_path, sizeof(w32_path));
+ } else {
+ w32_path[0] = '\0';
+ strncat(w32_path, filename, sizeof(w32_path));
+ }
+ return unix_slashes(w32_path);
+
+#else /* !bird */
+ char *p;
+
+ if (resolve) {
+ _fullpath(w32_path, filename, sizeof (w32_path));
+ } else
+ strncpy(w32_path, filename, sizeof (w32_path));
+
+ for (p = w32_path; p && *p; p++)
+ if (*p == '\\')
+ *p = '/';
+
+ return w32_path;
+#endif /* !bird */
+}
+#endif
+
+char *
+getcwd_fs(char* buf, int len)
+{
+ char *p = getcwd(buf, len);
+
+ if (p) {
+#if 1
+ p = unix_slashes(p);
+#else
+ char *q = w32ify(buf, 0);
+#if 1 /* bird - UPSTREAM? */
+ buf[0] = '\0';
+ strncat(buf, q, len);
+#else /* !bird */
+ strncpy(buf, q, len);
+#endif
+#endif
+ }
+
+ return p;
+}
+
+#ifdef unused
+/*
+ * Convert delimiter separated pathnames (e.g. PATH) or single file pathname
+ * (e.g. c:/foo, c:\bar) to NutC format. If we are handed a string that
+ * _NutPathToNutc() fails to convert, just return the path we were handed
+ * and assume the caller will know what to do with it (It was probably
+ * a mistake to try and convert it anyway due to some of the bizarre things
+ * that might look like pathnames in makefiles).
+ */
+char *
+convert_path_to_nutc(char *path)
+{
+ int count; /* count of path elements */
+ char *nutc_path; /* new NutC path */
+ int nutc_path_len; /* length of buffer to allocate for new path */
+ char *pathp; /* pointer to nutc_path used to build it */
+ char *etok; /* token separator for old path */
+ char *p; /* points to element of old path */
+ char sep; /* what flavor of separator used in old path */
+ char *rval;
+
+ /* is this a multi-element path ? */
+ for (p = path, etok = strpbrk(p, ":;"), count = 0;
+ etok;
+ etok = strpbrk(p, ":;"))
+ if ((etok - p) == 1) {
+ if (*(etok - 1) == ';' ||
+ *(etok - 1) == ':') {
+ p = ++etok;
+ continue; /* ignore empty bucket */
+ } else if (etok = strpbrk(etok+1, ":;"))
+ /* found one to count, handle drive letter */
+ p = ++etok, count++;
+ else
+ /* all finished, force abort */
+ p += strlen(p);
+ } else
+ /* found another one, no drive letter */
+ p = ++etok, count++;
+
+ if (count) {
+ count++; /* x1;x2;x3 <- need to count x3 */
+
+ /*
+ * Hazard a guess on how big the buffer needs to be.
+ * We have to convert things like c:/foo to /c=/foo.
+ */
+ nutc_path_len = strlen(path) + (count*2) + 1;
+ nutc_path = xmalloc(nutc_path_len);
+ pathp = nutc_path;
+ *pathp = '\0';
+
+ /*
+ * Loop through PATH and convert one elemnt of the path at at
+ * a time. Single file pathnames will fail this and fall
+ * to the logic below loop.
+ */
+ for (p = path, etok = strpbrk(p, ":;");
+ etok;
+ etok = strpbrk(p, ":;")) {
+
+ /* don't trip up on device specifiers or empty path slots */
+ if ((etok - p) == 1)
+ if (*(etok - 1) == ';' ||
+ *(etok - 1) == ':') {
+ p = ++etok;
+ continue;
+ } else if ((etok = strpbrk(etok+1, ":;")) == NULL)
+ break; /* thing found was a WINDOWS32 pathname */
+
+ /* save separator */
+ sep = *etok;
+
+ /* terminate the current path element -- temporarily */
+ *etok = '\0';
+
+#ifdef __NUTC__
+ /* convert to NutC format */
+ if (_NutPathToNutc(p, pathp, 0) == FALSE) {
+ free(nutc_path);
+ rval = savestring(path, strlen(path));
+ return rval;
+ }
+#else
+ *pathp++ = '/';
+ *pathp++ = p[0];
+ *pathp++ = '=';
+ *pathp++ = '/';
+ strcpy(pathp, &p[2]);
+#endif
+
+ pathp += strlen(pathp);
+ *pathp++ = ':'; /* use Unix style path separtor for new path */
+ *pathp = '\0'; /* make sure we are null terminaed */
+
+ /* restore path separator */
+ *etok = sep;
+
+ /* point p to first char of next path element */
+ p = ++etok;
+
+ }
+ } else {
+ nutc_path_len = strlen(path) + 3;
+ nutc_path = xmalloc(nutc_path_len);
+ pathp = nutc_path;
+ *pathp = '\0';
+ p = path;
+ }
+
+ /*
+ * OK, here we handle the last element in PATH (e.g. c of a;b;c)
+ * or the path was a single filename and will be converted
+ * here. Note, testing p here assures that we don't trip up
+ * on paths like a;b; which have trailing delimiter followed by
+ * nothing.
+ */
+ if (*p != '\0') {
+#ifdef __NUTC__
+ if (_NutPathToNutc(p, pathp, 0) == FALSE) {
+ free(nutc_path);
+ rval = savestring(path, strlen(path));
+ return rval;
+ }
+#else
+ *pathp++ = '/';
+ *pathp++ = p[0];
+ *pathp++ = '=';
+ *pathp++ = '/';
+ strcpy(pathp, &p[2]);
+#endif
+ } else
+ *(pathp-1) = '\0'; /* we're already done, don't leave trailing : */
+
+ rval = savestring(nutc_path, strlen(nutc_path));
+ free(nutc_path);
+ return rval;
+}
+
+#endif
diff --git a/src/kmk/w32/subproc/Makefile.kup b/src/kmk/w32/subproc/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kmk/w32/subproc/Makefile.kup
diff --git a/src/kmk/w32/subproc/NMakefile b/src/kmk/w32/subproc/NMakefile
new file mode 100644
index 0000000..325e55c
--- /dev/null
+++ b/src/kmk/w32/subproc/NMakefile
@@ -0,0 +1,60 @@
+# NOTE: If you have no 'make' program at all to process this makefile, run
+# 'build.bat' instead.
+#
+# Copyright (C) 1996-2016 Free Software Foundation, Inc.
+# This file is part of GNU Make.
+#
+# GNU Make is free software; you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option) any later
+# version.
+#
+# GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# NMakefile for GNU Make (subproc library)
+#
+LIB = lib
+CC = cl
+MAKE = nmake
+
+OUTDIR=.
+MAKEFILE=NMakefile
+
+CFLAGS_any = /nologo /MT /W4 /GX /Z7 /YX /D WIN32 /D WINDOWS32 /D _WINDOWS -I. -I../include -I../../
+CFLAGS_debug = $(CFLAGS_any) /Od /D _DEBUG /FR.\WinDebug\ /Fp.\WinDebug\subproc.pch /Fo.\WinDebug/
+CFLAGS_release = $(CFLAGS_any) /O2 /FR.\WinRel\ /Fp.\WinRel\subproc.pch /Fo.\WinRel/
+
+all: Release Debug
+
+Release:
+ $(MAKE) /f $(MAKEFILE) OUTDIR=WinRel CFLAGS="$(CFLAGS_release)" WinRel/subproc.lib
+Debug:
+ $(MAKE) /f $(MAKEFILE) OUTDIR=WinDebug CFLAGS="$(CFLAGS_debug)" WinDebug/subproc.lib
+
+clean:
+ rmdir /s /q WinRel WinDebug
+ erase *.pdb
+
+$(OUTDIR):
+ if not exist .\$@\nul mkdir .\$@
+
+OBJS = $(OUTDIR)/misc.obj $(OUTDIR)/w32err.obj $(OUTDIR)/sub_proc.obj
+
+$(OUTDIR)/subproc.lib: $(OUTDIR) $(OBJS)
+ $(LIB) -out:$@ @<<
+ $(OBJS)
+<<
+
+.c{$(OUTDIR)}.obj:
+ $(CC) $(CFLAGS) /c $<
+
+$(OUTDIR)/misc.obj: misc.c proc.h
+$(OUTDIR)/sub_proc.obj: sub_proc.c ../include/sub_proc.h ../include/w32err.h proc.h
+$(OUTDIR)/w32err.obj: w32err.c ../include/w32err.h
diff --git a/src/kmk/w32/subproc/misc.c b/src/kmk/w32/subproc/misc.c
new file mode 100644
index 0000000..8b17413
--- /dev/null
+++ b/src/kmk/w32/subproc/misc.c
@@ -0,0 +1,83 @@
+/* Process handling for Windows
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <windows.h>
+#include "proc.h"
+
+
+/*
+ * Description: Convert a NULL string terminated UNIX environment block to
+ * an environment block suitable for a windows32 system call
+ *
+ * Returns: TRUE= success, FALSE=fail
+ *
+ * Notes/Dependencies: the environment block is sorted in case-insensitive
+ * order, is double-null terminated, and is a char *, not a char **
+ */
+int _cdecl compare(const void *a1, const void *a2)
+{
+ return _stricoll(*((char**)a1),*((char**)a2));
+}
+bool_t
+arr2envblk(char **arr, char **envblk_out, int *envsize_needed)
+{
+ char **tmp;
+ int size_needed;
+ int arrcnt;
+ char *ptr;
+
+ arrcnt = 0;
+ while (arr[arrcnt]) {
+ arrcnt++;
+ }
+
+ tmp = (char**) calloc(arrcnt + 1, sizeof(char *));
+ if (!tmp) {
+ return FALSE;
+ }
+
+ arrcnt = 0;
+ size_needed = *envsize_needed = 0;
+ while (arr[arrcnt]) {
+ tmp[arrcnt] = arr[arrcnt];
+ size_needed += strlen(arr[arrcnt]) + 1;
+ arrcnt++;
+ }
+ size_needed++;
+ *envsize_needed = size_needed;
+
+ qsort((void *) tmp, (size_t) arrcnt, sizeof (char*), compare);
+
+ ptr = *envblk_out = calloc(size_needed, 1);
+ if (!ptr) {
+ free(tmp);
+ return FALSE;
+ }
+
+ arrcnt = 0;
+ while (tmp[arrcnt]) {
+ strcpy(ptr, tmp[arrcnt]);
+ ptr += strlen(tmp[arrcnt]) + 1;
+ arrcnt++;
+ }
+
+ free(tmp);
+ return TRUE;
+}
diff --git a/src/kmk/w32/subproc/proc.h b/src/kmk/w32/subproc/proc.h
new file mode 100644
index 0000000..7ccb5ea
--- /dev/null
+++ b/src/kmk/w32/subproc/proc.h
@@ -0,0 +1,29 @@
+/* Definitions for Windows
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef _PROC_H
+#define _PROC_H
+
+typedef int bool_t;
+
+#define E_SCALL 101
+#define E_IO 102
+#define E_NO_MEM 103
+#define E_FORK 104
+
+extern bool_t arr2envblk(char **arr, char **envblk_out, int *envsize_needed);
+
+#endif
diff --git a/src/kmk/w32/subproc/sub_proc.c b/src/kmk/w32/subproc/sub_proc.c
new file mode 100644
index 0000000..6ccfa47
--- /dev/null
+++ b/src/kmk/w32/subproc/sub_proc.c
@@ -0,0 +1,1714 @@
+/* Process handling for Windows.
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <io.h> /* for _get_osfhandle */
+#ifdef _MSC_VER
+# include <stddef.h> /* for intptr_t */
+#else
+# include <stdint.h>
+#endif
+#include <string.h>
+#include <process.h> /* for msvc _beginthreadex, _endthreadex */
+#include <signal.h>
+#include <windows.h>
+
+#include "makeint.h"
+#include "filedef.h"
+#include "variable.h"
+#include "sub_proc.h"
+#include "proc.h"
+#include "w32err.h"
+#include "debug.h"
+
+#ifdef KMK
+# include <assert.h>
+# include "kmkbuiltin.h"
+extern void kmk_cache_exec_image_a(const char *); /* imagecache.c */
+#endif
+
+static char *make_command_line(char *shell_name, char *exec_path, char **argv);
+
+typedef struct sub_process_t {
+#ifdef KMK
+ enum { kRegular = 0, kSubmit, kSubProcFreed } enmType;
+ intptr_t clue;
+#endif
+ intptr_t sv_stdin[2];
+ intptr_t sv_stdout[2];
+ intptr_t sv_stderr[2];
+ int using_pipes;
+ char *inp;
+ DWORD incnt;
+ char * volatile outp;
+ volatile DWORD outcnt;
+ char * volatile errp;
+ volatile DWORD errcnt;
+ pid_t pid;
+ int exit_code;
+ int signal;
+ long last_err;
+ long lerrno;
+} sub_process;
+
+static long process_file_io_private(sub_process *pproc, BOOL fNeedToWait); /* bird */
+
+/* keep track of children so we can implement a waitpid-like routine */
+static sub_process *proc_array[MAXIMUM_WAIT_OBJECTS];
+static int proc_index = 0;
+static int fake_exits_pending = 0;
+
+
+/*
+ * Fill a HANDLE list with handles to wait for.
+ */
+DWORD
+process_set_handles(HANDLE *handles)
+{
+ DWORD count = 0;
+ int i;
+
+ /* Build array of handles to wait for */
+ for (i = 0; i < proc_index; i++) {
+ /* Don't wait on child processes that have already finished */
+ if (fake_exits_pending && proc_array[i]->exit_code)
+ continue;
+
+ handles[count++] = (HANDLE) proc_array[i]->pid;
+ }
+
+ return count;
+}
+
+#ifndef KMK /* Inefficient! */
+/*
+ * When a process has been waited for, adjust the wait state
+ * array so that we don't wait for it again
+ */
+static void
+process_adjust_wait_state(sub_process* pproc)
+{
+ int i;
+
+ if (!proc_index)
+ return;
+
+ for (i = 0; i < proc_index; i++)
+ if (proc_array[i]->pid == pproc->pid)
+ break;
+
+ if (i < proc_index) {
+ proc_index--;
+ if (i != proc_index)
+ memmove(&proc_array[i], &proc_array[i+1],
+ (proc_index-i) * sizeof(sub_process*));
+ proc_array[proc_index] = NULL;
+ }
+}
+
+#endif /* !KMK */
+
+/*
+ * Waits for any of the registered child processes to finish.
+ */
+static sub_process *
+process_wait_for_any_private(int block, DWORD* pdwWaitStatus)
+{
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ DWORD retval, which;
+ int i;
+
+ if (!proc_index)
+ return NULL;
+
+ /* build array of handles to wait for */
+ for (i = 0; i < proc_index; i++) {
+ handles[i] = (HANDLE) proc_array[i]->pid;
+
+ if (fake_exits_pending && proc_array[i]->exit_code)
+ break;
+ }
+
+ /* wait for someone to exit */
+ if (!fake_exits_pending) {
+#ifdef KMK
+l_wait_again:
+#endif
+ retval = WaitForMultipleObjects(proc_index, handles, FALSE, (block ? INFINITE : 0));
+ which = retval - WAIT_OBJECT_0;
+ } else {
+ fake_exits_pending--;
+ retval = !WAIT_FAILED;
+ which = i;
+ }
+
+ /* If the pointer is not NULL, set the wait status result variable. */
+ if (pdwWaitStatus)
+ *pdwWaitStatus = retval;
+
+ /* return pointer to process */
+ if ((retval == WAIT_TIMEOUT) || (retval == WAIT_FAILED)) {
+ return NULL;
+ }
+ else {
+ sub_process* pproc = proc_array[which];
+#ifdef KMK
+ if (pproc->enmType == kSubmit) {
+ /* Try get the result from kSubmit.c. This may not succeed if the whole
+ result hasn't arrived yet, in which we just restart the wait. */
+ if (kSubmitSubProcGetResult(pproc->clue, &pproc->exit_code, &pproc->signal) != 0) {
+ goto l_wait_again;
+ }
+ }
+#endif
+#ifndef KMK /* Inefficient! */
+ process_adjust_wait_state(pproc);
+#else
+ proc_index--;
+ if ((int)which < proc_index)
+ proc_array[which] = proc_array[proc_index];
+ proc_array[proc_index] = NULL;
+#endif
+ return pproc;
+ }
+}
+
+/*
+ * Terminate a process.
+ */
+BOOL
+ process_kill(HANDLE proc, int signal)
+{
+ sub_process* pproc = (sub_process*) proc;
+#ifdef KMK
+ if (pproc->enmType == kRegular) {
+#endif
+ pproc->signal = signal;
+ return (TerminateProcess((HANDLE) pproc->pid, signal));
+#ifdef KMK
+ } else if (pproc->enmType == kSubmit)
+ return kSubmitSubProcKill(pproc->clue, signal) == 0;
+ assert(0);
+ return FALSE;
+#endif
+}
+
+/*
+ * Use this function to register processes you wish to wait for by
+ * calling process_file_io(NULL) or process_wait_any(). This must be done
+ * because it is possible for callers of this library to reuse the same
+ * handle for multiple processes launches :-(
+ */
+void
+process_register(HANDLE proc)
+{
+#ifdef KMK
+ assert(((sub_process *)proc)->enmType == kRegular);
+#endif
+ if (proc_index < MAXIMUM_WAIT_OBJECTS)
+ proc_array[proc_index++] = (sub_process *) proc;
+}
+
+#ifdef KMK
+
+/**
+ * Interface used by kmkbuiltin/kSubmit.c to register stuff going down in a
+ * worker process.
+ *
+ * @returns 0 on success, -1 if there are too many sub-processes already.
+ * @param hEvent The event semaphore to wait on.
+ * @param clue The clue to base.
+ * @param pPid Where to return the pid that job.c expects.
+ */
+int
+process_kmk_register_submit(HANDLE hEvent, intptr_t clue, pid_t *pPid)
+{
+ if (proc_index < MAXIMUM_WAIT_OBJECTS) {
+ sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc));
+ pSubProc->enmType = kSubmit;
+ pSubProc->clue = clue;
+ pSubProc->pid = (intptr_t)hEvent;
+
+ proc_array[proc_index++] = pSubProc;
+ *pPid = (intptr_t)pSubProc;
+ return 0;
+ }
+ return -1;
+}
+
+/**
+ * Interface used by kmkbuiltin/kRedirect.c to register a spawned process.
+ *
+ * @returns 0 on success, -1 if there are too many sub-processes already.
+ * @param hProcess The process handle.
+ * @param pPid Where to return the pid that job.c expects.
+ */
+int
+process_kmk_register_redirect(HANDLE hProcess, pid_t *pPid)
+{
+ if (proc_index < MAXIMUM_WAIT_OBJECTS) {
+ sub_process *pSubProc = (sub_process *)xcalloc(sizeof(*pSubProc));
+ pSubProc->enmType = kRegular;
+ pSubProc->pid = (intptr_t)hProcess;
+
+ proc_array[proc_index++] = pSubProc;
+ *pPid = (intptr_t)pSubProc;
+ return 0;
+ }
+ return -1;
+}
+
+#endif /* KMK */
+
+/*
+ * Return the number of processes that we are still waiting for.
+ */
+int
+process_used_slots(void)
+{
+ return proc_index;
+}
+
+/*
+ * Public function which works kind of like waitpid(). Wait for any
+ * of the children to die and return results. To call this function,
+ * you must do 1 of things:
+ *
+ * x = process_easy(...);
+ *
+ * or
+ *
+ * x = process_init_fd();
+ * process_register(x);
+ *
+ * or
+ *
+ * x = process_init();
+ * process_register(x);
+ *
+ * You must NOT then call process_pipe_io() because this function is
+ * not capable of handling automatic notification of any child
+ * death.
+ */
+
+HANDLE
+process_wait_for_any(int block, DWORD* pdwWaitStatus)
+{
+ sub_process* pproc = process_wait_for_any_private(block, pdwWaitStatus);
+
+ if (!pproc)
+ return NULL;
+ else {
+ /*
+ * Ouch! can't tell caller if this fails directly. Caller
+ * will have to use process_last_err()
+ */
+#ifdef KMK
+ /* Invalidate negative directory cache entries now that a
+ job has completed and possibly created new files that
+ was missing earlier. */
+ dir_cache_invalid_after_job ();
+
+ if (pproc->enmType == kRegular) {
+ (void)process_file_io_private(pproc, FALSE);
+ }
+#else
+ (void) process_file_io(pproc);
+#endif
+ return ((HANDLE) pproc);
+ }
+}
+
+long
+process_signal(HANDLE proc)
+{
+ if (proc == INVALID_HANDLE_VALUE) return 0;
+ return (((sub_process *)proc)->signal);
+}
+
+long
+process_last_err(HANDLE proc)
+{
+ if (proc == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
+ return (((sub_process *)proc)->last_err);
+}
+
+long
+process_exit_code(HANDLE proc)
+{
+ if (proc == INVALID_HANDLE_VALUE) return EXIT_FAILURE;
+ return (((sub_process *)proc)->exit_code);
+}
+
+void
+process_noinherit(int fd)
+{
+ HANDLE fh = (HANDLE)_get_osfhandle(fd);
+
+ if (fh && fh != INVALID_HANDLE_VALUE)
+ SetHandleInformation(fh, HANDLE_FLAG_INHERIT, 0);
+}
+
+/*
+2006-02:
+All the following functions are currently unused.
+All of them would crash gmake if called with argument INVALID_HANDLE_VALUE.
+Hence whoever wants to use one of this functions must invent and implement
+a reasonable error handling for this function.
+
+char *
+process_outbuf(HANDLE proc)
+{
+ return (((sub_process *)proc)->outp);
+}
+
+char *
+process_errbuf(HANDLE proc)
+{
+ return (((sub_process *)proc)->errp);
+}
+
+int
+process_outcnt(HANDLE proc)
+{
+ return (((sub_process *)proc)->outcnt);
+}
+
+int
+process_errcnt(HANDLE proc)
+{
+ return (((sub_process *)proc)->errcnt);
+}
+
+void
+process_pipes(HANDLE proc, int pipes[3])
+{
+ pipes[0] = ((sub_process *)proc)->sv_stdin[0];
+ pipes[1] = ((sub_process *)proc)->sv_stdout[0];
+ pipes[2] = ((sub_process *)proc)->sv_stderr[0];
+ return;
+}
+*/
+
+ HANDLE
+process_init()
+{
+ sub_process *pproc;
+ /*
+ * open file descriptors for attaching stdin/stdout/sterr
+ */
+ HANDLE stdin_pipes[2];
+ HANDLE stdout_pipes[2];
+ HANDLE stderr_pipes[2];
+ SECURITY_ATTRIBUTES inherit;
+ BYTE sd[SECURITY_DESCRIPTOR_MIN_LENGTH];
+
+ pproc = malloc(sizeof(*pproc));
+ memset(pproc, 0, sizeof(*pproc));
+
+ /* We can't use NULL for lpSecurityDescriptor because that
+ uses the default security descriptor of the calling process.
+ Instead we use a security descriptor with no DACL. This
+ allows nonrestricted access to the associated objects. */
+
+ if (!InitializeSecurityDescriptor((PSECURITY_DESCRIPTOR)(&sd),
+ SECURITY_DESCRIPTOR_REVISION)) {
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ return((HANDLE)pproc);
+ }
+
+ inherit.nLength = sizeof(inherit);
+ inherit.lpSecurityDescriptor = (PSECURITY_DESCRIPTOR)(&sd);
+ inherit.bInheritHandle = TRUE;
+
+ // By convention, parent gets pipe[0], and child gets pipe[1]
+ // This means the READ side of stdin pipe goes into pipe[1]
+ // and the WRITE side of the stdout and stderr pipes go into pipe[1]
+ if (CreatePipe( &stdin_pipes[1], &stdin_pipes[0], &inherit, 0) == FALSE ||
+ CreatePipe( &stdout_pipes[0], &stdout_pipes[1], &inherit, 0) == FALSE ||
+ CreatePipe( &stderr_pipes[0], &stderr_pipes[1], &inherit, 0) == FALSE) {
+
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ return((HANDLE)pproc);
+ }
+
+ //
+ // Mark the parent sides of the pipes as non-inheritable
+ //
+ if (SetHandleInformation(stdin_pipes[0],
+ HANDLE_FLAG_INHERIT, 0) == FALSE ||
+ SetHandleInformation(stdout_pipes[0],
+ HANDLE_FLAG_INHERIT, 0) == FALSE ||
+ SetHandleInformation(stderr_pipes[0],
+ HANDLE_FLAG_INHERIT, 0) == FALSE) {
+
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ return((HANDLE)pproc);
+ }
+ pproc->sv_stdin[0] = (intptr_t) stdin_pipes[0];
+ pproc->sv_stdin[1] = (intptr_t) stdin_pipes[1];
+ pproc->sv_stdout[0] = (intptr_t) stdout_pipes[0];
+ pproc->sv_stdout[1] = (intptr_t) stdout_pipes[1];
+ pproc->sv_stderr[0] = (intptr_t) stderr_pipes[0];
+ pproc->sv_stderr[1] = (intptr_t) stderr_pipes[1];
+
+ pproc->using_pipes = 1;
+
+ pproc->lerrno = 0;
+
+ return((HANDLE)pproc);
+}
+
+
+ HANDLE
+process_init_fd(HANDLE stdinh, HANDLE stdouth, HANDLE stderrh)
+{
+ sub_process *pproc;
+
+ pproc = malloc(sizeof(*pproc));
+ if (pproc) {
+ memset(pproc, 0, sizeof(*pproc));
+
+ /*
+ * Just pass the provided file handles to the 'child
+ * side' of the pipe, bypassing pipes altogether.
+ */
+ pproc->sv_stdin[1] = (intptr_t) stdinh;
+ pproc->sv_stdout[1] = (intptr_t) stdouth;
+ pproc->sv_stderr[1] = (intptr_t) stderrh;
+
+ pproc->last_err = pproc->lerrno = 0;
+ }
+
+ return((HANDLE)pproc);
+}
+
+
+static HANDLE
+find_file(const char *exec_path, const char *path_var,
+ char *full_fname, DWORD full_len)
+{
+ HANDLE exec_handle;
+ char *fname;
+ char *ext;
+ DWORD req_len;
+ int i;
+ static const char *extensions[] =
+ /* Should .com come before no-extension case? */
+ { ".exe", ".cmd", ".bat", "", ".com", NULL };
+
+ fname = xmalloc(strlen(exec_path) + 5);
+ strcpy(fname, exec_path);
+ ext = fname + strlen(fname);
+
+ for (i = 0; extensions[i]; i++) {
+ strcpy(ext, extensions[i]);
+ if (((req_len = SearchPath (path_var, fname, NULL, full_len,
+ full_fname, NULL)) > 0
+ /* For compatibility with previous code, which
+ used OpenFile, and with Windows operation in
+ general, also look in various default
+ locations, such as Windows directory and
+ Windows System directory. Warning: this also
+ searches PATH in the Make's environment, which
+ might not be what the Makefile wants, but it
+ seems to be OK as a fallback, after the
+ previous SearchPath failed to find on child's
+ PATH. */
+ || (req_len = SearchPath (NULL, fname, NULL, full_len,
+ full_fname, NULL)) > 0)
+ && req_len <= full_len
+ && (exec_handle =
+ CreateFile(full_fname,
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL)) != INVALID_HANDLE_VALUE) {
+ free(fname);
+ return(exec_handle);
+ }
+ }
+
+ free(fname);
+ return INVALID_HANDLE_VALUE;
+}
+
+/*
+ * Return non-zero of FNAME specifies a batch file and its name
+ * includes embedded whitespace.
+ */
+
+static int
+batch_file_with_spaces(const char *fname)
+{
+ size_t fnlen = strlen(fname);
+
+ return (fnlen > 4
+ && (_strnicmp(fname + fnlen - 4, ".bat", 4) == 0
+ || _strnicmp(fname + fnlen - 4, ".cmd", 4) == 0)
+ /* The set of characters in the 2nd arg to strpbrk
+ should be the same one used by make_command_line
+ below to decide whether an argv[] element needs
+ quoting. */
+ && strpbrk(fname, " \t") != NULL);
+}
+
+
+/*
+ * Description: Create the child process to be helped
+ *
+ * Returns: success <=> 0
+ *
+ * Notes/Dependencies:
+ */
+long
+process_begin(
+ HANDLE proc,
+ char **argv,
+ char **envp,
+ char *exec_path,
+ char *as_user)
+{
+ sub_process *pproc = (sub_process *)proc;
+ char *shell_name = 0;
+ int file_not_found=0;
+ HANDLE exec_handle;
+ char exec_fname[MAX_PATH];
+ const char *path_var = NULL;
+ char **ep;
+ char buf[MAX_PATH];
+ DWORD bytes_returned;
+ DWORD flags;
+ char *command_line;
+ STARTUPINFO startInfo;
+ PROCESS_INFORMATION procInfo;
+ char *envblk=NULL;
+ int envsize_needed = 0;
+ int pass_null_exec_path = 0;
+#ifdef KMK
+ size_t exec_path_len;
+ extern int process_priority;
+
+ assert (pproc->enmType == kRegular);
+#endif
+
+ /*
+ * Shell script detection... if the exec_path starts with #! then
+ * we want to exec shell-script-name exec-path, not just exec-path
+ * NT doesn't recognize #!/bin/sh or #!/etc/Tivoli/bin/perl. We do not
+ * hard-code the path to the shell or perl or whatever: Instead, we
+ * assume it's in the path somewhere (generally, the NT tools
+ * bin directory)
+ */
+
+#ifdef KMK
+ /* kmk performance: Don't bother looking for shell scripts in .exe files. */
+ exec_path_len = strlen(exec_path);
+ if (exec_path_len > 4
+ && exec_path[exec_path_len - 4] == '.'
+ && !stricmp(exec_path + exec_path_len - 3, "exe")) {
+ exec_handle = INVALID_HANDLE_VALUE;
+ exec_fname[0] = '\0';
+ }
+ else {
+#endif /* KMK */
+
+ /* Use the Makefile's value of PATH to look for the program to
+ execute, because it could be different from Make's PATH
+ (e.g., if the target sets its own value. */
+ if (envp)
+ for (ep = envp; *ep; ep++) {
+ if (strncmp (*ep, "PATH=", 5) == 0
+ || strncmp (*ep, "Path=", 5) == 0) {
+ path_var = *ep + 5;
+ break;
+ }
+ }
+ exec_handle = find_file(exec_path, path_var,
+ exec_fname, sizeof(exec_fname));
+#ifdef KMK
+ }
+#endif
+
+ /*
+ * If we couldn't open the file, just assume that Windows will be
+ * somehow able to find and execute it. If the first character
+ * of the command is '/', assume they set SHELL to a Unixy shell
+ * that have some magic mounts known only to it, and run the whole
+ * command via $SHELL -c "COMMAND" instead.
+ */
+ if (exec_handle == INVALID_HANDLE_VALUE) {
+ if (exec_path[0] == '/') {
+ char *new_argv0;
+ char **argvi = argv;
+ int arglen = 0;
+
+ strcpy(buf, variable_expand ("$(SHELL)"));
+ shell_name = &buf[0];
+ strcpy(exec_fname, "-c");
+ /* Construct a single command string in argv[0]. */
+ while (*argvi) {
+ arglen += strlen(*argvi) + 1;
+ argvi++;
+ }
+ new_argv0 = xmalloc(arglen + 1);
+ new_argv0[0] = '\0';
+ for (argvi = argv; *argvi; argvi++) {
+ strcat(new_argv0, *argvi);
+ strcat(new_argv0, " ");
+ }
+ /* Remove the extra blank at the end. */
+ new_argv0[arglen-1] = '\0';
+ free(argv[0]);
+ argv[0] = new_argv0;
+ argv[1] = NULL;
+ }
+ else
+ file_not_found++;
+ }
+ else {
+ /* Attempt to read the first line of the file */
+ if (ReadFile( exec_handle,
+ buf, sizeof(buf) - 1, /* leave room for trailing NULL */
+ &bytes_returned, 0) == FALSE || bytes_returned < 2) {
+
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_IO;
+ CloseHandle(exec_handle);
+ return(-1);
+ }
+ if (buf[0] == '#' && buf[1] == '!') {
+ /*
+ * This is a shell script... Change the command line from
+ * exec_path args to shell_name exec_path args
+ */
+ char *p;
+
+ /* Make sure buf is NULL terminated */
+ buf[bytes_returned] = 0;
+ /*
+ * Depending on the file system type, etc. the first line
+ * of the shell script may end with newline or newline-carriage-return
+ * Whatever it ends with, cut it off.
+ */
+ p= strchr(buf, '\n');
+ if (p)
+ *p = 0;
+ p = strchr(buf, '\r');
+ if (p)
+ *p = 0;
+
+ /*
+ * Find base name of shell
+ */
+ shell_name = strrchr( buf, '/');
+ if (shell_name) {
+ shell_name++;
+ } else {
+ shell_name = &buf[2];/* skipping "#!" */
+ }
+
+ }
+ CloseHandle(exec_handle);
+ }
+
+ flags = 0;
+
+ if (file_not_found)
+ command_line = make_command_line( shell_name, exec_path, argv);
+ else {
+ /* If exec_fname includes whitespace, CreateProcess
+ behaves erratically and unreliably, and often fails
+ if argv[0] also includes whitespace (and thus will
+ be quoted by make_command_line below). So in that
+ case, we don't pass exec_fname as the 1st arg to
+ CreateProcess, but instead replace argv[0] with
+ exec_fname (to keep its leading directories and
+ extension as found by find_file), and pass NULL to
+ CreateProcess as its 1st arg. This works around
+ the bugs in CreateProcess, which are probably
+ caused by its passing the command to cmd.exe with
+ some incorrect quoting. */
+ if (!shell_name
+ && batch_file_with_spaces(exec_fname)
+ && _stricmp(exec_path, argv[0]) == 0) {
+ char *new_argv, *p;
+ char **argvi;
+ int arglen, i;
+ pass_null_exec_path = 1;
+ /* Rewrite argv[] replacing argv[0] with exec_fname. */
+ for (argvi = argv + 1, arglen = strlen(exec_fname) + 1;
+ *argvi;
+ argvi++) {
+ arglen += strlen(*argvi) + 1;
+ }
+ new_argv = xmalloc(arglen);
+ p = strcpy(new_argv, exec_fname) + strlen(exec_fname) + 1;
+ for (argvi = argv + 1, i = 1; *argvi; argvi++, i++) {
+ strcpy(p, *argvi);
+ argv[i] = p;
+ p += strlen(*argvi) + 1;
+ }
+ argv[i] = NULL;
+ free (argv[0]);
+ argv[0] = new_argv;
+ }
+ command_line = make_command_line( shell_name, exec_fname, argv);
+ }
+
+ if ( command_line == NULL ) {
+ pproc->last_err = 0;
+ pproc->lerrno = E_NO_MEM;
+ return(-1);
+ }
+
+ if (envp) {
+ if (arr2envblk(envp, &envblk, &envsize_needed) == FALSE) {
+ pproc->lerrno = E_NO_MEM;
+ free( command_line );
+ if ((pproc->last_err == ERROR_INVALID_PARAMETER
+ || pproc->last_err == ERROR_MORE_DATA)
+ && envsize_needed > 32*1024) {
+ fprintf (stderr, "CreateProcess failed, probably because environment is too large (%d bytes).\n",
+ envsize_needed);
+ }
+ pproc->last_err = 0;
+ return(-1);
+ }
+ }
+
+ if (shell_name || file_not_found || pass_null_exec_path) {
+ exec_path = 0; /* Search for the program in %Path% */
+ } else {
+ exec_path = exec_fname;
+ }
+
+ /*
+ * Set up inherited stdin, stdout, stderr for child
+ */
+ memset(&startInfo, '\0', sizeof(startInfo));
+ GetStartupInfo(&startInfo);
+#ifndef KMK
+ startInfo.dwFlags = STARTF_USESTDHANDLES;
+#endif
+ startInfo.lpReserved = 0;
+ startInfo.cbReserved2 = 0;
+ startInfo.lpReserved2 = 0;
+#ifndef KMK
+ startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1];
+ startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1];
+ startInfo.hStdError = (HANDLE)pproc->sv_stderr[1];
+#else
+ if ( ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1])
+ || ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1])
+ || ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1]) ) {
+ startInfo.dwFlags = STARTF_USESTDHANDLES;
+ startInfo.hStdInput = (HANDLE)pproc->sv_stdin[1];
+ startInfo.hStdOutput = (HANDLE)pproc->sv_stdout[1];
+ startInfo.hStdError = (HANDLE)pproc->sv_stderr[1];
+ } else {
+ startInfo.dwFlags = 0;
+ startInfo.hStdInput = 0;
+ startInfo.hStdOutput = 0;
+ startInfo.hStdError = 0;
+ }
+#endif
+
+ if (as_user) {
+ free(envblk);
+ return -1;
+ } else {
+ DB (DB_JOBS, ("CreateProcess(%s,%s,...)\n",
+ exec_path ? exec_path : "NULL",
+ command_line ? command_line : "NULL"));
+#ifdef KMK
+ if (exec_fname[0])
+ kmk_cache_exec_image_a(exec_fname);
+ else if (exec_path)
+ kmk_cache_exec_image_a(exec_path);
+ else if (argv[0])
+ kmk_cache_exec_image_a(argv[0]);
+
+ switch (process_priority) {
+ case 1: flags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
+ case 2: flags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
+ case 3: flags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
+ case 4: flags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
+ case 5: flags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
+ }
+#endif
+ if (CreateProcess(
+ exec_path,
+ command_line,
+ NULL,
+ 0, /* default security attributes for thread */
+ TRUE, /* inherit handles (e.g. helper pipes, oserv socket) */
+ flags,
+ envblk,
+ 0, /* default starting directory */
+ &startInfo,
+ &procInfo) == FALSE) {
+
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_FORK;
+ fprintf(stderr, "process_begin: CreateProcess(%s, %s, ...) failed.\n",
+ exec_path ? exec_path : "NULL", command_line);
+ free(envblk);
+ free( command_line );
+ return(-1);
+ }
+#ifdef KMK
+ switch (process_priority) {
+ case 1: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_IDLE); break;
+ case 2: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
+ case 3: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_NORMAL); break;
+ case 4: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
+ case 5: SetThreadPriority(procInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
+ }
+ ResumeThread(procInfo.hThread);
+#endif
+ }
+
+ pproc->pid = (pid_t)procInfo.hProcess;
+ /* Close the thread handle -- we'll just watch the process */
+ CloseHandle(procInfo.hThread);
+
+ /* Close the halves of the pipes we don't need */
+ if ((HANDLE)pproc->sv_stdin[1] != INVALID_HANDLE_VALUE && pproc->sv_stdin[1])
+ CloseHandle((HANDLE)pproc->sv_stdin[1]);
+ if ((HANDLE)pproc->sv_stdout[1] != INVALID_HANDLE_VALUE && pproc->sv_stdout[1])
+ CloseHandle((HANDLE)pproc->sv_stdout[1]);
+ if ((HANDLE)pproc->sv_stderr[1] != INVALID_HANDLE_VALUE && pproc->sv_stderr[1])
+ CloseHandle((HANDLE)pproc->sv_stderr[1]);
+ pproc->sv_stdin[1] = 0;
+ pproc->sv_stdout[1] = 0;
+ pproc->sv_stderr[1] = 0;
+
+ free( command_line );
+ free(envblk);
+ pproc->lerrno=0;
+ return 0;
+}
+
+
+
+#if 0 /* unused */
+static DWORD
+proc_stdin_thread(sub_process *pproc)
+{
+ DWORD in_done;
+ for (;;) {
+ if (WriteFile( (HANDLE) pproc->sv_stdin[0], pproc->inp, pproc->incnt,
+ &in_done, NULL) == FALSE)
+ _endthreadex(0);
+ // This if should never be true for anonymous pipes, but gives
+ // us a chance to change I/O mechanisms later
+ if (in_done < pproc->incnt) {
+ pproc->incnt -= in_done;
+ pproc->inp += in_done;
+ } else {
+ _endthreadex(0);
+ }
+ }
+ return 0; // for compiler warnings only.. not reached
+}
+
+static DWORD
+proc_stdout_thread(sub_process *pproc)
+{
+ DWORD bufsize = 1024;
+ char c;
+ DWORD nread;
+ pproc->outp = malloc(bufsize);
+ if (pproc->outp == NULL)
+ _endthreadex(0);
+ pproc->outcnt = 0;
+
+ for (;;) {
+ if (ReadFile( (HANDLE)pproc->sv_stdout[0], &c, 1, &nread, NULL)
+ == FALSE) {
+/* map_windows32_error_to_string(GetLastError());*/
+ _endthreadex(0);
+ }
+ if (nread == 0)
+ _endthreadex(0);
+ if (pproc->outcnt + nread > bufsize) {
+ bufsize += nread + 512;
+ pproc->outp = realloc(pproc->outp, bufsize);
+ if (pproc->outp == NULL) {
+ pproc->outcnt = 0;
+ _endthreadex(0);
+ }
+ }
+ pproc->outp[pproc->outcnt++] = c;
+ }
+ return 0;
+}
+
+static DWORD
+proc_stderr_thread(sub_process *pproc)
+{
+ DWORD bufsize = 1024;
+ char c;
+ DWORD nread;
+ pproc->errp = malloc(bufsize);
+ if (pproc->errp == NULL)
+ _endthreadex(0);
+ pproc->errcnt = 0;
+
+ for (;;) {
+ if (ReadFile( (HANDLE)pproc->sv_stderr[0], &c, 1, &nread, NULL) == FALSE) {
+ map_windows32_error_to_string(GetLastError());
+ _endthreadex(0);
+ }
+ if (nread == 0)
+ _endthreadex(0);
+ if (pproc->errcnt + nread > bufsize) {
+ bufsize += nread + 512;
+ pproc->errp = realloc(pproc->errp, bufsize);
+ if (pproc->errp == NULL) {
+ pproc->errcnt = 0;
+ _endthreadex(0);
+ }
+ }
+ pproc->errp[pproc->errcnt++] = c;
+ }
+ return 0;
+}
+
+
+/*
+ * Purpose: collects output from child process and returns results
+ *
+ * Description:
+ *
+ * Returns:
+ *
+ * Notes/Dependencies:
+ */
+ long
+process_pipe_io(
+ HANDLE proc,
+ char *stdin_data,
+ int stdin_data_len)
+{
+ sub_process *pproc = (sub_process *)proc;
+ bool_t stdin_eof = FALSE, stdout_eof = FALSE, stderr_eof = FALSE;
+ HANDLE childhand = (HANDLE) pproc->pid;
+ HANDLE tStdin = NULL, tStdout = NULL, tStderr = NULL;
+ unsigned int dwStdin, dwStdout, dwStderr;
+ HANDLE wait_list[4];
+ DWORD wait_count;
+ DWORD wait_return;
+ HANDLE ready_hand;
+ bool_t child_dead = FALSE;
+ BOOL GetExitCodeResult;
+#ifdef KMK
+ assert (pproc->enmType == kRegular);
+#endif
+
+ /*
+ * Create stdin thread, if needed
+ */
+ pproc->inp = stdin_data;
+ pproc->incnt = stdin_data_len;
+ if (!pproc->inp) {
+ stdin_eof = TRUE;
+ CloseHandle((HANDLE)pproc->sv_stdin[0]);
+ pproc->sv_stdin[0] = 0;
+ } else {
+ tStdin = (HANDLE) _beginthreadex( 0, 1024,
+ (unsigned (__stdcall *) (void *))proc_stdin_thread,
+ pproc, 0, &dwStdin);
+ if (tStdin == 0) {
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ goto done;
+ }
+ }
+
+ /*
+ * Assume child will produce stdout and stderr
+ */
+ tStdout = (HANDLE) _beginthreadex( 0, 1024,
+ (unsigned (__stdcall *) (void *))proc_stdout_thread, pproc, 0,
+ &dwStdout);
+ tStderr = (HANDLE) _beginthreadex( 0, 1024,
+ (unsigned (__stdcall *) (void *))proc_stderr_thread, pproc, 0,
+ &dwStderr);
+
+ if (tStdout == 0 || tStderr == 0) {
+
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ goto done;
+ }
+
+
+ /*
+ * Wait for all I/O to finish and for the child process to exit
+ */
+
+ while (!stdin_eof || !stdout_eof || !stderr_eof || !child_dead) {
+ wait_count = 0;
+ if (!stdin_eof) {
+ wait_list[wait_count++] = tStdin;
+ }
+ if (!stdout_eof) {
+ wait_list[wait_count++] = tStdout;
+ }
+ if (!stderr_eof) {
+ wait_list[wait_count++] = tStderr;
+ }
+ if (!child_dead) {
+ wait_list[wait_count++] = childhand;
+ }
+
+ wait_return = WaitForMultipleObjects(wait_count, wait_list,
+ FALSE, /* don't wait for all: one ready will do */
+ child_dead? 1000 :INFINITE); /* after the child dies, subthreads have
+ one second to collect all remaining output */
+
+ if (wait_return == WAIT_FAILED) {
+/* map_windows32_error_to_string(GetLastError());*/
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ goto done;
+ }
+
+ ready_hand = wait_list[wait_return - WAIT_OBJECT_0];
+
+ if (ready_hand == tStdin) {
+ CloseHandle((HANDLE)pproc->sv_stdin[0]);
+ pproc->sv_stdin[0] = 0;
+ CloseHandle(tStdin);
+ tStdin = 0;
+ stdin_eof = TRUE;
+
+ } else if (ready_hand == tStdout) {
+
+ CloseHandle((HANDLE)pproc->sv_stdout[0]);
+ pproc->sv_stdout[0] = 0;
+ CloseHandle(tStdout);
+ tStdout = 0;
+ stdout_eof = TRUE;
+
+ } else if (ready_hand == tStderr) {
+
+ CloseHandle((HANDLE)pproc->sv_stderr[0]);
+ pproc->sv_stderr[0] = 0;
+ CloseHandle(tStderr);
+ tStderr = 0;
+ stderr_eof = TRUE;
+
+ } else if (ready_hand == childhand) {
+
+ DWORD ierr;
+ GetExitCodeResult = GetExitCodeProcess(childhand, &ierr);
+ if (ierr == CONTROL_C_EXIT) {
+ pproc->signal = SIGINT;
+ } else {
+ pproc->exit_code = ierr;
+ }
+ if (GetExitCodeResult == FALSE) {
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ goto done;
+ }
+ child_dead = TRUE;
+
+ } else {
+
+ /* ?? Got back a handle we didn't query ?? */
+ pproc->last_err = 0;
+ pproc->lerrno = E_FAIL;
+ goto done;
+ }
+ }
+
+ done:
+ if (tStdin != 0)
+ CloseHandle(tStdin);
+ if (tStdout != 0)
+ CloseHandle(tStdout);
+ if (tStderr != 0)
+ CloseHandle(tStderr);
+
+ if (pproc->lerrno)
+ return(-1);
+ else
+ return(0);
+
+}
+#endif /* unused */
+
+#ifndef KMK /* unused */
+/*
+ * Purpose: collects output from child process and returns results
+ *
+ * Description:
+ *
+ * Returns:
+ *
+ * Notes/Dependencies:
+ */
+ long
+process_file_io(
+ HANDLE proc)
+{
+ sub_process *pproc;
+
+ if (proc == NULL)
+ pproc = process_wait_for_any_private(1, 0);
+ else
+ pproc = (sub_process *)proc;
+
+ /* some sort of internal error */
+ if (!pproc)
+ return -1;
+
+ return process_file_io_private(proc, TRUE);
+}
+#endif /* !KMK - unused */
+
+/* private function, avoid some kernel calls. (bird) */
+static long
+process_file_io_private(
+ sub_process *pproc,
+ BOOL fNeedToWait)
+{
+ HANDLE childhand;
+ DWORD wait_return;
+ BOOL GetExitCodeResult;
+ DWORD ierr;
+
+ childhand = (HANDLE) pproc->pid;
+
+ /*
+ * This function is poorly named, and could also be used just to wait
+ * for child death if you're doing your own pipe I/O. If that is
+ * the case, close the pipe handles here.
+ */
+ if (pproc->sv_stdin[0]) {
+ CloseHandle((HANDLE)pproc->sv_stdin[0]);
+ pproc->sv_stdin[0] = 0;
+ }
+ if (pproc->sv_stdout[0]) {
+ CloseHandle((HANDLE)pproc->sv_stdout[0]);
+ pproc->sv_stdout[0] = 0;
+ }
+ if (pproc->sv_stderr[0]) {
+ CloseHandle((HANDLE)pproc->sv_stderr[0]);
+ pproc->sv_stderr[0] = 0;
+ }
+
+#ifdef KMK
+ if (childhand == NULL || childhand == INVALID_HANDLE_VALUE) {
+ goto done2;
+ }
+#endif
+
+ /*
+ * Wait for the child process to exit
+ */
+
+ if (fNeedToWait) { /* bird */
+ wait_return = WaitForSingleObject(childhand, INFINITE);
+
+ if (wait_return != WAIT_OBJECT_0) {
+/* map_windows32_error_to_string(GetLastError());*/
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ goto done2;
+ }
+ } /* bird */
+
+ GetExitCodeResult = GetExitCodeProcess(childhand, &ierr);
+ if (ierr == CONTROL_C_EXIT) {
+ pproc->signal = SIGINT;
+ } else {
+ pproc->exit_code = ierr;
+ }
+ if (GetExitCodeResult == FALSE) {
+ pproc->last_err = GetLastError();
+ pproc->lerrno = E_SCALL;
+ }
+
+done2:
+ if (pproc->lerrno)
+ return(-1);
+ else
+ return(0);
+
+}
+
+/*
+ * Description: Clean up any leftover handles, etc. It is up to the
+ * caller to manage and free the input, output, and stderr buffers.
+ */
+ void
+process_cleanup(
+ HANDLE proc)
+{
+ sub_process *pproc = (sub_process *)proc;
+ int i;
+
+#ifdef KMK
+ if (pproc->enmType == kRegular) {
+#endif
+ if (pproc->using_pipes) {
+ for (i= 0; i <= 1; i++) {
+ if ((HANDLE)pproc->sv_stdin[i]
+ && (HANDLE)pproc->sv_stdin[i] != INVALID_HANDLE_VALUE)
+ CloseHandle((HANDLE)pproc->sv_stdin[i]);
+ if ((HANDLE)pproc->sv_stdout[i]
+ && (HANDLE)pproc->sv_stdout[i] != INVALID_HANDLE_VALUE)
+ CloseHandle((HANDLE)pproc->sv_stdout[i]);
+ if ((HANDLE)pproc->sv_stderr[i]
+ && (HANDLE)pproc->sv_stderr[i] != INVALID_HANDLE_VALUE)
+ CloseHandle((HANDLE)pproc->sv_stderr[i]);
+ }
+ }
+ if ((HANDLE)pproc->pid)
+ CloseHandle((HANDLE)pproc->pid);
+#ifdef KMK
+ } else if (pproc->enmType == kSubmit) {
+ kSubmitSubProcCleanup(pproc->clue);
+ } else {
+ assert(0);
+ return;
+ }
+ pproc->enmType = kSubProcFreed;
+#endif
+
+ free(pproc);
+}
+
+
+/*
+ * Description:
+ * Create a command line buffer to pass to CreateProcess
+ *
+ * Returns: the buffer or NULL for failure
+ * Shell case: sh_name a:/full/path/to/script argv[1] argv[2] ...
+ * Otherwise: argv[0] argv[1] argv[2] ...
+ *
+ * Notes/Dependencies:
+ * CreateProcess does not take an argv, so this command creates a
+ * command line for the executable.
+ */
+
+static char *
+make_command_line( char *shell_name, char *full_exec_path, char **argv)
+{
+ int argc = 0;
+ char** argvi;
+ int* enclose_in_quotes = NULL;
+ int* enclose_in_quotes_i;
+ unsigned int bytes_required = 0;
+ char* command_line;
+ char* command_line_i;
+ int cygwin_mode = 0; /* HAVE_CYGWIN_SHELL */
+ int have_sh = 0; /* HAVE_CYGWIN_SHELL */
+#undef HAVE_CYGWIN_SHELL /* bird: paranoia */
+#ifdef HAVE_CYGWIN_SHELL
+ have_sh = (shell_name != NULL || strstr(full_exec_path, "sh.exe"));
+ cygwin_mode = 1;
+#endif
+
+ if (shell_name && full_exec_path) {
+ bytes_required
+ = strlen(shell_name) + 1 + strlen(full_exec_path);
+ /*
+ * Skip argv[0] if any, when shell_name is given.
+ * The special case of "-c" in full_exec_path means
+ * argv[0] is not the shell name, but the command string
+ * to pass to the shell.
+ */
+ if (*argv && strcmp(full_exec_path, "-c")) argv++;
+ /*
+ * Add one for the intervening space.
+ */
+ if (*argv) bytes_required++;
+ }
+
+ argvi = argv;
+ while (*(argvi++)) argc++;
+
+ if (argc) {
+ enclose_in_quotes = (int*) calloc(1, argc * sizeof(int));
+
+ if (!enclose_in_quotes) {
+ return NULL;
+ }
+ }
+
+ /* We have to make one pass through each argv[i] to see if we need
+ * to enclose it in ", so we might as well figure out how much
+ * memory we'll need on the same pass.
+ */
+
+ argvi = argv;
+ enclose_in_quotes_i = enclose_in_quotes;
+ while(*argvi) {
+ char* p = *argvi;
+ unsigned int backslash_count = 0;
+
+ /*
+ * We have to enclose empty arguments in ".
+ */
+ if (!(*p)) *enclose_in_quotes_i = 1;
+
+ while(*p) {
+ switch (*p) {
+ case '\"':
+ /*
+ * We have to insert a backslash for each "
+ * and each \ that precedes the ".
+ */
+ bytes_required += (backslash_count + 1);
+ backslash_count = 0;
+ break;
+
+#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
+ case '\\':
+ backslash_count++;
+ break;
+#endif
+ /*
+ * At one time we set *enclose_in_quotes_i for '*' or '?' to suppress
+ * wildcard expansion in programs linked with MSVC's SETARGV.OBJ so
+ * that argv in always equals argv out. This was removed. Say you have
+ * such a program named glob.exe. You enter
+ * glob '*'
+ * at the sh command prompt. Obviously the intent is to make glob do the
+ * wildcarding instead of sh. If we set *enclose_in_quotes_i for '*' or '?',
+ * then the command line that glob would see would be
+ * glob "*"
+ * and the _setargv in SETARGV.OBJ would _not_ expand the *.
+ */
+ case ' ':
+ case '\t':
+ *enclose_in_quotes_i = 1;
+ /* fall through */
+
+ default:
+ backslash_count = 0;
+ break;
+ }
+
+ /*
+ * Add one for each character in argv[i].
+ */
+ bytes_required++;
+
+ p++;
+ }
+
+ if (*enclose_in_quotes_i) {
+ /*
+ * Add one for each enclosing ",
+ * and one for each \ that precedes the
+ * closing ".
+ */
+ bytes_required += (backslash_count + 2);
+ }
+
+ /*
+ * Add one for the intervening space.
+ */
+ if (*(++argvi)) bytes_required++;
+ enclose_in_quotes_i++;
+ }
+
+ /*
+ * Add one for the terminating NULL.
+ */
+ bytes_required++;
+#ifdef KMK /* for the space before the final " in case we need it. */
+ bytes_required++;
+#endif
+
+ command_line = (char*) malloc(bytes_required);
+
+ if (!command_line) {
+ free(enclose_in_quotes);
+ return NULL;
+ }
+
+ command_line_i = command_line;
+
+ if (shell_name && full_exec_path) {
+ while(*shell_name) {
+ *(command_line_i++) = *(shell_name++);
+ }
+
+ *(command_line_i++) = ' ';
+
+ while(*full_exec_path) {
+ *(command_line_i++) = *(full_exec_path++);
+ }
+
+ if (*argv) {
+ *(command_line_i++) = ' ';
+ }
+ }
+
+ argvi = argv;
+ enclose_in_quotes_i = enclose_in_quotes;
+
+ while(*argvi) {
+ char* p = *argvi;
+ unsigned int backslash_count = 0;
+
+ if (*enclose_in_quotes_i) {
+ *(command_line_i++) = '\"';
+ }
+
+ while(*p) {
+ if (*p == '\"') {
+ if (cygwin_mode && have_sh) { /* HAVE_CYGWIN_SHELL */
+ /* instead of a \", cygwin likes "" */
+ *(command_line_i++) = '\"';
+ } else {
+
+ /*
+ * We have to insert a backslash for the "
+ * and each \ that precedes the ".
+ */
+ backslash_count++;
+
+ while(backslash_count) {
+ *(command_line_i++) = '\\';
+ backslash_count--;
+ };
+ }
+#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
+ } else if (*p == '\\') {
+ backslash_count++;
+ } else {
+ backslash_count = 0;
+#endif
+ }
+
+ /*
+ * Copy the character.
+ */
+ *(command_line_i++) = *(p++);
+ }
+
+ if (*enclose_in_quotes_i) {
+#if !defined(HAVE_MKS_SHELL) && !defined(HAVE_CYGWIN_SHELL)
+ /*
+ * Add one \ for each \ that precedes the
+ * closing ".
+ */
+ while(backslash_count--) {
+ *(command_line_i++) = '\\';
+ };
+#endif
+#ifdef KMK
+ /*
+ * ash it put off by echo "hello world" ending up as:
+ * G:/.../kmk_ash.exe -c "echo ""hello world"""
+ * It wants a space before the last '"'.
+ * (The 'test_shell' goals in Makefile.kmk tests this problem.)
+ */
+ if (command_line_i[-1] == '\"' /* && cygwin_mode && have_sh*/ && !argvi[1]) {
+ *(command_line_i++) = ' ';
+ }
+#endif
+
+ *(command_line_i++) = '\"';
+ }
+
+ /*
+ * Append an intervening space.
+ */
+ if (*(++argvi)) {
+ *(command_line_i++) = ' ';
+ }
+
+ enclose_in_quotes_i++;
+ }
+
+ /*
+ * Append the terminating NULL.
+ */
+ *command_line_i = '\0';
+
+ free(enclose_in_quotes);
+ return command_line;
+}
+
+/*
+ * Description: Given an argv and optional envp, launch the process
+ * using the default stdin, stdout, and stderr handles.
+ * Also, register process so that process_wait_for_any_private()
+ * can be used via process_file_io(NULL) or
+ * process_wait_for_any().
+ *
+ * Returns:
+ *
+ * Notes/Dependencies:
+ */
+HANDLE
+process_easy(
+ char **argv,
+ char **envp,
+ int outfd,
+ int errfd)
+{
+ HANDLE hIn = INVALID_HANDLE_VALUE;
+ HANDLE hOut = INVALID_HANDLE_VALUE;
+ HANDLE hErr = INVALID_HANDLE_VALUE;
+ HANDLE hProcess, tmpIn, tmpOut, tmpErr;
+ DWORD e;
+
+ if (proc_index >= MAXIMUM_WAIT_OBJECTS) {
+ DB (DB_JOBS, ("process_easy: All process slots used up\n"));
+ return INVALID_HANDLE_VALUE;
+ }
+#ifdef KMK /* We can effort here by lettering CreateProcess/kernel do the standard handle duplication. */
+ if (outfd != -1 || errfd != -1) {
+#endif
+ /* Standard handles returned by GetStdHandle can be NULL or
+ INVALID_HANDLE_VALUE if the parent process closed them. If that
+ happens, we open the null device and pass its handle to
+ CreateProcess as the corresponding handle to inherit. */
+ tmpIn = GetStdHandle(STD_INPUT_HANDLE);
+ if (DuplicateHandle(GetCurrentProcess(),
+ tmpIn,
+ GetCurrentProcess(),
+ &hIn,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE) {
+ if ((e = GetLastError()) == ERROR_INVALID_HANDLE) {
+ tmpIn = CreateFile("NUL", GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (tmpIn != INVALID_HANDLE_VALUE
+ && DuplicateHandle(GetCurrentProcess(),
+ tmpIn,
+ GetCurrentProcess(),
+ &hIn,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE)
+ CloseHandle(tmpIn);
+ }
+ if (hIn == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "process_easy: DuplicateHandle(In) failed (e=%ld)\n", e);
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+ if (outfd >= 0)
+ tmpOut = (HANDLE)_get_osfhandle (outfd);
+ else
+ tmpOut = GetStdHandle (STD_OUTPUT_HANDLE);
+ if (DuplicateHandle(GetCurrentProcess(),
+ tmpOut,
+ GetCurrentProcess(),
+ &hOut,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE) {
+ if ((e = GetLastError()) == ERROR_INVALID_HANDLE) {
+ tmpOut = CreateFile("NUL", GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (tmpOut != INVALID_HANDLE_VALUE
+ && DuplicateHandle(GetCurrentProcess(),
+ tmpOut,
+ GetCurrentProcess(),
+ &hOut,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE)
+ CloseHandle(tmpOut);
+ }
+ if (hOut == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "process_easy: DuplicateHandle(Out) failed (e=%ld)\n", e);
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+ if (errfd >= 0)
+ tmpErr = (HANDLE)_get_osfhandle (errfd);
+ else
+ tmpErr = GetStdHandle(STD_ERROR_HANDLE);
+ if (DuplicateHandle(GetCurrentProcess(),
+ tmpErr,
+ GetCurrentProcess(),
+ &hErr,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE) {
+ if ((e = GetLastError()) == ERROR_INVALID_HANDLE) {
+ tmpErr = CreateFile("NUL", GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (tmpErr != INVALID_HANDLE_VALUE
+ && DuplicateHandle(GetCurrentProcess(),
+ tmpErr,
+ GetCurrentProcess(),
+ &hErr,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS) == FALSE)
+ CloseHandle(tmpErr);
+ }
+ if (hErr == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "process_easy: DuplicateHandle(Err) failed (e=%ld)\n", e);
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+#ifdef KMK /* saving effort */
+ }
+#endif
+
+ hProcess = process_init_fd(hIn, hOut, hErr);
+
+ if (process_begin(hProcess, argv, envp, argv[0], NULL)) {
+ fake_exits_pending++;
+ /* process_begin() failed: make a note of that. */
+ if (!((sub_process*) hProcess)->last_err)
+ ((sub_process*) hProcess)->last_err = -1;
+#ifdef KMK
+ if (!((sub_process*) hProcess)->exit_code)
+#endif
+ ((sub_process*) hProcess)->exit_code = process_last_err(hProcess);
+
+ /* close up unused handles */
+ if (hIn != INVALID_HANDLE_VALUE)
+ CloseHandle(hIn);
+ if (hOut != INVALID_HANDLE_VALUE)
+ CloseHandle(hOut);
+ if (hErr != INVALID_HANDLE_VALUE)
+ CloseHandle(hErr);
+ }
+
+ process_register(hProcess);
+
+ return hProcess;
+}
diff --git a/src/kmk/w32/subproc/w32err.c b/src/kmk/w32/subproc/w32err.c
new file mode 100644
index 0000000..14eebed
--- /dev/null
+++ b/src/kmk/w32/subproc/w32err.c
@@ -0,0 +1,85 @@
+/* Error handling for Windows
+Copyright (C) 1996-2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <stdlib.h>
+#include <windows.h>
+#include "makeint.h"
+#include "w32err.h"
+
+/*
+ * Description: the windows32 version of perror()
+ *
+ * Returns: a pointer to a static error
+ *
+ * Notes/Dependencies: I got this from
+ * comp.os.ms-windows.programmer.win32
+ */
+const char *
+map_windows32_error_to_string (DWORD ercode) {
+/*
+ * We used to have an MSVC-specific '__declspec (thread)' qualifier
+ * here, with the following comment:
+ *
+ * __declspec (thread) necessary if you will use multiple threads on MSVC
+ *
+ * However, Make was never multithreaded on Windows (except when
+ * Ctrl-C is hit, in which case the main thread is stopped
+ * immediately, so it doesn't matter in this context). The functions
+ * on sub_proc.c that started and stopped additional threads were
+ * never used, and are now #ifdef'ed away. Until we need more than
+ * one thread, we have no problems with the following buffer being
+ * static. (If and when we do need it to be in thread-local storage,
+ * the corresponding GCC qualifier is '__thread'.)
+ */
+ static char szMessageBuffer[128];
+ /* Fill message buffer with a default message in
+ * case FormatMessage fails
+ */
+ wsprintf (szMessageBuffer, "Error %ld\n", ercode);
+
+ /*
+ * Special code for winsock error handling.
+ */
+ if (ercode > WSABASEERR) {
+#if 0
+ HMODULE hModule = GetModuleHandle("wsock32");
+ if (hModule != NULL) {
+ FormatMessage(FORMAT_MESSAGE_FROM_HMODULE,
+ hModule,
+ ercode,
+ LANG_NEUTRAL,
+ szMessageBuffer,
+ sizeof(szMessageBuffer),
+ NULL);
+ FreeLibrary(hModule);
+ }
+#else
+ O (fatal, NILF, szMessageBuffer);
+#endif
+ } else {
+ /*
+ * Default system message handling
+ */
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
+ NULL,
+ ercode,
+ LANG_NEUTRAL,
+ szMessageBuffer,
+ sizeof(szMessageBuffer),
+ NULL);
+ }
+ return szMessageBuffer;
+}
diff --git a/src/kmk/w32/tstFileInfo.c b/src/kmk/w32/tstFileInfo.c
new file mode 100644
index 0000000..94d47bb
--- /dev/null
+++ b/src/kmk/w32/tstFileInfo.c
@@ -0,0 +1,151 @@
+/* $Id: $ */
+/** @file
+ * Test program for some NtQueryInformationFile functionality.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+
+#include <stdio.h>
+#include <Windows.h>
+
+typedef enum _FILE_INFORMATION_CLASS {
+ FileDirectoryInformation = 1,
+ FileFullDirectoryInformation, // 2
+ FileBothDirectoryInformation, // 3
+ FileBasicInformation, // 4 wdm
+ FileStandardInformation, // 5 wdm
+ FileInternalInformation, // 6
+ FileEaInformation, // 7
+ FileAccessInformation, // 8
+ FileNameInformation, // 9
+ FileRenameInformation, // 10
+ FileLinkInformation, // 11
+ FileNamesInformation, // 12
+ FileDispositionInformation, // 13
+ FilePositionInformation, // 14 wdm
+ FileFullEaInformation, // 15
+ FileModeInformation, // 16
+ FileAlignmentInformation, // 17
+ FileAllInformation, // 18
+ FileAllocationInformation, // 19
+ FileEndOfFileInformation, // 20 wdm
+ FileAlternateNameInformation, // 21
+ FileStreamInformation, // 22
+ FilePipeInformation, // 23
+ FilePipeLocalInformation, // 24
+ FilePipeRemoteInformation, // 25
+ FileMailslotQueryInformation, // 26
+ FileMailslotSetInformation, // 27
+ FileCompressionInformation, // 28
+ FileObjectIdInformation, // 29
+ FileCompletionInformation, // 30
+ FileMoveClusterInformation, // 31
+ FileQuotaInformation, // 32
+ FileReparsePointInformation, // 33
+ FileNetworkOpenInformation, // 34
+ FileAttributeTagInformation, // 35
+ FileTrackingInformation, // 36
+ FileIdBothDirectoryInformation, // 37
+ FileIdFullDirectoryInformation, // 38
+ FileMaximumInformation
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef struct _FILE_NAME_INFORMATION
+{
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;
+
+typedef LONG NTSTATUS;
+
+typedef struct _IO_STATUS_BLOCK
+{
+ union
+ {
+ NTSTATUS Status;
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+NTSYSAPI
+NTSTATUS
+NTAPI
+NtQueryInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
+ ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
+
+
+
+int main(int argc, char **argv)
+{
+ int rc = 0;
+ int i;
+
+ NTSTATUS (NTAPI *pfnNtQueryInformationFile)(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation,
+ ULONG Length, FILE_INFORMATION_CLASS FileInformationClass);
+
+ pfnNtQueryInformationFile = GetProcAddress(LoadLibrary("ntdll.dll"), "NtQueryInformationFile");
+
+ for (i = 1; i < argc; i++)
+ {
+ HANDLE hFile = CreateFile(argv[i],
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ NULL);
+ if (hFile)
+ {
+ long rcNt;
+ char abBuf[4096];
+ IO_STATUS_BLOCK Ios;
+
+ memset(abBuf, 0, sizeof(abBuf));
+ memset(&Ios, 0, sizeof(Ios));
+ rcNt = pfnNtQueryInformationFile(hFile, &Ios, abBuf, sizeof(abBuf), FileNameInformation);
+ if (rcNt >= 0)
+ {
+ PFILE_NAME_INFORMATION pFileNameInfo = (PFILE_NAME_INFORMATION)abBuf;
+ printf("#%d: %s - rcNt=%#x - FileNameInformation:\n"
+ " FileName: %ls\n"
+ " FileNameLength: %lu\n",
+ i, argv[i], rcNt,
+ pFileNameInfo->FileName,
+ pFileNameInfo->FileNameLength
+ );
+ }
+ else
+ printf("#%d: %s - rcNt=%#x - FileNameInformation!\n", i, argv[i], rcNt);
+
+ CloseHandle(hFile);
+ }
+ else
+ {
+ printf("#%d: %s - open failed, last error %d\n", i, argv[i], GetLastError());
+ rc = 1;
+ }
+ }
+ return rc;
+}
+
diff --git a/src/kmk/w32/w32os.c b/src/kmk/w32/w32os.c
new file mode 100644
index 0000000..fd30661
--- /dev/null
+++ b/src/kmk/w32/w32os.c
@@ -0,0 +1,220 @@
+/* Windows32-based operating system interface for GNU Make.
+Copyright (C) 2016 Free Software Foundation, Inc.
+This file is part of GNU Make.
+
+GNU Make is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation; either version 3 of the License, or (at your option) any later
+version.
+
+GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "makeint.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <windows.h>
+#include <process.h>
+#include <io.h>
+#include "pathstuff.h"
+#ifndef CONFIG_NEW_WIN_CHILDREN
+# include "sub_proc.h"
+#else
+# include "winchildren.h"
+#endif
+#include "w32err.h"
+#include "os.h"
+#include "debug.h"
+
+/* This section provides OS-specific functions to support the jobserver. */
+
+static char jobserver_semaphore_name[MAX_PATH + 1];
+static HANDLE jobserver_semaphore = NULL;
+
+unsigned int
+jobserver_setup (int slots)
+{
+#ifndef CONFIG_NEW_WIN_CHILDREN
+ /* sub_proc.c cannot wait for more than MAXIMUM_WAIT_OBJECTS objects
+ * and one of them is the job-server semaphore object. Limit the
+ * number of available job slots to (MAXIMUM_WAIT_OBJECTS - 1). */
+
+ if (slots >= MAXIMUM_WAIT_OBJECTS)
+ {
+ slots = MAXIMUM_WAIT_OBJECTS - 1;
+ DB (DB_JOBS, (_("Jobserver slots limited to %d\n"), slots));
+ }
+#endif
+
+ sprintf (jobserver_semaphore_name, "gmake_semaphore_%d", _getpid ());
+
+ jobserver_semaphore = CreateSemaphore (
+ NULL, /* Use default security descriptor */
+ slots, /* Initial count */
+ slots, /* Maximum count */
+ jobserver_semaphore_name); /* Semaphore name */
+
+ if (jobserver_semaphore == NULL)
+ {
+ DWORD err = GetLastError ();
+ const char *estr = map_windows32_error_to_string (err);
+ ONS (fatal, NILF,
+ _("creating jobserver semaphore: (Error %ld: %s)"), err, estr);
+ }
+
+ return 1;
+}
+
+unsigned int
+jobserver_parse_auth (const char *auth)
+{
+ jobserver_semaphore = OpenSemaphore (
+ SEMAPHORE_ALL_ACCESS, /* Semaphore access setting */
+ FALSE, /* Child processes DON'T inherit */
+ auth); /* Semaphore name */
+
+ if (jobserver_semaphore == NULL)
+ {
+ DWORD err = GetLastError ();
+ const char *estr = map_windows32_error_to_string (err);
+ fatal (NILF, strlen (auth) + INTSTR_LENGTH + strlen (estr),
+ _("internal error: unable to open jobserver semaphore '%s': (Error %ld: %s)"),
+ auth, err, estr);
+ }
+ DB (DB_JOBS, (_("Jobserver client (semaphore %s)\n"), auth));
+
+ return 1;
+}
+
+char *
+jobserver_get_auth ()
+{
+ return xstrdup (jobserver_semaphore_name);
+}
+
+unsigned int
+jobserver_enabled ()
+{
+ return jobserver_semaphore != NULL;
+}
+
+/* Close jobserver semaphore */
+void
+jobserver_clear ()
+{
+ if (jobserver_semaphore != NULL)
+ {
+ CloseHandle (jobserver_semaphore);
+ jobserver_semaphore = NULL;
+ }
+}
+
+void
+jobserver_release (int is_fatal)
+{
+ if (! ReleaseSemaphore (
+ jobserver_semaphore, /* handle to semaphore */
+ 1, /* increase count by one */
+ NULL)) /* not interested in previous count */
+ {
+ if (is_fatal)
+ {
+ DWORD err = GetLastError ();
+ const char *estr = map_windows32_error_to_string (err);
+ ONS (fatal, NILF,
+ _("release jobserver semaphore: (Error %ld: %s)"), err, estr);
+ }
+ perror_with_name ("release_jobserver_semaphore", "");
+ }
+}
+
+unsigned int
+jobserver_acquire_all ()
+{
+ unsigned int tokens = 0;
+ while (1)
+ {
+ DWORD dwEvent = WaitForSingleObject (
+ jobserver_semaphore, /* Handle to semaphore */
+ 0); /* DON'T wait on semaphore */
+
+ if (dwEvent != WAIT_OBJECT_0)
+ return tokens;
+
+ ++tokens;
+ }
+}
+
+void
+jobserver_signal ()
+{
+}
+
+void jobserver_pre_child (int recursive)
+{
+}
+
+void jobserver_post_child (int recursive)
+{
+}
+
+void
+jobserver_pre_acquire ()
+{
+}
+
+/* Returns 1 if we got a token, or 0 if a child has completed.
+ The Windows implementation doesn't support load detection. */
+unsigned int
+jobserver_acquire (int timeout)
+{
+#ifndef CONFIG_NEW_WIN_CHILDREN
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS + 1]; /* bird: + 1 to prevent trashing the stack. */
+#else
+ HANDLE handles[2];
+#endif
+ DWORD dwHandleCount;
+ DWORD dwEvent;
+
+ /* Add jobserver semaphore to first slot. */
+ handles[0] = jobserver_semaphore;
+
+#ifndef CONFIG_NEW_WIN_CHILDREN
+ /* Build array of handles to wait for. */
+ dwHandleCount = 1 + process_set_handles (&handles[1]);
+ dwEvent = WaitForMultipleObjects (
+ dwHandleCount, /* number of objects in array */
+ handles, /* array of objects */
+ FALSE, /* wait for any object */
+ INFINITE); /* wait until object is signalled */
+#else
+ /* Add the completed children event as the 2nd one. */
+ handles[1] = (HANDLE)MkWinChildGetCompleteEventHandle ();
+ if (handles[1] == NULL)
+ return 0;
+ dwHandleCount = 2;
+ dwEvent = WaitForMultipleObjectsEx (dwHandleCount,
+ handles,
+ FALSE /*bWaitAll*/,
+ 256, /* INFINITE - paranoia, only wait 256 ms before checking again. */
+ TRUE /*bAlertable*/);
+#endif
+
+ if (dwEvent == WAIT_FAILED)
+ {
+ DWORD err = GetLastError ();
+ const char *estr = map_windows32_error_to_string (err);
+ ONS (fatal, NILF,
+ _("semaphore or child process wait: (Error %ld: %s)"),
+ err, estr);
+ }
+
+ /* WAIT_OBJECT_0 indicates that the semaphore was signalled. */
+ return dwEvent == WAIT_OBJECT_0;
+}
diff --git a/src/kmk/w32/winchildren.c b/src/kmk/w32/winchildren.c
new file mode 100644
index 0000000..635fe99
--- /dev/null
+++ b/src/kmk/w32/winchildren.c
@@ -0,0 +1,3729 @@
+/* $Id: winchildren.c 3359 2020-06-05 16:17:17Z bird $ */
+/** @file
+ * Child process creation and management for kmk.
+ */
+
+/*
+ * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+/* No GNU coding style here atm, convert if upstreamed. */
+
+/** @page pg_win_children Windows child process creation and managment
+ *
+ * This new implementation aims at addressing the following:
+ *
+ * 1. Speed up process creation by doing the expensive CreateProcess call
+ * in a worker thread.
+ *
+ * 2. No 64 process limit imposed by WaitForMultipleObjects.
+ *
+ * 3. Better distribute jobs among processor groups.
+ *
+ * 4. Offloading more expensive kmkbuiltin operations to worker threads,
+ * making the main thread focus on managing child processes.
+ *
+ * 5. Output synchronization using reusable pipes.
+ *
+ *
+ * To be quite honest, the first item (CreateProcess expense) didn't occur to me
+ * at first and was more of a sideeffect discovered along the way. A test
+ * rebuilding IPRT went from 4m52s to 3m19s on a 8 thread system.
+ *
+ * The 2nd and 3rd goals are related to newer build servers that have lots of
+ * CPU threads and various Windows NT (aka NT OS/2 at the time) design choices
+ * made in the late 1980ies.
+ *
+ * WaitForMultipleObjects does not support waiting for more than 64 objects,
+ * unlike poll and select. This is just something everyone ends up having to
+ * work around in the end.
+ *
+ * Affinity masks are uintptr_t sized, so 64-bit hosts can only manage 64
+ * processors and 32-bit only 32. Workaround was introduced with Windows 7
+ * (IIRC) and is called processor groups. The CPU threads are grouped into 1 or
+ * more groups of up to 64 processors. Processes are generally scheduled to a
+ * signle processor group at first, but threads may be changed to be scheduled
+ * on different groups. This code will try distribute children evenly among the
+ * processor groups, using a very simple algorithm (see details in code).
+ *
+ *
+ * @section sec_win_children_av Remarks on Microsoft Defender and other AV
+ *
+ * Part of the motivation for writing this code was horrible CPU utilization on
+ * a brand new AMD Threadripper 1950X system with lots of memory and SSDs,
+ * running 64-bit Windows 10 build 16299.
+ *
+ * Turns out Microsoft defender adds some overhead to CreateProcess
+ * and other stuff:
+ * - Old make with CreateProcess on main thread:
+ * - With runtime defender enabled: 14 min 6 seconds
+ * - With runtime defender disabled: 4 min 49 seconds
+ * - New make with CreateProcess on worker thread (this code):
+ * - With runtime defender enabled: 6 min 29 seconds
+ * - With runtime defender disabled: 4 min 36 seconds
+ * - With runtime defender disabled out dir only: 5 min 59 seconds
+ *
+ * See also kWorker / kSubmit for more bickering about AV & disk encryption.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Windows.h>
+#include <Winternl.h>
+
+#include "../makeint.h"
+#include "../job.h"
+#include "../filedef.h"
+#include "../debug.h"
+#include "../kmkbuiltin.h"
+#include "winchildren.h"
+
+#include <assert.h>
+#include <process.h>
+#include <intrin.h>
+
+#include "nt/nt_child_inject_standard_handles.h"
+#include "console.h"
+
+#ifndef KMK_BUILTIN_STANDALONE
+extern void kmk_cache_exec_image_w(const wchar_t *); /* imagecache.c */
+#endif
+
+/* Option values from main.c: */
+extern const char *win_job_object_mode;
+extern const char *win_job_object_name;
+extern int win_job_object_no_kill;
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#define MKWINCHILD_MAX_PATH 1024
+
+#define MKWINCHILD_DO_SET_PROCESSOR_GROUP
+
+/** Checks the UTF-16 environment variable pointed to is the PATH. */
+#define IS_PATH_ENV_VAR(a_cwcVar, a_pwszVar) \
+ ( (a_cwcVar) >= 5 \
+ && (a_pwszVar)[4] == L'=' \
+ && ((a_pwszVar)[0] == L'P' || (a_pwszVar)[0] == L'p') \
+ && ((a_pwszVar)[1] == L'A' || (a_pwszVar)[1] == L'a') \
+ && ((a_pwszVar)[2] == L'T' || (a_pwszVar)[2] == L't') \
+ && ((a_pwszVar)[3] == L'H' || (a_pwszVar)[3] == L'h') )
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Pointer to a childcare worker thread. */
+typedef struct WINCHILDCAREWORKER *PWINCHILDCAREWORKER;
+/** Pointer to a windows child process. */
+typedef struct WINCHILD *PWINCHILD;
+
+
+/**
+ * Child process type.
+ */
+typedef enum WINCHILDTYPE
+{
+ WINCHILDTYPE_INVALID = 0,
+ /** Normal child process. */
+ WINCHILDTYPE_PROCESS,
+#ifdef KMK
+ /** kmkbuiltin command. */
+ WINCHILDTYPE_BUILT_IN,
+ /** kmkbuiltin_append result write out. */
+ WINCHILDTYPE_APPEND,
+ /** kSubmit job. */
+ WINCHILDTYPE_SUBMIT,
+ /** kmk_redirect job. */
+ WINCHILDTYPE_REDIRECT,
+#endif
+ /** End of valid child types. */
+ WINCHILDTYPE_END
+} WINCHILDTYPE;
+
+
+/**
+ * Windows child process.
+ */
+typedef struct WINCHILD
+{
+ /** Magic / eyecatcher (WINCHILD_MAGIC). */
+ ULONG uMagic;
+ /** Child type. */
+ WINCHILDTYPE enmType;
+ /** Pointer to the next child process. */
+ PWINCHILD pNext;
+ /** The pid for this child. */
+ pid_t pid;
+ /** The make child structure associated with this child. */
+ struct child *pMkChild;
+
+ /** The process exit code. */
+ int iExitCode;
+ /** Kill signal, in case we or someone else killed it. */
+ int iSignal;
+ /** Set if core was dumped. */
+ int fCoreDumped;
+ /** Set if the a child process is a candidate for cl.exe where we supress
+ * annoying source name output. */
+ BOOL fProbableClExe;
+ /** The worker executing this child. */
+ PWINCHILDCAREWORKER pWorker;
+
+ /** Type specific data. */
+ union
+ {
+ /** Data for WINCHILDTYPE_PROCESS. */
+ struct
+ {
+ /** Argument vector (single allocation, strings following array). */
+ char **papszArgs;
+ /** Length of the argument strings. */
+ size_t cbArgsStrings;
+ /** Environment vector. Only a copy if fEnvIsCopy is set. */
+ char **papszEnv;
+ /** If we made a copy of the environment, this is the size of the
+ * strings and terminator string (not in array). This is done to
+ * speed up conversion, since MultiByteToWideChar can handle '\0'. */
+ size_t cbEnvStrings;
+ /** The make shell to use (copy). */
+ char *pszShell;
+ /** Handle to use for standard out. */
+ HANDLE hStdOut;
+ /** Handle to use for standard out. */
+ HANDLE hStdErr;
+ /** Whether to close hStdOut after creating the process. */
+ BOOL fCloseStdOut;
+ /** Whether to close hStdErr after creating the process. */
+ BOOL fCloseStdErr;
+ /** Whether to catch output from the process. */
+ BOOL fCatchOutput;
+
+ /** Child process handle. */
+ HANDLE hProcess;
+ } Process;
+
+ /** Data for WINCHILDTYPE_BUILT_IN. */
+ struct
+ {
+ /** The built-in command. */
+ PCKMKBUILTINENTRY pBuiltIn;
+ /** Number of arguments. */
+ int cArgs;
+ /** Argument vector (single allocation, strings following array). */
+ char **papszArgs;
+ /** Environment vector. Only a copy if fEnvIsCopy is set. */
+ char **papszEnv;
+ } BuiltIn;
+
+ /** Data for WINCHILDTYPE_APPEND. */
+ struct
+ {
+ /** The filename. */
+ char *pszFilename;
+ /** How much to append. */
+ size_t cbAppend;
+ /** What to append. */
+ char *pszAppend;
+ /** Whether to truncate the file. */
+ int fTruncate;
+ } Append;
+
+ /** Data for WINCHILDTYPE_SUBMIT. */
+ struct
+ {
+ /** The event we're to wait on (hooked up to a pipe) */
+ HANDLE hEvent;
+ /** Parameter for the cleanup callback. */
+ void *pvSubmitWorker;
+ /** Standard output catching pipe. Optional. */
+ PWINCCWPIPE pStdOut;
+ /** Standard error catching pipe. Optional. */
+ PWINCCWPIPE pStdErr;
+ } Submit;
+
+ /** Data for WINCHILDTYPE_REDIRECT. */
+ struct
+ {
+ /** Child process handle. */
+ HANDLE hProcess;
+ } Redirect;
+ } u;
+
+} WINCHILD;
+/** WINCHILD::uMagic value. */
+#define WINCHILD_MAGIC 0xbabebabeU
+
+
+/**
+ * Data for a windows childcare worker thread.
+ *
+ * We use one worker thread per child, reusing the threads when possible.
+ *
+ * This setup helps avoid the 64-bit handle with the WaitForMultipleObject API.
+ *
+ * It also helps using all CPUs on systems with more than one CPU group
+ * (typically systems with more than 64 CPU threads or/and multiple sockets, or
+ * special configs).
+ *
+ * This helps facilitates using pipes for collecting output child rather
+ * than temporary files. Pipes doesn't involve NTFS and can easily be reused.
+ *
+ * Finally, kBuild specific, this allows running kmkbuiltin_xxxx commands in
+ * threads.
+ */
+typedef struct WINCHILDCAREWORKER
+{
+ /** Magic / eyecatcher (WINCHILDCAREWORKER_MAGIC). */
+ ULONG uMagic;
+ /** The worker index. */
+ unsigned int idxWorker;
+ /** The processor group for this worker. */
+ unsigned int iProcessorGroup;
+ /** The thread ID. */
+ unsigned int tid;
+ /** The thread handle. */
+ HANDLE hThread;
+ /** The event the thread is idling on. */
+ HANDLE hEvtIdle;
+ /** The pipe catching standard output from a child. */
+ PWINCCWPIPE pStdOut;
+ /** The pipe catching standard error from a child. */
+ PWINCCWPIPE pStdErr;
+
+ /** Pointer to the current child. */
+ PWINCHILD volatile pCurChild;
+ /** List of children pending execution on this worker.
+ * This is updated atomitically just like g_pTailCompletedChildren. */
+ PWINCHILD volatile pTailTodoChildren;
+ /** TRUE if idle, FALSE if not. */
+ long volatile fIdle;
+} WINCHILDCAREWORKER;
+/** WINCHILD::uMagic value. */
+#define WINCHILDCAREWORKER_MAGIC 0xdad0dad0U
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Whether it's initialized or not. */
+static BOOL g_fInitialized = FALSE;
+/** Set when we're shutting down everything. */
+static BOOL volatile g_fShutdown = FALSE;
+/** Event used to wait for children. */
+static HANDLE g_hEvtWaitChildren = INVALID_HANDLE_VALUE;
+/** Number of childcare workers currently in g_papChildCareworkers. */
+static unsigned g_cChildCareworkers = 0;
+/** Maximum number of childcare workers in g_papChildCareworkers. */
+static unsigned g_cChildCareworkersMax = 0;
+/** Pointer to childcare workers. */
+static PWINCHILDCAREWORKER *g_papChildCareworkers = NULL;
+/** The processor group allocator state. */
+static MKWINCHILDCPUGROUPALLOCSTATE g_ProcessorGroupAllocator;
+/** Number of processor groups in the system. */
+static unsigned g_cProcessorGroups = 1;
+/** Array detailing how many active processors there are in each group. */
+static unsigned const *g_pacProcessorsInGroup = &g_cProcessorGroups;
+/** Kernel32!GetActiveProcessorGroupCount */
+static WORD (WINAPI *g_pfnGetActiveProcessorGroupCount)(VOID);
+/** Kernel32!GetActiveProcessorCount */
+static DWORD (WINAPI *g_pfnGetActiveProcessorCount)(WORD);
+/** Kernel32!SetThreadGroupAffinity */
+static BOOL (WINAPI *g_pfnSetThreadGroupAffinity)(HANDLE, CONST GROUP_AFFINITY *, GROUP_AFFINITY *);
+/** NTDLL!NtQueryInformationProcess */
+static NTSTATUS (NTAPI *g_pfnNtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
+/** Set if the windows host is 64-bit. */
+static BOOL g_f64BitHost = (K_ARCH_BITS == 64);
+/** Windows version info.
+ * @note Putting this before the volatile stuff, hoping to keep it in a
+ * different cache line than the static bits above. */
+static OSVERSIONINFOA g_VersionInfo = { sizeof(g_VersionInfo), 4, 0, 1381, VER_PLATFORM_WIN32_NT, {0} };
+
+/** Children that has been completed.
+ * This is updated atomically, pushing completed children in LIFO fashion
+ * (thus 'tail'), then hitting g_hEvtWaitChildren if head. */
+static PWINCHILD volatile g_pTailCompletedChildren = NULL;
+
+/** Number of idle pending children.
+ * This is updated before g_hEvtWaitChildren is signalled. */
+static unsigned volatile g_cPendingChildren = 0;
+
+/** Number of idle childcare worker threads. */
+static unsigned volatile g_cIdleChildcareWorkers = 0;
+/** Index of the last idle child careworker (just a hint). */
+static unsigned volatile g_idxLastChildcareWorker = 0;
+
+#ifdef WITH_RW_LOCK
+/** RW lock for serializing kmkbuiltin_redirect and CreateProcess. */
+static SRWLOCK g_RWLock;
+#endif
+
+/** The job object for this make instance, if we created/opened one. */
+static HANDLE g_hJob = NULL;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void mkWinChildInitJobObjectAssociation(void);
+
+#if K_ARCH_BITS == 32 && !defined(_InterlockedCompareExchangePointer)
+/** _InterlockedCompareExchangePointer is missing? (VS2010) */
+K_INLINE void *_InterlockedCompareExchangePointer(void * volatile *ppvDst, void *pvNew, void *pvOld)
+{
+ return (void *)_InterlockedCompareExchange((long volatile *)ppvDst, (intptr_t)pvNew, (intptr_t)pvOld);
+}
+#endif
+
+
+/**
+ * Initializes the windows child module.
+ *
+ * @param cJobSlots The number of job slots.
+ */
+void MkWinChildInit(unsigned int cJobSlots)
+{
+ HMODULE hmod;
+
+ /*
+ * Figure out how many childcare workers first.
+ */
+ static unsigned int const s_cMaxWorkers = 4096;
+ unsigned cWorkers;
+ if (cJobSlots >= 1 && cJobSlots < s_cMaxWorkers)
+ cWorkers = cJobSlots;
+ else
+ cWorkers = s_cMaxWorkers;
+
+ /*
+ * Allocate the array and the child completed event object.
+ */
+ g_papChildCareworkers = (PWINCHILDCAREWORKER *)xcalloc(cWorkers * sizeof(g_papChildCareworkers[0]));
+ g_cChildCareworkersMax = cWorkers;
+
+ g_hEvtWaitChildren = CreateEvent(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pszName*/);
+ if (!g_hEvtWaitChildren)
+ fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: CreateEvent failed: %u"), GetLastError());
+
+ /*
+ * NTDLL imports that we need.
+ */
+ hmod = GetModuleHandleA("NTDLL.DLL");
+ *(FARPROC *)&g_pfnNtQueryInformationProcess = GetProcAddress(hmod, "NtQueryInformationProcess");
+ if (!g_pfnNtQueryInformationProcess)
+ fatal(NILF, 0, _("MkWinChildInit: NtQueryInformationProcess not found"));
+
+#if K_ARCH_BITS == 32
+ /*
+ * Initialize g_f64BitHost.
+ */
+ if (!IsWow64Process(GetCurrentProcess(), &g_f64BitHost))
+ fatal(NILF, INTSTR_LENGTH, _("MkWinChildInit: IsWow64Process failed: %u"), GetLastError());
+#elif K_ARCH_BITS == 64
+ assert(g_f64BitHost);
+#else
+# error "K_ARCH_BITS is bad/missing"
+#endif
+
+ /*
+ * Figure out how many processor groups there are.
+ * For that we need to first figure the windows version.
+ */
+ if (!GetVersionExA(&g_VersionInfo))
+ {
+ DWORD uRawVer = GetVersion();
+ g_VersionInfo.dwMajorVersion = uRawVer & 0xff;
+ g_VersionInfo.dwMinorVersion = (uRawVer >> 8) & 0xff;
+ g_VersionInfo.dwBuildNumber = (uRawVer >> 16) & 0x7fff;
+ }
+ if (g_VersionInfo.dwMajorVersion >= 6)
+ {
+ hmod = GetModuleHandleA("KERNEL32.DLL");
+ *(FARPROC *)&g_pfnGetActiveProcessorGroupCount = GetProcAddress(hmod, "GetActiveProcessorGroupCount");
+ *(FARPROC *)&g_pfnGetActiveProcessorCount = GetProcAddress(hmod, "GetActiveProcessorCount");
+ *(FARPROC *)&g_pfnSetThreadGroupAffinity = GetProcAddress(hmod, "SetThreadGroupAffinity");
+ if ( g_pfnSetThreadGroupAffinity
+ && g_pfnGetActiveProcessorCount
+ && g_pfnGetActiveProcessorGroupCount)
+ {
+ unsigned int *pacProcessorsInGroup;
+ unsigned iGroup;
+ g_cProcessorGroups = g_pfnGetActiveProcessorGroupCount();
+ if (g_cProcessorGroups == 0)
+ g_cProcessorGroups = 1;
+
+ pacProcessorsInGroup = (unsigned int *)xmalloc(sizeof(g_pacProcessorsInGroup[0]) * g_cProcessorGroups);
+ g_pacProcessorsInGroup = pacProcessorsInGroup;
+ for (iGroup = 0; iGroup < g_cProcessorGroups; iGroup++)
+ pacProcessorsInGroup[iGroup] = g_pfnGetActiveProcessorCount(iGroup);
+
+ MkWinChildInitCpuGroupAllocator(&g_ProcessorGroupAllocator);
+ }
+ else
+ {
+ g_pfnSetThreadGroupAffinity = NULL;
+ g_pfnGetActiveProcessorCount = NULL;
+ g_pfnGetActiveProcessorGroupCount = NULL;
+ }
+ }
+
+#ifdef WITH_RW_LOCK
+ /*
+ * For serializing with standard file handle manipulation (kmkbuiltin_redirect).
+ */
+ InitializeSRWLock(&g_RWLock);
+#endif
+
+ /*
+ * Associate with a job object.
+ */
+ mkWinChildInitJobObjectAssociation();
+
+ /*
+ * This is dead code that was thought to fix a problem observed doing
+ * `tcc.exe /c "kmk |& tee bld.log"` and leading to a crash in cl.exe
+ * when spawned with fInheritHandles = FALSE, see hStdErr=NULL in the
+ * child. However, it turns out this was probably caused by not clearing
+ * the CRT file descriptor and handle table in the startup info.
+ * Leaving the code here in case it comes in handy after all.
+ */
+#if 0
+ {
+ struct
+ {
+ DWORD uStdHandle;
+ HANDLE hHandle;
+ } aHandles[3] = { { STD_INPUT_HANDLE, NULL }, { STD_OUTPUT_HANDLE, NULL }, { STD_ERROR_HANDLE, NULL } };
+ int i;
+
+ for (i = 0; i < 3; i++)
+ aHandles[i].hHandle = GetStdHandle(aHandles[i].uStdHandle);
+
+ for (i = 0; i < 3; i++)
+ if ( aHandles[i].hHandle == NULL
+ || aHandles[i].hHandle == INVALID_HANDLE_VALUE)
+ {
+ int fd = open("nul", _O_RDWR);
+ if (fd >= 0)
+ {
+ if (_dup2(fd, i) >= 0)
+ {
+ assert((HANDLE)_get_osfhandle(i) != aHandles[i].hHandle);
+ assert((HANDLE)_get_osfhandle(i) == GetStdHandle(aHandles[i].uStdHandle));
+ }
+ else
+ ONNNS(fatal, NILF, "_dup2(%d('nul'), %d) failed: %u (%s)", fd, i, errno, strerror(errno));
+ if (fd != i)
+ close(fd);
+ }
+ else
+ ONNS(fatal, NILF, "open(nul,RW) failed: %u (%s)", i, errno, strerror(errno));
+ }
+ else
+ {
+ int j;
+ for (j = i + 1; j < 3; j++)
+ if (aHandles[j].hHandle == aHandles[i].hHandle)
+ {
+ int fd = _dup(j);
+ if (fd >= 0)
+ {
+ if (_dup2(fd, j) >= 0)
+ {
+ aHandles[j].hHandle = (HANDLE)_get_osfhandle(j);
+ assert(aHandles[j].hHandle != aHandles[i].hHandle);
+ assert(aHandles[j].hHandle == GetStdHandle(aHandles[j].uStdHandle));
+ }
+ else
+ ONNNS(fatal, NILF, "_dup2(%d, %d) failed: %u (%s)", fd, j, errno, strerror(errno));
+ if (fd != j)
+ close(fd);
+ }
+ else
+ ONNS(fatal, NILF, "_dup(%d) failed: %u (%s)", j, errno, strerror(errno));
+ }
+ }
+ }
+#endif
+}
+
+/**
+ * Create or open a job object for this make instance and its children.
+ *
+ * Depending on the --job-object=mode value, we typically create/open a job
+ * object here if we're the root make instance. The job object is then
+ * typically configured to kill all remaining processes when the root make
+ * terminates, so that there aren't any stuck processes around messing up
+ * subsequent builds. This is very handy on build servers.
+ *
+ * If we're it no-kill mode, the job object is pretty pointless for manual
+ * cleanup as the job object becomes invisible (or something) when the last
+ * handle to it closes, i.e. g_hJob. On windows 8 and later it looks
+ * like any orphaned children are immediately assigned to the parent job
+ * object. Too bad for kmk_kill and such.
+ *
+ * win_job_object_mode values: login, root, each, none
+ */
+static void mkWinChildInitJobObjectAssociation(void)
+{
+ BOOL fCreate = TRUE;
+ char szJobName[128];
+ const char *pszJobName = win_job_object_name;
+
+ /* Skip if disabled. */
+ if (strcmp(win_job_object_mode, "none") == 0)
+ return;
+
+ /* Skip if not root make instance, unless we're having one job object
+ per make instance. */
+ if ( makelevel != 0
+ && strcmp(win_job_object_mode, "each") != 0)
+ return;
+
+ /* Format the the default job object name if --job-object-name
+ wasn't given. */
+ if (!pszJobName || *pszJobName == '\0')
+ {
+ pszJobName = szJobName;
+ if (strcmp(win_job_object_mode, "login") == 0)
+ {
+ /* Use the AuthenticationId like mspdbsrv.exe does. */
+ HANDLE hToken;
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ {
+ TOKEN_STATISTICS TokenStats;
+ DWORD cbRet = 0;
+ memset(&TokenStats, 0, sizeof(TokenStats));
+ if (GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbRet))
+ snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-login-%08x.%08x",
+ (unsigned)TokenStats.AuthenticationId.HighPart, (unsigned)TokenStats.AuthenticationId.LowPart);
+ else
+ {
+ ONN(message, 0, _("GetTokenInformation failed: %u (cbRet=%u)"), GetLastError(), cbRet);
+ return;
+ }
+ CloseHandle(hToken);
+ }
+ else
+ {
+ ON(message, 0, _("OpenProcessToken failed: %u"), GetLastError());
+ return;
+ }
+ }
+ else
+ {
+ SYSTEMTIME Now = {0};
+ GetSystemTime(&Now);
+ snprintf(szJobName, sizeof(szJobName), "kmk-job-obj-%04u-%02u-%02uT%02u-%02u-%02uZ%u",
+ Now.wYear, Now.wMonth, Now.wDay, Now.wHour, Now.wMinute, Now.wSecond, getpid());
+ }
+ }
+
+ /* In login mode and when given a job object name, we try open it first. */
+ if ( win_job_object_name
+ || strcmp(win_job_object_mode, "login") == 0)
+ {
+ g_hJob = OpenJobObjectA(JOB_OBJECT_ASSIGN_PROCESS, win_job_object_no_kill /*bInheritHandle*/, pszJobName);
+ if (g_hJob)
+ fCreate = FALSE;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr != ERROR_PATH_NOT_FOUND && dwErr != ERROR_FILE_NOT_FOUND)
+ {
+ OSN(message, 0, _("OpenJobObjectA(,,%s) failed: %u"), pszJobName, GetLastError());
+ return;
+ }
+ }
+ }
+
+ if (fCreate)
+ {
+ SECURITY_ATTRIBUTES SecAttr = { sizeof(SecAttr), NULL, TRUE /*bInheritHandle*/ };
+ g_hJob = CreateJobObjectA(win_job_object_no_kill ? &SecAttr : NULL, pszJobName);
+ if (g_hJob)
+ {
+ /* We need to set the BREAKAWAY_OK flag, as we don't want make CreateProcess
+ fail if someone tries to break way. Also set KILL_ON_JOB_CLOSE unless
+ --job-object-no-kill is given. */
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION Info;
+ DWORD cbActual = 0;
+ memset(&Info, 0, sizeof(Info));
+ if (QueryInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info), &cbActual))
+ {
+ Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ if (!win_job_object_no_kill)
+ Info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ else
+ Info.BasicLimitInformation.LimitFlags &= ~JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (!SetInformationJobObject(g_hJob, JobObjectExtendedLimitInformation, &Info, sizeof(Info)))
+ OSSN(message, 0, _("SetInformationJobObject(%s,JobObjectExtendedLimitInformation,{%s},) failed: %u"),
+ pszJobName, win_job_object_mode, GetLastError());
+ }
+ else
+ OSN(message, 0, _("QueryInformationJobObject(%s,JobObjectExtendedLimitInformation,,,) failed: %u"),
+ pszJobName, GetLastError());
+ }
+ else
+ {
+ OSN(message, 0, _("CreateJobObjectA(NULL,%s) failed: %u"), pszJobName, GetLastError());
+ return;
+ }
+ }
+
+ /* Make it our job object. */
+ if (!(AssignProcessToJobObject(g_hJob, GetCurrentProcess())))
+ OSN(message, 0, _("AssignProcessToJobObject(%s, me) failed: %u"), pszJobName, GetLastError());
+}
+
+/**
+ * Used by mkWinChildcareWorkerThread() and MkWinChildWait() to get the head
+ * child from a lifo (g_pTailCompletedChildren, pTailTodoChildren).
+ *
+ * @returns Head child.
+ * @param ppTail Pointer to the child variable.
+ * @param pChild Tail child.
+ */
+static PWINCHILD mkWinChildDequeFromLifo(PWINCHILD volatile *ppTail, PWINCHILD pChild)
+{
+ if (pChild->pNext)
+ {
+ PWINCHILD pPrev;
+ do
+ {
+ pPrev = pChild;
+ pChild = pChild->pNext;
+ } while (pChild->pNext);
+ pPrev->pNext = NULL;
+ }
+ else
+ {
+ PWINCHILD const pWantedChild = pChild;
+ pChild = _InterlockedCompareExchangePointer(ppTail, NULL, pWantedChild);
+ if (pChild != pWantedChild)
+ {
+ PWINCHILD pPrev;
+ do
+ {
+ pPrev = pChild;
+ pChild = pChild->pNext;
+ } while (pChild->pNext);
+ pPrev->pNext = NULL;
+ assert(pChild == pWantedChild);
+ }
+ }
+ return pChild;
+}
+
+/**
+ * Output error message while running on a worker thread.
+ *
+ * @returns -1
+ * @param pWorker The calling worker. Mainly for getting the
+ * current child and its stderr output unit. Pass
+ * NULL if the output should go thru the child
+ * stderr buffering.
+ * @param iType The error type:
+ * - 0: more of a info directly to stdout,
+ * - 1: child related error,
+ * - 2: child related error for immedate release.
+ * @param pszFormat The message format string.
+ * @param ... Argument for the message.
+ */
+static int MkWinChildError(PWINCHILDCAREWORKER pWorker, int iType, const char *pszFormat, ...)
+{
+ /*
+ * Format the message into stack buffer.
+ */
+ char szMsg[4096];
+ int cchMsg;
+ int cchPrefix;
+ va_list va;
+
+ /* Compose the prefix, being paranoid about it not exceeding the buffer in any way. */
+ const char *pszInfix = iType == 0 ? "info: " : "error: ";
+ const char *pszProgram = program;
+ if (strlen(pszProgram) > 80)
+ {
+#ifdef KMK
+ pszProgram = "kmk";
+#else
+ pszProgram = "gnumake";
+#endif
+ }
+ if (makelevel == 0)
+ cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s: %s", pszProgram, pszInfix);
+ else
+ cchPrefix = snprintf(szMsg, sizeof(szMsg) / 2, "%s[%u]: %s", pszProgram, makelevel, pszInfix);
+ assert(cchPrefix < sizeof(szMsg) / 2 && cchPrefix > 0);
+
+ /* Format the user specified message. */
+ va_start(va, pszFormat);
+ cchMsg = vsnprintf(&szMsg[cchPrefix], sizeof(szMsg) - 2 - cchPrefix, pszFormat, va);
+ va_end(va);
+ szMsg[sizeof(szMsg) - 2] = '\0';
+ cchMsg = strlen(szMsg);
+
+ /* Make sure there's a newline at the end of it (we reserved space for that). */
+ if (cchMsg <= 0 || szMsg[cchMsg - 1] != '\n')
+ {
+ szMsg[cchMsg++] = '\n';
+ szMsg[cchMsg] = '\0';
+ }
+
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ /*
+ * Try use the stderr of the current child of the worker.
+ */
+ if ( iType != 0
+ && iType != 3
+ && pWorker)
+ {
+ PWINCHILD pChild = pWorker->pCurChild;
+ if (pChild)
+ {
+ struct child *pMkChild = pChild->pMkChild;
+ if (pMkChild)
+ {
+ output_write_text(&pMkChild->output, 1 /*is_err*/, szMsg, cchMsg);
+ return -1;
+ }
+ }
+ }
+#endif
+
+ /*
+ * Fallback to writing directly to stderr.
+ */
+ maybe_con_fwrite(szMsg, cchMsg, 1, iType == 0 ? stdout : stderr);
+ return -1;
+}
+
+/**
+ * Duplicates the given UTF-16 string.
+ *
+ * @returns 0
+ * @param pwszSrc The UTF-16 string to duplicate.
+ * @param cwcSrc Length, may include the terminator.
+ * @param ppwszDst Where to return the duplicate.
+ */
+static int mkWinChildDuplicateUtf16String(const WCHAR *pwszSrc, size_t cwcSrc, WCHAR **ppwszDst)
+{
+ size_t cb = sizeof(WCHAR) * cwcSrc;
+ if (cwcSrc > 0 && pwszSrc[cwcSrc - 1] == L'\0')
+ *ppwszDst = (WCHAR *)memcpy(xmalloc(cb), pwszSrc, cb);
+ else
+ {
+ WCHAR *pwszDst = (WCHAR *)xmalloc(cb + sizeof(WCHAR));
+ memcpy(pwszDst, pwszSrc, cb);
+ pwszDst[cwcSrc] = L'\0';
+ *ppwszDst = pwszDst;
+ }
+ return 0;
+}
+
+
+/**
+ * Used to flush data we're read but not yet written at the termination of a
+ * process.
+ *
+ * @param pChild The child.
+ * @param pPipe The pipe.
+ */
+static void mkWinChildcareWorkerFlushUnwritten(PWINCHILD pChild, PWINCCWPIPE pPipe)
+{
+ DWORD cbUnwritten = pPipe->offPendingRead - pPipe->cbWritten;
+ assert(pPipe->cbWritten <= pPipe->cbBuffer - 16);
+ assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
+ if (cbUnwritten)
+ {
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ if (pChild && pChild->pMkChild)
+ {
+ output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten);
+ pPipe->cbWritten += cbUnwritten;
+ }
+ else
+#endif
+ {
+ DWORD cbWritten = 0;
+ if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
+ &pPipe->pbBuffer[pPipe->cbWritten], cbUnwritten, &cbWritten, NULL))
+ pPipe->cbWritten += cbWritten <= cbUnwritten ? cbWritten : cbUnwritten; /* paranoia */
+ }
+ pPipe->fHaveWrittenOut = TRUE;
+ }
+}
+
+/**
+ * This logic mirrors kwSandboxConsoleFlushAll.
+ *
+ * @returns TRUE if it looks like a CL.EXE source line, otherwise FALSE.
+ * @param pPipe The pipe.
+ * @param offStart The start of the output in the pipe buffer.
+ * @param offEnd The end of the output in the pipe buffer.
+ */
+static BOOL mkWinChildcareWorkerIsClExeSourceLine(PWINCCWPIPE pPipe, DWORD offStart, DWORD offEnd)
+{
+ if (offEnd < offStart + 2)
+ return FALSE;
+ if (offEnd - offStart > 80)
+ return FALSE;
+
+ if ( pPipe->pbBuffer[offEnd - 2] != '\r'
+ || pPipe->pbBuffer[offEnd - 1] != '\n')
+ return FALSE;
+
+ offEnd -= 2;
+ while (offEnd-- > offStart)
+ {
+ char ch = pPipe->pbBuffer[offEnd];
+ if (isalnum(ch) || ch == '.' || ch == ' ' || ch == '_' || ch == '-')
+ { /* likely */ }
+ else
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Adds output to the given standard output for the child.
+ *
+ * There is no pending read when this function is called, so we're free to
+ * reshuffle the buffer if desirable.
+ *
+ * @param pChild The child. Optional (kSubmit).
+ * @param iWhich Which standard descriptor number.
+ * @param cbNewData How much more output was caught.
+ */
+static void mkWinChildcareWorkerCaughtMoreOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, DWORD cbNewData)
+{
+ DWORD offStart = pPipe->cbWritten;
+ assert(offStart <= pPipe->offPendingRead);
+ assert(offStart <= pPipe->cbBuffer - 16);
+ assert(pPipe->offPendingRead <= pPipe->cbBuffer - 16);
+ if (cbNewData > 0)
+ {
+ DWORD offRest;
+
+ /* Move offPendingRead ahead by cbRead. */
+ pPipe->offPendingRead += cbNewData;
+ assert(pPipe->offPendingRead <= pPipe->cbBuffer);
+ if (pPipe->offPendingRead > pPipe->cbBuffer)
+ pPipe->offPendingRead = pPipe->cbBuffer;
+
+ /* Locate the last newline in the buffer. */
+ offRest = pPipe->offPendingRead;
+ while (offRest > offStart && pPipe->pbBuffer[offRest - 1] != '\n')
+ offRest--;
+
+ /* If none were found and we've less than 16 bytes left in the buffer, try
+ find a word boundrary to flush on instead. */
+ if ( offRest <= offStart
+ && pPipe->cbBuffer - pPipe->offPendingRead + offStart < 16)
+ {
+ offRest = pPipe->offPendingRead;
+ while ( offRest > offStart
+ && isalnum(pPipe->pbBuffer[offRest - 1]))
+ offRest--;
+ if (offRest == offStart)
+ offRest = pPipe->offPendingRead;
+ }
+ /* If this is a potential CL.EXE process, we will keep the source
+ filename unflushed and maybe discard it at the end. */
+ else if ( pChild
+ && pChild->fProbableClExe
+ && pPipe->iWhich == 1
+ && offRest == pPipe->offPendingRead
+ && mkWinChildcareWorkerIsClExeSourceLine(pPipe, offStart, offRest))
+ offRest = offStart;
+
+ if (offRest > offStart)
+ {
+ /* Write out offStart..offRest. */
+ DWORD cbToWrite = offRest - offStart;
+#ifdef CONFIG_WITH_OUTPUT_IN_MEMORY
+ if (pChild && pChild->pMkChild)
+ {
+ output_write_bin(&pChild->pMkChild->output, pPipe->iWhich == 2, &pPipe->pbBuffer[offStart], cbToWrite);
+ offStart += cbToWrite;
+ pPipe->cbWritten = offStart;
+ }
+ else
+#endif
+ {
+ DWORD cbWritten = 0;
+ if (WriteFile(GetStdHandle(pPipe->iWhich == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE),
+ &pPipe->pbBuffer[offStart], cbToWrite, &cbWritten, NULL))
+ {
+ offStart += cbWritten <= cbToWrite ? cbWritten : cbToWrite; /* paranoia */
+ pPipe->cbWritten = offStart;
+ }
+ }
+ pPipe->fHaveWrittenOut = TRUE;
+ }
+ }
+
+ /* Shuffle the data to the front of the buffer. */
+ if (offStart > 0)
+ {
+ DWORD cbUnwritten = pPipe->offPendingRead - offStart;
+ if (cbUnwritten > 0)
+ memmove(pPipe->pbBuffer, &pPipe->pbBuffer[offStart], cbUnwritten);
+ pPipe->offPendingRead -= pPipe->cbWritten;
+ pPipe->cbWritten = 0;
+ }
+}
+
+/**
+ * Catches output from the given pipe.
+ *
+ * @param pChild The child. Optional (kSubmit).
+ * @param pPipe The pipe.
+ * @param fDraining Set if we're draining the pipe after the process
+ * terminated.
+ */
+static void mkWinChildcareWorkerCatchOutput(PWINCHILD pChild, PWINCCWPIPE pPipe, BOOL fDraining)
+{
+ /*
+ * Deal with already pending read.
+ */
+ if (pPipe->fReadPending)
+ {
+ DWORD cbRead = 0;
+ if (GetOverlappedResult(pPipe->hPipeMine, &pPipe->Overlapped, &cbRead, !fDraining))
+ {
+ mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
+ pPipe->fReadPending = FALSE;
+ }
+ else if (fDraining && GetLastError() == ERROR_IO_INCOMPLETE)
+ return;
+ else
+ {
+ MkWinChildError(pChild ? pChild->pWorker : NULL, 2, "GetOverlappedResult failed: %u\n", GetLastError());
+ pPipe->fReadPending = FALSE;
+ if (fDraining)
+ return;
+ }
+ }
+
+ /*
+ * Read data till one becomes pending.
+ */
+ for (;;)
+ {
+ DWORD cbRead;
+
+ memset(&pPipe->Overlapped, 0, sizeof(pPipe->Overlapped));
+ pPipe->Overlapped.hEvent = pPipe->hEvent;
+ ResetEvent(pPipe->hEvent);
+
+ assert(pPipe->offPendingRead < pPipe->cbBuffer);
+ SetLastError(0);
+ cbRead = 0;
+ if (!ReadFile(pPipe->hPipeMine, &pPipe->pbBuffer[pPipe->offPendingRead],
+ pPipe->cbBuffer - pPipe->offPendingRead, &cbRead, &pPipe->Overlapped))
+ {
+ DWORD dwErr = GetLastError();
+ if (dwErr == ERROR_IO_PENDING)
+ pPipe->fReadPending = TRUE;
+ else
+ MkWinChildError(pChild ? pChild->pWorker : NULL, 2,
+ "ReadFile failed on standard %s: %u\n",
+ pPipe->iWhich == 1 ? "output" : "error", GetLastError());
+ return;
+ }
+
+ mkWinChildcareWorkerCaughtMoreOutput(pChild, pPipe, cbRead);
+ }
+}
+
+/**
+ * Makes sure the output pipes are drained and pushed to output.
+ *
+ * @param pChild The child. Optional (kSubmit).
+ * @param pStdOut The standard output pipe structure.
+ * @param pStdErr The standard error pipe structure.
+ */
+void MkWinChildcareWorkerDrainPipes(PWINCHILD pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr)
+{
+ mkWinChildcareWorkerCatchOutput(pChild, pStdOut, TRUE /*fDraining*/);
+ mkWinChildcareWorkerCatchOutput(pChild, pStdErr, TRUE /*fDraining*/);
+
+ /* Drop lone 'source.c' line from CL.exe, but only if no other output at all. */
+ if ( pChild
+ && pChild->fProbableClExe
+ && !pStdOut->fHaveWrittenOut
+ && !pStdErr->fHaveWrittenOut
+ && pStdErr->cbWritten == pStdErr->offPendingRead
+ && pStdOut->cbWritten < pStdOut->offPendingRead
+ && mkWinChildcareWorkerIsClExeSourceLine(pStdOut, pStdOut->cbWritten, pStdOut->offPendingRead))
+ {
+ if (!pStdOut->fReadPending)
+ pStdOut->cbWritten = pStdOut->offPendingRead = 0;
+ else
+ pStdOut->cbWritten = pStdOut->offPendingRead;
+ }
+ else
+ {
+ mkWinChildcareWorkerFlushUnwritten(pChild, pStdOut);
+ mkWinChildcareWorkerFlushUnwritten(pChild, pStdErr);
+ }
+}
+
+/**
+ * Commmon worker for waiting on a child process and retrieving the exit code.
+ *
+ * @returns Child exit code.
+ * @param pWorker The worker.
+ * @param pChild The child.
+ * @param hProcess The process handle.
+ * @param pwszJob The job name.
+ * @param fCatchOutput Set if we need to work the output pipes
+ * associated with the worker.
+ */
+static int mkWinChildcareWorkerWaitForProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild, HANDLE hProcess,
+ WCHAR const *pwszJob, BOOL fCatchOutput)
+{
+ DWORD const msStart = GetTickCount();
+ DWORD msNextMsg = msStart + 15000;
+
+ /* Reset the written indicators on the pipes before we start loop. */
+ pWorker->pStdOut->fHaveWrittenOut = FALSE;
+ pWorker->pStdErr->fHaveWrittenOut = FALSE;
+
+ for (;;)
+ {
+ /*
+ * Do the waiting and output catching.
+ */
+ DWORD dwStatus;
+ if (!fCatchOutput)
+ dwStatus = WaitForSingleObject(hProcess, 15001 /*ms*/);
+ else
+ {
+ HANDLE ahHandles[3] = { hProcess, pWorker->pStdOut->hEvent, pWorker->pStdErr->hEvent };
+ dwStatus = WaitForMultipleObjects(3, ahHandles, FALSE /*fWaitAll*/, 1000 /*ms*/);
+ if (dwStatus == WAIT_OBJECT_0 + 1)
+ mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdOut, FALSE /*fDraining*/);
+ else if (dwStatus == WAIT_OBJECT_0 + 2)
+ mkWinChildcareWorkerCatchOutput(pChild, pWorker->pStdErr, FALSE /*fDraining*/);
+ }
+ assert(dwStatus != WAIT_FAILED);
+
+ /*
+ * Get the exit code and return if the process was signalled as done.
+ */
+ if (dwStatus == WAIT_OBJECT_0)
+ {
+ DWORD dwExitCode = -42;
+ if (GetExitCodeProcess(hProcess, &dwExitCode))
+ {
+ pChild->iExitCode = (int)dwExitCode;
+ if (fCatchOutput)
+ MkWinChildcareWorkerDrainPipes(pChild, pWorker->pStdOut, pWorker->pStdErr);
+ return dwExitCode;
+ }
+ }
+ /*
+ * Loop again if just a timeout or pending output?
+ * Put out a message every 15 or 30 seconds if the job takes a while.
+ */
+ else if ( dwStatus == WAIT_TIMEOUT
+ || dwStatus == WAIT_OBJECT_0 + 1
+ || dwStatus == WAIT_OBJECT_0 + 2
+ || dwStatus == WAIT_IO_COMPLETION)
+ {
+ DWORD msNow = GetTickCount();
+ if (msNow >= msNextMsg)
+ {
+ if ( !pChild->pMkChild
+ || !pChild->pMkChild->recursive) /* ignore make recursions */
+ {
+ if ( !pChild->pMkChild
+ || !pChild->pMkChild->file
+ || !pChild->pMkChild->file->name)
+ MkWinChildError(NULL, 0, "Pid %u ('%ls') still running after %u seconds\n",
+ GetProcessId(hProcess), pwszJob, (msNow - msStart) / 1000);
+ else
+ MkWinChildError(NULL, 0, "Target '%s' (pid %u) still running after %u seconds\n",
+ pChild->pMkChild->file->name, GetProcessId(hProcess), (msNow - msStart) / 1000);
+ }
+
+ /* After 15s, 30s, 60s, 120s, 180s, ... */
+ if (msNextMsg == msStart + 15000)
+ msNextMsg += 15000;
+ else
+ msNextMsg += 30000;
+ }
+ continue;
+ }
+
+ /* Something failed. */
+ pChild->iExitCode = GetLastError();
+ if (pChild->iExitCode == 0)
+ pChild->iExitCode = -4242;
+ return pChild->iExitCode;
+ }
+}
+
+
+/**
+ * Closes standard handles that need closing before destruction.
+ *
+ * @param pChild The child (WINCHILDTYPE_PROCESS).
+ */
+static void mkWinChildcareWorkerCloseStandardHandles(PWINCHILD pChild)
+{
+ if ( pChild->u.Process.fCloseStdOut
+ && pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(pChild->u.Process.hStdOut);
+ pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
+ pChild->u.Process.fCloseStdOut = FALSE;
+ }
+ if ( pChild->u.Process.fCloseStdErr
+ && pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(pChild->u.Process.hStdErr);
+ pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
+ pChild->u.Process.fCloseStdErr = FALSE;
+ }
+}
+
+
+/**
+ * Does the actual process creation.
+ *
+ * @returns 0 if there is anything to wait on, otherwise non-zero windows error.
+ * @param pWorker The childcare worker.
+ * @param pChild The child.
+ * @param pwszImageName The image path.
+ * @param pwszCommandLine The command line.
+ * @param pwszzEnvironment The enviornment block.
+ */
+static int mkWinChildcareWorkerCreateProcess(PWINCHILDCAREWORKER pWorker, WCHAR const *pwszImageName,
+ WCHAR const *pwszCommandLine, WCHAR const *pwszzEnvironment, WCHAR const *pwszCwd,
+ BOOL pafReplace[3], HANDLE pahChild[3], BOOL fCatchOutput, HANDLE *phProcess)
+{
+ PROCESS_INFORMATION ProcInfo;
+ STARTUPINFOW StartupInfo;
+ DWORD fFlags = CREATE_UNICODE_ENVIRONMENT;
+ BOOL const fHaveHandles = pafReplace[0] | pafReplace[1] | pafReplace[2];
+ BOOL fRet;
+ DWORD dwErr;
+#ifdef KMK
+ extern int process_priority;
+#endif
+
+ /*
+ * Populate startup info.
+ *
+ * Turns out we can get away without passing TRUE for the inherit handles
+ * parameter to CreateProcess when we're not using STARTF_USESTDHANDLES.
+ * At least on NT, which is all worth caring about at this point + context IMO.
+ *
+ * Not inherting the handles is a good thing because it means we won't
+ * accidentally end up with a pipe handle or such intended for a different
+ * child process, potentially causing the EOF/HUP event to be delayed.
+ *
+ * Since the present handle inhertiance requirements only involves standard
+ * output and error, we'll never set the inherit handles flag and instead
+ * do manual handle duplication and planting.
+ */
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoW(&StartupInfo);
+ StartupInfo.lpReserved2 = 0; /* No CRT file handle + descriptor info possible, sorry. */
+ StartupInfo.cbReserved2 = 0;
+ if ( !fHaveHandles
+ && !fCatchOutput)
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+ else
+ {
+ fFlags |= CREATE_SUSPENDED;
+ StartupInfo.dwFlags &= ~STARTF_USESTDHANDLES;
+ }
+
+ /*
+ * Flags.
+ */
+#ifdef KMK
+ switch (process_priority)
+ {
+ case 1: fFlags |= CREATE_SUSPENDED | IDLE_PRIORITY_CLASS; break;
+ case 2: fFlags |= CREATE_SUSPENDED | BELOW_NORMAL_PRIORITY_CLASS; break;
+ case 3: fFlags |= CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS; break;
+ case 4: fFlags |= CREATE_SUSPENDED | HIGH_PRIORITY_CLASS; break;
+ case 5: fFlags |= CREATE_SUSPENDED | REALTIME_PRIORITY_CLASS; break;
+ }
+#endif
+ if (g_cProcessorGroups > 1)
+ fFlags |= CREATE_SUSPENDED;
+
+ /*
+ * Try create the process.
+ */
+ DB(DB_JOBS, ("CreateProcessW(%ls, %ls,,, TRUE, %#x...)\n", pwszImageName, pwszCommandLine, fFlags));
+ memset(&ProcInfo, 0, sizeof(ProcInfo));
+#ifdef WITH_RW_LOCK
+ AcquireSRWLockShared(&g_RWLock);
+#endif
+
+ fRet = CreateProcessW((WCHAR *)pwszImageName, (WCHAR *)pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
+ FALSE /*fInheritHandles*/, fFlags, (WCHAR *)pwszzEnvironment, pwszCwd, &StartupInfo, &ProcInfo);
+ dwErr = GetLastError();
+
+#ifdef WITH_RW_LOCK
+ ReleaseSRWLockShared(&g_RWLock);
+#endif
+ if (fRet)
+ *phProcess = ProcInfo.hProcess;
+ else
+ {
+ MkWinChildError(pWorker, 1, "CreateProcess(%ls) failed: %u\n", pwszImageName, dwErr);
+ return (int)dwErr;
+ }
+
+ /*
+ * If the child is suspended, we've got some adjustment work to be done.
+ */
+ dwErr = ERROR_SUCCESS;
+ if (fFlags & CREATE_SUSPENDED)
+ {
+ /*
+ * First do handle inhertiance as that's the most complicated.
+ */
+ if (fHaveHandles || fCatchOutput)
+ {
+ char szErrMsg[128];
+ if (fCatchOutput)
+ {
+ if (!pafReplace[1])
+ {
+ pafReplace[1] = TRUE;
+ pahChild[1] = pWorker->pStdOut->hPipeChild;
+ }
+ if (!pafReplace[2])
+ {
+ pafReplace[2] = TRUE;
+ pahChild[2] = pWorker->pStdErr->hPipeChild;
+ }
+ }
+ dwErr = nt_child_inject_standard_handles(ProcInfo.hProcess, pafReplace, pahChild, szErrMsg, sizeof(szErrMsg));
+ if (dwErr != 0)
+ MkWinChildError(pWorker, 1, "%s\n", szErrMsg);
+ }
+
+ /*
+ * Assign processor group (ignore failure).
+ */
+#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
+ if (g_cProcessorGroups > 1)
+ {
+ GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } };
+ fRet = g_pfnSetThreadGroupAffinity(ProcInfo.hThread, &Affinity, NULL);
+ assert(fRet);
+ }
+#endif
+
+#ifdef KMK
+ /*
+ * Set priority (ignore failure).
+ */
+ switch (process_priority)
+ {
+ case 1: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_IDLE); break;
+ case 2: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_BELOW_NORMAL); break;
+ case 3: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_NORMAL); break;
+ case 4: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_HIGHEST); break;
+ case 5: fRet = SetThreadPriority(ProcInfo.hThread, THREAD_PRIORITY_TIME_CRITICAL); break;
+ default: fRet = TRUE;
+ }
+ assert(fRet);
+#endif
+
+ /*
+ * Inject the job object if we're in a non-killing mode, to postpone
+ * the closing of the job object and maybe make it more useful.
+ */
+ if (win_job_object_no_kill && g_hJob)
+ {
+ HANDLE hWhatever = INVALID_HANDLE_VALUE;
+ DuplicateHandle(GetCurrentProcess(), g_hJob, ProcInfo.hProcess, &hWhatever, GENERIC_ALL,
+ TRUE /*bInheritHandle*/, DUPLICATE_SAME_ACCESS);
+ }
+
+ /*
+ * Resume the thread if the adjustments succeeded, otherwise kill it.
+ */
+ if (dwErr == ERROR_SUCCESS)
+ {
+ fRet = ResumeThread(ProcInfo.hThread);
+ assert(fRet);
+ if (!fRet)
+ {
+ dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, "ResumeThread failed on child process: %u\n", dwErr);
+ }
+ }
+ if (dwErr != ERROR_SUCCESS)
+ TerminateProcess(ProcInfo.hProcess, dwErr);
+ }
+
+ /*
+ * Close unnecessary handles and cache the image.
+ */
+ CloseHandle(ProcInfo.hThread);
+ kmk_cache_exec_image_w(pwszImageName);
+ return 0;
+}
+
+/**
+ * Converts a argument vector that has already been quoted correctly.
+ *
+ * The argument vector is typically the result of quote_argv().
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pWorker The childcare worker.
+ * @param papszArgs The argument vector to convert.
+ * @param ppwszCommandLine Where to return the command line.
+ */
+static int mkWinChildcareWorkerConvertQuotedArgvToCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs,
+ WCHAR **ppwszCommandLine)
+{
+ WCHAR *pwszCmdLine;
+ WCHAR *pwszDst;
+
+ /*
+ * Calc length the converted length.
+ */
+ unsigned cwcNeeded = 1;
+ unsigned i = 0;
+ const char *pszSrc;
+ while ((pszSrc = papszArgs[i]) != NULL)
+ {
+ int cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, NULL, 0);
+ if (cwcThis > 0 || *pszSrc == '\0')
+ cwcNeeded += cwcThis + 1;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
+ return dwErr;
+ }
+ i++;
+ }
+
+ /*
+ * Allocate and do the conversion.
+ */
+ pwszCmdLine = pwszDst = (WCHAR *)xmalloc(cwcNeeded * sizeof(WCHAR));
+ i = 0;
+ while ((pszSrc = papszArgs[i]) != NULL)
+ {
+ int cwcThis;
+ if (i > 0)
+ {
+ *pwszDst++ = ' ';
+ cwcNeeded--;
+ }
+
+ cwcThis = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, -1, pwszDst, cwcNeeded);
+ if (!cwcThis && *pszSrc != '\0')
+ {
+ DWORD dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
+ free(pwszCmdLine);
+ return dwErr;
+ }
+ if (cwcThis > 0 && pwszDst[cwcThis - 1] == '\0')
+ cwcThis--;
+ pwszDst += cwcThis;
+ cwcNeeded -= cwcThis;
+ i++;
+ }
+ *pwszDst++ = '\0';
+
+ *ppwszCommandLine = pwszCmdLine;
+ return 0;
+}
+
+
+#define MKWCCWCMD_F_CYGWIN_SHELL 1
+#define MKWCCWCMD_F_MKS_SHELL 2
+#define MKWCCWCMD_F_HAVE_SH 4
+#define MKWCCWCMD_F_HAVE_KASH_C 8 /**< kmk_ash -c "..." */
+
+/*
+ * @param pWorker The childcare worker if on one, otherwise NULL.
+ */
+static int mkWinChildcareWorkerConvertCommandline(PWINCHILDCAREWORKER pWorker, char **papszArgs, unsigned fFlags,
+ WCHAR **ppwszCommandLine)
+{
+ struct ARGINFO
+ {
+ size_t cchSrc;
+ size_t cwcDst; /**< converted size w/o terminator. */
+ size_t cwcDstExtra : 24; /**< Only set with fSlowly. */
+ size_t fSlowly : 1;
+ size_t fQuoteIt : 1;
+ size_t fEndSlashes : 1; /**< if escapes needed for trailing backslashes. */
+ size_t fExtraSpace : 1; /**< if kash -c "" needs an extra space before the quote. */
+ } *paArgInfo;
+ size_t cArgs;
+ size_t i;
+ size_t cwcNeeded;
+ WCHAR *pwszDst;
+ WCHAR *pwszCmdLine;
+
+ /*
+ * Count them first so we can allocate an info array of the stack.
+ */
+ cArgs = 0;
+ while (papszArgs[cArgs] != NULL)
+ cArgs++;
+ paArgInfo = (struct ARGINFO *)alloca(sizeof(paArgInfo[0]) * cArgs);
+
+ /*
+ * Preprocess them and calculate the exact command line length.
+ */
+ cwcNeeded = 1;
+ for (i = 0; i < cArgs; i++)
+ {
+ char *pszSrc = papszArgs[i];
+ size_t cchSrc = strlen(pszSrc);
+ paArgInfo[i].cchSrc = cchSrc;
+ if (cchSrc == 0)
+ {
+ /* empty needs quoting. */
+ paArgInfo[i].cwcDst = 2;
+ paArgInfo[i].cwcDstExtra = 0;
+ paArgInfo[i].fSlowly = 0;
+ paArgInfo[i].fQuoteIt = 1;
+ paArgInfo[i].fExtraSpace = 0;
+ paArgInfo[i].fEndSlashes = 0;
+ }
+ else
+ {
+ const char *pszSpace = memchr(pszSrc, ' ', cchSrc);
+ const char *pszTab = memchr(pszSrc, '\t', cchSrc);
+ const char *pszDQuote = memchr(pszSrc, '"', cchSrc);
+ const char *pszEscape = memchr(pszSrc, '\\', cchSrc);
+ int cwcDst = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cchSrc + 1, NULL, 0);
+ if (cwcDst >= 0)
+ --cwcDst;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[%u] (%s): %u\n"), i, pszSrc, dwErr);
+ return dwErr;
+ }
+#if 0
+ if (!pszSpace && !pszTab && !pszDQuote && !pszEscape)
+ {
+ /* no special handling needed. */
+ paArgInfo[i].cwcDst = cwcDst;
+ paArgInfo[i].cwcDstExtra = 0;
+ paArgInfo[i].fSlowly = 0;
+ paArgInfo[i].fQuoteIt = 0;
+ paArgInfo[i].fExtraSpace = 0;
+ paArgInfo[i].fEndSlashes = 0;
+ }
+ else if (!pszDQuote && !pszEscape)
+ {
+ /* Just double quote it. */
+ paArgInfo[i].cwcDst = cwcDst + 2;
+ paArgInfo[i].cwcDstExtra = 0;
+ paArgInfo[i].fSlowly = 0;
+ paArgInfo[i].fQuoteIt = 1;
+ paArgInfo[i].fExtraSpace = 0;
+ paArgInfo[i].fEndSlashes = 0;
+ }
+ else
+#endif
+ {
+ /* Complicated, need to scan the string to figure out what to do. */
+ size_t cwcDstExtra;
+ int cBackslashes;
+ char ch;
+
+ paArgInfo[i].fQuoteIt = 0;
+ paArgInfo[i].fSlowly = 1;
+ paArgInfo[i].fExtraSpace = 0;
+ paArgInfo[i].fEndSlashes = 0;
+
+ cwcDstExtra = 0;
+ cBackslashes = 0;
+ while ((ch = *pszSrc++) != '\0')
+ {
+ switch (ch)
+ {
+ default:
+ cBackslashes = 0;
+ break;
+
+ case '\\':
+ cBackslashes++;
+ break;
+
+ case '"':
+ if (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL))
+ cwcDstExtra += 1; /* just an extra '"' */
+ else
+ cwcDstExtra += 1 + cBackslashes; /* extra '\\' for the '"' and for each preceeding slash. */
+ cBackslashes = 0;
+ break;
+
+ case ' ':
+ case '\t':
+ if (!paArgInfo[i].fQuoteIt)
+ {
+ paArgInfo[i].fQuoteIt = 1;
+ cwcDstExtra += 2;
+ }
+ cBackslashes = 0;
+ break;
+ }
+ }
+
+ /* If we're quoting the argument and it ends with trailing '\\', it/they must be escaped. */
+ if ( cBackslashes > 0
+ && paArgInfo[i].fQuoteIt
+ && !(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
+ {
+ cwcDstExtra += cBackslashes;
+ paArgInfo[i].fEndSlashes = 1;
+ }
+
+ paArgInfo[i].cwcDst = cwcDst + cwcDstExtra;
+ paArgInfo[i].cwcDstExtra = cwcDstExtra;
+ }
+ }
+
+ if ( (fFlags & MKWCCWCMD_F_HAVE_KASH_C)
+ && paArgInfo[i].fQuoteIt)
+ {
+ paArgInfo[i].fExtraSpace = 1;
+ paArgInfo[i].cwcDst++;
+ paArgInfo[i].cwcDstExtra++;
+ }
+
+ cwcNeeded += (i != 0) + paArgInfo[i].cwcDst;
+ }
+
+ /*
+ * Allocate the result buffer and do the actual conversion.
+ */
+ pwszDst = pwszCmdLine = (WCHAR *)xmalloc(sizeof(WCHAR) * cwcNeeded);
+ for (i = 0; i < cArgs; i++)
+ {
+ char *pszSrc = papszArgs[i];
+ size_t cwcDst = paArgInfo[i].cwcDst;
+
+ if (i != 0)
+ *pwszDst++ = L' ';
+
+ if (paArgInfo[i].fQuoteIt)
+ {
+ *pwszDst++ = L'"';
+ cwcDst -= 2;
+ }
+
+ if (!paArgInfo[i].fSlowly)
+ {
+ int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc, pwszDst, cwcDst + 1);
+ assert(cwcDst2 >= 0);
+ pwszDst += cwcDst;
+ }
+ else
+ {
+ /* Do the conversion into the end of the output buffer, then move
+ it up to where it should be char by char. */
+ int cBackslashes;
+ size_t cwcLeft = paArgInfo[i].cwcDst - paArgInfo[i].cwcDstExtra;
+ WCHAR volatile *pwchSlowSrc = pwszDst + paArgInfo[i].cwcDstExtra;
+ WCHAR volatile *pwchSlowDst = pwszDst;
+ int cwcDst2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, paArgInfo[i].cchSrc,
+ (WCHAR *)pwchSlowSrc, cwcLeft + 1);
+ assert(cwcDst2 >= 0);
+
+ cBackslashes = 0;
+ while (cwcLeft-- > 0)
+ {
+ WCHAR wcSrc = *pwchSlowSrc++;
+ if (wcSrc != L'\\' && wcSrc != L'"')
+ cBackslashes = 0;
+ else if (wcSrc == L'\\')
+ cBackslashes++;
+ else if ( (fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
+ == (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_HAVE_SH))
+ {
+ *pwchSlowDst++ = L'"'; /* cygwin: '"' instead of '\\', no escaped slashes. */
+ cBackslashes = 0;
+ }
+ else
+ {
+ if (!(fFlags & (MKWCCWCMD_F_CYGWIN_SHELL | MKWCCWCMD_F_MKS_SHELL)))
+ cBackslashes += 1; /* one extra escape the '"' and one for each preceeding slash. */
+ while (cBackslashes > 0)
+ {
+ *pwchSlowDst++ = L'\\';
+ cBackslashes--;
+ }
+ }
+ *pwchSlowDst++ = wcSrc;
+ assert((uintptr_t)pwchSlowDst <= (uintptr_t)pwchSlowSrc);
+ }
+
+ if (paArgInfo[i].fEndSlashes)
+ while (cBackslashes-- > 0)
+ *pwchSlowDst++ = L'\\';
+
+ pwszDst += cwcDst;
+ assert(pwszDst == (WCHAR *)pwchSlowDst);
+ }
+
+ if (paArgInfo[i].fExtraSpace)
+ *pwszDst++ = L' ';
+ if (paArgInfo[i].fQuoteIt)
+ *pwszDst++ = L'"';
+ }
+ *pwszDst = L'\0';
+ *ppwszCommandLine = pwszCmdLine;
+ return 0;
+}
+
+static int mkWinChildcareWorkerConvertCommandlineWithShell(PWINCHILDCAREWORKER pWorker, const WCHAR *pwszShell, char **papszArgs,
+ WCHAR **ppwszCommandLine)
+{
+ MkWinChildError(pWorker, 1, "%s: not found!\n", papszArgs[0]);
+//__debugbreak();
+ return ERROR_FILE_NOT_FOUND;
+}
+
+/**
+ * Searches the environment block for the PATH variable.
+ *
+ * @returns Pointer to the path in the block or "." in pwszPathFallback.
+ * @param pwszzEnv The UTF-16 environment block to search.
+ * @param pwszPathFallback Fallback.
+ */
+static const WCHAR *mkWinChildcareWorkerFindPathValue(const WCHAR *pwszzEnv, WCHAR pwszPathFallback[4])
+{
+ while (*pwszzEnv)
+ {
+ size_t cwcVar = wcslen(pwszzEnv);
+ if (!IS_PATH_ENV_VAR(cwcVar, pwszzEnv))
+ pwszzEnv += cwcVar + 1;
+ else if (cwcVar > 5)
+ return &pwszzEnv[5];
+ else
+ break;
+ }
+ pwszPathFallback[0] = L'.';
+ pwszPathFallback[1] = L'\0';
+ return pwszPathFallback;
+}
+
+/**
+ * Checks if we need to had this executable file to the shell.
+ *
+ * @returns TRUE if it's shell fooder, FALSE if we think windows can handle it.
+ * @param hFile Handle to the file in question
+ */
+static BOOL mkWinChildcareWorkerCheckIfNeedShell(HANDLE hFile)
+{
+ /*
+ * Read the first 512 bytes and check for an executable image header.
+ */
+ union
+ {
+ DWORD dwSignature;
+ WORD wSignature;
+ BYTE ab[128];
+ } uBuf;
+ DWORD cbRead;
+ uBuf.dwSignature = 0;
+ if ( ReadFile(hFile, &uBuf, sizeof(uBuf), &cbRead, NULL /*pOverlapped*/)
+ && cbRead == sizeof(uBuf))
+ {
+ if (uBuf.wSignature == IMAGE_DOS_SIGNATURE)
+ return FALSE;
+ if (uBuf.dwSignature == IMAGE_NT_SIGNATURE)
+ return FALSE;
+ if ( uBuf.wSignature == IMAGE_OS2_SIGNATURE /* NE */
+ || uBuf.wSignature == 0x5d4c /* LX */
+ || uBuf.wSignature == IMAGE_OS2_SIGNATURE_LE /* LE */)
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Checks if the image path looks like microsoft CL.exe.
+ *
+ * @returns TRUE / FALSE.
+ * @param pwszImagePath The executable image path to evalutate.
+ * @param cwcImagePath The length of the image path.
+ */
+static BOOL mkWinChildIsProbableClExe(WCHAR const *pwszImagePath, size_t cwcImagePath)
+{
+ assert(pwszImagePath[cwcImagePath] == '\0');
+ return cwcImagePath > 7
+ && (pwszImagePath[cwcImagePath - 7] == L'/' || pwszImagePath[cwcImagePath - 7] == L'\\')
+ && (pwszImagePath[cwcImagePath - 6] == L'c' || pwszImagePath[cwcImagePath - 6] == L'C')
+ && (pwszImagePath[cwcImagePath - 5] == L'l' || pwszImagePath[cwcImagePath - 5] == L'L')
+ && pwszImagePath[cwcImagePath - 4] == L'.'
+ && (pwszImagePath[cwcImagePath - 3] == L'e' || pwszImagePath[cwcImagePath - 3] == L'E')
+ && (pwszImagePath[cwcImagePath - 2] == L'x' || pwszImagePath[cwcImagePath - 2] == L'X')
+ && (pwszImagePath[cwcImagePath - 1] == L'e' || pwszImagePath[cwcImagePath - 1] == L'E');
+}
+
+/**
+ * Temporary workaround for seemingly buggy kFsCache.c / dir-nt-bird.c.
+ *
+ * Something is not invalidated / updated correctly!
+ */
+static BOOL mkWinChildcareWorkerIsRegularFileW(PWINCHILDCAREWORKER pWorker, wchar_t const *pwszPath)
+{
+ BOOL fRet = FALSE;
+#ifdef KMK
+ if (utf16_regular_file_p(pwszPath))
+ fRet = TRUE;
+ else
+#endif
+ {
+ /* Don't believe the cache. */
+ DWORD dwAttr = GetFileAttributesW(pwszPath);
+ if (dwAttr != INVALID_FILE_ATTRIBUTES)
+ {
+ if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
+ {
+#ifdef KMK
+ extern void dir_cache_invalid_volatile(void);
+ dir_cache_invalid_volatile();
+ if (utf16_regular_file_p(pwszPath))
+ MkWinChildError(pWorker, 1, "kFsCache was out of sync! pwszPath=%S\n", pwszPath);
+ else
+ {
+ dir_cache_invalid_all();
+ if (utf16_regular_file_p(pwszPath))
+ MkWinChildError(pWorker, 1, "kFsCache was really out of sync! pwszPath=%S\n", pwszPath);
+ else
+ MkWinChildError(pWorker, 1, "kFsCache is really out of sync!! pwszPath=%S\n", pwszPath);
+ }
+#endif
+ fRet = TRUE;
+ }
+ }
+ }
+ return fRet;
+}
+
+
+/**
+ * Tries to locate the image file, searching the path and maybe falling back on
+ * the shell in case it knows more (think cygwin with its own view of the file
+ * system).
+ *
+ * This will also check for shell script, falling back on the shell too to
+ * handle those.
+ *
+ * @returns 0 on success, windows error code on failure.
+ * @param pWorker The childcare worker.
+ * @param pszArg0 The first argument.
+ * @param pwszSearchPath In case mkWinChildcareWorkerConvertEnvironment
+ * had a chance of locating the search path already.
+ * @param pwszzEnv The environment block, in case we need to look for
+ * the path.
+ * @param pszShell The shell.
+ * @param ppwszImagePath Where to return the pointer to the image path. This
+ * could be the shell.
+ * @param pfNeedShell Where to return shell vs direct execution indicator.
+ * @param pfProbableClExe Where to return an indicator of probably CL.EXE.
+ */
+static int mkWinChildcareWorkerFindImage(PWINCHILDCAREWORKER pWorker, char const *pszArg0, WCHAR *pwszSearchPath,
+ WCHAR const *pwszzEnv, const char *pszShell,
+ WCHAR **ppwszImagePath, BOOL *pfNeedShell, BOOL *pfProbableClExe)
+{
+ /** @todo Slap a cache on this code. We usually end up executing the same
+ * stuff over and over again (e.g. compilers, linkers, etc).
+ * Hitting the file system is slow on windows. */
+
+ /*
+ * Convert pszArg0 to unicode so we can work directly on that.
+ */
+ WCHAR wszArg0[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
+ DWORD dwErr;
+ size_t cbArg0 = strlen(pszArg0) + 1;
+ int const cwcArg0 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszArg0, cbArg0, wszArg0, MKWINCHILD_MAX_PATH);
+ if (cwcArg0 > 0)
+ {
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ WCHAR wszPathBuf[MKWINCHILD_MAX_PATH + 4]; /* +4 for painless '.exe' appending */
+ int cwc;
+
+ /*
+ * If there isn't an .exe suffix, we may have to add one.
+ * Also we ASSUME that .exe suffixes means no hash bang detection needed.
+ */
+ int const fHasExeSuffix = cwcArg0 > CSTRLEN(".exe")
+ && wszArg0[cwcArg0 - 4] == '.'
+ && (wszArg0[cwcArg0 - 3] == L'e' || wszArg0[cwcArg0 - 3] == L'E')
+ && (wszArg0[cwcArg0 - 2] == L'x' || wszArg0[cwcArg0 - 2] == L'X')
+ && (wszArg0[cwcArg0 - 1] == L'e' || wszArg0[cwcArg0 - 1] == L'E');
+
+ /*
+ * If there isn't any path specified, we need to search the PATH env.var.
+ */
+ int const fHasPath = wszArg0[1] == L':'
+ || wszArg0[0] == L'\\'
+ || wszArg0[0] == L'/'
+ || wmemchr(wszArg0, L'/', cwcArg0)
+ || wmemchr(wszArg0, L'\\', cwcArg0);
+
+ /* Before we do anything, flip UNIX slashes to DOS ones. */
+ WCHAR *pwc = wszArg0;
+ while ((pwc = wcschr(pwc, L'/')) != NULL)
+ *pwc++ = L'\\';
+
+ /* Don't need to set these all the time... */
+ *pfNeedShell = FALSE;
+ *pfProbableClExe = FALSE;
+
+ /*
+ * If any kind of path is specified in arg0, we will not search the
+ * PATH env.var and can limit ourselves to maybe slapping a .exe on to it.
+ */
+ if (fHasPath)
+ {
+ /*
+ * If relative to a CWD, turn it into an absolute one.
+ */
+ unsigned cwcPath = cwcArg0;
+ WCHAR *pwszPath = wszArg0;
+ if ( *pwszPath != L'\\'
+ && (pwszPath[1] != ':' || pwszPath[2] != L'\\') )
+ {
+ DWORD cwcAbsPath = GetFullPathNameW(wszArg0, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
+ if (cwcAbsPath > 0)
+ {
+ cwcPath = cwcAbsPath + 1; /* include terminator, like MultiByteToWideChar does. */
+ pwszPath = wszPathBuf;
+ }
+ }
+
+ /*
+ * Check with .exe suffix first.
+ * We don't open .exe files and look for hash bang stuff, we just
+ * assume they are executable images that CreateProcess can deal with.
+ */
+ if (!fHasExeSuffix)
+ {
+ pwszPath[cwcPath - 1] = L'.';
+ pwszPath[cwcPath ] = L'e';
+ pwszPath[cwcPath + 1] = L'x';
+ pwszPath[cwcPath + 2] = L'e';
+ pwszPath[cwcPath + 3] = L'\0';
+ }
+
+ if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
+ {
+ *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath + 4 - 1);
+ return mkWinChildDuplicateUtf16String(pwszPath, cwcPath + 4, ppwszImagePath);
+ }
+
+ /*
+ * If no suffix was specified, try without .exe too, but now we need
+ * to see if it's for the shell or CreateProcess.
+ */
+ if (!fHasExeSuffix)
+ {
+ pwszPath[cwcPath - 1] = L'\0';
+#ifdef KMK
+ if (mkWinChildcareWorkerIsRegularFileW(pWorker, pwszPath))
+#endif
+ {
+ hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
+ CloseHandle(hFile);
+ if (!*pfNeedShell)
+ {
+ *pfProbableClExe = mkWinChildIsProbableClExe(pwszPath, cwcPath - 1);
+ return mkWinChildDuplicateUtf16String(pwszPath, cwcPath, ppwszImagePath);
+ }
+ }
+ }
+ }
+ }
+ /*
+ * No path, need to search the PATH env.var. for the executable, maybe
+ * adding an .exe suffix while do so if that is missing.
+ */
+ else
+ {
+ BOOL fSearchedCwd = FALSE;
+ WCHAR wszPathFallback[4];
+ if (!pwszSearchPath)
+ pwszSearchPath = (WCHAR *)mkWinChildcareWorkerFindPathValue(pwszzEnv, wszPathFallback);
+
+ for (;;)
+ {
+ size_t cwcCombined;
+
+ /*
+ * Find the end of the current PATH component.
+ */
+ size_t cwcSkip;
+ WCHAR wcEnd;
+ size_t cwcComponent = 0;
+ WCHAR wc;
+ while ((wc = pwszSearchPath[cwcComponent]) != L'\0')
+ {
+ if (wc != ';' && wc != ':')
+ { /* likely */ }
+ else if (wc == ';')
+ break;
+ else if (cwcComponent != (pwszSearchPath[cwcComponent] != L'"' ? 1 : 2))
+ break;
+ cwcComponent++;
+ }
+ wcEnd = wc;
+
+ /* Trim leading spaces and double quotes. */
+ while ( cwcComponent > 0
+ && ((wc = *pwszSearchPath) == L'"' || wc == L' ' || wc == L'\t'))
+ {
+ pwszSearchPath++;
+ cwcComponent--;
+ }
+ cwcSkip = cwcComponent;
+
+ /* Trim trailing spaces & double quotes. */
+ while ( cwcComponent > 0
+ && ((wc = pwszSearchPath[cwcComponent - 1]) == L'"' || wc == L' ' || wc == L'\t'))
+ cwcComponent--;
+
+ /*
+ * Skip empty components. Join the component and the filename, making sure to
+ * resolve any CWD relative stuff first.
+ */
+ cwcCombined = cwcComponent + 1 + cwcArg0;
+ if (cwcComponent > 0 && cwcCombined <= MKWINCHILD_MAX_PATH)
+ {
+ /* Copy the component into wszPathBuf, maybe abspath'ing it. */
+ DWORD cwcAbsPath = 0;
+ if ( *pwszSearchPath != L'\\'
+ && (pwszSearchPath[1] != ':' || pwszSearchPath[2] != L'\\') )
+ {
+ /* To save an extra buffer + copying, we'll temporarily modify the PATH
+ value in our converted UTF-16 environment block. */
+ WCHAR const wcSaved = pwszSearchPath[cwcComponent];
+ pwszSearchPath[cwcComponent] = L'\0';
+ cwcAbsPath = GetFullPathNameW(pwszSearchPath, MKWINCHILD_MAX_PATH, wszPathBuf, NULL);
+ pwszSearchPath[cwcComponent] = wcSaved;
+ if (cwcAbsPath > 0 && cwcAbsPath + 1 + cwcArg0 <= MKWINCHILD_MAX_PATH)
+ cwcCombined = cwcAbsPath + 1 + cwcArg0;
+ else
+ cwcAbsPath = 0;
+ }
+ if (cwcAbsPath == 0)
+ {
+ memcpy(wszPathBuf, pwszSearchPath, cwcComponent * sizeof(WCHAR));
+ cwcAbsPath = cwcComponent;
+ }
+
+ /* Append the filename. */
+ if ((wc = wszPathBuf[cwcAbsPath - 1]) == L'\\' || wc == L'/' || wc == L':')
+ {
+ memcpy(&wszPathBuf[cwcAbsPath], wszArg0, cwcArg0 * sizeof(WCHAR));
+ cwcCombined--;
+ }
+ else
+ {
+ wszPathBuf[cwcAbsPath] = L'\\';
+ memcpy(&wszPathBuf[cwcAbsPath + 1], wszArg0, cwcArg0 * sizeof(WCHAR));
+ }
+ assert(wszPathBuf[cwcCombined - 1] == L'\0');
+
+ /* DOS slash conversion */
+ pwc = wszPathBuf;
+ while ((pwc = wcschr(pwc, L'/')) != NULL)
+ *pwc++ = L'\\';
+
+ /*
+ * Search with exe suffix first.
+ */
+ if (!fHasExeSuffix)
+ {
+ wszPathBuf[cwcCombined - 1] = L'.';
+ wszPathBuf[cwcCombined ] = L'e';
+ wszPathBuf[cwcCombined + 1] = L'x';
+ wszPathBuf[cwcCombined + 2] = L'e';
+ wszPathBuf[cwcCombined + 3] = L'\0';
+ }
+ if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
+ {
+ *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4) - 1);
+ return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined + (fHasExeSuffix ? 0 : 4), ppwszImagePath);
+ }
+ if (!fHasExeSuffix)
+ {
+ wszPathBuf[cwcCombined - 1] = L'\0';
+#ifdef KMK
+ if (mkWinChildcareWorkerIsRegularFileW(pWorker, wszPathBuf))
+#endif
+ {
+ /*
+ * Check if the file exists w/o the added '.exe' suffix. If it does,
+ * we need to check if we can pass it to CreateProcess or need the shell.
+ */
+ hFile = CreateFileW(wszPathBuf, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ NULL /*pSecAttr*/, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ *pfNeedShell = mkWinChildcareWorkerCheckIfNeedShell(hFile);
+ CloseHandle(hFile);
+ if (!*pfNeedShell)
+ {
+ *pfProbableClExe = mkWinChildIsProbableClExe(wszPathBuf, cwcCombined - 1);
+ return mkWinChildDuplicateUtf16String(wszPathBuf, cwcCombined, ppwszImagePath);
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Advance to the next component.
+ */
+ if (wcEnd != '\0')
+ pwszSearchPath += cwcSkip + 1;
+ else if (fSearchedCwd)
+ break;
+ else
+ {
+ fSearchedCwd = TRUE;
+ wszPathFallback[0] = L'.';
+ wszPathFallback[1] = L'\0';
+ pwszSearchPath = wszPathFallback;
+ }
+ }
+ }
+
+ /*
+ * We need the shell. It will take care of finding/reporting missing
+ * image files and such.
+ */
+ *pfNeedShell = TRUE;
+ if (pszShell)
+ {
+ cwc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszShell, strlen(pszShell) + 1, wszPathBuf, MKWINCHILD_MAX_PATH);
+ if (cwc > 0)
+ return mkWinChildDuplicateUtf16String(wszPathBuf, cwc, ppwszImagePath);
+ dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert shell (%s): %u\n"), pszShell, dwErr);
+ }
+ else
+ {
+ MkWinChildError(pWorker, 1, "%s: not found!\n", pszArg0);
+ dwErr = ERROR_FILE_NOT_FOUND;
+ }
+ }
+ else
+ {
+ dwErr = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert argv[0] (%s): %u\n"), pszArg0, dwErr);
+ }
+ return dwErr == ERROR_INSUFFICIENT_BUFFER ? ERROR_FILENAME_EXCED_RANGE : dwErr;
+}
+
+/**
+ * Creates the environment block.
+ *
+ * @returns 0 on success, windows error code on failure.
+ * @param pWorker The childcare worker if on one, otherwise NULL.
+ * @param papszEnv The environment vector to convert.
+ * @param cbEnvStrings The size of the environment strings, iff they are
+ * sequential in a block. Otherwise, zero.
+ * @param ppwszEnv Where to return the pointer to the environment
+ * block.
+ * @param ppwszSearchPath Where to return the pointer to the path value
+ * within the environment block. This will not be set
+ * if cbEnvStrings is non-zero, more efficient to let
+ * mkWinChildcareWorkerFindImage() search when needed.
+ */
+static int mkWinChildcareWorkerConvertEnvironment(PWINCHILDCAREWORKER pWorker, char **papszEnv, size_t cbEnvStrings,
+ WCHAR **ppwszEnv, WCHAR const **ppwszSearchPath)
+{
+ DWORD dwErr;
+ int cwcRc;
+ int cwcDst;
+ WCHAR *pwszzDst;
+
+ *ppwszSearchPath = NULL;
+
+ /*
+ * We've got a little optimization here with help from mkWinChildCopyStringArray.
+ */
+ if (cbEnvStrings)
+ {
+ cwcDst = cbEnvStrings + 32;
+ pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
+ cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
+ if (cwcRc != 0)
+ {
+ *ppwszEnv = pwszzDst;
+ return 0;
+ }
+
+ /* Resize the allocation and try again. */
+ dwErr = GetLastError();
+ if (dwErr == ERROR_INSUFFICIENT_BUFFER)
+ {
+ cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, NULL, 0);
+ if (cwcRc > 0)
+ cwcDst = cwcRc + 32;
+ else
+ cwcDst *= 2;
+ pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst);
+ cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, papszEnv[0], cbEnvStrings, pwszzDst, cwcDst);
+ if (cwcRc != 0)
+ {
+ *ppwszEnv = pwszzDst;
+ return 0;
+ }
+ dwErr = GetLastError();
+ }
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment block: %u\n"), dwErr);
+ }
+ /*
+ * Need to convert it string by string.
+ */
+ else
+ {
+ size_t offPathValue = ~(size_t)0;
+ size_t offDst;
+
+ /*
+ * Estimate the size first.
+ */
+ size_t cEnvVars;
+ size_t cwcDst = 32;
+ size_t iVar = 0;
+ const char *pszSrc;
+ while ((pszSrc = papszEnv[iVar]) != NULL)
+ {
+ cwcDst += strlen(pszSrc) + 1;
+ iVar++;
+ }
+ cEnvVars = iVar;
+
+ /* Allocate estimated WCHARs and convert the variables one by one, reallocating
+ the block as needed. */
+ pwszzDst = (WCHAR *)xmalloc(cwcDst * sizeof(WCHAR));
+ cwcDst--; /* save one wchar for the terminating empty string. */
+ offDst = 0;
+ for (iVar = 0; iVar < cEnvVars; iVar++)
+ {
+ size_t cwcLeft = cwcDst - offDst;
+ size_t const cbSrc = strlen(pszSrc = papszEnv[iVar]) + 1;
+ assert(cwcDst >= offDst);
+
+
+ cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft);
+ if (cwcRc > 0)
+ { /* likely */ }
+ else
+ {
+ dwErr = GetLastError();
+ if (dwErr == ERROR_INSUFFICIENT_BUFFER)
+ {
+ /* Need more space. So, calc exacly how much and resize the block accordingly. */
+ size_t cbSrc2 = cbSrc;
+ size_t iVar2 = iVar;
+ cwcLeft = 1;
+ for (;;)
+ {
+ size_t cwcRc2 = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, NULL, 0);
+ if (cwcRc2 > 0)
+ cwcLeft += cwcRc2;
+ else
+ cwcLeft += cbSrc * 4;
+
+ /* advance */
+ iVar2++;
+ if (iVar2 >= cEnvVars)
+ break;
+ pszSrc = papszEnv[iVar2];
+ cbSrc2 = strlen(pszSrc) + 1;
+ }
+ pszSrc = papszEnv[iVar];
+
+ /* Grow the allocation and repeat the conversion. */
+ if (offDst + cwcLeft > cwcDst + 1)
+ {
+ cwcDst = offDst + cwcLeft;
+ pwszzDst = (WCHAR *)xrealloc(pwszzDst, cwcDst * sizeof(WCHAR));
+ cwcDst--; /* save one wchar for the terminating empty string. */
+ cwcRc = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszSrc, cbSrc, &pwszzDst[offDst], cwcLeft - 1);
+ if (cwcRc <= 0)
+ dwErr = GetLastError();
+ }
+ }
+ if (cwcRc <= 0)
+ {
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert environment string #%u (%s): %u\n"),
+ iVar, pszSrc, dwErr);
+ free(pwszzDst);
+ return dwErr;
+ }
+ }
+
+ /* Look for the PATH. */
+ if ( offPathValue == ~(size_t)0
+ && IS_PATH_ENV_VAR(cwcRc, &pwszzDst[offDst]) )
+ offPathValue = offDst + 4 + 1;
+
+ /* Advance. */
+ offDst += cwcRc;
+ }
+ pwszzDst[offDst++] = '\0';
+
+ if (offPathValue != ~(size_t)0)
+ *ppwszSearchPath = &pwszzDst[offPathValue];
+ *ppwszEnv = pwszzDst;
+ return 0;
+ }
+ free(pwszzDst);
+ return dwErr;
+}
+
+/**
+ * Childcare worker: handle regular process.
+ *
+ * @param pWorker The worker.
+ * @param pChild The kSubmit child.
+ */
+static void mkWinChildcareWorkerThreadHandleProcess(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
+{
+ WCHAR *pwszSearchPath = NULL;
+ WCHAR *pwszzEnvironment = NULL;
+ WCHAR *pwszCommandLine = NULL;
+ WCHAR *pwszImageName = NULL;
+ BOOL fNeedShell = FALSE;
+ BOOL fProbableClExe = FALSE;
+ int rc;
+
+ /*
+ * First we convert the environment so we get the PATH we need to
+ * search for the executable.
+ */
+ rc = mkWinChildcareWorkerConvertEnvironment(pWorker, pChild->u.Process.papszEnv ? pChild->u.Process.papszEnv : environ,
+ pChild->u.Process.cbEnvStrings,
+ &pwszzEnvironment, &pwszSearchPath);
+ /*
+ * Find the executable and maybe checking if it's a shell script, then
+ * convert it to a command line.
+ */
+ if (rc == 0)
+ rc = mkWinChildcareWorkerFindImage(pWorker, pChild->u.Process.papszArgs[0], pwszSearchPath, pwszzEnvironment,
+ pChild->u.Process.pszShell, &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
+ if (rc == 0)
+ {
+ if (!fNeedShell)
+ rc = mkWinChildcareWorkerConvertCommandline(pWorker, pChild->u.Process.papszArgs, 0 /*fFlags*/, &pwszCommandLine);
+ else
+ rc = mkWinChildcareWorkerConvertCommandlineWithShell(pWorker, pwszImageName, pChild->u.Process.papszArgs,
+ &pwszCommandLine);
+
+ /*
+ * Create the child process.
+ */
+ if (rc == 0)
+ {
+ BOOL afReplace[3] = { FALSE, pChild->u.Process.hStdOut != INVALID_HANDLE_VALUE, pChild->u.Process.hStdErr != INVALID_HANDLE_VALUE };
+ HANDLE ahChild[3] = { INVALID_HANDLE_VALUE, pChild->u.Process.hStdOut, pChild->u.Process.hStdErr };
+ rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
+ NULL /*pwszCwd*/, afReplace, ahChild, pChild->u.Process.fCatchOutput,
+ &pChild->u.Process.hProcess);
+ mkWinChildcareWorkerCloseStandardHandles(pChild);
+ if (rc == 0)
+ {
+ /*
+ * Wait for the child to complete.
+ */
+ mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Process.hProcess, pwszImageName,
+ pChild->u.Process.fCatchOutput);
+ }
+ else
+ pChild->iExitCode = rc;
+ }
+ else
+ pChild->iExitCode = rc;
+ }
+ else
+ pChild->iExitCode = rc;
+ free(pwszCommandLine);
+ free(pwszImageName);
+ free(pwszzEnvironment);
+
+ /* In case we failed, we must make sure the child end of pipes
+ used by $(shell no_such_command.exe) are closed, otherwise
+ the main thread will be stuck reading the parent end. */
+ mkWinChildcareWorkerCloseStandardHandles(pChild);
+}
+
+#ifdef KMK
+
+/**
+ * Childcare worker: handle builtin command.
+ *
+ * @param pWorker The worker.
+ * @param pChild The kSubmit child.
+ */
+static void mkWinChildcareWorkerThreadHandleBuiltIn(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
+{
+ PCKMKBUILTINENTRY pBuiltIn = pChild->u.BuiltIn.pBuiltIn;
+ KMKBUILTINCTX Ctx =
+ {
+ pBuiltIn->uName.s.sz,
+ pChild->pMkChild ? &pChild->pMkChild->output : NULL,
+ pWorker,
+ };
+ if (pBuiltIn->uFnSignature == FN_SIG_MAIN)
+ pChild->iExitCode = pBuiltIn->u.pfnMain(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
+ pChild->u.BuiltIn.papszEnv, &Ctx);
+ else if (pBuiltIn->uFnSignature == FN_SIG_MAIN_SPAWNS)
+ pChild->iExitCode = pBuiltIn->u.pfnMainSpawns(pChild->u.BuiltIn.cArgs, pChild->u.BuiltIn.papszArgs,
+ pChild->u.BuiltIn.papszEnv, &Ctx, pChild->pMkChild, NULL /*pPid*/);
+ else
+ {
+ assert(0);
+ pChild->iExitCode = 98;
+ }
+}
+
+/**
+ * Childcare worker: handle append write-out.
+ *
+ * @param pWorker The worker.
+ * @param pChild The kSubmit child.
+ */
+static void mkWinChildcareWorkerThreadHandleAppend(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
+{
+ int fd = open(pChild->u.Append.pszFilename,
+ pChild->u.Append.fTruncate
+ ? O_WRONLY | O_TRUNC | O_CREAT | _O_NOINHERIT | _O_BINARY
+ : O_WRONLY | O_APPEND | O_CREAT | _O_NOINHERIT | _O_BINARY,
+ 0666);
+ if (fd >= 0)
+ {
+ ssize_t cbWritten = write(fd, pChild->u.Append.pszAppend, pChild->u.Append.cbAppend);
+ if (cbWritten == (ssize_t)pChild->u.Append.cbAppend)
+ {
+ if (close(fd) >= 0)
+ {
+ pChild->iExitCode = 0;
+ return;
+ }
+ MkWinChildError(pWorker, 1, "kmk_builtin_append: close failed on '%s': %u (%s)\n",
+ pChild->u.Append.pszFilename, errno, strerror(errno));
+ }
+ else
+ MkWinChildError(pWorker, 1, "kmk_builtin_append: error writing %lu bytes to on '%s': %u (%s)\n",
+ pChild->u.Append.cbAppend, pChild->u.Append.pszFilename, errno, strerror(errno));
+ close(fd);
+ }
+ else
+ MkWinChildError(pWorker, 1, "kmk_builtin_append: error opening '%s': %u (%s)\n",
+ pChild->u.Append.pszFilename, errno, strerror(errno));
+ pChild->iExitCode = 1;
+}
+
+/**
+ * Childcare worker: handle kSubmit job.
+ *
+ * @param pWorker The worker.
+ * @param pChild The kSubmit child.
+ */
+static void mkWinChildcareWorkerThreadHandleSubmit(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
+{
+ void *pvSubmitWorker = pChild->u.Submit.pvSubmitWorker;
+
+ /*
+ * Prep the wait handles.
+ */
+ HANDLE ahHandles[3] = { pChild->u.Submit.hEvent, NULL, NULL };
+ DWORD cHandles = 1;
+ if (pChild->u.Submit.pStdOut)
+ {
+ assert(pChild->u.Submit.pStdErr);
+ pChild->u.Submit.pStdOut->fHaveWrittenOut = FALSE;
+ ahHandles[cHandles++] = pChild->u.Submit.pStdOut->hEvent;
+ pChild->u.Submit.pStdErr->fHaveWrittenOut = FALSE;
+ ahHandles[cHandles++] = pChild->u.Submit.pStdErr->hEvent;
+ }
+
+ /*
+ * Wait loop.
+ */
+ for (;;)
+ {
+ int iExitCode = -42;
+ int iSignal = -1;
+ DWORD dwStatus;
+ if (cHandles == 1)
+ dwStatus = WaitForSingleObject(ahHandles[0], INFINITE);
+ else
+ {
+ dwStatus = WaitForMultipleObjects(cHandles, ahHandles, FALSE /*fWaitAll*/, INFINITE);
+ assert(dwStatus != WAIT_FAILED);
+ if (dwStatus == WAIT_OBJECT_0 + 1)
+ mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdOut, FALSE /*fDraining*/);
+ else if (dwStatus == WAIT_OBJECT_0 + 2)
+ mkWinChildcareWorkerCatchOutput(pChild, pChild->u.Submit.pStdErr, FALSE /*fDraining*/);
+ }
+ if (kSubmitSubProcGetResult((intptr_t)pvSubmitWorker, dwStatus == WAIT_OBJECT_0 /*fBlock*/, &iExitCode, &iSignal) == 0)
+ {
+ if (pChild->u.Submit.pStdOut)
+ MkWinChildcareWorkerDrainPipes(pChild, pChild->u.Submit.pStdOut, pChild->u.Submit.pStdErr);
+
+ pChild->iExitCode = iExitCode;
+ pChild->iSignal = iSignal;
+ /* Cleanup must be done on the main thread. */
+ return;
+ }
+
+ if (pChild->iSignal != 0)
+ kSubmitSubProcKill((intptr_t)pvSubmitWorker, pChild->iSignal);
+ }
+}
+
+/**
+ * Childcare worker: handle kmk_redirect process.
+ *
+ * @param pWorker The worker.
+ * @param pChild The redirect child.
+ */
+static void mkWinChildcareWorkerThreadHandleRedirect(PWINCHILDCAREWORKER pWorker, PWINCHILD pChild)
+{
+ mkWinChildcareWorkerWaitForProcess(pWorker, pChild, pChild->u.Redirect.hProcess, L"kmk_redirect", FALSE /*fCatchOutput*/);
+}
+
+#endif /* KMK */
+
+/**
+ * Childcare worker thread.
+ *
+ * @returns 0
+ * @param pvUser The worker instance.
+ */
+static unsigned int __stdcall mkWinChildcareWorkerThread(void *pvUser)
+{
+ PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvUser;
+ assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
+
+#ifdef MKWINCHILD_DO_SET_PROCESSOR_GROUP
+ /*
+ * Adjust process group if necessary.
+ *
+ * Note! It seems that setting the mask to zero means that we select all
+ * active processors. Couldn't find any alternative API for getting
+ * the correct active processor mask.
+ */
+ if (g_cProcessorGroups > 1)
+ {
+ GROUP_AFFINITY Affinity = { 0 /* == all active apparently */, pWorker->iProcessorGroup, { 0, 0, 0 } };
+ BOOL fRet = g_pfnSetThreadGroupAffinity(GetCurrentThread(), &Affinity, NULL);
+ assert(fRet); (void)fRet;
+# ifndef NDEBUG
+ {
+ GROUP_AFFINITY ActualAffinity = { 0xbeefbeefU, 0xbeef, { 0xbeef, 0xbeef, 0xbeef } };
+ fRet = GetThreadGroupAffinity(GetCurrentThread(), &ActualAffinity);
+ assert(fRet); (void)fRet;
+ assert(ActualAffinity.Group == pWorker->iProcessorGroup);
+ }
+# endif
+ }
+#endif
+
+ /*
+ * Work loop.
+ */
+ while (!g_fShutdown)
+ {
+ /*
+ * Try go idle.
+ */
+ PWINCHILD pChild = pWorker->pTailTodoChildren;
+ if (!pChild)
+ {
+ _InterlockedExchange(&pWorker->fIdle, TRUE);
+ pChild = pWorker->pTailTodoChildren;
+ if (!pChild)
+ {
+ DWORD dwStatus;
+
+ _InterlockedIncrement((long *)&g_cIdleChildcareWorkers);
+ _InterlockedExchange((long *)&g_idxLastChildcareWorker, pWorker->idxWorker);
+ dwStatus = WaitForSingleObject(pWorker->hEvtIdle, INFINITE);
+ _InterlockedExchange(&pWorker->fIdle, FALSE);
+ _InterlockedDecrement((long *)&g_cIdleChildcareWorkers);
+
+ assert(dwStatus != WAIT_FAILED);
+ if (dwStatus == WAIT_FAILED)
+ Sleep(20);
+
+ pChild = pWorker->pTailTodoChildren;
+ }
+ else
+ _InterlockedExchange(&pWorker->fIdle, FALSE);
+ }
+ if (pChild)
+ {
+ /*
+ * We got work to do. First job is to deque the job.
+ */
+ pChild = mkWinChildDequeFromLifo(&pWorker->pTailTodoChildren, pChild);
+ assert(pChild);
+ if (pChild)
+ {
+ PWINCHILD pTailExpect;
+
+ pChild->pWorker = pWorker;
+ pWorker->pCurChild = pChild;
+ switch (pChild->enmType)
+ {
+ case WINCHILDTYPE_PROCESS:
+ mkWinChildcareWorkerThreadHandleProcess(pWorker, pChild);
+ break;
+#ifdef KMK
+ case WINCHILDTYPE_BUILT_IN:
+ mkWinChildcareWorkerThreadHandleBuiltIn(pWorker, pChild);
+ break;
+ case WINCHILDTYPE_APPEND:
+ mkWinChildcareWorkerThreadHandleAppend(pWorker, pChild);
+ break;
+ case WINCHILDTYPE_SUBMIT:
+ mkWinChildcareWorkerThreadHandleSubmit(pWorker, pChild);
+ break;
+ case WINCHILDTYPE_REDIRECT:
+ mkWinChildcareWorkerThreadHandleRedirect(pWorker, pChild);
+ break;
+#endif
+ default:
+ assert(0);
+ }
+ pWorker->pCurChild = NULL;
+ pChild->pWorker = NULL;
+
+ /*
+ * Move the child to the completed list.
+ */
+ pTailExpect = NULL;
+ for (;;)
+ {
+ PWINCHILD pTailActual;
+ pChild->pNext = pTailExpect;
+ pTailActual = _InterlockedCompareExchangePointer(&g_pTailCompletedChildren, pChild, pTailExpect);
+ if (pTailActual != pTailExpect)
+ pTailExpect = pTailActual;
+ else
+ {
+ _InterlockedDecrement(&g_cPendingChildren);
+ if (pTailExpect)
+ break;
+ if (SetEvent(g_hEvtWaitChildren))
+ break;
+ MkWinChildError(pWorker, 1, "SetEvent(g_hEvtWaitChildren=%p) failed: %u\n",
+ g_hEvtWaitChildren, GetLastError());
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ _endthreadex(0);
+ return 0;
+}
+
+/**
+ * Creates a pipe for catching child output.
+ *
+ * This is a custom CreatePipe implementation that allows for overlapped I/O on
+ * our end of the pipe. Silly that they don't offer an API that does this.
+ *
+ * @returns The pipe that was created. NULL on failure.
+ * @param pPipe The structure for the pipe.
+ * @param iWhich Which standard descriptor this is a pipe for.
+ * @param idxWorker The worker index.
+ */
+PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker)
+{
+ /*
+ * We try generate a reasonably unique name from the get go, so this retry
+ * loop shouldn't really ever be needed. But you never know.
+ */
+ static unsigned s_iSeqNo = 0;
+ DWORD const cMaxInstances = 1;
+ DWORD const cbPipe = 4096;
+ DWORD const cMsTimeout = 0;
+ unsigned cTries = 256;
+ while (cTries-- > 0)
+ {
+ /* Create the pipe (our end). */
+ HANDLE hPipeRead;
+ DWORD fOpenMode = PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE;
+ DWORD fPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS;
+ WCHAR wszName[MAX_PATH];
+ s_iSeqNo++;
+ _snwprintf(wszName, MAX_PATH, L"\\\\.\\pipe\\kmk-winchildren-%u-%u-%u-%s-%u-%u",
+ GetCurrentProcessId(), GetCurrentThreadId(), idxWorker, iWhich == 1 ? L"out" : L"err", s_iSeqNo, GetTickCount());
+ hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
+ if (hPipeRead == INVALID_HANDLE_VALUE && GetLastError() == ERROR_INVALID_PARAMETER)
+ {
+ fOpenMode &= ~FILE_FLAG_FIRST_PIPE_INSTANCE;
+ fPipeMode &= ~PIPE_REJECT_REMOTE_CLIENTS;
+ hPipeRead = CreateNamedPipeW(wszName, fOpenMode, fPipeMode, cMaxInstances, cbPipe, cbPipe, cMsTimeout, NULL /*pSecAttr*/);
+ }
+ if (hPipeRead != INVALID_HANDLE_VALUE)
+ {
+ /* Connect the other end. */
+ HANDLE hPipeWrite = CreateFileW(wszName, GENERIC_WRITE | FILE_READ_ATTRIBUTES, 0 /*fShareMode*/, NULL /*pSecAttr*/,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/);
+ if (hPipeWrite != INVALID_HANDLE_VALUE)
+ {
+ /*
+ * Create the event object and we're done.
+ *
+ * It starts in signalled stated so we don't need special code
+ * for handing when we start waiting.
+ */
+ HANDLE hEvent = CreateEventW(NULL /*pSecAttr*/, TRUE /*fManualReset*/, TRUE /*fInitialState*/, NULL /*pwszName*/);
+ if (hEvent != NULL)
+ {
+ PWINCCWPIPE pPipe = (PWINCCWPIPE)xcalloc(sizeof(*pPipe));
+ pPipe->hPipeMine = hPipeRead;
+ pPipe->hPipeChild = hPipeWrite;
+ pPipe->hEvent = hEvent;
+ pPipe->iWhich = iWhich;
+ pPipe->fReadPending = FALSE;
+ pPipe->cbBuffer = cbPipe;
+ pPipe->pbBuffer = xcalloc(cbPipe);
+ return pPipe;
+ }
+
+ CloseHandle(hPipeWrite);
+ CloseHandle(hPipeRead);
+ return NULL;
+ }
+ CloseHandle(hPipeRead);
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Destroys a childcare worker pipe.
+ *
+ * @param pPipe The pipe.
+ */
+void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe)
+{
+ if (pPipe->hPipeChild != NULL)
+ {
+ CloseHandle(pPipe->hPipeChild);
+ pPipe->hPipeChild = NULL;
+ }
+
+ if (pPipe->hPipeMine != NULL)
+ {
+ if (pPipe->fReadPending)
+ if (!CancelIo(pPipe->hPipeMine))
+ WaitForSingleObject(pPipe->hEvent, INFINITE);
+ CloseHandle(pPipe->hPipeMine);
+ pPipe->hPipeMine = NULL;
+ }
+
+ if (pPipe->hEvent != NULL)
+ {
+ CloseHandle(pPipe->hEvent);
+ pPipe->hEvent = NULL;
+ }
+
+ if (pPipe->pbBuffer)
+ {
+ free(pPipe->pbBuffer);
+ pPipe->pbBuffer = NULL;
+ }
+}
+
+/**
+ * Initializes the processor group allocator.
+ *
+ * @param pState The allocator to initialize.
+ */
+void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState)
+{
+ /* We shift the starting group with the make nesting level as part of
+ our very simple distribution strategy. */
+ pState->idxGroup = makelevel;
+ pState->idxProcessorInGroup = 0;
+}
+
+/**
+ * Allocate CPU group for the next child process.
+ *
+ * @returns CPU group.
+ * @param pState The allocator state. Must be initialized by
+ * MkWinChildInitCpuGroupAllocator().
+ */
+unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState)
+{
+ unsigned int iGroup = 0;
+ if (g_cProcessorGroups > 1)
+ {
+ unsigned int cMaxInGroup;
+ unsigned int cInGroup;
+
+ iGroup = pState->idxGroup % g_cProcessorGroups;
+
+ /* Advance. We employ a very simple strategy that does 50% in
+ each group for each group cycle. Odd processor counts are
+ caught in odd group cycles. The init function selects the
+ starting group based on make nesting level to avoid stressing
+ out the first group. */
+ cInGroup = ++pState->idxProcessorInGroup;
+ cMaxInGroup = g_pacProcessorsInGroup[iGroup];
+ if ( !(cMaxInGroup & 1)
+ || !((pState->idxGroup / g_cProcessorGroups) & 1))
+ cMaxInGroup /= 2;
+ else
+ cMaxInGroup = cMaxInGroup / 2 + 1;
+ if (cInGroup >= cMaxInGroup)
+ {
+ pState->idxProcessorInGroup = 0;
+ pState->idxGroup++;
+ }
+ }
+ return iGroup;
+}
+
+/**
+ * Creates another childcare worker.
+ *
+ * @returns The new worker, if we succeeded.
+ */
+static PWINCHILDCAREWORKER mkWinChildcareCreateWorker(void)
+{
+ PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)xcalloc(sizeof(*pWorker));
+ pWorker->uMagic = WINCHILDCAREWORKER_MAGIC;
+ pWorker->idxWorker = g_cChildCareworkers;
+ pWorker->hEvtIdle = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
+ if (pWorker->hEvtIdle)
+ {
+ pWorker->pStdOut = MkWinChildcareCreateWorkerPipe(1, pWorker->idxWorker);
+ if (pWorker->pStdOut)
+ {
+ pWorker->pStdErr = MkWinChildcareCreateWorkerPipe(2, pWorker->idxWorker);
+ if (pWorker->pStdErr)
+ {
+ /* Before we start the thread, assign it to a processor group. */
+ pWorker->iProcessorGroup = MkWinChildAllocateCpuGroup(&g_ProcessorGroupAllocator);
+
+ /* Try start the thread. */
+ pWorker->hThread = (HANDLE)_beginthreadex(NULL, 0 /*cbStack*/, mkWinChildcareWorkerThread, pWorker,
+ 0, &pWorker->tid);
+ if (pWorker->hThread != NULL)
+ {
+ pWorker->idxWorker = g_cChildCareworkers++; /* paranoia */
+ g_papChildCareworkers[pWorker->idxWorker] = pWorker;
+ return pWorker;
+ }
+
+ /* Bail out! */
+ ONS (error, NILF, "_beginthreadex failed: %u (%s)\n", errno, strerror(errno));
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdErr);
+ }
+ else
+ ON (error, NILF, "Failed to create stderr pipe: %u\n", GetLastError());
+ MkWinChildcareDeleteWorkerPipe(pWorker->pStdOut);
+ }
+ else
+ ON (error, NILF, "Failed to create stdout pipe: %u\n", GetLastError());
+ CloseHandle(pWorker->hEvtIdle);
+ }
+ else
+ ON (error, NILF, "CreateEvent failed: %u\n", GetLastError());
+ pWorker->uMagic = ~WINCHILDCAREWORKER_MAGIC;
+ free(pWorker);
+ return NULL;
+}
+
+/**
+ * Helper for copying argument and environment vectors.
+ *
+ * @returns Single alloc block copy.
+ * @param papszSrc The source vector.
+ * @param pcbStrings Where to return the size of the strings & terminator.
+ */
+static char **mkWinChildCopyStringArray(char **papszSrc, size_t *pcbStrings)
+{
+ const char *psz;
+ char **papszDstArray;
+ char *pszDstStr;
+ size_t i;
+
+ /* Calc sizes first. */
+ size_t cbStrings = 1; /* (one extra for terminator string) */
+ size_t cStrings = 0;
+ while ((psz = papszSrc[cStrings]) != NULL)
+ {
+ cbStrings += strlen(psz) + 1;
+ cStrings++;
+ }
+ *pcbStrings = cbStrings;
+
+ /* Allocate destination. */
+ papszDstArray = (char **)xmalloc(cbStrings + (cStrings + 1) * sizeof(papszDstArray[0]));
+ pszDstStr = (char *)&papszDstArray[cStrings + 1];
+
+ /* Copy it. */
+ for (i = 0; i < cStrings; i++)
+ {
+ const char *pszSource = papszSrc[i];
+ size_t cchString = strlen(pszSource);
+ papszDstArray[i] = pszDstStr;
+ memcpy(pszDstStr, pszSource, cchString);
+ pszDstStr += cchString;
+ *pszDstStr++ = '\0';
+ }
+ *pszDstStr = '\0';
+ assert(&pszDstStr[1] - papszDstArray[0] == cbStrings);
+ papszDstArray[i] = NULL;
+ return papszDstArray;
+}
+
+/**
+ * Allocate and init a WINCHILD.
+ *
+ * @returns The new windows child structure.
+ * @param enmType The child type.
+ */
+static PWINCHILD mkWinChildNew(WINCHILDTYPE enmType)
+{
+ PWINCHILD pChild = xcalloc(sizeof(*pChild));
+ pChild->enmType = enmType;
+ pChild->fCoreDumped = 0;
+ pChild->iSignal = 0;
+ pChild->iExitCode = 222222;
+ pChild->uMagic = WINCHILD_MAGIC;
+ pChild->pid = (intptr_t)pChild;
+ return pChild;
+}
+
+/**
+ * Destructor for WINCHILD.
+ *
+ * @param pChild The child structure to destroy.
+ */
+static void mkWinChildDelete(PWINCHILD pChild)
+{
+ assert(pChild->uMagic == WINCHILD_MAGIC);
+ pChild->uMagic = ~WINCHILD_MAGIC;
+
+ switch (pChild->enmType)
+ {
+ case WINCHILDTYPE_PROCESS:
+ {
+ if (pChild->u.Process.papszArgs)
+ {
+ free(pChild->u.Process.papszArgs);
+ pChild->u.Process.papszArgs = NULL;
+ }
+ if (pChild->u.Process.cbEnvStrings && pChild->u.Process.papszEnv)
+ {
+ free(pChild->u.Process.papszEnv);
+ pChild->u.Process.papszEnv = NULL;
+ }
+ if (pChild->u.Process.pszShell)
+ {
+ free(pChild->u.Process.pszShell);
+ pChild->u.Process.pszShell = NULL;
+ }
+ if (pChild->u.Process.hProcess)
+ {
+ CloseHandle(pChild->u.Process.hProcess);
+ pChild->u.Process.hProcess = NULL;
+ }
+ mkWinChildcareWorkerCloseStandardHandles(pChild);
+ break;
+ }
+
+#ifdef KMK
+ case WINCHILDTYPE_BUILT_IN:
+ if (pChild->u.BuiltIn.papszArgs)
+ {
+ free(pChild->u.BuiltIn.papszArgs);
+ pChild->u.BuiltIn.papszArgs = NULL;
+ }
+ if (pChild->u.BuiltIn.papszEnv)
+ {
+ free(pChild->u.BuiltIn.papszEnv);
+ pChild->u.BuiltIn.papszEnv = NULL;
+ }
+ break;
+
+ case WINCHILDTYPE_APPEND:
+ if (pChild->u.Append.pszFilename)
+ {
+ free(pChild->u.Append.pszFilename);
+ pChild->u.Append.pszFilename = NULL;
+ }
+ if (pChild->u.Append.pszAppend)
+ {
+ free(pChild->u.Append.pszAppend);
+ pChild->u.Append.pszAppend = NULL;
+ }
+ break;
+
+ case WINCHILDTYPE_SUBMIT:
+ if (pChild->u.Submit.pvSubmitWorker)
+ {
+ kSubmitSubProcCleanup((intptr_t)pChild->u.Submit.pvSubmitWorker);
+ pChild->u.Submit.pvSubmitWorker = NULL;
+ }
+ break;
+
+ case WINCHILDTYPE_REDIRECT:
+ if (pChild->u.Redirect.hProcess)
+ {
+ CloseHandle(pChild->u.Redirect.hProcess);
+ pChild->u.Redirect.hProcess = NULL;
+ }
+ break;
+#endif /* KMK */
+
+ default:
+ assert(0);
+ }
+
+ free(pChild);
+}
+
+/**
+ * Queues the child with a worker, creating new workers if necessary.
+ *
+ * @returns 0 on success, windows error code on failure (child destroyed).
+ * @param pChild The child.
+ * @param pPid Where to return the PID (optional).
+ */
+static int mkWinChildPushToCareWorker(PWINCHILD pChild, pid_t *pPid)
+{
+ PWINCHILDCAREWORKER pWorker = NULL;
+ PWINCHILD pOldChild;
+ PWINCHILD pCurChild;
+
+ /*
+ * There are usually idle workers around, except for at the start.
+ */
+ if (g_cIdleChildcareWorkers > 0)
+ {
+ /*
+ * Try the idle hint first and move forward from it.
+ */
+ unsigned int const cWorkers = g_cChildCareworkers;
+ unsigned int iHint = g_idxLastChildcareWorker;
+ unsigned int i;
+ for (i = iHint; i < cWorkers; i++)
+ {
+ PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
+ if (pPossibleWorker->fIdle)
+ {
+ pWorker = pPossibleWorker;
+ break;
+ }
+ }
+ if (!pWorker)
+ {
+ /* Scan from the start. */
+ if (iHint > cWorkers)
+ iHint = cWorkers;
+ for (i = 0; i < iHint; i++)
+ {
+ PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
+ if (pPossibleWorker->fIdle)
+ {
+ pWorker = pPossibleWorker;
+ break;
+ }
+ }
+ }
+ }
+ if (!pWorker)
+ {
+ /*
+ * Try create more workers if we haven't reached the max yet.
+ */
+ if (g_cChildCareworkers < g_cChildCareworkersMax)
+ pWorker = mkWinChildcareCreateWorker();
+
+ /*
+ * Queue it with an existing worker. Look for one without anthing extra scheduled.
+ */
+ if (!pWorker)
+ {
+ unsigned int i = g_cChildCareworkers;
+ if (i == 0)
+ fatal(NILF, 0, _("Failed to create worker threads for managing child processes!\n"));
+ pWorker = g_papChildCareworkers[--i];
+ if (pWorker->pTailTodoChildren)
+ while (i-- > 0)
+ {
+ PWINCHILDCAREWORKER pPossibleWorker = g_papChildCareworkers[i];
+ if (!pPossibleWorker->pTailTodoChildren)
+ {
+ pWorker = pPossibleWorker;
+ break;
+ }
+ }
+ }
+ }
+
+ /*
+ * Do the queueing.
+ */
+ pOldChild = NULL;
+ for (;;)
+ {
+ pChild->pNext = pOldChild;
+ pCurChild = _InterlockedCompareExchangePointer((void **)&pWorker->pTailTodoChildren, pChild, pOldChild);
+ if (pCurChild == pOldChild)
+ {
+ DWORD volatile dwErr;
+ _InterlockedIncrement(&g_cPendingChildren);
+ if ( !pWorker->fIdle
+ || SetEvent(pWorker->hEvtIdle))
+ {
+ *pPid = pChild->pid;
+ return 0;
+ }
+
+ _InterlockedDecrement(&g_cPendingChildren);
+ dwErr = GetLastError();
+ assert(0);
+ mkWinChildDelete(pChild);
+ return dwErr ? dwErr : -20;
+ }
+ pOldChild = pCurChild;
+ }
+}
+
+/**
+ * Creates a regular child process (job.c).
+ *
+ * Will copy the information and push it to a childcare thread that does the
+ * actual process creation.
+ *
+ * @returns 0 on success, windows status code on failure.
+ * @param papszArgs The arguments.
+ * @param papszEnv The environment (optional).
+ * @param pszShell The SHELL variable value (optional).
+ * @param pMkChild The make child structure (optional).
+ * @param pPid Where to return the pid.
+ */
+int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid)
+{
+ PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
+ pChild->pMkChild = pMkChild;
+
+ pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
+ if ( !papszEnv
+ || !pMkChild
+ || pMkChild->environment == papszEnv)
+ {
+ pChild->u.Process.cbEnvStrings = 0;
+ pChild->u.Process.papszEnv = papszEnv;
+ }
+ else
+ pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv, &pChild->u.Process.cbEnvStrings);
+ if (pszShell)
+ pChild->u.Process.pszShell = xstrdup(pszShell);
+ pChild->u.Process.hStdOut = INVALID_HANDLE_VALUE;
+ pChild->u.Process.hStdErr = INVALID_HANDLE_VALUE;
+
+ /* We always catch the output in order to prevent character soups courtesy
+ of the microsoft CRT and/or linkers writing character by character to the
+ console. Always try write whole lines, even when --output-sync is none. */
+ pChild->u.Process.fCatchOutput = TRUE;
+
+ return mkWinChildPushToCareWorker(pChild, pPid);
+}
+
+/**
+ * Creates a chile process with a pipe hooked up to stdout.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param papszArgs The argument vector.
+ * @param papszEnv The environment vector (optional).
+ * @param fdErr File descriptor to hook up to stderr.
+ * @param pPid Where to return the pid.
+ * @param pfdReadPipe Where to return the read end of the pipe.
+ */
+int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe)
+{
+ /*
+ * Create the pipe.
+ */
+ HANDLE hReadPipe;
+ HANDLE hWritePipe;
+ if (CreatePipe(&hReadPipe, &hWritePipe, NULL, 0 /* default size */))
+ {
+ //if (SetHandleInformation(hWritePipe, HANDLE_FLAG_INHERIT /* clear */ , HANDLE_FLAG_INHERIT /*set*/))
+ {
+ int fdReadPipe = _open_osfhandle((intptr_t)hReadPipe, O_RDONLY);
+ if (fdReadPipe >= 0)
+ {
+ PWINCHILD pChild;
+ int rc;
+
+ /*
+ * Get a handle for fdErr. Ignore failure.
+ */
+ HANDLE hStdErr = INVALID_HANDLE_VALUE;
+ if (fdErr >= 0)
+ {
+ HANDLE hNative = (HANDLE)_get_osfhandle(fdErr);
+ if (!DuplicateHandle(GetCurrentProcess(), hNative, GetCurrentProcess(),
+ &hStdErr, 0 /*DesiredAccess*/, TRUE /*fInherit*/, DUPLICATE_SAME_ACCESS))
+ {
+ ONN(error, NILF, _("DuplicateHandle failed on stderr descriptor (%u): %u\n"), fdErr, GetLastError());
+ hStdErr = INVALID_HANDLE_VALUE;
+ }
+ }
+
+ /*
+ * Push it off to the worker thread.
+ */
+ pChild = mkWinChildNew(WINCHILDTYPE_PROCESS);
+ pChild->u.Process.papszArgs = mkWinChildCopyStringArray(papszArgs, &pChild->u.Process.cbArgsStrings);
+ pChild->u.Process.papszEnv = mkWinChildCopyStringArray(papszEnv ? papszEnv : environ,
+ &pChild->u.Process.cbEnvStrings);
+ //if (pszShell)
+ // pChild->u.Process.pszShell = xstrdup(pszShell);
+ pChild->u.Process.hStdOut = hWritePipe;
+ pChild->u.Process.hStdErr = hStdErr;
+ pChild->u.Process.fCloseStdErr = TRUE;
+ pChild->u.Process.fCloseStdOut = TRUE;
+
+ rc = mkWinChildPushToCareWorker(pChild, pPid);
+ if (rc == 0)
+ *pfdReadPipe = fdReadPipe;
+ else
+ {
+ ON(error, NILF, _("mkWinChildPushToCareWorker failed on pipe: %d\n"), rc);
+ close(fdReadPipe);
+ *pfdReadPipe = -1;
+ *pPid = -1;
+ }
+ return rc;
+ }
+
+ ON(error, NILF, _("_open_osfhandle failed on pipe: %u\n"), errno);
+ }
+ //else
+ // ON(error, NILF, _("SetHandleInformation failed on pipe: %u\n"), GetLastError());
+ if (hReadPipe != INVALID_HANDLE_VALUE)
+ CloseHandle(hReadPipe);
+ CloseHandle(hWritePipe);
+ }
+ else
+ ON(error, NILF, _("CreatePipe failed: %u\n"), GetLastError());
+ *pfdReadPipe = -1;
+ *pPid = -1;
+ return -1;
+}
+
+#ifdef KMK
+
+/**
+ * Interface used by kmkbuiltin.c for executing builtin commands on threads.
+ *
+ * @returns 0 on success, windows status code on failure.
+ * @param pBuiltIn The kmk built-in command entry.
+ * @param cArgs The number of arguments in papszArgs.
+ * @param papszArgs The argument vector.
+ * @param papszEnv The environment vector, optional.
+ * @param pMkChild The make child structure.
+ * @param pPid Where to return the pid.
+ */
+int MkWinChildCreateBuiltIn(PCKMKBUILTINENTRY pBuiltIn, int cArgs, char **papszArgs, char **papszEnv,
+ struct child *pMkChild, pid_t *pPid)
+{
+ size_t cbIgnored;
+ PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_BUILT_IN);
+ pChild->pMkChild = pMkChild;
+ pChild->u.BuiltIn.pBuiltIn = pBuiltIn;
+ pChild->u.BuiltIn.cArgs = cArgs;
+ pChild->u.BuiltIn.papszArgs = mkWinChildCopyStringArray(papszArgs, &cbIgnored);
+ pChild->u.BuiltIn.papszEnv = papszEnv ? mkWinChildCopyStringArray(papszEnv, &cbIgnored) : NULL;
+ return mkWinChildPushToCareWorker(pChild, pPid);
+}
+
+/**
+ * Interface used by append.c for do the slow file system part.
+ *
+ * This will append the given buffer to the specified file and free the buffer.
+ *
+ * @returns 0 on success, windows status code on failure.
+ *
+ * @param pszFilename The name of the file to append to.
+ * @param ppszAppend What to append. The pointer pointed to is set to
+ * NULL once we've taken ownership of the buffer and
+ * promise to free it.
+ * @param cbAppend How much to append.
+ * @param fTruncate Whether to truncate the file before appending to it.
+ * @param pMkChild The make child structure.
+ * @param pPid Where to return the pid.
+ */
+int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
+ struct child *pMkChild, pid_t *pPid)
+{
+ size_t cbFilename = strlen(pszFilename) + 1;
+ PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_APPEND);
+ pChild->pMkChild = pMkChild;
+ pChild->u.Append.fTruncate = fTruncate;
+ pChild->u.Append.pszAppend = *ppszAppend;
+ pChild->u.Append.cbAppend = cbAppend;
+ pChild->u.Append.pszFilename = (char *)memcpy(xmalloc(cbFilename), pszFilename, cbFilename);
+ *ppszAppend = NULL;
+ return mkWinChildPushToCareWorker(pChild, pPid);
+}
+
+/**
+ * Interface used by kSubmit.c for registering stuff to wait on.
+ *
+ * @returns 0 on success, windows status code on failure.
+ * @param hEvent The event object handle to wait on.
+ * @param pvSubmitWorker The argument to pass back to kSubmit to clean up.
+ * @param pStdOut Standard output pipe for the worker. Optional.
+ * @param pStdErr Standard error pipe for the worker. Optional.
+ * @param pMkChild The make child structure.
+ * @param pPid Where to return the pid.
+ */
+int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr,
+ struct child *pMkChild, pid_t *pPid)
+{
+ PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_SUBMIT);
+ pChild->pMkChild = pMkChild;
+ pChild->u.Submit.hEvent = (HANDLE)hEvent;
+ pChild->u.Submit.pvSubmitWorker = pvSubmitWorker;
+ pChild->u.Submit.pStdOut = pStdOut;
+ pChild->u.Submit.pStdErr = pStdErr;
+ return mkWinChildPushToCareWorker(pChild, pPid);
+}
+
+/**
+ * Interface used by redirect.c for registering stuff to wait on.
+ *
+ * @returns 0 on success, windows status code on failure.
+ * @param hProcess The process object to wait on.
+ * @param pPid Where to return the pid.
+ */
+int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid)
+{
+ PWINCHILD pChild = mkWinChildNew(WINCHILDTYPE_REDIRECT);
+ pChild->u.Redirect.hProcess = (HANDLE)hProcess;
+ return mkWinChildPushToCareWorker(pChild, pPid);
+}
+
+
+/**
+ * New interface used by redirect.c for spawning and waitin on a child.
+ *
+ * This interface is only used when kmk_builtin_redirect is already running on
+ * a worker thread.
+ *
+ * @returns exit status.
+ * @param pvWorker The worker instance.
+ * @param pszExecutable The executable image to run.
+ * @param papszArgs Argument vector.
+ * @param fQuotedArgv Whether the argument vector is already quoted and
+ * just need some space to be turned into a command
+ * line.
+ * @param papszEnvVars Environment vector.
+ * @param pszCwd The working directory of the child. Optional.
+ * @param pafReplace Which standard handles to replace. Maybe modified!
+ * @param pahReplace The replacement handles. Maybe modified!
+ *
+ */
+int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv,
+ char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3])
+{
+ PWINCHILDCAREWORKER pWorker = (PWINCHILDCAREWORKER)pvWorker;
+ WCHAR *pwszSearchPath = NULL;
+ WCHAR *pwszzEnvironment = NULL;
+ WCHAR *pwszCommandLine = NULL;
+ WCHAR *pwszImageName = NULL;
+ WCHAR *pwszCwd = NULL;
+ BOOL fNeedShell = FALSE;
+ PWINCHILD pChild;
+ int rc;
+ assert(pWorker->uMagic == WINCHILDCAREWORKER_MAGIC);
+ pChild = pWorker->pCurChild;
+ assert(pChild != NULL && pChild->uMagic == WINCHILD_MAGIC);
+
+ /*
+ * Convert the CWD first since it's optional and we don't need to clean
+ * up anything here if it fails.
+ */
+ if (pszCwd)
+ {
+ size_t cchCwd = strlen(pszCwd);
+ int cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, NULL, 0);
+ pwszCwd = xmalloc((cwcCwd + 1) * sizeof(WCHAR)); /* (+1 in case cwcCwd is 0) */
+ cwcCwd = MultiByteToWideChar(CP_ACP, 0 /*fFlags*/, pszCwd, cchCwd + 1, pwszCwd, cwcCwd + 1);
+ if (!cwcCwd)
+ {
+ rc = GetLastError();
+ MkWinChildError(pWorker, 1, _("MultiByteToWideChar failed to convert CWD (%s): %u\n"), pszCwd, (unsigned)rc);
+ return rc;
+ }
+ }
+
+ /*
+ * Before we search for the image, we convert the environment so we don't
+ * have to traverse it twice to find the PATH.
+ */
+ rc = mkWinChildcareWorkerConvertEnvironment(pWorker, papszEnvVars ? papszEnvVars : environ, 0/*cbEnvStrings*/,
+ &pwszzEnvironment, &pwszSearchPath);
+ /*
+ * Find the executable and maybe checking if it's a shell script, then
+ * convert it to a command line.
+ */
+ if (rc == 0)
+ rc = mkWinChildcareWorkerFindImage(pWorker, pszExecutable, pwszSearchPath, pwszzEnvironment, NULL /*pszShell*/,
+ &pwszImageName, &fNeedShell, &pChild->fProbableClExe);
+ if (rc == 0)
+ {
+ assert(!fNeedShell);
+ if (!fQuotedArgv)
+ rc = mkWinChildcareWorkerConvertCommandline(pWorker, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
+ else
+ rc = mkWinChildcareWorkerConvertQuotedArgvToCommandline(pWorker, papszArgs, &pwszCommandLine);
+
+ /*
+ * Create the child process.
+ */
+ if (rc == 0)
+ {
+ HANDLE hProcess;
+ rc = mkWinChildcareWorkerCreateProcess(pWorker, pwszImageName, pwszCommandLine, pwszzEnvironment,
+ pwszCwd, pafReplace, pahReplace, TRUE /*fCatchOutput*/, &hProcess);
+ if (rc == 0)
+ {
+ /*
+ * Wait for the child to complete.
+ */
+ rc = mkWinChildcareWorkerWaitForProcess(pWorker, pChild, hProcess, pwszImageName, TRUE /*fCatchOutput*/);
+ CloseHandle(hProcess);
+ }
+ }
+ }
+
+ free(pwszCwd);
+ free(pwszCommandLine);
+ free(pwszImageName);
+ free(pwszzEnvironment);
+
+ return rc;
+}
+
+#endif /* CONFIG_NEW_WIN_CHILDREN */
+
+/**
+ * Interface used to kill process when processing Ctrl-C and fatal errors.
+ *
+ * @returns 0 on success, -1 & errno on error.
+ * @param pid The process to kill (PWINCHILD).
+ * @param iSignal What to kill it with.
+ * @param pMkChild The make child structure for validation.
+ */
+int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild)
+{
+ PWINCHILD pChild = (PWINCHILD)pid;
+ if (pChild)
+ {
+ assert(pChild->uMagic == WINCHILD_MAGIC);
+ if (pChild->uMagic == WINCHILD_MAGIC)
+ {
+ switch (pChild->enmType)
+ {
+ case WINCHILDTYPE_PROCESS:
+ assert(pChild->pMkChild == pMkChild);
+ TerminateProcess(pChild->u.Process.hProcess, DBG_TERMINATE_PROCESS);
+ pChild->iSignal = iSignal;
+ break;
+#ifdef KMK
+ case WINCHILDTYPE_SUBMIT:
+ {
+ pChild->iSignal = iSignal;
+ SetEvent(pChild->u.Submit.hEvent);
+ break;
+ }
+
+ case WINCHILDTYPE_REDIRECT:
+ TerminateProcess(pChild->u.Redirect.hProcess, DBG_TERMINATE_PROCESS);
+ pChild->iSignal = iSignal;
+ break;
+
+ case WINCHILDTYPE_BUILT_IN:
+ break;
+
+#endif /* KMK */
+ default:
+ assert(0);
+ }
+ }
+ }
+ return -1;
+}
+
+/**
+ * Wait for a child process to complete
+ *
+ * @returns 0 on success, windows error code on failure.
+ * @param fBlock Whether to block.
+ * @param pPid Where to return the pid if a child process
+ * completed. This is set to zero if none.
+ * @param piExitCode Where to return the exit code.
+ * @param piSignal Where to return the exit signal number.
+ * @param pfCoreDumped Where to return the core dumped indicator.
+ * @param ppMkChild Where to return the associated struct child pointer.
+ */
+int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild)
+{
+ PWINCHILD pChild;
+
+ *pPid = 0;
+ *piExitCode = -222222;
+ *pfCoreDumped = 0;
+ *ppMkChild = NULL;
+
+ /*
+ * Wait if necessary.
+ */
+ if (fBlock && !g_pTailCompletedChildren && g_cPendingChildren > 0)
+ {
+ DWORD dwStatus = WaitForSingleObject(g_hEvtWaitChildren, INFINITE);
+ if (dwStatus == WAIT_FAILED)
+ return (int)GetLastError();
+ }
+
+ /*
+ * Try unlink the last child in the LIFO.
+ */
+ pChild = g_pTailCompletedChildren;
+ if (!pChild)
+ return 0;
+ pChild = mkWinChildDequeFromLifo(&g_pTailCompletedChildren, pChild);
+ assert(pChild);
+
+ /*
+ * Set return values and ditch the child structure.
+ */
+ *pPid = pChild->pid;
+ *piExitCode = pChild->iExitCode;
+ *pfCoreDumped = pChild->fCoreDumped;
+ *ppMkChild = pChild->pMkChild;
+ switch (pChild->enmType)
+ {
+ case WINCHILDTYPE_PROCESS:
+ break;
+#ifdef KMK
+ case WINCHILDTYPE_BUILT_IN:
+ case WINCHILDTYPE_APPEND:
+ case WINCHILDTYPE_SUBMIT:
+ case WINCHILDTYPE_REDIRECT:
+ break;
+#endif /* KMK */
+ default:
+ assert(0);
+ }
+ mkWinChildDelete(pChild);
+
+#ifdef KMK
+ /* Flush the volatile directory cache. */
+ dir_cache_invalid_after_job();
+#endif
+ return 0;
+}
+
+/**
+ * Get the child completed event handle.
+ *
+ * Needed when w32os.c is waiting for a job token to become available, given
+ * that completed children is the typical source of these tokens (esp. for kmk).
+ *
+ * @returns Zero if no active children, event handle if waiting is required.
+ */
+intptr_t MkWinChildGetCompleteEventHandle(void)
+{
+ /* We don't return the handle if we've got completed children. This
+ is a safe guard against being called twice in a row without any
+ MkWinChildWait call inbetween. */
+ if (!g_pTailCompletedChildren)
+ return (intptr_t)g_hEvtWaitChildren;
+ return 0;
+}
+
+/**
+ * Emulate execv() for restarting kmk after one or more makefiles has been made.
+ *
+ * Does not return.
+ *
+ * @param papszArgs The arguments.
+ * @param papszEnv The environment.
+ */
+void MkWinChildReExecMake(char **papszArgs, char **papszEnv)
+{
+ PROCESS_INFORMATION ProcInfo;
+ STARTUPINFOW StartupInfo;
+ WCHAR *pwszCommandLine;
+ WCHAR *pwszzEnvironment;
+ WCHAR *pwszPathIgnored;
+ int rc;
+
+ /*
+ * Get the executable name.
+ */
+ WCHAR wszImageName[MKWINCHILD_MAX_PATH];
+ DWORD cwcImageName = GetModuleFileNameW(GetModuleHandle(NULL), wszImageName, MKWINCHILD_MAX_PATH);
+ if (cwcImageName == 0)
+ ON(fatal, NILF, _("MkWinChildReExecMake: GetModuleFileName failed: %u\n"), GetLastError());
+
+ /*
+ * Create the command line and environment.
+ */
+ rc = mkWinChildcareWorkerConvertCommandline(NULL, papszArgs, 0 /*fFlags*/, &pwszCommandLine);
+ if (rc != 0)
+ ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertCommandline failed: %u\n"), rc);
+
+ rc = mkWinChildcareWorkerConvertEnvironment(NULL, papszEnv ? papszEnv : environ, 0 /*cbEnvStrings*/,
+ &pwszzEnvironment, &pwszPathIgnored);
+ if (rc != 0)
+ ON(fatal, NILF, _("MkWinChildReExecMake: mkWinChildcareWorkerConvertEnvironment failed: %u\n"), rc);
+
+#ifdef KMK
+ /*
+ * Flush the file system cache to avoid messing up tools fetching
+ * going on in the "exec'ed" make by keeping directories open.
+ */
+ dir_cache_invalid_all_and_close_dirs(1);
+#endif
+
+ /*
+ * Fill out the startup info and try create the process.
+ */
+ memset(&ProcInfo, 0, sizeof(ProcInfo));
+ memset(&StartupInfo, 0, sizeof(StartupInfo));
+ StartupInfo.cb = sizeof(StartupInfo);
+ GetStartupInfoW(&StartupInfo);
+ if (!CreateProcessW(wszImageName, pwszCommandLine, NULL /*pProcSecAttr*/, NULL /*pThreadSecAttr*/,
+ TRUE /*fInheritHandles*/, CREATE_UNICODE_ENVIRONMENT, pwszzEnvironment, NULL /*pwsz*/,
+ &StartupInfo, &ProcInfo))
+ ON(fatal, NILF, _("MkWinChildReExecMake: CreateProcessW failed: %u\n"), GetLastError());
+ CloseHandle(ProcInfo.hThread);
+
+ /*
+ * Wait for it to complete and forward the status code to our parent.
+ */
+ for (;;)
+ {
+ DWORD dwExitCode = -2222;
+ DWORD dwStatus = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
+ if ( dwStatus == WAIT_IO_COMPLETION
+ || dwStatus == WAIT_TIMEOUT /* whatever */)
+ continue; /* however unlikely, these aren't fatal. */
+
+ /* Get the status code and terminate. */
+ if (dwStatus == WAIT_OBJECT_0)
+ {
+ if (!GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
+ {
+ ON(fatal, NILF, _("MkWinChildReExecMake: GetExitCodeProcess failed: %u\n"), GetLastError());
+ dwExitCode = -2222;
+ }
+ }
+ else if (dwStatus)
+ dwExitCode = dwStatus;
+
+ CloseHandle(ProcInfo.hProcess);
+ for (;;)
+ exit(dwExitCode);
+ }
+}
+
+#ifdef WITH_RW_LOCK
+/** Serialization with kmkbuiltin_redirect. */
+void MkWinChildExclusiveAcquire(void)
+{
+ AcquireSRWLockExclusive(&g_RWLock);
+}
+
+/** Serialization with kmkbuiltin_redirect. */
+void MkWinChildExclusiveRelease(void)
+{
+ ReleaseSRWLockExclusive(&g_RWLock);
+}
+#endif /* WITH_RW_LOCK */
+
+/**
+ * Implementation of the CLOSE_ON_EXEC macro.
+ *
+ * @returns errno value.
+ * @param fd The file descriptor to hide from children.
+ */
+int MkWinChildUnrelatedCloseOnExec(int fd)
+{
+ if (fd >= 0)
+ {
+ HANDLE hNative = (HANDLE)_get_osfhandle(fd);
+ if (hNative != INVALID_HANDLE_VALUE && hNative != NULL)
+ {
+ if (SetHandleInformation(hNative, HANDLE_FLAG_INHERIT /*clear*/ , 0 /*set*/))
+ return 0;
+ }
+ return errno;
+ }
+ return EINVAL;
+}
+
diff --git a/src/kmk/w32/winchildren.h b/src/kmk/w32/winchildren.h
new file mode 100644
index 0000000..e70bd1e
--- /dev/null
+++ b/src/kmk/w32/winchildren.h
@@ -0,0 +1,115 @@
+/* $Id: winchildren.h 3313 2020-03-16 02:31:38Z bird $ */
+/** @file
+ * Child process creation and management for kmk.
+ */
+
+/*
+ * Copyright (c) 2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef INCLUDED_WINCHILDREN_H
+#define INCLUDED_WINCHILDREN_H
+
+/** Child processor group allocator state. */
+typedef struct MKWINCHILDCPUGROUPALLOCSTATE
+{
+ /** The group index for the worker allocator.
+ * This is ever increasing and must be modded by g_cProcessorGroups. */
+ unsigned int idxGroup;
+ /** The processor in group index for the worker allocator. */
+ unsigned int idxProcessorInGroup;
+} MKWINCHILDCPUGROUPALLOCSTATE;
+/** Pointer to a CPU group allocator state. */
+typedef MKWINCHILDCPUGROUPALLOCSTATE *PMKWINCHILDCPUGROUPALLOCSTATE;
+
+#ifdef DECLARE_HANDLE
+/**
+ * A childcare worker pipe.
+ */
+typedef struct WINCCWPIPE
+{
+ /** My end of the pipe. */
+ HANDLE hPipeMine;
+ /** The child end of the pipe. */
+ HANDLE hPipeChild;
+ /** The event for asynchronous reading. */
+ HANDLE hEvent;
+ /** Which pipe this is (1 == stdout, 2 == stderr). */
+ unsigned char iWhich;
+ /** Set if we've got a read pending already. */
+ BOOL fReadPending;
+ /** Indicator that we've written out something. This is cleared before
+ * we start catching output from a new child and use in the CL.exe
+ * supression heuristics. */
+ BOOL fHaveWrittenOut;
+ /** Number of bytes at the start of the buffer that we've already
+ * written out. We try write out whole lines. */
+ DWORD cbWritten;
+ /** The buffer offset of the read currently pending. */
+ DWORD offPendingRead;
+ /** Read buffer size. */
+ DWORD cbBuffer;
+ /** The read buffer allocation. */
+ unsigned char *pbBuffer;
+ /** Overlapped I/O structure. */
+ OVERLAPPED Overlapped;
+} WINCCWPIPE;
+#endif
+
+typedef struct WINCCWPIPE *PWINCCWPIPE;
+
+void MkWinChildInit(unsigned int cJobSlot);
+void MkWinChildReExecMake(char **papszArgs, char **papszEnv);
+intptr_t MkWinChildGetCompleteEventHandle(void);
+int MkWinChildCreate(char **papszArgs, char **papszEnv, const char *pszShell, struct child *pMkChild, pid_t *pPid);
+int MkWinChildCreateWithStdOutPipe(char **papszArgs, char **papszEnv, int fdErr, pid_t *pPid, int *pfdReadPipe);
+void MkWinChildInitCpuGroupAllocator(PMKWINCHILDCPUGROUPALLOCSTATE pState);
+unsigned int MkWinChildAllocateCpuGroup(PMKWINCHILDCPUGROUPALLOCSTATE pState);
+
+#ifdef KMK
+struct KMKBUILTINENTRY;
+int MkWinChildCreateBuiltIn(struct KMKBUILTINENTRY const *pBuiltIn, int cArgs, char **papszArgs,
+ char **papszEnv, struct child *pMkChild, pid_t *pPid);
+int MkWinChildCreateAppend(const char *pszFilename, char **ppszAppend, size_t cbAppend, int fTruncate,
+ struct child *pMkChild, pid_t *pPid);
+
+int MkWinChildCreateSubmit(intptr_t hEvent, void *pvSubmitWorker, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr,
+ struct child *pMkChild, pid_t *pPid);
+PWINCCWPIPE MkWinChildcareCreateWorkerPipe(unsigned iWhich, unsigned int idxWorker);
+void MkWinChildcareWorkerDrainPipes(struct WINCHILD *pChild, PWINCCWPIPE pStdOut, PWINCCWPIPE pStdErr);
+void MkWinChildcareDeleteWorkerPipe(PWINCCWPIPE pPipe);
+
+int MkWinChildCreateRedirect(intptr_t hProcess, pid_t *pPid);
+# ifdef DECLARE_HANDLE
+int MkWinChildBuiltInExecChild(void *pvWorker, const char *pszExecutable, char **papszArgs, BOOL fQuotedArgv,
+ char **papszEnvVars, const char *pszCwd, BOOL pafReplace[3], HANDLE pahReplace[3]);
+# endif
+#endif /* KMK */
+int MkWinChildKill(pid_t pid, int iSignal, struct child *pMkChild);
+int MkWinChildWait(int fBlock, pid_t *pPid, int *piExitCode, int *piSignal, int *pfCoreDumped, struct child **ppMkChild);
+void MkWinChildExclusiveAcquire(void);
+void MkWinChildExclusiveRelease(void);
+
+#undef CLOSE_ON_EXEC
+#define CLOSE_ON_EXEC(a_fd) MkWinChildUnrelatedCloseOnExec(a_fd)
+int MkWinChildUnrelatedCloseOnExec(int fd);
+
+
+#endif
+