From 9042a5edcae69be4b582856f3da5440be08dbb6f Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:26 +0300 Subject: [PATCH] builder: fix cannot use submodules defined inside src folder (fixes #15374) --- vlib/v/builder/builder.v | 70 +++++++++++++++++-- .../path4/hunam6/voak/foo/foo.v | 1 - .../path4/hunam6/voak/src/foo/foo.v | 5 ++ .../submodule_of_third_party_main.vv | 4 ++ vlib/v/util/module.v | 44 +++++++++--- 5 files changed, 110 insertions(+), 14 deletions(-) delete mode 100644 vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/foo/foo.v create mode 100644 vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/src/foo/foo.v diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index 3f5472f96..2c6bbb4e6 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -457,6 +457,66 @@ pub fn module_path(mod string) string { return mod.replace('.', os.path_separator) } +fn find_module_path_from_vmod_root(vmod_root string, mod string) !string { + vmod_path := os.join_path(vmod_root, 'v.mod') + if !os.is_file(vmod_path) { + return error('module not found') + } + manifest := vmod.from_file(vmod_path) or { return error('module not found') } + tail_path := mod_tail_after_vmod_name(mod, manifest.name) or { + return error('module not found') + } + if tail_path == '' { + return error('module not found') + } + try_path := os.join_path(vmod_root, 'src', tail_path) + if os.is_dir(try_path) { + return try_path + } + return error('module not found') +} + +fn find_module_path_from_search_root(search_path string, mod string) !string { + mod_path := module_path(mod) + try_path := os.join_path_single(search_path, mod_path) + if os.is_dir(try_path) { + return try_path + } + if src_try_path := find_module_path_from_vmod_root(search_path, mod) { + return src_try_path + } + mod_parts := mod.split('.') + for i := mod_parts.len - 1; i > 0; i-- { + candidate_root := os.join_path_single(search_path, mod_parts[..i].join(os.path_separator)) + if !os.is_file(os.join_path(candidate_root, 'v.mod')) { + continue + } + submodule_path := mod_parts[i..].join(os.path_separator) + src_try_path := os.join_path(candidate_root, 'src', submodule_path) + if os.is_dir(src_try_path) { + return src_try_path + } + } + return error('module not found') +} + +fn mod_tail_after_vmod_name(mod string, vmod_name string) !string { + if vmod_name == '' { + return error('module not found') + } + mod_parts := mod.split('.') + vmod_parts := vmod_name.split('.') + for i := 0; i + vmod_parts.len <= mod_parts.len; i++ { + if i > 1 { + break + } + if mod_parts[i..i + vmod_parts.len].join('.') == vmod_name { + return mod_parts[i + vmod_parts.len..].join(os.path_separator) + } + } + return error('module not found') +} + // TODO: try to merge this & util.module functions to create a // reliable multi use function. see comments in util/module.v pub fn (b &Builder) find_module_path(mod string, fpath string) !string { @@ -487,11 +547,11 @@ pub fn (b &Builder) find_module_path(mod string, fpath string) !string { if b.pref.is_verbose { println(' >> trying to find ${mod} in ${try_path} ..') } - if os.is_dir(try_path) { + if found_path := find_module_path_from_search_root(search_path, mod) { if b.pref.is_verbose { - println(' << found ${try_path} .') + println(' << found ${found_path} .') } - return try_path + return found_path } } // look up through parents @@ -501,8 +561,8 @@ pub fn (b &Builder) find_module_path(mod string, fpath string) !string { if b.pref.is_verbose { println(' >> trying to find ${mod} in ${try_path} ..') } - if os.is_dir(try_path) { - return try_path + if found_path := find_module_path_from_search_root(p1, mod) { + return found_path } parent_dir := os.dir(current_dir) if parent_dir == current_dir { diff --git a/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/foo/foo.v b/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/foo/foo.v deleted file mode 100644 index 49525808b..000000000 --- a/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/foo/foo.v +++ /dev/null @@ -1 +0,0 @@ -module foo diff --git a/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/src/foo/foo.v b/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/src/foo/foo.v new file mode 100644 index 000000000..7407f36b5 --- /dev/null +++ b/vlib/v/tests/multiple_paths_in_vmodules/path4/hunam6/voak/src/foo/foo.v @@ -0,0 +1,5 @@ +module foo + +pub fn hello() string { + return 'hello' +} diff --git a/vlib/v/tests/multiple_paths_in_vmodules/submodule_of_third_party_main.vv b/vlib/v/tests/multiple_paths_in_vmodules/submodule_of_third_party_main.vv index cbca77d73..dbdf2598b 100644 --- a/vlib/v/tests/multiple_paths_in_vmodules/submodule_of_third_party_main.vv +++ b/vlib/v/tests/multiple_paths_in_vmodules/submodule_of_third_party_main.vv @@ -1 +1,5 @@ import hunam6.voak.foo + +fn main() { + assert foo.hello() == 'hello' +} diff --git a/vlib/v/util/module.v b/vlib/v/util/module.v index 16ecae5cd..9916b87b9 100644 --- a/vlib/v/util/module.v +++ b/vlib/v/util/module.v @@ -4,6 +4,7 @@ module util // 2022-01-30 that already does handle v.mod lookup properly, stopping at .git folders, supporting `.v.mod.stop` etc. import os import v.pref +import v.vmod @[if trace_util_qualify ?] fn trace_qualify(callfn string, mod string, file_path string, kind_res string, result string, detail string) { @@ -139,7 +140,8 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri path_part := path_parts[j] // we reached a vmod folder if path_part in vmod_folders { - mod_full_name := try_path.split(os.path_separator)[j + 1..].join('.') + mod_full_name := normalize_src_based_mod_name(try_path.split(os.path_separator)[ + j + 1..].join('.'), try_path) return mod_full_name } } @@ -163,18 +165,19 @@ fn mod_path_to_full_name(pref_ &pref.Preferences, mod string, path string) !stri break } if last_v_mod > -1 { - mod_full_name := try_path_parts[last_v_mod..].join('.') + mod_full_name := normalize_src_based_mod_name(try_path_parts[last_v_mod..].join('.'), + try_path) return if mod_full_name.len < mod.len { mod } else { mod_full_name } } } } } - if pref_.path.len > 0 && os.is_dir(path) { - real_pref_path_dir := pref_path_to_source_root(pref_) - real_path := os.real_path(path) - prefix := real_pref_path_dir + os.path_separator - if real_path.starts_with(prefix) { - full_mod_name := real_path[prefix.len..].replace(os.path_separator, '.') + if os.is_abs_path(pref_.path) && os.is_abs_path(path) && os.is_dir(path) { // && path.contains(mod ) + rel_mod_path := path.replace(pref_.path.all_before_last(os.path_separator) + + os.path_separator, '') + if rel_mod_path != path { + full_mod_name := normalize_src_based_mod_name(rel_mod_path.replace(os.path_separator, + '.'), path) return full_mod_name } } @@ -193,3 +196,28 @@ fn pref_path_to_source_root(pref_ &pref.Preferences) string { } return real_pref_path_dir } + +fn normalize_src_based_mod_name(mod_full_name string, path string) string { + real_path := os.real_path(path) + mut mcache := vmod.get_cache() + vmod_file_location := mcache.get_by_folder(real_path) + if vmod_file_location.vmod_file == '' { + return mod_full_name + } + vmod_prefix := vmod_file_location.vmod_folder + os.path_separator + if !real_path.starts_with(vmod_prefix) { + return mod_full_name + } + rel_path := real_path.all_after(vmod_prefix) + rel_parts := rel_path.split(os.path_separator) + if rel_parts.len == 0 || rel_parts[0] != 'src' { + return mod_full_name + } + full_parts := mod_full_name.split('.') + if rel_parts.len > full_parts.len { + return mod_full_name + } + mut normalized_parts := full_parts[..full_parts.len - rel_parts.len].clone() + normalized_parts << rel_parts[1..] + return normalized_parts.join('.') +} -- 2.39.5