v2 / vlib / v / builder / compile.v
467 lines · 440 sloc · 14.71 KB · 50d147ef9738da88188f205afcc8d038c8b5629b
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module builder
5
6import os
7import v.pref
8import v.util
9import v.vmod
10
11pub type FnBackend = fn (mut b Builder)
12
13// should_find_windows_host_c_compiler reports whether Windows host compiler probing is needed.
14pub fn should_find_windows_host_c_compiler(pref_ &pref.Preferences) bool {
15 return pref_.backend == .c && pref_.os == .windows && !pref_.output_cross_c
16}
17
18pub fn compile(command string, pref_ &pref.Preferences, backend_cb FnBackend) {
19 check_if_input_file_exists(pref_)
20 check_if_output_folder_is_writable(pref_)
21 $if windows {
22 if should_find_windows_host_c_compiler(pref_) {
23 // Resolve the effective Windows C compiler before builder initialization.
24 mut probe := Builder{
25 pref: unsafe { pref_ }
26 }
27 probe.find_win_cc() or {}
28 }
29 }
30 mut pref_ref := unsafe { pref_ }
31 pref_ref.ccompiler_type = resolve_ccompiler_type(pref_ref.ccompiler, pref_ref.ccompiler_type)
32 // Construct the V object from command line arguments
33 mut b := new_builder(pref_)
34 if b.should_rebuild() {
35 b.rebuild(backend_cb)
36 }
37 b.exit_on_invalid_syntax()
38 // running does not require the parsers anymore
39 unsafe { b.myfree() }
40 b.run_compiled_executable_and_exit()
41}
42
43fn check_if_input_file_exists(pref_ &pref.Preferences) {
44 if pref_.path == '' || pref_.path == '-' {
45 return
46 }
47 if pref_.path.ends_with('.v') || pref_.path.ends_with('.vsh') || pref_.path.ends_with('.vv') {
48 if !os.exists(pref_.path) {
49 verror("${pref_.path} doesn't exist")
50 }
51 }
52}
53
54fn check_if_output_folder_is_writable(pref_ &pref.Preferences) {
55 if pref_.should_output_to_stdout() || pref_.check_only || pref_.only_check_syntax
56 || pref_.backend == .interpret {
57 return
58 }
59 odir := os.dir(pref_.out_name)
60 // When pref.out_name is just the name of an executable, i.e. `./v -o executable main.v`
61 // without a folder component, just use the current folder instead:
62 mut output_folder := odir
63 if odir.len == pref_.out_name.len {
64 output_folder = os.getwd()
65 }
66 os.mkdir_all(output_folder) or { verror(err.msg()) }
67 os.ensure_folder_is_writable(output_folder) or {
68 // An early error here, is better than an unclear C error later:
69 verror(err.msg())
70 }
71}
72
73// Temporary, will be done by -autofree
74@[unsafe]
75fn (mut b Builder) myfree() {
76 // for file in b.parsed_files {
77 // }
78 unsafe { b.parsed_files.free() }
79 util.free_caches()
80}
81
82fn (b &Builder) exit_on_invalid_syntax() {
83 util.free_caches()
84 // V should exit with an exit code of 1, when there are errors,
85 // even when -silent is passed in combination to -check-syntax:
86 if b.pref.only_check_syntax {
87 for pf in b.parsed_files {
88 if pf.errors.len > 0 {
89 exit(1)
90 }
91 }
92 if b.checker.nr_errors > 0 {
93 exit(1)
94 }
95 }
96}
97
98fn (mut b Builder) run_compiled_executable_and_exit() {
99 if b.pref.backend == .interpret {
100 // the interpreted code has already ran
101 return
102 }
103 if b.pref.skip_running {
104 return
105 }
106 if b.pref.only_check_syntax || b.pref.check_only {
107 return
108 }
109 if b.pref.should_output_to_stdout() {
110 return
111 }
112 if b.pref.os == .ios {
113 panic('Running iOS apps is not supported yet.')
114 }
115 if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) {
116 exit(0)
117 }
118 mut compiled_file := b.pref.out_name
119 if b.pref.backend == .wasm && !compiled_file.ends_with('.wasm') {
120 compiled_file += '.wasm'
121 }
122 if b.pref.backend == .c && b.pref.os == .windows && !compiled_file.ends_with('.exe') {
123 compiled_file += '.exe'
124 }
125 compiled_file = os.real_path(compiled_file)
126
127 mut run_args := []string{cap: b.pref.run_args.len + 1}
128
129 run_file := if b.pref.backend.is_js() {
130 node_basename := $if windows { 'node.exe' } $else { 'node' }
131 os.find_abs_path_of_executable(node_basename) or {
132 panic('Could not find `${node_basename}` in system path. Do you have Node.js installed?')
133 }
134 } else if b.pref.backend == .wasm {
135 mut actual_run := ['wasmer', 'wasmtime', 'wavm', 'wasm3', 'iwasm']
136 mut actual_rf := ''
137
138 // -autofree bug
139 // error: cannot convert 'struct string' to 'struct _option_string'
140 // mut actual_rf := ?string(none)
141
142 for runtime in actual_run {
143 basename := $if windows { runtime + '.exe' } $else { runtime }
144
145 if rf := os.find_abs_path_of_executable(basename) {
146 if basename == 'wavm' {
147 run_args << 'run'
148 }
149 actual_rf = rf
150 break
151 }
152 }
153
154 if actual_rf == '' {
155 panic('Could not find `wasmer`, `wasmtime`, `wavm`, `wasm3` or `ìwasm` in system path. Do you have any installed?')
156 }
157
158 actual_rf
159 } else {
160 compiled_file
161 }
162 if b.pref.backend.is_js() || b.pref.backend == .wasm {
163 run_args << compiled_file
164 }
165 run_args << b.pref.run_args
166
167 if b.pref.is_verbose {
168 println('running ${run_file} with arguments ${run_args.join(' ')}')
169 }
170 if b.pref.is_shared {
171 verror('can not run shared library ${run_file}')
172 }
173 mut ret := 0
174 if b.pref.use_os_system_to_run {
175 mut command_to_run := os.quoted_path(run_file)
176 if run_args.len > 0 {
177 command_to_run += ' ' + util.args_quote_paths(run_args)
178 }
179 ret = os.system(command_to_run)
180 // eprintln('> ret: ${ret:5} | command_to_run: ${command_to_run}')
181 } else {
182 mut run_process := os.new_process(run_file)
183 run_process.set_args(run_args)
184 // Ignore sigint and sigquit while running the compiled file,
185 // so ^C doesn't prevent v from deleting the compiled file.
186 // See also https://git.musl-libc.org/cgit/musl/tree/src/process/system.c
187 prev_int_handler := os.signal_opt(.int, eshcb) or { serror('set .int', err) }
188 mut prev_quit_handler := os.SignalHandler(eshcb)
189 $if !windows { // There's no sigquit on windows
190 prev_quit_handler = os.signal_opt(.quit, eshcb) or { serror('set .quit', err) }
191 }
192 run_process.wait()
193 os.signal_opt(.int, prev_int_handler) or { serror('restore .int', err) }
194 $if !windows {
195 os.signal_opt(.quit, prev_quit_handler) or { serror('restore .quit', err) }
196 }
197 ret = run_process.code
198 if run_process.err != '' {
199 eprintln(run_process.err)
200 }
201 run_process.close()
202 }
203 b.cleanup_run_executable_after_exit(compiled_file)
204 exit(ret)
205}
206
207fn eshcb(_ os.Signal) {
208}
209
210@[noreturn]
211fn serror(reason string, e IError) {
212 eprintln('could not ${reason} handler')
213 panic(e.msg())
214}
215
216fn (mut v Builder) cleanup_run_executable_after_exit(exefile string) {
217 if v.pref.is_crun {
218 return
219 }
220 if v.pref.reuse_tmpc {
221 v.pref.vrun_elog('keeping executable: ${exefile} , because -keepc was passed')
222 return
223 }
224 if !v.executable_exists {
225 v.pref.vrun_elog('remove run executable: ${exefile}')
226 os.rm(exefile) or {}
227 }
228}
229
230// 'strings' => 'VROOT/vlib/strings'
231// 'installed_mod' => '~/.vmodules/installed_mod'
232// 'local_mod' => '/path/to/current/dir/local_mod'
233pub fn (mut v Builder) set_module_lookup_paths() {
234 // Module search order:
235 // 0) V test files are very commonly located right inside the folder of the
236 // module, which they test. Adding the parent folder of the module folder
237 // with the _test.v files, *guarantees* that the tested module can be found
238 // without needing to set custom options/flags.
239 // 1) search in the *same* directory, as the compiled final v program source
240 // (i.e. the . in `v .` or file.v in `v file.v`)
241 // 2) search in the modules/ in the same directory.
242 // 3) search in the provided paths
243 // By default, these are what (3) contains:
244 // 3.1) search in vlib/
245 // 3.2) search in ~/.vmodules/ (i.e. modules installed with vpm)
246 lookup_root := v.module_lookup_root()
247 v.module_search_paths = []
248 if v.pref.is_test {
249 v.module_search_paths << os.dir(v.compiled_dir) // pdir of _test.v
250 }
251 v.module_search_paths << lookup_root
252 x := os.join_path(lookup_root, 'modules')
253 if v.pref.is_verbose {
254 println('x: "${x}"')
255 }
256
257 if source_root := source_root_from_vmod_root(lookup_root) {
258 source_modules := os.join_path(source_root, 'modules')
259 if source_modules !in v.module_search_paths && os.exists(source_modules) {
260 v.module_search_paths << source_modules
261 }
262 }
263 if os.exists(os.join_path(lookup_root, 'modules')) {
264 v.module_search_paths << os.join_path(lookup_root, 'modules')
265 }
266
267 v.module_search_paths << v.pref.lookup_path
268 if v.pref.is_verbose {
269 v.log('v.module_search_paths:')
270 println(v.module_search_paths)
271 }
272}
273
274fn (v &Builder) module_lookup_root() string {
275 // If `compiled_dir` is the base_url-configured source folder, treat the
276 // enclosing module folder as the lookup root so sibling `modules/` resolves.
277 mut mcache := vmod.get_cache()
278 vmod_file_location := mcache.get_by_folder(v.compiled_dir)
279 if vmod_file_location.vmod_file != '' && vmod_file_location.vmod_folder != v.compiled_dir {
280 if source_root := source_root_from_vmod_root(vmod_file_location.vmod_folder) {
281 if os.real_path(source_root) == v.compiled_dir {
282 return vmod_file_location.vmod_folder
283 }
284 }
285 }
286 return v.compiled_dir
287}
288
289pub fn (v Builder) get_builtin_files() []string {
290 if v.pref.no_builtin {
291 v.log('v.pref.no_builtin is true, get_builtin_files == []')
292 return []
293 }
294 v.log('v.pref.lookup_path: ${v.pref.lookup_path}')
295 // Lookup for built-in folder in lookup path.
296 // Assumption: `builtin/` folder implies usable implementation of builtin
297 for location in v.pref.lookup_path {
298 if os.exists(os.join_path(location, 'builtin')) {
299 mut builtin_files := []string{}
300 if v.pref.backend.is_js() {
301 builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', 'js'))
302 } else if v.pref.backend == .wasm {
303 builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', 'wasm'))
304 if v.pref.os == .browser {
305 builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', 'wasm',
306 'browser'))
307 } else {
308 builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin', 'wasm',
309 'wasi'))
310 }
311 } else {
312 builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin'))
313 }
314 if v.pref.is_bare {
315 builtin_files << v.v_files_from_dir(v.pref.bare_builtin_dir)
316 }
317 if v.pref.backend == .c {
318 // TODO: JavaScript backend doesn't handle os for now
319 if v.pref.is_vsh && os.exists(os.join_path(location, 'os')) {
320 builtin_files << v.v_files_from_dir(os.join_path(location, 'os'))
321 }
322 }
323 return builtin_files
324 }
325 }
326 // Panic. We couldn't find the folder.
327 verror('`builtin/` not included on module lookup path.\nDid you forget to add vlib to the path? (Use @vlib for default vlib)')
328}
329
330pub fn (v &Builder) get_user_files() []string {
331 if v.pref.path in ['vlib/builtin', 'vlib/strconv', 'vlib/strings', 'vlib/hash']
332 || v.pref.path.ends_with('vlib/builtin') {
333 // This means we are building a builtin module with `v build-module vlib/strings` etc
334 // get_builtin_files() has already added the files in this module,
335 // do nothing here to avoid duplicate definition errors.
336 v.log('Skipping user files.')
337 return []
338 }
339 mut dir := v.pref.path
340 v.log('get_v_files(${dir})')
341 // Need to store user files separately, because they have to be added after
342 // libs, but we dont know which libs need to be added yet
343 mut user_files := []string{}
344 // See cmd/tools/preludes/README.md for more info about what preludes are
345 mut preludes_path := os.join_path(v.pref.vroot, 'vlib', 'v', 'preludes')
346 if v.pref.backend == .js_node {
347 preludes_path = os.join_path(v.pref.vroot, 'vlib', 'v', 'preludes_js')
348 }
349 if v.pref.trace_calls {
350 user_files << os.join_path(preludes_path, 'trace_calls.v')
351 }
352 if v.pref.is_livemain || v.pref.is_liveshared {
353 user_files << os.join_path(preludes_path, 'live.v')
354 }
355 if v.pref.is_livemain {
356 user_files << os.join_path(preludes_path, 'live_main.v')
357 }
358 if v.pref.is_liveshared {
359 user_files << os.join_path(preludes_path, 'live_shared.v')
360 }
361 if v.pref.is_test {
362 if v.pref.backend == .js_node {
363 user_files << os.join_path(preludes_path, 'test_runner.v')
364 } else {
365 user_files << os.join_path(preludes_path, 'test_runner.c.v')
366 }
367 //
368 mut v_test_runner_prelude := os.getenv('VTEST_RUNNER')
369 if v.pref.test_runner != '' {
370 v_test_runner_prelude = v.pref.test_runner
371 }
372 if v_test_runner_prelude == '' {
373 v_test_runner_prelude = 'normal'
374 }
375 if !v_test_runner_prelude.contains('/') && !v_test_runner_prelude.contains('\\')
376 && !v_test_runner_prelude.ends_with('.v') {
377 v_test_runner_prelude = os.join_path(preludes_path,
378 'test_runner_${v_test_runner_prelude}.v')
379 }
380 if !os.is_file(v_test_runner_prelude) || !os.is_readable(v_test_runner_prelude) {
381 eprintln('test runner error: File ${v_test_runner_prelude} should be readable.')
382 verror('the supported test runners are: ${pref.supported_test_runners_list()}')
383 }
384 user_files << v_test_runner_prelude
385 }
386 if v.pref.is_test && v.pref.show_asserts {
387 user_files << os.join_path(preludes_path, 'tests_with_stats.v')
388 if v.pref.backend.is_js() {
389 user_files << os.join_path(preludes_path, 'stats_import.js.v')
390 }
391 }
392 if v.pref.is_prof {
393 user_files << os.join_path(preludes_path, 'profiled_program.v')
394 }
395 is_test := v.pref.is_test
396 mut is_internal_module_test := false
397 if is_test {
398 tcontent := util.read_file(dir) or { verror('${dir} does not exist') }
399 slines := tcontent.split_into_lines()
400 for sline in slines {
401 line := sline.trim_space()
402 if line.len > 2 {
403 if line[0] == `/` && line[1] == `/` {
404 continue
405 }
406 if line.starts_with('module ') {
407 is_internal_module_test = true
408 break
409 }
410 }
411 }
412 }
413 if is_internal_module_test {
414 // v volt/slack_test.v: compile all .v files to get the environment
415 single_test_v_file := os.real_path(dir)
416 if v.pref.is_verbose {
417 v.log('> Compiling an internal module _test.v file ${single_test_v_file} .')
418 v.log('> That brings in all other ordinary .v files in the same module too .')
419 }
420 user_files << single_test_v_file
421 dir = os.dir(single_test_v_file)
422 }
423 v.add_file_or_dir(mut user_files, dir)
424 for f in v.pref.file_list {
425 file := f.trim_space()
426 if file.len > 0 {
427 v.add_file_or_dir(mut user_files, file)
428 }
429 }
430 if user_files.len == 0 {
431 println('No input .v files')
432 exit(1)
433 }
434 if v.pref.is_verbose {
435 v.log('user_files: ${user_files}')
436 }
437 return user_files
438}
439
440fn (v &Builder) add_file_or_dir(mut user_files []string, dir string) {
441 does_exist := os.exists(dir)
442 if !does_exist {
443 verror("${dir} doesn't exist")
444 }
445 is_real_file := does_exist && !os.is_dir(dir)
446 resolved_link := if is_real_file && os.is_link(dir) { os.real_path(dir) } else { dir }
447 if is_real_file && (dir.ends_with('.v') || resolved_link.ends_with('.vsh')
448 || v.pref.raw_vsh_tmp_prefix != '' || dir.ends_with('.vv')) {
449 single_v_file := if resolved_link.ends_with('.vsh') { resolved_link } else { dir }
450 // Just compile one file and get parent dir
451 user_files << single_v_file
452 if v.pref.is_verbose {
453 v.log('> add one file: "${single_v_file}"')
454 }
455 } else if os.is_dir(dir) {
456 if v.pref.is_verbose {
457 v.log('> add all .v files from directory "${dir}" ...')
458 }
459 // Add .v files from the directory being compiled
460 user_files << v.v_files_from_dir(dir)
461 } else {
462 println('usage: `v file.v` or `v directory`')
463 ext := os.file_ext(dir)
464 println('unknown file extension `${ext}`')
465 exit(1)
466 }
467}
468