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