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