Skip to content

Commit

Permalink
tools.vpm: fix installing of modules with conflicting names, extend t…
Browse files Browse the repository at this point in the history
…ests (#19961)
  • Loading branch information
ttytm authored Nov 23, 2023
1 parent fb93828 commit 29eda89
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 143 deletions.
129 changes: 80 additions & 49 deletions cmd/tools/vpm/common.v
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mut:
is_installed bool
is_external bool
vcs ?VCS
manifest vmod.Manifest
}

struct ModuleVpmInfo {
Expand All @@ -43,7 +44,10 @@ struct ErrorOptions {
verbose bool // is used to only output the error message if the verbose setting is enabled.
}

const home_dir = os.home_dir()
const (
vexe = os.quoted_path(os.getenv('VEXE'))
home_dir = os.home_dir()
)

fn parse_query(query []string) ([]Module, []Module) {
mut vpm_modules, mut external_modules := []Module{}, []Module{}
Expand All @@ -61,26 +65,30 @@ fn parse_query(query []string) ([]Module, []Module) {
false
}
mut mod := if is_http || ident.starts_with('https://') {
// External module.
// External module. The idenifier is an URL.
publisher, name := get_ident_from_url(ident) or {
vpm_error(err.msg())
errors++
continue
}
// Resolve path, verify existence of manifest.
base := if is_http { publisher } else { '' }
install_path := normalize_mod_path(os.real_path(os.join_path(settings.vmodules_path,
base, name)))
if is_git_setting && !has_vmod(ident, install_path) {
vpm_error('failed to find `v.mod` for `${ident}`.')
// 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.
base := if is_http { publisher } else { '' }
install_path := normalize_mod_path(os.real_path(os.join_path(settings.vmodules_path,
base, manifest.name)))
Module{
name: name
name: manifest.name
url: ident
install_path: install_path
is_external: true
manifest: manifest
}
} else {
// VPM registered module.
Expand Down Expand Up @@ -112,11 +120,9 @@ fn parse_query(query []string) ([]Module, []Module) {
errors++
continue
}
// Resolve path, verify existence of manifest.
ident_as_path := info.name.replace('.', os.path_separator)
install_path := normalize_mod_path(os.real_path(os.join_path(settings.vmodules_path,
ident_as_path)))
if is_git_module && !has_vmod(info.url, install_path) {
// 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 {
Expand All @@ -125,34 +131,23 @@ fn parse_query(query []string) ([]Module, []Module) {
}
}
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.
ident_as_path := info.name.replace('.', os.path_separator)
install_path := normalize_mod_path(os.real_path(os.join_path(settings.vmodules_path,
ident_as_path)))
Module{
name: info.name
url: info.url
vcs: vcs
install_path: install_path
manifest: manifest
}
}
mod.version = version
mod.install_path_fmted = fmt_mod_path(mod.install_path)
if refs := os.execute_opt('git ls-remote --refs ${mod.install_path}') {
mod.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/').trim_space()
head := if refs.output.contains('refs/heads/') {
refs.output.all_after_last('refs/heads/').trim_space()
} else {
tag
}
vpm_log(@FILE_LINE, @FN, 'head: ${head}, tag: ${tag}')
if tag == head {
mod.installed_version = tag
}
}
}
mod.get_installed()
if mod.is_external {
external_modules << mod
} else {
Expand All @@ -171,23 +166,59 @@ fn parse_query(query []string) ([]Module, []Module) {
return vpm_modules, external_modules
}

fn has_vmod(url string, install_path string) bool {
if install_path != '' && os.exists((os.join_path(install_path, 'v.mod'))) {
// Skip fetching the repo when the module is already installed and has a `v.mod`.
return true
}
head_branch := os.execute_opt('git ls-remote --symref ${url} HEAD') or {
vpm_error('failed to find git HEAD for `${url}`.', details: err.msg())
return false
}.output.all_after_last('/').all_before(' ').all_before('\t')
// TODO: add unit test
fn (mut m Module) get_installed() {
refs := os.execute_opt('git ls-remote --refs ${m.install_path}') or { return }
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 {
// TODO: fetch manifest for mercurial repositories
return vmod.Manifest{
name: name
}
}
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_ := if url.ends_with('.git') { url.replace('.git', '') } else { url }
manifest_url := '${url_}/blob/${head_branch}/v.mod'
vpm_log(@FILE_LINE, @FN, 'manifest_url: ${manifest_url}')
has_vmod := http.head(manifest_url) or {
vpm_error('failed to retrieve module data for `${url}`.')
return false
}.status_code == 200
return has_vmod
// 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}')
raw_manifest_resp := http.get(manifest_url) or { continue }
if raw_manifest_resp.status_code != 200 {
return error('unsuccessful response status `${raw_manifest_resp.status_code}`.')
}
return vmod.decode(raw_manifest_resp.body) or {
return error('failed to decode manifest `${raw_manifest_resp.body}`. ${err}')
}
}
return error('failed to retrieve manifest.')
}

fn get_mod_date_info(mut pp pool.PoolProcessor, idx int, wid int) &ModuleDateInfo {
Expand Down
58 changes: 3 additions & 55 deletions cmd/tools/vpm/install.v
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ fn vpm_install_from_vpm(modules []Module) {
mut errors := 0
for m in modules {
vpm_log(@FILE_LINE, @FN, 'module: ${m}')
last_errors := errors
match m.install() {
.installed {}
.failed {
Expand All @@ -107,9 +106,7 @@ fn vpm_install_from_vpm(modules []Module) {
vpm_error('failed to increment the download count for `${m.name}`', details: err.msg())
errors++
}
if last_errors == errors {
println('Installed `${m.name}`.')
}
println('Installed `${m.name}`.')
resolve_dependencies(get_manifest(m.install_path), idents)
}
if errors > 0 {
Expand All @@ -123,7 +120,6 @@ fn vpm_install_from_vcs(modules []Module) {
mut errors := 0
for m in modules {
vpm_log(@FILE_LINE, @FN, 'module: ${m}')
last_errors := errors
match m.install() {
.installed {}
.failed {
Expand All @@ -134,56 +130,8 @@ fn vpm_install_from_vcs(modules []Module) {
continue
}
}
manifest := get_manifest(m.install_path) or { continue }
final_path := os.join_path(m.install_path.all_before_last(os.path_separator),
normalize_mod_path(manifest.name))
if m.install_path != final_path {
verbose_println('Relocating `${m.name} (${m.install_path_fmted})` to `${manifest.name} (${final_path})`...')
if os.exists(final_path) {
println('Target directory for `${m.name} (${final_path})` already exists.')
input := os.input('Replace it with the module directory? [Y/n]: ')
match input.to_lower() {
'', 'y' {
m.remove() or {
vpm_error('failed to remove `${final_path}`.', details: err.msg())
errors++
continue
}
}
else {
verbose_println('Skipping `${m.name}`.')
continue
}
}
}
// When the module should be relocated into a subdirectory we need to make sure
// it exists to not run into permission errors.
if m.install_path.count(os.path_separator) < final_path.count(os.path_separator)
&& !os.exists(final_path) {
os.mkdir_all(final_path) or {
vpm_error('failed to create directory for `${manifest.name}`.',
details: err.msg()
)
errors++
continue
}
}
os.mv(m.install_path, final_path) or {
errors++
vpm_error('failed to relocate module `${m.name}`.', details: err.msg())
os.rmdir_all(m.install_path) or {
vpm_error('failed to remove `${m.install_path}`.', details: err.msg())
errors++
continue
}
continue
}
verbose_println('Relocated `${m.name}` to `${manifest.name}`.')
}
if last_errors == errors {
println('Installed `${m.name}`.')
}
resolve_dependencies(manifest, urls)
println('Installed `${m.name}`.')
resolve_dependencies(m.manifest, urls)
}
if errors > 0 {
exit(1)
Expand Down
Loading

0 comments on commit 29eda89

Please sign in to comment.