vala: Add consts/methods to retrieve and check library version
[vala-gnome.git] / vala / valacodecontext.vala
blob65a5e2ab9adea13762197dcc2971a4cea71f3bf2
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 if (profile == Profile.POSIX) {
441 // import the Posix namespace by default (namespace of backend-specific standard library)
442 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "Posix", null));
443 source_file.add_using_directive (ns_ref);
444 root.add_using_directive (ns_ref);
445 } else if (profile == Profile.GOBJECT) {
446 // import the GLib namespace by default (namespace of backend-specific standard library)
447 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "GLib", null));
448 source_file.add_using_directive (ns_ref);
449 root.add_using_directive (ns_ref);
452 add_source_file (source_file);
453 } else if (filename.has_suffix (".vapi") || filename.has_suffix (".gir")) {
454 var source_file = new SourceFile (this, SourceFileType.PACKAGE, rpath, null, cmdline);
455 source_file.relative_filename = filename;
457 add_source_file (source_file);
458 } else if (filename.has_suffix (".c")) {
459 add_c_source_file (rpath);
460 } else if (filename.has_suffix (".h")) {
461 /* Ignore */
462 } else {
463 Report.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename));
464 return false;
467 return true;
471 * Visits the complete code tree file by file.
472 * It is possible to add new source files while visiting the tree.
474 * @param visitor the visitor to be called when traversing
476 public void accept (CodeVisitor visitor) {
477 root.accept (visitor);
479 // support queueing new source files
480 int index = 0;
481 while (index < source_files.size) {
482 var source_file = source_files[index];
483 source_file.accept (visitor);
484 index++;
489 * Resolve and analyze.
491 public void check () {
492 resolver.resolve (this);
494 if (report.get_errors () > 0) {
495 return;
498 analyzer.analyze (this);
500 if (report.get_errors () > 0) {
501 return;
504 flow_analyzer.analyze (this);
506 if (report.get_errors () > 0) {
507 return;
510 used_attr.check_unused (this);
513 public void add_define (string define) {
514 defines.add (define);
517 public bool is_defined (string define) {
518 return (define in defines);
521 public string? get_vapi_path (string pkg) {
522 var path = get_file_path (pkg + ".vapi", "vala" + Config.PACKAGE_SUFFIX + "/vapi", "vala/vapi", vapi_directories);
524 if (path == null) {
525 /* last chance: try the package compiled-in vapi dir */
526 var filename = Path.build_path ("/", Config.PACKAGE_DATADIR, "vapi", pkg + ".vapi");
527 if (FileUtils.test (filename, FileTest.EXISTS)) {
528 path = filename;
532 return path;
535 public string? get_gir_path (string gir) {
536 return get_file_path (gir + ".gir", "gir-1.0", null, gir_directories);
539 public string? get_gresource_path (string gresource, string resource) {
540 var filename = get_file_path (resource, null, null, { Path.get_dirname (gresource) });
541 if (filename == null) {
542 filename = get_file_path (resource, null, null, gresources_directories);
544 return filename;
548 * Returns the .metadata file associated with the given .gir file.
550 public string? get_metadata_path (string gir_filename) {
551 var basename = Path.get_basename (gir_filename);
552 var metadata_basename = "%s.metadata".printf (basename.substring (0, basename.length - ".gir".length));
554 // look into metadata directories
555 var metadata_filename = get_file_path (metadata_basename, null, null, metadata_directories);
556 if (metadata_filename != null) {
557 return metadata_filename;
560 // look into the same directory of .gir
561 metadata_filename = Path.build_path ("/", Path.get_dirname (gir_filename), metadata_basename);
562 if (FileUtils.test (metadata_filename, FileTest.EXISTS)) {
563 return metadata_filename;
566 return null;
569 string? get_file_path (string basename, string? versioned_data_dir, string? data_dir, string[] directories) {
570 string filename = null;
572 if (directories != null) {
573 foreach (unowned string dir in directories) {
574 filename = Path.build_path ("/", dir, basename);
575 if (FileUtils.test (filename, FileTest.EXISTS)) {
576 return filename;
581 if (data_dir != null) {
582 foreach (unowned string dir in Environment.get_system_data_dirs ()) {
583 filename = Path.build_path ("/", dir, data_dir, basename);
584 if (FileUtils.test (filename, FileTest.EXISTS)) {
585 return filename;
590 if (versioned_data_dir != null) {
591 foreach (unowned string dir in Environment.get_system_data_dirs ()) {
592 filename = Path.build_path ("/", dir, versioned_data_dir, basename);
593 if (FileUtils.test (filename, FileTest.EXISTS)) {
594 return filename;
599 return null;
602 public void write_dependencies (string filename) {
603 var stream = FileStream.open (filename, "w");
605 if (stream == null) {
606 Report.error (null, "unable to open `%s' for writing".printf (filename));
607 return;
610 stream.printf ("%s:", filename);
611 foreach (var src in source_files) {
612 if (src.file_type == SourceFileType.FAST && src.used) {
613 stream.printf (" %s", src.filename);
616 stream.printf ("\n\n");
619 private static bool ends_with_dir_separator (string s) {
620 return Path.is_dir_separator (s.get_char (s.length - 1));
624 * Returns canonicalized absolute pathname
625 * ported from glibc
627 * @param name the path being checked
628 * @return a canonicalized absolute pathname
630 public static string realpath (string name) {
631 string rpath;
633 // start of path component
634 weak string start;
635 // end of path component
636 weak string end;
638 if (!Path.is_absolute (name)) {
639 // relative path
640 rpath = Environment.get_current_dir ();
642 start = end = name;
643 } else {
644 // set start after root
645 start = end = Path.skip_root (name);
647 // extract root
648 rpath = name.substring (0, (int) ((char*) start - (char*) name));
651 long root_len = (long) ((char*) Path.skip_root (rpath) - (char*) rpath);
653 for (; start.get_char () != 0; start = end) {
654 // skip sequence of multiple path-separators
655 while (Path.is_dir_separator (start.get_char ())) {
656 start = start.next_char ();
659 // find end of path component
660 long len = 0;
661 for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) {
662 len++;
665 if (len == 0) {
666 break;
667 } else if (len == 1 && start.get_char () == '.') {
668 // do nothing
669 } else if (len == 2 && start.has_prefix ("..")) {
670 // back up to previous component, ignore if at root already
671 if (rpath.length > root_len) {
672 do {
673 rpath = rpath.substring (0, rpath.length - 1);
674 } while (!ends_with_dir_separator (rpath));
676 } else {
677 if (!ends_with_dir_separator (rpath)) {
678 rpath += Path.DIR_SEPARATOR_S;
681 // don't use len, substring works on bytes
682 rpath += start.substring (0, (long)((char*)end - (char*)start));
686 if (rpath.length > root_len && ends_with_dir_separator (rpath)) {
687 rpath = rpath.substring (0, rpath.length - 1);
690 if (Path.DIR_SEPARATOR != '/') {
691 // don't use backslashes internally,
692 // to avoid problems in #include directives
693 string[] components = rpath.split ("\\");
694 rpath = string.joinv ("/", components);
697 return rpath;
700 public bool pkg_config_exists (string package_name) {
701 string pc = pkg_config_command + " --exists " + package_name;
702 int exit_status;
704 try {
705 Process.spawn_command_line_sync (pc, null, null, out exit_status);
706 return (0 == exit_status);
707 } catch (SpawnError e) {
708 Report.error (null, e.message);
709 return false;
713 public string? pkg_config_modversion (string package_name) {
714 string pc = pkg_config_command + " --silence-errors --modversion " + package_name;
715 string? output = null;
716 int exit_status;
718 try {
719 Process.spawn_command_line_sync (pc, out output, null, out exit_status);
720 if (exit_status != 0) {
721 output = output[0:-1];
722 if (output == "") {
723 output = null;
726 } catch (SpawnError e) {
727 output = null;
730 return output;
733 public string? pkg_config_compile_flags (string package_name) {
734 string pc = pkg_config_command + " --cflags";
735 if (!compile_only) {
736 pc += " --libs";
738 pc += package_name;
740 string? output = null;
741 int exit_status;
743 try {
744 Process.spawn_command_line_sync (pc, out output, null, out exit_status);
745 if (exit_status != 0) {
746 Report.error (null, "%s exited with status %d".printf (pkg_config_command, exit_status));
747 return null;
749 } catch (SpawnError e) {
750 Report.error (null, e.message);
751 output = null;
754 return output;