v / cmd / tools / vself.v
435 lines · 407 sloc · 12.26 KB · 6b8a901e8ff6f821514c65e4a0a820052af241d4
Raw
1module main
2
3import os
4import os.cmdline
5import v.util.recompilation
6
7const args_ = arguments()
8const is_debug = args_.contains('-debug')
9
10// support a renamed `v` executable too:
11const vexe = os.getenv_opt('VEXE') or { @VEXE }
12
13const vroot = os.dir(vexe)
14const vself_flags_with_values = ['-o', '-os', '-cc', '-gc', '-cf', '-cflags', '-d', '-define']
15
16fn main() {
17 // make testing `v up` easier, by providing a way to force `v self` to fail,
18 // to test the fallback logic:
19 if os.getenv('VSELF_SHOULD_FAIL') != '' {
20 eprintln('v self failed')
21 exit(1)
22 }
23 vexe_name := os.file_name(vexe)
24 short_v_name := vexe_name.all_before('.')
25
26 recompilation.must_be_enabled(vroot,
27 'Please install V from source, to use `${vexe_name} self` .')
28 os.chdir(vroot)!
29 os.setenv('VCOLORS', 'always', true)
30 repeat_count, mut args := extract_repeat_count(args_[1..].filter(it != 'self'))
31 if args.len == 0 || ('-cc' !in args && '-prod' !in args && '-parallel-cc' !in args) {
32 // compiling by default, i.e. `v self`:
33 uos := os.user_os()
34 uname := os.uname()
35 if uos == 'macos' && uname.machine == 'arm64' {
36 // Apple silicon, like m1, m2 etc
37 // Use tcc by default for V, since tinycc is much faster and also
38 // it already supports compiling many programs like V itself, that do not depend on inlined objective-C code
39 args << '-cc tcc'
40 } else if uos == 'linux' && uname.machine in ['arm64', 'aarch64'] {
41 // Bundled TCC can hang while bootstrapping V on Linux ARM64, so
42 // prefer the system compiler for self-builds there.
43 args << ['-cc', os.getenv_opt('CC') or { 'cc' }]
44 }
45 }
46 if !has_gc_arg(args) {
47 args << ['-gc', 'none']
48 }
49 jargs := args.join(' ')
50 obinary := cmdline.option(args, '-o', '')
51 sargs := if obinary != '' { jargs } else { '${jargs} -o v2' }
52 options := if args.len > 0 { '(${sargs})' } else { '' }
53 final_binary := if obinary != '' { obinary } else { 'v2' }
54 pgo_cc_kind := pgo_compiler_kind(args)
55 for run_idx in 0 .. repeat_count {
56 run_label := if repeat_count > 1 { ' [${run_idx + 1}/${repeat_count}]' } else { '' }
57 println('V self compiling${run_label} ${options}...')
58 cmd := '${os.quoted_path(vexe)} ${sargs} ${os.quoted_path('cmd/v')}'
59 mut used_pgo := false
60 if pgo_cc_kind != '' {
61 used_pgo = compile_with_pgo(vroot, vexe, args, final_binary, pgo_cc_kind)
62 if !used_pgo {
63 eprintln('PGO self-build failed; falling back to a regular self-build.')
64 }
65 }
66 if !used_pgo {
67 if !try_compile(cmd) {
68 bootstrap_self_build(vroot, clone_args(args), final_binary) or {
69 eprintln('cannot compile to `${vroot}`: \n${err.msg()}')
70 exit(1)
71 }
72 }
73 }
74 if obinary == '' {
75 backup_old_version_and_rename_newer(short_v_name) or { panic(err.msg()) }
76 }
77 }
78 if obinary != '' {
79 return
80 }
81 println('V built successfully as executable "${vexe_name}".')
82}
83
84fn repeat_count_arg(arg string) int {
85 if arg.len < 2 || arg[0] != `x` {
86 return 0
87 }
88 for ch in arg[1..].bytes() {
89 if !ch.is_digit() {
90 return 0
91 }
92 }
93 count := arg[1..].int()
94 return if count > 0 { count } else { 0 }
95}
96
97fn extract_repeat_count(args []string) (int, []string) {
98 mut repeat_count := 1
99 mut filtered := []string{cap: args.len}
100 mut should_skip_repeat_check := false
101 for arg in args {
102 if should_skip_repeat_check {
103 filtered << arg
104 should_skip_repeat_check = false
105 continue
106 }
107 if arg in vself_flags_with_values {
108 filtered << arg
109 should_skip_repeat_check = true
110 continue
111 }
112 if repeat_count == 1 {
113 count := repeat_count_arg(arg)
114 if count > 0 {
115 repeat_count = count
116 continue
117 }
118 }
119 filtered << arg
120 }
121 return repeat_count, filtered
122}
123
124fn has_gc_arg(args []string) bool {
125 for arg in args {
126 if arg == '-gc' {
127 return true
128 }
129 if arg.starts_with('-gc=') {
130 return true
131 }
132 }
133 return false
134}
135
136fn has_profile_cflag(args []string) bool {
137 mut skip_next := false
138 for i, arg in args {
139 if skip_next {
140 skip_next = false
141 continue
142 }
143 if arg in ['-cflags', '-cf'] {
144 if i + 1 < args.len {
145 next_arg := args[i + 1]
146 if next_arg.contains('-fprofile') {
147 return true
148 }
149 skip_next = true
150 }
151 continue
152 }
153 if (arg.starts_with('-cflags=') || arg.starts_with('-cf=')) && arg.contains('-fprofile') {
154 return true
155 }
156 }
157 return false
158}
159
160fn pgo_compiler_kind(args []string) string {
161 if '-prod' !in args || '-no-prod-options' in args {
162 return ''
163 }
164 if os.user_os() == 'windows' {
165 return ''
166 }
167 if has_profile_cflag(args) {
168 return ''
169 }
170 mut ccompiler := cmdline.option(args, '-cc', '')
171 if ccompiler == '' {
172 ccompiler = os.getenv_opt('CC') or { 'cc' }
173 }
174 cc_file_name := os.file_name(ccompiler)
175 if cc_file_name.contains('clang') || cc_file_name.contains('gcc')
176 || cc_file_name.contains('g++') || ccompiler == 'cc' {
177 cc_ver := os.execute('${os.quoted_path(ccompiler)} --version').output
178 if cc_ver.contains('clang') {
179 _ := find_llvm_profdata() or { return '' }
180 return 'clang'
181 }
182 if cc_ver.contains('Free Software Foundation') || cc_ver.contains('GCC') {
183 return 'gcc'
184 }
185 }
186 if cc_file_name.contains('clang') {
187 _ := find_llvm_profdata() or { return '' }
188 return 'clang'
189 }
190 if cc_file_name.contains('gcc') || cc_file_name.contains('g++') {
191 return 'gcc'
192 }
193 return ''
194}
195
196fn find_llvm_profdata() !string {
197 if profdata := os.find_abs_path_of_executable('llvm-profdata') {
198 return profdata
199 }
200 $if macos {
201 xcrun_result := os.execute('xcrun --find llvm-profdata')
202 if xcrun_result.exit_code == 0 {
203 xcrun_path := xcrun_result.output.trim_space()
204 if xcrun_path != '' && os.exists(xcrun_path) {
205 return xcrun_path
206 }
207 }
208 }
209 return error('can not find llvm-profdata in PATH')
210}
211
212fn with_output_arg(args []string, output string) []string {
213 mut res := []string{cap: args.len + 2}
214 mut skip_next := false
215 for i, arg in args {
216 if skip_next {
217 skip_next = false
218 continue
219 }
220 if arg == '-o' {
221 if i + 1 < args.len {
222 skip_next = true
223 }
224 continue
225 }
226 if arg.starts_with('-o=') {
227 continue
228 }
229 res << arg
230 }
231 res << ['-o', output]
232 return res
233}
234
235fn clone_args(args []string) []string {
236 mut cloned := []string{cap: args.len}
237 for arg in args {
238 cloned << arg.clone()
239 }
240 return cloned
241}
242
243fn compose_v_cmd(vexe string, args []string, source string) string {
244 mut parts := []string{cap: args.len + 2}
245 parts << os.quoted_path(vexe)
246 for arg in args {
247 parts << os.quoted_path(arg)
248 }
249 parts << os.quoted_path(source)
250 return parts.join(' ')
251}
252
253fn run_cmd(cmd string) ! {
254 result := os.execute(cmd)
255 if result.exit_code != 0 {
256 return error(result.output)
257 }
258 if result.output.len > 0 {
259 println(result.output.trim_space())
260 }
261}
262
263fn try_compile(cmd string) bool {
264 result := os.execute(cmd)
265 if result.exit_code != 0 {
266 return false
267 }
268 if result.output.len > 0 {
269 println(result.output.trim_space())
270 }
271 return true
272}
273
274fn compile_with_pgo(vroot string, vexe string, args []string, out_binary string, cc_kind string) bool {
275 pgo_workspace := os.join_path(vroot, '.vself_pgo')
276 os.rmdir_all(pgo_workspace) or {}
277 os.mkdir_all(pgo_workspace) or {
278 eprintln('PGO disabled: can not create ${pgo_workspace}: ${err.msg()}')
279 return false
280 }
281 defer {
282 os.rmdir_all(pgo_workspace) or {}
283 }
284 profile_dir := os.join_path(pgo_workspace, 'profile')
285 os.mkdir_all(profile_dir) or {
286 eprintln('PGO disabled: can not create ${profile_dir}: ${err.msg()}')
287 return false
288 }
289 pgo_binary := os.join_path(pgo_workspace, 'v_pgo_gen')
290 training_output := os.join_path(pgo_workspace, 'cmd_v_training.c')
291 mut use_profile_flag := '-fprofile-use=${profile_dir}'
292 mut llvm_profdata := ''
293 mut profile_data := ''
294 if cc_kind == 'clang' {
295 llvm_profdata = find_llvm_profdata() or {
296 eprintln('PGO disabled: can not find `llvm-profdata`.')
297 return false
298 }
299 profile_data = os.join_path(pgo_workspace, 'code.profdata')
300 use_profile_flag = '-fprofile-use=${profile_data}'
301 }
302 mut generate_args := with_output_arg(args, pgo_binary)
303 generate_args << ['-cflags', '-fprofile-generate=${profile_dir}']
304 generate_cmd := compose_v_cmd(vexe, generate_args, 'cmd/v')
305 run_cmd(generate_cmd) or {
306 eprintln('PGO step failed while building the instrumented compiler.')
307 eprintln(err.msg())
308 return false
309 }
310 training_cmd := '${os.quoted_path(pgo_binary)} -o ${os.quoted_path(training_output)} ${os.quoted_path('cmd/v')}'
311 run_cmd(training_cmd) or {
312 eprintln('PGO step failed while generating the profiling data.')
313 eprintln(err.msg())
314 return false
315 }
316 if cc_kind == 'clang' {
317 merge_cmd := '${os.quoted_path(llvm_profdata)} merge -output=${os.quoted_path(profile_data)} ${os.quoted_path(profile_dir)}'
318 run_cmd(merge_cmd) or {
319 eprintln('PGO step failed while merging the profiling data.')
320 eprintln(err.msg())
321 return false
322 }
323 }
324 mut final_args := with_output_arg(args, out_binary)
325 final_args << ['-cflags', use_profile_flag]
326 if cc_kind == 'gcc' {
327 final_args << ['-cflags', '-fprofile-correction']
328 }
329 final_cmd := compose_v_cmd(vexe, final_args, 'cmd/v')
330 run_cmd(final_cmd) or {
331 eprintln('PGO step failed while building the final compiler binary.')
332 eprintln(err.msg())
333 return false
334 }
335 return true
336}
337
338fn bootstrap_self_build(vroot string, args []string, final_binary string) ! {
339 bootstrap_prefix := '.vself_bootstrap'
340 mut bootstrap_v1 := '${bootstrap_prefix}_v1'
341 mut bootstrap_v2 := '${bootstrap_prefix}_v2'
342 exe_ext := if os.user_os() == 'windows' { '.exe' } else { '' }
343 bootstrap_v1 += exe_ext
344 bootstrap_v2 += exe_ext
345 os.rm(bootstrap_v1) or {}
346 os.rm(bootstrap_v2) or {}
347 defer {
348 os.rm(bootstrap_v1) or {}
349 os.rm(bootstrap_v2) or {}
350 }
351 vc_source := os.join_path(vroot, 'vc',
352 if os.user_os() == 'windows' { 'v_win.c' } else { 'v.c' })
353 if !os.exists(vc_source) {
354 return error('bootstrap fallback failed: `${vc_source}` is missing')
355 }
356 cc := os.getenv_opt('CC') or {
357 if os.user_os() == 'windows' { 'gcc' } else { 'cc' }
358 }
359 bootstrap_v1_build_cmd := bootstrap_c_cmd(cc, bootstrap_v1, vc_source)
360 run_cmd(bootstrap_v1_build_cmd) or {
361 return error('bootstrap fallback failed while building v1.\n${err.msg()}')
362 }
363 mut bootstrap_args := ['-no-parallel']
364 bootstrap_args << with_output_arg(args, bootstrap_v2)
365 bootstrap_v1_cmd := os.join_path('.', bootstrap_v1)
366 bootstrap_v2_cmd := '${os.quoted_path(bootstrap_v1_cmd)} ${bootstrap_args.join(' ')} ${os.quoted_path('cmd/v')}'
367 run_cmd(bootstrap_v2_cmd) or {
368 return error('bootstrap fallback failed while building v2.\n${err.msg()}')
369 }
370 final_args := with_output_arg(args, final_binary)
371 bootstrap_v2_cmd_path := os.join_path('.', bootstrap_v2)
372 final_cmd := '${os.quoted_path(bootstrap_v2_cmd_path)} ${final_args.join(' ')} ${os.quoted_path('cmd/v')}'
373 run_cmd(final_cmd) or {
374 return error('bootstrap fallback failed while building the final compiler.\n${err.msg()}')
375 }
376}
377
378fn bootstrap_c_cmd(cc string, out_binary string, vc_source string) string {
379 mut parts := []string{cap: 8}
380 parts << os.quoted_path(cc)
381 if os.user_os() == 'windows' {
382 parts << ['-std=c99', '-municode', '-w', '-o', os.quoted_path(out_binary),
383 os.quoted_path(vc_source), '-lws2_32']
384 } else {
385 parts << ['-std=c99', '-w', '-o', os.quoted_path(out_binary),
386 os.quoted_path(vc_source), '-lm', '-lpthread']
387 }
388 return parts.join(' ')
389}
390
391fn list_folder(short_v_name string, bmessage string, message string) {
392 if !is_debug {
393 return
394 }
395 if bmessage != '' {
396 println(bmessage)
397 }
398 if os.user_os() == 'windows' {
399 os.system('dir ${short_v_name}*.exe')
400 } else {
401 os.system('ls -lartd ${short_v_name}*')
402 }
403 println(message)
404}
405
406fn backup_old_version_and_rename_newer(short_v_name string) !bool {
407 mut errors := []string{}
408 short_v_file := if os.user_os() == 'windows' { '${short_v_name}.exe' } else { '${short_v_name}' }
409 short_v2_file := if os.user_os() == 'windows' { 'v2.exe' } else { 'v2' }
410 short_bak_file := if os.user_os() == 'windows' { 'v_old.exe' } else { 'v_old' }
411 v_file := os.real_path(short_v_file)
412 v2_file := os.real_path(short_v2_file)
413 bak_file := os.real_path(short_bak_file)
414
415 list_folder(short_v_name, 'before:', 'removing ${bak_file} ...')
416 if os.exists(bak_file) {
417 os.rm(bak_file) or { errors << 'failed removing ${bak_file}: ${err.msg()}' }
418 }
419
420 list_folder(short_v_name, '', 'moving ${v_file} to ${bak_file} ...')
421 os.mv(v_file, bak_file) or { errors << err.msg() }
422
423 list_folder(short_v_name, '', 'removing ${v_file} ...')
424 os.rm(v_file) or {}
425
426 list_folder(short_v_name, '', 'moving ${v2_file} to ${v_file} ...')
427 os.mv_by_cp(v2_file, v_file) or { panic(err.msg()) }
428
429 list_folder(short_v_name, 'after:', '')
430
431 if errors.len > 0 {
432 eprintln('backup errors:\n >> ' + errors.join('\n >> '))
433 }
434 return true
435}
436