| 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. |
| 4 | module main |
| 5 | |
| 6 | import hash |
| 7 | import os |
| 8 | import term |
| 9 | import v.help |
| 10 | import v.pref |
| 11 | import v.util |
| 12 | import v.util.version |
| 13 | import v.builder |
| 14 | import v.builder.cbuilder |
| 15 | |
| 16 | @[markused] |
| 17 | const 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 | ] |
| 68 | const delegated_v2_exe_env = 'V_V2_EXE' |
| 69 | |
| 70 | @[unsafe] |
| 71 | fn 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 | |
| 80 | fn 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 | |
| 200 | fn 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 | |
| 212 | fn 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 | |
| 224 | fn 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] |
| 232 | fn 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 | |
| 286 | fn 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 | |
| 293 | fn 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] |
| 319 | fn 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 | |