v2 / vlib / v / util / util.v
656 lines · 614 sloc · 22.96 KB · 9755673d62e871d74284a9a1bc8dc928673e540c
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 util
5
6import os
7import os.filelock
8import term
9import rand
10import time
11import v.pref
12import v.util.recompilation
13import v.util.vflags
14import runtime
15
16// math.bits is needed by strconv.ftoa
17pub const builtin_module_parts = ['math.bits', 'strconv', 'dlmalloc', 'strconv.ftoa', 'strings',
18 'builtin', 'builtin.closure', 'builtin.overflow']
19pub const bundle_modules = ['clipboard', 'fontstash', 'gg', 'gx', 'sokol', 'szip', 'ui',
20 'builtin.closure', 'builtin.overflow']!
21
22pub const external_module_dependencies_for_tool = {
23 'vdoc': ['markdown']
24}
25
26const const_tabs = [
27 '',
28 '\t',
29 '\t\t',
30 '\t\t\t',
31 '\t\t\t\t',
32 '\t\t\t\t\t',
33 '\t\t\t\t\t\t',
34 '\t\t\t\t\t\t\t',
35 '\t\t\t\t\t\t\t\t',
36 '\t\t\t\t\t\t\t\t\t',
37 '\t\t\t\t\t\t\t\t\t\t',
38]!
39
40pub const nr_jobs = runtime.nr_jobs()
41
42pub fn module_is_builtin(mod string) bool {
43 return mod in builtin_module_parts
44}
45
46@[direct_array_access]
47pub fn tabs(n int) string {
48 return if n >= 0 && n < const_tabs.len { const_tabs[n] } else { '\t'.repeat(n) }
49}
50
51pub const stable_build_time = get_build_time()
52// get_build_time returns the current build time, while taking into account SOURCE_DATE_EPOCH
53// to support transparent reproducible builds. See also https://reproducible-builds.org/docs/source-date-epoch/
54// When SOURCE_DATE_EPOCH is not set, it will return the current UTC time.
55pub fn get_build_time() time.Time {
56 sde := os.getenv('SOURCE_DATE_EPOCH')
57 if sde == '' {
58 return time.utc()
59 }
60 return time.unix_nanosecond(sde.i64(), 0)
61}
62
63// set_vroot_folder sets the VCHILD env variable to 'true', and VEXE to the location of the V executable
64// It is called very early by launch_tool/3, so that those env variables can be available by all tools,
65// like `v doc`, `v fmt` etc, so they can use them to find how they were started.
66pub fn set_vroot_folder(vroot_path string) {
67 // Preparation for the compiler module:
68 // VEXE env variable is needed so that compiler.vexe_path() can return it
69 // later to whoever needs it. Note: guessing is a heuristic, so only try to
70 // guess the V executable name, if VEXE has not been set already.
71 vexe := os.getenv('VEXE')
72 if vexe == '' {
73 vname := if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
74 os.setenv('VEXE', os.real_path(os.join_path_single(vroot_path, vname)), true)
75 }
76 os.setenv('VCHILD', 'true', true)
77}
78
79fn tool_recompilation_args(tool_name string, user_os string) []string {
80 if user_os == 'freebsd' && tool_name == 'vdoc' {
81 // FreeBSD's default tcc setup can not compile vdoc reliably.
82 return ['-cc', 'cc']
83 }
84 if tool_name == 'vpm' && user_os == 'macos' {
85 // vpm performs HTTPS requests against the package registry, so it
86 // pulls in the TLS layer. Build it against OpenSSL instead of the
87 // bundled mbedtls: tcc miscompiles mbedtls big-int routines on
88 // Apple Silicon, which causes TLS handshakes to spin forever in
89 // mbedtls_mpi_sub_abs / ecp_modp (e.g. `v install sdl` would hang).
90 // Restricted to macOS: musl-based Linux builds lack the glibc
91 // headers OpenSSL pulls in (e.g. `sys/cdefs.h`), so forcing it
92 // there breaks `v install` in the docker-ubuntu-musl CI.
93 return ['-d', 'use_openssl']
94 }
95 return []string{}
96}
97
98fn temporary_tool_executable_path(vroot string, tool_name string) string {
99 sanitized_vroot := vroot.replace_each(['\\', '_', '/', '_', ':', '_'])
100 return path_of_executable(os.join_path(os.vtmp_dir(), 'tools', sanitized_vroot, tool_name))
101}
102
103fn fallback_tool_executable_path(vexe string, vroot string, tool_name string, tool_source string, tool_exe string, is_recompilation_disabled bool) string {
104 if !os.is_file(tool_source) {
105 return tool_exe
106 }
107 temporary_tool_exe := temporary_tool_executable_path(vroot, tool_name)
108 if is_recompilation_disabled && !os.exists(tool_exe) {
109 return temporary_tool_exe
110 }
111 tool_exe_dir := os.dir(tool_exe)
112 if os.is_writable(tool_exe_dir) {
113 return tool_exe
114 }
115 // Reuse a writable temp location when the packaged tool can not be updated in place.
116 if !os.exists(tool_exe) || os.exists(temporary_tool_exe)
117 || should_recompile_tool(vexe, tool_source, tool_name, tool_exe) {
118 return temporary_tool_exe
119 }
120 return tool_exe
121}
122
123// is_escape_sequence returns `true` if `c` is considered a valid escape sequence denoter.
124@[inline]
125pub fn is_escape_sequence(c u8) bool {
126 return c in [`x`, `u`, `e`, `n`, `r`, `t`, `v`, `a`, `f`, `b`, `\\`, `\``, `$`, `@`, `?`, `{`,
127 `}`, `'`, `"`, `U`]
128}
129
130// launch_tool - starts a V tool in a separate process, passing it the `args`.
131// All V tools are located in the cmd/tools folder, in files or folders prefixed by
132// the letter `v`, followed by the tool name, i.e. `cmd/tools/vdoc/` or `cmd/tools/vpm.v`.
133// The folder variant is suitable for larger and more complex tools, like `v doc`, because
134// it provides you the ability to split their source in separate .v files, organized by topic,
135// as well as have resources like static css/text/js files, that the tools can use.
136// launch_tool uses a timestamp based detection mechanism, so that after `v self`, each tool
137// will be recompiled too, before it is used, which guarantees that it would be up to date with
138// V itself. That mechanism can be disabled by package managers by creating/touching a small
139// `cmd/tools/.disable_autorecompilation` file, OR by changing the timestamps of all executables
140// in cmd/tools to be < 1024 seconds (in unix time).
141@[noreturn]
142pub fn launch_tool(is_verbose bool, tool_name string, args []string) {
143 vexe := pref.vexe_path()
144 vroot := os.dir(vexe)
145 set_vroot_folder(vroot)
146 tool_args := args_quote_paths(args)
147 tools_folder := os.join_path(vroot, 'cmd', 'tools')
148 tool_basename := os.real_path(os.join_path_single(tools_folder, tool_name))
149 mut tool_exe := ''
150 mut tool_source := ''
151 if os.is_dir(tool_basename) {
152 tool_exe = path_of_executable(os.join_path_single(tool_basename, os.file_name(tool_name)))
153 tool_source = tool_basename
154 } else {
155 tool_exe = path_of_executable(tool_basename)
156 tool_source = tool_basename + '.v'
157 }
158 original_tool_exe := tool_exe
159 if is_verbose {
160 println('launch_tool vexe : ${vexe}')
161 println('launch_tool vroot : ${vroot}')
162 println('launch_tool tool_source : ${tool_source}')
163 println('launch_tool tool_exe : ${tool_exe}')
164 println('launch_tool tool_args : ${tool_args}')
165 }
166 disabling_file := recompilation.disabling_file(vroot)
167 is_recompilation_disabled := os.exists(disabling_file)
168 tool_exe = fallback_tool_executable_path(vexe, vroot, tool_name, tool_source, tool_exe,
169 is_recompilation_disabled)
170 is_using_temporary_tool_exe := tool_exe != original_tool_exe
171 if !os.exists(tool_exe) && !os.exists(tool_source) {
172 eprintln('cannot find `${tool_name}`: missing both `${tool_exe}` and `${tool_source}`')
173 exit(1)
174 }
175 if !os.exists(tool_exe) && is_recompilation_disabled && !is_using_temporary_tool_exe {
176 eprintln('cannot find the prebuilt `${tool_name}` tool at `${tool_exe}`')
177 eprintln('Automatic tool recompilation is disabled by "${disabling_file}".')
178 eprintln('Please reinstall V from a complete package, or install V from source.')
179 exit(1)
180 }
181 should_compile := (!is_recompilation_disabled || is_using_temporary_tool_exe)
182 && should_recompile_tool(vexe, tool_source, tool_name, tool_exe)
183 if is_verbose {
184 println('launch_tool should_compile: ${should_compile}')
185 }
186 if should_compile {
187 emodules := external_module_dependencies_for_tool[tool_name]
188 for emodule in emodules {
189 check_module_is_installed(emodule, is_verbose, false) or { panic(err) }
190 }
191 mut compilation_command := '${os.quoted_path(vexe)} '
192 if tool_name in ['vself', 'vup', 'vdoctor', 'vsymlink'] {
193 // These tools will be called by users in cases where there
194 // is high chance of there being a problem somewhere. Thus
195 // it is better to always compile them with -g, so that in
196 // case these tools do crash/panic, their backtraces will have
197 // .v line numbers, to ease diagnostic in #bugs and issues.
198 compilation_command += ' -g '
199 }
200 if tool_name == 'vfmt' {
201 compilation_command += ' -d vfmt '
202 }
203 compilation_args := tool_recompilation_args(tool_name, os.user_os())
204 if compilation_args.len > 0 {
205 compilation_command += ' ${args_quote_paths(compilation_args)} '
206 }
207 if is_using_temporary_tool_exe {
208 tmp_tool_dir := os.dir(tool_exe)
209 if !os.is_dir(tmp_tool_dir) {
210 os.mkdir_all(tmp_tool_dir) or {
211 eprintln('cannot prepare temporary tool folder `${tmp_tool_dir}`: ${err}')
212 exit(1)
213 }
214 }
215 }
216 compilation_command += ' -o ${os.quoted_path(tool_exe)} '
217 compilation_command += os.quoted_path(tool_source)
218 if is_verbose {
219 println('Compiling ${tool_name} with: "${compilation_command}"')
220 }
221
222 current_work_dir := os.getwd()
223 tlog('recompiling ${tool_source}')
224 lockfile := tool_exe + '.lock'
225 tlog('lockfile: ${lockfile}')
226 mut l := filelock.new(lockfile)
227 if l.try_acquire() {
228 tlog('lockfile acquired')
229 tool_recompile_retry_max_count := int_max(1, os.getenv_opt('VUTIL_RETRY_MAX_COUNT') or {
230 '7'
231 }.int())
232 for i in 0 .. tool_recompile_retry_max_count {
233 tlog('looping i: ${i} / ${tool_recompile_retry_max_count}')
234 // ensure a stable and known working folder, when compiling V's tools, to avoid module lookup problems:
235 os.chdir(vroot) or {}
236 compile_sw := time.new_stopwatch()
237 tool_compilation := os.execute(compilation_command)
238 os.chdir(current_work_dir) or {}
239 tlog('tool_compilation.exit_code: ${tool_compilation.exit_code}, took: ${compile_sw.elapsed().milliseconds()}ms')
240 if tool_compilation.exit_code == 0 {
241 break
242 } else {
243 if tool_name == 'vup' {
244 eprintln('Cannot recompile the new version of `vup`: ${tool_compilation.exit_code}\n${tool_compilation.output}')
245 if os.exists(tool_exe) {
246 // Compilation failed, but we still have an already existing old `vup.exe`, that *probably* works.
247 // It is better to pretend the compilation succeeded, and try the old executable, then let it fail
248 // on its own, if it can not work too (it will produce a nicer diagnostic message), than to fail here
249 // right away, just because the new source is too breaking, for the older V frontend process,
250 // that is currently running :-|
251 eprintln('Trying an already existing old version of the `vup` tool instead...')
252 break
253 } else {
254 // No pre-existing `vup.exe` ... No choice but to show a message to the user :-( .
255 // Note: running `make` here from within the old V frontend process, is not reliable, since it can fail
256 // on windows and probably other systems, because the currently running executable is locked.
257 // `vup.exe` has logic to workaround that, and duplicating it here, is hard to debug/diagnose.
258 eprintln('Failed compilation of the `vup` tool, using the new V source code.')
259 eprintln('The new source code, is likely to be unsupported, by your existing older V executable.')
260 eprintln('Try running `make` or `makev.bat` manually.')
261 eprintln('If that fails, clone V from source in a new folder, and run `make` or `makev.bat` manually again there.')
262 l.release()
263 exit(1)
264 }
265 }
266 if i == tool_recompile_retry_max_count - 1 {
267 eprintln('cannot compile `${tool_source}`: ${tool_compilation.exit_code}\n${tool_compilation.output}')
268 l.release()
269 exit(1)
270 }
271 }
272 time.sleep((20 + rand.intn(40) or { 0 }) * time.millisecond)
273 }
274 tlog('lockfile releasing')
275 l.release()
276 tlog('lockfile released')
277 } else {
278 tlog('another process got the lock')
279 // wait till the other V tool recompilation process finished;
280 // the timeout is intentionally generous, since on slow CI VMs
281 // (e.g. FreeBSD QEMU), recompiling a tool can take >10s, and
282 // falling through with a missing tool_exe leads to ENOENT on exec:
283 if l.wait_acquire(60 * time.second) {
284 tlog('the other process finished')
285 l.release()
286 } else {
287 tlog('timeout...')
288 }
289 time.sleep((50 + rand.intn(40) or { 0 }) * time.millisecond)
290 tlog('result of the other process compiling ${tool_exe}: ${os.exists(tool_exe)}')
291 }
292 }
293 tlog('executing: ${tool_exe} with ${tool_args}')
294 $if windows {
295 cmd_system('${os.quoted_path(tool_exe)} ${tool_args}')
296 } $else $if js {
297 // no way to implement os.execvp in JS backend
298 cmd_system('${tool_exe} ${tool_args}')
299 } $else {
300 os.execvp(tool_exe, args) or {
301 eprintln('> error while executing: ${tool_exe} ${args}')
302 eprintln('> ${err}')
303 exit(1)
304 }
305 }
306 exit(2)
307}
308
309@[if trace_launch_tool ?]
310fn tlog(s string) {
311 ts := time.now().format_ss_micro()
312 eprintln('${term.yellow(ts)} ${term.gray(s)}')
313}
314
315@[noreturn]
316fn cmd_system(cmd string) {
317 res := os.system(cmd)
318 if res != 0 {
319 tlog('> error ${res}, while executing: ${cmd}')
320 }
321 exit(res)
322}
323
324// Note: should_recompile_tool/4 compares unix timestamps that have 1 second resolution
325// That means that a tool can get recompiled twice, if called in short succession.
326// TODO: use a nanosecond mtime timestamp, if available.
327pub fn should_recompile_tool(vexe string, tool_source string, tool_name string, tool_exe string) bool {
328 if os.is_dir(tool_source) {
329 source_files := os.walk_ext(tool_source, '.v')
330 mut newest_sfile := ''
331 mut newest_sfile_mtime := i64(0)
332 for sfile in source_files {
333 mtime := os.file_last_mod_unix(sfile)
334 if mtime > newest_sfile_mtime {
335 newest_sfile_mtime = mtime
336 newest_sfile = sfile
337 }
338 }
339 single_file_recompile := should_recompile_tool(vexe, newest_sfile, tool_name, tool_exe)
340 // eprintln('>>> should_recompile_tool: tool_source: ${tool_source} | ${single_file_recompile} | ${newest_sfile}')
341 return single_file_recompile
342 }
343 // TODO: Caching should be done on the `vlib/v` level.
344 mut should_compile := false
345 if !os.exists(tool_exe) {
346 should_compile = true
347 } else {
348 mtime_vexe := os.file_last_mod_unix(vexe)
349 mtime_tool_exe := os.file_last_mod_unix(tool_exe)
350 mtime_tool_source := os.file_last_mod_unix(tool_source)
351 if mtime_tool_exe <= mtime_vexe {
352 // v was recompiled, maybe after v up ...
353 // rebuild the tool too just in case
354 should_compile = true
355 if tool_name == 'vself' || tool_name == 'vup' {
356 // The purpose of vself/up is to update and recompile v itself.
357 // After the first 'v self' execution, v will be modified, so
358 // then a second 'v self' will detect, that v is newer than the
359 // vself executable, and try to recompile vself/up again, which
360 // will slow down the next v recompilation needlessly.
361 should_compile = false
362 }
363 }
364 if mtime_tool_exe <= mtime_tool_source {
365 // the user changed the source code of the tool, or git updated it:
366 should_compile = true
367 }
368 // GNU Guix and possibly other environments, have bit for bit reproducibility in mind,
369 // including filesystem attributes like modification times, so they set the modification
370 // times of executables to a small number like 0, 1 etc. In this case, we should not
371 // recompile even if other heuristics say that we should. Users in such environments,
372 // have to explicitly do: `v cmd/tools/vfmt.v`, and/or install v from source, and not
373 // use the system packaged one, if they desire to develop v itself.
374 if mtime_vexe < 1024 && mtime_tool_exe < 1024 {
375 should_compile = false
376 }
377 }
378 return should_compile
379}
380
381fn tool_source2name_and_exe(tool_source string) (string, string) {
382 sfolder := os.dir(tool_source)
383 tool_name := os.base(tool_source).replace('.v', '')
384 tool_exe := os.join_path_single(sfolder, path_of_executable(tool_name))
385 return tool_name, tool_exe
386}
387
388pub fn quote_path(s string) string {
389 return os.quoted_path(s)
390}
391
392pub fn args_quote_paths(args []string) string {
393 return args.map(quote_path(it)).join(' ')
394}
395
396pub fn path_of_executable(path string) string {
397 $if windows {
398 return path + '.exe'
399 }
400 return path
401}
402
403@[heap]
404struct SourceCache {
405mut:
406 sources map[string]string
407}
408
409@[unsafe]
410pub fn cached_read_source_file(path string) !string {
411 mut static cache := &SourceCache(unsafe { nil })
412 if cache == unsafe { nil } {
413 cache = &SourceCache{}
414 }
415
416 $if trace_cached_read_source_file ? {
417 println('cached_read_source_file ${path}')
418 }
419 if path == '' {
420 unsafe { cache.sources.free() }
421 unsafe { free(cache) }
422 cache = &SourceCache(unsafe { nil })
423 return error('memory source file cache cleared')
424 }
425
426 // eprintln('>> cached_read_source_file path: ${path}')
427 if res := cache.sources[path] {
428 // eprintln('>> cached')
429 $if trace_cached_read_source_file_cached ? {
430 println('cached_read_source_file cached ${path}')
431 }
432 return res
433 }
434 // eprintln('>> not cached | cache.sources.len: ${cache.sources.len}')
435 $if trace_cached_read_source_file_not_cached ? {
436 println('cached_read_source_file not cached ${path}')
437 }
438 raw_text := os.read_file(path) or { return error('failed to open ${path}') }
439 res := skip_bom(raw_text)
440 cache.sources[path] = res
441 return res
442}
443
444pub fn replace_op(s string) string {
445 return match s {
446 '+' { '_plus' }
447 '-' { '_minus' }
448 '*' { '_mult' }
449 '**' { '_pow' }
450 '/' { '_div' }
451 '%' { '_mod' }
452 '[]' { '_index' }
453 '[]=' { '_index_set' }
454 '<' { '_lt' }
455 '>' { '_gt' }
456 '==' { '_eq' }
457 else { '' }
458 }
459}
460
461// join_env_vflags_and_os_args returns all the arguments (the ones from the env variable VFLAGS too), passed on the command line.
462pub fn join_env_vflags_and_os_args() []string {
463 return vflags.join_env_vflags_and_os_args()
464}
465
466pub fn check_module_is_installed(modulename string, is_verbose bool, need_update bool) !bool {
467 mpath := os.join_path_single(os.vmodules_dir(), modulename)
468 mod_v_file := os.join_path_single(mpath, 'v.mod')
469 murl := 'https://github.com/vlang/${modulename}'
470 if is_verbose {
471 eprintln('check_module_is_installed: mpath: ${mpath}')
472 eprintln('check_module_is_installed: mod_v_file: ${mod_v_file}')
473 eprintln('check_module_is_installed: murl: ${murl}')
474 }
475 vexe := pref.vexe_path()
476 if os.exists(mod_v_file) {
477 if need_update {
478 update_cmd := "${os.quoted_path(vexe)} update '${modulename}'"
479 if is_verbose {
480 eprintln('check_module_is_installed: updating with ${update_cmd} ...')
481 }
482 update_res := os.execute(update_cmd)
483 if update_res.exit_code < 0 {
484 return error('can not start ${update_cmd}, error: ${update_res.output}')
485 }
486 if update_res.exit_code != 0 {
487 eprintln('Warning: `${modulename}` exists, but is not updated.
488V will continue, since updates can fail due to temporary network problems,
489and the existing module `${modulename}` may still work.')
490 if is_verbose {
491 eprintln('Details:')
492 eprintln(update_res.output)
493 }
494 eprintln('-'.repeat(50))
495 }
496 }
497 return true
498 }
499 if is_verbose {
500 eprintln('check_module_is_installed: cloning from ${murl} ...')
501 }
502 cloning_res :=
503 os.execute('${os.quoted_path(vexe)} retry -- git clone ${os.quoted_path(murl)} ${os.quoted_path(mpath)}')
504 if cloning_res.exit_code != 0 {
505 return error_with_code('cloning failed, details: ${cloning_res.output}',
506 cloning_res.exit_code)
507 }
508 if !os.exists(mod_v_file) {
509 return error('even after cloning, ${mod_v_file} is still missing')
510 }
511 if is_verbose {
512 eprintln('check_module_is_installed: done')
513 }
514 return true
515}
516
517pub fn ensure_modules_for_all_tools_are_installed(is_verbose bool) {
518 for tool_name, tool_modules in external_module_dependencies_for_tool {
519 if is_verbose {
520 eprintln('Installing modules for tool: ${tool_name} ...')
521 }
522 for emodule in tool_modules {
523 check_module_is_installed(emodule, is_verbose, false) or { panic(err) }
524 }
525 }
526}
527
528@[inline]
529pub fn strip_mod_name(name string) string {
530 // For generic types like main.Message[main.Payload], strip module prefixes
531 // from both the type name and the generic parameters
532 if bracket_pos := name.index('[') {
533 prefix := name[..bracket_pos]
534 suffix := name[bracket_pos..]
535 // Also strip module names from generic parameters inside brackets
536 // e.g., [main.Payload, main.Foo] -> [Payload, Foo]
537 mut result := prefix.all_after_last('.') + '['
538 params := suffix[1..suffix.len - 1] // Remove [ and ]
539 mut param_parts := []string{}
540 for param in params.split(', ') {
541 param_parts << param.all_after_last('.')
542 }
543 result += param_parts.join(', ')
544 result += ']'
545 return result
546 }
547 return name.all_after_last('.')
548}
549
550@[inline]
551pub fn strip_main_name(name string) string {
552 return name.replace('main.', '')
553}
554
555@[inline]
556pub fn no_dots(s string) string {
557 for ch in s {
558 if ch == `.` || ch == `-` {
559 return s.replace_each(['.', '__', '-', '_'])
560 }
561 }
562 return s
563}
564
565const map_prefix = 'map[string]'
566
567// no_cur_mod - removes cur_mod. prefix from typename,
568// but *only* when it is at the start, i.e.:
569// no_cur_mod('vproto.Abdcdef', 'proto') == 'vproto.Abdcdef'
570// even though proto. is a substring
571pub fn no_cur_mod(typename string, cur_mod string) string {
572 mut res := typename
573 mod_prefix := cur_mod + '.'
574 has_map_prefix := res.starts_with(map_prefix)
575 if has_map_prefix {
576 res = res.replace_once(map_prefix, '')
577 }
578 no_symbols := res.trim_left('&[]')
579 should_shorten := no_symbols.starts_with(mod_prefix)
580 if should_shorten {
581 res = res.replace_once(mod_prefix, '')
582 }
583 if has_map_prefix {
584 res = map_prefix + res
585 }
586 return res
587}
588
589pub fn prepare_tool_when_needed(source_name string) {
590 vexe := os.getenv('VEXE')
591 vroot := os.dir(vexe)
592 stool := os.join_path(vroot, 'cmd', 'tools', source_name)
593 tool_name, tool_exe := tool_source2name_and_exe(stool)
594 if should_recompile_tool(vexe, stool, tool_name, tool_exe) {
595 time.sleep((1001 + rand.intn(20) or { 0 }) * time.millisecond) // TODO: remove this when we can get mtime with a better resolution
596 recompile_file(vexe, stool)
597 }
598}
599
600pub fn recompile_file(vexe string, file string) {
601 cmd := '${os.quoted_path(vexe)} ${os.quoted_path(file)}'
602 $if trace_recompilation ? {
603 println('recompilation command: ${cmd}')
604 }
605 recompile_result := os.system(cmd)
606 if recompile_result != 0 {
607 eprintln('could not recompile ${file}')
608 exit(2)
609 }
610}
611
612// get_vtmp_folder returns the path to a folder, that is writable to V programs,
613// and specific to the user. It can be overridden by setting the env variable `VTMP`.
614pub fn get_vtmp_folder() string {
615 return os.vtmp_dir()
616}
617
618pub fn should_bundle_module(mod string) bool {
619 return mod in bundle_modules || (mod.contains('.') && mod.all_before('.') in bundle_modules)
620}
621
622// find_all_v_files - given a list of files/folders, finds all .v/.vsh files
623// if some of the files/folders on the list does not exist, or a file is not
624// a .v or .vsh file, returns an error instead.
625pub fn find_all_v_files(roots []string) ![]string {
626 mut files := []string{}
627 for file in roots {
628 if os.is_dir(file) {
629 files << os.walk_ext(file, '.v')
630 files << os.walk_ext(file, '.vsh')
631 continue
632 }
633 if !file.ends_with('.v') && !file.ends_with('.vv') && !file.ends_with('.vsh') {
634 return error('v fmt can only be used on .v files.\nOffending file: "${file}"')
635 }
636 if !os.exists(file) {
637 return error('"${file}" does not exist')
638 }
639 files << file
640 }
641 return files
642}
643
644// free_caches knows about all `util` caches and makes sure that they are freed
645// if you add another cached unsafe function using static, do not forget to add
646// a mechanism to clear its cache, and call it here.
647pub fn free_caches() {
648 unsafe {
649 cached_file2sourcelines('')
650 cached_read_source_file('') or { '' }
651 }
652}
653
654pub fn read_file(file_path string) !string {
655 return unsafe { cached_read_source_file(file_path) }
656}
657