/** * @file expMake.c * * Time-stamp: "2012-03-04 13:54:13 bkorb" * * This module implements Makefile construction functions. * * This file is part of AutoGen. * AutoGen Copyright (c) 1992-2012 by Bruce Korb - all rights reserved * * AutoGen is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * AutoGen is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /** * Figure out how to handle the line continuation. * If the line we just finished ends with a backslash, we're done. * Just add the newline character. If it ends with a semi-colon or a * doubled amphersand or doubled or-bar, then escape the newline with a * backslash. If the line ends with one of the keywords "then", "in" or * "else", also only add the escaped newline. Otherwise, add a * semi-colon, backslash and newline. * * @param ppzi pointer to pointer to input text * @param ppzo pointer to pointer to output text * @param tabch line prefix (tab) character * @param bol pointer to start of currently-being-output line * * @returns false to say the newline is dropped becase we're done * true to say the line was appended with the newline. */ static bool handle_eol(char ** ppzi, char ** ppzo, char tabch, char * bol) { char * pzScn = *ppzi; char * pzOut = *ppzo; int l_len = pzOut - bol; /* * Backup past trailing white space (other than newline). */ while (IS_NON_NL_WHITE_CHAR(pzOut[-1])) pzOut--; /* * Skip over empty lines, but leave leading white space * on the next non-empty line. */ { char* pz = pzScn; while (IS_WHITESPACE_CHAR(*pz)) { if (*(pz++) == NL) pzScn = pz; } } /* * The final newline is dropped. */ if (*pzScn == NUL) return false; switch (pzOut[-1]) { case '\\': /* * The newline is already escaped, so don't * insert our extra command termination. */ *(pzOut++) = NL; break; case '&': /* * A single ampersand is a backgrounded command. We must terminate * those statements, but not statements conjoined with '&&'. */ if ('&' != pzOut[-2]) goto append_statement_end; /* FALLTHROUGH */ case '|': case ';': skip_semi_colon: /* * Whatever the reason for a final '|', '&' or ';', * we will not add a semi-colon after it. */ memcpy(pzOut, MAKE_SCRIPT_NL + 2, MAKE_SCRIPT_NL_LEN - 2); pzOut += MAKE_SCRIPT_NL_LEN - 2; break; case 'n': // "then" or "in" if (l_len < 3) goto append_statement_end; if (pzOut[-2] == 'i') { if ((l_len == 3) || IS_WHITESPACE_CHAR(pzOut[-3])) goto skip_semi_colon; goto append_statement_end; } if ( (l_len < 5) || ( (l_len > 5) && ! IS_WHITESPACE_CHAR(pzOut[-5]) )) goto append_statement_end; if (strncmp(pzOut-4, HANDLE_EOL__THE, HANDLE_EOL__THE_LEN) == 0) goto skip_semi_colon; goto append_statement_end; case 'e': // "else" if ( (l_len < 5) || ( (l_len > 5) && ! IS_WHITESPACE_CHAR(pzOut[-5]) )) goto append_statement_end; if (strncmp(pzOut-4, HANDLE_EOL__ELS, HANDLE_EOL__ELS_LEN) == 0) goto skip_semi_colon; goto append_statement_end; default: append_statement_end: /* * Terminate the current command and escape the newline. */ memcpy(pzOut, MAKE_SCRIPT_NL, MAKE_SCRIPT_NL_LEN); pzOut += MAKE_SCRIPT_NL_LEN; } /* * We have now started our next output line and there are still data. * Indent with a tab, if called for. If we do insert a tab, then skip * leading tabs on the line. */ if (tabch) { *(pzOut++) = tabch; while (*pzScn == tabch) pzScn++; } *ppzi = pzScn; *ppzo = pzOut; return true; } /** * Pass through untreated sedable lines. Sometimes it is just very useful * to post-process Makefile files with sed(1) to clean it up. * * @param txt pointer to text. We skip initial white space. * @param tab pointer to where we stash the tab character to use * @returns true to say this was a sed line and was emitted, * false to say it was not and needs to be copied out. */ static bool handle_sed_expr(char ** src_p, char ** out_p) { char * src = *src_p; char * out = *out_p; switch (src[1]) { case 'i': if (strncmp(src+2, HANDLE_SED_IFNDEF, HANDLE_SED_IFNDEF_LEN) == 0) break; if (strncmp(src+2, HANDLE_SED_IFDEF, HANDLE_SED_IFDEF_LEN) == 0) break; return false; case 'e': if (strncmp(src+2, HANDLE_SED_ELSE, HANDLE_SED_ELSE_LEN) == 0) break; if (strncmp(src+2, HANDLE_SED_ENDIF, HANDLE_SED_ENDIF_LEN) == 0) break; /* FALLTHROUGH */ default: return false; } { char * p = BRK_NEWLINE_CHARS(src); size_t l; if (*p == NL) /* do not skip NUL */ p++; l = p - src; memcpy(out, src, l); *src_p = src + l; *out_p = out + l; } return true; } /** * Compute a maximal size for the output script. Leading and trailing white * space are trimmed. Dollar characters will likely be doubled and newlines * may get as many as MAKE_SCRIPT_NL_LEN characters inserted. Make sure * there's space. * * @param txt pointer to text. We skip initial white space. * @param tab pointer to where we stash the tab character to use * @returns the maximum number of bytes required to store result. */ static size_t script_size(char ** txt_p, char * tab) { char * txt = *txt_p; char * ptxte; size_t sz = 0; /* * skip all blank lines and other initial white space * in the source string. */ if (! IS_WHITESPACE_CHAR(*txt)) *tab = TAB; else { txt = SPN_WHITESPACE_CHARS(txt + 1); *tab = (txt[-1] == TAB) ? NUL : TAB; } /* * Do nothing with empty input. */ if (*txt == NUL) return 0; /* * "txt" is now our starting point. Do not modify it any more. */ *txt_p = txt; for (ptxte = txt - 1;;) { ptxte = BRK_MAKE_SCRIPT_CHARS(ptxte+1); if (*ptxte == NUL) break; sz += (*ptxte == '$') ? 1 : MAKE_SCRIPT_NL_LEN; } ptxte = SPN_WHITESPACE_BACK(txt, ptxte); *ptxte = NUL; sz += (ptxte - txt); return sz; } /*=gfunc makefile_script * * what: create makefile script * general_use: * * exparg: text, the text of the script * * doc: * This function will take ordinary shell script text and reformat it * so that it will work properly inside of a makefile shell script. * Not every shell construct can be supported; the intent is to have * most ordinary scripts work without much, if any, alteration. * * The following transformations are performed on the source text: * * @enumerate * @item * Trailing whitespace on each line is stripped. * * @item * Except for the last line, the string, " ; \\" is appended to the end of * every line that does not end with certain special characters or keywords. * Note that this will mutilate multi-line quoted strings, but @command{make} * renders it impossible to use multi-line constructs anyway. * * @item * If the line ends with a backslash, it is left alone. * * @item * If the line ends with a semi-colon, conjunction operator, pipe (vertical * bar) or one of the keywords "then", "else" or "in", then a space and a * backslash is added, but no semi-colon. * * @item * The dollar sign character is doubled, unless it immediately precedes an * opening parenthesis or the single character make macros '*', '<', '@@', * '?' or '%'. Other single character make macros that do not have enclosing * parentheses will fail. For shell usage of the "$@@", "$?" and "$*" * macros, you must enclose them with curly braces, e.g., "$@{?@}". * The ksh construct @code{$()} will not work. Though some * @command{make}s accept @code{$@{var@}} constructs, this function will * assume it is for shell interpretation and double the dollar character. * You must use @code{$(var)} for all @command{make} substitutions. * * @item * Double dollar signs are replaced by four before the next character * is examined. * * @item * Every line is prefixed with a tab, unless the first line * already starts with a tab. * * @item * The newline character on the last line, if present, is suppressed. * * @item * Blank lines are stripped. * * @item * Lines starting with "@@ifdef", "@@ifndef", "@@else" and "@@endif" are * presumed to be autoconf "sed" expression tags. These lines will be * emitted as-is, with no tab prefix and no line splicing backslash. * These lines can then be processed at configure time with * @code{AC_CONFIG_FILES} sed expressions, similar to: * * @example * sed "/^@@ifdef foo/d;/^@@endif foo/d;/^@@ifndef foo/,/^@@endif foo/d" * @end example * @end enumerate * * @noindent * This function is intended to be used approximately as follows: * * @example * $(TARGET) : $(DEPENDENCIES) * <+ (out-push-new) +> * ....mostly arbitrary shell script text.... * <+ (makefile-script (out-pop #t)) +> * @end example =*/ SCM ag_scm_makefile_script(SCM text_scm) { char * res_str; /*@< result string */ char * out; /*@< output scanning ptr */ char * bol; /*@< start of last output line */ char tabch; /*@< char to use for start-of-line tab */ char * text = ag_scm2zchars(text_scm, "make script"); size_t sz = script_size(&text, &tabch); if (sz == 0) return AG_SCM_STR02SCM(zNil); bol = out = res_str = ag_scribble(sz); /* * Force the initial line to start with a real tab. */ *(out++) = TAB; for (;;) { char * p = BRK_MAKE_SCRIPT_CHARS(text); size_t l = p - text; if (l > 0) { memcpy(out, text, l); text = p; out += l; } /* * "text" now points to one of three characters: * a newline, a dollar or a NUL byte. */ if (*text == NUL) break; if (*text == NL) { if (! handle_eol(&text, &out, tabch, bol)) break; bol = out; /* * As a special "hack", if a line starts with "@ifdef", "@ifndef", * "@else" or "@endif", then we assume post processing sed will * fix it up. Those lines get left alone. */ if (*text == '@') { if (handle_sed_expr(&text, &out)) bol = out; } } else { /* * Quadruple a double dollar, leave alone make-interesting * dollars, and double it otherwise. */ switch (text[1]) { case '(': case '*': case '@': case '<': case '%': case '?': /* one only */ break; case '$': /* * $$ in the shell means process id. Avoid having to do a * backward scan on the second '$' by handling the next '$' * now. We get FOUR '$' chars. */ text++; *(out++) = '$'; *(out++) = '$'; *(out++) = '$'; /* quadruple */ break; default: *(out++) = '$'; /* double */ } *(out++) = *(text++); } } { SCM res = AG_SCM_STR2SCM(res_str, out - res_str); return res; } } /* * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of agen5/expMake.c */