1 /**
2 	Generator for VisualD project files
3 	
4 	Copyright: © 2012-2013 Matthias Dondorff
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff
7 */
8 module dub.generators.visuald;
9 
10 import dub.compilers.compiler;
11 import dub.generators.generator;
12 import dub.internal.utils;
13 import dub.internal.vibecompat.core.file;
14 import dub.internal.vibecompat.core.log;
15 import dub.package_;
16 import dub.packagemanager;
17 import dub.project;
18 
19 import std.algorithm;
20 import std.array;
21 import std.conv;
22 import std.exception;
23 import std.format;
24 import std..string : format;
25 import std.uuid;
26 
27 
28 // Dubbing is developing dub...
29 //version = DUBBING;
30 
31 // TODO: handle pre/post build commands
32 
33 
34 class VisualDGenerator : ProjectGenerator {
35 	private {
36 		PackageManager m_pkgMgr;
37 		string[string] m_projectUuids;
38 	}
39 	
40 	this(Project app, PackageManager mgr)
41 	{
42 		super(app);
43 		m_pkgMgr = mgr;
44 	}
45 	
46 	override void generateTargets(GeneratorSettings settings, in TargetInfo[string] targets)
47 	{
48 		auto bs = targets[m_project.name].buildSettings;
49 		prepareGeneration(bs);
50 		logDebug("About to generate projects for %s, with %s direct dependencies.", m_project.mainPackage().name, m_project.mainPackage().dependencies().length);
51 		generateProjectFiles(settings, targets);
52 		generateSolutionFile(settings, targets);
53 		logInfo("VisualD project generated.");
54 		finalizeGeneration(bs, true);
55 	}
56 
57 	private {
58 		void generateSolutionFile(GeneratorSettings settings, in TargetInfo[string] targets)
59 		{
60 			auto ret = appender!(char[])();
61 			auto configs = m_project.getPackageConfigs(settings.platform, settings.config);
62 			auto some_uuid = generateUUID();
63 			
64 			// Solution header
65 			ret.put("Microsoft Visual Studio Solution File, Format Version 11.00\n");
66 			ret.put("# Visual Studio 2010\n");
67 
68 			bool[string] visited;
69 			void generateSolutionEntry(string pack) {
70 				if (pack in visited) return;
71 				visited[pack] = true;
72 
73 				auto ti = targets[pack];
74 
75 				auto uuid = guid(pack);
76 				ret.formattedWrite("Project(\"%s\") = \"%s\", \"%s\", \"%s\"\n",
77 					some_uuid, pack, projFileName(pack), uuid);
78 
79 				if (ti.linkDependencies.length && ti.buildSettings.targetType != TargetType.staticLibrary) {
80 					ret.put("\tProjectSection(ProjectDependencies) = postProject\n");
81 					foreach (d; ti.linkDependencies)
82 						if (!isHeaderOnlyPackage(d, targets)) {
83 							// TODO: clarify what "uuid = uuid" should mean
84 							ret.formattedWrite("\t\t%s = %s\n", guid(d), guid(d));
85 						}
86 					ret.put("\tEndProjectSection\n");
87 				}
88 
89 				ret.put("EndProject\n");
90 
91 				foreach (d; ti.dependencies) generateSolutionEntry(d);
92 			}
93 
94 			auto mainpack = m_project.mainPackage.name;
95 
96 			generateSolutionEntry(mainpack);
97 			
98 			// Global section contains configurations
99 			ret.put("Global\n");
100 			ret.put("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n");
101 			ret.formattedWrite("\t\t%s|Win32 = %s|Win32\n", settings.buildType, settings.buildType);
102 			ret.put("\tEndGlobalSection\n");
103 			ret.put("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n");
104 			
105 			const string[] sub = ["ActiveCfg", "Build.0"];
106 			const string[] conf = [settings.buildType~"|Win32"];
107 			auto projectUuid = guid(mainpack);
108 			foreach (t; targets.byKey)
109 				foreach (c; conf)
110 					foreach (s; sub)
111 						formattedWrite(ret, "\t\t%s.%s.%s = %s\n", guid(t), c, s, c);
112 			
113 			// TODO: for all dependencies
114 			ret.put("\tEndGlobalSection\n");
115 			
116 			ret.put("\tGlobalSection(SolutionProperties) = preSolution\n");
117 			ret.put("\t\tHideSolutionNode = FALSE\n");
118 			ret.put("\tEndGlobalSection\n");
119 			ret.put("EndGlobal\n");
120 
121 			// Writing solution file
122 			logDebug("About to write to .sln file with %s bytes", to!string(ret.data().length));
123 			auto sln = openFile(solutionFileName(), FileMode.CreateTrunc);
124 			scope(exit) sln.close();
125 			sln.put(ret.data());
126 			sln.flush();
127 		}
128 
129 		
130 		void generateProjectFiles(GeneratorSettings settings, in TargetInfo[string] targets)
131 		{
132 			bool[string] visited;
133 			void performRec(string name) {
134 				if (name in visited) return;
135 				visited[name] = true;
136 				generateProjectFile(name, settings, targets);
137 				foreach (d; targets[name].dependencies)
138 					performRec(d);
139 			}
140 
141 			performRec(m_project.mainPackage.name);
142 		}
143 
144 		bool isHeaderOnlyPackage(string pack, in TargetInfo[string] targets)
145 		const {
146 			auto buildsettings = targets[pack].buildSettings;
147 			if (!buildsettings.sourceFiles.any!(f => f.endsWith(".d"))())
148 				return true;
149 			return false;
150 		}
151 		
152 		void generateProjectFile(string packname, GeneratorSettings settings, in TargetInfo[string] targets)
153 		{
154 			int i = 0;
155 			auto ret = appender!(char[])();
156 			
157 			auto project_file_dir = m_project.mainPackage.path ~ projFileName(packname).parentPath;
158 			ret.put("<DProject>\n");
159 			ret.formattedWrite("  <ProjectGuid>%s</ProjectGuid>\n", guid(packname));
160 	
161 			// Several configurations (debug, release, unittest)
162 			generateProjectConfiguration(ret, packname, settings.buildType, settings, targets);
163 			//generateProjectConfiguration(ret, packname, "release", settings, targets);
164 			//generateProjectConfiguration(ret, packname, "unittest", settings, targets);
165 
166 			// Add all files
167 			auto files = targets[packname].buildSettings;
168 			SourceFile[string] sourceFiles;
169 			void addSourceFile(Path file_path, Path structure_path, bool build)
170 			{
171 				auto key = file_path.toString();
172 				auto sf = sourceFiles.get(key, SourceFile.init);
173 				sf.filePath = file_path;
174 				if (!sf.build) {
175 					sf.build = build;
176 					sf.structurePath = structure_path;
177 				}
178 				sourceFiles[key] = sf;
179 			}
180 
181 			void addFile(string s, bool build) {
182 				auto sp = Path(s);
183 				assert(sp.absolute, format("Source path in %s expected to be absolute: %s", packname, s));
184 				//if( !sp.absolute ) sp = pack.path ~ sp;
185 				addSourceFile(sp.relativeTo(project_file_dir), determineStructurePath(sp, targets[packname]), build);
186 			}
187 
188 			foreach (p; targets[packname].packages)
189 				if (!p.packageInfoFile.empty)
190 					addFile(p.packageInfoFile.toNativeString(), false);
191 
192 			if (files.targetType == TargetType.staticLibrary)
193 				foreach(s; files.sourceFiles.filter!(s => !isLinkerFile(s))) addFile(s, true);
194 			else
195 				foreach(s; files.sourceFiles.filter!(s => !s.endsWith(".lib"))) addFile(s, true);
196 
197 			foreach(s; files.importFiles) addFile(s, false);
198 			foreach(s; files.stringImportFiles) addFile(s, false);
199 
200 			// Create folders and files
201 			ret.formattedWrite("  <Folder name=\"%s\">", getPackageFileName(packname));
202 			Path lastFolder;
203 			foreach(source; sortedSources(sourceFiles.values)) {
204 				logDebug("source looking at %s", source.structurePath);
205 				auto cur = source.structurePath[0 .. source.structurePath.length-1];
206 				if(lastFolder != cur) {
207 					size_t same = 0;
208 					foreach(idx; 0..min(lastFolder.length, cur.length))
209 						if(lastFolder[idx] != cur[idx]) break;
210 						else same = idx+1;
211 
212 					const decrease = lastFolder.length - min(lastFolder.length, same);
213 					const increase = cur.length - min(cur.length, same);
214 
215 					foreach(unused; 0..decrease)
216 						ret.put("\n    </Folder>");
217 					foreach(idx; 0..increase)
218 						ret.formattedWrite("\n    <Folder name=\"%s\">", cur[same + idx].toString());
219 					lastFolder = cur;
220 				}
221 				ret.formattedWrite("\n      <File %spath=\"%s\" />", source.build ? "" : "tool=\"None\" ", source.filePath.toNativeString());
222 			}
223 			// Finalize all open folders
224 			foreach(unused; 0..lastFolder.length)
225 				ret.put("\n    </Folder>");
226 			ret.put("\n  </Folder>\n</DProject>");
227 
228 			logDebug("About to write to '%s.visualdproj' file %s bytes", getPackageFileName(packname), ret.data().length);
229 			auto proj = openFile(projFileName(packname), FileMode.CreateTrunc);
230 			scope(exit) proj.close();
231 			proj.put(ret.data());
232 			proj.flush();
233 		}
234 		
235 		void generateProjectConfiguration(Appender!(char[]) ret, string pack, string type, GeneratorSettings settings, in TargetInfo[string] targets)
236 		{
237 			auto project_file_dir = m_project.mainPackage.path ~ projFileName(pack).parentPath;
238 			auto buildsettings = targets[pack].buildSettings.dup;
239 			
240 			string[] getSettings(string setting)(){ return __traits(getMember, buildsettings, setting); }
241 			string[] getPathSettings(string setting)()
242 			{
243 				auto settings = getSettings!setting();
244 				auto ret = new string[settings.length];
245 				foreach (i; 0 .. settings.length) {
246 					// \" is interpreted as an escaped " by cmd.exe, so we need to avoid that
247 					auto p = Path(settings[i]).relativeTo(project_file_dir);
248 					p.endsWithSlash = false;
249 					ret[i] = '"' ~ p.toNativeString() ~ '"';
250 				}
251 				return ret;
252 			}
253 			
254 			foreach(architecture; settings.platform.architecture) {
255 				string arch;
256 				switch(architecture) {
257 					default: logWarn("Unsupported platform('%s'), defaulting to x86", architecture); goto case;
258 					case "x86": arch = "Win32"; break;
259 					case "x86_64": arch = "x64"; break;
260 				}
261 				ret.formattedWrite("  <Config name=\"%s\" platform=\"%s\">\n", to!string(type), arch);
262 
263 				// FIXME: handle compiler options in an abstract way instead of searching for DMD specific flags
264 			
265 				// debug and optimize setting
266 				ret.formattedWrite("    <symdebug>%s</symdebug>\n", buildsettings.options & BuildOptions.debugInfo ? "1" : "0");
267 				ret.formattedWrite("    <optimize>%s</optimize>\n", buildsettings.options & BuildOptions.optimize ? "1" : "0");
268 				ret.formattedWrite("    <useInline>%s</useInline>\n", buildsettings.options & BuildOptions.inline ? "1" : "0");
269 				ret.formattedWrite("    <release>%s</release>\n", buildsettings.options & BuildOptions.releaseMode ? "1" : "0");
270 
271 				// Lib or exe?
272 				enum 
273 				{
274 					Executable = 0,
275 					StaticLib = 1,
276 					DynamicLib = 2
277 				}
278 
279 				int output_type = StaticLib; // library
280 				string output_ext = "lib";
281 				if (buildsettings.targetType == TargetType.executable)
282 				{
283 					output_type = Executable;
284 					output_ext = "exe";
285 				}
286 				else if (buildsettings.targetType == TargetType.dynamicLibrary)
287 				{
288 					output_type = DynamicLib;
289 					output_ext = "dll";
290 				}
291 				string debugSuffix = type == "debug" ? "_d" : "";
292 				auto bin_path = pack == m_project.mainPackage.name ? Path(buildsettings.targetPath) : Path(".dub/lib/");
293 				bin_path.endsWithSlash = true;
294 				ret.formattedWrite("    <lib>%s</lib>\n", output_type);
295 				ret.formattedWrite("    <exefile>%s%s%s.%s</exefile>\n", bin_path.toNativeString(), buildsettings.targetName, debugSuffix, output_ext);
296 
297 				// include paths and string imports
298 				string imports = join(getPathSettings!"importPaths"(), " ");
299 				string stringImports = join(getPathSettings!"stringImportPaths"(), " ");
300 				ret.formattedWrite("    <imppath>%s</imppath>\n", imports);
301 				ret.formattedWrite("    <fileImppath>%s</fileImppath>\n", stringImports);
302 
303 				ret.formattedWrite("    <program>%s</program>\n", "$(DMDInstallDir)windows\\bin\\dmd.exe"); // FIXME: use the actually selected compiler!
304 				ret.formattedWrite("    <additionalOptions>%s</additionalOptions>\n", getSettings!"dflags"().join(" "));
305 
306 				// Add version identifiers
307 				string versions = join(getSettings!"versions"(), " ");
308 				ret.formattedWrite("    <versionids>%s</versionids>\n", versions);
309 
310 				// Add libraries, system libs need to be suffixed by ".lib".
311 				string linkLibs = join(map!(a => a~".lib")(getSettings!"libs"()), " ");
312 				string addLinkFiles = join(getSettings!"sourceFiles"().filter!(s => s.endsWith(".lib"))(), " ");
313 				if (arch == "x86") addLinkFiles ~= " phobos.lib";
314 				if (output_type != StaticLib) ret.formattedWrite("    <libfiles>%s %s</libfiles>\n", linkLibs, addLinkFiles);
315 
316 				// Unittests
317 				ret.formattedWrite("    <useUnitTests>%s</useUnitTests>\n", buildsettings.options & BuildOptions.unittests ? "1" : "0");
318 
319 				// compute directory for intermediate files (need dummy/ because of how -op determines the resulting path)
320 				size_t ndummy = 0;
321 				foreach (f; buildsettings.sourceFiles) {
322 					auto rpath = Path(f).relativeTo(project_file_dir);
323 					size_t nd = 0;
324 					foreach (i; 0 .. rpath.length)
325 						if (rpath[i] == "..")
326 							nd++;
327 					if (nd > ndummy) ndummy = nd;
328 				}
329 				string intersubdir = replicate("dummy/", ndummy) ~ getPackageFileName(pack);
330 		
331 				ret.put("    <obj>0</obj>\n");
332 				ret.put("    <link>0</link>\n");
333 				ret.put("    <subsystem>0</subsystem>\n");
334 				ret.put("    <multiobj>0</multiobj>\n");
335 				ret.put("    <singleFileCompilation>2</singleFileCompilation>\n");
336 				ret.put("    <oneobj>0</oneobj>\n");
337 				ret.put("    <trace>0</trace>\n");
338 				ret.put("    <quiet>0</quiet>\n");
339 				ret.formattedWrite("    <verbose>%s</verbose>\n", buildsettings.options & BuildOptions.verbose ? "1" : "0");
340 				ret.put("    <vtls>0</vtls>\n");
341 				ret.put("    <cpu>0</cpu>\n");
342 				ret.formattedWrite("    <isX86_64>%s</isX86_64>\n", arch == "x64" ? 1 : 0);
343 				ret.put("    <isLinux>0</isLinux>\n");
344 				ret.put("    <isOSX>0</isOSX>\n");
345 				ret.put("    <isWindows>0</isWindows>\n");
346 				ret.put("    <isFreeBSD>0</isFreeBSD>\n");
347 				ret.put("    <isSolaris>0</isSolaris>\n");
348 				ret.put("    <scheduler>0</scheduler>\n");
349 				ret.put("    <useDeprecated>0</useDeprecated>\n");
350 				ret.put("    <useAssert>0</useAssert>\n");
351 				ret.put("    <useInvariants>0</useInvariants>\n");
352 				ret.put("    <useIn>0</useIn>\n");
353 				ret.put("    <useOut>0</useOut>\n");
354 				ret.put("    <useArrayBounds>0</useArrayBounds>\n");
355 				ret.formattedWrite("    <noboundscheck>%s</noboundscheck>\n", buildsettings.options & BuildOptions.noBoundsCheck ? "1" : "0");
356 				ret.put("    <useSwitchError>0</useSwitchError>\n");
357 				ret.put("    <preservePaths>1</preservePaths>\n");
358 				ret.formattedWrite("    <warnings>%s</warnings>\n", buildsettings.options & BuildOptions.warningsAsErrors ? "1" : "0");
359 				ret.formattedWrite("    <infowarnings>%s</infowarnings>\n", buildsettings.options & BuildOptions.warnings ? "1" : "0");
360 				ret.formattedWrite("    <checkProperty>%s</checkProperty>\n", buildsettings.options & BuildOptions.property ? "1" : "0");
361 				ret.formattedWrite("    <genStackFrame>%s</genStackFrame>\n", buildsettings.options & BuildOptions.alwaysStackFrame ? "1" : "0");
362 				ret.put("    <pic>0</pic>\n");
363 				ret.formattedWrite("    <cov>%s</cov>\n", buildsettings.options & BuildOptions.coverage ? "1" : "0");
364 				ret.put("    <nofloat>0</nofloat>\n");
365 				ret.put("    <Dversion>2</Dversion>\n");
366 				ret.formattedWrite("    <ignoreUnsupportedPragmas>%s</ignoreUnsupportedPragmas>\n", buildsettings.options & BuildOptions.ignoreUnknownPragmas ? "1" : "0");
367 				ret.formattedWrite("    <compiler>%s</compiler>\n", settings.compiler.name == "ldc" ? 2 : settings.compiler.name == "gdc" ? 1 : 0);
368 				ret.formattedWrite("    <otherDMD>0</otherDMD>\n");
369 				ret.formattedWrite("    <outdir>%s</outdir>\n", bin_path.toNativeString());
370 				ret.formattedWrite("    <objdir>.dub/obj/%s/%s</objdir>\n", to!string(type), intersubdir);
371 				ret.put("    <objname />\n");
372 				ret.put("    <libname />\n");
373 				ret.put("    <doDocComments>0</doDocComments>\n");
374 				ret.put("    <docdir />\n");
375 				ret.put("    <docname />\n");
376 				ret.put("    <modules_ddoc />\n");
377 				ret.put("    <ddocfiles />\n");
378 				ret.put("    <doHdrGeneration>0</doHdrGeneration>\n");
379 				ret.put("    <hdrdir />\n");
380 				ret.put("    <hdrname />\n");
381 				ret.put("    <doXGeneration>1</doXGeneration>\n");
382 				ret.put("    <xfilename>$(IntDir)\\$(TargetName).json</xfilename>\n");
383 				ret.put("    <debuglevel>0</debuglevel>\n");
384 				ret.put("    <versionlevel>0</versionlevel>\n");
385 				ret.put("    <debugids />\n");
386 				ret.put("    <dump_source>0</dump_source>\n");
387 				ret.put("    <mapverbosity>0</mapverbosity>\n");
388 				ret.put("    <createImplib>0</createImplib>\n");
389 				ret.put("    <defaultlibname />\n");
390 				ret.put("    <debuglibname />\n");
391 				ret.put("    <moduleDepsFile />\n");
392 				ret.put("    <run>0</run>\n");
393 				ret.put("    <runargs />\n");
394 				ret.put("    <runCv2pdb>1</runCv2pdb>\n");
395 				ret.put("    <pathCv2pdb>$(VisualDInstallDir)cv2pdb\\cv2pdb.exe</pathCv2pdb>\n");
396 				ret.put("    <cv2pdbPre2043>0</cv2pdbPre2043>\n");
397 				ret.put("    <cv2pdbNoDemangle>0</cv2pdbNoDemangle>\n");
398 				ret.put("    <cv2pdbEnumType>0</cv2pdbEnumType>\n");
399 				ret.put("    <cv2pdbOptions />\n");
400 				ret.put("    <objfiles />\n");
401 				ret.put("    <linkswitches />\n");
402 				ret.put("    <libpaths />\n");
403 				ret.put("    <deffile />\n");
404 				ret.put("    <resfile />\n");
405 				ret.put("    <preBuildCommand />\n");
406 				ret.put("    <postBuildCommand />\n");
407 				ret.put("    <filesToClean>*.obj;*.cmd;*.build;*.dep</filesToClean>\n");
408 				ret.put("  </Config>\n");
409 			} // foreach(architecture)
410 		}
411 		
412 		void performOnDependencies(const Package main, string[string] configs, void delegate(const Package pack) op)
413 		{
414 			foreach (p; m_project.getTopologicalPackageList(false, main, configs)) {
415 				if (p is main) continue;
416 				op(p);
417 			}
418 		}
419 		
420 		string generateUUID() const {
421 			import std..string;
422 			return "{" ~ toUpper(randomUUID().toString()) ~ "}";
423 		}
424 		
425 		string guid(string projectName) {
426 			if(projectName !in m_projectUuids)
427 				m_projectUuids[projectName] = generateUUID();
428 			return m_projectUuids[projectName];
429 		}
430 
431 		auto solutionFileName() const {
432 			version(DUBBING) return getPackageFileName(m_project.mainPackage()) ~ ".dubbed.sln";
433 			else return getPackageFileName(m_project.mainPackage.name) ~ ".sln";
434 		}
435 
436 		Path projFileName(string pack) const {
437 			auto basepath = Path(".");//Path(".dub/");
438 			version(DUBBING) return basepath ~ (getPackageFileName(pack) ~ ".dubbed.visualdproj");
439 			else return basepath ~ (getPackageFileName(pack) ~ ".visualdproj");
440 		}
441 	}
442 
443 	// TODO: nice folders
444 	struct SourceFile {
445 		Path structurePath;
446 		Path filePath;
447 		bool build;
448 
449 		hash_t toHash() const nothrow @trusted { return structurePath.toHash() ^ filePath.toHash() ^ (build * 0x1f3e7b2c); }
450 		int opCmp(ref const SourceFile rhs) const { return sortOrder(this, rhs); }
451 		// "a < b" for folder structures (deepest folder first, else lexical)
452 		private final static int sortOrder(ref const SourceFile a, ref const SourceFile b) {
453 			assert(!a.structurePath.empty());
454 			assert(!b.structurePath.empty());
455 			auto as = a.structurePath;
456 			auto bs = b.structurePath;
457 
458 			// Check for different folders, compare folders only (omit last one).
459 			for(uint idx=0; idx<min(as.length-1, bs.length-1); ++idx)
460 				if(as[idx] != bs[idx])
461 					return as[idx].opCmp(bs[idx]);
462 
463 			if(as.length != bs.length) {
464 				// If length differ, the longer one is "smaller", that is more 
465 				// specialized and will be put out first.
466 				return as.length > bs.length? -1 : 1;
467 			}
468 			else {
469 				// Both paths indicate files in the same directory, use lexical
470 				// ordering for those.
471 				return as.head.opCmp(bs.head);
472 			}
473 		}
474 	}
475 
476 	auto sortedSources(SourceFile[] sources) {
477 		return sort(sources);
478 	}
479 
480 	unittest {
481 		SourceFile[] sfs = [
482 			{ Path("b/file.d"), Path("") },
483 			{ Path("b/b/fileA.d"), Path("") },
484 			{ Path("a/file.d"), Path("") },
485 			{ Path("b/b/fileB.d"), Path("") },
486 			{ Path("b/b/b/fileA.d"), Path("") },
487 			{ Path("b/c/fileA.d"), Path("") },
488 		];
489 		auto sorted = sort(sfs);
490 		SourceFile[] sortedSfs;
491 		foreach(sr; sorted)
492 			sortedSfs ~= sr;
493 		assert(sortedSfs[0].structurePath == Path("a/file.d"), "1");
494 		assert(sortedSfs[1].structurePath == Path("b/b/b/fileA.d"), "2");
495 		assert(sortedSfs[2].structurePath == Path("b/b/fileA.d"), "3");
496 		assert(sortedSfs[3].structurePath == Path("b/b/fileB.d"), "4");
497 		assert(sortedSfs[4].structurePath == Path("b/c/fileA.d"), "5");
498 		assert(sortedSfs[5].structurePath == Path("b/file.d"), "6");
499 	}
500 }
501 
502 private Path determineStructurePath(Path file_path, in ProjectGenerator.TargetInfo target)
503 {
504 	foreach (p; target.packages) {
505 		if (file_path.startsWith(p.path))
506 			return Path(getPackageFileName(p.name)) ~ file_path[p.path.length .. $];
507 	}
508 	return Path("misc/") ~ file_path.head;
509 }
510 
511 private string getPackageFileName(string pack)
512 {
513 	return pack.replace(":", "_");
514 }