369 lines
12 KiB
V
369 lines
12 KiB
V
// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved.
|
|
// Use of this source code is governed by an MIT license
|
|
// that can be found in the LICENSE file.
|
|
module builder
|
|
|
|
import time
|
|
import os
|
|
import rand
|
|
import v.pref
|
|
import v.util
|
|
import v.checker
|
|
|
|
pub type FnBackend = fn (mut b Builder)
|
|
|
|
pub fn compile(command string, pref &pref.Preferences, backend_cb FnBackend) {
|
|
odir := os.dir(pref.out_name)
|
|
// When pref.out_name is just the name of an executable, i.e. `./v -o executable main.v`
|
|
// without a folder component, just use the current folder instead:
|
|
mut output_folder := odir
|
|
if odir.len == pref.out_name.len {
|
|
output_folder = os.getwd()
|
|
}
|
|
os.is_writable_folder(output_folder) or {
|
|
// An early error here, is better than an unclear C error later:
|
|
verror(err.msg())
|
|
}
|
|
// Construct the V object from command line arguments
|
|
mut b := new_builder(pref)
|
|
if pref.is_verbose {
|
|
println('builder.compile() pref:')
|
|
// println(pref)
|
|
}
|
|
mut sw := time.new_stopwatch()
|
|
backend_cb(mut b)
|
|
mut timers := util.get_timers()
|
|
timers.show_remaining()
|
|
if pref.is_stats {
|
|
compilation_time_micros := 1 + sw.elapsed().microseconds()
|
|
scompilation_time_ms := util.bold('${f64(compilation_time_micros) / 1000.0:6.3f}')
|
|
mut all_v_source_lines, mut all_v_source_bytes := 0, 0
|
|
for pf in b.parsed_files {
|
|
all_v_source_lines += pf.nr_lines
|
|
all_v_source_bytes += pf.nr_bytes
|
|
}
|
|
mut sall_v_source_lines := all_v_source_lines.str()
|
|
mut sall_v_source_bytes := all_v_source_bytes.str()
|
|
sall_v_source_lines = util.bold('${sall_v_source_lines:10s}')
|
|
sall_v_source_bytes = util.bold('${sall_v_source_bytes:10s}')
|
|
println(' V source code size: $sall_v_source_lines lines, $sall_v_source_bytes bytes')
|
|
//
|
|
mut slines := b.stats_lines.str()
|
|
mut sbytes := b.stats_bytes.str()
|
|
slines = util.bold('${slines:10s}')
|
|
sbytes = util.bold('${sbytes:10s}')
|
|
println('generated target code size: $slines lines, $sbytes bytes')
|
|
//
|
|
vlines_per_second := int(1_000_000.0 * f64(all_v_source_lines) / f64(compilation_time_micros))
|
|
svlines_per_second := util.bold(vlines_per_second.str())
|
|
println('compilation took: $scompilation_time_ms ms, compilation speed: $svlines_per_second vlines/s')
|
|
}
|
|
b.exit_on_invalid_syntax()
|
|
// running does not require the parsers anymore
|
|
unsafe { b.myfree() }
|
|
if pref.is_test || pref.is_run {
|
|
b.run_compiled_executable_and_exit()
|
|
}
|
|
}
|
|
|
|
pub fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string {
|
|
vtmp := util.get_vtmp_folder()
|
|
mut uniq := ''
|
|
if !b.pref.reuse_tmpc {
|
|
uniq = '.$rand.u64()'
|
|
}
|
|
fname := os.file_name(os.real_path(base_file_name)) + '$uniq$postfix'
|
|
return os.real_path(os.join_path(vtmp, fname))
|
|
}
|
|
|
|
// Temporary, will be done by -autofree
|
|
[unsafe]
|
|
fn (mut b Builder) myfree() {
|
|
// for file in b.parsed_files {
|
|
// }
|
|
unsafe { b.parsed_files.free() }
|
|
util.free_caches()
|
|
}
|
|
|
|
fn (b &Builder) exit_on_invalid_syntax() {
|
|
util.free_caches()
|
|
// V should exit with an exit code of 1, when there are errors,
|
|
// even when -silent is passed in combination to -check-syntax:
|
|
if b.pref.only_check_syntax {
|
|
for pf in b.parsed_files {
|
|
if pf.errors.len > 0 {
|
|
exit(1)
|
|
}
|
|
}
|
|
if b.checker.nr_errors > 0 {
|
|
exit(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn (mut b Builder) run_compiled_executable_and_exit() {
|
|
if b.pref.backend == .interpret {
|
|
// the interpreted code has already ran
|
|
return
|
|
}
|
|
if b.pref.skip_running {
|
|
return
|
|
}
|
|
if b.pref.only_check_syntax || b.pref.check_only {
|
|
return
|
|
}
|
|
if b.pref.should_output_to_stdout() {
|
|
return
|
|
}
|
|
if b.pref.os == .ios {
|
|
panic('Running iOS apps is not supported yet.')
|
|
}
|
|
if b.pref.is_verbose {
|
|
}
|
|
if b.pref.is_test || b.pref.is_run {
|
|
compiled_file := os.real_path(b.pref.out_name)
|
|
run_file := if b.pref.backend.is_js() {
|
|
node_basename := $if windows { 'node.exe' } $else { 'node' }
|
|
os.find_abs_path_of_executable(node_basename) or {
|
|
panic('Could not find `node` in system path. Do you have Node.js installed?')
|
|
}
|
|
} else {
|
|
compiled_file
|
|
}
|
|
mut run_args := []string{cap: b.pref.run_args.len + 1}
|
|
if b.pref.backend.is_js() {
|
|
run_args << compiled_file
|
|
}
|
|
run_args << b.pref.run_args
|
|
mut run_process := os.new_process(run_file)
|
|
run_process.set_args(run_args)
|
|
if b.pref.is_verbose {
|
|
println('running $run_process.filename with arguments $run_process.args')
|
|
}
|
|
// Ignore sigint and sigquit while running the compiled file,
|
|
// so ^C doesn't prevent v from deleting the compiled file.
|
|
// See also https://git.musl-libc.org/cgit/musl/tree/src/process/system.c
|
|
prev_int_handler := os.signal_opt(.int, eshcb) or { serror('set .int', err) }
|
|
mut prev_quit_handler := os.SignalHandler(eshcb)
|
|
$if !windows { // There's no sigquit on windows
|
|
prev_quit_handler = os.signal_opt(.quit, eshcb) or { serror('set .quit', err) }
|
|
}
|
|
run_process.wait()
|
|
os.signal_opt(.int, prev_int_handler) or { serror('restore .int', err) }
|
|
$if !windows {
|
|
os.signal_opt(.quit, prev_quit_handler) or { serror('restore .quit', err) }
|
|
}
|
|
ret := run_process.code
|
|
run_process.close()
|
|
b.cleanup_run_executable_after_exit(compiled_file)
|
|
exit(ret)
|
|
}
|
|
exit(0)
|
|
}
|
|
|
|
fn eshcb(_ os.Signal) {
|
|
}
|
|
|
|
[noreturn]
|
|
fn serror(reason string, e IError) {
|
|
eprintln('could not $reason handler')
|
|
panic(e)
|
|
}
|
|
|
|
fn (mut v Builder) cleanup_run_executable_after_exit(exefile string) {
|
|
if v.pref.reuse_tmpc {
|
|
v.pref.vrun_elog('keeping executable: $exefile , because -keepc was passed')
|
|
return
|
|
}
|
|
v.pref.vrun_elog('remove run executable: $exefile')
|
|
os.rm(exefile) or {}
|
|
}
|
|
|
|
// 'strings' => 'VROOT/vlib/strings'
|
|
// 'installed_mod' => '~/.vmodules/installed_mod'
|
|
// 'local_mod' => '/path/to/current/dir/local_mod'
|
|
pub fn (mut v Builder) set_module_lookup_paths() {
|
|
// Module search order:
|
|
// 0) V test files are very commonly located right inside the folder of the
|
|
// module, which they test. Adding the parent folder of the module folder
|
|
// with the _test.v files, *guarantees* that the tested module can be found
|
|
// without needing to set custom options/flags.
|
|
// 1) search in the *same* directory, as the compiled final v program source
|
|
// (i.e. the . in `v .` or file.v in `v file.v`)
|
|
// 2) search in the modules/ in the same directory.
|
|
// 3) search in the provided paths
|
|
// By default, these are what (3) contains:
|
|
// 3.1) search in vlib/
|
|
// 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm)
|
|
v.module_search_paths = []
|
|
if v.pref.is_test {
|
|
v.module_search_paths << os.dir(v.compiled_dir) // pdir of _test.v
|
|
}
|
|
v.module_search_paths << v.compiled_dir
|
|
x := os.join_path(v.compiled_dir, 'modules')
|
|
if v.pref.is_verbose {
|
|
println('x: "$x"')
|
|
}
|
|
v.module_search_paths << os.join_path(v.compiled_dir, 'modules')
|
|
v.module_search_paths << v.pref.lookup_path
|
|
if v.pref.is_verbose {
|
|
v.log('v.module_search_paths:')
|
|
println(v.module_search_paths)
|
|
}
|
|
}
|
|
|
|
pub fn (v Builder) get_builtin_files() []string {
|
|
if v.pref.no_builtin {
|
|
v.log('v.pref.no_builtin is true, get_builtin_files == []')
|
|
return []
|
|
}
|
|
v.log('v.pref.lookup_path: $v.pref.lookup_path')
|
|
// Lookup for built-in folder in lookup path.
|
|
// Assumption: `builtin/` folder implies usable implementation of builtin
|
|
for location in v.pref.lookup_path {
|
|
if os.exists(os.join_path(location, 'builtin')) {
|
|
mut builtin_files := []string{}
|
|
if v.pref.backend.is_js() {
|
|
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
|
|
'js'))
|
|
} else {
|
|
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin'))
|
|
}
|
|
if v.pref.is_bare {
|
|
builtin_files << v.v_files_from_dir(v.pref.bare_builtin_dir)
|
|
}
|
|
if v.pref.backend == .c {
|
|
// TODO JavaScript backend doesn't handle os for now
|
|
if v.pref.is_vsh && os.exists(os.join_path(location, 'os')) {
|
|
builtin_files << v.v_files_from_dir(os.join_path(location, 'os'))
|
|
}
|
|
}
|
|
return builtin_files
|
|
}
|
|
}
|
|
// Panic. We couldn't find the folder.
|
|
verror('`builtin/` not included on module lookup path.\nDid you forget to add vlib to the path? (Use @vlib for default vlib)')
|
|
}
|
|
|
|
pub fn (v &Builder) get_user_files() []string {
|
|
if v.pref.path in ['vlib/builtin', 'vlib/strconv', 'vlib/strings', 'vlib/hash']
|
|
|| v.pref.path.ends_with('vlib/builtin') {
|
|
// This means we are building a builtin module with `v build-module vlib/strings` etc
|
|
// get_builtin_files() has already added the files in this module,
|
|
// do nothing here to avoid duplicate definition errors.
|
|
v.log('Skipping user files.')
|
|
return []
|
|
}
|
|
mut dir := v.pref.path
|
|
v.log('get_v_files($dir)')
|
|
// Need to store user files separately, because they have to be added after
|
|
// libs, but we dont know which libs need to be added yet
|
|
mut user_files := []string{}
|
|
// See cmd/tools/preludes/README.md for more info about what preludes are
|
|
vroot := os.dir(pref.vexe_path())
|
|
mut preludes_path := os.join_path(vroot, 'vlib', 'v', 'preludes')
|
|
if v.pref.backend == .js_node {
|
|
preludes_path = os.join_path(vroot, 'vlib', 'v', 'preludes_js')
|
|
}
|
|
if v.pref.is_livemain || v.pref.is_liveshared {
|
|
user_files << os.join_path(preludes_path, 'live.v')
|
|
}
|
|
if v.pref.is_livemain {
|
|
user_files << os.join_path(preludes_path, 'live_main.v')
|
|
}
|
|
if v.pref.is_liveshared {
|
|
user_files << os.join_path(preludes_path, 'live_shared.v')
|
|
}
|
|
if v.pref.is_test {
|
|
user_files << os.join_path(preludes_path, 'test_runner.v')
|
|
//
|
|
mut v_test_runner_prelude := os.getenv('VTEST_RUNNER')
|
|
if v.pref.test_runner != '' {
|
|
v_test_runner_prelude = v.pref.test_runner
|
|
}
|
|
if v_test_runner_prelude == '' {
|
|
v_test_runner_prelude = 'normal'
|
|
}
|
|
if !v_test_runner_prelude.contains('/') && !v_test_runner_prelude.contains('\\')
|
|
&& !v_test_runner_prelude.ends_with('.v') {
|
|
v_test_runner_prelude = os.join_path(preludes_path, 'test_runner_${v_test_runner_prelude}.v')
|
|
}
|
|
if !os.is_file(v_test_runner_prelude) || !os.is_readable(v_test_runner_prelude) {
|
|
eprintln('test runner error: File $v_test_runner_prelude should be readable.')
|
|
verror('supported test runners are: tap, json, simple, normal')
|
|
}
|
|
user_files << v_test_runner_prelude
|
|
}
|
|
if v.pref.is_test && v.pref.is_stats {
|
|
user_files << os.join_path(preludes_path, 'tests_with_stats.v')
|
|
}
|
|
if v.pref.backend.is_js() && v.pref.is_stats && v.pref.is_test {
|
|
user_files << os.join_path(preludes_path, 'stats_import.js.v')
|
|
}
|
|
if v.pref.is_prof {
|
|
user_files << os.join_path(preludes_path, 'profiled_program.v')
|
|
}
|
|
is_test := v.pref.is_test
|
|
mut is_internal_module_test := false
|
|
if is_test {
|
|
tcontent := util.read_file(dir) or { verror('$dir does not exist') }
|
|
slines := tcontent.split_into_lines()
|
|
for sline in slines {
|
|
line := sline.trim_space()
|
|
if line.len > 2 {
|
|
if line[0] == `/` && line[1] == `/` {
|
|
continue
|
|
}
|
|
if line.starts_with('module ') {
|
|
is_internal_module_test = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if is_internal_module_test {
|
|
// v volt/slack_test.v: compile all .v files to get the environment
|
|
single_test_v_file := os.real_path(dir)
|
|
if v.pref.is_verbose {
|
|
v.log('> Compiling an internal module _test.v file $single_test_v_file .')
|
|
v.log('> That brings in all other ordinary .v files in the same module too .')
|
|
}
|
|
user_files << single_test_v_file
|
|
dir = os.dir(single_test_v_file)
|
|
}
|
|
does_exist := os.exists(dir)
|
|
if !does_exist {
|
|
verror("$dir doesn't exist")
|
|
}
|
|
is_real_file := does_exist && !os.is_dir(dir)
|
|
resolved_link := if is_real_file && os.is_link(dir) { os.real_path(dir) } else { dir }
|
|
if is_real_file && (dir.ends_with('.v') || resolved_link.ends_with('.vsh')
|
|
|| dir.ends_with('.vv')) {
|
|
single_v_file := if resolved_link.ends_with('.vsh') { resolved_link } else { dir }
|
|
// Just compile one file and get parent dir
|
|
user_files << single_v_file
|
|
if v.pref.is_verbose {
|
|
v.log('> just compile one file: "$single_v_file"')
|
|
}
|
|
} else if os.is_dir(dir) {
|
|
if v.pref.is_verbose {
|
|
v.log('> add all .v files from directory "$dir" ...')
|
|
}
|
|
// Add .v files from the directory being compiled
|
|
user_files << v.v_files_from_dir(dir)
|
|
} else {
|
|
println('usage: `v file.v` or `v directory`')
|
|
ext := os.file_ext(dir)
|
|
println('unknown file extension `$ext`')
|
|
exit(1)
|
|
}
|
|
if user_files.len == 0 {
|
|
println('No input .v files')
|
|
exit(1)
|
|
}
|
|
if v.pref.is_verbose {
|
|
v.log('user_files: $user_files')
|
|
}
|
|
return user_files
|
|
}
|