1 /* valacodecontext.vala
3 * Copyright (C) 2006-2009 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 * Do not warn when using experimental features.
47 public bool experimental
{ get; set; }
50 * Enable experimental enhancements for non-null types.
52 public bool experimental_non_null
{ get; set; }
55 * Output C code, don't compile to object code.
57 public bool ccode_only
{ get; set; }
60 * Output C header file.
62 public string? header_filename
{ get; set; }
65 * Output internal C header file.
67 public string? internal_header_filename
{ get; set; }
69 public bool use_header
{ get; set; }
72 * Base directory used for header_filename in the VAPIs.
74 public string? includedir
{ get; set; }
77 * Output symbols file.
79 public string? symbols_filename
{ get; set; }
82 * Compile but do not link.
84 public bool compile_only
{ get; set; }
89 public string output
{ get; set; }
92 * Base source directory.
94 public string basedir
{ get; set; }
97 * Code output directory.
99 public string directory
{ get; set; }
102 * List of directories where to find .vapi files.
104 public string[] vapi_directories
;
107 * List of directories where to find .gir files.
109 public string[] gir_directories
;
112 * List of directories where to find .metadata files for .gir files.
114 public string[] metadata_directories
;
117 * Produce debug information.
119 public bool debug
{ get; set; }
122 * Optimization level.
124 public int optlevel
{ get; set; }
127 * Enable multithreading support.
129 public bool thread
{ get; set; }
132 * Enable memory profiler.
134 public bool mem_profiler
{ get; set; }
137 * Specifies the optional module initialization method.
139 public Method module_init_method
{ get; set; }
142 * Keep temporary files produced by the compiler.
144 public bool save_temps
{ get; set; }
146 public Profile profile
{ get; set; }
149 * Target major version number of glib for code generation.
151 public int target_glib_major
{ get; set; }
154 * Target minor version number of glib for code generation.
156 public int target_glib_minor
{ get; set; }
158 public bool verbose_mode
{ get; set; }
160 public bool version_header
{ get; set; }
162 public bool nostdpkg
{ get; set; }
165 * Returns true if the target version of glib is greater than or
166 * equal to the specified version.
168 public bool require_glib_version (int major
, int minor
) {
169 return (target_glib_major
> major
) || (target_glib_major
== major
&& target_glib_minor
>= minor
);
172 public bool save_csources
{
173 get { return save_temps
; }
176 public Report report
{ get; set; default = new
Report ();}
178 public Method? entry_point
{ get; set; }
180 public string entry_point_name
{ get; set; }
182 public bool run_output
{ get; set; }
184 private List
<SourceFile
> source_files
= new ArrayList
<SourceFile
> ();
185 private List
<string> c_source_files
= new ArrayList
<string> ();
186 private Namespace _root
= new
Namespace (null);
188 private List
<string> packages
= new ArrayList
<string> (str_equal
);
190 private Set
<string> defines
= new HashSet
<string> (str_hash
, str_equal
);
192 static StaticPrivate context_stack_key
= StaticPrivate ();
195 * The root namespace of the symbol tree.
197 * @return root namespace
199 public Namespace root
{
200 get { return _root
; }
203 public SymbolResolver resolver
{ get; private set; }
205 public SemanticAnalyzer analyzer
{ get; private set; }
207 public FlowAnalyzer flow_analyzer
{ get; private set; }
210 * The selected code generator.
212 public CodeGenerator codegen
{ get; set; }
214 public CodeContext () {
215 resolver
= new
SymbolResolver ();
216 analyzer
= new
SemanticAnalyzer ();
217 flow_analyzer
= new
FlowAnalyzer ();
221 * Return the topmost context from the context stack.
223 public static CodeContext
get () {
224 List
<CodeContext
>* context_stack
= context_stack_key
.get ();
226 return context_stack
->get (context_stack
->size
- 1);
230 * Push the specified context to the context stack.
232 public static void push (CodeContext context
) {
233 ArrayList
<CodeContext
>* context_stack
= context_stack_key
.get ();
234 if (context_stack
== null) {
235 context_stack
= new ArrayList
<CodeContext
> ();
236 context_stack_key
.set (context_stack
, null);
239 context_stack
->add (context
);
243 * Remove the topmost context from the context stack.
245 public static void pop () {
246 List
<CodeContext
>* context_stack
= context_stack_key
.get ();
248 context_stack
->remove_at (context_stack
->size
- 1);
252 * Returns a copy of the list of source files.
254 * @return list of source files
256 public List
<SourceFile
> get_source_files () {
261 * Returns a copy of the list of C source files.
263 * @return list of C source files
265 public List
<string> get_c_source_files () {
266 return c_source_files
;
270 * Adds the specified file to the list of source files.
272 * @param file a source file
274 public void add_source_file (SourceFile file
) {
275 source_files
.add (file
);
279 * Adds the specified file to the list of C source files.
281 * @param file a C source file
283 public void add_c_source_file (string file
) {
284 c_source_files
.add (file
);
288 * Returns a copy of the list of used packages.
290 * @return list of used packages
292 public List
<string> get_packages () {
297 * Returns whether the specified package is being used.
299 * @param pkg a package name
300 * @return true if the specified package is being used
302 public bool has_package (string pkg
) {
303 return packages
.contains (pkg
);
307 * Adds the specified package to the list of used packages.
309 * @param pkg a package name
311 public void add_package (string pkg
) {
316 * Pull the specified package into the context.
317 * The method is tolerant if the package has been already loaded.
319 * @param pkg a package name
320 * @return false if the package could not be loaded
323 public bool add_external_package (string pkg
) {
324 if (has_package (pkg
)) {
325 // ignore multiple occurences of the same package
330 var path
= get_vapi_path (pkg
);
333 path
= get_gir_path (pkg
);
336 Report
.error (null, "Package `%s' not found in specified Vala API directories or GObject-Introspection GIR directories".printf (pkg
));
342 add_source_file (new
SourceFile (this
, SourceFileType
.PACKAGE
, path
));
344 var deps_filename
= Path
.build_filename (Path
.get_dirname (path
), "%s.deps".printf (pkg
));
345 if (!add_packages_from_file (deps_filename
)) {
353 * Read the given filename and pull in packages.
354 * The method is tolerant if the file does not exist.
356 * @param filename a filanem
357 * @return false if an error occurs while reading the file or if a package could not be added
359 public bool add_packages_from_file (string filename
) {
360 if (!FileUtils
.test (filename
, FileTest
.EXISTS
)) {
366 FileUtils
.get_contents (filename
, out contents
);
367 foreach (string package
in contents
.split ("\n")) {
368 package
= package
.strip ();
370 add_external_package (package
);
373 } catch (FileError e
) {
374 Report
.error (null, "Unable to read dependency file: %s".printf (e
.message
));
382 * Add the specified source file to the context. Only .vala, .vapi, .gs,
383 * and .c extensions are supported.
385 * @param filename a filename
386 * @param is_source true to force adding the file as .vala or .gs
387 * @return false if the file is not recognized or the file does not exist
389 public bool add_source_filename (string filename
, bool is_source
= false) {
390 if (!FileUtils
.test (filename
, FileTest
.EXISTS
)) {
391 Report
.error (null, "%s not found".printf (filename
));
395 var rpath
= realpath (filename
);
396 if (is_source
|| filename
.has_suffix (".vala") || filename
.has_suffix (".gs")) {
397 var source_file
= new
SourceFile (this
, SourceFileType
.SOURCE
, rpath
);
398 source_file
.relative_filename
= filename
;
400 if (profile
== Profile
.POSIX
) {
401 // import the Posix namespace by default (namespace of backend-specific standard library)
402 var ns_ref
= new
UsingDirective (new
UnresolvedSymbol (null, "Posix", null));
403 source_file
.add_using_directive (ns_ref
);
404 root
.add_using_directive (ns_ref
);
405 } else if (profile
== Profile
.GOBJECT
) {
406 // import the GLib namespace by default (namespace of backend-specific standard library)
407 var ns_ref
= new
UsingDirective (new
UnresolvedSymbol (null, "GLib", null));
408 source_file
.add_using_directive (ns_ref
);
409 root
.add_using_directive (ns_ref
);
410 } else if (profile
== Profile
.DOVA
) {
411 // import the Dova namespace by default (namespace of backend-specific standard library)
412 var ns_ref
= new
UsingDirective (new
UnresolvedSymbol (null, "Dova", null));
413 source_file
.add_using_directive (ns_ref
);
414 root
.add_using_directive (ns_ref
);
417 add_source_file (source_file
);
418 } else if (filename
.has_suffix (".vapi") || filename
.has_suffix (".gir")) {
419 var source_file
= new
SourceFile (this
, SourceFileType
.PACKAGE
, rpath
);
420 source_file
.relative_filename
= filename
;
422 add_source_file (source_file
);
423 // look for a local .deps
424 var deps_filename
= "%s.deps".printf (filename
.substring (0, filename
.length
- ".vapi".length
));
425 if (!add_packages_from_file (deps_filename
)) {
428 } else if (filename
.has_suffix (".c")) {
429 add_c_source_file (rpath
);
431 Report
.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename
));
439 * Visits the complete code tree file by file.
440 * It is possible to add new source files while visiting the tree.
442 * @param visitor the visitor to be called when traversing
444 public void accept (CodeVisitor visitor
) {
445 root
.accept (visitor
);
447 // support queueing new source files
449 while (index
< source_files
.size
) {
450 var source_file
= source_files
[index
];
451 source_file
.accept (visitor
);
457 * Resolve and analyze.
459 public void check () {
460 resolver
.resolve (this
);
462 if (report
.get_errors () > 0) {
466 analyzer
.analyze (this
);
468 if (report
.get_errors () > 0) {
472 flow_analyzer
.analyze (this
);
475 public void add_define (string define
) {
476 defines
.add (define
);
479 public bool is_defined (string define
) {
480 return (define
in defines
);
483 public string?
get_vapi_path (string pkg
) {
484 var path
= get_file_path (pkg
+ ".vapi", "vala" + Config
.PACKAGE_SUFFIX
+ "/vapi", "vala/vapi", vapi_directories
);
487 /* last chance: try the package compiled-in vapi dir */
488 var filename
= Path
.build_filename (Config
.PACKAGE_DATADIR
, "vapi", pkg
+ ".vapi");
489 if (FileUtils
.test (filename
, FileTest
.EXISTS
)) {
497 public string?
get_gir_path (string gir
) {
498 return get_file_path (gir
+ ".gir", "gir-1.0", null, gir_directories
);
502 * Returns the .metadata file associated with the given .gir file.
504 public string?
get_metadata_path (string gir_filename
) {
505 var basename
= Path
.get_basename (gir_filename
);
506 var metadata_basename
= "%s.metadata".printf (basename
.substring (0, basename
.length
- ".gir".length
));
508 // look into metadata directories
509 var metadata_filename
= get_file_path (metadata_basename
, null, null, metadata_directories
);
510 if (metadata_filename
!= null) {
511 return metadata_filename
;
514 // look into the same directory of .gir
515 metadata_filename
= Path
.build_filename (Path
.get_dirname (gir_filename
), metadata_basename
);
516 if (FileUtils
.test (metadata_filename
, FileTest
.EXISTS
)) {
517 return metadata_filename
;
523 string?
get_file_path (string basename
, string? versioned_data_dir
, string? data_dir
, string[] directories
) {
524 string filename
= null;
526 if (directories
!= null) {
527 foreach (string dir
in directories
) {
528 filename
= Path
.build_filename (dir
, basename
);
529 if (FileUtils
.test (filename
, FileTest
.EXISTS
)) {
535 if (versioned_data_dir
!= null) {
536 foreach (string dir
in Environment
.get_system_data_dirs ()) {
537 filename
= Path
.build_filename (dir
, versioned_data_dir
, basename
);
538 if (FileUtils
.test (filename
, FileTest
.EXISTS
)) {
544 if (data_dir
!= null) {
545 foreach (string dir
in Environment
.get_system_data_dirs ()) {
546 filename
= Path
.build_filename (dir
, data_dir
, basename
);
547 if (FileUtils
.test (filename
, FileTest
.EXISTS
)) {
556 public void write_dependencies (string filename
) {
557 var stream
= FileStream
.open (filename
, "w");
559 if (stream
== null) {
560 Report
.error (null, "unable to open `%s' for writing".printf (filename
));
564 stream
.printf ("%s:", filename
);
565 foreach (var src
in source_files
) {
566 if (src
.file_type
== SourceFileType
.FAST
&& src
.used
) {
567 stream
.printf (" %s", src
.filename
);
570 stream
.printf ("\n\n");
573 private static bool ends_with_dir_separator (string s
) {
574 return Path
.is_dir_separator (s
.get_char (s
.length
- 1));
577 /* ported from glibc */
578 public static string realpath (string name
) {
581 // start of path component
583 // end of path component
586 if (!Path
.is_absolute (name
)) {
588 rpath
= Environment
.get_current_dir ();
592 // set start after root
593 start
= end
= Path
.skip_root (name
);
596 rpath
= name
.substring (0, (int) ((char*) start
- (char*) name
));
599 long root_len
= (long) ((char*) Path
.skip_root (rpath
) - (char*) rpath
);
601 for (; start
.get_char () != 0; start
= end
) {
602 // skip sequence of multiple path-separators
603 while (Path
.is_dir_separator (start
.get_char ())) {
604 start
= start
.next_char ();
607 // find end of path component
609 for (end
= start
; end
.get_char () != 0 && !Path
.is_dir_separator (end
.get_char ()); end
= end
.next_char ()) {
615 } else if (len
== 1 && start
.get_char () == '.') {
617 } else if (len
== 2 && start
.has_prefix ("..")) {
618 // back up to previous component, ignore if at root already
619 if (rpath
.length
> root_len
) {
621 rpath
= rpath
.substring (0, rpath
.length
- 1);
622 } while (!ends_with_dir_separator (rpath
));
625 if (!ends_with_dir_separator (rpath
)) {
626 rpath
+= Path
.DIR_SEPARATOR_S
;
629 rpath
+= start
.substring (0, len
);
633 if (rpath
.length
> root_len
&& ends_with_dir_separator (rpath
)) {
634 rpath
= rpath
.substring (0, rpath
.length
- 1);
637 if (Path
.DIR_SEPARATOR
!= '/') {
638 // don't use backslashes internally,
639 // to avoid problems in #include directives
640 string[] components
= rpath
.split ("\\");
641 rpath
= string.joinv ("/", components
);