| 1 | module vmod |
| 2 | |
| 3 | import os |
| 4 | |
| 5 | const mod_file_stop_paths = ['.git', '.hg', '.svn', '.v.mod.stop'] |
| 6 | |
| 7 | // used during lookup for v.mod to support @VEXEROOT |
| 8 | const private_file_cacher = new_mod_file_cacher() |
| 9 | |
| 10 | pub fn get_cache() &ModFileCacher { |
| 11 | return private_file_cacher |
| 12 | } |
| 13 | |
| 14 | // resolved_base_url returns the source folder configured by `base_url`, |
| 15 | // resolved relative to the folder containing the `v.mod` file. |
| 16 | pub fn (manifest Manifest) resolved_base_url(vmod_root string) string { |
| 17 | if manifest.base_url == '' { |
| 18 | return '' |
| 19 | } |
| 20 | return os.norm_path(os.join_path(vmod_root, manifest.base_url)) |
| 21 | } |
| 22 | |
| 23 | // source_root returns the folder where sources are looked up under a `v.mod`. |
| 24 | // When `base_url` is set, it points at that folder; otherwise it falls back to |
| 25 | // the folder containing `v.mod`. The previous implicit `src/` fallback is gone. |
| 26 | pub fn (manifest Manifest) source_root(vmod_root string) string { |
| 27 | base_url := manifest.resolved_base_url(vmod_root) |
| 28 | if base_url != '' { |
| 29 | return base_url |
| 30 | } |
| 31 | return os.norm_path(vmod_root) |
| 32 | } |
| 33 | |
| 34 | // This file provides a caching mechanism for seeking quickly whether a |
| 35 | // given folder has a v.mod file in it or in any of its parent folders. |
| 36 | // |
| 37 | // ModFileCacher.get(folder) works in such a way, that given this tree: |
| 38 | // examples/hanoi.v |
| 39 | // vlib/v.mod |
| 40 | // vlib/v/tests/project_with_c_code/mod1/v.mod |
| 41 | // vlib/v/tests/project_with_c_code/mod1/wrapper.c.v |
| 42 | // ----------------- |
| 43 | // ModFileCacher.get('examples') |
| 44 | // => ModFileAndFolder{'', 'examples'} |
| 45 | // ModFileCacher.get('vlib/v/tests') |
| 46 | // => ModFileAndFolder{'vlib/v.mod', 'vlib'} |
| 47 | // ModFileCacher.get('vlib/v') |
| 48 | // => ModFileAndFolder{'vlib/v.mod', 'vlib'} |
| 49 | // ModFileCacher.get('vlib/v/test/project_with_c_code/mod1') |
| 50 | // => ModFileAndFolder{'vlib/v/test/project_with_c_code/mod1/v.mod', 'vlib/v/test/project_with_c_code/mod1'} |
| 51 | pub struct ModFileAndFolder { |
| 52 | pub: |
| 53 | // vmod_file contains the full path of the found 'v.mod' file, or '' |
| 54 | // if no 'v.mod' file was found in file_path_dir, or in its parent folders. |
| 55 | vmod_file string |
| 56 | // vmod_folder contains the file_path_dir, if there is no 'v.mod' file in |
| 57 | // *any* of the parent folders, otherwise it is the first parent folder, |
| 58 | // where a v.mod file was found. |
| 59 | vmod_folder string |
| 60 | } |
| 61 | |
| 62 | @[heap] |
| 63 | pub struct ModFileCacher { |
| 64 | mut: |
| 65 | cache map[string]ModFileAndFolder |
| 66 | // folder_files caches os.ls(key) |
| 67 | folder_files map[string][]string |
| 68 | hits int |
| 69 | misses int |
| 70 | get_files_hits int |
| 71 | get_files_misses int |
| 72 | } |
| 73 | |
| 74 | pub fn new_mod_file_cacher() &ModFileCacher { |
| 75 | return &ModFileCacher{} |
| 76 | } |
| 77 | |
| 78 | @[if debug_mod_file_cacher ?] |
| 79 | pub fn (mcache &ModFileCacher) debug() { |
| 80 | eprintln('ModFileCacher hits: ${mcache.hits}, misses: ${mcache.misses} | get_files_hits: ${mcache.get_files_hits} | get_files_misses: ${mcache.get_files_misses}') |
| 81 | eprintln(' ModFileCacher.cache.len: ${mcache.cache.len}') |
| 82 | for k, v in mcache.cache { |
| 83 | eprintln(' K: ${k:-42s} | v.mod: ${v.vmod_file:-42s} | folder: `${v.vmod_folder}`') |
| 84 | } |
| 85 | eprintln(' ModFileCacher.folder_files:') |
| 86 | for k, v in mcache.folder_files { |
| 87 | eprintln(' K: ${k:-42s} | folder_files: ${v}') |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | pub fn (mut mcache ModFileCacher) get_by_file(vfile string) ModFileAndFolder { |
| 92 | return mcache.get_by_folder(os.dir(vfile)) |
| 93 | } |
| 94 | |
| 95 | pub fn (mut mcache ModFileCacher) get_by_folder(vfolder string) ModFileAndFolder { |
| 96 | mfolder := os.real_path(vfolder) |
| 97 | if mfolder in mcache.cache { |
| 98 | mcache.hits++ |
| 99 | return mcache.cache[mfolder] |
| 100 | } |
| 101 | traversed_folders, res := mcache.traverse(mfolder) |
| 102 | for tfolder in traversed_folders { |
| 103 | mcache.add(tfolder, res) |
| 104 | } |
| 105 | mcache.misses++ |
| 106 | return res |
| 107 | } |
| 108 | |
| 109 | fn (mut cacher ModFileCacher) add(path string, result ModFileAndFolder) { |
| 110 | cacher.cache[path] = result |
| 111 | } |
| 112 | |
| 113 | fn (mut mcache ModFileCacher) traverse(mfolder string) ([]string, ModFileAndFolder) { |
| 114 | mut cfolder := mfolder |
| 115 | mut folders_so_far := [cfolder] |
| 116 | mut levels := 0 |
| 117 | for { |
| 118 | if levels > 255 { |
| 119 | break |
| 120 | } |
| 121 | if cfolder == '/' || cfolder == '' { |
| 122 | break |
| 123 | } |
| 124 | if cfolder in mcache.cache { |
| 125 | mcache.hits++ |
| 126 | res := mcache.cache[cfolder] |
| 127 | if res.vmod_file.len == 0 { |
| 128 | mcache.mark_folders_as_vmod_free(folders_so_far) |
| 129 | } else { |
| 130 | mcache.mark_folders_with_vmod(folders_so_far, res) |
| 131 | } |
| 132 | return []string{}, res |
| 133 | } |
| 134 | files := mcache.get_files(cfolder) |
| 135 | if 'v.mod' in files { |
| 136 | // TODO: actually read the v.mod file and parse its contents to see |
| 137 | // if its source folder is different |
| 138 | res := ModFileAndFolder{ |
| 139 | vmod_file: os.join_path(cfolder, 'v.mod') |
| 140 | vmod_folder: cfolder |
| 141 | } |
| 142 | return folders_so_far, res |
| 143 | } |
| 144 | if mcache.check_for_stop(files) { |
| 145 | break |
| 146 | } |
| 147 | cfolder = os.dir(cfolder) |
| 148 | folders_so_far << cfolder |
| 149 | levels++ |
| 150 | } |
| 151 | mcache.mark_folders_as_vmod_free(folders_so_far) |
| 152 | return [mfolder], ModFileAndFolder{ |
| 153 | vmod_file: '' |
| 154 | vmod_folder: mfolder |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | fn (mut mcache ModFileCacher) mark_folders_with_vmod(folders_so_far []string, vmod ModFileAndFolder) { |
| 159 | for f in folders_so_far { |
| 160 | mcache.add(f, vmod) |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | fn (mut mcache ModFileCacher) mark_folders_as_vmod_free(folders_so_far []string) { |
| 165 | // No need to check these folders anymore, |
| 166 | // because their parents do not contain v.mod files |
| 167 | for f in folders_so_far { |
| 168 | mcache.add(f, vmod_file: '', vmod_folder: f) |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | fn (mcache &ModFileCacher) check_for_stop(files []string) bool { |
| 173 | for i in mod_file_stop_paths { |
| 174 | if i in files { |
| 175 | return true |
| 176 | } |
| 177 | } |
| 178 | return false |
| 179 | } |
| 180 | |
| 181 | fn (mut mcache ModFileCacher) get_files(cfolder string) []string { |
| 182 | if cfolder in mcache.folder_files { |
| 183 | mcache.get_files_hits++ |
| 184 | return mcache.folder_files[cfolder] |
| 185 | } |
| 186 | mcache.get_files_misses++ |
| 187 | mut files := []string{} |
| 188 | if os.exists(cfolder) && os.is_dir(cfolder) { |
| 189 | if listing := os.ls(cfolder) { |
| 190 | files = listing.clone() |
| 191 | } |
| 192 | } |
| 193 | mcache.folder_files[cfolder] = files |
| 194 | return files |
| 195 | } |
| 196 | |