v2 / vlib / v / builder / builder.v
1149 lines · 1080 sloc · 33.53 KB · b2efb322c44c336a7d88f8e5425c469d8f2dcdc6
Raw
1module builder
2
3import os
4import v.token
5import v.pref
6import v.errors
7import v.util
8import v.ast
9import v.ast.walker
10import v.vmod
11import v.checker
12import v.transformer
13import v.comptime
14import v.generics
15import v.parser
16import v.markused
17import v.depgraph
18import v.callgraph
19import v.dotgraph
20// import x.json2
21
22fn append_map_array(mut items map[string][]string, key string, value string) {
23 if key !in items {
24 items[key] = []string{}
25 }
26 items[key] << value
27}
28
29pub struct Builder {
30pub:
31 compiled_dir string // contains os.real_path() of the dir of the final file being compiled, or the dir itself when doing `v .`
32 module_path string
33pub mut:
34 checker &checker.Checker = unsafe { nil }
35 transformer &transformer.Transformer = unsafe { nil }
36 comptime &comptime.Comptime = unsafe { nil }
37 generics &generics.Generics = unsafe { nil }
38 out_name_c string
39 out_name_js string
40 stats_lines int // size of backend generated source code in lines
41 stats_bytes int // size of backend generated source code in bytes
42 nr_errors int // accumulated error count of scanner, parser, checker, and builder
43 nr_warnings int // accumulated warning count of scanner, parser, checker, and builder
44 nr_notices int // accumulated notice count of scanner, parser, checker, and builder
45 pref &pref.Preferences = unsafe { nil }
46 module_search_paths []string
47 parsed_files []&ast.File
48 //$if windows {
49 cached_msvc MsvcResult
50 //}
51 table &ast.Table = unsafe { nil }
52 ccoptions CcompilerOptions
53 // Note: changes in mod `builtin` force invalidation of every other .v file
54 mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os`
55 mod_invalidates_mods map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os`
56 path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os`
57 crun_cache_keys []string // target executable + top level source files; filled in by Builder.should_rebuild
58 executable_exists bool // if the executable already exists, don't remove new executable after `v run`
59 str_args string // for parallel_cc mode only, to know which cc args to use (like -I etc)
60 disable_flto bool
61}
62
63struct CFunctionCallCollector {
64mut:
65 names map[string]bool
66}
67
68fn c_function_call_collector_visit(node &ast.Node, data voidptr) bool {
69 mut c := unsafe { &CFunctionCallCollector(data) }
70 if node is ast.Expr && node is ast.CallExpr {
71 call := node as ast.CallExpr
72 if call.name.starts_with('C.') {
73 c.names[call.name] = true
74 }
75 }
76 return true
77}
78
79pub fn new_builder(pref_ &pref.Preferences) Builder {
80 rdir := os.real_path(pref_.path)
81 compiled_dir := if os.is_dir(rdir) { rdir } else { os.dir(rdir) }
82 mut table := ast.new_table()
83 table.is_fmt = false
84 if pref_.use_color == .always {
85 util.emanager.set_support_color(true)
86 }
87 if pref_.use_color == .never {
88 util.emanager.set_support_color(false)
89 }
90 table.pointer_size = if pref_.m64 && pref_.backend != .wasm { 8 } else { 4 }
91 mut msvc := MsvcResult{}
92 if pref_.ccompiler_type == .msvc || pref.cc_from_string(pref_.ccompiler) == .msvc {
93 $if windows {
94 msvc = find_msvc(pref_.m64) or {
95 MsvcResult{
96 valid: false
97 }
98 }
99 }
100 }
101 util.timing_set_should_print(pref_.show_timings || pref_.is_verbose)
102 if pref_.show_callgraph || pref_.show_depgraph {
103 dotgraph.start_digraph()
104 }
105 mut executable_name := pref_.out_name
106 $if windows {
107 executable_name += '.exe'
108 }
109 return Builder{
110 pref: pref_
111 table: table
112 checker: checker.new_checker(table, pref_)
113 transformer: transformer.new_transformer_with_table(table, pref_)
114 comptime: comptime.new_comptime_with_table(table, pref_)
115 generics: generics.new_generics_with_table(table, pref_)
116 compiled_dir: compiled_dir
117 cached_msvc: msvc
118 executable_exists: os.is_file(executable_name)
119 }
120}
121
122fn (v &Builder) msvc_object_path(path string) string {
123 path_without_obj_postfix := if path.ends_with('.obj') {
124 path[..path.len - 4]
125 } else if path.ends_with('.o') {
126 path[..path.len - 2]
127 } else {
128 path
129 }
130 return os.real_path(if v.pref.is_debug {
131 // MSVC debug builds use /MDd, so they need their own thirdparty object files.
132 '${path_without_obj_postfix}.debug.obj'
133 } else {
134 '${path_without_obj_postfix}.obj'
135 })
136}
137
138fn (mut v Builder) msvc_thirdparty_obj_path(mod string, path string, cached_path string) string {
139 base_path := if cached_path != '' {
140 cached_path
141 } else {
142 // Reuse the cache-derived .o path so different targets/options do not share one .obj.
143 v.pref.cache_manager.mod_postfix_with_key2cpath(mod, '.o', os.real_path(path))
144 }
145 return v.msvc_object_path(base_path)
146}
147
148pub fn (mut b Builder) interpret_text(code string, v_files []string) ! {
149 b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref)
150 b.parsed_files << parser.parse_text(code, '', mut b.table, .skip_comments, b.pref)
151 if b.should_stop_after_frontend_error() && b.has_frontend_errors() {
152 exit(1)
153 }
154 b.parse_imports()
155 b.check_unused_imports()
156 b.print_frontend_builder_errors()
157 if b.should_stop_after_frontend_error() && b.has_frontend_errors() {
158 exit(1)
159 }
160
161 if b.pref.only_check_syntax {
162 return error_with_code('stop_after_parser', 7001)
163 }
164
165 b.middle_stages()!
166}
167
168pub fn (mut b Builder) front_stages(v_files []string) ! {
169 mut timers := util.get_timers()
170 util.timing_start('ALL_FRONT_STAGES')
171 defer {
172 timers.show('ALL_FRONT_STAGES')
173 }
174 util.timing_start('PARSE')
175
176 util.timing_start('Builder.front_stages.parse_files')
177 b.parsed_files = parser.parse_files(v_files, mut b.table, b.pref)
178 timers.show('Builder.front_stages.parse_files')
179 if b.should_stop_after_frontend_error() && b.has_frontend_errors() {
180 exit(1)
181 }
182
183 b.parse_imports()
184 b.check_unused_imports()
185 b.print_frontend_builder_errors()
186 if b.should_stop_after_frontend_error() && b.has_frontend_errors() {
187 exit(1)
188 }
189
190 timers.show('SCAN')
191 timers.show('PARSE')
192 timers.show_if_exists('PARSE stmt')
193 if b.pref.only_check_syntax {
194 return error_with_code('stop_after_parser', 7001)
195 }
196}
197
198pub fn (mut b Builder) middle_stages() ! {
199 util.timing_start('CHECK')
200
201 util.timing_start('Checker.generic_insts_to_concrete')
202 b.table.generic_insts_to_concrete()
203 util.timing_measure('Checker.generic_insts_to_concrete')
204
205 b.checker.check_files(b.parsed_files)
206 util.timing_start('Checker.generic_insts_to_concrete.after_check')
207 b.table.generic_insts_to_concrete()
208 util.timing_measure('Checker.generic_insts_to_concrete.after_check')
209 util.timing_measure('CHECK')
210 $if trace_type_symbols_after_checker ? {
211 for t, s in b.table.type_symbols {
212 println('> t: ${t:10} | s.mod: ${s.mod:-40} | s.name: ${'${s.name#[..30]}':-30} | s.is_builtin: ${s.is_builtin:6} | s.is_pub: ${s.is_pub}')
213 }
214 }
215
216 if b.pref.new_generic_solver {
217 util.timing_start('GENERICS')
218 b.generics.solve_files(b.parsed_files)
219 util.timing_start('Checker.generic_insts_to_concrete.after_generics')
220 b.table.generic_insts_to_concrete()
221 util.timing_measure('Checker.generic_insts_to_concrete.after_generics')
222 util.timing_measure('GENERICS')
223 }
224
225 util.timing_start('COMPTIME')
226 b.comptime.solve_files(b.parsed_files)
227 util.timing_measure('COMPTIME')
228
229 if b.pref.dump_defines != '' {
230 b.dump_defines()
231 }
232 mut mcache := vmod.get_cache()
233 mcache.debug()
234 b.print_warnings_and_errors()
235 if b.checker.should_abort {
236 return error('too many errors/warnings/notices')
237 }
238 if b.checker.unresolved_fixed_sizes.len > 0 {
239 util.timing_start('Checker.update_unresolved_fixed_sizes')
240 b.checker.update_unresolved_fixed_sizes()
241 util.timing_measure('Checker.update_unresolved_fixed_sizes')
242 }
243 if b.pref.check_only {
244 return error_with_code('stop_after_checker', 8001)
245 }
246 util.timing_start('TRANSFORM')
247 b.transformer.transform_files(b.parsed_files)
248 util.timing_measure('TRANSFORM')
249
250 b.table.complete_interface_check()
251 if b.pref.skip_unused {
252 markused.mark_used(mut b.table, mut b.pref, b.parsed_files)
253 }
254 if b.pref.show_callgraph {
255 callgraph.show(mut b.table, b.pref, b.parsed_files)
256 }
257}
258
259pub fn (mut b Builder) front_and_middle_stages(v_files []string) ! {
260 b.front_stages(v_files)!
261 b.middle_stages()!
262}
263
264@[inline]
265fn (b &Builder) should_stop_after_frontend_error() bool {
266 return b.pref.fatal_errors
267 || (b.pref.output_mode == .stdout && !b.pref.check_only && !b.pref.is_vls)
268}
269
270@[inline]
271fn (b &Builder) has_frontend_errors() bool {
272 return b.parsed_files.any(it.errors.len > 0)
273}
274
275// parse all deps from already parsed files
276pub fn (mut b Builder) parse_imports() {
277 util.timing_start(@METHOD)
278 defer {
279 util.timing_measure(@METHOD)
280 }
281 mut done_imports := []string{}
282 if b.pref.is_vsh {
283 done_imports << 'os'
284 }
285 // TODO: (joe): decide if this is correct solution.
286 // in the case of building a module, the actual module files
287 // are passed via cmd line, so they have already been parsed
288 // by this stage. note that if one files from a module was
289 // parsed (but not all of them), then this will cause a problem.
290 // we could add a list of parsed files instead, but I think
291 // there is a better solution all around, I will revisit this.
292 // NOTE: there is a very similar occurrence with the way
293 // internal module test's work, and this was the reason there
294 // were issues with duplicate declarations, so we should sort
295 // that out in a similar way.
296 for file in b.parsed_files {
297 if file.mod.name != 'main' && file.mod.name !in done_imports {
298 done_imports << file.mod.name
299 }
300 }
301 // Note: b.parsed_files is appended in the loop,
302 // so we can not use the shorter `for in` form.
303 for i := 0; i < b.parsed_files.len; i++ {
304 ast_file := b.parsed_files[i]
305 append_map_array(mut b.path_invalidates_mods, ast_file.path, ast_file.mod.name)
306 if ast_file.mod.name != 'builtin' {
307 append_map_array(mut b.mod_invalidates_paths, 'builtin', ast_file.path)
308 append_map_array(mut b.mod_invalidates_mods, 'builtin', ast_file.mod.name)
309 }
310 for imp in ast_file.imports {
311 mod := imp.mod
312 append_map_array(mut b.mod_invalidates_paths, mod, ast_file.path)
313 append_map_array(mut b.mod_invalidates_mods, mod, ast_file.mod.name)
314 if mod == 'builtin' {
315 b.parsed_files[i].errors << b.error_with_pos('cannot import module "builtin"',
316 ast_file.path, imp.pos)
317 break
318 }
319 if mod in done_imports {
320 continue
321 }
322 import_path := b.find_module_path(mod, ast_file.path) or {
323 // v.parsers[i].error_with_token_index('cannot import module "${mod}" (not found)', v.parsers[i].import_ast.get_import_tok_idx(mod))
324 // break
325 b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (not found)',
326 ast_file.path, imp.pos)
327 break
328 }
329 v_files := b.v_files_from_dir(import_path)
330 if v_files.len == 0 {
331 // 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))
332 b.parsed_files[i].errors << b.error_with_pos('cannot import module "${mod}" (no .v files in "${import_path}")',
333 ast_file.path, imp.pos)
334 continue
335 }
336 // eprintln('>> ast_file.path: ${ast_file.path} , done: ${done_imports}, `import ${mod}` => ${v_files}')
337 // Add all imports referenced by these libs
338 parsed_files := parser.parse_files(v_files, mut b.table, b.pref)
339 for file in parsed_files {
340 mut name := file.mod.name
341 if name == '' {
342 name = file.mod.short_name
343 }
344 sname := name.all_after_last('.')
345 smod := mod.all_after_last('.')
346 if sname != smod {
347 msg := 'bad module definition: ${ast_file.path} imports module "${mod}" but ${file.path} is defined as module `${name}`'
348 b.parsed_files[i].errors << b.error_with_pos(msg, ast_file.path, imp.pos)
349 }
350 }
351 b.parsed_files << parsed_files
352 if b.should_stop_after_frontend_error() && parsed_files.any(it.errors.len > 0) {
353 return
354 }
355 done_imports << mod
356 }
357 }
358 b.resolve_deps()
359 $if trace_parsed_files ? {
360 b.show_parsed_files()
361 }
362 if b.pref.print_v_files {
363 b.show_parsed_files()
364 exit(0)
365 }
366 if b.pref.print_watched_files {
367 for p in b.parsed_files {
368 if p.is_parse_text {
369 // a generated snippet, `v watch` does not care about those, since they are duplicates for other files
370 continue
371 }
372 println(p.path)
373 for tp in p.template_paths {
374 println(tp)
375 }
376 }
377 exit(0)
378 }
379 if b.pref.dump_files != '' {
380 b.dump_files(b.parsed_files.map(it.path))
381 }
382 b.rebuild_modules()
383}
384
385pub fn (mut b Builder) resolve_deps() {
386 util.timing_start(@METHOD)
387 defer {
388 util.timing_measure(@METHOD)
389 }
390 graph := b.import_graph()
391 deps_resolved := graph.resolve()
392 if b.pref.is_verbose {
393 eprintln('------ resolved dependencies graph: ------')
394 eprintln(deps_resolved.display())
395 eprintln('------------------------------------------')
396 }
397 if b.pref.show_depgraph {
398 depgraph.show(deps_resolved, b.pref.path)
399 }
400 cycles := deps_resolved.display_cycles()
401 if cycles.len > 1 {
402 verror('error: import cycle detected between the following modules: \n' + cycles)
403 }
404 mut mods := []string{}
405 for node in deps_resolved.nodes {
406 mods << node.name
407 }
408 b.dump_modules(mods)
409 if b.pref.is_verbose {
410 eprintln('------ imported modules: ------')
411 eprintln(mods.str())
412 eprintln('-------------------------------')
413 }
414 unsafe {
415 mut reordered_parsed_files := []&ast.File{}
416 for m in mods {
417 for pf in b.parsed_files {
418 if m == pf.mod.name {
419 reordered_parsed_files << pf
420 // eprintln('pf.mod.name: ${pf.mod.name} | pf.path: ${pf.path}')
421 }
422 }
423 }
424 b.table.modules = mods
425 b.parsed_files = reordered_parsed_files
426 }
427}
428
429fn import_alias_for_mod(file &ast.File, mod string) ?string {
430 for import_m in file.imports {
431 if import_m.mod == mod {
432 return import_m.alias
433 }
434 }
435 return none
436}
437
438fn register_used_import(mut file ast.File, alias string) {
439 if alias !in file.used_imports {
440 file.used_imports << alias
441 }
442}
443
444fn file_needs_c_function_call_import_scan(file &ast.File) bool {
445 for import_m in file.imports {
446 alias := import_m.alias
447 if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports
448 || alias in file.auto_imports {
449 continue
450 }
451 return true
452 }
453 return false
454}
455
456fn (mut b Builder) collect_used_c_function_calls(file &ast.File) map[string]bool {
457 mut collector := CFunctionCallCollector{
458 names: map[string]bool{}
459 }
460 walker.inspect(file, &collector, c_function_call_collector_visit)
461 return collector.names
462}
463
464fn (mut b Builder) mark_imports_used_by_c_function_calls() {
465 for mut file in b.parsed_files {
466 if !file_needs_c_function_call_import_scan(file) {
467 continue
468 }
469 used_c_calls := b.collect_used_c_function_calls(file)
470 if used_c_calls.len == 0 {
471 continue
472 }
473 for c_name, _ in used_c_calls {
474 if c_fn := b.table.find_fn(c_name) {
475 alias := import_alias_for_mod(file, c_fn.mod) or { continue }
476 register_used_import(mut file, alias)
477 continue
478 }
479 c_typ := b.table.find_type(c_name)
480 if c_typ == 0 {
481 continue
482 }
483 c_sym := b.table.sym(c_typ)
484 if c_sym.kind == .placeholder {
485 continue
486 }
487 alias := import_alias_for_mod(file, c_sym.mod) or { continue }
488 register_used_import(mut file, alias)
489 }
490 }
491}
492
493fn (mut b Builder) add_unused_import_message(mut file ast.File, message string, pos token.Pos) {
494 file_path := if pos.file_idx < 0 { file.path } else { b.table.filelist[pos.file_idx] }
495 if b.pref.warns_are_errors {
496 err := errors.Error{
497 file_path: file_path
498 pos: pos
499 reporter: .parser
500 message: message
501 }
502 file.errors << err
503 if b.pref.output_mode == .stdout && !b.pref.check_only {
504 util.show_compiler_message('error:', err.CompilerMessage)
505 }
506 return
507 }
508 if b.pref.skip_warnings {
509 return
510 }
511 wrn := errors.Warning{
512 file_path: file_path
513 pos: pos
514 reporter: .parser
515 message: message
516 }
517 file.warnings << wrn
518 if b.pref.output_mode == .stdout && !b.pref.check_only {
519 util.show_compiler_message('warning:', wrn.CompilerMessage)
520 }
521}
522
523fn (mut b Builder) check_unused_imports() {
524 if b.pref.is_repl || b.pref.is_fmt {
525 return
526 }
527 b.mark_imports_used_by_c_function_calls()
528 for mut file in b.parsed_files {
529 for import_m in file.imports {
530 alias := import_m.alias
531 mod := import_m.mod
532 if (alias.len == 1 && alias[0] == `_`) || alias in file.used_imports
533 || alias in file.auto_imports {
534 continue
535 }
536 mod_alias := if alias == mod { alias } else { '${alias} (${mod})' }
537 b.add_unused_import_message(mut file,
538 "module '${mod_alias}' is imported but never used. Use `import ${mod_alias} as _`, to silence this warning, or just remove the unused import line",
539 import_m.mod_pos)
540 }
541 }
542}
543
544// graph of all imported modules
545pub fn (b &Builder) import_graph() &depgraph.DepGraph {
546 builtins := util.builtin_module_parts.clone()
547 mut graph := depgraph.new_dep_graph()
548 for p in b.parsed_files {
549 // eprintln('p.path: ${p.path}')
550 mut deps := []string{}
551 if p.mod.name !in builtins {
552 deps << 'builtin'
553 if b.pref.backend == .c {
554 // TODO: JavaScript backend doesn't handle os for now
555 // os import libraries so we exclude anything which could cause a loop
556 if p.path.ends_with('.vsh') {
557 deps << 'os'
558 }
559 }
560 }
561 for m in p.imports {
562 if m.mod == p.mod.name {
563 continue
564 }
565 deps << m.mod
566 }
567 graph.add(p.mod.name, deps)
568 }
569 $if trace_import_graph ? {
570 eprintln(graph.display())
571 }
572 return graph
573}
574
575pub fn (b &Builder) v_files_from_dir(dir string) []string {
576 if !os.exists(dir) {
577 if dir == 'compiler' && os.is_dir('vlib') {
578 println('looks like you are trying to build V with an old command')
579 println('use `v -o v cmd/v` instead of `v -o v compiler`')
580 }
581 verror("${dir} doesn't exist")
582 } else if !os.is_dir(dir) {
583 verror("${dir} isn't a directory!")
584 }
585 mut files := os.ls(dir) or { panic(err) }
586 mut source_dir := os.real_path(dir)
587 if b.pref.is_verbose {
588 println('v_files_from_dir ("${dir}")')
589 }
590 mut res := b.pref.should_compile_filtered_files(dir, files)
591 if res.len == 0 {
592 // An explicit `base_url` in v.mod still re-homes the source folder.
593 if source_root := source_root_from_vmod_root(dir) {
594 if source_root != dir && os.is_dir(source_root) {
595 if b.pref.is_verbose {
596 println('v_files_from_dir ("${source_root}") (v.mod base_url)')
597 }
598 files = os.ls(source_root) or { panic(err) }
599 source_dir = os.real_path(source_root)
600 res = b.pref.should_compile_filtered_files(source_root, files)
601 }
602 }
603 if res.len == 0 {
604 report_removed_src_layout_if_any(dir)
605 }
606 }
607 return b.with_same_module_subdir_files(source_dir, res)
608}
609
610// report_removed_src_layout_if_any prints a clear error if `dir` still relies on
611// the removed virtual `src/` module layout (sources under `dir/src/` instead of
612// at the module root). The explicit `base_url` opt-in in v.mod is unaffected.
613fn report_removed_src_layout_if_any(dir string) {
614 src_dir := os.join_path(dir, 'src')
615 if !os.is_dir(src_dir) {
616 return
617 }
618 src_files := os.ls(src_dir) or { return }
619 mut has_v_files := false
620 for f in src_files {
621 if f.ends_with('.v') {
622 has_v_files = true
623 break
624 }
625 }
626 if !has_v_files {
627 return
628 }
629 verror('the virtual `src/` module directory is no longer supported.\nV found .v source files under ${src_dir}, but will not treat `src/` as a virtual module root anymore.\nPlease move the sources up from `src/` into ${dir}:\n\tmv ${src_dir}/*.v ${dir}/\n\trmdir ${src_dir}\n\nIf you want to split one module across subdirectories after moving the root files, add `subdirs` to v.mod, for example:\n\tsubdirs: [\'admin\', \'repo\', \'commit\', \'ci\', \'security\', \'ssh\', \'user\']')
630}
631
632fn (b &Builder) with_same_module_subdir_files(source_dir string, v_files []string) []string {
633 mut mcache := vmod.get_cache()
634 vmod_file_location := mcache.get_by_folder(source_dir)
635 if vmod_file_location.vmod_file == '' {
636 return v_files
637 }
638 module_source_root := b.module_source_root(vmod_file_location.vmod_folder)
639 if source_dir != module_source_root {
640 return v_files
641 }
642 manifest := vmod.from_file(vmod_file_location.vmod_file) or { return v_files }
643 subdirs := manifest.unknown['subdirs'] or { return v_files }
644 if subdirs.len == 0 {
645 return v_files
646 }
647 mut res := v_files.clone()
648 mut seen := map[string]bool{}
649 for file in res {
650 seen[os.real_path(file)] = true
651 }
652 for subdir in subdirs {
653 normalized_subdir := normalize_same_module_subdir(subdir, manifest.base_url) or { continue }
654 subdir_path := os.join_path(module_source_root, normalized_subdir)
655 b.collect_same_module_v_files(vmod_file_location.vmod_folder, subdir_path, mut seen, mut
656 res)
657 }
658 return res
659}
660
661fn (b &Builder) module_source_root(module_root string) string {
662 real_module_root := os.real_path(module_root)
663 if source_root := source_root_from_vmod_root(real_module_root) {
664 if source_root != real_module_root && os.is_dir(source_root) {
665 return os.real_path(source_root)
666 }
667 }
668 return real_module_root
669}
670
671fn normalize_same_module_subdir(subdir string, base_url string) !string {
672 mut normalized := os.norm_path(subdir.trim_space().replace('\\', os.path_separator).replace('/',
673 os.path_separator))
674 if normalized == '' || normalized == '.' || os.is_abs_path(normalized) {
675 return error('invalid subdir')
676 }
677 if base_url != '' {
678 base := os.norm_path(base_url).trim_left(os.path_separator).trim_right(os.path_separator)
679 if base != '' {
680 base_prefix := base + os.path_separator
681 if normalized == base {
682 return error('invalid subdir')
683 }
684 if normalized.starts_with(base_prefix) {
685 normalized = normalized.all_after(base_prefix)
686 }
687 }
688 }
689 normalized = normalized.trim_left(os.path_separator).trim_right(os.path_separator)
690 if normalized == '' {
691 return error('invalid subdir')
692 }
693 parts := normalized.split(os.path_separator)
694 if '' in parts || '.' in parts || '..' in parts {
695 return error('invalid subdir')
696 }
697 return normalized
698}
699
700fn (b &Builder) collect_same_module_v_files(module_root string, dir string, mut seen map[string]bool, mut res []string) {
701 if !os.is_dir(dir) {
702 return
703 }
704 real_dir := os.real_path(dir)
705 if real_dir != os.real_path(module_root) && os.is_file(os.join_path(real_dir, 'v.mod')) {
706 return
707 }
708 mut entries := os.ls(real_dir) or { return }
709 entries.sort()
710 for file in b.pref.should_compile_filtered_files(real_dir, entries) {
711 real_file := os.real_path(file)
712 if real_file !in seen {
713 seen[real_file] = true
714 res << file
715 }
716 }
717 for entry in entries {
718 subdir_path := os.join_path(real_dir, entry)
719 if os.is_dir(subdir_path) {
720 b.collect_same_module_v_files(module_root, subdir_path, mut seen, mut res)
721 }
722 }
723}
724
725pub fn (b &Builder) log(s string) {
726 if b.pref.is_verbose {
727 println(s)
728 }
729}
730
731pub fn (b &Builder) info(s string) {
732 if b.pref.is_verbose {
733 println(s)
734 }
735}
736
737@[inline]
738pub fn module_path(mod string) string {
739 // submodule support
740 return mod.replace('.', os.path_separator)
741}
742
743fn manifest_from_vmod_root(vmod_root string) !vmod.Manifest {
744 vmod_path := os.join_path(vmod_root, 'v.mod')
745 if !os.is_file(vmod_path) {
746 return error('module not found')
747 }
748 return vmod.from_file(vmod_path) or { return error('module not found') }
749}
750
751fn source_root_from_vmod_root(vmod_root string) !string {
752 manifest := manifest_from_vmod_root(vmod_root)!
753 if manifest.base_url == '' {
754 return error('module not found')
755 }
756 return manifest.source_root(vmod_root)
757}
758
759fn find_module_path_from_vmod_root(vmod_root string, mod string) !string {
760 manifest := manifest_from_vmod_root(vmod_root)!
761 if manifest.base_url == '' {
762 return error('module not found')
763 }
764 tail_path := mod_tail_after_vmod_name(mod, manifest.name) or {
765 return error('module not found')
766 }
767 if tail_path == '' {
768 return error('module not found')
769 }
770 try_path := os.join_path(manifest.source_root(vmod_root), tail_path)
771 if os.is_dir(try_path) {
772 return try_path
773 }
774 return error('module not found')
775}
776
777fn find_module_path_from_search_root(search_path string, mod string) !string {
778 mod_path := module_path(mod)
779 try_path := os.join_path_single(search_path, mod_path)
780 if os.is_dir(try_path) {
781 return try_path
782 }
783 if src_try_path := find_module_path_from_vmod_root(search_path, mod) {
784 return src_try_path
785 }
786 mod_parts := mod.split('.')
787 for i := mod_parts.len - 1; i > 0; i-- {
788 candidate_root := os.join_path_single(search_path, mod_parts[..i].join(os.path_separator))
789 if !os.is_file(os.join_path(candidate_root, 'v.mod')) {
790 continue
791 }
792 source_root := source_root_from_vmod_root(candidate_root) or { continue }
793 submodule_path := mod_parts[i..].join(os.path_separator)
794 src_try_path := os.join_path(source_root, submodule_path)
795 if os.is_dir(src_try_path) {
796 return src_try_path
797 }
798 }
799 return error('module not found')
800}
801
802fn mod_tail_after_vmod_name(mod string, vmod_name string) !string {
803 if vmod_name == '' {
804 return error('module not found')
805 }
806 mod_parts := mod.split('.')
807 vmod_parts := vmod_name.split('.')
808 for i := 0; i + vmod_parts.len <= mod_parts.len; i++ {
809 if i > 1 {
810 break
811 }
812 if mod_parts[i..i + vmod_parts.len].join('.') == vmod_name {
813 return mod_parts[i + vmod_parts.len..].join(os.path_separator)
814 }
815 }
816 return error('module not found')
817}
818
819fn (b &Builder) module_path_has_v_files(path string) bool {
820 return b.v_files_from_dir(path).len > 0
821}
822
823// TODO: try to merge this & util.module functions to create a
824// reliable multi use function. see comments in util/module.v
825pub fn (b &Builder) find_module_path(mod string, fpath string) !string {
826 // support @VEXEROOT/v.mod relative paths:
827 mut mcache := vmod.get_cache()
828 resolved_fpath := os.real_path(fpath)
829 vmod_file_location := mcache.get_by_file(resolved_fpath)
830 mod_path := module_path(mod)
831 mut module_lookup_paths := []string{}
832 if vmod_file_location.vmod_file.len != 0
833 && vmod_file_location.vmod_folder !in b.module_search_paths {
834 module_lookup_paths << vmod_file_location.vmod_folder
835 }
836 module_lookup_paths << b.module_search_paths
837 // go up through parents looking for modules a folder.
838 // we need a proper solution that works most of the time. look at vdoc.get_parent_mod
839 if resolved_fpath.contains(os.path_separator + 'modules' + os.path_separator) {
840 parts := resolved_fpath.split(os.path_separator)
841 for i := parts.len - 2; i >= 0; i-- {
842 if parts[i] == 'modules' {
843 module_lookup_paths << parts[0..i + 1].join(os.path_separator)
844 break
845 }
846 }
847 }
848 mut empty_module_path := ''
849 for search_path in module_lookup_paths {
850 try_path := os.join_path(search_path, mod_path)
851 if b.pref.is_verbose {
852 println(' >> trying to find ${mod} in ${try_path} ..')
853 }
854 if found_path := find_module_path_from_search_root(search_path, mod) {
855 if b.module_path_has_v_files(found_path) {
856 if b.pref.is_verbose {
857 println(' << found ${found_path} .')
858 }
859 return found_path
860 }
861 if empty_module_path == '' {
862 empty_module_path = found_path
863 }
864 if b.pref.is_verbose {
865 println(' << skipped ${found_path} (no .v files) .')
866 }
867 }
868 }
869 // look up through parents
870 mut current_dir := os.dir(resolved_fpath)
871 for {
872 try_path := os.join_path(current_dir, mod_path)
873 if b.pref.is_verbose {
874 println(' >> trying to find ${mod} in ${try_path} ..')
875 }
876 if found_path := find_module_path_from_search_root(current_dir, mod) {
877 if b.module_path_has_v_files(found_path) {
878 if b.pref.is_verbose {
879 println(' << found ${found_path} .')
880 }
881 return found_path
882 }
883 if empty_module_path == '' {
884 empty_module_path = found_path
885 }
886 if b.pref.is_verbose {
887 println(' << skipped ${found_path} (no .v files) .')
888 }
889 }
890 parent_dir := os.dir(current_dir)
891 if parent_dir == current_dir {
892 break
893 }
894 current_dir = parent_dir
895 }
896 if empty_module_path != '' {
897 return empty_module_path
898 }
899 smodule_lookup_paths := module_lookup_paths.join(', ')
900 return error('module "${mod}" not found in:\n${smodule_lookup_paths}')
901}
902
903pub fn (b &Builder) show_total_warns_and_errors_stats() {
904 if b.nr_errors == 0 && b.nr_warnings == 0 && b.nr_notices == 0 {
905 return
906 }
907 if b.pref.is_stats {
908 mut nr_errors := b.checker.nr_errors
909 mut nr_warnings := b.checker.nr_warnings
910 mut nr_notices := b.checker.nr_notices
911
912 if b.pref.check_only {
913 nr_errors = b.nr_errors
914 nr_warnings = b.nr_warnings
915 nr_notices = b.nr_notices
916 }
917
918 estring := util.bold(nr_errors.str())
919 wstring := util.bold(nr_warnings.str())
920 nstring := util.bold(nr_notices.str())
921
922 if b.pref.check_only {
923 println('summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices')
924 } else {
925 println('checker summary: ${estring} V errors, ${wstring} V warnings, ${nstring} V notices')
926 }
927 }
928 if !b.pref.is_vls && b.checker.nr_errors > 0 && b.pref.path.ends_with('.v')
929 && os.is_file(b.pref.path) && !b.pref.path.ends_with('vrepl_temp.v') {
930 if b.checker.errors.any(it.message.starts_with('unknown ')) {
931 // Sometimes users try to `v main.v`, when they have several .v files in their project.
932 // Then, they encounter puzzling errors about missing or unknown types. In this case,
933 // the intended command may have been `v .` instead, so just suggest that:
934 old_cmd := util.bold('v ${b.pref.path}')
935 new_cmd := util.bold('v ${os.dir(b.pref.path)}')
936 eprintln(util.color('notice',
937 'If the code of your project is in a folder with multiple .v files, try `${new_cmd}` instead of `${old_cmd}`'))
938 }
939 }
940}
941
942pub fn (mut b Builder) print_warnings_and_errors() {
943 defer {
944 b.show_total_warns_and_errors_stats()
945 }
946
947 for file in b.parsed_files {
948 b.nr_errors += file.errors.len
949 b.nr_warnings += file.warnings.len
950 b.nr_notices += file.notices.len
951 }
952
953 if b.pref.output_mode == .silent {
954 if b.nr_errors > 0 {
955 exit(1)
956 }
957 return
958 }
959
960 if b.pref.check_only {
961 if !b.pref.skip_notes && !b.pref.json_errors {
962 for file in b.parsed_files {
963 for err in file.notices {
964 kind := if b.pref.is_verbose {
965 '${err.reporter} notice #${b.nr_notices}:'
966 } else {
967 'notice:'
968 }
969 util.show_compiler_message(kind, err.CompilerMessage)
970 }
971 }
972 }
973
974 mut json_errors := []util.JsonError{}
975 for file in b.parsed_files {
976 for err in file.errors {
977 kind := if b.pref.is_verbose {
978 '${err.reporter} error #${b.nr_errors}:'
979 } else {
980 'error:'
981 }
982
983 if b.pref.json_errors {
984 json_errors << util.JsonError{
985 message: err.message
986 path: os.to_slash(err.file_path)
987 line_nr: err.pos.line_nr + 1
988 col: err.pos.col + 1
989 }
990 // util.print_json_error(kind, err.CompilerMessage)
991 } else {
992 util.show_compiler_message(kind, err.CompilerMessage)
993 }
994 }
995 }
996 if b.pref.json_errors {
997 if !b.pref.is_vls
998 || b.pref.linfo.method !in [.definition, .completion, .signature_help, .hover] {
999 util.print_json_errors(json_errors)
1000 }
1001 // eprintln(json2.encode_pretty(json_errors))
1002 }
1003 if !b.pref.skip_warnings {
1004 for file in b.parsed_files {
1005 for err in file.warnings {
1006 kind := if b.pref.is_verbose {
1007 '${err.reporter} warning #${b.nr_warnings}:'
1008 } else {
1009 'warning:'
1010 }
1011 util.show_compiler_message(kind, err.CompilerMessage)
1012 }
1013 }
1014 }
1015
1016 b.show_total_warns_and_errors_stats()
1017 if b.nr_errors > 0 {
1018 exit(1)
1019 }
1020 }
1021
1022 if b.pref.is_verbose && b.checker.nr_warnings > 1 {
1023 println('${b.checker.nr_warnings} warnings')
1024 }
1025 if b.pref.is_verbose && b.checker.nr_notices > 1 {
1026 println('${b.checker.nr_notices} notices')
1027 }
1028 if b.checker.nr_notices > 0 && !b.pref.skip_notes {
1029 for err in b.checker.notices {
1030 kind := if b.pref.is_verbose {
1031 '${err.reporter} notice #${b.checker.nr_notices}:'
1032 } else {
1033 'notice:'
1034 }
1035 util.show_compiler_message(kind, err.CompilerMessage)
1036 }
1037 }
1038 if b.checker.nr_warnings > 0 && !b.pref.skip_warnings {
1039 for err in b.checker.warnings {
1040 kind := if b.pref.is_verbose {
1041 '${err.reporter} warning #${b.checker.nr_warnings}:'
1042 } else {
1043 'warning:'
1044 }
1045 util.show_compiler_message(kind, err.CompilerMessage)
1046 }
1047 }
1048
1049 if b.pref.is_verbose && b.checker.nr_errors > 1 {
1050 println('${b.checker.nr_errors} errors')
1051 }
1052 if b.checker.nr_errors > 0 {
1053 for err in b.checker.errors {
1054 kind := if b.pref.is_verbose {
1055 '${err.reporter} error #${b.checker.nr_errors}:'
1056 } else {
1057 'error:'
1058 }
1059 util.show_compiler_message(kind, err.CompilerMessage)
1060 }
1061 b.show_total_warns_and_errors_stats()
1062 exit(1)
1063 }
1064 if b.table.redefined_fns.len > 0 {
1065 mut total_conflicts := 0
1066 for fn_name in b.table.redefined_fns {
1067 // Find where this function was already declared
1068 mut redefines := []FunctionRedefinition{}
1069 mut redefine_conflicts := map[string]int{}
1070 for file in b.parsed_files {
1071 for stmt in file.stmts {
1072 if stmt is ast.FnDecl {
1073 if stmt.name == fn_name {
1074 fheader := b.table.stringify_fn_decl(&stmt, 'main',
1075 map[string]string{}, false)
1076 redefines << FunctionRedefinition{
1077 fpath: file.path
1078 fline: stmt.pos.line_nr
1079 f: stmt
1080 fheader: fheader
1081 }
1082 redefine_conflicts[fheader]++
1083 }
1084 }
1085 }
1086 }
1087 if redefines.len > 0 {
1088 util.show_compiler_message('builder error:',
1089 message: 'redefinition of function `${fn_name}`'
1090 )
1091 for redefine in redefines {
1092 util.show_compiler_message('conflicting declaration:',
1093 message: redefine.fheader
1094 file_path: redefine.fpath
1095 pos: redefine.f.pos
1096 )
1097 }
1098 total_conflicts++
1099 }
1100 }
1101 if total_conflicts > 0 {
1102 b.show_total_warns_and_errors_stats()
1103 exit(1)
1104 }
1105 }
1106}
1107
1108struct FunctionRedefinition {
1109 fpath string
1110 fline int
1111 fheader string
1112 f ast.FnDecl
1113}
1114
1115pub fn (b &Builder) error_with_pos(s string, fpath string, pos token.Pos) errors.Error {
1116 return errors.Error{
1117 file_path: fpath
1118 pos: pos
1119 reporter: .builder
1120 message: s
1121 }
1122}
1123
1124fn (b &Builder) print_frontend_builder_errors() {
1125 if b.pref.check_only || b.pref.output_mode != .stdout {
1126 return
1127 }
1128 for file in b.parsed_files {
1129 for err in file.errors {
1130 if err.reporter == .builder {
1131 util.show_compiler_message('builder error:', err.CompilerMessage)
1132 }
1133 }
1134 }
1135}
1136
1137@[noreturn]
1138pub fn verror(s string) {
1139 util.verror('builder error', s)
1140}
1141
1142pub fn (mut b Builder) show_parsed_files() {
1143 for p in b.parsed_files {
1144 if p.is_parse_text {
1145 println(p.path + ':parse_text')
1146 }
1147 println(p.path)
1148 }
1149}
1150