1 #*****************************************************************************
2 #* FILE PURPOSE:Scan C files for include references
3 #*
4 #******************************************************************************
5 #* FILE NAME: makedep.awk
6 #*
7 #* DESCRIPTION:
8 #*
9 #* NOTE: Written for Thompson Automation TAWK compiler
10 #*
11 #* NOTE: Processes #includes even in false conditional compilation
12 #*
13 #* (C) Copyright 1999, Telogy Networks, Inc.
14 #******************************************************************************
16 local progname = "makedep"
17 local version_string = "V1.01"
18 local version_date = "Aug 17, 2001"
20 #*******************************************************************************
21 # Types & Constants
22 #*******************************************************************************
23 local true = 1
24 local false = 0
26 local c_re_include = /^[ \t]*#[ \t]*include[ \t]+/
27 local s_re_include = /^[ \t]*\.(include|copy)[ \t]+/
28 local re_include = c_re_include
30 #*******************************************************************************
31 # Command Line Options
32 #*******************************************************************************
33 local quiet = false
34 local no_warn = false
35 local bracket_includes = true
36 local quote_includes = true
37 local abs_paths = true
38 local recurse = true
39 local depend_not_found = false
40 local stdout_format = false
41 local indent_string = " "
42 local objext = "obj"
43 local outfile_spec = ""
45 local outfile = ""
46 local infile = ""
47 local infile_basename
49 local err_file
51 local orig_argc
54 local paths[]
55 local depends[] # actually [][]
56 local processed_files[]
58 #*******************************************************************************
59 # Syntax
60 #*******************************************************************************
61 local comment_start = "# "
62 local comment_stop = ""
63 local eol_continue = "\\"
65 #*******************************************************************************
66 # File Processing
67 #*******************************************************************************
68 local file_error = false
70 #*******************************************************************************
71 # Program Debug & Control
72 #*******************************************************************************
73 local trace_file = "trace.out"
74 local trace_classes[]
75 local exit_code = -1
76 local banner_done = false
78 #*******************************************************************************
79 # Top Level Functions
80 #*******************************************************************************
81 BEGIN \
82 {
83 local i
84 local j
85 local option
86 local arg
87 local sub_option
88 local input_found = false
89 local any_gen = false
91 trace_classes[ "includes" ] = 0
92 trace_classes[ "flow" ] = 0
93 trace_classes[ "file" ] = 0
94 trace_classes[ "fileops" ] = 0 # 1 = inhibit file delete rename, etc
97 paths[0] = 1
98 paths[1] = "" # place holder for "current place" directory
100 trace( "flow", "BEGIN" )
102 err_file = stdout
104 for ( i=1; i < ARGC; i++)
105 {
106 if (ARGV[i] ~ /^-/)
107 {
108 option = substr(ARGV[i], 2, 1)
109 arg = substr(ARGV[i], 3)
111 if (option == "I")
112 {
113 if (arg == "")
114 {
115 # don't process this argument as a file
116 ARGV[i] = ""
118 i++
119 arg = ARGV[i]
120 }
121 paths[0]++
122 paths[paths[0]] = arg
123 }
124 else if (option == "a")
125 {
126 re_include = s_re_include
127 }
128 else if (option == "e")
129 {
130 if (arg == "")
131 {
132 # don't process this argument as a file
133 ARGV[i] = ""
135 i++
136 arg = ARGV[i]
137 }
138 objext = arg
139 }
140 else if (option == "f")
141 {
142 depend_not_found = true
143 }
144 else if (option == "p")
145 {
146 stdout_format = true
147 }
148 else if (option == "o")
149 {
150 if (arg == "")
151 {
152 # don't process this argument as a file
153 ARGV[i] = ""
155 i++
156 arg = ARGV[i]
157 }
158 outfile_spec = arg
159 }
160 else if (option == "r")
161 {
162 recurse = false
163 }
164 else if (option == "s")
165 {
166 bracket_includes = false
167 }
168 else if (option == "q")
169 {
170 quiet = true
171 }
172 else if (option == "w")
173 {
174 no_warn = true
175 }
176 else if (option == "d")
177 {
178 process_trace_option(arg)
179 }
180 else if (option == "h" || option == "?")
181 {
182 help()
183 }
184 else
185 {
186 fatal_error("bad option:" ARGV[i])
187 }
189 # don't process this argument as a file
190 ARGV[i] = ""
191 }
192 else
193 {
194 input_found = true
195 ARGV[i] = to_abs_path(ARGV[i])
196 }
197 }
199 if (!quiet)
200 {
201 banner()
202 }
204 if (stdout_format)
205 {
206 if (outfile_spec != "")
207 {
208 fatal_error("can't specify both -p and -o")
209 }
211 err_file = stderr
212 outfile = stdout
213 }
215 if (outfile_spec == "")
216 {
217 outfile_spec = "./$.d"
218 }
220 if (!input_found)
221 {
222 fatal_error("nothing to do, use " progname "-h for help");
223 }
225 # we add more arguments as we go so remember the number of original args
226 orig_argc = ARGC
227 }
229 match( $0, re_include ) != 0 \
230 {
231 local num_depends
232 local filename
233 local abs_filename
234 local this_dir
236 trace("includes", "#include line:" $0)
238 # find directory of current file
239 this_dir = file_path(FILENAME)
240 if (this_dir == "")
241 {
242 this_dir = "./"
243 }
245 # assume that the file name is the second arg
246 filename = $2
248 # is this a bracket include?
249 if (filename ~ /^</)
250 {
251 # yes, should we process it?
252 if (!bracket_includes)
253 {
254 # no
255 next
256 }
257 paths[1] = ""
258 }
259 else
260 {
261 # no, must be a quote include, should we process it?
262 if (!quote_includes)
263 {
264 # no
265 next
266 }
267 paths[1] = this_dir
268 }
270 # strip off the quotes or brackets
271 if (filename ~ /^[<"]/)
272 {
273 filename = substr(filename, 2, length(filename)-2)
274 }
276 abs_filename = find_file(filename, paths);
277 if (abs_filename == "")
278 {
279 warn("include file not found", filename)
280 abs_filename = filename
281 }
282 else
283 {
284 if (recurse && !(abs_filename in processed_files))
285 {
286 trace("flow", "queueing file " abs_filename)
287 ARGV[ARGC] = abs_filename
288 processed_files[abs_filename] = 1
289 ARGC++
290 depends[abs_filename][0] = 0
291 }
292 }
294 num_depends = depends[FILENAME][0]+1
295 depends[FILENAME][num_depends] = abs_filename
296 depends[FILENAME][0] = num_depends
298 next
299 }
301 # all other lines do nothing
302 {
303 next
304 }
306 END \
307 {
308 local arg
310 trace( "flow", "END" )
312 if (exit_code >= 0)
313 {
314 exit(exit_code)
315 }
317 for (arg=1; arg < orig_argc; arg++)
318 {
319 delete processed_files
320 infile = ARGV[arg]
322 if (infile != "" && !stdout_format)
323 {
324 file_done()
326 infile_basename = basename(infile)
327 infile_basename = strip_ext(infile_basename)
328 outfile = outfile_spec
329 gsubs("$", infile_basename, outfile, 0)
331 gen_header()
332 file_error = false
333 }
335 process_depends(infile, 1)
336 }
337 }
339 #*******************************************************************************
340 # Command Line Functions
341 #*******************************************************************************
342 function process_trace_option(arg)
343 {
344 local i
345 local setting
347 if ( arg ~ /^\+/ )
348 {
349 setting = 1
350 }
351 else if ( arg ~ /^-/ )
352 {
353 setting = 0
354 }
355 else
356 {
357 fatal_error("bad debug option:" ARGV[i])
358 }
360 arg = substr(arg,2)
361 if (arg == "")
362 {
363 for (i in trace_classes)
364 {
365 trace_classes[i] = setting
366 }
367 }
368 else if (arg in trace_classes)
369 {
370 trace_classes[arg] = setting
371 }
372 else
373 {
374 banner()
375 print "Valid Trace Classes are:"
376 for (i in trace_classes)
377 {
378 print "\t" i
379 }
380 fatal_error("unknown trace class " arg)
381 }
382 }
384 function banner()
385 {
386 if (banner_done)
387 {
388 return
389 }
391 banner_done = true
392 print toupper(progname) " " version_string ": C include dependency generator/lister" >err_file
393 print "Last updated " version_date >err_file
394 }
396 function help()
397 {
398 banner()
399 print "usage " progname " <options> <file>"
400 print "where options is 0 or more of the following:"
401 print "(default values are listed in paren's)"
402 print " -Idir add an include path"
403 print " -a use asm format includes"
404 print " -eext set obj extension to ext"
405 print " -s don't process standard include files ie. <>"
406 print " -r don't recurse to find dependencies of found include file"
407 print " -o specify output file (./$.d)"
408 print " -p pipe output to stdout w/o extra formating"
409 print " -q quiet mode (no banner)"
410 print " -w suppress warnings"
411 print " -d(+|-)[trace class] this program's diagnostics (see source)"
412 print " -h or -? this help"
413 exit_code = 10
414 exit
415 }
417 #*******************************************************************************
418 # File Functions
419 #*******************************************************************************
420 function file_done()
421 {
422 close( outfile )
423 if ( file_error )
424 {
425 delete_file( outfile )
426 }
427 }
429 function file_time( name )
430 {
431 return ctime(filetime(name))
432 }
434 function file_exists( name )
435 {
436 return ( filemode(name) != "" )
437 }
439 function rename_file( old_name, new_name )
440 {
441 trace( "file", "rename " old_name " to " new_name )
442 if ( !traceif( "fileops" ) )
443 {
444 delete_file( new_name )
445 if ( !file_exists( old_name ) )
446 {
447 print "File does not exist: " old_name
448 }
449 else
450 {
451 rename(old_name,new_name)
452 }
453 }
454 }
456 function delete_file( name )
457 {
458 trace( "file", "delete " name )
459 if ( !traceif( "fileops" ) && file_exists( name ) )
460 {
461 rmfile(name)
462 }
463 }
465 # return filename without extension
466 function strip_ext( filename )
467 {
468 local new_name
470 # match the extension ( last dot and file component after it )
471 if ( match( filename, /\.[^ \t/\\.]*$/ ) != 0 )
472 {
473 # has an extension, return everything up to but not including it
474 new_name = substr( filename, 1, RSTART-1 )
475 }
476 else
477 {
478 # does not have an extension, return it all
479 new_name = filename
480 }
482 trace( "file", "strip_ext of " filename " gives " new_name )
483 return new_name
484 }
486 # return extension
487 function file_ext( filename )
488 {
489 local new_name
491 # match the extension ( last dot and file component after it )
492 if ( match( filename, /\.[^ \t/\\.]*$/ ) != 0 )
493 {
494 # has an extension, return it
495 new_name = substr( filename, RSTART )
496 }
497 else
498 {
499 # does not have an extension, return it all
500 new_name = ""
501 }
503 trace( "file", "filename_ext of " filename " gives " new_name )
504 return new_name
505 }
507 # return filename.ext w/o leading paths
508 function basename( filename )
509 {
510 local new_name
512 new_name = filename
514 # turn all backslashes to forward slashes
515 gsubs("\\", "/", new_name)
517 # match the basename (everything after the last slash)
518 if ( match( new_name, /\/[^/]*$/ ) != 0 )
519 {
520 # match found, return it
521 new_name = substr(new_name, RSTART+1)
522 }
523 else
524 {
525 # no slashes found, strip off any drive spec
526 # for this purpose a drive spec is everything before the first colon
527 if ( match( new_name, /:[^:]*$/ ) != 0 )
528 {
529 # match found, return it
530 new_name = substr(new_name, RSTART+1)
531 }
532 }
534 trace( "file", "basename of " filename " gives " new_name )
535 return new_name
536 }
538 # return drive & directories of filename
539 function file_path( filename )
540 {
541 local new_name
543 new_name = filename
545 # turn all backslashes to forward slashes
546 gsubs("\\", "/", new_name)
548 # match the basename (everything after the last slash)
549 if ( match( new_name, /\/[^/]*$/ ) != 0 )
550 {
551 # match found, return it
552 new_name = substr(new_name, 1, RSTART-1)
553 }
554 else
555 {
556 # no slashes found, strip off any drive spec
557 # for this purpose a drive spec is everything before the first colon
558 if ( match( new_name, /:[^:]*$/ ) != 0 )
559 {
560 # match found, return it
561 new_name = substr(new_name, 1, RSTART-1)
562 }
563 else
564 {
565 # no drive & no directories
566 new_name = ""
567 }
568 }
570 trace( "file", "basename of " filename " gives " new_name )
571 return new_name
572 }
574 function to_abs_path(filename)
575 {
576 # not implemented yet
577 return filename
578 }
580 function find_file(filename, path_array)
581 {
582 local i
583 local test_file
584 local num_paths
586 num_paths = path_array[0]
588 for (i=1; i <= num_paths; i++)
589 {
590 test_file = path_array[i]
592 if (test_file != "")
593 {
594 if (!(test_file ~ /\/$/))
595 {
596 test_file = test_file "/"
597 }
598 test_file = test_file filename
599 test_file = to_abs_path(test_file)
601 if (file_exists(test_file))
602 {
603 return test_file
604 }
605 }
606 }
608 return ""
609 }
611 #*******************************************************************************
612 # Output Processing
613 #*******************************************************************************
614 function process_depends(filename, level)
615 {
616 local i
617 local j
618 local abs_file
619 local num_depends
621 num_depends = depends[filename][0]
623 for (i=1; i <= num_depends; i++)
624 {
625 abs_file = depends[filename][i]
627 for (j=0; j < level; j++)
628 {
629 printf("%s", indent_string) >outfile
630 }
632 if (abs_file in processed_files)
633 {
634 # printf("%s %-40s already included %s %s\n", \
635 # comment_start, abs_file, comment_stop, eol_continue) >outfile
636 }
637 else
638 {
639 processed_files[abs_file] = 1
641 if (abs_file in depends)
642 {
643 printf("%-40s %s\n", abs_file, eol_continue) >outfile
644 # printf("%-40s %s %s %s %s\n", \
645 # abs_file, comment_start, file_time(abs_file), \
646 # comment_stop, eol_continue) >outfile
647 process_depends(abs_file, level+1)
648 }
649 else
650 {
651 if (depend_not_found)
652 {
653 printf("%-40s %s\n", abs_file, eol_continue) >outfile
654 # printf("%-40s %s not found %s %s\n", \
655 # abs_file, comment_start, comment_stop, eol_continue) >outfile
656 }
657 else
658 {
659 # printf("%s %-40s not found %s %s\n", \
660 # comment_start, abs_file, comment_stop, eol_continue) >outfile
661 }
662 }
663 }
664 }
665 }
667 function gen_header()
668 {
669 # print header
671 local infile_ext
672 local outfile_ext
674 infile_ext = file_ext(infile)
675 outfile_ext = file_ext(outfile)
677 print comment_start "Dependency file for " infile comment_stop >outfile
678 print comment_start "Generated on " ctime() comment_stop >outfile
679 print comment_start "From " infile " dated " ctime(filetime(infile)) comment_stop >outfile
680 print comment_start "This file was generated by " progname ", do not edit!!" comment_stop >outfile
681 print "" >outfile
682 print infile_basename "." objext, ":", infile, infile_basename outfile_ext, eol_continue >outfile
683 }
685 #*******************************************************************************
686 # Support Functions
687 #*******************************************************************************
688 function error( desc )
689 {
690 print "Error: " desc " in " FILENAME " line " FNR >err_file
691 file_error = true
692 }
694 function warn( desc, arg )
695 {
696 local err
698 if (no_warn)
699 {
700 return
701 }
703 if (arg != "")
704 {
705 arg = sprintf("for item %s ", arg)
706 }
708 printf("Warning: %s%s in %s line %d\n", arg, desc, FILENAME, FNR) >err_file
709 }
711 function fatal_error( desc )
712 {
713 print "Error: " desc >err_file
715 exit_code = 3
716 exit
717 }
719 function trace( class, msg )
720 {
721 if ( traceif( class ) )
722 {
723 print msg >trace_file
724 }
725 }
727 function traceif( class )
728 {
729 if ( !(class in trace_classes) )
730 {
731 print "trace class", class, "missing" >trace_file
732 return true
733 }
734 return ( trace_classes[class] != 0 )
735 }