/
parse.v
212 lines (204 loc) · 6.22 KB
/
parse.v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
module main
import os
import net.http
import v.vmod
struct Module {
mut:
name string
url string
version string // specifies the requested version.
install_path string
install_path_fmted string
installed_version string
is_installed bool
is_external bool
vcs ?VCS
manifest vmod.Manifest
}
struct Parser {
mut:
modules map[string]Module
checked_settings_vcs bool
is_git_setting bool
errors int
}
fn parse_query(query []string) []Module {
mut p := Parser{
is_git_setting: settings.vcs == .git
}
for m in query {
p.parse_module(m)
}
if p.errors > 0 && p.errors == query.len {
exit(1)
}
return p.modules.values()
}
fn (mut p Parser) parse_module(m string) {
if m in p.modules {
return
}
ident, version := m.rsplit_once('@') or { m, '' }
println('Scanning `${ident}`...')
is_http := if ident.starts_with('http://') {
vpm_warn('installing `${ident}` via http.',
details: 'Support for `http` is deprecated, use `https` to ensure future compatibility.'
)
true
} else {
false
}
mut mod := if is_http || ident.starts_with('https://') {
// External module. The identifier is an URL.
publisher, name := get_ident_from_url(ident) or {
vpm_error(err.msg())
p.errors++
return
}
// Verify VCS. Only needed once for external modules.
if !p.checked_settings_vcs {
p.checked_settings_vcs = true
settings.vcs.is_executable() or {
vpm_error(err.msg())
exit(1)
}
}
// Fetch manifest.
manifest := fetch_manifest(name, ident, version, p.is_git_setting) or {
vpm_error('failed to find `v.mod` for `${ident}${at_version(version)}`.',
details: err.msg()
)
p.errors++
return
}
// Resolve path.
mod_path := normalize_mod_path(os.join_path(if is_http { publisher } else { '' },
manifest.name))
Module{
name: manifest.name
url: ident
install_path: os.real_path(os.join_path(settings.vmodules_path, mod_path))
is_external: true
manifest: manifest
}
} else {
// VPM registered module.
info := get_mod_vpm_info(ident) or {
vpm_error('failed to retrieve metadata for `${ident}`.', details: err.msg())
p.errors++
return
}
// Verify VCS.
mut is_git_module := true
vcs := if info.vcs != '' {
info_vcs := vcs_from_str(info.vcs) or {
vpm_error('skipping `${info.name}`, since it uses an unsupported version control system `${info.vcs}`.')
p.errors++
return
}
is_git_module = info_vcs == .git
if !is_git_module && version != '' {
vpm_error('skipping `${info.name}`, version installs are currently only supported for projects using `git`.')
p.errors++
return
}
info_vcs
} else {
VCS.git
}
vcs.is_executable() or {
vpm_error(err.msg())
p.errors++
return
}
// Fetch manifest.
manifest := fetch_manifest(info.name, info.url, version, is_git_module) or {
// Add link with issue template requesting to add a manifest.
mut details := ''
if resp := http.head('${info.url}/issues/new') {
if resp.status_code == 200 {
issue_tmpl_url := '${info.url}/issues/new?title=Missing%20Manifest&body=${info.name}%20is%20missing%20a%20manifest,%20please%20consider%20adding%20a%20v.mod%20file%20with%20the%20modules%20metadata.'
details = 'Help to ensure future-compatibility by adding a `v.mod` file or opening an issue at:\n`${issue_tmpl_url}`'
}
}
vpm_warn('`${info.name}` is missing a manifest file.', details: details)
vpm_log(@FILE_LINE, @FN, 'vpm manifest detection error: ${err}')
vmod.Manifest{}
}
// Resolve path.
mod_path := normalize_mod_path(info.name.replace('.', os.path_separator))
Module{
name: info.name
url: info.url
vcs: vcs
install_path: os.real_path(os.join_path(settings.vmodules_path, mod_path))
manifest: manifest
}
}
mod.install_path_fmted = fmt_mod_path(mod.install_path)
mod.version = version
mod.get_installed()
p.modules[m] = mod
if mod.manifest.dependencies.len > 0 {
verbose_println('Found ${mod.manifest.dependencies.len} dependencies for `${mod.name}`: ${mod.manifest.dependencies}.')
for d in mod.manifest.dependencies {
p.parse_module(d)
}
}
}
fn (mut m Module) get_installed() {
refs := os.execute_opt('git ls-remote --refs ${m.install_path}') or { return }
vpm_log(@FILE_LINE, @FN, 'refs: ${refs}')
m.is_installed = true
// In case the head just temporarily matches a tag, make sure that there
// really is a version installation before adding it as `installed_version`.
// NOTE: can be refined for branch installations. E.g., for `sdl`.
if refs.output.contains('refs/tags/') {
tag := refs.output.all_after_last('refs/tags/').all_before('\n').trim_space()
head := if refs.output.contains('refs/heads/') {
refs.output.all_after_last('refs/heads/').all_before('\n').trim_space()
} else {
tag
}
vpm_log(@FILE_LINE, @FN, 'head: ${head}, tag: ${tag}')
if tag == head {
m.installed_version = tag
}
}
}
fn fetch_manifest(name string, url string, version string, is_git bool) !vmod.Manifest {
if !is_git {
manifest_url := '${url}/raw-file/tip/v.mod'
vpm_log(@FILE_LINE, @FN, 'manifest_url: ${manifest_url}')
return get_manifest_from_resp(http.get(manifest_url) or {
return error('failed to retrieve manifest. ${err}')
})
}
v := if version != '' {
version
} else {
head_branch := os.execute_opt('git ls-remote --symref ${url} HEAD') or {
return error('failed to find git HEAD. ${err}')
}
head_branch.output.all_after_last('/').all_before(' ').all_before('\t')
}
url_ := url.trim_string_right('.git')
// Scan both URLS. E.g.:
// https://github.com/publisher/module/raw/v0.7.0/v.mod
// https://gitlab.com/publisher/module/-/raw/main/v.mod
raw_paths := ['raw/', '/-/raw/']
for i, raw_p in raw_paths {
manifest_url := '${url_}/${raw_p}/${v}/v.mod'
vpm_log(@FILE_LINE, @FN, 'manifest_url ${i}: ${manifest_url}')
return get_manifest_from_resp(http.get(manifest_url) or { continue })
}
return error('failed to retrieve manifest.')
}
fn get_manifest_from_resp(resp http.Response) !vmod.Manifest {
if resp.status_code != 200 {
return error('unsuccessful response status `${resp.status_code}`.')
}
return vmod.decode(resp.body) or {
return error('failed to decode manifest `${resp.body}`. ${err}')
}
}