Skip to content

Commit 0f1eed4

Browse files
committed
Fix option parsing conflict with build request.
Because of our use of the relaxed option parsing of Lyra, to account for the B2 adhoc options, there was a conflict where invalid options would feed into the build request. That caused all kinds of confusion for the user seeing messages about unknown targets or invalid requests instead of proper option errors. The fix is to only feed the request parsing unrecognized arguments. And fix the option parsing so that it checks and errors when it encounters bad known option usage. This change also fixes an inverted interpretation of the `---keep-going` option. fixes #562
1 parent 09fd2d4 commit 0f1eed4

8 files changed

Lines changed: 153 additions & 65 deletions

File tree

doc/src/history.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
-- _Paolo Pastori_
4040
* Fix GLOB_ARCHIVE builtin rule to work with BSD archive (ar).
4141
-- _Paolo Pastori_
42+
* Fix parsing of options to not conflict with command line build request.
43+
-- _René Ferdinand Rivera Morell_
44+
* Fix inversion of logic for `--keep-going` option.
45+
-- _Paolo Pastori_
4246

4347
== Version 5.4.2
4448

src/build-system.jam

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Copyright 2003, 2005, 2007 Dave Abrahams
2-
# Copyright 2006, 2007 Rene Rivera
2+
# Copyright 2006-2026 René Ferdinand Rivera Morell
33
# Copyright 2003, 2004, 2005, 2006 Vladimir Prus
44
# Distributed under the Boost Software License, Version 1.0.
55
# (See accompanying file LICENSE.txt or copy at
@@ -39,9 +39,6 @@ import virtual-target ;
3939
#
4040
################################################################################
4141

42-
# Shortcut used in this module for accessing used command-line parameters.
43-
.argv = [ modules.peek : ARGV ] ;
44-
4542
# Flag indicating we should display additional debugging information related to
4643
# locating and loading Boost Build configuration files.
4744
.debug-config = [ args.get-arg debug-configuration ] ;
@@ -523,7 +520,7 @@ local rule process-explicit-toolset-requests
523520
local extra-properties ;
524521

525522
local option-toolsets = [ regex.split-list [ args.get-arg toolsets ] : "," ] ;
526-
local feature-toolsets = [ regex.split-list [ MATCH ^toolset=(.*)$ : $(.argv) ] : "," ] ;
523+
local feature-toolsets = [ regex.split-list [ MATCH ^toolset=(.*)$ : [ args.extra-args ] ] : "," ] ;
527524

528525
for local t in $(option-toolsets) $(feature-toolsets)
529526
{
@@ -564,7 +561,7 @@ local rule process-explicit-toolset-requests
564561
# Make sure we get an appropriate property into the build request in
565562
# case toolset has been specified using the "--toolset=..." command-line
566563
# option form.
567-
if ! $(t) in $(.argv) $(feature-toolsets)
564+
if ! $(t) in [ args.extra-args ] $(feature-toolsets)
568565
{
569566
if $(.debug-config)
570567
{
@@ -683,7 +680,7 @@ local rule should-clean-project ( project )
683680
# target ids containing explicit project references. See what to do about
684681
# those as such 'lazy loading' may cause problems that are then extremely
685682
# difficult to debug.
686-
local build-request = [ build-request.from-command-line $(.argv)
683+
local build-request = [ build-request.from-extra-args [ args.extra-args ]
687684
$(extra-properties) ] ;
688685
local target-ids = [ $(build-request).get-at 1 ] ;
689686
local properties = [ $(build-request).get-at 2 ] ;
@@ -895,7 +892,7 @@ local rule should-clean-project ( project )
895892
local os = [ modules.peek : OS OSPLAT JAMUNAME ] "" ;
896893
local timestamp = [ modules.peek : JAMDATE ] ;
897894
local cwd = [ PWD ] ;
898-
local command = $(.argv) ;
895+
local command = [ modules.peek : ARGV ] ;
899896
local bb-version = [ version.boost-build ] ;
900897
.header on $(xml-file) =
901898
"<?xml version=\"1.0\" encoding=\"utf-8\"?>"

src/build/build-request.jam

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2002 Dave Abrahams
2+
# Copyright 2026 René Ferdinand Rivera Morell
23
# Distributed under the Boost Software License, Version 1.0.
34
# (See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
45

@@ -172,37 +173,24 @@ local rule looks-like-implicit-value ( v )
172173
# specified in the command line, and second is the set of requested build
173174
# properties. Expects all the project files to already be loaded.
174175
#
175-
rule from-command-line ( command-line * )
176+
rule from-extra-args ( extra-args * )
176177
{
177178
local targets ;
178179
local properties ;
179180

180-
command-line = $(command-line[2-]) ;
181-
local skip-next = ;
182-
for local e in $(command-line)
181+
for local e in $(extra-args)
183182
{
184-
if $(skip-next)
183+
# Build request spec either has "=" in it or completely consists of
184+
# implicit feature values.
185+
local fs = feature-space ;
186+
if [ MATCH "(.+=.*)" : $(e) ]
187+
|| [ looks-like-implicit-value $(e:D=) : $(feature-space) ]
185188
{
186-
skip-next = ;
189+
properties += $(e) ;
187190
}
188-
else if ! [ MATCH ^(-) : $(e) ]
191+
else if $(e)
189192
{
190-
# Build request spec either has "=" in it or completely consists of
191-
# implicit feature values.
192-
local fs = feature-space ;
193-
if [ MATCH "(.+=.*)" : $(e) ]
194-
|| [ looks-like-implicit-value $(e:D=) : $(feature-space) ]
195-
{
196-
properties += $(e) ;
197-
}
198-
else if $(e)
199-
{
200-
targets += $(e) ;
201-
}
202-
}
203-
else if [ MATCH "^(-[-ldjfsto])$" : $(e) ]
204-
{
205-
skip-next = true ;
193+
targets += $(e) ;
206194
}
207195
}
208196
return [ new vector
@@ -361,52 +349,52 @@ rule __test__ ( )
361349

362350
try ;
363351
{
364-
r = [ build-request.from-command-line bjam gcc/debug runtime-link=dynamic/static ] ;
352+
r = [ build-request.from-extra-args gcc/debug runtime-link=dynamic/static ] ;
365353
build-request.convert-command-line-elements [ $(r).get-at 2 ] ;
366354
}
367355
catch \"static\" is not an implicit feature value ;
368356

369-
r = [ build-request.from-command-line bjam debug runtime-link=dynamic ] ;
357+
r = [ build-request.from-extra-args debug runtime-link=dynamic ] ;
370358
assert.equal [ $(r).get-at 1 ] : ;
371359
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic ;
372360

373361
assert.equal
374362
[ build-request.convert-command-line-elements debug runtime-link=dynamic ]
375363
: debug <runtime-link>dynamic ;
376364

377-
r = [ build-request.from-command-line bjam -d2 --debug debug target runtime-link=dynamic ] ;
365+
r = [ build-request.from-extra-args debug target runtime-link=dynamic ] ;
378366
assert.equal [ $(r).get-at 1 ] : target ;
379367
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic ;
380368

381369
assert.equal
382370
[ build-request.convert-command-line-elements debug runtime-link=dynamic ]
383371
: debug <runtime-link>dynamic ;
384372

385-
r = [ build-request.from-command-line bjam debug runtime-link=dynamic,static ] ;
373+
r = [ build-request.from-extra-args debug runtime-link=dynamic,static ] ;
386374
assert.equal [ $(r).get-at 1 ] : ;
387375
assert.equal [ $(r).get-at 2 ] : debug runtime-link=dynamic,static ;
388376

389377
assert.equal
390378
[ build-request.convert-command-line-elements debug runtime-link=dynamic,static ]
391379
: debug <runtime-link>dynamic <runtime-link>static ;
392380

393-
r = [ build-request.from-command-line bjam debug gcc/runtime-link=dynamic,static ] ;
381+
r = [ build-request.from-extra-args debug gcc/runtime-link=dynamic,static ] ;
394382
assert.equal [ $(r).get-at 1 ] : ;
395383
assert.equal [ $(r).get-at 2 ] : debug gcc/runtime-link=dynamic,static ;
396384

397385
assert.equal
398386
[ build-request.convert-command-line-elements debug gcc/runtime-link=dynamic,static ]
399387
: debug gcc/<runtime-link>dynamic gcc/<runtime-link>static ;
400388

401-
r = [ build-request.from-command-line bjam msvc gcc,borland/runtime-link=static ] ;
389+
r = [ build-request.from-extra-args msvc gcc,borland/runtime-link=static ] ;
402390
assert.equal [ $(r).get-at 1 ] : ;
403391
assert.equal [ $(r).get-at 2 ] : msvc gcc,borland/runtime-link=static ;
404392

405393
assert.equal
406394
[ build-request.convert-command-line-elements msvc gcc,borland/runtime-link=static ]
407395
: msvc gcc/<runtime-link>static borland/<runtime-link>static ;
408396

409-
r = [ build-request.from-command-line bjam gcc-3.0 ] ;
397+
r = [ build-request.from-extra-args gcc-3.0 ] ;
410398
assert.equal [ $(r).get-at 1 ] : ;
411399
assert.equal [ $(r).get-at 2 ] : gcc-3.0 ;
412400

src/engine/jam.cpp

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
/* This file is ALSO:
1616
* Copyright 2001-2004 David Abrahams.
17-
* Copyright 2018-2025 Rene Rivera
17+
* Copyright 2018-2026 René Ferdinand Rivera Morell
1818
* Distributed under the Boost Software License, Version 1.0.
1919
* (See accompanying file LICENSE.txt or copy at
2020
* https://www.bfgroup.xyz/b2/LICENSE.txt)
@@ -222,6 +222,18 @@ struct args_data
222222
std::string out_filename;
223223
} args_data;
224224

225+
namespace b2 { namespace args {
226+
list_ref extra_args()
227+
{
228+
list_ref result;
229+
for (auto const & a : args_data.extra_args)
230+
{
231+
result.push_back(a);
232+
}
233+
return result;
234+
}
235+
}} // namespace b2::args
236+
225237
int guarded_main(int argc, char * argv[])
226238
{
227239
int status = 0;
@@ -230,8 +242,18 @@ int guarded_main(int argc, char * argv[])
230242
module_t * environ_module;
231243
b2::system_info sys_info;
232244

245+
// On each reparse of arguments we need to reset the data we collect of
246+
// arguments.
247+
b2::args::set_reparse_callback([]() { args_data = { }; });
248+
233249
auto & cli = b2::args::lyra_cli().relaxed();
234250

251+
// In specifying options we do validation at the time of parsing the option
252+
// value. We need to do it this way as we use the relaxed option parser
253+
// which will ignore options it fails on, for example by not being able to
254+
// convert the value or otherwise. Hence we do our own parse checks and
255+
// failures.
256+
235257
cli |= lyra::arg(args_data.extra_args, "request")
236258
.help("Targets, requirements, etc.")
237259
.choices([](const std::string & v) {
@@ -266,40 +288,54 @@ int guarded_main(int argc, char * argv[])
266288
/* Undocumented -p3 (acts like both -p1 -p2) means separate pipe action
267289
* stdout and stderr.
268290
*/
269-
cli |= lyra::opt(globs.pipe_action, "x")
291+
cli |= lyra::opt(
292+
[](const std::string & v) {
293+
if (!lyra::detail::from_string(v, globs.pipe_action)
294+
|| globs.pipe_action < 0 || globs.pipe_action > 3)
295+
{
296+
out_printf("error: Invalid value for the '-p' option.\n");
297+
b2::clean_exit(EXITBAD);
298+
}
299+
},
300+
"x")
270301
.name("-p")
271302
.help(
272303
"x=0, pipes action stdout and stderr "
273-
"merged into action output.")
274-
.choices([](int val) { return 0 <= val && val <= 3; });
304+
"merged into action output.");
275305

276306
cli |= lyra::opt(globs.quitquick)
277307
.name("-q")
278308
.help("Quit quickly as soon as a target fails.");
279309

280310
cli |= lyra::opt(
281311
[](const std::string & v) {
282-
globs.quitquick = (v == "on" || v == "yes" || v == "true");
312+
if (v != "on" && v != "yes" && v != "true" && v != "off"
313+
&& v != "no" && v != "false")
314+
{
315+
out_printf(
316+
"error: Invalid value for the '--keep-going' option.\n");
317+
b2::clean_exit(EXITBAD);
318+
}
319+
globs.quitquick = (v == "off" || v == "no" || v == "false");
283320
},
284321
"x")
285322
.name("--keep-going")
286323
.help(
287324
"Specify if we continue to build after failures or not "
288-
"(--keep-going=no is equivalent to -q)")
289-
.choices("on", "yes", "true", "off", "no", "false");
325+
"(--keep-going=no is equivalent to -q)");
290326

291-
cli |= lyra::opt(globs.jobs, "x")
327+
cli |= lyra::opt(
328+
[](const std::string & v) {
329+
if (!lyra::detail::from_string(v, globs.jobs) || globs.jobs < 1)
330+
{
331+
out_printf("error: Invalid value for the '-j' option.\n");
332+
b2::clean_exit(EXITBAD);
333+
}
334+
},
335+
"x")
292336
.name("-j")
293337
.name("--jobs")
294-
.help("Run up to x shell commands concurrently.")
295-
.choices([](int val) {
296-
if (val < 1)
297-
{
298-
err_printf("Invalid value for the '-j' option.\n");
299-
b2::clean_exit(EXITBAD);
300-
}
301-
return true;
302-
});
338+
.help("Run up to x shell commands concurrently.");
303339

304340
cli |= lyra::opt(globs.newestfirst)
305341
.name("-g")
@@ -410,7 +446,6 @@ int guarded_main(int argc, char * argv[])
410446
argv[2] = argv[0];
411447
arg_v = argv = (argv + 2);
412448
globs.debug_interface = global_config::debug_interface_child;
413-
args_data = {};
414449
b2::args::set_args(arg_c, arg_v);
415450
b2::args::process_args(true);
416451
}
@@ -429,7 +464,6 @@ int guarded_main(int argc, char * argv[])
429464
arg_c = argc = debug_child_data.argc;
430465
arg_v = argv = (char **)debug_child_data.argv;
431466
globs.debug_interface = global_config::debug_interface_child;
432-
args_data = {};
433467
b2::args::set_args(arg_c, arg_v);
434468
b2::args::process_args(true);
435469
}
@@ -605,7 +639,6 @@ int guarded_main(int argc, char * argv[])
605639
}
606640

607641
// Process options.
608-
args_data = {};
609642
b2::args::set_args(arg_c, arg_v);
610643
b2::args::process_args();
611644

@@ -675,7 +708,7 @@ struct SetConsoleCodepage
675708
UINT orig_console_output_cp = 0;
676709
};
677710

678-
static const SetConsoleCodepage g_console_codepage_setter {};
711+
static const SetConsoleCodepage g_console_codepage_setter { };
679712

680713
} // namespace
681714
#endif

src/engine/mod_args.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 René Ferdinand Rivera Morell
2+
Copyright 2024-2026 René Ferdinand Rivera Morell
33
Distributed under the Boost Software License, Version 1.0.
44
(See accompanying file LICENSE.txt or https://www.bfgroup.xyz/b2/LICENSE.txt)
55
*/
@@ -12,6 +12,7 @@ Distributed under the Boost Software License, Version 1.0.
1212
#include "startup.h"
1313
#include "value.h"
1414

15+
#include <functional>
1516
#include <memory>
1617
#include <sstream>
1718
#include <string>
@@ -34,6 +35,7 @@ struct args_reg
3435
std::unordered_map<std::string, std::shared_ptr<list_ref>> options;
3536
std::vector<std::string> args;
3637
bool need_reparse = true;
38+
std::function<void()> reparse_callback;
3739

3840
args_reg() { cli.style_print_short_first(); }
3941

@@ -48,6 +50,7 @@ struct args_reg
4850

4951
void reparse()
5052
{
53+
if (reparse_callback) reparse_callback();
5154
need_reparse = false;
5255
// We can call this multiple times. Hence we go back to a clean state
5356
// on the collected values to get reproducible parsing.
@@ -100,7 +103,7 @@ struct args_reg
100103
return globs.display_help ? list_ref("true") : list_ref();
101104
if (name == "debug-configuration")
102105
return globs.debug_configuration ? list_ref("true") : list_ref();
103-
return {};
106+
return { };
104107
}
105108

106109
bool has_opt(const value_ref & name) { return (options.count(name) > 0); }
@@ -127,8 +130,7 @@ lyra::cli & lyra_cli() { return args_reg::ref().cli; }
127130
void process_args(bool silent)
128131
{
129132
args_reg::ref().reparse();
130-
if (silent)
131-
return;
133+
if (silent) return;
132134

133135
std::ostringstream out;
134136
int return_code = EXITOK;
@@ -149,6 +151,11 @@ void process_args(bool silent)
149151
}
150152
}
151153

154+
void set_reparse_callback(std::function<void()> const & f)
155+
{
156+
args_reg::ref().reparse_callback = f;
157+
}
158+
152159
const char * args_module::init_code = R"jam(
153160
rule __test__ ( )
154161
{

0 commit comments

Comments
 (0)