codegen: Custom abstract methods of GLib.Source are handled differently
[vala-gnome.git] / vala / valacodecontext.vala
blobd3955df70a6bdd35a6b5b44acb2328010ddb5772
1 /* valacodecontext.vala
3 * Copyright (C) 2006-2012 Jürg Billeter
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 * Author:
20 * Jürg Billeter <j@bitron.ch>
23 using GLib;
25 /**
26 * The root of the code tree.
28 public class Vala.CodeContext {
29 /**
30 * Enable run-time checks for programming errors.
32 public bool assert { get; set; }
34 /**
35 * Enable additional run-time checks such as type checks.
37 public bool checking { get; set; }
39 /**
40 * Do not warn when using deprecated features.
42 public bool deprecated { get; set; }
44 /**
45 * Hide the symbols marked as internal
47 public bool hide_internal { get; set; }
49 /**
50 * Do not check whether used symbols exist in local packages.
52 public bool since_check { get; set; }
54 /**
55 * Do not warn when using experimental features.
57 public bool experimental { get; set; }
59 /**
60 * Enable experimental enhancements for non-null types.
62 public bool experimental_non_null { get; set; }
64 /**
65 * Enable GObject creation tracing.
67 public bool gobject_tracing { get; set; }
69 /**
70 * Output C code, don't compile to object code.
72 public bool ccode_only { get; set; }
74 /**
75 * Command to run pkg-config.
77 public string pkg_config_command { get; set; default = "pkg-config"; }
79 /**
80 * Enable support for ABI stability.
82 public bool abi_stability { get; set; }
84 /**
85 * Output C header file.
87 public string? header_filename { get; set; }
89 /**
90 * Output internal C header file.
92 public string? internal_header_filename { get; set; }
94 public bool use_header { get; set; }
96 /**
97 * Base directory used for header_filename in the VAPIs.
99 public string? includedir { get; set; }
102 * Output symbols file.
104 public string? symbols_filename { get; set; }
107 * Compile but do not link.
109 public bool compile_only { get; set; }
112 * Output filename.
114 public string output { get; set; }
117 * Base source directory.
119 public string basedir { get; set; }
122 * Code output directory.
124 public string directory { get; set; }
127 * List of directories where to find .vapi files.
129 public string[] vapi_directories { get; set; default = {}; }
132 * List of directories where to find .gir files.
134 public string[] gir_directories { get; set; default = {}; }
137 * List of directories where to find .metadata files for .gir files.
139 public string[] metadata_directories { get; set; default = {}; }
142 * Produce debug information.
144 public bool debug { get; set; }
147 * Optimization level.
149 public int optlevel { get; set; }
152 * Enable memory profiler.
154 public bool mem_profiler { get; set; }
157 * Specifies the optional module initialization method.
159 public Method module_init_method { get; set; }
162 * Keep temporary files produced by the compiler.
164 public bool save_temps { get; set; }
166 public Profile profile { get; set; }
169 * Target major version number of glib for code generation.
171 public int target_glib_major { get; set; }
174 * Target minor version number of glib for code generation.
176 public int target_glib_minor { get; set; }
178 public bool verbose_mode { get; set; }
180 public bool version_header { get; set; }
182 public bool nostdpkg { get; set; }
184 public bool use_fast_vapi { get; set; }
187 * Include comments in generated vapi.
189 public bool vapi_comments { get; set; }
192 * Returns true if the target version of glib is greater than or
193 * equal to the specified version.
195 public bool require_glib_version (int major, int minor) {
196 return (target_glib_major > major) || (target_glib_major == major && target_glib_minor >= minor);
199 public bool save_csources {
200 get { return save_temps; }
203 public Report report { get; set; default = new Report ();}
205 public Method? entry_point { get; set; }
207 public string entry_point_name { get; set; }
209 public bool run_output { get; set; }
211 public string[] gresources { get; set; default = {}; }
213 public string[] gresources_directories { get; set; default = {}; }
215 private List<SourceFile> source_files = new ArrayList<SourceFile> ();
216 private List<string> c_source_files = new ArrayList<string> ();
217 private Namespace _root = new Namespace (null);
219 private List<string> packages = new ArrayList<string> (str_equal);
221 private Set<string> defines = new HashSet<string> (str_hash, str_equal);
223 static StaticPrivate context_stack_key = StaticPrivate ();
226 * The root namespace of the symbol tree.
228 public Namespace root {
229 get { return _root; }
232 public SymbolResolver resolver { get; private set; }
234 public SemanticAnalyzer analyzer { get; private set; }
236 public FlowAnalyzer flow_analyzer { get; private set; }
239 * The selected code generator.
241 public CodeGenerator codegen { get; set; }
244 * Mark attributes used by the compiler and report unused at the end.
246 public UsedAttr used_attr { get; set; }
248 public CodeContext () {
249 resolver = new SymbolResolver ();
250 analyzer = new SemanticAnalyzer ();
251 flow_analyzer = new FlowAnalyzer ();
252 used_attr = new UsedAttr ();
256 * Return the topmost context from the context stack.
258 public static CodeContext get () {
259 List<CodeContext>* context_stack = context_stack_key.get ();
261 return context_stack->get (context_stack->size - 1);
265 * Push the specified context to the context stack.
267 public static void push (CodeContext context) {
268 ArrayList<CodeContext>* context_stack = context_stack_key.get ();
269 if (context_stack == null) {
270 context_stack = new ArrayList<CodeContext> ();
271 context_stack_key.set (context_stack, null);
274 context_stack->add (context);
278 * Remove the topmost context from the context stack.
280 public static void pop () {
281 List<CodeContext>* context_stack = context_stack_key.get ();
283 context_stack->remove_at (context_stack->size - 1);
287 * Returns a copy of the list of source files.
289 * @return list of source files
291 public List<SourceFile> get_source_files () {
292 return source_files;
296 * Returns a copy of the list of C source files.
298 * @return list of C source files
300 public List<string> get_c_source_files () {
301 return c_source_files;
305 * Adds the specified file to the list of source files.
307 * @param file a source file
309 public void add_source_file (SourceFile file) {
310 source_files.add (file);
314 * Adds the specified file to the list of C source files.
316 * @param file a C source file
318 public void add_c_source_file (string file) {
319 c_source_files.add (file);
323 * Returns a copy of the list of used packages.
325 * @return list of used packages
327 public List<string> get_packages () {
328 return packages;
332 * Returns whether the specified package is being used.
334 * @param pkg a package name
335 * @return true if the specified package is being used
337 public bool has_package (string pkg) {
338 return packages.contains (pkg);
342 * Adds the specified package to the list of used packages.
344 * @param pkg a package name
346 public void add_package (string pkg) {
347 packages.add (pkg);
351 * Pull the specified package into the context.
352 * The method is tolerant if the package has been already loaded.
354 * @param pkg a package name
355 * @return false if the package could not be loaded
358 public bool add_external_package (string pkg) {
359 if (has_package (pkg)) {
360 // ignore multiple occurrences of the same package
361 return true;
364 // first try .vapi
365 var path = get_vapi_path (pkg);
366 if (path == null) {
367 // try with .gir
368 path = get_gir_path (pkg);
370 if (path == null) {
371 Report.error (null, "Package `%s' not found in specified Vala API directories or GObject-Introspection GIR directories".printf (pkg));
372 return false;
375 add_package (pkg);
377 add_source_file (new SourceFile (this, SourceFileType.PACKAGE, path));
379 if (verbose_mode) {
380 stdout.printf ("Loaded package `%s'\n", path);
383 var deps_filename = Path.build_path ("/", Path.get_dirname (path), pkg + ".deps");
384 if (!add_packages_from_file (deps_filename)) {
385 return false;
388 return true;
392 * Read the given filename and pull in packages.
393 * The method is tolerant if the file does not exist.
395 * @param filename a filename
396 * @return false if an error occurs while reading the file or if a package could not be added
398 public bool add_packages_from_file (string filename) {
399 if (!FileUtils.test (filename, FileTest.EXISTS)) {
400 return true;
403 try {
404 string contents;
405 FileUtils.get_contents (filename, out contents);
406 foreach (string package in contents.split ("\n")) {
407 package = package.strip ();
408 if (package != "") {
409 add_external_package (package);
412 } catch (FileError e) {
413 Report.error (null, "Unable to read dependency file: %s".printf (e.message));
414 return false;
417 return true;
421 * Add the specified source file to the context. Only .vala, .vapi, .gs,
422 * and .c extensions are supported.
424 * @param filename a filename
425 * @param is_source true to force adding the file as .vala or .gs
426 * @param cmdline true if the file came from the command line.
427 * @return false if the file is not recognized or the file does not exist
429 public bool add_source_filename (string filename, bool is_source = false, bool cmdline = false) {
430 if (!FileUtils.test (filename, FileTest.EXISTS)) {
431 Report.error (null, "%s not found".printf (filename));
432 return false;
435 var rpath = realpath (filename);
436 if (is_source || filename.has_suffix (".vala") || filename.has_suffix (".gs")) {
437 var source_file = new SourceFile (this, SourceFileType.SOURCE, rpath, null, cmdline);
438 source_file.relative_filename = filename;
440 // import the GLib namespace by default (namespace of backend-specific standard library)
441 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "GLib", null));
442 source_file.add_using_directive (ns_ref);
443 root.add_using_directive (ns_ref);
445 add_source_file (source_file);
446 } else if (filename.has_suffix (".vapi") || filename.has_suffix (".gir")) {
447 var source_file = new SourceFile (this, SourceFileType.PACKAGE, rpath, null, cmdline);
448 source_file.relative_filename = filename;
450 add_source_file (source_file);
451 } else if (filename.has_suffix (".c")) {
452 add_c_source_file (rpath);
453 } else if (filename.has_suffix (".h")) {
454 /* Ignore */
455 } else {
456 Report.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename));
457 return false;
460 return true;
464 * Visits the complete code tree file by file.
465 * It is possible to add new source files while visiting the tree.
467 * @param visitor the visitor to be called when traversing
469 public void accept (CodeVisitor visitor) {
470 root.accept (visitor);
472 // support queueing new source files
473 int index = 0;
474 while (index < source_files.size) {
475 var source_file = source_files[index];
476 source_file.accept (visitor);
477 index++;
482 * Resolve and analyze.
484 public void check () {
485 resolver.resolve (this);
487 if (report.get_errors () > 0) {
488 return;
491 analyzer.analyze (this);
493 if (report.get_errors () > 0) {
494 return;
497 flow_analyzer.analyze (this);
499 if (report.get_errors () > 0) {
500 return;
503 used_attr.check_unused (this);
506 public void add_define (string define) {
507 defines.add (define);
510 public bool is_defined (string define) {
511 return (define in defines);
514 public string? get_vapi_path (string pkg) {
515 var path = get_file_path (pkg + ".vapi", "vala" + Config.PACKAGE_SUFFIX + "/vapi", "vala/vapi", vapi_directories);
517 if (path == null) {
518 /* last chance: try the package compiled-in vapi dir */
519 var filename = Path.build_path ("/", Config.PACKAGE_DATADIR, "vapi", pkg + ".vapi");
520 if (FileUtils.test (filename, FileTest.EXISTS)) {
521 path = filename;
525 return path;
528 public string? get_gir_path (string gir) {
529 return get_file_path (gir + ".gir", "gir-1.0", null, gir_directories);
532 public string? get_gresource_path (string gresource, string resource) {
533 var filename = get_file_path (resource, null, null, { Path.get_dirname (gresource) });
534 if (filename == null) {
535 filename = get_file_path (resource, null, null, gresources_directories);
537 return filename;
541 * Returns the .metadata file associated with the given .gir file.
543 public string? get_metadata_path (string gir_filename) {
544 var basename = Path.get_basename (gir_filename);
545 var metadata_basename = "%s.metadata".printf (basename.substring (0, basename.length - ".gir".length));
547 // look into metadata directories
548 var metadata_filename = get_file_path (metadata_basename, null, null, metadata_directories);
549 if (metadata_filename != null) {
550 return metadata_filename;
553 // look into the same directory of .gir
554 metadata_filename = Path.build_path ("/", Path.get_dirname (gir_filename), metadata_basename);
555 if (FileUtils.test (metadata_filename, FileTest.EXISTS)) {
556 return metadata_filename;
559 return null;
562 string? get_file_path (string basename, string? versioned_data_dir, string? data_dir, string[] directories) {
563 string filename = null;
565 if (directories != null) {
566 foreach (unowned string dir in directories) {
567 filename = Path.build_path ("/", dir, basename);
568 if (FileUtils.test (filename, FileTest.EXISTS)) {
569 return filename;
574 if (data_dir != null) {
575 foreach (unowned string dir in Environment.get_system_data_dirs ()) {
576 filename = Path.build_path ("/", dir, data_dir, basename);
577 if (FileUtils.test (filename, FileTest.EXISTS)) {
578 return filename;
583 if (versioned_data_dir != null) {
584 foreach (unowned string dir in Environment.get_system_data_dirs ()) {
585 filename = Path.build_path ("/", dir, versioned_data_dir, basename);
586 if (FileUtils.test (filename, FileTest.EXISTS)) {
587 return filename;
592 return null;
595 public void write_dependencies (string filename) {
596 var stream = FileStream.open (filename, "w");
598 if (stream == null) {
599 Report.error (null, "unable to open `%s' for writing".printf (filename));
600 return;
603 stream.printf ("%s:", filename);
604 foreach (var src in source_files) {
605 if (src.file_type == SourceFileType.FAST && src.used) {
606 stream.printf (" %s", src.filename);
609 stream.printf ("\n\n");
612 private static bool ends_with_dir_separator (string s) {
613 return Path.is_dir_separator (s.get_char (s.length - 1));
617 * Returns canonicalized absolute pathname
618 * ported from glibc
620 * @param name the path being checked
621 * @return a canonicalized absolute pathname
623 public static string realpath (string name) {
624 string rpath;
626 // start of path component
627 weak string start;
628 // end of path component
629 weak string end;
631 if (!Path.is_absolute (name)) {
632 // relative path
633 rpath = Environment.get_current_dir ();
635 start = end = name;
636 } else {
637 // set start after root
638 start = end = Path.skip_root (name);
640 // extract root
641 rpath = name.substring (0, (int) ((char*) start - (char*) name));
644 long root_len = (long) ((char*) Path.skip_root (rpath) - (char*) rpath);
646 for (; start.get_char () != 0; start = end) {
647 // skip sequence of multiple path-separators
648 while (Path.is_dir_separator (start.get_char ())) {
649 start = start.next_char ();
652 // find end of path component
653 long len = 0;
654 for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) {
655 len++;
658 if (len == 0) {
659 break;
660 } else if (len == 1 && start.get_char () == '.') {
661 // do nothing
662 } else if (len == 2 && start.has_prefix ("..")) {
663 // back up to previous component, ignore if at root already
664 if (rpath.length > root_len) {
665 do {
666 rpath = rpath.substring (0, rpath.length - 1);
667 } while (!ends_with_dir_separator (rpath));
669 } else {
670 if (!ends_with_dir_separator (rpath)) {
671 rpath += Path.DIR_SEPARATOR_S;
674 // don't use len, substring works on bytes
675 rpath += start.substring (0, (long)((char*)end - (char*)start));
679 if (rpath.length > root_len && ends_with_dir_separator (rpath)) {
680 rpath = rpath.substring (0, rpath.length - 1);
683 if (Path.DIR_SEPARATOR != '/') {
684 // don't use backslashes internally,
685 // to avoid problems in #include directives
686 string[] components = rpath.split ("\\");
687 rpath = string.joinv ("/", components);
690 return rpath;
693 public bool pkg_config_exists (string package_name) {
694 string pc = pkg_config_command + " --exists " + package_name;
695 int exit_status;
697 try {
698 Process.spawn_command_line_sync (pc, null, null, out exit_status);
699 return (0 == exit_status);
700 } catch (SpawnError e) {
701 Report.error (null, e.message);
702 return false;
706 public string? pkg_config_modversion (string package_name) {
707 string pc = pkg_config_command + " --silence-errors --modversion " + package_name;
708 string? output = null;
709 int exit_status;
711 try {
712 Process.spawn_command_line_sync (pc, out output, null, out exit_status);
713 if (exit_status != 0) {
714 output = output[0:-1];
715 if (output == "") {
716 output = null;
719 } catch (SpawnError e) {
720 output = null;
723 return output;
726 public string? pkg_config_compile_flags (string package_name) {
727 string pc = pkg_config_command + " --cflags";
728 if (!compile_only) {
729 pc += " --libs";
731 pc += package_name;
733 string? output = null;
734 int exit_status;
736 try {
737 Process.spawn_command_line_sync (pc, out output, null, out exit_status);
738 if (exit_status != 0) {
739 Report.error (null, "%s exited with status %d".printf (pkg_config_command, exit_status));
740 return null;
742 } catch (SpawnError e) {
743 Report.error (null, e.message);
744 output = null;
747 return output;