#============================================================================== # # mkdepend : generate dependency information from C/C++ files # # Copyright (c) 1998, Nat Pryce # # Permission is hereby granted, without written agreement and without # license or royalty fees, to use, copy, modify, and distribute this # software and its documentation for any purpose, provided that the # above copyright notice and the following two paragraphs appear in # all copies of this software. # # IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, # SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF # THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE AUTHOR HAS BEEN ADVISED # OF THE POSSIBILITY OF SUCH DAMAGE. # # THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" # BASIS, AND THE AUTHOR HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, # UPDATES, ENHANCEMENTS, OR MODIFICATIONS. #============================================================================== # # Modified heavily by David Gravereaux about 9/17/2006. # Original can be found @ # http://web.archive.org/web/20070616205924/http://www.doc.ic.ac.uk/~np2/software/mkdepend.html # #============================================================================== # RCS: @(#) $Id: mkdepend.tcl,v 1.1.4.4 2007/10/19 14:30:02 dgp Exp $ #============================================================================== array set mode_data {} set mode_data(vc32) {cl -nologo -E} set source_extensions [list .c .cpp .cxx .cc] set excludes [list] if [info exists env(INCLUDE)] { set rawExcludes [split [string trim $env(INCLUDE) ";"] ";"] foreach exclude $rawExcludes { lappend excludes [file normalize $exclude] } } # openOutput -- # # Opens the output file. # # Arguments: # file The file to open # # Results: # None. proc openOutput {file} { global output set output [open $file w] puts $output "# Automatically generated at [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%S"] by [info script]\n" } # closeOutput -- # # Closes output file. # # Arguments: # none # # Results: # None. proc closeOutput {} { global output if {[string match stdout $output] != 0} { close $output } } # readDepends -- # # Read off CCP pipe for #line references. # # Arguments: # chan The pipe channel we are reading in. # # Results: # Raw dependency list pairs. proc readDepends {chan} { set line "" array set depends {} while {[gets $chan line] != -1} { if {[regexp {^#line [0-9]+ \"(.*)\"$} $line dummy fname] != 0} { set fname [file normalize $fname] if {![info exists target]} { # this is ourself set target $fname puts stderr "processing [file tail $fname]" } else { # don't include ourselves as a dependency of ourself. if {![string compare $fname $target]} {continue} # store in an array so multiple occurances are not counted. set depends($target|$fname) "" } } } set result {} foreach n [array names depends] { set pair [split $n "|"] lappend result [list [lindex $pair 0] [lindex $pair 1]] } return $result } # writeDepends -- # # Write the processed list out to the file. # # Arguments: # out The channel to write to. # depends The list of dependency pairs # # Results: # None. proc writeDepends {out depends} { foreach pair $depends { puts $out "[lindex $pair 0] : \\\n\t[join [lindex $pair 1] " \\\n\t"]" } } # stringStartsWith -- # # Compares second string to the beginning of the first. # # Arguments: # str The string to test the beginning of. # prefix The string to test against # # Results: # the result of the comparison. proc stringStartsWith {str prefix} { set front [string range $str 0 [expr {[string length $prefix] - 1}]] return [expr {[string compare [string tolower $prefix] \ [string tolower $front]] == 0}] } # filterExcludes -- # # Remove non-project header files. # # Arguments: # depends List of dependency pairs. # excludes List of directories that should be removed # # Results: # the processed dependency list. proc filterExcludes {depends excludes} { set filtered {} foreach pair $depends { set excluded 0 set file [lindex $pair 1] foreach dir $excludes { if [stringStartsWith $file $dir] { set excluded 1 break; } } if {!$excluded} { lappend filtered $pair } } return $filtered } # replacePrefix -- # # Take the normalized search path and put back the # macro name for it. # # Arguments: # file filename. # # Results: # filename properly replaced with macro for it. proc replacePrefix {file} { global srcPathList srcPathReplaceList foreach was $srcPathList is $srcPathReplaceList { regsub $was $file $is file } return $file } # rebaseFiles -- # # Replaces normalized paths with original macro names. # # Arguments: # depends Dependency pair list. # # Results: # The processed dependency pair list. proc rebaseFiles {depends} { set rebased {} foreach pair $depends { lappend rebased [list \ [replacePrefix [lindex $pair 0]] \ [replacePrefix [lindex $pair 1]]] } return $rebased } # compressDeps -- # # Compresses same named tragets into one pair with # multiple deps. # # Arguments: # depends Dependency pair list. # # Results: # The processed list. proc compressDeps {depends} { array set compressed [list] foreach pair $depends { lappend compressed([lindex $pair 0]) [lindex $pair 1] } set result [list] foreach n [array names compressed] { lappend result [list $n [lsort $compressed($n)]] } return $result } # addSearchPath -- # # Adds a new set of path and replacement string to the global list. # # Arguments: # newPathInfo comma seperated path and replacement string # # Results: # None. proc addSearchPath {newPathInfo} { global srcPathList srcPathReplaceList set infoList [split $newPathInfo ,] lappend srcPathList [file normalize [lindex $infoList 0]] lappend srcPathReplaceList [lindex $infoList 1] } # displayUsage -- # # Displays usage to stderr # # Arguments: # none. # # Results: # None. proc displayUsage {} { puts stderr "mkdepend.tcl \[options\] genericDir,macroName compatDir,macroName platformDir,macroName" } # readInputListFile -- # # Open and read the object file list. # # Arguments: # objectListFile - name of the file to open. # # Results: # None. proc readInputListFile {objectListFile} { global srcFileList srcPathList source_extensions set f [open $objectListFile r] set fl [read $f] close $f # fix native path seperator so it isn't treated as an escape. regsub -all {\\} $fl {/} fl # Treat the string as a list so filenames between double quotes are # treated as list elements. foreach fname $fl { # Compiled .res resource files should be ignored. if {[file extension $fname] ne ".obj"} {continue} # Just filename without path or extension because the path is # the build directory, not where the source files are located. set baseName [file rootname [file tail $fname]] set found 0 foreach path $srcPathList { foreach ext $source_extensions { set test [file join $path ${baseName}${ext}] if {[file exist $test]} { lappend srcFileList $test set found 1 break } } if {$found} break } } } # main -- # # The main procedure of this script. # # Arguments: # none. # # Results: # None. proc main {} { global argc argv mode mode_data srcFileList srcPathList excludes global remove_prefix target_prefix output env set srcPathList [list] set srcFileList [list] if {$argc == 1} {displayUsage} # Parse mkdepend input for {set i 0} {$i < [llength $argv]} {incr i} { switch -glob -- [set arg [lindex $argv $i]] { -vc32 { set mode vc32 } -bc32 { set mode bc32 } -wc32 { set mode wc32 } -lc32 { set mode lc32 } -mgw32 { set mode mgw32 } -passthru:* { set passthru [string range $arg 10 end] regsub -all {"} $passthru {\"} passthru regsub -all {\\} $passthru {/} passthru } -out:* { openOutput [string range $arg 5 end] } @* { set objfile [string range $arg 1 end] regsub -all {\\} $objfile {/} objfile readInputListFile $objfile } -? - -help - --help { displayUsage exit 1 } default { if {![info exist mode]} { puts stderr "mode not set" displayUsage } addSearchPath $arg } } } # Execute the CPP command and parse output foreach srcFile $srcFileList { if {[catch { set command "$mode_data($mode) $passthru \"$srcFile\"" set input [open |$command r] set depends [readDepends $input] set status [catch {close $input} result] if {$status == 1 && [lindex $::errorCode 0] eq "CHILDSTATUS"} { foreach { - pid code } $::errorCode break if {$code == 2} { # preprocessor died a cruel death. error $result } } } err]} { puts stderr "error ocurred: $err\n" continue } set depends [filterExcludes $depends $excludes] set depends [rebaseFiles $depends] set depends [compressDeps $depends] writeDepends $output $depends } closeOutput } # kick it up. main