Skip to content

Commit

Permalink
tools.vpm: add manifest fetch for hg repositories, add test (#20107)
Browse files Browse the repository at this point in the history
  • Loading branch information
ttytm committed Dec 7, 2023
1 parent 5b74f3a commit 7b44bb9
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/macos_ci.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Install dependencies
run: |
echo "PKG_CONFIG_PATH is '$PKG_CONFIG_PATH'"
brew install libpq openssl
brew install libpq openssl mercurial
export LIBRARY_PATH="$LIBRARY_PATH:/usr/local/opt/openssl/lib/"
echo "LIBRARY_PATH is '$LIBRARY_PATH'"
- name: Build V
Expand Down
3 changes: 3 additions & 0 deletions cmd/tools/vpm/common.v
Expand Up @@ -106,6 +106,9 @@ fn get_mod_vpm_info(name string) !ModuleVpmInfo {
fn get_ident_from_url(raw_url string) !(string, string) {
url := urllib.parse(raw_url) or { return error('failed to parse module URL `${raw_url}`.') }
publisher, mut name := url.path.trim_left('/').rsplit_once('/') or {
if settings.vcs == .hg && raw_url.count(':') > 1 {
return '', 'test_module'
}
return error('failed to retrieve module name for `${url}`.')
}
name = name.trim_string_right('.git')
Expand Down
63 changes: 55 additions & 8 deletions cmd/tools/vpm/install_test.v
Expand Up @@ -4,6 +4,7 @@ module main
import os
import rand
import v.vmod
import test_utils

// Running tests appends a tsession path to VTMP, which is automatically cleaned up after the test.
// The following will result in e.g. `$VTMP/tsession_7fe8e93bd740_1612958707536/test-vmodules/`.
Expand All @@ -22,7 +23,7 @@ fn testsuite_end() {

fn get_vmod(path string) vmod.Manifest {
return vmod.from_file(os.join_path(test_path, path, 'v.mod')) or {
eprintln('Failed to parse v.mod for `${path}`')
eprintln('Failed to parse v.mod for `${path}`. ${err}')
exit(1)
}
}
Expand Down Expand Up @@ -166,15 +167,14 @@ fn test_install_potentially_conflicting() {

fn test_get_installed_version() {
test_project_path := os.join_path(test_path, 'test_project')
os.mkdir_all(test_project_path)!
mut res := os.execute('git init ${test_project_path}')
assert res.exit_code == 0, res.str()
os.chdir(test_project_path)!
os.write_file('v.mod', '')!
if os.getenv('CI') != '' {
os.execute_or_exit('git config --global user.email "v@vi.com"')
os.execute_or_exit('git config --global user.name "V CI"')
if os.execute('git config user.name').exit_code == 1 {
os.execute_or_exit('git config user.email "ci@vlang.io"')
os.execute_or_exit('git config user.name "V CI"')
}
mut res := os.execute('git init')
assert res.exit_code == 0, res.str()
os.write_file('v.mod', '')!
res = os.execute('git add .')
assert res.exit_code == 0, res.str()
res = os.execute('git commit -m "initial commit"')
Expand Down Expand Up @@ -211,3 +211,50 @@ fn test_get_installed_version() {
assert mod.is_installed
assert mod.installed_version == 'v0.1.0'
}

fn test_install_from_hg_url() ! {
hg_path := os.find_abs_path_of_executable('hg') or {
eprintln('skipping test, since `hg` is not executable.')
return
}
test_module_path := os.join_path(os.temp_dir(), rand.ulid(), 'hg_test_module')
defer {
os.rmdir_all(test_module_path) or {}
}
// Initialize project without manifest file.
mut res := os.execute('hg init ${test_module_path}')
assert res.exit_code == 0, res.str()
mut p, mut port := test_utils.hg_serve(hg_path, test_module_path)
// Trying to install it should fail.
res = os.execute('${vexe} install --hg http://127.0.0.1:${port}')
if res.exit_code != 1 {
p.signal_kill()
assert false, res.str()
}
assert res.output.contains('failed to find `v.mod`'), res.output
p.signal_kill()
// Create and commit manifest.
name := 'my_awesome_v_module'
version := '1.0.0'
os.write_file(os.join_path(test_module_path, 'v.mod'), "Module{
name: '${name}'
version: '${version}'
}")!
os.chdir(test_module_path)!
res = os.execute('hg add')
assert res.exit_code == 0, res.str()
res = os.execute('hg --config ui.username=v_ci commit -m "add v.mod"')
assert res.exit_code == 0, res.str()
p, port = test_utils.hg_serve(hg_path, test_module_path)
// Trying to install the module should work now.
res = os.execute('${vexe} install --hg http://127.0.0.1:${port}')
if res.exit_code != 0 {
p.signal_kill()
assert false, res.str()
}
p.signal_kill()
// Get manifest from the vmodules directory.
manifest := get_vmod(name)
assert manifest.name == name
assert manifest.version == version
}
2 changes: 1 addition & 1 deletion cmd/tools/vpm/install_version_input_test.v
Expand Up @@ -27,7 +27,7 @@ fn testsuite_end() {

fn get_vmod(path string) vmod.Manifest {
return vmod.from_file(os.join_path(test_path, path, 'v.mod')) or {
eprintln('Failed to parse v.mod for `${path}`')
eprintln('Failed to parse v.mod for `${path}`. ${err}')
exit(1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/tools/vpm/install_version_test.v
Expand Up @@ -20,7 +20,7 @@ fn testsuite_end() {

fn get_vmod(path string) vmod.Manifest {
return vmod.from_file(os.join_path(test_path, path, 'v.mod')) or {
eprintln('Failed to parse v.mod for `${path}`')
eprintln('Failed to parse v.mod for `${path}`. ${err}')
exit(1)
}
}
Expand Down
28 changes: 16 additions & 12 deletions cmd/tools/vpm/parse.v
Expand Up @@ -54,7 +54,7 @@ fn (mut p Parser) parse_module(m string) {
false
}
mut mod := if is_http || ident.starts_with('https://') {
// External module. The idenifier is an URL.
// External module. The identifier is an URL.
publisher, name := get_ident_from_url(ident) or {
vpm_error(err.msg())
p.errors++
Expand Down Expand Up @@ -175,10 +175,11 @@ fn (mut m Module) get_installed() {

fn fetch_manifest(name string, url string, version string, is_git bool) !vmod.Manifest {
if !is_git {
// TODO: fetch manifest for mercurial repositories
return vmod.Manifest{
name: name
}
manifest_url := '${url}/raw-file/tip/v.mod'
vpm_log(@FILE_LINE, @FN, 'manifest_url: ${manifest_url}')
return get_manifest_from_resp(http.get(manifest_url) or {
return error('failed to retrieve manifest. ${err}')
})
}
v := if version != '' {
version
Expand All @@ -196,13 +197,16 @@ fn fetch_manifest(name string, url string, version string, is_git bool) !vmod.Ma
for i, raw_p in raw_paths {
manifest_url := '${url_}/${raw_p}/${v}/v.mod'
vpm_log(@FILE_LINE, @FN, 'manifest_url ${i}: ${manifest_url}')
raw_manifest_resp := http.get(manifest_url) or { continue }
if raw_manifest_resp.status_code != 200 {
return error('unsuccessful response status `${raw_manifest_resp.status_code}`.')
}
return vmod.decode(raw_manifest_resp.body) or {
return error('failed to decode manifest `${raw_manifest_resp.body}`. ${err}')
}
return get_manifest_from_resp(http.get(manifest_url) or { continue })
}
return error('failed to retrieve manifest.')
}

fn get_manifest_from_resp(resp http.Response) !vmod.Manifest {
if resp.status_code != 200 {
return error('unsuccessful response status `${resp.status_code}`.')
}
return vmod.decode(resp.body) or {
return error('failed to decode manifest `${resp.body}`. ${err}')
}
}
35 changes: 35 additions & 0 deletions cmd/tools/vpm/test_utils/utils.v
@@ -0,0 +1,35 @@
module test_utils

import os
import net
import time

fn hg_serve(hg_path string, path string) (&os.Process, int) {
mut port := 8000
for {
if mut l := net.listen_tcp(.ip6, ':${port}') {
l.close() or { panic(err) }
break
}
port++
}
mut p := os.new_process(hg_path)
p.set_work_folder(path)
p.set_args(['serve', '--print-url', '--port', port.str()])
p.set_redirect_stdio()
p.run()
mut i := 0
for p.is_alive() {
if i == 500 { // Wait max. 5 seconds.
p.signal_kill()
eprintln('Failed to serve mercurial repository on localhost.')
exit(1)
}
if p.stdout_read().contains(':${port}') {
break
}
time.sleep(10 * time.millisecond)
i++
}
return p, port
}

0 comments on commit 7b44bb9

Please sign in to comment.