From 50f3ac49689a8ad9d3c9908fa2335308b317d06b Mon Sep 17 00:00:00 2001 From: Turiiya <34311583+ttytm@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:10:51 +0100 Subject: [PATCH] tools.vpm: extend recursive dependency fix for module installation (#20015) --- cmd/tools/vpm/dependency_test.v | 9 ++ cmd/tools/vpm/parse.v | 250 ++++++++++++++++---------------- 2 files changed, 136 insertions(+), 123 deletions(-) diff --git a/cmd/tools/vpm/dependency_test.v b/cmd/tools/vpm/dependency_test.v index 5fe46a2607..7b05002538 100644 --- a/cmd/tools/vpm/dependency_test.v +++ b/cmd/tools/vpm/dependency_test.v @@ -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') @@ -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') +} diff --git a/cmd/tools/vpm/parse.v b/cmd/tools/vpm/parse.v index a82a5815f6..d8a8ca6ebd 100644 --- a/cmd/tools/vpm/parse.v +++ b/cmd/tools/vpm/parse.v @@ -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' - 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 - } - 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()) - errors++ - continue - } - // 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 - } - // 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()) - 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 - } - } - 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 - } - } - } + mut p := Parser{ + is_git_setting: settings.vcs.cmd == 'git' } - if errors > 0 && errors == query.len { + for m in query { + p.parse_module(m) + } + if p.errors > 0 && p.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) + 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 + } + // 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 := supported_vcs[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 { + supported_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) + } } - return modules } // TODO: add unit test