#!/usr/pkg/bin/jimsh

# Separate command line arguments into options and source files
set opts {}
set sources {}

proc usage {{msg {}}} {
	puts stderr "Usage: build-jim-ext ?--notest? ?--cross? ?--install? ?--static? ?cc-options? ?-o modname? sources..."
	if {$msg ne ""} {
		puts stderr \n$msg
	}
	exit 1
}

set linker "gcc"
set testmod 1
set cross 0
set install 0
set static 0
set verbose 0
set keep 0
set includepaths {}
set libpaths {}
set libs {}
for {set i 0} {$i < [llength $argv]} {incr i} {
	set arg [lindex $argv $i]
	switch -glob -- $arg {
		*.c {
			lappend sources $arg
		}
		*.cpp {
			lappend sources $arg
			set linker "c++"
		}
		--notest {
			# Don't test to see if the module can be loaded
			set testmod 0
		}
		--cross {
			# Don't use standard include/lib paths if cross compiling
			set cross 1
		}
		--install {
			# Install to $DESTDIR//usr/pkg/lib/jim
			set install 1
		}
		--static {
			# Build a static extension that can be linked
			set static 1
		}
		--verbose {
			set verbose 1
		}
		--keep {
			# Don't remove intermediate files
			set keep 1
		}
		--help {
			usage "Easily builds dynamic (loadable) modules for jim"
		}
		-o {
			incr i
			set modname [file rootname [lindex $argv $i]]
			if {$modname eq ""} {
				usage "Option -o requires an argument"
			}
		}
		-I* {
			lappend includepaths $arg
			if {$arg eq "-I"} {
				lappend includepaths [lindex $argv $i]
			}
		}
		-L* {
			lappend libpaths $arg
			if {$arg eq "-L"} {
				lappend libpaths [lindex $argv $i]
			}
		}
		-l* {
			lappend libs $arg
		}
		-* {
			lappend opts $arg
		}
		default {
			usage "Unexpected '$arg'"
		}
	}
}

if {$sources eq ""} {
	usage "No sources provided"
}
if {![info exists modname]} {
	set modname [file rootname [file tail [lindex $sources 0]]]
	# Remove jim- prefix if one exists
	regsub "^jim-" $modname "" modname
}

if {$static} {
	set target libjim-$modname.a
} else {
	set target $modname.so
}
puts "Building $target from $sources\n"

if {!$cross} {
	# If not cross compiling, add the standard location after any user include paths
	lappend includepaths -I/usr/pkg/include
}

# Work around Tcl's strange behaviour of exec failing if stderr is produced
#
proc exec-catch {verbose cmdlist} {
	if {$verbose} {
		puts [join $cmdlist]
	}
	flush stdout
	set rc [catch {
		exec {*}$cmdlist
	} msg errinfo]

	# Handle failed case.
	# Note that Tcl returns rc=1 if there is any stderr,
	# even if the exit code is 0
	if {$rc} {
		if {[dict get $errinfo -errorcode] ne "NONE"} {
			if {!$verbose} {
				puts stderr [join $cmdlist]
			}
			puts stderr $msg
			return 1
		}
	}
	if {$msg ne ""} {
		puts stderr $msg
	}
	return 0
}

set CPPFLAGS "-D_GNU_SOURCE"

set ljim ""
set shobj_cflags ""
set shobj_ldflags ""
if {!$static} {
	set shobj_cflags "-fPIC"
	if {"@JIM_STATICLIB@" eq "1"} {
		puts stderr "Warning: libjim is static. Dynamic module may not work on some platforms.\n"
		set shobj_ldflags "-shared"
	} else {
		# If shared, link against the shared libjim to resolve symbols
		set ljim -ljim
		set shobj_ldflags "-shared"
	}
}

set objs {}
foreach source $sources {
	set obj [file rootname [file tail $source]].o
	if {[string match *.c $source]} {
		set compiler "gcc"
	} else {
		set compiler "c++"
	}
	set compile "$compiler -O2 -I/usr/include $CPPFLAGS $shobj_cflags $includepaths $opts -c -o $obj $source"
	puts "Compile: $obj"
	lappend objs $obj

	set rc [exec-catch $verbose $compile]
	if {$rc} {
		file delete {*}$objs
		exit $rc
	}
}

if {$static} {
	set ar "ar cq $target $objs"
	set ranlib "ranlib $target"

	puts "Ar:      $target"
	set rc [exec-catch $verbose $ar]
	if {$rc == 0} {
		set rc [exec-catch $verbose $ranlib]
	}
	file delete {*}$objs
	if {$rc} {
		file delete $target
		exit $rc
	}
} else {
	if {!$cross} {
		# If not cross compiling, add the standard location after any user lib paths
		lappend libpaths -L/usr/pkg/lib
	}

	set link "$linker -O2 -I/usr/include -Wl,-zrelro -L/usr/lib -Wl,-R/usr/lib -Wl,-R/usr/pkg/lib $shobj_ldflags $libpaths $opts -o $target $objs $ljim  $libs"

	puts "Link:    $target"
	set rc [exec-catch $verbose $link]
	if {!$keep} {
		file delete {*}$objs
	}
	if {$rc} {
		file delete $target
		exit $rc
	}

	if {$testmod && !$cross} {
		# Now, is testing even possible?
		# We must be running a compatible jimsh with the load command at least
		set testmod 0
		set rc [catch {
			# This will avoid attempting on Tcl and on jimsh without load
			if {[info version] > 0.73 && [exists -command load]} {
				set testmod 1
			}
		} msg]
	}

	set rc [catch {
		if {$testmod && !$cross} {
			puts "Test:    load $target"
			load ./$target
		}
		if {$install} {
			set dest [env DESTDIR ""]/usr/pkg/lib/jim
			puts "Install: $target => $dest"
			file mkdir $dest
			file copy $target $dest/$target
		}
		puts "\nSuccess!"
	} msg]
	if {$rc} {
		puts stderr $msg
		exit 1
	}
}
