/* * Copyright 1993, 2000 Christopher Seiwald. * * This file is part of Jam - see jam.c for Copyright information. */ /* This file is ALSO: * Copyright 2001-2004 David Abrahams. * Copyright 2005 Reece H. Dunn. * Copyright 2005 Rene Rivera. * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) */ #include "jam.h" #include "lists.h" #include "parse.h" #include "variable.h" #include "expand.h" #include "hash.h" #include "filesys.h" #include "newstr.h" #include "strings.h" #include "pathsys.h" #include #include /* * variable.c - handle Jam multi-element variables. * * External routines: * * var_defines() - load a bunch of variable=value settings. * var_string() - expand a string with variables in it. * var_get() - get value of a user defined symbol. * var_set() - set a variable in jam's user defined symbol table. * var_swap() - swap a variable's value with the given one. * var_done() - free variable tables. * * Internal routines: * * var_enter() - make new var symbol table entry, returning var ptr. * var_dump() - dump a variable to stdout. * * 04/13/94 (seiwald) - added shorthand L0 for null list pointer * 08/23/94 (seiwald) - Support for '+=' (append to variable) * 01/22/95 (seiwald) - split environment variables at blanks or :'s * 05/10/95 (seiwald) - split path variables at SPLITPATH (not :) * 09/11/00 (seiwald) - defunct var_list() removed */ static struct hash *varhash = 0; /* * VARIABLE - a user defined multi-value variable */ typedef struct _variable VARIABLE ; struct _variable { char * symbol; LIST * value; }; static VARIABLE * var_enter( char * symbol ); static void var_dump( char * symbol, LIST * value, char * what ); /* * var_hash_swap() - swap all variable settings with those passed * * Used to implement separate settings spaces for modules */ void var_hash_swap( struct hash * * new_vars ) { struct hash * old = varhash; varhash = *new_vars; *new_vars = old; } /* * var_defines() - load a bunch of variable=value settings * * If preprocess is false, take the value verbatim. * * Otherwise, if the variable value is enclosed in quotes, strip the * quotes. * * Otherwise, if variable name ends in PATH, split value at :'s. * * Otherwise, split the value at blanks. */ void var_defines( char * const * e, int preprocess ) { string buf[1]; string_new( buf ); for ( ; *e; ++e ) { char * val; # ifdef OS_MAC /* On the mac (MPW), the var=val is actually var\0val */ /* Think different. */ if ( ( val = strchr( *e, '=' ) ) || ( val = *e + strlen( *e ) ) ) # else if ( ( val = strchr( *e, '=' ) ) ) # endif { LIST * l = L0; char * pp; char * p; # ifdef OPT_NO_EXTERNAL_VARIABLE_SPLIT char split = '\0'; # else # ifdef OS_MAC char split = ','; # else char split = ' '; # endif # endif size_t len = strlen( val + 1 ); int quoted = ( val[1] == '"' ) && ( val[len] == '"' ) && ( len > 1 ); if ( quoted && preprocess ) { string_append_range( buf, val + 2, val + len ); l = list_new( l, newstr( buf->value ) ); string_truncate( buf, 0 ); } else { /* Split *PATH at :'s, not spaces. */ if ( val - 4 >= *e ) { if ( !strncmp( val - 4, "PATH", 4 ) || !strncmp( val - 4, "Path", 4 ) || !strncmp( val - 4, "path", 4 ) ) split = SPLITPATH; } /* Do the split. */ for ( pp = val + 1; preprocess && ( ( p = strchr( pp, split ) ) != 0 ); pp = p + 1 ) { string_append_range( buf, pp, p ); l = list_new( l, newstr( buf->value ) ); string_truncate( buf, 0 ); } l = list_new( l, newstr( pp ) ); } /* Get name. */ string_append_range( buf, *e, val ); var_set( buf->value, l, VAR_SET ); string_truncate( buf, 0 ); } } string_free( buf ); } /* * var_string() - expand a string with variables in it * * Copies in to out; doesn't modify targets & sources. */ int var_string( char * in, char * out, int outsize, LOL * lol ) { char * out0 = out; char * oute = out + outsize - 1; while ( *in ) { char * lastword; int dollar = 0; /* Copy white space. */ while ( isspace( *in ) ) { if ( out >= oute ) return -1; *out++ = *in++; } lastword = out; /* Copy non-white space, watching for variables. */ while ( *in && !isspace( *in ) ) { if ( out >= oute ) return -1; if ( ( in[ 0 ] == '$' ) && ( in[ 1 ] == '(' ) ) { ++dollar; *out++ = *in++; } #ifdef OPT_AT_FILES else if ( ( in[ 0 ] == '@' ) && ( in[ 1 ] == '(' ) ) { int depth = 1; char * ine = in + 2; char * split = 0; /* Scan the content of the response file @() section. */ while ( *ine && ( depth > 0 ) ) { switch ( *ine ) { case '(': ++depth; break; case ')': --depth; break; case ':': if ( ( depth == 1 ) && ( ine[ 1 ] == 'E' ) && ( ine[ 2 ] == '=' ) ) split = ine; break; } ++ine; } if ( !split ) { /* the @() reference doesn't match the @(foo:E=bar) format. hence we leave it alone by copying directly to output. */ int l = 0; if ( out + 2 >= oute ) return -1; *( out++ ) = '@'; *( out++ ) = '('; l = var_string( in + 2, out, oute - out, lol ); if ( l < 0 ) return -1; out += l; if ( out + 1 >= oute ) return -1; *( out++ ) = ')'; } else if ( depth == 0 ) { string file_name_v; int file_name_l = 0; const char * file_name_s = 0; /* Expand the temporary file name var inline. */ #if 0 string_copy( &file_name_v, "$(" ); string_append_range( &file_name_v, in + 2, split ); string_push_back( &file_name_v, ')' ); #else string_new( &file_name_v ); string_append_range( &file_name_v, in + 2, split ); #endif file_name_l = var_string( file_name_v.value, out, oute - out + 1, lol ); string_free( &file_name_v ); if ( file_name_l < 0 ) return -1; file_name_s = out; /* For stdout/stderr we will create a temp file and generate * a command that outputs the content as needed. */ if ( ( strcmp( "STDOUT", out ) == 0 ) || ( strcmp( "STDERR", out ) == 0 ) ) { int err_redir = strcmp( "STDERR", out ) == 0; out[ 0 ] = '\0'; file_name_s = path_tmpfile(); file_name_l = strlen(file_name_s); #ifdef OS_NT if ( ( out + 7 + file_name_l + ( err_redir ? 5 : 0 ) ) >= oute ) return -1; sprintf( out,"type \"%s\"%s", file_name_s, err_redir ? " 1>&2" : "" ); #else if ( ( out + 6 + file_name_l + ( err_redir ? 5 : 0 ) ) >= oute ) return -1; sprintf( out,"cat \"%s\"%s", file_name_s, err_redir ? " 1>&2" : "" ); #endif /* We also make sure that the temp files created by this * get nuked eventually. */ file_remove_atexit( file_name_s ); } /* Expand the file value into the file reference. */ var_string_to_file( split + 3, ine - split - 4, file_name_s, lol ); /* Continue on with the expansion. */ out += strlen( out ); } /* And continue with the parsing just past the @() reference. */ in = ine; } #endif else { *out++ = *in++; } } /* Add zero to 'out' so that 'lastword' is correctly zero-terminated. */ if ( out >= oute ) return -1; /* Do not increment, intentionally. */ *out = '\0'; /* If a variable encountered, expand it and and embed the * space-separated members of the list in the output. */ if ( dollar ) { LIST * l = var_expand( L0, lastword, out, lol, 0 ); out = lastword; while ( l ) { int so = strlen( l->string ); if ( out + so >= oute ) return -1; strcpy( out, l->string ); out += so; l = list_next( l ); if ( l ) *out++ = ' '; } list_free( l ); } } if ( out >= oute ) return -1; *out++ = '\0'; return out - out0; } void var_string_to_file( const char * in, int insize, const char * out, LOL * lol ) { char const * ine = in + insize; FILE * out_file = 0; int out_debug = DEBUG_EXEC ? 1 : 0; if ( globs.noexec ) { /* out_debug = 1; */ } else if ( strcmp( out, "STDOUT" ) == 0 ) { out_file = stdout; } else if ( strcmp( out, "STDERR" ) == 0 ) { out_file = stderr; } else { /* Handle "path to file" filenames. */ string out_name; if ( ( out[ 0 ] == '"' ) && ( out[ strlen( out ) - 1 ] == '"' ) ) { string_copy( &out_name, out + 1 ); string_truncate( &out_name, out_name.size - 1 ); } else { string_copy( &out_name,out ); } out_file = fopen( out_name.value, "w" ); if ( !out_file ) { printf( "failed to write output file '%s'!\n", out_name.value ); exit( EXITBAD ); } string_free( &out_name ); } if ( out_debug ) printf( "\nfile %s\n", out ); while ( *in && ( in < ine ) ) { int dollar = 0; const char * output_0 = in; const char * output_1 = in; /* Copy white space. */ while ( ( output_1 < ine ) && isspace( *output_1 ) ) ++output_1; if ( output_0 < output_1 ) { if ( out_file ) fwrite( output_0, output_1 - output_0, 1, out_file ); if ( out_debug ) fwrite( output_0, output_1 - output_0, 1, stdout ); } output_0 = output_1; /* Copy non-white space, watching for variables. */ while ( ( output_1 < ine ) && *output_1 && !isspace( *output_1 ) ) { if ( ( output_1[ 0 ] == '$' ) && ( output_1[ 1 ] == '(' ) ) ++dollar; ++output_1; } /* If a variable encountered, expand it and embed the space-separated * members of the list in the output. */ if ( dollar ) { LIST * l = var_expand( L0, (char *)output_0, (char *)output_1, lol, 0 ); while ( l ) { if ( out_file ) fputs( l->string, out_file ); if ( out_debug ) puts( l->string ); l = list_next( l ); if ( l ) { if ( out_file ) fputc( ' ', out_file ); if ( out_debug ) fputc( ' ', stdout ); } } list_free( l ); } else if ( output_0 < output_1 ) { if ( out_file ) { const char * output_n = output_0; while ( output_n < output_1 ) { output_n += fwrite( output_n, 1, output_1-output_n, out_file ); } } if ( out_debug ) { const char * output_n = output_0; while ( output_n < output_1 ) { output_n += fwrite( output_n, 1, output_1-output_n, stdout ); } } } in = output_1; } if ( out_file && ( out_file != stdout ) && ( out_file != stderr ) ) { fflush( out_file ); fclose( out_file ); } if ( out_debug ) fputc( '\n', stdout ); } /* * var_get() - get value of a user defined symbol. * * Returns NULL if symbol unset. */ LIST * var_get( char * symbol ) { LIST * result = 0; #ifdef OPT_AT_FILES /* Some "fixed" variables... */ if ( strcmp( "TMPDIR", symbol ) == 0 ) { result = list_new( L0, newstr( (char *)path_tmpdir() ) ); } else if ( strcmp( "TMPNAME", symbol ) == 0 ) { result = list_new( L0, newstr( (char *)path_tmpnam() ) ); } else if ( strcmp( "TMPFILE", symbol ) == 0 ) { result = list_new( L0, newstr( (char *)path_tmpfile() ) ); } else if ( strcmp( "STDOUT", symbol ) == 0 ) { result = list_new( L0, newstr( "STDOUT" ) ); } else if ( strcmp( "STDERR", symbol ) == 0 ) { result = list_new( L0, newstr( "STDERR" ) ); } else #endif { VARIABLE var; VARIABLE * v = &var; v->symbol = symbol; if ( varhash && hashcheck( varhash, (HASHDATA * *)&v ) ) { if ( DEBUG_VARGET ) var_dump( v->symbol, v->value, "get" ); result = v->value; } } return result; } /* * var_set() - set a variable in Jam's user defined symbol table. * * 'flag' controls the relationship between new and old values of the variable: * SET replaces the old with the new; APPEND appends the new to the old; DEFAULT * only uses the new if the variable was previously unset. * * Copies symbol. Takes ownership of value. */ void var_set( char * symbol, LIST * value, int flag ) { VARIABLE * v = var_enter( symbol ); if ( DEBUG_VARSET ) var_dump( symbol, value, "set" ); switch ( flag ) { case VAR_SET: /* Replace value */ list_free( v->value ); v->value = value; break; case VAR_APPEND: /* Append value */ v->value = list_append( v->value, value ); break; case VAR_DEFAULT: /* Set only if unset */ if ( !v->value ) v->value = value; else list_free( value ); break; } } /* * var_swap() - swap a variable's value with the given one. */ LIST * var_swap( char * symbol, LIST * value ) { VARIABLE * v = var_enter( symbol ); LIST * oldvalue = v->value; if ( DEBUG_VARSET ) var_dump( symbol, value, "set" ); v->value = value; return oldvalue; } /* * var_enter() - make new var symbol table entry, returning var ptr. */ static VARIABLE * var_enter( char * symbol ) { VARIABLE var; VARIABLE * v = &var; if ( !varhash ) varhash = hashinit( sizeof( VARIABLE ), "variables" ); v->symbol = symbol; v->value = 0; if ( hashenter( varhash, (HASHDATA * *)&v ) ) v->symbol = newstr( symbol ); /* never freed */ return v; } /* * var_dump() - dump a variable to stdout. */ static void var_dump( char * symbol, LIST * value, char * what ) { printf( "%s %s = ", what, symbol ); list_print( value ); printf( "\n" ); } /* * var_done() - free variable tables. */ static void delete_var_( void * xvar, void * data ) { VARIABLE * v = (VARIABLE *)xvar; freestr( v->symbol ); list_free( v-> value ); } void var_done() { hashenumerate( varhash, delete_var_, (void *)0 ); hashdone( varhash ); }