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 std.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 std.array: empty; 94 import std.file: exists; 95 import std.path: buildPath; 96 import std.conv: text; 97 import std.exception: enforce; 98 99 version(unittest) gDubInfos = null; 100 101 if("default" !in gDubInfos) { 102 103 enforce(buildPath(options.projectPath, "dub.selections.json").exists, 104 "Cannot find dub.selections.json"); 105 106 const configs = dub.getConfigs(options); 107 108 bool oneConfigOk; 109 Exception dubInfoFailure; 110 111 if(configs.configurations.empty) { 112 gDubInfos["default"] = dub.configToDubInfo(options, ""); 113 oneConfigOk = true; 114 } else { 115 foreach(config; configs.configurations) { 116 try { 117 gDubInfos[config] = dub.configToDubInfo(options, config); 118 119 // dub adds certain flags to certain configurations automatically but these flags 120 // don't know up in the output to `dub describe`. Special case them here. 121 122 // unittest should only apply to the main package, hence [0]. 123 // This doesn't show up in `dub describe`, it's secret info that dub knows 124 // so we have to add it manually here. 125 if(config == "unittest") { 126 if(config !in gDubInfos) 127 throw new Exception( 128 text("Configuration `", config, "` not found in ", 129 () @trusted { return gDubInfos.keys; }())); 130 if(gDubInfos[config].packages.length == 0) 131 throw new Exception( 132 text("No main package in `", config, "` configuration")); 133 gDubInfos[config].packages[0].dflags ~= " -unittest"; 134 } 135 136 try 137 callPreBuildCommands(output, options, gDubInfos[config]); 138 catch(Exception e) { 139 output.log("Error calling prebuild commands: ", e.msg); 140 throw e; 141 } 142 143 oneConfigOk = true; 144 145 } catch(Exception ex) { 146 output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg); 147 if(dubInfoFailure is null) dubInfoFailure = ex; 148 } 149 } 150 151 if(configs.default_ !in gDubInfos) 152 throw new Exception("Non-existent config info for " ~ configs.default_); 153 154 gDubInfos["default"] = gDubInfos[configs.default_]; 155 } 156 157 if(!oneConfigOk) { 158 assert(dubInfoFailure !is null, 159 "Internal error: no configurations worked and no exception to throw"); 160 throw dubInfoFailure; 161 } 162 } 163 164 return gDubInfos["default"]; 165 } 166 167 168 private void callPreBuildCommands(T)(auto ref T output, 169 in from!"reggae.options".Options options, 170 in from!"reggae.dub.info".DubInfo dubInfo) 171 @safe 172 { 173 import reggae.io: log; 174 import std.process: executeShell, Config; 175 import std.string: replace; 176 import std.exception: enforce; 177 import std.conv: text; 178 179 const string[string] env = null; 180 Config config = Config.none; 181 size_t maxOutput = size_t.max; 182 immutable workDir = options.projectPath; 183 184 if(dubInfo.packages.length == 0) return; 185 186 foreach(const package_; dubInfo.packages) { 187 foreach(const dubCommandString; package_.preBuildCommands) { 188 auto cmd = dubCommandString.replace("$project", options.projectPath); 189 output.log("Executing pre-build command `", cmd, "`"); 190 const ret = executeShell(cmd, env, config, maxOutput, workDir); 191 enforce(ret.status == 0, text("Error calling ", cmd, ":\n", ret.output)); 192 } 193 } 194 }