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