v2 / vlib / v / pref / default.v
608 lines · 572 sloc · 17.23 KB · 45545c2fda3dfafa31fb7341b31b786ad143e67d
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 pref
5
6import os
7import strings
8import v.vcache
9
10pub const default_module_path = os.vmodules_dir()
11
12pub fn new_preferences() &Preferences {
13 mut p := &Preferences{}
14 p.fill_with_defaults()
15 return p
16}
17
18const windows_default_gc_defines = ['gcboehm', 'gcboehm_full', 'gcboehm_incr', 'gcboehm_opt',
19 'gcboehm_leak', 'vgc']
20
21fn (p &Preferences) default_thread_stack_size() int {
22 return match p.arch {
23 .arm32, .rv32, .i386, .ppc, .wasm32 { 2 * 1024 * 1024 }
24 else { 8 * 1024 * 1024 }
25 }
26}
27
28fn (p &Preferences) is_linux_wayland_only_session() bool {
29 if p.os != .linux {
30 return false
31 }
32 if os.getenv('DISPLAY') != '' {
33 return false
34 }
35 return os.getenv('WAYLAND_DISPLAY') != ''
36 || os.getenv('XDG_SESSION_TYPE').to_lower() == 'wayland'
37}
38
39fn (mut p Preferences) expand_lookup_paths() {
40 if p.vroot == '' {
41 // Location of all vlib files
42 p.vroot = os.dir(vexe_path())
43 }
44 p.vlib = os.join_path(p.vroot, 'vlib')
45 p.vmodules_paths = os.vmodules_paths()
46
47 if p.lookup_path.len == 0 {
48 p.lookup_path = ['@vlib', '@vmodules']
49 }
50 mut expanded_paths := []string{}
51 for path in p.lookup_path {
52 match path {
53 '@vlib' { expanded_paths << p.vlib }
54 '@vmodules' { expanded_paths << p.vmodules_paths }
55 else { expanded_paths << path.replace('@vroot', p.vroot) }
56 }
57 }
58 p.lookup_path = expanded_paths
59}
60
61fn (mut p Preferences) expand_exclude_paths() {
62 mut res := []string{}
63 static_replacement_list := ['@vroot', p.vroot, '@vlib', p.vlib]
64 for x in p.exclude {
65 y := x.replace_each(static_replacement_list)
66 if y.contains('@vmodules') {
67 // @vmodules is a list of paths, each of which should be expanded in the complete exclusion list:
68 for vmp in p.vmodules_paths {
69 res << y.replace('@vmodules', vmp)
70 }
71 continue
72 }
73 res << y
74 }
75 p.exclude = res
76}
77
78fn (mut p Preferences) setup_os_and_arch_when_not_explicitly_set() {
79 if p.os == .wasm32_emscripten {
80 // TODO: remove after `$if wasm32_emscripten {` works
81 p.parse_define('emscripten')
82 }
83 host_os := if p.backend == .wasm { OS.wasi } else { get_host_os() }
84 if p.os == ._auto {
85 p.os = host_os
86 p.build_options << '-os ${host_os.lower()}'
87 }
88
89 if !p.output_cross_c && p.os != host_os {
90 if p.os == .linux && p.arch == ._auto {
91 // The bundled linuxroot sysroot currently only contains x86_64 runtime files.
92 p.set_default_arch(.amd64)
93 }
94 // TODO: generalise this not only for macos->linux, after considering the consequences for vab/Android:
95 if host_os == .macos && p.os == .linux {
96 p.parse_define('use_bundled_libgc')
97 }
98 }
99}
100
101fn (mut p Preferences) set_default_arch(arch Arch) {
102 if p.arch != ._auto || arch == ._auto {
103 return
104 }
105 p.arch = arch
106 p.build_options << '-arch ${arch}'
107}
108
109fn arch_from_ccompiler_name(ccompiler string) Arch {
110 name := os.file_name(ccompiler).to_lower_ascii()
111 if name.contains('x86_64') || name.contains('amd64') {
112 return .amd64
113 }
114 if name.contains('aarch64') || name.contains('arm64-v8a') || name.contains('arm64') {
115 return .arm64
116 }
117 if name.contains('armeabi-v7a') || name.contains('armv7')
118 || name.contains('arm-linux-androideabi') || name.contains('arm32') {
119 return .arm32
120 }
121 if name.contains('riscv64') {
122 return .rv64
123 }
124 if name.contains('riscv32') {
125 return .rv32
126 }
127 if name.contains('i686') || name.contains('i386') || name.contains('x86') {
128 return .i386
129 }
130 if name.contains('s390x') {
131 return .s390x
132 }
133 if name.contains('ppc64le') {
134 return .ppc64le
135 }
136 if name.contains('loongarch64') {
137 return .loongarch64
138 }
139 if name.contains('sparc64') {
140 return .sparc64
141 }
142 if name.contains('ppc64') {
143 return .ppc64
144 }
145 return ._auto
146}
147
148fn (mut p Preferences) resolve_default_arch() {
149 if p.arch != ._auto {
150 return
151 }
152 host_os := if p.backend == .wasm { OS.wasi } else { get_host_os() }
153 if p.os != host_os {
154 p.set_default_arch(arch_from_ccompiler_name(p.ccompiler))
155 }
156 p.set_default_arch(get_host_arch())
157}
158
159pub fn (mut p Preferences) defines_map_unique_keys() string {
160 mut defines_map := map[string]bool{}
161 for d in p.compile_defines {
162 defines_map[d] = true
163 }
164 for d in p.compile_defines_all {
165 defines_map[d] = true
166 }
167 keys := defines_map.keys()
168 skeys := keys.sorted()
169 return skeys.join(',')
170}
171
172fn (mut p Preferences) disable_tcc_shared_backtraces() {
173 if p.is_shared && p.ccompiler_type == .tinyc && 'no_backtrace' !in p.compile_defines_all {
174 // TCC shared libraries should not depend on TCC's backtrace runtime symbols.
175 p.parse_define('no_backtrace')
176 }
177}
178
179// fill_with_defaults initializes unset preferences and derives build options from them.
180pub fn (mut p Preferences) fill_with_defaults() {
181 p.setup_os_and_arch_when_not_explicitly_set()
182 p.expand_lookup_paths()
183 p.expand_exclude_paths()
184 rpath := os.real_path(p.path)
185 if p.out_name == '' {
186 target_dir := if os.is_dir(rpath) { rpath } else { os.dir(rpath) }
187 p.out_name = os.join_path(target_dir, p.default_output_name(rpath))
188 // Do *NOT* be tempted to generate binaries in the current work folder,
189 // when -o is not given by default, like Go, Clang, GCC etc do.
190 //
191 // These compilers also are frequently used with an external build system,
192 // in part because of that shortcoming, to ensure that they work in a
193 // predictable work folder/environment.
194 //
195 // In comparison, with V, building an executable by default places it
196 // next to its source code, so that it can be used directly with
197 // functions like `os.resource_abs_path()` and `os.executable()` to
198 // locate resources relative to it. That enables running examples like
199 // this:
200 // `./v run examples/flappylearning/`
201 // instead of:
202 // `./v -o examples/flappylearning/flappylearning run examples/flappylearning/`
203 // This topic comes up periodically from time to time on Discord, and
204 // many CI breakages already happened, when someone decides to make V
205 // behave in this aspect similarly to the dumb behaviour of other
206 // compilers.
207 //
208 // If you do decide to break it, please *at the very least*, test it
209 // extensively, and make a PR about it, instead of committing directly
210 // and breaking the CI, VC, and users doing `v up`.
211 } else if p.out_name_is_dir {
212 p.out_name = os.join_path(p.out_name, p.default_output_name(rpath))
213 }
214 npath := rpath.replace('\\', '/')
215 p.building_v = !p.is_repl && (npath.ends_with('cmd/v') || npath.ends_with('cmd/tools/vfmt.v'))
216 if p.os == .linux {
217 $if !linux {
218 p.parse_define('cross_compile')
219 }
220 }
221 if p.output_cross_c {
222 // avoid linking any GC related code, since the target may not have an usable GC system
223 p.gc_mode = .no_gc
224 p.use_cache = false
225 p.skip_unused = false
226 p.parse_define('no_backtrace') // the target may not have usable backtrace() and backtrace_symbols()
227 p.parse_define('cross') // TODO: remove when `$if cross {` works
228 }
229 if p.gc_mode == .unknown {
230 if p.backend != .c || p.building_v || p.is_bare || p.os == .windows || p.is_musl {
231 p.gc_mode = .no_gc
232 p.build_options << ['-gc', 'none']
233 } else {
234 // enable the GC by default
235 p.gc_mode = .boehm_full_opt
236 // NOTE: these are added to p.compile_defines[_all]
237 // more than once when building modules for usecache
238 p.parse_define('gcboehm')
239 p.parse_define('gcboehm_full')
240 p.parse_define('gcboehm_opt')
241 }
242 }
243 if p.is_debug {
244 p.parse_define('debug')
245 }
246 p.try_to_use_tcc_by_default()
247 if p.ccompiler == '' {
248 p.default_c_compiler()
249 }
250 if p.cppcompiler == '' {
251 p.default_cpp_compiler()
252 }
253 p.find_cc_if_cross_compiling()
254 p.resolve_default_arch()
255 if !p.thread_stack_size_set_by_flag {
256 p.thread_stack_size = p.default_thread_stack_size()
257 }
258 p.ccompiler_type = cc_from_string(p.ccompiler)
259 p.normalize_gc_defaults_for_resolved_ccompiler()
260 p.is_test = p.path.ends_with('_test.v') || p.path.ends_with('_test.vv')
261 || p.path.all_before_last('.v').all_before_last('.').ends_with('_test')
262 p.is_vsh = p.path.ends_with('.vsh') || p.raw_vsh_tmp_prefix != ''
263 p.is_script = p.is_vsh || p.path.ends_with('.v') || p.path.ends_with('.vv')
264 if p.third_party_option == '' {
265 p.third_party_option = p.cflags
266 $if !windows {
267 if !p.third_party_option.contains('-fPIC') {
268 p.third_party_option += ' -fPIC'
269 }
270 }
271 }
272
273 final_os := p.os.lower()
274 p.parse_define(final_os)
275 if p.is_linux_wayland_only_session() && 'linux_wayland_session' !in p.compile_defines_all {
276 p.parse_define('linux_wayland_session')
277 }
278
279 // Prepare the cache manager. All options that can affect the generated cached .c files
280 // should go into res.cache_manager.vopts, which is used as a salt for the cache hash.
281 vhash := @VHASH
282 p.cache_manager = vcache.new_cache_manager([
283 vhash,
284 // ensure that different v versions use separate build artefacts
285 '${p.backend} | ${final_os} | ${p.ccompiler} | ${p.is_prod} | ${p.sanitize}',
286 p.defines_map_unique_keys(),
287 p.cflags.trim_space(),
288 p.third_party_option.trim_space(),
289 p.lookup_path.str(),
290 ])
291 // eprintln('prefs.cache_manager: ${p}')
292 // disable use_cache for specific cases:
293 if os.user_os() == 'windows' {
294 p.use_cache = false
295 }
296 if p.build_mode == .build_module {
297 // eprintln('-usecache and build-module flags are not compatible')
298 p.use_cache = false
299 }
300 if p.is_shared {
301 // eprintln('-usecache and -shared flags are not compatible')
302 p.use_cache = false
303 }
304 if p.bare_builtin_dir == '' && p.os == .wasm32 {
305 p.bare_builtin_dir = os.join_path(p.vroot, 'vlib', 'builtin', 'wasm_bare')
306 } else if p.bare_builtin_dir == '' {
307 p.bare_builtin_dir = os.join_path(p.vroot, 'vlib', 'builtin', 'linux_bare')
308 }
309
310 $if prealloc {
311 if !p.no_parallel && p.is_verbose {
312 eprintln('disabling parallel cgen, since V was built with -prealloc')
313 }
314 p.no_parallel = true
315 }
316}
317
318// normalize_gc_defaults_for_resolved_ccompiler clears stale compiler-dependent
319// defaults after the effective C compiler has been resolved.
320pub fn (mut p Preferences) normalize_gc_defaults_for_resolved_ccompiler() {
321 p.disable_tcc_shared_backtraces()
322 if p.prealloc {
323 p.gc_mode = .no_gc
324 p.clear_gc_options()
325 return
326 }
327 if p.os != .windows || p.ccompiler_type != .msvc || p.gc_set_by_flag {
328 return
329 }
330 p.gc_mode = .no_gc
331 p.clear_gc_options()
332}
333
334fn (p &Preferences) default_output_name(rpath string) string {
335 filename := os.file_name(rpath).trim_space()
336 mut base := filename.all_before_last('.')
337 if os.file_ext(base) in ['.c', '.js', '.wasm'] {
338 base = base.all_before_last('.')
339 }
340 if base == '' {
341 // The file name is just `.v` or `.vsh` or `.*`
342 base = filename
343 }
344 if needs_safe_default_output_name(base, filename, rpath) {
345 base = safe_default_output_name(filename)
346 }
347 if p.raw_vsh_tmp_prefix != '' {
348 return p.raw_vsh_tmp_prefix + '.' + base
349 }
350 return base
351}
352
353fn needs_safe_default_output_name(base string, filename string, rpath string) bool {
354 if base == '' || base in ['.', '..', '-'] {
355 return true
356 }
357 if base == filename && filename.starts_with('.') && !os.is_dir(rpath) {
358 return true
359 }
360 if base.ends_with('.c') || base.ends_with('.js') || base.ends_with('.wasm') {
361 return true
362 }
363 for ch in base {
364 if ch < ` ` || ch == 127 {
365 return true
366 }
367 }
368 return false
369}
370
371fn safe_default_output_name(filename string) string {
372 mut sanitized := strings.new_builder(filename.len + 4)
373 for ch in filename {
374 if ch < ` ` || ch == 127 {
375 sanitized.write_u8(`_`)
376 } else {
377 sanitized.write_u8(ch)
378 }
379 }
380 sanitized.write_string('.out')
381 return sanitized.str()
382}
383
384fn (mut p Preferences) find_cc_if_cross_compiling() {
385 if p.os == get_host_os() {
386 return
387 }
388 if p.ccompiler_set_by_flag {
389 // Only mingw compilers can cross-compile for Windows (others lack Windows headers),
390 // so override any non-mingw compiler with the proper cross-compiler.
391 if p.os == .windows && !p.ccompiler.contains('mingw') {
392 p.ccompiler = p.vcross_compiler_name()
393 return
394 }
395 // Respect explicit `-cc` selection even in cross-compilation mode.
396 return
397 }
398 if p.os == .windows && p.ccompiler == 'msvc' {
399 // Allow for explicit overrides like `v -showcc -cc msvc -os windows file.v`,
400 // this makes flag passing more easily debuggable on other OSes too, not only
401 // on windows (building will stop later, when -showcc already could display all
402 // options).
403 return
404 }
405 p.ccompiler = p.vcross_compiler_name()
406}
407
408fn (mut p Preferences) try_to_use_tcc_by_default() {
409 preferred_tcc := default_tcc_compiler()
410 if p.ccompiler == 'tcc' {
411 p.ccompiler = if preferred_tcc != '' { preferred_tcc } else { 'tcc' }
412 return
413 }
414 if p.ccompiler == '' {
415 // -prealloc uses thread-local allocator state. The bundled tcc does not
416 // support TLS declarations, so use the platform C compiler by default.
417 if p.prealloc {
418 return
419 }
420 // use an optimizing compiler (i.e. gcc or clang) on -prod mode
421 if p.is_prod {
422 return
423 }
424 p.ccompiler = preferred_tcc
425 return
426 }
427}
428
429fn usable_system_tcc_compiler() string {
430 if get_host_os() != .termux {
431 return ''
432 }
433 system_tcc := os.find_abs_path_of_executable('tcc') or { return '' }
434 tcc_probe := os.execute('${os.quoted_path(system_tcc)} -v')
435 if tcc_probe.exit_code != 0 {
436 return ''
437 }
438 return system_tcc
439}
440
441fn usable_bundled_tcc_compiler(vroot string) string {
442 vtccexe := os.join_path(vroot, 'thirdparty', 'tcc', 'tcc.exe')
443 if !os.is_file(vtccexe) || !os.is_executable(vtccexe) {
444 return ''
445 }
446 // Unsupported hosts can still have a placeholder `thirdparty/tcc/` checkout.
447 tcc_probe := os.execute('${os.quoted_path(vtccexe)} -v')
448 if tcc_probe.exit_code != 0 {
449 return ''
450 }
451 return vtccexe
452}
453
454// default_tcc_compiler returns the preferred TinyCC path when it exists and works on the host.
455pub fn default_tcc_compiler() string {
456 vexe := vexe_path()
457 vroot := os.dir(vexe)
458 bundled_tcc := usable_bundled_tcc_compiler(vroot)
459 if bundled_tcc != '' {
460 return bundled_tcc
461 }
462 return usable_system_tcc_compiler()
463}
464
465fn (mut p Preferences) clear_gc_options() {
466 p.compile_defines = p.compile_defines.filter(it !in windows_default_gc_defines)
467 p.compile_defines_all = p.compile_defines_all.filter(it !in windows_default_gc_defines)
468 for define in windows_default_gc_defines {
469 p.compile_values.delete(define)
470 }
471 mut build_options := []string{cap: p.build_options.len + 2}
472 mut i := 0
473 for i < p.build_options.len {
474 option := p.build_options[i]
475 if option == '-gc' {
476 i += 2
477 continue
478 }
479 if option.starts_with('-gc ') {
480 i++
481 continue
482 }
483 if option.starts_with('-d ') {
484 define := option[3..].all_before('=')
485 if define in windows_default_gc_defines {
486 i++
487 continue
488 }
489 }
490 build_options << option
491 i++
492 }
493 build_options << ['-gc', 'none']
494 p.build_options = build_options
495}
496
497pub fn (mut p Preferences) default_c_compiler() {
498 // TODO: fix $if after 'string'
499 $if windows {
500 p.ccompiler = 'gcc'
501 return
502 }
503 if p.os == .ios {
504 $if !ios {
505 ios_sdk := if p.is_ios_simulator { 'iphonesimulator' } else { 'iphoneos' }
506 ios_sdk_path_res := os.execute_or_exit('xcrun --sdk ${ios_sdk} --show-sdk-path')
507 mut isysroot := ios_sdk_path_res.output.replace('\n', '')
508 arch := if p.is_ios_simulator {
509 '-arch x86_64 -arch arm64'
510 } else {
511 '-arch armv7 -arch armv7s -arch arm64'
512 }
513 // On macOS, /usr/bin/cc is a hardlink/wrapper for xcrun. clang on darwin hosts
514 // will automatically change the build target based off of the selected sdk, making xcrun -sdk iphoneos pointless
515 p.ccompiler = '/usr/bin/cc'
516 p.cflags = '-isysroot ${isysroot} ${arch}' + p.cflags
517 return
518 }
519 }
520 p.ccompiler = 'cc'
521 return
522}
523
524pub fn (mut p Preferences) default_cpp_compiler() {
525 if p.ccompiler.contains('clang') {
526 p.cppcompiler = 'clang++'
527 return
528 }
529 p.cppcompiler = 'c++'
530}
531
532pub fn vexe_path() string {
533 vexe := os.getenv('VEXE')
534 if vexe != '' {
535 return vexe
536 }
537 myexe := os.executable()
538 mut real_vexe_path := myexe
539 for {
540 $if tinyc {
541 $if x32 {
542 // TODO: investigate why exactly tcc32 segfaults on os.real_path here,
543 // and remove this cludge.
544 break
545 }
546 }
547 real_vexe_path = os.real_path(real_vexe_path)
548 break
549 }
550 os.setenv('VEXE', real_vexe_path, true)
551 return real_vexe_path
552}
553
554pub fn (p &Preferences) vcross_linker_name() string {
555 vlname := os.getenv('VCROSS_LINKER_NAME')
556 if vlname != '' {
557 return vlname
558 }
559 $if macos {
560 return '/opt/homebrew/opt/llvm/bin/ld.lld'
561 }
562 $if windows {
563 return 'ld.lld.exe'
564 }
565 return 'ld.lld'
566}
567
568pub fn (p &Preferences) vcross_compiler_name() string {
569 vccname := os.getenv('VCROSS_COMPILER_NAME')
570 if vccname != '' {
571 return vccname
572 }
573 if p.os == .windows {
574 if p.os == .freebsd {
575 return 'clang'
576 }
577 if p.m64 {
578 return 'x86_64-w64-mingw32-gcc'
579 }
580 return 'i686-w64-mingw32-gcc'
581 }
582 if p.os == .linux {
583 return 'clang'
584 }
585 if p.os == .freebsd {
586 return 'clang'
587 }
588 if p.os == .wasm32_emscripten {
589 if os.user_os() == 'windows' {
590 return 'emcc.bat'
591 }
592 return 'emcc'
593 }
594 if p.backend == .c && !p.out_name.ends_with('.c') {
595 eprintln('Note: V can only cross compile to Windows and Linux for now by default.')
596 eprintln('It will use `cc` as a cross compiler for now, although that will probably fail.')
597 eprintln('Set `VCROSS_COMPILER_NAME` to the name of your cross compiler, for your target OS: ${p.os} .')
598 }
599 return 'cc'
600}
601
602// vroot_file reads the given file, given a path relative to @VEXEROOT .
603// Its goal is to give all backends a shared infrastructure to read their own static preludes (like C headers etc),
604// without each having to implement their own way of lookup/embedding/caching them.
605pub fn (mut p Preferences) vroot_file(path string) string {
606 full_path := os.join_path(p.vroot, path)
607 return os.read_file(full_path) or { '/* missing @VEXEROOT content of path: ${full_path} */' }
608}
609