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 }