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