v / cmd / v2 / v2.v
310 lines · 271 sloc · 8.1 KB · 10a27a35f62541cee8c09d75d37629f27d89d055
Raw
1// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module main
5
6import os
7import time
8import v2.pref
9import v2.builder
10
11fn main() {
12 compile_args, runtime_args := split_eval_runtime_args(os.args[1..])
13
14 // Check for subcommands
15 if compile_args.len > 0 && compile_args[0] == 'ast' {
16 run_ast_command(compile_args[1..])
17 return
18 }
19 if compile_args.len > 0 && compile_args[0] == 'test-self' {
20 run_test_self()
21 return
22 }
23
24 mut prefs := pref.new_preferences_from_args(compile_args)
25
26 files := get_files(compile_args)
27 if files.len == 0 {
28 eprintln('At least 1 .v file expected')
29 exit(1)
30 }
31 mut eval_runtime_args := [files[0]]
32 eval_runtime_args << runtime_args
33 prefs.eval_runtime_args = eval_runtime_args
34
35 mut b := builder.new_builder(&prefs)
36 b.build(files)
37
38 // Auto-run test binaries after compilation (matching v1 behavior)
39 if prefs.output_file == '' && is_test_file(files) && should_auto_run_test_binary(&prefs) {
40 last := os.file_name(files.last())
41 output_name := if last.ends_with('.vv2') {
42 last.all_before_last('.vv2')
43 } else {
44 last.all_before_last('.v')
45 }
46 if os.exists(output_name) {
47 ret := os.system('./' + output_name)
48 os.rm(output_name) or {}
49 c_file := output_name + '.c'
50 if !prefs.keep_c && os.exists(c_file) {
51 os.rm(c_file) or {}
52 }
53 exit(ret)
54 }
55 }
56}
57
58fn split_eval_runtime_args(args []string) ([]string, []string) {
59 for i, arg in args {
60 if arg == '--' {
61 return args[..i], args[i + 1..]
62 }
63 }
64 return args, []string{}
65}
66
67fn run_ast_command(args []string) {
68 if args.len == 0 {
69 eprintln('Usage: v2 ast <file.v>')
70 eprintln('Dumps AST to <file>_ast.json and <file>_ast_transformed.json')
71 exit(1)
72 }
73
74 // Find the vast2 tool relative to vexe
75 vroot := os.dir(@VEXE)
76 vast2_path := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2')
77
78 // Build vast2 if it doesn't exist
79 if !os.exists(vast2_path) {
80 eprintln('Building vast2 tool...')
81 vast2_source := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2.v')
82 build_result := os.execute('${@VEXE} ${vast2_source}')
83 if build_result.exit_code != 0 {
84 eprintln('Failed to build vast2 tool:')
85 eprintln(build_result.output)
86 exit(1)
87 }
88 }
89
90 // Run vast2 with the provided arguments
91 cmd := '${vast2_path} ${args.join(' ')}'
92 result := os.execute(cmd)
93 print(result.output)
94 if result.exit_code != 0 {
95 exit(result.exit_code)
96 }
97}
98
99fn is_test_file(files []string) bool {
100 for file in files {
101 if file.ends_with('_test.v') || file.ends_with('_test.vv2') {
102 return true
103 }
104 }
105 return false
106}
107
108fn should_auto_run_test_binary(prefs &pref.Preferences) bool {
109 if prefs == unsafe { nil } {
110 return true
111 }
112 if !prefs.can_run_target_binary_locally() {
113 return false
114 }
115 return true
116}
117
118// get_files extracts source files from args, excluding options and their values
119fn get_files(args []string) []string {
120 return pref.source_files_from_args(args)
121}
122
123fn resolve_own_path() string {
124 arg0 := os.args[0]
125 if os.is_abs_path(arg0) {
126 return arg0
127 }
128 return os.join_path(os.getwd(), arg0)
129}
130
131// detect_vroot walks up from `start` looking for a directory containing vlib/builtin.
132fn detect_vroot(start string) string {
133 mut dir := start
134 if !os.is_abs_path(dir) {
135 dir = os.join_path(os.getwd(), dir)
136 }
137 for _ in 0 .. 10 {
138 if os.is_dir(os.join_path(dir, 'vlib', 'builtin')) {
139 return dir
140 }
141 parent := os.dir(dir)
142 if parent == dir {
143 break
144 }
145 dir = parent
146 }
147 return dir
148}
149
150fn run_test_self() {
151 t0 := time.now()
152 // Resolve the v2 binary's own path. Cannot use @VEXE because when v1
153 // compiles v2, @VEXE bakes in v1's path instead of v2's.
154 vexe := resolve_own_path()
155 // Walk up from the binary to find the repo root (directory containing vlib/builtin).
156 // This allows running from subdirectories like cmd/v2/.
157 vroot := detect_vroot(vexe)
158 v2_dir := os.join_path(vroot, 'cmd', 'v2')
159
160 mut all_test_files := []string{}
161
162 // Builtin test files
163 for name in ['array_test.v', 'string_test.v', 'map_test.v'] {
164 all_test_files << os.join_path(vroot, 'vlib', 'builtin', name)
165 }
166
167 // Math test
168 all_test_files << os.join_path(vroot, 'vlib', 'math', 'math_test.v')
169
170 // Sumtype tests in cmd/v2/
171 v2_files := os.ls(v2_dir) or { []string{} }
172 for file in v2_files {
173 if file.starts_with('test_sumtype') && file.ends_with('.v') {
174 all_test_files << os.join_path(v2_dir, file)
175 }
176 }
177
178 // Cleanc regression tests
179 cleanc_tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
180 cleanc_files := os.ls(cleanc_tests_dir) or { []string{} }
181 for file in cleanc_files {
182 if file.ends_with('.v') && file != 'run_tests.v' {
183 all_test_files << os.join_path(cleanc_tests_dir, file)
184 }
185 }
186
187 total := all_test_files.len
188 mut passed := 0
189 mut failed := 0
190 mut failed_files := []string{}
191
192 eprintln('---- v2 test-self: ${total} test files ----')
193
194 for i, test_file in all_test_files {
195 short_name := test_file.replace(vroot + '/', '')
196 t1 := time.now()
197
198 // Determine output binary path
199 base := os.file_name(test_file).all_before_last('.v')
200 out_bin := os.join_path(os.temp_dir(), 'v2_test_self_${base}')
201
202 // Compile (use os.system to avoid pipe deadlocks with popen/GC)
203 compile_cmd := '${vexe} -o ${out_bin} "${test_file}" > /dev/null 2>&1'
204 compile_ret := os.system(compile_cmd)
205 compile_ms := f64(time.since(t1)) / f64(time.millisecond)
206
207 if compile_ret != 0 || !os.exists(out_bin) {
208 failed++
209 failed_files << short_name
210 eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms ${short_name}')
211 os.rm(out_bin) or {}
212 os.rm('${out_bin}.c') or {}
213 continue
214 }
215
216 // Run the compiled binary
217 t2 := time.now()
218 run_ret := os.system(out_bin + ' > /dev/null 2>&1')
219 run_ms := f64(time.since(t2)) / f64(time.millisecond)
220
221 if run_ret != 0 {
222 failed++
223 failed_files << short_name
224 eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
225 } else {
226 passed++
227 eprintln('OK [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
228 }
229
230 os.rm(out_bin) or {}
231 os.rm('${out_bin}.c') or {}
232 }
233
234 elapsed := time.since(t0)
235 eprintln('------------------------------------------------------------------------')
236 eprintln('${passed} passed, ${failed} failed, ${total} total (${elapsed})')
237 if failed_files.len > 0 {
238 eprintln('Failed:')
239 for f in failed_files {
240 eprintln(' ${f}')
241 }
242 }
243 // Always run the self-compilation chain, even when some tests fail.
244 // Self-compilation chain: v2 -> v3 -> v4 -> v5
245 eprintln('')
246 eprintln('---- v2 self-compilation chain ----')
247
248 v2_source := os.join_path(v2_dir, 'v2.v')
249 backend := 'cleanc'
250 tmpdir := os.temp_dir()
251 v3_bin := os.join_path(tmpdir, 'v2_self_v3')
252 v4_bin := os.join_path(tmpdir, 'v2_self_v4')
253 v5_bin := os.join_path(tmpdir, 'v2_self_v5')
254
255 steps := [
256 [vexe, v3_bin, 'v2 -> v3'],
257 [v3_bin, v4_bin, 'v3 -> v4'],
258 [v4_bin, v5_bin, 'v4 -> v5'],
259 ]
260
261 for step in steps {
262 compiler := step[0]
263 out := step[1]
264 label := step[2]
265 ts := time.now()
266 cmd := '${compiler} -gc none -o ${out} -backend ${backend} "${v2_source}" > /dev/null 2>&1'
267 ret := os.system(cmd)
268 ms := f64(time.since(ts)) / f64(time.millisecond)
269 if ret != 0 || !os.exists(out) {
270 eprintln(' FAIL ${label} (${ms:.1} ms)')
271 // Clean up
272 for bin in [v3_bin, v4_bin, v5_bin] {
273 os.rm(bin) or {}
274 os.rm('${bin}.c') or {}
275 }
276 exit(1)
277 }
278 eprintln('OK ${label} (${ms:.1} ms)')
279 }
280
281 // Verify that v3 runs and produces expected output
282 result := os.execute('${v3_bin} 2>&1')
283 expected := 'At least 1 .v file expected'
284 if !result.output.contains(expected) {
285 eprintln("FAIL v3 output check: expected '${expected}', got:")
286 eprintln(result.output)
287 for bin in [v3_bin, v4_bin, v5_bin] {
288 os.rm(bin) or {}
289 os.rm('${bin}.c') or {}
290 }
291 exit(1)
292 }
293 eprintln('OK v3 output verified')
294
295 // Clean up
296 for bin in [v3_bin, v4_bin, v5_bin] {
297 os.rm(bin) or {}
298 os.rm('${bin}.c') or {}
299 }
300
301 total_elapsed := time.since(t0)
302 eprintln('')
303 if failed > 0 {
304 eprintln('=== SELF-COMPILATION OK, but ${failed} test(s) failed ===')
305 eprintln('Total time: ${total_elapsed}')
306 exit(1)
307 }
308 eprintln('=== SELF-COMPILATION TEST PASSED ===')
309 eprintln('Total time: ${total_elapsed}')
310}
311