update for 0.4.0 release
[vala-lang.git] / compiler / valacompiler.vala
blob0babf2e8e9f6d639bf8fbe0750347c85353c1ea1
1 /* valacompiler.vala
3 * Copyright (C) 2006-2008 Jürg Billeter
4 * Copyright (C) 1996-2002, 2004, 2005, 2006 Free Software Foundation, Inc.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 * Author:
21 * Jürg Billeter <j@bitron.ch>
24 using GLib;
26 class Vala.Compiler {
27 static string basedir;
28 static string directory;
29 static bool version;
30 [NoArrayLength ()]
31 static string[] sources;
32 [NoArrayLength ()]
33 static string[] vapi_directories;
34 static string library;
35 [NoArrayLength ()]
36 static string[] packages;
37 static string target_glib;
39 static bool ccode_only;
40 static bool compile_only;
41 static string output;
42 static bool debug;
43 static bool thread;
44 static bool disable_assert;
45 static bool disable_checking;
46 static bool disable_non_null;
47 static bool non_null_experimental;
48 static string cc_command;
49 [NoArrayLength]
50 static string[] cc_options;
51 static bool save_temps;
52 [NoArrayLength]
53 static string[] defines;
54 static bool quiet_mode;
56 private CodeContext context;
58 const OptionEntry[] options = {
59 { "vapidir", 0, 0, OptionArg.FILENAME_ARRAY, ref vapi_directories, "Look for package bindings in DIRECTORY", "DIRECTORY..." },
60 { "pkg", 0, 0, OptionArg.STRING_ARRAY, ref packages, "Include binding for PACKAGE", "PACKAGE..." },
61 { "library", 0, 0, OptionArg.STRING, ref library, "Library name", "NAME" },
62 { "basedir", 'b', 0, OptionArg.FILENAME, ref basedir, "Base source directory", "DIRECTORY" },
63 { "directory", 'd', 0, OptionArg.FILENAME, ref directory, "Output directory", "DIRECTORY" },
64 { "version", 0, 0, OptionArg.NONE, ref version, "Display version number", null },
65 { "ccode", 'C', 0, OptionArg.NONE, ref ccode_only, "Output C code", null },
66 { "compile", 'c', 0, OptionArg.NONE, ref compile_only, "Compile but do not link", null },
67 { "output", 'o', 0, OptionArg.FILENAME, ref output, "Place output in file FILE", "FILE" },
68 { "debug", 'g', 0, OptionArg.NONE, ref debug, "Produce debug information", null },
69 { "thread", 0, 0, OptionArg.NONE, ref thread, "Enable multithreading support", null },
70 { "define", 'D', 0, OptionArg.STRING_ARRAY, ref defines, "Define SYMBOL", "SYMBOL..." },
71 { "disable-assert", 0, 0, OptionArg.NONE, ref disable_assert, "Disable assertions", null },
72 { "disable-checking", 0, 0, OptionArg.NONE, ref disable_checking, "Disable run-time checks", null },
73 { "disable-non-null", 0, 0, OptionArg.NONE, ref disable_non_null, "Disable non-null types", null },
74 { "enable-non-null-experimental", 0, 0, OptionArg.NONE, ref non_null_experimental, "Enable experimental enhancements for non-null types", null },
75 { "cc", 0, 0, OptionArg.STRING, ref cc_command, "Use COMMAND as C compiler command", "COMMAND" },
76 { "Xcc", 'X', 0, OptionArg.STRING_ARRAY, ref cc_options, "Pass OPTION to the C compiler", "OPTION..." },
77 { "save-temps", 0, 0, OptionArg.NONE, ref save_temps, "Keep temporary files", null },
78 { "quiet", 'q', 0, OptionArg.NONE, ref quiet_mode, "Do not print messages to the console", null },
79 { "target-glib", 0, 0, OptionArg.STRING, ref target_glib, "Target version of glib for code generation", "MAJOR.MINOR" },
80 { "", 0, 0, OptionArg.FILENAME_ARRAY, ref sources, null, "FILE..." },
81 { null }
84 private int quit () {
85 if (Report.get_errors () == 0 && Report.get_warnings () == 0) {
86 return 0;
88 if (Report.get_errors () == 0) {
89 if (!quiet_mode) {
90 stdout.printf ("Compilation succeeded - %d warning(s)\n", Report.get_warnings ());
92 return 0;
93 } else {
94 if (!quiet_mode) {
95 stdout.printf ("Compilation failed: %d error(s), %d warning(s)\n", Report.get_errors (), Report.get_warnings ());
97 return 1;
101 private bool add_package (CodeContext context, string pkg) {
102 if (context.has_package (pkg)) {
103 // ignore multiple occurences of the same package
104 return true;
107 var package_path = context.get_package_path (pkg, vapi_directories);
109 if (package_path == null) {
110 return false;
113 context.add_package (pkg);
115 context.add_source_file (new SourceFile (context, package_path, true));
117 var deps_filename = Path.build_filename (Path.get_dirname (package_path), "%s.deps".printf (pkg));
118 if (FileUtils.test (deps_filename, FileTest.EXISTS)) {
119 try {
120 string deps_content;
121 ulong deps_len;
122 FileUtils.get_contents (deps_filename, out deps_content, out deps_len);
123 foreach (string dep in deps_content.split ("\n")) {
124 if (dep != "") {
125 if (!add_package (context, dep)) {
126 Report.error (null, "%s, dependency of %s, not found in specified Vala API directories".printf (dep, pkg));
130 } catch (FileError e) {
131 Report.error (null, "Unable to read dependency file: %s".printf (e.message));
135 return true;
138 private int run () {
139 context = new CodeContext ();
141 // default to build executable
142 if (!ccode_only && !compile_only && output == null) {
143 // strip extension if there is one
144 // else we use the default output file of the C compiler
145 if (sources[0].rchr (-1, '.') != null) {
146 long dot = sources[0].pointer_to_offset (sources[0].rchr (-1, '.'));
147 output = Path.get_basename (sources[0].substring (0, dot));
151 context.library = library;
152 context.assert = !disable_assert;
153 context.checking = !disable_checking;
154 context.non_null = !disable_non_null || non_null_experimental;
155 context.non_null_experimental = non_null_experimental;
156 Report.set_verbose_errors (!quiet_mode);
158 context.ccode_only = ccode_only;
159 context.compile_only = compile_only;
160 context.output = output;
161 if (basedir != null) {
162 context.basedir = realpath (basedir);
164 if (directory != null) {
165 context.directory = realpath (directory);
166 } else {
167 context.directory = context.basedir;
169 context.debug = debug;
170 context.thread = thread;
171 context.save_temps = save_temps;
173 int glib_major = 2;
174 int glib_minor = 12;
175 if (target_glib != null && target_glib.scanf ("%d.%d", out glib_major, out glib_minor) != 2) {
176 Report.error (null, "Invalid format for --target-glib");
179 context.target_glib_major = glib_major;
180 context.target_glib_minor = glib_minor;
181 if (context.target_glib_major != 2) {
182 Report.error (null, "This version of valac only supports GLib 2");
185 if (defines != null) {
186 foreach (string define in defines) {
187 context.add_define (define);
191 context.codegen = new CCodeGenerator ();
193 /* default package */
194 if (!add_package (context, "glib-2.0")) {
195 Report.error (null, "glib-2.0 not found in specified Vala API directories");
198 if (packages != null) {
199 foreach (string package in packages) {
200 if (!add_package (context, package)) {
201 Report.error (null, "%s not found in specified Vala API directories".printf (package));
204 packages = null;
207 if (Report.get_errors () > 0) {
208 return quit ();
211 foreach (string source in sources) {
212 if (FileUtils.test (source, FileTest.EXISTS)) {
213 var rpath = realpath (source);
214 if (source.has_suffix (".vala") || source.has_suffix (".gs")) {
215 var source_file = new SourceFile (context, rpath);
217 // import the GLib namespace by default (namespace of backend-specific standard library)
218 source_file.add_using_directive (new UsingDirective (new UnresolvedSymbol (null, "GLib", null)));
220 context.add_source_file (source_file);
221 } else if (source.has_suffix (".vapi")) {
222 context.add_source_file (new SourceFile (context, rpath, true));
223 } else if (source.has_suffix (".c")) {
224 context.add_c_source_file (rpath);
225 } else {
226 Report.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (source));
228 } else {
229 Report.error (null, "%s not found".printf (source));
232 sources = null;
234 if (Report.get_errors () > 0) {
235 return quit ();
238 var parser = new Parser ();
239 parser.parse (context);
241 var genie_parser = new Genie.Parser ();
242 genie_parser.parse (context);
244 if (Report.get_errors () > 0) {
245 return quit ();
248 var attributeprocessor = new AttributeProcessor ();
249 attributeprocessor.process (context);
251 if (Report.get_errors () > 0) {
252 return quit ();
255 var resolver = new SymbolResolver ();
256 resolver.resolve (context);
258 if (Report.get_errors () > 0) {
259 return quit ();
262 var analyzer = new SemanticAnalyzer ();
263 analyzer.analyze (context);
265 if (Report.get_errors () > 0) {
266 return quit ();
269 var cfg_builder = new CFGBuilder ();
270 cfg_builder.build_cfg (context);
272 if (Report.get_errors () > 0) {
273 return quit ();
276 if (context.non_null_experimental) {
277 var null_checker = new NullChecker ();
278 null_checker.check (context);
280 if (Report.get_errors () > 0) {
281 return quit ();
285 context.codegen.emit (context);
287 if (Report.get_errors () > 0) {
288 return quit ();
291 if (library != null) {
292 var interface_writer = new InterfaceWriter ();
293 string vapi_filename = "%s.vapi".printf (library);
295 // put .vapi file in current directory unless -d has been explicitly specified
296 if (directory != null && !Path.is_absolute (vapi_filename)) {
297 vapi_filename = "%s%c%s".printf (context.directory, Path.DIR_SEPARATOR, vapi_filename);
300 interface_writer.write_file (context, vapi_filename);
303 var gir_writer = new GIRWriter ();
304 string gir_filename = "%s.gir".printf (library);
306 // put .gir file in current directory unless -d has been explicitly specified
307 if (directory != null && !Path.is_absolute (gir_filename)) {
308 gir_filename = "%s%c%s".printf (context.directory, Path.DIR_SEPARATOR, gir_filename);
311 gir_writer.write_file (context, gir_filename);
314 library = null;
317 if (!ccode_only) {
318 var ccompiler = new CCodeCompiler ();
319 if (cc_command == null && Environment.get_variable ("CC") != null) {
320 cc_command = Environment.get_variable ("CC");
322 if (cc_options == null) {
323 ccompiler.compile (context, cc_command, new string[] { null });
324 } else {
325 ccompiler.compile (context, cc_command, cc_options);
329 return quit ();
332 private static bool ends_with_dir_separator (string s) {
333 return Path.is_dir_separator (s.offset (s.len () - 1).get_char ());
336 /* ported from glibc */
337 private static string realpath (string name) {
338 string rpath;
340 // start of path component
341 weak string start;
342 // end of path component
343 weak string end;
345 if (!Path.is_absolute (name)) {
346 // relative path
347 rpath = Environment.get_current_dir ();
349 start = end = name;
350 } else {
351 // set start after root
352 start = end = Path.skip_root (name);
354 // extract root
355 rpath = name.substring (0, name.pointer_to_offset (start));
358 long root_len = rpath.pointer_to_offset (Path.skip_root (rpath));
360 for (; start.get_char () != 0; start = end) {
361 // skip sequence of multiple path-separators
362 while (Path.is_dir_separator (start.get_char ())) {
363 start = start.next_char ();
366 // find end of path component
367 long len = 0;
368 for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) {
369 len++;
372 if (len == 0) {
373 break;
374 } else if (len == 1 && start.get_char () == '.') {
375 // do nothing
376 } else if (len == 2 && start.has_prefix ("..")) {
377 // back up to previous component, ignore if at root already
378 if (rpath.len () > root_len) {
379 do {
380 rpath = rpath.substring (0, rpath.len () - 1);
381 } while (!ends_with_dir_separator (rpath));
383 } else {
384 if (!ends_with_dir_separator (rpath)) {
385 rpath += Path.DIR_SEPARATOR_S;
388 rpath += start.substring (0, len);
392 if (rpath.len () > root_len && ends_with_dir_separator (rpath)) {
393 rpath = rpath.substring (0, rpath.len () - 1);
396 if (Path.DIR_SEPARATOR != '/') {
397 // don't use backslashes internally,
398 // to avoid problems in #include directives
399 string[] components = rpath.split ("\\");
400 rpath = string.joinv ("/", components);
403 return rpath;
406 static int main (string[] args) {
407 try {
408 var opt_context = new OptionContext ("- Vala Compiler");
409 opt_context.set_help_enabled (true);
410 opt_context.add_main_entries (options, null);
411 opt_context.parse (ref args);
412 } catch (OptionError e) {
413 stdout.printf ("%s\n", e.message);
414 stdout.printf ("Run '%s --help' to see a full list of available command line options.\n", args[0]);
415 return 1;
418 if (version) {
419 stdout.printf ("Vala %s\n", Config.PACKAGE_VERSION);
420 return 0;
423 if (sources == null) {
424 stderr.printf ("No source file specified.\n");
425 return 1;
428 var compiler = new Compiler ();
429 return compiler.run ();