Skip to content

Commit c9b806b

Browse files
quaesitor-scientiamPythonWillRulewozcode
authored
cmd/v: export the build facts and defines to the test runner again (#28801)
`v test <dir>` evaluates every `// vtest build:` expression through `cmd/tools/testing`, which reads its facts from the `VBUILD_FACTS` and `VBUILD_DEFINES` environment variables. Nothing has set them since 2a7447b removed the launcher's `setup_vbuild_env_vars`, so the runner saw an empty fact set: `amd64`, `windows`, `tinyc`, `gcc` were all false and `!windows`, `!tinyc` always true, on every host (`v test vlib/context` skipped its four `amd64 || arm64` tests). The compiler's own evaluation for `v file_test.v` (`v3_test_build_facts`) was unaffected, so the two disagreed. Factor the C compiler selection and the libc define out of `driver.run` into `v3_select_c_compiler` / `v3_apply_libc_define`, build the facts and defines of a set of compiler options on top of them in `driver.vtest_build_environment`, and have the launcher export the result with `pref.set_build_flags_and_defines` before it starts a tool that runs a test session (`test`, `test-self`, `test-cleancode`, `test-fmt`, `build-examples`, `build-tools`, `build-vbinaries`). `cmd/tools/vtest_build_facts_test.v` runs `v test` on fixtures constrained by the host OS and arch, their negations and an optional define, and checks which ones executed; it fails on the previous launcher. `vlib/v/driver/vtest_build_environment_test.v` pins the resolver. Fixes #28798 Co-authored-by: Richard Wheeler <18647491+PythonWillRule@users.noreply.github.com> Co-authored-by: WOZCODE <contact@withwoz.com>
1 parent 63f05ad commit c9b806b

6 files changed

Lines changed: 431 additions & 26 deletions

File tree

‎cmd/tools/vtest_build_facts_test.v‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import os
2+
import v.cmdexec
3+
import v.pref
4+
5+
// `v test` evaluates `// vtest build:` in cmd/tools/testing against the facts the
6+
// launcher exports, while `v file_test.v` derives them in the compiler. Both must
7+
// see the same host, compiler and defines; these fixtures pin the runner's side.
8+
9+
const facts_root = os.join_path(os.vtmp_dir(), 'vtest.build_facts.${os.getpid()}')
10+
const facts_original_cwd = os.getwd()
11+
12+
fn write_facts_fixture(name string, constraint string, passing bool) {
13+
body := if passing {
14+
"import os\nfn test_probe() { os.write_file(@FILE + '.executed', 'passed')!; assert true }\n"
15+
} else {
16+
"fn test_probe() { assert false, 'a skipped fixture must not run' }\n"
17+
}
18+
os.write_file(os.join_path(facts_root, name), '// vtest build: ${constraint}\n\n' + body) or {
19+
panic(err)
20+
}
21+
}
22+
23+
fn testsuite_begin() {
24+
os.rmdir_all(facts_root) or {}
25+
os.mkdir_all(facts_root)!
26+
// The nested `v test` must not rebuild the tool over the runner executing this
27+
// test; the cached tool binary is keyed by the prefix options and lives elsewhere.
28+
for name in ['VTEST_ONLY', 'VTEST_ONLY_FN', 'VTEST_RUNNER', 'VBUILD_FACTS', 'VBUILD_DEFINES',
29+
'VTOOLS_NO_CACHE'] {
30+
os.unsetenv(name)
31+
}
32+
os.setenv('VEXE', @VEXE, true)
33+
os.setenv('VJOBS', '1', true)
34+
os.setenv('VCOLORS', 'never', true)
35+
os.setenv('VTEST_HIDE_OK', '0', true)
36+
os.setenv('VTEST_HIDE_SKIP', '0', true)
37+
// One attempt: the runner compiles `VTEST_MAX_COMPILATION_RETRIES` times, so 0 would skip compiling.
38+
os.setenv('VTEST_MAX_COMPILATION_RETRIES', '1', true)
39+
os.setenv('V_C_ERROR_BUG_REPORT_DISABLED', '1', true)
40+
os.setenv('V_MACOS_V3_NO_FALLBACK', '1', true)
41+
host := pref.host_target()
42+
write_facts_fixture('host_os_test.v', host.os, true)
43+
write_facts_fixture('not_host_os_test.v', '!${host.os}', false)
44+
write_facts_fixture('host_arch_test.v', host.arch, true)
45+
write_facts_fixture('not_host_arch_test.v', '!${host.arch}', false)
46+
write_facts_fixture('define_test.v', 'build_facts_probe?', true)
47+
write_facts_fixture('not_define_test.v', '!build_facts_probe?', true)
48+
os.chdir(facts_root)!
49+
}
50+
51+
fn testsuite_end() {
52+
os.chdir(facts_original_cwd) or { panic(err) }
53+
os.rmdir_all(facts_root) or {}
54+
}
55+
56+
fn run_facts_session(prefix_args []string) os.Result {
57+
for marker in os.walk_ext(facts_root, '.executed') {
58+
os.rm(marker) or { panic(err) }
59+
}
60+
mut args := prefix_args.clone()
61+
args << ['test', facts_root]
62+
return cmdexec.run_with_timeout(@VEXE, args, 300_000)
63+
}
64+
65+
fn assert_facts_summary(result os.Result, expected string, executed []string) {
66+
assert result.exit_code == 0, result.output
67+
summaries := result.output.split_into_lines().map(it.trim_space()).filter(it.starts_with('Summary for all V _test.v files:'))
68+
assert summaries.len == 1, result.output
69+
assert summaries[0].starts_with('Summary for all V _test.v files: ${expected}.'), result.output
70+
mut markers := os.walk_ext(facts_root, '.executed').map(os.file_name(it).all_before('.executed'))
71+
markers.sort()
72+
assert markers == executed, result.output
73+
}
74+
75+
fn test_the_runner_sees_the_host_facts() {
76+
result := run_facts_session([])
77+
assert_facts_summary(result, '3 passed, 3 skipped, 6 total', ['host_arch_test.v', 'host_os_test.v',
78+
'not_define_test.v'])
79+
}
80+
81+
fn test_the_runner_sees_the_user_defines() {
82+
result := run_facts_session(['-d', 'build_facts_probe'])
83+
assert_facts_summary(result, '3 passed, 3 skipped, 6 total', ['define_test.v', 'host_arch_test.v',
84+
'host_os_test.v'])
85+
}

‎cmd/v/v.v‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,9 +320,27 @@ fn run_external_tool(args []string, command_index int, command string) {
320320
if command_index >= 0 {
321321
tool_args = external_tool_runtime_args(command, prefix_args, args[command_index..])
322322
}
323+
if command in test_session_commands {
324+
export_vtest_build_environment(vroot, prefix_args)
325+
}
323326
launch_external_tool(vroot, tool_name, tool_source, prefix_args, tool_args)
324327
}
325328

329+
// test_session_commands start a `cmd/tools/` program that evaluates the
330+
// `// vtest build:` constraints of the files it compiles, through
331+
// `cmd/tools/testing`, against the `VBUILD_FACTS`/`VBUILD_DEFINES` environment.
332+
const test_session_commands = ['test', 'test-self', 'test-cleancode', 'test-fmt', 'build-examples',
333+
'build-tools', 'build-vbinaries']
334+
335+
// export_vtest_build_environment records the facts and defines of the
336+
// compilation that the prefix compiler options describe, as the compiler itself
337+
// resolves them, so that the test runner skips and builds the same files that
338+
// `v file_test.v` would.
339+
fn export_vtest_build_environment(vroot string, prefix_args []string) {
340+
environment := driver.vtest_build_environment(vroot, prefix_args)
341+
pref.set_build_flags_and_defines(environment.facts, environment.defines)
342+
}
343+
326344
fn external_tool_runtime_args(command string, prefix_args []string, command_args []string) []string {
327345
mut tool_args := []string{}
328346
// `v build-tools` consumes compiler options itself and applies them to every

‎vlib/v/driver/driver.v‎

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7324,6 +7324,73 @@ fn v3_select_implicit_c_compiler(c_compiler string, c_compiler_explicit bool, im
73247324
return c_compiler
73257325
}
73267326

7327+
// V3CCompilerSelection is the C compiler that receives a compilation's first
7328+
// build attempt, and the name (`tinyc`, `gcc`, `clang`, ...) the generated code
7329+
// and the `// vtest build:` facts describe it by.
7330+
struct V3CCompilerSelection {
7331+
bundled_tcc_available bool
7332+
implicit_tcc string
7333+
c_compiler string
7334+
use_implicit_tcc_semantics bool
7335+
effective_c_compiler string
7336+
}
7337+
7338+
// v3_select_c_compiler normalises the requested C compiler (a Windows target
7339+
// built on another host uses the MinGW GCC instead of TCC; `msvc` on Windows is
7340+
// `cl`) and resolves it against the bundled and the system TCC: an implicit TCC
7341+
// takes over from the platform default when the compilation allows it, and an
7342+
// explicit `-cc` is kept as given.
7343+
fn v3_select_c_compiler(vroot string, requested V3BundledTccProbeOptions) V3CCompilerSelection {
7344+
options := V3BundledTccProbeOptions{
7345+
...requested
7346+
c_compiler: v3_c_compiler_command_alias(v3_windows_cross_c_compiler(requested.c_compiler,
7347+
requested.host_target, requested.target), requested.host_os)
7348+
}
7349+
bundled_tcc_available := v3_bundled_tcc_available(options)
7350+
allow_system_tcc := options.backend == 'c' && !options.c_only && !options.is_prod
7351+
&& !options.is_c_debug && !options.c_compiler_explicit
7352+
&& (!options.parallel_cc || options.target.os == 'windows')
7353+
&& options.target.os == options.host_target.os
7354+
&& options.target.arch == options.host_target.arch
7355+
&& v3_system_tcc_runtime_available(vroot, options.target.os)
7356+
implicit_tcc := v3_default_tcc_compiler(options.bundled_tcc, bundled_tcc_available,
7357+
allow_system_tcc, options.dump_c_flags, options.host_os)
7358+
c_compiler := v3_select_implicit_c_compiler(options.c_compiler, options.c_compiler_explicit,
7359+
implicit_tcc)
7360+
// Generate for the compiler that receives the first build attempt. If implicit
7361+
// TCC cannot be used, the caller regenerates before invoking the `cc` fallback.
7362+
use_implicit_tcc_semantics := options.backend == 'c' && !options.c_compiler_explicit
7363+
&& implicit_tcc != ''
7364+
return V3CCompilerSelection{
7365+
bundled_tcc_available: bundled_tcc_available
7366+
implicit_tcc: implicit_tcc
7367+
c_compiler: c_compiler
7368+
use_implicit_tcc_semantics: use_implicit_tcc_semantics
7369+
effective_c_compiler: v3_effective_c_compiler_for_codegen(options.backend,
7370+
c_compiler, use_implicit_tcc_semantics, options.target)
7371+
}
7372+
}
7373+
7374+
// v3_apply_libc_define records the C library a compilation targets as its
7375+
// `musl`/`glibc` define: the requested one (`-musl`, `-glibc`, a `*musl-gcc`
7376+
// compiler), or, when `infer_host` allows it, the host's own. It returns the
7377+
// libc mode the compilation should use.
7378+
fn v3_apply_libc_define(mut user_defines []string, mut compile_values map[string]string, requested_libc_mode string, c_compiler string, infer_host bool) string {
7379+
mut libc_mode := requested_libc_mode
7380+
if v3_c_compiler_implies_musl(c_compiler) {
7381+
libc_mode = 'musl'
7382+
}
7383+
if libc_mode != '' {
7384+
v3_set_libc_define(mut user_defines, mut compile_values, libc_mode)
7385+
} else if !v3_has_libc_define(user_defines) && infer_host {
7386+
host_libc := v3_detect_host_libc()
7387+
if host_libc != '' {
7388+
v3_set_libc_define(mut user_defines, mut compile_values, host_libc)
7389+
}
7390+
}
7391+
return libc_mode
7392+
}
7393+
73277394
fn v3_platform_c_compiler(host_os string) string {
73287395
return if host_os == 'windows' { 'gcc' } else { 'cc' }
73297396
}
@@ -10060,9 +10127,7 @@ pub fn run(args []string) {
1006010127
bundled_tcc := os.join_path(prefs.vroot, 'thirdparty', 'tcc', 'tcc.exe')
1006110128
host_os := os.user_os()
1006210129
host_target := pref.host_target()
10063-
c_compiler = v3_windows_cross_c_compiler(c_compiler, host_target, target)
10064-
c_compiler = v3_c_compiler_command_alias(c_compiler, host_os)
10065-
bundled_tcc_available := v3_bundled_tcc_available(V3BundledTccProbeOptions{
10130+
selection := v3_select_c_compiler(prefs.vroot, V3BundledTccProbeOptions{
1006610131
backend: backend
1006710132
c_only: c_only
1006810133
is_prod: is_prod
@@ -10076,31 +10141,16 @@ pub fn run(args []string) {
1007610141
target: target
1007710142
bundled_tcc: bundled_tcc
1007810143
})
10079-
allow_system_tcc := backend == 'c' && !c_only && !is_prod && !is_c_debug && !c_compiler_explicit
10080-
&& (!parallel_cc || target.os == 'windows') && target.os == host_target.os
10081-
&& target.arch == host_target.arch
10082-
&& v3_system_tcc_runtime_available(prefs.vroot, target.os)
10083-
implicit_tcc := v3_default_tcc_compiler(bundled_tcc, bundled_tcc_available, allow_system_tcc, dump_c_flags.len > 0, host_os)
10084-
c_compiler = v3_select_implicit_c_compiler(c_compiler, c_compiler_explicit, implicit_tcc)
10085-
// Generate for the compiler that receives the first build attempt. If implicit
10086-
// TCC cannot be used, regenerate below before invoking the `cc` fallback.
10087-
use_implicit_tcc_semantics := backend == 'c' && !c_compiler_explicit && implicit_tcc != ''
10088-
effective_c_compiler := v3_effective_c_compiler_for_codegen(backend, c_compiler, use_implicit_tcc_semantics, target)
10144+
bundled_tcc_available := selection.bundled_tcc_available
10145+
implicit_tcc := selection.implicit_tcc
10146+
c_compiler = selection.c_compiler
10147+
use_implicit_tcc_semantics := selection.use_implicit_tcc_semantics
10148+
effective_c_compiler := selection.effective_c_compiler
1008910149
macos_linux_cross_compile := v3_macos_linux_cross_compile(host_target, target, backend,
1009010150
c_compiler)
10091-
if v3_c_compiler_implies_musl(c_compiler) {
10092-
libc_mode = 'musl'
10093-
}
10094-
if libc_mode != '' {
10095-
v3_set_libc_define(mut user_defines, mut compile_values, libc_mode)
10096-
} else if !v3_has_libc_define(user_defines)
10097-
&& v3_should_infer_host_libc(c_only, is_o, generate_c_project, output_cross_c, target,
10098-
host_target) {
10099-
host_libc := v3_detect_host_libc()
10100-
if host_libc != '' {
10101-
v3_set_libc_define(mut user_defines, mut compile_values, host_libc)
10102-
}
10103-
}
10151+
libc_mode = v3_apply_libc_define(mut user_defines, mut compile_values, libc_mode, c_compiler,
10152+
v3_should_infer_host_libc(c_only, is_o, generate_c_project, output_cross_c, target,
10153+
host_target))
1010410154
incompatible_direct_test := v3_direct_test_input_is_incompatible(is_test_command, input_file, backend, target, effective_c_compiler, is_prod, user_defines)
1010510155
if incompatible_direct_test {
1010610156
// Directory test discovery already excludes incompatible backend/platform files.
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
module driver
2+
3+
import os
4+
import v.pref
5+
6+
// TestBuildEnvironment is what a `// vtest build:` expression is evaluated
7+
// against: the facts of a compilation (its OS, architecture, C compiler kind,
8+
// `prod`, the CI job) and the defines it sets.
9+
pub struct TestBuildEnvironment {
10+
pub:
11+
facts []string
12+
defines []string
13+
}
14+
15+
// vtest_build_environment resolves the facts and defines of the compilation
16+
// that the compiler options `args` describe (`-os`, `-arch`, `-cc`, `-prod`,
17+
// `-d`, ...), the way `run` resolves them for `v <args> file_test.v`. A test
18+
// runner started with those options exports the result through
19+
// pref.set_build_flags_and_defines, so that its `// vtest build:` decisions
20+
// match the compiler's own.
21+
pub fn vtest_build_environment(vroot string, args []string) TestBuildEnvironment {
22+
mut backend := 'c'
23+
mut target_os := os.user_os()
24+
mut target_arch := pref.host_arch()
25+
mut cross_output := false
26+
mut c_compiler := 'cc'
27+
mut c_compiler_explicit := false
28+
mut is_prod := false
29+
mut is_c_debug := false
30+
mut parallel_cc := false
31+
mut dump_c_flags := false
32+
mut libc_mode := ''
33+
mut user_defines := []string{}
34+
mut compile_values := map[string]string{}
35+
mut i := 0
36+
for i < args.len {
37+
arg := args[i]
38+
has_value := i + 1 < args.len
39+
if arg in ['-b', '-backend'] && has_value {
40+
backend = if args[i + 1] in ['js_browser', 'js_node'] { 'js' } else { args[i + 1] }
41+
i += 2
42+
} else if arg == '-os' && has_value {
43+
target_os = args[i + 1]
44+
i += 2
45+
} else if arg == '-cross' {
46+
cross_output = true
47+
i++
48+
} else if arg == '-arch' && has_value {
49+
target_arch = args[i + 1]
50+
i += 2
51+
} else if arg == '-cc' && has_value {
52+
c_compiler = args[i + 1]
53+
c_compiler_explicit = true
54+
i += 2
55+
} else if arg == '-prod' {
56+
is_prod = true
57+
i++
58+
} else if arg in ['-cg', '-cdebug'] {
59+
is_c_debug = true
60+
i++
61+
} else if arg == '-parallel-cc' {
62+
parallel_cc = true
63+
i++
64+
} else if arg == '-musl' {
65+
libc_mode = 'musl'
66+
i++
67+
} else if arg == '-glibc' {
68+
libc_mode = 'glibc'
69+
i++
70+
} else if arg in ['-d', '-define'] && has_value {
71+
record_user_define(mut user_defines, mut compile_values, args[i + 1])
72+
i += 2
73+
} else if arg == '-dump-c-flags' {
74+
// Its value is optional, so it must not be read as a `-d` shorthand.
75+
dump_c_flags = true
76+
i += if has_value { 2 } else { 1 }
77+
} else if arg.starts_with('-d') && arg.len > 2 && !v3_driver_option_consumes_value(arg) {
78+
record_user_define(mut user_defines, mut compile_values, arg[2..])
79+
i++
80+
} else if v3_driver_option_consumes_value(arg) && has_value {
81+
i += 2
82+
} else {
83+
i++
84+
}
85+
}
86+
if pref.normalized_os(target_os.trim_space().to_lower()) == 'cross' {
87+
cross_output = true
88+
target_os = os.user_os()
89+
}
90+
target := pref.target_from(target_os, target_arch) or { pref.host_target() }
91+
host_target := pref.host_target()
92+
selection := v3_select_c_compiler(vroot, V3BundledTccProbeOptions{
93+
backend: backend
94+
is_prod: is_prod
95+
is_c_debug: is_c_debug
96+
c_compiler: c_compiler
97+
c_compiler_explicit: c_compiler_explicit
98+
dump_c_flags: dump_c_flags
99+
parallel_cc: parallel_cc
100+
host_os: os.user_os()
101+
host_target: host_target
102+
target: target
103+
bundled_tcc: os.join_path(vroot, 'thirdparty', 'tcc', 'tcc.exe')
104+
})
105+
v3_apply_libc_define(mut user_defines, mut compile_values, libc_mode, selection.c_compiler,
106+
v3_should_infer_host_libc(false, false, '', cross_output, target, host_target))
107+
mut defines := []string{}
108+
for define in os.getenv('VBUILD_DEFINES').split_any(',') {
109+
name := define.trim_space()
110+
if name.len > 0 && name !in defines {
111+
defines << name
112+
}
113+
}
114+
for define in user_defines {
115+
name := define.all_before('=').trim_space()
116+
if name.len > 0 && name !in defines {
117+
defines << name
118+
}
119+
}
120+
return TestBuildEnvironment{
121+
facts: v3_test_build_facts(target, selection.effective_c_compiler, is_prod)
122+
defines: defines
123+
}
124+
}

0 commit comments

Comments
 (0)