#!/usr/bin/env python
#
#	Standard interface for compiling occam-pi libraries and programs
#	Copyright (C) 2007 University of Kent
#	Copyright (C) 2008, 2009 Adam Sampson <ats@offog.org>
#
#	This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#

import sys, os, getopt, re, shlex, platform

#{{{  globals
VERSION = "$Revision: 6853 $"[11:-2]
CONTACT = "kroc-bugs@kent.ac.uk"
autogen_message = "Automatically generated by occbuild %s; do not edit" % VERSION

std_libs = True
verbose = False
in_tree = None
programs = {}
search_path = []
occ21_opts = []
tock_opts = []
linker_opts = []
needs = []
includes_before = []
includes_after = []
static_link = False
prefix = None
destdir = ""
target_alias = "avr"
target_cpu = "avr"
target_os = "none"
target_prefix = "avr-"
patterns = None
build_shared = True
#}}}

#{{{  helper functions
def warn(*s):
	print >>sys.stderr, "occbuild: " + "".join(map(str, s))

def die(*s):
	warn(*s)
	sys.exit(1)

def running_windows():
	return platform.system() == "Windows"

def find_file(name, path, ext=''):
	"""Given a pathsep-delimited path string, find name,.
	Returns path to name, if found, otherwise None.
	Also allows for files with implicit extensions (eg, .exe), but
	always returning name, as was provided.
	>>> find_file('ls', '/usr/bin:/bin', ext='.exe')
	'/bin/ls'
	"""
	if os.path.isfile(name):
		return name
	if	ext and os.path.isfile(name + ext):
		# Already absolute path.
		return name + ext
	for p in path.split(os.pathsep):
		candidate = os.path.join(p, name)
		if (os.path.isfile(candidate)):
			return candidate
		if ext and os.path.isfile(candidate + ext):
			return candidate + ext
	return None


def adjust_for_windows(cmd):
	if running_windows():
		return cmd
	command = cmd[0]
	cmd[0] = find_file(cmd[0], os.environ['PATH'], '.exe')
	if not cmd[0]:
		die('Could not find command: ', command)
	if not cmd[0].endswith('.exe'):
		cmd.insert(0, 'env')
	return cmd

def run_command(cmd):
	"""Run a program with arguments and return the exit code."""
	cmd = adjust_for_windows(cmd)
	try:
		import subprocess
		return subprocess.call(cmd)
	except ImportError:
		return os.spawnvp(os.P_WAIT, cmd[0], cmd)

def safe_sorted(l):
	"""Like sorted(), but returns a mutable list."""
	l = l[:]
	l.sort()
	return l

def tidy_spaces(string):
	"""Remove leading and trailing whitespace, and collapse all interior
	whitespace into single spaces."""
	return re.sub('\s+', ' ', string.strip())

def split_options(opts):
	"""Split an argument we've been given into a list of arguments to pass
	to another program."""
	if running_windows():
		return shlex.split(opts, posix=False)
	else:
		return shlex.split(opts)

def split_ext(file):
	"""Given a filename, return its basename and extension."""
	file = os.path.basename(file)
	i = file.rfind(".")
	if i == -1:
		return (file, "")
	else:
		return (file[:i], file[i + 1:])

def make_relative_path(source, dest):
	"""Given two directories, find the relative path from the first to the
	second."""
	def norm(path):
		return os.path.realpath(os.path.normpath(os.path.abspath(path)))
	source = norm(source)
	dest = norm(dest)
	source_split = source.split(os.path.sep)
	dest_split = dest.split(os.path.sep)

	# Remove any common prefix.
	while (source_split != [] and dest_split != []
	       and source_split[0] == dest_split[0]):
		source_split = source_split[1:]
		dest_split = dest_split[1:]

	# Go up from what's left of source, and come back down what's left of
	# dest.
	output = []
	for c in source_split:
		output.append(os.path.pardir)
	output += dest_split

	if output == []:
		return "."
	else:
		return reduce(os.path.join, output)

def run(cmd):
	"""Run a command, and check that it succeeded."""
	if verbose:
		warn("Running command: ", " ".join(cmd))
	rc = run_command(cmd)
	if rc != 0:
		die("Command failed: ", " ".join(cmd))

def capture(cmd):
	"""Run a command, checking that it succeeded, and return its output."""
	if verbose:
		warn("Running command: ", " ".join(cmd))

	cmd = adjust_for_windows(cmd)

	try:
		import subprocess
		p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
		data = p.communicate()[0]
		status = p.wait()
	except ImportError:
		(pr, pw) = os.pipe()
		pid = os.fork()
		if pid == 0:
			os.close(pr)
			os.dup2(pw, 1)
			os.execvp(cmd[0], cmd)
			os._exit(1)

		os.close(pw)
		data = ""
		while 1:
			s = os.read(pr, 4096)
			if s == "":
				break
			data += s
		os.close(pr)

		(pid, status) = os.waitpid(pid, 0)

	if status != 0:
		die("Command failed: ", " ".join(cmd))

	return data

def find_in_path(file, path):
	"""Search for file in all the directories listed in path."""
	for dir in path:
		fn = dir + "/" + file
		if exists(fn):
			return fn
	return None

def set_ld_path():
	"""Set LD_LIBRARY_PATH (or the system equivalent) based on the search
	directories to run a program from the tree."""

	if target_os.startswith("darwin"):
		var = "DYLD_LIBRARY_PATH"
	else:
		var = "LD_LIBRARY_PATH"

	dirs = ["."] + search_path
	if in_tree is not None:
		# forall and the runtime support libraries are not included
		# explicitly in the search path, so we must add them.
		# Other modules should have been listed as dependencies.
		dirs += [
			in_tree + "/modules/inmoslibs/libsrc/forall",
			in_tree + "/runtime/ccsp",
			in_tree + "/runtime/libkrocif",
			in_tree + "/runtime/libtvm",
			]
	path = ":".join([os.path.join(os.getcwd(), dir) for dir in dirs])
	old_path = os.getenv(var)
	if old_path is not None:
		path += ":" + old_path
	os.putenv(var, path)

def build_default_search_path(tc_search_path):
	"""Build the default search path, based on environment variables and
	the directories the toolchain thinks should be in it."""

	global search_path

	if in_tree is not None:
		# forall and the TVM specials are not included explicitly in
		# the search path, so we must add them. Other modules should
		# have been listed as dependencies.
		search_path += [
			in_tree + "/modules/inmoslibs/libsrc/forall",
			in_tree + "/tvm/posix",
			]
	else:
		search_path += tc_search_path

	old_path = os.getenv("ISEARCH")
	if old_path is not None:
		search_path += old_path.split(':')

def set_isearch():
	"""Set ISEARCH using search path.  This is for the benefit of occ21."""
	if running_windows():
		sep = ";"
	else:
		sep = ":"
	path = sep.join(search_path)
	if verbose:
		warn("Setting ISEARCH to: ", path)
	os.putenv("ISEARCH", path)

def make_static(library, objs):
	"""Link a bunch of native objects into a static library."""

	run(programs["ar"] + ["rc", library] + objs)
	run(programs["ranlib"] + [library])

def make_shared(library, objs, extra_opts = []):
	"""Link a bunch of native objects into a shared library."""

	opts = []
	if target_os.startswith("darwin"):
		opts = ["-dynamiclib", "-read_only_relocs", "suppress"]
	elif target_os.startswith("solaris"):
		opts = ["-G"]
	else:
		opts = ["-shared"]

	run(programs["cc"] + opts + ["-o", library] + objs + linker_opts + extra_opts)

def make_module(name, custom):
	"""Generate a .module file, calling the provided function to fill in
	any toolchain-specific lines."""

	ofn = name + ".module"
	f = open(ofn, "w")
	f.write('-- Include file for "%s" module\n' % name)
	f.write('-- %s\n' % autogen_message)
	f.write("\n")

	guard = ofn.replace("_", ".").upper()
	f.write("#IF NOT (DEFINED (%s))\n" % guard)
	f.write("#DEFINE %s\n" % guard)
	f.write("\n")

	def do_list(format, list):
		for item in list:
			f.write(format % item)
		if list != []:
			f.write('\n')

	do_list('#INCLUDE "%s.module"\n', needs)
	do_list('#INCLUDE "%s"\n', includes_before)
	custom(f)
	do_list('#INCLUDE "%s"\n', includes_after)

	f.write("#ENDIF\n")
	f.close()

def exists(file):
	"""Does a file or directory exist?"""
	return os.access(file, os.F_OK)

def make_stub(file):
	"""Make a stub file to serve as a placeholder."""
	f = open(file, "w")
	f.close()

def is_stub(file):
	"""Is a file an empty stub?"""
	return os.stat(file).st_size == 0

def delete(file):
	"""Delete a file, if it exists."""
	if exists(file):
		os.unlink(file)

def install(file, dir, is_exec = False):
	"""Install a file (if it exists) into a directory. Make the directory
	if it doesn't already exist. If is_exec, then make the file
	executable."""
	if not exists(file):
		return
	if not exists(dir):
		try:
			os.makedirs(dir)
		except OSError:
			die("Unable to create directory: ", dir)
	cmd = programs["install"][:]
	if is_exec:
		cmd += ["-m755"]
	else:
		cmd += ["-m644"]
	cmd += [file, "%s/%s" % (dir, os.path.basename(file))]
	run(cmd)

def get_install_dir(default, default_suffix):
	"""Figure out the location of an installation directory, based on how
	occbuild was originally configured."""

	default_prefix = "/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr"
	if prefix is None or prefix == default_prefix:
		dir = default
	elif default.startswith(default_prefix):
		dir = prefix + default[len(default_prefix):]
	else:
		dir = prefix + default_suffix

	return destdir + dir

def get_uselibs(etc_file):
	"""Extract all the USELIB comments from a .etc file."""
	comments = capture(programs["tce-dump.pl"] + ["-C", etc_file])
	uselibs = re.findall("\.USELIB\s+(.*?)\s*\n", comments)
	return uselibs

def resolve_library(lib):
	"""Find the location of a library in search_path. Returns None if not
	found."""
	if os.path.exists(lib + ".lib"):
		return lib + ".lib"
	for dir in search_path:
		path = os.path.join(dir, lib + ".lib")
		if os.path.exists(path):
			return path
	return None

def resolve_libraries(libs):
	"""Find the location of several libraries in search_path. Dies if any
	are not found."""
	paths = {}
	result = []
	for lib in libs:
		path = resolve_library(lib)
		if path is None:
			die("Unable to resolve library: ", lib)
		if not paths.has_key(path):
			result.append(path)
			paths[path] = True
	return result
#}}}

#{{{  class Toolchain
class Toolchain:
	"""An occam toolchain. This class is abstract, and only here for
	documentation of the methods that all toolchains need to support."""

	def object(self, source):
		"""Compile single source file (e.g. foo.occ), giving foo.tce."""
		pass

	def library(self, occam_objs, native_objs, base):
		"""Link occam object files (.tce) and native object files (.o)
		into a library, giving base.lib."""
		pass

	def program(self, occam_objs, native_objs, source, output):
		"""Compile source, and link it with occam object files (.tce)
		and native object files (.o) into an executable, giving
		output."""
		pass

	def run(self, program, args):
		"""Run executable program. (This may involve setting up
		environment variables or using an interpreter.)"""
		pass

	def install(self, file):
		"""Install file (and its associated files) into an appropriate
		directory based on its extension -- .lib, .inc, .h, or nothing
		for an executable.  Executables should be installed into a
		directory that will be in the user's path."""
		pass

	def install_examples(self, package, file):
		"""Install a program executable that is an example for a
		package; this will typically not be on the user's path."""
		pass

	def clean(self, file):
		"""Delete file along with any other files that were generated
		during its compilation."""
		pass

	def cflags(self):
		"""Print to stdout the CFLAGS necessary when compiling native
		objects for this toolchain. This should at least include
		-DOCCBUILD_nameoftoolchain."""
		pass

	def search_path(self):
		"""Return a list of directories that should be included in
		search_path when compiling outside the KRoC tree."""
		pass

	def update_patterns(self, patterns):
		"""Adjust the patterns dict -- for example, changing library
		prefixes to something other than the default."""
		pass
#}}}
#{{{  class KRoCToolchain
class KRoCToolchain(Toolchain):
	def kroc_cmd(self):
		cmd = programs["kroc"][:]
		cmd += ["--oc-opts", " ".join(occ21_opts + ["-DEF", "OCCBUILD.KROC"])]
		if static_link:
			cmd += ["--cc-opts", "-static"]
		return cmd

	def object(self, source):
		run(self.kroc_cmd() + ["-c", source])

	def library(self, occam_objs, native_objs, base):
		def custom(f):
			if occam_objs != []:
				f.write('#USE "%s.lib"\n' % base)
			if occam_objs != [] or native_objs != []:
				f.write('#PRAGMA COMMENT ".USELIB %s"\n'
				        % base)
				f.write('-- occbuild:has-native-library\n')
			if linker_opts != []:
				f.write('#PRAGMA COMMENT ".LDFLAGS %s"\n'
					% ' '.join(linker_opts))
			f.write('\n')
		make_module(base, custom)

		need_mods = needs[:]
		if std_libs:
			need_mods.append("forall")

		# Look for all the other modules we need.
		full_path = search_path + capture(programs["kroc"] + ["--incpath"]).strip().split(":")
		lib_opts = []
		for need in need_mods:
			fn = find_in_path("%s.module" % need, full_path)
			if fn is None:
				die("Cannot find module ", need, " in search path ", ":".join(full_path))

			f = open(fn)
			data = f.read()
			f.close()

			# If this module uses a native library, we must link
			# against it when building a shared library.
			if data.find('occbuild:has-native-library') != -1:
				lib_opts.append(patterns["link"] % need)

		if std_libs:
			lib_opts.append(patterns["rtlink"] % "krocif")
		lib_opts.append(patterns["rtlink"] % "ccsp")

		objs = [split_ext(o)[0] + ".o" for o in occam_objs] + native_objs
		if objs != []:
			make_static(patterns["a"] % base, objs)

			if build_shared:
				kroc_ldflags = split_options(capture(programs["kroc"] + ["--ldflags"]))
				cclibpath = split_options(capture(programs["kroc"] + ["--cclibpath"]))
				make_shared(patterns["so"] % base, objs, kroc_ldflags + cclibpath + lib_opts)

		if occam_objs == []:
			make_stub(base + ".lib")
		else:
			run(programs["ilibr"] + ["-o", base + ".lib"] + occam_objs)

	def program(self, occam_objs, native_objs, source, output):
		run(self.kroc_cmd() + ["-o", output, source] + [split_ext(o)[0] + ".o" for o in occam_objs] + native_objs)

	def run(self, program, args):
		if not "/" in program:
			program = "./" + program
		set_ld_path()
		run([program] + args)

	def install(self, file):
		bindir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/bin", "/bin")
		libdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/lib", "/lib")
		includedir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/include/kroc", "/include/kroc")
		krocdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/share/kroc", "/share/kroc")
		vtlibdir = krocdir + "/" + target_prefix + "vtlib"
		vtincludedir = krocdir + "/" + target_prefix + "vtinclude"

		(base, ext) = split_ext(file)
		if ext == "lib":
			if not is_stub(file):
				install(file, vtlibdir)
			install(patterns["a"] % base, libdir)
			if build_shared:
				install(patterns["so"] % base, libdir)
			install(base + ".module", vtincludedir)
		elif ext == "inc":
			install(file, vtincludedir)
		elif ext == "h":
			install(file, includedir)
		elif ext == "":
			install(file, bindir, True)

	def install_examples(self, package, file):
		examplesdir = "%s/%s" % (get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/lib/kroc/avr-examples", "/lib/kroc/" + target_prefix + "examples"), package)

		(base, ext) = split_ext(file)
		if ext == "":
			install(file, examplesdir, True)

	def clean(self, file):
		delete(file)
		(base, ext) = split_ext(file)
		if ext == "tce":
			delete(base + ".o")
			delete(base + ".etc")
			delete(base + ".s")
		elif ext == "lib":
			delete(base + ".lbb")
			delete(patterns["a"] % base)
			delete(patterns["so"] % base)
			delete(base + ".module")
		elif ext == "":
			self.clean(base + ".tce")

	def cflags(self):
		cflags = "-DOCCBUILD_KROC"
		cflags += " " + capture(programs["kroc"] + ["--cflags"])
		cflags += " " + capture(programs["kroc"] + ["--ccincpath"])
		print tidy_spaces(cflags)
	
	def search_path(self):
		krocdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/share/kroc", "/share/kroc")
		return [
			krocdir + "/" + target_prefix + "vtlib",
			krocdir + "/" + target_prefix + "vtinclude",
			]
	
	def update_patterns(self, patterns):
		pass
#}}}
#{{{  class TVMToolchain
class TVMToolchain(Toolchain):
	def get_transputer(self):
		"""Return the transputer that this target architecture
		corresponds to."""
		# FIXME: there are more of these in libtvm's configure.ac
		if target_cpu.startswith("avr"):
			return "t2"
		else:
			return "t8"

	def plinker_cmd(self):
		# Might need to do something here if the system can't execute
		# Perl directly.
		cmd = programs["plinker.pl"]
		if self.get_transputer() == "t2":
			# 16-bit word size; generate 16-bit TEncode file.
			cmd += ["-s"]
		return cmd

	def occ21_cmd(self):
		cmd = programs["occ21"][:]
		transputer = self.get_transputer()
		if transputer == "t2":
			cmd += ["-t2", "-V"]
		elif transputer == "t4":
			cmd += ["-t4"]
		else:
			cmd += ["-t8", "-zqa"]
		cmd += [
			"-etc", "-w", "-y", "-znd", "-znec",
			"-udo", "-zncc", "-init", "-xin",
			"-mobiles", "-zrpe", "-zcxdiv", "-zcxrem",
			"-zep"
			]
		cmd += occ21_opts
		cmd += ["-DEF", "OCCBUILD.TVM"]
		return cmd

	def set_firmware(self):
		if in_tree is not None:
			firmware = os.getenv("TVM_FIRMWARE_FILE")
			if firmware is None:
				firmware = os.path.join(os.getcwd(), in_tree + "/tvm/posix/tvm-posix.tbc")
				os.putenv("TVM_FIRMWARE_FILE", firmware)

	def object(self, source):
		run(self.occ21_cmd() + [source])

	def library(self, occam_objs, native_objs, base):
		def custom(f):
			if occam_objs != []:
				f.write('#USE "%s.lib"\n' % base)
				f.write('#PRAGMA COMMENT ".USELIB %s"\n' % base)
				f.write("#PRAGMA COMMENT \"(spragma (uselib %s))\"\n" % base)
			if native_objs != []:
				f.write("#PRAGMA COMMENT \"(spragma (dynlib %s))\"\n"
				        % (patterns["libname"] % base))
			f.write('\n')
		make_module(base, custom)

		if native_objs != []:
			if not build_shared:
				die("Cannot build FFI libraries for this platform")
			make_shared(patterns["so"] % base, native_objs)

		if occam_objs == []:
			make_stub(base + ".lib")
		else:
			run(programs["ilibr"] + ["-o", base + ".lib"] + occam_objs)

	def program(self, occam_objs, native_objs, source, output):
		if native_objs != []:
			die("Native objects in a Transterpreter program are not yet supported; build a library instead")
			# You can *almost* get there by building an FFI library
			# and a stub that invokes the right #PRAGMA here, but
			# the slinker currently won't do the right thing if you
			# use a .tce file that you haven't actually #USEd.

		self.object(source)
		(base, _) = split_ext(source)
		occam_objs = [base + ".tce"] + occam_objs

		libs = []
		if std_libs:
			libs.append("forall")
		for obj in occam_objs:
			libs += get_uselibs(obj)
		libs = resolve_libraries(libs)
		occam_objs.reverse()
		run(self.plinker_cmd() + ["-o", output + ".tbc"] + libs + occam_objs)

		# Generate a wrapper script (because we have to produce
		# something with the target name, and it might as well be
		# useful).
		f = open(output, "w")
		f.write("#!/bin/sh\n")
		f.write("# %s\n" % autogen_message)
		f.write("exec %s $0.tbc \"$@\"\n" % (" ".join(programs["tvm_installed"])))
		f.close()
		os.chmod(output, 0755)

	def run(self, program, args):
		set_ld_path()
		self.set_firmware()
		run(programs["tvm"] + [program + ".tbc"] + args)

	def install(self, file):
		bindir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/bin", "/bin")
		includedir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/include/tvm", "/include/tvm")
		libdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/lib/avr-tvm", "/lib/" + target_prefix + "tvm")
		tvmdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/share/tvm", "/share/tvm")
		vtlibdir = tvmdir + "/" + target_prefix + "vtlib"
		vtincludedir = tvmdir + "/" + target_prefix + "vtinclude"

		(base, ext) = split_ext(file)
		if ext == "lib":
			if not is_stub(file):
				install(file, vtlibdir)
			install(patterns["so"] % base, libdir)
			install(base + ".module", vtincludedir)
		elif ext == "inc":
			install(file, vtincludedir)
		elif ext == "h":
			install(file, includedir)
		elif ext == "":
			install(file, bindir, True)
			install(base + ".tbc", bindir)

	def install_examples(self, package, file):
		examplesdir = "%s/%s" % (get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/lib/avr-tvm/avr-examples", "/lib/tvm/" + target_prefix + "examples"), package)

		(base, ext) = split_ext(file)
		if ext == "":
			install(file, examplesdir, True)
			install(base + ".tbc", examplesdir)
		elif ext == "tbc":
			install(base + ".tbc", examplesdir)

	def clean(self, file):
		delete(file)
		(base, ext) = split_ext(file)
		if ext == "tce":
			pass
		elif ext == "lib":
			delete(base + ".lbb")
			delete(patterns["so"] % base)
			delete(base + ".module")
		elif ext == "":
			self.clean(base + ".tce")
			delete(base + ".tbc")

	def cflags(self):
		print "-DOCCBUILD_TVM"
		# FIXME: what cflags?
	
	def search_path(self):
		tvmdir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/share/tvm", "/share/tvm")
		return [
			tvmdir + "/" + target_prefix + "vtlib",
			tvmdir + "/" + target_prefix + "vtinclude",
			]
	
	def update_patterns(self, patterns):
		for key in patterns.keys():
			patterns[key] = patterns[key].replace('occam_', 'occam_tvm_')
#}}}
#{{{  class TockToolchain
class TockToolchain:
	def tock_cmd(self, args):
		cmd = programs["tock"] + ["-k", "--run-indent", "-v"] + []
		if search_path != []:
			cmd += ["-I" + ":".join(search_path)]
			for dir in search_path:
				cmd += ["--compiler-flags", "-I" + dir]
				cmd += ["--linker-flags", "-L" + dir]
		if static_link:
			cmd += ["--compiler-flags", "-static"]
		cmd += tock_opts
		cmd += args
		return cmd

	def object(self, source):
		need_mods = needs[:]
		impl = []
		if std_libs:
			need_mods.append("forall")
			impl.append("-u")
			impl.append("forall.module")
		run(self.tock_cmd(["-c"]) + impl + [source]) #["-u" + m for m in need_mods] +
		(base, ext) = split_ext(source)
#TODO we should only make the stub if compilation succeeded and put out a .tock.o file
		make_stub(base + ".tce")

	def library(self, occam_objs, native_objs, base):
		need_mods = needs[:]
		if std_libs:
			need_mods.append("forall")

		# Look for all the other modules we need.
		full_path = search_path + ["."] + capture(programs["tock"] + ["--module-path"]).strip().split(":")
		lib_opts = []
		resolved_mods = []
		for need in need_mods:
			fn = find_in_path("%s.module" % need, full_path)
			if fn is None:
				die("Cannot find module ", need, " in search path ", ":".join(full_path))
			resolved_mods += [fn]

		def custom(f):
#			for need in resolved_mods:
#				f.write('#INCLUDE "%s"\n' % need)
			f.write("-- Objs: " + " ".join(occam_objs) + "\n")
			if occam_objs != []:
				f.write("#PRAGMA TOCKSIZES \".module\"\n")
				f.write("#PRAGMA TOCKINCLUDE \".module\"\n")
			if native_objs != [] or occam_objs != []:
				f.write("#PRAGMA TOCKNATIVELINK \"" + (patterns["link"] % base) + "\"\n")
			for obj in occam_objs:
				g = open (split_ext(obj)[0] + ".tock.inc","r")
				contents = g.read()
				f.write(contents)
				g.close()
		make_module(base, custom)

		lib_opts.append(patterns["rtlink"] % "ccsp")

		objs = [split_ext(o)[0] + ".tock.o" for o in occam_objs] + native_objs
		if objs != []:
			make_static(patterns["a"] % base, objs)

			if build_shared:
				make_shared(patterns["so"] % base, objs, lib_opts)

#		if occam_objs == []:
		make_stub(base + ".lib")
		f = open(base + ".lib.tock.inc", "w")
		f.write("#INCLUDE \"" + base + ".module\"\n")
		f.close()
		f = open(base + ".lib.tock.h", "w")
		f.write("#include \"" + base + ".module.tock.h\"\n")
		f.close()

		f = open (base + ".module.tock.h", "w")
		for obj in occam_objs:
			g = open (split_ext(obj)[0] + ".tock.h","r")
			contents = g.read()
			f.write(contents)
			g.close()
		f.close()

		f = open (base + ".module.tock.sizes", "w")
		all_sizes = [split_ext(obj)[0] + ".tock.sizes" for obj in occam_objs]
		for sizes in all_sizes:
			g = open (sizes,"r")
			for line in g:
				match = re.match("DependsOnSizes\\s+\"(.*)\"", line);
				if match is None:
					f.write(line)
				elif not (match.group(1) in all_sizes):
					f.write(line)
			g.close()
		f.close()

#		else:
#			run(programs["ilibr"] + ["-o", base + ".lib"] + occam_objs)

	def program(self, occam_objs, native_objs, source, output):
		need_mods = needs[:]
		impl = []
		if std_libs:
			need_mods.append("forall")
			impl.append("-u")
			impl.append("forall.module")

		run(self.tock_cmd(["--linker-flags", " ".join(native_objs)] + impl + ["-o", output]) + [source])
#" ".join([split_ext(o)[0] + ".tock.o" for o in occam_objs] +

	def run(self, program, args):
		if not "/" in program:
			program = "./" + program
		set_ld_path()
		run([program] + args)

	def install(self, file):
		bindir = get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/bin", "/bin")
		libdir = capture(programs["tock"] + ["--lib-path"]).strip()
		moduledir = capture(programs["tock"] + ["--module-path"]).strip()
		includedir = capture(programs["tock"] + ["--include-path"]).strip()

		(base, ext) = split_ext(file)
		if ext == "lib":
			if not is_stub(file):
				install(file, libdir)
			install(patterns["a"] % base, libdir)
			if build_shared:
				install(patterns["so"] % base, libdir)
			install(base + ".module", moduledir)
		elif ext == "inc":
			install(file, includedir)
		elif ext == "h":
			install(file, includedir)
		elif ext == "":
			install(file, bindir, True)

	def install_examples(self, package, file):
		examplesdir = examplesdir
		examplesdir = "%s/%s" % (get_install_dir("/Users/jadudm/svn/kroc/distribution-trunk/distribution/osx/install-avr/lib/kroc/avr-examples", "/lib/tock/examples"), package)

		(base, ext) = split_ext(file)
		if ext == "":
			install(file, examplesdir, True)

	def clean(self, file):
		delete(file)
		(base, ext) = split_ext(file)
		if ext == "tce":
			delete(base + ".o")
			delete(base + ".etc")
			delete(base + ".s")
		elif ext == "lib":
			delete(base + ".lbb")
			delete(patterns["a"] % base)
			delete(patterns["so"] % base)
			delete(base + ".module")
		elif ext == "":
			self.clean(base + ".tce")
#TODO add clean rules for the Tock stuff!
	def cflags(self):
		cflags = "-DOCCBUILD_KROC"
		print tidy_spaces(cflags)
	def search_path(self):
		return capture(programs["tock"] + ["--module-path"]).strip().split(":") + capture(programs["tock"] + ["--include-path"]).strip().split(":") + capture(programs["tock"] + ["--lib-path"]).strip().split(":")

	def update_patterns(self,patterns):
		for key in patterns.keys():
			patterns[key] = patterns[key].replace('occam_', 'occam_tock_')
#}}}
#{{{  toolchains
toolchains = {
	"kroc": KRoCToolchain,
	"tock": TockToolchain,
	"tvm": TVMToolchain,
	}
#}}}

#{{{  usage
def usage(f):
	print >>f, """occbuild version """ + VERSION + """, for """ + target_cpu + """ """ + target_os + """
Compile occam-pi libraries and programs
Usage:

  occbuild [OPTIONS] --object NAME.occ
    Compile source file NAME.occ to produce NAME.tce

  occbuild [OPTIONS] --library LIBNAME.lib [OBJECT ...]
    Link object files (.tce or .o) to produce LIBNAME.lib

  occbuild [OPTIONS] --program PROGNAME.occ [OBJECT ...]
    Compile PROGNAME.occ with additional OBJECTs to produce PROGNAME

  occbuild [OPTIONS] --run PROGNAME [-- ARGS ...]
    Run compiled program PROGNAME with arguments ARGS

  occbuild [OPTIONS] --install FILE ...
    Install files (.inc, .lib, .h or programs) to system locations

  occbuild [OPTIONS] --install-examples PACKAGE PROGRAM ...
    Install PROGRAMs as examples for PACKAGE

  occbuild [OPTIONS] --clean FILE ...
    Remove generated files

  occbuild [OPTIONS] --cflags
    Print CFLAGS necessary to build FFI objects to stdout

Options for all modes:
  -v, --verbose       Print progress reports to stderr
  --help              Show usage
  --toolchain CHAIN   Use toolchain CHAIN (""" + "/".join(safe_sorted(toolchains.keys())) + """, default tvm)
  --search DIR        Add DIR to search path for include files and libraries

For each program prog of """ + ", ".join(safe_sorted(programs.keys())) + """:
  --prog PATH         Path to prog (or set PROG=PATH in the environment)
  --prog-opts OPTS    Additional options for prog

Options for --object/--program mode:
  --occ21-opts OPTS   Additional options for occ21
  -D DEFINE           Passed through to occ21 as -DEF

Options for --library mode:
  -l LIBRARY          } Passed through to native linker
  -L PATH             }
  -r PATH             }
  -R PATH             }
  -W OPTION           }
  -m OPTION           }
  -p OPTION           }
  --need MODULE       Module depends on other module MODULE
  --no-std-libs       Do not link module against standard libraries
  --include INCLUDE   Module uses include file INCLUDE
  --include-after INCLUDE
                      Module uses include file INCLUDE after library is loaded

Options for --program mode:
  --static            Force program to be statically linked
  --output OUTPUT     Use OUTPUT as the output base filename

Options for --install/--install-examples mode:
  --prefix PREFIX     Select installation prefix PREFIX (mandatory)
  --destdir DESTDIR   Use staging directory DESTDIR

Report bugs to <""" + CONTACT + """>."""
#}}}
#{{{  main
def main(args):
	#{{{  get default program locations and arguments from the environment
	def tool(prog, default):
		programs[prog] = split_options(os.getenv(prog.upper(), default))
	tool("cc", "no")
	tool("ar", "no")
	tool("ranlib", ":")
	for prog in ("kroc", "ilibr", "occ21", "plinker.pl", "tce-dump.pl", "tock", "tvm"):
		tool(prog, target_prefix + prog)
	for prog in ("install",):
		tool(prog, prog)
	#}}}

	#{{{  parse options
	long_opts = [
		"object", "library=", "program=", "run=",
		"install", "install-examples=", "clean", "cflags",
		"help", "no-std-libs", "verbose",
		"in-tree=", "toolchain=", "search=",
		"occ21-opts=", "need=", "include=", "include-after=",
		"static", "output=",
		"prefix=", "destdir=",
		]
	for prog in programs.keys():
		long_opts += [prog + "=", prog + "-opts="]
	try:
		opts, args = getopt.getopt(args, "vD:l:L:r:R:W:m:p:", long_opts)
	except getopt.GetoptError:
		usage(sys.stderr)
		sys.exit(1)

	mode = None
	target = None
	output = None
	toolchain = None
	global std_libs, verbose, in_tree, search_path
	global occ21_opts, tock_opts, linker_opts, needs
	global includes_before, includes_after, static_link
	global prefix, destdir, examples
	for (o, a) in opts:
		if o in ("--object", "--library", "--program", "--run",
		         "--install", "--install-examples",
		         "--clean", "--cflags"):
			if mode is not None:
				die("Multiple modes specified")
			mode = o[2:]
			target = a
		elif o == "--help":
			usage(sys.stdout)
			sys.exit(0)
		elif o in ("-v", "--verbose"):
			verbose = True
		elif o == "--in-tree":
			in_tree = a
		elif o == "--toolchain":
			if toolchain is not None:
				die("Multiple toolchains specified")
			if a not in toolchains:
				die("Unknown toolchain specified: ", a)
			toolchain = toolchains[a]()
		elif o == "--search":
			search_path.append(a)
		elif o == "--occ21-opts":
			occ21_opts += split_options(a)
		elif o == "-D":
			occ21_opts += ["-DEF", a]
			tock_opts += ["-D", a]
		elif o in ("-l", "-L", "-r", "-R", "-W", "-m", "-p"):
			linker_opts.append("%s%s" % (o, a))
		elif o == "--need":
			needs.append(a)
		elif o == "--include":
			includes_before.append(a)
		elif o == "--include-after":
			includes_after.append(a)
		elif o == "--static":
			static_link = True
		elif o == "--output":
			output = a
		elif o == "--prefix":
			prefix = a
		elif o == "--destdir":
			destdir = a
		elif o == "--no-std-libs":
			std_libs = False
		else:
			for prog in programs.keys():
				if o == "--" + prog:
					programs[prog][0] = a
				elif o == "--" + prog + "-opts":
					programs[prog] += split_options(a)

	if mode is None:
		usage(sys.stderr)
		sys.exit(1)
	#}}}

	if toolchain is None:
		toolchain = toolchains["tvm"]()

	#{{{  adjust program locations for --in-tree
	programs["tvm_installed"] = programs["tvm"]
	if in_tree is not None:
		programs["kroc"] = [in_tree + "/tools/kroc/kroc", "--in-tree", in_tree] + programs["kroc"][1:]
		programs["ilibr"] = [in_tree + "/tools/ilibr/ilibr"] + programs["ilibr"][1:]
		programs["occ21"] = [in_tree + "/tools/occ21/occ21"] + programs["occ21"][1:]
		programs["plinker.pl"] = [in_tree + "/tools/plinker/plinker.pl"] + programs["plinker.pl"][1:]
		programs["tce-dump.pl"] = [in_tree + "/tools/plinker/tce-dump.pl"] + programs["tce-dump.pl"][1:]
		programs["tvm"] = [in_tree + "/tvm/posix/tvm"] + programs["tvm"][1:]
	#}}}

	#{{{  construct and export search_path
	build_default_search_path(toolchain.search_path())

	if mode in ("object", "program"):
		# If we're building a source file in another directory
		# (e.g. ../../libsrc/foo.occ), then we need to add search paths
		# relative to that directory too. This handles the case when
		# we're building with a separate object directory (even for
		# modules in the whole KRoC tree).
		here = os.getcwd()
		for source in [target] + args:
			if source is None or source == "":
				continue

			sd = os.path.dirname(source)
			if sd != ".":
				added_paths = [sd]

				for dir in search_path:
					# Work out the relative path from the
					# current directory to the directory in
					# the path.
					rel = make_relative_path(here, dir)

					# Follow the same relative path from
					# the source directory.
					added_paths.append(os.path.normpath(os.path.join(sd, rel)))

				search_path += added_paths

	# Tidy up the search path, now we've added everything to it.
	tidy_search_path = []
	for dir in search_path:
		# Make the paths absolute, and normalise them.
		dir = os.path.normpath(os.path.abspath(dir))

		# Remove duplicate paths.
		if not dir in tidy_search_path:
			tidy_search_path.append(dir)
	search_path = tidy_search_path

	set_isearch()
	for dir in search_path:
		programs["cc"] += ["-L" + dir]
		programs["kroc"] += ["-I" + dir, "-L" + dir]
	#}}}

	#{{{  construct filename patterns
	global patterns, build_shared
	patterns = {
		"libname": "occam_%s",
		"link": "-loccam_%s",
		"rtlink": "-l%s",
		"so": "liboccam_%s.so",
		"a": "liboccam_%s.a",
		}
	if target_os.startswith("cygwin"):
		build_shared = False
	elif target_os.startswith("mingw32"):
		patterns["so"] = "occam_%s.dll"
	elif target_os.startswith("darwin"):
		patterns["so"] = "liboccam_%s.dylib"
		build_shared = (toolchain.__class__ == toolchains["tvm"])
	elif target_cpu.startswith("avr"):
		build_shared = False

	toolchain.update_patterns(patterns)
	#}}}

	if mode == "object":
		if args == []:
			die("No source files specified")
		for source in args:
			if not source.endswith(".occ"):
				die("Source filenames must end in .occ: ", source)
			toolchain.object(source)
	elif mode == "library":
		occam_objs = []
		native_objs = []
		for object in args:
			(_, ext) = split_ext(object)
			if ext == "tce":
				occam_objs.append(object)
			elif ext == "o":
				native_objs.append(object)
			else:
				die("Object filename must end in .tce or .o: ", object)
		(base, ext) = split_ext(target)
		if ext != "lib":
			die("Library filename must end in .lib: ", target)
		toolchain.library(occam_objs, native_objs, base)
	elif mode == "program":
		occam_objs = []
		native_objs = []
		for object in args:
			(_, ext) = split_ext(object)
			if ext == "tce":
				occam_objs.append(object)
			elif ext == "o":
				native_objs.append(object)
			else:
				die("Object filename must end in .tce or .o: ", object)
		if not target.endswith(".occ"):
			die("Source filename must end in .occ: ", target)
		if output is None:
			(output, _) = split_ext(target)
		toolchain.program(occam_objs, native_objs, target, output)
	elif mode == "run":
		toolchain.run(target, args)
	elif mode == "install":
		if prefix is None:
			die("When using --install mode, you must give --prefix")
		for file in args:
			toolchain.install(file)
	elif mode == "install-examples":
		if prefix is None:
			die("When using --install-examples mode, you must give --prefix")
		for file in args:
			toolchain.install_examples(target, file)
	elif mode == "clean":
		for file in args:
			toolchain.clean(file)
	elif mode == "cflags":
		if args != []:
			die("Unexpected arguments specified")
		toolchain.cflags()
	else:
		die("Mode ", mode, " not implemented")
#}}}

if __name__ == "__main__":
	main(sys.argv[1:])
