From 2a015f5761e2115bd6ffa227ef63e12daef24bff Mon Sep 17 00:00:00 2001 From: Dave Abrahams Date: Tue, 27 Nov 2001 16:41:59 +0000 Subject: [PATCH] ---------------------------------------------------------------------- committing in tools/build Modified Files: build_system.htm Documented: local rules the RULENAMES rule the EXPORT rule the BACKTRACE rule new IMPORT semantics -d+12 Dependency Graph Output Crude Argument Binding Variable numbers of arguments jam_src/compile.c implemented RULENAMES, EXPORT, varargs support, new IMPORT semantics removed unused variables jam_src/make1.c jam_src/hdrmacro.c removed unused variables jam_src/jamgram.{c,h,y,yy} "module local x" does not change module local value of x if it is already set. jam_src/lists.[ch] added list_pop_front() new/assert.jam new/boost-build.jam new/build-system.jam new/errors.jam new/modules.jam new/os.path.jam beginnings of new build system test/check-arguments.jam test/check-jam-patches.jam test/echo_args.jam Added tests for recent core modifications; comments Added Files: new/feature.jam new/property.jam new/readme.txt new/sequence.jam new/test.jam beginnings of new build system ---------------------------------------------------------------------- [SVN r11789] --- build_system.htm | 156 +++++++++++--- historic/jam/src/compile.c | 217 +++++++++++++------ historic/jam/src/hdrmacro.c | 1 - historic/jam/src/jamgram.c | 8 +- historic/jam/src/jamgram.y | 8 +- historic/jam/src/jamgram.yy | 8 +- historic/jam/src/lists.c | 16 ++ historic/jam/src/lists.h | 1 + historic/jam/src/make1.c | 1 - jam_src/compile.c | 217 +++++++++++++------ jam_src/hdrmacro.c | 1 - jam_src/jamgram.c | 8 +- jam_src/jamgram.y | 8 +- jam_src/jamgram.yy | 8 +- jam_src/lists.c | 16 ++ jam_src/lists.h | 1 + jam_src/make1.c | 1 - new/assert.jam | 43 ++-- new/boost-build.jam | 2 +- new/build-system.jam | 11 +- new/errors.jam | 147 +++++++++++-- new/feature.jam | 383 ++++++++++++++++++++++++++++++++++ new/modules.jam | 74 +++++-- new/os.path.jam | 23 +- new/property.jam | 33 +++ new/readme.txt | 9 + new/sequence.jam | 120 +++++++++++ new/test.jam | 2 + test/check-arguments.jam | 21 +- test/check-jam-patches.jam | 72 ++++++- test/echo_args.jam | 14 +- v1/build_system.htm | 156 +++++++++++--- v2/build/feature.jam | 383 ++++++++++++++++++++++++++++++++++ v2/build/property.jam | 33 +++ v2/build/readme.txt | 9 + v2/doc/boost-build.jam | 2 +- v2/errors.jam | 147 +++++++++++-- v2/modules.jam | 74 +++++-- v2/os.path.jam | 23 +- v2/test/check-arguments.jam | 21 +- v2/test/check-jam-patches.jam | 72 ++++++- v2/test/echo_args.jam | 14 +- v2/test/test.jam | 2 + v2/util/assert.jam | 43 ++-- v2/util/sequence.jam | 120 +++++++++++ 45 files changed, 2356 insertions(+), 373 deletions(-) create mode 100644 new/feature.jam create mode 100644 new/property.jam create mode 100644 new/readme.txt create mode 100644 new/sequence.jam create mode 100644 new/test.jam create mode 100644 v2/build/feature.jam create mode 100644 v2/build/property.jam create mode 100644 v2/build/readme.txt create mode 100644 v2/test/test.jam create mode 100644 v2/util/sequence.jam diff --git a/build_system.htm b/build_system.htm index 6ec1afc93..a561ac0c1 100644 --- a/build_system.htm +++ b/build_system.htm @@ -138,7 +138,10 @@ @@ -154,8 +157,10 @@ Debugging Support @@ -1312,8 +1317,35 @@ rule foobar { ECHO foobar ; } # a trivial rule $(x)bar ; # invokes foobar + + Furthermore, if the first expression expands to more than one + list item, everything after the first item becomes part of the + first argument. This allows a crude form of argument binding: -

Argument lists

+
+# return the elements of sequence for which predicate returns non-nil
+rule filter ( sequence * : predicate + )
+{
+    local result ;
+    for local x in $(sequence)
+    {
+        if [ $(predicate) $(x) ] { result += $(x); }
+    }
+    return $(result);
+}
+
+# true iff x == y
+rule equal ( x y )
+{
+    if $(x) = $(y) { return true; }
+}
+
+# bind 3 to the first argument of equal
+ECHO [ filter 1 2 3 4 5 4 3 : equal 3 ] ; # prints "3 3"
+
+
+ +

Argument lists

You can now describe the arguments accepted by a rule, and refer to them by name within the rule. For example, the following prints ``I'm @@ -1350,7 +1382,11 @@ report I 2 : sorry : Joe Dave Pete ; * - Bind to zero or more unbound elements of the actual argument. + Bind to zero or more unbound elements of the actual + argument. When ``*'' appears where an argument name + is expected, any number of additional arguments are + accepted. This feature can be used to implement + "varargs" rules. + @@ -1358,7 +1394,7 @@ report I 2 : sorry : Joe Dave Pete ; Bind to one or more unbound elements of the actual argument. -

The acutal and formal arguments are checked for inconsistencies, which +

The actual and formal arguments are checked for inconsistencies, which cause Jam to exit with an error code:

@@ -1382,8 +1418,11 @@ report I 2 : sorry : Joe Dave Pete ;

Module Support

Boost Jam introduces support for modules, which provide some - rudimentary namespace protection for rules and variables. A new keyword, - ``module'' was also introduced. + rudimentary namespace protection for rules and variables. A new + keyword, ``module'' was also introduced. The features + described in this section are primitives, meaning that + they are meant to provide the operations needed to write Jam + rules which provide a more elegant module interface.

@@ -1423,13 +1462,6 @@ module your_module

-

Note that, like the IMPORT and CALLER_MODULE rules, module - declaration is really a primitive, and is best used through a - wrapper interface which implements a system of module files using the - built-in INCLUDE rule. -

Local Variables
@@ -1493,35 +1525,81 @@ ECHO [ M.get x ] ; # prints "a b c" +
Local Rules
+ +
+ local rule rulename... +
+ +

The rule is declared locally to the current module. It is + not entered in the global module with qualification, and its + name will not appear in the result of +

+ [ RULENAMES module-name ]. +
+ +
The RULENAMES Rule
+ +
+
+rule RULENAMES ( module ? )
+
+
+ Returns a list of the names of all non-local rules in the + given module. If module is ommitted, the names of all + non-local rules in the global module are returned. +
The IMPORT Rule
IMPORT allows rule name aliasing across modules:
-rule IMPORT ( target_module ? : source_module ?
-                : rule_names * : target_names * )
+rule IMPORT (  source_module ? : source_rules *
+                : target_module ? : target_rules * )
 
- Rules are copied from the source_module into the - target_module. If either module name is missing, the global - module is used in its place. If any rule_names are supplied, - they specify which rules from the source_module to import; - otherwise all rules are imported. The rules are given the names in - target_names; if not enough target_names are supplied, - the excess rules are given the same names as they had in - source_module. For example, + The IMPORT rule copies rules from the source_module into the + target_module as local rules. If either source_module or + target_module is not supplied, it refers to the global + module. source_rules specifies which rules from the source_module to + import; TARGET_RULES specifies the names to give those rules in + target_module. If source_rules contains a name which doesn't + correspond to a rule in source_module, or if it contains a different + number of items than target_rules, an error is issued. For example,
-IMPORT m1 : m2 ;               # imports all rules from m2 into m1
-IMPORT    : m2 : my-rule ;     # imports m2.my-rule into the global module
-IMPORT m1 : m2 : r1 x : r2 y ; # imports m2.r1 as r2 and m2.x as y into m1
+# import m1.rule1 into m2 as local rule m1-rule1.
+IMPORT m1 : rule1 : m2 : m1-rule1 ;
+
+# import all non-local rules from m1 into m2
+IMPORT m1 : [ RULENAMES m1 ] : m2 : [ RULENAMES m1 ] ;
+
+
+ +
The EXPORT Rule
+ + EXPORT allows rule name aliasing across modules: + +
+
+rule EXPORT ( module ? : rules * )
+
+
+ The EXPORT rule marks rules from the source_module as non-local + (and thus exportable). If an element of rules does not name a + rule in module, an error is issued. For example, +
+
+module X {
+  local rule r { ECHO X.r ; }
+}
+IMPORT X : r : : r ; # error - r is local in X
+EXPORT X : r ;
+IMPORT X : r : : r ; # OK.
 
- Like the module declaration syntax and - the CALLER_MODULE rule, this - rule is a primitive, and is probably best wrapped in a Jam rule.
The CALLER_MODULE Rule
@@ -1555,10 +1633,6 @@ callers = [ X.get-caller ] [ Y.call-X ] [ X.call-Y ] ; ECHO {$(callers)} ; - Like the module declaration syntax and - the IMPORT rule, this rule is a - primitive, and is probably best wrapped in a Jam rule. - @@ -1717,6 +1791,17 @@ ECHO [ SUBST xyz (.)(.)(.) [$1] ($2) {$3} ] ;

Debugging Support

+
The BACKTRACE rule
+ +
+rule BACKTRACE ( )
+
+ + Returns a list of quadruples: filename line module + rulename..., describing each shallower level of the call + stack. This rule can be used to generate useful diagnostic + messages from Jam rules. +

The -d command-line option admits new arguments:

diff --git a/historic/jam/src/compile.c b/historic/jam/src/compile.c index 90205adea..aab2a36ec 100644 --- a/historic/jam/src/compile.c +++ b/historic/jam/src/compile.c @@ -97,7 +97,9 @@ static LIST *builtin_echo( PARSE *parse, FRAME *frame ); static LIST *builtin_exit( PARSE *parse, FRAME *frame ); static LIST *builtin_flags( PARSE *parse, FRAME *frame ); static LIST *builtin_hdrmacro( PARSE *parse, FRAME *frame ); +static LIST *builtin_rulenames( PARSE *parse, FRAME *frame ); static LIST *builtin_import( PARSE *parse, FRAME *frame ); +static LIST *builtin_export( PARSE *parse, FRAME *frame ); static LIST *builtin_caller_module( PARSE *parse, FRAME *frame ); static LIST *builtin_backtrace( PARSE *parse, FRAME *frame ); @@ -154,7 +156,6 @@ static void lol_build( LOL* lol, char** elements ) static RULE* bind_builtin( char* name, LIST*(*f)(PARSE*, FRAME*), int flags, char** args ) { argument_list* arg_list = 0; - RULE* builtin_rule; if ( args ) { @@ -204,15 +205,33 @@ compile_builtins() { char* args[] = { - "target_module", "?" - , ":", "source_module", "?" - , ":", "rule_names", "*" - , ":", "target_names", "*", 0 + "module", "?", 0 + }; + + bind_builtin( "RULENAMES", builtin_rulenames, 0, args ); + } + + { + char* args[] = { + "source_module", "?" + , ":", "source_rules", "*" + , ":", "target_module", "?" + , ":", "target_rules", "*", 0 }; bind_builtin( "IMPORT", builtin_import, 0, args ); } + + { + char* args[] = { + "module", "?" + , ":", "rules", "*", 0 + }; + + bind_builtin( "EXPORT", builtin_export, 0, args ); + } + { char* args[] = { "levels", "?", 0 }; bind_builtin( "CALLER_MODULE", builtin_caller_module, 0, args ); @@ -636,7 +655,7 @@ static void argument_error( char* message, RULE* rule, FRAME* frame, LIST* arg ) LOL* actual = frame->args; assert( frame->procedure != 0 ); backtrace_line( frame->prev ); - printf( "*** argument error\n* rule %s ( ", frame->procedure->file, frame->procedure->line, frame->rulename ); + printf( "*** argument error\n* rule %s ( ", frame->rulename ); lol_print( rule->arguments->data ); printf( ")\n* called with: ( " ); lol_print( actual ); @@ -667,11 +686,17 @@ collect_arguments( RULE* rule, FRAME* frame ) { LIST *formal = lol_get( all_formal, n ); LIST *actual = lol_get( all_actual, n ); + while ( formal ) { char* name = formal->string; char modifier = 0; LIST* value = 0; + + /* Stop now if a variable number of arguments are specified */ + if ( name[0] == '*' && name[1] == 0 ) + return locals; + if ( formal->next ) { char *next = formal->next->string; @@ -814,6 +839,7 @@ evaluate_rule( module *prev_module = frame->module; LIST* l = var_expand( L0, rulename, rulename+strlen(rulename), frame->args, 0 ); + LIST* more_args = L0; if ( !l ) { @@ -840,7 +866,11 @@ evaluate_rule( enter_module( rule->procedure->module ); } - list_free( l ); + /* drop the rule name */ + l = list_pop_front( l ); + + /* tack the rest of the expansion onto the front of the first argument */ + frame->args->list[0] = list_append( l, lol_get( frame->args, 0 ) ); /* record current rule name in frame */ if ( rule->procedure ) @@ -993,13 +1023,21 @@ compile_set_module( LIST *nt = parse_evaluate( parse->left, frame ); LIST *ns = parse_evaluate( parse->right, frame ); LIST *l; + int setflag; + char *trace; + switch( parse->num ) + { + case ASSIGN_SET: setflag = VAR_SET; trace = "="; break; + default: setflag = VAR_APPEND; trace = ""; break; + } + if( DEBUG_COMPILE ) { debug_compile( 0, "set module", frame); printf( "(%s)", frame->module->name ); list_print( nt ); - printf( " = " ); + printf( " %s ", trace ); list_print( ns ); printf( "\n" ); } @@ -1010,7 +1048,7 @@ compile_set_module( for( l = nt; l; l = list_next( l ) ) { bind_module_var( frame->module, l->string ); - var_set( l->string, list_copy( L0, ns ), VAR_SET ); + var_set( l->string, list_copy( L0, ns ), setflag ); } list_free( nt ); @@ -1275,84 +1313,131 @@ builtin_hdrmacro( return L0; } - -/* - * builtin_import() - IMPORT ( TARGET_MODULE ? : SOURCE_MODULE ? : RULE_NAMES * : TARGET_NAMES * ) +/* builtin_rulenames() - RULENAMES ( MODULE ? ) * - * The IMPORT rule imports rules from the SOURCE_MODULE into the - * TARGET_MODULE. If either SOURCE_MODULE or TARGET_MODULE is not supplied, it - * refers to the root module. If any RULE_NAMES are supplied, they specify which - * rules from the SOURCE_MODULE to import, otherwise all rules are imported. The - * rules are given the names in TARGET_NAMES; if not enough TARGET_NAMES are - * supplied, the excess rules are given the names in RULE_NAMES. If RULE_NAMES - * is not supplied, TARGET_NAMES is ignored. + * Returns a list of the non-local rule names in the given MODULE. If + * MODULE is not supplied, returns the list of rule names in the + * global module. */ -struct import_data -{ - module* target_module; - LIST* target_names; -}; -typedef struct import_data import_data; - -static void import_rule1( void* r_, void* data_ ) +/* helper function for builtin_rulenames(), below */ +static void add_rule_name( void* r_, void* result_ ) { RULE* r = r_; - import_data* data = data_; - - char* target_name = data->target_names ? data->target_names->string : r->name; - if (data->target_names) - data->target_names = list_next(data->target_names); + LIST** result = result_; if ( !r->local_only ) - import_rule( r, data->target_module, target_name ); + *result = list_new( *result, copystr( r->name ) ); } static LIST * -builtin_import( +builtin_rulenames( PARSE *parse, FRAME *frame ) { - LIST *target_module_name = lol_get( frame->args, 0 ); - LIST *source_module_name = lol_get( frame->args, 1 ); - LIST *rule_names = lol_get( frame->args, 2 ); - LIST *target_names = lol_get( frame->args, 3 ); + LIST *arg0 = lol_get( frame->args, 0 ); + LIST *result = L0; + module* source_module = bindmodule( arg0 ? arg0->string : 0 ); - module* target_module = bindmodule( target_module_name ? target_module_name->string : 0 ); - module* source_module = bindmodule( source_module_name ? source_module_name->string : 0 ); + hashenumerate( source_module->rules, add_rule_name, &result ); + return result; +} + +static void unknown_rule( FRAME *frame, char* key, char *module_name, char *rule_name ) +{ + backtrace_line( frame->prev ); + printf( "%s error: rule \"%s\" unknown in module \"%s\"\n", key, rule_name, module_name ); + backtrace( frame->prev ); + exit(1); - if ( rule_names == 0 ) - { - import_data data; - data.target_module = target_module; - data.target_names = target_names; - hashenumerate( source_module->rules, import_rule1, &data ); - } - else - { - LIST *old_name, *target_name; +} + +/* + * builtin_import() - IMPORT ( SOURCE_MODULE ? : SOURCE_RULES * : TARGET_MODULE ? : TARGET_RULES * ) + * + * The IMPORT rule imports rules from the SOURCE_MODULE into the + * TARGET_MODULE as local rules. If either SOURCE_MODULE or + * TARGET_MODULE is not supplied, it refers to the global + * module. SOURCE_RULES specifies which rules from the SOURCE_MODULE + * to import; TARGET_RULES specifies the names to give those rules in + * TARGET_MODULE. If SOURCE_RULES contains a name which doesn't + * correspond to a rule in SOURCE_MODULE, or if it contains a + * different number of items than TARGET_RULES, an error is issued. + * + */ +static LIST * +builtin_import( + PARSE *parse, + FRAME *frame ) +{ + LIST *source_module_list = lol_get( frame->args, 0 ); + LIST *source_rules = lol_get( frame->args, 1 ); + LIST *target_module_list = lol_get( frame->args, 2 ); + LIST *target_rules = lol_get( frame->args, 3 ); + + module* target_module = bindmodule( target_module_list ? target_module_list->string : 0 ); + module* source_module = bindmodule( source_module_list ? source_module_list->string : 0 ); + + LIST *source_name, *target_name; - for ( old_name = rule_names, target_name = target_names; - old_name; - old_name = list_next( old_name ) - , target_name = list_next( target_name ) ) - { - RULE r_, *r = &r_; - r_.name = old_name->string; + for ( source_name = source_rules, target_name = target_rules; + source_name && target_name; + source_name = list_next( source_name ) + , target_name = list_next( target_name ) ) + { + RULE r_, *r = &r_, *imported; + r_.name = source_name->string; - if ( !target_name ) - target_name = old_name; - - if ( hashcheck( source_module->rules, (HASHDATA**)&r ) ) - { - import_rule( r, target_module, target_name->string ); - } - } + if ( !hashcheck( source_module->rules, (HASHDATA**)&r ) ) + unknown_rule( frame, "IMPORT", source_module->name, r_.name ); + + imported = import_rule( r, target_module, target_name->string ); + imported->local_only = 1; + } + + if ( source_name || target_name ) + { + backtrace_line( frame->prev ); + printf( "import error: length of source and target rule name lists don't match" ); + backtrace( frame->prev ); + exit(1); } return L0; } + +/* + * builtin_export() - EXPORT ( MODULE ? : RULES * ) + * + * The EXPORT rule marks RULES from the SOURCE_MODULE as non-local + * (and thus exportable). If an element of RULES does not name a rule + * in MODULE, an error is issued. + */ +static LIST * +builtin_export( + PARSE *parse, + FRAME *frame ) +{ + LIST *module_list = lol_get( frame->args, 0 ); + LIST *rules = lol_get( frame->args, 1 ); + + module* m = bindmodule( module_list ? module_list->string : 0 ); + + + for ( ; rules; rules = list_next( rules ) ) + { + RULE r_, *r = &r_; + r_.name = rules->string; + + if ( !hashcheck( m->rules, (HASHDATA**)&r ) ) + unknown_rule( frame, "EXPORT", m->name, r_.name ); + + r->local_only = 0; + } + return L0; +} + /* Retrieve the file and line number that should be indicated for a * given frame in debug output or an error backtrace */ @@ -1439,8 +1524,6 @@ static LIST *builtin_caller_module( PARSE *parse, FRAME *frame ) { LIST* levels_arg = lol_get( frame->args, 0 ); int levels = levels_arg ? atoi( levels_arg->string ) : 0 ; - char buffer[4096] = ""; - int len; int i; for (i = 0; i < levels + 2 && frame->prev; ++i) diff --git a/historic/jam/src/hdrmacro.c b/historic/jam/src/hdrmacro.c index 702a9ccd7..739534814 100644 --- a/historic/jam/src/hdrmacro.c +++ b/historic/jam/src/hdrmacro.c @@ -69,7 +69,6 @@ static struct hash* header_macros_hash = 0; void macro_headers( TARGET *t ) { - LIST *hdrrule; static regexp *re = 0; FILE *f; char buf[ 1024 ]; diff --git a/historic/jam/src/jamgram.c b/historic/jam/src/jamgram.c index 3d85c487c..d91f5af03 100644 --- a/historic/jam/src/jamgram.c +++ b/historic/jam/src/jamgram.c @@ -73,7 +73,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -836,10 +836,10 @@ case 8: { yyval.parse = pnull(); ; break;} case 9: -{ yyval.parse = yyvsp[0].parse; ; +{ yyval.parse = yyvsp[0].parse; yyval.number = ASSIGN_SET; ; break;} case 10: -{ yyval.parse = yyvsp[0].parse; ; +{ yyval.parse = yyvsp[0].parse; yyval.number = ASSIGN_APPEND; ; break;} case 11: { yyval.parse = yyvsp[-1].parse; ; @@ -866,7 +866,7 @@ case 18: { yyval.parse = pset( yyvsp[-3].parse, yyvsp[-1].parse, yyvsp[-2].number ); ; break;} case 19: -{ yyval.parse = psetmodule( yyvsp[-2].parse, yyvsp[-1].parse ); ; +{ yyval.parse = psetmodule( yyvsp[-2].parse, yyvsp[-1].parse, yyvsp[-1].number ); ; break;} case 20: { yyval.parse = pset1( yyvsp[-5].parse, yyvsp[-3].parse, yyvsp[-1].parse, yyvsp[-2].number ); ; diff --git a/historic/jam/src/jamgram.y b/historic/jam/src/jamgram.y index 7dc5eed7c..dac17eb0a 100644 --- a/historic/jam/src/jamgram.y +++ b/historic/jam/src/jamgram.y @@ -115,7 +115,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -161,9 +161,9 @@ null : /* empty */ ; assign_list_opt : _EQUALS list - { $$.parse = $2.parse; } + { $$.parse = $2.parse; $$.number = ASSIGN_SET; } | null - { $$.parse = $1.parse; } + { $$.parse = $1.parse; $$.number = ASSIGN_APPEND; } ; arglist_opt : _LPAREN lol _RPAREN @@ -187,7 +187,7 @@ rule : _LBRACE block _RBRACE | arg assign list _SEMIC { $$.parse = pset( $1.parse, $3.parse, $2.number ); } | MODULE LOCAL list assign_list_opt _SEMIC - { $$.parse = psetmodule( $3.parse, $4.parse ); } + { $$.parse = psetmodule( $3.parse, $4.parse, $4.number ); } | arg ON list assign list _SEMIC { $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); } | RETURN list _SEMIC diff --git a/historic/jam/src/jamgram.yy b/historic/jam/src/jamgram.yy index 56f45dd1d..4690159a3 100644 --- a/historic/jam/src/jamgram.yy +++ b/historic/jam/src/jamgram.yy @@ -74,7 +74,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -120,9 +120,9 @@ null : /* empty */ ; assign_list_opt : `=` list - { $$.parse = $2.parse; } + { $$.parse = $2.parse; $$.number = ASSIGN_SET; } | null - { $$.parse = $1.parse; } + { $$.parse = $1.parse; $$.number = ASSIGN_APPEND; } ; arglist_opt : `(` lol `)` @@ -146,7 +146,7 @@ rule : `{` block `}` | arg assign list `;` { $$.parse = pset( $1.parse, $3.parse, $2.number ); } | `module` `local` list assign_list_opt `;` - { $$.parse = psetmodule( $3.parse, $4.parse ); } + { $$.parse = psetmodule( $3.parse, $4.parse, $4.number ); } | arg `on` list assign list `;` { $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); } | `return` list `;` diff --git a/historic/jam/src/lists.c b/historic/jam/src/lists.c index 919c71210..b8d2dae79 100644 --- a/historic/jam/src/lists.c +++ b/historic/jam/src/lists.c @@ -149,6 +149,22 @@ list_free( LIST *head ) } } +/* + * list_pop_front() - remove the front element from a list of strings + */ +LIST * list_pop_front( LIST *l ) +{ + LIST * result = l->next; + if( result ) + { + result->tail = l->tail; + l->next = L0; + l->tail = l; + } + list_free( l ); + return result; +} + /* * list_print() - print a list of strings to stdout */ diff --git a/historic/jam/src/lists.h b/historic/jam/src/lists.h index 22ccd9486..7e051e173 100644 --- a/historic/jam/src/lists.h +++ b/historic/jam/src/lists.h @@ -79,6 +79,7 @@ LIST * list_new( LIST *head, char *string ); void list_print( LIST *l ); int list_length( LIST *l ); LIST * list_sublist( LIST *l, int start, int count ); +LIST * list_pop_front( LIST *l ); # define list_next( l ) ((l)->next) diff --git a/historic/jam/src/make1.c b/historic/jam/src/make1.c index d1367679e..83e1b3954 100644 --- a/historic/jam/src/make1.c +++ b/historic/jam/src/make1.c @@ -440,7 +440,6 @@ make1cmds( ACTIONS *a0 ) SETTINGS *boundvars; LIST *nt, *ns; ACTIONS *a1; - CMD *cmd; int start, chunk, length; /* Only do rules with commands to execute. */ diff --git a/jam_src/compile.c b/jam_src/compile.c index 90205adea..aab2a36ec 100644 --- a/jam_src/compile.c +++ b/jam_src/compile.c @@ -97,7 +97,9 @@ static LIST *builtin_echo( PARSE *parse, FRAME *frame ); static LIST *builtin_exit( PARSE *parse, FRAME *frame ); static LIST *builtin_flags( PARSE *parse, FRAME *frame ); static LIST *builtin_hdrmacro( PARSE *parse, FRAME *frame ); +static LIST *builtin_rulenames( PARSE *parse, FRAME *frame ); static LIST *builtin_import( PARSE *parse, FRAME *frame ); +static LIST *builtin_export( PARSE *parse, FRAME *frame ); static LIST *builtin_caller_module( PARSE *parse, FRAME *frame ); static LIST *builtin_backtrace( PARSE *parse, FRAME *frame ); @@ -154,7 +156,6 @@ static void lol_build( LOL* lol, char** elements ) static RULE* bind_builtin( char* name, LIST*(*f)(PARSE*, FRAME*), int flags, char** args ) { argument_list* arg_list = 0; - RULE* builtin_rule; if ( args ) { @@ -204,15 +205,33 @@ compile_builtins() { char* args[] = { - "target_module", "?" - , ":", "source_module", "?" - , ":", "rule_names", "*" - , ":", "target_names", "*", 0 + "module", "?", 0 + }; + + bind_builtin( "RULENAMES", builtin_rulenames, 0, args ); + } + + { + char* args[] = { + "source_module", "?" + , ":", "source_rules", "*" + , ":", "target_module", "?" + , ":", "target_rules", "*", 0 }; bind_builtin( "IMPORT", builtin_import, 0, args ); } + + { + char* args[] = { + "module", "?" + , ":", "rules", "*", 0 + }; + + bind_builtin( "EXPORT", builtin_export, 0, args ); + } + { char* args[] = { "levels", "?", 0 }; bind_builtin( "CALLER_MODULE", builtin_caller_module, 0, args ); @@ -636,7 +655,7 @@ static void argument_error( char* message, RULE* rule, FRAME* frame, LIST* arg ) LOL* actual = frame->args; assert( frame->procedure != 0 ); backtrace_line( frame->prev ); - printf( "*** argument error\n* rule %s ( ", frame->procedure->file, frame->procedure->line, frame->rulename ); + printf( "*** argument error\n* rule %s ( ", frame->rulename ); lol_print( rule->arguments->data ); printf( ")\n* called with: ( " ); lol_print( actual ); @@ -667,11 +686,17 @@ collect_arguments( RULE* rule, FRAME* frame ) { LIST *formal = lol_get( all_formal, n ); LIST *actual = lol_get( all_actual, n ); + while ( formal ) { char* name = formal->string; char modifier = 0; LIST* value = 0; + + /* Stop now if a variable number of arguments are specified */ + if ( name[0] == '*' && name[1] == 0 ) + return locals; + if ( formal->next ) { char *next = formal->next->string; @@ -814,6 +839,7 @@ evaluate_rule( module *prev_module = frame->module; LIST* l = var_expand( L0, rulename, rulename+strlen(rulename), frame->args, 0 ); + LIST* more_args = L0; if ( !l ) { @@ -840,7 +866,11 @@ evaluate_rule( enter_module( rule->procedure->module ); } - list_free( l ); + /* drop the rule name */ + l = list_pop_front( l ); + + /* tack the rest of the expansion onto the front of the first argument */ + frame->args->list[0] = list_append( l, lol_get( frame->args, 0 ) ); /* record current rule name in frame */ if ( rule->procedure ) @@ -993,13 +1023,21 @@ compile_set_module( LIST *nt = parse_evaluate( parse->left, frame ); LIST *ns = parse_evaluate( parse->right, frame ); LIST *l; + int setflag; + char *trace; + switch( parse->num ) + { + case ASSIGN_SET: setflag = VAR_SET; trace = "="; break; + default: setflag = VAR_APPEND; trace = ""; break; + } + if( DEBUG_COMPILE ) { debug_compile( 0, "set module", frame); printf( "(%s)", frame->module->name ); list_print( nt ); - printf( " = " ); + printf( " %s ", trace ); list_print( ns ); printf( "\n" ); } @@ -1010,7 +1048,7 @@ compile_set_module( for( l = nt; l; l = list_next( l ) ) { bind_module_var( frame->module, l->string ); - var_set( l->string, list_copy( L0, ns ), VAR_SET ); + var_set( l->string, list_copy( L0, ns ), setflag ); } list_free( nt ); @@ -1275,84 +1313,131 @@ builtin_hdrmacro( return L0; } - -/* - * builtin_import() - IMPORT ( TARGET_MODULE ? : SOURCE_MODULE ? : RULE_NAMES * : TARGET_NAMES * ) +/* builtin_rulenames() - RULENAMES ( MODULE ? ) * - * The IMPORT rule imports rules from the SOURCE_MODULE into the - * TARGET_MODULE. If either SOURCE_MODULE or TARGET_MODULE is not supplied, it - * refers to the root module. If any RULE_NAMES are supplied, they specify which - * rules from the SOURCE_MODULE to import, otherwise all rules are imported. The - * rules are given the names in TARGET_NAMES; if not enough TARGET_NAMES are - * supplied, the excess rules are given the names in RULE_NAMES. If RULE_NAMES - * is not supplied, TARGET_NAMES is ignored. + * Returns a list of the non-local rule names in the given MODULE. If + * MODULE is not supplied, returns the list of rule names in the + * global module. */ -struct import_data -{ - module* target_module; - LIST* target_names; -}; -typedef struct import_data import_data; - -static void import_rule1( void* r_, void* data_ ) +/* helper function for builtin_rulenames(), below */ +static void add_rule_name( void* r_, void* result_ ) { RULE* r = r_; - import_data* data = data_; - - char* target_name = data->target_names ? data->target_names->string : r->name; - if (data->target_names) - data->target_names = list_next(data->target_names); + LIST** result = result_; if ( !r->local_only ) - import_rule( r, data->target_module, target_name ); + *result = list_new( *result, copystr( r->name ) ); } static LIST * -builtin_import( +builtin_rulenames( PARSE *parse, FRAME *frame ) { - LIST *target_module_name = lol_get( frame->args, 0 ); - LIST *source_module_name = lol_get( frame->args, 1 ); - LIST *rule_names = lol_get( frame->args, 2 ); - LIST *target_names = lol_get( frame->args, 3 ); + LIST *arg0 = lol_get( frame->args, 0 ); + LIST *result = L0; + module* source_module = bindmodule( arg0 ? arg0->string : 0 ); - module* target_module = bindmodule( target_module_name ? target_module_name->string : 0 ); - module* source_module = bindmodule( source_module_name ? source_module_name->string : 0 ); + hashenumerate( source_module->rules, add_rule_name, &result ); + return result; +} + +static void unknown_rule( FRAME *frame, char* key, char *module_name, char *rule_name ) +{ + backtrace_line( frame->prev ); + printf( "%s error: rule \"%s\" unknown in module \"%s\"\n", key, rule_name, module_name ); + backtrace( frame->prev ); + exit(1); - if ( rule_names == 0 ) - { - import_data data; - data.target_module = target_module; - data.target_names = target_names; - hashenumerate( source_module->rules, import_rule1, &data ); - } - else - { - LIST *old_name, *target_name; +} + +/* + * builtin_import() - IMPORT ( SOURCE_MODULE ? : SOURCE_RULES * : TARGET_MODULE ? : TARGET_RULES * ) + * + * The IMPORT rule imports rules from the SOURCE_MODULE into the + * TARGET_MODULE as local rules. If either SOURCE_MODULE or + * TARGET_MODULE is not supplied, it refers to the global + * module. SOURCE_RULES specifies which rules from the SOURCE_MODULE + * to import; TARGET_RULES specifies the names to give those rules in + * TARGET_MODULE. If SOURCE_RULES contains a name which doesn't + * correspond to a rule in SOURCE_MODULE, or if it contains a + * different number of items than TARGET_RULES, an error is issued. + * + */ +static LIST * +builtin_import( + PARSE *parse, + FRAME *frame ) +{ + LIST *source_module_list = lol_get( frame->args, 0 ); + LIST *source_rules = lol_get( frame->args, 1 ); + LIST *target_module_list = lol_get( frame->args, 2 ); + LIST *target_rules = lol_get( frame->args, 3 ); + + module* target_module = bindmodule( target_module_list ? target_module_list->string : 0 ); + module* source_module = bindmodule( source_module_list ? source_module_list->string : 0 ); + + LIST *source_name, *target_name; - for ( old_name = rule_names, target_name = target_names; - old_name; - old_name = list_next( old_name ) - , target_name = list_next( target_name ) ) - { - RULE r_, *r = &r_; - r_.name = old_name->string; + for ( source_name = source_rules, target_name = target_rules; + source_name && target_name; + source_name = list_next( source_name ) + , target_name = list_next( target_name ) ) + { + RULE r_, *r = &r_, *imported; + r_.name = source_name->string; - if ( !target_name ) - target_name = old_name; - - if ( hashcheck( source_module->rules, (HASHDATA**)&r ) ) - { - import_rule( r, target_module, target_name->string ); - } - } + if ( !hashcheck( source_module->rules, (HASHDATA**)&r ) ) + unknown_rule( frame, "IMPORT", source_module->name, r_.name ); + + imported = import_rule( r, target_module, target_name->string ); + imported->local_only = 1; + } + + if ( source_name || target_name ) + { + backtrace_line( frame->prev ); + printf( "import error: length of source and target rule name lists don't match" ); + backtrace( frame->prev ); + exit(1); } return L0; } + +/* + * builtin_export() - EXPORT ( MODULE ? : RULES * ) + * + * The EXPORT rule marks RULES from the SOURCE_MODULE as non-local + * (and thus exportable). If an element of RULES does not name a rule + * in MODULE, an error is issued. + */ +static LIST * +builtin_export( + PARSE *parse, + FRAME *frame ) +{ + LIST *module_list = lol_get( frame->args, 0 ); + LIST *rules = lol_get( frame->args, 1 ); + + module* m = bindmodule( module_list ? module_list->string : 0 ); + + + for ( ; rules; rules = list_next( rules ) ) + { + RULE r_, *r = &r_; + r_.name = rules->string; + + if ( !hashcheck( m->rules, (HASHDATA**)&r ) ) + unknown_rule( frame, "EXPORT", m->name, r_.name ); + + r->local_only = 0; + } + return L0; +} + /* Retrieve the file and line number that should be indicated for a * given frame in debug output or an error backtrace */ @@ -1439,8 +1524,6 @@ static LIST *builtin_caller_module( PARSE *parse, FRAME *frame ) { LIST* levels_arg = lol_get( frame->args, 0 ); int levels = levels_arg ? atoi( levels_arg->string ) : 0 ; - char buffer[4096] = ""; - int len; int i; for (i = 0; i < levels + 2 && frame->prev; ++i) diff --git a/jam_src/hdrmacro.c b/jam_src/hdrmacro.c index 702a9ccd7..739534814 100644 --- a/jam_src/hdrmacro.c +++ b/jam_src/hdrmacro.c @@ -69,7 +69,6 @@ static struct hash* header_macros_hash = 0; void macro_headers( TARGET *t ) { - LIST *hdrrule; static regexp *re = 0; FILE *f; char buf[ 1024 ]; diff --git a/jam_src/jamgram.c b/jam_src/jamgram.c index 3d85c487c..d91f5af03 100644 --- a/jam_src/jamgram.c +++ b/jam_src/jamgram.c @@ -73,7 +73,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -836,10 +836,10 @@ case 8: { yyval.parse = pnull(); ; break;} case 9: -{ yyval.parse = yyvsp[0].parse; ; +{ yyval.parse = yyvsp[0].parse; yyval.number = ASSIGN_SET; ; break;} case 10: -{ yyval.parse = yyvsp[0].parse; ; +{ yyval.parse = yyvsp[0].parse; yyval.number = ASSIGN_APPEND; ; break;} case 11: { yyval.parse = yyvsp[-1].parse; ; @@ -866,7 +866,7 @@ case 18: { yyval.parse = pset( yyvsp[-3].parse, yyvsp[-1].parse, yyvsp[-2].number ); ; break;} case 19: -{ yyval.parse = psetmodule( yyvsp[-2].parse, yyvsp[-1].parse ); ; +{ yyval.parse = psetmodule( yyvsp[-2].parse, yyvsp[-1].parse, yyvsp[-1].number ); ; break;} case 20: { yyval.parse = pset1( yyvsp[-5].parse, yyvsp[-3].parse, yyvsp[-1].parse, yyvsp[-2].number ); ; diff --git a/jam_src/jamgram.y b/jam_src/jamgram.y index 7dc5eed7c..dac17eb0a 100644 --- a/jam_src/jamgram.y +++ b/jam_src/jamgram.y @@ -115,7 +115,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -161,9 +161,9 @@ null : /* empty */ ; assign_list_opt : _EQUALS list - { $$.parse = $2.parse; } + { $$.parse = $2.parse; $$.number = ASSIGN_SET; } | null - { $$.parse = $1.parse; } + { $$.parse = $1.parse; $$.number = ASSIGN_APPEND; } ; arglist_opt : _LPAREN lol _RPAREN @@ -187,7 +187,7 @@ rule : _LBRACE block _RBRACE | arg assign list _SEMIC { $$.parse = pset( $1.parse, $3.parse, $2.number ); } | MODULE LOCAL list assign_list_opt _SEMIC - { $$.parse = psetmodule( $3.parse, $4.parse ); } + { $$.parse = psetmodule( $3.parse, $4.parse, $4.number ); } | arg ON list assign list _SEMIC { $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); } | RETURN list _SEMIC diff --git a/jam_src/jamgram.yy b/jam_src/jamgram.yy index 56f45dd1d..4690159a3 100644 --- a/jam_src/jamgram.yy +++ b/jam_src/jamgram.yy @@ -74,7 +74,7 @@ # define prule( s,p ) parse_make( compile_rule,p,P0,P0,s,S0,0 ) # define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 ) # define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a ) -# define psetmodule( l,r ) parse_make( compile_set_module,l,r,P0,S0,S0,0 ) +# define psetmodule( l,r,a ) parse_make( compile_set_module,l,r,P0,S0,S0,a ) # define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a ) # define psetc( s,p,a,l ) parse_make( compile_setcomp,p,a,P0,s,S0,l ) # define psete( s,l,s1,f ) parse_make( compile_setexec,l,P0,P0,s,s1,f ) @@ -120,9 +120,9 @@ null : /* empty */ ; assign_list_opt : `=` list - { $$.parse = $2.parse; } + { $$.parse = $2.parse; $$.number = ASSIGN_SET; } | null - { $$.parse = $1.parse; } + { $$.parse = $1.parse; $$.number = ASSIGN_APPEND; } ; arglist_opt : `(` lol `)` @@ -146,7 +146,7 @@ rule : `{` block `}` | arg assign list `;` { $$.parse = pset( $1.parse, $3.parse, $2.number ); } | `module` `local` list assign_list_opt `;` - { $$.parse = psetmodule( $3.parse, $4.parse ); } + { $$.parse = psetmodule( $3.parse, $4.parse, $4.number ); } | arg `on` list assign list `;` { $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); } | `return` list `;` diff --git a/jam_src/lists.c b/jam_src/lists.c index 919c71210..b8d2dae79 100644 --- a/jam_src/lists.c +++ b/jam_src/lists.c @@ -149,6 +149,22 @@ list_free( LIST *head ) } } +/* + * list_pop_front() - remove the front element from a list of strings + */ +LIST * list_pop_front( LIST *l ) +{ + LIST * result = l->next; + if( result ) + { + result->tail = l->tail; + l->next = L0; + l->tail = l; + } + list_free( l ); + return result; +} + /* * list_print() - print a list of strings to stdout */ diff --git a/jam_src/lists.h b/jam_src/lists.h index 22ccd9486..7e051e173 100644 --- a/jam_src/lists.h +++ b/jam_src/lists.h @@ -79,6 +79,7 @@ LIST * list_new( LIST *head, char *string ); void list_print( LIST *l ); int list_length( LIST *l ); LIST * list_sublist( LIST *l, int start, int count ); +LIST * list_pop_front( LIST *l ); # define list_next( l ) ((l)->next) diff --git a/jam_src/make1.c b/jam_src/make1.c index d1367679e..83e1b3954 100644 --- a/jam_src/make1.c +++ b/jam_src/make1.c @@ -440,7 +440,6 @@ make1cmds( ACTIONS *a0 ) SETTINGS *boundvars; LIST *nt, *ns; ACTIONS *a1; - CMD *cmd; int start, chunk, length; /* Only do rules with commands to execute. */ diff --git a/new/assert.jam b/new/assert.jam index 04f2c8282..609094c9e 100644 --- a/new/assert.jam +++ b/new/assert.jam @@ -3,53 +3,64 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. -import errors : error ; +import errors : error-skip-frames lol->list ; +# assert the equality of A and B rule equal ( a * : b * ) { if $(a) != $(b) { - error assertion failure: \"$(a)\" "!=" \"$(b)\" ; + error-skip-frames 3 assertion failure: \"$(a)\" "!=" \"$(b)\" ; } } -rule result ( expected * : rule-name args * ) +# assert that EXPECTED is the result of calling RULE-NAME with the +# given arguments +rule result ( expected * : rule-name args * : * ) { - + local result__ ; module [ CALLER_MODULE ] { - result = [ $(rule-name) $(args) ] ; + result__ = [ + $(rule-name) $(args) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ] ; } - if $(result) != $(expected) + if $(result__) != $(expected) { - error assertion failure: "[" $(rule-name) \"$(args)\" "]" + error-skip-frames 3 assertion failure: "[" $(rule-name) + [ lol->list $(args) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ] + "]" : expected: \"$(expected)\" - : got: \"$(result)\" ; + : got: \"$(result__)\" ; } } +# assert that the given variable is nonempty. rule nonempty-variable ( name ) { local empty ; if $($(variable)) = $(empty) { - error assertion failure: expecting non-empty variable $(variable) ; + error-skip-frames 3 assertion failure: expecting non-empty variable $(variable) ; } } -rule true ( rule-name args * ) +# assert that the result of calling RULE-NAME on the given arguments +# has a true logical value (is neither an empty list nor all empty +# strings). +rule true ( rule-name args * : * ) { - local result caller-module = [ CALLER_MODULE ] ; - - module $(caller-module) + local result__ ; + module [ CALLER_MODULE ] { - result = [ $(rule-name) $(args) ] ; + result__ = [ + $(rule-name) $(args) : $(2) $(3) : $(4) + : $(5) : $(6) : $(7) : $(8) : $(9) ] ; } - if ! $(result) + if ! $(result__) { - error assertion failure: expecting true result from + error-skip-frames 3 assertion failure: expecting true result from "[" $(rule-name) \"$(args)\" "]" ; } } diff --git a/new/boost-build.jam b/new/boost-build.jam index 9787e78ec..ff7a528b2 100644 --- a/new/boost-build.jam +++ b/new/boost-build.jam @@ -7,7 +7,7 @@ SEARCH on modules.jam = $(BOOST_BUILD_PATH) ; module modules { include modules.jam ; } # Bring the import rule into the global module -IMPORT : modules : import ; +IMPORT modules : import : : import ; import modules ; # The modules module can tolerate being included twice import build-system ; diff --git a/new/build-system.jam b/new/build-system.jam index ffc64c694..8e4ea1bd9 100644 --- a/new/build-system.jam +++ b/new/build-system.jam @@ -3,4 +3,13 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. -import os ; +# this rule will be used to generate build instructions for the +# given module (Jamfile) once its declarations have been read. +rule construct ( module-name ) +{ +} + +JAMFILE ?= Jamfile ; +import $(JAMFILE) ; + +construct $(JAMFILE) ; diff --git a/new/errors.jam b/new/errors.jam index 699eef6b3..c3b44319f 100644 --- a/new/errors.jam +++ b/new/errors.jam @@ -3,44 +3,153 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. -# A utility rule used to report the including module when there is an error, so -# that editors may find it. -rule report-module ( prefix ? : suffix ? : frames ? ) +# Print a stack backtrace leading to this rule's caller. Each +# argument represents a line of output to be printed after the first +# line of the backtrace. +rule backtrace ( skip-frames messages * : * ) { - frames ?= 1 ; - # We report some large line number so that emacs, etc., will at least locate the file. - ECHO $(prefix) [ modules.binding [ CALLER_MODULE $(frames) ] ] ":" line 99999 $(suffix) ; -} - -rule backtrace -{ - local digits = 1 2 3 4 5 6 7 8 9 ; + local frame-skips = 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61 65 69 73 77 81 ; + local drop-elements = $(frame-skips[$(skip-frames)]) ; + if ! ( $(skip-frames) in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) + { + ECHO warning: backtrace doesn't support skipping + $(skip-frames) frames; using 1 instead. ; + drop-elements = 5 ; + } + + # get the whole backtrace, then drop the initial quadruples + # corresponding to the frames that must be skipped. local bt = [ BACKTRACE ] ; - bt = $(bt[5-]) ; + bt = $(bt[$(drop-elements)-]) ; + + local args = messages 2 3 4 5 6 7 8 9 ; while $(bt) { ECHO $(bt[1]):$(bt[2]): "in" $(bt[4]) ; - for local n in $(digits) + # the first time through, print each argument on a separate + # line + for local n in $(args) { if $($(n))-is-not-empty { ECHO $($(n)) ; } } - digits = ; - + args = ; # kill args so that this never happens again + + # Move on to the next quadruple bt = $(bt[5-]) ; } } -rule error +module local args = messages 2 3 4 5 6 7 8 9 ; +module local disabled last-$(args) ; + +# try-catch -- +# +# This is not really an exception-handling mechanism, but it does +# allow us to perform some error-checking on our +# error-checking. Errors are suppressed after a try, and the first one +# is recorded. Use catch to check that the error message matched +# expectations. + +# begin looking for error messages +rule try ( ) { - backtrace error: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; - EXIT ; + disabled += true ; + last-$(args) = ; } +# stop looking for error messages; generate an error if an argument of +# messages is not found in the corresponding argument in the error call. +rule catch ( messages * : * ) +{ + import sequence ; + disabled = $(disabled[2-]) ; # pop the stack + + for local n in $(args) + { + if ! $($(n)) in $(last-$(n)) + { + local v = [ sequence.join $($(n)) : " " ] ; + v ?= "" ; + local joined = [ sequence.join $(last-$(n)) : " " ] ; + + last-$(args) = ; + error-skip-frames 3 expected \"$(v)\" in argument $(n) of error + : got \"$(joined)\" instead ; + } + } +} + +rule error-skip-frames ( skip-frames messages * : * ) +{ + if ! $(disabled) + { + backtrace $(skip-frames) error: $(messages) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + EXIT ; + } + else if ! $(last-$(args)) + { + for local n in $(args) + { + last-$(n) = $($(n)) ; + } + } +} + +# Print an error message with a stack backtrace and exit. +rule error ( messages * : * ) +{ + error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +} + +# Print a warning message with a stack backtrace and exit. rule warning { - backtrace warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + backtrace 2 warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +} + +# convert an arbitrary argument list into a list with ":" separators +# and quoted elements representing the same information. This is +# mostly useful for formatting descriptions of the arguments with +# which a rule was called when reporting an error. +rule lol->list ( * ) +{ + local result ; + local remaining = 1 2 3 4 5 6 7 8 9 ; + while $($(remaining)) + { + local n = $(remaining[1]) ; + remaining = $(remaining[2-]) ; + + if $(n) != 1 + { + result += ":" ; + } + result += \"$(n)\" ; + } + return $(result) ; +} + +rule __test__ ( ) +{ + # show that we can correctly catch an expected error + try ; + { + error an error occurred : somewhere ; + } + catch an error occurred : somewhere ; + + # show that unexpected errors generate real errors + try ; + { + try ; + { + error an error occurred : somewhere ; + } + catch an error occurred : nowhere ; + } + catch expected \"nowhere\" in argument 2 ; } diff --git a/new/feature.jam b/new/feature.jam new file mode 100644 index 000000000..2a13b2e14 --- /dev/null +++ b/new/feature.jam @@ -0,0 +1,383 @@ +# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears in +# all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. + +import errors : error lol->list ; +import property ; +import sequence ; +import regex ; +import set ; + +module local all-attributes = + + implicit # features whose values alone identify the + # feature. For example, a user is not required to + # write "gcc", but can simply write + # "gcc". Implicit feature names also don't appear in + # variant paths, although the values do. Thus: + # bin/gcc/... as opposed to bin/toolset-gcc/.... There + # should typically be only a few such features, to + # avoid possible name clashes. + + executed # the feature corresponds to the name of a module + # containing an execute rule, used to actually prepare + # the build. For example, the toolset feature would be + # executed. + + + composite # features which actually correspond to groups of + # properties. For example, a build variant is a + # composite feature. When generating targets from a + # set of build properties, composite features are + # recursively expanded and /added/ to the build + # property set, so rules can find them if + # neccessary. Non-composite non-free features override + # components of composite features in a build property + # set. + + + optional # An optional feature is allowed to have no value at + # all in a particular build. Normal non-free features + # are always given the first of their values if no + # value is otherwise specified. + + + symmetric # A symmetric feature has no default value, and is + # therefore not automatically included in all + # variants. A symmetric feature, when relevant to the + # toolset, always generates a corresponding subvariant + # directory. + + free # as described in previous documentation + + path # the (free) feature's value describes a path which + # might be given relative to the directory of the + # Jamfile. + + dependency # the value of the (free) feature specifies a + # dependency of the target. + + propagated # when used in a build request, the (free) feature is + # propagated to top-level targets which are + # dependencies of the requested build. Propagated + # features would be most useful for settings such as + # warning levels, output message style, etc., which + # don't affect the built products at all. + ; + +module local all-features ; +module local all-implicit-values ; + +# declare a new feature with the given name, values, and attributes. +rule feature ( name : values * : attributes * ) +{ + local error ; + + # if there are any unknown attributes... + if ! ( $(attributes) in $(all-attributes) ) + { + error = unknown attributes: + [ set.difference $(attributes) : $(all-attributes) ] ; + } + else if $(name) in $(all-features) + { + error = feature already defined: ; + } + else if implicit in $(attributes) + { + if free in $(attributes) + { + error = free features cannot also be implicit ; + } + } + + if $(error) + { + error $(error) + : "in" feature declaration: + : feature [ errors.lol->list $(1) : $(2) : $(3) ] ; + } + + module local $(name).values ; + module local $(name).attributes = $(attributes) ; + module local $(name).subfeatures = ; + + all-features += $(name) ; + extend $(name) : $(values) ; +} + +# returns true iff all elements of names are valid features. +rule valid ( name + ) +{ + if $(names) in $(all-features) + { + return true ; + } +} + +# return the attibutes of the given feature +rule attributes ( feature ) +{ + return $($(feature).attributes) ; +} + +# return the values of the given feature +rule values ( feature ) +{ + return $($(feature).values) ; +} + +rule implied-feature ( implicit-value ) +{ + local feature = $($(implicit-value).implicit-feature) ; + if ! $(feature) + { + error \"$(implicit-value)\" is not a value of an implicit feature ; + } + return $(feature) ; +} + +local rule find-implied-subfeature ( feature subvalue : value-string ? ) +{ + local v + = subfeature($(feature),$(subvalue)) + subfeature($(feature),$(value-string),$(subvalue)) ; + + # declaring these module local here prevents us from picking up + # enclosing definitions. + module local $(v) ; + + local subfeature = $($(v)) ; + return $(subfeature[1]) ; +} + +rule implied-subfeature ( feature subvalue : value-string ? ) +{ + local subfeature = [ find-implied-subfeature $(feature) $(subvalue) + : $(value-string) ] ; + + if ! $(subfeature) + { + error \"$(subvalue)\" is not a known subfeature value of + feature \"$(feature)\" ; + } + + return $(subfeature) ; +} + +# generate an error if the feature is unknown +local rule validate-feature ( feature ) +{ + if ! $(feature) in $(all-features) + { + error unknown feature \"$(feature)\" ; + } +} + +# expand-subfeatures toolset : gcc-2.95.2-linux-x86 -> gcc 2.95.2 linux x86 +# equivalent to: +# expand-subfeatures : gcc-2.95.2-linux-x86 +local rule expand-subfeatures ( feature ? : value ) +{ + if $(feature) + { + validate-feature $(feature) ; + } + + local components = [ regex.split $(value) "-" ] ; + if ! $(feature) + { + feature = [ implied-feature $(components[1]) ] ; + } + + # get the top-level feature's value + local value = $(components[1]:G=) ; + + local result = $(components[1]:G=$(feature)) ; + for local subvalue in $(components[2-]) + { + local subfeature = [ implied-subfeature $(feature) $(subvalue) : $(value) ] ; + result += $(subvalue:G=$(feature)-$(subfeature)) ; + } + + return $(result) ; +} + +local rule extend-feature ( feature : values * ) +{ + validate-feature $(feature) ; + if implicit in $(attributes) + { + for local v in $(values) + { + module local $(v).implicit-feature ; + if $($(v).implicit-feature) + { + error $(v) is already associated with the \"$($(v).implicit-feature)\" feature ; + } + $(v).implicit-feature = $(feature) ; + } + + all-implicit-values += $(values) ; + } + $(feature).values += $(values) ; +} + +local rule validate-value-string ( feature value-string ) +{ + local values = $(value-string) ; + if $($(feature).subfeatures) + { + values = [ regex.split $(value-string) - ] ; + } + + if ! ( $(values[1]) in $($(feature).values) ) + { + return \"$(values[1])\" is not a known value of feature \"$(feature)\" ; + } + + if $(values[2]) + { + # this will validate any subfeature values in value-string + implied-subfeature $(feature) [ sequence.join $(values[2-]) - ] + : $(values[1]) ; + } + +} + +# extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ; +# extend-subfeature toolset : target-platform : mingw ; +local rule extend-subfeature ( feature value-string ? : subfeature : subvalues * ) +{ + validate-feature $(feature) ; + if $(value-string) + { + validate-value-string $(feature) $(value-string) ; + } + + for local subvalue in $(subvalues) + { + local v + = subfeature($(feature),$(value-string),$(subvalue)) + subfeature($(feature),$(subvalue)) ; + module local $(v[1]) = $(subfeature) ; + } +} + +rule extend ( feature-or-property subfeature ? : values * ) +{ + local feature value-string ; + if $(feature-or-property:G) + { + feature = [ property.get-feature $(feature-or-property) ] ; + value-string = $(feature-or-property:G=) ; + } + else + { + feature = $(feature-or-property) ; + } + + if $(subfeature) + { + extend-subfeature $(feature) $(value-string) + : $(subfeature) : $(values) ; + } + else + { + if $(value-string) + { + error can only be specify a property as the first argument + when extending a subfeature + : usage: + : " extend" feature ":" values... + : " | extend" value-string subfeature ":" values... + ; + } + + extend-feature $(feature) : $(values) ; + } +} + +# subfeature +# +# subfeature toolset gcc-2.95.2 target-platform : aix linux mac cygwin +# +rule subfeature ( feature value-string ? : subfeature : subvalues * ) +{ + validate-feature $(feature) ; + if $(subfeature) in $($(feature).subfeatures) + { + error \"$(subfeature)\" already declared as a subfeature of \"$(feature)\" ; + } + $(feature).subfeatures += $(subfeature) ; + extend-subfeature $(feature) $(value-string) : $(subfeature) : $(subvalues) ; +} + +# tests of module features +local rule __test__ ( ) +{ + import errors : try catch ; + import assert ; + feature __test__toolset : gcc : implicit executed ; + feature __test__define : : free ; + + extend-feature __test__toolset : msvc metrowerks ; + subfeature __test__toolset gcc : version : 2.95.2 2.95.3 2.95.4 + 3.0 3.0.1 3.0.2 ; + + assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 : + expand-subfeatures __test__toolset : gcc-3.0.1 ; + + assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 : + expand-subfeatures : gcc-3.0.1 ; + + feature __test__dummy : dummy1 dummy2 ; + subfeature __test__dummy : subdummy : x y z ; + + # test error checking + try ; + { + validate-feature __test__foobar ; + } + catch unknown feature ; + + try ; + { + feature __test__foobar : : baz ; + } + catch unknown attributes: baz ; + + feature __test__feature1 ; + try ; + { + feature __test__feature1 ; + } + catch feature already defined: ; + + try ; + { + feature __test__feature2 : : free implicit ; + } + catch free features cannot also be implicit ; + + try ; + { + implied-feature lackluster ; + } + catch \"lackluster\" is not a value of an implicit feature ; + + try ; + { + implied-subfeature __test__toolset 3.0.1 ; + } + catch \"3.0.1\" is not a known subfeature value of + feature \"__test__toolset\" ; + + try ; + { + implied-subfeature __test__toolset not-a-version : gcc ; + } + catch \"not-a-version\" is not a known subfeature value of + feature \"__test__toolset\" ; +} \ No newline at end of file diff --git a/new/modules.jam b/new/modules.jam index 3c1869ae7..1d1ab0ffa 100644 --- a/new/modules.jam +++ b/new/modules.jam @@ -5,6 +5,8 @@ # Keep a record so that no module is included multiple times module local loaded-modules ; +module local loading-modules ; +module local untested ; # meant to be invoked from import when no __test__ rule is defined in a given # module @@ -19,24 +21,22 @@ rule binding ( module ) return $($(module).__binding__) ; } -# load the indicated module. Any members of rules-opt will be available without -# qualification in the caller's module. Any members of rename-opt will be taken -# as the names of the rules in the caller's module, in place of the names they -# have in the imported module. If rules-opt = '*', all rules from the indicated -# module are imported into the caller's module. -rule import ( module-name : rules-opt * : rename-opt * ) +# load the indicated module if it is not already loaded. +rule load ( module-name ) { - # First see if the module needs to be loaded if ! ( $(module-name) in $(loaded-modules) ) { loaded-modules += $(module-name) ; + loading-modules += $(module-name) ; + local suppress-test = $(untested[1]) ; # suppress tests until all recursive loads are complete. + untested += $(module-name) ; # add the module to the stack of untested modules module $(module-name) { module local __name__ = $(module-name) ; # Prepare a default behavior, in case no __test__ is defined. - IMPORT $(module-name) : modules : no_test_defined : __test__ ; + IMPORT modules : no_test_defined : $(module-name) : __test__ ; # Add some grist so that the module will have a unique target name local module-target = $(module-name:G=module@:S=.jam) ; @@ -45,34 +45,61 @@ rule import ( module-name : rules-opt * : rename-opt * ) BINDRULE on $(module-target) = modules.record-binding ; include $(module-name:G=module@:S=.jam) ; - # run the module's test, if any. - if nonempty$(BOOST_BUILD_TEST) + } + loading-modules = $(loading-modules[1--2]) ; + + if ! $(suppress-test) && $(BOOST_BUILD_TEST)-is-nonempty + { + # run any pending tests + for local m in $(untested) { - ECHO testing module $(module-name)... ; - local ignored = [ __test__ ] ; + ECHO testing module $(m)... ; + module $(m) + { + __test__ ; + } } + untested = ; } } - - # If any rules are to be imported, do so now. - if $(rules-opt) + else if $(module-name) in $(loading-modules) { - if $(rules-opt) = * - { - rules-opt = ; - } - IMPORT [ CALLER_MODULE ] - : $(module-name) : $(rules-opt) : $(rename-opt) ; + ECHO loading \"$(module-name)\" ; + ECHO circular module loading dependency: ; + EXIT $(loading-modules) $(module-name) ; } } -# This helper is used by import (above) to record the binding (path) of +# This helper is used by load (above) to record the binding (path) of # each loaded module. rule record-binding ( module-target : binding ) { module local $(module-target:G=:S=).__binding__ = $(binding) ; } +# load the indicated module and import rule names into the current +# module. Any members of rules-opt will be available without +# qualification in the caller's module. Any members of rename-opt will +# be taken as the names of the rules in the caller's module, in place +# of the names they have in the imported module. If rules-opt = '*', +# all rules from the indicated module are imported into the caller's +# module. If rename-opt is supplied, it must have the same number of +# elements as rules-opt. +rule import ( module-name : rules-opt * : rename-opt * ) +{ + load $(module-name) ; + + local source-names = $(rules-opt) ; + if $(rules-opt) = * + { + source-names = [ RULENAMES module-name ] ; + } + + local target-names = $(rename-opt) ; + target-names ?= $(source-names) ; + IMPORT $(module-name) : $(source-names) : [ CALLER_MODULE ] : $(target-names) ; +} + # Returns the module-local value of a variable. rule peek ( module-name variable ) { @@ -82,9 +109,10 @@ rule peek ( module-name variable ) } } -rule __test__ ( ) +local rule __test__ ( ) { import assert ; + module modules.__test__ { module local foo = bar ; diff --git a/new/os.path.jam b/new/os.path.jam index 172598282..0e6d0c28c 100644 --- a/new/os.path.jam +++ b/new/os.path.jam @@ -3,7 +3,28 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. +if $(NT) +{ + module local slash = \\ ; +} +else +{ + module local slash = / ; +} + rule split ( path ) { - + return [ SUBST $(<[1]) "^([/$(SLASH)]+).*" $1 ] # rooting slash(es), if any + [ split $(<) "[/$(SLASH)]" ] # the rest. + ; +} + +rule join ( elements * ) +{ + local slashes = $(slash) / ; + local result prev = $(elements[1]) ; + for local e in $(elements[2-]) + {` + if ! ( $(prev) in $(slashes) ) && + } } \ No newline at end of file diff --git a/new/property.jam b/new/property.jam new file mode 100644 index 000000000..8e7a064c6 --- /dev/null +++ b/new/property.jam @@ -0,0 +1,33 @@ +import errors ; + +# Given a property name, return the corresponding feature name +rule get-feature ( property ) +{ + return [ SUBST $(property:G) ^<(.*)> $1 ] ; +} + +rule is-valid ( property ) +{ + import feature ; + if ! $(property:G) + { + return ; + } + else + { + local f = [ get-feature $(property) ] ; + local value = $(property:G=) ; + + if [ features.is-valid $(f) ] && + ( free in [ features.attributes $(f) ] + || $(value) in [ features.values $(f) ] ) + + { + return true ; + } + else + { + return ; + } + } +} diff --git a/new/readme.txt b/new/readme.txt new file mode 100644 index 000000000..a7f7af4b8 --- /dev/null +++ b/new/readme.txt @@ -0,0 +1,9 @@ +Development code for new build system. To test, execute: + + jam -sBOOST_BUILD_PATH=.:$BOOST_ROOT -sBOOST_BUILD_TEST=1 -sJAMFILE=test.jam + +on unix, or + + jam -sBOOST_BUILD_PATH=.;%BOOST_ROOT% -sBOOST_BUILD_TEST=1 -sJAMFILE=test.jam + +on windows diff --git a/new/sequence.jam b/new/sequence.jam new file mode 100644 index 000000000..6c7523432 --- /dev/null +++ b/new/sequence.jam @@ -0,0 +1,120 @@ +# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears in +# all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. + +import assert ; + +# Note that algorithms in this module execute largely in the caller's +# module namespace, so that local rules can be used as function +# objects. Also note that most predicates can be multi-element +# lists. In that case, all but the first element are prepended to the +# first argument which is passed to the rule named by the first +# element. + +# Return the elements e of $(sequence) for which [ $(predicate) e ] is +# has a non-null value. +rule filter ( predicate + : sequence * ) +{ + # trailing underscores hopefully prevent collisions with module + # locals in the caller + local result__ ; + + module [ CALLER_MODULE ] + { + for local e in $(sequence) + { + if [ $(predicate) $(e) ] + { + result__ += $(e) ; + } + } + } + return $(result__) ; +} + +rule less ( a b ) +{ + if $(a) < $(b) + { + return true ; + } +} + +# insertion-sort s using the BinaryPredicate ordered. +rule insertion-sort ( s * : ordered * ) +{ + ordered ?= sequence.less ; + local result__ = $(s[1]) ; + module [ CALLER_MODULE ] + { + for local x in $(s[2-]) + { + local head tail ; + tail = $(result__) ; + while $(tail) && [ $(ordered) $(tail[1]) $(x) ] + { + head += $(tail[1]) ; + tail = $(tail[2-]) ; + } + result__ = $(head) $(x) $(tail) ; + } + } + return $(result__) ; +} + +# join the elements of s into one long string. If joint is supplied, it is used as a separator. +rule join ( s * : joint ? ) +{ + local result ; + joint ?= "" ; + for local x in $(s) + { + result = $(result)$(joint)$(x) ; + result ?= $(x) ; + } + return $(result) ; +} + +local rule __test__ ( ) +{ + # use a unique module so we can test the use of local rules. + module sequence.__test__ + { + + local rule is-even ( n ) + { + if $(n) in 0 2 4 6 8 + { + return true ; + } + } + + assert.result 4 6 4 2 8 + : sequence.filter is-even : 1 4 6 3 4 7 2 3 8 ; + + # test that argument binding works + local rule is-equal-test ( x y ) + { + if $(x) = $(y) + { + return true ; + } + } + + assert.result 3 3 3 : sequence.filter is-equal-test 3 : 1 2 3 4 3 5 3 5 7 ; + + local rule test-greater ( a b ) + { + if $(a) > $(b) + { + return true ; + } + } + + assert.result 1 2 3 4 5 6 7 8 9 : sequence.insertion-sort 9 6 5 3 8 7 1 2 4 ; + assert.result 9 8 7 6 5 4 3 2 1 : sequence.insertion-sort 9 6 5 3 8 7 1 2 4 : test-greater ; + assert.result foo-bar-baz : sequence.join foo bar baz : - ; + assert.result substandard : sequence.join sub stan dard ; + } +} \ No newline at end of file diff --git a/new/test.jam b/new/test.jam new file mode 100644 index 000000000..16f9a2312 --- /dev/null +++ b/new/test.jam @@ -0,0 +1,2 @@ +import feature ; +import os ; diff --git a/test/check-arguments.jam b/test/check-arguments.jam index 4baa401f5..c87bf3a51 100644 --- a/test/check-arguments.jam +++ b/test/check-arguments.jam @@ -10,10 +10,13 @@ include recursive.jam ; -# A prefix for all of the jam code we're going to test +# Prefixes for all of the jam code we're going to test local ECHO_ARGS = "include echo_args.jam ; echo_args " ; +local ECHO_VARARGS = "include echo_args.jam ; echo_varargs " + ; + # Check that it will find missing arguments Jam-fail $(ECHO_ARGS)";" : "missing argument a" @@ -52,3 +55,19 @@ Jam $(ECHO_ARGS)"1 : 2 : 3 4 ;" : "a= 1 b= c= : d= 2 : e= 3 4" ; Jam $(ECHO_ARGS)"1 : 2 : 3 4 5 ;" : "a= 1 b= c= : d= 2 : e= 3 4 5" ; + +# +# Check varargs +# +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 : 8 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7 : 8" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 : 8 : 9 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7 : 8 : 9" ; + + diff --git a/test/check-jam-patches.jam b/test/check-jam-patches.jam index 2db5f8b4d..4d8c86a73 100644 --- a/test/check-jam-patches.jam +++ b/test/check-jam-patches.jam @@ -14,6 +14,36 @@ if $(NT) Jam "include test_nt_line_length.jam ;" ; } +# a little utility for assertions +rule identity ( list * ) +{ + return $(list) ; +} + +# +# test rule indirection +# +rule select ( n list * ) +{ + return $(list[$(n)]) ; +} + +rule indirect1 ( rule + : args * ) +{ + return [ $(rule) $(args) ] ; +} + +assert-equal a : indirect1 select 1 : a b c d e ; +assert-equal b : indirect1 select 2 : a b c d e ; + +x = reset ; +rule reset-x ( new-value ) +{ + x = $(new-value) ; +} +$(x)-x bar ; # invokes reset-x... +assert-equal bar : identity $(x) ; # which changes x + # Check that unmatched subst returns an empty list assert-equal # nothing : SUBST "abc" "d+" x ; @@ -92,12 +122,6 @@ assert-equal x y x-y assert-index -3--2 : c d ; } -# a little utility for assertions -rule identity ( list * ) -{ - return $(list) ; -} - # # test module primitives # @@ -109,7 +133,8 @@ rule identity ( list * ) rule my_module.not_really ( ) { return something ; } - IMPORT my_module : : identity : id ; + # import the identity rule into my_module as "id" + IMPORT : identity : my_module : id ; module my_module { # assert-equal operates in its own module, so call id in here and use @@ -120,7 +145,10 @@ rule identity ( list * ) module local w y ; module local x2 x3 z = 1 2 3 ; - module local x3 ; # should reset x3 + module local x3 ; # should not affect x3 + assert-equal 1 2 3 : identity $(x3) ; + + module local x3 = ; # should reset x3 rule shift1 ( ) { @@ -144,8 +172,27 @@ rule identity ( list * ) local rule not_really ( ) { return nothing ; } } + local expected = shift1 shift2 get ; + if ! ( $(expected) in [ RULENAMES my_module ] ) + || ! ( [ RULENAMES my_module ] in $(expected) ) + { + EXIT "[ RULENAMES my_module ] =" [ RULENAMES my_module ] "!=" shift1 shift2 get ; + } + + # show that not_really was actually a local definition assert-equal something : my_module.not_really ; + + if not_really in [ RULENAMES my_module ] + { + EXIT unexpectedly found local rule "not_really" in "my_module" ; + } + EXPORT my_module : not_really ; + + if ! ( not_really in [ RULENAMES my_module ] ) + { + EXIT unexpectedly failed to find exported rule "not_really" in "my_module" ; + } my_module.shift1 ; y = $(y[2-]) ; @@ -167,7 +214,9 @@ rule identity ( list * ) shift1 nothing ; assert-equal $(x) : identity $(y) ; - IMPORT : my_module : shift1 shift2 : shifty ; + # import my_module.shift1 into the global module as "shifty", and + # my_module.shift2 into the global module as "shift2". + IMPORT my_module : shift1 shift2 : : shifty shift2 ; shifty ; y = $(y[2-]) ; @@ -177,8 +226,9 @@ rule identity ( list * ) y = $(y[2-]) ; assert-equal $(x) : identity $(y) ; - - IMPORT : my_module ; + # import everything from my_module into the global module using + # the same names. + IMPORT my_module : [ RULENAMES my_module ] : : [ RULENAMES my_module ] ; shift1 ; y = $(y[2-]) ; diff --git a/test/echo_args.jam b/test/echo_args.jam index b6c290e84..99fff9868 100644 --- a/test/echo_args.jam +++ b/test/echo_args.jam @@ -1,4 +1,16 @@ rule echo_args ( a b ? c ? : d + : e * ) { - ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) ; + ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) ; } + +rule echo_varargs ( a b ? c ? : d + : e * : * ) +{ + ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) + ": rest= "$(4[1]) $(4[2]) + ": "$(5[1]) $(5[2]) + ": "$(6[1]) $(6[2]) + ": "$(7[1]) $(7[2]) + ": "$(8[1]) $(8[2]) + ": "$(9[1]) $(9[2]) ; +} + diff --git a/v1/build_system.htm b/v1/build_system.htm index 6ec1afc93..a561ac0c1 100644 --- a/v1/build_system.htm +++ b/v1/build_system.htm @@ -138,7 +138,10 @@ @@ -154,8 +157,10 @@ Debugging Support @@ -1312,8 +1317,35 @@ rule foobar { ECHO foobar ; } # a trivial rule $(x)bar ; # invokes foobar + + Furthermore, if the first expression expands to more than one + list item, everything after the first item becomes part of the + first argument. This allows a crude form of argument binding: -

Argument lists

+
+# return the elements of sequence for which predicate returns non-nil
+rule filter ( sequence * : predicate + )
+{
+    local result ;
+    for local x in $(sequence)
+    {
+        if [ $(predicate) $(x) ] { result += $(x); }
+    }
+    return $(result);
+}
+
+# true iff x == y
+rule equal ( x y )
+{
+    if $(x) = $(y) { return true; }
+}
+
+# bind 3 to the first argument of equal
+ECHO [ filter 1 2 3 4 5 4 3 : equal 3 ] ; # prints "3 3"
+
+
+ +

Argument lists

You can now describe the arguments accepted by a rule, and refer to them by name within the rule. For example, the following prints ``I'm @@ -1350,7 +1382,11 @@ report I 2 : sorry : Joe Dave Pete ; * - Bind to zero or more unbound elements of the actual argument. + Bind to zero or more unbound elements of the actual + argument. When ``*'' appears where an argument name + is expected, any number of additional arguments are + accepted. This feature can be used to implement + "varargs" rules. + @@ -1358,7 +1394,7 @@ report I 2 : sorry : Joe Dave Pete ; Bind to one or more unbound elements of the actual argument. -

The acutal and formal arguments are checked for inconsistencies, which +

The actual and formal arguments are checked for inconsistencies, which cause Jam to exit with an error code:

@@ -1382,8 +1418,11 @@ report I 2 : sorry : Joe Dave Pete ;

Module Support

Boost Jam introduces support for modules, which provide some - rudimentary namespace protection for rules and variables. A new keyword, - ``module'' was also introduced. + rudimentary namespace protection for rules and variables. A new + keyword, ``module'' was also introduced. The features + described in this section are primitives, meaning that + they are meant to provide the operations needed to write Jam + rules which provide a more elegant module interface.

@@ -1423,13 +1462,6 @@ module your_module

-

Note that, like the IMPORT and CALLER_MODULE rules, module - declaration is really a primitive, and is best used through a - wrapper interface which implements a system of module files using the - built-in INCLUDE rule. -

Local Variables
@@ -1493,35 +1525,81 @@ ECHO [ M.get x ] ; # prints "a b c" +
Local Rules
+ +
+ local rule rulename... +
+ +

The rule is declared locally to the current module. It is + not entered in the global module with qualification, and its + name will not appear in the result of +

+ [ RULENAMES module-name ]. +
+ +
The RULENAMES Rule
+ +
+
+rule RULENAMES ( module ? )
+
+
+ Returns a list of the names of all non-local rules in the + given module. If module is ommitted, the names of all + non-local rules in the global module are returned. +
The IMPORT Rule
IMPORT allows rule name aliasing across modules:
-rule IMPORT ( target_module ? : source_module ?
-                : rule_names * : target_names * )
+rule IMPORT (  source_module ? : source_rules *
+                : target_module ? : target_rules * )
 
- Rules are copied from the source_module into the - target_module. If either module name is missing, the global - module is used in its place. If any rule_names are supplied, - they specify which rules from the source_module to import; - otherwise all rules are imported. The rules are given the names in - target_names; if not enough target_names are supplied, - the excess rules are given the same names as they had in - source_module. For example, + The IMPORT rule copies rules from the source_module into the + target_module as local rules. If either source_module or + target_module is not supplied, it refers to the global + module. source_rules specifies which rules from the source_module to + import; TARGET_RULES specifies the names to give those rules in + target_module. If source_rules contains a name which doesn't + correspond to a rule in source_module, or if it contains a different + number of items than target_rules, an error is issued. For example,
-IMPORT m1 : m2 ;               # imports all rules from m2 into m1
-IMPORT    : m2 : my-rule ;     # imports m2.my-rule into the global module
-IMPORT m1 : m2 : r1 x : r2 y ; # imports m2.r1 as r2 and m2.x as y into m1
+# import m1.rule1 into m2 as local rule m1-rule1.
+IMPORT m1 : rule1 : m2 : m1-rule1 ;
+
+# import all non-local rules from m1 into m2
+IMPORT m1 : [ RULENAMES m1 ] : m2 : [ RULENAMES m1 ] ;
+
+
+ +
The EXPORT Rule
+ + EXPORT allows rule name aliasing across modules: + +
+
+rule EXPORT ( module ? : rules * )
+
+
+ The EXPORT rule marks rules from the source_module as non-local + (and thus exportable). If an element of rules does not name a + rule in module, an error is issued. For example, +
+
+module X {
+  local rule r { ECHO X.r ; }
+}
+IMPORT X : r : : r ; # error - r is local in X
+EXPORT X : r ;
+IMPORT X : r : : r ; # OK.
 
- Like the module declaration syntax and - the CALLER_MODULE rule, this - rule is a primitive, and is probably best wrapped in a Jam rule.
The CALLER_MODULE Rule
@@ -1555,10 +1633,6 @@ callers = [ X.get-caller ] [ Y.call-X ] [ X.call-Y ] ; ECHO {$(callers)} ; - Like the module declaration syntax and - the IMPORT rule, this rule is a - primitive, and is probably best wrapped in a Jam rule. - @@ -1717,6 +1791,17 @@ ECHO [ SUBST xyz (.)(.)(.) [$1] ($2) {$3} ] ;

Debugging Support

+
The BACKTRACE rule
+ +
+rule BACKTRACE ( )
+
+ + Returns a list of quadruples: filename line module + rulename..., describing each shallower level of the call + stack. This rule can be used to generate useful diagnostic + messages from Jam rules. +

The -d command-line option admits new arguments:

    @@ -1728,6 +1813,13 @@ ECHO [ SUBST xyz (.)(.)(.) [$1] ($2) {$3} ] ;
  • -d+11 - enables parser debugging, if Jam has been compiled with the "--debug" option to the parser generator named by $(YACC). + +
  • -d+12 - enables dependency graph output + . This feature was ``stolen'' from a version of Jam + modified by Craig + McPheeters. +
diff --git a/v2/build/feature.jam b/v2/build/feature.jam new file mode 100644 index 000000000..2a13b2e14 --- /dev/null +++ b/v2/build/feature.jam @@ -0,0 +1,383 @@ +# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears in +# all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. + +import errors : error lol->list ; +import property ; +import sequence ; +import regex ; +import set ; + +module local all-attributes = + + implicit # features whose values alone identify the + # feature. For example, a user is not required to + # write "gcc", but can simply write + # "gcc". Implicit feature names also don't appear in + # variant paths, although the values do. Thus: + # bin/gcc/... as opposed to bin/toolset-gcc/.... There + # should typically be only a few such features, to + # avoid possible name clashes. + + executed # the feature corresponds to the name of a module + # containing an execute rule, used to actually prepare + # the build. For example, the toolset feature would be + # executed. + + + composite # features which actually correspond to groups of + # properties. For example, a build variant is a + # composite feature. When generating targets from a + # set of build properties, composite features are + # recursively expanded and /added/ to the build + # property set, so rules can find them if + # neccessary. Non-composite non-free features override + # components of composite features in a build property + # set. + + + optional # An optional feature is allowed to have no value at + # all in a particular build. Normal non-free features + # are always given the first of their values if no + # value is otherwise specified. + + + symmetric # A symmetric feature has no default value, and is + # therefore not automatically included in all + # variants. A symmetric feature, when relevant to the + # toolset, always generates a corresponding subvariant + # directory. + + free # as described in previous documentation + + path # the (free) feature's value describes a path which + # might be given relative to the directory of the + # Jamfile. + + dependency # the value of the (free) feature specifies a + # dependency of the target. + + propagated # when used in a build request, the (free) feature is + # propagated to top-level targets which are + # dependencies of the requested build. Propagated + # features would be most useful for settings such as + # warning levels, output message style, etc., which + # don't affect the built products at all. + ; + +module local all-features ; +module local all-implicit-values ; + +# declare a new feature with the given name, values, and attributes. +rule feature ( name : values * : attributes * ) +{ + local error ; + + # if there are any unknown attributes... + if ! ( $(attributes) in $(all-attributes) ) + { + error = unknown attributes: + [ set.difference $(attributes) : $(all-attributes) ] ; + } + else if $(name) in $(all-features) + { + error = feature already defined: ; + } + else if implicit in $(attributes) + { + if free in $(attributes) + { + error = free features cannot also be implicit ; + } + } + + if $(error) + { + error $(error) + : "in" feature declaration: + : feature [ errors.lol->list $(1) : $(2) : $(3) ] ; + } + + module local $(name).values ; + module local $(name).attributes = $(attributes) ; + module local $(name).subfeatures = ; + + all-features += $(name) ; + extend $(name) : $(values) ; +} + +# returns true iff all elements of names are valid features. +rule valid ( name + ) +{ + if $(names) in $(all-features) + { + return true ; + } +} + +# return the attibutes of the given feature +rule attributes ( feature ) +{ + return $($(feature).attributes) ; +} + +# return the values of the given feature +rule values ( feature ) +{ + return $($(feature).values) ; +} + +rule implied-feature ( implicit-value ) +{ + local feature = $($(implicit-value).implicit-feature) ; + if ! $(feature) + { + error \"$(implicit-value)\" is not a value of an implicit feature ; + } + return $(feature) ; +} + +local rule find-implied-subfeature ( feature subvalue : value-string ? ) +{ + local v + = subfeature($(feature),$(subvalue)) + subfeature($(feature),$(value-string),$(subvalue)) ; + + # declaring these module local here prevents us from picking up + # enclosing definitions. + module local $(v) ; + + local subfeature = $($(v)) ; + return $(subfeature[1]) ; +} + +rule implied-subfeature ( feature subvalue : value-string ? ) +{ + local subfeature = [ find-implied-subfeature $(feature) $(subvalue) + : $(value-string) ] ; + + if ! $(subfeature) + { + error \"$(subvalue)\" is not a known subfeature value of + feature \"$(feature)\" ; + } + + return $(subfeature) ; +} + +# generate an error if the feature is unknown +local rule validate-feature ( feature ) +{ + if ! $(feature) in $(all-features) + { + error unknown feature \"$(feature)\" ; + } +} + +# expand-subfeatures toolset : gcc-2.95.2-linux-x86 -> gcc 2.95.2 linux x86 +# equivalent to: +# expand-subfeatures : gcc-2.95.2-linux-x86 +local rule expand-subfeatures ( feature ? : value ) +{ + if $(feature) + { + validate-feature $(feature) ; + } + + local components = [ regex.split $(value) "-" ] ; + if ! $(feature) + { + feature = [ implied-feature $(components[1]) ] ; + } + + # get the top-level feature's value + local value = $(components[1]:G=) ; + + local result = $(components[1]:G=$(feature)) ; + for local subvalue in $(components[2-]) + { + local subfeature = [ implied-subfeature $(feature) $(subvalue) : $(value) ] ; + result += $(subvalue:G=$(feature)-$(subfeature)) ; + } + + return $(result) ; +} + +local rule extend-feature ( feature : values * ) +{ + validate-feature $(feature) ; + if implicit in $(attributes) + { + for local v in $(values) + { + module local $(v).implicit-feature ; + if $($(v).implicit-feature) + { + error $(v) is already associated with the \"$($(v).implicit-feature)\" feature ; + } + $(v).implicit-feature = $(feature) ; + } + + all-implicit-values += $(values) ; + } + $(feature).values += $(values) ; +} + +local rule validate-value-string ( feature value-string ) +{ + local values = $(value-string) ; + if $($(feature).subfeatures) + { + values = [ regex.split $(value-string) - ] ; + } + + if ! ( $(values[1]) in $($(feature).values) ) + { + return \"$(values[1])\" is not a known value of feature \"$(feature)\" ; + } + + if $(values[2]) + { + # this will validate any subfeature values in value-string + implied-subfeature $(feature) [ sequence.join $(values[2-]) - ] + : $(values[1]) ; + } + +} + +# extend-subfeature toolset gcc-2.95.2 : target-platform : mingw ; +# extend-subfeature toolset : target-platform : mingw ; +local rule extend-subfeature ( feature value-string ? : subfeature : subvalues * ) +{ + validate-feature $(feature) ; + if $(value-string) + { + validate-value-string $(feature) $(value-string) ; + } + + for local subvalue in $(subvalues) + { + local v + = subfeature($(feature),$(value-string),$(subvalue)) + subfeature($(feature),$(subvalue)) ; + module local $(v[1]) = $(subfeature) ; + } +} + +rule extend ( feature-or-property subfeature ? : values * ) +{ + local feature value-string ; + if $(feature-or-property:G) + { + feature = [ property.get-feature $(feature-or-property) ] ; + value-string = $(feature-or-property:G=) ; + } + else + { + feature = $(feature-or-property) ; + } + + if $(subfeature) + { + extend-subfeature $(feature) $(value-string) + : $(subfeature) : $(values) ; + } + else + { + if $(value-string) + { + error can only be specify a property as the first argument + when extending a subfeature + : usage: + : " extend" feature ":" values... + : " | extend" value-string subfeature ":" values... + ; + } + + extend-feature $(feature) : $(values) ; + } +} + +# subfeature +# +# subfeature toolset gcc-2.95.2 target-platform : aix linux mac cygwin +# +rule subfeature ( feature value-string ? : subfeature : subvalues * ) +{ + validate-feature $(feature) ; + if $(subfeature) in $($(feature).subfeatures) + { + error \"$(subfeature)\" already declared as a subfeature of \"$(feature)\" ; + } + $(feature).subfeatures += $(subfeature) ; + extend-subfeature $(feature) $(value-string) : $(subfeature) : $(subvalues) ; +} + +# tests of module features +local rule __test__ ( ) +{ + import errors : try catch ; + import assert ; + feature __test__toolset : gcc : implicit executed ; + feature __test__define : : free ; + + extend-feature __test__toolset : msvc metrowerks ; + subfeature __test__toolset gcc : version : 2.95.2 2.95.3 2.95.4 + 3.0 3.0.1 3.0.2 ; + + assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 : + expand-subfeatures __test__toolset : gcc-3.0.1 ; + + assert.result <__test__toolset>gcc <__test__toolset-version>3.0.1 : + expand-subfeatures : gcc-3.0.1 ; + + feature __test__dummy : dummy1 dummy2 ; + subfeature __test__dummy : subdummy : x y z ; + + # test error checking + try ; + { + validate-feature __test__foobar ; + } + catch unknown feature ; + + try ; + { + feature __test__foobar : : baz ; + } + catch unknown attributes: baz ; + + feature __test__feature1 ; + try ; + { + feature __test__feature1 ; + } + catch feature already defined: ; + + try ; + { + feature __test__feature2 : : free implicit ; + } + catch free features cannot also be implicit ; + + try ; + { + implied-feature lackluster ; + } + catch \"lackluster\" is not a value of an implicit feature ; + + try ; + { + implied-subfeature __test__toolset 3.0.1 ; + } + catch \"3.0.1\" is not a known subfeature value of + feature \"__test__toolset\" ; + + try ; + { + implied-subfeature __test__toolset not-a-version : gcc ; + } + catch \"not-a-version\" is not a known subfeature value of + feature \"__test__toolset\" ; +} \ No newline at end of file diff --git a/v2/build/property.jam b/v2/build/property.jam new file mode 100644 index 000000000..8e7a064c6 --- /dev/null +++ b/v2/build/property.jam @@ -0,0 +1,33 @@ +import errors ; + +# Given a property name, return the corresponding feature name +rule get-feature ( property ) +{ + return [ SUBST $(property:G) ^<(.*)> $1 ] ; +} + +rule is-valid ( property ) +{ + import feature ; + if ! $(property:G) + { + return ; + } + else + { + local f = [ get-feature $(property) ] ; + local value = $(property:G=) ; + + if [ features.is-valid $(f) ] && + ( free in [ features.attributes $(f) ] + || $(value) in [ features.values $(f) ] ) + + { + return true ; + } + else + { + return ; + } + } +} diff --git a/v2/build/readme.txt b/v2/build/readme.txt new file mode 100644 index 000000000..a7f7af4b8 --- /dev/null +++ b/v2/build/readme.txt @@ -0,0 +1,9 @@ +Development code for new build system. To test, execute: + + jam -sBOOST_BUILD_PATH=.:$BOOST_ROOT -sBOOST_BUILD_TEST=1 -sJAMFILE=test.jam + +on unix, or + + jam -sBOOST_BUILD_PATH=.;%BOOST_ROOT% -sBOOST_BUILD_TEST=1 -sJAMFILE=test.jam + +on windows diff --git a/v2/doc/boost-build.jam b/v2/doc/boost-build.jam index 9787e78ec..ff7a528b2 100644 --- a/v2/doc/boost-build.jam +++ b/v2/doc/boost-build.jam @@ -7,7 +7,7 @@ SEARCH on modules.jam = $(BOOST_BUILD_PATH) ; module modules { include modules.jam ; } # Bring the import rule into the global module -IMPORT : modules : import ; +IMPORT modules : import : : import ; import modules ; # The modules module can tolerate being included twice import build-system ; diff --git a/v2/errors.jam b/v2/errors.jam index 699eef6b3..c3b44319f 100644 --- a/v2/errors.jam +++ b/v2/errors.jam @@ -3,44 +3,153 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. -# A utility rule used to report the including module when there is an error, so -# that editors may find it. -rule report-module ( prefix ? : suffix ? : frames ? ) +# Print a stack backtrace leading to this rule's caller. Each +# argument represents a line of output to be printed after the first +# line of the backtrace. +rule backtrace ( skip-frames messages * : * ) { - frames ?= 1 ; - # We report some large line number so that emacs, etc., will at least locate the file. - ECHO $(prefix) [ modules.binding [ CALLER_MODULE $(frames) ] ] ":" line 99999 $(suffix) ; -} - -rule backtrace -{ - local digits = 1 2 3 4 5 6 7 8 9 ; + local frame-skips = 5 9 13 17 21 25 29 33 37 41 45 49 53 57 61 65 69 73 77 81 ; + local drop-elements = $(frame-skips[$(skip-frames)]) ; + if ! ( $(skip-frames) in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ) + { + ECHO warning: backtrace doesn't support skipping + $(skip-frames) frames; using 1 instead. ; + drop-elements = 5 ; + } + + # get the whole backtrace, then drop the initial quadruples + # corresponding to the frames that must be skipped. local bt = [ BACKTRACE ] ; - bt = $(bt[5-]) ; + bt = $(bt[$(drop-elements)-]) ; + + local args = messages 2 3 4 5 6 7 8 9 ; while $(bt) { ECHO $(bt[1]):$(bt[2]): "in" $(bt[4]) ; - for local n in $(digits) + # the first time through, print each argument on a separate + # line + for local n in $(args) { if $($(n))-is-not-empty { ECHO $($(n)) ; } } - digits = ; - + args = ; # kill args so that this never happens again + + # Move on to the next quadruple bt = $(bt[5-]) ; } } -rule error +module local args = messages 2 3 4 5 6 7 8 9 ; +module local disabled last-$(args) ; + +# try-catch -- +# +# This is not really an exception-handling mechanism, but it does +# allow us to perform some error-checking on our +# error-checking. Errors are suppressed after a try, and the first one +# is recorded. Use catch to check that the error message matched +# expectations. + +# begin looking for error messages +rule try ( ) { - backtrace error: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; - EXIT ; + disabled += true ; + last-$(args) = ; } +# stop looking for error messages; generate an error if an argument of +# messages is not found in the corresponding argument in the error call. +rule catch ( messages * : * ) +{ + import sequence ; + disabled = $(disabled[2-]) ; # pop the stack + + for local n in $(args) + { + if ! $($(n)) in $(last-$(n)) + { + local v = [ sequence.join $($(n)) : " " ] ; + v ?= "" ; + local joined = [ sequence.join $(last-$(n)) : " " ] ; + + last-$(args) = ; + error-skip-frames 3 expected \"$(v)\" in argument $(n) of error + : got \"$(joined)\" instead ; + } + } +} + +rule error-skip-frames ( skip-frames messages * : * ) +{ + if ! $(disabled) + { + backtrace $(skip-frames) error: $(messages) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + EXIT ; + } + else if ! $(last-$(args)) + { + for local n in $(args) + { + last-$(n) = $($(n)) ; + } + } +} + +# Print an error message with a stack backtrace and exit. +rule error ( messages * : * ) +{ + error-skip-frames 3 $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +} + +# Print a warning message with a stack backtrace and exit. rule warning { - backtrace warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; + backtrace 2 warning: $(1) : $(2) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ; +} + +# convert an arbitrary argument list into a list with ":" separators +# and quoted elements representing the same information. This is +# mostly useful for formatting descriptions of the arguments with +# which a rule was called when reporting an error. +rule lol->list ( * ) +{ + local result ; + local remaining = 1 2 3 4 5 6 7 8 9 ; + while $($(remaining)) + { + local n = $(remaining[1]) ; + remaining = $(remaining[2-]) ; + + if $(n) != 1 + { + result += ":" ; + } + result += \"$(n)\" ; + } + return $(result) ; +} + +rule __test__ ( ) +{ + # show that we can correctly catch an expected error + try ; + { + error an error occurred : somewhere ; + } + catch an error occurred : somewhere ; + + # show that unexpected errors generate real errors + try ; + { + try ; + { + error an error occurred : somewhere ; + } + catch an error occurred : nowhere ; + } + catch expected \"nowhere\" in argument 2 ; } diff --git a/v2/modules.jam b/v2/modules.jam index 3c1869ae7..1d1ab0ffa 100644 --- a/v2/modules.jam +++ b/v2/modules.jam @@ -5,6 +5,8 @@ # Keep a record so that no module is included multiple times module local loaded-modules ; +module local loading-modules ; +module local untested ; # meant to be invoked from import when no __test__ rule is defined in a given # module @@ -19,24 +21,22 @@ rule binding ( module ) return $($(module).__binding__) ; } -# load the indicated module. Any members of rules-opt will be available without -# qualification in the caller's module. Any members of rename-opt will be taken -# as the names of the rules in the caller's module, in place of the names they -# have in the imported module. If rules-opt = '*', all rules from the indicated -# module are imported into the caller's module. -rule import ( module-name : rules-opt * : rename-opt * ) +# load the indicated module if it is not already loaded. +rule load ( module-name ) { - # First see if the module needs to be loaded if ! ( $(module-name) in $(loaded-modules) ) { loaded-modules += $(module-name) ; + loading-modules += $(module-name) ; + local suppress-test = $(untested[1]) ; # suppress tests until all recursive loads are complete. + untested += $(module-name) ; # add the module to the stack of untested modules module $(module-name) { module local __name__ = $(module-name) ; # Prepare a default behavior, in case no __test__ is defined. - IMPORT $(module-name) : modules : no_test_defined : __test__ ; + IMPORT modules : no_test_defined : $(module-name) : __test__ ; # Add some grist so that the module will have a unique target name local module-target = $(module-name:G=module@:S=.jam) ; @@ -45,34 +45,61 @@ rule import ( module-name : rules-opt * : rename-opt * ) BINDRULE on $(module-target) = modules.record-binding ; include $(module-name:G=module@:S=.jam) ; - # run the module's test, if any. - if nonempty$(BOOST_BUILD_TEST) + } + loading-modules = $(loading-modules[1--2]) ; + + if ! $(suppress-test) && $(BOOST_BUILD_TEST)-is-nonempty + { + # run any pending tests + for local m in $(untested) { - ECHO testing module $(module-name)... ; - local ignored = [ __test__ ] ; + ECHO testing module $(m)... ; + module $(m) + { + __test__ ; + } } + untested = ; } } - - # If any rules are to be imported, do so now. - if $(rules-opt) + else if $(module-name) in $(loading-modules) { - if $(rules-opt) = * - { - rules-opt = ; - } - IMPORT [ CALLER_MODULE ] - : $(module-name) : $(rules-opt) : $(rename-opt) ; + ECHO loading \"$(module-name)\" ; + ECHO circular module loading dependency: ; + EXIT $(loading-modules) $(module-name) ; } } -# This helper is used by import (above) to record the binding (path) of +# This helper is used by load (above) to record the binding (path) of # each loaded module. rule record-binding ( module-target : binding ) { module local $(module-target:G=:S=).__binding__ = $(binding) ; } +# load the indicated module and import rule names into the current +# module. Any members of rules-opt will be available without +# qualification in the caller's module. Any members of rename-opt will +# be taken as the names of the rules in the caller's module, in place +# of the names they have in the imported module. If rules-opt = '*', +# all rules from the indicated module are imported into the caller's +# module. If rename-opt is supplied, it must have the same number of +# elements as rules-opt. +rule import ( module-name : rules-opt * : rename-opt * ) +{ + load $(module-name) ; + + local source-names = $(rules-opt) ; + if $(rules-opt) = * + { + source-names = [ RULENAMES module-name ] ; + } + + local target-names = $(rename-opt) ; + target-names ?= $(source-names) ; + IMPORT $(module-name) : $(source-names) : [ CALLER_MODULE ] : $(target-names) ; +} + # Returns the module-local value of a variable. rule peek ( module-name variable ) { @@ -82,9 +109,10 @@ rule peek ( module-name variable ) } } -rule __test__ ( ) +local rule __test__ ( ) { import assert ; + module modules.__test__ { module local foo = bar ; diff --git a/v2/os.path.jam b/v2/os.path.jam index 172598282..0e6d0c28c 100644 --- a/v2/os.path.jam +++ b/v2/os.path.jam @@ -3,7 +3,28 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. +if $(NT) +{ + module local slash = \\ ; +} +else +{ + module local slash = / ; +} + rule split ( path ) { - + return [ SUBST $(<[1]) "^([/$(SLASH)]+).*" $1 ] # rooting slash(es), if any + [ split $(<) "[/$(SLASH)]" ] # the rest. + ; +} + +rule join ( elements * ) +{ + local slashes = $(slash) / ; + local result prev = $(elements[1]) ; + for local e in $(elements[2-]) + {` + if ! ( $(prev) in $(slashes) ) && + } } \ No newline at end of file diff --git a/v2/test/check-arguments.jam b/v2/test/check-arguments.jam index 4baa401f5..c87bf3a51 100644 --- a/v2/test/check-arguments.jam +++ b/v2/test/check-arguments.jam @@ -10,10 +10,13 @@ include recursive.jam ; -# A prefix for all of the jam code we're going to test +# Prefixes for all of the jam code we're going to test local ECHO_ARGS = "include echo_args.jam ; echo_args " ; +local ECHO_VARARGS = "include echo_args.jam ; echo_varargs " + ; + # Check that it will find missing arguments Jam-fail $(ECHO_ARGS)";" : "missing argument a" @@ -52,3 +55,19 @@ Jam $(ECHO_ARGS)"1 : 2 : 3 4 ;" : "a= 1 b= c= : d= 2 : e= 3 4" ; Jam $(ECHO_ARGS)"1 : 2 : 3 4 5 ;" : "a= 1 b= c= : d= 2 : e= 3 4 5" ; + +# +# Check varargs +# +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 : 8 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7 : 8" ; +Jam $(ECHO_VARARGS)"1 : 2 : 3 4 5 : 6 7 : 8 : 9 ;" + : "a= 1 b= c= : d= 2 : e= 3 4 5 : rest= 6 7 : 8 : 9" ; + + diff --git a/v2/test/check-jam-patches.jam b/v2/test/check-jam-patches.jam index 2db5f8b4d..4d8c86a73 100644 --- a/v2/test/check-jam-patches.jam +++ b/v2/test/check-jam-patches.jam @@ -14,6 +14,36 @@ if $(NT) Jam "include test_nt_line_length.jam ;" ; } +# a little utility for assertions +rule identity ( list * ) +{ + return $(list) ; +} + +# +# test rule indirection +# +rule select ( n list * ) +{ + return $(list[$(n)]) ; +} + +rule indirect1 ( rule + : args * ) +{ + return [ $(rule) $(args) ] ; +} + +assert-equal a : indirect1 select 1 : a b c d e ; +assert-equal b : indirect1 select 2 : a b c d e ; + +x = reset ; +rule reset-x ( new-value ) +{ + x = $(new-value) ; +} +$(x)-x bar ; # invokes reset-x... +assert-equal bar : identity $(x) ; # which changes x + # Check that unmatched subst returns an empty list assert-equal # nothing : SUBST "abc" "d+" x ; @@ -92,12 +122,6 @@ assert-equal x y x-y assert-index -3--2 : c d ; } -# a little utility for assertions -rule identity ( list * ) -{ - return $(list) ; -} - # # test module primitives # @@ -109,7 +133,8 @@ rule identity ( list * ) rule my_module.not_really ( ) { return something ; } - IMPORT my_module : : identity : id ; + # import the identity rule into my_module as "id" + IMPORT : identity : my_module : id ; module my_module { # assert-equal operates in its own module, so call id in here and use @@ -120,7 +145,10 @@ rule identity ( list * ) module local w y ; module local x2 x3 z = 1 2 3 ; - module local x3 ; # should reset x3 + module local x3 ; # should not affect x3 + assert-equal 1 2 3 : identity $(x3) ; + + module local x3 = ; # should reset x3 rule shift1 ( ) { @@ -144,8 +172,27 @@ rule identity ( list * ) local rule not_really ( ) { return nothing ; } } + local expected = shift1 shift2 get ; + if ! ( $(expected) in [ RULENAMES my_module ] ) + || ! ( [ RULENAMES my_module ] in $(expected) ) + { + EXIT "[ RULENAMES my_module ] =" [ RULENAMES my_module ] "!=" shift1 shift2 get ; + } + + # show that not_really was actually a local definition assert-equal something : my_module.not_really ; + + if not_really in [ RULENAMES my_module ] + { + EXIT unexpectedly found local rule "not_really" in "my_module" ; + } + EXPORT my_module : not_really ; + + if ! ( not_really in [ RULENAMES my_module ] ) + { + EXIT unexpectedly failed to find exported rule "not_really" in "my_module" ; + } my_module.shift1 ; y = $(y[2-]) ; @@ -167,7 +214,9 @@ rule identity ( list * ) shift1 nothing ; assert-equal $(x) : identity $(y) ; - IMPORT : my_module : shift1 shift2 : shifty ; + # import my_module.shift1 into the global module as "shifty", and + # my_module.shift2 into the global module as "shift2". + IMPORT my_module : shift1 shift2 : : shifty shift2 ; shifty ; y = $(y[2-]) ; @@ -177,8 +226,9 @@ rule identity ( list * ) y = $(y[2-]) ; assert-equal $(x) : identity $(y) ; - - IMPORT : my_module ; + # import everything from my_module into the global module using + # the same names. + IMPORT my_module : [ RULENAMES my_module ] : : [ RULENAMES my_module ] ; shift1 ; y = $(y[2-]) ; diff --git a/v2/test/echo_args.jam b/v2/test/echo_args.jam index b6c290e84..99fff9868 100644 --- a/v2/test/echo_args.jam +++ b/v2/test/echo_args.jam @@ -1,4 +1,16 @@ rule echo_args ( a b ? c ? : d + : e * ) { - ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) ; + ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) ; } + +rule echo_varargs ( a b ? c ? : d + : e * : * ) +{ + ECHO a= $(a) b= $(b) c= $(c) ":" d= $(d) ":" e= $(e) + ": rest= "$(4[1]) $(4[2]) + ": "$(5[1]) $(5[2]) + ": "$(6[1]) $(6[2]) + ": "$(7[1]) $(7[2]) + ": "$(8[1]) $(8[2]) + ": "$(9[1]) $(9[2]) ; +} + diff --git a/v2/test/test.jam b/v2/test/test.jam new file mode 100644 index 000000000..16f9a2312 --- /dev/null +++ b/v2/test/test.jam @@ -0,0 +1,2 @@ +import feature ; +import os ; diff --git a/v2/util/assert.jam b/v2/util/assert.jam index 04f2c8282..609094c9e 100644 --- a/v2/util/assert.jam +++ b/v2/util/assert.jam @@ -3,53 +3,64 @@ # all copies. This software is provided "as is" without express or implied # warranty, and with no claim as to its suitability for any purpose. -import errors : error ; +import errors : error-skip-frames lol->list ; +# assert the equality of A and B rule equal ( a * : b * ) { if $(a) != $(b) { - error assertion failure: \"$(a)\" "!=" \"$(b)\" ; + error-skip-frames 3 assertion failure: \"$(a)\" "!=" \"$(b)\" ; } } -rule result ( expected * : rule-name args * ) +# assert that EXPECTED is the result of calling RULE-NAME with the +# given arguments +rule result ( expected * : rule-name args * : * ) { - + local result__ ; module [ CALLER_MODULE ] { - result = [ $(rule-name) $(args) ] ; + result__ = [ + $(rule-name) $(args) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ] ; } - if $(result) != $(expected) + if $(result__) != $(expected) { - error assertion failure: "[" $(rule-name) \"$(args)\" "]" + error-skip-frames 3 assertion failure: "[" $(rule-name) + [ lol->list $(args) : $(3) : $(4) : $(5) : $(6) : $(7) : $(8) : $(9) ] + "]" : expected: \"$(expected)\" - : got: \"$(result)\" ; + : got: \"$(result__)\" ; } } +# assert that the given variable is nonempty. rule nonempty-variable ( name ) { local empty ; if $($(variable)) = $(empty) { - error assertion failure: expecting non-empty variable $(variable) ; + error-skip-frames 3 assertion failure: expecting non-empty variable $(variable) ; } } -rule true ( rule-name args * ) +# assert that the result of calling RULE-NAME on the given arguments +# has a true logical value (is neither an empty list nor all empty +# strings). +rule true ( rule-name args * : * ) { - local result caller-module = [ CALLER_MODULE ] ; - - module $(caller-module) + local result__ ; + module [ CALLER_MODULE ] { - result = [ $(rule-name) $(args) ] ; + result__ = [ + $(rule-name) $(args) : $(2) $(3) : $(4) + : $(5) : $(6) : $(7) : $(8) : $(9) ] ; } - if ! $(result) + if ! $(result__) { - error assertion failure: expecting true result from + error-skip-frames 3 assertion failure: expecting true result from "[" $(rule-name) \"$(args)\" "]" ; } } diff --git a/v2/util/sequence.jam b/v2/util/sequence.jam new file mode 100644 index 000000000..6c7523432 --- /dev/null +++ b/v2/util/sequence.jam @@ -0,0 +1,120 @@ +# (C) Copyright David Abrahams 2001. Permission to copy, use, modify, sell and +# distribute this software is granted provided this copyright notice appears in +# all copies. This software is provided "as is" without express or implied +# warranty, and with no claim as to its suitability for any purpose. + +import assert ; + +# Note that algorithms in this module execute largely in the caller's +# module namespace, so that local rules can be used as function +# objects. Also note that most predicates can be multi-element +# lists. In that case, all but the first element are prepended to the +# first argument which is passed to the rule named by the first +# element. + +# Return the elements e of $(sequence) for which [ $(predicate) e ] is +# has a non-null value. +rule filter ( predicate + : sequence * ) +{ + # trailing underscores hopefully prevent collisions with module + # locals in the caller + local result__ ; + + module [ CALLER_MODULE ] + { + for local e in $(sequence) + { + if [ $(predicate) $(e) ] + { + result__ += $(e) ; + } + } + } + return $(result__) ; +} + +rule less ( a b ) +{ + if $(a) < $(b) + { + return true ; + } +} + +# insertion-sort s using the BinaryPredicate ordered. +rule insertion-sort ( s * : ordered * ) +{ + ordered ?= sequence.less ; + local result__ = $(s[1]) ; + module [ CALLER_MODULE ] + { + for local x in $(s[2-]) + { + local head tail ; + tail = $(result__) ; + while $(tail) && [ $(ordered) $(tail[1]) $(x) ] + { + head += $(tail[1]) ; + tail = $(tail[2-]) ; + } + result__ = $(head) $(x) $(tail) ; + } + } + return $(result__) ; +} + +# join the elements of s into one long string. If joint is supplied, it is used as a separator. +rule join ( s * : joint ? ) +{ + local result ; + joint ?= "" ; + for local x in $(s) + { + result = $(result)$(joint)$(x) ; + result ?= $(x) ; + } + return $(result) ; +} + +local rule __test__ ( ) +{ + # use a unique module so we can test the use of local rules. + module sequence.__test__ + { + + local rule is-even ( n ) + { + if $(n) in 0 2 4 6 8 + { + return true ; + } + } + + assert.result 4 6 4 2 8 + : sequence.filter is-even : 1 4 6 3 4 7 2 3 8 ; + + # test that argument binding works + local rule is-equal-test ( x y ) + { + if $(x) = $(y) + { + return true ; + } + } + + assert.result 3 3 3 : sequence.filter is-equal-test 3 : 1 2 3 4 3 5 3 5 7 ; + + local rule test-greater ( a b ) + { + if $(a) > $(b) + { + return true ; + } + } + + assert.result 1 2 3 4 5 6 7 8 9 : sequence.insertion-sort 9 6 5 3 8 7 1 2 4 ; + assert.result 9 8 7 6 5 4 3 2 1 : sequence.insertion-sort 9 6 5 3 8 7 1 2 4 : test-greater ; + assert.result foo-bar-baz : sequence.join foo bar baz : - ; + assert.result substandard : sequence.join sub stan dard ; + } +} \ No newline at end of file