1 module reggae.dub.interop.fetch;
2 
3 
4 import reggae.from;
5 
6 
7 package void dubFetch(O)(
8     auto ref O output,
9     ref from!"reggae.dub.interop.dublib".Dub dub,
10     in from!"reggae.options".Options options,
11     in string dubSelectionsJson)
12     @trusted
13 {
14     import reggae.dub.interop.exec: callDub, dubEnvArgs;
15     import reggae.io: log;
16     import std.array: replace;
17     import std.json: parseJSON, JSONType;
18     import std.file: readText;
19     import std.parallelism: parallel;
20 
21     const(VersionedPackage)[] pkgsToFetch;
22 
23     const json = parseJSON(readText(dubSelectionsJson));
24 
25     foreach(dubPackageName, versionJson; json["versions"].object) {
26 
27         // skip the ones with a defined path
28         if(versionJson.type != JSONType..string) continue;
29 
30         // versions are usually `==1.2.3`, so strip the equals sign
31         const version_ = versionJson.str.replace("==", "");
32         const pkg = VersionedPackage(dubPackageName, version_);
33 
34         if(needDubFetch(dub, pkg)) pkgsToFetch ~= pkg;
35     }
36 
37     output.log("Fetching dub packages");
38     foreach(pkg; pkgsToFetch.parallel) {
39         const cmd = ["dub", "fetch", pkg.name, "--version=" ~ pkg.version_] ~ dubEnvArgs;
40         callDub(output, options, cmd);
41     }
42 
43     output.log("Reloading project");
44     dub.reinit;
45     output.log("Project reloaded");
46 }
47 
48 
49 private struct VersionedPackage {
50     string name;
51     string version_;
52 }
53 
54 
55 private bool needDubFetch(
56     ref from!"reggae.dub.interop.dublib".Dub dub,
57     in VersionedPackage pkg)
58     @safe
59 {
60     // first check the file system explicitly
61     if(pkgExistsOnFS(pkg)) return false;
62     // next ask dub (this is slower)
63     if(dub.getPackage(pkg.name, pkg.version_)) return false;
64 
65     return true;
66 }
67 
68 
69 // dub fetch can sometimes take >10s (!) despite the package already being
70 // on disk
71 private bool pkgExistsOnFS(in VersionedPackage pkg) @safe {
72     import reggae.path: dubPackagesDir;
73     import std.path: buildPath;
74     import std.file: exists;
75     import std.string: replace;
76 
77     // Some versions include a `+` and that becomes `_` in the path
78     const version_ = pkg.version_.replace("+", "_");
79 
80     return buildPath(
81         dubPackagesDir,
82         pkg.name ~ "-" ~ version_,
83         pkg.name ~ ".lock"
84     ).exists;
85 }