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