1 /*
2  * This file is part of gir-to-d.
3  *
4  * gir-to-d is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation, either version 3
7  * of the License, or (at your option) any later version.
8  *
9  * gir-to-d is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with gir-to-d.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 module gtd.GirPackage;
19 
20 import std.algorithm;
21 import std.array : join, replace;
22 import std.conv;
23 import std.file;
24 import std.path : baseName, buildNormalizedPath;
25 import std.range : array, back, chain, empty;
26 import std.regex : ctRegex, matchFirst;
27 import std.stdio;
28 import std.string : split, splitLines, strip;
29 import std.uni;
30 
31 import gtd.DefReader;
32 import gtd.GirAlias;
33 import gtd.GirConstant;
34 import gtd.GirEnum;
35 import gtd.GirFunction;
36 import gtd.GirStruct;
37 import gtd.GirVersion;
38 import gtd.GirWrapper;
39 import gtd.GlibTypes;
40 import gtd.IndentedStringBuilder;
41 import gtd.LinkedHasMap: Map = LinkedHashMap;
42 import gtd.Log;
43 import gtd.XMLReader;
44 
45 final class GirPackage
46 {
47 	string name;
48 	string cTypePrefix;
49 	string srcDir;
50 	GirVersion _version;
51 	GirWrapper wrapper;
52 
53 	string[] lookupAliases;     /// Aliases defined in the lookupfile.
54 	string[] lookupConstants;   /// Constants defined in the lookupfile.
55 	string[] lookupEnums;       /// Enums defined in the lookupfile.
56 	string[] lookupFuncts;      /// Functions defined in the lookupfile.
57 	string[] lookupStructs;     /// Structs defined in the lookupfile.
58 
59 	static GirPackage[string] namespaces;
60 	static GirInclude[string] includes;
61 
62 	string[] libraries;
63 	Map!(string, GirAlias)    collectedAliases;   /// Aliases defined in the gir file.
64 	Map!(string, GirFunction) collectedCallbacks;
65 	Map!(string, GirConstant) collectedConstants;
66 	Map!(string, GirEnum)     collectedEnums;     /// Enums defined in the gir file.
67 	Map!(string, GirFunction) collectedFunctions;
68 	Map!(string, GirStruct)   collectedStructs;
69 	GirEnum stockIDs;           /// The StockID enum (Deprecated).
70 	GirEnum gdkKeys;            /// The GdkKey enum.
71 
72 	public this(string pack, GirWrapper wrapper, string srcDir)
73 	{
74 		this.name = pack;
75 		this.wrapper = wrapper;
76 		this.srcDir = srcDir;
77 		this.stockIDs = new GirEnum(wrapper, this);
78 		this.gdkKeys  = new GirEnum(wrapper, this);
79 
80 		GirInclude gobject;
81 		gobject.name = "GObject";
82 		gobject._version = "2.0";
83 		includes["GObject"] = gobject;
84 	}
85 
86 	void parseGIR(string girFile)
87 	{
88 		if ( !exists(girFile) )
89 			error("GIR file: '", girFile, "' not found.");
90 
91 		auto reader = new XMLReader!string(readText(girFile), girFile);
92 
93 		while ( !reader.empty && reader.front.value != "repository" )
94 			reader.popFront();
95 
96 		reader.popFront();
97 
98 		while ( !reader.empty && reader.front.value == "include" )
99 		{
100 			string incName = reader.front.attributes["name"];
101 
102 			GirInclude inc = includes.get(incName, GirInclude.init);
103 			inc.name = incName;
104 			inc._version = reader.front.attributes["version"];
105 			includes[incName] = inc;
106 
107 			reader.popFront();
108 		}
109 
110 		while ( !reader.empty && reader.front.value != "namespace" )
111 			reader.popFront();
112 
113 		if ( name.empty )
114 			name = reader.front.attributes["name"].toLower();
115 
116 		namespaces[reader.front.attributes["name"]] = this;
117 		checkVersion(reader.front.attributes["version"]);
118 		
119 		if ( "c:identifier-prefixes" in reader.front.attributes )
120 		{
121 			auto p = reader.front.attributes["c:identifier-prefixes"];
122 			if ( p.length )
123 				cTypePrefix = p.split(',')[0];
124 		}
125 
126 		if ( "shared-library" in reader.front.attributes )
127 		{
128 			version(Windows)
129 				libraries ~= reader.front.attributes["shared-library"].split(',').map!(a => a.startsWith("lib")?a:"lib"~a~".dll").array;
130 			else version(OSX)
131 				libraries ~= reader.front.attributes["shared-library"].split(',').map!(a => a.baseName.startsWith("lib")?a.baseName:"lib"~a.baseName~".dylib").array;
132 			else
133 				libraries ~= reader.front.attributes["shared-library"].split(',').map!(a => a.startsWith("lib")?a:"lib"~a~".so").array;
134 
135 			libraries = libraries.sort().uniq.array;
136 		}
137 		reader.popFront();
138 
139 		while ( !reader.empty && !reader.endTag("namespace") )
140 		{
141 			if ( reader.front.type == XMLNodeType.EndTag )
142 			{
143 				reader.popFront();
144 				continue;
145 			}
146 
147 			switch (reader.front.value)
148 			{
149 				case "alias":
150 					GirAlias gtkAlias = new GirAlias(wrapper);
151 					gtkAlias.parse(reader);
152 
153 					if ( gtkAlias.cType == "GType" )
154 						break;
155 
156 					collectedAliases[gtkAlias.name] = gtkAlias;
157 					break;
158 				case "attribute":
159 					//TODO: Do we need these attibutes?
160 					//dbus.name ccode.ordering deprecated replacement.
161 					reader.skipTag();
162 					break;
163 				case "glib:boxed":
164 					reader.skipTag();
165 					break;
166 				case "bitfield":
167 				case "enumeration":
168 					GirEnum gtkEnum = new GirEnum(wrapper, this);
169 					gtkEnum.parse(reader);
170 					collectedEnums[gtkEnum.name] = gtkEnum;
171 					break;
172 				case "class":
173 				case "interface":
174 				case "record":
175 				case "union":
176 					GirStruct gtkStruct = new GirStruct(wrapper, this);
177 					gtkStruct.parse(reader);
178 
179 					//Workaround: Dont overwrite the regular pango classes.
180 					if ( gtkStruct.cType.among("PangoCairoFont", "PangoCairoFontMap") )
181 						gtkStruct.name = gtkStruct.cType;
182 
183 					collectedStructs[gtkStruct.name] = gtkStruct;
184 
185 					//Don't generate classes named Object.
186 					if ( gtkStruct.name == "Object" )
187 						gtkStruct.name = "Object"~ cTypePrefix;
188 					if ( name == "pango" )
189 						gtkStruct.name = "Pg"~gtkStruct.name;
190 					break;
191 				case "callback":
192 					GirFunction callback = new GirFunction(wrapper, null);
193 					callback.parse(reader);
194 					collectedCallbacks[callback.name] = callback;
195 					break;
196 				case "constant":
197 					parseConstant(reader);
198 					break;
199 				case "function":
200 					parseFunction(reader);
201 					break;
202 				case "function-macro":
203 					// We are not able to wrap these.
204 					reader.skipTag();
205 					break;
206 				case "field":
207 					// We are not able to wrap these.
208 					reader.skipTag();
209 					break;
210 				default:
211 					error("Unexpected tag: ", reader.front.value, " in GirPackage: ", name, reader);
212 			}
213 			reader.popFront();
214 		}
215 	}
216 
217 	void parseConstant(T)(XMLReader!T reader)
218 	{
219 		if ( reader.front.attributes["name"].startsWith("STOCK_") )
220 		{
221 			GirEnumMember member = GirEnumMember(wrapper);
222 			member.parse(reader);
223 			member.name = member.name[6..$];
224 
225 			stockIDs.members ~= member;
226 			return;
227 		}
228 		else if ( "c:type" in reader.front.attributes && reader.front.attributes["c:type"].startsWith("GDK_KEY_") )
229 		{
230 			GirEnumMember member = GirEnumMember(wrapper);
231 			member.parse(reader);
232 			member.name = "GDK_"~ member.name[4..$];
233 
234 			gdkKeys.members ~= member;
235 			return;
236 		}
237 		// The version attribute of the namspace tag is usualy set to MAJOR.0.
238 		else if ( reader.front.attributes["name"].startsWith("MAJOR_VERSION") )
239 		{
240 			_version.major = to!uint(reader.front.attributes["value"]);
241 		}
242 		else if ( reader.front.attributes["name"].startsWith("MINOR_VERSION") )
243 		{
244 			_version.minor = to!uint(reader.front.attributes["value"]);
245 		}
246 		else if ( reader.front.attributes["name"].startsWith("MICRO_VERSION") )
247 		{
248 			_version.micro = to!uint(reader.front.attributes["value"]);
249 		}
250 
251 		GirConstant constant = new GirConstant(wrapper, this);
252 		constant.parse(reader);
253 
254 		if (constant.name != "true" && constant.name != "false")
255 			collectedConstants[constant.name] = constant;
256 	}
257 
258 	void parseFunction(T)(XMLReader!T reader)
259 	{
260 		GirFunction funct = new GirFunction(wrapper, null);
261 		funct.parse(reader);
262 		collectedFunctions[funct.name] = funct;
263 
264 		checkVersion(funct.libVersion);
265 	}
266 
267 	GirPackage getNamespace(string name)
268 	{
269 		if ( name.empty )
270 			return null;
271 			
272 		if ( name !in namespaces )
273 		{
274 			if ( auto inc = name in includes )
275 			{
276 				if ( inc.skip ) return null;
277 
278 				namespaces[name] = inc.parse(wrapper, srcDir);
279 			}
280 			else
281 			{
282 				foreach ( inc; includes.byValue.filter!(inc => inc.name !in namespaces) )
283 					namespaces[inc.name] = inc.parse(wrapper, srcDir);
284 
285 				if ( !includes.byValue.filter!(inc => inc.name !in namespaces).empty )
286 					return getNamespace(name);
287 				else
288 					return null;
289 			}
290 		}
291 
292 		return namespaces.get(name, null);
293 	}
294 
295 	GirStruct getStruct(string name)
296 	{
297 		GirPackage pack = this;
298 
299 		if ( name.canFind(".") )
300 		{
301 			string[] vals = name.split(".");
302 
303 			if ( !getNamespace(vals[0]) )
304 				return null;
305 
306 			pack = namespaces[vals[0]];
307 			name = vals[1];
308 		}
309 		return pack.collectedStructs.get(name, pack.collectedStructs.get("lookup"~name, null));
310 	}
311 
312 	GirEnum getEnum(string name)
313 	{
314 		GirPackage pack = this;
315 
316 		if ( name.canFind(".") )
317 		{
318 			string[] vals = name.split(".");
319 
320 			if ( !getNamespace(vals[0]) )
321 				return null;
322 
323 			pack = namespaces[vals[0]];
324 			name = vals[1];
325 		}
326 		return pack.collectedEnums.get(name, null);
327 	}
328 
329 	void checkVersion(string _version)
330 	{
331 		if (this._version < _version)
332 			this._version = GirVersion(_version);
333 	}
334 
335 	void checkVersion(GirVersion _version)
336 	{
337 		if (this._version < _version)
338 			this._version = _version;
339 	}
340 
341 	void writeClasses()
342 	{
343 		try
344 		{
345 			if ( !exists(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/"))) )
346 				mkdirRecurse(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/")));
347 		}
348 		catch (FileException ex)
349 		{
350 			error("Failed to create directory: ", ex.msg);
351 		}
352 
353 		foreach ( strct; collectedStructs )
354 			strct.writeClass();
355 	}
356 
357 	void writeTypes()
358 	{
359 		string buff = wrapper.licence;
360 		auto indenter = new IndentedStringBuilder();
361 
362 		buff ~= "module "~ name ~".c.types;\n\n";
363 
364 		buff ~= getTypeImports();
365 
366 		buff ~= indenter.format(lookupAliases);
367 		foreach ( a; collectedAliases )
368 		{
369 			buff ~= "\n";
370 			buff ~= indenter.format(a.getAliasDeclaration());
371 		}
372 
373 		buff ~= indenter.format(lookupEnums);
374 		foreach ( e; collectedEnums )
375 		{
376 			buff ~= "\n";
377 			buff ~= indenter.format(e.getEnumDeclaration());
378 		}
379 
380 		buff ~= indenter.format(lookupStructs);
381 		foreach ( s; collectedStructs )
382 		{
383 			if ( s.noExternal || s.noDeclaration )
384 				continue;
385 
386 			buff ~= "\n";
387 			buff ~= indenter.format(s.getStructDeclaration());
388 		}
389 
390 		buff ~= indenter.format(lookupFuncts);
391 		foreach ( f; collectedCallbacks )
392 		{
393 			buff ~= "\n";
394 			buff ~= indenter.format(f.getCallbackDeclaration());
395 		}
396 
397 		buff ~= indenter.format(lookupConstants);
398 		foreach ( c; collectedConstants )
399 		{
400 			buff ~= "\n";
401 			buff ~= indenter.format(c.getConstantDeclaration());
402 		}
403 
404 		if ( stockIDs.members !is null )
405 		{
406 			stockIDs.cName = "StockID";
407 			stockIDs.doc = "StockIds";
408 			buff ~= "\n";
409 			buff ~= indenter.format(stockIDs.getEnumDeclaration());
410 		}
411 
412 		if ( gdkKeys.members !is null )
413 			writeGdkKeys();
414 
415 		wrapper.writeFile(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/"), "c/types.d"), buff, true);
416 	}
417 
418 	void writeGdkKeys()
419 	{
420 		string buff = wrapper.licence;
421 
422 		buff ~= "module "~ name ~".Keysyms;\n\n";
423 
424 		buff ~= "/**\n";
425 		buff ~= " * GdkKeysyms.\n";
426 		buff ~= " */\n";
427 		buff ~= "public enum GdkKeysyms\n";
428 		buff ~= "{\n";
429 
430 		foreach ( member; gdkKeys.members )
431 		{
432 			buff ~= "\t"~ tokenToGtkD(member.name, wrapper.aliasses, false) ~" = "~ member.value ~",\n";
433 		}
434 
435 		buff ~= "}\n";
436 		buff ~= "alias Keysyms = GdkKeysyms;\n";
437 
438 		wrapper.writeFile(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/"), "Keysyms.d"), buff, true);
439 	}
440 
441 	void writeLoaderTable()
442 	{
443 		string buff = wrapper.licence;
444 
445 		buff ~= "module "~ name ~".c.functions;\n\n";
446 		buff ~= "import std.stdio;\n";
447 		buff ~= "import "~ name ~".c.types;\n";
448 
449 		if ( name == "glib" )
450 			buff ~= "import gobject.c.types;\n";
451 		if ( name == "gdk" || name == "pango" )
452 			buff ~= "import cairo.c.types;\n";
453 
454 		buff ~= "import gtkd.Loader;\n\n";
455 		buff ~= getLibraries();
456 		buff ~= "\n\nshared static this()\n{";
457 
458 		foreach ( strct; collectedStructs )
459 		{
460 			if ( strct.functions.empty || strct.noExternal )
461 				continue;
462 
463 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
464 
465 			foreach ( funct; strct.functions )
466 			{
467 				if ( funct.type == GirFunctionType.Callback || funct.type == GirFunctionType.Signal || funct.name.empty )
468 					continue;
469 
470 				buff ~= "\tLinker.link("~ funct.cType ~", \""~ funct.cType ~"\", LIBRARY_"~ name.replace(".","").toUpper() ~");\n";
471 			}
472 		}
473 
474 		//WORKAROUND: Windows has two functions with different names in the 32 and 64 bit versions.
475 		if (name == "glib")
476 		{
477 			buff ~= "\n\tversion(Win64)\n";
478 			buff ~= "\t{\n";
479 			buff ~= "\t\tLinker.link(g_module_name, \"g_module_name_uft8\", LIBRARY_GLIB);\n";
480 			buff ~= "\t\tLinker.link(g_module_open, \"g_module_open_utf8\", LIBRARY_GLIB);\n";
481 			buff ~= "\t}\n";
482 		}
483 
484 		buff ~= "}\n\n"
485 			~ "__gshared extern(C)\n"
486 			~ "{\n";
487 
488 		foreach ( strct; collectedStructs )
489 		{
490 			if ( strct.functions.empty || strct.noExternal )
491 				continue;
492 
493 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
494 
495 			foreach ( funct; strct.functions )
496 			{
497 				if ( funct.type == GirFunctionType.Callback || funct.type == GirFunctionType.Signal || funct.name.empty )
498 					continue;
499 
500 				buff ~= "\t"~ funct.getLinkerExternal() ~"\n";
501 			}
502 		}
503 
504 		buff ~= "}\n\n";
505 
506 		foreach ( strct; collectedStructs )
507 		{
508 			if ( strct.functions.empty || strct.noExternal )
509 				continue;
510 
511 			buff ~= "\n// "~ name ~"."~ strct.name ~"\n\n";
512 
513 			foreach ( funct; strct.functions )
514 			{
515 				if ( funct.type == GirFunctionType.Callback || funct.type == GirFunctionType.Signal || funct.name.empty )
516 					continue;
517 
518 				if (name == "glgdk")
519 					buff ~= "alias glc_"~ funct.cType ~" "~ funct.cType ~";\n";
520 				else
521 					buff ~= "alias c_"~ funct.cType ~" "~ funct.cType ~";\n";
522 			}
523 		}
524 
525 		wrapper.writeFile(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/"), "c", "functions.d"), buff, true);
526 	}
527 
528 	void writeExternalFunctions()
529 	{
530 		string buff = wrapper.licence;
531 
532 		buff ~= "module "~ name ~".c.functions;\n\n";
533 		buff ~= "import std.stdio;\n";
534 		buff ~= "import "~ name ~".c.types;\n";
535 
536 		if ( name == "glib" )
537 			buff ~= "import gobject.c.types;\n";
538 		if ( name == "gdk" || name == "pango" )
539 			buff ~= "import cairo.c.types;\n\n";
540 
541 		buff ~= getLibraries();
542 
543 		buff ~= "\n\n__gshared extern(C)\n"
544 			~ "{\n";
545 
546 		foreach ( strct; collectedStructs )
547 		{
548 			if ( strct.functions.empty || strct.noExternal )
549 				continue;
550 
551 			buff ~= "\n\t// "~ name ~"."~ strct.name ~"\n\n";
552 
553 			foreach ( funct; strct.functions )
554 			{
555 				if ( funct.type == GirFunctionType.Callback || funct.type == GirFunctionType.Signal || funct.name.empty )
556 					continue;
557 
558 				buff ~= "\t"~ funct.getExternal() ~"\n";
559 			}
560 		}
561 
562 		buff ~= "}";
563 
564 		wrapper.writeFile(buildNormalizedPath(wrapper.outputDir, srcDir, name.replace(".","/"), "c", "functions.d"), buff, true);
565 	}
566 
567 	private string getLibraries()
568 	{
569 		string lib = "version (Windows)\n\t";
570 		lib ~= "static immutable LIBRARY_"~ name.replace(".","").toUpper() ~" = ["~ getDllNames() ~"];";
571 		lib ~= "\nelse version (OSX)\n\t";
572 		lib ~= "static immutable LIBRARY_"~ name.replace(".","").toUpper() ~" = ["~ getDylibNames() ~"];";
573 		lib ~= "\nelse\n\t";
574 		lib ~= "static immutable LIBRARY_"~ name.replace(".","").toUpper() ~" = ["~ getSoNames() ~"];";
575 
576 		return lib;
577 	}
578 
579 	private auto dllRegex = ctRegex!(`([a-z0-9_]+)(-[0-9\.]+)?(-[0-9]+)?\.dll`);
580 	private auto dylibRegex = ctRegex!(`([a-z0-9_]+)(-[0-9\.]+?)?(\.[0-9]+)?\.dylib`);
581 	private auto soRegex = ctRegex!(`([a-z0-9_]+)(-[0-9\.]+)?\.so(\.[0-9]+)?`);
582 
583 	private string getDllNames()
584 	{
585 		version (Windows)
586 		{
587 			//TODO: Only the gir files form msys are currently supported on windows.
588 
589 			string libs;
590 
591 			foreach ( lib; libraries )
592 			{
593 				auto match = matchFirst(lib, dllRegex);
594 
595 				//Msys
596 				libs ~= "\""~ lib;
597 
598 				//wingtk
599 				libs ~= ";";
600 				libs ~= match[1].replace("lib", "");
601 				if ( !match[2].empty )
602 					libs ~= "-"~ match[2][1..$];
603 				if ( !match[3].empty && match[2].canFind('.') )
604 					libs ~= "-"~ match[3][1..$];
605 				else if ( !match[3].empty && !match[2].empty )
606 					libs ~= "-"~ match[2][$-1] ~"."~ match[3][1..$];
607 				libs ~= ".dll";
608 
609 				//vcpkg
610 				libs ~= ";";
611 				libs ~= match[1].replace("lib", "");
612 				if ( !match[2].empty && match[2].canFind('.') )
613 					libs ~= "-"~ match[2][1..$].split('.')[0];
614 				else if ( !match[2].empty )
615 					libs ~= "-"~ match[2][1..$];
616 				libs ~= ".dll\"";
617 
618 				if ( lib != libraries.back )
619 					libs ~= ", ";
620 			}
621 
622 			return libs;			
623 		}
624 		else version (OSX)
625 		{
626 			string libs;
627 
628 			foreach ( lib; libraries )
629 			{
630 				auto match = matchFirst(lib, dylibRegex);
631 
632 				//Msys
633 				libs ~= "\""~ match[1];
634 				if ( !match[2].empty )
635 					libs ~= "-"~ match[2][1..$];
636 				if ( !match[3].empty )
637 					libs ~= "-"~ match[3][1..$];
638 				libs ~= ".dll";
639 
640 				//wingtk
641 				libs ~= ";";
642 				libs ~= match[1].replace("lib", "");
643 				if ( !match[2].empty )
644 					libs ~= "-"~ match[2][1..$];
645 				if ( !match[3].empty && match[2].canFind('.') )
646 					libs ~= "-"~ match[3][1..$];
647 				else if ( !match[3].empty && !match[2].empty )
648 					libs ~= "-"~ match[2][$-1] ~"."~ match[3][1..$];
649 				libs ~= ".dll";
650 
651 				//vcpkg
652 				libs ~= ";";
653 				libs ~= match[1].replace("lib", "");
654 				if ( !match[2].empty && match[2].canFind('.') )
655 					libs ~= "-"~ match[2][1..$].split('.')[0];
656 				else if ( !match[2].empty )
657 					libs ~= "-"~ match[2][1..$];
658 				libs ~= ".dll\"";
659 
660 				if ( lib != libraries.back )
661 					libs ~= ", ";
662 			}
663 
664 			return libs;
665 		}
666 		else
667 		{
668 			string libs;
669 
670 			foreach ( lib; libraries )
671 			{
672 				auto match = matchFirst(lib, soRegex);
673 
674 				//Msys
675 				libs ~= "\""~ match[1];
676 				if ( !match[2].empty )
677 					libs ~= "-"~ match[2][1..$];
678 				if ( !match[3].empty )
679 					libs ~= "-"~ match[3][1..$];
680 				libs ~= ".dll";
681 
682 				//wingtk
683 				libs ~= ";";
684 				libs ~= match[1].replace("lib", "");
685 				if ( !match[2].empty )
686 					libs ~= "-"~ match[2][1..$];
687 				if ( !match[3].empty && match[2].canFind('.') )
688 					libs ~= "-"~ match[3][1..$];
689 				else if ( !match[3].empty && !match[2].empty )
690 					libs ~= "-"~ match[2][$-1] ~"."~ match[3][1..$];
691 				libs ~= ".dll";
692 
693 				//vcpkg
694 				libs ~= ";";
695 				libs ~= match[1].replace("lib", "");
696 				if ( !match[2].empty && match[2].canFind('.') )
697 					libs ~= "-"~ match[2][1..$].split('.')[0];
698 				else if ( !match[2].empty )
699 					libs ~= "-"~ match[2][1..$];
700 				libs ~= ".dll\"";
701 
702 				if ( lib != libraries.back )
703 					libs ~= ", ";
704 			}
705 
706 			return libs;
707 		}
708 	}
709 
710 	private string getDylibNames()
711 	{
712 		version (Windows)
713 		{
714 			string libs;
715 
716 			foreach ( lib; libraries )
717 			{
718 				auto match = matchFirst(lib, dllRegex);
719 
720 				libs ~= "\""~ match[1];
721 				if ( !match[2].empty )
722 					libs ~= "-"~ match[2][1..$];
723 				if ( !match[3].empty )
724 					libs ~= "."~ match[3][1..$];
725 				libs ~= ".dylib\"";
726 
727 				if ( lib != libraries.back )
728 					libs ~= ", ";
729 			}
730 
731 			return libs;
732 		}
733 		else version (OSX)
734 		{
735 			return "\""~ libraries.join("\", \"") ~"\"";
736 		}
737 		else
738 		{
739 			string libs;
740 
741 			foreach ( lib; libraries )
742 			{
743 				auto match = matchFirst(lib, soRegex);
744 
745 				libs ~= "\""~ match[1];
746 				if ( !match[2].empty )
747 					libs ~= "-"~ match[2][1..$];
748 				if ( !match[3].empty )
749 					libs ~= "."~ match[3][1..$];
750 				libs ~= ".dylib\"";
751 
752 				if ( lib != libraries.back )
753 					libs ~= ", ";
754 			}
755 
756 			return libs;
757 		}
758 	}
759 
760 	private string getSoNames()
761 	{
762 		version (Windows)
763 		{
764 			string libs;
765 
766 			foreach ( lib; libraries )
767 			{
768 				auto match = matchFirst(lib, dllRegex);
769 
770 				libs ~= "\""~ match[1];
771 				if ( !match[2].empty && !match[3].empty )
772 					libs ~= "-"~ match[2][1..$];
773 				libs ~= ".so";
774 				if ( !match[2].empty && match[3].empty )
775 					libs ~= "."~ match[2][1..$];
776 				if ( !match[3].empty )
777 					libs ~= "."~ match[3][1..$];
778 				libs ~= "\"";
779 
780 				if ( lib != libraries.back )
781 					libs ~= ", ";
782 			}
783 
784 			return libs;
785 		}
786 		else version (OSX)
787 		{
788 			string libs;
789 
790 			foreach ( lib; libraries )
791 			{
792 				auto match = matchFirst(lib, dylibRegex);
793 
794 				libs ~= "\""~ match[1];
795 				if ( !match[2].empty )
796 					libs ~= "-"~ match[2][1..$];
797 				libs ~= ".so";
798 				if ( !match[3].empty )
799 					libs ~= "."~ match[3][1..$];
800 				libs ~= "\"";
801 
802 				if ( lib != libraries.back )
803 					libs ~= ", ";
804 			}
805 
806 			return libs;
807 		}
808 		else
809 		{
810 			return "\""~ libraries.join("\", \"") ~"\"";
811 		}
812 	}
813 
814 	private string getTypeImports()
815 	{
816 		string imports;
817 		string[] usedNamespaces;
818 		string[] packages;
819 
820 		foreach ( strct; collectedStructs )
821 		{
822 			if ( strct.noExternal )
823 				continue;
824 
825 			usedNamespaces = chain(usedNamespaces, strct.usedNamespaces()).sort.uniq.array;
826 		}
827 
828 		foreach ( ns; usedNamespaces )
829 		{
830 			GirPackage pack = getNamespace(ns);
831 
832 			if ( pack && pack != this && !packages.canFind(pack.name) )
833 				packages ~= pack.name;
834 		}
835 
836 		packages = packages.sort().array;
837 
838 		foreach ( pack; packages )
839 		{
840 			imports ~= "public import "~ pack ~".c.types;\n";
841 		}
842 
843 		if ( !imports.empty )
844 			imports ~= "\n";
845 
846 		return imports;
847 	}
848 }
849 
850 struct GirInclude
851 {
852 	string name;
853 	string _version;
854 
855 	bool skip;
856 	string[] lookupText;
857 	string lookupFile;
858 	size_t lookupLine;
859 
860 	string girFileName()
861 	{
862 		return name ~"-"~ _version ~".gir";
863 	}
864 
865 	GirPackage parse(GirWrapper wrapper, string srcDir)
866 	{
867 		GirPackage inc = new GirPackage(name.toLower, wrapper, srcDir);
868 		inc.parseGIR(wrapper.getAbsoluteGirPath(girFileName()));
869 
870 		if ( auto text = name in defaultLookupText )
871 			wrapper.proccess(new DefReader(*text, "Default rules"), inc, true);
872 
873 		wrapper.proccess(new DefReader(lookupText, lookupFile, lookupLine), inc, true);
874 
875 		return inc;
876 	}
877 }