v / cmd / tools
Raw file | 928 loc (870 sloc) | 20.9 KB | Latest commit hash 2029d1830
1// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module main
5
6import os
7import rand
8import os.cmdline
9import net.http
10import net.urllib
11import json
12import v.help
13import v.vmod
14import sync.pool
15
16const (
17 default_vpm_server_urls = ['https://vpm.vlang.io', 'https://vpm.url4e.com']
18 vpm_server_urls = rand.shuffle_clone(default_vpm_server_urls) or { [] } // ensure that all queries are distributed fairly
19 valid_vpm_commands = ['help', 'search', 'install', 'update', 'upgrade', 'outdated',
20 'list', 'remove', 'show']
21 excluded_dirs = ['cache', 'vlib']
22 supported_vcs_systems = ['git', 'hg']
23 supported_vcs_folders = ['.git', '.hg']
24 supported_vcs_update_cmds = {
25 'git': 'pull --recurse-submodules' // pulling with `--depth=1` leads to conflicts, when the upstream is more than 1 commit newer
26 'hg': 'pull --update'
27 }
28 supported_vcs_install_cmds = {
29 'git': 'clone --depth=1 --recursive --shallow-submodules'
30 'hg': 'clone'
31 }
32 supported_vcs_outdated_steps = {
33 'git': ['fetch', 'rev-parse @', 'rev-parse @{u}']
34 'hg': ['incoming']
35 }
36 supported_vcs_version_cmds = {
37 'git': 'version'
38 'hg': 'version'
39 }
40)
41
42struct Mod {
43 id int
44 name string
45 url string
46 nr_downloads int
47 vcs string
48}
49
50struct Vmod {
51mut:
52 name string
53 version string
54 deps []string
55}
56
57enum Source {
58 git
59 hg
60 vpm
61}
62
63fn main() {
64 init_settings()
65 // This tool is intended to be launched by the v frontend,
66 // which provides the path to V inside os.getenv('VEXE')
67 // args are: vpm [options] SUBCOMMAND module names
68 params := cmdline.only_non_options(os.args[1..])
69 options := cmdline.only_options(os.args[1..])
70 verbose_println('cli params: ${params}')
71 if params.len < 1 {
72 vpm_help()
73 exit(5)
74 }
75 vpm_command := params[0]
76 mut module_names := params[1..].clone()
77 ensure_vmodules_dir_exist()
78 // println('module names: ') println(module_names)
79 match vpm_command {
80 'help' {
81 vpm_help()
82 }
83 'search' {
84 vpm_search(module_names)
85 }
86 'install' {
87 if module_names.len == 0 && os.exists('./v.mod') {
88 println('Detected v.mod file inside the project directory. Using it...')
89 manifest := vmod.from_file('./v.mod') or { panic(err) }
90 module_names = manifest.dependencies.clone()
91 }
92
93 if '--once' in options {
94 module_names = vpm_once_filter(module_names)
95
96 if module_names.len == 0 {
97 return
98 }
99 }
100
101 external_module_names := module_names.filter(it.starts_with('https://'))
102 vpm_module_names := module_names.filter(it !in external_module_names)
103
104 if vpm_module_names.len > 0 {
105 vpm_install(vpm_module_names, Source.vpm)
106 }
107
108 if external_module_names.len > 0 {
109 mut external_source := Source.git
110
111 if '--hg' in options {
112 external_source = Source.hg
113 }
114
115 vpm_install(external_module_names, external_source)
116 }
117 }
118 'update' {
119 vpm_update(module_names)
120 }
121 'upgrade' {
122 vpm_upgrade()
123 }
124 'outdated' {
125 vpm_outdated()
126 }
127 'list' {
128 vpm_list()
129 }
130 'remove' {
131 vpm_remove(module_names)
132 }
133 'show' {
134 vpm_show(module_names)
135 }
136 else {
137 eprintln('Error: you tried to run "v ${vpm_command}"')
138 eprintln('... but the v package management tool vpm only knows about these commands:')
139 for validcmd in valid_vpm_commands {
140 eprintln(' v ${validcmd}')
141 }
142 exit(3)
143 }
144 }
145}
146
147fn vpm_search(keywords []string) {
148 search_keys := keywords.map(it.replace('_', '-'))
149 if settings.is_help {
150 help.print_and_exit('search')
151 exit(0)
152 }
153 if search_keys.len == 0 {
154 eprintln('´v search´ requires *at least one* keyword.')
155 exit(2)
156 }
157 modules := get_all_modules()
158 installed_modules := get_installed_modules()
159 joined := search_keys.join(', ')
160 mut index := 0
161 for mod in modules {
162 for k in search_keys {
163 if !mod.contains(k) {
164 continue
165 }
166 if index == 0 {
167 println('Search results for "${joined}":\n')
168 }
169 index++
170 mut parts := mod.split('.')
171 // in case the author isn't present
172 if parts.len == 1 {
173 parts << parts[0]
174 parts[0] = ' '
175 } else {
176 parts[0] = ' by ${parts[0]} '
177 }
178 installed := if mod in installed_modules { ' (installed)' } else { '' }
179 println('${index}. ${parts[1]}${parts[0]}[${mod}]${installed}')
180 break
181 }
182 }
183 if index == 0 {
184 vexe := os.getenv('VEXE')
185 vroot := os.real_path(os.dir(vexe))
186 mut messages := ['No module(s) found for `${joined}` .']
187 for vlibmod in search_keys {
188 if os.is_dir(os.join_path(vroot, 'vlib', vlibmod)) {
189 messages << 'There is already an existing "${vlibmod}" module in vlib, so you can just `import ${vlibmod}` .'
190 }
191 }
192 for m in messages {
193 println(m)
194 }
195 } else {
196 eprintln('\nUse "v install author_name.module_name" to install the module.')
197 }
198}
199
200fn vpm_install_from_vpm(module_names []string) {
201 mut errors := 0
202 for n in module_names {
203 name := n.trim_space().replace('_', '-')
204 mod := get_module_meta_info(name) or {
205 errors++
206 eprintln('Errors while retrieving meta data for module ${name}:')
207 eprintln(err)
208 continue
209 }
210 mut vcs := mod.vcs
211 if vcs == '' {
212 vcs = supported_vcs_systems[0]
213 }
214 if vcs !in supported_vcs_systems {
215 errors++
216 eprintln('Skipping module "${name}", since it uses an unsupported VCS {${vcs}} .')
217 continue
218 }
219 if !ensure_vcs_is_installed(vcs) {
220 errors++
221 eprintln('VPM needs `${vcs}` to be installed.')
222 continue
223 }
224 //
225 minfo := mod_name_info(mod.name)
226 if os.exists(minfo.final_module_path) {
227 vpm_update([name])
228 continue
229 }
230 println('Installing module "${name}" from "${mod.url}" to "${minfo.final_module_path}" ...')
231 increment_module_download_count(name) or {
232 errors++
233 eprintln('Errors while incrementing the download count for ${name}:')
234 }
235 vcs_install_cmd := '${vcs} ${supported_vcs_install_cmds[vcs]}'
236 cmd := '${vcs_install_cmd} "${mod.url}" "${minfo.final_module_path}"'
237 verbose_println(' command: ${cmd}')
238 cmdres := os.execute(cmd)
239 if cmdres.exit_code != 0 {
240 errors++
241 eprintln('Failed installing module "${name}" to "${minfo.final_module_path}" .')
242 print_failed_cmd(cmd, cmdres)
243 continue
244 }
245 resolve_dependencies(name, minfo.final_module_path, module_names)
246 }
247 if errors > 0 {
248 exit(1)
249 }
250}
251
252fn print_failed_cmd(cmd string, cmdres os.Result) {
253 verbose_println('Failed command: ${cmd}')
254 verbose_println('Failed command output:\n${cmdres.output}')
255}
256
257fn ensure_vcs_is_installed(vcs string) bool {
258 mut res := true
259 cmd := '${vcs} ${supported_vcs_version_cmds[vcs]}'
260 cmdres := os.execute(cmd)
261 if cmdres.exit_code != 0 {
262 print_failed_cmd(cmd, cmdres)
263 res = false
264 }
265 return res
266}
267
268fn vpm_install_from_vcs(module_names []string, vcs_key string) {
269 mut errors := 0
270 for n in module_names {
271 url := n.trim_space()
272
273 first_cut_pos := url.last_index('/') or {
274 errors++
275 eprintln('Errors while retrieving name for module "${url}" :')
276 eprintln(err)
277 continue
278 }
279
280 mod_name := url.substr(first_cut_pos + 1, url.len)
281
282 second_cut_pos := url.substr(0, first_cut_pos).last_index('/') or {
283 errors++
284 eprintln('Errors while retrieving name for module "${url}" :')
285 eprintln(err)
286 continue
287 }
288
289 repo_name := url.substr(second_cut_pos + 1, first_cut_pos)
290 mut name := os.join_path(repo_name, mod_name)
291 mod_name_as_path := name.replace('-', '_').to_lower()
292 mut final_module_path := os.real_path(os.join_path(settings.vmodules_path, mod_name_as_path))
293 if os.exists(final_module_path) {
294 vpm_update([name.replace('-', '_')])
295 continue
296 }
297 if !ensure_vcs_is_installed(vcs_key) {
298 errors++
299 eprintln('VPM needs `${vcs_key}` to be installed.')
300 continue
301 }
302 println('Installing module "${name}" from "${url}" to "${final_module_path}" ...')
303 vcs_install_cmd := '${vcs_key} ${supported_vcs_install_cmds[vcs_key]}'
304 cmd := '${vcs_install_cmd} "${url}" "${final_module_path}"'
305 verbose_println(' command: ${cmd}')
306 cmdres := os.execute(cmd)
307 if cmdres.exit_code != 0 {
308 errors++
309 eprintln('Failed installing module "${name}" to "${final_module_path}" .')
310 print_failed_cmd(cmd, cmdres)
311 continue
312 }
313 vmod_path := os.join_path(final_module_path, 'v.mod')
314 if os.exists(vmod_path) {
315 data := os.read_file(vmod_path) or { return }
316 vmod_ := parse_vmod(data) or {
317 eprintln(err)
318 return
319 }
320 minfo := mod_name_info(vmod_.name)
321 if final_module_path != minfo.final_module_path {
322 println('Relocating module from "${name}" to "${vmod_.name}" ( "${minfo.final_module_path}" ) ...')
323 if os.exists(minfo.final_module_path) {
324 eprintln('Warning module "${minfo.final_module_path}" already exsits!')
325 eprintln('Removing module "${minfo.final_module_path}" ...')
326 os.rmdir_all(minfo.final_module_path) or {
327 errors++
328 eprintln('Errors while removing "${minfo.final_module_path}" :')
329 eprintln(err)
330 continue
331 }
332 }
333 os.mv(final_module_path, minfo.final_module_path) or {
334 errors++
335 eprintln('Errors while relocating module "${name}" :')
336 eprintln(err)
337 os.rmdir_all(final_module_path) or {
338 errors++
339 eprintln('Errors while removing "${final_module_path}" :')
340 eprintln(err)
341 continue
342 }
343 continue
344 }
345 println('Module "${name}" relocated to "${vmod_.name}" successfully.')
346 publisher_dir := final_module_path.all_before_last(os.path_separator)
347 if os.is_dir_empty(publisher_dir) {
348 os.rmdir(publisher_dir) or {
349 errors++
350 eprintln('Errors while removing "${publisher_dir}" :')
351 eprintln(err)
352 }
353 }
354 final_module_path = minfo.final_module_path
355 }
356 name = vmod_.name
357 }
358 resolve_dependencies(name, final_module_path, module_names)
359 }
360 if errors > 0 {
361 exit(1)
362 }
363}
364
365fn vpm_once_filter(module_names []string) []string {
366 installed_modules := get_installed_modules()
367 mut toinstall := []string{}
368 for mn in module_names {
369 if mn !in installed_modules {
370 toinstall << mn
371 }
372 }
373 return toinstall
374}
375
376fn vpm_install(module_names []string, source Source) {
377 if settings.is_help {
378 help.print_and_exit('install')
379 exit(0)
380 }
381 if module_names.len == 0 {
382 eprintln('´v install´ requires *at least one* module name.')
383 exit(2)
384 }
385 match source {
386 .vpm {
387 vpm_install_from_vpm(module_names)
388 }
389 .git {
390 vpm_install_from_vcs(module_names, 'git')
391 }
392 .hg {
393 vpm_install_from_vcs(module_names, 'hg')
394 }
395 }
396}
397
398pub struct ModUpdateInfo {
399mut:
400 name string
401 final_path string
402 has_err bool
403}
404
405fn update_module(mut pp pool.PoolProcessor, idx int, wid int) &ModUpdateInfo {
406 mut result := &ModUpdateInfo{
407 name: pp.get_item[string](idx)
408 }
409 zname := url_to_module_name(result.name)
410 result.final_path = valid_final_path_of_existing_module(result.name) or { return result }
411 println('Updating module "${zname}" in "${result.final_path}" ...')
412 vcs := vcs_used_in_dir(result.final_path) or { return result }
413 if !ensure_vcs_is_installed(vcs[0]) {
414 result.has_err = true
415 println('VPM needs `${vcs}` to be installed.')
416 return result
417 }
418 path_flag := if vcs[0] == 'hg' { '-R' } else { '-C' }
419 vcs_cmd := '${vcs[0]} ${path_flag} "${result.final_path}" ${supported_vcs_update_cmds[vcs[0]]}'
420 verbose_println(' command: ${vcs_cmd}')
421 vcs_res := os.execute('${vcs_cmd}')
422 if vcs_res.exit_code != 0 {
423 result.has_err = true
424 println('Failed updating module "${zname}" in "${result.final_path}".')
425 print_failed_cmd(vcs_cmd, vcs_res)
426 return result
427 } else {
428 verbose_println(' ${vcs_res.output.trim_space()}')
429 increment_module_download_count(zname) or {
430 result.has_err = true
431 eprintln('Errors while incrementing the download count for ${zname}:')
432 }
433 }
434 return result
435}
436
437fn vpm_update(m []string) {
438 mut module_names := m.clone()
439 if settings.is_help {
440 help.print_and_exit('update')
441 exit(0)
442 }
443 if module_names.len == 0 {
444 module_names = get_installed_modules()
445 }
446 if settings.is_verbose {
447 vpm_update_verbose(module_names)
448 return
449 }
450 mut pp := pool.new_pool_processor(callback: update_module)
451 pp.work_on_items(module_names)
452 for res in pp.get_results[ModUpdateInfo]() {
453 if res.has_err {
454 exit(1)
455 }
456 resolve_dependencies(res.name, res.final_path, module_names)
457 }
458}
459
460fn vpm_update_verbose(module_names []string) {
461 mut errors := 0
462 for name in module_names {
463 zname := url_to_module_name(name)
464 final_module_path := valid_final_path_of_existing_module(name) or { continue }
465 println('Updating module "${zname}" in "${final_module_path}" ...')
466 vcs := vcs_used_in_dir(final_module_path) or { continue }
467 if !ensure_vcs_is_installed(vcs[0]) {
468 errors++
469 println('VPM needs `${vcs}` to be installed.')
470 continue
471 }
472 path_flag := if vcs[0] == 'hg' { '-R' } else { '-C' }
473 vcs_cmd := '${vcs[0]} ${path_flag} "${final_module_path}" ${supported_vcs_update_cmds[vcs[0]]}'
474 verbose_println(' command: ${vcs_cmd}')
475 vcs_res := os.execute('${vcs_cmd}')
476 if vcs_res.exit_code != 0 {
477 errors++
478 println('Failed updating module "${zname}" in "${final_module_path}" .')
479 print_failed_cmd(vcs_cmd, vcs_res)
480 continue
481 } else {
482 verbose_println(' ${vcs_res.output.trim_space()}')
483 increment_module_download_count(zname) or {
484 errors++
485 eprintln('Errors while incrementing the download count for ${zname}:')
486 }
487 }
488 resolve_dependencies(name, final_module_path, module_names)
489 }
490 if errors > 0 {
491 exit(1)
492 }
493}
494
495pub struct ModDateInfo {
496 name string
497mut:
498 outdated bool
499 exec_err bool
500}
501
502fn get_mod_date_info(mut pp pool.PoolProcessor, idx int, wid int) &ModDateInfo {
503 mut result := &ModDateInfo{
504 name: pp.get_item[string](idx)
505 }
506 final_module_path := valid_final_path_of_existing_module(result.name) or { return result }
507 vcs := vcs_used_in_dir(final_module_path) or { return result }
508 is_hg := vcs[0] == 'hg'
509 vcs_cmd_steps := supported_vcs_outdated_steps[vcs[0]]
510 mut outputs := []string{}
511 for step in vcs_cmd_steps {
512 path_flag := if is_hg { '-R' } else { '-C' }
513 cmd := '${vcs[0]} ${path_flag} "${final_module_path}" ${step}'
514 res := os.execute('${cmd}')
515 if res.exit_code < 0 {
516 verbose_println('Error command: ${cmd}')
517 verbose_println('Error details:\n${res.output}')
518 result.exec_err = true
519 return result
520 }
521 if is_hg {
522 if res.exit_code == 1 {
523 result.outdated = true
524 return result
525 }
526 } else {
527 outputs << res.output
528 }
529 }
530 // vcs[0] == 'git'
531 if !is_hg && outputs[1] != outputs[2] {
532 result.outdated = true
533 }
534 return result
535}
536
537fn get_outdated() ![]string {
538 module_names := get_installed_modules()
539 mut outdated := []string{}
540 mut pp := pool.new_pool_processor(callback: get_mod_date_info)
541 pp.work_on_items(module_names)
542 for res in pp.get_results[ModDateInfo]() {
543 if res.exec_err {
544 return error('Error while checking latest commits for "${res.name}" .')
545 }
546 if res.outdated {
547 outdated << res.name
548 }
549 }
550 return outdated
551}
552
553fn vpm_upgrade() {
554 outdated := get_outdated() or { exit(1) }
555 if outdated.len > 0 {
556 vpm_update(outdated)
557 } else {
558 println('Modules are up to date.')
559 }
560}
561
562fn vpm_outdated() {
563 outdated := get_outdated() or { exit(1) }
564 if outdated.len > 0 {
565 eprintln('Outdated modules:')
566 for m in outdated {
567 eprintln(' ${m}')
568 }
569 } else {
570 println('Modules are up to date.')
571 }
572}
573
574fn vpm_list() {
575 module_names := get_installed_modules()
576 if module_names.len == 0 {
577 eprintln('You have no modules installed.')
578 exit(0)
579 }
580 for mod in module_names {
581 println(mod)
582 }
583}
584
585fn vpm_remove(module_names []string) {
586 if settings.is_help {
587 help.print_and_exit('remove')
588 exit(0)
589 }
590 if module_names.len == 0 {
591 eprintln('´v remove´ requires *at least one* module name.')
592 exit(2)
593 }
594 for name in module_names {
595 final_module_path := valid_final_path_of_existing_module(name) or { continue }
596 eprintln('Removing module "${name}" ...')
597 verbose_println('removing folder ${final_module_path}')
598 os.rmdir_all(final_module_path) or {
599 verbose_println('error while removing "${final_module_path}": ${err.msg()}')
600 }
601 // delete author directory if it is empty
602 author := name.split('.')[0]
603 author_dir := os.real_path(os.join_path(settings.vmodules_path, author))
604 if !os.exists(author_dir) {
605 continue
606 }
607 if os.is_dir_empty(author_dir) {
608 verbose_println('removing author folder ${author_dir}')
609 os.rmdir(author_dir) or {
610 verbose_println('error while removing "${author_dir}": ${err.msg()}')
611 }
612 }
613 }
614}
615
616fn valid_final_path_of_existing_module(modulename string) ?string {
617 name := if mod := get_mod_by_url(modulename) { mod.name } else { modulename }
618 minfo := mod_name_info(name)
619 if !os.exists(minfo.final_module_path) {
620 eprintln('No module with name "${minfo.mname_normalised}" exists at ${minfo.final_module_path}')
621 return none
622 }
623 if !os.is_dir(minfo.final_module_path) {
624 eprintln('Skipping "${minfo.final_module_path}", since it is not a folder.')
625 return none
626 }
627 vcs_used_in_dir(minfo.final_module_path) or {
628 eprintln('Skipping "${minfo.final_module_path}", since it does not use a supported vcs.')
629 return none
630 }
631 return minfo.final_module_path
632}
633
634fn ensure_vmodules_dir_exist() {
635 if !os.is_dir(settings.vmodules_path) {
636 println('Creating "${settings.vmodules_path}/" ...')
637 os.mkdir(settings.vmodules_path) or { panic(err) }
638 }
639}
640
641fn vpm_help() {
642 help.print_and_exit('vpm')
643}
644
645fn vcs_used_in_dir(dir string) ?[]string {
646 mut vcs := []string{}
647 for repo_subfolder in supported_vcs_folders {
648 checked_folder := os.real_path(os.join_path(dir, repo_subfolder))
649 if os.is_dir(checked_folder) {
650 vcs << repo_subfolder.replace('.', '')
651 }
652 }
653 if vcs.len == 0 {
654 return none
655 }
656 return vcs
657}
658
659fn get_installed_modules() []string {
660 dirs := os.ls(settings.vmodules_path) or { return [] }
661 mut modules := []string{}
662 for dir in dirs {
663 adir := os.join_path(settings.vmodules_path, dir)
664 if dir in excluded_dirs || !os.is_dir(adir) {
665 continue
666 }
667 if os.exists(os.join_path(adir, 'v.mod')) && os.exists(os.join_path(adir, '.git', 'config')) {
668 // an official vlang module with a short module name, like `vsl`, `ui` or `markdown`
669 modules << dir
670 continue
671 }
672 author := dir
673 mods := os.ls(adir) or { continue }
674 for m in mods {
675 vcs_used_in_dir(os.join_path(adir, m)) or { continue }
676 modules << '${author}.${m}'
677 }
678 }
679 return modules
680}
681
682struct ModNameInfo {
683mut:
684 mname string // The-user.The-mod , *never* The-user.The-mod.git
685 mname_normalised string // the_user.the_mod
686 mname_as_path string // the_user/the_mod
687 final_module_path string // ~/.vmodules/the_user/the_mod
688}
689
690fn mod_name_info(mod_name string) ModNameInfo {
691 mut info := ModNameInfo{}
692 info.mname = if mod_name.ends_with('.git') { mod_name.replace('.git', '') } else { mod_name }
693 info.mname_normalised = info.mname.replace('-', '_').to_lower()
694 info.mname_as_path = info.mname_normalised.replace('.', os.path_separator)
695 info.final_module_path = os.real_path(os.join_path(settings.vmodules_path, info.mname_as_path))
696 return info
697}
698
699fn url_to_module_name(modulename string) string {
700 mut res := if mod := get_mod_by_url(modulename) { mod.name } else { modulename }
701 if res.ends_with('.git') {
702 res = res.replace('.git', '')
703 }
704 return res
705}
706
707fn get_all_modules() []string {
708 url := get_working_server_url()
709 r := http.get(url) or { panic(err) }
710 if r.status_code != 200 {
711 eprintln('Failed to search vpm.vlang.io. Status code: ${r.status_code}')
712 exit(1)
713 }
714 s := r.body
715 mut read_len := 0
716 mut modules := []string{}
717 for read_len < s.len {
718 mut start_token := "<a href='/mod"
719 end_token := '</a>'
720 // get the start index of the module entry
721 mut start_index := s.index_after(start_token, read_len)
722 if start_index == -1 {
723 start_token = '<a href="/mod'
724 start_index = s.index_after(start_token, read_len)
725 if start_index == -1 {
726 break
727 }
728 }
729 // get the index of the end of anchor (a) opening tag
730 // we use the previous start_index to make sure we are getting a module and not just a random 'a' tag
731 start_token = '>'
732 start_index = s.index_after(start_token, start_index) + start_token.len
733
734 // get the index of the end of module entry
735 end_index := s.index_after(end_token, start_index)
736 if end_index == -1 {
737 break
738 }
739 modules << s[start_index..end_index]
740 read_len = end_index
741 if read_len >= s.len {
742 break
743 }
744 }
745 return modules
746}
747
748fn resolve_dependencies(name string, module_path string, module_names []string) {
749 vmod_path := os.join_path(module_path, 'v.mod')
750 if !os.exists(vmod_path) {
751 return
752 }
753 data := os.read_file(vmod_path) or { return }
754 vmod_ := parse_vmod(data) or {
755 eprintln(err)
756 return
757 }
758 mut deps := []string{}
759 // filter out dependencies that were already specified by the user
760 for d in vmod_.deps {
761 if d !in module_names {
762 deps << d
763 }
764 }
765 if deps.len > 0 {
766 println('Resolving ${deps.len} dependencies for module "${name}" ...')
767 verbose_println('Found dependencies: ${deps}')
768 vpm_install(deps, Source.vpm)
769 }
770}
771
772fn parse_vmod(data string) !Vmod {
773 manifest := vmod.decode(data) or { return error('Parsing v.mod file failed, ${err}') }
774 mut vmod_ := Vmod{}
775 vmod_.name = manifest.name
776 vmod_.version = manifest.version
777 vmod_.deps = manifest.dependencies
778 return vmod_
779}
780
781fn get_working_server_url() string {
782 server_urls := if settings.server_urls.len > 0 {
783 settings.server_urls
784 } else {
785 vpm_server_urls
786 }
787 for url in server_urls {
788 verbose_println('Trying server url: ${url}')
789 http.head(url) or {
790 verbose_println(' ${url} failed.')
791 continue
792 }
793 return url
794 }
795 panic('No responding vpm server found. Please check your network connectivity and try again later.')
796}
797
798// settings context:
799struct VpmSettings {
800mut:
801 is_help bool
802 is_verbose bool
803 server_urls []string
804 vmodules_path string
805}
806
807const (
808 settings = &VpmSettings{}
809)
810
811fn init_settings() {
812 mut s := &VpmSettings(unsafe { nil })
813 unsafe {
814 s = settings
815 }
816 s.is_help = '-h' in os.args || '--help' in os.args || 'help' in os.args
817 s.is_verbose = '-v' in os.args
818 s.server_urls = cmdline.options(os.args, '-server-url')
819 s.vmodules_path = os.vmodules_dir()
820}
821
822fn verbose_println(s string) {
823 if settings.is_verbose {
824 println(s)
825 }
826}
827
828fn get_mod_by_url(name string) !Mod {
829 if purl := urllib.parse(name) {
830 verbose_println('purl: ${purl}')
831 mod := Mod{
832 name: purl.path.trim_left('/').trim_right('/').replace('/', '.')
833 url: name
834 }
835 verbose_println(mod.str())
836 return mod
837 }
838 return error('invalid url: ${name}')
839}
840
841fn get_module_meta_info(name string) !Mod {
842 if mod := get_mod_by_url(name) {
843 return mod
844 }
845 mut errors := []string{}
846
847 for server_url in vpm_server_urls {
848 modurl := server_url + '/api/packages/${name}'
849 verbose_println('Retrieving module metadata from: "${modurl}" ...')
850 r := http.get(modurl) or {
851 errors << 'Http server did not respond to our request for "${modurl}" .'
852 errors << 'Error details: ${err}'
853 continue
854 }
855 if r.status_code == 404 || r.body.trim_space() == '404' {
856 errors << 'Skipping module "${name}", since "${server_url}" reported that "${name}" does not exist.'
857 continue
858 }
859 if r.status_code != 200 {
860 errors << 'Skipping module "${name}", since "${server_url}" responded with ${r.status_code} http status code. Please try again later.'
861 continue
862 }
863 s := r.body
864 if s.len > 0 && s[0] != `{` {
865 errors << 'Invalid json data'
866 errors << s.trim_space().limit(100) + ' ...'
867 continue
868 }
869 mod := json.decode(Mod, s) or {
870 errors << 'Skipping module "${name}", since its information is not in json format.'
871 continue
872 }
873 if '' == mod.url || '' == mod.name {
874 errors << 'Skipping module "${name}", since it is missing name or url information.'
875 continue
876 }
877 return mod
878 }
879 return error(errors.join_lines())
880}
881
882fn increment_module_download_count(name string) ! {
883 mut errors := []string{}
884
885 for server_url in vpm_server_urls {
886 modurl := server_url + '/api/packages/${name}/incr_downloads'
887 r := http.post(modurl, '') or {
888 errors << 'Http server did not respond to our request for "${modurl}" .'
889 errors << 'Error details: ${err}'
890 continue
891 }
892 if r.status_code != 200 {
893 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.'
894 continue
895 }
896 return
897 }
898 return error(errors.join_lines())
899}
900
901fn vpm_show(module_names []string) {
902 installed_modules := get_installed_modules()
903 for module_name in module_names {
904 if module_name !in installed_modules {
905 module_meta_info := get_module_meta_info(module_name) or { continue }
906 print('
907Name: ${module_meta_info.name}
908Homepage: ${module_meta_info.url}
909Downloads: ${module_meta_info.nr_downloads}
910Installed: False
911--------
912')
913 continue
914 }
915 path := os.join_path(os.vmodules_dir(), module_name.replace('.', os.path_separator))
916 mod := vmod.from_file(os.join_path(path, 'v.mod')) or { continue }
917 print('Name: ${mod.name}
918Version: ${mod.version}
919Description: ${mod.description}
920Homepage: ${mod.repo_url}
921Author: ${mod.author}
922License: ${mod.license}
923Location: ${path}
924Requires: ${mod.dependencies.join(', ')}
925--------
926')
927 }
928}