3737
3838import std;
3939import mcpp.toolchain.cenv_probe;
40+ import mcpp.platform;
4041
4142namespace cp = mcpp::toolchain::cenv_probe;
4243
@@ -64,6 +65,77 @@ std::filesystem::path cxx() {
6465 return p;
6566}
6667
68+ // A CLANG, FOR THE TWO TESTS THAT NEED ONE, AND FOUND ON PATH RATHER THAN AT A
69+ // FIXED LOCATION.
70+ //
71+ // `find_a_cxx_compiler` answers `/usr/bin/c++` first, which is GCC on every
72+ // host this suite runs on --- and GCC takes no `--target`. The two tests below
73+ // therefore skipped on EVERY host, including the CI shards that have an LLVM
74+ // toolchain, which is the shape of coverage that looks like coverage and is
75+ // not. Searching PATH makes them run wherever a clang is reachable; where none
76+ // is, they skip and say so, and `CenvProbeArgv` still pins the invariant with
77+ // no compiler at all.
78+ // Whether this compiler both takes `--target` and has the back end the two
79+ // tests below name. A clang that answers for neither is not a smaller version
80+ // of one that does; it is a different tool for this purpose.
81+ bool answers_for_a_cross_target (const std::filesystem::path& bin) {
82+ auto r = mcpp::platform::process::capture_stdout (
83+ {bin.string (), " --target=riscv64-none-elf" , " -x" , " c++" , " -E" , " -dM" , " -" });
84+ return r.exit_code == 0
85+ && r.output .find (" #define __riscv " ) != std::string::npos;
86+ }
87+
88+ std::filesystem::path find_a_clang () {
89+ if (const char * env = std::getenv (" MCPP_TEST_CLANGXX" ); env && *env)
90+ return env;
91+ const char * path = std::getenv (" PATH" );
92+ if (!path) return {};
93+ std::string_view rest{path};
94+ #ifdef _WIN32
95+ constexpr char kSep = ' ;' ;
96+ constexpr std::string_view kName = " clang++.exe" ;
97+ #else
98+ constexpr char kSep = ' :' ;
99+ constexpr std::string_view kName = " clang++" ;
100+ #endif
101+ while (!rest.empty ()) {
102+ auto at = rest.find (kSep );
103+ auto dir = rest.substr (0 , at);
104+ rest = (at == std::string_view::npos) ? std::string_view{}
105+ : rest.substr (at + 1 );
106+ if (dir.empty ()) continue ;
107+ std::error_code ec;
108+ auto candidate = std::filesystem::path (dir) / kName ;
109+ if (std::filesystem::exists (candidate, ec) && answers_for_a_cross_target (candidate))
110+ return candidate;
111+ }
112+ // AND THE PAYLOAD mcpp ITSELF INSTALLS. A clang on PATH is not
113+ // necessarily one built with the back end this test names --- the host
114+ // this was written on carries a vendor clang with neither RISC-V nor
115+ // AArch64, so a PATH-only search skipped for a reason that has nothing to
116+ // do with what is being tested. mcpp's own LLVM payload has them, and
117+ // every CI shard that resolves an `llvm@` toolchain has downloaded it.
118+ std::error_code ec;
119+ auto home = std::getenv (" MCPP_HOME" )
120+ ? std::filesystem::path (std::getenv (" MCPP_HOME" ))
121+ : std::filesystem::path (
122+ std::getenv (" HOME" ) ? std::getenv (" HOME" ) : " ." ) / " .mcpp" ;
123+ auto payloads = home / " registry" / " data" / " xpkgs" / " xim-x-llvm" ;
124+ if (std::filesystem::is_directory (payloads, ec))
125+ for (auto const & ver : std::filesystem::directory_iterator (payloads, ec)) {
126+ auto candidate = ver.path () / " bin" / kName ;
127+ if (std::filesystem::exists (candidate, ec)
128+ && answers_for_a_cross_target (candidate))
129+ return candidate;
130+ }
131+ return {};
132+ }
133+
134+ std::filesystem::path clangxx () {
135+ static const std::filesystem::path p = find_a_clang ();
136+ return p;
137+ }
138+
67139// The host's own word size, measured the same way `cenv::realise`'s callers
68140// would declare it — this test's "declared" values are deliberately chosen
69141// relative to the REAL host, so a correct declaration is one that could
@@ -295,17 +367,12 @@ namespace {
295367// Whether this compiler answers for a target it is not hosted on. A GCC
296368// driver does not take `--target`, and a clang built without the RISC-V
297369// backend answers nothing useful; both skip.
298- bool answers_for_riscv () {
299- TmpCache probe;
300- auto r = cp::verify (cxx (), {" --target=riscv64-none-elf" }, 0 , 0 ,
301- {" __riscv" }, {}, probe.dir );
302- return r.has_value () && r->mismatches .empty ();
303- }
370+ bool answers_for_riscv () { return !clangxx ().empty (); }
304371} // namespace
305372
306373TEST (CenvProbe, AnArgvWithATargetMeasuresThatTargetAndNotTheHost) {
307- if (cxx ().empty ()) GTEST_SKIP () << " no C ++ compiler found to probe " ;
308- if (!answers_for_riscv ()) GTEST_SKIP () << " this compiler does not answer for riscv64-none-elf" ;
374+ if (clangxx ().empty ()) GTEST_SKIP () << " no clang ++ on PATH " ;
375+ if (!answers_for_riscv ()) GTEST_SKIP () << " this clang does not answer for riscv64-none-elf" ;
309376 TmpCache cache;
310377 // The freestanding probe's own shape, with the target selection the
311378 // caller (`mcpp.build.prepare`) now supplies. `__linux__` must be absent
@@ -317,23 +384,23 @@ TEST(CenvProbe, AnArgvWithATargetMeasuresThatTargetAndNotTheHost) {
317384 };
318385 std::vector<std::string> defined {" __unix__" , " __riscv" };
319386 std::vector<std::string> undefined{" __linux__" , " _WIN32" };
320- auto r = cp::verify (cxx (), argv, 32 , 0 , defined , undefined, cache.dir );
387+ auto r = cp::verify (clangxx (), argv, 32 , 0 , defined , undefined, cache.dir );
321388 ASSERT_TRUE (r.has_value ()) << r.error ();
322389 for (auto const & mm : r->mismatches )
323390 ADD_FAILURE () << mm.fact << " : declared " << mm.declared
324391 << " , measured " << mm.measured ;
325392}
326393
327394TEST (CenvProbe, TheSameArgvWithoutATargetMeasuresTheHost) {
328- if (cxx ().empty ()) GTEST_SKIP () << " no C ++ compiler found to probe " ;
329- if (!answers_for_riscv ()) GTEST_SKIP () << " this compiler does not answer for riscv64-none-elf" ;
395+ if (clangxx ().empty ()) GTEST_SKIP () << " no clang ++ on PATH " ;
396+ if (!answers_for_riscv ()) GTEST_SKIP () << " this clang does not answer for riscv64-none-elf" ;
330397 TmpCache cache;
331398 // The mirror of the test above, and the one that makes it mean
332399 // something: with the target removed, the expectation that held for the
333400 // target must now FAIL. A test that only asserts the fixed shape passes
334401 // identically against a probe that ignores its argv.
335402 std::vector<std::string> argv{" -ffreestanding" , " -D__unix__" , " -fno-short-wchar" };
336- auto r = cp::verify (cxx (), argv, 0 , 0 , {" __riscv" }, {}, cache.dir );
403+ auto r = cp::verify (clangxx (), argv, 0 , 0 , {" __riscv" }, {}, cache.dir );
337404 ASSERT_TRUE (r.has_value ()) << r.error ();
338405 EXPECT_FALSE (r->mismatches .empty ())
339406 << " a probe with no target selection must not report the target's "
0 commit comments