1 module mage.msbuild.cpp; 2 3 import mage; 4 import mage.msbuild : VSInfo, trPlatform; 5 import mage.msbuild.clcompile; 6 import mage.msbuild.link; 7 import mage.util.option; 8 9 import std.typetuple : allSatisfy; 10 import std.uuid; 11 12 13 struct MSBuildProject 14 { 15 string name; 16 UUID guid; 17 string toolsVersion; 18 MSBuildConfig[] configs; 19 Path[] headers; 20 Path[] cpps; 21 Path[] otherFiles; 22 Target target; 23 24 /// Decides whether this project is the first to show up in the sln file. 25 bool isStartup = false; 26 27 @disable this(); 28 29 this(string name) 30 { 31 this.name = name; 32 this.guid = randomUUID(); 33 } 34 } 35 36 struct MSBuildConfig 37 { 38 string name; 39 string architecture; 40 string type; 41 Path outputFile; 42 Path intermediatesDir; 43 Option!bool useDebugLibs; 44 string platformToolset; 45 string characterSet; 46 Option!bool wholeProgramOptimization; 47 Option!bool linkIncremental; 48 ClCompile clCompile; 49 Link link; 50 Path[] headerFiles; 51 Path[] compilationUnits; 52 } 53 54 55 string trWarningLevel(int level) { 56 return "Level %s".format(level); 57 } 58 59 string trOptimization(int level) { 60 try { 61 return [ "Disabled", "MinSize", "MaxSpeed", "Full" ][level]; 62 } 63 catch(core.exception.RangeError) { 64 log.warning("Unsupported warning level '%'".format(level)); 65 } 66 return null; 67 } 68 69 string trType2FileExt(string type) 70 { 71 try { 72 return [ "Application" : ".exe", "StaticLibrary" : ".lib" ][type]; 73 } 74 catch(core.exception.RangeError) { 75 log.warning(`Unsupported config type "%s"`.format(type)); 76 } 77 return null; 78 } 79 80 81 MSBuildProject createProject(MagicContext context, ref in VSInfo info, Target target) 82 { 83 string name; 84 { 85 auto varName = target["name"]; 86 if(!varName.hasValue()) 87 { 88 log.error("Target must have a `name' property!"); 89 assert(0); 90 } 91 name = varName.get!string(); 92 } 93 94 auto _= log.Block(`Create Cpp MSBuildProject for "%s"`, name); 95 96 auto localDefaults = Properties("%s_defaults".format(name)); 97 localDefaults["outputDir"] = Path("$(SolutionDir)$(Platform)$(Configuration)"); 98 localDefaults["characterSet"] = "Unicode"; 99 100 target.properties.prettyPrint(); 101 102 auto projEnv = Environment("%s_proj_env".format(name), target.properties, *G.env[0], localDefaults, *G.env[1]); 103 context.setActive(projEnv); 104 scope(exit) context.popActive(); 105 106 auto proj = MSBuildProject(name); 107 if(auto var = target.tryGet("isStartup")) { 108 proj.isStartup = var.get!bool; 109 } 110 proj.target = target; 111 proj.toolsVersion = info.toolsVersion; 112 113 auto cfgs = projEnv.first("configurations") 114 .enforce("No `configurations' found."); 115 foreach(ref Config cfg; *cfgs) 116 { 117 proj.configs.length += 1; 118 auto projCfg = &proj.configs[$-1]; 119 120 import std.traits; 121 pragma(msg, fullyQualifiedName!(typeof(cfg))); 122 auto env = Environment(projEnv.name ~ "_cfg", cfg.properties, projEnv.env); 123 context.setActive(env); 124 scope(exit) context.popActive(); 125 126 Properties fallback; 127 auto fallbackEnv = Environment(env.name ~ "_fallback", fallback); 128 fallbackEnv["characterSet"] = "Unicode"; 129 fallbackEnv["wholeProgramOptimization"] = false; 130 fallbackEnv["intermediatesDir"] = Path("$(SolutionDir)temp/$(TargetName)_$(Platform)_$(Configuration)"); 131 fallbackEnv["linkIncremental"] = false; 132 env.internal = &fallbackEnv; 133 134 projCfg.name = env.configName(); 135 log.info("Configuration: %s".format(projCfg.name)); 136 fallback.name = "%s_fallback".format(projCfg.name); 137 138 projCfg.architecture = env.configArchitecture(); 139 log.info("Architecture: %s".format(projCfg.architecture)); 140 141 projCfg.type = env.configType(); 142 projCfg.useDebugLibs = env.configUseDebugLibgs(projCfg.name); 143 projCfg.platformToolset = env.configPlatformToolset(info); 144 projCfg.characterSet = env.configCharacterSet(); 145 projCfg.wholeProgramOptimization = env.configWholeProgramOptimization(); 146 projCfg.outputFile = env.configOutputFile(proj, *projCfg); 147 projCfg.intermediatesDir = env.configIntermediatesDir(); 148 projCfg.linkIncremental = env.configLinkIncremental(); 149 env.configFiles(projCfg.headerFiles, projCfg.compilationUnits); 150 151 sanitize(*projCfg); 152 153 projCfg.clCompile = createClCompile(*projCfg, env); 154 projCfg.link = createLink(*projCfg, info, env); 155 } 156 157 return proj; 158 } 159 160 161 private auto required(ref Environment env, string propName) 162 { 163 return env.first(propName).enforce("Missing required property `%s'.".format(propName)); 164 } 165 166 private auto optional(ref Environment env, string propName) 167 { 168 auto pVar = env.first(propName); 169 if(pVar is null || !pVar.hasValue()) { 170 log.trace("Missing optional property `%s'.", propName); 171 assert(env.internal, "Missing fallback environment."); 172 pVar = env.internal.first(propName); 173 assert(pVar, "Missing fallback value."); 174 } 175 return pVar; 176 } 177 178 179 string configName(ref Environment env) 180 { 181 return env.required("name") 182 .get!string; 183 } 184 185 string configArchitecture(ref Environment env) 186 { 187 auto arch = env.required("architecture"); 188 return trPlatform(arch.get!string); 189 } 190 191 string configType(ref Environment env) 192 { 193 auto type = env.required("type") 194 .get!string; 195 switch(type) { 196 case "executable": return "Application"; 197 case "library": 198 { 199 auto libType = env.required("libType") 200 .get!LibraryType; 201 final switch(libType) 202 { 203 case LibraryType.Static: return "StaticLibrary"; 204 case LibraryType.Shared: assert(0, "Not implemented."); 205 } 206 } 207 default: break; 208 } 209 210 assert(0, "Unknown config type: %s".format(type)); 211 } 212 213 string configCharacterSet(ref Environment env) 214 { 215 auto varCharset = env.optional("characterSet"); 216 // TODO Check for correct values. 217 return varCharset.get!string; 218 } 219 220 bool configWholeProgramOptimization(ref Environment env) 221 { 222 auto varValue = env.optional("wholeProgramOptimization"); 223 return varValue.get!bool; 224 } 225 226 Path configOutputFile(ref Environment env, ref MSBuildProject proj, ref MSBuildConfig cfg) 227 { 228 Path path; 229 230 auto pVar = env.first("outputFile"); 231 if(pVar) 232 { 233 path = pVar.get!Path; 234 } 235 else 236 { 237 pVar = env.first("outputDir") 238 .enforce("Neither `outputFile' not `outputDir' was found, " 239 "but need at least one of them."); 240 path = absolute(pVar.get!Path() ~ (proj.name ~ trType2FileExt(cfg.type)), env["mageFilePath"].get!Path.parent); 241 } 242 243 return path; 244 } 245 246 Path configIntermediatesDir(ref Environment env) 247 { 248 auto pVar = env.optional("intermediatesDir"); 249 return pVar.get!Path.absolute(env["mageFilePath"].get!Path.parent); 250 } 251 252 bool configLinkIncremental(ref Environment env) 253 { 254 auto pVar = env.optional("linkIncremental"); 255 return pVar.get!bool; 256 } 257 258 /// Params: 259 /// cfgName = If `env' does not contain the property "useDebugLibs", 260 /// and this argument contains the string "debug" (ignoring the case), 261 /// this function will yield $(D true). 262 bool configUseDebugLibgs(ref Environment env, string cfgName) 263 { 264 auto pVar = env.first("useDebugLibs"); 265 if(pVar) { 266 return pVar.get!bool; 267 } 268 269 bool isRelease = cfgName.canFind!((a, b) => a.toLower() == b.toLower())("release"); 270 return !isRelease; 271 } 272 273 string configPlatformToolset(ref Environment env, ref in VSInfo info) 274 { 275 auto pVar = env.first("platformToolset"); 276 if(pVar) { 277 return pVar.get!string; 278 } 279 return info.platformToolset; 280 } 281 282 void configFiles(ref Environment env, ref Path[] headerFiles, ref Path[] compilationUnits) 283 { 284 auto files = env.required("sourceFiles").get!(Path[]); 285 auto mageFilePath = env.required("mageFilePath").get!Path; 286 auto filesRoot = mageFilePath.parent; 287 foreach(ref file; files) 288 { 289 auto _block = log.Block(`Processing file "%s"`, file); 290 file = file.absolute(filesRoot); 291 292 auto ext = file.extension; 293 if(ext == ".h" || ext == ".hpp") 294 { 295 headerFiles ~= file; 296 } 297 else if(ext == ".cpp" || ext == ".cxx" || ext == ".c") 298 { 299 compilationUnits ~= file; 300 } 301 else 302 { 303 log.warning(`Unknown file extension: %s`, ext); 304 } 305 } 306 } 307 308 void sanitize(ref MSBuildConfig cfg) 309 { 310 auto useDebugLibs = cfg.useDebugLibs && cfg.useDebugLibs.unwrap(); 311 auto wholeProgramOptimization = cfg.wholeProgramOptimization && cfg.wholeProgramOptimization.unwrap(); 312 313 if(useDebugLibs && wholeProgramOptimization) { 314 log.trace(`When using debug libs, the option "wholeProgramOptimization" ` ~ 315 `cannot be set. Visual Studio itself forbids that. Ignoring it for now.`); 316 cfg.wholeProgramOptimization = false; 317 } 318 319 // TODO Check which options are not compatible with linkIncremental in visual studio. 320 321 // TODO More. 322 }