Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vpm.tools: add parse functions to handle module updates #20214

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
vpm.tools: add parse functions to handle module updates
  • Loading branch information
ttytm committed Dec 18, 2023
commit 54f1f6b890776512f1d114effaa7b115c829557f
118 changes: 112 additions & 6 deletions cmd/tools/vpm/parse.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import os
import net.http
import v.vmod

struct Module {
struct Parser {
mut:
modules map[string]Module
checked_settings_vcs bool
errors int
}

pub struct Module {
mut:
name string
url string
Expand All @@ -19,11 +26,10 @@ mut:
manifest vmod.Manifest
}

struct Parser {
mut:
modules map[string]Module
checked_settings_vcs bool
errors int
struct InstalledModule {
path string
is_global bool
url string
}

enum ModuleKind {
Expand Down Expand Up @@ -207,6 +213,106 @@ fn (mut m Module) get_installed() {
}
}

fn (mut p Parser) parse_outdated() {
for entry in os.ls(settings.vmodules_path) or { return } {
path := os.join_path(settings.vmodules_path, entry)
if entry in excluded_dirs || !os.is_dir(path) {
continue
}
// Global modules `vmodules_dir/module`.
if os.exists(os.join_path(path, 'v.mod')) {
if !is_outdated(path) {
continue
}
vcs := vcs_used_in_dir(path) or { continue }
args := vcs_info[vcs].args
// TODO: mercurial
url := os.execute_opt('${vcs.str()} ${args.path} ${path} config --get remote.origin.url') or {
vpm_error('failed to get url for `${entry}`.', details: err.msg())
continue
}.output.trim_space()
vpm_log(@FILE_LINE, @FN, 'url: ${url}')
if url.starts_with('https://github.com/vlang')
|| url.starts_with('[email protected]:vlang') {
p.parse_module(entry)
} else {
p.parse_module(url)
}
continue
}
// Modules under publisher namespace `vmodules_dir/publisher/module`.
for mod in os.ls(path) or { continue } {
mod_path := os.join_path(path, mod)
if os.exists(os.join_path(mod_path, 'v.mod')) && is_outdated(mod_path) {
p.parse_module('${entry}.${mod}')
}
}
}
}

fn (mut p Parser) parse_update_query(query []string) {
q_urls := query.filter(it.starts_with('https://') || it.starts_with('git@'))
mut installed := map[string]InstalledModule{}
for entry in os.ls(settings.vmodules_path) or { return } {
path := os.join_path(settings.vmodules_path, entry)
if entry in excluded_dirs || !os.is_dir(path) {
continue
}
// Global modules `vmodules_dir/module`.
if os.exists(os.join_path(path, 'v.mod')) {
vcs := vcs_used_in_dir(path) or { continue }
args := vcs_info[vcs].args
// TODO: mercurial
url := os.execute_opt('${vcs.str()} ${args.path} ${path} config --get remote.origin.url') or {
vpm_error('failed to get url for `${entry}`.', details: err.msg())
continue
}.output.trim_space()
vpm_log(@FILE_LINE, @FN, 'url: ${url}')
mod := InstalledModule{
path: path
is_global: true
url: if url.starts_with('https://github.com/vlang')
|| url.starts_with('[email protected]:vlang') {
''
} else {
url
}
}
if url in q_urls {
installed[url] = mod
} else {
installed[entry] = mod
}
continue
}
// Modules under a publisher namespace `vmodules_dir/publisher/module`.
for mod in os.ls(path) or { continue } {
mod_path := os.join_path(path, mod)
if os.exists(os.join_path(mod_path, 'v.mod')) {
installed['${entry}.${mod}'] = InstalledModule{
path: mod_path
}
}
}
}
for m in query {
if m !in installed {
vpm_error('failed to update `${m}`. Not installed.')
p.errors++
continue
}
if !is_outdated(installed[m].path) {
verbose_println('Skipping `${m}`. Already up to date.')
continue
}
if installed[m].is_global && installed[m].url != '' {
p.parse_module(installed[m].url)
} else {
p.parse_module(m)
}
}
}

fn get_tmp_path(relative_path string) !string {
tmp_path := os.real_path(os.join_path(settings.tmp_path, relative_path))
if os.exists(tmp_path) {
Expand Down
77 changes: 36 additions & 41 deletions cmd/tools/vpm/update.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import os
import sync.pool
import v.help

struct UpdateSession {
idents []string
}

pub struct UpdateResult {
mut:
success bool
Expand All @@ -17,61 +13,60 @@ fn vpm_update(query []string) {
if settings.is_help {
help.print_and_exit('update')
}
idents := if query.len == 0 {
get_installed_modules()
mut p := Parser{}
if query.len == 0 {
p.parse_outdated()
} else {
query.clone()
p.parse_update_query(query)
}
// In case dependencies have changed, new modules may need to be installed.
mut to_update, mut to_install := []Module{}, []Module{}
for m in p.modules.values() {
if m.is_installed {
to_update << m
} else {
to_install << m
}
}
if to_update.len == 0 {
if p.errors > 0 {
exit(1)
} else {
println('All modules are up to date.')
exit(0)
}
}
vpm_log(@FILE_LINE, @FN, 'Modules to update: ${to_update}')
vpm_log(@FILE_LINE, @FN, 'Modules to install: ${to_install}')
mut pp := pool.new_pool_processor(callback: update_module)
ctx := UpdateSession{idents}
pp.set_shared_context(ctx)
pp.work_on_items(idents)
pp.work_on_items(to_update)
mut errors := 0
for res in pp.get_results[UpdateResult]() {
if !res.success {
errors++
continue
}
}
if errors > 0 {
if to_install.len != 0 {
install_modules(to_install)
}
if p.errors > 0 || errors > 0 {
exit(1)
}
}

fn update_module(mut pp pool.PoolProcessor, idx int, wid int) &UpdateResult {
ident := pp.get_item[string](idx)
// Usually, the module `ident`ifier. `get_name_from_url` is only relevant for `v update <module_url>`.
name := get_name_from_url(ident) or { ident }
install_path := get_path_of_existing_module(ident) or {
vpm_error('failed to find path for `${name}`.', verbose: true)
return &UpdateResult{}
}
vcs := vcs_used_in_dir(install_path) or {
vpm_error('failed to find version control system for `${name}`.', verbose: true)
return &UpdateResult{}
}
vcs.is_executable() or {
vpm_error(err.msg())
return &UpdateResult{}
}
m := pp.get_item[Module](idx)
vcs := m.vcs or { settings.vcs }
args := vcs_info[vcs].args
cmd := [vcs.str(), args.path, os.quoted_path(install_path), args.update].join(' ')
vpm_log(@FILE_LINE, @FN, 'cmd: ${cmd}')
println('Updating module `${name}` in `${fmt_mod_path(install_path)}`...')
cmd := [vcs.str(), args.path, os.quoted_path(m.install_path), args.update].join(' ')
vpm_log(@FILE_LINE, @FN, '> cmd: ${cmd}')
println('Updating module `${m.name}` in `${fmt_mod_path(m.install_path)}`...')
res := os.execute_opt(cmd) or {
vpm_error('failed to update module `${name}` in `${install_path}`.', details: err.msg())
vpm_error('failed to update module `${m.name}` in `${m.install_path}`.', details: err.msg())
return &UpdateResult{}
}
vpm_log(@FILE_LINE, @FN, 'cmd output: ${res.output.trim_space()}')
if res.output.contains('Already up to date.') {
println('Skipped module `${ident}`. Already up to date.')
} else {
println('Updated module `${ident}`.')
}
vpm_log(@FILE_LINE, @FN, '>> output: ${res.output.trim_space()}')
// Don't bail if the download count increment has failed.
increment_module_download_count(name) or { vpm_error(err.msg(), verbose: true) }
ctx := unsafe { &UpdateSession(pp.get_shared_context()) }
vpm_log(@FILE_LINE, @FN, 'ident: ${ident}; ctx: ${ctx}')
resolve_dependencies(get_manifest(install_path), ctx.idents)
increment_module_download_count(m.name) or { vpm_error(err.msg(), verbose: true) }
return &UpdateResult{true}
}