Skip to content

Commit

Permalink
tools.vpm: extend recursive dependency fix for module installation (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ttytm committed Nov 28, 2023
1 parent 920be09 commit 50f3ac4
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 114 deletions.
9 changes: 9 additions & 0 deletions cmd/tools/vpm/dependency_test.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// vtest retry: 3
import os
import v.vmod
import time

const v = os.quoted_path(@VEXE)
const test_path = os.join_path(os.vtmp_dir(), 'vpm_dependency_test')
Expand Down Expand Up @@ -72,3 +73,11 @@ fn test_resolve_external_dependencies_during_module_install() {
assert get_mod_name(os.join_path(test_path, 'webview', 'v.mod')) == 'webview'
assert get_mod_name(os.join_path(test_path, 'miniaudio', 'v.mod')) == 'miniaudio'
}

fn test_install_with_recursive_dependencies() {
spawn fn () {
time.sleep(2 * time.minute)
exit(1)
}()
os.execute_or_exit('${v} install https://gitlab.com/tobealive/a')
}
232 changes: 118 additions & 114 deletions cmd/tools/vpm/parse.v
Original file line number Diff line number Diff line change
Expand Up @@ -18,134 +18,138 @@ mut:
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 modules := []Module{}
mut dependencies := map[string]bool{}
mut checked_settings_vcs := false
mut errors := 0
is_git_setting := settings.vcs.cmd == 'git'
mut p := Parser{
is_git_setting: settings.vcs.cmd == 'git'
}
for m in query {
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
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 idenifier is an URL.
publisher, name := get_ident_from_url(ident) or {
vpm_error(err.msg())
p.errors++
return
}
mut mod := if is_http || ident.starts_with('https://') {
// External module. The idenifier is an URL.
publisher, name := get_ident_from_url(ident) or {
// 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())
errors++
continue
exit(1)
}
// Verify VCS. Only needed once for external modules.
if !checked_settings_vcs {
checked_settings_vcs = true
settings.vcs.is_executable() or {
vpm_error(err.msg())
exit(1)
}
}
// Fetch manifest.
manifest := fetch_manifest(name, ident, version, is_git_setting) or {
vpm_error('failed to find `v.mod` for `${ident}${at_version(version)}`.',
details: err.msg()
)
errors++
continue
}
// 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 := supported_vcs[info.vcs] or {
vpm_error('skipping `${info.name}`, since it uses an unsupported version control system `${info.vcs}`.')
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
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 {
// VPM registered module.
info := get_mod_vpm_info(ident) or {
vpm_error('failed to retrieve metadata for `${ident}`.', details: err.msg())
errors++
continue
}
// Verify VCS.
mut is_git_module := true
vcs := if info.vcs != '' {
info_vcs := supported_vcs[info.vcs] or {
vpm_error('skipping `${info.name}`, since it uses an unsupported version control system `${info.vcs}`.')
errors++
continue
}
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`.')
errors++
continue
}
info_vcs
} else {
supported_vcs['git']
}
vcs.is_executable() or {
vpm_error(err.msg())
errors++
continue
}
// 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
}
supported_vcs['git']
}
vcs.is_executable() or {
vpm_error(err.msg())
p.errors++
return
}
mod.install_path_fmted = fmt_mod_path(mod.install_path)
mod.version = version
mod.get_installed()
modules << 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 {
if !dependencies[d] {
dependencies[d] = true
// 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
}
}
if errors > 0 && errors == query.len {
exit(1)
}
if dependencies.len > 0 {
vpm_log(@FILE_LINE, @FN, 'dependencies: ${dependencies}')
deps := dependencies.keys().filter(it !in query)
vpm_log(@FILE_LINE, @FN, 'dependencies filtered: ${deps}')
println('Scanning dependencies...')
modules << parse_query(deps)
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)
}
}
return modules
}

// TODO: add unit test
Expand Down

0 comments on commit 50f3ac4

Please sign in to comment.