v2 / cmd / v / vvm.v
190 lines · 174 sloc · 4.91 KB · 07ee934f822c7a2980e0dc31ca635a6c734dcbca
Raw
1module main
2
3import os
4import v.pref
5import v.util.version
6
7const vvmrc_file_name = '.vvmrc'
8
9const vvmrc_skip_env = 'V_SKIP_VVMRC'
10
11const vvmrc_latest_aliases = ['latest', 'current']
12
13const vvmrc_stop_paths = ['.git', '.hg', '.svn', '.v.mod.stop']
14
15fn 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
58fn 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
68fn 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
94fn 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
103fn 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
113fn 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
121fn is_vvmrc_latest_alias(version_name string) bool {
122 return normalize_vvmrc_version(version_name).to_lower_ascii() in vvmrc_latest_aliases
123}
124
125fn 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
143fn 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
160fn 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
179fn 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