v/vlib/v/builder/builder.v

599 lines
16 KiB
V
Raw Normal View History

module builder
2020-04-16 15:35:19 +02:00
import os
import v.token
2020-04-16 15:35:19 +02:00
import v.pref
import v.errors
2020-04-16 15:35:19 +02:00
import v.util
import v.ast
2020-04-16 15:35:19 +02:00
import v.vmod
import v.checker
import v.transformer
2020-04-16 15:35:19 +02:00
import v.parser
import v.markused
import v.depgraph
2021-07-24 09:25:16 +02:00
import v.callgraph
import v.dotgraph
pub struct Builder {
pub:
compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .`
module_path string
2020-05-09 18:34:04 +02:00
pub mut:
checker &checker.Checker
transformer &transformer.Transformer
out_name_c string
out_name_js string
stats_lines int // size of backend generated source code in lines
stats_bytes int // size of backend generated source code in bytes
nr_errors int // accumulated error count of scanner, parser, checker, and builder
nr_warnings int // accumulated warning count of scanner, parser, checker, and builder
nr_notices int // accumulated notice count of scanner, parser, checker, and builder
pref &pref.Preferences
2020-05-09 18:34:04 +02:00
module_search_paths []string
parsed_files []&ast.File
2020-07-26 15:54:18 +02:00
cached_msvc MsvcResult
table &ast.Table
ccoptions CcompilerOptions
//
// NB: changes in mod `builtin` force invalidation of every other .v file
mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os`
mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os`
path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os`
}
pub fn new_builder(pref &pref.Preferences) Builder {
2020-04-07 00:44:19 +02:00
rdir := os.real_path(pref.path)
compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
mut table := ast.new_table()
table.is_fmt = false
if pref.use_color == .always {
util.emanager.set_support_color(true)
}
if pref.use_color == .never {
util.emanager.set_support_color(false)
}
msvc := find_msvc(pref.m64) or {
2020-07-07 20:35:49 +02:00
if pref.ccompiler == 'msvc' {
// verror('Cannot find MSVC on this OS')
2020-07-07 20:35:49 +02:00
}
2020-07-26 15:54:18 +02:00
MsvcResult{
valid: false
}
2020-07-07 20:35:49 +02:00
}
util.timing_set_should_print(pref.show_timings || pref.is_verbose)
if pref.show_callgraph || pref.show_depgraph {
dotgraph.start_digraph()
}
2020-04-16 15:35:19 +02:00
return Builder{
pref: pref
table: table
2021-02-05 08:27:14 +01:00
checker: checker.new_checker(table, pref)
transformer: transformer.new_transformer_with_table(table, pref)
2020-04-07 00:44:19 +02:00
compiled_dir: compiled_dir
2020-07-07 20:35:49 +02:00
cached_msvc: msvc
}
}
pub fn (mut b Builder) front_stages(v_files []string) ? {
mut timers := util.get_timers()
util.timing_start('PARSE')
util.timing_start('Builder.front_stages.parse_files')
b.parsed_files = parser.parse_files(v_files, b.table, b.pref)
timers.show('Builder.front_stages.parse_files')
b.parse_imports()
timers.show('SCAN')
timers.show('PARSE')
timers.show_if_exists('PARSE stmt')
if b.pref.only_check_syntax {
return error_with_code('stop_after_parser', 9999)
}
}
pub fn (mut b Builder) middle_stages() ? {
util.timing_start('CHECK')
util.timing_start('Checker.generic_insts_to_concrete')
b.table.generic_insts_to_concrete()
util.timing_measure('Checker.generic_insts_to_concrete')
b.checker.check_files(b.parsed_files)
util.timing_measure('CHECK')
b.print_warnings_and_errors()
if b.checker.should_abort {
return error('too many errors/warnings/notices')
}
if b.pref.check_only {
return error_with_code('stop_after_checker', 9999)
}
util.timing_start('TRANSFORM')
b.transformer.transform_files(b.parsed_files)
util.timing_measure('TRANSFORM')
//
b.table.complete_interface_check()
if b.pref.skip_unused {
markused.mark_used(mut b.table, b.pref, b.parsed_files)
}
2021-07-24 09:25:16 +02:00
if b.pref.show_callgraph {
callgraph.show(mut b.table, b.pref, b.parsed_files)
}
}
pub fn (mut b Builder) front_and_middle_stages(v_files []string) ? {
b.front_stages(v_files) ?
b.middle_stages() ?
}
2020-02-03 07:31:54 +01:00
// parse all deps from already parsed files
2020-04-25 17:49:16 +02:00
pub fn (mut b Builder) parse_imports() {
util.timing_start(@METHOD)
defer {
util.timing_measure(@METHOD)
}
mut done_imports := []string{}
if b.pref.is_vsh {
2020-07-26 15:54:18 +02:00
done_imports << 'os'
}
// TODO (joe): decide if this is correct solution.
// in the case of building a module, the actual module files
// are passed via cmd line, so they have already been parsed
// by this stage. note that if one files from a module was
// parsed (but not all of them), then this will cause a problem.
// we could add a list of parsed files instead, but I think
// there is a better solution all around, I will revisit this.
// NOTE: there is a very similar occurance with the way
// internal module test's work, and this was the reason there
// were issues with duplicate declarations, so we should sort
// that out in a similar way.
2020-07-26 15:54:18 +02:00
for file in b.parsed_files {
if file.mod.name != 'main' && file.mod.name !in done_imports {
done_imports << file.mod.name
}
2020-07-26 15:54:18 +02:00
}
// NB: b.parsed_files is appended in the loop,
// so we can not use the shorter `for in` form.
2020-04-16 15:35:19 +02:00
for i := 0; i < b.parsed_files.len; i++ {
2020-02-03 07:31:54 +01:00
ast_file := b.parsed_files[i]
b.path_invalidates_mods[ast_file.path] << ast_file.mod.name
if ast_file.mod.name != 'builtin' {
b.mod_invalidates_paths['builtin'] << ast_file.path
b.mod_invalidates_mods['builtin'] << ast_file.mod.name
}
for imp in ast_file.imports {
2020-02-03 07:31:54 +01:00
mod := imp.mod
b.mod_invalidates_paths[mod] << ast_file.path
b.mod_invalidates_mods[mod] << ast_file.mod.name
if mod == 'builtin' {
b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"',
ast_file.path, imp.pos)
break
}
2020-02-03 07:31:54 +01:00
if mod in done_imports {
continue
}
import_path := b.find_module_path(mod, ast_file.path) or {
// v.parsers[i].error_with_token_index('cannot import module "$mod" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod))
// break
b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (not found)',
ast_file.path, imp.pos)
break
2020-02-03 07:31:54 +01:00
}
v_files := b.v_files_from_dir(import_path)
if v_files.len == 0 {
// v.parsers[i].error_with_token_index('cannot import module "$mod" (no .v files in "$import_path")', v.parsers[i].import_ast.get_import_tok_idx(mod))
b.parsed_files[i].errors << b.error_with_pos('cannot import module "$mod" (no .v files in "$import_path")',
ast_file.path, imp.pos)
continue
2020-02-03 07:31:54 +01:00
}
// eprintln('>> ast_file.path: $ast_file.path , done: $done_imports, `import $mod` => $v_files')
2020-02-03 07:31:54 +01:00
// Add all imports referenced by these libs
parsed_files := parser.parse_files(v_files, b.table, b.pref)
2020-02-03 07:31:54 +01:00
for file in parsed_files {
mut name := file.mod.name
if name == '' {
name = file.mod.short_name
}
sname := name.all_after_last('.')
smod := mod.all_after_last('.')
if sname != smod {
msg := 'bad module definition: $ast_file.path imports module "$mod" but $file.path is defined as module `$name`'
b.parsed_files[i].errors << b.error_with_pos(msg, ast_file.path, imp.pos)
2020-02-03 07:31:54 +01:00
}
}
b.parsed_files << parsed_files
done_imports << mod
}
}
b.resolve_deps()
if b.pref.print_v_files {
for p in b.parsed_files {
println(p.path)
}
exit(0)
}
b.rebuild_modules()
}
2020-04-25 17:49:16 +02:00
pub fn (mut b Builder) resolve_deps() {
util.timing_start(@METHOD)
defer {
util.timing_measure(@METHOD)
}
graph := b.import_graph()
deps_resolved := graph.resolve()
if b.pref.is_verbose {
eprintln('------ resolved dependencies graph: ------')
eprintln(deps_resolved.display())
eprintln('------------------------------------------')
}
if b.pref.show_depgraph {
depgraph.show(deps_resolved, b.pref.path)
}
cycles := deps_resolved.display_cycles()
if cycles.len > 1 {
verror('error: import cycle detected between the following modules: \n' + cycles)
}
mut mods := []string{}
for node in deps_resolved.nodes {
mods << node.name
}
if b.pref.is_verbose {
eprintln('------ imported modules: ------')
eprintln(mods.str())
eprintln('-------------------------------')
}
mut reordered_parsed_files := []&ast.File{}
for m in mods {
for pf in b.parsed_files {
if m == pf.mod.name {
reordered_parsed_files << pf
2020-04-16 15:35:19 +02:00
// eprintln('pf.mod.name: $pf.mod.name | pf.path: $pf.path')
}
}
}
b.table.modules = mods
b.parsed_files = reordered_parsed_files
}
// graph of all imported modules
pub fn (b &Builder) import_graph() &depgraph.DepGraph {
2020-12-20 15:21:32 +01:00
builtins := util.builtin_module_parts.clone()
2020-04-23 01:16:58 +02:00
mut graph := depgraph.new_dep_graph()
for p in b.parsed_files {
// eprintln('p.path: $p.path')
mut deps := []string{}
if p.mod.name !in builtins {
deps << 'builtin'
if b.pref.backend == .c {
// TODO JavaScript backend doesn't handle os for now
if b.pref.is_vsh && p.mod.name !in ['os', 'dl'] {
deps << 'os'
}
}
}
for m in p.imports {
if m.mod == p.mod.name {
continue
}
deps << m.mod
}
graph.add(p.mod.name, deps)
}
$if trace_import_graph ? {
eprintln(graph.display())
}
return graph
2020-02-03 07:31:54 +01:00
}
2020-04-07 00:44:19 +02:00
pub fn (b Builder) v_files_from_dir(dir string) []string {
2020-02-03 07:31:54 +01:00
if !os.exists(dir) {
if dir == 'compiler' && os.is_dir('vlib') {
println('looks like you are trying to build V with an old command')
2020-02-09 10:08:04 +01:00
println('use `v -o v cmd/v` instead of `v -o v compiler`')
2020-02-03 07:31:54 +01:00
}
verror("$dir doesn't exist")
2020-04-07 00:44:19 +02:00
} else if !os.is_dir(dir) {
2020-03-24 11:14:11 +01:00
verror("$dir isn't a directory!")
2020-02-03 07:31:54 +01:00
}
mut files := os.ls(dir) or { panic(err) }
2020-04-07 00:44:19 +02:00
if b.pref.is_verbose {
2020-02-03 07:31:54 +01:00
println('v_files_from_dir ("$dir")')
}
return b.pref.should_compile_filtered_files(dir, files)
}
2020-04-07 00:44:19 +02:00
pub fn (b Builder) log(s string) {
if b.pref.is_verbose {
2020-02-03 07:31:54 +01:00
println(s)
}
}
2020-03-01 21:56:07 +01:00
2020-04-07 00:44:19 +02:00
pub fn (b Builder) info(s string) {
if b.pref.is_verbose {
2020-03-31 19:58:44 +02:00
println(s)
}
}
2020-03-01 21:56:07 +01:00
[inline]
pub fn module_path(mod string) string {
2020-03-01 21:56:07 +01:00
// submodule support
2020-03-07 22:26:26 +01:00
return mod.replace('.', os.path_separator)
2020-03-01 21:56:07 +01:00
}
2021-02-05 08:05:13 +01:00
// TODO: try to merge this & util.module functions to create a
// reliable multi use function. see comments in util/module.v
pub fn (b &Builder) find_module_path(mod string, fpath string) ?string {
// support @VROOT/v.mod relative paths:
mut mcache := vmod.get_cache()
vmod_file_location := mcache.get_by_file(fpath)
2020-03-01 21:56:07 +01:00
mod_path := module_path(mod)
mut module_lookup_paths := []string{}
if vmod_file_location.vmod_file.len != 0
&& vmod_file_location.vmod_folder !in b.module_search_paths {
module_lookup_paths << vmod_file_location.vmod_folder
}
module_lookup_paths << b.module_search_paths
module_lookup_paths << os.getwd()
// go up through parents looking for modules a folder.
// we need a proper solution that works most of the time. look at vdoc.get_parent_mod
if fpath.contains(os.path_separator + 'modules' + os.path_separator) {
parts := fpath.split(os.path_separator)
for i := parts.len - 2; i >= 0; i-- {
if parts[i] == 'modules' {
module_lookup_paths << parts[0..i + 1].join(os.path_separator)
break
}
}
}
for search_path in module_lookup_paths {
2020-04-07 00:44:19 +02:00
try_path := os.join_path(search_path, mod_path)
if b.pref.is_verbose {
2020-03-01 21:56:07 +01:00
println(' >> trying to find $mod in $try_path ..')
}
if os.is_dir(try_path) {
2020-04-07 00:44:19 +02:00
if b.pref.is_verbose {
2020-03-01 21:56:07 +01:00
println(' << found $try_path .')
}
return try_path
}
}
// look up through parents
path_parts := fpath.split(os.path_separator)
for i := path_parts.len - 2; i > 0; i-- {
p1 := path_parts[0..i].join(os.path_separator)
try_path := os.join_path(p1, mod_path)
if b.pref.is_verbose {
println(' >> trying to find $mod in $try_path ..')
}
if os.is_dir(try_path) {
return try_path
}
}
smodule_lookup_paths := module_lookup_paths.join(', ')
return error('module "$mod" not found in:\n$smodule_lookup_paths')
2020-03-01 21:56:07 +01:00
}
pub fn (b &Builder) show_total_warns_and_errors_stats() {
if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 {
return
}
if b.pref.is_stats {
mut nr_errors := b.checker.errors.len
mut nr_warnings := b.checker.warnings.len
mut nr_notices := b.checker.notices.len
if b.pref.check_only {
nr_errors = b.nr_errors
nr_warnings = b.nr_warnings
nr_notices = b.nr_notices
}
estring := util.bold(nr_errors.str())
wstring := util.bold(nr_warnings.str())
nstring := util.bold(nr_notices.str())
if b.pref.check_only {
println('summary: $estring V errors, $wstring V warnings, $nstring V notices')
} else {
println('checker summary: $estring V errors, $wstring V warnings, $nstring V notices')
}
}
}
pub fn (mut b Builder) print_warnings_and_errors() {
2020-07-26 15:54:18 +02:00
defer {
b.show_total_warns_and_errors_stats()
}
for file in b.parsed_files {
b.nr_errors += file.errors.len
b.nr_warnings += file.warnings.len
b.nr_notices += file.notices.len
}
if b.pref.output_mode == .silent {
if b.nr_errors > 0 {
exit(1)
}
return
}
if b.pref.check_only {
for file in b.parsed_files {
if !b.pref.skip_warnings {
for err in file.notices {
kind := if b.pref.is_verbose {
'$err.reporter notice #$b.nr_notices:'
} else {
'notice:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
}
for file in b.parsed_files {
for err in file.errors {
kind := if b.pref.is_verbose {
'$err.reporter error #$b.nr_errors:'
} else {
'error:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
for file in b.parsed_files {
if !b.pref.skip_warnings {
for err in file.warnings {
kind := if b.pref.is_verbose {
'$err.reporter warning #$b.nr_warnings:'
} else {
'warning:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
}
b.show_total_warns_and_errors_stats()
if b.nr_errors > 0 {
exit(1)
}
exit(0)
}
2020-05-09 15:16:48 +02:00
if b.pref.is_verbose && b.checker.nr_warnings > 1 {
println('$b.checker.nr_warnings warnings')
}
if b.pref.is_verbose && b.checker.nr_notices > 1 {
println('$b.checker.nr_notices notices')
}
if b.checker.nr_notices > 0 && !b.pref.skip_warnings {
for err in b.checker.notices {
kind := if b.pref.is_verbose {
'$err.reporter notice #$b.checker.nr_notices:'
} else {
'notice:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
}
if b.checker.nr_warnings > 0 && !b.pref.skip_warnings {
for err in b.checker.warnings {
kind := if b.pref.is_verbose {
'$err.reporter warning #$b.checker.nr_warnings:'
} else {
'warning:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
2020-04-13 01:56:01 +02:00
}
2020-05-09 15:16:48 +02:00
//
if b.pref.is_verbose && b.checker.nr_errors > 1 {
println('$b.checker.nr_errors errors')
}
if b.checker.nr_errors > 0 {
for err in b.checker.errors {
kind := if b.pref.is_verbose {
'$err.reporter error #$b.checker.nr_errors:'
} else {
'error:'
}
ferror := util.formatted_error(kind, err.message, err.file_path, err.pos)
eprintln(ferror)
if err.details.len > 0 {
eprintln('Details: $err.details')
}
}
b.show_total_warns_and_errors_stats()
exit(1)
}
if b.table.redefined_fns.len > 0 {
mut total_conflicts := 0
for fn_name in b.table.redefined_fns {
// Find where this function was already declared
mut redefines := []FunctionRedefinition{}
mut redefine_conflicts := map[string]int{}
for file in b.parsed_files {
for stmt in file.stmts {
if stmt is ast.FnDecl {
if stmt.name == fn_name {
fheader := stmt.stringify(b.table, 'main', map[string]string{})
redefines << FunctionRedefinition{
fpath: file.path
fline: stmt.pos.line_nr
f: stmt
fheader: fheader
}
redefine_conflicts[fheader]++
}
}
}
}
if redefines.len > 0 {
eprintln('redefinition of function `$fn_name`')
for redefine in redefines {
eprintln(util.formatted_error('conflicting declaration:', redefine.fheader,
redefine.fpath, redefine.f.pos))
}
total_conflicts++
}
}
if total_conflicts > 0 {
b.show_total_warns_and_errors_stats()
exit(1)
}
}
}
struct FunctionRedefinition {
fpath string
fline int
fheader string
f ast.FnDecl
}
pub fn (b &Builder) error_with_pos(s string, fpath string, pos token.Pos) errors.Error {
if !b.pref.check_only {
ferror := util.formatted_error('builder error:', s, fpath, pos)
eprintln(ferror)
exit(1)
}
return errors.Error{
file_path: fpath
pos: pos
reporter: .builder
message: s
}
}
[noreturn]
pub fn verror(s string) {
util.verror('builder error', s)
}