Skip to content

Commit

Permalink
tools: extend vpm to support specifying git version tags when install…
Browse files Browse the repository at this point in the history
…ing modules (#19835)
  • Loading branch information
ttytm committed Nov 11, 2023
1 parent 44cf145 commit 19bc165
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 52 deletions.
17 changes: 11 additions & 6 deletions cmd/tools/vpm/common.v
Expand Up @@ -19,6 +19,7 @@ mut:
version string // specifies the requested version.
install_path string
is_installed bool
is_external bool
installed_version string
}

Expand Down Expand Up @@ -48,22 +49,22 @@ fn parse_query(query []string) ([]Module, []Module) {
mut errors := 0
for m in query {
ident, version := m.rsplit_once('@') or { m, '' }
mut is_external := false
mut mod := if ident.starts_with('https://') {
is_external = true
name := get_name_from_url(ident) or {
vpm_error(err.msg())
errors++
continue
}
if !has_vmod(ident) {
install_path := os.real_path(os.join_path(settings.vmodules_path, name))
if !has_vmod(ident, install_path) {
errors++
continue
}
Module{
name: name
url: ident
install_path: os.real_path(os.join_path(settings.vmodules_path, name))
install_path: install_path
is_external: true
}
} else {
info := get_mod_vpm_info(ident) or {
Expand All @@ -85,7 +86,7 @@ fn parse_query(query []string) ([]Module, []Module) {
mod.is_installed = true
mod.installed_version = v.output.all_after_last('/').trim_space()
}
if is_external {
if mod.is_external {
external_modules << mod
} else {
vpm_modules << mod
Expand All @@ -97,7 +98,11 @@ fn parse_query(query []string) ([]Module, []Module) {
return vpm_modules, external_modules
}

fn has_vmod(url string) bool {
fn has_vmod(url string, install_path string) bool {
if os.exists((os.join_path(install_path, 'v.mod'))) {
// Safe time fetchting 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
Expand Down
14 changes: 7 additions & 7 deletions cmd/tools/vpm/dependency_test.v
Expand Up @@ -50,20 +50,20 @@ fn test_install_dependencies_in_module_dir() {
assert v_mod.dependencies == ['markdown', 'pcre', 'https://github.com/spytheman/vtray']
// Run `v install`
res := os.execute_or_exit('${v} install')
assert res.output.contains('Detected v.mod file inside the project directory. Using it...')
assert res.output.contains('Installing module `markdown`')
assert res.output.contains('Installing module `pcre`')
assert res.output.contains('Installing module `vtray`')
assert res.output.contains('Detected v.mod file inside the project directory. Using it...'), res.output
assert res.output.contains('Installing `markdown`'), res.output
assert res.output.contains('Installing `pcre`'), res.output
assert res.output.contains('Installing `vtray`'), res.output
assert get_mod_name(os.join_path(test_path, 'markdown', 'v.mod')) == 'markdown'
assert get_mod_name(os.join_path(test_path, 'pcre', 'v.mod')) == 'pcre'
assert get_mod_name(os.join_path(test_path, 'vtray', 'v.mod')) == 'vtray'
}

fn test_resolve_external_dependencies_during_module_install() {
res := os.execute_or_exit('${v} install https://github.com/ttytm/emoji-mart-desktop')
assert res.output.contains('Resolving 2 dependencies')
assert res.output.contains('Installing module `webview`')
assert res.output.contains('Installing module `miniaudio`')
assert res.output.contains('Resolving 2 dependencies'), res.output
assert res.output.contains('Installing `webview`'), res.output
assert res.output.contains('Installing `miniaudio`'), res.output
// The external dependencies should have been installed to `<vmodules_dir>/<dependency_name>`
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'
Expand Down
117 changes: 86 additions & 31 deletions cmd/tools/vpm/install.v
Expand Up @@ -4,6 +4,12 @@ import os
import v.vmod
import v.help

enum InstallResult {
installed
failed
skipped
}

fn vpm_install(query []string) {
if settings.is_help {
help.print_and_exit('vpm')
Expand Down Expand Up @@ -88,8 +94,8 @@ fn vpm_install_from_vpm(modules []Module) {
last_errors := errors
vcs := if m.vcs != '' {
supported_vcs[m.vcs] or {
errors++
vpm_error('skipping `${m.name}`, since it uses an unsupported version control system `${m.vcs}`.')
errors++
continue
}
} else {
Expand All @@ -100,18 +106,19 @@ fn vpm_install_from_vpm(modules []Module) {
errors++
continue
}
if os.exists(m.install_path) {
vpm_update([m.name])
continue
}
m.install(vcs) or {
errors++
vpm_error(err.msg())
continue
match m.install(vcs) {
.installed {}
.failed {
errors++
continue
}
.skipped {
continue
}
}
increment_module_download_count(m.name) or {
errors++
vpm_error('failed to increment the download count for `${m.name}`', details: err.msg())
errors++
}
if last_errors == errors {
println('Installed `${m.name}`.')
Expand All @@ -124,27 +131,27 @@ fn vpm_install_from_vpm(modules []Module) {
}

fn vpm_install_from_vcs(modules []Module) {
mut errors := 0
vpm_log(@FILE_LINE, @FN, 'modules: ${modules}')
vcs := supported_vcs[settings.vcs]
vcs.is_executable() or {
vpm_error(err.msg())
exit(1)
}
urls := modules.map(it.url)
mut errors := 0
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
}
vcs.is_executable() or {
vpm_error(err.msg())
errors++
continue
}
m.install(vcs) or {
errors++
vpm_error(err.msg())
continue
match m.install(vcs) {
.installed {}
.failed {
errors++
continue
}
.skipped {
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()))
Expand Down Expand Up @@ -201,13 +208,57 @@ fn vpm_install_from_vcs(modules []Module) {
}
}

fn (m Module) install(vcs &VCS) ! {
cmd := '${vcs.cmd} ${vcs.args.install} "${m.url}" "${m.install_path}"'
fn (m Module) install(vcs &VCS) InstallResult {
if m.is_installed {
// Case: installed, but not an explicit version. Update instead of continuing the installation.
if m.version == '' && m.installed_version == '' {
vpm_update([if m.is_external { m.url } else { m.name }])
return .skipped
}
// Case: installed, but conflicting. Confirmation or -[-f]orce flag required.
if settings.is_force || m.confirm_install() {
m.remove() or {
vpm_error('failed to remove `${m.name}`.', details: err.msg())
return .failed
}
} else {
return .skipped
}
}
install_arg := if m.version != '' {
'${vcs.args.install} --single-branch -b ${m.version}'
} else {
vcs.args.install
}
cmd := '${vcs.cmd} ${install_arg} "${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}`.')
println('Installing `${m.name}`...')
verbose_println(' cloning from `${m.url}` to `${m.install_path}`')
res := os.execute_opt(cmd) or {
vpm_error('failed to install `${m.name}`.', details: err.msg())
return .failed
}
vpm_log(@FILE_LINE, @FN, 'cmd output: ${res.output}')
return .installed
}

fn (m Module) confirm_install() bool {
if m.installed_version == m.version {
println('Module `${m.name}${at_version(m.installed_version)}` is already installed, use --force to overwrite.')
return false
} else {
install_version := at_version(if m.version == '' { 'latest' } else { m.version })
println('Module `${m.name}${at_version(m.installed_version)}` is already installed at `${m.install_path}`.')
input := os.input('Replace it with `${m.name}${install_version}`? [Y/n]: ')
match input.to_lower() {
'', 'y' {
return true
}
else {
verbose_println('Skipping `${m.name}`.')
return false
}
}
}
}

Expand All @@ -220,3 +271,7 @@ fn (m Module) remove() ! {
}
verbose_println('Removed `${m.name}`.')
}

fn at_version(version string) string {
return if version != '' { '@${version}' } else { '' }
}
16 changes: 8 additions & 8 deletions cmd/tools/vpm/install_test.v
Expand Up @@ -24,7 +24,7 @@ fn testsuite_end() {

fn test_install_from_vpm_ident() {
res := os.execute_or_exit('${v} install nedpals.args')
assert res.output.contains('Skipping download count increment for `nedpals.args`.')
assert res.output.contains('Skipping download count increment for `nedpals.args`.'), res.output
mod := vmod.from_file(os.join_path(test_path, 'nedpals', 'args', 'v.mod')) or {
assert false, err.msg()
return
Expand All @@ -45,7 +45,7 @@ fn test_install_from_vpm_short_ident() {

fn test_install_from_git_url() {
res := os.execute_or_exit('${v} install https://github.com/vlang/markdown')
assert res.output.contains('Installing module `markdown` from `https://github.com/vlang/markdown`')
assert res.output.contains('Installing `markdown`'), res.output
mod := vmod.from_file(os.join_path(test_path, 'markdown', 'v.mod')) or {
assert false, err.msg()
return
Expand Down Expand Up @@ -86,7 +86,7 @@ fn test_install_once() {
install_cmd := '${@VEXE} install https://github.com/vlang/markdown https://github.com/vlang/pcre --once -v'
// Try installing two modules, one of which is already installed.
mut res := os.execute_or_exit(install_cmd)
assert res.output.contains("Already installed modules: ['markdown']")
assert res.output.contains("Already installed modules: ['markdown']"), res.output
mod := vmod.from_file(os.join_path(test_path, 'pcre', 'v.mod')) or {
assert false, err.msg()
return
Expand All @@ -99,7 +99,7 @@ fn test_install_once() {

// Try installing two modules that are both already installed.
res = os.execute_or_exit(install_cmd)
assert res.output.contains('All modules are already installed.')
assert res.output.contains('All modules are already installed.'), res.output
assert md_last_modified == os.file_last_mod_unix(os.join_path(test_path, 'markdown',
'v.mod'))
}
Expand All @@ -108,13 +108,13 @@ fn test_missing_repo_name_in_url() {
incomplete_url := 'https://github.com/vlang'
res := os.execute('${v} install ${incomplete_url}')
assert res.exit_code == 1
assert res.output.contains('failed to retrieve module name for `${incomplete_url}`')
assert res.output.contains('failed to retrieve module name for `${incomplete_url}`'), res.output
}

fn test_missing_vmod_in_url() {
assert has_vmod('https://github.com/vlang/v') // head branch == `master`.
assert has_vmod('https://github.com/v-analyzer/v-analyzer') // head branch == `main`.
assert !has_vmod('https://github.com/octocat/octocat.github.io') // not a V module.
assert has_vmod('https://github.com/vlang/v', '') // head branch == `master`.
assert has_vmod('https://github.com/v-analyzer/v-analyzer', '') // head branch == `main`.
assert !has_vmod('https://github.com/octocat/octocat.github.io', '') // not a V module.
res := os.execute('${v} install https://github.com/octocat/octocat.github.io')
assert res.exit_code == 1
assert res.output.contains('failed to find `v.mod` for `https://github.com/octocat/octocat.github.io`'), res.output
Expand Down

0 comments on commit 19bc165

Please sign in to comment.