#! /bin/sh
# Restart with Tcl... \
exec tclsh "$0" ${1+"$@"}

package require Tcl 8.3

# Set up some global variables; some may also be adjusted from the
# command-line, so this procedure also acts as a command-line parser
proc SetupGlobals {} {
    global SOURCEDIRS MAKEFILE argv argv0 markerString includeRE

    set SOURCEDIRS {../compat ../generic}
    set MAKEFILE test.mk
    set markerString "### Automatically generated after this line:\
	    do not edit ###"
    set includeRE {^#\s*include\s+[<"](?:.*?/)?([^/]+?)[">]\s*$}

    for {set i 0} {$i<[llength $argv]} {incr i} {
	switch -- [lindex $argv $i] {
	    -makefile {
		set MAKEFILE [lindex $argv [incr i]]
	    }
	    -directory {
		set filename [lindex $argv [incr i]]
		if {[string equal $filename "-"]} {
		    set SOURCEDIRS {}
		} else {
		    lappend SOURCEDIRS $filename
		}
	    }
	    -version {
		puts "makedep version 0.1a1, using Tcl [package require Tcl]"
		exit 0
	    }
	    -help - default {
		puts stderr "usage: $argv0 ?-option argument ...?\n"
		puts stderr "Options that are understood are:"
		puts stderr "    -makefile filename\tSpecifies the makefile to\
			place the dependency\n                     \
			\tinformation in.\n    -directory dir    \tSpecifies\
			an additional directory to search\
			for\n                     \tsource and include\
			files; the special value \"-\"\n                     \
			\tclears the list of directories.\n   \
			-help             \tPrint this message.\n   \
			-version          \tPrint the script version number."
		exit 1
	    }
	}
    }
}

# get list of source files; *always* process the current directory
proc MakeFileList {} {
    global SOURCEDIRS filelist tails objects

    set filelist [glob -types f {*.[ch]}]
    foreach dir $SOURCEDIRS {
	eval lappend filelist [glob -types f -directory $dir {*.[ch]}]
    }

    array set tails {}
    set objects {}
    foreach filename $filelist {
	if {[info exist tail([file tail $filename])]} {
	    error "\"[info exist tail([file tail $filename])]\" has\
		    potential duplicate in \"$filename\""
	}
	set tails([file tail $filename]) $filename
	if {[string equal [string index $filename end] "c"]} {
	    lappend objects [file rootname [file tail $filename]].o
	} else {
	    lappend objects ""
	}
    }
}

# work out what file includes what
proc ReadSourceFiles {} {
    global includes filelist includeRE tails

    array set includes {}
    foreach filename $filelist {
	set includes($filename) {}
	set f [open $filename r]
	while {[gets $f line] >= 0} {
	    # skip non-include lines
	    if {![regexp $includeRE $line -> incfile]} {
		continue
	    }
	    # skip inclusions of files not in the source tree we examined
	    # earlier (assume it refers to a system include file.)
	    if {![info exist tails($incfile)]} {
		continue
	    }
	    # map to the file to actually include
	    lappend includes($filename) $tails($incfile)
	}
	close $f
    }
}

# filename comparison routine; produces a relatively aesthetic
# ordering for dependency lists.
proc cmpFilenames {a b} {
    set cmp [string compare [file extension $a] [file extension $b]]
    if {$cmp == 0} {
	set cmp [string compare [file tail $a] [file tail $b]]
    }
    return $cmp
}

# Now calculate the dependencies, which are effectively the
# transitive-closure of the includes - we simplify by assuming that
# every .c file generates a .o file in the current directory (not
# actually true, but the difference won't be harmful)
proc BuildDependencyGraph {} {
    global dependencies objects filelist includes

    set dependencies {}
    foreach object $objects source $filelist {
	if {![string length $object]} {
	    continue
	}
	array set ihash {}
	set inc [list $source]
	for {set i 0} {$i < [llength $inc]} {incr i} {
	    set file [lindex $inc $i]
	    if {[info exist ihash($file)]} {
		continue
	    }
	    set ihash($file) 1
	    eval lappend inc $includes($file)
	}
	lappend dependencies "${object}:\
		[lsort -command cmpFilenames [array names ihash]]"
	unset ihash
    }
}

# Splice the dependcy-information into the makefile
proc UpdateMakefile {} {
    global MAKEFILE markerString dependencies

    set lines {}
    if {[file exists $MAKEFILE]} {
	set f [open $MAKEFILE]
	while {[gets $f line] >= 0 && ![string equal $line $markerString]} {
	    lappend lines $line
	}
	close $f
    }
    lappend lines $markerString ""
    set f [open $MAKEFILE w]
    foreach line $lines {puts $f $line}
    foreach line $dependencies {puts $f $line}
    close $f
}

SetupGlobals
MakeFileList
ReadSourceFiles
BuildDependencyGraph
UpdateMakefile

exit
