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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

tools.vpm: add manifest fetch for hg repositories, add test #20107

Merged
merged 6 commits into from
Dec 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/macos_ci.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything would be fine here. This just makes sure a name is returned when a localhost address is fetched during tests that doesn't contain a path after the domain. The eventual module name is derived from the manifest.

}
return error('failed to retrieve module name for `${url}`.')
}
vpm_log(@FILE_LINE, @FN, 'raw_url: ${raw_url}; publisher: ${publisher}; name: ${name}')
Expand Down
63 changes: 55 additions & 8 deletions cmd/tools/vpm/install_test.v
Original file line number Diff line number Diff line change
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}')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice ... I did not know, that git init folder will create it.

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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module test_utils

import os
import net
import time

fn hg_serve(hg_path string, path string) (&os.Process, int) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, this should have needed pub. I am not sure, why it did not fail to compile... Perhaps because it is used in a test file?

mut port := 8000
Copy link
Member

@spytheman spytheman Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start probing from some more obscure port. 8000 and 8080 are very likely to be taken by other tests, between the time you check, and the time the mercurial server starts.

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
}