Skip to content

Commit 1d0d86d

Browse files
authored
all: veb host controller (#28019), v new/init hang (#28061), v up libgc dependency (#27148) (#28089)
1 parent 32f44b9 commit 1d0d86d

11 files changed

Lines changed: 146 additions & 17 deletions
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Regression test for https://github.com/vlang/v/issues/27148
2+
// The diagnostic tools (vself, vup, vdoctor, vsymlink) are compiled with `-g` so
3+
// their crash backtraces have .v line numbers. On macOS the default GC (boehm)
4+
// made them link `@rpath/libgc.dylib`; if that dylib could not be found, the
5+
// tool failed to even start with a dynamic loader error. They are now compiled
6+
// with `-gc none` as well, so they carry no such runtime dependency.
7+
import os
8+
9+
fn test_vdoctor_has_no_libgc_dependency() {
10+
$if !macos {
11+
// The dynamic-loader failure and the `otool -L` check are macOS specific.
12+
return
13+
}
14+
otool := os.find_abs_path_of_executable('otool') or {
15+
eprintln('skipping test, `otool` is not available')
16+
return
17+
}
18+
vexe := @VEXE
19+
// `v doctor` builds the vdoctor tool through `util.launch_tool` (the code path
20+
// that received the `-gc none` fix) and then runs it.
21+
res := os.execute('${os.quoted_path(vexe)} doctor')
22+
assert res.exit_code == 0, res.output
23+
vdoctor_exe := os.join_path(os.dir(vexe), 'cmd', 'tools', 'vdoctor')
24+
if !os.exists(vdoctor_exe) {
25+
eprintln('skipping test, `${vdoctor_exe}` was not produced')
26+
return
27+
}
28+
libs := os.execute('${os.quoted_path(otool)} -L ${os.quoted_path(vdoctor_exe)}')
29+
assert libs.exit_code == 0, libs.output
30+
assert !libs.output.contains('libgc'), 'vdoctor must not depend on libgc:\n${libs.output}'
31+
}

‎cmd/tools/vcreate/vcreate.v‎

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ fn main() {
6666
description: [
6767
'Creates a new V project in a directory with the specified project name.',
6868
'',
69-
'A setup prompt is started to create a `v.mod` file with the projects metadata.',
70-
'The <project_name> argument can be omitted and entered in the prompts dialog.',
69+
'When run in a terminal, a setup prompt is started to create a `v.mod` file with',
70+
'the projects metadata; the <project_name> argument can then be omitted and',
71+
'entered in the prompts dialog. When stdin is not a terminal (piped/redirected,',
72+
'e.g. in CI) the prompts are skipped, defaults are used, and <project_name> is',
73+
'required.',
7174
'If git is installed, `git init` will be performed during the setup.',
7275
].join_lines()
7376
parent: &Command{
@@ -84,6 +87,7 @@ fn main() {
8487
'Sets up a V project within the current directory.',
8588
'',
8689
"If no `v.mod` exists, a setup prompt is started to create one with the project's metadata.",
90+
'The prompts run only when stdin is a terminal; otherwise the defaults are used.',
8791
'If no `.v` file exists, a project template is generated. If the current directory is not a',
8892
'git project and git is installed, `git init` will be performed during the setup.',
8993
].join_lines()
@@ -148,12 +152,23 @@ fn init_project(cmd Command) ! {
148152
c.create_git_repo('.')
149153
}
150154

155+
// prompt_input asks for a line of input, but only when stdin is a terminal.
156+
// When it is not (piped input, CI, some IDE terminals), the interactive prompt
157+
// would block forever waiting for input that never arrives, making
158+
// `v new`/`v init` appear to hang, so return `default` instead. See #28061.
159+
fn prompt_input(prompt string, default string) string {
160+
if os.is_atty(0) == 0 {
161+
return default
162+
}
163+
return os.input(prompt)
164+
}
165+
151166
fn (mut c Create) prompt(args []string) {
152167
if c.name == '' {
153-
c.name = check_name(args[0] or { os.input('Input your project name: ') })
168+
c.name = check_name(args[0] or { prompt_input('Input your project name: ', '') })
154169
if c.name == '' {
155170
eprintln('')
156-
cerror('project name cannot be empty')
171+
cerror('project name cannot be empty; pass it on the command line, e.g. `v new my_project`')
157172
exit(1)
158173
}
159174
if c.name.contains('-') {
@@ -167,14 +182,14 @@ fn (mut c Create) prompt(args []string) {
167182
exit(3)
168183
}
169184
}
170-
c.description = os.input('Input your project description: ')
185+
c.description = prompt_input('Input your project description: ', '')
171186
default_version := '0.0.0'
172-
c.version = os.input('Input your project version: (${default_version}) ')
187+
c.version = prompt_input('Input your project version: (${default_version}) ', default_version)
173188
if c.version == '' {
174189
c.version = default_version
175190
}
176191
default_license := 'MIT'
177-
c.license = os.input('Input your project license: (${default_license}) ')
192+
c.license = prompt_input('Input your project license: (${default_license}) ', default_license)
178193
if c.license == '' {
179194
c.license = default_license
180195
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// vtest build: !windows
2+
// Regression test for https://github.com/vlang/v/issues/28061
3+
// `v new <name>` / `v init` used to block on interactive prompts even when stdin
4+
// is not a terminal, so piping/redirecting stdin made them appear to hang. They
5+
// must instead fall back to defaults for the metadata prompts.
6+
import os
7+
import v.vmod
8+
9+
const vexe = @VEXE
10+
11+
fn test_new_non_interactive_uses_defaults() {
12+
tdir := os.join_path(os.vtmp_dir(), 'vcreate_noninteractive_${os.getpid()}')
13+
os.rmdir_all(tdir) or {}
14+
os.mkdir_all(tdir) or { panic(err) }
15+
defer {
16+
os.rmdir_all(tdir) or {}
17+
}
18+
old_wd := os.getwd()
19+
os.chdir(tdir) or { panic(err) }
20+
defer {
21+
os.chdir(old_wd) or {}
22+
}
23+
name := 'my_ni_project'
24+
// `< /dev/null` guarantees a non-terminal stdin that returns EOF immediately.
25+
res := os.execute('${os.quoted_path(vexe)} new ${name} < /dev/null')
26+
assert res.exit_code == 0, res.output
27+
mod := vmod.from_file(os.join_path(tdir, name, 'v.mod')) or {
28+
assert false, err.str()
29+
return
30+
}
31+
assert mod.name == name
32+
assert mod.description == ''
33+
assert mod.version == '0.0.0'
34+
assert mod.license == 'MIT'
35+
}

‎cmd/tools/vup.v‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ fn (app App) recompile_vup() bool {
129129
eprintln('> Skipping recompiling vup.v, `${vexe_path}` is missing.')
130130
return false
131131
}
132-
vup_result := os.execute('${os.quoted_path(vexe_path)} -g cmd/tools/vup.v')
132+
// `-gc none` matches how `util.launch_tool` builds vup, so this self-rebuild
133+
// after a successful update does not overwrite the GC-free executable with a
134+
// libgc-linked one (which could fail to start in the dynamic loader). See #27148.
135+
vup_result := os.execute('${os.quoted_path(vexe_path)} -g -gc none cmd/tools/vup.v')
133136
if vup_result.exit_code != 0 {
134137
eprintln('> Failed recompiling vup.v .')
135138
eprintln(vup_result.output)

‎doc/docs.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ by using any of the following commands in a terminal:
8686

8787
The `v new --web` template uses `veb`, V's web framework.
8888

89+
When run in a terminal, `v new` and `v init` interactively prompt for the project's
90+
description, version and license. When stdin is *not* a terminal (for example when the
91+
input is piped or redirected, as in CI), the prompts are skipped and the defaults are
92+
used instead of blocking on input; in that case the project name must be passed as an
93+
argument, e.g. `v new abc`.
94+
8995
## Table of Contents
9096

9197
<table>
@@ -6323,6 +6329,9 @@ Package are up to date.
63236329
Complete!
63246330
```
63256331

6332+
The prompts above appear only when running in a terminal; with a non-terminal
6333+
stdin the defaults are used instead (see [Getting started](#getting-started)).
6334+
63266335
Example `v.mod`:
63276336
```v ignore
63286337
Module {

‎vlib/v/tests/local_submodule_in_project_root_codegen_test.v‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ fn write_project() {
2121
basepath := project_path()
2222
os.rmdir_all(basepath) or {}
2323
os.mkdir_all(basepath) or { panic(err) }
24-
os.write_file(os.join_path(basepath, 'v.mod'), "Module {\n\tname: 'issue_28074'\n\tversion: '0.0.1'\n}\n") or {
25-
panic(err)
26-
}
24+
os.write_file(os.join_path(basepath, 'v.mod'),
25+
"Module {\n\tname: 'issue_28074'\n\tversion: '0.0.1'\n}\n") or { panic(err) }
2726
os.write_file(os.join_path(basepath, 'main.v'),
2827
['module main', '', 'import calculator', '', 'fn main() {', '\tprintln(calculator.evaluate(calculator.Op.add))', '}'].join('\n') +
2928
'\n') or { panic(err) }

‎vlib/v/util/module.v‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ fn mod_path_to_full_name_with_options(pref_ &pref.Preferences, mod string, path
334334
rel_mod_path := path.replace(abs_pref_path.all_before_last(os.path_separator) +
335335
os.path_separator, '')
336336
if rel_mod_path != path {
337-
mod_full_name := normalize_base_url_mod_name(rel_mod_path.replace(os.path_separator,
338-
'.'), path)
337+
mod_full_name :=
338+
normalize_base_url_mod_name(rel_mod_path.replace(os.path_separator, '.'), path)
339339
// A file that sits directly in the project root maps to `path` ending in
340340
// `/.`, so `rel_mod_path` becomes `.` and yields an empty/dotted module
341341
// name. That is not a valid qualified name (it later produces `module `

‎vlib/v/util/util.v‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,11 @@ pub fn launch_tool(is_verbose bool, tool_name string, args []string) {
195195
// it is better to always compile them with -g, so that in
196196
// case these tools do crash/panic, their backtraces will have
197197
// .v line numbers, to ease diagnostic in #bugs and issues.
198-
compilation_command += ' -g '
198+
// `-gc none` also keeps these small, short-lived tools from gaining a
199+
// runtime dependency on `libgc.dylib`/`libgc.so`, which can be missing or
200+
// unfound (e.g. a vroot path with spaces) and then make the tool fail to
201+
// even start with a dynamic loader error. See #27148.
202+
compilation_command += ' -g -gc none '
199203
}
200204
if tool_name == 'vfmt' {
201205
compilation_command += ' -d vfmt '

‎vlib/veb/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1094,7 +1094,7 @@ pub fn (app &Example) index(mut ctx Context) veb.Result {
10941094
mut example_app := &Example{}
10951095
// set the controllers hostname to 'example.com' and handle all routes starting with '/',
10961096
// we handle requests with any route to 'example.com'
1097-
app.register_controller[Example, Context]('example.com', '/', mut example_app)!
1097+
app.register_host_controller[Example, Context]('example.com', '/', mut example_app)!
10981098
```
10991099

11001100
## Context Methods

‎vlib/veb/controller.v‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ pub fn (mut c Controller) register_host_controller[A, X](host string, path strin
7272
}
7373

7474
// controller_host generates a controller which only handles incoming requests from the `host` domain
75-
pub fn controller_host[A, X](host string, path string, mut global_app A) &ControllerPath {
76-
mut ctrl := controller[A, X](path, mut global_app)
75+
pub fn controller_host[A, X](host string, path string, mut global_app A) !&ControllerPath {
76+
mut ctrl := controller[A, X](path, mut global_app)!
7777
ctrl.host = host
7878
return ctrl
7979
}

0 commit comments

Comments
 (0)