valacodecontext.vala 15.9 KB
Newer Older
1 2
/* valacodecontext.vala
 *
3
 * Copyright (C) 2006-2012  Jürg Billeter
4 5 6 7
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
8
 * version 2.1 of the License, or (at your option) any later version.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 */

using GLib;

25 26 27
/**
 * The root of the code tree.
 */
28
public class Vala.CodeContext {
29 30 31 32 33 34
	/**
	 * Enable run-time checks for programming errors.
	 */
	public bool assert { get; set; }

	/**
35
	 * Enable additional run-time checks such as type checks.
36 37 38
	 */
	public bool checking { get; set; }

39 40 41 42 43
	/**
	 * Do not warn when using deprecated features.
	 */
	public bool deprecated { get; set; }

44 45 46 47 48
	/**
	 * Hide the symbols marked as internal
	 */
	public bool hide_internal { get; set; }

49 50 51 52 53
	/**
	 * Do not warn when using experimental features.
	 */
	public bool experimental { get; set; }

54 55 56
	/**
	 * Enable experimental enhancements for non-null types.
	 */
57
	public bool experimental_non_null { get; set; }
58

59 60 61 62 63
	/**
	 * Enable GObject creation tracing.
	 */
	public bool gobject_tracing { get; set; }

64 65 66 67 68
	/**
	 * Output C code, don't compile to object code.
	 */
	public bool ccode_only { get; set; }

69 70 71
	/**
	 * Output C header file.
	 */
72 73 74 75 76 77
	public string? header_filename { get; set; }

	/**
	 * Output internal C header file.
	 */
	public string? internal_header_filename { get; set; }
78

79 80
	public bool use_header { get; set; }

81 82 83 84 85
	/**
	 * Base directory used for header_filename in the VAPIs.
	 */
	public string? includedir { get; set; }

86 87 88 89 90
	/**
	 * Output symbols file.
	 */
	public string? symbols_filename { get; set; }

91 92 93 94 95 96 97 98 99 100
	/**
	 * Compile but do not link.
	 */
	public bool compile_only { get; set; }

	/**
	 * Output filename.
	 */
	public string output { get; set; }

101 102 103 104 105
	/**
	 * Base source directory.
	 */
	public string basedir { get; set; }

106 107 108 109 110
	/**
	 * Code output directory.
	 */
	public string directory { get; set; }

111 112 113 114 115 116 117 118 119 120
	/**
	 * List of directories where to find .vapi files.
	 */
	public string[] vapi_directories;

	/**
	 * List of directories where to find .gir files.
	 */
	public string[] gir_directories;

121 122 123 124 125
	/**
	 * List of directories where to find .metadata files for .gir files.
	 */
	public string[] metadata_directories;

126 127 128 129 130 131 132 133 134 135
	/**
	 * Produce debug information.
	 */
	public bool debug { get; set; }

	/**
	 * Optimization level.
	 */
	public int optlevel { get; set; }

136 137 138 139 140
	/**
	 * Enable multithreading support.
	 */
	public bool thread { get; set; }

141 142 143 144 145
	/**
	 * Enable memory profiler.
	 */
	public bool mem_profiler { get; set; }

146 147 148 149 150
	/**
	 * Specifies the optional module initialization method.
	 */
	public Method module_init_method { get; set; }

151 152 153 154 155
	/**
	 * Keep temporary files produced by the compiler.
	 */
	public bool save_temps { get; set; }

156 157
	public Profile profile { get; set; }

158 159 160 161 162 163 164 165 166 167
	/**
	 * Target major version number of glib for code generation.
	 */
	public int target_glib_major { get; set; }

	/**
	 * Target minor version number of glib for code generation.
	 */
	public int target_glib_minor { get; set; }

168 169
	public bool verbose_mode { get; set; }

170 171
	public bool version_header { get; set; }

172 173
	public bool nostdpkg { get; set; }

174 175
	public bool use_fast_vapi { get; set; }

176 177 178 179 180
	/**
	 * Include comments in generated vapi.
	 */
	public bool vapi_comments { get; set; }

181 182 183 184 185 186 187 188
	/**
	 * Returns true if the target version of glib is greater than or 
	 * equal to the specified version.
	 */
	public bool require_glib_version (int major, int minor) {
		return (target_glib_major > major) || (target_glib_major == major && target_glib_minor >= minor);
	}

189 190 191 192
	public bool save_csources {
		get { return save_temps; }
	}

193 194
	public Report report { get; set; default = new Report ();}

195 196
	public Method? entry_point { get; set; }

197 198
	public string entry_point_name { get; set; }

199 200
	public bool run_output { get; set; }

201 202
	public string[] gresources;

203 204
	private List<SourceFile> source_files = new ArrayList<SourceFile> ();
	private List<string> c_source_files = new ArrayList<string> ();
205
	private Namespace _root = new Namespace (null);
206

207
	private List<string> packages = new ArrayList<string> (str_equal);
208

209 210
	private Set<string> defines = new HashSet<string> (str_hash, str_equal);

211
	static StaticPrivate context_stack_key = StaticPrivate ();
212

213
	/**
214
	 * The root namespace of the symbol tree.
215 216 217
	 *
	 * @return root namespace
	 */
218
	public Namespace root {
219
		get { return _root; }
220
	}
221

222 223 224 225 226 227
	public SymbolResolver resolver { get; private set; }

	public SemanticAnalyzer analyzer { get; private set; }

	public FlowAnalyzer flow_analyzer { get; private set; }

228 229 230
	/**
	 * The selected code generator.
	 */
231
	public CodeGenerator codegen { get; set; }
232

233
	public CodeContext () {
234 235 236
		resolver = new SymbolResolver ();
		analyzer = new SemanticAnalyzer ();
		flow_analyzer = new FlowAnalyzer ();
237 238
	}

239 240 241 242
	/**
	 * Return the topmost context from the context stack.
	 */
	public static CodeContext get () {
243
		List<CodeContext>* context_stack = context_stack_key.get ();
244 245

		return context_stack->get (context_stack->size - 1);
246 247 248 249 250 251
	}

	/**
	 * Push the specified context to the context stack.
	 */
	public static void push (CodeContext context) {
252
		ArrayList<CodeContext>* context_stack = context_stack_key.get ();
253 254
		if (context_stack == null) {
			context_stack = new ArrayList<CodeContext> ();
255
			context_stack_key.set (context_stack, null);
256
		}
257 258

		context_stack->add (context);
259 260 261 262 263 264
	}

	/**
	 * Remove the topmost context from the context stack.
	 */
	public static void pop () {
265
		List<CodeContext>* context_stack = context_stack_key.get ();
266 267

		context_stack->remove_at (context_stack->size - 1);
268 269
	}

270 271 272 273 274
	/**
	 * Returns a copy of the list of source files.
	 *
	 * @return list of source files
	 */
275
	public List<SourceFile> get_source_files () {
276
		return source_files;
277
	}
278 279 280 281 282 283

	/**
	 * Returns a copy of the list of C source files.
	 *
	 * @return list of C source files
	 */
284
	public List<string> get_c_source_files () {
285
		return c_source_files;
286
	}
287 288 289 290 291 292
	
	/**
	 * Adds the specified file to the list of source files.
	 *
	 * @param file a source file
	 */
293
	public void add_source_file (SourceFile file) {
294
		source_files.add (file);
295
	}
296

297 298 299 300 301
	/**
	 * Adds the specified file to the list of C source files.
	 *
	 * @param file a C source file
	 */
302
	public void add_c_source_file (string file) {
303 304 305
		c_source_files.add (file);
	}

306 307 308 309 310
	/**
	 * Returns a copy of the list of used packages.
	 *
	 * @return list of used packages
	 */
311
	public List<string> get_packages () {
312
		return packages;
313 314 315 316 317 318 319 320
	}

	/**
	 * Returns whether the specified package is being used.
	 *
	 * @param pkg a package name
	 * @return    true if the specified package is being used
	 */
321
	public bool has_package (string pkg) {
322
		return packages.contains (pkg);
323 324 325 326 327 328 329
	}

	/**
	 * Adds the specified package to the list of used packages.
	 *
	 * @param pkg a package name
	 */
330
	public void add_package (string pkg) {
331
		packages.add (pkg);
332 333
	}

334 335 336 337 338 339 340 341 342 343
	/**
	 * Pull the specified package into the context.
	 * The method is tolerant if the package has been already loaded.
	 *
	 * @param pkg a package name
	 * @return false if the package could not be loaded
	 *
	 */
	public bool add_external_package (string pkg) {
		if (has_package (pkg)) {
344
			// ignore multiple occurrences of the same package
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
			return true;
		}

		// first try .vapi
		var path = get_vapi_path (pkg);
		if (path == null) {
			// try with .gir
			path = get_gir_path (pkg);
		}
		if (path == null) {
			Report.error (null, "Package `%s' not found in specified Vala API directories or GObject-Introspection GIR directories".printf (pkg));
			return false;
		}

		add_package (pkg);

		add_source_file (new SourceFile (this, SourceFileType.PACKAGE, path));

363 364 365 366
		if (verbose_mode) {
			stdout.printf ("Loaded package `%s'\n", path);
		}

367
		var deps_filename = Path.build_path ("/", Path.get_dirname (path), pkg + ".deps");
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
		if (!add_packages_from_file (deps_filename)) {
			return false;
		}

		return true;
	}

	/**
	 * Read the given filename and pull in packages.
	 * The method is tolerant if the file does not exist.
	 *
	 * @param filename a filanem
	 * @return false if an error occurs while reading the file or if a package could not be added
	 */
	public bool add_packages_from_file (string filename) {
		if (!FileUtils.test (filename, FileTest.EXISTS)) {
			return true;
		}

		try {
			string contents;
			FileUtils.get_contents (filename, out contents);
			foreach (string package in contents.split ("\n")) {
				package = package.strip ();
				if (package != "") {
					add_external_package (package);
				}
			}
		} catch (FileError e) {
			Report.error (null, "Unable to read dependency file: %s".printf (e.message));
			return false;
		}

		return true;
	}

404 405 406 407 408 409
	/**
	 * Add the specified source file to the context. Only .vala, .vapi, .gs,
	 * and .c extensions are supported.
	 *
	 * @param filename a filename
	 * @param is_source true to force adding the file as .vala or .gs
410
	 * @param cmdline true if the file came from the command line.
411 412
	 * @return false if the file is not recognized or the file does not exist
	 */
413
	public bool add_source_filename (string filename, bool is_source = false, bool cmdline = false) {
414 415 416 417 418 419 420
		if (!FileUtils.test (filename, FileTest.EXISTS)) {
			Report.error (null, "%s not found".printf (filename));
			return false;
		}

		var rpath = realpath (filename);
		if (is_source || filename.has_suffix (".vala") || filename.has_suffix (".gs")) {
421
			var source_file = new SourceFile (this, SourceFileType.SOURCE, rpath, null, cmdline);
422 423
			source_file.relative_filename = filename;

Jürg Billeter's avatar
Jürg Billeter committed
424 425 426 427
			// import the GLib namespace by default (namespace of backend-specific standard library)
			var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "GLib", null));
			source_file.add_using_directive (ns_ref);
			root.add_using_directive (ns_ref);
428 429 430

			add_source_file (source_file);
		} else if (filename.has_suffix (".vapi") || filename.has_suffix (".gir")) {
431
			var source_file = new SourceFile (this, SourceFileType.PACKAGE, rpath, null, cmdline);
432 433 434 435 436
			source_file.relative_filename = filename;

			add_source_file (source_file);
		} else if (filename.has_suffix (".c")) {
			add_c_source_file (rpath);
437 438
		} else if (filename.has_suffix (".h")) {
			/* Ignore */
439 440 441 442 443 444 445 446
		} else {
			Report.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename));
			return false;
		}

		return true;
	}

447 448
	/**
	 * Visits the complete code tree file by file.
449
	 * It is possible to add new source files while visiting the tree.
450 451 452
	 *
	 * @param visitor the visitor to be called when traversing
	 */
453
	public void accept (CodeVisitor visitor) {
454
		root.accept (visitor);
455

456 457 458 459 460 461
		// support queueing new source files
		int index = 0;
		while (index < source_files.size) {
			var source_file = source_files[index];
			source_file.accept (visitor);
			index++;
462
		}
463
	}
464

465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483
	/**
	 * Resolve and analyze.
	 */
	public void check () {
		resolver.resolve (this);

		if (report.get_errors () > 0) {
			return;
		}

		analyzer.analyze (this);

		if (report.get_errors () > 0) {
			return;
		}

		flow_analyzer.analyze (this);
	}

484 485 486 487 488 489 490 491
	public void add_define (string define) {
		defines.add (define);
	}

	public bool is_defined (string define) {
		return (define in defines);
	}

492 493
	public string? get_vapi_path (string pkg) {
		var path = get_file_path (pkg + ".vapi", "vala" + Config.PACKAGE_SUFFIX + "/vapi", "vala/vapi", vapi_directories);
494 495 496

		if (path == null) {
			/* last chance: try the package compiled-in vapi dir */
497
			var filename = Path.build_path ("/", Config.PACKAGE_DATADIR, "vapi", pkg + ".vapi");
498 499 500 501 502 503 504 505
			if (FileUtils.test (filename, FileTest.EXISTS)) {
				path = filename;
			}
		}

		return path;
	}

506 507
	public string? get_gir_path (string gir) {
		return get_file_path (gir + ".gir", "gir-1.0", null, gir_directories);
508 509
	}

510 511 512 513 514 515 516 517 518 519 520 521 522 523
	/*
	 * Returns the .metadata file associated with the given .gir file.
	 */
	public string? get_metadata_path (string gir_filename) {
		var basename = Path.get_basename (gir_filename);
		var metadata_basename = "%s.metadata".printf (basename.substring (0, basename.length - ".gir".length));

		// look into metadata directories
		var metadata_filename = get_file_path (metadata_basename, null, null, metadata_directories);
		if (metadata_filename != null) {
			return metadata_filename;
		}

		// look into the same directory of .gir
524
		metadata_filename = Path.build_path ("/", Path.get_dirname (gir_filename), metadata_basename);
525 526 527 528 529 530 531 532
		if (FileUtils.test (metadata_filename, FileTest.EXISTS)) {
			return metadata_filename;
		}

		return null;
	}

	string? get_file_path (string basename, string? versioned_data_dir, string? data_dir, string[] directories) {
533
		string filename = null;
534

535 536
		if (directories != null) {
			foreach (string dir in directories) {
537
				filename = Path.build_path ("/", dir, basename);
538 539 540 541 542 543
				if (FileUtils.test (filename, FileTest.EXISTS)) {
					return filename;
				}
			}
		}

544
		if (data_dir != null) {
545
			foreach (string dir in Environment.get_system_data_dirs ()) {
546
				filename = Path.build_path ("/", dir, data_dir, basename);
547 548 549
				if (FileUtils.test (filename, FileTest.EXISTS)) {
					return filename;
				}
550
			}
551 552
		}

553
		if (versioned_data_dir != null) {
554
			foreach (string dir in Environment.get_system_data_dirs ()) {
555
				filename = Path.build_path ("/", dir, versioned_data_dir, basename);
556 557 558 559 560 561
				if (FileUtils.test (filename, FileTest.EXISTS)) {
					return filename;
				}
			}
		}

562 563
		return null;
	}
564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580

	public void write_dependencies (string filename) {
		var stream = FileStream.open (filename, "w");

		if (stream == null) {
			Report.error (null, "unable to open `%s' for writing".printf (filename));
			return;
		}

		stream.printf ("%s:", filename);
		foreach (var src in source_files) {
			if (src.file_type == SourceFileType.FAST && src.used) {
				stream.printf (" %s", src.filename);
			}
		}
		stream.printf ("\n\n");
	}
581 582

	private static bool ends_with_dir_separator (string s) {
583
		return Path.is_dir_separator (s.get_char (s.length - 1));
584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
	}

	/* ported from glibc */
	public static string realpath (string name) {
		string rpath;

		// start of path component
		weak string start;
		// end of path component
		weak string end;

		if (!Path.is_absolute (name)) {
			// relative path
			rpath = Environment.get_current_dir ();

			start = end = name;
		} else {
			// set start after root
			start = end = Path.skip_root (name);

			// extract root
605
			rpath = name.substring (0, (int) ((char*) start - (char*) name));
606 607
		}

608
		long root_len = (long) ((char*) Path.skip_root (rpath) - (char*) rpath);
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654

		for (; start.get_char () != 0; start = end) {
			// skip sequence of multiple path-separators
			while (Path.is_dir_separator (start.get_char ())) {
				start = start.next_char ();
			}

			// find end of path component
			long len = 0;
			for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) {
				len++;
			}

			if (len == 0) {
				break;
			} else if (len == 1 && start.get_char () == '.') {
				// do nothing
			} else if (len == 2 && start.has_prefix ("..")) {
				// back up to previous component, ignore if at root already
				if (rpath.length > root_len) {
					do {
						rpath = rpath.substring (0, rpath.length - 1);
					} while (!ends_with_dir_separator (rpath));
				}
			} else {
				if (!ends_with_dir_separator (rpath)) {
					rpath += Path.DIR_SEPARATOR_S;
				}

				rpath += start.substring (0, len);
			}
		}

		if (rpath.length > root_len && ends_with_dir_separator (rpath)) {
			rpath = rpath.substring (0, rpath.length - 1);
		}

		if (Path.DIR_SEPARATOR != '/') {
			// don't use backslashes internally,
			// to avoid problems in #include directives
			string[] components = rpath.split ("\\");
			rpath = string.joinv ("/", components);
		}

		return rpath;
	}
655
}