summaryrefslogtreecommitdiff
path: root/dmake/function.c
diff options
context:
space:
mode:
Diffstat (limited to 'dmake/function.c')
-rw-r--r--dmake/function.c726
1 files changed, 726 insertions, 0 deletions
diff --git a/dmake/function.c b/dmake/function.c
new file mode 100644
index 000000000000..c0942db09213
--- /dev/null
+++ b/dmake/function.c
@@ -0,0 +1,726 @@
+/* $RCSfile: function.c,v $
+-- $Revision: 1.12 $
+-- last change: $Author: kz $ $Date: 2008-03-05 18:28:45 $
+--
+-- 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(data);
+ FREE(tokens);
+ FREE(tokens_after);
+ }
+
+ 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( !(rcp.st_attr & A_SILENT) ) {
+ 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;
+}