v/compiler/main.v

1178 lines
30 KiB
V
Raw Normal View History

2019-06-23 04:21:30 +02:00
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
2019-06-22 20:20:28 +02:00
module main
2019-08-29 00:52:32 +02:00
import (
os
strings
benchmark
term
2019-08-29 00:52:32 +02:00
)
2019-06-22 20:20:28 +02:00
const (
2019-09-30 21:39:52 +02:00
Version = '0.1.21'
2019-06-22 20:20:28 +02:00
)
enum BuildMode {
// `v program.v'
// Build user code only, and add pre-compiled vlib (`cc program.o builtin.o os.o...`)
2019-07-12 07:23:16 +02:00
default_mode
2019-06-22 20:20:28 +02:00
// `v -embed_vlib program.v`
// vlib + user code in one file (slower compilation, but easier when working on vlib and cross-compiling)
2019-07-12 07:23:16 +02:00
embed_vlib
2019-06-22 20:20:28 +02:00
// `v -lib ~/v/os`
// build any module (generate os.o + os.vh)
2019-09-10 16:36:14 +02:00
build_module
2019-06-22 20:20:28 +02:00
}
const (
supported_platforms = ['windows', 'mac', 'linux', 'freebsd', 'openbsd',
2019-09-26 23:23:27 +02:00
'netbsd', 'dragonfly', 'msvc', 'android', 'js', 'solaris']
2019-06-22 20:20:28 +02:00
)
enum OS {
mac
linux
windows
2019-08-17 21:19:37 +02:00
freebsd
openbsd
netbsd
dragonfly
msvc // TODO not an OS
2019-10-04 14:48:09 +02:00
js // TODO
2019-09-26 23:30:41 +02:00
android
solaris
2019-06-23 06:34:41 +02:00
}
2019-06-22 20:20:28 +02:00
enum Pass {
2019-07-16 17:59:07 +02:00
// A very short pass that only looks at imports in the beginning of
// each file
imports
2019-07-16 17:59:07 +02:00
// First pass, only parses and saves declarations (fn signatures,
// consts, types).
2019-06-22 20:20:28 +02:00
// Skips function bodies.
2019-07-16 17:59:07 +02:00
// We need this because in V things can be used before they are
// declared.
decl
2019-06-22 20:20:28 +02:00
// Second pass, parses function bodies and generates C or machine code.
main
}
2019-06-22 20:20:28 +02:00
struct V {
mut:
2019-10-04 14:48:09 +02:00
os OS // the OS to build for
out_name_c string // name of the temporary C file
files []string // all V files that need to be parsed and compiled
dir string // directory (or file) being compiled (TODO rename to path?)
table &Table // table with types, vars, functions etc
cgen &CGen // C code generator
2019-09-13 13:25:05 +02:00
pref &Preferences // all the preferences and settings extracted to a struct for reusability
2019-10-04 14:48:09 +02:00
lang_dir string // "~/code/v"
out_name string // "program.exe"
vroot string
2019-10-04 14:48:09 +02:00
mod string // module being built with -lib
2019-09-20 02:01:52 +02:00
parsers []Parser
vgen_buf strings.Builder // temporary buffer for generated V code (.str() etc)
2019-06-22 20:20:28 +02:00
}
struct Preferences {
mut:
2019-10-04 14:48:09 +02:00
build_mode BuildMode
nofmt bool // disable vfmt
is_test bool // `v test string_test.v`
is_script bool // single file mode (`v program.v`), main function can be skipped
is_live bool // for hot code reloading
is_so bool
is_prof bool // benchmark every function
translated bool // `v translate doom.v` are we running V code translated from C? allow globals, ++ expressions, etc
is_prod bool // use "-O2"
is_verbose bool // print extra information with `v.log()`
obfuscate bool // `v -obf program.v`, renames functions to "f_XXX"
is_repl bool
is_run bool
show_c_cmd bool // `v -show_c_cmd` prints the C command to build program.v.c
sanitize bool // use Clang's new "-fsanitize" option
is_debuggable bool
is_debug bool // keep compiled C files
is_stats bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
2019-10-04 14:48:09 +02:00
no_auto_free bool // `v -nofree` disable automatic `free()` insertion for better performance in some applications (e.g. compilers)
cflags string // Additional options which will be passed to the C compiler.
// For example, passing -cflags -Os will cause the C compiler to optimize the generated binaries for size.
// You could pass several -cflags XXX arguments. They will be merged with each other.
// You can also quote several options at the same time: -cflags '-Os -fno-inline-small-functions'.
2019-10-04 14:48:09 +02:00
ccompiler string // the name of the used C compiler
building_v bool
autofree bool
compress bool
skip_builtin bool // Skips re-compilation of the builtin module
// to increase compilation time.
// This is on by default, since a vast majority of users do not
// work on the builtin module itself.
}
2019-06-22 20:20:28 +02:00
fn main() {
2019-07-03 13:20:43 +02:00
// There's no `flags` module yet, so args have to be parsed manually
args := env_vflags_and_os_args()
2019-06-22 20:20:28 +02:00
// Print the version and exit.
if '-v' in args || '--version' in args || 'version' in args {
2019-09-11 00:12:46 +02:00
version_hash := vhash()
println('V $Version $version_hash')
2019-06-22 20:20:28 +02:00
return
}
if '-h' in args || '--help' in args || 'help' in args {
println(HelpText)
2019-06-23 02:40:36 +02:00
return
2019-06-22 20:20:28 +02:00
}
2019-06-24 17:42:44 +02:00
if 'translate' in args {
2019-08-17 21:19:37 +02:00
println('Translating C to V will be available in V 0.3')
return
}
2019-07-31 04:40:38 +02:00
if 'up' in args {
2019-08-17 21:19:37 +02:00
update_v()
return
}
2019-08-01 01:34:28 +02:00
if 'get' in args {
println('use `v install` to install modules from vpm.vlang.io ')
2019-08-17 21:19:37 +02:00
return
}
2019-08-27 18:35:48 +02:00
if 'symlink' in args {
create_symlink()
return
}
2019-08-01 01:34:28 +02:00
if 'install' in args {
2019-09-10 10:22:16 +02:00
install_v(args)
return
2019-08-17 21:19:37 +02:00
}
// TODO quit if the compiler is too old
// u := os.file_last_mod_unix('v')
2019-06-22 22:00:38 +02:00
// If there's no tmp path with current version yet, the user must be using a pre-built package
//
2019-06-22 20:20:28 +02:00
// Just fmt and exit
2019-08-17 21:19:37 +02:00
if 'fmt' in args {
vfmt(args)
2019-06-22 20:20:28 +02:00
return
}
// Construct the V object from command line arguments
mut v := new_v(args)
if args.join(' ').contains(' test v') {
v.test_v()
return
}
if v.pref.is_verbose {
2019-06-22 20:20:28 +02:00
println(args)
}
// Generate the docs and exit
2019-08-17 21:19:37 +02:00
if 'doc' in args {
// v.gen_doc_html_for_module(args.last())
exit(0)
2019-06-22 20:20:28 +02:00
}
2019-08-17 21:19:37 +02:00
if 'run' in args {
2019-08-09 22:37:31 +02:00
// always recompile for now, too error prone to skip recompilation otherwise
// for example for -repl usage, especially when piping lines to v
2019-08-17 21:19:37 +02:00
v.compile()
v.run_compiled_executable_and_exit()
}
2019-08-17 21:19:37 +02:00
// No args? REPL
if args.len < 2 || (args.len == 2 && args[1] == '-') || 'runrepl' in args {
run_repl()
return
}
mut tmark := benchmark.new_benchmark()
v.compile()
if v.pref.is_stats {
tmark.stop()
println( 'compilation took: ' + tmark.total_duration().str() + 'ms')
}
2019-08-17 21:19:37 +02:00
if v.pref.is_test {
v.run_compiled_executable_and_exit()
}
// TODO remove
if v.pref.autofree {
println('started freeing v struct')
v.table.typesmap.free()
v.table.obf_ids.free()
v.cgen.lines.free()
free(v.cgen)
2019-09-19 16:25:00 +02:00
for _, f in v.table.fns {
2019-09-23 19:34:08 +02:00
//f.local_vars.free()
2019-09-19 16:25:00 +02:00
f.args.free()
//f.defer_text.free()
}
v.table.fns.free()
free(v.table)
2019-10-04 14:48:09 +02:00
//for p in parsers {}
println('done!')
}
2019-06-22 20:20:28 +02:00
}
2019-09-20 02:01:52 +02:00
fn (v mut V) add_parser(parser Parser) {
2019-10-04 14:48:09 +02:00
v.parsers << parser
}
fn (v &V) get_file_parser_index(file string) ?int {
2019-10-04 14:48:09 +02:00
for i, p in v.parsers {
if os.realpath(p.file_path) == os.realpath(file) {
return i
2019-10-04 14:48:09 +02:00
}
}
return error('parser for "$file" not found')
}
// find existing parser or create new one. returns v.parsers index
fn (v mut V) parse(file string, pass Pass) int {
//println('parse($file, $pass)')
pidx := v.get_file_parser_index(file) or {
mut p := v.new_parser_from_file(file)
p.parse(pass)
//if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
v.add_parser(p)
return v.parsers.len-1
}
v.parsers[pidx].parse(pass)
//if v.parsers[i].pref.autofree { v.parsers[i].scanner.text.free() free(v.parsers[i].scanner) }
return pidx
2019-09-20 02:01:52 +02:00
}
fn (v mut V) compile() {
// Emily: Stop people on linux from being able to build with msvc
if os.user_os() != 'windows' && v.os == .msvc {
verror('Cannot build with msvc on ${os.user_os()}')
}
mut cgen := v.cgen
2019-06-22 20:20:28 +02:00
cgen.genln('// Generated by V')
if v.pref.is_verbose {
println('all .v files before:')
println(v.files)
}
2019-08-01 01:34:28 +02:00
v.add_v_files_to_compile()
2019-09-14 22:48:30 +02:00
if v.pref.is_verbose || v.pref.is_debug {
2019-06-22 20:20:28 +02:00
println('all .v files:')
println(v.files)
2019-06-22 20:20:28 +02:00
}
/*
2019-09-26 04:28:43 +02:00
if v.pref.is_debug {
println('\nparsers:')
for q in v.parsers {
println(q.file_name)
}
println('\nfiles:')
for q in v.files {
println(q)
}
}
*/
2019-06-22 20:20:28 +02:00
// First pass (declarations)
for file in v.files {
2019-10-04 14:48:09 +02:00
v.parse(file, .decl)
2019-06-22 20:20:28 +02:00
}
// Main pass
2019-07-29 18:21:36 +02:00
cgen.pass = Pass.main
2019-08-16 07:50:36 +02:00
if v.pref.is_debug {
2019-09-14 22:48:30 +02:00
$if js {
cgen.genln('const VDEBUG = 1;\n')
} $else {
cgen.genln('#define VDEBUG (1)')
}
}
if v.os == .js {
cgen.genln('#define _VJS (1) ')
2019-08-16 07:50:36 +02:00
}
2019-08-23 02:10:37 +02:00
if v.pref.building_v {
cgen.genln('#ifndef V_COMMIT_HASH')
cgen.genln('#define V_COMMIT_HASH "' + vhash() + '"')
cgen.genln('#endif')
}
2019-09-14 22:48:30 +02:00
$if js {
cgen.genln(js_headers)
} $else {
cgen.genln(CommonCHeaders)
}
v.generate_hotcode_reloading_declarations()
2019-08-23 02:10:37 +02:00
imports_json := 'json' in v.table.imports
2019-06-22 20:20:28 +02:00
// TODO remove global UI hack
if v.os == .mac && ((v.pref.build_mode == .embed_vlib && 'ui' in
v.table.imports) || (v.pref.build_mode == .build_module &&
v.dir.contains('/ui'))) {
2019-06-22 20:20:28 +02:00
cgen.genln('id defaultFont = 0; // main.v')
}
2019-09-26 04:28:43 +02:00
// We need the cjson header for all the json decoding that will be done in
// default mode
if v.pref.build_mode == .default_mode {
2019-06-22 20:20:28 +02:00
if imports_json {
2019-06-23 09:03:52 +02:00
cgen.genln('#include "cJSON.h"')
2019-06-22 20:20:28 +02:00
}
}
if v.pref.build_mode == .embed_vlib || v.pref.build_mode == .default_mode {
2019-09-26 04:28:43 +02:00
//if v.pref.build_mode in [.embed_vlib, .default_mode] {
2019-06-22 20:20:28 +02:00
// If we declare these for all modes, then when running `v a.v` we'll get
// `/usr/bin/ld: multiple definition of 'total_m'`
// TODO
//cgen.genln('i64 total_m = 0; // For counting total RAM allocated')
2019-09-15 18:43:35 +02:00
//if v.pref.is_test {
2019-09-16 17:29:04 +02:00
$if !js {
cgen.genln('int g_test_oks = 0;')
cgen.genln('int g_test_fails = 0;')
2019-09-16 17:29:04 +02:00
}
2019-09-26 04:28:43 +02:00
if imports_json {
2019-08-17 21:19:37 +02:00
cgen.genln('
2019-06-22 20:20:28 +02:00
#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
')
}
}
if '-debug_alloc' in os.args {
2019-06-22 20:20:28 +02:00
cgen.genln('#define DEBUG_ALLOC 1')
}
2019-09-14 22:48:30 +02:00
//cgen.genln('/*================================== FNS =================================*/')
2019-06-22 20:20:28 +02:00
cgen.genln('this line will be replaced with definitions')
defs_pos := cgen.lines.len - 1
for file in v.files {
2019-10-04 14:48:09 +02:00
v.parse(file, .main)
//if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
2019-06-22 20:20:28 +02:00
// p.g.gen_x64()
// Format all files (don't format automatically generated vlib headers)
if !v.pref.nofmt && !file.contains('/vlib/') {
2019-06-22 20:20:28 +02:00
// new vfmt is not ready yet
}
}
2019-10-04 14:48:09 +02:00
// Generate .vh if we are building a module
if v.pref.build_mode == .build_module {
v.generate_vh()
}
// parse generated V code (str() methods etc)
2019-10-04 14:48:09 +02:00
mut vgen_parser := v.new_parser_from_string(v.vgen_buf.str(), 'vgen')
// free the string builder which held the generated methods
v.vgen_buf.free()
2019-09-26 04:28:43 +02:00
vgen_parser.parse(.main)
// v.parsers.add(vgen_parser)
v.log('Done parsing.')
2019-06-22 20:20:28 +02:00
// Write everything
mut d := strings.new_builder(10000)// Avoid unnecessary allocations
2019-09-14 22:48:30 +02:00
$if !js {
d.writeln(cgen.includes.join_lines())
d.writeln(cgen.typedefs.join_lines())
d.writeln(v.type_definitions())
d.writeln('\nstring _STR(const char*, ...);\n')
d.writeln('\nstring _STR_TMP(const char*, ...);\n')
d.writeln(cgen.fns.join_lines()) // fn definitions
} $else {
d.writeln(v.type_definitions())
}
2019-06-22 20:20:28 +02:00
d.writeln(cgen.consts.join_lines())
d.writeln(cgen.thread_args.join_lines())
if v.pref.is_prof {
2019-06-22 20:20:28 +02:00
d.writeln('; // Prof counters:')
d.writeln(v.prof_counters())
2019-06-22 20:20:28 +02:00
}
2019-09-26 04:28:43 +02:00
cgen.lines[defs_pos] = d.str()
2019-09-14 22:48:30 +02:00
v.generate_main()
v.generate_hot_reload_code()
if v.pref.is_verbose {
v.log('flags=')
for flag in v.get_os_cflags() {
println(' * ' + flag.format())
}
}
2019-09-14 22:48:30 +02:00
$if js {
cgen.genln('main__main();')
2019-09-14 22:48:30 +02:00
}
cgen.save()
v.cc()
}
fn (v mut V) generate_main() {
mut cgen := v.cgen
2019-09-14 22:48:30 +02:00
$if js { return }
2019-08-23 02:10:37 +02:00
// if v.build_mode in [.default, .embed_vlib] {
if v.pref.build_mode == .default_mode || v.pref.build_mode == .embed_vlib {
2019-08-17 21:19:37 +02:00
mut consts_init_body := cgen.consts_init.join_lines()
2019-06-22 20:20:28 +02:00
// vlib can't have `init_consts()`
2019-08-17 21:19:37 +02:00
cgen.genln('void init_consts() {
#ifdef _WIN32
2019-09-20 16:03:13 +02:00
DWORD consoleMode;
isConsole = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &consoleMode);
2019-09-20 16:03:13 +02:00
int mode = isConsole ? _O_U16TEXT : _O_U8TEXT;
_setmode(_fileno(stdin), mode);
_setmode(_fileno(stdout), _O_U8TEXT);
2019-08-17 21:19:37 +02:00
SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | 0x0004);
// ENABLE_VIRTUAL_TERMINAL_PROCESSING
setbuf(stdout,0);
#endif
g_str_buf=malloc(1000);
2019-08-17 21:19:37 +02:00
$consts_init_body
}')
2019-06-22 20:20:28 +02:00
// _STR function can't be defined in vlib
cgen.genln('
string _STR(const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
2019-08-17 21:19:37 +02:00
size_t len = vsnprintf(0, 0, fmt, argptr) + 1;
2019-06-22 20:20:28 +02:00
va_end(argptr);
2019-08-17 21:19:37 +02:00
byte* buf = malloc(len);
2019-06-22 20:20:28 +02:00
va_start(argptr, fmt);
vsprintf((char *)buf, fmt, argptr);
2019-06-22 20:20:28 +02:00
va_end(argptr);
2019-08-17 21:19:37 +02:00
#ifdef DEBUG_ALLOC
puts("_STR:");
puts(buf);
#endif
2019-06-22 20:20:28 +02:00
return tos2(buf);
}
string _STR_TMP(const char *fmt, ...) {
va_list argptr;
va_start(argptr, fmt);
//size_t len = vsnprintf(0, 0, fmt, argptr) + 1;
2019-06-22 20:20:28 +02:00
va_end(argptr);
va_start(argptr, fmt);
vsprintf((char *)g_str_buf, fmt, argptr);
2019-06-22 20:20:28 +02:00
va_end(argptr);
2019-08-17 21:19:37 +02:00
#ifdef DEBUG_ALLOC
//puts("_STR_TMP:");
//puts(g_str_buf);
#endif
2019-06-22 20:20:28 +02:00
return tos2(g_str_buf);
}
')
}
2019-06-22 20:20:28 +02:00
// Make sure the main function exists
// Obviously we don't need it in libraries
if v.pref.build_mode != .build_module {
if !v.table.main_exists() && !v.pref.is_test {
2019-06-22 20:20:28 +02:00
// It can be skipped in single file programs
if v.pref.is_script {
//println('Generating main()...')
v.gen_main_start(true)
cgen.genln('$cgen.fn_main;')
v.gen_main_end('return 0')
2019-06-22 20:20:28 +02:00
}
else {
verror('function `main` is not declared in the main module')
2019-06-22 20:20:28 +02:00
}
}
else if v.pref.is_test {
if v.table.main_exists() {
verror('test files cannot have function `main`')
}
if !v.table.has_at_least_one_test_fn() {
verror('test files need to have at least one test function')
}
// Generate a C `main`, which calls every single test function
v.gen_main_start(false)
if v.pref.is_stats { cgen.genln('BenchedTests bt = main__start_testing();') }
for _, f in v.table.fns {
if f.name.starts_with('main__test_') {
if v.pref.is_stats { cgen.genln('BenchedTests_testing_step_start(&bt, tos3("$f.name"));') }
cgen.genln('$f.name();')
if v.pref.is_stats { cgen.genln('BenchedTests_testing_step_end(&bt);') }
2019-06-22 20:20:28 +02:00
}
}
if v.pref.is_stats { cgen.genln('BenchedTests_end_testing(&bt);') }
v.gen_main_end('return g_test_fails > 0')
}
else if v.table.main_exists() {
v.gen_main_start(true)
cgen.genln(' main__main();')
v.gen_main_end('return 0')
2019-06-22 20:20:28 +02:00
}
}
}
fn (v mut V) gen_main_start(add_os_args bool){
v.cgen.genln('int main(int argc, char** argv) { ')
v.cgen.genln(' init_consts();')
if add_os_args && 'os' in v.table.imports {
v.cgen.genln(' os__args = os__init_os_args(argc, (byteptr*)argv);')
}
v.generate_hotcode_reloading_main_caller()
v.cgen.genln('')
}
fn (v mut V) gen_main_end(return_statement string){
v.cgen.genln('')
v.cgen.genln(' $return_statement;')
v.cgen.genln('}')
}
fn final_target_out_name(out_name string) string {
mut cmd := if out_name.starts_with('/') {
out_name
}
else {
'./' + out_name
}
$if windows {
cmd = out_name
cmd = cmd.replace('/', '\\')
2019-08-09 22:37:31 +02:00
cmd += '.exe'
}
return cmd
}
fn (v V) run_compiled_executable_and_exit() {
if v.pref.is_verbose {
2019-08-17 21:19:37 +02:00
println('============ running $v.out_name ============')
}
mut cmd := '"' + final_target_out_name(v.out_name).replace('.exe','') + '"'
if os.args.len > 3 {
cmd += ' ' + os.args.right(3).join(' ')
}
if v.pref.is_test {
ret := os.system(cmd)
2019-06-22 20:20:28 +02:00
if ret != 0 {
exit(1)
2019-06-22 20:20:28 +02:00
}
2019-08-17 21:19:37 +02:00
}
if v.pref.is_run {
ret := os.system(cmd)
2019-08-17 21:19:37 +02:00
// TODO: make the runner wrapping as transparent as possible
// (i.e. use execve when implemented). For now though, the runner
// just returns the same exit code as the child process.
exit( ret )
2019-06-22 20:20:28 +02:00
}
exit(0)
2019-06-22 20:20:28 +02:00
}
fn (v &V) v_files_from_dir(dir string) []string {
2019-06-22 20:20:28 +02:00
mut res := []string
if !os.file_exists(dir) {
verror('$dir doesn\'t exist')
} else if !os.dir_exists(dir) {
verror('$dir isn\'t a directory')
2019-06-22 20:20:28 +02:00
}
mut files := os.ls(dir)
if v.pref.is_verbose {
2019-06-22 20:20:28 +02:00
println('v_files_from_dir ("$dir")')
}
files.sort()
for file in files {
if !file.ends_with('.v') && !file.ends_with('.vh') {
continue
}
if file.ends_with('_test.v') {
continue
}
if file.ends_with('_win.v') && (v.os != .windows && v.os != .msvc) {
2019-06-22 20:20:28 +02:00
continue
}
2019-08-17 21:19:37 +02:00
if file.ends_with('_lin.v') && v.os != .linux {
2019-06-22 20:20:28 +02:00
continue
}
2019-08-17 21:19:37 +02:00
if file.ends_with('_mac.v') && v.os != .mac {
2019-07-15 19:19:53 +02:00
continue
2019-08-17 21:19:37 +02:00
}
if file.ends_with('_nix.v') && (v.os == .windows || v.os == .msvc) {
2019-08-17 21:19:37 +02:00
continue
}
2019-09-14 22:48:30 +02:00
if file.ends_with('_js.v') && v.os != .js {
continue
}
if file.ends_with('_c.v') && v.os == .js {
continue
}
res << '$dir${os.PathSeparator}$file'
2019-06-22 20:20:28 +02:00
}
return res
}
// Parses imports, adds necessary libs, and then user files
2019-08-01 01:34:28 +02:00
fn (v mut V) add_v_files_to_compile() {
2019-10-04 14:48:09 +02:00
// Parse builtin imports
for file in v.get_builtin_files() {
// add builtins first
v.files << file
mut p := v.new_parser_from_file(file)
p.parse(.imports)
//if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
v.add_parser(p)
2019-10-04 14:48:09 +02:00
}
// Parse user imports
for file in v.get_user_files() {
mut p := v.new_parser_from_file(file)
p.parse(.imports)
//if p.pref.autofree { p.scanner.text.free() free(p.scanner) }
v.add_parser(p)
2019-10-04 14:48:09 +02:00
}
// Parse lib imports
v.parse_lib_imports()
if v.pref.is_verbose {
v.log('imports:')
println(v.table.imports)
}
// resolve deps & add imports in correct order
for mod in v.resolve_deps().imports() {
// if mod == v.mod { continue } // Building this module? Skip. TODO it's a hack.
if mod == 'builtin' { continue } // builtin already added
if mod == 'main' { continue } // main files will get added last
2019-10-04 14:48:09 +02:00
// use cached built module if exists
// vh_path := '$v_modules_path/${mod}.vh'
// if os.file_exists(vh_path) {
// println('using cached module `$mod`: $vh_path')
// v.files << vh_path
// continue
// }
2019-10-04 14:48:09 +02:00
// standard module
mod_path := v.find_module_path(mod) or { verror(err) break }
vfiles := v.v_files_from_dir(mod_path)
2019-10-04 14:48:09 +02:00
for file in vfiles {
v.files << file
}
}
// add remaining main files last
for _, fit in v.table.file_imports {
if fit.module_name != 'main' { continue }
v.files << fit.file_path
}
}
// get builtin files
fn (v &V) get_builtin_files() []string {
$if js {
return v.v_files_from_dir('$v.vroot${os.PathSeparator}vlib${os.PathSeparator}builtin${os.PathSeparator}js')
2019-10-04 14:48:09 +02:00
}
return v.v_files_from_dir('$v.vroot${os.PathSeparator}vlib${os.PathSeparator}builtin')
2019-10-04 14:48:09 +02:00
}
// get user files
fn (v &V) get_user_files() []string {
mut dir := v.dir
2019-10-04 14:48:09 +02:00
v.log('get_v_files($dir)')
2019-06-22 20:20:28 +02:00
// 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
if v.pref.is_test && v.pref.is_stats {
user_files << [v.vroot, 'vlib', 'benchmark', 'tests', 'always_imported.v'].join( os.PathSeparator )
}
2019-06-22 20:20:28 +02:00
// v volt/slack_test.v: compile all .v files to get the environment
// I need to implement user packages! TODO
is_test_with_imports := dir.ends_with('_test.v') &&
(dir.contains('${os.PathSeparator}volt') || dir.contains('${os.PathSeparator}c2volt'))// TODO
2019-06-22 20:20:28 +02:00
if is_test_with_imports {
user_files << dir
pos := dir.last_index(os.PathSeparator)
dir = dir.left(pos) + os.PathSeparator// TODO WHY IS THIS .neEDED?
2019-06-22 20:20:28 +02:00
}
if dir.ends_with('.v') {
// Just compile one file and get parent dir
user_files << dir
dir = dir.all_before('${os.PathSeparator}')
2019-06-22 20:20:28 +02:00
}
else {
2019-09-14 22:48:30 +02:00
// Add .v files from the directory being compiled
files := v.v_files_from_dir(dir)
2019-06-22 20:20:28 +02:00
for file in files {
user_files << file
}
}
if user_files.len == 0 {
println('No input .v files')
exit(1)
2019-06-22 20:20:28 +02:00
}
if v.pref.is_verbose {
v.log('user_files:')
2019-06-22 20:20:28 +02:00
println(user_files)
}
2019-10-04 14:48:09 +02:00
return user_files
}
// parse deps from already parsed builtin/user files
fn (v mut V) parse_lib_imports() {
mut done_fits := []string
mut done_imports := []string
2019-10-04 14:48:09 +02:00
for {
for _, fit in v.table.file_imports {
if fit.file_path in done_fits { continue }
for _, mod in fit.imports {
import_path := v.find_module_path(mod) or {
pidx := v.get_file_parser_index(fit.file_path) or { verror(err) break }
v.parsers[pidx].error_with_token_index('cannot import module "$mod" (not found)', fit.get_import_tok_idx(mod))
break
}
vfiles := v.v_files_from_dir(import_path)
if vfiles.len == 0 {
pidx := v.get_file_parser_index(fit.file_path) or { verror(err) break }
v.parsers[pidx].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', fit.get_import_tok_idx(mod))
}
// Add all imports referenced by these libs
for file in vfiles {
if file in done_imports { continue }
pid := v.parse(file, .imports)
done_imports << file
p_mod := v.parsers[pid].import_table.module_name
if p_mod != mod {
v.parsers[pid].error_with_token_index('bad module definition: $fit.file_path imports module "$mod" but $file is defined as module `$p_mod`', 1)
}
}
2019-06-22 20:20:28 +02:00
}
done_fits << fit.file_path
}
if v.table.file_imports.size == done_fits.len { break}
2019-06-22 20:20:28 +02:00
}
2019-10-04 14:48:09 +02:00
}
// return resolved dep graph (order deps)
fn (v &V) resolve_deps() &DepGraph {
mut dep_graph := new_dep_graph()
dep_graph.from_import_tables(v.table.file_imports)
2019-07-21 17:53:35 +02:00
deps_resolved := dep_graph.resolve()
if !deps_resolved.acyclic {
deps_resolved.display()
2019-10-04 14:48:09 +02:00
verror('import cycle detected')
2019-06-22 20:20:28 +02:00
}
2019-10-04 14:48:09 +02:00
return deps_resolved
2019-06-22 20:20:28 +02:00
}
fn get_arg(joined_args, arg, def string) string {
return get_all_after(joined_args, '-$arg', def)
}
fn get_all_after(joined_args, arg, def string) string {
key := '$arg '
2019-06-22 20:20:28 +02:00
mut pos := joined_args.index(key)
if pos == -1 {
return def
}
pos += key.len
mut space := joined_args.index_after(' ', pos)
if space == -1 {
space = joined_args.len
}
res := joined_args.substr(pos, space)
// println('get_arg($arg) = "$res"')
return res
}
fn (v &V) log(s string) {
if !v.pref.is_verbose {
2019-06-22 20:20:28 +02:00
return
}
println(s)
}
2019-09-01 21:51:16 +02:00
fn new_v(args[]string) &V {
mut vgen_buf := strings.new_builder(1000)
vgen_buf.writeln('module main\nimport strings')
2019-09-26 04:28:43 +02:00
joined_args := args.join(' ')
target_os := get_arg(joined_args, 'os', '')
mut out_name := get_arg(joined_args, 'o', 'a.out')
2019-06-22 20:20:28 +02:00
mut dir := args.last()
if 'run' in args {
dir = get_all_after(joined_args, 'run', '')
2019-06-26 14:13:02 +02:00
}
2019-09-13 17:59:17 +02:00
if dir.ends_with(os.PathSeparator) {
dir = dir.all_before_last(os.PathSeparator)
}
adir := os.realpath(dir)
2019-06-22 20:20:28 +02:00
if args.len < 2 {
dir = ''
}
// println('new compiler "$dir"')
2019-06-22 20:20:28 +02:00
// build mode
mut build_mode := BuildMode.default_mode
2019-08-17 21:19:37 +02:00
mut mod := ''
2019-08-01 01:34:28 +02:00
//if args.contains('-lib') {
2019-08-17 21:19:37 +02:00
if joined_args.contains('build module ') {
build_mode = .build_module
2019-09-14 22:48:30 +02:00
// v build module ~/v/os => os.o
mod = if adir.contains(os.PathSeparator) {
adir.all_after(os.PathSeparator)
} else {
adir
}
println('Building module "${mod}" (dir="$dir")...')
//out_name = '$TmpPath/vlib/${base}.o'
2019-08-01 01:34:28 +02:00
out_name = mod + '.o'
println('$out_name')
2019-06-22 20:20:28 +02:00
// Cross compiling? Use separate dirs for each os
2019-10-04 14:48:09 +02:00
/*
2019-06-22 20:20:28 +02:00
if target_os != os.user_os() {
os.mkdir('$TmpPath/vlib/$target_os')
out_name = '$TmpPath/vlib/$target_os/${base}.o'
2019-08-17 21:19:37 +02:00
println('target_os=$target_os user_os=${os.user_os()}')
println('!Cross compiling $out_name')
2019-06-22 20:20:28 +02:00
}
2019-10-04 14:48:09 +02:00
*/
2019-06-22 20:20:28 +02:00
}
// TODO embed_vlib is temporarily the default mode. It's much slower.
else if !('-embed_vlib' in args) {
build_mode = .embed_vlib
2019-06-22 20:20:28 +02:00
}
2019-08-17 21:19:37 +02:00
//
2019-06-22 20:20:28 +02:00
is_test := dir.ends_with('_test.v')
is_script := dir.ends_with('.v')
if is_script && !os.file_exists(dir) {
println('`$dir` does not exist')
exit(1)
2019-06-22 20:20:28 +02:00
}
// No -o provided? foo.v => foo
if out_name == 'a.out' && dir.ends_with('.v') {
out_name = dir.left(dir.len - 2)
}
// if we are in `/foo` and run `v .`, the executable should be `foo`
if dir == '.' && out_name == 'a.out' {
2019-09-13 17:59:17 +02:00
base := os.getwd().all_after(os.PathSeparator)
2019-06-22 20:20:28 +02:00
out_name = base.trim_space()
}
mut _os := OS.mac
2019-06-22 20:20:28 +02:00
// No OS specifed? Use current system
if target_os == '' {
$if linux {
2019-08-17 21:19:37 +02:00
_os = .linux
2019-06-22 20:20:28 +02:00
}
$if mac {
_os = .mac
2019-06-22 20:20:28 +02:00
}
$if windows {
_os = .windows
2019-06-22 20:20:28 +02:00
}
2019-07-15 17:24:40 +02:00
$if freebsd {
2019-08-17 21:19:37 +02:00
_os = .freebsd
2019-07-15 17:24:40 +02:00
}
2019-07-15 20:18:05 +02:00
$if openbsd {
2019-08-17 21:19:37 +02:00
_os = .openbsd
2019-07-15 20:18:05 +02:00
}
$if netbsd {
2019-08-17 21:19:37 +02:00
_os = .netbsd
2019-07-15 20:18:05 +02:00
}
$if dragonfly {
2019-08-17 21:19:37 +02:00
_os = .dragonfly
2019-07-15 20:18:05 +02:00
}
2019-09-26 23:30:41 +02:00
$if solaris {
_os = .solaris
}
2019-06-22 20:20:28 +02:00
}
else {
switch target_os {
case 'linux': _os = .linux
case 'windows': _os = .windows
case 'mac': _os = .mac
case 'freebsd': _os = .freebsd
case 'openbsd': _os = .openbsd
case 'netbsd': _os = .netbsd
case 'dragonfly': _os = .dragonfly
case 'msvc': _os = .msvc
2019-09-14 22:48:30 +02:00
case 'js': _os = .js
2019-09-26 23:30:41 +02:00
case 'solaris': _os = .solaris
2019-06-22 20:20:28 +02:00
}
}
2019-09-14 22:48:30 +02:00
//println('OS=$_os')
2019-10-04 14:48:09 +02:00
// Location of all vlib files
2019-08-17 21:19:37 +02:00
vroot := os.dir(os.executable())
//println('VROOT=$vroot')
// v.exe's parent directory should contain vlib
if !os.dir_exists(vroot) || !os.dir_exists(vroot + '/vlib/builtin') {
2019-09-30 22:56:53 +02:00
println('vlib not found, downloading it...')
ret := os.system('git clone --depth=1 https://github.com/vlang/v .')
if ret != 0 {
println('failed to `git clone` vlib')
println('make sure you are online and have git installed')
exit(1)
}
//println('vlib not found. It should be next to the V executable. ')
//println('Go to https://vlang.io to install V.')
//exit(1)
2019-08-17 21:19:37 +02:00
}
//println('out_name:$out_name')
mut out_name_c := os.realpath('${out_name}.tmp.c')
cflags := get_cmdline_cflags(args)
rdir := os.realpath( dir )
rdir_name := os.filename( rdir )
obfuscate := '-obf' in args
is_repl := '-repl' in args
pref := &Preferences {
2019-06-22 20:20:28 +02:00
is_test: is_test
is_script: is_script
is_so: '-shared' in args
is_prod: '-prod' in args
is_verbose: '-verbose' in args || '--verbose' in args
is_debuggable: '-g' in args
is_debug: '-debug' in args || '-g' in args
is_stats: '-stats' in args
obfuscate: obfuscate
is_prof: '-prof' in args
is_live: '-live' in args
sanitize: '-sanitize' in args
nofmt: '-nofmt' in args
show_c_cmd: '-show_c_cmd' in args
translated: 'translated' in args
is_run: 'run' in args
autofree: '-autofree' in args
2019-09-19 14:52:38 +02:00
compress: '-compress' in args
2019-09-08 16:19:42 +02:00
is_repl: is_repl
build_mode: build_mode
cflags: cflags
ccompiler: find_c_compiler()
2019-09-13 17:59:17 +02:00
building_v: !is_repl && (rdir_name == 'compiler' || dir.contains('vlib'))
2019-08-17 21:19:37 +02:00
}
2019-07-08 03:42:36 +02:00
if pref.is_verbose || pref.is_debug {
println('C compiler=$pref.ccompiler')
}
if pref.is_so {
2019-09-13 17:59:17 +02:00
out_name_c = out_name.all_after(os.PathSeparator) + '_shared_lib.c'
}
2019-08-31 15:38:13 +02:00
return &V{
os: _os
out_name: out_name
dir: dir
2019-08-17 21:19:37 +02:00
lang_dir: vroot
table: new_table(obfuscate)
out_name_c: out_name_c
cgen: new_cgen(out_name_c)
2019-08-17 21:19:37 +02:00
vroot: vroot
pref: pref
2019-08-17 21:19:37 +02:00
mod: mod
vgen_buf: vgen_buf
2019-06-22 20:20:28 +02:00
}
}
fn env_vflags_and_os_args() []string {
mut args := []string
vflags := os.getenv('VFLAGS')
if '' != vflags {
args << os.args[0]
args << vflags.split(' ')
if os.args.len > 1 {
args << os.args.right(1)
}
} else{
args << os.args
}
return args
}
2019-07-31 04:40:38 +02:00
fn update_v() {
2019-08-17 21:19:37 +02:00
println('Updating V...')
vroot := os.dir(os.executable())
s := os.exec('git -C "$vroot" pull --rebase origin master') or {
verror(err)
2019-08-29 02:30:17 +02:00
return
}
2019-08-17 21:19:37 +02:00
println(s.output)
$if windows {
v_backup_file := '$vroot/v_old.exe'
if os.file_exists( v_backup_file ) {
os.rm( v_backup_file )
}
os.mv('$vroot/v.exe', v_backup_file)
s2 := os.exec('"$vroot/make.bat"') or {
verror(err)
2019-08-29 02:30:17 +02:00
return
}
2019-08-17 21:19:37 +02:00
println(s2.output)
} $else {
s2 := os.exec('make -C "$vroot"') or {
verror(err)
2019-08-29 02:30:17 +02:00
return
}
2019-08-17 21:19:37 +02:00
println(s2.output)
}
}
2019-08-01 01:34:28 +02:00
fn vfmt(args[]string) {
file := args.last()
if !os.file_exists(file) {
println('"$file" does not exist')
exit(1)
}
if !file.ends_with('.v') {
println('v fmt can only be used on .v files')
exit(1)
}
println('vfmt is temporarily disabled')
}
2019-09-10 10:22:16 +02:00
fn install_v(args[]string) {
if args.len < 3 {
println('usage: v install [module] [module] [...]')
return
}
names := args.slice(2, args.len)
vexec := os.executable()
vroot := os.dir(vexec)
vget := '$vroot/tools/vget'
if true {
//println('Building vget...')
os.chdir(vroot + '/tools')
vget_compilation := os.exec('$vexec -o $vget vget.v') or {
verror(err)
2019-09-10 10:22:16 +02:00
return
}
if vget_compilation.exit_code != 0 {
verror( vget_compilation.output )
2019-09-10 10:22:16 +02:00
return
}
}
vgetresult := os.exec('$vget ' + names.join(' ')) or {
verror(err)
2019-09-10 10:22:16 +02:00
return
}
if vgetresult.exit_code != 0 {
verror( vgetresult.output )
2019-09-10 10:22:16 +02:00
return
}
}
2019-09-28 13:40:09 +02:00
fn (v &V) test_vget() {
2019-09-28 14:18:48 +02:00
/*
2019-09-28 13:58:24 +02:00
vexe := os.executable()
ret := os.system('$vexe install nedpals.args')
2019-09-28 13:40:09 +02:00
if ret != 0 {
println('failed to run v install')
exit(1)
}
if !os.file_exists(v_modules_path + '/nedpals/args') {
2019-09-28 13:40:09 +02:00
println('v failed to install a test module')
exit(1)
}
println('vget is OK')
2019-09-28 14:18:48 +02:00
*/
2019-09-28 13:40:09 +02:00
}
fn (v &V) test_v() {
2019-09-30 20:58:02 +02:00
args := env_vflags_and_os_args()
vexe := os.executable()
parent_dir := os.dir(vexe)
if !os.dir_exists(parent_dir + '/vlib') {
2019-09-30 21:39:52 +02:00
println('vlib/ is missing, it must be next to the V executable')
exit(1)
}
if !os.dir_exists(parent_dir + '/compiler') {
println('compiler/ is missing, it must be next to the V executable')
exit(1)
}
// Make sure v.c can be compiled without warnings
$if mac {
os.system('$vexe -o v.c compiler')
if os.system('cc -Werror v.c') != 0 {
println('cc failed to build v.c without warnings')
exit(1)
}
println('v.c can be compiled without warnings. This is good :)')
}
// Emily: pass args from the invocation to the test
// e.g. `v -g -os msvc test v` -> `$vexe -g -os msvc $file`
mut joined_args := args.right(1).join(' ')
joined_args = joined_args.left(joined_args.last_index('test'))
// println('$joined_args')
2019-09-01 13:14:39 +02:00
mut failed := false
2019-09-30 20:58:02 +02:00
test_files := os.walk_ext(parent_dir, '_test.v')
2019-09-16 20:26:05 +02:00
ok := term.ok_message('OK')
fail := term.fail_message('FAIL')
println('Testing...')
2019-09-16 20:26:05 +02:00
mut tmark := benchmark.new_benchmark()
for dot_relative_file in test_files {
relative_file := dot_relative_file.replace('./', '')
file := os.realpath( relative_file )
tmpc_filepath := file.replace('_test.v', '_test.tmp.c')
mut cmd := '"$vexe" $joined_args -debug "$file"'
if os.user_os() == 'windows' { cmd = '"$cmd"' }
tmark.step()
r := os.exec(cmd) or {
tmark.fail()
2019-09-01 13:14:39 +02:00
failed = true
println(tmark.step_message('$relative_file $fail'))
2019-09-01 13:14:39 +02:00
continue
}
if r.exit_code != 0 {
2019-09-01 13:14:39 +02:00
failed = true
tmark.fail()
println(tmark.step_message('$relative_file $fail\n`$file`\n (\n$r.output\n)'))
2019-08-17 21:19:37 +02:00
} else {
tmark.ok()
println(tmark.step_message('$relative_file $ok'))
2019-08-17 21:19:37 +02:00
}
os.rm( tmpc_filepath )
2019-08-17 21:19:37 +02:00
}
tmark.stop()
println( tmark.total_message('running V tests') )
2019-08-17 21:19:37 +02:00
println('\nBuilding examples...')
2019-09-30 21:39:52 +02:00
examples := os.walk_ext(parent_dir + '/examples', '.v')
mut bmark := benchmark.new_benchmark()
for relative_file in examples {
if relative_file.contains('vweb') {
continue
}
file := os.realpath( relative_file )
tmpc_filepath := file.replace('.v', '.tmp.c')
mut cmd := '"$vexe" $joined_args -debug "$file"'
if os.user_os() == 'windows' { cmd = '"$cmd"' }
bmark.step()
r := os.exec(cmd) or {
2019-09-01 13:14:39 +02:00
failed = true
bmark.fail()
println(bmark.step_message('$relative_file $fail'))
2019-09-01 13:14:39 +02:00
continue
}
if r.exit_code != 0 {
2019-09-01 13:14:39 +02:00
failed = true
bmark.fail()
println(bmark.step_message('$relative_file $fail \n`$file`\n (\n$r.output\n)'))
2019-08-17 21:19:37 +02:00
} else {
bmark.ok()
println(bmark.step_message('$relative_file $ok'))
2019-08-17 21:19:37 +02:00
}
os.rm(tmpc_filepath)
2019-08-17 21:19:37 +02:00
}
bmark.stop()
println( bmark.total_message('building examples') )
2019-09-28 13:40:09 +02:00
v.test_vget()
2019-09-01 13:14:39 +02:00
if failed {
exit(1)
}
2019-08-17 21:19:37 +02:00
}
2019-08-16 08:58:27 +02:00
2019-08-27 18:35:48 +02:00
fn create_symlink() {
vexe := os.executable()
link_path := '/usr/local/bin/v'
2019-08-31 15:38:13 +02:00
ret := os.system('ln -sf $vexe $link_path')
if ret == 0 {
println('symlink "$link_path" has been created')
} else {
println('failed to create symlink "$link_path", '+
'make sure you run with sudo')
}
2019-08-27 18:35:48 +02:00
}
pub fn verror(s string) {
2019-08-29 02:30:17 +02:00
println('V error: $s')
os.flush_stdout()
exit(1)
}
fn vhash() string {
mut buf := [50]byte
buf[0] = 0
2019-09-16 20:26:05 +02:00
C.snprintf(*char(buf), 50, '%s', C.V_COMMIT_HASH )
return tos_clone(buf)
}