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