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 output.log("Creating dub instance "); 34 auto dub = Dub(options); 35 output.log("Created dub instance"); 36 37 dubFetch(output, dub, options, dubSelectionsJson); 38 39 file.writeln("import reggae.dub.info;"); 40 file.writeln("enum isDubProject = true;"); 41 42 output.log(" Getting dub build information"); 43 auto dubInfo = getDubInfo(output, dub, options); 44 output.log(" Got dub build information"); 45 46 const targetType = dubInfo.packages.length 47 ? dubInfo.packages[0].targetType 48 : TargetType.sourceLibrary; 49 50 file.writeln(`const configToDubInfo = assocList([`); 51 52 const keys = () @trusted { return gDubInfos.keys; }(); 53 foreach(config; keys) { 54 file.writeln(` assocEntry("`, config, `", `, gDubInfos[config], `),`); 55 } 56 file.writeln(`]);`); 57 file.writeln; 58 } 59 60 61 private string ensureDubSelectionsJson 62 (O) 63 (ref O output, in from!"reggae.options".Options options) 64 @safe 65 { 66 import reggae.dub.interop.exec: callDub, dubEnvArgs; 67 import reggae.io: log; 68 import std.path: buildPath; 69 import std.file: exists; 70 import std.exception: enforce; 71 72 const path = buildPath(options.projectPath, "dub.selections.json"); 73 74 if(!path.exists) { 75 output.log("Creating dub.selections.json"); 76 const cmd = ["dub", "upgrade"] ~ dubEnvArgs; 77 callDub(output, options, cmd); 78 } 79 80 enforce(path.exists, "Could not create dub.selections.json"); 81 82 return path; 83 } 84 85 86 87 private from!"reggae.dub.info".DubInfo getDubInfo 88 (T) 89 (auto ref T output, 90 ref from!"reggae.dub.interop.dublib".Dub dub, 91 in from!"reggae.options".Options options) 92 { 93 import reggae.dub.interop: gDubInfos; 94 import reggae.io: log; 95 import std.array: empty; 96 import std.file: exists; 97 import std.path: buildPath; 98 import std.conv: text; 99 import std.exception: enforce; 100 101 version(unittest) gDubInfos = null; 102 103 if("default" !in gDubInfos) { 104 105 enforce(buildPath(options.projectPath, "dub.selections.json").exists, 106 "Cannot find dub.selections.json"); 107 108 const configs = dub.getConfigs(options); 109 110 bool oneConfigOk; 111 Exception dubInfoFailure; 112 113 if(configs.configurations.empty) { 114 gDubInfos["default"] = dub.configToDubInfo(options, ""); 115 oneConfigOk = true; 116 } else { 117 foreach(config; configs.configurations) { 118 try { 119 gDubInfos[config] = dub.configToDubInfo(options, config); 120 121 // dub adds certain flags to certain configurations automatically but these flags 122 // don't know up in the output to `dub describe`. Special case them here. 123 124 // unittest should only apply to the main package, hence [0]. 125 // This doesn't show up in `dub describe`, it's secret info that dub knows 126 // so we have to add it manually here. 127 if(config == "unittest") { 128 if(config !in gDubInfos) 129 throw new Exception( 130 text("Configuration `", config, "` not found in ", 131 () @trusted { return gDubInfos.keys; }())); 132 if(gDubInfos[config].packages.length == 0) 133 throw new Exception( 134 text("No main package in `", config, "` configuration")); 135 gDubInfos[config].packages[0].dflags ~= " -unittest"; 136 } 137 138 try 139 callPreBuildCommands(output, options, gDubInfos[config]); 140 catch(Exception e) { 141 output.log("Error calling prebuild commands: ", e.msg); 142 throw e; 143 } 144 145 oneConfigOk = true; 146 147 } catch(Exception ex) { 148 output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg); 149 if(dubInfoFailure is null) dubInfoFailure = ex; 150 } 151 } 152 153 if(configs.default_ !in gDubInfos) 154 throw new Exception("Non-existent config info for " ~ configs.default_); 155 156 gDubInfos["default"] = gDubInfos[configs.default_]; 157 } 158 159 if(!oneConfigOk) { 160 assert(dubInfoFailure !is null, 161 "Internal error: no configurations worked and no exception to throw"); 162 throw dubInfoFailure; 163 } 164 } 165 166 return gDubInfos["default"]; 167 } 168 169 170 private void callPreBuildCommands(T)(auto ref T output, 171 in from!"reggae.options".Options options, 172 in from!"reggae.dub.info".DubInfo dubInfo) 173 @safe 174 { 175 import reggae.io: log; 176 import std.process: executeShell, Config; 177 import std.string: replace; 178 import std.exception: enforce; 179 import std.conv: text; 180 181 const string[string] env = null; 182 Config config = Config.none; 183 size_t maxOutput = size_t.max; 184 immutable workDir = options.projectPath; 185 186 if(dubInfo.packages.length == 0) return; 187 188 foreach(const package_; dubInfo.packages) { 189 foreach(const dubCommandString; package_.preBuildCommands) { 190 auto cmd = dubCommandString.replace("$project", options.projectPath); 191 output.log("Executing pre-build command `", cmd, "`"); 192 const ret = executeShell(cmd, env, config, maxOutput, workDir); 193 enforce(ret.status == 0, text("Error calling ", cmd, ":\n", ret.output)); 194 } 195 } 196 }