v / cmd / v / v.v
334 lines · 317 sloc · 9.11 KB · 81a5657604ec6da99c25e26546870c6888d6fdde
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 main
5
6import hash
7import os
8import term
9import v.help
10import v.pref
11import v.util
12import v.util.version
13import v.builder
14import v.builder.cbuilder
15
16@[markused]
17const external_tools = [
18 'ast',
19 'bin2v',
20 'bug',
21 'bug-report',
22 'bug-report-send',
23 'build-examples',
24 'build-tools',
25 'build-vbinaries',
26 'bump',
27 'check-md',
28 'complete',
29 'compress',
30 'cover',
31 'diff',
32 'doc',
33 'doctor',
34 'download',
35 'fmt',
36 'git-fmt-hook',
37 'gret',
38 'ls',
39 'missdoc',
40 'quest',
41 'reduce',
42 'repl',
43 'repeat',
44 'retry',
45 'self',
46 'setup-freetype',
47 'shader',
48 'share',
49 'should-compile-all',
50 'sqlite',
51 'symlink',
52 'scan',
53 'test',
54 'test-all', // runs most of the tests and other checking tools, that will be run by the CI
55 'test-cleancode',
56 'test-fmt',
57 'test-parser',
58 'test-self',
59 'time',
60 'timeout',
61 'tracev',
62 'up',
63 'vet',
64 'wipe-cache',
65 'watch',
66 'where',
67]
68const delegated_v2_exe_env = 'V_V2_EXE'
69
70@[unsafe]
71fn timers_pointer(p &util.Timers) &util.Timers {
72 // TODO: the static variable here is used as a workaround for the current incompatibility of -usecache and globals in the main module:
73 mut static ptimers := unsafe { &util.Timers(nil) }
74 if p != unsafe { nil } {
75 ptimers = p
76 }
77 return ptimers
78}
79
80fn main() {
81 unbuffer_stdout()
82 mut timers_should_print := false
83 $if time_v ? {
84 timers_should_print = true
85 }
86 if '-show-timings' in os.args {
87 timers_should_print = true
88 }
89 mut timers := unsafe {
90 timers_pointer(util.new_timers(
91 should_print: timers_should_print
92 label: 'main'
93 ))
94 }
95 timers.start('v start')
96 timers.show('v start')
97 timers.start('TOTAL')
98 // use at_exit here, instead of defer, since some code paths later do early exit(0) or exit(1), for showing errors, or after `v run`
99 at_exit(fn () {
100 mut timers := unsafe { timers_pointer(nil) }
101 timers.show('TOTAL')
102 })!
103 timers.start('v parsing CLI args')
104 args := os.args[1..]
105
106 if args.len == 0 || args[0] in ['-', 'repl'] {
107 if args.len == 0 {
108 // Running `./v` without args launches repl
109 if os.is_atty(0) == 0 {
110 mut args_and_flags := util.join_env_vflags_and_os_args()[1..].clone()
111 args_and_flags << ['run', '-']
112 pref.parse_args_and_show_errors(external_tools, args_and_flags, true)
113 }
114 }
115 util.launch_tool(false, 'vrepl', os.args[1..])
116 return
117 }
118 mut args_and_flags := util.join_env_vflags_and_os_args()[1..]
119 prefs, command := pref.parse_args_and_show_errors(external_tools, args_and_flags, true)
120 maybe_delegate_to_vvmrc(command, prefs)
121 maybe_delegate_to_v2(command, prefs)
122 if prefs.use_cache && os.user_os() == 'windows' {
123 eprintln('-usecache is currently disabled on windows')
124 exit(1)
125 }
126 timers.show('v parsing CLI args')
127
128 setup_vbuild_env_vars(prefs)
129
130 // Start calling the correct functions/external tools
131 // Note for future contributors: Please add new subcommands in the `match` block below.
132 if command in external_tools {
133 // External tools
134 util.launch_tool(prefs.is_verbose, 'v' + command, os.args[1..])
135 return
136 }
137 match command {
138 'run', 'crun', 'build', 'build-module' {
139 rebuild(prefs)
140 return
141 }
142 'help' {
143 invoke_help_and_exit(args)
144 }
145 'version' {
146 println(version.full_v_version(prefs.is_verbose))
147 return
148 }
149 'new', 'init' {
150 util.launch_tool(prefs.is_verbose, 'vcreate', os.args[1..])
151 return
152 }
153 'install', 'link', 'list', 'outdated', 'remove', 'search', 'show', 'unlink', 'update',
154 'upgrade' {
155 util.launch_tool(prefs.is_verbose, 'vpm', os.args[1..])
156 return
157 }
158 'vlib-docs' {
159 util.launch_tool(prefs.is_verbose, 'vdoc', ['doc', 'vlib'])
160 }
161 'interpret' {
162 eprintln('use v -v2 -eval file.v')
163 exit(1)
164 }
165 'get' {
166 eprintln('V Error: Use `v install` to install modules from vpm.vlang.io')
167 exit(1)
168 }
169 'translate' {
170 util.launch_tool(prefs.is_verbose, 'translate', os.args[1..])
171 // exit(1)
172 // return
173 }
174 else {
175 if command.ends_with('.v') || os.exists(command) {
176 // println('command')
177 // println(prefs.path)
178 rebuild(prefs)
179 return
180 }
181 }
182 }
183
184 if prefs.is_help {
185 invoke_help_and_exit(args)
186 }
187
188 other_commands := ['run', 'crun', 'build', 'build-module', 'help', 'version', 'new', 'init',
189 'install', 'link', 'list', 'outdated', 'remove', 'search', 'show', 'unlink', 'update',
190 'upgrade', 'vlib-docs', 'translate']
191 mut all_commands := []string{}
192 all_commands << external_tools
193 all_commands << other_commands
194 all_commands.sort()
195 eprintln(util.new_suggestion(command, all_commands, similarity_threshold: 0.2).say('v: unknown command `${command}`'))
196 eprintln('Run ${term.highlight_command('v help')} for usage.')
197 exit(1)
198}
199
200fn invoke_help_and_exit(remaining []string) {
201 match remaining.len {
202 0, 1 { help.print_and_exit('default', exit_code: 0) }
203 2 { help.print_and_exit(remaining[1], exit_code: 0) }
204 else {}
205 }
206
207 eprintln('${term.highlight_command('v help')}: provide only one help topic.')
208 eprintln('For usage information, use ${term.highlight_command('v help')}.')
209 exit(1)
210}
211
212fn maybe_delegate_to_v2(command string, prefs &pref.Preferences) {
213 is_ownership := '-ownership' in os.args
214 if !prefs.use_v2 && !is_ownership {
215 return
216 }
217 if !is_v2_relevant_command(command, prefs) {
218 eprintln('v: `-v2`/`-ownership` currently support direct compilation only. Use `v -v2 hello.v` or `v -ownership module_dir`.')
219 exit(1)
220 }
221 launch_v2_compiler(prefs.is_verbose, os.args[1..].filter(it != '-v2'), is_ownership)
222}
223
224fn is_v2_relevant_command(command string, prefs &pref.Preferences) bool {
225 if prefs.path == '' || prefs.is_run || prefs.is_crun {
226 return false
227 }
228 return prefs.path == command && (command.ends_with('.v') || os.exists(command))
229}
230
231@[noreturn]
232fn launch_v2_compiler(is_verbose bool, args []string, is_ownership bool) {
233 vexe := pref.vexe_path()
234 vroot := os.dir(vexe)
235 util.set_vroot_folder(vroot)
236 tool_name := if is_ownership { 'v2_ownership' } else { 'v2' }
237 mut v2_exe := os.getenv(delegated_v2_exe_env)
238 if v2_exe == '' {
239 v2_main_source := os.join_path(vroot, 'cmd', 'v2', 'v2.v')
240 v2_cmd_dir := os.join_path(vroot, 'cmd', 'v2')
241 v2_vlib_dir := os.join_path(vroot, 'vlib', 'v2')
242 v2_exe = cached_v2_executable_path(vroot, is_ownership)
243 v2_exe_dir := os.dir(v2_exe)
244 os.mkdir_all(v2_exe_dir) or {
245 eprintln('cannot create `${v2_exe_dir}`: ${err}')
246 exit(1)
247 }
248 if util.should_recompile_tool(vexe, v2_cmd_dir, tool_name, v2_exe)
249 || util.should_recompile_tool(vexe, v2_vlib_dir, tool_name, v2_exe) {
250 d_flag := if is_ownership { '-d ownership ' } else { '' }
251 compilation_command := '${os.quoted_path(vexe)} ${d_flag}-o ${os.quoted_path(v2_exe)} ${os.quoted_path(v2_main_source)}'
252 if is_verbose {
253 println('Compiling ${tool_name} with: "${compilation_command}"')
254 }
255 current_work_dir := os.getwd()
256 os.chdir(vroot) or {}
257 tool_compilation := os.execute(compilation_command)
258 os.chdir(current_work_dir) or {}
259 if tool_compilation.exit_code != 0 {
260 eprintln('cannot compile `${v2_main_source}`: ${tool_compilation.exit_code}\n${tool_compilation.output}')
261 exit(1)
262 }
263 }
264 } else if !os.is_file(v2_exe) {
265 eprintln('v: `${delegated_v2_exe_env}` points to a missing executable: `${v2_exe}`')
266 exit(1)
267 }
268 os.setenv('VCHILD', 'true', true)
269 os.setenv('VEXE', os.real_path(v2_exe), true)
270 $if windows {
271 mut process := os.new_process(v2_exe)
272 process.set_args(args)
273 process.wait()
274 exit_code := if process.code == -1 { 1 } else { process.code }
275 process.close()
276 exit(exit_code)
277 } $else {
278 os.execvp(v2_exe, args) or {
279 eprintln('> error while executing: ${v2_exe} ${args}')
280 panic(err)
281 }
282 }
283 exit(2)
284}
285
286fn cached_v2_executable_path(vroot string, is_ownership bool) string {
287 vroot_hash := hash.sum64_string(os.real_path(vroot), 0).hex_full()
288 exe_name := if is_ownership { 'v2_ownership' } else { 'v2' }
289 return util.path_of_executable(os.join_path(os.vtmp_dir(), 'v', 'delegated_v2', vroot_hash,
290 exe_name))
291}
292
293fn rebuild(prefs &pref.Preferences) {
294 match prefs.backend {
295 .c {
296 $if no_bootstrapv ? {
297 // TODO: improve the bootstrapping with a split C backend here.
298 // C code generated by `VEXE=v cmd/tools/builders/c_builder -os cross -o c.c cmd/tools/builders/c_builder.v`
299 // is enough to bootstrap the C backend, and thus the rest, but currently bootstrapping relies on
300 // `v -os cross -o v.c cmd/v` having a functional C codegen inside instead.
301 util.launch_tool(prefs.is_verbose, 'builders/c_builder', os.args[1..])
302 }
303 builder.compile('build', prefs, cbuilder.compile_c)
304 }
305 .js_node, .js_freestanding, .js_browser {
306 util.launch_tool(prefs.is_verbose, 'builders/js_builder', os.args[1..])
307 }
308 .interpret {
309 eprintln('use v -v2 -eval file.v')
310 exit(1)
311 }
312 .wasm {
313 util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..])
314 }
315 }
316}
317
318@[manualfree]
319fn setup_vbuild_env_vars(prefs &pref.Preferences) {
320 mut facts := []string{cap: 10}
321 facts << prefs.os.lower()
322 facts << prefs.ccompiler_type.str()
323 facts << prefs.arch.str()
324 if prefs.is_prod {
325 facts << 'prod'
326 }
327 github_job := os.getenv('GITHUB_JOB')
328 if github_job != '' {
329 facts << github_job
330 }
331 pref.set_build_flags_and_defines(facts, prefs.compile_defines_all)
332 unsafe { github_job.free() }
333 unsafe { facts.free() }
334}
335