v4 / vlib / v3 / pref / pref.v
464 lines · 442 sloc · 11.46 KB · 1498e02bf64c6e33255fc0d3055b6e72900af9a5
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 has_os_specific := map[string]bool{}
197 for file in all_files {
198 if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
199 continue
200 }
201 if file_has_incompatible_os_suffix(file, target_os) {
202 continue
203 }
204 if base := os_specific_base(file, target_os) {
205 has_os_specific[base] = true
206 }
207 }
208 mut v_files := []string{}
209 for file in all_files {
210 if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
211 continue
212 }
213 if file_has_incompatible_os_suffix(file, target_os) {
214 continue
215 }
216 if base := default_file_base(file) {
217 if has_os_specific[base] {
218 continue
219 }
220 }
221 if file.contains('_notd_') {
222 feature := extract_define_feature(file, '_notd_')
223 if feature.len > 0 && feature in user_defines {
224 continue
225 }
226 } else if file.contains('_d_') {
227 feature := extract_define_feature(file, '_d_')
228 if feature.len == 0 || feature !in user_defines {
229 continue
230 }
231 }
232 v_files << os.join_path_single(dir, file)
233 }
234 return v_files
235}
236
237pub fn is_test_file_for_backend(path string, backend string) bool {
238 file := os.file_name(path)
239 if file.ends_with('_test.v') {
240 return true
241 }
242 if file.ends_with('_test.c.v') {
243 return backend == 'c'
244 }
245 if file.ends_with('_test.js.v') {
246 return backend == 'js'
247 }
248 if !file.ends_with('.v') {
249 return false
250 }
251 base := file[..file.len - 2]
252 if !base.contains('.') {
253 return false
254 }
255 backend_suffix := base.all_after_last('.')
256 test_base := base.all_before_last('.')
257 if !test_base.ends_with('_test') {
258 return false
259 }
260 return backend_suffix == backend
261}
262
263// default_file_base supports default file base handling for pref.
264fn default_file_base(file string) ?string {
265 for marker in ['_default.c.v', '_default.v'] {
266 if file.ends_with(marker) {
267 return file[..file.len - marker.len]
268 }
269 }
270 return none
271}
272
273// os_specific_base supports os specific base handling for pref.
274fn os_specific_base(file string, target_os string) ?string {
275 if _ := default_file_base(file) {
276 return none
277 }
278 mut suffixes := []string{}
279 os_name := normalized_os(target_os)
280 if os_name != 'windows' {
281 suffixes << '_nix'
282 }
283 match os_name {
284 'windows' {
285 suffixes << '_windows'
286 }
287 'macos' {
288 suffixes << '_macos'
289 suffixes << '_darwin'
290 }
291 'linux' {
292 suffixes << '_linux'
293 }
294 'android' {
295 suffixes << '_android'
296 }
297 'ios' {
298 suffixes << '_ios'
299 }
300 'freebsd' {
301 suffixes << '_freebsd'
302 suffixes << '_bsd'
303 }
304 'openbsd' {
305 suffixes << '_openbsd'
306 suffixes << '_bsd'
307 }
308 'netbsd' {
309 suffixes << '_netbsd'
310 suffixes << '_bsd'
311 }
312 'dragonfly' {
313 suffixes << '_dragonfly'
314 suffixes << '_bsd'
315 }
316 'solaris' {
317 suffixes << '_solaris'
318 }
319 else {}
320 }
321
322 for suffix in suffixes {
323 for ext in ['.c.v', '.v'] {
324 marker := suffix + ext
325 if file.ends_with(marker) {
326 return file[..file.len - marker.len]
327 }
328 }
329 }
330 return none
331}
332
333// extract_define_feature supports extract define feature handling for pref.
334fn extract_define_feature(file string, marker string) string {
335 idx := file.index(marker) or { return '' }
336 rest := file[idx + marker.len..]
337 if rest.ends_with('.c.v') {
338 return rest[..rest.len - 4]
339 }
340 if rest.ends_with('.v') {
341 return rest[..rest.len - 2]
342 }
343 return rest
344}
345
346// normalized_os supports normalized os handling for pref.
347pub fn normalized_os(target_os string) string {
348 return match target_os {
349 'darwin' { 'macos' }
350 'mac' { 'macos' }
351 else { target_os }
352 }
353}
354
355// normalized_target_os supports normalized target os handling for Preferences.
356pub fn (p &Preferences) normalized_target_os() string {
357 return normalized_os(p.target_os)
358}
359
360// is_cross_target reports whether is cross target applies in pref.
361pub fn (p &Preferences) is_cross_target() bool {
362 return p.normalized_target_os() != normalized_os(os.user_os())
363}
364
365// comptime_flag_value supports comptime flag value handling for pref.
366pub fn comptime_flag_value(p &Preferences, name string) bool {
367 match name {
368 'macos', 'darwin', 'mac' {
369 return p.normalized_target_os() == 'macos'
370 }
371 'linux' {
372 return p.normalized_target_os() == 'linux'
373 }
374 'windows' {
375 return p.normalized_target_os() == 'windows'
376 }
377 'freebsd' {
378 return p.normalized_target_os() == 'freebsd'
379 }
380 'openbsd' {
381 return p.normalized_target_os() == 'openbsd'
382 }
383 'netbsd' {
384 return p.normalized_target_os() == 'netbsd'
385 }
386 'dragonfly' {
387 return p.normalized_target_os() == 'dragonfly'
388 }
389 'android' {
390 return p.normalized_target_os() == 'android'
391 }
392 'posix', 'unix' {
393 return p.normalized_target_os() != 'windows'
394 }
395 'bsd' {
396 tos := p.normalized_target_os()
397 return tos == 'macos' || tos == 'freebsd' || tos == 'openbsd' || tos == 'netbsd'
398 || tos == 'dragonfly'
399 }
400 'x64', 'amd64' {
401 $if amd64 {
402 return true
403 }
404 return false
405 }
406 'arm64', 'aarch64' {
407 $if arm64 {
408 return true
409 }
410 return false
411 }
412 'little_endian' {
413 $if little_endian {
414 return true
415 }
416 return false
417 }
418 'big_endian' {
419 $if big_endian {
420 return true
421 }
422 return false
423 }
424 'debug' {
425 $if debug {
426 return true
427 }
428 return false
429 }
430 'native' {
431 return p.backend == 'arm64'
432 }
433 'builtin_write_buf_to_fd_should_use_c_write' {
434 return p.backend == 'arm64'
435 }
436 'tinyc' {
437 return p.backend == 'arm64'
438 }
439 'no_backtrace' {
440 return p.backend == 'arm64' || name in p.user_defines
441 }
442 'gcboehm', 'gcboehm_opt', 'prealloc', 'autofree', 'no_bounds_checking', 'freestanding',
443 'nofloat' {
444 return name in p.user_defines
445 }
446 else {
447 return name in p.user_defines
448 }
449 }
450}
451
452// comptime_optional_flag_value supports comptime optional flag value handling for pref.
453pub fn comptime_optional_flag_value(p &Preferences, name string) bool {
454 if name in p.user_defines {
455 return true
456 }
457 return comptime_flag_value(p, name)
458}
459
460// comptime_pkgconfig_value supports comptime pkgconfig value handling for pref.
461pub fn comptime_pkgconfig_value(name string) bool {
462 result := os.execute('pkg-config --exists ${name}')
463 return result.exit_code == 0
464}
465