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     dub.reinit;
44 }
45 
46 
47 private struct VersionedPackage {
48     string name;
49     string version_;
50 }
51 
52 
53 private bool needDubFetch(
54     ref from!"reggae.dub.interop.dublib".Dub dub,
55     in VersionedPackage pkg)
56     @safe
57 {
58     // first check the file system explicitly
59     if(pkgExistsOnFS(pkg)) return false;
60     // next ask dub (this is slower)
61     if(dub.getPackage(pkg.name, pkg.version_)) return false;
62 
63     return true;
64 }
65 
66 
67 // dub fetch can sometimes take >10s (!) despite the package already being
68 // on disk
69 private bool pkgExistsOnFS(in VersionedPackage pkg) @safe {
70     import reggae.path: dubPackagesDir;
71     import std.path: buildPath;
72     import std.file: exists;
73     import std.string: replace;
74 
75     // Some versions include a `+` and that becomes `_` in the path
76     const version_ = pkg.version_.replace("+", "_");
77 
78     return buildPath(
79         dubPackagesDir,
80         pkg.name ~ "-" ~ version_,
81         pkg.name ~ ".lock"
82     ).exists;
83 }