3 * Copyright (C) 2006-2009 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
21 * Jürg Billeter <j@bitron.ch>
27 static string basedir
;
28 static string directory
;
30 [CCode (array_length
= false, array_null_terminated
= true)]
32 static string[] sources
;
33 [CCode (array_length
= false, array_null_terminated
= true)]
35 static string[] vapi_directories
;
36 static string vapi_filename
;
37 static string library
;
39 [CCode (array_length
= false, array_null_terminated
= true)]
41 static string[] packages
;
42 static string target_glib
;
44 static bool ccode_only
;
45 static string header_filename
;
46 static string internal_header_filename
;
47 static string internal_vapi_filename
;
48 static string includedir
;
49 static bool compile_only
;
53 static bool disable_assert
;
54 static bool enable_checking
;
55 static bool deprecated
;
56 static bool experimental
;
57 static bool non_null_experimental
;
58 static bool disable_dbus_transformation
;
59 static string cc_command
;
60 [CCode (array_length
= false, array_null_terminated
= true)]
62 static string[] cc_options
;
63 static string dump_tree
;
64 static bool save_temps
;
65 [CCode (array_length
= false, array_null_terminated
= true)]
67 static string[] defines
;
68 static bool quiet_mode
;
69 static bool verbose_mode
;
70 static string profile
;
72 private CodeContext context
;
74 const OptionEntry
[] options
= {
75 { "vapidir", 0, 0, OptionArg
.FILENAME_ARRAY
, ref vapi_directories
, "Look for package bindings in DIRECTORY", "DIRECTORY..." },
76 { "pkg", 0, 0, OptionArg
.STRING_ARRAY
, ref packages
, "Include binding for PACKAGE", "PACKAGE..." },
77 { "vapi", 0, 0, OptionArg
.FILENAME
, ref vapi_filename
, "Output VAPI file name", "FILE" },
78 { "library", 0, 0, OptionArg
.STRING
, ref library
, "Library name", "NAME" },
79 { "gir", 0, 0, OptionArg
.STRING
, ref gir
, "GObject-Introspection repository file name", "NAME-VERSION.gir" },
80 { "basedir", 'b', 0, OptionArg
.FILENAME
, ref basedir
, "Base source directory", "DIRECTORY" },
81 { "directory", 'd', 0, OptionArg
.FILENAME
, ref directory
, "Output directory", "DIRECTORY" },
82 { "version", 0, 0, OptionArg
.NONE
, ref version
, "Display version number", null },
83 { "ccode", 'C', 0, OptionArg
.NONE
, ref ccode_only
, "Output C code", null },
84 { "header", 'H', 0, OptionArg
.FILENAME
, ref header_filename
, "Output C header file", "FILE" },
85 { "includedir", 0, 0, OptionArg
.FILENAME
, ref includedir
, "Directory used to include the C header file", "DIRECTORY" },
86 { "internal-header", 'h', 0, OptionArg
.FILENAME
, ref internal_header_filename
, "Output internal C header file", "FILE" },
87 { "internal-vapi", 0, 0, OptionArg
.FILENAME
, ref internal_vapi_filename
, "Output vapi with internal api", "FILE" },
88 { "compile", 'c', 0, OptionArg
.NONE
, ref compile_only
, "Compile but do not link", null },
89 { "output", 'o', 0, OptionArg
.FILENAME
, ref output
, "Place output in file FILE", "FILE" },
90 { "debug", 'g', 0, OptionArg
.NONE
, ref debug
, "Produce debug information", null },
91 { "thread", 0, 0, OptionArg
.NONE
, ref thread
, "Enable multithreading support", null },
92 { "define", 'D', 0, OptionArg
.STRING_ARRAY
, ref defines
, "Define SYMBOL", "SYMBOL..." },
93 { "disable-assert", 0, 0, OptionArg
.NONE
, ref disable_assert
, "Disable assertions", null },
94 { "enable-checking", 0, 0, OptionArg
.NONE
, ref enable_checking
, "Enable additional run-time checks", null },
95 { "enable-deprecated", 0, 0, OptionArg
.NONE
, ref deprecated
, "Enable deprecated features", null },
96 { "enable-experimental", 0, 0, OptionArg
.NONE
, ref experimental
, "Enable experimental features", null },
97 { "enable-non-null-experimental", 0, 0, OptionArg
.NONE
, ref non_null_experimental
, "Enable experimental enhancements for non-null types", null },
98 { "disable-dbus-transformation", 0, 0, OptionArg
.NONE
, ref disable_dbus_transformation
, "Disable transformation of D-Bus member names", null },
99 { "cc", 0, 0, OptionArg
.STRING
, ref cc_command
, "Use COMMAND as C compiler command", "COMMAND" },
100 { "Xcc", 'X', 0, OptionArg
.STRING_ARRAY
, ref cc_options
, "Pass OPTION to the C compiler", "OPTION..." },
101 { "dump-tree", 0, 0, OptionArg
.FILENAME
, ref dump_tree
, "Write code tree to FILE", "FILE" },
102 { "save-temps", 0, 0, OptionArg
.NONE
, ref save_temps
, "Keep temporary files", null },
103 { "profile", 0, 0, OptionArg
.STRING
, ref profile
, "Use the given profile instead of the default", "PROFILE" },
104 { "quiet", 'q', 0, OptionArg
.NONE
, ref quiet_mode
, "Do not print messages to the console", null },
105 { "verbose", 'v', 0, OptionArg
.NONE
, ref verbose_mode
, "Print additional messages to the console", null },
106 { "target-glib", 0, 0, OptionArg
.STRING
, ref target_glib
, "Target version of glib for code generation", "MAJOR.MINOR" },
107 { "", 0, 0, OptionArg
.FILENAME_ARRAY
, ref sources
, null, "FILE..." },
111 private int quit () {
112 if (context
.report
.get_errors () == 0 && context
.report
.get_warnings () == 0) {
115 if (context
.report
.get_errors () == 0) {
117 stdout
.printf ("Compilation succeeded - %d warning(s)\n", context
.report
.get_warnings ());
122 stdout
.printf ("Compilation failed: %d error(s), %d warning(s)\n", context
.report
.get_errors (), context
.report
.get_warnings ());
128 private bool add_package (CodeContext context
, string pkg
) {
129 if (context
.has_package (pkg
)) {
130 // ignore multiple occurences of the same package
134 var package_path
= context
.get_package_path (pkg
, vapi_directories
);
136 if (package_path
== null) {
140 context
.add_package (pkg
);
142 context
.add_source_file (new
SourceFile (context
, package_path
, true));
144 var deps_filename
= Path
.build_filename (Path
.get_dirname (package_path
), "%s.deps".printf (pkg
));
145 if (FileUtils
.test (deps_filename
, FileTest
.EXISTS
)) {
149 FileUtils
.get_contents (deps_filename
, out deps_content
, out deps_len
);
150 foreach (string dep
in deps_content
.split ("\n")) {
153 if (!add_package (context
, dep
)) {
154 Report
.error (null, "%s, dependency of %s, not found in specified Vala API directories".printf (dep
, pkg
));
158 } catch (FileError e
) {
159 Report
.error (null, "Unable to read dependency file: %s".printf (e
.message
));
167 context
= new
CodeContext ();
168 CodeContext
.push (context
);
170 // default to build executable
171 if (!ccode_only
&& !compile_only
&& output
== null) {
172 // strip extension if there is one
173 // else we use the default output file of the C compiler
174 if (sources
[0].rchr (-1, '.') != null) {
175 long dot
= sources
[0].pointer_to_offset (sources
[0].rchr (-1, '.'));
176 output
= Path
.get_basename (sources
[0].substring (0, dot
));
180 context
.assert
= !disable_assert
;
181 context
.checking
= enable_checking
;
182 context
.deprecated
= deprecated
;
183 context
.experimental
= experimental
;
184 context
.non_null_experimental
= non_null_experimental
;
185 context
.dbus_transformation
= !disable_dbus_transformation
;
186 context
.report
.set_verbose_errors (!quiet_mode
);
187 context
.verbose_mode
= verbose_mode
;
189 context
.ccode_only
= ccode_only
;
190 context
.compile_only
= compile_only
;
191 context
.header_filename
= header_filename
;
192 context
.internal_header_filename
= internal_header_filename
;
193 context
.includedir
= includedir
;
194 context
.output
= output
;
195 if (basedir
== null) {
196 context
.basedir
= realpath (".");
198 context
.basedir
= realpath (basedir
);
200 if (directory
!= null) {
201 context
.directory
= realpath (directory
);
203 context
.directory
= context
.basedir
;
205 context
.debug
= debug
;
206 context
.thread
= thread
;
207 context
.save_temps
= save_temps
;
208 if (profile
== "posix") {
209 context
.profile
= Profile
.POSIX
;
210 context
.add_define ("POSIX");
211 } else if (profile
== "gobject-2.0" || profile
== "gobject" || profile
== null) {
213 context
.profile
= Profile
.GOBJECT
;
214 context
.add_define ("GOBJECT");
215 context
.add_define ("VALA_0_7_6_NEW_METHODS");
217 Report
.error (null, "Unknown profile %s".printf (profile
));
220 if (defines
!= null) {
221 foreach (string define
in defines
) {
222 context
.add_define (define
);
226 if (context
.profile
== Profile
.POSIX
) {
227 /* default package */
228 if (!add_package (context
, "posix")) {
229 Report
.error (null, "posix not found in specified Vala API directories");
231 } else if (context
.profile
== Profile
.GOBJECT
) {
234 if (target_glib
!= null && target_glib
.scanf ("%d.%d", out glib_major
, out glib_minor
) != 2) {
235 Report
.error (null, "Invalid format for --target-glib");
238 context
.target_glib_major
= glib_major
;
239 context
.target_glib_minor
= glib_minor
;
240 if (context
.target_glib_major
!= 2) {
241 Report
.error (null, "This version of valac only supports GLib 2");
244 /* default packages */
245 if (!add_package (context
, "glib-2.0")) {
246 Report
.error (null, "glib-2.0 not found in specified Vala API directories");
248 if (!add_package (context
, "gobject-2.0")) {
249 Report
.error (null, "gobject-2.0 not found in specified Vala API directories");
253 context
.codegen
= new
CCodeGenerator ();
255 if (packages
!= null) {
256 foreach (string package
in packages
) {
257 if (!add_package (context
, package
)) {
258 Report
.error (null, "%s not found in specified Vala API directories".printf (package
));
264 if (context
.report
.get_errors () > 0) {
268 foreach (string source
in sources
) {
269 if (FileUtils
.test (source
, FileTest
.EXISTS
)) {
270 var rpath
= realpath (source
);
271 if (source
.has_suffix (".vala") || source
.has_suffix (".gs")) {
272 var source_file
= new
SourceFile (context
, rpath
);
274 if (context
.profile
== Profile
.POSIX
) {
275 // import the Posix namespace by default (namespace of backend-specific standard library)
276 source_file
.add_using_directive (new
UsingDirective (new
UnresolvedSymbol (null, "Posix", null)));
277 } else if (context
.profile
== Profile
.GOBJECT
) {
278 // import the GLib namespace by default (namespace of backend-specific standard library)
279 source_file
.add_using_directive (new
UsingDirective (new
UnresolvedSymbol (null, "GLib", null)));
282 context
.add_source_file (source_file
);
283 } else if (source
.has_suffix (".vapi")) {
284 context
.add_source_file (new
SourceFile (context
, rpath
, true));
285 } else if (source
.has_suffix (".c")) {
286 context
.add_c_source_file (rpath
);
288 Report
.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (source
));
291 Report
.error (null, "%s not found".printf (source
));
296 if (context
.report
.get_errors () > 0) {
300 var parser
= new
Parser ();
301 parser
.parse (context
);
303 var genie_parser
= new Genie
.Parser ();
304 genie_parser
.parse (context
);
306 if (context
.report
.get_errors () > 0) {
310 var resolver
= new
SymbolResolver ();
311 resolver
.resolve (context
);
313 if (context
.report
.get_errors () > 0) {
317 var analyzer
= new
SemanticAnalyzer ();
318 analyzer
.analyze (context
);
320 if (!ccode_only
&& !compile_only
&& library
== null) {
321 // building program, require entry point
322 if (context
.entry_point
== null) {
323 Report
.error (null, "program does not contain a static `main' method");
327 if (dump_tree
!= null) {
328 var code_writer
= new
CodeWriter (true);
329 code_writer
.write_file (context
, dump_tree
);
332 if (context
.report
.get_errors () > 0) {
336 var flow_analyzer
= new
FlowAnalyzer ();
337 flow_analyzer
.analyze (context
);
339 if (context
.report
.get_errors () > 0) {
343 if (context
.non_null_experimental
) {
344 var null_checker
= new
NullChecker ();
345 null_checker
.check (context
);
347 if (context
.report
.get_errors () > 0) {
352 context
.codegen
.emit (context
);
354 if (context
.report
.get_errors () > 0) {
358 if (vapi_filename
== null && library
!= null) {
359 // keep backward compatibility with --library option
360 vapi_filename
= "%s.vapi".printf (library
);
363 if (vapi_filename
!= null) {
364 var interface_writer
= new
CodeWriter ();
366 // put .vapi file in current directory unless -d has been explicitly specified
367 if (directory
!= null && !Path
.is_absolute (vapi_filename
)) {
368 vapi_filename
= "%s%c%s".printf (context
.directory
, Path
.DIR_SEPARATOR
, vapi_filename
);
371 interface_writer
.write_file (context
, vapi_filename
);
374 if (library
!= null) {
376 if (context
.profile
== Profile
.GOBJECT
) {
377 long gir_len
= gir
.len ();
378 unowned
string? last_hyphen
= gir
.rchr (gir_len
, '-');
380 if (last_hyphen
== null || !gir
.has_suffix (".gir")) {
381 Report
.error (null, "GIR file name `%s' is not well-formed, expected NAME-VERSION.gir".printf (gir
));
383 long offset
= gir
.pointer_to_offset (last_hyphen
);
384 string gir_namespace
= gir
.substring (0, offset
);
385 string gir_version
= gir
.substring (offset
+ 1, gir_len
- offset
- 5);
386 gir_version
.canon ("0123456789.", '?');
387 if (gir_namespace
== "" || gir_version
== "" || !gir_version
[0].isdigit () || gir_version
.contains ("?")) {
388 Report
.error (null, "GIR file name `%s' is not well-formed, expected NAME-VERSION.gir".printf (gir
));
390 var gir_writer
= new
GIRWriter ();
392 // put .gir file in current directory unless -d has been explicitly specified
393 string gir_directory
= ".";
394 if (directory
!= null) {
395 gir_directory
= context
.directory
;
398 gir_writer
.write_file (context
, gir_directory
, gir_namespace
, gir_version
, library
);
408 if (internal_vapi_filename
!= null) {
409 if (internal_header_filename
== null ||
410 header_filename
== null) {
411 Report
.error (null, "--internal-vapi may only be used in combination with --header and --internal-header");
415 var interface_writer
= new
CodeWriter (false, true);
416 interface_writer
.set_cheader_override(header_filename
, internal_header_filename
);
417 string vapi_filename
= internal_vapi_filename
;
419 // put .vapi file in current directory unless -d has been explicitly specified
420 if (directory
!= null && !Path
.is_absolute (vapi_filename
)) {
421 vapi_filename
= "%s%c%s".printf (context
.directory
, Path
.DIR_SEPARATOR
, vapi_filename
);
424 interface_writer
.write_file (context
, vapi_filename
);
426 internal_vapi_filename
= null;
430 var ccompiler
= new
CCodeCompiler ();
431 if (cc_command
== null && Environment
.get_variable ("CC") != null) {
432 cc_command
= Environment
.get_variable ("CC");
434 if (cc_options
== null) {
435 ccompiler
.compile (context
, cc_command
, new
string[] { });
437 ccompiler
.compile (context
, cc_command
, cc_options
);
444 private static bool ends_with_dir_separator (string s
) {
445 return Path
.is_dir_separator (s
.offset (s
.len () - 1).get_char ());
448 /* ported from glibc */
449 private static string realpath (string name
) {
452 // start of path component
454 // end of path component
457 if (!Path
.is_absolute (name
)) {
459 rpath
= Environment
.get_current_dir ();
463 // set start after root
464 start
= end
= Path
.skip_root (name
);
467 rpath
= name
.substring (0, name
.pointer_to_offset (start
));
470 long root_len
= rpath
.pointer_to_offset (Path
.skip_root (rpath
));
472 for (; start
.get_char () != 0; start
= end
) {
473 // skip sequence of multiple path-separators
474 while (Path
.is_dir_separator (start
.get_char ())) {
475 start
= start
.next_char ();
478 // find end of path component
480 for (end
= start
; end
.get_char () != 0 && !Path
.is_dir_separator (end
.get_char ()); end
= end
.next_char ()) {
486 } else if (len
== 1 && start
.get_char () == '.') {
488 } else if (len
== 2 && start
.has_prefix ("..")) {
489 // back up to previous component, ignore if at root already
490 if (rpath
.len () > root_len
) {
492 rpath
= rpath
.substring (0, rpath
.len () - 1);
493 } while (!ends_with_dir_separator (rpath
));
496 if (!ends_with_dir_separator (rpath
)) {
497 rpath
+= Path
.DIR_SEPARATOR_S
;
500 rpath
+= start
.substring (0, len
);
504 if (rpath
.len () > root_len
&& ends_with_dir_separator (rpath
)) {
505 rpath
= rpath
.substring (0, rpath
.len () - 1);
508 if (Path
.DIR_SEPARATOR
!= '/') {
509 // don't use backslashes internally,
510 // to avoid problems in #include directives
511 string[] components
= rpath
.split ("\\");
512 rpath
= string.joinv ("/", components
);
518 static int main (string[] args
) {
520 var opt_context
= new
OptionContext ("- Vala Compiler");
521 opt_context
.set_help_enabled (true);
522 opt_context
.add_main_entries (options
, null);
523 opt_context
.parse (ref args
);
524 } catch (OptionError e
) {
525 stdout
.printf ("%s\n", e
.message
);
526 stdout
.printf ("Run '%s --help' to see a full list of available command line options.\n", args
[0]);
531 stdout
.printf ("Vala %s\n", Config
.PACKAGE_VERSION
);
535 if (sources
== null) {
536 stderr
.printf ("No source file specified.\n");
540 var compiler
= new
Compiler ();
541 return compiler
.run ();