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:
parent
c9ce5f89c7
commit
12f01318c2
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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('')
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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')
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user