1 module reggae.options; 2 3 import reggae.types; 4 import reggae.path: buildPath; 5 6 import std.file: thisExePath; 7 import std.path: absolutePath; 8 import std.file: exists; 9 10 enum version_ = "0.5.24+"; 11 12 Options defaultOptions; 13 14 enum BuildLanguage { 15 D, 16 Python, 17 Ruby, 18 JavaScript, 19 Lua, 20 } 21 22 enum DubArchitecture { 23 x86, 24 x86_64, 25 x86_mscoff, 26 } 27 28 version(Windows) { 29 enum defaultCC = "cl.exe"; 30 enum defaultCXX = "cl.exe"; 31 } else { 32 enum defaultCC = "gcc"; 33 enum defaultCXX = "g++"; 34 } 35 36 struct Options { 37 Backend backend; 38 string projectPath; 39 string dflags; 40 string ranFromPath; 41 string cCompiler; 42 string cppCompiler; 43 string dCompiler; 44 bool help; 45 bool perModule; 46 bool allAtOnce; 47 bool isDubProject; 48 bool oldNinja; 49 bool noCompilationDB; 50 bool cacheBuildInfo; 51 string[] args; 52 string workingDir; 53 bool version_; 54 bool export_; 55 bool verbose; 56 string[] dependencies; 57 string dubObjsDir; 58 bool dubDepObjsInsteadOfStaticLib; 59 string dubBuildType = "debug"; 60 string dubArchOverride; 61 string dubConfig; 62 63 string[string] userVars; //must always be the last member variable 64 65 Options dup() @safe pure const nothrow { 66 return Options(backend, 67 projectPath, dflags, ranFromPath, cCompiler, cppCompiler, dCompiler, 68 help, perModule, isDubProject, oldNinja, noCompilationDB, cacheBuildInfo); 69 } 70 71 //finished setup 72 void finalize(string[] args) @safe { 73 import std.process; 74 import std.file: thisExePath; 75 76 this.args = args; 77 ranFromPath = thisExePath; 78 79 if(!cCompiler) cCompiler = environment.get("CC", defaultCC); 80 if(!cppCompiler) cppCompiler = environment.get("CXX", defaultCXX); 81 if(!dCompiler) dCompiler = environment.get("DC", "dmd"); 82 83 if(backend == Backend.none && !export_) 84 backend = Backend.ninja; 85 86 isDubProject = _dubProjectFile != ""; 87 88 // if there's a dub package file, add it to the list of dependencies so the project 89 // is rebuilt if it changes 90 if(isDubProject) { 91 dependencies ~= _dubProjectFile; 92 dependencies ~= buildPath(projectPath, "dub.selections.json"); 93 } 94 95 if(isDubProject && backend == Backend.tup) { 96 throw new Exception("dub integration not supported with the tup backend"); 97 } 98 } 99 100 package string _dubProjectFile() @safe nothrow { 101 foreach(fileName; ["dub.sdl", "dub.json", "package.json"]) { 102 const name = buildPath(projectPath, fileName); 103 if(name.exists) return name; 104 } 105 return ""; 106 } 107 108 string reggaeFilePath() @safe const { 109 import std.algorithm, std.array, std.exception, std.conv; 110 111 auto langFiles = [dlangFile, pythonFile, rubyFile, jsFile, luaFile]; 112 auto foundFiles = langFiles.filter!exists.array; 113 114 enforce(foundFiles.length < 2, 115 text("Reggae builds may only use one language. Found: ", 116 foundFiles.map!(a => reggaeFileLanguage(a).to!string).join(", "))); 117 118 if(!foundFiles.empty) return foundFiles.front; 119 120 return buildPath(projectPath, "reggaefile.d").absolutePath; 121 } 122 123 string dlangFile() @safe const pure nothrow { 124 return buildPath(projectPath, "reggaefile.d"); 125 } 126 127 string pythonFile() @safe const pure nothrow { 128 return buildPath(projectPath, "reggaefile.py"); 129 } 130 131 string rubyFile() @safe const pure nothrow { 132 return buildPath(projectPath, "reggaefile.rb"); 133 } 134 135 string jsFile() @safe const pure nothrow { 136 return buildPath(projectPath, "reggaefile.js"); 137 } 138 139 string luaFile() @safe const pure nothrow { 140 return buildPath(projectPath, "reggaefile.lua"); 141 } 142 143 string toString() @safe const pure { 144 import std.conv: text; 145 import std.traits: isSomeString, isAssociativeArray, Unqual; 146 147 string repr = "Options(Backend."; 148 149 foreach(member; this.tupleof) { 150 151 static if(isSomeString!(typeof(member))) 152 repr ~= "`" ~ text(member) ~ "`, "; 153 else static if(isAssociativeArray!(typeof(member))) 154 {} 155 else static if(is(Unqual!(typeof(member)) == DubArchitecture)) 156 repr ~= `DubArchitecture.` ~ text(member) ~ ", "; 157 else 158 repr ~= text(member, ", "); 159 } 160 161 repr ~= ")"; 162 return repr; 163 } 164 165 const (string)[] rerunArgs() @safe pure const { 166 return args; 167 } 168 169 bool isScriptBuild() @safe const { 170 import reggae.rules.common: getLanguage, Language; 171 return getLanguage(reggaeFilePath) != Language.D; 172 } 173 174 BuildLanguage reggaeFileLanguage(in string fileName) @safe const { 175 import std.exception; 176 import std.path: extension; 177 178 with(BuildLanguage) { 179 immutable extToLang = [".d": D, ".py": Python, ".rb": Ruby, ".js": JavaScript, ".lua": Lua]; 180 enforce(extension(fileName) in extToLang, "Unsupported build description language in " ~ fileName); 181 return extToLang[extension(fileName)]; 182 } 183 } 184 185 BuildLanguage reggaeFileLanguage() @safe const { 186 return reggaeFileLanguage(reggaeFilePath); 187 } 188 189 string[] reggaeFileDependencies() @safe const { 190 return [ranFromPath, reggaeFilePath] ~ getReggaeFileDependenciesDlang ~ dependencies; 191 } 192 193 bool isJsonBuild() @safe const { 194 return reggaeFileLanguage != BuildLanguage.D; 195 } 196 197 bool earlyExit() @safe pure const nothrow { 198 return help || version_; 199 } 200 201 string[] compilerVariables() @safe pure nothrow const { 202 return ["CC = " ~ cCompiler, "CXX = " ~ cppCompiler, "DC = " ~ dCompiler]; 203 } 204 205 string eraseProjectPath(in string str) @safe pure nothrow const { 206 import std.string; 207 import std.path: dirSeparator; 208 return str.replace(projectPath ~ dirSeparator, ""); 209 } 210 } 211 212 Options getOptions(string[] args) { 213 return getOptions(defaultOptions, args); 214 } 215 216 //getopt is @system 217 Options getOptions(Options defaultOptions, string[] args) @trusted { 218 import std.getopt; 219 import std.algorithm; 220 import std.array; 221 import std.path: buildNormalizedPath; 222 import std.exception: enforce; 223 import std.conv: ConvException; 224 225 Options options = defaultOptions; 226 227 //escape spaces so that if we try using these arguments again the shell won't complain 228 auto origArgs = args.map!(a => a.canFind(" ") ? `"` ~ a ~ `"` : a).array; 229 230 try { 231 auto helpInfo = getopt( 232 args, 233 "backend|b", "Backend to use (ninja|make|binary|tup, default is ninja).", &options.backend, 234 "dflags", "D compiler flags.", &options.dflags, 235 "d", "User-defined variables (e.g. -d myvar=foo).", &options.userVars, 236 "dc", "D compiler to use (default dmd).", &options.dCompiler, 237 "cc", "C compiler to use (default " ~ defaultCC ~ ").", &options.cCompiler, 238 "cxx", "C++ compiler to use (default " ~ defaultCXX ~ ").", &options.cppCompiler, 239 "per-module", "Compile D files per module (default is per package)", &options.perModule, 240 "all-at-once", "Compile D files all at once (default is per package)", &options.allAtOnce, 241 "old-ninja", "Generate a Ninja build compatible with older versions of Ninja", &options.oldNinja, 242 "no-comp-db", "Don't generate a JSON compilation database", &options.noCompilationDB, 243 "cache-build-info", "Cache the build information", &options.cacheBuildInfo, 244 "C", "Change directory to run in (similar to make -C and ninja -C)", &options.workingDir, 245 "version", "Prints version information", &options.version_, 246 "export", "Export build system - removes dependencies on reggae itself", &options.export_, 247 "verbose", "Verbose output", &options.verbose, 248 "dub-objs-dir", "Directory to place object files for dub dependencies", &options.dubObjsDir, 249 "dub-arch", "Architecture (x86, x86_64, x86_mscoff)", &options.dubArchOverride, 250 "dub-deps-objs", "Use object files instead of static library for dub dependencies", &options.dubDepObjsInsteadOfStaticLib, 251 "dub-build-type", "Dub build type (debug, release, ...)", &options.dubBuildType, 252 "dub-config", "Only use this dub configuration", &options.dubConfig, 253 ); 254 255 if(helpInfo.helpWanted) { 256 defaultGetoptPrinter("Usage: reggae [-b <ninja|make|binary|tup>] </path/to/project>", 257 helpInfo.options); 258 options.help = true; 259 } 260 } catch(ConvException ex) { 261 import std.algorithm: canFind; 262 263 if(ex.msg.canFind("Backend")) 264 throw new Exception("Unsupported backend, -b must be one of: make|ninja|tup|binary"); 265 else if(ex.msg.canFind("DubArchitecture")) 266 throw new Exception("Unsupported architecture, --dub-arch must be one of: x86|x86_64|x86_mscoff"); 267 else 268 assert(0); 269 } 270 271 if(options.version_) { 272 import std.stdio; 273 writeln("reggae v", version_); 274 } 275 276 immutable argsPath = args.length > 1 ? args[1] : "."; 277 options.projectPath = argsPath.absolutePath.buildNormalizedPath; 278 options.finalize(origArgs); 279 280 enforce(!options.perModule || !options.allAtOnce, "Cannot specify both --per-module and --all-at-once"); 281 enforce(options.backend != Backend.none || options.export_, "A backend must be specified with -b/--backend"); 282 283 if(options.workingDir == "") { 284 import std.file; 285 options.workingDir = getcwd.absolutePath; 286 } else { 287 options.workingDir = options.workingDir.absolutePath; 288 } 289 290 return options; 291 } 292 293 294 immutable hiddenDir = ".reggae"; 295 296 297 //returns the list of files that the `reggaefile` depends on 298 //this will usually be empty, but won't be if the reggaefile imports other D files 299 string[] getReggaeFileDependenciesDlang() @trusted { 300 import std.string: chomp; 301 import std.stdio: File; 302 import std.algorithm: splitter; 303 import std.array: array; 304 305 immutable fileName = buildPath(hiddenDir, "reggaefile.dep"); 306 if(!fileName.exists) return []; 307 308 auto file = File(fileName); 309 file.readln; 310 return file.readln.chomp.splitter(" ").array; 311 } 312 313 314 Options withProjectPath(in Options options, in string projectPath) @safe pure nothrow { 315 auto modOptions = options.dup; 316 modOptions.projectPath = projectPath; 317 return modOptions; 318 } 319 320 321 string banner() @safe pure nothrow { 322 auto ret = "# Automatically generated by reggae version " ~ version_ ~ "\n"; 323 ret ~= "# Do not edit by hand\n"; 324 return ret; 325 }