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