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