diff options
Diffstat (limited to 'jam-files/engine/builtins.c')
-rw-r--r-- | jam-files/engine/builtins.c | 2310 |
1 files changed, 2310 insertions, 0 deletions
diff --git a/jam-files/engine/builtins.c b/jam-files/engine/builtins.c new file mode 100644 index 00000000..b28a484e --- /dev/null +++ b/jam-files/engine/builtins.c @@ -0,0 +1,2310 @@ +/* + * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc. + * + * This file is part of Jam - see jam.c for Copyright information. + */ + +#include "jam.h" + +#include "lists.h" +#include "parse.h" +#include "builtins.h" +#include "rules.h" +#include "filesys.h" +#include "newstr.h" +#include "regexp.h" +#include "frames.h" +#include "hash.h" +#include "strings.h" +#include "pwd.h" +#include "pathsys.h" +#include "make.h" +#include "hdrmacro.h" +#include "compile.h" +#include "native.h" +#include "variable.h" +#include "timestamp.h" +#include "md5.h" +#include <ctype.h> + +#if defined(USE_EXECUNIX) +# include <sys/types.h> +# include <sys/wait.h> +#else +/* + NT does not have wait() and associated macros, it uses the return value + of system() instead. Status code group are documented at + http://msdn.microsoft.com/en-gb/library/ff565436.aspx +*/ +# define WIFEXITED(w) (((w) & 0XFFFFFF00) == 0) +# define WEXITSTATUS(w)(w) +#endif + +/* + * builtins.c - builtin jam rules + * + * External routines: + * + * load_builtin() - define builtin rules + * + * Internal routines: + * + * builtin_depends() - DEPENDS/INCLUDES rule. + * builtin_echo() - ECHO rule. + * builtin_exit() - EXIT rule. + * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule. + * builtin_glob() - GLOB rule. + * builtin_match() - MATCH rule. + * + * 01/10/01 (seiwald) - split from compile.c + */ + + +/* + * compile_builtin() - define builtin rules + */ + +#define P0 (PARSE *)0 +#define C0 (char *)0 + +#if defined( OS_NT ) || defined( OS_CYGWIN ) + LIST * builtin_system_registry ( PARSE *, FRAME * ); + LIST * builtin_system_registry_names( PARSE *, FRAME * ); +#endif + +int glob( char * s, char * c ); + +void backtrace ( FRAME * ); +void backtrace_line ( FRAME * ); +void print_source_line( PARSE * ); + + +RULE * bind_builtin( char * name, LIST * (* f)( PARSE *, FRAME * ), int flags, char * * args ) +{ + argument_list* arg_list = 0; + + if ( args ) + { + arg_list = args_new(); + lol_build( arg_list->data, args ); + } + + return new_rule_body( root_module(), name, arg_list, + parse_make( f, P0, P0, P0, C0, C0, flags ), 1 ); +} + + +RULE * duplicate_rule( char * name, RULE * other ) +{ + return import_rule( other, root_module(), name ); +} + + +void load_builtins() +{ + duplicate_rule( "Always", + bind_builtin( "ALWAYS", + builtin_flags, T_FLAG_TOUCHED, 0 ) ); + + duplicate_rule( "Depends", + bind_builtin( "DEPENDS", + builtin_depends, 0, 0 ) ); + + duplicate_rule( "echo", + duplicate_rule( "Echo", + bind_builtin( "ECHO", + builtin_echo, 0, 0 ) ) ); + + { + char * args[] = { "message", "*", ":", "result-value", "?", 0 }; + duplicate_rule( "exit", + duplicate_rule( "Exit", + bind_builtin( "EXIT", + builtin_exit, 0, args ) ) ); + } + + { + char * args[] = { "directories", "*", ":", "patterns", "*", ":", "case-insensitive", "?", 0 }; + duplicate_rule( "Glob", + bind_builtin( "GLOB", builtin_glob, 0, args ) ); + } + + { + char * args[] = { "patterns", "*", 0 }; + bind_builtin( "GLOB-RECURSIVELY", + builtin_glob_recursive, 0, args ); + } + + duplicate_rule( "Includes", + bind_builtin( "INCLUDES", + builtin_depends, 1, 0 ) ); + + { + char * args[] = { "targets", "*", ":", "targets-to-rebuild", "*", 0 }; + bind_builtin( "REBUILDS", + builtin_rebuilds, 0, args ); + } + + duplicate_rule( "Leaves", + bind_builtin( "LEAVES", + builtin_flags, T_FLAG_LEAVES, 0 ) ); + + duplicate_rule( "Match", + bind_builtin( "MATCH", + builtin_match, 0, 0 ) ); + + { + char * args[] = { "string", ":", "delimiters" }; + bind_builtin( "SPLIT_BY_CHARACTERS", + builtin_split_by_characters, 0, 0 ); + } + + duplicate_rule( "NoCare", + bind_builtin( "NOCARE", + builtin_flags, T_FLAG_NOCARE, 0 ) ); + + duplicate_rule( "NOTIME", + duplicate_rule( "NotFile", + bind_builtin( "NOTFILE", + builtin_flags, T_FLAG_NOTFILE, 0 ) ) ); + + duplicate_rule( "NoUpdate", + bind_builtin( "NOUPDATE", + builtin_flags, T_FLAG_NOUPDATE, 0 ) ); + + duplicate_rule( "Temporary", + bind_builtin( "TEMPORARY", + builtin_flags, T_FLAG_TEMP, 0 ) ); + + bind_builtin( "ISFILE", + builtin_flags, T_FLAG_ISFILE, 0 ); + + duplicate_rule( "HdrMacro", + bind_builtin( "HDRMACRO", + builtin_hdrmacro, 0, 0 ) ); + + /* FAIL_EXPECTED is used to indicate that the result of a target build + * action should be inverted (ok <=> fail) this can be useful when + * performing test runs from Jamfiles. + */ + bind_builtin( "FAIL_EXPECTED", + builtin_flags, T_FLAG_FAIL_EXPECTED, 0 ); + + bind_builtin( "RMOLD", + builtin_flags, T_FLAG_RMOLD, 0 ); + + { + char * args[] = { "targets", "*", 0 }; + bind_builtin( "UPDATE", + builtin_update, 0, args ); + } + + { + char * args[] = { "targets", "*", + ":", "log", "?", + ":", "ignore-minus-n", "?", + ":", "ignore-minus-q", "?", 0 }; + bind_builtin( "UPDATE_NOW", + builtin_update_now, 0, args ); + } + + { + char * args[] = { "string", "pattern", "replacements", "+", 0 }; + duplicate_rule( "subst", + bind_builtin( "SUBST", + builtin_subst, 0, args ) ); + } + + { + char * args[] = { "module", "?", 0 }; + bind_builtin( "RULENAMES", + builtin_rulenames, 0, args ); + } + + + { + char * args[] = { "module", "?", 0 }; + bind_builtin( "VARNAMES", + builtin_varnames, 0, args ); + } + + { + char * args[] = { "module", "?", 0 }; + bind_builtin( "DELETE_MODULE", + builtin_delete_module, 0, args ); + } + + { + char * args[] = { "source_module", "?", + ":", "source_rules", "*", + ":", "target_module", "?", + ":", "target_rules", "*", + ":", "localize", "?", 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 ); + } + + { + char * args[] = { "levels", "?", 0 }; + bind_builtin( "BACKTRACE", + builtin_backtrace, 0, args ); + } + + { + char * args[] = { 0 }; + bind_builtin( "PWD", + builtin_pwd, 0, args ); + } + + { + char * args[] = { "target", "*", ":", "path", "*", 0 }; + bind_builtin( "SEARCH_FOR_TARGET", + builtin_search_for_target, 0, args ); + } + + { + char * args[] = { "modules_to_import", "+", ":", "target_module", "?", 0 }; + bind_builtin( "IMPORT_MODULE", + builtin_import_module, 0, args ); + } + + { + char * args[] = { "module", "?", 0 }; + bind_builtin( "IMPORTED_MODULES", + builtin_imported_modules, 0, args ); + } + + { + char * args[] = { "instance_module", ":", "class_module", 0 }; + bind_builtin( "INSTANCE", + builtin_instance, 0, args ); + } + + { + char * args[] = { "sequence", "*", 0 }; + bind_builtin( "SORT", + builtin_sort, 0, args ); + } + + { + char * args[] = { "path_parts", "*", 0 }; + bind_builtin( "NORMALIZE_PATH", + builtin_normalize_path, 0, args ); + } + + { + char * args[] = { "args", "*", 0 }; + bind_builtin( "CALC", + builtin_calc, 0, args ); + } + + { + char * args[] = { "module", ":", "rule", 0 }; + bind_builtin( "NATIVE_RULE", + builtin_native_rule, 0, args ); + } + + { + char * args[] = { "module", ":", "rule", ":", "version", 0 }; + bind_builtin( "HAS_NATIVE_RULE", + builtin_has_native_rule, 0, args ); + } + + { + char * args[] = { "module", "*", 0 }; + bind_builtin( "USER_MODULE", + builtin_user_module, 0, args ); + } + + { + char * args[] = { 0 }; + bind_builtin( "NEAREST_USER_LOCATION", + builtin_nearest_user_location, 0, args ); + } + + { + char * args[] = { "file", 0 }; + bind_builtin( "CHECK_IF_FILE", + builtin_check_if_file, 0, args ); + } + +#ifdef HAVE_PYTHON + { + char * args[] = { "python-module", ":", "function", ":", + "jam-module", ":", "rule-name", 0 }; + bind_builtin( "PYTHON_IMPORT_RULE", + builtin_python_import_rule, 0, args ); + } +#endif + +# if defined( OS_NT ) || defined( OS_CYGWIN ) + { + char * args[] = { "key_path", ":", "data", "?", 0 }; + bind_builtin( "W32_GETREG", + builtin_system_registry, 0, args ); + } + + { + char * args[] = { "key_path", ":", "result-type", 0 }; + bind_builtin( "W32_GETREGNAMES", + builtin_system_registry_names, 0, args ); + } +# endif + + { + char * args[] = { "command", ":", "*", 0 }; + duplicate_rule( "SHELL", + bind_builtin( "COMMAND", + builtin_shell, 0, args ) ); + } + + { + char * args[] = { "string", 0 }; + bind_builtin( "MD5", + builtin_md5, 0, args ) ; + } + + { + char * args[] = { "name", ":", "mode", 0 }; + bind_builtin( "FILE_OPEN", + builtin_file_open, 0, args ); + } + + { + char * args[] = { "string", ":", "width", 0 }; + bind_builtin( "PAD", + builtin_pad, 0, args ); + } + + { + char * args[] = { "targets", "*", 0 }; + bind_builtin( "PRECIOUS", + builtin_precious, 0, args ); + } + + { + char * args [] = { 0 }; + bind_builtin( "SELF_PATH", builtin_self_path, 0, args ); + } + + { + char * args [] = { "path", 0 }; + bind_builtin( "MAKEDIR", builtin_makedir, 0, args ); + } + + /* Initialize builtin modules. */ + init_set(); + init_path(); + init_regex(); + init_property_set(); + init_sequence(); + init_order(); +} + + +/* + * builtin_calc() - CALC rule. + * + * The CALC rule performs simple mathematical operations on two arguments. + */ + +LIST * builtin_calc( PARSE * parse, FRAME * frame ) +{ + LIST * arg = lol_get( frame->args, 0 ); + + LIST * result = 0; + long lhs_value; + long rhs_value; + long result_value; + char buffer [ 16 ]; + char const * lhs; + char const * op; + char const * rhs; + + if ( arg == 0 ) return L0; + lhs = arg->string; + + arg = list_next( arg ); + if ( arg == 0 ) return L0; + op = arg->string; + + arg = list_next( arg ); + if ( arg == 0 ) return L0; + rhs = arg->string; + + lhs_value = atoi( lhs ); + rhs_value = atoi( rhs ); + + if ( strcmp( "+", op ) == 0 ) + { + result_value = lhs_value + rhs_value; + } + else if ( strcmp( "-", op ) == 0 ) + { + result_value = lhs_value - rhs_value; + } + else + { + return L0; + } + + sprintf( buffer, "%ld", result_value ); + result = list_new( result, newstr( buffer ) ); + return result; +} + + +/* + * builtin_depends() - DEPENDS/INCLUDES rule. + * + * The DEPENDS/INCLUDES builtin rule appends each of the listed sources on the + * dependency/includes list of each of the listed targets. It binds both the + * targets and sources as TARGETs. + */ + +LIST * builtin_depends( PARSE * parse, FRAME * frame ) +{ + LIST * targets = lol_get( frame->args, 0 ); + LIST * sources = lol_get( frame->args, 1 ); + LIST * l; + + for ( l = targets; l; l = list_next( l ) ) + { + TARGET * t = bindtarget( l->string ); + + /* If doing INCLUDES, switch to the TARGET's include */ + /* TARGET, creating it if needed. The internal include */ + /* TARGET shares the name of its parent. */ + + if ( parse->num ) + { + if ( !t->includes ) + { + t->includes = copytarget( t ); + t->includes->original_target = t; + } + t = t->includes; + } + + t->depends = targetlist( t->depends, sources ); + } + + /* Enter reverse links */ + for ( l = sources; l; l = list_next( l ) ) + { + TARGET * s = bindtarget( l->string ); + s->dependants = targetlist( s->dependants, targets ); + } + + return L0; +} + + +/* + * builtin_rebuilds() - REBUILDS rule. + * + * The REBUILDS builtin rule appends each of the listed rebuild-targets in its + * 2nd argument on the rebuilds list of each of the listed targets in its first + * argument. + */ + +LIST * builtin_rebuilds( PARSE * parse, FRAME * frame ) +{ + LIST * targets = lol_get( frame->args, 0 ); + LIST * rebuilds = lol_get( frame->args, 1 ); + LIST * l; + + for ( l = targets; l; l = list_next( l ) ) + { + TARGET * t = bindtarget( l->string ); + t->rebuilds = targetlist( t->rebuilds, rebuilds ); + } + + return L0; +} + + +/* + * builtin_echo() - ECHO rule. + * + * The ECHO builtin rule echoes the targets to the user. No other actions are + * taken. + */ + +LIST * builtin_echo( PARSE * parse, FRAME * frame ) +{ + list_print( lol_get( frame->args, 0 ) ); + printf( "\n" ); + fflush( stdout ); + return L0; +} + + +/* + * builtin_exit() - EXIT rule. + * + * The EXIT builtin rule echoes the targets to the user and exits the program + * with a failure status. + */ + +LIST * builtin_exit( PARSE * parse, FRAME * frame ) +{ + list_print( lol_get( frame->args, 0 ) ); + printf( "\n" ); + if ( lol_get( frame->args, 1 ) ) + { + exit( atoi( lol_get( frame->args, 1 )->string ) ); + } + else + { + exit( EXITBAD ); /* yeech */ + } + return L0; +} + + +/* + * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule. + * + * Builtin_flags() marks the target with the appropriate flag, for use by make0(). + * It binds each target as a TARGET. + */ + +LIST * builtin_flags( PARSE * parse, FRAME * frame ) +{ + LIST * l = lol_get( frame->args, 0 ); + for ( ; l; l = list_next( l ) ) + bindtarget( l->string )->flags |= parse->num; + return L0; +} + + +/* + * builtin_globbing() - GLOB rule. + */ + +struct globbing +{ + LIST * patterns; + LIST * results; + LIST * case_insensitive; +}; + + +static void downcase_inplace( char * p ) +{ + for ( ; *p; ++p ) + *p = tolower( *p ); +} + + +static void builtin_glob_back +( + void * closure, + char * file, + int status, + time_t time +) +{ + PROFILE_ENTER( BUILTIN_GLOB_BACK ); + + struct globbing * globbing = (struct globbing *)closure; + LIST * l; + PATHNAME f; + string buf[ 1 ]; + + /* Null out directory for matching. We wish we had file_dirscan() pass up a + * PATHNAME. + */ + path_parse( file, &f ); + f.f_dir.len = 0; + + /* For globbing, we unconditionally ignore current and parent directory + * items. Since they items always exist, there is no reason why caller of + * GLOB would want to see them. We could also change file_dirscan(), but + * then paths with embedded "." and ".." would not work anywhere. + */ + if ( !strcmp( f.f_base.ptr, "." ) || !strcmp( f.f_base.ptr, ".." ) ) + { + PROFILE_EXIT( BUILTIN_GLOB_BACK ); + return; + } + + string_new( buf ); + path_build( &f, buf, 0 ); + + if ( globbing->case_insensitive ) + downcase_inplace( buf->value ); + + for ( l = globbing->patterns; l; l = l->next ) + { + if ( !glob( l->string, buf->value ) ) + { + globbing->results = list_new( globbing->results, newstr( file ) ); + break; + } + } + + string_free( buf ); + + PROFILE_EXIT( BUILTIN_GLOB_BACK ); +} + + +static LIST * downcase_list( LIST * in ) +{ + LIST * result = 0; + + string s[ 1 ]; + string_new( s ); + + while ( in ) + { + string_copy( s, in->string ); + downcase_inplace( s->value ); + result = list_append( result, list_new( 0, newstr( s->value ) ) ); + in = in->next; + } + + string_free( s ); + return result; +} + + +LIST * builtin_glob( PARSE * parse, FRAME * frame ) +{ + LIST * l = lol_get( frame->args, 0 ); + LIST * r = lol_get( frame->args, 1 ); + + struct globbing globbing; + + globbing.results = L0; + globbing.patterns = r; + + globbing.case_insensitive +# if defined( OS_NT ) || defined( OS_CYGWIN ) + = l; /* Always case-insensitive if any files can be found. */ +# else + = lol_get( frame->args, 2 ); +# endif + + if ( globbing.case_insensitive ) + globbing.patterns = downcase_list( r ); + + for ( ; l; l = list_next( l ) ) + file_dirscan( l->string, builtin_glob_back, &globbing ); + + if ( globbing.case_insensitive ) + list_free( globbing.patterns ); + + return globbing.results; +} + + +static int has_wildcards( char const * str ) +{ + size_t const index = strcspn( str, "[]*?" ); + return str[ index ] == '\0' ? 0 : 1; +} + + +/* + * If 'file' exists, append 'file' to 'list'. Returns 'list'. + */ + +static LIST * append_if_exists( LIST * list, char * file ) +{ + time_t time; + timestamp( file, &time ); + return time > 0 + ? list_new( list, newstr( file ) ) + : list; +} + + +LIST * glob1( char * dirname, char * pattern ) +{ + LIST * plist = list_new( L0, pattern ); + struct globbing globbing; + + globbing.results = L0; + globbing.patterns = plist; + + globbing.case_insensitive +# if defined( OS_NT ) || defined( OS_CYGWIN ) + = plist; /* always case-insensitive if any files can be found */ +# else + = L0; +# endif + + if ( globbing.case_insensitive ) + globbing.patterns = downcase_list( plist ); + + file_dirscan( dirname, builtin_glob_back, &globbing ); + + if ( globbing.case_insensitive ) + list_free( globbing.patterns ); + + list_free( plist ); + + return globbing.results; +} + + +LIST * glob_recursive( char * pattern ) +{ + LIST * result = L0; + + /* Check if there's metacharacters in pattern */ + if ( !has_wildcards( pattern ) ) + { + /* No metacharacters. Check if the path exists. */ + result = append_if_exists(result, pattern); + } + else + { + /* Have metacharacters in the pattern. Split into dir/name. */ + PATHNAME path[ 1 ]; + path_parse( pattern, path ); + + if ( path->f_dir.ptr ) + { + LIST * dirs = L0; + string dirname[ 1 ]; + string basename[ 1 ]; + string_new( dirname ); + string_new( basename ); + + string_append_range( dirname, path->f_dir.ptr, + path->f_dir.ptr + path->f_dir.len ); + + path->f_grist.ptr = 0; + path->f_grist.len = 0; + path->f_dir.ptr = 0; + path->f_dir.len = 0; + path_build( path, basename, 0 ); + + dirs = has_wildcards( dirname->value ) + ? glob_recursive( dirname->value ) + : list_new( dirs, dirname->value ); + + if ( has_wildcards( basename->value ) ) + { + for ( ; dirs; dirs = dirs->next ) + result = list_append( result, glob1( dirs->string, + basename->value ) ); + } + else + { + string file_string[ 1 ]; + string_new( file_string ); + + /* No wildcard in basename. */ + for ( ; dirs; dirs = dirs->next ) + { + path->f_dir.ptr = dirs->string; + path->f_dir.len = strlen( dirs->string ); + path_build( path, file_string, 0 ); + + result = append_if_exists( result, file_string->value ); + + string_truncate( file_string, 0 ); + } + + string_free( file_string ); + } + + string_free( dirname ); + string_free( basename ); + } + else + { + /** No directory, just a pattern. */ + result = list_append( result, glob1( ".", pattern ) ); + } + } + + return result; +} + + +LIST * builtin_glob_recursive( PARSE * parse, FRAME * frame ) +{ + LIST * result = L0; + LIST * l = lol_get( frame->args, 0 ); + for ( ; l; l = l->next ) + result = list_append( result, glob_recursive( l->string ) ); + return result; +} + + +/* + * builtin_match() - MATCH rule, regexp matching. + */ + +LIST * builtin_match( PARSE * parse, FRAME * frame ) +{ + LIST * l; + LIST * r; + LIST * result = 0; + + string buf[ 1 ]; + string_new( buf ); + + /* For each pattern */ + + for ( l = lol_get( frame->args, 0 ); l; l = l->next ) + { + /* Result is cached and intentionally never freed. */ + regexp * re = regex_compile( l->string ); + + /* For each string to match against. */ + for ( r = lol_get( frame->args, 1 ); r; r = r->next ) + { + if ( regexec( re, r->string ) ) + { + int i; + int top; + + /* Find highest parameter */ + + for ( top = NSUBEXP; top-- > 1; ) + if ( re->startp[ top ] ) + break; + + /* And add all parameters up to highest onto list. */ + /* Must have parameters to have results! */ + for ( i = 1; i <= top; ++i ) + { + string_append_range( buf, re->startp[ i ], re->endp[ i ] ); + result = list_new( result, newstr( buf->value ) ); + string_truncate( buf, 0 ); + } + } + } + } + + string_free( buf ); + return result; +} + +LIST * builtin_split_by_characters( PARSE * parse, FRAME * frame ) +{ + LIST * l1 = lol_get( frame->args, 0 ); + LIST * l2 = lol_get( frame->args, 1 ); + + LIST * result = 0; + + char* s = strdup (l1->string); + char* delimiters = l2->string; + char* t; + + t = strtok (s, delimiters); + while (t) + { + result = list_new(result, newstr(t)); + t = strtok (NULL, delimiters); + } + + free (s); + + return result; +} + +LIST * builtin_hdrmacro( PARSE * parse, FRAME * frame ) +{ + LIST * l = lol_get( frame->args, 0 ); + + for ( ; l; l = list_next( l ) ) + { + TARGET * t = bindtarget( l->string ); + + /* Scan file for header filename macro definitions. */ + if ( DEBUG_HEADER ) + printf( "scanning '%s' for header file macro definitions\n", + l->string ); + + macro_headers( t ); + } + + return L0; +} + + +/* + * builtin_rulenames() - RULENAMES ( MODULE ? ). + * + * 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. + */ + +static void add_rule_name( void * r_, void * result_ ) +{ + RULE * r = (RULE *)r_; + LIST * * result = (LIST * *)result_; + if ( r->exported ) + *result = list_new( *result, copystr( r->name ) ); +} + + +LIST * builtin_rulenames( PARSE * parse, FRAME * frame ) +{ + LIST * arg0 = lol_get( frame->args, 0 ); + LIST * result = L0; + module_t * source_module = bindmodule( arg0 ? arg0->string : 0 ); + + if ( source_module->rules ) + hashenumerate( source_module->rules, add_rule_name, &result ); + return result; +} + + +/* + * builtin_varnames() - VARNAMES ( MODULE ? ). + * + * Returns a list of the variable names in the given MODULE. If MODULE is not + * supplied, returns the list of variable names in the global module. + */ + +/* helper function for builtin_varnames(), below. Used with hashenumerate, will + * prepend the key of each element to the list + */ +static void add_hash_key( void * np, void * result_ ) +{ + LIST * * result = (LIST * *)result_; + *result = list_new( *result, copystr( *(char * *)np ) ); +} + + +static struct hash * get_running_module_vars() +{ + struct hash * dummy; + struct hash * vars = NULL; + /* Get the global variables pointer (that of the currently running module). + */ + var_hash_swap( &vars ); + dummy = vars; + /* Put the global variables pointer in its right place. */ + var_hash_swap( &dummy ); + return vars; +} + + +LIST * builtin_varnames( PARSE * parse, FRAME * frame ) +{ + LIST * arg0 = lol_get( frame->args, 0 ); + LIST * result = L0; + module_t * source_module = bindmodule( arg0 ? arg0->string : 0 ); + + /* The running module _always_ has its 'variables' member set to NULL due to + * the way enter_module() and var_hash_swap() work. + */ + struct hash * vars = source_module == frame->module + ? get_running_module_vars() + : source_module->variables; + + if ( vars ) + hashenumerate( vars, add_hash_key, &result ); + return result; +} + + +/* + * builtin_delete_module() - MODULE ?. + * + * Clears all rules and variables from the given module. + */ + +LIST * builtin_delete_module( PARSE * parse, FRAME * frame ) +{ + LIST * arg0 = lol_get( frame->args, 0 ); + LIST * result = L0; + module_t * source_module = bindmodule( arg0 ? arg0->string : 0 ); + delete_module( source_module ); + 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 ); +} + + +/* + * builtin_import() - IMPORT + * ( + * SOURCE_MODULE ? : + * SOURCE_RULES * : + * TARGET_MODULE ? : + * TARGET_RULES * : + * LOCALIZE ? + * ) + * + * 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. If LOCALIZE is specified, the rules will be + * executed in TARGET_MODULE, with corresponding access to its module local + * variables. + */ + +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 ); + LIST * localize = lol_get( frame->args, 4 ); + + module_t * target_module = + bindmodule( target_module_list ? target_module_list->string : 0 ); + module_t * source_module = + bindmodule( source_module_list ? source_module_list->string : 0 ); + + LIST * source_name; + LIST * target_name; + + 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_; + RULE * r = &r_; + RULE * imported; + r_.name = source_name->string; + + if ( !source_module->rules || + !hashcheck( source_module->rules, (HASHDATA * *)&r ) ) + unknown_rule( frame, "IMPORT", source_module->name, r_.name ); + + imported = import_rule( r, target_module, target_name->string ); + if ( localize ) + imported->module = target_module; + /* This rule is really part of some other module. Just refer to it here, + * but do not let it out. + */ + imported->exported = 0; + } + + if ( source_name || target_name ) + { + backtrace_line( frame->prev ); + printf( "import error: length of source and target rule name lists don't match!\n" ); + printf( " source: " ); + list_print( source_rules ); + printf( "\n target: " ); + list_print( target_rules ); + printf( "\n" ); + 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. + */ + +LIST * builtin_export( PARSE * parse, FRAME * frame ) +{ + LIST * module_list = lol_get( frame->args, 0 ); + LIST * rules = lol_get( frame->args, 1 ); + module_t * m = bindmodule( module_list ? module_list->string : 0 ); + + for ( ; rules; rules = list_next( rules ) ) + { + RULE r_; + RULE * r = &r_; + r_.name = rules->string; + + if ( !m->rules || !hashcheck( m->rules, (HASHDATA * *)&r ) ) + unknown_rule( frame, "EXPORT", m->name, r_.name ); + + r->exported = 1; + } + return L0; +} + + +/* + * get_source_line() - Retrieve the file and line number that should be + * indicated for a given procedure in debug output or an error backtrace. + */ + +static void get_source_line( PARSE * procedure, char * * file, int * line ) +{ + if ( procedure ) + { + char * f = procedure->file; + int l = procedure->line; + if ( !strcmp( f, "+" ) ) + { + f = "jambase.c"; + l += 3; + } + *file = f; + *line = l; + } + else + { + *file = "(builtin)"; + *line = -1; + } +} + + +void print_source_line( PARSE * p ) +{ + char * file; + int line; + + get_source_line( p, &file, &line ); + if ( line < 0 ) + printf( "(builtin):" ); + else + printf( "%s:%d:", file, line ); +} + + +/* + * backtrace_line() - print a single line of error backtrace for the given + * frame. + */ + +void backtrace_line( FRAME * frame ) +{ + if ( frame == 0 ) + { + printf( "(no frame):" ); + } + else + { + print_source_line( frame->procedure ); + printf( " in %s\n", frame->rulename ); + } +} + + +/* + * backtrace() - Print the entire backtrace from the given frame to the Jambase + * which invoked it. + */ + +void backtrace( FRAME * frame ) +{ + if ( !frame ) return; + while ( ( frame = frame->prev ) ) + backtrace_line( frame ); +} + + +/* + * builtin_backtrace() - A Jam version of the backtrace function, taking no + * arguments and returning a list of quadruples: FILENAME LINE MODULE. RULENAME + * describing each frame. Note that the module-name is always followed by a + * period. + */ + +LIST * builtin_backtrace( PARSE * parse, FRAME * frame ) +{ + LIST * levels_arg = lol_get( frame->args, 0 ); + int levels = levels_arg ? atoi( levels_arg->string ) : ( (unsigned int)(-1) >> 1 ) ; + + LIST * result = L0; + for ( ; ( frame = frame->prev ) && levels ; --levels ) + { + char * file; + int line; + char buf[32]; + get_source_line( frame->procedure, &file, &line ); + sprintf( buf, "%d", line ); + result = list_new( result, newstr( file ) ); + result = list_new( result, newstr( buf ) ); + result = list_new( result, newstr( frame->module->name ) ); + result = list_new( result, newstr( frame->rulename ) ); + } + return result; +} + + +/* + * builtin_caller_module() - CALLER_MODULE ( levels ? ) + * + * If levels is not supplied, returns the name of the module of the rule which + * called the one calling this one. If levels is supplied, it is interpreted as + * an integer specifying a number of additional levels of call stack to traverse + * in order to locate the module in question. If no such module exists, returns + * the empty list. Also returns the empty list when the module in question is + * the global module. This rule is needed for implementing module import + * behavior. + */ + +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 ; + + int i; + for ( i = 0; ( i < levels + 2 ) && frame->prev; ++i ) + frame = frame->prev; + + if ( frame->module == root_module() ) + return L0; + + { + LIST * result; + string name; + string_copy( &name, frame->module->name ); + string_pop_back( &name ); + result = list_new( L0, newstr(name.value) ); + string_free( &name ); + return result; + } +} + + +/* + * Return the current working directory. + * + * Usage: pwd = [ PWD ] ; + */ + +LIST * builtin_pwd( PARSE * parse, FRAME * frame ) +{ + return pwd(); +} + + +/* + * Adds targets to the list of target that jam will attempt to update. + */ + +LIST * builtin_update( PARSE * parse, FRAME * frame ) +{ + LIST * result = list_copy( L0, targets_to_update() ); + LIST * arg1 = lol_get( frame->args, 0 ); + clear_targets_to_update(); + for ( ; arg1; arg1 = list_next( arg1 ) ) + mark_target_for_updating( newstr( arg1->string ) ); + return result; +} + +extern int anyhow; +int last_update_now_status; + +/* Takes a list of target names as first argument, and immediately + updates them. + Second parameter, if specified, if the descriptor (converted to a string) + of a log file where all build output is redirected. + Third parameter, if non-empty, specifies that the -n option should have + no effect -- that is, all out-of-date targets should be rebuild. +*/ +LIST * builtin_update_now( PARSE * parse, FRAME * frame ) +{ + LIST * targets = lol_get( frame->args, 0 ); + LIST * log = lol_get( frame->args, 1 ); + LIST * force = lol_get (frame->args, 2); + LIST * continue_ = lol_get(frame->args, 3); + int status = 0; + int original_stdout; + int original_stderr; + int n; + int targets_count; + const char** targets2; + int i; + int original_noexec; + int original_quitquick; + + + if (log) + { + int fd = atoi(log->string); + /* Redirect stdout and stderr, temporary, to the log file. */ + original_stdout = dup (0); + original_stderr = dup (1); + dup2 (fd, 0); + dup2 (fd, 1); + } + + if (force) + { + original_noexec = globs.noexec; + globs.noexec = 0; + original_quitquick = globs.quitquick; + globs.quitquick = 0; + } + + if (continue_) + { + original_quitquick = globs.quitquick; + globs.quitquick = 0; + } + + targets_count = list_length( targets ); + targets2 = (const char * *)BJAM_MALLOC( targets_count * sizeof( char * ) ); + for (i = 0 ; targets; targets = list_next( targets ) ) + targets2[ i++ ] = targets->string; + status |= make( targets_count, targets2, anyhow); + free( targets ); + + if (force) + { + globs.noexec = original_noexec; + globs.quitquick = original_quitquick; + } + + if (continue_) + { + globs.quitquick = original_quitquick; + } + + if (log) + { + /* Flush whatever stdio might have buffered, while descriptions + 0 and 1 still refer to the log file. */ + fflush (stdout); + fflush (stderr); + dup2 (original_stdout, 0); + dup2 (original_stderr, 1); + close (original_stdout); + close (original_stderr); + } + + last_update_now_status = status; + + if (status == 0) + return list_new (L0, newstr ("ok")); + else + return L0; +} + +LIST * builtin_search_for_target( PARSE * parse, FRAME * frame ) +{ + LIST * arg1 = lol_get( frame->args, 0 ); + LIST * arg2 = lol_get( frame->args, 1 ); + TARGET * t = search_for_target( arg1->string, arg2 ); + return list_new( L0, t->name ); +} + + +LIST * builtin_import_module( PARSE * parse, FRAME * frame ) +{ + LIST * arg1 = lol_get( frame->args, 0 ); + LIST * arg2 = lol_get( frame->args, 1 ); + module_t * m = arg2 ? bindmodule( arg2->string ) : root_module(); + import_module( arg1, m ); + return L0; +} + + +LIST * builtin_imported_modules( PARSE * parse, FRAME * frame ) +{ + LIST * arg0 = lol_get( frame->args, 0 ); + return imported_modules( bindmodule( arg0 ? arg0->string : 0 ) ); +} + + +LIST * builtin_instance( PARSE * parse, FRAME * frame ) +{ + LIST * arg1 = lol_get( frame->args, 0 ); + LIST * arg2 = lol_get( frame->args, 1 ); + module_t * const instance = bindmodule( arg1->string ); + module_t * const class_module = bindmodule( arg2->string ); + instance->class_module = class_module; + return L0; +} + + +LIST * builtin_sort( PARSE * parse, FRAME * frame ) +{ + LIST * arg1 = lol_get( frame->args, 0 ); + return list_sort( arg1 ); +} + + +LIST * builtin_normalize_path( PARSE * parse, FRAME * frame ) +{ + LIST * arg = lol_get( frame->args, 0 ); + + /* First, we iterate over all '/'-separated elements, starting from the end + * of string. If we see a '..', we remove a previous path elements. If we + * see '.', we remove it. The removal is done by overwriting data using '\1' + * in the string. After the whole string has been processed, we do a second + * pass, removing all the entered '\1' characters. + */ + + string in[ 1 ]; + string out[ 1 ]; + /* Last character of the part of string still to be processed. */ + char * end; + /* Working pointer. */ + char * current; + /* Number of '..' elements seen and not processed yet. */ + int dotdots = 0; + int rooted = 0; + char * result = 0; + + /* Make a copy of input: we should not change it. Prepend a '/' before it as + * a guard for the algorithm later on and remember whether it was originally + * rooted or not. + */ + string_new( in ); + string_push_back( in, '/' ); + for ( ; arg; arg = list_next( arg ) ) + { + if ( arg->string[ 0 ] != '\0' ) + { + if ( in->size == 1 ) + rooted = ( ( arg->string[ 0 ] == '/' ) || + ( arg->string[ 0 ] == '\\' ) ); + else + string_append( in, "/" ); + string_append( in, arg->string ); + } + } + + /* Convert \ into /. On Windows, paths using / and \ are equivalent, and we + * want this function to obtain a canonic representation. + */ + for ( current = in->value, end = in->value + in->size; + current < end; ++current ) + if ( *current == '\\' ) + *current = '/'; + + /* Now we remove any extra path elements by overwriting them with '\1' + * characters and cound how many more unused '..' path elements there are + * remaining. Note that each remaining path element with always starts with + * a '/' character. + */ + for ( end = in->value + in->size - 1; end >= in->value; ) + { + /* Set 'current' to the next occurence of '/', which always exists. */ + for ( current = end; *current != '/'; --current ); + + if ( current == end ) + { + /* Found a trailing or duplicate '/'. Remove it. */ + *current = '\1'; + } + else if ( ( end - current == 1 ) && ( *(current + 1) == '.' ) ) + { + /* Found '/.'. Remove them all. */ + *current = '\1'; + *(current + 1) = '\1'; + } + else if ( ( end - current == 2 ) && ( *(current + 1) == '.' ) && ( *(current + 2) == '.' ) ) + { + /* Found '/..'. Remove them all. */ + *current = '\1'; + *(current + 1) = '\1'; + *(current + 2) = '\1'; + ++dotdots; + } + else if ( dotdots ) + { + memset( current, '\1', end - current + 1 ); + --dotdots; + } + end = current - 1; + } + + string_new( out ); + + /* Now we know that we need to add exactly dotdots '..' path elements to the + * front and that our string is either empty or has a '/' as its first + * significant character. If we have any dotdots remaining then the passed + * path must not have been rooted or else it is invalid we return an empty + * list. + */ + if ( dotdots ) + { + if ( rooted ) return L0; + do + string_append( out, "/.." ); + while ( --dotdots ); + } + + /* Now we actually remove all the path characters marked for removal. */ + for ( current = in->value; *current; ++current ) + if ( *current != '\1' ) + string_push_back( out, *current ); + + /* Here we know that our string contains no '\1' characters and is either + * empty or has a '/' as its initial character. If the original path was not + * rooted and we have a non-empty path we need to drop the initial '/'. If + * the original path was rooted and we have an empty path we need to add + * back the '/'. + */ + result = newstr( out->size ? out->value + !rooted : ( rooted ? "/" : "." ) ); + + string_free( out ); + string_free( in ); + + return list_new( 0, result ); +} + + +LIST * builtin_native_rule( PARSE * parse, FRAME * frame ) +{ + LIST * module_name = lol_get( frame->args, 0 ); + LIST * rule_name = lol_get( frame->args, 1 ); + + module_t * module = bindmodule( module_name->string ); + + native_rule_t n; + native_rule_t * np = &n; + n.name = rule_name->string; + if ( module->native_rules && hashcheck( module->native_rules, (HASHDATA * *)&np ) ) + { + new_rule_body( module, np->name, np->arguments, np->procedure, 1 ); + } + else + { + backtrace_line( frame->prev ); + printf( "error: no native rule \"%s\" defined in module \"%s\"\n", + n.name, module->name ); + backtrace( frame->prev ); + exit( 1 ); + } + return L0; +} + + +LIST * builtin_has_native_rule( PARSE * parse, FRAME * frame ) +{ + LIST * module_name = lol_get( frame->args, 0 ); + LIST * rule_name = lol_get( frame->args, 1 ); + LIST * version = lol_get( frame->args, 2 ); + + module_t * module = bindmodule( module_name->string ); + + native_rule_t n; + native_rule_t * np = &n; + n.name = rule_name->string; + if ( module->native_rules && hashcheck( module->native_rules, (HASHDATA * *)&np ) ) + { + int expected_version = atoi( version->string ); + if ( np->version == expected_version ) + return list_new( 0, newstr( "true" ) ); + } + return L0; +} + + +LIST * builtin_user_module( PARSE * parse, FRAME * frame ) +{ + LIST * module_name = lol_get( frame->args, 0 ); + for ( ; module_name; module_name = module_name->next ) + { + module_t * m = bindmodule( module_name->string ); + m->user_module = 1; + } + return L0; +} + + +LIST * builtin_nearest_user_location( PARSE * parse, FRAME * frame ) +{ + FRAME * nearest_user_frame = + frame->module->user_module ? frame : frame->prev_user; + if ( !nearest_user_frame ) + return L0; + + { + LIST * result = 0; + char * file; + int line; + char buf[32]; + + get_source_line( nearest_user_frame->procedure, &file, &line ); + sprintf( buf, "%d", line ); + result = list_new( result, newstr( file ) ); + result = list_new( result, newstr( buf ) ); + return result; + } +} + + +LIST * builtin_check_if_file( PARSE * parse, FRAME * frame ) +{ + LIST * name = lol_get( frame->args, 0 ); + return file_is_file( name->string ) == 1 + ? list_new( 0, newstr( "true" ) ) + : L0 ; +} + + +LIST * builtin_md5( PARSE * parse, FRAME * frame ) +{ + LIST * l = lol_get( frame->args, 0 ); + char* s = l->string; + + md5_state_t state; + md5_byte_t digest[16]; + char hex_output[16*2 + 1]; + + int di; + + md5_init(&state); + md5_append(&state, (const md5_byte_t *)s, strlen(s)); + md5_finish(&state, digest); + + for (di = 0; di < 16; ++di) + sprintf(hex_output + di * 2, "%02x", digest[di]); + + return list_new (0, newstr(hex_output)); +} + +LIST *builtin_file_open( PARSE *parse, FRAME *frame ) +{ + char* name = lol_get(frame->args, 0)->string; + char* mode = lol_get(frame->args, 1)->string; + int fd; + char buffer[sizeof("4294967295")]; + + if (strcmp(mode, "w") == 0) + { + fd = open(name, O_WRONLY|O_CREAT|O_TRUNC, 0666); + } + else + { + fd = open(name, O_RDONLY); + } + + if (fd != -1) + { + sprintf(buffer, "%d", fd); + return list_new(L0, newstr(buffer)); + } + else + { + return L0; + } +} + +LIST *builtin_pad( PARSE *parse, FRAME *frame ) +{ + char *string = lol_get(frame->args, 0)->string; + char *width_s = lol_get(frame->args, 1)->string; + + int current = strlen (string); + int desired = atoi(width_s); + if (current >= desired) + return list_new (L0, string); + else + { + char *buffer = malloc (desired + 1); + int i; + LIST *result; + + strcpy (buffer, string); + for (i = current; i < desired; ++i) + buffer[i] = ' '; + buffer[desired] = '\0'; + result = list_new (L0, newstr (buffer)); + free (buffer); + return result; + } +} + +LIST *builtin_precious( PARSE *parse, FRAME *frame ) +{ + LIST* targets = lol_get(frame->args, 0); + + for ( ; targets; targets = list_next( targets ) ) + { + TARGET* t = bindtarget (targets->string); + t->flags |= T_FLAG_PRECIOUS; + } + + return L0; +} + +LIST *builtin_self_path( PARSE *parse, FRAME *frame ) +{ + extern char *saved_argv0; + char *p = executable_path (saved_argv0); + if (p) + { + LIST* result = list_new (0, newstr (p)); + free(p); + return result; + } + else + { + return L0; + } +} + +LIST *builtin_makedir( PARSE *parse, FRAME *frame ) +{ + LIST *path = lol_get(frame->args, 0); + + if (file_mkdir(path->string) == 0) + { + LIST *result = list_new (0, newstr(path->string)); + return result; + } + else + { + return L0; + } +} + +#ifdef HAVE_PYTHON + +LIST * builtin_python_import_rule( PARSE * parse, FRAME * frame ) +{ + static int first_time = 1; + char * python_module = lol_get( frame->args, 0 )->string; + char * python_function = lol_get( frame->args, 1 )->string; + char * jam_module = lol_get( frame->args, 2 )->string; + char * jam_rule = lol_get( frame->args, 3 )->string; + + PyObject * pName; + PyObject * pModule; + PyObject * pDict; + PyObject * pFunc; + + if ( first_time ) + { + /* At the first invocation, we add the value of the global + * EXTRA_PYTHONPATH to the sys.path Python variable. + */ + LIST * extra = 0; + module_t * outer_module = frame->module; + + first_time = 0; + + if ( outer_module != root_module() ) + { + exit_module( outer_module ); + enter_module( root_module() ); + } + + extra = var_get( "EXTRA_PYTHONPATH" ); + + if ( outer_module != root_module() ) + { + exit_module( root_module() ); + enter_module( outer_module ); + } + + for ( ; extra; extra = extra->next ) + { + string buf[ 1 ]; + string_new( buf ); + string_append( buf, "import sys\nsys.path.append(\"" ); + string_append( buf, extra->string ); + string_append( buf, "\")\n" ); + PyRun_SimpleString( buf->value ); + string_free( buf ); + } + } + + pName = PyString_FromString( python_module ); + pModule = PyImport_Import( pName ); + Py_DECREF( pName ); + + if ( pModule != NULL ) + { + pDict = PyModule_GetDict( pModule ); + pFunc = PyDict_GetItemString( pDict, python_function ); + + if ( pFunc && PyCallable_Check( pFunc ) ) + { + module_t * m = bindmodule( jam_module ); + RULE * r = bindrule( jam_rule, m ); + + /* Make pFunc owned. */ + Py_INCREF( pFunc ); + + r->python_function = pFunc; + } + else + { + if ( PyErr_Occurred() ) + PyErr_Print(); + fprintf( stderr, "Cannot find function \"%s\"\n", python_function ); + } + Py_DECREF( pModule ); + } + else + { + PyErr_Print(); + fprintf( stderr, "Failed to load \"%s\"\n", python_module ); + } + return L0; + +} + +#endif + +void lol_build( LOL * lol, char * * elements ) +{ + LIST * l = L0; + lol_init( lol ); + + while ( elements && *elements ) + { + if ( !strcmp( *elements, ":" ) ) + { + lol_add( lol, l ); + l = L0 ; + } + else + { + l = list_new( l, newstr( *elements ) ); + } + ++elements; + } + + if ( l != L0 ) + lol_add( lol, l ); +} + + +#ifdef HAVE_PYTHON + +/* + * Calls the bjam rule specified by name passed in 'args'. The name is looked up + * in the context of bjam's 'python_interface' module. Returns the list of + * string retured by the rule. + */ + +PyObject* bjam_call( PyObject * self, PyObject * args ) +{ + FRAME inner[ 1 ]; + LIST * result; + PARSE * p; + char * rulename; + + /* Build up the list of arg lists. */ + frame_init( inner ); + inner->prev = 0; + inner->prev_user = 0; + inner->module = bindmodule( "python_interface" ); + inner->procedure = 0; + + /* Extract the rule name and arguments from 'args'. */ + + /* PyTuple_GetItem returns borrowed reference. */ + rulename = PyString_AsString( PyTuple_GetItem( args, 0 ) ); + { + int i = 1; + int size = PyTuple_Size( args ); + for ( ; i < size; ++i ) + { + PyObject * a = PyTuple_GetItem( args, i ); + if ( PyString_Check( a ) ) + { + lol_add( inner->args, list_new( 0, newstr( + PyString_AsString( a ) ) ) ); + } + else if ( PySequence_Check( a ) ) + { + LIST * l = 0; + int s = PySequence_Size( a ); + int i = 0; + for ( ; i < s; ++i ) + { + /* PySequence_GetItem returns new reference. */ + PyObject * e = PySequence_GetItem( a, i ); + char * s = PyString_AsString( e ); + if ( !s ) + { + printf( "Invalid parameter type passed from Python\n" ); + exit( 1 ); + } + l = list_new( l, newstr( s ) ); + Py_DECREF( e ); + } + lol_add( inner->args, l ); + } + } + } + + result = evaluate_rule( rulename, inner ); + + frame_free( inner ); + + /* Convert the bjam list into a Python list result. */ + { + PyObject * pyResult = PyList_New( list_length( result ) ); + int i = 0; + while ( result ) + { + PyList_SetItem( pyResult, i, PyString_FromString( result->string ) ); + result = list_next( result ); + i += 1; + } + list_free( result ); + return pyResult; + } +} + + +/* + * Accepts four arguments: + * - module name + * - rule name, + * - Python callable. + * - (optional) bjam language function signature. + * Creates a bjam rule with the specified name in the specified module, which will + * invoke the Python callable. + */ + +PyObject * bjam_import_rule( PyObject * self, PyObject * args ) +{ + char * module; + char * rule; + PyObject * func; + PyObject * bjam_signature = NULL; + module_t * m; + RULE * r; + + if ( !PyArg_ParseTuple( args, "ssO|O:import_rule", + &module, &rule, &func, &bjam_signature ) ) + return NULL; + + if ( !PyCallable_Check( func ) ) + { + PyErr_SetString( PyExc_RuntimeError, + "Non-callable object passed to bjam.import_rule" ); + return NULL; + } + + m = bindmodule( *module ? module : 0 ); + r = bindrule( rule, m ); + + /* Make pFunc owned. */ + Py_INCREF( func ); + + r->python_function = func; + r->arguments = 0; + + if (bjam_signature) + { + argument_list * arg_list = args_new(); + Py_ssize_t i; + + Py_ssize_t s = PySequence_Size (bjam_signature); + for (i = 0; i < s; ++i) + { + PyObject* v = PySequence_GetItem (bjam_signature, i); + lol_add(arg_list->data, list_from_python (v)); + Py_DECREF(v); + } + r->arguments = arg_list; + } + + Py_INCREF( Py_None ); + return Py_None; +} + + +/* + * Accepts four arguments: + * - an action name + * - an action body + * - a list of variable that will be bound inside the action + * - integer flags. + * Defines an action on bjam side. + */ + +PyObject * bjam_define_action( PyObject * self, PyObject * args ) +{ + char * name; + char * body; + module_t * m; + PyObject * bindlist_python; + int flags; + LIST * bindlist = L0; + int n; + int i; + + if ( !PyArg_ParseTuple( args, "ssO!i:define_action", &name, &body, + &PyList_Type, &bindlist_python, &flags ) ) + return NULL; + + n = PyList_Size( bindlist_python ); + for ( i = 0; i < n; ++i ) + { + PyObject * next = PyList_GetItem( bindlist_python, i ); + if ( !PyString_Check( next ) ) + { + PyErr_SetString( PyExc_RuntimeError, + "bind list has non-string type" ); + return NULL; + } + bindlist = list_new( bindlist, PyString_AsString( next ) ); + } + + new_rule_actions( root_module(), name, newstr( body ), bindlist, flags ); + + Py_INCREF( Py_None ); + return Py_None; +} + + +/* + * Returns the value of a variable in root Jam module. + */ + +PyObject * bjam_variable( PyObject * self, PyObject * args ) +{ + char * name; + LIST * value; + PyObject * result; + int i; + + if ( !PyArg_ParseTuple( args, "s", &name ) ) + return NULL; + + enter_module( root_module() ); + value = var_get( name ); + exit_module( root_module() ); + + result = PyList_New( list_length( value ) ); + for ( i = 0; value; value = list_next( value ), ++i ) + PyList_SetItem( result, i, PyString_FromString( value->string ) ); + + return result; +} + + +PyObject * bjam_backtrace( PyObject * self, PyObject * args ) +{ + PyObject * result = PyList_New( 0 ); + struct frame * f = frame_before_python_call; + + for ( ; f = f->prev; ) + { + PyObject * tuple = PyTuple_New( 4 ); + char * file; + int line; + char buf[ 32 ]; + + get_source_line( f->procedure, &file, &line ); + sprintf( buf, "%d", line ); + + /* PyTuple_SetItem steals reference. */ + PyTuple_SetItem( tuple, 0, PyString_FromString( file ) ); + PyTuple_SetItem( tuple, 1, PyString_FromString( buf ) ); + PyTuple_SetItem( tuple, 2, PyString_FromString( f->module->name ) ); + PyTuple_SetItem( tuple, 3, PyString_FromString( f->rulename ) ); + + PyList_Append( result, tuple ); + Py_DECREF( tuple ); + } + return result; +} + +PyObject * bjam_caller( PyObject * self, PyObject * args ) +{ + PyObject *result = PyString_FromString( + frame_before_python_call->prev->module->name); + return result; +} + +#endif /* #ifdef HAVE_PYTHON */ + + +#ifdef HAVE_POPEN + +#if defined(_MSC_VER) || defined(__BORLANDC__) + #define popen windows_popen_wrapper + #define pclose _pclose + + /* + * This wrapper is a workaround for a funny _popen() feature on Windows + * where it eats external quotes in some cases. The bug seems to be related + * to the quote stripping functionality used by the Windows cmd.exe + * interpreter when its /S is not specified. + * + * Cleaned up quote from the cmd.exe help screen as displayed on Windows XP + * SP3: + * + * 1. If all of the following conditions are met, then quote characters on + * the command line are preserved: + * + * - no /S switch + * - exactly two quote characters + * - no special characters between the two quote characters, where + * special is one of: &<>()@^| + * - there are one or more whitespace characters between the two quote + * characters + * - the string between the two quote characters is the name of an + * executable file. + * + * 2. Otherwise, old behavior is to see if the first character is a quote + * character and if so, strip the leading character and remove the last + * quote character on the command line, preserving any text after the + * last quote character. + * + * This causes some commands containing quotes not to be executed correctly. + * For example: + * + * "\Long folder name\aaa.exe" --name="Jurko" --no-surname + * + * would get its outermost quotes stripped and would be executed as: + * + * \Long folder name\aaa.exe" --name="Jurko --no-surname + * + * which would report an error about '\Long' not being a valid command. + * + * cmd.exe help seems to indicate it would be enough to add an extra space + * character in front of the command to avoid this but this does not work, + * most likely due to the shell first stripping all leading whitespace + * characters from the command. + * + * Solution implemented here is to quote the whole command in case it + * contains any quote characters. Note thought this will not work correctly + * should Windows ever 'fix' this feature. + * (03.06.2008.) (Jurko) + */ + static FILE * windows_popen_wrapper( char * command, char * mode ) + { + int extra_command_quotes_needed = ( strchr( command, '"' ) != 0 ); + string quoted_command; + FILE * result; + + if ( extra_command_quotes_needed ) + { + string_new( "ed_command ); + string_append( "ed_command, "\"" ); + string_append( "ed_command, command ); + string_append( "ed_command, "\"" ); + command = quoted_command.value; + } + + result = _popen( command, "r" ); + + if ( extra_command_quotes_needed ) + string_free( "ed_command ); + + return result; + } +#endif + + +static char * rtrim(char *s) +{ + char *p = s; + while(*p) ++p; + for(--p; p >= s && isspace(*p); *p-- = 0); + return s; +} + +LIST * builtin_shell( PARSE * parse, FRAME * frame ) +{ + LIST * command = lol_get( frame->args, 0 ); + LIST * result = 0; + string s; + int ret; + char buffer[ 1024 ]; + FILE * p = NULL; + int exit_status = -1; + int exit_status_opt = 0; + int no_output_opt = 0; + int strip_eol_opt = 0; + + /* Process the variable args options. */ + { + int a = 1; + LIST * arg = lol_get( frame->args, a ); + while ( arg ) + { + if ( strcmp( "exit-status", arg->string ) == 0 ) + { + exit_status_opt = 1; + } + else if ( strcmp( "no-output", arg->string ) == 0 ) + { + no_output_opt = 1; + } + else if ( strcmp("strip-eol", arg->string) == 0 ) + { + strip_eol_opt = 1; + } + arg = lol_get( frame->args, ++a ); + } + } + + /* The following fflush() call seems to be indicated as a workaround for a + * popen() bug on POSIX implementations related to synhronizing input + * stream positions for the called and the calling process. + */ + fflush( NULL ); + + p = popen( command->string, "r" ); + if ( p == NULL ) + return L0; + + string_new( &s ); + + while ( ( ret = fread( buffer, sizeof( char ), sizeof( buffer ) - 1, p ) ) > 0 ) + { + buffer[ret] = 0; + if ( !no_output_opt ) + { + if ( strip_eol_opt ) + rtrim(buffer); + string_append( &s, buffer ); + } + } + + exit_status = pclose( p ); + + /* The command output is returned first. */ + result = list_new( L0, newstr( s.value ) ); + string_free( &s ); + + /* The command exit result next. */ + if ( exit_status_opt ) + { + if ( WIFEXITED(exit_status) ) + exit_status = WEXITSTATUS(exit_status); + else + exit_status = -1; + sprintf( buffer, "%d", exit_status ); + result = list_new( result, newstr( buffer ) ); + } + + return result; +} + +#else /* #ifdef HAVE_POPEN */ + +LIST * builtin_shell( PARSE * parse, FRAME * frame ) +{ + return L0; +} + +#endif /* #ifdef HAVE_POPEN */ |