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