| 1 | module main |
| 2 | |
| 3 | import os |
| 4 | import v.pref |
| 5 | import v.util.version |
| 6 | |
| 7 | const vvmrc_file_name = '.vvmrc' |
| 8 | |
| 9 | const vvmrc_skip_env = 'V_SKIP_VVMRC' |
| 10 | |
| 11 | const vvmrc_latest_aliases = ['latest', 'current'] |
| 12 | |
| 13 | const vvmrc_stop_paths = ['.git', '.hg', '.svn', '.v.mod.stop'] |
| 14 | |
| 15 | fn maybe_delegate_to_vvmrc(command string, prefs &pref.Preferences) { |
| 16 | if os.getenv(vvmrc_skip_env) != '' { |
| 17 | return |
| 18 | } |
| 19 | if !is_vvmrc_relevant_command(command, prefs) { |
| 20 | return |
| 21 | } |
| 22 | target_path := if prefs.path != '' { prefs.path } else { command } |
| 23 | vvmrc_path := find_project_vvmrc(target_path) |
| 24 | if vvmrc_path == '' { |
| 25 | return |
| 26 | } |
| 27 | requested_version := parse_vvmrc_version(os.read_file(vvmrc_path) or { '' }) |
| 28 | if requested_version == '' || is_vvmrc_latest_alias(requested_version) { |
| 29 | return |
| 30 | } |
| 31 | normalized_requested_version := normalize_vvmrc_version(requested_version) |
| 32 | if normalized_requested_version == normalize_vvmrc_version(version.v_version) { |
| 33 | return |
| 34 | } |
| 35 | vversion_exe := resolve_vversion_executable(normalized_requested_version) or { |
| 36 | eprintln('v: warning: `${vvmrc_path}` requests V `${requested_version}`, but no matching compiler executable was found. Continuing with V ${version.v_version}.') |
| 37 | return |
| 38 | } |
| 39 | this_vexe := os.real_path(pref.vexe_path()) |
| 40 | if os.real_path(vversion_exe) == this_vexe { |
| 41 | return |
| 42 | } |
| 43 | if prefs.is_verbose { |
| 44 | eprintln('v: `.vvmrc` selected V `${requested_version}` from `${vvmrc_path}` => ${vversion_exe}') |
| 45 | } |
| 46 | mut process := os.new_process(vversion_exe) |
| 47 | process.set_args(os.args[1..]) |
| 48 | mut envs := os.environ() |
| 49 | envs[vvmrc_skip_env] = '1' |
| 50 | envs['VEXE'] = vversion_exe |
| 51 | process.set_environment(envs) |
| 52 | process.wait() |
| 53 | exit_code := if process.code == -1 { 1 } else { process.code } |
| 54 | process.close() |
| 55 | exit(exit_code) |
| 56 | } |
| 57 | |
| 58 | fn is_vvmrc_relevant_command(command string, prefs &pref.Preferences) bool { |
| 59 | if prefs.path in ['', '-'] { |
| 60 | return false |
| 61 | } |
| 62 | if command in ['run', 'crun', 'build', 'build-module'] { |
| 63 | return true |
| 64 | } |
| 65 | return command.ends_with('.v') || os.exists(command) |
| 66 | } |
| 67 | |
| 68 | fn find_project_vvmrc(target_path string) string { |
| 69 | if target_path in ['', '-'] { |
| 70 | return '' |
| 71 | } |
| 72 | mut folder := if os.is_dir(target_path) { target_path } else { os.dir(target_path) } |
| 73 | if folder == '' { |
| 74 | folder = os.getwd() |
| 75 | } |
| 76 | mut current := os.real_path(folder) |
| 77 | for _ in 0 .. 256 { |
| 78 | vvmrc_path := os.join_path(current, vvmrc_file_name) |
| 79 | if os.is_file(vvmrc_path) { |
| 80 | return vvmrc_path |
| 81 | } |
| 82 | if has_vvmrc_stop_marker(current) { |
| 83 | break |
| 84 | } |
| 85 | parent := os.dir(current) |
| 86 | if parent in ['', current] { |
| 87 | break |
| 88 | } |
| 89 | current = parent |
| 90 | } |
| 91 | return '' |
| 92 | } |
| 93 | |
| 94 | fn has_vvmrc_stop_marker(folder string) bool { |
| 95 | for stop_path in vvmrc_stop_paths { |
| 96 | if os.exists(os.join_path(folder, stop_path)) { |
| 97 | return true |
| 98 | } |
| 99 | } |
| 100 | return false |
| 101 | } |
| 102 | |
| 103 | fn parse_vvmrc_version(content string) string { |
| 104 | for raw_line in content.split_into_lines() { |
| 105 | line := raw_line.all_before('#').trim_space() |
| 106 | if line != '' { |
| 107 | return line |
| 108 | } |
| 109 | } |
| 110 | return '' |
| 111 | } |
| 112 | |
| 113 | fn normalize_vvmrc_version(version_name string) string { |
| 114 | mut normalized := version_name.trim_space() |
| 115 | if normalized.len > 1 && normalized[0] in [`v`, `V`] && normalized[1].is_digit() { |
| 116 | normalized = normalized[1..] |
| 117 | } |
| 118 | return normalized |
| 119 | } |
| 120 | |
| 121 | fn is_vvmrc_latest_alias(version_name string) bool { |
| 122 | return normalize_vvmrc_version(version_name).to_lower_ascii() in vvmrc_latest_aliases |
| 123 | } |
| 124 | |
| 125 | fn resolve_vversion_executable(version_name string) !string { |
| 126 | raw_version := version_name.trim_space() |
| 127 | if raw_version == '' { |
| 128 | return error('empty version') |
| 129 | } |
| 130 | for candidate_name in versioned_v_executable_names(raw_version) { |
| 131 | if found_path := os.find_abs_path_of_executable(candidate_name) { |
| 132 | return found_path |
| 133 | } |
| 134 | } |
| 135 | for candidate_path in versioned_v_executable_paths(raw_version) { |
| 136 | if os.is_file(candidate_path) { |
| 137 | return candidate_path |
| 138 | } |
| 139 | } |
| 140 | return error('v executable for `${raw_version}` was not found') |
| 141 | } |
| 142 | |
| 143 | fn versioned_v_executable_names(version_name string) []string { |
| 144 | mut names := []string{} |
| 145 | raw_version := version_name.trim_space() |
| 146 | if raw_version == '' { |
| 147 | return names |
| 148 | } |
| 149 | names << raw_version |
| 150 | names << 'v${raw_version}' |
| 151 | if raw_version.starts_with('v') { |
| 152 | trimmed := raw_version[1..] |
| 153 | if trimmed != '' { |
| 154 | names << trimmed |
| 155 | } |
| 156 | } |
| 157 | return unique_strings(names) |
| 158 | } |
| 159 | |
| 160 | fn versioned_v_executable_paths(version_name string) []string { |
| 161 | mut paths := []string{} |
| 162 | normalized_version := normalize_vvmrc_version(version_name) |
| 163 | if normalized_version == '' { |
| 164 | return paths |
| 165 | } |
| 166 | paths << os.join_path('/usr/lib/v', normalized_version, 'bin', 'v') |
| 167 | paths << os.join_path('/usr/local/bin', 'v${normalized_version}') |
| 168 | for env_name in ['VVM_HOME', 'VVM_DIR'] { |
| 169 | vvm_root := os.getenv(env_name).trim_space() |
| 170 | if vvm_root == '' { |
| 171 | continue |
| 172 | } |
| 173 | paths << os.join_path(vvm_root, normalized_version, 'bin', 'v') |
| 174 | paths << os.join_path(vvm_root, 'versions', normalized_version, 'bin', 'v') |
| 175 | } |
| 176 | return unique_strings(paths) |
| 177 | } |
| 178 | |
| 179 | fn unique_strings(items []string) []string { |
| 180 | mut seen := map[string]bool{} |
| 181 | mut result := []string{} |
| 182 | for item in items { |
| 183 | if item in seen { |
| 184 | continue |
| 185 | } |
| 186 | seen[item] = true |
| 187 | result << item |
| 188 | } |
| 189 | return result |
| 190 | } |
| 191 | |