1#==============================================================================
2#
3# mkdepend : generate dependency information from C/C++ files
4#
5# Copyright (c) 1998, Nat Pryce
6#
7# Permission is hereby granted, without written agreement and without
8# license or royalty fees, to use, copy, modify, and distribute this
9# software and its documentation for any purpose, provided that the
10# above copyright notice and the following two paragraphs appear in
11# all copies of this software.
12#
13# IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
14# SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
15# THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE AUTHOR HAS BEEN ADVISED
16# OF THE POSSIBILITY OF SUCH DAMAGE.
17#
18# THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20# PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
21# BASIS, AND THE AUTHOR HAS NO OBLIGATION TO PROVIDE  MAINTENANCE, SUPPORT,
22# UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23#==============================================================================
24#
25# Modified heavily by David Gravereaux <davygrvy@pobox.com> about 9/17/2006.
26# Original can be found @
27#	http://web.archive.org/web/20070616205924/http://www.doc.ic.ac.uk/~np2/software/mkdepend.html
28#==============================================================================
29
30array set mode_data {}
31set mode_data(vc32) {cl -nologo -E}
32
33set source_extensions [list .c .cpp .cxx .cc]
34
35set excludes [list]
36if [info exists env(INCLUDE)] {
37    set rawExcludes [split [string trim $env(INCLUDE) ";"] ";"]
38    foreach exclude $rawExcludes {
39	lappend excludes [file normalize $exclude]
40    }
41}
42
43
44# openOutput --
45#
46#	Opens the output file.
47#
48# Arguments:
49#	file	The file to open
50#
51# Results:
52#	None.
53
54proc openOutput {file} {
55    global output
56    set output [open $file w]
57    puts $output "# Automatically generated at [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S"] by [info script]\n"
58}
59
60# closeOutput --
61#
62#	Closes output file.
63#
64# Arguments:
65#	none
66#
67# Results:
68#	None.
69
70proc closeOutput {} {
71    global output
72    if {[string match stdout $output] != 0} {
73        close $output
74    }
75}
76
77# readDepends --
78#
79#	Read off CCP pipe for #line references.
80#
81# Arguments:
82#	chan	The pipe channel we are reading in.
83#
84# Results:
85#	Raw dependency list pairs.
86
87proc readDepends {chan} {
88    set line ""
89    array set depends {}
90
91    while {[gets $chan line] >= 0} {
92        if {[regexp {^#line [0-9]+ \"(.*)\"$} $line dummy fname] != 0} {
93	    set fname [file normalize $fname]
94            if {![info exists target]} {
95		# this is ourself
96		set target $fname
97		puts stderr "processing [file tail $fname]"
98            } else {
99		# don't include ourselves as a dependency of ourself.
100		if {![string compare $fname $target]} {continue}
101		# store in an array so multiple occurrences are not counted.
102                set depends($target|$fname) ""
103            }
104        }
105    }
106
107    set result {}
108    foreach n [array names depends] {
109        set pair [split $n "|"]
110        lappend result [list [lindex $pair 0] [lindex $pair 1]]
111    }
112
113    return $result
114}
115
116# writeDepends --
117#
118#	Write the processed list out to the file.
119#
120# Arguments:
121#	out		The channel to write to.
122#	depends		The list of dependency pairs
123#
124# Results:
125#	None.
126
127proc writeDepends {out depends} {
128    foreach pair $depends {
129        puts $out "[lindex $pair 0] : \\\n\t[join [lindex $pair 1] " \\\n\t"]"
130    }
131}
132
133# stringStartsWith --
134#
135#	Compares second string to the beginning of the first.
136#
137# Arguments:
138#	str		The string to test the beginning of.
139#	prefix		The string to test against
140#
141# Results:
142#	the result of the comparison.
143
144proc stringStartsWith {str prefix} {
145    set front [string range $str 0 [expr {[string length $prefix] - 1}]]
146    return [expr {[string compare [string tolower $prefix] \
147                                  [string tolower $front]] == 0}]
148}
149
150# filterExcludes --
151#
152#	Remove non-project header files.
153#
154# Arguments:
155#	depends		List of dependency pairs.
156#	excludes	List of directories that should be removed
157#
158# Results:
159#	the processed dependency list.
160
161proc filterExcludes {depends excludes} {
162    set filtered {}
163
164    foreach pair $depends {
165        set excluded 0
166        set file [lindex $pair 1]
167
168        foreach dir $excludes {
169            if [stringStartsWith $file $dir] {
170                set excluded 1
171                break;
172            }
173        }
174
175        if {!$excluded} {
176            lappend filtered $pair
177        }
178    }
179
180    return $filtered
181}
182
183# replacePrefix --
184#
185#	Take the normalized search path and put back the
186#	macro name for it.
187#
188# Arguments:
189#	file	filename.
190#
191# Results:
192#	filename properly replaced with macro for it.
193
194proc replacePrefix {file} {
195    global srcPathList srcPathReplaceList
196
197    foreach was $srcPathList is $srcPathReplaceList {
198	regsub $was $file $is file
199    }
200    return $file
201}
202
203# rebaseFiles --
204#
205#	Replaces normalized paths with original macro names.
206#
207# Arguments:
208#	depends		Dependency pair list.
209#
210# Results:
211#	The processed dependency pair list.
212
213proc rebaseFiles {depends} {
214    set rebased {}
215    foreach pair $depends {
216        lappend rebased [list \
217                [replacePrefix [lindex $pair 0]] \
218		[replacePrefix [lindex $pair 1]]]
219
220    }
221    return $rebased
222}
223
224# compressDeps --
225#
226#	Compresses same named tragets into one pair with
227#	multiple deps.
228#
229# Arguments:
230#	depends	Dependency pair list.
231#
232# Results:
233#	The processed list.
234
235proc compressDeps {depends} {
236    array set compressed [list]
237
238    foreach pair $depends {
239	lappend compressed([lindex $pair 0]) [lindex $pair 1]
240    }
241
242    set result [list]
243    foreach n [array names compressed] {
244        lappend result [list $n [lsort $compressed($n)]]
245    }
246
247    return $result
248}
249
250# addSearchPath --
251#
252#	Adds a new set of path and replacement string to the global list.
253#
254# Arguments:
255#	newPathInfo	comma seperated path and replacement string
256#
257# Results:
258#	None.
259
260proc addSearchPath {newPathInfo} {
261    global srcPathList srcPathReplaceList
262
263    set infoList [split $newPathInfo ,]
264    lappend srcPathList [file normalize [lindex $infoList 0]]
265    lappend srcPathReplaceList [lindex $infoList 1]
266}
267
268
269# displayUsage --
270#
271#	Displays usage to stderr
272#
273# Arguments:
274#	none.
275#
276# Results:
277#	None.
278
279proc displayUsage {} {
280    puts stderr "mkdepend.tcl \[options\] genericDir,macroName compatDir,macroName platformDir,macroName"
281}
282
283# readInputListFile --
284#
285#	Open and read the object file list.
286#
287# Arguments:
288#	objectListFile - name of the file to open.
289#
290# Results:
291#	None.
292
293proc readInputListFile {objectListFile} {
294    global srcFileList srcPathList source_extensions
295    set f [open $objectListFile r]
296    set fl [read $f]
297    close $f
298
299    # fix native path seperator so it isn't treated as an escape.
300    regsub -all {\\} $fl {/} fl
301
302    # Treat the string as a list so filenames between double quotes are
303    # treated as list elements.
304    foreach fname $fl {
305	# Compiled .res resource files should be ignored.
306	if {[file extension $fname] ne ".obj"} {continue}
307
308	# Just filename without path or extension because the path is
309	# the build directory, not where the source files are located.
310	set baseName [file rootname [file tail $fname]]
311
312	set found 0
313	foreach path $srcPathList {
314	    foreach ext $source_extensions {
315		set test [file join $path ${baseName}${ext}]
316		if {[file exist $test]} {
317		    lappend srcFileList $test
318		    set found 1
319		    break
320		}
321	    }
322	    if {$found} break
323	}
324    }
325}
326
327# main --
328#
329#	The main procedure of this script.
330#
331# Arguments:
332#	none.
333#
334# Results:
335#	None.
336
337proc main {} {
338    global argc argv mode mode_data srcFileList srcPathList excludes
339    global remove_prefix target_prefix output env
340
341    set srcPathList [list]
342    set srcFileList [list]
343
344    if {$argc == 1} {displayUsage}
345
346    # Parse mkdepend input
347    for {set i 0} {$i < [llength $argv]} {incr i} {
348	switch -glob -- [set arg [lindex $argv $i]] {
349	    -vc32 {
350		set mode vc32
351	    }
352	    -bc32 {
353		set mode bc32
354	    }
355	    -wc32 {
356		set mode wc32
357	    }
358	    -lc32 {
359		set mode lc32
360	    }
361	    -mgw32 {
362		set mode mgw32
363	    }
364	    -passthru:* {
365		set passthru [string range $arg 10 end]
366		regsub -all {"} $passthru {\"} passthru
367		regsub -all {\\} $passthru {/} passthru
368	    }
369	    -out:* {
370		openOutput [string range $arg 5 end]
371	    }
372	    @* {
373		set objfile [string range $arg 1 end]
374		regsub -all {\\} $objfile {/} objfile
375		readInputListFile $objfile
376	    }
377	    -? - -help - --help {
378		displayUsage
379		exit 1
380	    }
381	    default {
382		if {![info exist mode]} {
383		    puts stderr "mode not set"
384		    displayUsage
385		}
386		addSearchPath $arg
387	    }
388	}
389    }
390
391    # Execute the CPP command and parse output
392
393    foreach srcFile $srcFileList {
394	if {[catch {
395	    set command "$mode_data($mode) $passthru \"$srcFile\""
396	    set input [open |$command r]
397	    set depends [readDepends $input]
398	    set status [catch {close $input} result]
399	    if {$status == 1 && [lindex $::errorCode 0] eq "CHILDSTATUS"} {
400		foreach { - pid code } $::errorCode break
401		if {$code == 2} {
402		    # preprocessor died a cruel death.
403		    error $result
404		}
405	    }
406	} err]} {
407	    puts stderr "error ocurred: $err\n"
408	    continue
409	}
410	set depends [filterExcludes $depends $excludes]
411	set depends [rebaseFiles $depends]
412	set depends [compressDeps $depends]
413	writeDepends $output $depends
414    }
415
416    closeOutput
417}
418
419# kick it up.
420main
421