1 /** 2 Generator for MonoD project files 3 4 Copyright: © 2013-2013 rejectedsoftware e.K. 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module dub.generators.monod; 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 // TODO: handle pre/post build commands 28 29 class MonoDGenerator : ProjectGenerator { 30 private { 31 Project m_app; 32 PackageManager m_pkgMgr; 33 string[string] m_projectUuids; 34 bool m_singleProject = true; 35 Config[] m_allConfigs; 36 } 37 38 this(Project app, PackageManager mgr) 39 { 40 m_app = app; 41 m_pkgMgr = mgr; 42 m_allConfigs ~= Config("Debug", "AnyCPU", "Any CPU"); 43 } 44 45 void generateProject(GeneratorSettings settings) 46 { 47 logWarn("Note that the latest Mono-D has direct support for building DUB projects. It is recommended to directly open package.json instead of generating a Mono-D project."); 48 49 auto buildsettings = settings.buildSettings; 50 m_app.addBuildSettings(buildsettings, settings.platform, settings.config); 51 52 prepareGeneration(buildsettings); 53 54 logDebug("About to generate projects for %s, with %s direct dependencies.", m_app.mainPackage().name, m_app.mainPackage().dependencies().length); 55 generateProjects(m_app.mainPackage(), settings); 56 generateSolution(settings); 57 58 finalizeGeneration(buildsettings, true); 59 } 60 61 private void generateSolution(GeneratorSettings settings) 62 { 63 auto sln = openFile(m_app.mainPackage().name ~ ".sln", FileMode.CreateTrunc); 64 scope(exit) sln.close(); 65 66 // Writing solution file 67 logDebug("About to write to .sln file."); 68 69 // Solution header 70 sln.put('\n'); 71 sln.put("Microsoft Visual Studio Solution File, Format Version 11.00\n"); 72 sln.put("# Visual Studio 2010\n"); 73 74 generateSolutionEntry(sln, settings, m_app.mainPackage); 75 if( !m_singleProject ) 76 performOnDependencies(m_app.mainPackage, pack => generateSolutionEntry(sln, settings, pack)); 77 78 sln.put("Global\n"); 79 80 // configuration platforms 81 sln.put("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n"); 82 foreach(config; m_allConfigs) 83 sln.formattedWrite("\t\t%s|%s = %s|%s\n", config.configName, config.platformName2, 84 config.configName, config.platformName2); 85 sln.put("\tEndGlobalSection\n"); 86 87 // configuration platforms per project 88 sln.put("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n"); 89 auto projectUuid = guid(m_app.mainPackage.name); 90 foreach(config; m_allConfigs) 91 foreach(s; ["ActiveCfg", "Build.0"]) 92 sln.formattedWrite("\t\t%s.%s|%s.%s = %s|%s\n", 93 projectUuid, config.configName, config.platformName2, s, 94 config.configName, config.platformName2); 95 // TODO: for all dependencies 96 sln.put("\tEndGlobalSection\n"); 97 98 // solution properties 99 sln.put("\tGlobalSection(SolutionProperties) = preSolution\n"); 100 sln.put("\t\tHideSolutionNode = FALSE\n"); 101 sln.put("\tEndGlobalSection\n"); 102 103 // monodevelop properties 104 sln.put("\tGlobalSection(MonoDevelopProperties) = preSolution\n"); 105 sln.formattedWrite("\t\tStartupItem = %s\n", "monodtest/monodtest.dproj"); 106 sln.put("\tEndGlobalSection\n"); 107 108 sln.put("EndGlobal\n"); 109 } 110 111 private void generateSolutionEntry(RangeFile ret, GeneratorSettings settings, const Package pack) 112 { 113 auto projUuid = generateUUID(); 114 auto projName = pack.name; 115 auto projPath = pack.name ~ ".dproj"; 116 auto projectUuid = guid(projName); 117 118 // Write project header, like so 119 // Project("{002A2DE9-8BB6-484D-9802-7E4AD4084715}") = "derelict", "..\inbase\source\derelict.visualdproj", "{905EF5DA-649E-45F9-9C15-6630AA815ACB}" 120 ret.formattedWrite("Project(\"%s\") = \"%s\", \"%s\", \"%s\"\n", 121 projUuid, projName, projPath, projectUuid); 122 123 if( !m_singleProject ){ 124 if(pack.dependencies.length > 0) { 125 ret.put(" ProjectSection(ProjectDependencies) = postProject\n"); 126 foreach(id, dependency; pack.dependencies) { 127 // TODO: clarify what "uuid = uuid" should mean 128 auto uuid = guid(id); 129 ret.formattedWrite(" %s = %s\n", uuid, uuid); 130 } 131 ret.put(" EndProjectSection\n"); 132 } 133 } 134 135 ret.put("EndProject\n"); 136 } 137 138 private void generateProjects(in Package pack, GeneratorSettings settings) 139 { 140 bool[const(Package)] visited; 141 142 void generateRec(in Package p){ 143 if( p in visited ) return; 144 visited[p] = true; 145 146 generateProject(p, settings); 147 148 if( !m_singleProject ) 149 performOnDependencies(p, &generateRec); 150 } 151 generateRec(pack); 152 } 153 154 private void generateProject(in Package pack, GeneratorSettings settings) 155 { 156 logDebug("About to write to '%s.dproj' file", pack.name); 157 auto sln = openFile(pack.name ~ ".dproj", FileMode.CreateTrunc); 158 scope(exit) sln.close(); 159 160 sln.put("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); 161 sln.put("<Project DefaultTargets=\"Build\" ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n"); 162 // TODO: property groups 163 164 auto projName = pack.name; 165 166 auto buildsettings = settings.buildSettings; 167 m_app.addBuildSettings(buildsettings, settings.platform, m_app.getDefaultConfiguration(settings.platform)); 168 169 // Mono-D does not have a setting for string import paths 170 settings.compiler.prepareBuildSettings(buildsettings, BuildSetting.all & ~BuildSetting.stringImportPaths); 171 172 sln.put(" <PropertyGroup>\n"); 173 sln.put(" <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\n"); 174 sln.put(" <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\n"); 175 sln.put(" <ProductVersion>10.0.0</ProductVersion>\n"); 176 sln.put(" <SchemaVersion>2.0</SchemaVersion>\n"); 177 sln.formattedWrite(" <ProjectGuid>%s</ProjectGuid>\n", guid(pack.name)); 178 sln.put(" <PreferOneStepBuild>True</PreferOneStepBuild>\n"); 179 sln.put(" <UseDefaultCompiler>True</UseDefaultCompiler>\n"); 180 sln.put(" <IncrementalLinking>True</IncrementalLinking>\n"); 181 sln.put(" <Compiler>DMD2</Compiler>\n"); 182 if( !buildsettings.versions.empty ){ 183 sln.put(" <VersionIds>\n"); 184 sln.put(" <VersionIds>\n"); 185 foreach(ver; buildsettings.versions) 186 sln.formattedWrite(" <String>%s</String>\n", ver); 187 sln.put(" </VersionIds>\n"); 188 sln.put(" </VersionIds>\n"); 189 } 190 if( !buildsettings.importPaths.empty ){ 191 sln.put(" <Includes>\n"); 192 sln.put(" <Includes>\n"); 193 foreach(dir; buildsettings.importPaths) 194 sln.formattedWrite(" <Path>%s</Path>\n", dir); 195 sln.put(" </Includes>\n"); 196 sln.put(" </Includes>\n"); 197 } 198 if( !buildsettings.libs.empty ){ 199 sln.put(" <Libs>\n"); 200 sln.put(" <Libs>\n"); 201 foreach(dir; buildsettings.libs) 202 sln.formattedWrite(" <Lib>%s</Lib>\n", settings.platform.platform.canFind("windows") ? dir ~ ".lib" : dir); 203 sln.put(" </Libs>\n"); 204 sln.put(" </Libs>\n"); 205 } 206 sln.formattedWrite(" <ExtraCompilerArguments>%s</ExtraCompilerArguments>\n", buildsettings.dflags.join(" ")); 207 sln.formattedWrite(" <ExtraLinkerArguments>%s</ExtraLinkerArguments>\n", buildsettings.lflags.join(" ")); 208 sln.put(" </PropertyGroup>\n"); 209 210 void generateProperties(Config config) 211 { 212 sln.formattedWrite(" <PropertyGroup Condition=\" '$(Configuration)|$(Platform)' == '%s|%s' \">\n", 213 config.configName, config.platformName); 214 215 sln.put(" <DebugSymbols>True</DebugSymbols>\n"); 216 auto outpath = Path(buildsettings.targetPath).toNativeString(); 217 sln.formattedWrite(" <OutputPath>%s</OutputPath>\n", outpath.length ? outpath : "."); 218 sln.put(" <Externalconsole>True</Externalconsole>\n"); 219 sln.put(" <Target>Executable</Target>\n"); 220 sln.formattedWrite(" <OutputName>%s</OutputName>\n", buildsettings.targetName); 221 sln.put(" <UnittestMode>False</UnittestMode>\n"); 222 sln.formattedWrite(" <ObjectsDirectory>%s</ObjectsDirectory>\n", (Path("obj/")~config.configName).toNativeString()); 223 sln.put(" <DebugLevel>0</DebugLevel>\n"); 224 sln.put(" </PropertyGroup>\n"); 225 } 226 227 foreach(config; m_allConfigs) 228 generateProperties(config); 229 230 231 bool[const(Package)] visited; 232 void generateSourceEntry(Path path, Path base_path, bool compile = true) 233 { 234 auto rel_path = path.relativeTo(pack.path); 235 rel_path.normalize(); 236 237 Path pretty_path; 238 foreach (i; 0 .. rel_path.length) 239 if (rel_path[i] != "..") { 240 pretty_path = rel_path[i .. $]; 241 break; 242 } 243 string kind = compile ? "Compile" : "None"; 244 245 if (base_path == pretty_path) { 246 sln.formattedWrite(" <%s Include=\"%s\" />\n", kind, rel_path.toNativeString()); 247 } else { 248 sln.formattedWrite(" <%s Include=\"%s\">\n", kind, rel_path.toNativeString()); 249 sln.formattedWrite(" <Link>%s</Link>\n", pretty_path.toNativeString()); 250 sln.formattedWrite(" </%s>\n", kind); 251 } 252 } 253 254 sln.put(" <ItemGroup>\n"); 255 // add source files 256 foreach (s; buildsettings.sourceFiles) { 257 auto sp = Path(s); 258 if (!sp.absolute) sp = pack.path ~ sp; 259 generateSourceEntry(sp, pack.path); 260 } 261 // TODO: add all files in stringImportFolders 262 // add package.json files 263 foreach (p; m_app.getTopologicalPackageList()) 264 generateSourceEntry(p.packageInfoFile, pack.path, false); 265 sln.put(" </ItemGroup>\n"); 266 sln.put("</Project>"); 267 } 268 269 void performOnDependencies(const Package main, void delegate(const Package pack) op) 270 { 271 bool[const(Package)] visited; 272 void perform_rec(const Package parent_pack){ 273 foreach(id, dependency; parent_pack.dependencies){ 274 logDiagnostic("Retrieving package %s from package manager.", id); 275 auto pack = m_pkgMgr.getBestPackage(id, dependency); 276 if( pack in visited ) continue; 277 visited[pack] = true; 278 if(pack is null) { 279 logWarn("Package %s (%s) could not be retrieved continuing...", id, to!string(dependency)); 280 continue; 281 } 282 logDiagnostic("Performing on retrieved package %s", pack.name); 283 op(pack); 284 perform_rec(pack); 285 } 286 } 287 288 perform_rec(main); 289 } 290 291 string generateUUID() 292 const { 293 return "{" ~ randomUUID().toString() ~ "}"; 294 } 295 296 string guid(string projectName) 297 { 298 if(projectName !in m_projectUuids) 299 m_projectUuids[projectName] = generateUUID(); 300 return m_projectUuids[projectName]; 301 } 302 } 303 304 struct Config { 305 string configName; 306 string platformName; 307 string platformName2; 308 }