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