1 /** 2 A module for providing interop between reggae and dub 3 */ 4 module reggae.dub.interop; 5 6 7 import reggae.from; 8 public import reggae.dub.interop.reggaefile; 9 10 11 from!"reggae.dub.info".DubInfo[string] gDubInfos; 12 13 14 void writeDubConfig(T)(auto ref T output, 15 in from!"reggae.options".Options options, 16 from!"std.stdio".File file) { 17 import reggae.io: log; 18 import reggae.dub.info: TargetType; 19 import reggae.dub.interop.fetch: dubFetch; 20 import reggae.dub.interop.dublib: Dub; 21 22 output.log("Writing dub configuration"); 23 scope(exit) output.log("Finished writing dub configuration"); 24 25 if(!options.isDubProject) { 26 file.writeln("enum isDubProject = false;"); 27 return; 28 } 29 30 // must check for dub.selections.json before creating dub instance 31 const dubSelectionsJson = ensureDubSelectionsJson(output, options); 32 33 auto dub = Dub(options); 34 35 dubFetch(output, dub, options, dubSelectionsJson); 36 37 file.writeln("import reggae.dub.info;"); 38 file.writeln("enum isDubProject = true;"); 39 40 output.log(" Getting dub build information"); 41 auto dubInfo = getDubInfo(output, dub, options); 42 output.log(" Got dub build information"); 43 44 const targetType = dubInfo.packages.length 45 ? dubInfo.packages[0].targetType 46 : TargetType.sourceLibrary; 47 48 file.writeln(`const configToDubInfo = assocList([`); 49 50 const keys = () @trusted { return gDubInfos.keys; }(); 51 foreach(config; keys) { 52 file.writeln(` assocEntry("`, config, `", `, gDubInfos[config], `),`); 53 } 54 file.writeln(`]);`); 55 file.writeln; 56 } 57 58 59 private string ensureDubSelectionsJson 60 (O) 61 (ref O output, in from!"reggae.options".Options options) 62 @safe 63 { 64 import reggae.dub.interop.exec: callDub, dubEnvArgs; 65 import reggae.io: log; 66 import reggae.path: buildPath; 67 import std.file: exists; 68 import std.exception: enforce; 69 70 const path = buildPath(options.projectPath, "dub.selections.json"); 71 72 if(!path.exists) { 73 output.log("Creating dub.selections.json"); 74 const cmd = ["dub", "upgrade"] ~ dubEnvArgs; 75 callDub(output, options, cmd); 76 } 77 78 enforce(path.exists, "Could not create dub.selections.json"); 79 80 return path; 81 } 82 83 84 85 private from!"reggae.dub.info".DubInfo getDubInfo 86 (T) 87 (auto ref T output, 88 ref from!"reggae.dub.interop.dublib".Dub dub, 89 in from!"reggae.options".Options options) 90 { 91 import reggae.dub.interop: gDubInfos; 92 import reggae.io: log; 93 import reggae.path: buildPath; 94 import std.file: exists; 95 import std.exception: enforce; 96 97 version(unittest) gDubInfos = null; 98 99 if("default" !in gDubInfos) { 100 101 enforce(buildPath(options.projectPath, "dub.selections.json").exists, 102 "Cannot find dub.selections.json"); 103 104 auto settings = dub.getGeneratorSettings(options); 105 const configs = dubConfigurations(output, dub, options, settings); 106 bool atLeastOneConfigOk; 107 Exception dubInfoFailure; 108 109 foreach(config; configs.configurations) { 110 try { 111 handleDubConfig(output, dub, options, settings, config); 112 atLeastOneConfigOk = true; 113 } catch(Exception ex) { 114 output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg); 115 if(dubInfoFailure is null) dubInfoFailure = ex; 116 } 117 } 118 119 gDubInfos["default"] = gDubInfos[configs.default_]; 120 121 if(!atLeastOneConfigOk) { 122 assert(dubInfoFailure !is null, 123 "Internal error: no configurations worked and no exception to throw"); 124 throw dubInfoFailure; 125 } 126 } 127 128 return gDubInfos["default"]; 129 } 130 131 private from!"reggae.dub.interop.configurations".DubConfigurations 132 dubConfigurations 133 (O) 134 (ref O output, 135 ref from!"reggae.dub.interop.dublib".Dub dub, 136 in from!"reggae.options".Options options, 137 from!"dub.generators.generator".GeneratorSettings settings) 138 { 139 import reggae.dub.interop.configurations: DubConfigurations; 140 import reggae.io: log; 141 142 if(options.dubConfig == "") { 143 144 output.log("Getting dub configurations"); 145 auto ret = dub.getConfigs(settings.platform); 146 output.log("Number of dub configurations: ", ret.configurations.length); 147 148 // this happens e.g. the targetType is "none" 149 if(ret.configurations.length == 0) 150 return DubConfigurations([""], ""); 151 152 return ret; 153 } else { 154 return DubConfigurations([options.dubConfig], options.dubConfig); 155 } 156 } 157 158 private void handleDubConfig 159 (O) 160 (ref O output, 161 ref from!"reggae.dub.interop.dublib".Dub dub, 162 in from!"reggae.options".Options options, 163 from!"dub.generators.generator".GeneratorSettings settings, 164 in string config) 165 { 166 import reggae.io: log; 167 import std.conv: text; 168 169 output.log("Querying dub configuration '", config, "'"); 170 gDubInfos[config] = dub.configToDubInfo(settings, config); 171 172 // dub adds certain flags to certain configurations automatically but these flags 173 // don't know up in the output to `dub describe`. Special case them here. 174 175 // unittest should only apply to the main package, hence [0]. 176 // This doesn't show up in `dub describe`, it's secret info that dub knows 177 // so we have to add it manually here. 178 if(config == "unittest") { 179 if(config !in gDubInfos) 180 throw new Exception( 181 text("Configuration `", config, "` not found in ", 182 () @trusted { return gDubInfos.keys; }())); 183 if(gDubInfos[config].packages.length == 0) 184 throw new Exception( 185 text("No main package in `", config, "` configuration")); 186 gDubInfos[config].packages[0].dflags ~= "-unittest"; 187 } 188 189 try 190 callPreBuildCommands(output, options, gDubInfos[config]); 191 catch(Exception e) { 192 output.log("Error calling prebuild commands: ", e.msg); 193 throw e; 194 } 195 } 196 197 198 private void callPreBuildCommands(T)(auto ref T output, 199 in from!"reggae.options".Options options, 200 in from!"reggae.dub.info".DubInfo dubInfo) 201 @safe 202 { 203 import reggae.io: log; 204 import std.process: executeShell, Config; 205 import std..string: replace; 206 import std.exception: enforce; 207 import std.conv: text; 208 209 const string[string] env = null; 210 Config config = Config.none; 211 size_t maxOutput = size_t.max; 212 immutable workDir = options.projectPath; 213 214 if(dubInfo.packages.length == 0) return; 215 216 foreach(const package_; dubInfo.packages) { 217 foreach(const dubCommandString; package_.preBuildCommands) { 218 auto cmd = dubCommandString.replace("$project", options.projectPath); 219 output.log("Executing pre-build command `", cmd, "`"); 220 const ret = executeShell(cmd, env, config, maxOutput, workDir); 221 enforce(ret.status == 0, text("Error calling ", cmd, ":\n", ret.output)); 222 } 223 } 224 }