1/* 2 * ---------------------------------------------------------------------------- 3 * nmakehlp.c -- 4 * 5 * This is used to fix limitations within nmake and the environment. 6 * 7 * Copyright (c) 2002 by David Gravereaux. 8 * Copyright (c) 2006 by Pat Thoyts 9 * 10 * See the file "license.terms" for information on usage and redistribution of 11 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * ---------------------------------------------------------------------------- 14 * RCS: @(#) $Id: nmakehlp.c,v 1.2 2007/09/24 23:35:22 rolf Exp $ 15 * ---------------------------------------------------------------------------- 16 */ 17 18#define _CRT_SECURE_NO_DEPRECATE 19#include <windows.h> 20#pragma comment (lib, "user32.lib") 21#pragma comment (lib, "kernel32.lib") 22#include <stdio.h> 23#include <math.h> 24#if defined(_M_IA64) || defined(_M_AMD64) 25#pragma comment(lib, "bufferoverflowU") 26#endif 27 28/* ISO hack for dumb VC++ */ 29#ifdef _MSC_VER 30#define snprintf _snprintf 31#endif 32 33 34 35/* protos */ 36 37int CheckForCompilerFeature(const char *option); 38int CheckForLinkerFeature(const char *option); 39int IsIn(const char *string, const char *substring); 40int GrepForDefine(const char *file, const char *string); 41int SubstituteFile(const char *substs, const char *filename); 42const char * GetVersionFromFile(const char *filename, const char *match); 43DWORD WINAPI ReadFromPipe(LPVOID args); 44 45/* globals */ 46 47#define CHUNK 25 48#define STATICBUFFERSIZE 1000 49typedef struct { 50 HANDLE pipe; 51 char buffer[STATICBUFFERSIZE]; 52} pipeinfo; 53 54pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'}; 55pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'}; 56 57/* 58 * exitcodes: 0 == no, 1 == yes, 2 == error 59 */ 60 61int 62main( 63 int argc, 64 char *argv[]) 65{ 66 char msg[300]; 67 DWORD dwWritten; 68 int chars; 69 70 /* 71 * Make sure children (cl.exe and link.exe) are kept quiet. 72 */ 73 74 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX); 75 76 /* 77 * Make sure the compiler and linker aren't effected by the outside world. 78 */ 79 80 SetEnvironmentVariable("CL", ""); 81 SetEnvironmentVariable("LINK", ""); 82 83 if (argc > 1 && *argv[1] == '-') { 84 switch (*(argv[1]+1)) { 85 case 'c': 86 if (argc != 3) { 87 chars = snprintf(msg, sizeof(msg) - 1, 88 "usage: %s -c <compiler option>\n" 89 "Tests for whether cl.exe supports an option\n" 90 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 91 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 92 &dwWritten, NULL); 93 return 2; 94 } 95 return CheckForCompilerFeature(argv[2]); 96 case 'l': 97 if (argc != 3) { 98 chars = snprintf(msg, sizeof(msg) - 1, 99 "usage: %s -l <linker option>\n" 100 "Tests for whether link.exe supports an option\n" 101 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 102 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 103 &dwWritten, NULL); 104 return 2; 105 } 106 return CheckForLinkerFeature(argv[2]); 107 case 'f': 108 if (argc == 2) { 109 chars = snprintf(msg, sizeof(msg) - 1, 110 "usage: %s -f <string> <substring>\n" 111 "Find a substring within another\n" 112 "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]); 113 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 114 &dwWritten, NULL); 115 return 2; 116 } else if (argc == 3) { 117 /* 118 * If the string is blank, there is no match. 119 */ 120 121 return 0; 122 } else { 123 return IsIn(argv[2], argv[3]); 124 } 125 case 'g': 126 if (argc == 2) { 127 chars = snprintf(msg, sizeof(msg) - 1, 128 "usage: %s -g <file> <string>\n" 129 "grep for a #define\n" 130 "exitcodes: integer of the found string (no decimals)\n", 131 argv[0]); 132 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 133 &dwWritten, NULL); 134 return 2; 135 } 136 return GrepForDefine(argv[2], argv[3]); 137 case 's': 138 if (argc == 2) { 139 chars = snprintf(msg, sizeof(msg) - 1, 140 "usage: %s -s <substitutions file> <file>\n" 141 "Perform a set of string map type substutitions on a file\n" 142 "exitcodes: 0\n", 143 argv[0]); 144 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 145 &dwWritten, NULL); 146 return 2; 147 } 148 return SubstituteFile(argv[2], argv[3]); 149 case 'V': 150 if (argc != 4) { 151 chars = snprintf(msg, sizeof(msg) - 1, 152 "usage: %s -V filename matchstring\n" 153 "Extract a version from a file:\n" 154 "eg: pkgIndex.tcl \"package ifneeded http\"", 155 argv[0]); 156 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, 157 &dwWritten, NULL); 158 return 0; 159 } 160 printf("%s\n", GetVersionFromFile(argv[2], argv[3])); 161 return 0; 162 } 163 } 164 chars = snprintf(msg, sizeof(msg) - 1, 165 "usage: %s -c|-l|-f|-g|-V ...\n" 166 "This is a little helper app to equalize shell differences between WinNT and\n" 167 "Win9x and get nmake.exe to accomplish its job.\n", 168 argv[0]); 169 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL); 170 return 2; 171} 172 173int 174CheckForCompilerFeature( 175 const char *option) 176{ 177 STARTUPINFO si; 178 PROCESS_INFORMATION pi; 179 SECURITY_ATTRIBUTES sa; 180 DWORD threadID; 181 char msg[300]; 182 BOOL ok; 183 HANDLE hProcess, h, pipeThreads[2]; 184 char cmdline[100]; 185 186 hProcess = GetCurrentProcess(); 187 188 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 189 ZeroMemory(&si, sizeof(STARTUPINFO)); 190 si.cb = sizeof(STARTUPINFO); 191 si.dwFlags = STARTF_USESTDHANDLES; 192 si.hStdInput = INVALID_HANDLE_VALUE; 193 194 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); 195 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 196 sa.lpSecurityDescriptor = NULL; 197 sa.bInheritHandle = FALSE; 198 199 /* 200 * Create a non-inheritible pipe. 201 */ 202 203 CreatePipe(&Out.pipe, &h, &sa, 0); 204 205 /* 206 * Dupe the write side, make it inheritible, and close the original. 207 */ 208 209 DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, 210 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 211 212 /* 213 * Same as above, but for the error side. 214 */ 215 216 CreatePipe(&Err.pipe, &h, &sa, 0); 217 DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, 218 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 219 220 /* 221 * Base command line. 222 */ 223 224 lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X -Fp.\\_junk.pch "); 225 226 /* 227 * Append our option for testing 228 */ 229 230 lstrcat(cmdline, option); 231 232 /* 233 * Filename to compile, which exists, but is nothing and empty. 234 */ 235 236 lstrcat(cmdline, " .\\nul"); 237 238 ok = CreateProcess( 239 NULL, /* Module name. */ 240 cmdline, /* Command line. */ 241 NULL, /* Process handle not inheritable. */ 242 NULL, /* Thread handle not inheritable. */ 243 TRUE, /* yes, inherit handles. */ 244 DETACHED_PROCESS, /* No console for you. */ 245 NULL, /* Use parent's environment block. */ 246 NULL, /* Use parent's starting directory. */ 247 &si, /* Pointer to STARTUPINFO structure. */ 248 &pi); /* Pointer to PROCESS_INFORMATION structure. */ 249 250 if (!ok) { 251 DWORD err = GetLastError(); 252 int chars = snprintf(msg, sizeof(msg) - 1, 253 "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); 254 255 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| 256 FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], 257 (300-chars), 0); 258 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL); 259 return 2; 260 } 261 262 /* 263 * Close our references to the write handles that have now been inherited. 264 */ 265 266 CloseHandle(si.hStdOutput); 267 CloseHandle(si.hStdError); 268 269 WaitForInputIdle(pi.hProcess, 5000); 270 CloseHandle(pi.hThread); 271 272 /* 273 * Start the pipe reader threads. 274 */ 275 276 pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); 277 pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); 278 279 /* 280 * Block waiting for the process to end. 281 */ 282 283 WaitForSingleObject(pi.hProcess, INFINITE); 284 CloseHandle(pi.hProcess); 285 286 /* 287 * Wait for our pipe to get done reading, should it be a little slow. 288 */ 289 290 WaitForMultipleObjects(2, pipeThreads, TRUE, 500); 291 CloseHandle(pipeThreads[0]); 292 CloseHandle(pipeThreads[1]); 293 294 /* 295 * Look for the commandline warning code in both streams. 296 * - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002. 297 */ 298 299 return !(strstr(Out.buffer, "D4002") != NULL 300 || strstr(Err.buffer, "D4002") != NULL 301 || strstr(Out.buffer, "D9002") != NULL 302 || strstr(Err.buffer, "D9002") != NULL); 303} 304 305int 306CheckForLinkerFeature( 307 const char *option) 308{ 309 STARTUPINFO si; 310 PROCESS_INFORMATION pi; 311 SECURITY_ATTRIBUTES sa; 312 DWORD threadID; 313 char msg[300]; 314 BOOL ok; 315 HANDLE hProcess, h, pipeThreads[2]; 316 char cmdline[100]; 317 318 hProcess = GetCurrentProcess(); 319 320 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 321 ZeroMemory(&si, sizeof(STARTUPINFO)); 322 si.cb = sizeof(STARTUPINFO); 323 si.dwFlags = STARTF_USESTDHANDLES; 324 si.hStdInput = INVALID_HANDLE_VALUE; 325 326 ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); 327 sa.nLength = sizeof(SECURITY_ATTRIBUTES); 328 sa.lpSecurityDescriptor = NULL; 329 sa.bInheritHandle = TRUE; 330 331 /* 332 * Create a non-inheritible pipe. 333 */ 334 335 CreatePipe(&Out.pipe, &h, &sa, 0); 336 337 /* 338 * Dupe the write side, make it inheritible, and close the original. 339 */ 340 341 DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE, 342 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 343 344 /* 345 * Same as above, but for the error side. 346 */ 347 348 CreatePipe(&Err.pipe, &h, &sa, 0); 349 DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE, 350 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); 351 352 /* 353 * Base command line. 354 */ 355 356 lstrcpy(cmdline, "link.exe -nologo "); 357 358 /* 359 * Append our option for testing. 360 */ 361 362 lstrcat(cmdline, option); 363 364 ok = CreateProcess( 365 NULL, /* Module name. */ 366 cmdline, /* Command line. */ 367 NULL, /* Process handle not inheritable. */ 368 NULL, /* Thread handle not inheritable. */ 369 TRUE, /* yes, inherit handles. */ 370 DETACHED_PROCESS, /* No console for you. */ 371 NULL, /* Use parent's environment block. */ 372 NULL, /* Use parent's starting directory. */ 373 &si, /* Pointer to STARTUPINFO structure. */ 374 &pi); /* Pointer to PROCESS_INFORMATION structure. */ 375 376 if (!ok) { 377 DWORD err = GetLastError(); 378 int chars = snprintf(msg, sizeof(msg) - 1, 379 "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err); 380 381 FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS| 382 FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars], 383 (300-chars), 0); 384 WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL); 385 return 2; 386 } 387 388 /* 389 * Close our references to the write handles that have now been inherited. 390 */ 391 392 CloseHandle(si.hStdOutput); 393 CloseHandle(si.hStdError); 394 395 WaitForInputIdle(pi.hProcess, 5000); 396 CloseHandle(pi.hThread); 397 398 /* 399 * Start the pipe reader threads. 400 */ 401 402 pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID); 403 pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID); 404 405 /* 406 * Block waiting for the process to end. 407 */ 408 409 WaitForSingleObject(pi.hProcess, INFINITE); 410 CloseHandle(pi.hProcess); 411 412 /* 413 * Wait for our pipe to get done reading, should it be a little slow. 414 */ 415 416 WaitForMultipleObjects(2, pipeThreads, TRUE, 500); 417 CloseHandle(pipeThreads[0]); 418 CloseHandle(pipeThreads[1]); 419 420 /* 421 * Look for the commandline warning code in the stderr stream. 422 */ 423 424 return !(strstr(Out.buffer, "LNK1117") != NULL || 425 strstr(Err.buffer, "LNK1117") != NULL || 426 strstr(Out.buffer, "LNK4044") != NULL || 427 strstr(Err.buffer, "LNK4044") != NULL); 428} 429 430DWORD WINAPI 431ReadFromPipe( 432 LPVOID args) 433{ 434 pipeinfo *pi = (pipeinfo *) args; 435 char *lastBuf = pi->buffer; 436 DWORD dwRead; 437 BOOL ok; 438 439 again: 440 if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) { 441 CloseHandle(pi->pipe); 442 return (DWORD)-1; 443 } 444 ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L); 445 if (!ok || dwRead == 0) { 446 CloseHandle(pi->pipe); 447 return 0; 448 } 449 lastBuf += dwRead; 450 goto again; 451 452 return 0; /* makes the compiler happy */ 453} 454 455int 456IsIn( 457 const char *string, 458 const char *substring) 459{ 460 return (strstr(string, substring) != NULL); 461} 462 463/* 464 * Find a specified #define by name. 465 * 466 * If the line is '#define TCL_VERSION "8.5"', it returns 85 as the result. 467 */ 468 469int 470GrepForDefine( 471 const char *file, 472 const char *string) 473{ 474 char s1[51], s2[51], s3[51]; 475 FILE *f = fopen(file, "rt"); 476 477 if (f == NULL) { 478 return 0; 479 } 480 481 do { 482 int r = fscanf(f, "%50s", s1); 483 484 if (r == 1 && !strcmp(s1, "#define")) { 485 /* 486 * Get next two words. 487 */ 488 489 r = fscanf(f, "%50s %50s", s2, s3); 490 if (r != 2) { 491 continue; 492 } 493 494 /* 495 * Is the first word what we're looking for? 496 */ 497 498 if (!strcmp(s2, string)) { 499 double d1; 500 501 fclose(f); 502 503 /* 504 * Add 1 past first double quote char. "8.5" 505 */ 506 507 d1 = atof(s3 + 1); /* 8.5 */ 508 while (floor(d1) != d1) { 509 d1 *= 10.0; 510 } 511 return ((int) d1); /* 85 */ 512 } 513 } 514 } while (!feof(f)); 515 516 fclose(f); 517 return 0; 518} 519 520/* 521 * GetVersionFromFile -- 522 * Looks for a match string in a file and then returns the version 523 * following the match where a version is anything acceptable to 524 * package provide or package ifneeded. 525 */ 526 527const char * 528GetVersionFromFile( 529 const char *filename, 530 const char *match) 531{ 532 size_t cbBuffer = 100; 533 static char szBuffer[100]; 534 char *szResult = NULL; 535 FILE *fp = fopen(filename, "rt"); 536 537 if (fp != NULL) { 538 /* 539 * Read data until we see our match string. 540 */ 541 542 while (fgets(szBuffer, cbBuffer, fp) != NULL) { 543 LPSTR p, q; 544 545 p = strstr(szBuffer, match); 546 if (p != NULL) { 547 /* 548 * Skip to first digit. 549 */ 550 551 while (*p && !isdigit(*p)) { 552 ++p; 553 } 554 555 /* 556 * Find ending whitespace. 557 */ 558 559 q = p; 560 while (*q && (isalnum(*q) || *q == '.')) { 561 ++q; 562 } 563 564 memcpy(szBuffer, p, q - p); 565 szBuffer[q-p] = 0; 566 szResult = szBuffer; 567 break; 568 } 569 } 570 fclose(fp); 571 } 572 return szResult; 573} 574 575/* 576 * List helpers for the SubstituteFile function 577 */ 578 579typedef struct list_item_t { 580 struct list_item_t *nextPtr; 581 char * key; 582 char * value; 583} list_item_t; 584 585/* insert a list item into the list (list may be null) */ 586static list_item_t * 587list_insert(list_item_t **listPtrPtr, const char *key, const char *value) 588{ 589 list_item_t *itemPtr = malloc(sizeof(list_item_t)); 590 if (itemPtr) { 591 itemPtr->key = strdup(key); 592 itemPtr->value = strdup(value); 593 itemPtr->nextPtr = NULL; 594 595 while(*listPtrPtr) { 596 listPtrPtr = &(*listPtrPtr)->nextPtr; 597 } 598 *listPtrPtr = itemPtr; 599 } 600 return itemPtr; 601} 602 603static void 604list_free(list_item_t **listPtrPtr) 605{ 606 list_item_t *tmpPtr, *listPtr = *listPtrPtr; 607 while (listPtr) { 608 tmpPtr = listPtr; 609 listPtr = listPtr->nextPtr; 610 free(tmpPtr->key); 611 free(tmpPtr->value); 612 free(tmpPtr); 613 } 614} 615 616/* 617 * SubstituteFile -- 618 * As windows doesn't provide anything useful like sed and it's unreliable 619 * to use the tclsh you are building against (consider x-platform builds - 620 * eg compiling AMD64 target from IX86) we provide a simple substitution 621 * option here to handle autoconf style substitutions. 622 * The substitution file is whitespace and line delimited. The file should 623 * consist of lines matching the regular expression: 624 * \s*\S+\s+\S*$ 625 * 626 * Usage is something like: 627 * nmakehlp -S << $** > $@ 628 * @PACKAGE_NAME@ $(PACKAGE_NAME) 629 * @PACKAGE_VERSION@ $(PACKAGE_VERSION) 630 * << 631 */ 632 633int 634SubstituteFile( 635 const char *substitutions, 636 const char *filename) 637{ 638 size_t cbBuffer = 1024; 639 static char szBuffer[1024], szCopy[1024]; 640 char *szResult = NULL; 641 list_item_t *substPtr = NULL; 642 FILE *fp, *sp; 643 644 fp = fopen(filename, "rt"); 645 if (fp != NULL) { 646 647 /* 648 * Build a list of substutitions from the first filename 649 */ 650 651 sp = fopen(substitutions, "rt"); 652 if (sp != NULL) { 653 while (fgets(szBuffer, cbBuffer, sp) != NULL) { 654 char *ks, *ke, *vs, *ve; 655 ks = szBuffer; 656 while (ks && *ks && isspace(*ks)) ++ks; 657 ke = ks; 658 while (ke && *ke && !isspace(*ke)) ++ke; 659 vs = ke; 660 while (vs && *vs && isspace(*vs)) ++vs; 661 ve = vs; 662 while (ve && *ve && !(*ve == '\r' || *ve == '\n')) ++ve; 663 *ke = 0, *ve = 0; 664 list_insert(&substPtr, ks, vs); 665 } 666 fclose(sp); 667 } 668 669 /* debug: dump the list */ 670#ifdef _DEBUG 671 { 672 int n = 0; 673 list_item_t *p = NULL; 674 for (p = substPtr; p != NULL; p = p->nextPtr, ++n) { 675 fprintf(stderr, "% 3d '%s' => '%s'\n", n, p->key, p->value); 676 } 677 } 678#endif 679 680 /* 681 * Run the substitutions over each line of the input 682 */ 683 684 while (fgets(szBuffer, cbBuffer, fp) != NULL) { 685 list_item_t *p = NULL; 686 for (p = substPtr; p != NULL; p = p->nextPtr) { 687 char *m = strstr(szBuffer, p->key); 688 if (m) { 689 char *cp, *op, *sp; 690 cp = szCopy; 691 op = szBuffer; 692 while (op != m) *cp++ = *op++; 693 sp = p->value; 694 while (sp && *sp) *cp++ = *sp++; 695 op += strlen(p->key); 696 while (*op) *cp++ = *op++; 697 *cp = 0; 698 memcpy(szBuffer, szCopy, sizeof(szCopy)); 699 } 700 } 701 printf(szBuffer); 702 } 703 704 list_free(&substPtr); 705 } 706 fclose(fp); 707 return 0; 708} 709 710/* 711 * Local variables: 712 * mode: c 713 * c-basic-offset: 4 714 * fill-column: 78 715 * indent-tabs-mode: t 716 * tab-width: 8 717 * End: 718 */ 719