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
|
json_types []ast.Type // to avoid json gen duplicates
|
||||||
pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name
|
pcs []ProfileCounterMeta // -prof profile counter fn_names => fn counter name
|
||||||
hotcode_fn_names []string
|
hotcode_fn_names []string
|
||||||
|
hotcode_fpaths []string
|
||||||
embedded_files []ast.EmbeddedFile
|
embedded_files []ast.EmbeddedFile
|
||||||
sql_i int
|
sql_i int
|
||||||
sql_stmt_name string
|
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.pcs << g.pcs
|
||||||
global_g.json_types << g.json_types
|
global_g.json_types << g.json_types
|
||||||
global_g.hotcode_fn_names << g.hotcode_fn_names
|
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
|
global_g.test_function_names << g.test_function_names
|
||||||
unsafe { g.free_builders() }
|
unsafe { g.free_builders() }
|
||||||
for k, v in g.autofree_methods {
|
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_' .
|
// with 'impl_live_' .
|
||||||
if is_livemain {
|
if is_livemain {
|
||||||
g.hotcode_fn_names << name
|
g.hotcode_fn_names << name
|
||||||
|
g.hotcode_fpaths << g.file.path
|
||||||
}
|
}
|
||||||
mut impl_fn_name := name
|
mut impl_fn_name := name
|
||||||
if is_live_wrap {
|
if is_live_wrap {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module c
|
module c
|
||||||
|
|
||||||
|
import os
|
||||||
import v.pref
|
import v.pref
|
||||||
import v.util
|
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 &live_fn_mutex,')
|
||||||
g.writeln('\t\t\t\t\t v_bind_live_symbols')
|
g.writeln('\t\t\t\t\t v_bind_live_symbols')
|
||||||
g.writeln('\t\t);')
|
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,
|
// g_live_info gives access to the LiveReloadInfo methods,
|
||||||
// to the custom user code, through calling v_live_info()
|
// 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\tv__live__executable__start_reloader(live_info);')
|
||||||
g.writeln('\t}\t// end of live code initialization section')
|
g.writeln('\t}\t// end of live code initialization section')
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
module live
|
module live
|
||||||
|
|
||||||
pub const (
|
pub const is_used = 1
|
||||||
is_used = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
pub type FNLinkLiveSymbols = fn (linkcb voidptr)
|
pub type FNLinkLiveSymbols = fn (linkcb voidptr)
|
||||||
|
|
||||||
pub type FNLiveReloadCB = fn (info &LiveReloadInfo)
|
pub type FNLiveReloadCB = fn (info &LiveReloadInfo)
|
||||||
|
|
||||||
[minify]
|
|
||||||
pub struct LiveReloadInfo {
|
pub struct LiveReloadInfo {
|
||||||
pub:
|
pub:
|
||||||
vexe string // full path to the v compiler
|
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_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
|
live_linkfn FNLinkLiveSymbols // generated C callback; receives a dlopen handle
|
||||||
so_extension string // .so or .dll
|
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:
|
pub mut:
|
||||||
|
monitored_files []string // an array, containing all paths that should be monitored for changes
|
||||||
live_lib voidptr // the result of dl.open
|
live_lib voidptr // the result of dl.open
|
||||||
reloads int // how many times a reloading was tried
|
reloads int // how many times a reloading was tried
|
||||||
reloads_ok int // how many times the reloads succeeded
|
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)
|
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 ?]
|
[if debuglive ?]
|
||||||
fn elog(r &live.LiveReloadInfo, s string) {
|
fn elog(r &live.LiveReloadInfo, s string) {
|
||||||
eprintln(s)
|
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
|
// Note: r.reloader() is executed in a new, independent thread
|
||||||
fn reloader(mut r live.LiveReloadInfo) {
|
fn reloader(mut r live.LiveReloadInfo) {
|
||||||
// elog(r,'reloader, r: $r')
|
// 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 {
|
for {
|
||||||
if r.cb_recheck != unsafe { nil } {
|
if r.cb_recheck != unsafe { nil } {
|
||||||
r.cb_recheck(r)
|
r.cb_recheck(r)
|
||||||
}
|
}
|
||||||
now_ts := os.file_last_mod_unix(r.original)
|
sw := time.new_stopwatch()
|
||||||
if last_ts != now_ts {
|
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++
|
r.reloads++
|
||||||
last_ts = now_ts
|
last_ts = now_ts
|
||||||
r.last_mod_ts = last_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 (
|
const (
|
||||||
vexe = os.getenv('VEXE')
|
vexe = os.getenv('VEXE')
|
||||||
vtmp_folder = os.join_path(os.vtmp_dir(), 'v', 'tests', 'live')
|
vtmp_folder = os.join_path(os.vtmp_dir(), 'v', 'tests', 'live')
|
||||||
tmp_file = os.join_path(vtmp_folder, 'generated_live_program.tmp.v')
|
main_source_file = os.join_path(vtmp_folder, 'main.v')
|
||||||
source_file = os.join_path(vtmp_folder, 'generated_live_program.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')
|
genexe_file = os.join_path(vtmp_folder, 'generated_live_program')
|
||||||
output_file = os.join_path(vtmp_folder, 'generated_live_program.output.txt')
|
output_file = os.join_path(vtmp_folder, 'generated_live_program.output.txt')
|
||||||
res_original_file = os.join_path(vtmp_folder, 'ORIGINAL.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)
|
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) {
|
fn atomic_write_source(source string) {
|
||||||
// Note: here wrtiting is done in 2 steps, since os.write_file can take some time,
|
// 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.
|
// 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() {
|
fn testsuite_begin() {
|
||||||
os.rmdir_all(vtmp_folder) or {}
|
os.rmdir_all(vtmp_folder) or {}
|
||||||
os.mkdir_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 {
|
if os.user_os() !in ['linux', 'solaris'] && os.getenv('FORCE_LIVE_TEST').len == 0 {
|
||||||
eprintln('Testing the runtime behaviour of -live mode,')
|
eprintln('Testing the runtime behaviour of -live mode,')
|
||||||
eprintln('is reliable only on Linux/macOS for now.')
|
eprintln('is reliable only on Linux/macOS for now.')
|
||||||
@ -77,6 +78,7 @@ fn testsuite_begin() {
|
|||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
atomic_write_source(live_program_source)
|
atomic_write_source(live_program_source)
|
||||||
|
// os.system('tree $vtmp_folder') exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
[debuglivetest]
|
[debuglivetest]
|
||||||
@ -85,6 +87,7 @@ fn vprintln(s string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn testsuite_end() {
|
fn testsuite_end() {
|
||||||
|
// os.system('tree $vtmp_folder') exit(1)
|
||||||
vprintln('source: $source_file')
|
vprintln('source: $source_file')
|
||||||
vprintln('output: $output_file')
|
vprintln('output: $output_file')
|
||||||
vprintln('---------------------------------------------------------------------------')
|
vprintln('---------------------------------------------------------------------------')
|
||||||
@ -117,7 +120,8 @@ fn wait_for_file(new string) {
|
|||||||
time.sleep(100 * time.millisecond)
|
time.sleep(100 * time.millisecond)
|
||||||
expected_file := os.join_path(vtmp_folder, new + '.txt')
|
expected_file := os.join_path(vtmp_folder, new + '.txt')
|
||||||
eprintln('waiting for $expected_file ...')
|
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++ {
|
for i := 0; i <= max_wait_cycles; i++ {
|
||||||
if i % 25 == 0 {
|
if i % 25 == 0 {
|
||||||
vprintln(' checking ${i:-10d} for $expected_file ...')
|
vprintln(' checking ${i:-10d} for $expected_file ...')
|
||||||
@ -147,7 +151,9 @@ fn setup_cycles_environment() {
|
|||||||
fn test_live_program_can_be_compiled() {
|
fn test_live_program_can_be_compiled() {
|
||||||
setup_cycles_environment()
|
setup_cycles_environment()
|
||||||
eprintln('Compiling...')
|
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 &'
|
cmd := '${os.quoted_path(genexe_file)} > /dev/null &'
|
||||||
eprintln('Running with: $cmd')
|
eprintln('Running with: $cmd')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module main
|
module mymodule
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
@ -29,30 +29,20 @@ fn myprintln(s string) {
|
|||||||
|
|
||||||
[live]
|
[live]
|
||||||
fn pmessage() string {
|
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'
|
return 'ORIGINAL'
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const delay = 20
|
||||||
delay = 20
|
|
||||||
)
|
|
||||||
|
|
||||||
fn edefault(name string, default string) string {
|
pub fn mymain() {
|
||||||
res := os.getenv(name)
|
|
||||||
if res == '' {
|
|
||||||
return default
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
mut info := live.info()
|
mut info := live.info()
|
||||||
info.recheck_period_ms = 5
|
info.recheck_period_ms = 5
|
||||||
myprintln('START')
|
myprintln('START')
|
||||||
myprintln('DATE: ' + time.now().str())
|
myprintln('DATE: ' + time.now().str())
|
||||||
pmessage()
|
pmessage()
|
||||||
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
|
// NB: 1000 * 20 = maximum of ~20s runtime
|
||||||
for i := 0; i < max_cycles; i++ {
|
for i := 0; i < max_cycles; i++ {
|
||||||
s := pmessage()
|
s := pmessage()
|
||||||
@ -61,7 +51,7 @@ fn main() {
|
|||||||
if s == 'STOP' {
|
if s == 'STOP' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.sleep(delay * time.millisecond)
|
time.sleep(mymodule.delay * time.millisecond)
|
||||||
}
|
}
|
||||||
pmessage()
|
pmessage()
|
||||||
pmessage()
|
pmessage()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user