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.format; 23 import std.uuid; 24 import std.exception; 25 26 27 // Dubbing is developing dub... 28 //version = DUBBING; 29 30 // TODO: handle pre/post build commands 31 32 33 class VisualDGenerator : ProjectGenerator { 34 private { 35 Project m_app; 36 PackageManager m_pkgMgr; 37 string[string] m_projectUuids; 38 bool m_combinedProject; 39 } 40 41 this(Project app, PackageManager mgr, bool combined_project) 42 { 43 m_combinedProject = combined_project; 44 m_app = app; 45 m_pkgMgr = mgr; 46 } 47 48 void generateProject(GeneratorSettings settings) 49 { 50 auto buildsettings = settings.buildSettings; 51 m_app.addBuildSettings(buildsettings, settings.platform, settings.config); 52 53 prepareGeneration(buildsettings); 54 55 logDebug("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, m_app.mainPackage().dependencies().length); 56 generateProjects(m_app.mainPackage(), settings); 57 generateSolution(settings); 58 logInfo("VisualD project generated."); 59 60 finalizeGeneration(buildsettings, true); 61 } 62 63 private { 64 void generateSolution(GeneratorSettings settings) 65 { 66 auto ret = appender!(char[])(); 67 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 68 69 // Solution header 70 ret.formattedWrite(" 71 Microsoft Visual Studio Solution File, Format Version 11.00 72 # Visual Studio 2010"); 73 74 generateSolutionEntry(ret, m_app.mainPackage, settings); 75 if (!m_combinedProject) { 76 performOnDependencies(m_app.mainPackage, configs, (pack){ 77 generateSolutionEntry(ret, pack, settings); 78 }); 79 } 80 81 // Global section contains configurations 82 ret.formattedWrite(" 83 Global 84 GlobalSection(SolutionConfigurationPlatforms) = preSolution 85 Debug|Win32 = Debug|Win32 86 Release|Win32 = Release|Win32 87 Unittest|Win32 = Unittest|Win32 88 EndGlobalSection 89 GlobalSection(ProjectConfigurationPlatforms) = postSolution"); 90 91 generateSolutionConfig(ret, m_app.mainPackage()); 92 93 // TODO: for all dependencies 94 95 ret.formattedWrite(" 96 GlobalSection(SolutionProperties) = preSolution 97 HideSolutionNode = FALSE 98 EndGlobalSection 99 EndGlobal"); 100 101 // Writing solution file 102 logDebug("About to write to .sln file with %s bytes", to!string(ret.data().length)); 103 auto sln = openFile(solutionFileName(), FileMode.CreateTrunc); 104 scope(exit) sln.close(); 105 sln.put(ret.data()); 106 sln.flush(); 107 } 108 109 void generateSolutionEntry(Appender!(char[]) ret, const Package pack, GeneratorSettings settings) 110 { 111 auto projUuid = generateUUID(); 112 auto projName = pack.name; 113 auto projPath = projFileName(pack); 114 auto projectUuid = guid(projName); 115 116 // Write project header, like so 117 // Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "derelict", "..\inbase\source\derelict.visualdproj", "{905EF5DA-649E-45F9-9C15-6630AA815ACB}" 118 ret.formattedWrite("\nProject(\"%s\") = \"%s\", \"%s\", \"%s\"", 119 projUuid, projName, projPath, projectUuid); 120 121 if (!m_combinedProject) { 122 void addDepsRec(in Package p) 123 { 124 foreach(id, dependency; p.dependencies) { 125 auto deppack = m_app.getDependency(id, true); 126 if (!deppack) continue; 127 if (isHeaderOnlyPackage(deppack, settings)) { 128 addDepsRec(deppack); 129 } else if (!m_app.isRedundantDependency(p, deppack)) { 130 // TODO: clarify what "uuid = uuid" should mean 131 auto uuid = guid(id); 132 ret.formattedWrite("\n %s = %s", uuid, uuid); 133 } 134 } 135 } 136 137 if(pack.dependencies.length > 0) { 138 ret.formattedWrite(" 139 ProjectSection(ProjectDependencies) = postProject"); 140 addDepsRec(pack); 141 ret.formattedWrite(" 142 EndProjectSection"); 143 } 144 } 145 146 ret.formattedWrite("\nEndProject"); 147 } 148 149 void generateSolutionConfig(Appender!(char[]) ret, const Package pack) { 150 const string[] sub = [ "ActiveCfg", "Build.0" ]; 151 const string[] conf = [ "Debug|Win32", "Release|Win32" /*, "Unittest|Win32" */]; 152 auto projectUuid = guid(pack.name()); 153 foreach(c; conf) 154 foreach(s; sub) 155 formattedWrite(ret, "\n\t\t%s.%s.%s = %s", to!string(projectUuid), c, s, c); 156 } 157 158 void generateProjects(const Package main, GeneratorSettings settings) { 159 160 // TODO: cyclic check 161 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 162 163 generateProj(main, settings); 164 165 if (!m_combinedProject) { 166 bool[string] generatedProjects; 167 generatedProjects[main.name] = true; 168 performOnDependencies(main, configs, (const Package dependency) { 169 if(dependency.name in generatedProjects) 170 return; 171 generateProj(dependency, settings); 172 } ); 173 } 174 } 175 176 bool isHeaderOnlyPackage(in Package pack, in GeneratorSettings settings) 177 const { 178 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 179 auto pbuildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); 180 if (!pbuildsettings.sourceFiles.any!(f => f.endsWith(".d"))()) 181 return true; 182 return false; 183 } 184 185 void generateProj(const Package pack, GeneratorSettings settings) 186 { 187 int i = 0; 188 auto ret = appender!(char[])(); 189 190 auto projName = pack.name; 191 auto project_file_dir = m_app.mainPackage.path ~ projFileName(pack).parentPath; 192 ret.put("<DProject>\n"); 193 ret.formattedWrite(" <ProjectGuid>%s</ProjectGuid>\n", guid(projName)); 194 195 // Several configurations (debug, release, unittest) 196 generateProjectConfiguration(ret, pack, "debug", settings); 197 generateProjectConfiguration(ret, pack, "release", settings); 198 generateProjectConfiguration(ret, pack, "unittest", settings); 199 200 // Add all files 201 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 202 auto files = pack.getBuildSettings(settings.platform, configs[pack.name]); 203 bool[SourceFile] sourceFiles; 204 if (m_combinedProject) { 205 206 bool[const(Package)] basePackagesAdded; 207 208 // add all package.json files to the project 209 // and all source files 210 performOnDependencies(pack, configs, (prj) { 211 212 string[] prjFiles; 213 214 // Avoid multiples package.json when using sub-packages. 215 // Only add the package info file if no other package/sub-package from the same base package 216 // has been seen yet. 217 { 218 const(Package) base = prj.basePackage(); 219 220 if (base !in basePackagesAdded) 221 { 222 prjFiles ~= prj.packageInfoFile.toNativeString(); 223 basePackagesAdded[base] = true; 224 } 225 } 226 227 prjFiles = prjFiles ~ prj.getBuildSettings(settings.platform, configs[prj.name]).sourceFiles; 228 229 foreach(s; prjFiles){ 230 auto sp = Path(s); 231 if( !sp.absolute ) sp = prj.path ~ sp; 232 SourceFile sf; 233 sf.pkg = pack.name; 234 sf.filePath = sp.relativeTo(project_file_dir); 235 236 // regroup in Folder by base package 237 sf.structurePath = Path(prj.basePackage().name) ~ sp.relativeTo(prj.path); 238 sourceFiles[sf] = true; 239 } 240 }); 241 } 242 243 files.sourceFiles ~= pack.packageInfoFile.toNativeString(); 244 245 foreach(s; files.sourceFiles){ 246 auto sp = Path(s); 247 if( !sp.absolute ) sp = pack.path ~ sp; 248 SourceFile sf; 249 sf.pkg = pack.name; 250 sf.filePath = sp.relativeTo(project_file_dir); 251 sf.structurePath = sp.relativeTo(pack.path); 252 sourceFiles[sf] = true; 253 } 254 255 // Create folders and files 256 ret.formattedWrite(" <Folder name=\"%s\">", getPackageFileName(pack)); 257 Path lastFolder; 258 foreach(source; sortedSources(sourceFiles.keys)) { 259 logDebug("source looking at %s", source.structurePath); 260 auto cur = source.structurePath[0 .. source.structurePath.length-1]; 261 if(lastFolder != cur) { 262 size_t same = 0; 263 foreach(idx; 0..min(lastFolder.length, cur.length)) 264 if(lastFolder[idx] != cur[idx]) break; 265 else same = idx+1; 266 267 const decrease = lastFolder.length - min(lastFolder.length, same); 268 const increase = cur.length - min(cur.length, same); 269 270 foreach(unused; 0..decrease) 271 ret.put("\n </Folder>"); 272 foreach(idx; 0..increase) 273 ret.formattedWrite("\n <Folder name=\"%s\">", cur[same + idx].toString()); 274 lastFolder = cur; 275 } 276 ret.formattedWrite("\n <File path=\"%s\" />", source.filePath.toNativeString()); 277 } 278 // Finalize all open folders 279 foreach(unused; 0..lastFolder.length) 280 ret.put("\n </Folder>"); 281 ret.put("\n </Folder>\n</DProject>"); 282 283 logDebug("About to write to '%s.visualdproj' file %s bytes", getPackageFileName(pack), ret.data().length); 284 auto proj = openFile(projFileName(pack), FileMode.CreateTrunc); 285 scope(exit) proj.close(); 286 proj.put(ret.data()); 287 proj.flush(); 288 } 289 290 void generateProjectConfiguration(Appender!(char[]) ret, const Package pack, string type, GeneratorSettings settings) 291 { 292 auto project_file_dir = m_app.mainPackage.path ~ projFileName(pack).parentPath; 293 auto configs = m_app.getPackageConfigs(settings.platform, settings.config); 294 auto buildsettings = settings.buildSettings; 295 auto pbuildsettings = pack.getBuildSettings(settings.platform, configs[pack.name]); 296 m_app.addBuildSettings(buildsettings, settings.platform, settings.config, pack); 297 m_app.addBuildTypeSettings(buildsettings, settings.platform, type); 298 settings.compiler.extractBuildOptions(buildsettings); 299 300 string[] getSettings(string setting)(){ return __traits(getMember, buildsettings, setting); } 301 string[] getPathSettings(string setting)() 302 { 303 auto settings = getSettings!setting(); 304 auto ret = new string[settings.length]; 305 foreach (i; 0 .. settings.length) ret[i] = (Path(settings[i]).relativeTo(project_file_dir)).toNativeString(); 306 return ret; 307 } 308 309 foreach(architecture; settings.platform.architecture) { 310 string arch; 311 switch(architecture) { 312 default: logWarn("Unsupported platform('%s'), defaulting to x86", architecture); goto case; 313 case "x86": arch = "Win32"; break; 314 case "x86_64": arch = "x64"; break; 315 } 316 ret.formattedWrite(" <Config name=\"%s\" platform=\"%s\">\n", to!string(type), arch); 317 318 // FIXME: handle compiler options in an abstract way instead of searching for DMD specific flags 319 320 // debug and optimize setting 321 ret.formattedWrite(" <symdebug>%s</symdebug>\n", buildsettings.options & BuildOptions.debugInfo ? "1" : "0"); 322 ret.formattedWrite(" <optimize>%s</optimize>\n", buildsettings.options & BuildOptions.optimize ? "1" : "0"); 323 ret.formattedWrite(" <useInline>%s</useInline>\n", buildsettings.options & BuildOptions.inline ? "1" : "0"); 324 ret.formattedWrite(" <release>%s</release>\n", buildsettings.options & BuildOptions.release ? "1" : "0"); 325 326 // Lib or exe? 327 bool is_lib = pbuildsettings.targetType != TargetType.executable; 328 string debugSuffix = type == "debug" ? "_d" : ""; 329 auto bin_path = pack is m_app.mainPackage ? Path(pbuildsettings.targetPath) : Path(".dub/lib/"); 330 bin_path.endsWithSlash = true; 331 ret.formattedWrite(" <lib>%s</lib>\n", is_lib ? "1" : "0"); 332 ret.formattedWrite(" <exefile>%s%s%s.%s</exefile>\n", bin_path.toNativeString(), pbuildsettings.targetName, debugSuffix, is_lib ? "lib" : "exe"); 333 334 // include paths and string imports 335 string imports = join(getPathSettings!"importPaths"(), " "); 336 string stringImports = join(getPathSettings!"stringImportPaths"(), " "); 337 ret.formattedWrite(" <imppath>%s</imppath>\n", imports); 338 ret.formattedWrite(" <fileImppath>%s</fileImppath>\n", stringImports); 339 340 ret.formattedWrite(" <program>%s</program>\n", "$(DMDInstallDir)windows\\bin\\dmd.exe"); // FIXME: use the actually selected compiler! 341 ret.formattedWrite(" <additionalOptions>%s</additionalOptions>\n", getSettings!"dflags"().join(" ")); 342 343 // Add version identifiers 344 string versions = join(getSettings!"versions"(), " "); 345 ret.formattedWrite(" <versionids>%s</versionids>\n", versions); 346 347 // Add libraries, system libs need to be suffixed by ".lib". 348 string linkLibs = join(map!(a => a~".lib")(getSettings!"libs"()), " "); 349 string addLinkFiles = join(getSettings!"sourceFiles"().filter!(s => s.endsWith(".lib"))(), " "); 350 if (!is_lib) ret.formattedWrite(" <libfiles>%s %s phobos.lib</libfiles>\n", linkLibs, addLinkFiles); 351 352 // Unittests 353 ret.formattedWrite(" <useUnitTests>%s</useUnitTests>\n", buildsettings.options & BuildOptions.unittests ? "1" : "0"); 354 355 // compute directory for intermediate files (need dummy/ because of how -op determines the resulting path) 356 auto relpackpath = pack.path.relativeTo(project_file_dir); 357 uint ndummy = 0; 358 foreach (i; 0 .. relpackpath.length) 359 if (relpackpath[i] == "..") ndummy++; 360 string intersubdir = (ndummy*2 > relpackpath.length ? replicate("dummy/", ndummy*2-relpackpath.length) : "") ~ getPackageFileName(pack); 361 362 ret.put(" <obj>0</obj>\n"); 363 ret.put(" <link>0</link>\n"); 364 ret.put(" <subsystem>0</subsystem>\n"); 365 ret.put(" <multiobj>0</multiobj>\n"); 366 ret.put(" <singleFileCompilation>2</singleFileCompilation>\n"); 367 ret.put(" <oneobj>0</oneobj>\n"); 368 ret.put(" <trace>0</trace>\n"); 369 ret.put(" <quiet>0</quiet>\n"); 370 ret.formattedWrite(" <verbose>%s</verbose>\n", buildsettings.options & BuildOptions.verbose ? "1" : "0"); 371 ret.put(" <vtls>0</vtls>\n"); 372 ret.put(" <cpu>0</cpu>\n"); 373 ret.formattedWrite(" <isX86_64>%s</isX86_64>\n", arch == "x64" ? 1 : 0); 374 ret.put(" <isLinux>0</isLinux>\n"); 375 ret.put(" <isOSX>0</isOSX>\n"); 376 ret.put(" <isWindows>0</isWindows>\n"); 377 ret.put(" <isFreeBSD>0</isFreeBSD>\n"); 378 ret.put(" <isSolaris>0</isSolaris>\n"); 379 ret.put(" <scheduler>0</scheduler>\n"); 380 ret.put(" <useDeprecated>0</useDeprecated>\n"); 381 ret.put(" <useAssert>0</useAssert>\n"); 382 ret.put(" <useInvariants>0</useInvariants>\n"); 383 ret.put(" <useIn>0</useIn>\n"); 384 ret.put(" <useOut>0</useOut>\n"); 385 ret.put(" <useArrayBounds>0</useArrayBounds>\n"); 386 ret.formattedWrite(" <noboundscheck>%s</noboundscheck>\n", buildsettings.options & BuildOptions.noBoundsChecks ? "1" : "0"); 387 ret.put(" <useSwitchError>0</useSwitchError>\n"); 388 ret.put(" <preservePaths>1</preservePaths>\n"); 389 ret.formattedWrite(" <warnings>%s</warnings>\n", buildsettings.options & BuildOptions.warningsAsErrors ? "1" : "0"); 390 ret.formattedWrite(" <infowarnings>%s</infowarnings>\n", buildsettings.options & BuildOptions.warnings ? "1" : "0"); 391 ret.formattedWrite(" <checkProperty>%s</checkProperty>\n", buildsettings.options & BuildOptions.property ? "1" : "0"); 392 ret.formattedWrite(" <genStackFrame>%s</genStackFrame>\n", buildsettings.options & BuildOptions.alwaysStackFrame ? "1" : "0"); 393 ret.put(" <pic>0</pic>\n"); 394 ret.formattedWrite(" <cov>%s</cov>\n", buildsettings.options & BuildOptions.coverage ? "1" : "0"); 395 ret.put(" <nofloat>0</nofloat>\n"); 396 ret.put(" <Dversion>2</Dversion>\n"); 397 ret.formattedWrite(" <ignoreUnsupportedPragmas>%s</ignoreUnsupportedPragmas>\n", buildsettings.options & BuildOptions.ignoreUnknownPragmas ? "1" : "0"); 398 ret.formattedWrite(" <compiler>%s</compiler>\n", settings.compiler.name == "ldc" ? 2 : settings.compiler.name == "gdc" ? 1 : 0); 399 ret.formattedWrite(" <otherDMD>0</otherDMD>\n"); 400 ret.formattedWrite(" <outdir>%s</outdir>\n", bin_path.toNativeString()); 401 ret.formattedWrite(" <objdir>.dub/obj/%s/%s</objdir>\n", to!string(type), intersubdir); 402 ret.put(" <objname />\n"); 403 ret.put(" <libname />\n"); 404 ret.put(" <doDocComments>0</doDocComments>\n"); 405 ret.put(" <docdir />\n"); 406 ret.put(" <docname />\n"); 407 ret.put(" <modules_ddoc />\n"); 408 ret.put(" <ddocfiles />\n"); 409 ret.put(" <doHdrGeneration>0</doHdrGeneration>\n"); 410 ret.put(" <hdrdir />\n"); 411 ret.put(" <hdrname />\n"); 412 ret.put(" <doXGeneration>1</doXGeneration>\n"); 413 ret.put(" <xfilename>$(IntDir)\\$(TargetName).json</xfilename>\n"); 414 ret.put(" <debuglevel>0</debuglevel>\n"); 415 ret.put(" <versionlevel>0</versionlevel>\n"); 416 ret.put(" <debugids />\n"); 417 ret.put(" <dump_source>0</dump_source>\n"); 418 ret.put(" <mapverbosity>0</mapverbosity>\n"); 419 ret.put(" <createImplib>0</createImplib>\n"); 420 ret.put(" <defaultlibname />\n"); 421 ret.put(" <debuglibname />\n"); 422 ret.put(" <moduleDepsFile />\n"); 423 ret.put(" <run>0</run>\n"); 424 ret.put(" <runargs />\n"); 425 ret.put(" <runCv2pdb>1</runCv2pdb>\n"); 426 ret.put(" <pathCv2pdb>$(VisualDInstallDir)cv2pdb\\cv2pdb.exe</pathCv2pdb>\n"); 427 ret.put(" <cv2pdbPre2043>0</cv2pdbPre2043>\n"); 428 ret.put(" <cv2pdbNoDemangle>0</cv2pdbNoDemangle>\n"); 429 ret.put(" <cv2pdbEnumType>0</cv2pdbEnumType>\n"); 430 ret.put(" <cv2pdbOptions />\n"); 431 ret.put(" <objfiles />\n"); 432 ret.put(" <linkswitches />\n"); 433 ret.put(" <libpaths />\n"); 434 ret.put(" <deffile />\n"); 435 ret.put(" <resfile />\n"); 436 ret.put(" <preBuildCommand />\n"); 437 ret.put(" <postBuildCommand />\n"); 438 ret.put(" <filesToClean>*.obj;*.cmd;*.build;*.dep</filesToClean>\n"); 439 ret.put(" </Config>\n"); 440 } // foreach(architecture) 441 } 442 443 void performOnDependencies(const Package main, string[string] configs, void delegate(const Package pack) op) 444 { 445 foreach (p; m_app.getTopologicalPackageList(false, main, configs)) { 446 if (p is main) continue; 447 op(p); 448 } 449 } 450 451 string generateUUID() const { 452 return "{" ~ 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_app.mainPackage()) ~ ".dubbed.sln"; 463 else return getPackageFileName(m_app.mainPackage()) ~ ".sln"; 464 } 465 466 Path projFileName(ref const Package pack) const { 467 auto basepath = Path(".");//Path(".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 struct SourceFile { 475 string pkg; 476 Path structurePath; 477 Path filePath; 478 bool build = true; 479 480 int opCmp(ref const SourceFile rhs) const { return sortOrder(this, rhs); } 481 // "a < b" for folder structures (deepest folder first, else lexical) 482 private final static int sortOrder(ref const SourceFile a, ref const SourceFile b) { 483 enforce(!a.structurePath.empty()); 484 enforce(!b.structurePath.empty()); 485 auto as = a.structurePath; 486 auto bs = b.structurePath; 487 488 // Check for different folders, compare folders only (omit last one). 489 for(uint idx=0; idx<min(as.length-1, bs.length-1); ++idx) 490 if(as[idx] != bs[idx]) 491 return as[idx].opCmp(bs[idx]); 492 493 if(as.length != bs.length) { 494 // If length differ, the longer one is "smaller", that is more 495 // specialized and will be put out first. 496 return as.length > bs.length? -1 : 1; 497 } 498 else { 499 // Both paths indicate files in the same directory, use lexical 500 // ordering for those. 501 return as.head.opCmp(bs.head); 502 } 503 } 504 } 505 506 auto sortedSources(SourceFile[] sources) { 507 return sort(sources); 508 } 509 510 unittest { 511 SourceFile[] sfs = [ 512 { "", Path("b/file.d"), Path("") }, 513 { "", Path("b/b/fileA.d"), Path("") }, 514 { "", Path("a/file.d"), Path("") }, 515 { "", Path("b/b/fileB.d"), Path("") }, 516 { "", Path("b/b/b/fileA.d"), Path("") }, 517 { "", Path("b/c/fileA.d"), Path("") }, 518 ]; 519 auto sorted = sort(sfs); 520 SourceFile[] sortedSfs; 521 foreach(sr; sorted) 522 sortedSfs ~= sr; 523 assert(sortedSfs[0].structurePath == Path("a/file.d"), "1"); 524 assert(sortedSfs[1].structurePath == Path("b/b/b/fileA.d"), "2"); 525 assert(sortedSfs[2].structurePath == Path("b/b/fileA.d"), "3"); 526 assert(sortedSfs[3].structurePath == Path("b/b/fileB.d"), "4"); 527 assert(sortedSfs[4].structurePath == Path("b/c/fileA.d"), "5"); 528 assert(sortedSfs[5].structurePath == Path("b/file.d"), "6"); 529 } 530 } 531 532 private string getPackageFileName(in Package pack) 533 { 534 return pack.name.replace(":", "_"); 535 }