diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 5f5840e0..a29c2258 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -210,7 +210,7 @@ fn pkgappenddiscovered(st: *pkgdiscover, path: str) bool = { }; // Preserve the caller's toolchain environment while pinning the locale and -// temporary directory used by the current build plan or test product. +// temporary directory used by the current build plan. fn toolenv(tmpdir: str, out: *[]str) bool = { let inherited: []str = os.getenvs(); if (inherited.len > PKG_COUNT_MAX - 2) { @@ -243,6 +243,73 @@ fn toolenv(tmpdir: str, out: *[]str) bool = { return true; }; +fn pkgfreeownedstr(value: str) void = { + if (value.ptr != nil && value.len != 0) { + os.free(value.ptr: *void, value.len: u64); + }; +}; + +fn pkgfreestrs(values: []str) void = { + if (values.ptr != nil && values.cap != 0) { + os.free(values.ptr: *void, + (values.cap: u64) * (size(str): u64)); + }; +}; + +// Go's AppendPWD appends, then os/exec keeps the last duplicate value. WW's +// executor preserves duplicates, so discard earlier exact PWD entries here. +fn runenv(tmpdir: str, pwd: str, out: *[]str, tmpowned: *str, + pwdowned: *str) bool = { + let inherited: []str = os.getenvs(); + if (inherited.len > PKG_COUNT_MAX - 3) { + pkgputln(os.STDERR_FILENO, + "wwtest package: package graph is too large"); + return false; + }; + let allocation: ([]str | nomem) = pkgallocstrs(inherited.len + 3); + let env: []str; + match (allocation) { + case let value: []str => env = value; + case nomem => { + pkgputln(os.STDERR_FILENO, "wwtest package: out of memory"); + return false; + }; + }; + let i: i32 = 0; + for (i < inherited.len) { + if (!strings.hasprefix(inherited[i], "TMPDIR=") + && !strings.hasprefix(inherited[i], "LC_ALL=") + && !strings.hasprefix(inherited[i], "PWD=")) { + append(env, inherited[i]); + }; + i += 1; + }; + append(env, "LC_ALL=C"); + let tmpenv: str; + if (!pkgstring(&tmpenv, "TMPDIR=", tmpdir)) { + pkgfreestrs(env); + return false; + }; + let pwdenv: str; + if (!pkgstring(&pwdenv, "PWD=", pwd)) { + pkgfreeownedstr(tmpenv); + pkgfreestrs(env); + return false; + }; + append(env, tmpenv); + append(env, pwdenv); + *out = env; + *tmpowned = tmpenv; + *pwdowned = pwdenv; + return true; +}; + +fn freerunenv(env: []str, tmpowned: str, pwdowned: str) void = { + pkgfreeownedstr(tmpowned); + pkgfreeownedstr(pwdowned); + pkgfreestrs(env); +}; + fn pkgwrite(fd: i32, s: str) bool = { let r: (i64 | os.oserror) = os.writeall(fd, s.ptr, s.len: u64); match (r) { @@ -1453,25 +1520,37 @@ fn pkgstartrun(g: *pkggroup, filters: []str, timeoutarg: str, }; append(ra, g.bin); let packagearg: str; - if (!pkgstring(&packagearg, "-package=", g.pkg)) { return false; }; + if (!pkgstring(&packagearg, "-package=", g.pkg)) { + pkgfreestrs(ra); + return false; + }; append(ra, packagearg); if (list) { append(ra, "-list"); }; if (timeoutarg.len != 0) { append(ra, timeoutarg); }; let i: i32 = 0; for (i < filters.len) { append(ra, filters[i]); i += 1; }; let env: []str; - if (!toolenv(g.root, &env)) { return false; }; + let tmpowned: str; + let pwdowned: str; + if (!runenv(g.root, g.dir, &env, &tmpowned, &pwdowned)) { + pkgfreeownedstr(packagearg); + pkgfreestrs(ra); + return false; + }; let rcmd: exec.command; rcmd.path = g.bin; rcmd.argv = ra; rcmd.env = env; - rcmd.dir = ""; + rcmd.dir = g.dir; rcmd.stdoutpath = g.runout; rcmd.stderrpath = g.runerr; rcmd.deadline.sec = 0i64; rcmd.deadline.nsec = 0i64; rcmd.grace = 0i64: time.duration; exec.start(h, &rcmd); + freerunenv(env, tmpowned, pwdowned); + pkgfreeownedstr(packagearg); + pkgfreestrs(ra); return true; }; @@ -1758,8 +1837,7 @@ export fn packagecommand(args: []str) int = { pkgusage(); return 2; }; - // -o names the -c artifact; a plain run always executes from the - // temp root, so a caller-owned name has nothing to name. + // A non-compile run leaves no caller-owned artifact for -o to name. if (outname.len != 0 && !compileonly) { pkgputln(os.STDERR_FILENO, "wwtest package: -o needs -c"); return 2;