diff --git a/cmd/tools/vpm.v b/cmd/tools/vpm.v index 135cb1ed8c..2031e156c0 100644 --- a/cmd/tools/vpm.v +++ b/cmd/tools/vpm.v @@ -4,7 +4,6 @@ module main import os -import regex import os.cmdline import net.http import json @@ -30,86 +29,27 @@ const ( 'git': ['git fetch', 'git rev-parse @', 'git rev-parse @{u}'] 'hg': ['hg incoming'] } - settings = &VpmSettings{} - normal_flags = ['-v', '-h', '-f', '-force'] - flags_with_value = ['-git', '-hg'] ) -// settings context: -struct VpmSettings { -mut: - is_help bool - is_verbose bool - is_global bool - is_forced bool - server_urls []string - vmodules_path string -} - -fn init_settings() { - mut s := &VpmSettings(0) - unsafe { - s = settings - } - s.is_help = '-h' in os.args || '--help' in os.args || 'help' in os.args - s.is_forced = '-f' in os.args || '-force' in os.args - s.is_verbose = '-v' in os.args - s.server_urls = cmdline.options(os.args, '-server-url') - s.vmodules_path = os.vmodules_dir() -} - struct Mod { id int + name string url string nr_downloads int vcs string +} + +struct Vmod { mut: - name string - failed bool - installed bool - updated bool + name string + version string + deps []string } -fn (mod Mod) path() string { - return real_path_of_module(mod.name) -} - -fn real_path_of_module(name string) string { - mod_name_as_path := name.replace('.', os.path_separator).replace('-', '_').to_lower() - name_of_vmodules_folder := os.join_path(settings.vmodules_path, mod_name_as_path) - return os.real_path(name_of_vmodules_folder) -} - -fn module_from_url(url string, vcs string) ?Mod { - query := r'([\w+]+\://)?((\w*@)?((\w+\.)+(\w*)))[/+\:](\w+)/([\w+\-]+)(\.\w*)?' - mut re := regex.regex_opt(query) or { panic(err) } - - start, end := re.match_string(url) - - if start < 0 || end <= start { - panic('"$url" is not a valid url!') - } - - author := re.get_group_by_id(url, 6) - name := re.get_group_by_id(url, 7) - - return Mod{ - name: '${author}.$name' - url: url - vcs: vcs - } -} - -fn module_from_manifest(manifest vmod.Manifest) Mod { - return Mod{ - name: manifest.name - url: manifest.repo_url - } -} - -fn module_from_file(vpath string) ?Mod { - manifest := vmod.from_file(vpath) or { panic(err) } - return module_from_manifest(manifest) +enum Source { + git + hg + vpm } fn main() { @@ -117,10 +57,8 @@ fn main() { // This tool is intended to be launched by the v frontend, // which provides the path to V inside os.getenv('VEXE') // args are: vpm [options] SUBCOMMAND module names - mut modules := map[string]Mod{} - params := cmdline.only_non_options(os.args[1..]) - + options := cmdline.only_options(os.args[1..]) verbose_println('cli params: $params') if params.len < 1 { vpm_help() @@ -129,46 +67,32 @@ fn main() { vpm_command := params[0] mut module_names := params[1..] ensure_vmodules_dir_exist() - verbose_println('module names: ') + // println('module names: ') println(module_names) match vpm_command { 'help' { vpm_help() } 'search' { - if settings.is_help { - vhelp.show_topic('search') - exit(0) - } vpm_search(module_names) } 'install' { - if settings.is_help { - vhelp.show_topic('install') - exit(0) - } - - modules = parse_modules() - - if modules.len == 0 && os.exists('./v.mod') { + if module_names.len == 0 && os.exists('./v.mod') { println('Detected v.mod file inside the project directory. Using it...') - resolve_dependencies('./v.mod', mut modules) + manifest := vmod.from_file('./v.mod') or { panic(err) } + module_names = manifest.dependencies + } + mut source := Source.vpm + if '--git' in options { + source = Source.git + } + if '--hg' in options { + source = Source.hg } - vpm_install(mut modules) + vpm_install(module_names, source) } 'update' { - if settings.is_help { - vhelp.show_topic('update') - exit(0) - } - - modules = parse_modules() - - if modules.len == 0 { - modules = get_installed_modules() - } - - vpm_update(mut modules) + vpm_update(module_names) } 'upgrade' { vpm_upgrade() @@ -196,80 +120,12 @@ fn main() { } } -fn parse_modules() map[string]Mod { - mut modules := map[string]Mod{} - args := os.args[1..] - url_query := r'([\w+]+\://)?((\w*@)?((\w+\.)+(\w*)))[/+\:](\w+)/([\w+\-]+)(\.\w*)?' - mod_query := r'(\w*)(\.\w*)?' - - mut url_re := regex.regex_opt(url_query) or { panic(err) } - mut mod_re := regex.regex_opt(mod_query) or { panic(err) } - - for git in cmdline.options(args, '-git') { - arg_git_mod := module_from_url(git, 'git') or { - println('Error in parsing url:') - println(err) - continue - } - modules[arg_git_mod.name] = arg_git_mod - } - - for hg in cmdline.options(args, '-hg') { - hg_mod := module_from_url(hg, 'hg') or { - println('Error in parsing url:') - println(err) - continue - } - modules[hg_mod.name] = hg_mod - } - - mut ignore := true - for arg in args { - if arg in normal_flags { - continue - } - if ignore { - ignore = false - continue - } - if arg in flags_with_value { - ignore = true - continue - } - - // detect urls without option as git - mut start, mut end := url_re.match_string(arg) - if start >= 0 && end > start { - git_mod := module_from_url(arg, 'git') or { - println('Error in parsing url:') - println(err) - continue - } - modules[git_mod.name] = git_mod - continue - } - - start, end = mod_re.match_string(arg) - if start >= 0 && end > start { - vpm_mod := get_module_meta_info(arg) or { - println('Errors while retrieving meta data for module $arg:') - println(err) - continue - } - - modules[vpm_mod.name] = vpm_mod - continue - } - - println('Error in parsing module name:') - println('"$arg" is not a valid module name or url!') - } - return modules -} - fn vpm_search(keywords []string) { search_keys := keywords.map(it.replace('_', '-')) - + if settings.is_help { + vhelp.show_topic('search') + exit(0) + } if search_keys.len == 0 { println('´v search´ requires *at least one* keyword.') exit(2) @@ -278,17 +134,17 @@ fn vpm_search(keywords []string) { installed_modules := get_installed_modules() joined := search_keys.join(', ') mut index := 0 - for _, mod in modules { + for mod in modules { // TODO for some reason .filter results in substr error, so do it manually for k in search_keys { - if !mod.name.contains(k) { + if !mod.contains(k) { continue } if index == 0 { println('Search results for "$joined":\n') } index++ - mut parts := mod.name.split('.') + mut parts := mod.split('.') // in case the author isn't present if parts.len == 1 { parts << parts[0] @@ -296,8 +152,8 @@ fn vpm_search(keywords []string) { } else { parts[0] = ' by ${parts[0]} ' } - installed := if mod.name in installed_modules { ' (installed)' } else { '' } - println('${index}. ${parts[1]}${parts[0]}[$mod.name]$installed') + installed := if mod in installed_modules { ' (installed)' } else { '' } + println('${index}. ${parts[1]}${parts[0]}[$mod]$installed') break } } @@ -318,10 +174,14 @@ fn vpm_search(keywords []string) { } } -fn vpm_install(mut modules map[string]Mod) { +fn vpm_install_from_vpm(module_names []string) { mut errors := 0 - for _, mut mod in modules { - if mod.failed || mod.updated || mod.installed { + for n in module_names { + name := n.trim_space().replace('_', '-') + mod := get_module_meta_info(name) or { + errors++ + println('Errors while retrieving meta data for module $name:') + println(err) continue } mut vcs := mod.vcs @@ -330,57 +190,85 @@ fn vpm_install(mut modules map[string]Mod) { } if vcs !in supported_vcs_systems { errors++ - println('Skipping module "$mod.name", since it uses an unsupported VCS {$vcs} .') + println('Skipping module "$name", since it uses an unsupported VCS {$vcs} .') continue } - mut final_module_path := mod.path() - + mod_name_as_path := mod.name.replace('.', os.path_separator).replace('-', '_').to_lower() + final_module_path := os.real_path(os.join_path(settings.vmodules_path, mod_name_as_path)) if os.exists(final_module_path) { - mut mods := { - mod.name: mod - } - vpm_update(mut mods) + vpm_update([name]) continue } - println('Installing module "$mod.name" from $mod.url to $final_module_path ...') + println('Installing module "$name" from $mod.url to $final_module_path ...') vcs_install_cmd := supported_vcs_install_cmds[vcs] cmd := '$vcs_install_cmd "$mod.url" "$final_module_path"' verbose_println(' command: $cmd') cmdres := os.execute(cmd) - mod.updated = true if cmdres.exit_code != 0 { errors++ - println('Failed installing module "$mod.name" to "$final_module_path" .') + println('Failed installing module "$name" to "$final_module_path" .') + verbose_println('Failed command: $cmd') + verbose_println('Failed command output:\n$cmdres.output') + continue + } + resolve_dependencies(name, final_module_path, module_names) + } + if errors > 0 { + exit(1) + } +} + +fn vpm_install_from_vcs(module_names []string, vcs_key string) { + mut errors := 0 + for n in module_names { + url := n.trim_space() + + first_cut_pos := url.last_index('/') or { + errors++ + println('Errors while retrieving name for module $url:') + println(err) + continue + } + + mod_name := url.substr(first_cut_pos + 1, url.len) + + second_cut_pos := url.substr(0, first_cut_pos).last_index('/') or { + errors++ + println('Errors while retrieving name for module $url:') + println(err) + continue + } + + repo_name := url.substr(second_cut_pos + 1, first_cut_pos) + mut name := repo_name + os.path_separator + mod_name + mod_name_as_path := name.replace('-', '_').to_lower() + mut final_module_path := os.real_path(os.join_path(settings.vmodules_path, mod_name_as_path)) + if os.exists(final_module_path) { + vpm_update([name.replace('-', '_')]) + continue + } + println('Installing module "$name" from $url to $final_module_path ...') + vcs_install_cmd := supported_vcs_install_cmds[vcs_key] + cmd := '$vcs_install_cmd "$url" "$final_module_path"' + verbose_println(' command: $cmd') + cmdres := os.execute(cmd) + if cmdres.exit_code != 0 { + errors++ + println('Failed installing module "$name" to "$final_module_path" .') verbose_println('Failed command: $cmd') verbose_println('Failed command output:\n$cmdres.output') - mod.failed = true continue } vmod_path := os.join_path(final_module_path, 'v.mod') if os.exists(vmod_path) { - vmod := module_from_file(vmod_path) or { - println('Error in reading v.mod from "$vmod_path":') - println(err) - continue - } - mod_path := vmod.path() - if final_module_path == mod_path { - continue - } - println('Relocating module from "$mod.name" to "$vmod.name" ( $mod_path ) ...') + data := os.read_file(vmod_path) or { return } + vmod := parse_vmod(data) + mod_path := os.real_path(os.join_path(settings.vmodules_path, vmod.name.replace('.', + os.path_separator))) + println('Relocating module from "$name" to "$vmod.name" ( $mod_path ) ...') if os.exists(mod_path) { println('Warning module "$mod_path" already exsits!') - if !settings.is_forced { - println('Undoing module "$final_module_path" installation ...') - os.rmdir_all(final_module_path) or { - errors++ - println('Errors while removing "$final_module_path" :') - println(err) - continue - } - continue - } println('Removing module "$mod_path" ...') os.rmdir_all(mod_path) or { errors++ @@ -391,111 +279,129 @@ fn vpm_install(mut modules map[string]Mod) { } os.mv(final_module_path, mod_path) or { errors++ - println('Errors while relocating module "$mod.name" :') + println('Errors while relocating module "$name" :') println(err) + os.rmdir_all(final_module_path) or { + errors++ + println('Errors while removing "$final_module_path" :') + println(err) + continue + } continue } - println('Module "$mod.name" relocated to "$vmod.name" successfully.') + println('Module "$name" relocated to "$vmod.name" successfully.') final_module_path = mod_path - mod.name = vmod.name + name = vmod.name } - resolve_dependencies(os.join_path(mod.path(), 'v.mod'), mut modules) + resolve_dependencies(name, final_module_path, module_names) } if errors > 0 { exit(1) } } -fn vpm_update(mut modules map[string]Mod) { +fn vpm_install(module_names []string, source Source) { + if settings.is_help { + vhelp.show_topic('install') + exit(0) + } + if module_names.len == 0 { + println('´v install´ requires *at least one* module name.') + exit(2) + } + + if source == .vpm { + vpm_install_from_vpm(module_names) + } + if source == .git { + vpm_install_from_vcs(module_names, 'git') + } + if source == .hg { + vpm_install_from_vcs(module_names, 'hg') + } +} + +fn vpm_update(m []string) { + mut module_names := m.clone() + if settings.is_help { + vhelp.show_topic('update') + exit(0) + } + if module_names.len == 0 { + module_names = get_installed_modules() + } mut errors := 0 - for _, mut mod in modules { - if mod.updated || mod.failed { - continue - } - mut final_module_path := mod.path() - if !os.exists(final_module_path) { - println('Error in updating "$mod.name" module:') - println('"$mod.name" is not insalled!') - continue - } + for name in module_names { + final_module_path := valid_final_path_of_existing_module(name) or { continue } os.chdir(final_module_path) - println('Updating module "$mod.name"...') + println('Updating module "$name"...') verbose_println(' work folder: $final_module_path') - vcs := vcs_used_in_path(final_module_path) - vcs_cmd := supported_vcs_update_cmds[vcs] + vcs := vcs_used_in_dir(final_module_path) or { continue } + vcs_cmd := supported_vcs_update_cmds[vcs[0]] verbose_println(' command: $vcs_cmd') vcs_res := os.execute('$vcs_cmd') if vcs_res.exit_code != 0 { errors++ - println('Failed updating module "$mod.name".') + println('Failed updating module "$name".') verbose_println('Failed command: $vcs_cmd') verbose_println('Failed details:\n$vcs_res.output') - mod.failed = true continue } else { verbose_println(' $vcs_res.output.trim_space()') - mod.updated = true - mod.installed = true } - resolve_dependencies(os.join_path(mod.path(), 'v.mod'), mut modules) + resolve_dependencies(name, final_module_path, module_names) } if errors > 0 { exit(1) } } -fn get_outdated() ?map[string]Mod { - modules := get_installed_modules() - mut outdated := map[string]Mod{} - for _, mod in modules { - final_module_path := mod.path() +fn get_outdated() ?[]string { + module_names := get_installed_modules() + mut outdated := []string{} + for name in module_names { + final_module_path := valid_final_path_of_existing_module(name) or { continue } os.chdir(final_module_path) - vcs := vcs_used_in_path(final_module_path) - vcs_cmd_steps := supported_vcs_outdated_steps[vcs] + vcs := vcs_used_in_dir(final_module_path) or { continue } + vcs_cmd_steps := supported_vcs_outdated_steps[vcs[0]] mut outputs := []string{} for step in vcs_cmd_steps { res := os.execute(step) if res.exit_code < 0 { verbose_println('Error command: $step') verbose_println('Error details:\n$res.output') - panic('Error while checking latest commits for "$mod.name".') + return error('Error while checking latest commits for "$name".') } - if vcs == 'hg' { + if vcs[0] == 'hg' { if res.exit_code == 1 { - outdated[mod.name] = mod + outdated << name } } else { outputs << res.output } } - if vcs == 'git' && outputs[1] != outputs[2] { - outdated[mod.name] = mod + if vcs[0] == 'git' && outputs[1] != outputs[2] { + outdated << name } } return outdated } fn vpm_upgrade() { - mut outdated := get_outdated() or { - println(err) - exit(1) - } + outdated := get_outdated() or { exit(1) } if outdated.len > 0 { - vpm_update(mut &outdated) + vpm_update(outdated) } else { println('Modules are up to date.') } } fn vpm_outdated() { - outdated := get_outdated() or { - println(err) - exit(1) - } + outdated := get_outdated() or { exit(1) } if outdated.len > 0 { println('Outdated modules:') - for _, m in outdated { - println(' $m.name') + for m in outdated { + println(' $m') } } else { println('Modules are up to date.') @@ -503,14 +409,14 @@ fn vpm_outdated() { } fn vpm_list() { - modules := get_installed_modules() - if modules.len == 0 { + module_names := get_installed_modules() + if module_names.len == 0 { println('You have no modules installed.') exit(0) } println('Installed modules:') - for _, mod in modules { - println(' $mod.name') + for mod in module_names { + println(' $mod') } } @@ -546,16 +452,21 @@ fn vpm_remove(module_names []string) { } fn valid_final_path_of_existing_module(name string) ?string { - final_module_path := real_path_of_module(name) + mod_name_as_path := name.replace('.', os.path_separator).replace('-', '_').to_lower() + name_of_vmodules_folder := os.join_path(settings.vmodules_path, mod_name_as_path) + final_module_path := os.real_path(name_of_vmodules_folder) if !os.exists(final_module_path) { - println('No module with name "$name" exists at $final_module_path') + println('No module with name "$name" exists at $name_of_vmodules_folder') return none } if !os.is_dir(final_module_path) { - println('Skipping "$final_module_path", since it is not a folder.') + println('Skipping "$name_of_vmodules_folder", since it is not a folder.') + return none + } + vcs_used_in_dir(final_module_path) or { + println('Skipping "$name_of_vmodules_folder", since it does not use a supported vcs.') return none } - return final_module_path } @@ -570,69 +481,44 @@ fn vpm_help() { vhelp.show_topic('vpm') } -fn vcs_used_in_path(dir string) string { +fn vcs_used_in_dir(dir string) ?[]string { + mut vcs := []string{} for repo_subfolder in supported_vcs_folders { checked_folder := os.real_path(os.join_path(dir, repo_subfolder)) if os.is_dir(checked_folder) { - return repo_subfolder.replace('.', '') + vcs << repo_subfolder.replace('.', '') } } - return 'git' + if vcs.len == 0 { + return none + } + return vcs } -fn get_installed_modules() map[string]Mod { - dirs := os.ls(settings.vmodules_path) or { return map[string]Mod{} } - mut modules := map[string]Mod{} +fn get_installed_modules() []string { + dirs := os.ls(settings.vmodules_path) or { return [] } + mut modules := []string{} for dir in dirs { adir := os.join_path(settings.vmodules_path, dir) if dir in excluded_dirs || !os.is_dir(adir) { continue } - mut vmod_path := os.join_path(adir, 'v.mod') - if os.exists(vmod_path) && os.exists(os.join_path(adir, '.git', 'config')) { + if os.exists(os.join_path(adir, 'v.mod')) && os.exists(os.join_path(adir, '.git', 'config')) { // an official vlang module with a short module name, like `vsl`, `ui` or `markdown` - - mut mod := module_from_file(vmod_path) or { - println('Error while reading "$vmod_path":') - println(err) - url := 'https://github.com/vlang/$dir' - modules[dir] = Mod{ - name: dir - url: url - installed: true - vcs: 'git' - } - continue - } - modules[dir] = mod + modules << dir continue } author := dir mods := os.ls(adir) or { continue } for m in mods { - vmod_path = os.join_path(adir, m, 'v.mod') - if os.exists(vmod_path) { - mut mod := module_from_file(vmod_path) or { - println('Error while reading "$vmod_path":') - println(err) - name := '${author}.$m' - url := 'https://github.com/vlang/$author/$m' - modules[name] = Mod{ - name: name - url: url - installed: true - vcs: 'git' - } - continue - } - modules[mod.name] = mod - } + vcs_used_in_dir(os.join_path(adir, m)) or { continue } + modules << '${author}.$m' } } return modules } -fn get_all_modules() map[string]Mod { +fn get_all_modules() []string { url := get_working_server_url() r := http.get(url) or { panic(err) } if r.status_code != 200 { @@ -641,7 +527,7 @@ fn get_all_modules() map[string]Mod { } s := r.text mut read_len := 0 - mut names := []string{} + mut modules := []string{} for read_len < s.len { mut start_token := ' 0 { - println('Resolving $deps.len dependencies for module "$manifest.name"...') + println('Resolving $deps.len dependencies for module "$name"...') verbose_println('Found dependencies: $deps') - vpm_update(mut modules) - vpm_install(mut modules) + vpm_install(deps, Source.vpm) } } +fn parse_vmod(data string) Vmod { + manifest := vmod.decode(data) or { vmod.Manifest{} } + mut vmod := Vmod{} + vmod.name = manifest.name + vmod.version = manifest.version + vmod.deps = manifest.dependencies + return vmod +} + fn get_working_server_url() string { server_urls := if settings.server_urls.len > 0 { settings.server_urls @@ -759,6 +601,30 @@ fn get_working_server_url() string { panic('No responding vpm server found. Please check your network connectivity and try again later.') } +// settings context: +struct VpmSettings { +mut: + is_help bool + is_verbose bool + server_urls []string + vmodules_path string +} + +const ( + settings = &VpmSettings{} +) + +fn init_settings() { + mut s := &VpmSettings(0) + unsafe { + s = settings + } + s.is_help = '-h' in os.args || '--help' in os.args || 'help' in os.args + s.is_verbose = '-v' in os.args + s.server_urls = cmdline.options(os.args, '-server-url') + s.vmodules_path = os.vmodules_dir() +} + fn verbose_println(s string) { if settings.is_verbose { println(s) @@ -804,30 +670,29 @@ fn get_module_meta_info(name string) ?Mod { fn vpm_show(module_names []string) { installed_modules := get_installed_modules() - mut installed_modules_names := []string{} - for _, installed in installed_modules { - installed_modules_names << installed.name - } for module_name in module_names { - if module_name !in installed_modules_names { + if module_name !in installed_modules { module_meta_info := get_module_meta_info(module_name) or { continue } - println('Name: $module_meta_info.name') - println('Homepage: $module_meta_info.url') - println('Downloads: $module_meta_info.nr_downloads') - println('Installed: False') - println('--------') + print(' +Name: $module_meta_info.name +Homepage: $module_meta_info.url +Downloads: $module_meta_info.nr_downloads +Installed: False +-------- +') continue } - path := real_path_of_module(module_name) - mod := vmod.from_file(path) or { continue } - println('Name: $mod.name') - println('Version: $mod.version') - println('Description: $mod.description') - println('Homepage: $mod.repo_url') - println('Author: $mod.author') - println('License: $mod.license') - println('Location: $path') - println('Requires: ${mod.dependencies.join(', ')}') - println('--------') + path := os.join_path(os.vmodules_dir(), module_name.replace('.', os.path_separator)) + mod := vmod.from_file(os.join_path(path, 'v.mod')) or { continue } + print('Name: $mod.name +Version: $mod.version +Description: $mod.description +Homepage: $mod.repo_url +Author: $mod.author +License: $mod.license +Location: $path +Requires: ${mod.dependencies.join(', ')} +-------- +') } } diff --git a/cmd/v/help/install.txt b/cmd/v/help/install.txt index 1a2503e01a..ba0b3cb175 100644 --- a/cmd/v/help/install.txt +++ b/cmd/v/help/install.txt @@ -1,13 +1,13 @@ Usage: - v install [GIT_REPO_URL...|MODULE...] + v install [MODULE...] Installs each MODULE. If no MODULEs, the modules listed in the `v.mod` file are installed instead. Options: - -git - Install from git repository url - -hg - Install from mercurial repository url - -f|-force - force module installation, regardless of existing install + --vpm - [Default] Install from vpm + --git - Install from git repository url + --hg - Install from mercurial repository url -help - Show usage info. -v - Print more details about the performed operation. -server-url - When doing network operations, use this vpm server. Can be given multiple times. diff --git a/doc/docs.md b/doc/docs.md index 61194f9e1d..033fa7a0b4 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -2463,13 +2463,12 @@ v install ui ``` Modules could install directly from git or mercurial repositories. -The -git flag is the default for repository urls, and can be skipped. ```powershell -v install [-git|-hg] [url] +v install [--git|--hg] [url] ``` **Example:** ```powershell -v install https://github.com/vlang/markdown +v install --git https://github.com/vlang/markdown ``` Removing a module with v: