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 11 enum BuildLanguage { 12 D, 13 Python, 14 Ruby, 15 JavaScript, 16 Lua, 17 } 18 19 struct Options { 20 Backend backend; 21 string projectPath; 22 string dflags; 23 string ranFromPath; 24 string cCompiler; 25 string cppCompiler; 26 string dCompiler; 27 bool noFetch; 28 bool help; 29 bool perModule; 30 bool isDubProject; 31 bool oldNinja; 32 string[string] userVars; 33 34 Options dup() @safe pure const nothrow { 35 return Options(backend, 36 projectPath, dflags, ranFromPath, cCompiler, cppCompiler, dCompiler, 37 noFetch, help, perModule, isDubProject, oldNinja); 38 } 39 40 //finished setup 41 void finalize() @safe{ 42 ranFromPath = thisExePath(); 43 44 if(!cCompiler) cCompiler = "gcc"; 45 if(!cppCompiler) cppCompiler = "g++"; 46 if(!dCompiler) dCompiler = "dmd"; 47 48 isDubProject = _isDubProject; 49 50 if(isDubProject && backend == Backend.tup) { 51 throw new Exception("dub integration not supported with the tup backend"); 52 } 53 } 54 55 private bool _isDubProject() @safe nothrow { 56 return buildPath(projectPath, "dub.json").exists || 57 buildPath(projectPath, "package.json").exists; 58 } 59 60 string reggaeFilePath() @safe const { 61 import std.algorithm, std.array, std.exception, std.conv; 62 63 auto langs = [dlangFile, pythonFile, rubyFile]. 64 filter!exists. 65 map!(a => reggaeFileLanguage(a)). 66 array; 67 68 enforce(langs.length < 2, text("Reggae builds may only use one language. Found: ", 69 langs.map!(to!string).join(", "))); 70 71 if(dlangFile.exists) return dlangFile; 72 if(pythonFile.exists) return pythonFile; 73 if(rubyFile.exists) return rubyFile; 74 if(jsFile.exists) return jsFile; 75 if(luaFile.exists) return luaFile; 76 77 immutable path = isDubProject ? "" : projectPath; 78 return buildPath(path, "reggaefile.d").absolutePath; 79 } 80 81 string dlangFile() @safe const pure nothrow { 82 return projectBuildFile; 83 } 84 85 string pythonFile() @safe const pure nothrow { 86 return buildPath(projectPath, "reggaefile.py"); 87 } 88 89 string rubyFile() @safe const pure nothrow { 90 return buildPath(projectPath, "reggaefile.rb"); 91 } 92 93 string jsFile() @safe const pure nothrow { 94 return buildPath(projectPath, "reggaefile.js"); 95 } 96 97 string luaFile() @safe const pure nothrow { 98 return buildPath(projectPath, "reggaefile.lua"); 99 } 100 101 string projectBuildFile() @safe const pure nothrow { 102 return buildPath(projectPath, "reggaefile.d"); 103 } 104 105 string toString() @safe const pure { 106 import std.conv: text; 107 import std.traits: isSomeString, isAssociativeArray; 108 109 string repr = "Options(Backend."; 110 111 foreach(member; this.tupleof) { 112 static if(isSomeString!(typeof(member))) 113 repr ~= `"` ~ text(member) ~ `", `; 114 else static if(isAssociativeArray!(typeof(member))) 115 {} 116 else 117 repr ~= text(member, ", "); 118 } 119 120 repr ~= ")"; 121 return repr; 122 } 123 124 string[] rerunArgs() @safe pure const { 125 import std.conv: to; 126 127 auto args = [ranFromPath, "-b", backend.to!string, ]; 128 129 if(dflags != "") args ~= ["--dflags='" ~ dflags ~ "'"]; 130 if(oldNinja) args ~= "--old_ninja"; 131 if(cCompiler != "") args ~= ["--cc", cCompiler]; 132 if(cppCompiler != "") args ~= ["--cxx", cppCompiler]; 133 if(dCompiler != "") args ~= ["--dc", dCompiler]; 134 135 args ~= projectPath; 136 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.algorithm; 147 148 if(fileName.endsWith(".d")) 149 return BuildLanguage.D; 150 else if(fileName.endsWith(".py")) 151 return BuildLanguage.Python; 152 else if(fileName.endsWith(".rb")) 153 return BuildLanguage.Ruby; 154 else if(fileName.endsWith(".js")) 155 return BuildLanguage.JavaScript; 156 else if(fileName.endsWith(".lua")) 157 return BuildLanguage.Lua; 158 else throw new Exception("Unknown language for " ~ fileName); 159 } 160 161 BuildLanguage reggaeFileLanguage() @safe const { 162 return reggaeFileLanguage(reggaeFilePath); 163 } 164 165 } 166 167 168 //getopt is @system 169 Options getOptions(string[] args) @trusted { 170 import std.getopt; 171 172 Options options; 173 try { 174 auto helpInfo = getopt( 175 args, 176 "backend|b", "Backend to use (ninja|make). Mandatory.", &options.backend, 177 "dflags", "D compiler flags.", &options.dflags, 178 "d", "User-defined variables (e.g. -d myvar=foo).", &options.userVars, 179 "dc", "D compiler to use (default dmd).", &options.dCompiler, 180 "cc", "C compiler to use (default gcc).", &options.cCompiler, 181 "cxx", "C++ compiler to use (default g++).", &options.cppCompiler, 182 "nofetch", "Assume dub packages are present (no dub fetch).", &options.noFetch, 183 "per_module", "Compile D files per module (default is per package)", &options.perModule, 184 "old_ninja", "Generate a Ninja build compatible with older versions of Ninja", &options.oldNinja, 185 ); 186 187 if(helpInfo.helpWanted) { 188 defaultGetoptPrinter("Usage: reggae -b <ninja|make> </path/to/project>", 189 helpInfo.options); 190 options.help = true; 191 } 192 193 } catch(ConvException ex) { 194 throw new Exception("Unsupported backend, -b must be one of: make|ninja|tup|binary"); 195 } 196 197 if(args.length > 1) options.projectPath = args[1].absolutePath; 198 options.finalize(); 199 200 return options; 201 }