| /* |
| -- |
| -- SYNOPSIS |
| -- GNU style functions for dmake. |
| -- |
| -- DESCRIPTION |
| -- All GNU style functions understood by dmake are implemented in this |
| -- file. Currently the only such function is $(mktmp ...) which is |
| -- not part of GNU-make is an extension provided by dmake. |
| -- |
| -- AUTHOR |
| -- Dennis Vadura, dvadura@dmake.wticorp.com |
| -- |
| -- WWW |
| -- http://dmake.wticorp.com/ |
| -- |
| -- COPYRIGHT |
| -- Copyright (c) 1996,1997 by WTI Corp. All rights reserved. |
| -- |
| -- This program is NOT free software; you can redistribute it and/or |
| -- modify it under the terms of the Software License Agreement Provided |
| -- in the file <distribution-root>/readme/license.txt. |
| -- |
| -- LOG |
| -- Use cvs log to obtain detailed change logs. |
| */ |
| |
| #include "extern.h" |
| |
| static char *_exec_mktmp ANSI((char *, char *, char *)); |
| static char *_exec_subst ANSI((char *, char *, char *)); |
| static char *_exec_iseq ANSI((char *, char *, char *, int)); |
| static char *_exec_sort ANSI((char *)); |
| static char *_exec_echo ANSI((char *)); |
| static char *_exec_uniq ANSI((char *)); |
| static char *_exec_shell ANSI((char *, int)); |
| static char *_exec_call ANSI((char *, char *)); |
| static char *_exec_assign ANSI((char *)); |
| static char *_exec_foreach ANSI((char *, char *, char *)); |
| static char *_exec_andor ANSI((char *, int)); |
| static char *_exec_not ANSI((char *)); |
| static int _mystrcmp ANSI((const DMPVOID, const DMPVOID)); |
| |
| |
| PUBLIC char * |
| Exec_function(buf)/* |
| ==================== |
| Execute the function given by the value of args. |
| |
| So far mktmp is the only valid function, anything else elicits and error |
| message. It is my hope to support the GNU style functions in this portion |
| of the code at some time in the future. */ |
| char *buf; |
| { |
| char *fname; |
| char *args; |
| char *mod1; |
| char *mod2 = NIL(char); |
| int mod_count = 0; |
| char *res = NIL(char); |
| |
| /* This must succeed since the presence of ' ', \t or \n is what |
| * determines if this function is called in the first place. |
| * Unfortunately this prohibits the use of whitespaces in parameters |
| * for macro functions. */ |
| /* ??? Using ScanToken to find the next ' ', \t or \n and discarding |
| * the returned, evaluated result is a misuse of that function. */ |
| FREE(ScanToken(buf, &args, FALSE)); |
| fname = DmSubStr(buf, args); |
| /* args points to the whitespace after the found token, this leads |
| * to leading whitespaces. */ |
| if( *args ) { |
| args = DmStrSpn(args," \t"); /* strip whitespace before */ |
| if( *args ) { /* ... and after value */ |
| char *q; |
| for(q=args+strlen(args)-1; ((*q == ' ')||(*q == '\t')); q--); |
| *++q = '\0'; |
| } |
| } |
| |
| /* ??? Some function macros expect comma seperated parameters, but |
| * no decent parser is included. The desirable solution would be |
| * to parse fname for the correct number of parameters in fname |
| * when a function is recognized. We only count the parameters |
| * at the moment. Note "" is a valid parameter. */ |
| if( (mod1 = strchr(fname,',')) != NIL(char) ){ |
| *mod1 = '\0'; |
| mod1++; |
| mod_count++; |
| |
| if( (mod2 = strchr(mod1,',')) != NIL(char) ){ |
| *mod2 = '\0'; |
| mod2++; |
| mod_count++; |
| } |
| } |
| |
| /* ??? At the moment only the leading part of fname compared if it |
| * matches a known function macro. For example assignXXX or even |
| * assign,,,, is also erroneously accepted. */ |
| switch( *fname ) { |
| case 'a': |
| if(strncmp(fname,"assign",6) == 0) |
| res = _exec_assign(args); |
| else if(strncmp(fname,"and",3) == 0) |
| res = _exec_andor(args, TRUE); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'e': |
| if(strncmp(fname,"eq",2) == 0) |
| if( mod_count == 2 ) |
| res = _exec_iseq(mod1,mod2,args,TRUE); |
| else |
| Fatal( "Two comma-seperated arguments expected in [%s].\n", buf ); |
| else if (strncmp(fname,"echo",4) == 0) |
| res = _exec_echo(args); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'f': |
| if(strncmp(fname,"foreach",7) == 0) |
| if( mod_count == 2 ) |
| res = _exec_foreach(mod1,mod2,args); |
| else |
| Fatal( "Two comma-seperated arguments expected in [%s].\n", buf ); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'm': |
| if(strncmp(fname,"mktmp",5) == 0) |
| if( mod_count < 3 ) |
| res = _exec_mktmp(mod1,mod2,args); |
| else |
| Fatal( "Maximal two comma-seperated arguments expected in [%s].\n", buf ); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'n': |
| if( strncmp(fname,"null", 4) == 0 ) |
| res = _exec_iseq(mod1,NIL(char),args,TRUE); |
| else if (strncmp(fname,"nil",3) == 0 ) { |
| FREE(Expand(args)); |
| res = DmStrDup(""); |
| } |
| else if (strncmp(fname,"not",3) == 0 ) |
| res = _exec_not(args); |
| else if (strncmp(fname,"normpath",8) == 0 ) { |
| char *eargs = Expand(args); |
| |
| if( mod_count == 0 ) { |
| res = exec_normpath(eargs); |
| } |
| else if( mod_count == 1 ) { |
| char *para = Expand(mod1); |
| int tmpUseWinpath = UseWinpath; |
| |
| if( !*para || strcmp(para, "\"\"") == 0 ) { |
| UseWinpath = FALSE; |
| } else { |
| UseWinpath = TRUE; |
| } |
| res = exec_normpath(eargs); |
| UseWinpath = tmpUseWinpath; |
| FREE(para); |
| } |
| else |
| Fatal( "One or no comma-seperated arguments expected in [%s].\n", buf ); |
| |
| FREE(eargs); |
| } |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case '!': |
| if(strncmp(fname,"!null",5) == 0) |
| res = _exec_iseq(mod1,NIL(char),args,FALSE); |
| else if(strncmp(fname,"!eq",3) ==0) |
| if( mod_count == 2 ) |
| res = _exec_iseq(mod1,mod2,args,FALSE); |
| else |
| Fatal( "Two comma-seperated arguments expected in [%s].\n", buf ); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'o': |
| if(strncmp(fname,"or",2) == 0) |
| res = _exec_andor(args, FALSE); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 's': |
| if(strncmp(fname,"sort",4) == 0) |
| res = _exec_sort(args); |
| else if(strncmp(fname,"shell",5)==0) |
| if( mod_count == 0 ) { |
| res = _exec_shell(args, FALSE); |
| } |
| else if( mod_count == 1 ) { |
| char *emod = Expand(mod1); |
| if(strncmp(emod,"expand",7)==0) |
| res = _exec_shell(args, TRUE); |
| else |
| Fatal( "Unknown argument [%s] to shell in [%s].\n", emod, buf ); |
| FREE(emod); |
| } |
| else |
| Fatal( "One or no comma-seperated arguments expected in [%s].\n", buf ); |
| else if(strncmp(fname,"strip",5)==0) |
| res = Tokenize(Expand(args)," ",'t',TRUE); |
| else if(strncmp(fname,"subst",5)==0) { |
| if( mod_count == 2 ) |
| res = _exec_subst(mod1,mod2,args); |
| else |
| Fatal( "Two comma-seperated arguments expected in [%s].\n", buf ); |
| } |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| case 'u': |
| if(strncmp(fname,"uniq",4) == 0) |
| res = _exec_uniq(args); |
| else |
| res = _exec_call(fname,args); |
| break; |
| |
| default: |
| res = _exec_call(fname,args); |
| } |
| |
| if( res == NIL(char) ) res = DmStrDup(""); |
| |
| FREE(fname); |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_assign( macrostring ) |
| char *macrostring; |
| { |
| if ( !Parse_macro(macrostring, M_MULTI|M_FORCE) ) { |
| Error( "Dynamic macro assignment failed, while making [%s]\n", |
| Current_target ? Current_target->CE_NAME : "NIL"); |
| return(DmStrDup("")); |
| } |
| |
| return(DmStrDup(LastMacName)); |
| } |
| |
| |
| static char * |
| _exec_echo(data) |
| char *data; |
| { |
| return(DmStrDup(DmStrSpn(data," \t"))); |
| } |
| |
| |
| static char * |
| _exec_call( var, list )/* |
| ========================= |
| Return the (recursively expanded) value of macro var. Expand list and |
| discard the result. |
| */ |
| char *var; /* Name of the macro (until first whitespace). */ |
| char *list; /* Rest data (after the whitespace). */ |
| { |
| char *res = NIL(char); |
| |
| /* the argument part is expanded. */ |
| FREE(Expand(list)); |
| |
| /* Prepend '$(' and append ')' so that Expand will return the value |
| * of the 'var' macro. */ |
| var = DmStrJoin(DmStrJoin("$(",var,-1,FALSE),")",-1,TRUE); |
| res = Expand(var); |
| |
| FREE(var); |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_foreach( var, list, data ) |
| char *var; |
| char *list; |
| char *data; |
| { |
| char *res = NIL(char); |
| char *s; |
| TKSTR tk; |
| HASHPTR hp; |
| |
| var = Expand(var); |
| list = Expand(list); |
| |
| data = DmStrSpn(data," \t\n"); |
| SET_TOKEN(&tk,list); |
| /* push previous macro definition and redefine. */ |
| hp = Def_macro(var,"",M_MULTI|M_NOEXPORT|M_FORCE|M_PUSH); |
| |
| while( *(s=Get_token(&tk, "", FALSE)) != '\0' ) { |
| Def_macro(var,s,M_MULTI|M_NOEXPORT|M_FORCE); |
| res = DmStrAdd(res,Expand(data),TRUE); |
| } |
| |
| CLEAR_TOKEN(&tk); |
| Pop_macro(hp); /* Get back old macro definition. */ |
| FREE(hp->ht_name); |
| if(hp->ht_value) FREE(hp->ht_value); |
| FREE(hp); |
| FREE(var); |
| FREE(list); |
| |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_mktmp( file, text, data ) |
| char *file; |
| char *text; |
| char *data; |
| { |
| char *tmpname; |
| char *name; |
| FILE *tmpfile = NIL(FILE); |
| |
| /* This is only a test of the recipe line so prevent the tempfile side |
| * effects. */ |
| if( Suppress_temp_file ) return(NIL(char)); |
| |
| name = Current_target ? Current_target->CE_NAME:"makefile text"; |
| |
| if( file && *file ) { |
| /* Expand the file parameter to mktmp if present. */ |
| tmpname = Expand(file); |
| |
| if( *tmpname ) { |
| #ifdef HAVE_MKSTEMP |
| /* Only use umask if we are also using mkstemp - this basically |
| * avoids using the incompatible implementation from MSVC. */ |
| mode_t mask; |
| |
| /* Create tempfile with 600 permissions. */ |
| mask = umask(0066); |
| #endif |
| |
| if( (tmpfile = fopen(tmpname, "w")) == NIL(FILE) ) |
| Open_temp_error( tmpname, name ); |
| #ifdef HAVE_MKSTEMP |
| umask(mask); |
| #endif |
| |
| Def_macro("TMPFILE", tmpname, M_EXPANDED|M_MULTI); |
| Link_temp( Current_target, tmpfile, tmpname ); |
| |
| /* Don't free tmpname if it is used. It is stored in a FILELIST |
| * member in Link_temp() and freed by Unlink_temp_files(). */ |
| } |
| else |
| FREE(tmpname); |
| } |
| |
| /* If file expanded to a non empty value tmpfile is already opened, |
| * otherwise open it now. */ |
| if( !tmpfile ) |
| tmpfile = Start_temp( "", Current_target, &tmpname ); |
| |
| /* If the text parameter is given return its expanded value |
| * instead of the used filename. */ |
| if( !text || !*text ) { |
| /* tmpname is freed by Unlink_temp_files(). */ |
| text = DmStrDup(DO_WINPATH(tmpname)); |
| } |
| else { |
| text = Expand(text); |
| } |
| |
| data = Expand(data); |
| |
| Append_line( data, TRUE, tmpfile, name, FALSE, FALSE ); |
| Close_temp( Current_target, tmpfile ); |
| FREE(data); |
| |
| return( text ); |
| } |
| |
| |
| static char * |
| _exec_iseq( lhs, rhs, data, eq ) |
| char *lhs; |
| char *rhs; |
| char *data; |
| int eq; |
| { |
| char *l = Expand(lhs); |
| char *r = Expand(rhs); |
| char *i = DmStrSpn(data, " \t\n"); |
| char *e = strchr(i, ' '); |
| char *res = NIL(char); |
| int val = strcmp(l,r); |
| |
| if( (!val && eq) || (val && !eq) ) { |
| if( e != NIL(char) ) *e = '\0'; |
| res = Expand(i); |
| } |
| else if( e != NIL(char) ) { |
| e = DmStrSpn(e," \t\n"); |
| if( *e ) res = Expand(e); |
| } |
| |
| FREE(l); |
| FREE(r); |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_sort( args ) |
| char *args; |
| { |
| char *res = NIL(char); |
| char *data = Expand(args); |
| char **tokens; |
| char *p; |
| char *white = " \t\n"; |
| int j; |
| int i; |
| |
| for(i=0,p=DmStrSpn(data,white);*p;p=DmStrSpn(DmStrPbrk(p,white),white),i++); |
| |
| if( i != 0 ) { |
| TALLOC(tokens, i, char *); |
| |
| for( i=0,p=DmStrSpn(data,white); *p; p=DmStrSpn(p,white),i++){ |
| tokens[i] = p; |
| p = DmStrPbrk(p,white); |
| if( *p ) *p++ = '\0'; |
| } |
| |
| qsort( tokens, i, sizeof(char *), _mystrcmp ); |
| |
| for( j=0; j<i; j++ ) res = DmStrApp(res, tokens[j]); |
| FREE(data); |
| FREE(tokens); |
| } |
| |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_uniq( args ) |
| char *args; |
| { |
| char *res = NIL(char); |
| char *data = Expand(args); |
| char **tokens; |
| char **tokens_after; |
| char *p; |
| char *white = " \t\n"; |
| int j; |
| int i; |
| char *last = ""; |
| int k = 0; |
| |
| for(i=0,p=DmStrSpn(data,white);*p;p=DmStrSpn(DmStrPbrk(p,white),white),i++); |
| |
| if( i != 0 ) { |
| TALLOC(tokens, i, char *); |
| TALLOC(tokens_after, i, char *); |
| |
| for( i=0,p=DmStrSpn(data,white); *p; p=DmStrSpn(p,white),i++){ |
| tokens[i] = p; |
| p = DmStrPbrk(p,white); |
| if( *p ) *p++ = '\0'; |
| } |
| |
| qsort( tokens, i, sizeof(char *), _mystrcmp ); |
| |
| for( j=0; j<i; j++ ) { |
| if (strcmp(tokens[j], last) != 0) { |
| tokens_after[k++] = tokens[j]; |
| last = tokens[j]; |
| } |
| } |
| |
| for( j=0; j<k; j++ ) res = DmStrApp(res, tokens_after[j]); |
| FREE(tokens); |
| FREE(tokens_after); |
| } |
| |
| FREE(data); |
| return(res); |
| } |
| |
| static int |
| _mystrcmp( p, q ) |
| const DMPVOID p; |
| const DMPVOID q; |
| { |
| return(strcmp(*((const char **)p),*((const char **)q))); |
| } |
| |
| |
| static char * |
| _exec_subst( pat, subst, data ) |
| char *pat; |
| char *subst; |
| char *data; |
| { |
| char *res; |
| |
| pat = Expand(pat); |
| subst = Expand(subst); |
| |
| /* This implies FREE(Expand(data)) */ |
| res = Apply_edit( Expand(data), pat, subst, TRUE, FALSE ); |
| FREE(pat); |
| FREE(subst); |
| |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_shell( data, expand )/* |
| ============================= |
| Capture the stdout of an execuded command. |
| If expand is TRUE expand the result. */ |
| char *data; |
| int expand; |
| { |
| extern char *tempnam(); |
| int bsize; |
| char *buffer; |
| char *tmpnm; |
| FILE *old_stdout_redir = stdout_redir; |
| |
| int wait = Wait_for_completion; |
| int old_is_exec_shell = Is_exec_shell; |
| CELLPTR old_Shell_exec_target = Shell_exec_target; |
| uint16 vflag = Verbose; |
| int tflag = Trace; |
| char *res = NIL(char); |
| CELL cell; |
| STRING rcp; |
| HASH cname; |
| |
| if( Suppress_temp_file ) return(NIL(char)); |
| |
| /* Set the temp CELL used for building prerequisite candidates to |
| * all zero so that we don't have to keep initializing all the |
| * fields. */ |
| { |
| register char *s = (char *) &cell; |
| register int n = sizeof(CELL); |
| while( n ) { *s++ = '\0'; n--; } |
| } |
| rcp.st_string = DmStrSpn(data, " \t+-%@"); |
| rcp.st_attr = Rcp_attribute( data ); |
| rcp.st_next = NIL(STRING); |
| cname.ht_name = "Shell escape"; |
| cell.ce_name = &cname; |
| cell.ce_all.cl_prq = &cell; |
| cell.ce_all.cl_next = NIL(LINK); |
| cell.ce_all.cl_flag = 0; |
| cell.ce_fname = cname.ht_name; |
| cell.ce_recipe = &rcp; |
| cell.ce_flag = F_TARGET|F_RULES; |
| /* Setting A_SILENT supresses the recipe output from Print_cmnd(). */ |
| cell.ce_attr = A_PHONY|A_SILENT|A_SHELLESC; |
| |
| if( Measure & M_TARGET ) |
| Do_profile_output( "s", M_TARGET, &cell ); |
| |
| /* Print the shell escape command. */ |
| if( Verbose & V_FORCEECHO ) { |
| printf( "%s: Executing shell macro: %s\n", Pname, data ); |
| fflush(stdout); |
| } |
| |
| if( (stdout_redir = Get_temp(&tmpnm, "w+")) == NIL(FILE) ) |
| Open_temp_error( tmpnm, cname.ht_name ); |
| |
| bsize = (Buffer_size < BUFSIZ)?BUFSIZ:Buffer_size; |
| buffer = MALLOC(bsize,char); |
| |
| /* As this function redirects the output of stdout we have to make sure |
| * that only this single command is executed and all previous recipe lines |
| * that belong to the same target have finished. With Shell_exec_target and |
| * Wait_for_completion set this is realized. Current_target being NIL(CELL) |
| * outside of recipe lines makes sure that no waiting for previous recipe |
| * lines has to be done. */ |
| Wait_for_completion = TRUE; |
| Is_exec_shell = TRUE; |
| Shell_exec_target = Current_target; |
| Verbose &= V_LEAVE_TMP; |
| Trace = FALSE; |
| |
| /* The actual redirection happens in runargv(). */ |
| Exec_commands( &cell ); |
| |
| Unlink_temp_files( &cell ); |
| |
| Trace = tflag; |
| Verbose = vflag; |
| Wait_for_completion = wait; |
| Is_exec_shell = old_is_exec_shell; |
| Shell_exec_target = old_Shell_exec_target; |
| |
| /* Now we have to read the temporary file, get the tokens and return them |
| * as a string. */ |
| rewind(stdout_redir); |
| while( fgets(buffer, bsize, stdout_redir) ) { |
| char *p = strchr(buffer, '\n'); |
| |
| if( p == NIL(char) ) |
| res = DmStrJoin(res,buffer,-1,TRUE); |
| else { |
| *p = '\0'; |
| /* You might encounter '\r\n' on windows, handle it. */ |
| if( p > buffer && *(p-1) == '\r') |
| *(p-1) = '\0'; |
| res = DmStrApp(res,buffer); |
| } |
| } |
| |
| fclose(stdout_redir); |
| Remove_file(tmpnm); |
| FREE(tmpnm); |
| FREE(buffer); |
| |
| stdout_redir = old_stdout_redir; |
| |
| if ( expand ) { |
| char *exp_res; |
| exp_res = Expand(res); |
| FREE(res); |
| res = exp_res; |
| } |
| |
| return(res); |
| } |
| |
| |
| static char * |
| _exec_andor( args, doand ) |
| char *args; |
| int doand; |
| { |
| char *next; |
| char *p; |
| char *white = " \t\n"; |
| int res=doand; |
| |
| args = DmStrSpn(args,white); |
| do { |
| p=ScanToken(args, &next, TRUE); |
| |
| if (doand ? !*p : *p) { |
| res = !doand; |
| FREE(p); |
| break; |
| } |
| |
| FREE(p); |
| } |
| while (*(args=DmStrSpn(next,white))); |
| |
| return(res ? DmStrDup("t") : DmStrDup("")); |
| } |
| |
| |
| static char * |
| _exec_not( args ) |
| char *args; |
| { |
| char *white = " \t\n"; |
| char *p=Expand(args); |
| int res = (*DmStrSpn(p,white) == '\0'); |
| |
| FREE(p); |
| return(res ? DmStrDup("t") : DmStrDup("")); |
| } |
| |
| |
| char * |
| exec_normpath( args )/* |
| ======================= |
| Normalize token-wise. The normalised filenames are returned in a new |
| string, the original string is not freed. Quoted tokens remain quoted |
| after the normalizaton. */ |
| char *args; |
| { |
| TKSTR str; |
| char *s, *res; |
| |
| /* This honors .WINPATH . */ |
| SET_TOKEN( &str, args ); |
| res = NIL(char); |
| while( *(s = Get_token( &str, "", FALSE )) != '\0' ) { |
| if(str.tk_quote == 0) { |
| /* Add leading quote. */ |
| res = DmStrApp(res, "\""); |
| res = DmStrJoin(res, DO_WINPATH(normalize_path(s)), -1, TRUE); |
| /* Append the trailing quote. */ |
| res = DmStrJoin(res, "\"", 1, TRUE); |
| } else { |
| res = DmStrApp(res, DO_WINPATH(normalize_path(s))); |
| } |
| } |
| return res; |
| } |