v/vlib/v/live/executable/reloader.v

164 lines
4.5 KiB
V

module executable
import os
import time
import dl
import strconv
import v.live
pub const (
is_used = 1
)
// The live reloader code is implemented here.
// Note: new_live_reload_info will be called by generated C code inside main()
pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_mutex voidptr, live_linkfn live.FNLinkLiveSymbols) &live.LiveReloadInfo {
file_base := os.file_name(original).replace('.v', '')
so_dir := os.cache_dir()
mut so_extension := dl.dl_ext
$if macos {
so_extension = '.dylib'
}
// $if msvc { so_extension = '.dll' } $else { so_extension = '.so' }
return &live.LiveReloadInfo{
original: original
vexe: vexe
vopts: vopts
live_fn_mutex: live_fn_mutex
live_linkfn: live_linkfn
so_extension: so_extension
so_name_template: '$so_dir/tmp.%d.$file_base'
live_lib: 0
reloads: 0
reload_time_ms: 0
}
}
// Note: start_reloader will be called by generated code inside main(), to start
// the hot code reloader thread. start_reloader is executed in the context of
// the original main thread.
pub fn start_reloader(mut r live.LiveReloadInfo) {
// The shared library should be loaded once in the main thread
// If that fails, the program would crash anyway, just provide
// an error message to the user and exit:
r.reloads++
compile_and_reload_shared_lib(mut r) or {
eprintln(err)
exit(1)
}
go reloader(mut r)
}
fn elog(r &live.LiveReloadInfo, s string) {
$if debuglive ? {
eprintln(s)
}
}
fn compile_and_reload_shared_lib(mut r live.LiveReloadInfo) ?bool {
sw := time.new_stopwatch()
new_lib_path := compile_lib(mut r) or { return error('errors while compiling $r.original') }
elog(r, '> compile_and_reload_shared_lib compiled: $new_lib_path')
load_lib(mut r, new_lib_path)
r.reload_time_ms = int(sw.elapsed().milliseconds())
return true
}
fn compile_lib(mut r live.LiveReloadInfo) ?string {
new_lib_path, new_lib_path_with_extension := current_shared_library_path(mut r)
cmd := '${os.quoted_path(r.vexe)} $r.vopts -o ${os.quoted_path(new_lib_path)} ${os.quoted_path(r.original)}'
elog(r, '> compilation cmd: $cmd')
cwatch := time.new_stopwatch()
recompilation_result := os.execute(cmd)
elog(r, 'compilation took: ${cwatch.elapsed().milliseconds()}ms')
if recompilation_result.exit_code != 0 {
eprintln('recompilation error:')
eprintln(recompilation_result.output)
return none
}
if !os.exists(new_lib_path_with_extension) {
eprintln('new_lib_path: $new_lib_path_with_extension does not exist')
return none
}
return new_lib_path_with_extension
}
fn current_shared_library_path(mut r live.LiveReloadInfo) (string, string) {
lib_path := strconv.v_sprintf(r.so_name_template.replace('\\', '\\\\'), r.reloads)
lib_path_with_extension := lib_path + r.so_extension
return lib_path, lib_path_with_extension
}
fn load_lib(mut r live.LiveReloadInfo, new_lib_path string) {
elog(r, 'live mutex locking...')
C.pthread_mutex_lock(r.live_fn_mutex)
elog(r, 'live mutex locked')
//
if r.cb_locked_before != voidptr(0) {
r.cb_locked_before(r)
}
//
protected_load_lib(mut r, new_lib_path)
//
r.reloads_ok++
if r.cb_locked_after != voidptr(0) {
r.cb_locked_after(r)
}
//
elog(r, 'live mutex unlocking...')
C.pthread_mutex_unlock(r.live_fn_mutex)
elog(r, 'live mutex unlocked')
}
fn protected_load_lib(mut r live.LiveReloadInfo, new_lib_path string) {
if r.live_lib != 0 {
dl.close(r.live_lib)
r.live_lib = C.NULL
}
r.live_lib = dl.open(new_lib_path, dl.rtld_lazy)
if r.live_lib == 0 {
eprintln('opening $new_lib_path failed')
exit(1)
}
r.live_linkfn(r.live_lib)
elog(r, '> load_lib OK, new live_lib: $r.live_lib')
// removing the .so file from the filesystem after dlopen-ing
// it is safe, since it will still be mapped in memory
os.rm(new_lib_path) or {}
}
// 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)
for {
if r.cb_recheck != voidptr(0) {
r.cb_recheck(r)
}
now_ts := os.file_last_mod_unix(r.original)
if last_ts != now_ts {
r.reloads++
last_ts = now_ts
r.last_mod_ts = last_ts
if r.cb_before != voidptr(0) {
r.cb_before(r)
}
compile_and_reload_shared_lib(mut r) or {
if r.cb_compile_failed != voidptr(0) {
r.cb_compile_failed(r)
}
if r.cb_after != voidptr(0) {
r.cb_after(r)
}
continue
}
if r.cb_after != voidptr(0) {
r.cb_after(r)
}
}
if r.recheck_period_ms > 0 {
time.sleep(r.recheck_period_ms * time.millisecond)
}
}
}