| 1 | module pref |
| 2 | |
| 3 | import os |
| 4 | |
| 5 | // Preferences represents preferences data used by pref. |
| 6 | pub struct Preferences { |
| 7 | pub 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. |
| 20 | pub fn new_preferences() &Preferences { |
| 21 | return &Preferences{} |
| 22 | } |
| 23 | |
| 24 | // detect_vroot resolves detect vroot information for pref. |
| 25 | fn 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. |
| 40 | fn 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. |
| 68 | pub 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. |
| 74 | pub 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. |
| 121 | fn 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). |
| 135 | fn 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. |
| 144 | pub 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. |
| 191 | pub 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 | |
| 237 | pub 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. |
| 264 | fn 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. |
| 274 | fn 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. |
| 334 | fn 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. |
| 347 | pub 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. |
| 356 | pub 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. |
| 361 | pub 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. |
| 366 | pub 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. |
| 453 | pub 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. |
| 461 | pub fn comptime_pkgconfig_value(name string) bool { |
| 462 | result := os.execute('pkg-config --exists ${name}') |
| 463 | return result.exit_code == 0 |
| 464 | } |
| 465 | |