From c62446b2abebb30193aedccb22a613e45cc32b4a Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 21 Apr 2026 15:25:21 +0300 Subject: [PATCH] builder: fix hot reload on windows (fixes #3548) --- vlib/sokol/c/declaration.c.v | 24 ++++++++++++++++++++---- vlib/v/builder/msvc_windows.v | 3 +++ vlib/v/gen/c/live.v | 13 ++++++++++++- vlib/v/live/live_test.v | 27 +++++++++++++++++++++++++++ vlib/v/pref/pref.v | 2 ++ 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/vlib/sokol/c/declaration.c.v b/vlib/sokol/c/declaration.c.v index f7b5a0d59..d9dea950d 100644 --- a/vlib/sokol/c/declaration.c.v +++ b/vlib/sokol/c/declaration.c.v @@ -29,6 +29,14 @@ $if sokol_wayland ? { $if windows { #flag windows -lopengl32 + $if msvc { + $if livemain ? { + #define SOKOL_DLL + } + $if sharedlive ? { + #define SOKOL_DLL + } + } } // Note that -lm is needed *only* for sokol_gl.h's usage of sqrtf(), @@ -112,6 +120,8 @@ $if !no_sokol_app ? { // The live-reload dylib should reuse the host app's sokol_app symbols on macOS. #define SOKOL_APP_IMPL } + } $else $if windows && msvc && sharedlive ? { + // The live-reload DLL should call the host executable's sokol_app symbols. } $else { #define SOKOL_APP_IMPL } @@ -120,13 +130,19 @@ $if !no_sokol_app ? { #include "sokol_app.h" } -@[use_once] -#define SOKOL_GFX_IMPL +$if windows && msvc && sharedlive ? { +} $else { + @[use_once] + #define SOKOL_GFX_IMPL +} #define SOKOL_NO_DEPRECATED #include "sokol_gfx.h" -@[use_once] -#define SOKOL_IMPL +$if windows && msvc && sharedlive ? { +} $else { + @[use_once] + #define SOKOL_IMPL +} #include "util/sokol_gl.h" #include "sokol_v.post.h" diff --git a/vlib/v/builder/msvc_windows.v b/vlib/v/builder/msvc_windows.v index de6a67031..5009fd996 100644 --- a/vlib/v/builder/msvc_windows.v +++ b/vlib/v/builder/msvc_windows.v @@ -357,6 +357,9 @@ pub fn (mut v Builder) cc_msvc() { a << '/nologo' // NOTE: /NOLOGO is explicitly not recognised! a << '/OUT:${os.quoted_path(v.pref.out_name)}' + if v.pref.is_livemain { + a << '/IMPLIB:${os.quoted_path(v.pref.out_name[..v.pref.out_name.len - 4] + '.lib')}' + } a << r.library_paths() if !all_cflags.contains('/DEBUG') { // only use /DEBUG, if the user *did not* provide its own: diff --git a/vlib/v/gen/c/live.v b/vlib/v/gen/c/live.v index 339a7f75a..3682e542d 100644 --- a/vlib/v/gen/c/live.v +++ b/vlib/v/gen/c/live.v @@ -121,7 +121,18 @@ fn (mut g Gen) generate_hotcode_reloading_main_caller() { ccpath := util.cescaped_path(g.pref.ccompiler) ccompiler := '-cc ${ccpath}' so_debug_flag := if g.pref.is_debug { '-cg' } else { '' } - vopts := '${ccompiler} ${so_debug_flag} -sharedlive -shared' + mut vopts := '${ccompiler} ${so_debug_flag} -sharedlive -shared' + if g.pref.os == .windows && g.is_cc_msvc && 'sokol' in g.table.imports { + mut import_lib_path := g.pref.out_name + ext := os.file_ext(import_lib_path) + if ext != '' { + import_lib_path = import_lib_path[..import_lib_path.len - ext.len] + '.lib' + } else { + import_lib_path += '.lib' + } + escaped_import_lib_path := util.cescaped_path(os.abs_path(import_lib_path)) + vopts += " -ldflags \\\"${escaped_import_lib_path}\\\"" + } g.writeln('\t\t// start background reloading thread') g.writeln('\t\tvoid* live_fn_mutex_addr = v_live_fn_mutex_ptr();') diff --git a/vlib/v/live/live_test.v b/vlib/v/live/live_test.v index 18fd809fb..d61a8fa84 100644 --- a/vlib/v/live/live_test.v +++ b/vlib/v/live/live_test.v @@ -188,6 +188,14 @@ fn run_in_background(cmd string) { log.warn('the live program should be running in the background now') } +fn must_exec_cmd(cmd string) { + res := os.execute(cmd) + if res.exit_code == 0 { + return + } + panic('command failed: ${cmd}\n${res.output}') +} + fn test_live_program_can_be_compiled() { setup_cycles_environment() compile_cmd := '${os.quoted_path(vexe)} -cg -keepc -nocolor -live -o ${os.quoted_path(genexe_file)} ${os.quoted_path(main_source_file)}' @@ -226,3 +234,22 @@ fn test_live_program_can_be_changed_4() { change_source('STOP') assert true } + +fn test_live_windows_sokol_sharedlive_build_uses_host_import_lib() { + $if !windows || !msvc { + return + } + tmp_dir := os.join_path(os.vtmp_dir(), 'live_windows_sokol_compile') + os.mkdir_all(tmp_dir) or { panic(err) } + defer { + os.rmdir_all(tmp_dir) or {} + } + source := os.join_path(@VEXEROOT, 'examples', 'sokol', 'drawing.v') + exe_path := os.join_path(tmp_dir, 'drawing_live.exe') + lib_path := exe_path[..exe_path.len - 4] + '.lib' + dll_path := os.join_path(tmp_dir, 'drawing_live_shared.dll') + must_exec_cmd('${os.quoted_path(vexe)} -nocolor -cc msvc -live -o ${os.quoted_path(exe_path)} ${os.quoted_path(source)}') + assert os.exists(lib_path) + must_exec_cmd('${os.quoted_path(vexe)} -nocolor -cc msvc -sharedlive -shared -ldflags ${os.quoted_path(lib_path)} -o ${os.quoted_path(dll_path)} ${os.quoted_path(source)}') + assert os.exists(dll_path) +} diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 35cb36845..a00c57e83 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -682,6 +682,8 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin } '-live' { res.is_livemain = true + res.compile_defines << 'livemain' + res.compile_defines_all << 'livemain' } '-sharedlive' { res.is_liveshared = true -- 2.39.5