44#
55# ⚠️ IT USED TO FIND WHATEVER THE MACHINE HAD, AND `command -v` CANNOT TELL THE
66# DIFFERENCE. Measured 2026-08-25 with a build program that printed its own
7- # PATH: mcpp 's own environment appeared nowhere in it, and
7+ # PATH: the project 's declared environment appeared nowhere in it, and
88# `command -v qemu-system-riscv64` returned a shim that answers, when run,
99#
1010# [error] qemu-system-riscv64 is not installed in this subos (_)
1313# environment the build could not reach.
1414#
1515# ⭐⭐ THIS FILE ASSERTS BOTH DIRECTIONS, BECAUSE ONLY ONE OF THEM IS THE
16- # FEATURE. Prepending unconditionally would have passed the first half and is
17- # the design that was withdrawn: a shared directory in front of every project
18- # makes what a build sees depend on what else was installed on that machine.
19- # The declaration is what puts it there, so a project that declares nothing
20- # must come out byte-for-byte unchanged.
16+ # FEATURE. Prepending unconditionally would pass the "declared" half, and that
17+ # is the design that was withdrawn: a shared directory in front of every
18+ # project makes what a build sees depend on what else was installed on that
19+ # machine. The declaration is what puts it there, so a project that declares
20+ # nothing must come out unchanged.
21+ #
22+ # ⚠️⚠️ AND "UNCHANGED" IS COMPARED AGAINST THE INHERITED VALUE, NOT AGAINST A
23+ # PATTERN. The first version of this half rejected a first entry matching
24+ # `*/subos/*/bin` — and CI's own PATH already begins with one, because the
25+ # runner activates an xlings environment to get mcpp at all:
26+ #
27+ # MCPP: /home/runner/.xlings/subos/default/bin/mcpp
28+ #
29+ # so it reported mcpp prepending something that was already there. A test for
30+ # "did not change it" has to hold the before and the after side by side.
2131set -e
2232
2333MCPP=" ${MCPP:- mcpp} "
@@ -31,26 +41,15 @@ probe='import std;
3141
3242int main() {
3343 const char* p = std::getenv("PATH");
34- std::string_view path(p ? p : "");
35- std::string_view first;
36- for (auto part : std::views::split(path, ' " '" ' :' " '" ' )) {
37- first = std::string_view(part);
38- break;
39- }
40- std::println("PROBE_FIRST={}", first);
41- std::println("PROBE_ENTRIES={}", std::ranges::count(path, ' " '" ' :' " '" ' ) + 1);
44+ std::println("PROBE_PATH={}", p ? p : "");
4245 return 1;
4346}'
4447
45- # Returns "<first>|<entries>" , or nothing if the program did not run.
48+ # Echoes the child's PATH , or nothing if the build program did not run.
4649run_probe () {
47- local dir=" $1 "
48- local out
50+ local dir=" $1 " out
4951 out=" $( cd " $dir " && " $MCPP " build 2>&1 || true) "
50- local f e
51- f=" $( printf ' %s\n' " $out " | grep -oP ' PROBE_FIRST=\K.*' | head -1) "
52- e=" $( printf ' %s\n' " $out " | grep -oP ' PROBE_ENTRIES=\K[0-9]+' | head -1) "
53- [ -n " $f " ] && printf ' %s|%s\n' " $f " " $e "
52+ printf ' %s\n' " $out " | grep -oP ' PROBE_PATH=\K.*' | head -1
5453}
5554
5655make_project () {
@@ -62,23 +61,26 @@ make_project() {
6261 printf ' %s\n' " $probe " > " $dir /build.mcpp"
6362}
6463
64+ # The value every assertion below is relative to. mcpp inherits this shell's
65+ # PATH, so this is exactly what an unchanged child would report.
66+ inherited=" $PATH "
67+
6568# ── Half one: a project that declared nothing ─────────────────────────────
6669make_project " $work /plain" " "
6770plain=" $( run_probe " $work /plain" ) "
6871if [ -z " $plain " ]; then
6972 echo " SKIP: the build program did not report — it may not have run here"
7073 exit 0
7174fi
72- plain_first=" ${plain%% |* } "
7375
74- case " $plain_first " in
75- * /subos/ * /bin)
76- echo " FAIL: a project that declared no environment got one in front anyway "
77- echo " got: $plain_first "
78- exit 1 ;;
79- * )
80- echo " ok a project that declares nothing keeps the PATH it was given " ;;
81- esac
76+ if [ " $plain " = " $inherited " ] ; then
77+ echo " ok a project that declares nothing gets the PATH mcpp was started with "
78+ else
79+ echo " FAIL: a project that declared no environment had its PATH changed "
80+ diff <( printf ' %s\n ' " $inherited " | tr ' : ' ' \n ' ) \
81+ <( printf ' %s\n ' " $plain " | tr ' : ' ' \n ' ) | head -6 | sed ' s/^/ / '
82+ exit 1
83+ fi
8284
8385# ── Half two: the same project, declaring one ─────────────────────────────
8486#
@@ -93,25 +95,31 @@ if [ -z "$declared" ]; then
9395 echo " SKIP: the declaring project's build program did not report"
9496 exit 0
9597fi
96- declared_first=" ${declared%% |* } "
97- declared_entries=" ${declared##* |} "
9898
99- case " $declared_first " in
100- * /subos/* /bin)
101- echo " ok a project that declares one gets it first: $declared_first " ;;
102- * )
103- echo " FAIL: the declared environment is not at the front of PATH"
104- echo " got: $declared_first "
105- exit 1 ;;
99+ if [ " $declared " = " $inherited " ]; then
100+ echo " FAIL: the declaration changed nothing — the environment is not in front"
101+ echo " PATH: $( printf ' %s' " $declared " | cut -c1-100) …"
102+ exit 1
103+ fi
104+
105+ first=" ${declared%%:* } "
106+ case " $first " in
107+ * /subos/* /bin) echo " ok the declared environment is first: $first " ;;
108+ * ) echo " FAIL: something other than a subos was prepended"
109+ echo " got: $first "
110+ exit 1 ;;
106111esac
107112
108- # ⭐ AND THE HOST IS STILL BEHIND IT. One entry means the inherited PATH was
109- # replaced rather than extended, which would break every build program that
110- # calls `git`, `python3` or a shell.
111- if [ " ${declared_entries:- 1} " -gt 1 ]; then
112- echo " ok the inherited PATH survives behind it ($declared_entries entries)"
113+ # ⭐ AND THE INHERITED VALUE IS STILL THERE, WHOLE, BEHIND IT. Prefixing means
114+ # the rest is untouched; a test that only checked the first entry would pass on
115+ # a PATH that had thrown everything else away, which would break every build
116+ # program that calls `git`, `python3` or a shell.
117+ if [ " ${declared#*: } " = " $inherited " ]; then
118+ echo " ok and the inherited PATH follows it, unchanged"
113119else
114- echo " FAIL: PATH was replaced, not prefixed — only $declared_entries entry"
120+ echo " FAIL: the inherited PATH was not preserved behind the prefix"
121+ diff <( printf ' %s\n' " $inherited " | tr ' :' ' \n' ) \
122+ <( printf ' %s\n' " ${declared#*: } " | tr ' :' ' \n' ) | head -6 | sed ' s/^/ /'
115123 exit 1
116124fi
117125
0 commit comments