diff --git a/cmd/tools/vpm/common.v b/cmd/tools/vpm/common.v index 00ec8154ea0161..b84f94e92a699a 100644 --- a/cmd/tools/vpm/common.v +++ b/cmd/tools/vpm/common.v @@ -1,21 +1,21 @@ module main -import json import os -import v.vmod -import sync.pool import net.http import net.urllib +import sync.pool +import v.vmod +import json import term import log struct Module { mut: - // Fields that can be populated via VPM API. + // Fields determined by the url or the info received from the VPM API. name string url string vcs string - // Fields based on input / environment. + // Fields based on preference / environment. version string // specifies the requested version. install_path string is_installed bool @@ -44,7 +44,7 @@ struct ErrorOptions { } fn parse_query(query []string) ([]Module, []Module) { - mut vpm_modules, mut extended_modules := []Module{}, []Module{} + mut vpm_modules, mut external_modules := []Module{}, []Module{} mut errors := 0 for m in query { ident, version := m.rsplit_once('@') or { m, '' } @@ -86,7 +86,7 @@ fn parse_query(query []string) ([]Module, []Module) { mod.installed_version = v.output.all_after_last('/').trim_space() } if is_external { - extended_modules << mod + external_modules << mod } else { vpm_modules << mod } @@ -94,7 +94,7 @@ fn parse_query(query []string) ([]Module, []Module) { if errors > 0 && errors == query.len { exit(1) } - return vpm_modules, extended_modules + return vpm_modules, external_modules } fn has_vmod(url string) bool { @@ -120,12 +120,12 @@ fn get_mod_date_info(mut pp pool.PoolProcessor, idx int, wid int) &ModuleDateInf mut result := &ModuleDateInfo{ name: pp.get_item[string](idx) } - final_module_path := get_path_of_existing_module(result.name) or { return result } - vcs := vcs_used_in_dir(final_module_path) or { return result } + path := get_path_of_existing_module(result.name) or { return result } + vcs := vcs_used_in_dir(path) or { return result } is_hg := vcs.cmd == 'hg' mut outputs := []string{} for step in vcs.args.outdated { - cmd := '${vcs.cmd} ${vcs.args.path} "${final_module_path}" ${step}' + cmd := '${vcs.cmd} ${vcs.args.path} "${path}" ${step}' res := os.execute(cmd) if res.exit_code < 0 { verbose_println('Error command: ${cmd}') @@ -151,8 +151,8 @@ fn get_mod_vpm_info(name string) !ModuleVpmInfo { return error('invalid module name `${name}`.') } mut errors := []string{} - for server_url in vpm_server_urls { - modurl := server_url + '/api/packages/${name}' + for url in vpm_server_urls { + modurl := url + '/api/packages/${name}' verbose_println('Retrieving metadata for `${name}` from `${modurl}`...') r := http.get(modurl) or { errors << 'Http server did not respond to our request for `${modurl}`.' @@ -160,11 +160,11 @@ fn get_mod_vpm_info(name string) !ModuleVpmInfo { continue } if r.status_code == 404 || r.body.trim_space() == '404' { - errors << 'Skipping module `${name}`, since `${server_url}` reported that `${name}` does not exist.' + errors << 'Skipping module `${name}`, since `${url}` reported that `${name}` does not exist.' continue } if r.status_code != 200 { - errors << 'Skipping module `${name}`, since `${server_url}` responded with ${r.status_code} http status code. Please try again later.' + errors << 'Skipping module `${name}`, since `${url}` responded with ${r.status_code} http status code. Please try again later.' continue } s := r.body @@ -198,10 +198,10 @@ fn get_name_from_url(raw_url string) !string { } fn get_outdated() ![]string { - module_names := get_installed_modules() + installed := get_installed_modules() mut outdated := []string{} mut pp := pool.new_pool_processor(callback: get_mod_date_info) - pp.work_on_items(module_names) + pp.work_on_items(installed) for res in pp.get_results[ModuleDateInfo]() { if res.exec_err { return error('failed to check the latest commits for `${res.name}`.') @@ -216,9 +216,7 @@ fn get_outdated() ![]string { fn get_all_modules() []string { url := get_working_server_url() r := http.get(url) or { - vpm_error(err.msg(), - verbose: true - ) + vpm_error(err.msg(), verbose: true) exit(1) } if r.status_code != 200 { @@ -282,6 +280,24 @@ fn get_installed_modules() []string { return modules } +fn get_path_of_existing_module(mod_name string) ?string { + name := get_name_from_url(mod_name) or { mod_name.replace('-', '_').to_lower() } + path := os.real_path(os.join_path(settings.vmodules_path, name.replace('.', os.path_separator))) + if !os.exists(path) { + vpm_error('failed to find `${name}` at `${path}`.') + return none + } + if !os.is_dir(path) { + vpm_error('skipping `${path}`, since it is not a directory.') + return none + } + vcs_used_in_dir(path) or { + vpm_error('skipping `${path}`, since it uses an unsupported version control system.') + return none + } + return path +} + fn get_working_server_url() string { server_urls := if settings.server_urls.len > 0 { settings.server_urls @@ -291,27 +307,26 @@ fn get_working_server_url() string { for url in server_urls { verbose_println('Trying server url: ${url}') http.head(url) or { - verbose_println(' ${url} failed.') + vpm_error('failed to connect to server url `${url}`.', details: err.msg()) continue } return url } - panic('No responding vpm server found. Please check your network connectivity and try again later.') + vpm_error('No responding vpm server found. Please check your network connectivity and try again later.') + exit(1) } fn ensure_vmodules_dir_exist() { if !os.is_dir(settings.vmodules_path) { - println('Creating `${settings.vmodules_path}` ...') + println('Creating `${settings.vmodules_path}`...') os.mkdir(settings.vmodules_path) or { - vpm_error(err.msg(), - verbose: true - ) + vpm_error(err.msg(), verbose: true) exit(1) } } } -fn ensure_vcs_is_installed(vcs &VCS) ! { +fn (vcs &VCS) is_executable() ! { os.find_abs_path_of_executable(vcs.cmd) or { return error('VPM needs `${vcs.cmd}` to be installed.') } @@ -323,16 +338,15 @@ fn increment_module_download_count(name string) ! { return } mut errors := []string{} - - for server_url in vpm_server_urls { - modurl := server_url + '/api/packages/${name}/incr_downloads' + for url in vpm_server_urls { + modurl := url + '/api/packages/${name}/incr_downloads' r := http.post(modurl, '') or { errors << 'Http server did not respond to our request for `${modurl}`.' errors << 'Error details: ${err}' continue } if r.status_code != 200 { - errors << 'Failed to increment the download count for module `${name}`, since `${server_url}` responded with ${r.status_code} http status code. Please try again later.' + errors << 'Failed to increment the download count for module `${name}`, since `${url}` responded with ${r.status_code} http status code. Please try again later.' continue } return @@ -340,22 +354,21 @@ fn increment_module_download_count(name string) ! { return error(errors.join_lines()) } -fn resolve_dependencies(name string, module_path string, module_names []string) { - vmod_path := os.join_path(module_path, 'v.mod') - if !os.exists(vmod_path) { - return - } - manifest := vmod.from_file(vmod_path) or { - vpm_error(err.msg(), - verbose: true - ) - return +fn get_manifest(path string) ?vmod.Manifest { + return vmod.from_file(os.join_path(path, 'v.mod')) or { + eprintln(term.ecolorize(term.yellow, 'warning: ') + + 'failed to find v.mod file for `${path.all_after_last(os.path_separator)}`.') + return none } +} + +fn resolve_dependencies(manifest ?vmod.Manifest, modules []string) { + mod := manifest or { return } // Filter out modules that are both contained in the input query and listed as // dependencies in the mod file of the module that is supposed to be installed. - deps := manifest.dependencies.filter(it !in module_names) + deps := mod.dependencies.filter(it !in modules) if deps.len > 0 { - println('Resolving ${deps.len} dependencies for module `${name}` ...') + println('Resolving ${deps.len} dependencies for module `${mod.name}`...') verbose_println('Found dependencies: ${deps}') vpm_install(deps) } @@ -370,27 +383,9 @@ fn vcs_used_in_dir(dir string) ?VCS { return none } -fn get_path_of_existing_module(mod_name string) ?string { - name := get_name_from_url(mod_name) or { mod_name.replace('-', '_').to_lower() } - path := os.real_path(os.join_path(settings.vmodules_path, name.replace('.', os.path_separator))) - if !os.exists(path) { - vpm_error('failed to find `${name}` at `${path}`.') - return none - } - if !os.is_dir(path) { - vpm_error('skipping `${path}`, since it is not a directory.') - return none - } - vcs_used_in_dir(path) or { - vpm_error('skipping `${path}`, since it uses an unsupported version control system.') - return none - } - return path -} - -fn verbose_println(s string) { +fn verbose_println(msg string) { if settings.is_verbose { - println(s) + println(msg) } } diff --git a/cmd/tools/vpm/install.v b/cmd/tools/vpm/install.v index fce679bb9e6656..0078e1fe9026cc 100644 --- a/cmd/tools/vpm/install.v +++ b/cmd/tools/vpm/install.v @@ -4,14 +4,12 @@ import os import v.vmod import v.help -fn vpm_install(requested_modules []string) { - vpm_log(@FILE_LINE, @FN, 'requested_modules: ${requested_modules}') - +fn vpm_install(query []string) { if settings.is_help { help.print_and_exit('vpm') } - mut vpm_modules, mut external_modules := parse_query(if requested_modules.len == 0 { + mut vpm_modules, mut external_modules := parse_query(if query.len == 0 { if os.exists('./v.mod') { // Case: `v install` was run in a directory of another V-module to install its dependencies // - without additional module arguments. @@ -29,7 +27,7 @@ fn vpm_install(requested_modules []string) { exit(2) } } else { - requested_modules + query }) installed_modules := get_installed_modules() @@ -66,7 +64,7 @@ fn vpm_install(requested_modules []string) { } if already_installed.len > 0 { verbose_println('Already installed modules: ${already_installed}') - if already_installed.len == requested_modules.len { + if already_installed.len == query.len { println('All modules are already installed.') exit(0) } @@ -81,22 +79,13 @@ fn vpm_install(requested_modules []string) { } } -fn install_module(vcs &VCS, name string, url string, final_module_path string) ! { - cmd := '${vcs.cmd} ${vcs.args.install} "${url}" "${final_module_path}"' - vpm_log(@FILE_LINE, @FN, 'command: ${cmd}') - println('Installing module `${name}` from `${url}` to `${final_module_path}` ...') - os.execute_opt(cmd) or { - vpm_log(@FILE_LINE, @FN, 'cmd output: ${err}') - return error('failed to install module `${name}` to `${final_module_path}`.') - } -} - fn vpm_install_from_vpm(modules []Module) { vpm_log(@FILE_LINE, @FN, 'modules: ${modules}') - names := modules.map(it.name) + idents := modules.map(it.name) mut errors := 0 for m in modules { vpm_log(@FILE_LINE, @FN, 'module: ${m}') + last_errors := errors vcs := if m.vcs != '' { supported_vcs[m.vcs] or { errors++ @@ -106,7 +95,7 @@ fn vpm_install_from_vpm(modules []Module) { } else { supported_vcs['git'] } - ensure_vcs_is_installed(vcs) or { + vcs.is_executable() or { vpm_error(err.msg()) errors++ continue @@ -115,7 +104,7 @@ fn vpm_install_from_vpm(modules []Module) { vpm_update([m.name]) continue } - install_module(vcs, m.name, m.url, m.install_path) or { + m.install(vcs) or { errors++ vpm_error(err.msg()) continue @@ -124,7 +113,10 @@ fn vpm_install_from_vpm(modules []Module) { errors++ vpm_error('failed to increment the download count for `${m.name}`', details: err.msg()) } - resolve_dependencies(m.name, m.install_path, names) + if last_errors == errors { + println('Installed `${m.name}`.') + } + resolve_dependencies(get_manifest(m.install_path), idents) } if errors > 0 { exit(1) @@ -137,27 +129,23 @@ fn vpm_install_from_vcs(modules []Module) { urls := modules.map(it.url) for m in modules { vpm_log(@FILE_LINE, @FN, 'module: ${m}') + last_errors := errors if os.exists(m.install_path) { vpm_update([m.name]) continue } - ensure_vcs_is_installed(vcs) or { + vcs.is_executable() or { vpm_error(err.msg()) errors++ continue } - install_module(vcs, m.name, m.url, m.install_path) or { + m.install(vcs) or { errors++ vpm_error(err.msg()) continue } - manifest := vmod.from_file(os.join_path(m.install_path, 'v.mod')) or { - vpm_error('Module `${m.name}` is lacking a v.mod file.', - details: err.msg() - verbose: true - ) - continue - } + // Note: increment error count when v.mod becomes mandatory for external modules. + manifest := get_manifest(m.install_path) or { continue } final_path := os.real_path(os.join_path(settings.vmodules_path, manifest.name.replace('-', '_').to_lower())) if m.install_path != final_path { @@ -167,14 +155,8 @@ fn vpm_install_from_vcs(modules []Module) { input := os.input('Replace it with the module directory? [Y/n]: ') match input.to_lower() { '', 'y' { - mut err_msg := '' - $if windows { - os.execute_opt('rd /s /q ${final_path}') or { err_msg = err.msg() } - } $else { - os.rmdir_all(final_path) or { err_msg = err.msg() } - } - if err_msg != '' { - vpm_error('failed to remove `${final_path}`.', details: err_msg) + m.remove() or { + vpm_error('failed to remove `${final_path}`.', details: err.msg()) errors++ continue } @@ -209,9 +191,32 @@ fn vpm_install_from_vcs(modules []Module) { } verbose_println('Relocated `${m.name}` to `${manifest.name}`.') } - resolve_dependencies(manifest.name, final_path, urls) + if last_errors == errors { + println('Installed `${m.name}`.') + } + resolve_dependencies(manifest, urls) } if errors > 0 { exit(1) } } + +fn (m Module) install(vcs &VCS) ! { + cmd := '${vcs.cmd} ${vcs.args.install} "${m.url}" "${m.install_path}"' + vpm_log(@FILE_LINE, @FN, 'command: ${cmd}') + println('Installing module `${m.name}` from `${m.url}` to `${m.install_path}` ...') + os.execute_opt(cmd) or { + vpm_log(@FILE_LINE, @FN, 'cmd output: ${err}') + return error('failed to install module `${m.name}` to `${m.install_path}`.') + } +} + +fn (m Module) remove() ! { + verbose_println('Removing `${m.name}` from `${m.install_path}`...') + $if windows { + os.execute_opt('rd /s /q ${m.install_path}')! + } $else { + os.rmdir_all(m.install_path)! + } + verbose_println('Removed `${m.name}`.') +} diff --git a/cmd/tools/vpm/update.v b/cmd/tools/vpm/update.v index 09b058b0a3c3db..a8e2035f06577f 100644 --- a/cmd/tools/vpm/update.v +++ b/cmd/tools/vpm/update.v @@ -11,6 +11,29 @@ mut: has_err bool } +fn vpm_update(query []string) { + if settings.is_help { + help.print_and_exit('update') + } + modules := if query.len == 0 { + get_installed_modules() + } else { + query.clone() + } + if settings.is_verbose { + vpm_update_verbose(modules) + return + } + mut pp := pool.new_pool_processor(callback: update_module) + pp.work_on_items(modules) + for res in pp.get_results[ModUpdateInfo]() { + if res.has_err { + exit(1) + } + resolve_dependencies(get_manifest(res.final_path), modules) + } +} + fn update_module(mut pp pool.PoolProcessor, idx int, wid int) &ModUpdateInfo { mut result := &ModUpdateInfo{ name: pp.get_item[string](idx) @@ -19,7 +42,7 @@ fn update_module(mut pp pool.PoolProcessor, idx int, wid int) &ModUpdateInfo { result.final_path = get_path_of_existing_module(result.name) or { return result } println('Updating module `${name}` in `${result.final_path}` ...') vcs := vcs_used_in_dir(result.final_path) or { return result } - ensure_vcs_is_installed(vcs) or { + vcs.is_executable() or { result.has_err = true vpm_error(err.msg()) return result @@ -28,43 +51,17 @@ fn update_module(mut pp pool.PoolProcessor, idx int, wid int) &ModUpdateInfo { vpm_log(@FILE_LINE, @FN, 'cmd: ${cmd}') res := os.execute_opt(cmd) or { result.has_err = true - vpm_error('failed to update module `${name}` in `${result.final_path}`.', - details: err.msg() - ) + vpm_error('failed to update module `${name}` in `${result.final_path}`.', details: err.msg()) return result } vpm_log(@FILE_LINE, @FN, 'cmd output: ${res.output.trim_space()}') increment_module_download_count(name) or { result.has_err = true - vpm_error(err.msg(), - verbose: true - ) + vpm_error(err.msg(), verbose: true) } return result } -fn vpm_update(m []string) { - mut module_names := m.clone() - if settings.is_help { - help.print_and_exit('update') - } - if module_names.len == 0 { - module_names = get_installed_modules() - } - if settings.is_verbose { - vpm_update_verbose(module_names) - return - } - mut pp := pool.new_pool_processor(callback: update_module) - pp.work_on_items(module_names) - for res in pp.get_results[ModUpdateInfo]() { - if res.has_err { - exit(1) - } - resolve_dependencies(res.name, res.final_path, module_names) - } -} - fn vpm_update_verbose(modules []string) { mut errors := 0 for mod in modules { @@ -72,7 +69,7 @@ fn vpm_update_verbose(modules []string) { install_path := get_path_of_existing_module(mod) or { continue } println('Updating module `${name}` in `${install_path}` ...') vcs := vcs_used_in_dir(install_path) or { continue } - ensure_vcs_is_installed(vcs) or { + vcs.is_executable() or { errors++ vpm_error(err.msg()) continue @@ -89,11 +86,9 @@ fn vpm_update_verbose(modules []string) { vpm_log(@FILE_LINE, @FN, 'cmd: ${res.output.trim_space()}') increment_module_download_count(name) or { errors++ - vpm_error(err.msg(), - verbose: true - ) + vpm_error(err.msg(), verbose: true) } - resolve_dependencies(name, install_path, modules) + resolve_dependencies(get_manifest(install_path), modules) } if errors > 0 { exit(1) diff --git a/cmd/tools/vpm/vpm.v b/cmd/tools/vpm/vpm.v index 7b142f2794c4bd..8dfa8d6468fa89 100644 --- a/cmd/tools/vpm/vpm.v +++ b/cmd/tools/vpm/vpm.v @@ -61,20 +61,21 @@ fn main() { help.print_and_exit('vpm', exit_code: 5) } vpm_command := params[0] - mut requested_modules := params[1..].clone() + mut query := params[1..].clone() + vpm_log(@FILE_LINE, @FN, 'query: ${query}') ensure_vmodules_dir_exist() match vpm_command { 'help' { help.print_and_exit('vpm') } 'search' { - vpm_search(requested_modules) + vpm_search(query) } 'install' { - vpm_install(requested_modules) + vpm_install(query) } 'update' { - vpm_update(requested_modules) + vpm_update(query) } 'upgrade' { vpm_upgrade() @@ -86,10 +87,10 @@ fn main() { vpm_list() } 'remove' { - vpm_remove(requested_modules) + vpm_remove(query) } 'show' { - vpm_show(requested_modules) + vpm_show(query) } else { // Unreachable in regular usage. V will catch unknown commands beforehand. @@ -121,49 +122,45 @@ fn vpm_outdated() { } fn vpm_list() { - module_names := get_installed_modules() - if module_names.len == 0 { + installed := get_installed_modules() + if installed.len == 0 { println('You have no modules installed.') exit(0) } - for mod in module_names { - println(mod) + for m in installed { + println(m) } } -fn vpm_remove(module_names []string) { +fn vpm_remove(query []string) { if settings.is_help { help.print_and_exit('remove') } - if module_names.len == 0 { + if query.len == 0 { vpm_error('specify at least one module name for removal.') exit(2) } - for name in module_names { - final_module_path := get_path_of_existing_module(name) or { continue } - println('Removing module "${name}" ...') + for m in query { + final_module_path := get_path_of_existing_module(m) or { continue } + println('Removing module "${m}" ...') vpm_log(@FILE_LINE, @FN, 'removing: ${final_module_path}') - os.rmdir_all(final_module_path) or { vpm_error(err.msg(), - verbose: true - ) } + os.rmdir_all(final_module_path) or { vpm_error(err.msg(), verbose: true) } // Delete author directory if it is empty. - author := name.split('.')[0] + author := m.split('.')[0] author_dir := os.real_path(os.join_path(settings.vmodules_path, author)) if !os.exists(author_dir) { continue } if os.is_dir_empty(author_dir) { verbose_println('Removing author folder ${author_dir}') - os.rmdir(author_dir) or { vpm_error(err.msg(), - verbose: true - ) } + os.rmdir(author_dir) or { vpm_error(err.msg(), verbose: true) } } } } -fn vpm_show(modules []string) { +fn vpm_show(query []string) { installed_modules := get_installed_modules() - for m in modules { + for m in query { if m !in installed_modules { info := get_mod_vpm_info(m) or { continue } print('Name: ${info.name}