Files

311 lines
8.1 KiB
V

// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module main
import os
import time
import v2.pref
import v2.builder
fn main() {
compile_args, runtime_args := split_eval_runtime_args(os.args[1..])
// Check for subcommands
if compile_args.len > 0 && compile_args[0] == 'ast' {
run_ast_command(compile_args[1..])
return
}
if compile_args.len > 0 && compile_args[0] == 'test-self' {
run_test_self()
return
}
mut prefs := pref.new_preferences_from_args(compile_args)
files := get_files(compile_args)
if files.len == 0 {
eprintln('At least 1 .v file expected')
exit(1)
}
mut eval_runtime_args := [files[0]]
eval_runtime_args << runtime_args
prefs.eval_runtime_args = eval_runtime_args
mut b := builder.new_builder(&prefs)
b.build(files)
// Auto-run test binaries after compilation (matching v1 behavior)
if prefs.output_file == '' && is_test_file(files) && should_auto_run_test_binary(&prefs) {
last := os.file_name(files.last())
output_name := if last.ends_with('.vv2') {
last.all_before_last('.vv2')
} else {
last.all_before_last('.v')
}
if os.exists(output_name) {
ret := os.system('./' + output_name)
os.rm(output_name) or {}
c_file := output_name + '.c'
if !prefs.keep_c && os.exists(c_file) {
os.rm(c_file) or {}
}
exit(ret)
}
}
}
fn split_eval_runtime_args(args []string) ([]string, []string) {
for i, arg in args {
if arg == '--' {
return args[..i], args[i + 1..]
}
}
return args, []string{}
}
fn run_ast_command(args []string) {
if args.len == 0 {
eprintln('Usage: v2 ast <file.v>')
eprintln('Dumps AST to <file>_ast.json and <file>_ast_transformed.json')
exit(1)
}
// Find the vast2 tool relative to vexe
vroot := os.dir(@VEXE)
vast2_path := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2')
// Build vast2 if it doesn't exist
if !os.exists(vast2_path) {
eprintln('Building vast2 tool...')
vast2_source := os.join_path(vroot, 'cmd', 'tools', 'vast2', 'vast2.v')
build_result := os.execute('${@VEXE} ${vast2_source}')
if build_result.exit_code != 0 {
eprintln('Failed to build vast2 tool:')
eprintln(build_result.output)
exit(1)
}
}
// Run vast2 with the provided arguments
cmd := '${vast2_path} ${args.join(' ')}'
result := os.execute(cmd)
print(result.output)
if result.exit_code != 0 {
exit(result.exit_code)
}
}
fn is_test_file(files []string) bool {
for file in files {
if file.ends_with('_test.v') || file.ends_with('_test.vv2') {
return true
}
}
return false
}
fn should_auto_run_test_binary(prefs &pref.Preferences) bool {
if prefs == unsafe { nil } {
return true
}
if !prefs.can_run_target_binary_locally() {
return false
}
return true
}
// get_files extracts source files from args, excluding options and their values
fn get_files(args []string) []string {
return pref.source_files_from_args(args)
}
fn resolve_own_path() string {
arg0 := os.args[0]
if os.is_abs_path(arg0) {
return arg0
}
return os.join_path(os.getwd(), arg0)
}
// detect_vroot walks up from `start` looking for a directory containing vlib/builtin.
fn detect_vroot(start string) string {
mut dir := start
if !os.is_abs_path(dir) {
dir = os.join_path(os.getwd(), dir)
}
for _ in 0 .. 10 {
if os.is_dir(os.join_path(dir, 'vlib', 'builtin')) {
return dir
}
parent := os.dir(dir)
if parent == dir {
break
}
dir = parent
}
return dir
}
fn run_test_self() {
t0 := time.now()
// Resolve the v2 binary's own path. Cannot use @VEXE because when v1
// compiles v2, @VEXE bakes in v1's path instead of v2's.
vexe := resolve_own_path()
// Walk up from the binary to find the repo root (directory containing vlib/builtin).
// This allows running from subdirectories like cmd/v2/.
vroot := detect_vroot(vexe)
v2_dir := os.join_path(vroot, 'cmd', 'v2')
mut all_test_files := []string{}
// Builtin test files
for name in ['array_test.v', 'string_test.v', 'map_test.v'] {
all_test_files << os.join_path(vroot, 'vlib', 'builtin', name)
}
// Math test
all_test_files << os.join_path(vroot, 'vlib', 'math', 'math_test.v')
// Sumtype tests in cmd/v2/
v2_files := os.ls(v2_dir) or { []string{} }
for file in v2_files {
if file.starts_with('test_sumtype') && file.ends_with('.v') {
all_test_files << os.join_path(v2_dir, file)
}
}
// Cleanc regression tests
cleanc_tests_dir := os.join_path(vroot, 'vlib', 'v2', 'gen', 'cleanc', 'tests')
cleanc_files := os.ls(cleanc_tests_dir) or { []string{} }
for file in cleanc_files {
if file.ends_with('.v') && file != 'run_tests.v' {
all_test_files << os.join_path(cleanc_tests_dir, file)
}
}
total := all_test_files.len
mut passed := 0
mut failed := 0
mut failed_files := []string{}
eprintln('---- v2 test-self: ${total} test files ----')
for i, test_file in all_test_files {
short_name := test_file.replace(vroot + '/', '')
t1 := time.now()
// Determine output binary path
base := os.file_name(test_file).all_before_last('.v')
out_bin := os.join_path(os.temp_dir(), 'v2_test_self_${base}')
// Compile (use os.system to avoid pipe deadlocks with popen/GC)
compile_cmd := '${vexe} -o ${out_bin} "${test_file}" > /dev/null 2>&1'
compile_ret := os.system(compile_cmd)
compile_ms := f64(time.since(t1)) / f64(time.millisecond)
if compile_ret != 0 || !os.exists(out_bin) {
failed++
failed_files << short_name
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms ${short_name}')
os.rm(out_bin) or {}
os.rm('${out_bin}.c') or {}
continue
}
// Run the compiled binary
t2 := time.now()
run_ret := os.system(out_bin + ' > /dev/null 2>&1')
run_ms := f64(time.since(t2)) / f64(time.millisecond)
if run_ret != 0 {
failed++
failed_files << short_name
eprintln(' FAIL [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
} else {
passed++
eprintln('OK [${i + 1:4}/${total}] C: ${compile_ms:7.1} ms, R: ${run_ms:7.1} ms ${short_name}')
}
os.rm(out_bin) or {}
os.rm('${out_bin}.c') or {}
}
elapsed := time.since(t0)
eprintln('------------------------------------------------------------------------')
eprintln('${passed} passed, ${failed} failed, ${total} total (${elapsed})')
if failed_files.len > 0 {
eprintln('Failed:')
for f in failed_files {
eprintln(' ${f}')
}
}
// Always run the self-compilation chain, even when some tests fail.
// Self-compilation chain: v2 -> v3 -> v4 -> v5
eprintln('')
eprintln('---- v2 self-compilation chain ----')
v2_source := os.join_path(v2_dir, 'v2.v')
backend := 'cleanc'
tmpdir := os.temp_dir()
v3_bin := os.join_path(tmpdir, 'v2_self_v3')
v4_bin := os.join_path(tmpdir, 'v2_self_v4')
v5_bin := os.join_path(tmpdir, 'v2_self_v5')
steps := [
[vexe, v3_bin, 'v2 -> v3'],
[v3_bin, v4_bin, 'v3 -> v4'],
[v4_bin, v5_bin, 'v4 -> v5'],
]
for step in steps {
compiler := step[0]
out := step[1]
label := step[2]
ts := time.now()
cmd := '${compiler} -gc none -o ${out} -backend ${backend} "${v2_source}" > /dev/null 2>&1'
ret := os.system(cmd)
ms := f64(time.since(ts)) / f64(time.millisecond)
if ret != 0 || !os.exists(out) {
eprintln(' FAIL ${label} (${ms:.1} ms)')
// Clean up
for bin in [v3_bin, v4_bin, v5_bin] {
os.rm(bin) or {}
os.rm('${bin}.c') or {}
}
exit(1)
}
eprintln('OK ${label} (${ms:.1} ms)')
}
// Verify that v3 runs and produces expected output
result := os.execute('${v3_bin} 2>&1')
expected := 'At least 1 .v file expected'
if !result.output.contains(expected) {
eprintln("FAIL v3 output check: expected '${expected}', got:")
eprintln(result.output)
for bin in [v3_bin, v4_bin, v5_bin] {
os.rm(bin) or {}
os.rm('${bin}.c') or {}
}
exit(1)
}
eprintln('OK v3 output verified')
// Clean up
for bin in [v3_bin, v4_bin, v5_bin] {
os.rm(bin) or {}
os.rm('${bin}.c') or {}
}
total_elapsed := time.since(t0)
eprintln('')
if failed > 0 {
eprintln('=== SELF-COMPILATION OK, but ${failed} test(s) failed ===')
eprintln('Total time: ${total_elapsed}')
exit(1)
}
eprintln('=== SELF-COMPILATION TEST PASSED ===')
eprintln('Total time: ${total_elapsed}')
}