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 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * ----------------------------------------------------------------------------
13 * RCS: @(#) $Id: nmakehlp.c,v 1.1.4.4 2006/10/18 08:49:33 patthoyts Exp $
14 * ----------------------------------------------------------------------------
15 */
16
17#define _CRT_SECURE_NO_DEPRECATE
18#include <windows.h>
19#pragma comment (lib, "user32.lib")
20#pragma comment (lib, "kernel32.lib")
21#include <stdio.h>
22#include <math.h>
23#if defined(_M_IA64) || defined(_M_AMD64)
24#pragma comment(lib, "bufferoverflowU")
25#endif
26
27/* ISO hack for dumb VC++ */
28#ifdef _MSC_VER
29#define   snprintf	_snprintf
30#endif
31
32
33
34/* protos */
35
36int		CheckForCompilerFeature(const char *option);
37int		CheckForLinkerFeature(const char *option);
38int		IsIn(const char *string, const char *substring);
39int		GrepForDefine(const char *file, const char *string);
40DWORD WINAPI	ReadFromPipe(LPVOID args);
41
42/* globals */
43
44#define CHUNK	25
45#define STATICBUFFERSIZE    1000
46typedef struct {
47    HANDLE pipe;
48    char buffer[STATICBUFFERSIZE];
49} pipeinfo;
50
51pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'};
52pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'};
53
54/*
55 * exitcodes: 0 == no, 1 == yes, 2 == error
56 */
57
58int
59main(
60    int argc,
61    char *argv[])
62{
63    char msg[300];
64    DWORD dwWritten;
65    int chars;
66
67    /*
68     * Make sure children (cl.exe and link.exe) are kept quiet.
69     */
70
71    SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
72
73    /*
74     * Make sure the compiler and linker aren't effected by the outside world.
75     */
76
77    SetEnvironmentVariable("CL", "");
78    SetEnvironmentVariable("LINK", "");
79
80    if (argc > 1 && *argv[1] == '-') {
81	switch (*(argv[1]+1)) {
82	case 'c':
83	    if (argc != 3) {
84		chars = snprintf(msg, sizeof(msg) - 1,
85		        "usage: %s -c <compiler option>\n"
86			"Tests for whether cl.exe supports an option\n"
87			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
88		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
89			&dwWritten, NULL);
90		return 2;
91	    }
92	    return CheckForCompilerFeature(argv[2]);
93	case 'l':
94	    if (argc != 3) {
95		chars = snprintf(msg, sizeof(msg) - 1,
96	       		"usage: %s -l <linker option>\n"
97			"Tests for whether link.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 CheckForLinkerFeature(argv[2]);
104	case 'f':
105	    if (argc == 2) {
106		chars = snprintf(msg, sizeof(msg) - 1,
107			"usage: %s -f <string> <substring>\n"
108			"Find a substring within another\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	    } else if (argc == 3) {
114		/*
115		 * If the string is blank, there is no match.
116		 */
117
118		return 0;
119	    } else {
120		return IsIn(argv[2], argv[3]);
121	    }
122	case 'g':
123	    if (argc == 2) {
124		chars = snprintf(msg, sizeof(msg) - 1,
125			"usage: %s -g <file> <string>\n"
126			"grep for a #define\n"
127			"exitcodes: integer of the found string (no decimals)\n",
128			argv[0]);
129		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
130			&dwWritten, NULL);
131		return 2;
132	    }
133	    return GrepForDefine(argv[2], argv[3]);
134	}
135    }
136    chars = snprintf(msg, sizeof(msg) - 1,
137	    "usage: %s -c|-l|-f ...\n"
138	    "This is a little helper app to equalize shell differences between WinNT and\n"
139	    "Win9x and get nmake.exe to accomplish its job.\n",
140	    argv[0]);
141    WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
142    return 2;
143}
144
145int
146CheckForCompilerFeature(
147    const char *option)
148{
149    STARTUPINFO si;
150    PROCESS_INFORMATION pi;
151    SECURITY_ATTRIBUTES sa;
152    DWORD threadID;
153    char msg[300];
154    BOOL ok;
155    HANDLE hProcess, h, pipeThreads[2];
156    char cmdline[100];
157
158    hProcess = GetCurrentProcess();
159
160    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
161    ZeroMemory(&si, sizeof(STARTUPINFO));
162    si.cb = sizeof(STARTUPINFO);
163    si.dwFlags   = STARTF_USESTDHANDLES;
164    si.hStdInput = INVALID_HANDLE_VALUE;
165
166    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
167    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
168    sa.lpSecurityDescriptor = NULL;
169    sa.bInheritHandle = FALSE;
170
171    /*
172     * Create a non-inheritible pipe.
173     */
174
175    CreatePipe(&Out.pipe, &h, &sa, 0);
176
177    /*
178     * Dupe the write side, make it inheritible, and close the original.
179     */
180
181    DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
182	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
183
184    /*
185     * Same as above, but for the error side.
186     */
187
188    CreatePipe(&Err.pipe, &h, &sa, 0);
189    DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
190	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
191
192    /*
193     * Base command line.
194     */
195
196    lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X ");
197
198    /*
199     * Append our option for testing
200     */
201
202    lstrcat(cmdline, option);
203
204    /*
205     * Filename to compile, which exists, but is nothing and empty.
206     */
207
208    lstrcat(cmdline, " .\\nul");
209
210    ok = CreateProcess(
211	    NULL,	    /* Module name. */
212	    cmdline,	    /* Command line. */
213	    NULL,	    /* Process handle not inheritable. */
214	    NULL,	    /* Thread handle not inheritable. */
215	    TRUE,	    /* yes, inherit handles. */
216	    DETACHED_PROCESS, /* No console for you. */
217	    NULL,	    /* Use parent's environment block. */
218	    NULL,	    /* Use parent's starting directory. */
219	    &si,	    /* Pointer to STARTUPINFO structure. */
220	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
221
222    if (!ok) {
223	DWORD err = GetLastError();
224	int chars = snprintf(msg, sizeof(msg) - 1,
225		"Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
226
227	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
228		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars],
229		(300-chars), 0);
230	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL);
231	return 2;
232    }
233
234    /*
235     * Close our references to the write handles that have now been inherited.
236     */
237
238    CloseHandle(si.hStdOutput);
239    CloseHandle(si.hStdError);
240
241    WaitForInputIdle(pi.hProcess, 5000);
242    CloseHandle(pi.hThread);
243
244    /*
245     * Start the pipe reader threads.
246     */
247
248    pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
249    pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
250
251    /*
252     * Block waiting for the process to end.
253     */
254
255    WaitForSingleObject(pi.hProcess, INFINITE);
256    CloseHandle(pi.hProcess);
257
258    /*
259     * Wait for our pipe to get done reading, should it be a little slow.
260     */
261
262    WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
263    CloseHandle(pipeThreads[0]);
264    CloseHandle(pipeThreads[1]);
265
266    /*
267     * Look for the commandline warning code in both streams.
268     *  - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002.
269     */
270
271    return !(strstr(Out.buffer, "D4002") != NULL
272             || strstr(Err.buffer, "D4002") != NULL
273             || strstr(Out.buffer, "D9002") != NULL
274             || strstr(Err.buffer, "D9002") != NULL);
275}
276
277int
278CheckForLinkerFeature(
279    const char *option)
280{
281    STARTUPINFO si;
282    PROCESS_INFORMATION pi;
283    SECURITY_ATTRIBUTES sa;
284    DWORD threadID;
285    char msg[300];
286    BOOL ok;
287    HANDLE hProcess, h, pipeThreads[2];
288    char cmdline[100];
289
290    hProcess = GetCurrentProcess();
291
292    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
293    ZeroMemory(&si, sizeof(STARTUPINFO));
294    si.cb = sizeof(STARTUPINFO);
295    si.dwFlags   = STARTF_USESTDHANDLES;
296    si.hStdInput = INVALID_HANDLE_VALUE;
297
298    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
299    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
300    sa.lpSecurityDescriptor = NULL;
301    sa.bInheritHandle = TRUE;
302
303    /*
304     * Create a non-inheritible pipe.
305     */
306
307    CreatePipe(&Out.pipe, &h, &sa, 0);
308
309    /*
310     * Dupe the write side, make it inheritible, and close the original.
311     */
312
313    DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
314	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
315
316    /*
317     * Same as above, but for the error side.
318     */
319
320    CreatePipe(&Err.pipe, &h, &sa, 0);
321    DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
322	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
323
324    /*
325     * Base command line.
326     */
327
328    lstrcpy(cmdline, "link.exe -nologo ");
329
330    /*
331     * Append our option for testing.
332     */
333
334    lstrcat(cmdline, option);
335
336    ok = CreateProcess(
337	    NULL,	    /* Module name. */
338	    cmdline,	    /* Command line. */
339	    NULL,	    /* Process handle not inheritable. */
340	    NULL,	    /* Thread handle not inheritable. */
341	    TRUE,	    /* yes, inherit handles. */
342	    DETACHED_PROCESS, /* No console for you. */
343	    NULL,	    /* Use parent's environment block. */
344	    NULL,	    /* Use parent's starting directory. */
345	    &si,	    /* Pointer to STARTUPINFO structure. */
346	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
347
348    if (!ok) {
349	DWORD err = GetLastError();
350	int chars = snprintf(msg, sizeof(msg) - 1,
351		"Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
352
353	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
354		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars],
355		(300-chars), 0);
356	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, lstrlen(msg), &err,NULL);
357	return 2;
358    }
359
360    /*
361     * Close our references to the write handles that have now been inherited.
362     */
363
364    CloseHandle(si.hStdOutput);
365    CloseHandle(si.hStdError);
366
367    WaitForInputIdle(pi.hProcess, 5000);
368    CloseHandle(pi.hThread);
369
370    /*
371     * Start the pipe reader threads.
372     */
373
374    pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
375    pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
376
377    /*
378     * Block waiting for the process to end.
379     */
380
381    WaitForSingleObject(pi.hProcess, INFINITE);
382    CloseHandle(pi.hProcess);
383
384    /*
385     * Wait for our pipe to get done reading, should it be a little slow.
386     */
387
388    WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
389    CloseHandle(pipeThreads[0]);
390    CloseHandle(pipeThreads[1]);
391
392    /*
393     * Look for the commandline warning code in the stderr stream.
394     */
395
396    return !(strstr(Out.buffer, "LNK1117") != NULL ||
397             strstr(Err.buffer, "LNK1117") != NULL ||
398             strstr(Out.buffer, "LNK4044") != NULL ||
399             strstr(Err.buffer, "LNK4044") != NULL);
400}
401
402DWORD WINAPI
403ReadFromPipe(
404    LPVOID args)
405{
406    pipeinfo *pi = (pipeinfo *) args;
407    char *lastBuf = pi->buffer;
408    DWORD dwRead;
409    BOOL ok;
410
411  again:
412    if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) {
413	CloseHandle(pi->pipe);
414	return (DWORD)-1;
415    }
416    ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L);
417    if (!ok || dwRead == 0) {
418	CloseHandle(pi->pipe);
419	return 0;
420    }
421    lastBuf += dwRead;
422    goto again;
423
424    return 0;  /* makes the compiler happy */
425}
426
427int
428IsIn(
429    const char *string,
430    const char *substring)
431{
432    return (strstr(string, substring) != NULL);
433}
434
435/*
436 * Find a specified #define by name.
437 *
438 * If the line is '#define TCL_VERSION "8.5"', it returns 85 as the result.
439 */
440
441int
442GrepForDefine(
443    const char *file,
444    const char *string)
445{
446    FILE *f;
447    char s1[51], s2[51], s3[51];
448    int r = 0;
449    double d1;
450
451    f = fopen(file, "rt");
452    if (f == NULL) {
453	return 0;
454    }
455
456    do {
457	r = fscanf(f, "%50s", s1);
458	if (r == 1 && !strcmp(s1, "#define")) {
459	    /*
460	     * Get next two words.
461	     */
462
463	    r = fscanf(f, "%50s %50s", s2, s3);
464	    if (r != 2) {
465		continue;
466	    }
467
468	    /*
469	     * Is the first word what we're looking for?
470	     */
471
472	    if (!strcmp(s2, string)) {
473		fclose(f);
474
475		/*
476		 * Add 1 past first double quote char. "8.5"
477		 */
478
479		d1 = atof(s3 + 1);		  /*    8.5  */
480		while (floor(d1) != d1) {
481		    d1 *= 10.0;
482		}
483		return ((int) d1);		  /*    85   */
484	    }
485	}
486    } while (!feof(f));
487
488    fclose(f);
489    return 0;
490}
491