1/* ----------------------------------------------------------------------------
2 * nmakehlp.c --
3 *
4 *	This is used to fix limitations within nmake and the environment.
5 *
6 * Copyright (c) 2002 by David Gravereaux.
7 *
8 * See the file "license.terms" for information on usage and redistribution
9 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10 *
11 * ----------------------------------------------------------------------------
12 * RCS: @(#) $Id: nmakehlp.c,v 1.4 2004/04/29 17:40:44 davygrvy Exp $
13 * ----------------------------------------------------------------------------
14 */
15#include <windows.h>
16#pragma comment (lib, "user32.lib")
17#pragma comment (lib, "kernel32.lib")
18#include <stdio.h>
19#include <math.h>
20
21/* protos */
22int CheckForCompilerFeature (const char *option);
23int CheckForLinkerFeature (const char *option);
24int IsIn (const char *string, const char *substring);
25int GrepForDefine (const char *file, const char *string);
26DWORD WINAPI ReadFromPipe (LPVOID args);
27
28/* globals */
29#define CHUNK	25
30#define STATICBUFFERSIZE    1000
31typedef struct {
32    HANDLE pipe;
33    char buffer[STATICBUFFERSIZE];
34} pipeinfo;
35
36pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'};
37pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'};
38
39
40
41/* exitcodes: 0 == no, 1 == yes, 2 == error */
42int
43main (int argc, char *argv[])
44{
45    char msg[300];
46    DWORD dwWritten;
47    int chars;
48
49    /* make sure children (cl.exe and link.exe) are kept quiet. */
50    SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
51
52    /* Make sure the compiler and linker aren't effected by the outside world. */
53    SetEnvironmentVariable("CL", "");
54    SetEnvironmentVariable("LINK", "");
55
56    if (argc > 1 && *argv[1] == '-') {
57	switch (*(argv[1]+1)) {
58	case 'c':
59	    if (argc != 3) {
60		chars = wsprintf(msg, "usage: %s -c <compiler option>\n"
61			"Tests for whether cl.exe supports an option\n"
62			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
63		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
64		return 2;
65	    }
66	    return CheckForCompilerFeature(argv[2]);
67	case 'l':
68	    if (argc != 3) {
69		chars = wsprintf(msg, "usage: %s -l <linker option>\n"
70			"Tests for whether link.exe supports an option\n"
71			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
72		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
73		return 2;
74	    }
75	    return CheckForLinkerFeature(argv[2]);
76	case 'f':
77	    if (argc == 2) {
78		chars = wsprintf(msg, "usage: %s -f <string> <substring>\n"
79		    "Find a substring within another\n"
80		    "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
81		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
82		return 2;
83	    } else if (argc == 3) {
84		/* if the string is blank, there is no match */
85		return 0;
86	    } else {
87		return IsIn(argv[2], argv[3]);
88	    }
89	case 'g':
90	    if (argc == 2) {
91		chars = wsprintf(msg, "usage: %s -g <file> <string>\n"
92		    "grep for a #define\n"
93		    "exitcodes: integer of the found string (no decimals)\n", argv[0]);
94		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
95		return 2;
96	    }
97	    return GrepForDefine(argv[2], argv[3]);
98	}
99    }
100    chars = wsprintf(msg, "usage: %s -c|-l|-f ...\n"
101	    "This is a little helper app to equalize shell differences between WinNT and\n"
102	    "Win9x and get nmake.exe to accomplish its job.\n",
103	    argv[0]);
104    WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
105    return 2;
106}
107
108int
109CheckForCompilerFeature (const char *option)
110{
111    STARTUPINFO si;
112    PROCESS_INFORMATION pi;
113    SECURITY_ATTRIBUTES sa;
114    DWORD threadID;
115    char msg[300];
116    BOOL ok;
117    HANDLE hProcess, h, pipeThreads[2];
118    char cmdline[100];
119
120    hProcess = GetCurrentProcess();
121
122    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
123    ZeroMemory(&si, sizeof(STARTUPINFO));
124    si.cb = sizeof(STARTUPINFO);
125    si.dwFlags   = STARTF_USESTDHANDLES;
126    si.hStdInput = INVALID_HANDLE_VALUE;
127
128    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
129    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
130    sa.lpSecurityDescriptor = NULL;
131    sa.bInheritHandle = FALSE;
132
133    /* create a non-inheritible pipe. */
134    CreatePipe(&Out.pipe, &h, &sa, 0);
135
136    /* dupe the write side, make it inheritible, and close the original. */
137    DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput,
138	    0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
139
140    /* Same as above, but for the error side. */
141    CreatePipe(&Err.pipe, &h, &sa, 0);
142    DuplicateHandle(hProcess, h, hProcess, &si.hStdError,
143	    0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
144
145    /* base command line */
146    strcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X ");
147    /* append our option for testing */
148    strcat(cmdline, option);
149    /* filename to compile, which exists, but is nothing and empty. */
150    strcat(cmdline, " .\\nul");
151
152    ok = CreateProcess(
153	    NULL,	    /* Module name. */
154	    cmdline,	    /* Command line. */
155	    NULL,	    /* Process handle not inheritable. */
156	    NULL,	    /* Thread handle not inheritable. */
157	    TRUE,	    /* yes, inherit handles. */
158	    DETACHED_PROCESS, /* No console for you. */
159	    NULL,	    /* Use parent's environment block. */
160	    NULL,	    /* Use parent's starting directory. */
161	    &si,	    /* Pointer to STARTUPINFO structure. */
162	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
163
164    if (!ok) {
165	DWORD err = GetLastError();
166	int chars = wsprintf(msg, "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
167
168	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
169		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID) &msg[chars],
170		(300-chars), 0);
171	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, strlen(msg), &err, NULL);
172	return 2;
173    }
174
175    /* close our references to the write handles that have now been inherited. */
176    CloseHandle(si.hStdOutput);
177    CloseHandle(si.hStdError);
178
179    WaitForInputIdle(pi.hProcess, 5000);
180    CloseHandle(pi.hThread);
181
182    /* start the pipe reader threads. */
183    pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
184    pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
185
186    /* block waiting for the process to end. */
187    WaitForSingleObject(pi.hProcess, INFINITE);
188    CloseHandle(pi.hProcess);
189
190    /* wait for our pipe to get done reading, should it be a little slow. */
191    WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
192    CloseHandle(pipeThreads[0]);
193    CloseHandle(pipeThreads[1]);
194
195    /* look for the commandline warning code in both streams. */
196    return !(strstr(Out.buffer, "D4002") != NULL || strstr(Err.buffer, "D4002") != NULL);
197}
198
199int
200CheckForLinkerFeature (const char *option)
201{
202    STARTUPINFO si;
203    PROCESS_INFORMATION pi;
204    SECURITY_ATTRIBUTES sa;
205    DWORD threadID;
206    char msg[300];
207    BOOL ok;
208    HANDLE hProcess, h, pipeThreads[2];
209    char cmdline[100];
210
211    hProcess = GetCurrentProcess();
212
213    ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
214    ZeroMemory(&si, sizeof(STARTUPINFO));
215    si.cb = sizeof(STARTUPINFO);
216    si.dwFlags   = STARTF_USESTDHANDLES;
217    si.hStdInput = INVALID_HANDLE_VALUE;
218
219    ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
220    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
221    sa.lpSecurityDescriptor = NULL;
222    sa.bInheritHandle = TRUE;
223
224    /* create a non-inheritible pipe. */
225    CreatePipe(&Out.pipe, &h, &sa, 0);
226
227    /* dupe the write side, make it inheritible, and close the original. */
228    DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput,
229	    0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
230
231    /* Same as above, but for the error side. */
232    CreatePipe(&Err.pipe, &h, &sa, 0);
233    DuplicateHandle(hProcess, h, hProcess, &si.hStdError,
234	    0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
235
236    /* base command line */
237    strcpy(cmdline, "link.exe -nologo ");
238    /* append our option for testing */
239    strcat(cmdline, option);
240
241    ok = CreateProcess(
242	    NULL,	    /* Module name. */
243	    cmdline,	    /* Command line. */
244	    NULL,	    /* Process handle not inheritable. */
245	    NULL,	    /* Thread handle not inheritable. */
246	    TRUE,	    /* yes, inherit handles. */
247	    DETACHED_PROCESS, /* No console for you. */
248	    NULL,	    /* Use parent's environment block. */
249	    NULL,	    /* Use parent's starting directory. */
250	    &si,	    /* Pointer to STARTUPINFO structure. */
251	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
252
253    if (!ok) {
254	DWORD err = GetLastError();
255	int chars = wsprintf(msg, "Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
256
257	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
258		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID) &msg[chars],
259		(300-chars), 0);
260	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, strlen(msg), &err, NULL);
261	return 2;
262    }
263
264    /* close our references to the write handles that have now been inherited. */
265    CloseHandle(si.hStdOutput);
266    CloseHandle(si.hStdError);
267
268    WaitForInputIdle(pi.hProcess, 5000);
269    CloseHandle(pi.hThread);
270
271    /* start the pipe reader threads. */
272    pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
273    pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
274
275    /* block waiting for the process to end. */
276    WaitForSingleObject(pi.hProcess, INFINITE);
277    CloseHandle(pi.hProcess);
278
279    /* wait for our pipe to get done reading, should it be a little slow. */
280    WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
281    CloseHandle(pipeThreads[0]);
282    CloseHandle(pipeThreads[1]);
283
284    /* look for the commandline warning code in the stderr stream. */
285    return !(strstr(Out.buffer, "LNK1117") != NULL || strstr(Err.buffer, "LNK1117") != NULL);
286}
287
288DWORD WINAPI
289ReadFromPipe (LPVOID args)
290{
291    pipeinfo *pi = (pipeinfo *) args;
292    char *lastBuf = pi->buffer;
293    DWORD dwRead;
294    BOOL ok;
295
296again:
297    if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) {
298	CloseHandle(pi->pipe);
299	return -1;
300    }
301    ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L);
302    if (!ok || dwRead == 0) {
303	CloseHandle(pi->pipe);
304	return 0;
305    }
306    lastBuf += dwRead;
307    goto again;
308
309    return 0;  /* makes the compiler happy */
310}
311
312int
313IsIn (const char *string, const char *substring)
314{
315    return (strstr(string, substring) != NULL);
316}
317
318/*
319 *  Find a specified #define by name.
320 *
321 *  If the line is '#define TCL_VERSION "8.5"', it returns
322 *  85 as the result.
323 */
324
325int
326GrepForDefine (const char *file, const char *string)
327{
328    FILE *f;
329    char s1[51], s2[51], s3[51];
330    int r = 0;
331    double d1;
332
333    f = fopen(file, "rt");
334    if (f == NULL) {
335	return 0;
336    }
337
338    do {
339	r = fscanf(f, "%50s", s1);
340	if (r == 1 && !strcmp(s1, "#define")) {
341	    /* get next two words */
342	    r = fscanf(f, "%50s %50s", s2, s3);
343	    if (r != 2) continue;
344	    /* is the first word what we're looking for? */
345	    if (!strcmp(s2, string)) {
346		fclose(f);
347		/* add 1 past first double quote char. "8.5" */
348		d1 = atof(s3 + 1);		  /*    8.5  */
349		while (floor(d1) != d1) {
350		    d1 *= 10.0;
351		}
352		return ((int) d1);		  /*    85   */
353	    }
354	}
355    } while (!feof(f));
356
357    fclose(f);
358    return 0;
359}
360