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 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")) {
463 Report
.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename
));
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
481 while (index
< source_files
.size
) {
482 var source_file
= source_files
[index
];
483 source_file
.accept (visitor
);
489 * Resolve and analyze.
491 public void check () {
492 resolver
.resolve (this
);
494 if (report
.get_errors () > 0) {
498 analyzer
.analyze (this
);
500 if (report
.get_errors () > 0) {
504 flow_analyzer
.analyze (this
);
506 if (report
.get_errors () > 0) {
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
);
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
)) {
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
);
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
;
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
)) {
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
)) {
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
)) {
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
));
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
627 * @param name the path being checked
628 * @return a canonicalized absolute pathname
630 public static string realpath (string name
) {
633 // start of path component
635 // end of path component
638 if (!Path
.is_absolute (name
)) {
640 rpath
= Environment
.get_current_dir ();
644 // set start after root
645 start
= end
= Path
.skip_root (name
);
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
661 for (end
= start
; end
.get_char () != 0 && !Path
.is_dir_separator (end
.get_char ()); end
= end
.next_char ()) {
667 } else if (len
== 1 && start
.get_char () == '.') {
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
) {
673 rpath
= rpath
.substring (0, rpath
.length
- 1);
674 } while (!ends_with_dir_separator (rpath
));
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
);
700 public bool pkg_config_exists (string package_name
) {
701 string pc
= pkg_config_command
+ " --exists " + package_name
;
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
);
713 public string?
pkg_config_modversion (string package_name
) {
714 string pc
= pkg_config_command
+ " --silence-errors --modversion " + package_name
;
715 string? output
= null;
719 Process
.spawn_command_line_sync (pc
, out output
, null, out exit_status
);
720 if (exit_status
!= 0) {
721 output
= output
[0:-1];
726 } catch (SpawnError e
) {
733 public string?
pkg_config_compile_flags (string package_name
) {
734 string pc
= pkg_config_command
+ " --cflags";
740 string? output
= null;
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
));
749 } catch (SpawnError e
) {
750 Report
.error (null, e
.message
);