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