1 /*++
2 
3 Copyright (c) 2004 - 2010, Intel Corporation. All rights reserved.<BR>
4 This program and the accompanying materials
5 are licensed and made available under the terms and conditions of the BSD License
6 which accompanies this distribution.  The full text of the license may be found at
7 http://opensource.org/licenses/bsd-license.php
8 
9 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
10 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
11 
12 Module Name:
13 
14   MakeDeps.c
15 
16 Abstract:
17 
18   Recursively scan source files to find include files and emit them to
19   create dependency lists.
20 
21 --*/
22 
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdlib.h>
26 #include <ctype.h>
27 
28 #include "Tiano.h"
29 #include "EfiUtilityMsgs.h"
30 
31 //
32 // Structure to maintain a linked list of strings
33 //
34 typedef struct _STRING_LIST {
35   struct _STRING_LIST *Next;
36   char                *Str;
37 } STRING_LIST;
38 
39 #define UTILITY_NAME      "MakeDeps"
40 #define UTILITY_VERSION   "v1.0"
41 
42 #define MAX_LINE_LEN      2048
43 #define MAX_PATH          2048
44 #define START_NEST_DEPTH  1
45 #define MAX_NEST_DEPTH    1000  // just in case we get in an endless loop.
46 //
47 // Define the relative paths used by the special #include macros
48 //
49 #define PROTOCOL_DIR_PATH       "Protocol\\"
50 #define GUID_DIR_PATH           "Guid\\"
51 #define ARCH_PROTOCOL_DIR_PATH  "ArchProtocol\\"
52 #define PPI_PROTOCOL_DIR_PATH   "Ppi\\"
53 
54 //
55 // Use this structure to keep track of all the special #include forms
56 //
57 typedef struct {
58   INT8  *IncludeMacroName;
59   INT8  *PathName;
60 } INCLUDE_MACRO_CONVERSION;
61 
62 //
63 // This data is used to convert #include macros like:
64 //    #include EFI_PROTOCOL_DEFINITION(xxx)
65 // into
66 //    #include Protocol/xxx/xxx.h
67 //
68 static const INCLUDE_MACRO_CONVERSION mMacroConversion[] = {
69   "EFI_PROTOCOL_DEFINITION",
70   PROTOCOL_DIR_PATH,
71   "EFI_GUID_DEFINITION",
72   GUID_DIR_PATH,
73   "EFI_ARCH_PROTOCOL_DEFINITION",
74   ARCH_PROTOCOL_DIR_PATH,
75   "EFI_PROTOCOL_PRODUCER",
76   PROTOCOL_DIR_PATH,
77   "EFI_PROTOCOL_CONSUMER",
78   PROTOCOL_DIR_PATH,
79   "EFI_PROTOCOL_DEPENDENCY",
80   PROTOCOL_DIR_PATH,
81   "EFI_ARCH_PROTOCOL_PRODUCER",
82   ARCH_PROTOCOL_DIR_PATH,
83   "EFI_ARCH_PROTOCOL_CONSUMER",
84   ARCH_PROTOCOL_DIR_PATH,
85   "EFI_ARCH_PROTOCOL_DEPENDENCY",
86   ARCH_PROTOCOL_DIR_PATH,
87   "EFI_PPI_DEFINITION",
88   PPI_PROTOCOL_DIR_PATH,
89   "EFI_PPI_PRODUCER",
90   PPI_PROTOCOL_DIR_PATH,
91   "EFI_PPI_CONSUMER",
92   PPI_PROTOCOL_DIR_PATH,
93   "EFI_PPI_DEPENDENCY",
94   PPI_PROTOCOL_DIR_PATH,
95   NULL,
96   NULL
97 };
98 
99 typedef struct _SYMBOL {
100   struct _SYMBOL  *Next;
101   INT8            *Name;
102   INT8            *Value;
103 } SYMBOL;
104 
105 typedef enum {
106   SearchCurrentDir,
107   SearchIncludePaths,
108   SearchAllPaths,
109 } FILE_SEARCH_TYPE;
110 
111 //
112 // Here's all our globals. We need a linked list of include paths, a linked
113 // list of source files, a linked list of subdirectories (appended to each
114 // include path when searching), and flags to keep track of command-line options.
115 //
116 static struct {
117   STRING_LIST *IncludePaths;            // all include paths to search
118   STRING_LIST *ParentPaths;             // all parent paths to search
119   STRING_LIST *SourceFiles;             // all source files to parse
120   STRING_LIST *SubDirs;                 // appended to each include path when searching
121   SYMBOL      *SymbolTable;             // for replacement strings
122   FILE        *OutFptr;                 // output dependencies to this file
123   BOOLEAN     Verbose;                  // for more detailed output
124   BOOLEAN     IgnoreNotFound;           // no warnings if files not found
125   BOOLEAN     QuietMode;                // -q - don't print missing file warnings
126   BOOLEAN     NoSystem;                 // don't process #include <system> files
127   BOOLEAN     NeverFail;                // always return success
128   BOOLEAN     NoDupes;                  // to not list duplicate dependency files (for timing purposes)
129   BOOLEAN     UseSumDeps;               // use summary dependency files if found
130   BOOLEAN     IsAsm;                    // The SourceFiles are assembler files
131   BOOLEAN     IsCl;                     // The SourceFiles are the output of cl with /showIncludes
132   INT8        TargetFileName[MAX_PATH]; // target object filename
133   INT8        SumDepsPath[MAX_PATH];    // path to summary files
134   INT8        TmpFileName[MAX_PATH];    // temp file name for output file
135   INT8        *OutFileName;             // -o option
136 } mGlobals;
137 
138 static
139 STATUS
140 ProcessFile (
141   INT8              *TargetFileName,
142   INT8              *FileName,
143   UINT32            NestDepth,
144   STRING_LIST       *ProcessedFiles,
145   FILE_SEARCH_TYPE  FileSearchType
146   );
147 
148 static
149 STATUS
150 ProcessClOutput (
151   INT8            *TargetFileName,
152   INT8            *FileName,
153   STRING_LIST     *ProcessedFiles
154   );
155 
156 static
157 FILE  *
158 FindFile (
159   INT8              *FileName,
160   UINT32            FileNameLen,
161   FILE_SEARCH_TYPE  FileSearchType
162   );
163 
164 static
165 void
166 PrintDependency (
167   INT8    *Target,
168   INT8    *DependentFile
169   );
170 
171 static
172 void
173 ReplaceSymbols (
174   INT8    *Str,
175   UINT32  StrSize
176   );
177 
178 static
179 STATUS
180 ProcessArgs (
181   int   Argc,
182   char  *Argv[]
183   );
184 
185 static
186 void
187 Usage (
188   VOID
189   );
190 
191 static
192 void
193 FreeLists (
194   VOID
195   );
196 
197 int
main(int Argc,char * Argv[])198 main (
199   int   Argc,
200   char  *Argv[]
201   )
202 /*++
203 
204 Routine Description:
205 
206   Call the routine to parse the command-line options, then process each file
207   to build dependencies.
208 
209 Arguments:
210 
211   Argc - Standard C main() argc.
212   Argv - Standard C main() argv.
213 
214 Returns:
215 
216   0       if successful
217   nonzero otherwise
218 
219 --*/
220 {
221   STRING_LIST *File;
222   STRING_LIST ProcessedFiles;
223   STRING_LIST *TempList;
224   STATUS      Status;
225   INT8        *Cptr;
226   INT8        TargetFileName[MAX_PATH];
227 
228   SetUtilityName (UTILITY_NAME);
229   //
230   // Process the command-line arguments
231   //
232   Status = ProcessArgs (Argc, Argv);
233   if (Status != STATUS_SUCCESS) {
234     return STATUS_ERROR;
235   }
236   //
237   // Go through the list of source files and process each.
238   //
239   memset (&ProcessedFiles, 0, sizeof (STRING_LIST));
240   File = mGlobals.SourceFiles;
241   while (File != NULL) {
242     //
243     // Clear out our list of processed files
244     //
245     TempList = ProcessedFiles.Next;
246     while (ProcessedFiles.Next != NULL) {
247       TempList = ProcessedFiles.Next->Next;
248       free (ProcessedFiles.Next->Str);
249       free (ProcessedFiles.Next);
250       ProcessedFiles.Next = TempList;
251     }
252     //
253     // Replace filename extension with ".obj" if they did not
254     // specifically specify the target file
255     //
256     if (mGlobals.TargetFileName[0] == 0) {
257       strcpy (TargetFileName, File->Str);
258       //
259       // Find the .extension
260       //
261       for (Cptr = TargetFileName + strlen (TargetFileName) - 1;
262            (*Cptr != '\\') && (Cptr > TargetFileName) && (*Cptr != '.');
263            Cptr--
264           )
265         ;
266       if (Cptr == TargetFileName) {
267         Error (NULL, 0, 0, File->Str, "could not locate extension in filename");
268         goto Finish;
269       }
270       //
271       // Tack on the ".obj"
272       //
273       strcpy (Cptr, ".obj");
274     } else {
275       //
276       // Copy the target filename they specified
277       //
278       strcpy (TargetFileName, mGlobals.TargetFileName);
279     }
280 
281     if (mGlobals.IsCl) {
282       Status = ProcessClOutput (TargetFileName, File->Str, &ProcessedFiles);
283     } else {
284       Status = ProcessFile (TargetFileName, File->Str, START_NEST_DEPTH,
285                             &ProcessedFiles, SearchCurrentDir);
286     }
287     if (Status != STATUS_SUCCESS) {
288       goto Finish;
289     }
290 
291     File = File->Next;
292   }
293 
294 Finish:
295   //
296   // Free up memory
297   //
298   FreeLists ();
299   //
300   // Free up our processed files list
301   //
302   TempList = ProcessedFiles.Next;
303   while (ProcessedFiles.Next != NULL) {
304     TempList = ProcessedFiles.Next->Next;
305     free (ProcessedFiles.Next->Str);
306     free (ProcessedFiles.Next);
307     ProcessedFiles.Next = TempList;
308   }
309   //
310   // Close our temp output file
311   //
312   if ((mGlobals.OutFptr != stdout) && (mGlobals.OutFptr != NULL)) {
313     fclose (mGlobals.OutFptr);
314   }
315 
316   if (mGlobals.NeverFail) {
317     return STATUS_SUCCESS;
318   }
319 
320   if (mGlobals.OutFileName != NULL) {
321     if (GetUtilityStatus () == STATUS_ERROR) {
322       //
323       // If any errors, then delete our temp output
324       // Also try to delete target file to improve the incremental build
325       //
326       remove (mGlobals.TmpFileName);
327       remove (TargetFileName);
328     } else {
329       //
330       // Otherwise, rename temp file to output file
331       //
332       remove (mGlobals.OutFileName);
333       rename (mGlobals.TmpFileName, mGlobals.OutFileName);
334     }
335   }
336 
337   return GetUtilityStatus ();
338 }
339 
340 static
341 STATUS
ProcessFile(INT8 * TargetFileName,INT8 * FileName,UINT32 NestDepth,STRING_LIST * ProcessedFiles,FILE_SEARCH_TYPE FileSearchType)342 ProcessFile (
343   INT8              *TargetFileName,
344   INT8              *FileName,
345   UINT32            NestDepth,
346   STRING_LIST       *ProcessedFiles,
347   FILE_SEARCH_TYPE  FileSearchType
348   )
349 /*++
350 
351 Routine Description:
352 
353   Given a source file name, open the file and parse all #include lines.
354 
355 Arguments:
356 
357   TargetFileName - name of the usually .obj target
358   FileName       - name of the file to process
359   NestDepth      - how deep we're nested in includes
360   ProcessedFiles - list of processed files.
361   FileSearchType - search type for FileName
362 
363 Returns:
364 
365   standard status.
366 
367 --*/
368 {
369   FILE        *Fptr;
370   INT8        Line[MAX_LINE_LEN];
371   INT8        *Cptr;
372   INT8        *EndPtr;
373   INT8        *SaveCptr;
374   INT8        EndChar;
375   INT8        FileNameCopy[MAX_PATH];
376   INT8        MacroIncludeFileName[MAX_LINE_LEN];
377   INT8        SumDepsFile[MAX_PATH];
378   STATUS      Status;
379   UINT32      Index;
380   UINT32      LineNum;
381   STRING_LIST *ListPtr;
382   STRING_LIST ParentPath;
383 
384   Status  = STATUS_SUCCESS;
385   Fptr    = NULL;
386   //
387   // Print the file being processed. Indent so you can tell the include nesting
388   // depth.
389   //
390   if (mGlobals.Verbose) {
391     fprintf (stdout, "%*cProcessing file '%s'\n", NestDepth * 2, ' ', FileName);
392   }
393   //
394   // If we're using summary dependency files, and a matching .dep file is
395   // found for this file, then just emit the summary dependency file as
396   // a dependency and return.
397   //
398   if (mGlobals.UseSumDeps) {
399     strcpy (SumDepsFile, mGlobals.SumDepsPath);
400     strcat (SumDepsFile, FileName);
401     for (Cptr = SumDepsFile + strlen (SumDepsFile) - 1;
402          (*Cptr != '\\') && (Cptr > SumDepsFile) && (*Cptr != '.');
403          Cptr--
404         )
405       ;
406     if (*Cptr == '.') {
407       strcpy (Cptr, ".dep");
408     } else {
409       strcat (SumDepsFile, ".dep");
410     }
411     //
412     // See if the summary dep file exists. Could use _stat() function, but
413     // it's less portable.
414     //
415     if ((Fptr = fopen (SumDepsFile, "r")) != NULL) {
416       PrintDependency (TargetFileName, SumDepsFile);
417       fclose (Fptr);
418       return STATUS_SUCCESS;
419     }
420   }
421 
422   //
423   // Make sure we didn't exceed our maximum nesting depth
424   //
425   if (NestDepth > MAX_NEST_DEPTH) {
426     Error (NULL, 0, 0, FileName, "max nesting depth exceeded on file");
427     goto Finish;
428   }
429   //
430   // Make a local copy of the filename. Then we can manipulate it
431   // if we have to.
432   //
433   strcpy (FileNameCopy, FileName);
434 
435   if (FileSearchType == SearchCurrentDir) {
436     //
437     // Try to open the source file locally
438     //
439     if ((Fptr = fopen (FileNameCopy, "r")) == NULL) {
440       Error (NULL, 0, 0, FileNameCopy, "could not open source file");
441       return STATUS_ERROR;
442     }
443   } else {
444     //
445     // Try to find it among the paths.
446     //
447     Fptr = FindFile (FileNameCopy, sizeof (FileNameCopy), FileSearchType);
448     if (Fptr == NULL) {
449       //
450       // If this is not the top-level file, and the command-line argument
451       // said to ignore missing files, then return ok
452       //
453       if (NestDepth != START_NEST_DEPTH) {
454         if (mGlobals.IgnoreNotFound) {
455           if (!mGlobals.QuietMode) {
456             DebugMsg (NULL, 0, 0, FileNameCopy, "could not find file");
457           }
458 
459           return STATUS_SUCCESS;
460         } else {
461           Error (NULL, 0, 0, FileNameCopy, "could not find file");
462           return STATUS_ERROR;
463         }
464       } else {
465         //
466         // Top-level (first) file. Emit an error.
467         //
468         Error (NULL, 0, 0, FileNameCopy, "could not find file");
469         return STATUS_ERROR;
470       }
471     }
472   }
473 
474   //
475   // If we're not doing duplicates, and we've already seen this filename,
476   // then return
477   //
478   if (mGlobals.NoDupes) {
479     for (ListPtr = ProcessedFiles->Next; ListPtr != NULL; ListPtr = ListPtr->Next) {
480       if (_stricmp (FileNameCopy, ListPtr->Str) == 0) {
481         break;
482       }
483     }
484     //
485     // If we found a match, we're done. If we didn't, create a new element
486     // and add it to the list.
487     //
488     if (ListPtr != NULL) {
489       //
490       // Print a message if verbose mode
491       //
492       if (mGlobals.Verbose) {
493         DebugMsg (NULL, 0, 0, FileNameCopy, "duplicate include -- not processed again");
494       }
495       fclose (Fptr);
496       return STATUS_SUCCESS;
497     }
498 
499     ListPtr       = malloc (sizeof (STRING_LIST));
500     ListPtr->Str  = malloc (strlen (FileNameCopy) + 1);
501     strcpy (ListPtr->Str, FileNameCopy);
502     ListPtr->Next         = ProcessedFiles->Next;
503     ProcessedFiles->Next  = ListPtr;
504   }
505 
506   //
507   // Print the dependency, with string substitution
508   //
509   PrintDependency (TargetFileName, FileNameCopy);
510 
511   //
512   // Get the file path and push to ParentPaths
513   //
514   Cptr = FileNameCopy + strlen (FileNameCopy) - 1;
515   for (; (Cptr > FileNameCopy) && (*Cptr != '\\') && (*Cptr != '/'); Cptr--);
516   if ((*Cptr == '\\') || (*Cptr == '/')) {
517     *(Cptr + 1) = 0;
518   } else {
519     strcpy (FileNameCopy, ".\\");
520   }
521   ParentPath.Next = mGlobals.ParentPaths;
522   ParentPath.Str = FileNameCopy;
523   mGlobals.ParentPaths = &ParentPath;
524 
525   //
526   // Now read in lines and find all #include lines. Allow them to indent, and
527   // to put spaces between the # and include.
528   //
529   LineNum = 0;
530   while ((fgets (Line, sizeof (Line), Fptr) != NULL) && (Status == STATUS_SUCCESS)) {
531     LineNum++;
532     Cptr = Line;
533     //
534     // Skip preceeding spaces on the line
535     //
536     while (*Cptr && (isspace (*Cptr))) {
537       Cptr++;
538     }
539     //
540     // Check for # character, there is no # for asm
541     //
542     if ((*Cptr == '#') || (mGlobals.IsAsm)) {
543       if (*Cptr == '#') {
544         Cptr++;
545       }
546 
547       //
548       // Check for "include", case insensitive for asm
549       //
550       while (*Cptr && (isspace (*Cptr))) {
551         Cptr++;
552       }
553       if (((!mGlobals.IsAsm) && (strncmp (Cptr, "include", 7) == 0)) ||
554           (mGlobals.IsAsm && (_strnicmp (Cptr, "include", 7) == 0))) {
555         //
556         // Skip over "include" and move on to filename as "file" or <file> or file for asm
557         //
558         Cptr += 7;
559         while (*Cptr && (isspace (*Cptr))) {
560           Cptr++;
561         }
562 
563         if (*Cptr == '<') {
564           EndChar = '>';
565         } else if (*Cptr == '"') {
566           EndChar = '"';
567         } else if (mGlobals.IsAsm) {
568           //
569           // Handle include file for asm
570           // Set EndChar to null so we fall through on processing below.
571           //
572           EndChar = 0;
573 
574           //
575           // Look for the end of include file name
576           //
577           EndPtr = Cptr;
578           while (*EndPtr && (!isspace (*EndPtr))) {
579             EndPtr++;
580           }
581 
582           //
583           // Null terminate the filename and try to process it.
584           //
585           *EndPtr = 0;
586           Status  = ProcessFile (TargetFileName, Cptr, NestDepth + 1,
587                                  ProcessedFiles, SearchAllPaths);
588         } else {
589           //
590           // Handle special #include MACRO_NAME(file)
591           // Set EndChar to null so we fall through on processing below.
592           //
593           EndChar = 0;
594           //
595           // Look for all the special include macros and convert accordingly.
596           //
597           for (Index = 0; mMacroConversion[Index].IncludeMacroName != NULL; Index++) {
598             //
599             // Save the start of the string in case some macros are substrings
600             // of others.
601             //
602             SaveCptr = Cptr;
603             if (strncmp (
604                   Cptr,
605                   mMacroConversion[Index].IncludeMacroName,
606                   strlen (mMacroConversion[Index].IncludeMacroName)
607                   ) == 0) {
608               //
609               // Skip over the macro name
610               //
611               Cptr += strlen (mMacroConversion[Index].IncludeMacroName);
612               //
613               // Skip over open parenthesis, blank spaces, then find closing
614               // parenthesis or blank space
615               //
616               while (*Cptr && (isspace (*Cptr))) {
617                 Cptr++;
618               }
619 
620               if (*Cptr == '(') {
621                 Cptr++;
622                 while (*Cptr && (isspace (*Cptr))) {
623                   Cptr++;
624                 }
625 
626                 EndPtr = Cptr;
627                 while (*EndPtr && !isspace (*EndPtr) && (*EndPtr != ')')) {
628                   EndPtr++;
629                 }
630 
631                 *EndPtr = 0;
632                 //
633                 // Create the path
634                 //
635                 strcpy (MacroIncludeFileName, mMacroConversion[Index].PathName);
636                 strcat (MacroIncludeFileName, Cptr);
637                 strcat (MacroIncludeFileName, "\\");
638                 strcat (MacroIncludeFileName, Cptr);
639                 strcat (MacroIncludeFileName, ".h");
640                 //
641                 // Process immediately, then break out of the outside FOR loop.
642                 //
643                 Status = ProcessFile (TargetFileName, MacroIncludeFileName, NestDepth + 1,
644                                       ProcessedFiles, SearchAllPaths);
645                 break;
646               }
647             }
648             //
649             // Restore the start
650             //
651             Cptr = SaveCptr;
652           }
653           //
654           // Don't recognize the include line? Ignore it. We assume that the
655           // file compiles anyway.
656           //
657           if (mMacroConversion[Index].IncludeMacroName == NULL) {
658             //
659             // Warning (FileNameCopy, LineNum, 0, "could not parse line", NULL);
660             // Status = STATUS_WARNING;
661             //
662           }
663         }
664         //
665         // Process "normal" includes. If the endchar is 0, then the
666         // file has already been processed. Otherwise look for the
667         // endchar > or ", and process the include file.
668         //
669         if (EndChar != 0) {
670           Cptr++;
671           EndPtr = Cptr;
672           while (*EndPtr && (*EndPtr != EndChar)) {
673             EndPtr++;
674           }
675 
676           if (*EndPtr == EndChar) {
677             //
678             // If we're processing it, do it
679             //
680             if (EndChar != '>') {
681               //
682               // Null terminate the filename and try to process it.
683               //
684               *EndPtr = 0;
685               Status  = ProcessFile (TargetFileName, Cptr, NestDepth + 1,
686                                      ProcessedFiles, SearchAllPaths);
687             } else if (!mGlobals.NoSystem) {
688               //
689               // Null terminate the filename and try to process it.
690               //
691               *EndPtr = 0;
692               Status  = ProcessFile (TargetFileName, Cptr, NestDepth + 1,
693                                      ProcessedFiles, SearchIncludePaths);
694             }
695           } else {
696             Warning (FileNameCopy, LineNum, 0, "malformed include", "missing closing %c", EndChar);
697             Status = STATUS_WARNING;
698             goto Finish;
699           }
700         }
701       }
702     }
703   }
704   //
705   // Pop the file path from ParentPaths
706   //
707   mGlobals.ParentPaths = ParentPath.Next;
708 
709 Finish:
710   //
711   // Close open files and return status
712   //
713   if (Fptr != NULL) {
714     fclose (Fptr);
715   }
716 
717   return Status;
718 }
719 
720 static
721 STATUS
ProcessClOutput(INT8 * TargetFileName,INT8 * FileName,STRING_LIST * ProcessedFiles)722 ProcessClOutput (
723   INT8            *TargetFileName,
724   INT8            *FileName,
725   STRING_LIST     *ProcessedFiles
726   )
727 /*++
728 
729 Routine Description:
730 
731   Given a source file name, open the file and parse all "Note: including file: xxx.h" lines.
732 
733 Arguments:
734 
735   TargetFileName - name of the usually .obj target
736   FileName       - name of the file to process
737   ProcessedFiles - list of processed files.
738 
739 Returns:
740 
741   standard status.
742 
743 --*/
744 {
745   FILE        *Fptr;
746   INT8        Line[MAX_LINE_LEN];
747   INT8        IncludeFileName[MAX_LINE_LEN];
748   STRING_LIST *ListPtr;
749   BOOLEAN     ClError;
750   INT32       Ret;
751   INT8        Char;
752 
753   if ((Fptr = fopen (FileName, "r")) == NULL) {
754     Error (NULL, 0, 0, FileName, "could not open file for reading");
755     return STATUS_ERROR;
756   }
757   if (fgets (Line, sizeof (Line), Fptr) != NULL) {
758     //
759     // First line is the source file name, print it
760     //
761     printf ("%s", Line);
762   } else {
763     //
764     // No output from cl
765     //
766     fclose (Fptr);
767     Error (NULL, 0, 0, NULL, "incorrect cl tool path may be used ");
768     return STATUS_ERROR;
769   }
770 
771   ClError = FALSE;
772   while (fgets (Line, sizeof (Line), Fptr) != NULL) {
773     Ret = sscanf (Line, "Note: including file: %s %c", IncludeFileName, &Char);
774     if (Ret == 2) {
775       //
776       // There is space in include file name. It's VS header file. Ignore it.
777       //
778       continue;
779     } else if ( Ret != 1) {
780       //
781       // Cl error info, print it
782       // the tool will return error code to stop the nmake
783       //
784       ClError = TRUE;
785       printf ("%s", Line);
786       continue;
787     }
788 
789     //
790     // If we're not doing duplicates, and we've already seen this filename,
791     // then continue
792     //
793     if (mGlobals.NoDupes) {
794       for (ListPtr = ProcessedFiles->Next; ListPtr != NULL; ListPtr = ListPtr->Next) {
795         if (_stricmp (IncludeFileName, ListPtr->Str) == 0) {
796           break;
797         }
798       }
799       //
800       // If we found a match, we're done. If we didn't, create a new element
801       // and add it to the list.
802       //
803       if (ListPtr != NULL) {
804         //
805         // Print a message if verbose mode
806         //
807         if (mGlobals.Verbose) {
808           DebugMsg (NULL, 0, 0, IncludeFileName, "duplicate include -- not processed again");
809         }
810 
811         continue;
812       }
813 
814       ListPtr       = malloc (sizeof (STRING_LIST));
815       ListPtr->Str  = malloc (strlen (IncludeFileName) + 1);
816       strcpy (ListPtr->Str, IncludeFileName);
817       ListPtr->Next         = ProcessedFiles->Next;
818       ProcessedFiles->Next  = ListPtr;
819     }
820 
821     PrintDependency (TargetFileName, IncludeFileName);
822   }
823 
824   fclose (Fptr);
825 
826   if (ClError) {
827     Error (NULL, 0, 0, NULL, "cl error");
828     return STATUS_ERROR;
829   } else {
830     return STATUS_SUCCESS;
831   }
832 }
833 
834 static
835 void
PrintDependency(INT8 * TargetFileName,INT8 * DependentFile)836 PrintDependency (
837   INT8    *TargetFileName,
838   INT8    *DependentFile
839   )
840 /*++
841 
842 Routine Description:
843 
844   Given a target (.obj) file name, and a dependent file name, do any string
845   substitutions (per the command line options) on the file names, then
846   print the dependency line of form:
847 
848   TargetFileName : DependentFile
849 
850 Arguments:
851 
852   TargetFileName - build target file name
853   DependentFile  - file on which TargetFileName depends
854 
855 Returns:
856 
857   None
858 
859 --*/
860 {
861   INT8  Str[MAX_PATH];
862 
863   //
864   // Go through the symbols and do replacements
865   //
866   strcpy (Str, TargetFileName);
867   ReplaceSymbols (Str, sizeof (Str));
868   fprintf (mGlobals.OutFptr, "%s : ", Str);
869   strcpy (Str, DependentFile);
870   ReplaceSymbols (Str, sizeof (Str));
871   fprintf (mGlobals.OutFptr, "%s\n", Str);
872   //
873   // Add pseudo target to avoid incremental build failure when the file is deleted
874   //
875   fprintf (mGlobals.OutFptr, "%s : \n", Str);
876 }
877 
878 static
879 void
ReplaceSymbols(INT8 * Str,UINT32 StrSize)880 ReplaceSymbols (
881   INT8    *Str,
882   UINT32  StrSize
883   )
884 {
885   SYMBOL  *Sym;
886   INT8    StrCopy[MAX_LINE_LEN];
887   INT8    *From;
888   INT8    *To;
889   BOOLEAN Replaced;
890 
891   //
892   // Go through the entire string to look for replacement strings at
893   // every position.
894   //
895   From  = Str;
896   To    = StrCopy;
897   while (*From) {
898     //
899     // Copy the character
900     //
901     *To       = *From;
902     Replaced  = FALSE;
903     //
904     // Go through each symbol and try to find a string substitution
905     //
906     Sym = mGlobals.SymbolTable;
907     while (Sym != NULL) {
908       if (_strnicmp (From, Sym->Value, strlen (Sym->Value)) == 0) {
909         //
910         // Replace the string, then advance the pointers past the
911         // replaced strings
912         //
913         strcpy (To, Sym->Name);
914         To += strlen (Sym->Name);
915         From += strlen (Sym->Value);
916         Replaced = TRUE;
917         //
918         // Break from the while()
919         //
920         break;
921       } else {
922         Sym = Sym->Next;
923       }
924     }
925 
926     if (!Replaced) {
927       From++;
928       To++;
929     }
930   }
931   //
932   // Null terminate, and return it
933   //
934   *To = 0;
935   if (strlen (StrCopy) < StrSize) {
936     strcpy (Str, StrCopy);
937   }
938 }
939 //
940 // Given a filename, try to find it along the include paths.
941 //
942 static
943 FILE *
FindFile(INT8 * FileName,UINT32 FileNameLen,FILE_SEARCH_TYPE FileSearchType)944 FindFile (
945   INT8              *FileName,
946   UINT32            FileNameLen,
947   FILE_SEARCH_TYPE  FileSearchType
948   )
949 {
950   FILE        *Fptr;
951   STRING_LIST *List;
952   STRING_LIST *SubDir;
953   INT8        FullFileName[MAX_PATH * 2];
954 
955   //
956   // Traverse the list of paths and try to find the file
957   //
958   if (FileSearchType == SearchAllPaths) {
959     List = mGlobals.ParentPaths;
960     while (List != NULL) {
961       //
962       // Put the path and filename together
963       //
964       if (strlen (List->Str) + strlen (FileName) + 1 > sizeof (FullFileName)) {
965         Error (
966           __FILE__,
967           __LINE__,
968           0,
969           "application error",
970           "cannot concatenate '%s' + '%s'",
971           List->Str,
972           FileName
973           );
974         return NULL;
975       }
976       //
977       // Append the filename to this include path and try to open the file.
978       //
979       strcpy (FullFileName, List->Str);
980       strcat (FullFileName, FileName);
981       if ((Fptr = fopen (FullFileName, "r")) != NULL) {
982         //
983         // Return the file name
984         //
985         if (FileNameLen <= strlen (FullFileName)) {
986           Error (__FILE__, __LINE__, 0, "application error", "internal path name of insufficient length");
987           //
988           // fprintf (stdout, "File length > %d: %s\n", FileNameLen, FullFileName);
989           //
990           return NULL;
991         }
992 
993         strcpy (FileName, FullFileName);
994         return Fptr;
995       }
996 
997       List = List->Next;
998     }
999   }
1000 
1001   List = mGlobals.IncludePaths;
1002   while (List != NULL) {
1003     //
1004     // Put the path and filename together
1005     //
1006     if (strlen (List->Str) + strlen (FileName) + 1 > sizeof (FullFileName)) {
1007       Error (
1008         __FILE__,
1009         __LINE__,
1010         0,
1011         "application error",
1012         "cannot concatenate '%s' + '%s'",
1013         List->Str,
1014         FileName
1015         );
1016       return NULL;
1017     }
1018     //
1019     // Append the filename to this include path and try to open the file.
1020     //
1021     strcpy (FullFileName, List->Str);
1022     strcat (FullFileName, FileName);
1023     if ((Fptr = fopen (FullFileName, "r")) != NULL) {
1024       //
1025       // Return the file name
1026       //
1027       if (FileNameLen <= strlen (FullFileName)) {
1028         Error (__FILE__, __LINE__, 0, "application error", "internal path name of insufficient length");
1029         //
1030         // fprintf (stdout, "File length > %d: %s\n", FileNameLen, FullFileName);
1031         //
1032         return NULL;
1033       }
1034 
1035       strcpy (FileName, FullFileName);
1036       return Fptr;
1037     }
1038     //
1039     // Didn't find it there. Now try this directory with every subdirectory
1040     // the user specified on the command line
1041     //
1042     for (SubDir = mGlobals.SubDirs; SubDir != NULL; SubDir = SubDir->Next) {
1043       strcpy (FullFileName, List->Str);
1044       strcat (FullFileName, SubDir->Str);
1045       strcat (FullFileName, FileName);
1046       if ((Fptr = fopen (FullFileName, "r")) != NULL) {
1047         //
1048         // Return the file name
1049         //
1050         if (FileNameLen <= strlen (FullFileName)) {
1051           Error (__FILE__, __LINE__, 0, "application error", "internal path name of insufficient length");
1052           return NULL;
1053         }
1054 
1055         strcpy (FileName, FullFileName);
1056         return Fptr;
1057       }
1058     }
1059 
1060     List = List->Next;
1061   }
1062   //
1063   // Not found
1064   //
1065   return NULL;
1066 }
1067 //
1068 // Process the command-line arguments
1069 //
1070 static
1071 STATUS
ProcessArgs(int Argc,char * Argv[])1072 ProcessArgs (
1073   int   Argc,
1074   char  *Argv[]
1075   )
1076 {
1077   STRING_LIST *NewList;
1078   STRING_LIST *LastIncludePath;
1079   STRING_LIST *LastSourceFile;
1080   SYMBOL      *Symbol;
1081 
1082   //
1083   // Clear our globals
1084   //
1085   memset ((char *) &mGlobals, 0, sizeof (mGlobals));
1086   mGlobals.NoDupes = TRUE;
1087   //
1088   // Skip program name
1089   //
1090   Argc--;
1091   Argv++;
1092   //
1093   // Initialize locals
1094   //
1095   LastIncludePath = NULL;
1096   LastSourceFile  = NULL;
1097   //
1098   // Process until no more args
1099   //
1100   while (Argc) {
1101     //
1102     // -i path    add include search path
1103     //
1104     if (_stricmp (Argv[0], "-i") == 0) {
1105       //
1106       // check for one more arg
1107       //
1108       if (Argc > 1) {
1109         //
1110         // Allocate memory for a new list element, fill it in, and
1111         // add it to our list of include paths. Always make sure it
1112         // has a "\" on the end of it.
1113         //
1114         NewList = malloc (sizeof (STRING_LIST));
1115         if (NewList == NULL) {
1116           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1117           return STATUS_ERROR;
1118         }
1119 
1120         NewList->Next = NULL;
1121         NewList->Str  = malloc (strlen (Argv[1]) + 2);
1122         if (NewList->Str == NULL) {
1123           free (NewList);
1124           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1125           return STATUS_ERROR;
1126         }
1127 
1128         strcpy (NewList->Str, Argv[1]);
1129         if (NewList->Str[strlen (NewList->Str) - 1] != '\\') {
1130           strcat (NewList->Str, "\\");
1131         }
1132         //
1133         // Add it to the end of the our list of include paths
1134         //
1135         if (mGlobals.IncludePaths == NULL) {
1136           mGlobals.IncludePaths = NewList;
1137         } else {
1138           LastIncludePath->Next = NewList;
1139         }
1140 
1141         LastIncludePath = NewList;
1142         //
1143         // fprintf (stdout, "Added path: %s\n", NewList->Str);
1144         //
1145       } else {
1146         Error (NULL, 0, 0, Argv[0], "option requires an include path");
1147         Usage ();
1148         return STATUS_ERROR;
1149       }
1150 
1151       Argc--;
1152       Argv++;
1153     } else if (_stricmp (Argv[0], "-f") == 0) {
1154       //
1155       // Check for one more arg
1156       //
1157       if (Argc > 1) {
1158         //
1159         // Allocate memory for a new list element, fill it in, and
1160         // add it to our list of source files.
1161         //
1162         NewList = malloc (sizeof (STRING_LIST));
1163         if (NewList == NULL) {
1164           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1165           return STATUS_ERROR;
1166         }
1167 
1168         NewList->Next = NULL;
1169         //
1170         // Allocate space to replace ".c" with ".obj", plus null termination
1171         //
1172         NewList->Str = malloc (strlen (Argv[1]) + 5);
1173         if (NewList->Str == NULL) {
1174           free (NewList);
1175           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1176           return STATUS_ERROR;
1177         }
1178 
1179         strcpy (NewList->Str, Argv[1]);
1180         if (mGlobals.SourceFiles == NULL) {
1181           mGlobals.SourceFiles = NewList;
1182         } else {
1183           LastSourceFile->Next = NewList;
1184         }
1185 
1186         LastSourceFile = NewList;
1187       } else {
1188         Error (NULL, 0, 0, Argv[0], "option requires a file name");
1189         Usage ();
1190         return STATUS_ERROR;
1191       }
1192 
1193       Argc--;
1194       Argv++;
1195     } else if (_stricmp (Argv[0], "-s") == 0) {
1196       //
1197       // -s subdir    add subdirectory subdir to list of subdirecties to scan.
1198       // Check for one more arg first.
1199       //
1200       if (Argc > 1) {
1201         //
1202         // Allocate memory for a new list element, fill it in, and
1203         // add it to our list of subdirectory include paths. Always
1204         // make sure it has a "\" on the end of it.
1205         //
1206         NewList = malloc (sizeof (STRING_LIST));
1207         if (NewList == NULL) {
1208           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1209           return STATUS_ERROR;
1210         }
1211 
1212         NewList->Str = malloc (strlen (Argv[1]) + 2);
1213         if (NewList->Str == NULL) {
1214           free (NewList);
1215           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1216           return STATUS_ERROR;
1217         }
1218 
1219         strcpy (NewList->Str, Argv[1]);
1220         if (NewList->Str[strlen (NewList->Str) - 1] != '\\') {
1221           strcat (NewList->Str, "\\");
1222         }
1223 
1224         NewList->Next     = mGlobals.SubDirs;
1225         mGlobals.SubDirs  = NewList;
1226       } else {
1227         Error (NULL, 0, 0, Argv[0], "option requires a subdirectory name");
1228         Usage ();
1229         return STATUS_ERROR;
1230       }
1231 
1232       Argc--;
1233       Argv++;
1234     } else if (_stricmp (Argv[0], "-sub") == 0) {
1235       //
1236       // -sub symname symvalue  to do string substitution in the output
1237       //
1238       if (Argc > 2) {
1239         //
1240         // Allocate memory for the symbol object
1241         //
1242         Symbol = malloc (sizeof (SYMBOL));
1243         if (Symbol == NULL) {
1244           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1245           return STATUS_ERROR;
1246         }
1247         //
1248         // Allocate memory for the symbol name and value, then save copies
1249         //
1250         Symbol->Name = malloc (strlen (Argv[1]) + 1);
1251         if (Symbol->Name == NULL) {
1252           free (Symbol);
1253           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1254           return STATUS_ERROR;
1255         }
1256 
1257         strcpy (Symbol->Name, Argv[1]);
1258         Symbol->Value = malloc (strlen (Argv[2]) + 1);
1259         if (Symbol->Value == NULL) {
1260           free (Symbol->Name);
1261           free (Symbol);
1262           Error (__FILE__, __LINE__, 0, "memory allocation failure", NULL);
1263           return STATUS_ERROR;
1264         }
1265 
1266         strcpy (Symbol->Value, Argv[2]);
1267         //
1268         // Add it to the list
1269         //
1270         Symbol->Next          = mGlobals.SymbolTable;
1271         mGlobals.SymbolTable  = Symbol;
1272       } else {
1273         Error (NULL, 0, 0, Argv[0], "option requires a symbol name and value");
1274         Usage ();
1275         return STATUS_ERROR;
1276       }
1277       //
1278       // Skip over args
1279       //
1280       Argc -= 2;
1281       Argv += 2;
1282     } else if (_stricmp (Argv[0], "-nosystem") == 0) {
1283       mGlobals.NoSystem = TRUE;
1284     } else if (_stricmp (Argv[0], "-nodupes") == 0) {
1285       mGlobals.NoDupes = TRUE;
1286     } else if (_stricmp (Argv[0], "-nodups") == 0) {
1287       mGlobals.NoDupes = TRUE;
1288     } else if (_stricmp (Argv[0], "-target") == 0) {
1289       //
1290       // -target TargetFileName  - Target object file (only one allowed right
1291       // now) is TargetFileName rather than SourceFile.obj
1292       //
1293       if (Argc > 1) {
1294         strcpy (mGlobals.TargetFileName, Argv[1]);
1295       } else {
1296         Error (NULL, 0, 0, Argv[0], "option requires a target file name");
1297         Usage ();
1298         return STATUS_ERROR;
1299       }
1300 
1301       Argc--;
1302       Argv++;
1303     } else if (_stricmp (Argv[0], "-usesumdeps") == 0) {
1304       //
1305       // -usesumdeps Path - if we find an included file xxx.h, and file
1306       // Path/xxx.dep exists, list Path/xxx.dep as a dependency rather than
1307       // xxx.h and don't parse xxx.h. This allows you to create a dependency
1308       // file for a commonly included file, and have its dependency file updated
1309       // only if its included files are updated. Then anyone else including this
1310       // common include file can simply have a dependency on that file's .dep file
1311       // rather than on all the files included by it. Confusing enough?
1312       //
1313       mGlobals.UseSumDeps = 1;
1314       if (Argc > 1) {
1315         strcpy (mGlobals.SumDepsPath, Argv[1]);
1316         //
1317         // Add slash on end if not there
1318         //
1319         if (mGlobals.SumDepsPath[strlen (mGlobals.SumDepsPath) - 1] != '\\') {
1320           strcat (mGlobals.SumDepsPath, "\\");
1321         }
1322       } else {
1323         Error (NULL, 0, 0, Argv[0], "option requires path to summary dependency files");
1324         Usage ();
1325         return STATUS_ERROR;
1326       }
1327 
1328       Argc--;
1329       Argv++;
1330 
1331     } else if (_stricmp (Argv[0], "-o") == 0) {
1332       //
1333       // -o OutputFileName    - specify an output filename for dependency list
1334       // check for one more arg
1335       //
1336       if (Argc > 1) {
1337         mGlobals.OutFileName = Argv[1];
1338         //
1339         // Use temp file for output
1340         // This can avoid overwriting previous existed dep file when error
1341         // ocurred in this tool
1342         //
1343         sprintf (mGlobals.TmpFileName, "%s2", mGlobals.OutFileName);
1344         //
1345         // Try to open the temp file
1346         //
1347         if ((mGlobals.OutFptr = fopen (mGlobals.TmpFileName, "w")) == NULL) {
1348           Error (NULL, 0, 0, mGlobals.TmpFileName, "could not open file for writing");
1349           return STATUS_ERROR;
1350         }
1351       } else {
1352         Error (NULL, 0, 0, Argv[0], "option requires output file name");
1353         Usage ();
1354         return STATUS_ERROR;
1355       }
1356 
1357       Argc--;
1358       Argv++;
1359     } else if (_stricmp (Argv[0], "-v") == 0) {
1360       mGlobals.Verbose = TRUE;
1361     } else if (_stricmp (Argv[0], "-neverfail") == 0) {
1362       mGlobals.NeverFail = TRUE;
1363     } else if (_stricmp (Argv[0], "-q") == 0) {
1364       mGlobals.QuietMode = TRUE;
1365     } else if (_stricmp (Argv[0], "-ignorenotfound") == 0) {
1366       mGlobals.IgnoreNotFound = TRUE;
1367     } else if (_stricmp (Argv[0], "-asm") == 0) {
1368       if (mGlobals.IsCl) {
1369         Error (NULL, 0, 0, Argv[0], "option conflict with -cl");
1370         return STATUS_ERROR;
1371       }
1372       mGlobals.IsAsm = TRUE;
1373     } else if (_stricmp (Argv[0], "-cl") == 0) {
1374       if (mGlobals.IsAsm) {
1375         Error (NULL, 0, 0, Argv[0], "option conflict with -asm");
1376         return STATUS_ERROR;
1377       }
1378       mGlobals.IsCl = TRUE;
1379     } else if ((_stricmp (Argv[0], "-h") == 0) || (strcmp (Argv[0], "-?") == 0)) {
1380       Usage ();
1381       return STATUS_ERROR;
1382     } else {
1383       Error (NULL, 0, 0, Argv[0], "unrecognized option");
1384       Usage ();
1385       return STATUS_ERROR;
1386     }
1387 
1388     Argc--;
1389     Argv++;
1390   }
1391   //
1392   // Had to specify at least one source file
1393   //
1394   if (mGlobals.SourceFiles == NULL) {
1395     Error (NULL, 0, 0, "must specify one source file name", NULL);
1396     Usage ();
1397     return STATUS_ERROR;
1398   }
1399   //
1400   // Assume output to stdout if not specified
1401   //
1402   if (mGlobals.OutFptr == NULL) {
1403     mGlobals.OutFptr = stdout;
1404   }
1405 
1406   return STATUS_SUCCESS;
1407 }
1408 //
1409 // Free the global string lists we allocated memory for
1410 //
1411 static
1412 void
FreeLists(VOID)1413 FreeLists (
1414   VOID
1415   )
1416 {
1417   STRING_LIST *Temp;
1418   SYMBOL      *NextSym;
1419 
1420   //
1421   // printf ("Free lists.....");
1422   //
1423   // Traverse the include paths, freeing each
1424   // printf ("freeing include paths\n");
1425   //
1426   while (mGlobals.IncludePaths != NULL) {
1427     Temp = mGlobals.IncludePaths->Next;
1428     //
1429     // printf ("Freeing include path string '%s' at 0x%X\n",
1430     //  mGlobals.IncludePaths->Str, (int)(mGlobals.IncludePaths->Str));
1431     //
1432     free (mGlobals.IncludePaths->Str);
1433     //
1434     // printf ("Freeing include path object at 0x%X\n", (int)(mGlobals.IncludePaths));
1435     //
1436     free (mGlobals.IncludePaths);
1437     mGlobals.IncludePaths = Temp;
1438   }
1439   //
1440   // Traverse the source files, freeing each
1441   //
1442   while (mGlobals.SourceFiles != NULL) {
1443     Temp = mGlobals.SourceFiles->Next;
1444     free (mGlobals.SourceFiles->Str);
1445     free (mGlobals.SourceFiles);
1446     mGlobals.SourceFiles = Temp;
1447   }
1448   //
1449   // Traverse the subdirectory list, freeing each
1450   //
1451   while (mGlobals.SubDirs != NULL) {
1452     Temp = mGlobals.SubDirs->Next;
1453     free (mGlobals.SubDirs->Str);
1454     free (mGlobals.SubDirs);
1455     mGlobals.SubDirs = Temp;
1456   }
1457   //
1458   // Free the symbol table
1459   //
1460   while (mGlobals.SymbolTable != NULL) {
1461     NextSym = mGlobals.SymbolTable->Next;
1462     free (mGlobals.SymbolTable->Name);
1463     free (mGlobals.SymbolTable->Value);
1464     mGlobals.SymbolTable = NextSym;
1465   }
1466   //
1467   // printf ("done\n");
1468   //
1469 }
1470 
1471 static
1472 void
Usage(VOID)1473 Usage (
1474   VOID
1475   )
1476 /*++
1477 
1478 Routine Description:
1479 
1480   Print usage information for this utility.
1481 
1482 Arguments:
1483 
1484   None.
1485 
1486 Returns:
1487 
1488   Nothing.
1489 
1490 --*/
1491 {
1492   int         Index;
1493   const char  *Str[] = {
1494     UTILITY_NAME" "UTILITY_VERSION" - Intel Make Dependencies Utility",
1495     "  Copyright (C), 2004 - 2008 Intel Corporation",
1496 
1497 #if ( defined(UTILITY_BUILD) && defined(UTILITY_VENDOR) )
1498     "  Built from "UTILITY_BUILD", project of "UTILITY_VENDOR,
1499 #endif
1500     "",
1501     "Usage:",
1502     "  "UTILITY_NAME" [OPTION]...",
1503     "Options:",
1504     "  -h or -?         for this help information",
1505     "  -f SourceFile    add SourceFile to list of files to scan",
1506     "  -i IncludePath   add IncludePath to list of search paths",
1507     "  -o OutputFile    write output dependencies to OutputFile",
1508     "  -s SubDir        for each IncludePath, also search IncludePath\\SubDir",
1509     "  -v               for verbose output",
1510     "  -ignorenotfound  don't warn for files not found",
1511     "  -target Target   for single SourceFile, target is Target, not SourceFile.obj",
1512     "  -q               quiet mode to not report files not found if ignored",
1513     "  -sub sym str     replace all occurrances of 'str' with 'sym' in the output",
1514     "  -nosystem        not process system <include> files",
1515     "  -neverfail       always return a success return code",
1516     //
1517     //    "  -nodupes         keep track of include files, don't rescan duplicates",
1518     //
1519     "  -usesumdeps path use summary dependency files in 'path' directory.",
1520     "  -asm             The SourceFiles are assembler files",
1521     "  -cl              The SourceFiles are the output of cl with /showIncludes",
1522     NULL
1523   };
1524   for (Index = 0; Str[Index] != NULL; Index++) {
1525     fprintf (stdout, "%s\n", Str[Index]);
1526   }
1527 }
1528