mirror of
https://github.com/vlang/v.git
synced 2025-09-13 14:32:26 +03:00
222 lines
6.2 KiB
V
222 lines
6.2 KiB
V
module main
|
|
|
|
import os
|
|
import net.http
|
|
import v.vmod
|
|
|
|
struct Module {
|
|
mut:
|
|
name string
|
|
url string
|
|
version string // specifies the requested version.
|
|
tmp_path string
|
|
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
|
|
errors int
|
|
}
|
|
|
|
enum ModuleKind {
|
|
registered
|
|
https
|
|
http
|
|
ssh
|
|
}
|
|
|
|
fn parse_query(query []string) []Module {
|
|
mut p := Parser{}
|
|
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) {
|
|
kind := match true {
|
|
m.starts_with('https://') { ModuleKind.https }
|
|
m.starts_with('git@') { ModuleKind.ssh }
|
|
m.starts_with('http://') { ModuleKind.http }
|
|
else { ModuleKind.registered }
|
|
}
|
|
ident, version := if kind == .ssh {
|
|
if m.count('@') > 1 {
|
|
m.all_before_last('@'), m.all_after_last('@')
|
|
} else {
|
|
m, ''
|
|
}
|
|
} else {
|
|
m.rsplit_once('@') or { m, '' }
|
|
}
|
|
key := match kind {
|
|
.registered { m }
|
|
.ssh { ident.replace(':', '/') + at_version(version) }
|
|
else { ident.all_after('//').trim_string_right('.git') + at_version(version) }
|
|
}
|
|
if key in p.modules {
|
|
return
|
|
}
|
|
println('Scanning `${m}`...')
|
|
mut mod := if kind != ModuleKind.registered {
|
|
// External module. The identifier is an URL.
|
|
if kind == .http {
|
|
vpm_warn('installing `${ident}` via http.',
|
|
details: 'Support for `http` is deprecated, use `https` to ensure future compatibility.'
|
|
)
|
|
}
|
|
publisher, name := get_ident_from_url(if kind == .ssh {
|
|
'https://' + ident['git@'.len..].replace(':', '/')
|
|
} else {
|
|
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)
|
|
}
|
|
}
|
|
tmp_path := get_tmp_path(os.join_path(publisher, name, version)) or {
|
|
vpm_error('failed to get temporary directory for `${ident}`.', details: err.msg())
|
|
p.errors++
|
|
return
|
|
}
|
|
settings.vcs.clone(ident, version, tmp_path) or {
|
|
vpm_error('failed to install `${ident}`.', details: err.msg())
|
|
p.errors++
|
|
return
|
|
}
|
|
manifest := get_manifest(tmp_path) or {
|
|
vpm_error('failed to find `v.mod` for `${ident}${at_version(version)}`.',
|
|
details: err.msg()
|
|
)
|
|
rmdir_all(tmp_path) or {}
|
|
p.errors++
|
|
return
|
|
}
|
|
mod_path := normalize_mod_path(os.join_path(if kind == .http { publisher } else { '' },
|
|
manifest.name))
|
|
Module{
|
|
name: manifest.name
|
|
url: ident
|
|
version: version
|
|
install_path: os.real_path(os.join_path(settings.vmodules_path, mod_path))
|
|
is_external: true
|
|
tmp_path: tmp_path
|
|
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.
|
|
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
|
|
}
|
|
info_vcs
|
|
} else {
|
|
VCS.git
|
|
}
|
|
vcs.is_executable() or {
|
|
vpm_error(err.msg())
|
|
p.errors++
|
|
return
|
|
}
|
|
mod_path := normalize_mod_path(info.name.replace('.', os.path_separator))
|
|
tmp_path := get_tmp_path(os.join_path(mod_path, version)) or {
|
|
vpm_error('failed to get temporary directory for `${ident}`.', details: err.msg())
|
|
p.errors++
|
|
return
|
|
}
|
|
vcs.clone(info.url, version, tmp_path) or {
|
|
vpm_error('failed to install `${ident}`.', details: err.msg())
|
|
p.errors++
|
|
return
|
|
}
|
|
manifest := get_manifest(tmp_path) 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 = 'Please help us 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{}
|
|
}
|
|
Module{
|
|
name: info.name
|
|
url: info.url
|
|
version: version
|
|
vcs: vcs
|
|
install_path: os.real_path(os.join_path(settings.vmodules_path, mod_path))
|
|
tmp_path: tmp_path
|
|
manifest: manifest
|
|
}
|
|
}
|
|
mod.install_path_fmted = fmt_mod_path(mod.install_path)
|
|
mod.get_installed()
|
|
p.modules[key] = 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 get_tmp_path(relative_path string) !string {
|
|
tmp_path := os.real_path(os.join_path(settings.tmp_path, relative_path))
|
|
if os.exists(tmp_path) {
|
|
// It's unlikely that the tmp_path already exists, but it might
|
|
// occur if vpm was canceled during an installation or update.
|
|
rmdir_all(tmp_path)!
|
|
}
|
|
return tmp_path
|
|
}
|