1 #*****************************************************************************
2 #* FILE PURPOSE:Scan C files for include references
3 #*
4 #******************************************************************************
5 #* FILE NAME: makedep.awk
6 #*
7 #* DESCRIPTION:
8 #*
9 #* NOTE: Ported to gawk in 2010
10 #*
11 #* NOTE: Processes #includes even in false conditional compilation
12 #*
13 #* (C) Copyright 1999-2001, Telogy Networks, Inc.
14 #* (C) Copyright 2010, Texas Instruments Incorperated
15 #******************************************************************************
17 BEGIN \
18 {
19 progname = "makedep"
20 version_string = "V1.80"
21 version_date = "Sept 8, 2010"
23 #*******************************************************************************
24 # Types & Constants
25 #*******************************************************************************
26 true = 1
27 false = 0
29 c_re_include = "^[ \t]*#[ \t]*include[ \t]+"
30 s_re_include = "^[ \t]*\\.(include|copy)[ \t]+"
31 re_include = c_re_include
33 #*******************************************************************************
34 # Command Line Options
35 #*******************************************************************************
36 quiet = false
37 no_warn = false
38 bracket_includes = true
39 quote_includes = true
40 abs_paths = true
41 recurse = true
42 depend_not_found = false
43 stdout_format = false
44 include_full_report = true
45 indent_string = " "
46 objext = "obj"
47 outfile_spec = ""
49 outfile = ""
50 infile = ""
51 infile_basename = ""
52 cur_filename = ""
54 err_file = ""
56 orig_argc = 0
59 # paths[]
60 # depends[] # actually [][]
61 # processed_files[]
63 #*******************************************************************************
64 # Syntax
65 #*******************************************************************************
66 comment_start = "# "
67 comment_stop = ""
68 eol_continue = "\\"
70 #*******************************************************************************
71 # File Processing
72 #*******************************************************************************
73 file_error = false
75 #*******************************************************************************
76 # Program Debug & Control
77 #*******************************************************************************
78 trace_file = "trace.out"
79 # trace_classes[]
80 exit_code = -1
81 banner_done = false
82 any_traces = false
84 }
86 #*******************************************************************************
87 # Top Level Functions
88 #*******************************************************************************
89 BEGIN \
90 {
91 i = 0
92 j = 0
93 option = ""
94 arg = ""
95 sub_option = ""
96 input_found = false
97 any_gen = false
99 trace_classes[ "includes" ] = 0
100 trace_classes[ "flow" ] = 0
101 trace_classes[ "file" ] = 0
102 trace_classes[ "fileops" ] = 0 # 1 = inhibit file delete rename, etc
105 paths[0] = 1
106 paths[1] = "" # place holder for "current place" directory
108 trace( "flow", "BEGIN" )
110 err_file = "/dev/stdout"
112 for ( i=1; i < ARGC; i++)
113 {
114 if (ARGV[i] ~ /^-/)
115 {
116 option = substr(ARGV[i], 2, 1)
117 arg = substr(ARGV[i], 3)
119 if (option == "I")
120 {
121 if (arg == "")
122 {
123 # don't process this argument as a file
124 ARGV[i] = ""
126 i++
127 arg = ARGV[i]
128 }
129 paths[0]++
130 paths[paths[0]] = arg
131 }
132 else if (option == "a")
133 {
134 re_include = s_re_include
135 }
136 else if (option == "e")
137 {
138 if (arg == "")
139 {
140 # don't process this argument as a file
141 ARGV[i] = ""
143 i++
144 arg = ARGV[i]
145 }
146 objext = arg
147 }
148 else if (option == "f")
149 {
150 depend_not_found = true
151 }
152 else if (option == "p")
153 {
154 stdout_format = true
155 }
156 else if (option == "o")
157 {
158 if (arg == "")
159 {
160 # don't process this argument as a file
161 ARGV[i] = ""
163 i++
164 arg = ARGV[i]
165 }
166 outfile_spec = arg
167 }
168 else if (option == "r")
169 {
170 recurse = false
171 }
172 else if (option == "s")
173 {
174 bracket_includes = false
175 }
176 else if (option == "q")
177 {
178 quiet = true
179 }
180 else if (option == "w")
181 {
182 no_warn = true
183 }
184 else if (option == "d")
185 {
186 process_trace_option(arg)
187 }
188 else if (option == "h" || option == "?")
189 {
190 help()
191 }
192 else
193 {
194 fatal_error("bad option:" ARGV[i])
195 }
197 # don't process this argument as a file
198 ARGV[i] = ""
199 }
200 else
201 {
202 input_found = true
203 ARGV[i] = to_abs_path(ARGV[i])
204 }
205 }
207 if (!quiet)
208 {
209 banner()
210 }
212 if (stdout_format)
213 {
214 if (outfile_spec != "")
215 {
216 fatal_error("can't specify both -p and -o")
217 }
219 err_file = "/dev/stderr"
220 outfile = "/dev/stdout"
221 }
223 if (outfile_spec == "")
224 {
225 outfile_spec = "./$.d"
226 }
228 if (!input_found)
229 {
230 fatal_error("nothing to do, use " progname "-h for help");
231 }
233 # we add more arguments as we go so remember the number of original args
234 orig_argc = ARGC
235 }
237 FILENAME != cur_filename \
238 {
239 cur_filename = FILENAME
240 depends[FILENAME] = ""
241 trace("flow", "Processing file: " FILENAME )
242 }
244 $0 ~ re_include \
245 {
246 num_depends = 0
247 filename = ""
248 abs_filename = ""
249 this_dir = ""
251 trace("includes", "#include line:" $0)
253 # find directory of current file
254 this_dir = file_path(FILENAME)
255 if (this_dir == "")
256 {
257 this_dir = "./"
258 }
260 # assume that the file name is the second arg
261 filename = $2
263 # is this a bracket include?
264 if (filename ~ /^</)
265 {
266 # yes, should we process it?
267 if (!bracket_includes)
268 {
269 # no
270 next
271 }
272 paths[1] = ""
273 }
274 else
275 {
276 # no, must be a quote include, should we process it?
277 if (!quote_includes)
278 {
279 # no
280 next
281 }
282 paths[1] = this_dir
283 }
285 # strip off the quotes or brackets
286 if (filename ~ /^[<"]/)
287 {
288 filename = substr(filename, 2, length(filename)-2)
289 }
291 abs_filename = find_file(filename, paths);
292 if (abs_filename == "")
293 {
294 warn("include file not found", filename)
295 abs_filename = filename
296 }
297 else
298 {
299 if (recurse && !(abs_filename in processed_files))
300 {
301 trace("flow", "queueing file " abs_filename)
302 ARGV[ARGC] = abs_filename
303 processed_files[abs_filename] = 1
304 ARGC++
305 depends_count[abs_filename] = 0
306 }
307 }
309 cur_depends = depends[FILENAME]
310 if (cur_depends == "")
311 {
312 depends[FILENAME] = abs_filename
313 }
314 else
315 {
316 depends[FILENAME] = cur_depends "|" abs_filename
317 }
319 next
320 }
322 # all other lines do nothing
323 {
324 next
325 }
327 END \
328 {
329 arg = ""
331 trace( "flow", "END" )
333 if (exit_code >= 0)
334 {
335 exit(exit_code)
336 }
338 for (arg=1; arg < orig_argc; arg++)
339 {
340 infile = ARGV[arg]
342 if (infile == "")
343 continue
345 if (!stdout_format)
346 {
347 file_done()
349 infile_basename = basename(infile)
350 infile_basename = strip_ext(infile_basename)
351 outfile = outfile_spec
352 gsub("\\$", infile_basename, outfile)
354 gen_header()
355 file_error = false
356 }
358 delete processed_files
359 process_depends(infile, 1)
361 if (include_full_report)
362 {
363 gen_header(true)
364 delete processed_files
365 process_depends(infile, 1, true)
366 }
367 }
368 }
370 #*******************************************************************************
371 # Command Line Functions
372 #*******************************************************************************
373 function process_trace_option(arg, i, setting)
374 {
375 # local i
376 # local setting
378 if ( arg ~ /^\+/ )
379 {
380 setting = 1
381 }
382 else if ( arg ~ /^-/ )
383 {
384 setting = 0
385 }
386 else
387 {
388 fatal_error("bad debug option:" ARGV[i])
389 }
391 arg = substr(arg,2)
392 if (arg == "")
393 {
394 for (i in trace_classes)
395 {
396 trace_classes[i] = setting
397 }
398 }
399 else if (arg in trace_classes)
400 {
401 trace_classes[arg] = setting
402 }
403 else
404 {
405 banner()
406 print "Valid Trace Classes are:"
407 for (i in trace_classes)
408 {
409 print "\t" i
410 }
411 fatal_error("unknown trace class " arg)
412 }
414 any_traces = false
415 for (i in trace_classes)
416 {
417 any_traces = any_traces || trace_classes[i]
418 }
419 }
421 function banner()
422 {
423 if (banner_done)
424 {
425 return
426 }
428 banner_done = true
429 print toupper(progname) " " version_string ": C include dependency generator/lister" > err_file
430 print "Last updated " version_date > err_file
431 }
433 function help()
434 {
435 banner()
436 print "usage " progname " <options> <file>"
437 print "where options is 0 or more of the following:"
438 print "(default values are listed in paren's)"
439 print " -Idir add an include path"
440 print " -a use asm format includes"
441 print " -eext set obj extension to ext"
442 print " -s don't process standard include files ie. <>"
443 print " -r don't recurse to find dependencies of found include file"
444 print " -o specify output file (./$.d)"
445 print " -p pipe output to stdout w/o extra formating"
446 print " -q quiet mode (no banner)"
447 print " -w suppress warnings"
448 print " -d(+|-)[trace class] this program's diagnostics (see source)"
449 print " -h or -? this help"
450 exit_code = 10
451 exit
452 }
454 #*******************************************************************************
455 # File Functions
456 #*******************************************************************************
457 function file_done()
458 {
459 close( outfile )
460 if ( file_error )
461 {
462 delete_file( outfile )
463 }
464 }
466 function file_time( name )
467 {
468 return ctime(filetime(name))
469 }
471 function file_exists(file, dummy, ret)
472 {
473 ret=0;
474 if ( (getline dummy < file) >=0 )
475 {
476 # file exists (possibly empty) and can be read
477 ret = 1;
478 close(file);
479 }
480 return ret;
481 }
483 function rename_file( old_name, new_name )
484 {
485 trace( "file", "rename " old_name " to " new_name )
486 if ( !traceif( "fileops" ) )
487 {
488 delete_file( new_name )
489 if ( !file_exists( old_name ) )
490 {
491 print "File does not exist: " old_name
492 }
493 else
494 {
495 rename(old_name,new_name)
496 }
497 }
498 }
500 function delete_file( name )
501 {
502 trace( "file", "delete " name )
503 if ( !traceif( "fileops" ) && file_exists( name ) )
504 {
505 rmfile(name)
506 }
507 }
509 # return filename without extension
510 function strip_ext( filename, new_name )
511 {
512 # local new_name
514 # match the extension ( last dot and file component after it )
515 if ( match( filename, /\.[^ \t/\\.]*$/ ) != 0 )
516 {
517 # has an extension, return everything up to but not including it
518 new_name = substr( filename, 1, RSTART-1 )
519 }
520 else
521 {
522 # does not have an extension, return it all
523 new_name = filename
524 }
526 trace( "file", "strip_ext of " filename " gives " new_name )
527 return new_name
528 }
530 # return extension
531 function file_ext( filename )
532 {
533 local new_name
535 # match the extension ( last dot and file component after it )
536 if ( match( filename, /\.[^ \t/\\.]*$/ ) != 0 )
537 {
538 # has an extension, return it
539 new_name = substr( filename, RSTART )
540 }
541 else
542 {
543 # does not have an extension, return it all
544 new_name = ""
545 }
547 trace( "file", "filename_ext of " filename " gives " new_name )
548 return new_name
549 }
551 # return filename.ext w/o leading paths
552 function basename( filename, new_name )
553 {
554 # local new_name
556 new_name = filename
558 # turn all backslashes to forward slashes
559 gsub("\\", "/", new_name)
561 # match the basename (everything after the last slash)
562 if ( match( new_name, /\/[^/]*$/ ) != 0 )
563 {
564 # match found, return it
565 new_name = substr(new_name, RSTART+1)
566 }
567 else
568 {
569 # no slashes found, strip off any drive spec
570 # for this purpose a drive spec is everything before the first colon
571 if ( match( new_name, /:[^:]*$/ ) != 0 )
572 {
573 # match found, return it
574 new_name = substr(new_name, RSTART+1)
575 }
576 }
578 trace( "file", "basename of " filename " gives " new_name )
579 return new_name
580 }
582 # return drive & directories of filename
583 function file_path( filename, new_name )
584 {
585 # local new_name
587 new_name = filename
589 # turn all backslashes to forward slashes
590 gsub("\\", "/", new_name)
592 # match the basename (everything after the last slash)
593 if ( match( new_name, /\/[^/]*$/ ) != 0 )
594 {
595 # match found, return it
596 new_name = substr(new_name, 1, RSTART-1)
597 }
598 else
599 {
600 # no slashes found, strip off any drive spec
601 # for this purpose a drive spec is everything before the first colon
602 if ( match( new_name, /:[^:]*$/ ) != 0 )
603 {
604 # match found, return it
605 new_name = substr(new_name, 1, RSTART-1)
606 }
607 else
608 {
609 # no drive & no directories
610 new_name = ""
611 }
612 }
614 trace( "file", "file_path of " filename " gives " new_name )
615 return new_name
616 }
618 function to_abs_path(filename)
619 {
620 # not implemented yet
621 return filename
622 }
624 function find_file(filename, path_array, i, test_file, num_paths)
625 {
626 # local i
627 # local test_file
628 # local num_paths
630 num_paths = path_array[0]
632 for (i=1; i <= num_paths; i++)
633 {
634 test_file = path_array[i]
636 if (test_file != "")
637 {
638 if (!(test_file ~ /\/$/))
639 {
640 test_file = test_file "/"
641 }
642 test_file = test_file filename
643 test_file = to_abs_path(test_file)
645 if (file_exists(test_file))
646 {
647 return test_file
648 }
649 }
650 }
652 return ""
653 }
655 #*******************************************************************************
656 # Output Processing
657 #*******************************************************************************
658 function process_depends(filename, level, report, i, j, abs_file, num_depends, these_depends, this_indent)
659 {
660 # local i
661 # local j
662 # local abs_file
663 # local num_depends
664 # local this_indent
666 split(depends[filename], these_depends, "|")
668 # length(array) does not work on some older versions of awk/gawk
669 # also awk says that array enumeration can be in any random order
670 num_depends = 0
671 for (i in these_depends) num_depends++
673 if (report)
674 {
675 this_indent = "# "
676 }
677 else
678 {
679 this_indent = " "
680 }
682 for (j=1; j < level; j++)
683 {
684 this_indent = this_indent indent_string
685 }
687 #trace("flow", "process_depends level=" level " : " filename " => (" num_depends ") " depends[filename] )
689 for (i=1; i <= num_depends; i++)
690 {
691 abs_file = these_depends[i]
693 trace("flow", this_indent "process_depends level=" level " report=" report " : " filename ".depends[" i "] => " these_depends[i] )
695 if (abs_file in processed_files)
696 {
697 if (report)
698 {
699 printf("%s %-68s already included\n", \
700 this_indent, abs_file) >outfile
701 }
702 }
703 else
704 {
705 processed_files[abs_file] = 1
707 if (abs_file in depends)
708 {
709 if (report)
710 {
711 printf("%s %-68s %s\n", \
712 this_indent, abs_file, file_time(abs_file)) >outfile
713 }
714 else
715 {
716 printf("%s %-68s %s\n", this_indent, abs_file, eol_continue) >outfile
717 }
719 process_depends(abs_file, level+1, report)
720 }
721 else
722 {
723 if (report)
724 {
725 printf("%s %-68s not found\n", \
726 this_indent, abs_file) >outfile
727 }
728 else
729 {
730 if (depend_not_found)
731 {
732 printf("%s %-68s %s\n", this_indent, abs_file, eol_continue) >outfile
733 }
734 }
735 }
736 }
737 }
739 # ensure line continuation is terminated
740 if (level == 1)
741 print "" >outfile
742 }
744 function gen_header( report, infile_ext, outfile_ext)
745 {
746 # print header
748 # local infile_ext
749 # local outfile_ext
751 infile_ext = file_ext(infile)
752 outfile_ext = file_ext(outfile)
754 if (report)
755 {
756 print comment_start "Full include report for " infile comment_stop >outfile
757 }
758 else
759 {
760 print comment_start "Dependency file for " infile comment_stop >outfile
761 print comment_start "Generated on " ctime() comment_stop >outfile
762 print comment_start "From " infile " dated " ctime(filetime(infile)) comment_stop >outfile
763 print comment_start "This file was generated by " progname ", do not edit!!" comment_stop >outfile
764 print "" >outfile
765 print infile_basename "." objext, ":", infile, infile_basename outfile_ext, eol_continue >outfile
766 }
767 }
769 #*******************************************************************************
770 # Support Functions
771 #*******************************************************************************
772 function error( desc )
773 {
774 print "Error: " desc " in " FILENAME " line " FNR >err_file
775 file_error = true
776 }
778 function ctime(time)
779 {
780 if (time == 0)
781 {
782 time = systime()
783 }
784 return strftime("%a %b %d %H:%M:%S %Z %Y", systime())
785 }
787 function filetime(filename)
788 {
789 # not implemented
790 return 1
791 }
793 function warn( desc, arg, err )
794 {
795 # local err
797 if (no_warn)
798 {
799 return
800 }
802 if (arg != "")
803 {
804 arg = sprintf("for item %s ", arg)
805 }
807 printf("Warning: %s%s in %s line %d\n", arg, desc, FILENAME, FNR) >err_file
808 }
810 function fatal_error( desc )
811 {
812 print "Error: " desc > "/dev/stderr"
814 exit_code = 3
815 exit
816 }
818 function trace( class, msg )
819 {
820 if ( traceif( class ) )
821 {
822 print msg >trace_file
823 }
824 }
826 function traceif( class )
827 {
828 if ( !(class in trace_classes) )
829 {
830 print "trace class", class, "missing" >trace_file
831 return true
832 }
833 return ( trace_classes[class] != 0 )
834 }