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