1 /** 2 A module for providing interop between reggae and dub 3 */ 4 5 module reggae.dub.interop; 6 7 import reggae.options; 8 import reggae.dub.info; 9 import reggae.dub.call; 10 import reggae.dub.json; 11 import std.stdio; 12 import std.exception; 13 import std.conv; 14 import std.process; 15 16 17 DubInfo[string] gDubInfos; 18 19 20 @safe: 21 22 void maybeCreateReggaefile(in Options options) { 23 import std.file: exists; 24 import std.stdio: writeln; 25 26 if(options.isDubProject && !options.projectBuildFile.exists) { 27 writeln("[Reggae] Creating default dub project reggaefile"); 28 createReggaefile(options); 29 } 30 } 31 32 // default build for a dub project when there is no reggaefile 33 void createReggaefile(in Options options) { 34 import std.path; 35 writeln("[Reggae] Creating reggaefile.d from dub information"); 36 auto file = File(buildPath(options.workingDir, "reggaefile.d"), "w"); 37 file.writeln(q{import reggae;}); 38 file.writeln(q{mixin build!(dubDefaultTarget!(), dubTestTarget!());}); 39 40 if(!options.noFetch) dubFetch(options); 41 } 42 43 44 private DubInfo _getDubInfo(in Options options) { 45 import std.array; 46 import std.file: exists; 47 import std.path: buildPath; 48 import std.stdio: writeln; 49 50 version(unittest) 51 gDubInfos = null; 52 53 if("default" !in gDubInfos) { 54 55 if(!buildPath(options.projectPath, "dub.selections.json").exists) { 56 writeln("[Reggae] Calling dub upgrade to create dub.selections.json"); 57 callDub(options, ["dub", "upgrade"]); 58 } 59 60 DubConfigurations getConfigsImpl() { 61 import reggae.dub.call: getConfigurations; 62 immutable dubBuildArgs = ["dub", "--annotate", "build", "--compiler=" ~ options.dCompiler, 63 "--print-configs", "--build=docs"]; 64 immutable dubBuildOutput = callDub(options, dubBuildArgs); 65 return getConfigurations(dubBuildOutput); 66 } 67 68 DubConfigurations getConfigs() { 69 try { 70 return getConfigsImpl; 71 } catch(Exception _) { 72 writeln("[Reggae] Calling 'dub fetch' since getting the configuration failed"); 73 dubFetch(options); 74 return getConfigsImpl; 75 } 76 } 77 78 const configs = getConfigs(); 79 80 if(configs.configurations.empty) { 81 immutable descOutput = callDub(options, ["dub", "describe"]); 82 gDubInfos["default"] = getDubInfo(descOutput); 83 } else { 84 foreach(config; configs.configurations) { 85 immutable descOutput = callDub(options, ["dub", "describe", "-c", config]); 86 gDubInfos[config] = getDubInfo(descOutput); 87 88 //dub adds certain flags to certain configurations automatically but these flags 89 //don't know up in the output to `dub describe`. Special case them here. 90 91 //unittest should only apply to the main package, hence [0] 92 if(config == "unittest") gDubInfos[config].packages[0].dflags ~= " -unittest"; 93 94 callPreBuildCommands(options, gDubInfos[config]); 95 96 } 97 gDubInfos["default"] = gDubInfos[configs.default_]; 98 } 99 } 100 101 return gDubInfos["default"]; 102 } 103 104 private string callDub(in Options options, in string[] args) { 105 import std.process; 106 import std.exception: enforce; 107 import std.conv: text; 108 import std.string; 109 110 const string[string] env = null; 111 Config config = Config.none; 112 size_t maxOutput = size_t.max; 113 immutable workDir = options.projectPath; 114 115 immutable ret = execute(args, env, config, maxOutput, workDir); 116 enforce(ret.status == 0, text("Error calling '", args.join(" "), "' (", ret.status, ")", ":\n", 117 ret.output)); 118 return ret.output; 119 } 120 121 private void callPreBuildCommands(in Options options, in DubInfo dubInfo) { 122 import std.process; 123 const string[string] env = null; 124 Config config = Config.none; 125 size_t maxOutput = size_t.max; 126 immutable workDir = options.projectPath; 127 128 foreach(cmd; dubInfo.packages[0].preBuildCommands) { 129 writeln("Calling dub prebuildCommand '", cmd, "'"); 130 immutable ret = executeShell(cmd, env, config, maxOutput, workDir); 131 enforce(ret.status == 0, text("Error calling ", cmd, ":\n", ret.output)); 132 } 133 } 134 135 private void dubFetch(in Options options) @trusted { 136 import std.array: join, replace; 137 import std.stdio: writeln; 138 import std.path: buildPath; 139 import std.json: parseJSON, JSON_TYPE; 140 import std.file: readText; 141 142 const fileName = buildPath(options.projectPath, "dub.selections.json"); 143 auto json = parseJSON(readText(fileName)); 144 145 auto versions = json["versions"]; 146 147 foreach(dubPackage, versionJson; versions.object) { 148 149 // skip the ones with a defined path 150 if(versionJson.type != JSON_TYPE.STRING) continue; 151 152 // versions are usually `==1.2.3`, so strip the sign 153 const version_ = versionJson.str.replace("==", ""); 154 155 if(!needDubFetch(dubPackage, version_)) continue; 156 157 const cmd = ["dub", "fetch", dubPackage, "--version=" ~ version_]; 158 159 writeln("[Reggae] Fetching package with command '", cmd.join(" "), "'"); 160 try 161 callDub(options, cmd); 162 catch(Exception ex) { 163 // local packages can't be fetched, so it's normal to get an error 164 if(!options.dubLocalPackages) 165 throw ex; 166 } 167 } 168 } 169 170 // dub fetch can sometimes take >10s (!) despite the package already being 171 // on disk 172 bool needDubFetch(in string dubPackage, in string version_) { 173 import std.path: buildPath; 174 import std.process: environment; 175 import std.file: exists; 176 177 const path = buildPath(environment["HOME"], ".dub", "packages", dubPackage ~ "-" ~ version_); 178 return !path.exists; 179 } 180 181 enum TargetType { 182 executable, 183 library, 184 staticLibrary, 185 sourceLibrary, 186 none, 187 } 188 189 190 void writeDubConfig(in Options options, File file) { 191 import std.conv: to; 192 import std.stdio: writeln; 193 194 writeln("[Reggae] Writing dub configuration"); 195 196 file.writeln("import reggae.dub.info;"); 197 198 if(options.isDubProject) { 199 200 file.writeln("enum isDubProject = true;"); 201 auto dubInfo = _getDubInfo(options); 202 const targetType = dubInfo.packages[0].targetType; 203 204 try { 205 targetType.to!TargetType; 206 } catch(Exception ex) { 207 throw new Exception(text("Unsupported dub targetType '", targetType, "'")); 208 } 209 210 file.writeln(`const configToDubInfo = assocList([`); 211 212 const keys = () @trusted { return gDubInfos.keys; }(); 213 foreach(config; keys) { 214 file.writeln(` assocEntry("`, config, `", `, gDubInfos[config], `),`); 215 } 216 file.writeln(`]);`); 217 file.writeln; 218 } else { 219 file.writeln("enum isDubProject = false;"); 220 } 221 }