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