1 module regenerate; 2 3 import std.string; 4 import std.file; 5 import std.path; 6 import std.range; 7 import std.algorithm; 8 import std.process; 9 import std.getopt; 10 import std.experimental.logger; 11 import std.stdio : writeln; 12 13 struct Options 14 { 15 @("Path to temporary directory, `temp` by default.") 16 string tempDirectory = "temp"; 17 18 @("Path to d++ executable. Only needed when doing bindings. Found in path if not provided.") 19 string dppExecutablePath; 20 21 @("Path to bindbc generator. Only needed when doing bindings.") 22 string generatorRepoPath; 23 24 @("Path to `cimgui.h`.") 25 string cimguiHeaderPath; 26 27 @("Whether to get cimgui from github and compile into a dll and lib.") 28 bool compileImgui; 29 30 enum CimguiBranch 31 { 32 docking, master 33 } 34 @("Controls which branch of cimgui to build. (Can be `docking` or `master`.)") 35 CimguiBranch cimguiBranch = CimguiBranch.docking; 36 37 @("Whether to regenerate bindbc-cimgui bindings using the generator.") 38 bool generateBindings; 39 40 @("Whether to get dpp from github and compile it.") 41 bool compileDpp; 42 } 43 44 __gshared Options op; 45 __gshared bool hasErrors; 46 47 void _error(string message) 48 { 49 hasErrors = true; 50 error(message); 51 } 52 53 int main(string[] args) 54 { 55 string getoptMixin() 56 { 57 auto ret = "auto helpInformation = getopt(args"; 58 static foreach (field; Options.tupleof) 59 { 60 import std.format; 61 ret ~= `, "%s", "%s", &op.%1$s`.format(__traits(identifier, field), __traits(getAttributes, field)[0]); 62 } 63 ret ~= ");"; 64 return ret; 65 } 66 mixin(getoptMixin()); 67 68 if (helpInformation.helpWanted || op == Options.init) 69 { 70 defaultGetoptPrinter("Help message", helpInformation.options); 71 return 0; 72 } 73 74 void ensurePathExistsIfProvided(string name, string path) 75 { 76 if (path && !exists(path)) 77 { 78 _error(name ~ " not found by the specified path " ~ path); 79 } 80 } 81 ensurePathExistsIfProvided("Dpp", op.dppExecutablePath); 82 ensurePathExistsIfProvided("Generator", op.generatorRepoPath); 83 ensurePathExistsIfProvided("cimgui header", op.cimguiHeaderPath); 84 85 if (op.compileDpp && op.dppExecutablePath) 86 { 87 _error("Incompatible arguments: `compileDpp` and `dppExecutablePath`"); 88 } 89 90 if (op.compileImgui && op.cimguiHeaderPath) 91 { 92 _error("Incompatible arguments: `compileImgui` and `cimguiHeaderPath`"); 93 } 94 95 op.tempDirectory = absolutePath(op.tempDirectory); 96 mkdirRecurse(op.tempDirectory); 97 98 if (hasErrors) 99 return 1; 100 101 if (op.compileDpp) 102 { 103 op.dppExecutablePath = dppCompilationWorkflow(); 104 105 if (!op.dppExecutablePath) 106 return 1; 107 108 log("Dpp has been written to " ~ op.dppExecutablePath); 109 } 110 111 if (op.compileImgui) 112 { 113 string outputDirectory = imguiCompilationWorkflow(); 114 115 if (!outputDirectory) 116 return 1; 117 118 log("Imgui binaries have been written to " ~ outputDirectory); 119 } 120 121 if (op.generateBindings) 122 { 123 generateBindingsWorkflow(); 124 } 125 126 return hasErrors ? 1 : 0; 127 } 128 129 string takeAfterLast(string str, string pattern) 130 { 131 auto index = lastIndexOf(str, pattern); 132 return str[index + 1..$]; 133 } 134 135 // Returns the path to the cloned repo 136 string gitClone(string repoUrl, bool recursive = true, string branch = null) 137 { 138 string repoName = repoUrl.takeAfterLast("/"); 139 string repoPath = buildPath(op.tempDirectory, repoName); 140 if (exists(repoPath)) 141 { 142 log("Found " ~ repoName ~ " clone at " ~ repoPath ~ ", skipping cloning."); 143 return repoPath; 144 } 145 146 string[] args = ["git", "clone", repoUrl]; 147 if (branch) 148 { 149 args ~= "--branch=" ~ branch; 150 } 151 if (recursive) 152 { 153 args ~= "--recursive"; 154 } 155 156 auto result = _exec(args, op.tempDirectory); 157 if (result.status == 0) 158 return repoPath; 159 160 _error("Failed to clone " ~ repoName ~ ": " ~ result.output); 161 return null; 162 } 163 164 string dppCompilationWorkflow() 165 { 166 const dppRepoUrl = "https://github.com/atilaneves/dpp"; 167 log("You will need to install LLVM if it's not installed already, see the dpp repo: " ~ dppRepoUrl); 168 169 if (!checkCommandsExist(["git", "dub"])) 170 return null; 171 172 const dppClonedRepoPath = gitClone(dppRepoUrl); 173 if (!dppClonedRepoPath) 174 return null; 175 176 const result = _exec(["dub", "build"], dppClonedRepoPath); 177 if (result.status != 0) 178 { 179 _error("Failed to build dpp: " ~ result.output); 180 return null; 181 } 182 183 string dppExecutablePath; 184 version (Windows) 185 { 186 dppExecutablePath = buildPath(dppClonedRepoPath, "bin/d++.exe"); 187 if (!exists(dppExecutablePath)) 188 { 189 _error("Expected d++ at " ~ dppExecutablePath); 190 } 191 } 192 return dppExecutablePath; 193 } 194 195 string gitCloneImgui() 196 { 197 const repoUrl = "https://www.github.com/cimgui/cimgui"; 198 string commitHash, branch; 199 if (op.cimguiBranch == Options.CimguiBranch.docking) 200 { 201 commitHash = "873c03c3673033bf8e8dd22901d4a3934b7407b2"; 202 branch = "docking_inter"; 203 } 204 else 205 { 206 commitHash = "17ffa736d353591b9545d1cedaa6373482f7f1a3"; 207 branch = "master"; 208 } 209 const recursive = false; 210 211 const clonedRepoPath = gitClone(repoUrl, recursive, branch); 212 if (!clonedRepoPath) 213 return null; 214 215 if (commitHash) 216 { 217 auto result = _exec(["git", "checkout", commitHash], clonedRepoPath); 218 if (result.status != 0) return null; 219 } 220 221 // result = _exec(["git", "submodule", "init"], clonedRepoPath); 222 // if (result.status != 0) return null; 223 224 auto result = _exec(["git", "submodule", "update", "--init", "--recursive"], clonedRepoPath); 225 if (result.status != 0) return null; 226 227 return clonedRepoPath; 228 } 229 230 string imguiCompilationWorkflow() 231 { 232 if (!checkCommandsExist(["git", "cmake"])) 233 return null; 234 235 const imguiClonedRepoPath = gitCloneImgui(); 236 if (!imguiClonedRepoPath) 237 return null; 238 239 const imguiBuildDirectory = "imgui_build"; 240 const configuration = "RelWithDebInfo"; 241 auto result = _exec( 242 ["cmake", "-Hcimgui", "-B" ~ imguiBuildDirectory, "-DCMAKE_BUILD_TYPE=" ~ configuration], op.tempDirectory); 243 if (result.status != 0) 244 { 245 _error("Failed to run cmake: " ~ result.output); 246 return null; 247 } 248 249 result = _exec(["cmake", "--build", imguiBuildDirectory, "--config", configuration], op.tempDirectory); 250 if (result.status != 0) 251 { 252 _error("Failed to run cmake build: " ~ result.output); 253 return null; 254 } 255 256 // The output should be in RelWithDebInfo 257 const outputDirectory = buildPath(op.tempDirectory, imguiBuildDirectory, configuration); 258 return outputDirectory; 259 } 260 261 262 void generateBindingsWorkflow() 263 { 264 if (!checkCommandsExist(["git", "dub"])) 265 return; 266 267 string generatorRepoPath; 268 if (op.generatorRepoPath) 269 { 270 generatorRepoPath = op.generatorRepoPath; 271 } 272 else 273 { 274 generatorRepoPath = gitClone("https://github.com/MrcSnm/bindbc-generator"); 275 if (!generatorRepoPath) 276 return; 277 } 278 279 string imguiPath; 280 string cimguiHeaderPath; 281 if (op.cimguiHeaderPath) 282 { 283 imguiPath = dirName(op.cimguiHeaderPath); 284 cimguiHeaderPath = op.cimguiHeaderPath; 285 } 286 else 287 { 288 imguiPath = gitCloneImgui(); 289 if (!imguiPath) 290 return; 291 cimguiHeaderPath = buildPath(imguiPath, "cimgui.h"); 292 } 293 294 295 string cimguiPluginGenPath = buildPath(generatorRepoPath, "bindbc/cimgui"); 296 auto generatorArgs = [ 297 "dub", "--", 298 "--recompile", "--load-all", 299 "--file", cimguiHeaderPath, 300 "--temp-path", op.tempDirectory, 301 "--presets", "cimgui" 302 ]; 303 if (op.dppExecutablePath) 304 { 305 generatorArgs ~= "--dpp-path"; 306 generatorArgs ~= op.dppExecutablePath; 307 } 308 309 string argumentsString = escapeShellCommand(generatorArgs); 310 311 // We need to append the plugin arguments as strings, since otherwise they get escaped 312 // and the default parser in the generator cannot deal with that. 313 argumentsString ~= ` --plugin-args cimgui-overloads="[%s, %s, %s]"`.format(imguiPath, cimguiPluginGenPath, "d-conv"); 314 log(argumentsString); 315 316 auto result = executeShell(argumentsString, null, Config.none, size_t.max, generatorRepoPath); 317 if (result.status != 0) 318 { 319 _error("Failed to apply the generator: "); 320 writeln(result.output); 321 return; 322 } 323 writeln(result.output); 324 325 log("Find the output files somewhere in " ~ generatorRepoPath ~ " and in here " ~ cimguiPluginGenPath); 326 } 327 328 329 auto _exec(string[] args, string workingDirectory = null) 330 { 331 string cmd = escapeShellCommand(args); 332 log(cmd); 333 return execute(args, null, Config.none, size_t.max, workingDirectory); 334 } 335 336 bool existsCommand(string command) 337 { 338 version (Windows) 339 { 340 return execute(["where", command]).status == 0; 341 } 342 assert(0, "Linux and the alike are not implemented"); 343 } 344 345 bool checkCommandsExist(string[] commands) 346 { 347 foreach (command; commands) 348 { 349 if (!existsCommand(command)) 350 { 351 _error("`" ~ command ~ "` not found in path"); 352 } 353 } 354 return !hasErrors; 355 }