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
20 * Jürg Billeter <j@bitron.ch>
26 * The root of the code tree.
28 public class Vala
.CodeContext
{
30 * Enable run-time checks for programming errors.
32 public bool assert
{ get; set; }
35 * Enable additional run-time checks such as type checks.
37 public bool checking
{ get; set; }
40 * Do not warn when using deprecated features.
42 public bool deprecated
{ get; set; }
45 * Hide the symbols marked as internal
47 public bool hide_internal
{ get; set; }
50 * Do not check whether used symbols exist in local packages.
52 public bool since_check
{ get; set; }
55 * Do not warn when using experimental features.
57 public bool experimental
{ get; set; }
60 * Enable experimental enhancements for non-null types.
62 public bool experimental_non_null
{ get; set; }
65 * Enable GObject creation tracing.
67 public bool gobject_tracing
{ get; set; }
70 * Output C code, don't compile to object code.
72 public bool ccode_only
{ get; set; }
75 * Command to run pkg-config.
77 public string pkg_config_command
{ get; set; default = "pkg-config"; }
80 * Enable support for ABI stability.
82 public bool abi_stability
{ get; set; }
85 * Output C header file.
87 public string? header_filename
{ get; set; }
90 * Output internal C header file.
92 public string? internal_header_filename
{ get; set; }
94 public bool use_header
{ get; set; }
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; }
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 () {
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 () {
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
) {
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
365 var path
= get_vapi_path (pkg
);
368 path
= get_gir_path (pkg
);
371 Report
.error (null, "Package `%s' not found in specified Vala API directories or GObject-Introspection GIR directories".printf (pkg
));
377 add_source_file (new
SourceFile (this
, SourceFileType
.PACKAGE
, path
));
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
)) {
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
)) {
405 FileUtils
.get_contents (filename
, out contents
);
406 foreach (string package
in contents
.split ("\n")) {
407 package
= package
.strip ();
409 add_external_package (package
);
412 } catch (FileError e
) {
413 Report
.error (null, "Unable to read dependency file: %s".printf (e
.message
));
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
));
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")) {
456 Report
.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename
));
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
474 while (index
< source_files
.size
) {
475 var source_file
= source_files
[index
];
476 source_file
.accept (visitor
);
482 * Resolve and analyze.
484 public void check () {
485 resolver
.resolve (this
);
487 if (report
.get_errors () > 0) {
491 analyzer
.analyze (this
);
493 if (report
.get_errors () > 0) {
497 flow_analyzer
.analyze (this
);
499 if (report
.get_errors () > 0) {
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
);
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
)) {
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
);
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
;
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
)) {
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
)) {
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
)) {
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
));
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
620 * @param name the path being checked
621 * @return a canonicalized absolute pathname
623 public static string realpath (string name
) {
626 // start of path component
628 // end of path component
631 if (!Path
.is_absolute (name
)) {
633 rpath
= Environment
.get_current_dir ();
637 // set start after root
638 start
= end
= Path
.skip_root (name
);
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
654 for (end
= start
; end
.get_char () != 0 && !Path
.is_dir_separator (end
.get_char ()); end
= end
.next_char ()) {
660 } else if (len
== 1 && start
.get_char () == '.') {
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
) {
666 rpath
= rpath
.substring (0, rpath
.length
- 1);
667 } while (!ends_with_dir_separator (rpath
));
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
);
693 public bool pkg_config_exists (string package_name
) {
694 string pc
= pkg_config_command
+ " --exists " + package_name
;
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
);
706 public string?
pkg_config_modversion (string package_name
) {
707 string pc
= pkg_config_command
+ " --silence-errors --modversion " + package_name
;
708 string? output
= null;
712 Process
.spawn_command_line_sync (pc
, out output
, null, out exit_status
);
713 if (exit_status
!= 0) {
714 output
= output
[0:-1];
719 } catch (SpawnError e
) {
726 public string?
pkg_config_compile_flags (string package_name
) {
727 string pc
= pkg_config_command
+ " --cflags";
733 string? output
= null;
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
));
742 } catch (SpawnError e
) {
743 Report
.error (null, e
.message
);