1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

v.live, cgen: enable using [live] in modules too (monitor used .v files for changes, not just the top level one) (#16396)

This commit is contained in:
Delyan Angelov 2022-11-11 22:06:42 +02:00 committed by GitHub
parent c9ce5f89c7
commit 12f01318c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 83 additions and 37 deletions

View File

@ -168,6 +168,7 @@ mut:
json_types []ast.Type // to avoid json gen duplicates
pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name
hotcode_fn_names []string
hotcode_fpaths []string
embedded_files []ast.EmbeddedFile
sql_i int
sql_stmt_name string
@ -404,6 +405,7 @@ pub fn gen(files []&ast.File, table &ast.Table, pref &pref.Preferences) (string,
global_g.pcs << g.pcs
global_g.json_types << g.json_types
global_g.hotcode_fn_names << g.hotcode_fn_names
global_g.hotcode_fpaths << g.hotcode_fpaths
global_g.test_function_names << g.test_function_names
unsafe { g.free_builders() }
for k, v in g.autofree_methods {

View File

@ -245,6 +245,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
// with 'impl_live_' .
if is_livemain {
g.hotcode_fn_names << name
g.hotcode_fpaths << g.file.path
}
mut impl_fn_name := name
if is_live_wrap {

View File

@ -1,5 +1,6 @@
module c
import os
import v.pref
import v.util
@ -95,9 +96,21 @@ fn (mut g Gen) generate_hotcode_reloading_main_caller() {
g.writeln('\t\t\t\t\t &live_fn_mutex,')
g.writeln('\t\t\t\t\t v_bind_live_symbols')
g.writeln('\t\t);')
mut already_added := map[string]bool{}
for f in g.hotcode_fpaths {
already_added[f] = true
}
mut idx := 0
for f, _ in already_added {
fpath := os.real_path(f)
g.writeln('\t\tv__live__executable__add_live_monitored_file(live_info, ${ctoslit(fpath)}); // source V file with [live] ${
idx + 1}/$already_added.len')
idx++
}
g.writeln('')
// g_live_info gives access to the LiveReloadInfo methods,
// to the custom user code, through calling v_live_info()
g.writeln('\t\t g_live_info = (void*)live_info;')
g.writeln('\t\tg_live_info = (void*)live_info;')
g.writeln('\t\tv__live__executable__start_reloader(live_info);')
g.writeln('\t}\t// end of live code initialization section')
g.writeln('')

View File

@ -1,14 +1,11 @@
module live
pub const (
is_used = 1
)
pub const is_used = 1
pub type FNLinkLiveSymbols = fn (linkcb voidptr)
pub type FNLiveReloadCB = fn (info &LiveReloadInfo)
[minify]
pub struct LiveReloadInfo {
pub:
vexe string // full path to the v compiler
@ -17,8 +14,9 @@ pub:
live_fn_mutex voidptr // the address of the C mutex, that locks the [live] fns during reloads.
live_linkfn FNLinkLiveSymbols // generated C callback; receives a dlopen handle
so_extension string // .so or .dll
so_name_template string // a sprintf template for the shared libraries location
so_name_template string // a template for the shared libraries location
pub mut:
monitored_files []string // an array, containing all paths that should be monitored for changes
live_lib voidptr // the result of dl.open
reloads int // how many times a reloading was tried
reloads_ok int // how many times the reloads succeeded

View File

@ -46,6 +46,19 @@ pub fn start_reloader(mut r live.LiveReloadInfo) {
spawn reloader(mut r)
}
// add_live_monitored_file will be called by the generated code inside main(), to add a list of all the .v files
// that were used during the main program compilation. Any change to any of them, will later trigger a
// recompilation and reloading of the produced shared library. This makes it possible for [live] functions
// inside modules to also work, not just in the top level program.
pub fn add_live_monitored_file(mut lri live.LiveReloadInfo, path string) {
mtime := os.file_last_mod_unix(path)
lri.monitored_files << path
elog(lri, '${@FN} mtime: ${mtime:12} path: $path')
if lri.last_mod_ts < mtime {
lri.last_mod_ts = mtime
}
}
[if debuglive ?]
fn elog(r &live.LiveReloadInfo, s string) {
eprintln(s)
@ -126,13 +139,21 @@ fn protected_load_lib(mut r live.LiveReloadInfo, new_lib_path string) {
// Note: r.reloader() is executed in a new, independent thread
fn reloader(mut r live.LiveReloadInfo) {
// elog(r,'reloader, r: $r')
mut last_ts := os.file_last_mod_unix(r.original)
mut last_ts := r.last_mod_ts
mut monitored_file_paths := r.monitored_files.clone()
// it is much more likely that the user will be changing *the latest* files
// => put them first, so the search can be cut earlier:
monitored_file_paths.reverse_in_place()
for {
if r.cb_recheck != unsafe { nil } {
r.cb_recheck(r)
}
now_ts := os.file_last_mod_unix(r.original)
if last_ts != now_ts {
sw := time.new_stopwatch()
now_ts := get_latest_ts_from_monitored_files(monitored_file_paths, last_ts)
$if trace_check_monitored_files ? {
eprintln('check if last_ts: $last_ts < now_ts: $now_ts , took $sw.elapsed().microseconds() microseconds')
}
if last_ts < now_ts {
r.reloads++
last_ts = now_ts
r.last_mod_ts = last_ts
@ -157,3 +178,18 @@ fn reloader(mut r live.LiveReloadInfo) {
}
}
}
fn get_latest_ts_from_monitored_files(monitored_file_paths []string, last_ts i64) i64 {
mut latest_ts := i64(0)
for f in monitored_file_paths {
mtime := os.file_last_mod_unix(f)
if mtime > latest_ts {
latest_ts = mtime
if mtime > last_ts {
// no need to check further, since we already know, that there is a newer file, so return early its timestamp
return mtime
}
}
}
return latest_ts
}

View File

@ -34,8 +34,9 @@ TODO: Cleanup this when/if v has better process control/communication primitives
const (
vexe = os.getenv('VEXE')
vtmp_folder = os.join_path(os.vtmp_dir(), 'v', 'tests', 'live')
tmp_file = os.join_path(vtmp_folder, 'generated_live_program.tmp.v')
source_file = os.join_path(vtmp_folder, 'generated_live_program.v')
main_source_file = os.join_path(vtmp_folder, 'main.v')
tmp_file = os.join_path(vtmp_folder, 'mymodule', 'generated_live_module.tmp')
source_file = os.join_path(vtmp_folder, 'mymodule', 'mymodule.v')
genexe_file = os.join_path(vtmp_folder, 'generated_live_program')
output_file = os.join_path(vtmp_folder, 'generated_live_program.output.txt')
res_original_file = os.join_path(vtmp_folder, 'ORIGINAL.txt')
@ -50,14 +51,6 @@ fn get_source_template() string {
return src.replace('#OUTPUT_FILE#', output_file)
}
fn edefault(name string, default string) string {
res := os.getenv(name)
if res == '' {
return default
}
return res
}
fn atomic_write_source(source string) {
// Note: here wrtiting is done in 2 steps, since os.write_file can take some time,
// during which the file will be modified, but it will still be not completely written.
@ -70,6 +63,14 @@ fn atomic_write_source(source string) {
fn testsuite_begin() {
os.rmdir_all(vtmp_folder) or {}
os.mkdir_all(vtmp_folder) or {}
os.mkdir_all(os.join_path(vtmp_folder, 'mymodule'))!
os.write_file(os.join_path(vtmp_folder, 'v.mod'), '')!
os.write_file(os.join_path(vtmp_folder, 'main.v'), '
import mymodule
fn main() {
mymodule.mymain()
}
')!
if os.user_os() !in ['linux', 'solaris'] && os.getenv('FORCE_LIVE_TEST').len == 0 {
eprintln('Testing the runtime behaviour of -live mode,')
eprintln('is reliable only on Linux/macOS for now.')
@ -77,6 +78,7 @@ fn testsuite_begin() {
exit(0)
}
atomic_write_source(live_program_source)
// os.system('tree $vtmp_folder') exit(1)
}
[debuglivetest]
@ -85,6 +87,7 @@ fn vprintln(s string) {
}
fn testsuite_end() {
// os.system('tree $vtmp_folder') exit(1)
vprintln('source: $source_file')
vprintln('output: $output_file')
vprintln('---------------------------------------------------------------------------')
@ -117,7 +120,8 @@ fn wait_for_file(new string) {
time.sleep(100 * time.millisecond)
expected_file := os.join_path(vtmp_folder, new + '.txt')
eprintln('waiting for $expected_file ...')
max_wait_cycles := edefault('WAIT_CYCLES', '1').int()
// os.system('tree $vtmp_folder')
max_wait_cycles := os.getenv_opt('WAIT_CYCLES') or { '1' }.int()
for i := 0; i <= max_wait_cycles; i++ {
if i % 25 == 0 {
vprintln(' checking ${i:-10d} for $expected_file ...')
@ -147,7 +151,9 @@ fn setup_cycles_environment() {
fn test_live_program_can_be_compiled() {
setup_cycles_environment()
eprintln('Compiling...')
os.system('${os.quoted_path(vexe)} -nocolor -live -o ${os.quoted_path(genexe_file)} ${os.quoted_path(source_file)}')
compile_cmd := '${os.quoted_path(vexe)} -cg -keepc -nocolor -live -o ${os.quoted_path(genexe_file)} ${os.quoted_path(main_source_file)}'
eprintln('> compile_cmd: $compile_cmd')
os.system(compile_cmd)
//
cmd := '${os.quoted_path(genexe_file)} > /dev/null &'
eprintln('Running with: $cmd')

View File

@ -1,4 +1,4 @@
module main
module mymodule
import time
import os
@ -29,30 +29,20 @@ fn myprintln(s string) {
[live]
fn pmessage() string {
_ := some_constant * math.sin(2.0 * math.pi * f64(time.ticks() % 6000) / 6000)
_ := mymodule.some_constant * math.sin(2.0 * math.pi * f64(time.ticks() % 6000) / 6000)
return 'ORIGINAL'
}
const (
delay = 20
)
const delay = 20
fn edefault(name string, default string) string {
res := os.getenv(name)
if res == '' {
return default
}
return res
}
fn main() {
pub fn mymain() {
mut info := live.info()
info.recheck_period_ms = 5
myprintln('START')
myprintln('DATE: ' + time.now().str())
pmessage()
pmessage()
max_cycles := edefault('LIVE_CYCLES', '1').int()
max_cycles := os.getenv_opt('LIVE_CYCLES') or { '1' }.int()
// NB: 1000 * 20 = maximum of ~20s runtime
for i := 0; i < max_cycles; i++ {
s := pmessage()
@ -61,7 +51,7 @@ fn main() {
if s == 'STOP' {
break
}
time.sleep(delay * time.millisecond)
time.sleep(mymodule.delay * time.millisecond)
}
pmessage()
pmessage()