v: add support for `v crun examples/hello_world.v`, use crun mode for .vsh files by default. (#14554)
							parent
							
								
									80879586df
								
							
						
					
					
						commit
						8cd891c9b6
					
				|  | @ -7,6 +7,7 @@ Examples: | |||
|    v hello.v                 Compile the file `hello.v` and output it as `hello` or `hello.exe`. | ||||
|    v run hello.v             Same as above but also run the produced executable immediately after compilation. | ||||
|    v -cg run hello.v         Same as above, but make debugging easier (in case your program crashes). | ||||
|    v crun hello.v            Same as above, but do not recompile, if the executable already exists, and is newer than the sources. | ||||
|    v -o h.c hello.v          Translate `hello.v` to `h.c`. Do not compile further. | ||||
|    v -o - hello.v            Translate `hello.v` and output the C source code to stdout. Do not compile further. | ||||
| 
 | ||||
|  | @ -20,7 +21,10 @@ V supports the following commands: | |||
|    init              Setup the file structure for an already existing V project. | ||||
| 
 | ||||
| * Ordinary development: | ||||
|    run               Compile and run a V program. | ||||
|    run               Compile and run a V program. Delete the executable after the run. | ||||
|    crun              Compile and run a V program without deleting the executable. | ||||
|                      If you run the same program a second time, without changing the source files, | ||||
|                      V will just run the executable, without recompilation. Suitable for scripting. | ||||
|    test              Run all test files in the provided directory. | ||||
|    fmt               Format the V code provided. | ||||
|    vet               Report suspicious code constructs. | ||||
|  |  | |||
							
								
								
									
										80
									
								
								cmd/v/v.v
								
								
								
								
							
							
						
						
									
										80
									
								
								cmd/v/v.v
								
								
								
								
							|  | @ -93,17 +93,21 @@ fn main() { | |||
| 		return | ||||
| 	} | ||||
| 	match command { | ||||
| 		'run', 'crun', 'build', 'build-module' { | ||||
| 			rebuild(prefs) | ||||
| 			return | ||||
| 		} | ||||
| 		'help' { | ||||
| 			invoke_help_and_exit(args) | ||||
| 		} | ||||
| 		'version' { | ||||
| 			println(version.full_v_version(prefs.is_verbose)) | ||||
| 			return | ||||
| 		} | ||||
| 		'new', 'init' { | ||||
| 			util.launch_tool(prefs.is_verbose, 'vcreate', os.args[1..]) | ||||
| 			return | ||||
| 		} | ||||
| 		'translate' { | ||||
| 			eprintln('Translating C to V will be available in V 0.3') | ||||
| 			exit(1) | ||||
| 		} | ||||
| 		'install', 'list', 'outdated', 'remove', 'search', 'show', 'update', 'upgrade' { | ||||
| 			util.launch_tool(prefs.is_verbose, 'vpm', os.args[1..]) | ||||
| 			return | ||||
|  | @ -118,42 +122,24 @@ fn main() { | |||
| 			eprintln('V Error: Use `v install` to install modules from vpm.vlang.io') | ||||
| 			exit(1) | ||||
| 		} | ||||
| 		'version' { | ||||
| 			println(version.full_v_version(prefs.is_verbose)) | ||||
| 			return | ||||
| 		'translate' { | ||||
| 			eprintln('Translating C to V will be available in V 0.3') | ||||
| 			exit(1) | ||||
| 		} | ||||
| 		else {} | ||||
| 	} | ||||
| 	if command in ['run', 'build', 'build-module'] || command.ends_with('.v') || os.exists(command) { | ||||
| 		// println('command')
 | ||||
| 		// println(prefs.path)
 | ||||
| 		match prefs.backend { | ||||
| 			.c { | ||||
| 				$if no_bootstrapv ? { | ||||
| 					// TODO: improve the bootstrapping with a split C backend here.
 | ||||
| 					// C code generated by `VEXE=v cmd/tools/builders/c_builder -os cross -o c.c cmd/tools/builders/c_builder.v`
 | ||||
| 					// is enough to bootstrap the C backend, and thus the rest, but currently bootstrapping relies on
 | ||||
| 					// `v -os cross -o v.c cmd/v` having a functional C codegen inside instead.
 | ||||
| 					util.launch_tool(prefs.is_verbose, 'builders/c_builder', os.args[1..]) | ||||
| 				} | ||||
| 				builder.compile('build', prefs, cbuilder.compile_c) | ||||
| 			} | ||||
| 			.js_node, .js_freestanding, .js_browser { | ||||
| 				util.launch_tool(prefs.is_verbose, 'builders/js_builder', os.args[1..]) | ||||
| 			} | ||||
| 			.native { | ||||
| 				util.launch_tool(prefs.is_verbose, 'builders/native_builder', os.args[1..]) | ||||
| 			} | ||||
| 			.interpret { | ||||
| 				util.launch_tool(prefs.is_verbose, 'builders/interpret_builder', os.args[1..]) | ||||
| 		else { | ||||
| 			if command.ends_with('.v') || os.exists(command) { | ||||
| 				// println('command')
 | ||||
| 				// println(prefs.path)
 | ||||
| 				rebuild(prefs) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	if prefs.is_help { | ||||
| 		invoke_help_and_exit(args) | ||||
| 	} | ||||
| 	eprintln('v $command: unknown command\nRun ${term.highlight_command('v help')} for usage.') | ||||
| 	eprintln('v $command: unknown command') | ||||
| 	eprintln('Run ${term.highlight_command('v help')} for usage.') | ||||
| 	exit(1) | ||||
| } | ||||
| 
 | ||||
|  | @ -163,7 +149,31 @@ fn invoke_help_and_exit(remaining []string) { | |||
| 		2 { help.print_and_exit(remaining[1]) } | ||||
| 		else {} | ||||
| 	} | ||||
| 	println('${term.highlight_command('v help')}: provide only one help topic.') | ||||
| 	println('For usage information, use ${term.highlight_command('v help')}.') | ||||
| 	eprintln('${term.highlight_command('v help')}: provide only one help topic.') | ||||
| 	eprintln('For usage information, use ${term.highlight_command('v help')}.') | ||||
| 	exit(1) | ||||
| } | ||||
| 
 | ||||
| fn rebuild(prefs &pref.Preferences) { | ||||
| 	match prefs.backend { | ||||
| 		.c { | ||||
| 			$if no_bootstrapv ? { | ||||
| 				// TODO: improve the bootstrapping with a split C backend here.
 | ||||
| 				// C code generated by `VEXE=v cmd/tools/builders/c_builder -os cross -o c.c cmd/tools/builders/c_builder.v`
 | ||||
| 				// is enough to bootstrap the C backend, and thus the rest, but currently bootstrapping relies on
 | ||||
| 				// `v -os cross -o v.c cmd/v` having a functional C codegen inside instead.
 | ||||
| 				util.launch_tool(prefs.is_verbose, 'builders/c_builder', os.args[1..]) | ||||
| 			} | ||||
| 			builder.compile('build', prefs, cbuilder.compile_c) | ||||
| 		} | ||||
| 		.js_node, .js_freestanding, .js_browser { | ||||
| 			util.launch_tool(prefs.is_verbose, 'builders/js_builder', os.args[1..]) | ||||
| 		} | ||||
| 		.native { | ||||
| 			util.launch_tool(prefs.is_verbose, 'builders/native_builder', os.args[1..]) | ||||
| 		} | ||||
| 		.interpret { | ||||
| 			util.launch_tool(prefs.is_verbose, 'builders/interpret_builder', os.args[1..]) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import v.dotgraph | |||
| 
 | ||||
| pub struct Builder { | ||||
| pub: | ||||
| 	compiled_dir string // contains os.real_path() of the dir of the final file beeing compiled, or the dir itself when doing `v .`
 | ||||
| 	compiled_dir string // contains os.real_path() of the dir of the final file being compiled, or the dir itself when doing `v .`
 | ||||
| 	module_path  string | ||||
| pub mut: | ||||
| 	checker             &checker.Checker | ||||
|  | @ -40,6 +40,7 @@ pub mut: | |||
| 	mod_invalidates_paths map[string][]string // changes in mod `os`, invalidate only .v files, that do `import os`
 | ||||
| 	mod_invalidates_mods  map[string][]string // changes in mod `os`, force invalidation of mods, that do `import os`
 | ||||
| 	path_invalidates_mods map[string][]string // changes in a .v file from `os`, invalidates `os`
 | ||||
| 	crun_cache_keys       []string // target executable + top level source files; filled in by Builder.should_rebuild
 | ||||
| } | ||||
| 
 | ||||
| pub fn new_builder(pref &pref.Preferences) Builder { | ||||
|  |  | |||
|  | @ -3,9 +3,7 @@ | |||
| // that can be found in the LICENSE file.
 | ||||
| module builder | ||||
| 
 | ||||
| import time | ||||
| import os | ||||
| import rand | ||||
| import v.pref | ||||
| import v.util | ||||
| import v.checker | ||||
|  | @ -13,6 +11,19 @@ import v.checker | |||
| pub type FnBackend = fn (mut b Builder) | ||||
| 
 | ||||
| pub fn compile(command string, pref &pref.Preferences, backend_cb FnBackend) { | ||||
| 	check_if_output_folder_is_writable(pref) | ||||
| 	// Construct the V object from command line arguments
 | ||||
| 	mut b := new_builder(pref) | ||||
| 	if b.should_rebuild() { | ||||
| 		b.rebuild(backend_cb) | ||||
| 	} | ||||
| 	b.exit_on_invalid_syntax() | ||||
| 	// running does not require the parsers anymore
 | ||||
| 	unsafe { b.myfree() } | ||||
| 	b.run_compiled_executable_and_exit() | ||||
| } | ||||
| 
 | ||||
| fn check_if_output_folder_is_writable(pref &pref.Preferences) { | ||||
| 	odir := os.dir(pref.out_name) | ||||
| 	// When pref.out_name is just the name of an executable, i.e. `./v -o executable main.v`
 | ||||
| 	// without a folder component, just use the current folder instead:
 | ||||
|  | @ -24,56 +35,6 @@ pub fn compile(command string, pref &pref.Preferences, backend_cb FnBackend) { | |||
| 		// An early error here, is better than an unclear C error later:
 | ||||
| 		verror(err.msg()) | ||||
| 	} | ||||
| 	// Construct the V object from command line arguments
 | ||||
| 	mut b := new_builder(pref) | ||||
| 	if pref.is_verbose { | ||||
| 		println('builder.compile() pref:') | ||||
| 		// println(pref)
 | ||||
| 	} | ||||
| 	mut sw := time.new_stopwatch() | ||||
| 	backend_cb(mut b) | ||||
| 	mut timers := util.get_timers() | ||||
| 	timers.show_remaining() | ||||
| 	if pref.is_stats { | ||||
| 		compilation_time_micros := 1 + sw.elapsed().microseconds() | ||||
| 		scompilation_time_ms := util.bold('${f64(compilation_time_micros) / 1000.0:6.3f}') | ||||
| 		mut all_v_source_lines, mut all_v_source_bytes := 0, 0 | ||||
| 		for pf in b.parsed_files { | ||||
| 			all_v_source_lines += pf.nr_lines | ||||
| 			all_v_source_bytes += pf.nr_bytes | ||||
| 		} | ||||
| 		mut sall_v_source_lines := all_v_source_lines.str() | ||||
| 		mut sall_v_source_bytes := all_v_source_bytes.str() | ||||
| 		sall_v_source_lines = util.bold('${sall_v_source_lines:10s}') | ||||
| 		sall_v_source_bytes = util.bold('${sall_v_source_bytes:10s}') | ||||
| 		println('        V  source  code size: $sall_v_source_lines lines, $sall_v_source_bytes bytes') | ||||
| 		//
 | ||||
| 		mut slines := b.stats_lines.str() | ||||
| 		mut sbytes := b.stats_bytes.str() | ||||
| 		slines = util.bold('${slines:10s}') | ||||
| 		sbytes = util.bold('${sbytes:10s}') | ||||
| 		println('generated  target  code size: $slines lines, $sbytes bytes') | ||||
| 		//
 | ||||
| 		vlines_per_second := int(1_000_000.0 * f64(all_v_source_lines) / f64(compilation_time_micros)) | ||||
| 		svlines_per_second := util.bold(vlines_per_second.str()) | ||||
| 		println('compilation took: $scompilation_time_ms ms, compilation speed: $svlines_per_second vlines/s') | ||||
| 	} | ||||
| 	b.exit_on_invalid_syntax() | ||||
| 	// running does not require the parsers anymore
 | ||||
| 	unsafe { b.myfree() } | ||||
| 	if pref.is_test || pref.is_run { | ||||
| 		b.run_compiled_executable_and_exit() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string { | ||||
| 	vtmp := util.get_vtmp_folder() | ||||
| 	mut uniq := '' | ||||
| 	if !b.pref.reuse_tmpc { | ||||
| 		uniq = '.$rand.u64()' | ||||
| 	} | ||||
| 	fname := os.file_name(os.real_path(base_file_name)) + '$uniq$postfix' | ||||
| 	return os.real_path(os.join_path(vtmp, fname)) | ||||
| } | ||||
| 
 | ||||
| // Temporary, will be done by -autofree
 | ||||
|  | @ -118,47 +79,45 @@ fn (mut b Builder) run_compiled_executable_and_exit() { | |||
| 	if b.pref.os == .ios { | ||||
| 		panic('Running iOS apps is not supported yet.') | ||||
| 	} | ||||
| 	if !(b.pref.is_test || b.pref.is_run || b.pref.is_crun) { | ||||
| 		exit(0) | ||||
| 	} | ||||
| 	compiled_file := os.real_path(b.pref.out_name) | ||||
| 	run_file := if b.pref.backend.is_js() { | ||||
| 		node_basename := $if windows { 'node.exe' } $else { 'node' } | ||||
| 		os.find_abs_path_of_executable(node_basename) or { | ||||
| 			panic('Could not find `node` in system path. Do you have Node.js installed?') | ||||
| 		} | ||||
| 	} else { | ||||
| 		compiled_file | ||||
| 	} | ||||
| 	mut run_args := []string{cap: b.pref.run_args.len + 1} | ||||
| 	if b.pref.backend.is_js() { | ||||
| 		run_args << compiled_file | ||||
| 	} | ||||
| 	run_args << b.pref.run_args | ||||
| 	mut run_process := os.new_process(run_file) | ||||
| 	run_process.set_args(run_args) | ||||
| 	if b.pref.is_verbose { | ||||
| 		println('running $run_process.filename with arguments $run_process.args') | ||||
| 	} | ||||
| 	if b.pref.is_test || b.pref.is_run { | ||||
| 		compiled_file := os.real_path(b.pref.out_name) | ||||
| 		run_file := if b.pref.backend.is_js() { | ||||
| 			node_basename := $if windows { 'node.exe' } $else { 'node' } | ||||
| 			os.find_abs_path_of_executable(node_basename) or { | ||||
| 				panic('Could not find `node` in system path. Do you have Node.js installed?') | ||||
| 			} | ||||
| 		} else { | ||||
| 			compiled_file | ||||
| 		} | ||||
| 		mut run_args := []string{cap: b.pref.run_args.len + 1} | ||||
| 		if b.pref.backend.is_js() { | ||||
| 			run_args << compiled_file | ||||
| 		} | ||||
| 		run_args << b.pref.run_args | ||||
| 		mut run_process := os.new_process(run_file) | ||||
| 		run_process.set_args(run_args) | ||||
| 		if b.pref.is_verbose { | ||||
| 			println('running $run_process.filename with arguments $run_process.args') | ||||
| 		} | ||||
| 		// Ignore sigint and sigquit while running the compiled file,
 | ||||
| 		// so ^C doesn't prevent v from deleting the compiled file.
 | ||||
| 		// See also https://git.musl-libc.org/cgit/musl/tree/src/process/system.c
 | ||||
| 		prev_int_handler := os.signal_opt(.int, eshcb) or { serror('set .int', err) } | ||||
| 		mut prev_quit_handler := os.SignalHandler(eshcb) | ||||
| 		$if !windows { // There's no sigquit on windows
 | ||||
| 			prev_quit_handler = os.signal_opt(.quit, eshcb) or { serror('set .quit', err) } | ||||
| 		} | ||||
| 		run_process.wait() | ||||
| 		os.signal_opt(.int, prev_int_handler) or { serror('restore .int', err) } | ||||
| 		$if !windows { | ||||
| 			os.signal_opt(.quit, prev_quit_handler) or { serror('restore .quit', err) } | ||||
| 		} | ||||
| 		ret := run_process.code | ||||
| 		run_process.close() | ||||
| 		b.cleanup_run_executable_after_exit(compiled_file) | ||||
| 		exit(ret) | ||||
| 	// Ignore sigint and sigquit while running the compiled file,
 | ||||
| 	// so ^C doesn't prevent v from deleting the compiled file.
 | ||||
| 	// See also https://git.musl-libc.org/cgit/musl/tree/src/process/system.c
 | ||||
| 	prev_int_handler := os.signal_opt(.int, eshcb) or { serror('set .int', err) } | ||||
| 	mut prev_quit_handler := os.SignalHandler(eshcb) | ||||
| 	$if !windows { // There's no sigquit on windows
 | ||||
| 		prev_quit_handler = os.signal_opt(.quit, eshcb) or { serror('set .quit', err) } | ||||
| 	} | ||||
| 	exit(0) | ||||
| 	run_process.wait() | ||||
| 	os.signal_opt(.int, prev_int_handler) or { serror('restore .int', err) } | ||||
| 	$if !windows { | ||||
| 		os.signal_opt(.quit, prev_quit_handler) or { serror('restore .quit', err) } | ||||
| 	} | ||||
| 	ret := run_process.code | ||||
| 	run_process.close() | ||||
| 	b.cleanup_run_executable_after_exit(compiled_file) | ||||
| 	exit(ret) | ||||
| } | ||||
| 
 | ||||
| fn eshcb(_ os.Signal) { | ||||
|  | @ -171,6 +130,9 @@ fn serror(reason string, e IError) { | |||
| } | ||||
| 
 | ||||
| fn (mut v Builder) cleanup_run_executable_after_exit(exefile string) { | ||||
| 	if v.pref.is_crun { | ||||
| 		return | ||||
| 	} | ||||
| 	if v.pref.reuse_tmpc { | ||||
| 		v.pref.vrun_elog('keeping executable: $exefile , because -keepc was passed') | ||||
| 		return | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ module builder | |||
| 
 | ||||
| import os | ||||
| import hash | ||||
| import time | ||||
| import rand | ||||
| import strings | ||||
| import v.util | ||||
| import v.pref | ||||
|  | @ -11,11 +13,27 @@ pub fn (mut b Builder) rebuild_modules() { | |||
| 	if !b.pref.use_cache || b.pref.build_mode == .build_module { | ||||
| 		return | ||||
| 	} | ||||
| 	all_files := b.parsed_files.map(it.path) | ||||
| 	$if trace_invalidations ? { | ||||
| 		eprintln('> rebuild_modules all_files: $all_files') | ||||
| 	} | ||||
| 	invalidations := b.find_invalidated_modules_by_files(all_files) | ||||
| 	$if trace_invalidations ? { | ||||
| 		eprintln('> rebuild_modules invalidations: $invalidations') | ||||
| 	} | ||||
| 	if invalidations.len > 0 { | ||||
| 		vexe := pref.vexe_path() | ||||
| 		for imp in invalidations { | ||||
| 			b.v_build_module(vexe, imp) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (mut b Builder) find_invalidated_modules_by_files(all_files []string) []string { | ||||
| 	util.timing_start('${@METHOD} source_hashing') | ||||
| 	mut new_hashes := map[string]string{} | ||||
| 	mut old_hashes := map[string]string{} | ||||
| 	mut sb_new_hashes := strings.new_builder(1024) | ||||
| 	all_files := b.parsed_files.map(it.path) | ||||
| 	//
 | ||||
| 	mut cm := vcache.new_cache_manager(all_files) | ||||
| 	sold_hashes := cm.load('.hashes', 'all_files') or { ' ' } | ||||
|  | @ -31,8 +49,7 @@ pub fn (mut b Builder) rebuild_modules() { | |||
| 		old_hashes[cpath] = chash | ||||
| 	} | ||||
| 	// eprintln('old_hashes: $old_hashes')
 | ||||
| 	for p in b.parsed_files { | ||||
| 		cpath := p.path | ||||
| 	for cpath in all_files { | ||||
| 		ccontent := util.read_file(cpath) or { '' } | ||||
| 		chash := hash.sum64_string(ccontent, 7).hex_full() | ||||
| 		new_hashes[cpath] = chash | ||||
|  | @ -48,6 +65,7 @@ pub fn (mut b Builder) rebuild_modules() { | |||
| 	cm.save('.hashes', 'all_files', snew_hashes) or {} | ||||
| 	util.timing_measure('${@METHOD} source_hashing') | ||||
| 
 | ||||
| 	mut invalidations := []string{} | ||||
| 	if new_hashes != old_hashes { | ||||
| 		util.timing_start('${@METHOD} rebuilding') | ||||
| 		// eprintln('> b.mod_invalidates_paths: $b.mod_invalidates_paths')
 | ||||
|  | @ -148,13 +166,13 @@ pub fn (mut b Builder) rebuild_modules() { | |||
| 		} | ||||
| 		if invalidated_mod_paths.len > 0 { | ||||
| 			impaths := invalidated_mod_paths.keys() | ||||
| 			vexe := pref.vexe_path() | ||||
| 			for imp in impaths { | ||||
| 				b.v_build_module(vexe, imp) | ||||
| 				invalidations << imp | ||||
| 			} | ||||
| 		} | ||||
| 		util.timing_measure('${@METHOD} rebuilding') | ||||
| 	} | ||||
| 	return invalidations | ||||
| } | ||||
| 
 | ||||
| fn (mut b Builder) v_build_module(vexe string, imp_path string) { | ||||
|  | @ -237,3 +255,114 @@ fn (mut b Builder) handle_usecache(vexe string) { | |||
| 	} | ||||
| 	b.ccoptions.post_args << libs | ||||
| } | ||||
| 
 | ||||
| pub fn (mut b Builder) should_rebuild() bool { | ||||
| 	mut exe_name := b.pref.out_name | ||||
| 	$if windows { | ||||
| 		exe_name = exe_name + '.exe' | ||||
| 	} | ||||
| 	if !os.is_file(exe_name) { | ||||
| 		return true | ||||
| 	} | ||||
| 	if !b.pref.is_crun { | ||||
| 		return true | ||||
| 	} | ||||
| 	mut v_program_files := []string{} | ||||
| 	is_file := os.is_file(b.pref.path) | ||||
| 	is_dir := os.is_dir(b.pref.path) | ||||
| 	if is_file { | ||||
| 		v_program_files << b.pref.path | ||||
| 	} else if is_dir { | ||||
| 		v_program_files << b.v_files_from_dir(b.pref.path) | ||||
| 	} | ||||
| 	v_program_files.sort() // ensure stable keys for the dependencies cache
 | ||||
| 	b.crun_cache_keys = v_program_files | ||||
| 	b.crun_cache_keys << exe_name | ||||
| 	// just check the timestamps for now:
 | ||||
| 	exe_stamp := os.file_last_mod_unix(exe_name) | ||||
| 	source_stamp := most_recent_timestamp(v_program_files) | ||||
| 	if exe_stamp <= source_stamp { | ||||
| 		return true | ||||
| 	} | ||||
| 	////////////////////////////////////////////////////////////////////////////
 | ||||
| 	// The timestamps for the top level files were found ok,
 | ||||
| 	// however we want to *also* make sure that a full rebuild will be done
 | ||||
| 	// if any of the dependencies (if we know them) are changed.
 | ||||
| 	mut cm := vcache.new_cache_manager(b.crun_cache_keys) | ||||
| 	// always rebuild, when the compilation options changed between 2 sequential cruns:
 | ||||
| 	sbuild_options := cm.load('.build_options', '.crun') or { return true } | ||||
| 	if sbuild_options != b.pref.build_options.join('\n') { | ||||
| 		return true | ||||
| 	} | ||||
| 	sdependencies := cm.load('.dependencies', '.crun') or { | ||||
| 		// empty/wiped out cache, we do not know what the dependencies are, so just
 | ||||
| 		// rebuild, which will fill in the dependencies cache for the next crun
 | ||||
| 		return true | ||||
| 	} | ||||
| 	dependencies := sdependencies.split('\n') | ||||
| 	// we have already compiled these source files, and have their dependencies
 | ||||
| 	dependencies_stamp := most_recent_timestamp(dependencies) | ||||
| 	if dependencies_stamp < exe_stamp { | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| fn most_recent_timestamp(files []string) i64 { | ||||
| 	mut res := i64(0) | ||||
| 	for f in files { | ||||
| 		f_stamp := os.file_last_mod_unix(f) | ||||
| 		if res <= f_stamp { | ||||
| 			res = f_stamp | ||||
| 		} | ||||
| 	} | ||||
| 	return res | ||||
| } | ||||
| 
 | ||||
| pub fn (mut b Builder) rebuild(backend_cb FnBackend) { | ||||
| 	mut sw := time.new_stopwatch() | ||||
| 	backend_cb(mut b) | ||||
| 	if b.pref.is_crun { | ||||
| 		// save the dependencies after the first compilation, they will be used for subsequent ones:
 | ||||
| 		mut cm := vcache.new_cache_manager(b.crun_cache_keys) | ||||
| 		dependency_files := b.parsed_files.map(it.path) | ||||
| 		cm.save('.dependencies', '.crun', dependency_files.join('\n')) or {} | ||||
| 		cm.save('.build_options', '.crun', b.pref.build_options.join('\n')) or {} | ||||
| 	} | ||||
| 	mut timers := util.get_timers() | ||||
| 	timers.show_remaining() | ||||
| 	if b.pref.is_stats { | ||||
| 		compilation_time_micros := 1 + sw.elapsed().microseconds() | ||||
| 		scompilation_time_ms := util.bold('${f64(compilation_time_micros) / 1000.0:6.3f}') | ||||
| 		mut all_v_source_lines, mut all_v_source_bytes := 0, 0 | ||||
| 		for pf in b.parsed_files { | ||||
| 			all_v_source_lines += pf.nr_lines | ||||
| 			all_v_source_bytes += pf.nr_bytes | ||||
| 		} | ||||
| 		mut sall_v_source_lines := all_v_source_lines.str() | ||||
| 		mut sall_v_source_bytes := all_v_source_bytes.str() | ||||
| 		sall_v_source_lines = util.bold('${sall_v_source_lines:10s}') | ||||
| 		sall_v_source_bytes = util.bold('${sall_v_source_bytes:10s}') | ||||
| 		println('        V  source  code size: $sall_v_source_lines lines, $sall_v_source_bytes bytes') | ||||
| 		//
 | ||||
| 		mut slines := b.stats_lines.str() | ||||
| 		mut sbytes := b.stats_bytes.str() | ||||
| 		slines = util.bold('${slines:10s}') | ||||
| 		sbytes = util.bold('${sbytes:10s}') | ||||
| 		println('generated  target  code size: $slines lines, $sbytes bytes') | ||||
| 		//
 | ||||
| 		vlines_per_second := int(1_000_000.0 * f64(all_v_source_lines) / f64(compilation_time_micros)) | ||||
| 		svlines_per_second := util.bold(vlines_per_second.str()) | ||||
| 		println('compilation took: $scompilation_time_ms ms, compilation speed: $svlines_per_second vlines/s') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| pub fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string { | ||||
| 	vtmp := util.get_vtmp_folder() | ||||
| 	mut uniq := '' | ||||
| 	if !b.pref.reuse_tmpc { | ||||
| 		uniq = '.$rand.u64()' | ||||
| 	} | ||||
| 	fname := os.file_name(os.real_path(base_file_name)) + '$uniq$postfix' | ||||
| 	return os.real_path(os.join_path(vtmp, fname)) | ||||
| } | ||||
|  |  | |||
|  | @ -111,7 +111,8 @@ pub mut: | |||
| 	is_prof           bool // benchmark every function
 | ||||
| 	is_prod           bool // use "-O2"
 | ||||
| 	is_repl           bool | ||||
| 	is_run            bool | ||||
| 	is_run            bool // compile and run a v program, passing arguments to it, and deleting the executable afterwards
 | ||||
| 	is_crun           bool // similar to run, but does not recompile the executable, if there were no changes to the sources
 | ||||
| 	is_debug          bool // turned on by -g or -cg, it tells v to pass -g to the C backend compiler.
 | ||||
| 	is_vlines         bool // turned on by -g (it slows down .tmp.c generation slightly).
 | ||||
| 	is_stats          bool // `v -stats file_test.v` will produce more detailed statistics for the tests that were run
 | ||||
|  | @ -706,7 +707,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin | |||
| 					if command == '' { | ||||
| 						command = arg | ||||
| 						command_pos = i | ||||
| 						if command == 'run' { | ||||
| 						if command in ['run', 'crun'] { | ||||
| 							break | ||||
| 						} | ||||
| 					} else if is_source_file(command) && is_source_file(arg) | ||||
|  | @ -734,6 +735,12 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin | |||
| 	if res.is_debug { | ||||
| 		res.parse_define('debug') | ||||
| 	} | ||||
| 	if command == 'crun' { | ||||
| 		res.is_crun = true | ||||
| 	} | ||||
| 	if command == 'run' { | ||||
| 		res.is_run = true | ||||
| 	} | ||||
| 	if command == 'run' && res.is_prod && os.is_atty(1) > 0 { | ||||
| 		eprintln_cond(show_output, "Note: building an optimized binary takes much longer. It shouldn't be used with `v run`.") | ||||
| 		eprintln_cond(show_output, 'Use `v run` without optimization, or build an optimized binary with -prod first, then run it separately.') | ||||
|  | @ -744,8 +751,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin | |||
| 		eprintln('Cannot save output binary in a .v file.') | ||||
| 		exit(1) | ||||
| 	} | ||||
| 	if command == 'run' { | ||||
| 		res.is_run = true | ||||
| 	if res.is_run || res.is_crun { | ||||
| 		if command_pos + 2 > args.len { | ||||
| 			eprintln('v run: no v files listed') | ||||
| 			exit(1) | ||||
|  | @ -798,7 +804,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin | |||
| 		// `v build.vsh gcc` is the same as `v run build.vsh gcc`,
 | ||||
| 		// i.e. compiling, then running the script, passing the args
 | ||||
| 		// after it to the script:
 | ||||
| 		res.is_run = true | ||||
| 		res.is_crun = true | ||||
| 		res.path = command | ||||
| 		res.run_args = args[command_pos + 1..] | ||||
| 	} else if command == 'interpret' { | ||||
|  |  | |||
|  | @ -0,0 +1,51 @@ | |||
| import os | ||||
| import time | ||||
| 
 | ||||
| const crun_folder = os.join_path(os.temp_dir(), 'crun_folder') | ||||
| 
 | ||||
| const vprogram_file = os.join_path(crun_folder, 'vprogram.vv') | ||||
| 
 | ||||
| const vexe = os.getenv('VEXE') | ||||
| 
 | ||||
| fn testsuite_begin() { | ||||
| 	os.setenv('VCACHE', crun_folder, true) | ||||
| 	os.rmdir_all(crun_folder) or {} | ||||
| 	os.mkdir_all(crun_folder) or {} | ||||
| 	assert os.is_dir(crun_folder) | ||||
| } | ||||
| 
 | ||||
| fn testsuite_end() { | ||||
| 	os.chdir(os.wd_at_startup) or {} | ||||
| 	os.rmdir_all(crun_folder) or {} | ||||
| 	assert !os.is_dir(crun_folder) | ||||
| } | ||||
| 
 | ||||
| fn test_saving_simple_v_program() ? { | ||||
| 	os.write_file(vprogram_file, 'print("hello")')? | ||||
| 	assert true | ||||
| } | ||||
| 
 | ||||
| fn test_crun_simple_v_program_several_times() ? { | ||||
| 	mut sw := time.new_stopwatch() | ||||
| 	mut times := []i64{} | ||||
| 	for i in 0 .. 10 { | ||||
| 		vcrun() | ||||
| 		times << sw.elapsed().microseconds() | ||||
| 		time.sleep(50 * time.millisecond) | ||||
| 		sw.restart() | ||||
| 	} | ||||
| 	dump(times) | ||||
| 	assert times.first() > times.last() * 5 // cruns compile just once, if the source file is not changed
 | ||||
| 	$if !windows { | ||||
| 		os.system('ls -la $crun_folder') | ||||
| 		os.system('find $crun_folder') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn vcrun() { | ||||
| 	cmd := '${os.quoted_path(vexe)} crun ${os.quoted_path(vprogram_file)}' | ||||
| 	eprintln('now: $time.now().format_ss_milli() | cmd: $cmd') | ||||
| 	res := os.execute(cmd) | ||||
| 	assert res.exit_code == 0 | ||||
| 	assert res.output == 'hello' | ||||
| } | ||||
		Loading…
	
		Reference in New Issue