fix issue with repeated I in case of amend commit
[LibreOffice.git] / guw / guw.cc
blob431866cb83c18666594a6f019c564392184e8df2
1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Copyright 2000, 2010 Oracle and/or its affiliates.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * This file is part of OpenOffice.org.
11 * OpenOffice.org is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License version 3
13 * only, as published by the Free Software Foundation.
15 * OpenOffice.org is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License version 3 for more details
19 * (a copy is included in the LICENSE file that accompanied this code).
21 * You should have received a copy of the GNU Lesser General Public License
22 * version 3 along with OpenOffice.org. If not, see
23 * <http://www.openoffice.org/license.html>
24 * for a copy of the LGPLv3 License.
26 ************************************************************************/
28 // guw - A wrapper program to execute windows programs with parameters that
29 // contain cygwin (POSIX) style pathnames.
31 // Todo: Add a -? switch to guw to issue a help page.
33 #include <string>
34 #include <list>
35 #include <vector>
37 #include <iostream>
38 #include <sstream>
39 #include <fstream>
41 #include <cstddef>
42 #include <cerrno>
44 #include <sys/cygwin.h>
45 #include <windows.h>
46 #include <regex.h>
49 using std::string;
50 using std::list;
51 using std::vector;
52 using std::cout;
53 using std::cerr;
54 using std::endl;
55 using std::size_t;
57 void init_ignorepara(string command);
58 bool is_ignorepara(const string &para);
59 int winFormat(string &para);
60 void do_atcommand(string &para);
61 void myCygpath(string &path, int shortname = 1 );
62 void replace_cyg_env( void );
63 void Fatal( const string text );
65 int match2s(const string argument, const char *pattern, string &sub1, string &sub2);
66 void rep_subn_cyg(string &argument);
67 void rep_subn( string &argument, const char *pattern, int subexp, const char repl);
68 void rep_char( string &argument, const char from, const char to);
70 bool debug = false;
71 bool debug_light = false;
73 // The commands are treated case insensitive, the parameters
74 // are case sensitive.
75 const string ignorepara[] = { "echo /TEST",
76 "cl -clr: -Z",
77 "climaker StarOffice/OpenOffice",
78 "csc -target:",
79 "g++ -DLOCAL_RULE_LANGS -DUPD -DMINOR"
80 " -DBUILD_ID -DSC_INFO_OSVERSION",
81 "gcc -DUDATA_SO_SUFFIX -DSTATIC_O"
82 " -DPACKAGE -DU_MAKE",
83 "lib /OUT: -out: -def: -machine:",
84 "link /BASE: /COMMENT: /DEBUG: /DLL /ENTRY:"
85 " /MACHINE: /MAP /NODEFAULTLIB /OPT: /RELEASE"
86 " /STACK: /SUBSYSTEM: -NODEFAULTLIB:"
87 " -def: delayload: -implib: -map: -out:",
88 "rc -D",
89 "regcomp -env: vnd.sun.star.expand:"
90 " vnd.openoffice.pymodule: file:",
91 "regmerge /UCR",
92 "rsc -DOOO_" };
94 vector<string> ignorepara_vec;
96 // environment variables that are "winformatted" when -env is given
97 const string transformvars[] = { "SOLAR_VERSION",
98 "SOLARVERSION",
99 "SOLARVER",
100 "SRC_ROOT",
101 "SOLARENV",
102 "CLASSPATH",
103 "JAVA_HOME" };
106 int main(int argc, char **argv) {
108 // initialize arglist with arguments
109 list<string> arglist(argv, argv + argc);
111 // Drop the first (filename) argument
112 arglist.pop_front();
114 // iterarot over cmdline elements
115 list<string>::iterator ele = arglist.begin();
117 // Allowed switch values
118 bool conv_cyg_arg = false;
120 // Look for switches to guw
121 // Supported: -env
122 // -dbg
123 // -ldbg
124 while ( !arglist.empty()
125 && ele!=arglist.end()
126 && (ele->find("-", 0) == 0) ) {
127 if (ele->find("-env", 0) == 0) {
128 if ( conv_cyg_arg )
129 Fatal("-env used twice!");
131 conv_cyg_arg = true;
132 ele = arglist.erase(ele);
133 continue;
135 else if (ele->find("-dbg", 0) == 0) {
137 debug = true;
138 ele = arglist.erase(ele);
139 continue;
141 else if (ele->find("-ldbg", 0) == 0) {
143 debug_light = true;
144 ele = arglist.erase(ele);
145 continue;
147 else {
148 // Ignore this switch
149 ++ele;
153 // The next entry must be the program
154 string command;
155 if ( !arglist.empty() ) {
156 command.assign(*arglist.begin());
157 arglist.pop_front();
159 else
160 Fatal("guw needs at least one parameter.");
162 if ( debug )
163 cerr << "Command: " << command << "\n" << endl;
164 // Initialize parameter exception list (for this command)
165 init_ignorepara(command);
168 // Do something if -env was found
169 if ( conv_cyg_arg )
170 replace_cyg_env();
173 // loop and and DOSify the parameters
174 if ( debug )
175 cerr << "Transform the parameter\n" << endl;
177 ele=arglist.begin();
178 while ( ele != arglist.end() ) {
180 if ((*ele)[0] == '@')
181 do_atcommand(*ele);
182 else if (!is_ignorepara(*ele)) {
183 if ( debug ) {
184 cerr << "----------------" << endl;
185 cerr << "Process parameter: " << *ele << endl;
187 winFormat(*ele);
188 if ( debug )
189 cerr << "Transformed to: " << *ele << "\n" << endl;
192 ++ele;
195 // create the argv[] for execvp(argv[0], argv);
196 ele=arglist.begin();
198 // const char *nargv[arglist.size()+2]; // or ..
199 char *nargv[arglist.size()+2];
201 // nargv[0] = command.c_str(); // or ..
202 nargv[0] = new char[command.length()+1];
203 // strcpy(nargv[0], command.c_str());
204 command.copy(nargv[0], command.length());
205 nargv[0][command.length()] = 0;
207 if ( debug )
208 cerr << "----------------\n" << endl;
209 if ( debug || debug_light )
210 cerr << "Execute: " << nargv[0];
212 int count = 1, sLen;
213 while ( ele != arglist.end() ) {
214 // nargv[count] = ele->c_str(); // or ..
215 sLen = ele->length();
216 nargv[count] = new char[sLen+1];
217 // strcpy(nargv[count], ele->c_str());
218 ele->copy(nargv[count], sLen);
219 nargv[count][sLen] = 0;
221 if ( debug || debug_light )
222 cerr << " " << nargv[count];
224 ++count;
225 ++ele;
227 // last nargv[] must be NULL
228 nargv[count] = NULL;
229 if ( debug || debug_light )
230 cerr << endl;
232 // Unfortunately the prototype of execvp does not like const char*,
233 // actually not const char* nargv[] coming from .c_str(). So either
234 // we copy everything into newly allocated variables or we force it
235 // with a cast. const_cast<char * const *>()
236 // execvp(nargv[0], const_cast<char * const *>(nargv) );
237 if ( execvp(nargv[0], nargv ) < 0 ) {
238 perror("Execvp error. Aborting.");
239 exit(1);
242 // Omit the deleting of the dynamically allocated nargv[] elements
243 // here as this part will never be reached.
245 return 0;
248 // Initialize exception list from global var ignorepara[]
249 void init_ignorepara(string fullcommand) {
250 const size_t kplen = sizeof(ignorepara)/sizeof(string *);
251 string shortcommand, cmd, para, sub2;
253 // First lowercase everything
254 for(size_t i=0;i<fullcommand.length();i++)
255 fullcommand[i] = tolower(fullcommand[i]);
257 // Remove a potential .exe
258 size_t slen = fullcommand.length();
260 // for slen == 3 this would yield string::npos otherwise
261 if ( slen > 4 && fullcommand.rfind(".exe") == slen - 4 )
262 fullcommand.erase(slen-4);
264 // get the program name - Only one subexpression
265 if (!match2s(fullcommand, "([[:alnum:]_~. +-]+)$",
266 shortcommand, sub2)) {
267 Fatal("No basename found in: " + fullcommand);
270 for (size_t i=0; i != kplen; ++i) {
271 std::istringstream line(ignorepara[i]);
272 line >> cmd;
273 if (shortcommand == cmd)
274 while (line >> para) {
275 ignorepara_vec.push_back(para);
278 return ;
281 // Check if command/parameter is in exception list.
282 bool is_ignorepara(const string &para) {
284 for( vector<string>::iterator it = ignorepara_vec.begin();
285 it != ignorepara_vec.end(); ++it ) {
286 if ( para.find(*it) != string::npos ) {
287 if ( debug )
288 cerr << "Found execption para: " << para << endl;
290 return true;
294 return false;
297 // Reformat para to DOSish format
298 int winFormat(string &para) {
299 string su1, su2;
301 // Instead of ([/[:alnum:]_~. +-]+) use ((/?[[:alnum:]_~. +-]+)+)
303 // find [-][-]X<something>=<path>, sometimes with quotes or "/" at the end
304 if (match2s(para, "^(-?-?[[:alpha:]][[:alnum:]_.-]*=)[\'\"]?((/?[[:alnum:]_~. +-]+)+)[\'\"]?$",
305 su1, su2)) {
307 myCygpath(su2);
308 para.assign(su1 + su2);
309 if ( debug )
310 cerr << " WinFormat - ([-][-]<something>=<path>)\n"
311 << " " << para << endl;
314 // find -X<something>:<path>, sometimes with quotes or "/" at the end
315 else if (match2s(para, "^(-[[:alpha:]][[:alnum:]_.]*:)[\'\"]?((/?[[:alnum:]_~. +-]+)+)[\'\"]?$",
316 su1, su2)) {
318 myCygpath(su2);
319 para.assign(su1 + su2);
320 if ( debug )
321 cerr << " WinFormat - (-<something>:<path>)\n"
322 << " " << para << endl;
325 // find -X<something>:<NO-path>, and prevents translating of these.
326 else if (match2s(para, "^(-[[:alpha:]][[:alnum:]_]*:)(.*)$",
327 su1, su2)) {
329 // myCygpath(su2);
330 // para.assign(su1 + su2);
331 if ( debug )
332 cerr << " WinFormat - (-<something>:<NO-path>)\n"
333 << " " << para << endl;
336 // See iz35982 for the reason for the special treatment of this switch.
337 // This regex evaluates <something>:///<path>, sometimes with
338 // quotes or "/" at the end
339 else if (match2s(para, "^([[:alpha:]][[:alnum:]_]*:)[\'\"]?///((/?[[:alnum:]_~. +-]+)+)[\'\"]?$",
340 su1, su2)) {
342 myCygpath(su2);
343 para.assign(su1 + "///" + su2);
344 // Replace \ to /
345 rep_char( para, '\\', '/');
347 if ( debug )
348 cerr << " WinFormat - (<something>:///<path>)\n"
349 << " " << para << endl;
352 // find -X<absolute path>, sometimes with quotes or "/" at the end
353 else if (match2s(para, "^(-[[:alpha:]])[\'\"]?((/[[:alnum:]_~. +-]+)+)[\'\"]?$",
354 su1, su2)) {
356 myCygpath(su2);
357 para.assign(su1 + su2);
358 if ( debug )
359 cerr << " WinFormat - (-X<absolute path>)\n"
360 << " " << para << endl;
363 // find -FX<path> (MSVC switches for output naming), sometimes with quotes
364 // or "/" at the end
365 else if (match2s(para, "^(-F[ARdemopr])[\'\"]?(/[/[:alnum:]_~. +-]+)[\'\"]?$",
366 su1, su2)) {
368 myCygpath(su2);
369 para.assign(su1 + su2);
370 if ( debug )
371 cerr << " WinFormat - (compiler naming (-FX<absolute path>) path)\n"
372 << " " << para << endl;
375 else{
376 // No parameter found, assume a path
378 // replace the colon in drives with 0x1F"
379 // (Unused ascii US - unit separator)
380 rep_subn( para, "(^|[;,])[[:alpha:]](:)", 2, 0x1F);
382 // Replace remaining : to ;
383 rep_char( para, ':', ';');
385 // Replace back US to ':';
386 rep_char( para, 0x1F, ':');
388 /* Search for posix path ;entry; (The regex accepts valid paths with at
389 * least one /) and replace with DOS path, accept quotes.
390 * since iz28717 we also accept ',' as path seperator. */
391 rep_subn_cyg(para);
393 if ( debug )
394 cerr << " WinFormat - full path\n"
395 << " " << para << endl;
399 // Sanity check for -X<abspath>
400 if (match2s(para, "^(-[[:alpha:]])[\'\"]?((/[[:alnum:]_~. +-]+)+)",
401 su1, su2)) {
402 Fatal("Not converted -X/... type switch in :" + para);
404 // Sanity check for [-]X<something>(:|=)<abspath> case
405 if (match2s(para, "^(-?[[:alpha:]][[:alnum:]_.]+[=:])[\'\"]?((/[[:alnum:]_~. +-]+)+)",
406 su1, su2)) {
407 Fatal("Not processed [-]X<something>(=|:)/... in :" + para);
411 return 1;
414 // Reformat para to DOSish format
415 void do_atcommand(string &para) {
416 string at, filename, token;
418 // Workaround, iz28717, keep number of @'s.
419 match2s(para, "^(@+)(.*)",at ,filename);
420 if ( debug ) {
421 cerr << "----------------" << endl;
422 cerr << "Process @-file" << endl;
423 cerr << " :" << at << ": before filename :" << filename << ":" << endl;
426 // Read at file into memory
427 std::ifstream atin(filename.c_str());
428 list<string> newtoken;
429 while (atin >> token) {
430 // Read / transform tokens
431 if ( debug )
432 cerr << "@ token :" << token << ":" << endl;
433 if (!is_ignorepara(token))
434 winFormat(token);
436 newtoken.push_back(token);
438 atin.close();
440 // Write token tokens bak to file
441 if ( debug || debug_light )
442 cerr << "New @-file parameter:" << endl;
444 // for debugging ..
445 // filename += ".bak";
447 std::ofstream atout(filename.c_str());
448 list<string>::iterator tok = newtoken.begin();
449 while ( tok != newtoken.end() ) {
450 if ( debug || debug_light )
451 cerr << ( tok != newtoken.begin() ? " " : "" ) << *tok ;
453 atout << ( tok != newtoken.begin() ? " " : "" ) << *tok ;
454 ++tok;
456 // We want a dos file
457 atout << '\r' << endl;
458 atout.close();
460 // Transform the filename
461 winFormat(filename);
462 para = at + filename;
463 if ( debug || debug_light ) {
464 cerr << "\nNew @-file name: " << para << "\n" << endl;
468 void myCygpath(string &path, int shortname /* =1 */ )
470 static char convpath[MAX_PATH];
471 static char buf[MAX_PATH];
472 int err;
474 // Only use cygwin_conv_to_win32_path() on absolute paths as it errors
475 // out if its path doen't exist. Unfortunatelt there are a lot of not
476 // existing relative pathes used as parameters during an OOo build.
477 if( path.find("/", 0) == 0) {
478 err = cygwin_conv_to_win32_path( path.c_str(), convpath );
480 else {
481 rep_char( path, '/', '\\');
482 // see below, we copy convpath back to path, that's stupid
483 path.copy( convpath, path.length());
484 convpath[path.length()] = 0;
485 err = 0;
488 if (err)
489 Fatal("converting: " + path + " - " + strerror(errno) );
491 // Only convert to short dos names when space is present
492 if (shortname && (path.find(" ", 0) != string::npos) ) {
493 DWORD len = GetShortPathName (convpath, buf, MAX_PATH);
494 if (!len) {
495 Fatal("cannot create short name of " + string(convpath) );
498 path.assign(buf);
500 else
501 path.assign(convpath);
505 void replace_cyg_env( void ) {
506 // Transform certain environment variables
507 if ( debug )
508 cerr << "Transforming some environment variables" << endl;
510 const size_t nvars = sizeof(transformvars)/sizeof(string *);
512 char *currvar;
513 string newvar;
515 for (size_t i=0; i != nvars; ++i) {
516 if ( currvar = getenv(transformvars[i].c_str() ) ) {
517 // Only transform existent vars
518 if ( debug )
519 cerr << "Transform variable: " << transformvars[i] << "="
520 << currvar << endl;
521 newvar.assign(currvar);
522 winFormat(newvar);
523 if( setenv(transformvars[i].c_str(), newvar.c_str(), 1) )
524 Fatal("setenv failed on " + transformvars[i] + "=" + newvar +
525 " with error: " + strerror(errno));
526 if ( debug )
527 cerr << "To: " << transformvars[i] << "="
528 << newvar << "\n" << endl;
534 void Fatal( const string text ) {
535 // End with error
536 cerr << "Error: " << text << endl;
537 exit(1);
542 match2s(const string argument, const char *pattern, string &sub1, string &sub2)
544 int status;
545 regex_t re;
547 const int maxsub = 3; // Only 3 needed, 4 is for debug
548 regmatch_t match[maxsub];
550 if (regcomp(&re, pattern, REG_EXTENDED) != 0) {
551 Fatal("regcomp had a problem."); /* report error */
553 status = regexec(&re, argument.c_str(), maxsub, match, 0);
554 regfree(&re);
556 if (status == REG_NOMATCH) {
557 return(0); /* no match */
558 } else if (status == 0) {
559 string tstr(argument.c_str() + match[0].rm_so,
560 match[0].rm_eo - match[0].rm_so);
561 // cout << "Match: " << tstr << endl;
563 sub1.assign(argument.c_str() + match[1].rm_so, match[1].rm_eo - match[1].rm_so);
564 // cout << "Match1: " << sub1 << endl;
566 sub2.assign(argument.c_str() + match[2].rm_so, match[2].rm_eo - match[2].rm_so);
567 // cout << "Match2: " << sub2 << endl;
569 return(1); /* match found */
570 } else {
571 Fatal("regexec had a problem.");
574 // Not reached.
575 return(1);
579 // Replace path entry with myCygpath() version
580 void rep_subn_cyg(string &argument)
582 // accept ["']<entry>["']:["']<entry>["']:... to make the
583 // $(WRAPCMD) echo 1 ICON $(EMQ)"$(APP1ICON)$(EMQ)"
584 // work in ?tg_app.mk.
585 // FIXME: Better would be to use a DOSified $(APP1ICON) there and remove
586 // the special " treatment here.
587 const char *pattern = "(^|[;,])[\'\"]?([[:alnum:]_~. +-]*(/[[:alnum:]_~. +-]+)+/?)[\'\"]?([;,]|$)";
588 const int subexp = 2;
590 int status, pos=0;
591 regex_t re;
593 string repstr;
594 string::size_type oLen, nLen;
596 const int maxsub = subexp+1; // One more than the maximal subexpression
597 regmatch_t match[maxsub];
599 if (regcomp(&re, pattern, REG_EXTENDED) != 0) {
600 Fatal("regcomp had a problem."); /* report error */
602 status = regexec (&re, argument.c_str() + pos, maxsub, match, 0);
603 while (status == 0) { /* While matches found. */
604 // Classical assert()
605 if (match[subexp].rm_eo == -1) {
606 Fatal("Nonexisting subexpression specified!");
609 oLen = match[subexp].rm_eo - match[subexp].rm_so;
610 repstr.assign(argument.c_str() + pos + match[subexp].rm_so, oLen);
612 // Do not replace with shortpaths
613 myCygpath(repstr, 0);
614 nLen = repstr.length();
616 // replace
617 argument.replace( pos + match[subexp].rm_so, oLen, repstr );
619 /* Substring found between match[0].rm_so and match[0].rm_eo. */
620 /* This call to regexec() finds the next match. */
622 pos += match[0].rm_eo + nLen - oLen ;
624 // Either the last match did end in ';' or we are at the end of para.
625 // REG_NOTBOL is not used because we skip over the ';' by using pos.
626 status = regexec (&re, argument.c_str() + pos, maxsub, match, 0);
629 if (status != REG_NOMATCH) {
630 Fatal("regexec had a problem.");
633 regfree(&re);
636 // Replace all occurrences of subexpression number "subexp" with "repl"
637 void rep_subn( string &argument, const char *pattern, int subexp, const char repl)
639 int status, pos=0;
640 regex_t re;
642 const int maxsub = subexp+1; // One more than the maximal subexpression
643 regmatch_t match[maxsub];
645 if (regcomp(&re, pattern, REG_EXTENDED) != 0) {
646 Fatal("regcomp had a problem."); /* report error */
648 status = regexec (&re, argument.c_str() + pos, maxsub, match, 0);
649 while (status == 0) { /* While matches found. */
650 // Classical assert()
651 if (match[subexp].rm_eo == -1) {
652 Fatal("Nonexisting subexpression specified!");
655 argument[pos + match[subexp].rm_so] = repl;
657 /* Substring found between match[0].rm_so and match[0].rm_eo. */
658 /* This call to regexec() finds the next match. */
659 pos += match[0].rm_eo;
660 status = regexec (&re, argument.c_str() + pos, maxsub, match, REG_NOTBOL);
663 if (status != REG_NOMATCH) {
664 Fatal("regexec had a problem.");
667 regfree(&re);
670 // Replace all char1 with char2
671 void rep_char( string &argument, const char from, const char to)
673 string::size_type loc = 0;
675 while ( (loc = argument.find( from, loc )) != string::npos ) {
676 argument[loc] = to;