/* valacompiler.vala
 *
 * Copyright (C) 2006-2012  Jürg Billeter
 * Copyright (C) 1996-2002, 2004, 2005, 2006 Free Software Foundation, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library 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
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 */

using GLib;

class Vala.Compiler {
	private const string DEFAULT_COLORS = "error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01";

	static string basedir;
	static string directory;
	static bool version;
	static bool api_version;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] sources;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] vapi_directories;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] gir_directories;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] metadata_directories;
	static string vapi_filename;
	static string library;
	static string shared_library;
	static string gir;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] packages;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] fast_vapis;
	static string target_glib;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] gresources;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] gresources_directories;

	static bool ccode_only;
	static bool abi_stability;
	static string header_filename;
	static string internal_header_filename;
	static string internal_vapi_filename;
	static string fast_vapi_filename;
	static bool vapi_comments;
	static string symbols_filename;
	static string includedir;
	static bool compile_only;
	static string output;
	static bool debug;
	static bool mem_profiler;
	static bool disable_assert;
	static bool enable_checking;
	static bool deprecated;
	static bool hide_internal;
	static bool experimental;
	static bool experimental_non_null;
	static bool gobject_tracing;
	static bool disable_since_check;
	static bool disable_warnings;
	static bool keep_going;
	static bool list_sources;
	static string cc_command;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] cc_options;
	static string pkg_config_command;
	static string dump_tree;
	static bool save_temps;
	[CCode (array_length = false, array_null_terminated = true)]
	static string[] defines;
	static bool quiet_mode;
	static bool verbose_mode;
	static Profile profile;
	static bool nostdpkg;
	static bool enable_version_header;
	static bool disable_version_header;
	static bool fatal_warnings;
	static bool disable_colored_output;
	static Report.Colored colored_output = Report.Colored.AUTO;
	static string dependencies;
	static string depfile;

	static string entry_point;

	static bool run_output;
	static string run_args;

	private CodeContext context;

	const OptionEntry[] options = {
		{ "vapidir", 0, 0, OptionArg.FILENAME_ARRAY, ref vapi_directories, "Look for package bindings in DIRECTORY", "DIRECTORY..." },
		{ "girdir", 0, 0, OptionArg.FILENAME_ARRAY, ref gir_directories, "Look for .gir files in DIRECTORY", "DIRECTORY..." },
		{ "metadatadir", 0, 0, OptionArg.FILENAME_ARRAY, ref metadata_directories, "Look for GIR .metadata files in DIRECTORY", "DIRECTORY..." },
		{ "pkg", 0, 0, OptionArg.STRING_ARRAY, ref packages, "Include binding for PACKAGE", "PACKAGE..." },
		{ "vapi", 0, 0, OptionArg.FILENAME, ref vapi_filename, "Output VAPI file name", "FILE" },
		{ "library", 0, 0, OptionArg.STRING, ref library, "Library name", "NAME" },
		{ "shared-library", 0, 0, OptionArg.STRING, ref shared_library, "Shared library name used in generated gir", "NAME" },
		{ "gir", 0, 0, OptionArg.STRING, ref gir, "GObject-Introspection repository file name", "NAME-VERSION.gir" },
		{ "basedir", 'b', 0, OptionArg.FILENAME, ref basedir, "Base source directory", "DIRECTORY" },
		{ "directory", 'd', 0, OptionArg.FILENAME, ref directory, "Change output directory from current working directory", "DIRECTORY" },
		{ "version", 0, 0, OptionArg.NONE, ref version, "Display version number", null },
		{ "api-version", 0, 0, OptionArg.NONE, ref api_version, "Display API version number", null },
		{ "ccode", 'C', 0, OptionArg.NONE, ref ccode_only, "Output C code", null },
		{ "header", 'H', 0, OptionArg.FILENAME, ref header_filename, "Output C header file", "FILE" },
		{ "use-header", 0, OptionFlags.OPTIONAL_ARG | OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) option_deprecated, "Use C header file (DEPRECATED AND IGNORED)", null },
		{ "includedir", 0, 0, OptionArg.FILENAME, ref includedir, "Directory used to include the C header file", "DIRECTORY" },
		{ "internal-header", 'h', 0, OptionArg.FILENAME, ref internal_header_filename, "Output internal C header file", "FILE" },
		{ "internal-vapi", 0, 0, OptionArg.FILENAME, ref internal_vapi_filename, "Output vapi with internal api", "FILE" },
		{ "fast-vapi", 0, 0, OptionArg.STRING, ref fast_vapi_filename, "Output vapi without performing symbol resolution", null },
		{ "use-fast-vapi", 0, 0, OptionArg.STRING_ARRAY, ref fast_vapis, "Use --fast-vapi output during this compile", null },
		{ "vapi-comments", 0, 0, OptionArg.NONE, ref vapi_comments, "Include comments in generated vapi", null },
		{ "deps", 0, 0, OptionArg.STRING, ref dependencies, "Write make-style dependency information to this file", null },
		{ "depfile", 0, 0, OptionArg.STRING, ref depfile, "Write make-style external dependency information for build systems to this file", null },
		{ "list-sources", 0, 0, OptionArg.NONE, ref list_sources, "Output a list of all source and binding files which are used", null },
		{ "symbols", 0, 0, OptionArg.FILENAME, ref symbols_filename, "Output symbols file", "FILE" },
		{ "compile", 'c', 0, OptionArg.NONE, ref compile_only, "Compile but do not link", null },
		{ "output", 'o', 0, OptionArg.FILENAME, ref output, "Place output in file FILE", "FILE" },
		{ "debug", 'g', 0, OptionArg.NONE, ref debug, "Produce debug information", null },
		{ "thread", 0, OptionFlags.OPTIONAL_ARG | OptionFlags.NO_ARG, OptionArg.CALLBACK, (void*) option_deprecated, "Enable multithreading support (DEPRECATED AND IGNORED)", null },
		{ "enable-mem-profiler", 0, 0, OptionArg.NONE, ref mem_profiler, "Enable GLib memory profiler", null },
		{ "define", 'D', 0, OptionArg.STRING_ARRAY, ref defines, "Define SYMBOL", "SYMBOL..." },
		{ "main", 0, 0, OptionArg.STRING, ref entry_point, "Use SYMBOL as entry point", "SYMBOL..." },
		{ "nostdpkg", 0, 0, OptionArg.NONE, ref nostdpkg, "Do not include standard packages", null },
		{ "disable-assert", 0, 0, OptionArg.NONE, ref disable_assert, "Disable assertions", null },
		{ "enable-checking", 0, 0, OptionArg.NONE, ref enable_checking, "Enable additional run-time checks", null },
		{ "enable-deprecated", 0, 0, OptionArg.NONE, ref deprecated, "Enable deprecated features", null },
		{ "hide-internal", 0, 0, OptionArg.NONE, ref hide_internal, "Hide symbols marked as internal", null },
		{ "enable-experimental", 0, 0, OptionArg.NONE, ref experimental, "Enable experimental features", null },
		{ "disable-warnings", 0, 0, OptionArg.NONE, ref disable_warnings, "Disable warnings", null },
		{ "fatal-warnings", 0, 0, OptionArg.NONE, ref fatal_warnings, "Treat warnings as fatal", null },
		{ "disable-since-check", 0, 0, OptionArg.NONE, ref disable_since_check, "Do not check whether used symbols exist in local packages", null },
		{ "keep-going", 'k', 0, OptionArg.NONE, ref keep_going, "Continue as much as possible after an error", null },
		{ "enable-experimental-non-null", 0, 0, OptionArg.NONE, ref experimental_non_null, "Enable experimental enhancements for non-null types", null },
		{ "enable-gobject-tracing", 0, 0, OptionArg.NONE, ref gobject_tracing, "Enable GObject creation tracing", null },
		{ "cc", 0, 0, OptionArg.STRING, ref cc_command, "Use COMMAND as C compiler command", "COMMAND" },
		{ "Xcc", 'X', 0, OptionArg.STRING_ARRAY, ref cc_options, "Pass OPTION to the C compiler", "OPTION..." },
		{ "pkg-config", 0, 0, OptionArg.STRING, ref pkg_config_command, "Use COMMAND as pkg-config command", "COMMAND" },
		{ "dump-tree", 0, 0, OptionArg.FILENAME, ref dump_tree, "Write code tree to FILE", "FILE" },
		{ "save-temps", 0, 0, OptionArg.NONE, ref save_temps, "Keep temporary files", null },
		{ "profile", 0, OptionFlags.OPTIONAL_ARG, OptionArg.CALLBACK, (void*) option_parse_profile, "Minimum runtime dependency: 'gobject' (default) or 'posix' (minimal libc)", "PROFILE" },
		{ "quiet", 'q', 0, OptionArg.NONE, ref quiet_mode, "Do not print messages to the console", null },
		{ "verbose", 'v', 0, OptionArg.NONE, ref verbose_mode, "Print additional messages to the console", null },
		{ "no-color", 0, 0, OptionArg.NONE, ref disable_colored_output, "Disable colored output, alias for --color=never", null },
		{ "color", 0, OptionFlags.OPTIONAL_ARG, OptionArg.CALLBACK, (void*) option_parse_color, "Enable color output, options are 'always', 'never', or 'auto'", "WHEN" },
		{ "target-glib", 0, 0, OptionArg.STRING, ref target_glib, "Target version of glib for code generation", "'MAJOR.MINOR', or 'auto'" },
		{ "gresources", 0, 0, OptionArg.FILENAME_ARRAY, ref gresources, "XML of gresources", "FILE..." },
		{ "gresourcesdir", 0, 0, OptionArg.FILENAME_ARRAY, ref gresources_directories, "Look for resources in DIRECTORY", "DIRECTORY..." },
		{ "enable-version-header", 0, 0, OptionArg.NONE, ref enable_version_header, "Write vala build version in generated files", null },
		{ "disable-version-header", 0, 0, OptionArg.NONE, ref disable_version_header, "Do not write vala build version in generated files", null },
		{ "run-args", 0, 0, OptionArg.STRING, ref run_args, "Arguments passed to directly compiled executable", null },
		{ "abi-stability", 0, 0, OptionArg.NONE, ref abi_stability, "Enable support for ABI stability", null },
		{ OPTION_REMAINING, 0, 0, OptionArg.FILENAME_ARRAY, ref sources, null, "FILE..." },
		{ null }
	};

	static bool option_parse_color (string option_name, string? val, void* data) throws OptionError {
		switch (val) {
			case "auto": colored_output = Report.Colored.AUTO; break;
			case "never": colored_output = Report.Colored.NEVER; break;
			case null:
			case "always": colored_output = Report.Colored.ALWAYS; break;
			default: throw new OptionError.FAILED ("Invalid --color argument '%s'", val);
		}
		return true;
	}

	static bool option_parse_profile (string option_name, string? val, void* data) throws OptionError {
		switch (val) {
			case null:
			case "gobject-2.0":
			case "gobject": profile = Profile.GOBJECT; break;
			case "libc": profile = Vala.Profile.LIBC; break;
			case "posix": profile = Profile.POSIX; break;
			default: throw new OptionError.FAILED ("Invalid --profile argument '%s'", val);
		}
		return true;
	}

	static bool option_deprecated (string option_name, string? val, void* data) throws OptionError {
		print ("Command-line option `%s` is deprecated and will be ignored\n", option_name);
		return true;
	}

	private int quit () {
		if (context.report.get_errors () == 0 && context.report.get_warnings () == 0) {
			CodeContext.pop ();
			return 0;
		}
		if (context.report.get_errors () == 0 && (!fatal_warnings || context.report.get_warnings () == 0)) {
			if (!quiet_mode) {
				print ("Compilation succeeded - %d warning(s)\n", context.report.get_warnings ());
			}
			CodeContext.pop ();
			return 0;
		} else {
			if (!quiet_mode) {
				print ("Compilation failed: %d error(s), %d warning(s)\n", context.report.get_errors (), context.report.get_warnings ());
			}
			CodeContext.pop ();
			return 1;
		}
	}

	private int run () {
		context = new CodeContext ();
		CodeContext.push (context);

		if (disable_colored_output) {
			colored_output = Report.Colored.NEVER;
		}

		if (colored_output != Report.Colored.NEVER) {
			unowned string env_colors = Environment.get_variable ("VALA_COLORS");
			if (env_colors != null) {
				context.report.set_colors (env_colors, colored_output);
			} else {
				context.report.set_colors (DEFAULT_COLORS, colored_output);
			}
		}


		// default to build executable
		if (!ccode_only && !compile_only && output == null) {
			// strip extension if there is one
			// else we use the default output file of the C compiler
			if (sources[0].last_index_of_char ('.') != -1) {
				int dot = sources[0].last_index_of_char ('.');
				output = Path.get_basename (sources[0].substring (0, dot));
			}
		}

		context.assert = !disable_assert;
		context.checking = enable_checking;
		context.deprecated = deprecated;
		context.since_check = !disable_since_check;
		context.hide_internal = hide_internal;
		context.experimental = experimental;
		context.experimental_non_null = experimental_non_null;
		context.gobject_tracing = gobject_tracing;
		context.keep_going = keep_going;
		context.report.enable_warnings = !disable_warnings;
		context.report.set_verbose_errors (!quiet_mode);
		context.verbose_mode = verbose_mode;
		context.version_header = !disable_version_header;

		context.ccode_only = ccode_only;
		if (ccode_only && cc_options != null) {
			Report.warning (null, "-X has no effect when -C or --ccode is set");
		}
		context.abi_stability = abi_stability;
		context.compile_only = compile_only;
		context.header_filename = header_filename;
		context.internal_header_filename = internal_header_filename;
		context.symbols_filename = symbols_filename;
		context.includedir = includedir;
		context.output = output;
		if (output != null && ccode_only) {
			Report.warning (null, "--output and -o have no effect when -C or --ccode is set");
		}
		if (basedir == null) {
			context.basedir = CodeContext.realpath (".");
		} else {
			context.basedir = CodeContext.realpath (basedir);
		}
		if (directory != null) {
			context.directory = CodeContext.realpath (directory);
		} else {
			context.directory = context.basedir;
		}
		context.vapi_directories = vapi_directories;
		context.vapi_comments = vapi_comments;
		context.gir_directories = gir_directories;
		context.metadata_directories = metadata_directories;
		context.debug = debug;
		context.mem_profiler = mem_profiler;
		context.save_temps = save_temps;
		if (ccode_only && save_temps) {
			Report.warning (null, "--save-temps has no effect when -C or --ccode is set");
		}
		nostdpkg |= fast_vapi_filename != null;

		context.entry_point_name = entry_point;

		context.run_output = run_output;

		if (pkg_config_command == null) {
			pkg_config_command = Environment.get_variable ("PKG_CONFIG") ?? "pkg-config";
		}
		context.pkg_config_command = pkg_config_command;

		context.set_target_profile (profile, !nostdpkg);

		if (target_glib != null) {
			context.set_target_glib_version (target_glib);
		}

		if (defines != null) {
			foreach (string define in defines) {
				context.add_define (define);
			}
		}

		if (packages != null) {
			foreach (string package in packages) {
				context.add_external_package (package);
			}
			packages = null;
		}

		if (fast_vapis != null) {
			foreach (string vapi in fast_vapis) {
				var rpath = CodeContext.realpath (vapi);
				var source_file = new SourceFile (context, SourceFileType.FAST, rpath);
				context.add_source_file (source_file);
			}
			context.use_fast_vapi = true;
		}

		context.gresources = gresources;
		context.gresources_directories = gresources_directories;

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (context.profile == Profile.GOBJECT) {
			context.codegen = new GDBusServerModule ();
		} else {
			context.codegen = new CCodeDelegateModule ();
		}

		bool has_c_files = false;
		bool has_h_files = false;

		foreach (string source in sources) {
			if (context.add_source_filename (source, run_output, true)) {
				if (source.has_suffix (".c")) {
					has_c_files = true;
				} else if (source.has_suffix (".h")) {
					has_h_files = true;
				}
			}
		}
		sources = null;
		if (ccode_only && (has_c_files || has_h_files)) {
			Report.warning (null, "C header and source files are ignored when -C or --ccode is set");
		}

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (list_sources) {
			foreach (SourceFile file in context.get_source_files ()) {
				print ("%s\n", file.filename);
			}
			if (!ccode_only) {
				foreach (string filename in context.get_c_source_files ()) {
					print ("%s\n", filename);
				}
			}
			return 0;
		}

		var parser = new Parser ();
		parser.parse (context);

		var genie_parser = new Genie.Parser ();
		genie_parser.parse (context);

		var gir_parser = new GirParser ();
		gir_parser.parse (context);

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (fast_vapi_filename != null) {
			var interface_writer = new CodeWriter (CodeWriterType.FAST);
			interface_writer.write_file (context, fast_vapi_filename);
			return quit ();
		}

		context.check ();

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (!ccode_only && !compile_only && library == null) {
			// building program, require entry point
			if (!has_c_files && context.entry_point == null) {
				Report.error (null, "program does not contain a static `main' method");
			}
		}

		if (dump_tree != null) {
			var code_writer = new CodeWriter (CodeWriterType.DUMP);
			code_writer.write_file (context, dump_tree);
		}

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		context.codegen.emit (context);

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (vapi_filename == null && library != null) {
			// keep backward compatibility with --library option
			vapi_filename = "%s.vapi".printf (library);
		}

		if (library != null) {
			if (gir != null) {
				if (context.profile == Profile.GOBJECT) {
					string gir_base = Path.get_basename (gir);
					long gir_len = gir_base.length;
					int last_hyphen = gir_base.last_index_of_char ('-');

					if (last_hyphen == -1 || !gir_base.has_suffix (".gir")) {
						Report.error (null, "GIR file name `%s' is not well-formed, expected NAME-VERSION.gir", gir);
					} else {
						string gir_namespace = gir_base.substring (0, last_hyphen);
						string gir_version = gir_base.substring (last_hyphen + 1, gir_len - last_hyphen - 5);
						gir_version.canon ("0123456789.", '?');
						if (gir_namespace == "" || gir_version == "" || !gir_version[0].isdigit () || gir_version.contains ("?")) {
							Report.error (null, "GIR file name `%s' is not well-formed, expected NAME-VERSION.gir", gir);
						} else {
							var gir_writer = new GIRWriter ();

							// put .gir file in current directory unless -d has been explicitly specified
							string gir_directory = ".";
							if (directory != null) {
								gir_directory = context.directory;
							}

							gir_writer.write_file (context, gir_directory, gir, gir_namespace, gir_version, library, shared_library);
						}
					}
				}

				gir = null;
			}

			library = null;
		} else {
			if (gir != null) {
				Report.warning (null, "--gir has no effect without --library");
				gir = null;
			}
		}

		// The GIRWriter places the gir_namespace and gir_version into the top namespace, so write the vapi after that stage
		if (vapi_filename != null) {
			var interface_writer = new CodeWriter ();

			// put .vapi file in current directory unless -d has been explicitly specified
			if (directory != null && !Path.is_absolute (vapi_filename)) {
				vapi_filename = "%s%c%s".printf (context.directory, Path.DIR_SEPARATOR, vapi_filename);
			}

			interface_writer.write_file (context, vapi_filename);
		}

		if (internal_vapi_filename != null) {
			if (internal_header_filename == null ||
			    header_filename == null) {
				Report.error (null, "--internal-vapi may only be used in combination with --header and --internal-header");
				return quit();
			}

			var interface_writer = new CodeWriter (CodeWriterType.INTERNAL);

			if (context.includedir != null) {
				var prefixed_header_filename = Path.build_path ("/", context.includedir, Path.get_basename (header_filename));
				var prefixed_internal_header_filename = Path.build_path ("/", context.includedir, Path.get_basename (internal_header_filename));
				interface_writer.set_cheader_override (prefixed_header_filename, prefixed_internal_header_filename);
			} else {
				interface_writer.set_cheader_override (header_filename, internal_header_filename);
			}

			string vapi_filename = internal_vapi_filename;

			// put .vapi file in current directory unless -d has been explicitly specified
			if (directory != null && !Path.is_absolute (vapi_filename)) {
				vapi_filename = "%s%c%s".printf (context.directory, Path.DIR_SEPARATOR, vapi_filename);
			}

			interface_writer.write_file (context, vapi_filename);

			internal_vapi_filename = null;
		}

		if (dependencies != null) {
			context.write_dependencies (dependencies);
		}

		if (depfile != null) {
			context.write_external_dependencies (depfile);
		}

		if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 0)) {
			return quit ();
		}

		if (!ccode_only) {
			var ccompiler = new CCodeCompiler ();
			if (cc_command == null && Environment.get_variable ("CC") != null) {
				cc_command = Environment.get_variable ("CC");
			}
			if (cc_options == null) {
				ccompiler.compile (context, cc_command, new string[] { });
			} else {
				ccompiler.compile (context, cc_command, cc_options);
			}
		}

		return quit ();
	}

	static int run_source (string[] args) {
		try {
			var opt_context = new OptionContext ("- Vala Interpreter");
			opt_context.set_help_enabled (true);
			opt_context.add_main_entries (options, null);
			opt_context.parse (ref args);
		} catch (OptionError e) {
			print ("%s\n", e.message);
			print ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
			return 1;
		}

		if (version) {
			print ("Vala %s\n", Vala.BUILD_VERSION);
			return 0;
		} else if (api_version) {
			print ("%s\n", Vala.API_VERSION);
			return 0;
		}

		if (sources == null) {
			printerr ("No source file specified.\n");
			return 1;
		}

		output = "%s/%s.XXXXXX".printf (Environment.get_tmp_dir (), Path.get_basename (sources[0]));
		int outputfd = FileUtils.mkstemp (output);
		if (outputfd < 0) {
			return 1;
		}

		ccode_only = false;
		compile_only = false;
		run_output = true;
		disable_warnings = true;
		quiet_mode = true;
		library = null;
		shared_library = null;

		var compiler = new Compiler ();
		int ret = compiler.run ();
		if (ret != 0) {
			return ret;
		}

		FileUtils.close (outputfd);
		if (FileUtils.chmod (output, 0700) != 0) {
			FileUtils.unlink (output);
			return 1;
		}

		string[] target_args = { output };
		if (run_args != null) {
			string[] target_run_args = run_args.split (" ");
			foreach (string arg in target_run_args) {
				target_args += arg;
			}
		}

		try {
			Pid pid;
			var loop = new MainLoop ();
			int child_status = 0;

			Process.spawn_async (null, target_args, null, SpawnFlags.CHILD_INHERITS_STDIN | SpawnFlags.DO_NOT_REAP_CHILD, null, out pid);

			FileUtils.unlink (output);
			ChildWatch.add (pid, (pid, status) => {
				child_status = (status & 0xff00) >> 8;
				loop.quit ();
			});

			loop.run ();

			return child_status;
		} catch (SpawnError e) {
			print ("%s\n", e.message);
			return 1;
		}
	}

	static int main (string[] args) {
		// initialize locale
		Intl.setlocale (LocaleCategory.ALL, "");

		if (Vala.get_build_version () != Vala.BUILD_VERSION) {
			printerr ("Integrity check failed (libvala %s doesn't match valac %s)\n", Vala.get_build_version (), Vala.BUILD_VERSION);
			return 1;
		}

		if (Path.get_basename (args[0]) == "vala" || Path.get_basename (args[0]) == "vala" + Config.PACKAGE_SUFFIX) {
			return run_source (args);
		}

		try {
			var opt_context = new OptionContext ("- Vala Compiler");
			opt_context.set_help_enabled (true);
			opt_context.add_main_entries (options, null);
			opt_context.parse (ref args);
		} catch (OptionError e) {
			print ("%s\n", e.message);
			print ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
			return 1;
		}

		if (version) {
			print ("Vala %s\n", Vala.BUILD_VERSION);
			return 0;
		} else if (api_version) {
			print ("%s\n", Vala.API_VERSION);
			return 0;
		}

		if (sources == null && fast_vapis == null) {
			printerr ("No source file specified.\n");
			return 1;
		}

		var compiler = new Compiler ();
		return compiler.run ();
	}
}
