vxx / vlib / v3 / pref / pref.v
483 lines · 461 sloc · 11.78 KB · 2b81918e62db9ab38787518603a7f93bee80868c
Raw
1module pref
2
3import os
4
5// Preferences represents preferences data used by pref.
6pub struct Preferences {
7pub mut:
8 verbose bool
9 output_file string
10 target_os string = os.user_os()
11 user_defines []string
12 backend string = 'c'
13 c99 bool
14 vroot string = detect_vroot()
15 selfhost bool
16 building_v bool // compiling the V compiler itself: no generics, skip monomorphization
17}
18
19// new_preferences supports new preferences handling for pref.
20pub fn new_preferences() &Preferences {
21 return &Preferences{}
22}
23
24// detect_vroot resolves detect vroot information for pref.
25fn detect_vroot() string {
26 baked_root := @VMODROOT
27 if baked_root.len > 0 {
28 return baked_root
29 }
30 if os.args.len > 0 && os.args[0].len > 0 {
31 vroot := detect_vroot_from(os.args[0])
32 if vroot.len > 0 {
33 return vroot
34 }
35 }
36 return detect_vroot_from(os.getwd())
37}
38
39// detect_vroot_from resolves detect vroot from information for pref.
40fn detect_vroot_from(start string) string {
41 if start.len == 0 {
42 return ''
43 }
44 mut dir := start
45 if !os.is_abs_path(dir) {
46 cwd := os.getwd()
47 if cwd.len > 0 {
48 dir = os.join_path_single(cwd, dir)
49 }
50 }
51 if !os.is_dir(dir) {
52 dir = os.dir(dir)
53 }
54 for _ in 0 .. 8 {
55 if os.is_dir(os.join_path_single(os.join_path_single(dir, 'vlib'), 'builtin')) {
56 return dir
57 }
58 parent := os.dir(dir)
59 if parent == dir {
60 break
61 }
62 dir = parent
63 }
64 return ''
65}
66
67// get_vlib_module_path returns get vlib module path data for Preferences.
68pub fn (p &Preferences) get_vlib_module_path(mod string) string {
69 mod_path := mod.replace('.', os.path_separator)
70 return os.join_path_single(os.join_path_single(p.vroot, 'vlib'), mod_path)
71}
72
73// get_module_path returns get module path data for Preferences.
74pub fn (p &Preferences) get_module_path(mod string, importing_file_path string) string {
75 mod_path := mod.replace('.', os.path_separator)
76 // Absolutize the importer like V1's Builder.find_module_path does
77 // (`os.real_path(fpath)`). When the input is given as a relative path (e.g.
78 // `v3 -o out .`), the parsed file paths are relative too (`./doka.v`), and a
79 // parent-directory walk starting from `.` could never climb above the project
80 // dir to find sibling modules.
81 abs_importer := os.real_path(importing_file_path)
82 importer_dir := os.dir(abs_importer)
83 // 1. relative to the importing file's directory
84 relative_path := os.join_path_single(importer_dir, mod_path)
85 if dir_is_module(relative_path) {
86 return relative_path
87 }
88 // 2. vlib
89 vlib_path := os.join_path_single(os.join_path_single(p.vroot, 'vlib'), mod_path)
90 if dir_is_module(vlib_path) {
91 return vlib_path
92 }
93 // 3. ~/.vmodules (or $VMODULES)
94 vmodules_path := os.join_path_single(vmodules_dir(), mod_path)
95 if dir_is_module(vmodules_path) {
96 return vmodules_path
97 }
98 // 4. walk up the parent directories of the importing file, like V1's
99 // Builder.find_module_path. This finds sibling projects: e.g. importing
100 // `viper` from ~/code/doka/doka.v resolves to ~/code/viper.
101 mut current_dir := importer_dir
102 for {
103 try_path := os.join_path_single(current_dir, mod_path)
104 if dir_is_module(try_path) {
105 return try_path
106 }
107 parent_dir := os.dir(current_dir)
108 if parent_dir == current_dir {
109 break
110 }
111 current_dir = parent_dir
112 }
113 return ''
114}
115
116// dir_is_module reports whether `dir` is a usable module directory: it must exist
117// and contain at least one `.v` source file. V1 applies the same `.v`-files guard
118// (`module_path_has_v_files`); without it the parent-directory walk could match an
119// unrelated directory that merely shares a module's name (e.g. `~/code/util`),
120// shadowing the real module.
121fn dir_is_module(dir string) bool {
122 if dir.len == 0 || !os.is_dir(dir) {
123 return false
124 }
125 entries := os.ls(dir) or { return false }
126 for entry in entries {
127 if entry.ends_with('.v') {
128 return true
129 }
130 }
131 return false
132}
133
134// vmodules_dir returns the user's global modules directory ($VMODULES or ~/.vmodules).
135fn vmodules_dir() string {
136 env_dir := os.getenv('VMODULES')
137 if env_dir.len > 0 {
138 return env_dir
139 }
140 return os.join_path_single(os.home_dir(), '.vmodules')
141}
142
143// file_has_incompatible_os_suffix converts file has incompatible os suffix data for pref.
144pub fn file_has_incompatible_os_suffix(file string, current_os string) bool {
145 os_name := normalized_os(current_os)
146 if os_name == 'windows' && file.contains('_nix.') {
147 return true
148 }
149 if os_name != 'windows' && file.contains('_windows.') {
150 return true
151 }
152 if os_name != 'linux' && file.contains('_linux.') {
153 return true
154 }
155 if os_name != 'macos' && (file.contains('_macos.') || file.contains('_darwin.')) {
156 return true
157 }
158 if os_name != 'macos' && os_name != 'freebsd' && os_name != 'openbsd' && os_name != 'netbsd'
159 && os_name != 'dragonfly' && file.contains('_bsd.') {
160 return true
161 }
162 if os_name != 'android' && file.contains('_android') {
163 return true
164 }
165 if os_name != 'ios' && file.contains('_ios.') {
166 return true
167 }
168 if os_name != 'freebsd' && file.contains('_freebsd.') {
169 return true
170 }
171 if os_name != 'openbsd' && file.contains('_openbsd.') {
172 return true
173 }
174 if os_name != 'netbsd' && file.contains('_netbsd.') {
175 return true
176 }
177 if os_name != 'dragonfly' && file.contains('_dragonfly.') {
178 return true
179 }
180 if os_name != 'solaris' && file.contains('_solaris.') {
181 return true
182 }
183 if file.contains('.amd64.') || file.contains('_amd64.') || file.contains('.arm64.')
184 || file.contains('_arm64.') {
185 return true
186 }
187 return false
188}
189
190// get_v_files_from_dir returns get v files from dir data for pref.
191pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string) []string {
192 if dir == '' || !os.is_dir(dir) {
193 return []string{}
194 }
195 all_files := os.ls(dir) or { return []string{} }
196 mut sorted_files := all_files.clone()
197 sorted_files.sort()
198 mut has_os_specific := map[string]bool{}
199 for file in sorted_files {
200 if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
201 continue
202 }
203 if file_has_incompatible_os_suffix(file, target_os) {
204 continue
205 }
206 if base := os_specific_base(file, target_os) {
207 has_os_specific[base] = true
208 }
209 }
210 mut v_files := []string{}
211 for backend_specific in [false, true] {
212 for file in sorted_files {
213 if file.ends_with('.c.v') != backend_specific {
214 continue
215 }
216 if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
217 continue
218 }
219 if file_has_incompatible_os_suffix(file, target_os) {
220 continue
221 }
222 if base := default_file_base(file) {
223 if has_os_specific[base] {
224 continue
225 }
226 }
227 if file.contains('_notd_') {
228 feature := extract_define_feature(file, '_notd_')
229 if feature.len > 0 && feature in user_defines {
230 continue
231 }
232 } else if file.contains('_d_') {
233 feature := extract_define_feature(file, '_d_')
234 if feature.len == 0 || feature !in user_defines {
235 continue
236 }
237 }
238 v_files << os.join_path_single(dir, file)
239 }
240 }
241 return v_files
242}
243
244pub fn is_test_file_for_backend(path string, backend string) bool {
245 file := os.file_name(path)
246 if file.ends_with('_test.v') {
247 return true
248 }
249 if file.ends_with('_test.c.v') {
250 return backend == 'c'
251 }
252 if file.ends_with('_test.js.v') {
253 return backend == 'js'
254 }
255 if !file.ends_with('.v') {
256 return false
257 }
258 base := file[..file.len - 2]
259 if !base.contains('.') {
260 return false
261 }
262 backend_suffix := base.all_after_last('.')
263 test_base := base.all_before_last('.')
264 if !test_base.ends_with('_test') {
265 return false
266 }
267 return backend_suffix == backend
268}
269
270// default_file_base supports default file base handling for pref.
271fn default_file_base(file string) ?string {
272 for marker in ['_default.c.v', '_default.v'] {
273 if file.ends_with(marker) {
274 return file[..file.len - marker.len]
275 }
276 }
277 return none
278}
279
280// os_specific_base supports os specific base handling for pref.
281fn os_specific_base(file string, target_os string) ?string {
282 if _ := default_file_base(file) {
283 return none
284 }
285 mut suffixes := []string{}
286 os_name := normalized_os(target_os)
287 if os_name != 'windows' {
288 suffixes << '_nix'
289 }
290 match os_name {
291 'windows' {
292 suffixes << '_windows'
293 }
294 'macos' {
295 suffixes << '_macos'
296 suffixes << '_darwin'
297 }
298 'linux' {
299 suffixes << '_linux'
300 }
301 'android' {
302 suffixes << '_android'
303 }
304 'ios' {
305 suffixes << '_ios'
306 }
307 'freebsd' {
308 suffixes << '_freebsd'
309 suffixes << '_bsd'
310 }
311 'openbsd' {
312 suffixes << '_openbsd'
313 suffixes << '_bsd'
314 }
315 'netbsd' {
316 suffixes << '_netbsd'
317 suffixes << '_bsd'
318 }
319 'dragonfly' {
320 suffixes << '_dragonfly'
321 suffixes << '_bsd'
322 }
323 'solaris' {
324 suffixes << '_solaris'
325 }
326 else {}
327 }
328
329 for suffix in suffixes {
330 for ext in ['.c.v', '.v'] {
331 marker := suffix + ext
332 if file.ends_with(marker) {
333 return file[..file.len - marker.len]
334 }
335 }
336 }
337 return none
338}
339
340// extract_define_feature supports extract define feature handling for pref.
341fn extract_define_feature(file string, marker string) string {
342 idx := file.index(marker) or { return '' }
343 rest := file[idx + marker.len..]
344 if rest.ends_with('.c.v') {
345 return rest[..rest.len - 4]
346 }
347 if rest.ends_with('.v') {
348 return rest[..rest.len - 2]
349 }
350 return rest
351}
352
353// normalized_os supports normalized os handling for pref.
354pub fn normalized_os(target_os string) string {
355 return match target_os {
356 'darwin' { 'macos' }
357 'mac' { 'macos' }
358 else { target_os }
359 }
360}
361
362// normalized_target_os supports normalized target os handling for Preferences.
363pub fn (p &Preferences) normalized_target_os() string {
364 return normalized_os(p.target_os)
365}
366
367// is_cross_target reports whether is cross target applies in pref.
368pub fn (p &Preferences) is_cross_target() bool {
369 return p.normalized_target_os() != normalized_os(os.user_os())
370}
371
372// comptime_flag_value supports comptime flag value handling for pref.
373pub fn comptime_flag_value(p &Preferences, name string) bool {
374 match name {
375 'macos', 'darwin', 'mac' {
376 return p.normalized_target_os() == 'macos'
377 }
378 'linux' {
379 return p.normalized_target_os() == 'linux'
380 }
381 'windows' {
382 return p.normalized_target_os() == 'windows'
383 }
384 'freebsd' {
385 return p.normalized_target_os() == 'freebsd'
386 }
387 'openbsd' {
388 return p.normalized_target_os() == 'openbsd'
389 }
390 'netbsd' {
391 return p.normalized_target_os() == 'netbsd'
392 }
393 'dragonfly' {
394 return p.normalized_target_os() == 'dragonfly'
395 }
396 'android' {
397 return p.normalized_target_os() == 'android'
398 }
399 'posix', 'unix' {
400 return p.normalized_target_os() != 'windows'
401 }
402 'bsd' {
403 tos := p.normalized_target_os()
404 return tos == 'macos' || tos == 'freebsd' || tos == 'openbsd' || tos == 'netbsd'
405 || tos == 'dragonfly'
406 }
407 'x64' {
408 $if x64 {
409 return true
410 }
411 return false
412 }
413 'x32' {
414 $if x32 {
415 return true
416 }
417 return false
418 }
419 'amd64' {
420 $if amd64 {
421 return true
422 }
423 return false
424 }
425 'arm64', 'aarch64' {
426 $if arm64 {
427 return true
428 }
429 return false
430 }
431 'little_endian' {
432 $if little_endian {
433 return true
434 }
435 return false
436 }
437 'big_endian' {
438 $if big_endian {
439 return true
440 }
441 return false
442 }
443 'debug' {
444 $if debug {
445 return true
446 }
447 return false
448 }
449 'native' {
450 return p.backend == 'arm64'
451 }
452 'builtin_write_buf_to_fd_should_use_c_write' {
453 return p.backend == 'arm64'
454 }
455 'tinyc' {
456 return p.backend == 'arm64'
457 }
458 'no_backtrace' {
459 return p.backend == 'arm64' || name in p.user_defines
460 }
461 'gcboehm', 'gcboehm_opt', 'prealloc', 'autofree', 'no_bounds_checking', 'freestanding',
462 'nofloat' {
463 return name in p.user_defines
464 }
465 else {
466 return name in p.user_defines
467 }
468 }
469}
470
471// comptime_optional_flag_value supports comptime optional flag value handling for pref.
472pub fn comptime_optional_flag_value(p &Preferences, name string) bool {
473 if name in p.user_defines {
474 return true
475 }
476 return comptime_flag_value(p, name)
477}
478
479// comptime_pkgconfig_value supports comptime pkgconfig value handling for pref.
480pub fn comptime_pkgconfig_value(name string) bool {
481 result := os.execute('pkg-config --exists ${name}')
482 return result.exit_code == 0
483}
484