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