#!/usr/bin/env expect

#
# \brief  Tool for assisting the development of Genode applications
# \author Norman Feske
# \author Johannes Schlatow
# \date   2019-11-07
#

proc _find_tool_dir { } {
	global argv0

	set path [file normalize $argv0]
	if {[file type $path] == "link"} {

		set link_target [file readlink $path]

		# resolve relative symlink used as symlink target
		if {[file pathtype $link_target] == "relative"} {
			set path [file join [file dirname $argv0] $link_target]
			set path [file normalize $path]
		} else {
			set path $link_target
		}
	}

	# strip binary name and 'bin/' path
	return [file dirname [file dirname [file normalize $path]]]
}

set tool_name [file tail $argv0]
set goa_dir   [_find_tool_dir]
set tool_dir  [file join $goa_dir "share" "goa"]
set gaol      [file join $tool_dir gaol]

# assist the user a bit
if {[llength $argv] == 0} {
	puts stderr "\n  usage: $tool_name help\n"
	exit 1
}

# 'verbose' is used by utility functions and needs to be defined early on
set verbose 0

source [file join $tool_dir lib util.tcl]

#exit_if_not_installed git find sed make diff tar wget
exit_if_not_installed find sed make diff tar

source [file join $tool_dir lib command_line.tcl]

if {$config::disable_sandbox} {
	lappend gaol --disable-sandbox
} else {
	exit_if_not_installed bwrap }

source [file join $tool_dir lib actions generic.tcl]

##################
## Main program ##
##################

##
## Update Goa
##

if {$perform(update-goa)} {
	goa update $args(switch_to_goa_branch)
	exit
}


##
## List goa versions
##

if {$perform(versions)} {
	puts [avail_goa_branches]
	exit
}


##
## Create and initialize depot directory
##

if {$perform(depot-dir)} {
	goa depot-dir
}


##
## Add depot user
##

if {$perform(add-depot-user)} {
	goa add-depot-user $args(new_depot_user) $args(depot_url) \
	                   $args(pubkey_file) $args(gpg_user_id)
	exit
}

source [file join $tool_dir lib node.tcl]
source [file join $tool_dir lib hid.tcl]
source [file join $tool_dir lib query.tcl]
source [file join $tool_dir lib actions versions.tcl]
source [file join $tool_dir lib actions depot.tcl]

##
## Show archive info
##
#
if {$perform(info)} {
	goa archive-info $args(archive)
	exit
}


##
## Show archive versions
##

if {$perform(archive-versions)} {
	goa archive-versions $args(archives)
	exit
}


#
# The following commands only work when the current working directory is a goa
# project.
#
if {![looks_like_goa_project_dir $config::project_dir] && ![has_src_but_no_artifacts $config::project_dir]} {
	exit_with_error "$config::project_dir does not look like a goa project" }

##
# Compare with exported/published archive
# 
if {$perform(exported)} {

	assert_definition_of_depot_user

	set exported_archives {}

	set status [expr [goa compare-src exported_archives] && \
	                 [goa compare-raw exported_archives] && \
	                 [goa compare-api exported_archives] && \
	                 [goa compare-pkgs $args(compare_pkg) exported_archives]]
	if {!$status} {
		log "Version update recommended"
		exit 1
	} else {
		log "Exported archives are up-to-date"

		if {$perform(published)} {
			set result 0
			foreach archive $exported_archives {
				if {![file exists [file join $config::public_dir $archive.tar.xz.sig]]} {
					log "$archive has not been published"
					set result 1
				}
			}
			exit $result
		}
	}

	exit
}

##
# Bump project version
#

if {$perform(bump-version)} {
	if {$args(if_needed)} {
		assert_definition_of_depot_user

		set dummy {}
		set status [expr [goa compare-src dummy] && \
		                 [goa compare-raw dummy] && \
		                 [goa compare-api dummy] && \
		                 [goa compare-pkgs $args(compare_pkg) dummy]]

		if {$status} {
			log "Skipping version bump"
			exit
		} else {
			log "Version update required"
		}
	}
	goa bump-version $args(target_version)
	exit
}


source [file join $tool_dir lib actions import.tcl]

##
## Diff
##
if {$perform(diff)} {
	goa diff src
	goa diff raw
	exit
}


##
## Import
##
if {$perform(import)} {
	goa import
}


##
## Build-directory preparation
##

source [file join $tool_dir lib actions build.tcl]

if {$perform(build-dir)} {
	if {[has_src_but_no_artifacts $config::project_dir]} {
		exit_with_error "$config::project_dir has a 'src' directory but lacks an" \
		                "'artifacts' file. You may start with an empty file."
	}
}

if {$perform(export)} {

	goa prepare_depot_with_apis
}



#
# At this point, a 'src/' directory exists if any source code is part of the
# project or was imported. Should no 'src/' directory exist, the project
# contains merely pkg-runtime content. In this case, we can skip the
# build-related steps.
#
if {![file exists src]} {
	set perform(build-dir) 0
	set perform(build)     0
}

if {$perform(install-toolchain)} {
	goa install-toolchain $args(keep_mounted)
}


if {$perform(build-dir)} {

	# unless explicitly configured, enable strict warnings if using the base API
	if {$config::warn_strict == "" && [goa using_api base]} {
		set warn_strict 1
	} elseif {$config::warn_strict == 1} {
		set warn_strict 1
	}

	set api_dirs { }

	goa build-dir
}


if {$perform(build)} {

	goa build
	goa extract_artifacts_from_build_dir
	goa check_abis
	goa extract_api_artifacts
}


if {$perform(extract-abi-symbols)} {

	goa extract-abi-symbols
}


source [file join $tool_dir lib actions run.tcl]

if {$perform(run-dir)} {

	source [file join $tool_dir lib run common.tcl]

	set target_file [file join $tool_dir lib run $config::target.tcl]
	if {$config::target == "common" || ![file exists $target_file]} {
		exit_with_error "Target '$config::target' not available (see 'goa help targets')" }

	source $target_file

	goa run-dir
}


if {$perform(run)} {

	# start recording
	set filename [file join $config::var_dir $config::project_name.log]
	log_file -noappend $filename

	run_genode

	# stop recording
	log_file

	if {$perform(backtrace)} {
		if {![info exists config::binary_name]} {
			# try extracting binary name from log
			set fd [open $filename]
			while {[gets $fd line] != -1} {
				if {[regexp {\-\> (.*)\] backtrace} $line dummy binary_name]} {
					set config::binary_name $binary_name
					break
				} elseif {[regexp {\-\> (.*)\] Call stack of} $line dummy binary_name]} {
					set config::binary_name $binary_name
					break
				}
			}
			close $fd
		}

		if {![info exists config::binary_name]} {
			exit_with_error "unable to identify binary name for backtrace\n" \
			                "\n You can define the binary name explicitly by " \
			                "\n specifying the '--binary-name <name>' command-line " \
			                "\n argument."
		}

		if {![file exists [file join $config::run_dir $config::binary_name]]} {
			exit_with_error "binary '$config::binary_name' does not exist in run directory" \
			                "\n You can define the binary name explicitly by " \
			                "\n specifying the '--binary-name <name>' command-line " \
			                "\n argument."
		}

		set     cmd [goa gaol_with_toolchain 1]
		lappend cmd --ro-bind [file join $tool_dir lib]
		lappend cmd --ro-bind $config::var_dir
		lappend cmd --ro-bind $config::depot_dir
		lappend cmd --chdir $config::run_dir
		exec {*}$cmd [file join $tool_dir backtrace] $config::binary_name >@ stdout < $filename
	}

	exit
}


if {$perform(export)} {

	set exported_archives ""
	goa export-api
	goa export-raw
	goa export-src
	for_each_pkg pkg $args(publish_pkg) {
		goa export-pkg $pkg exported_archives }
	goa export-bin   exported_archives
	if {$config::debug} { goa export-dbg }
	goa export-index exported_archives

	array set export_projects { }
	goa import-dependencies $exported_archives export_projects
	goa export-dependencies export_projects
}


if {$perform(publish)} {

	assert_definition_of_depot_user

	set pubkey_file [file join $config::depot_dir $config::depot_user pubkey]
	if {![file exists $pubkey_file]} {
		exit_with_error "missing public key at $pubkey_file\n" \
		                "\n You may use the 'goa add-depot-user' command." \
		                "\n To learn more about this command:\n" \
		                "\n   goa help add-depot-user\n" }

	# determine to-be-published archives
	lassign [goa published-archives] archives index_archive

	# download archives from other users that are not present in public
	goa download-foreign $archives

	# add index archive to archives
	if {$index_archive != ""} {
		lappend archives $index_archive }

	# publish archives
	goa publish $archives
}
