1 #!/usr/bin/env dub
2 /+ dub.sdl:
3     name "download_llvm"
4 +/
5 module download_llvm;
6 
7 enum downloadPath = "tmp";
8 
9 enum Architecture
10 {
11     bit64 = 64,
12     bit32 = 32,
13     x86_64 = bit64,
14     x86 = bit32,
15     x86_mscoff = bit32,
16 }
17 
18 version (Windows)
19 {
20     enum llvmArchives = [
21         64: [
22             "10.0.0": "LLVM-10.0.0-win64.exe",
23             "9.0.0": "LLVM-9.0.0-win64.exe"
24         ],
25 
26         32: [
27             "10.0.0": "LLVM-10.0.0-win32.exe",
28             "9.0.0": "LLVM-9.0.0-win32.exe"
29         ]
30     ];
31 
32     enum llvmUrls = [
33         "10.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-%s/%s",
34         "9.0.0": "https://releases.llvm.org/%s/%s",
35     ];
36 }
37 
38 version (OSX)
39 {
40     enum llvmArchives = [
41         64: [
42             "10.0.0": "clang+llvm-10.0.0-x86_64-apple-darwin.tar.xz",
43             "9.0.0": "clang+llvm-9.0.0-x86_64-darwin-apple.tar.xz",
44             "8.0.0": "clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz",
45             "6.0.0": "clang+llvm-6.0.0-x86_64-apple-darwin.tar.xz"
46         ]
47     ];
48 
49     enum dstepLLVMArchives = [
50         64: [
51             "10.0.0": "llvm-10.0.0-macos-x86_64.tar.xz"
52         ]
53     ];
54 
55     enum llvmUrls = [
56         "10.0.0": "https://github.com/llvm/llvm-project/releases/download/llvmorg-%s/%s",
57         "9.0.0": "https://releases.llvm.org/%s/%s",
58         "8.0.0": "https://releases.llvm.org/%s/%s",
59         "6.0.0": "https://releases.llvm.org/%s/%s"
60     ];
61 
62     enum dstepLLVMUrls = [
63         "10.0.0": "https://github.com/jacob-carlborg/llvm-project/releases/download/dstep-%s/%s"
64     ];
65 }
66 
67 else version (linux)
68 {
69     enum llvmArchives = [0: ["":""]];
70 
71     enum dstepLLVMArchives = [
72         64: [
73             "10.0.0": "llvm-10.0.0-linux-x86_64.tar.xz"
74         ]
75     ];
76 
77     enum llvmUrls = ["": ""];
78 
79     enum dstepLLVMUrls = [
80         "10.0.0": "https://github.com/jacob-carlborg/llvm-project/releases/download/dstep-%s/%s"
81     ];
82 }
83 
84 struct Config
85 {
86     bool dstepLLVM = false;
87     Architecture architecture = defaultArchitecture;
88 }
89 
90 void main(string[] args)
91 {
92     import std.getopt : getopt;
93 
94     Config config;
95 
96     auto helpInfo = getopt(
97         args,
98         "dstep", &config.dstepLLVM,
99         "arch", &config.architecture
100     );
101 
102     downloadLLVM(config);
103 
104     if (!shouldInstallUsingPackageManager(config))
105         extractArchive(config);
106 }
107 
108 bool shouldInstallUsingPackageManager(Config config)
109 {
110     version (linux)
111         return !config.dstepLLVM;
112 
113     else
114         return false;
115 }
116 
117 void installUsingPackageManager()
118 {
119     import std.format : format;
120 
121     const llvmMajorVersion = .llvmMajorVersion == "6" ? "6.0" : .llvmMajorVersion;
122 
123     executeShell("curl -L https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -");
124 
125     const repo = format!"deb https://apt.llvm.org/xenial/ llvm-toolchain-xenial-%s main"(llvmMajorVersion);
126     execute("sudo", "add-apt-repository", "-y", repo);
127     execute("sudo", "apt-get", "-q", "update");
128 
129     const libclangPackage = format!"libclang-%s-dev"(llvmMajorVersion);
130     execute("sudo", "apt-get", "install", "-y", libclangPackage);
131 }
132 
133 void downloadLLVM(Config config)
134 {
135     import std.file : exists, mkdirRecurse;
136     import std.net.curl : download;
137     import std.path : buildPath;
138     import std.stdio : writefln;
139 
140     if (shouldInstallUsingPackageManager(config))
141     {
142         installUsingPackageManager();
143         return;
144     }
145 
146     auto archivePath = buildPath(downloadPath, llvmArchive(config));
147 
148     if (!exists(archivePath))
149     {
150         writefln("Downloading LLVM %s to %s", llvmVersion, archivePath);
151         mkdirRecurse(downloadPath);
152         download(llvmUrl(config), archivePath);
153     }
154 
155     else
156         writefln("LLVM %s already exists", llvmVersion);
157 }
158 
159 string componentsToStrip(Config config)
160 {
161     if (config.dstepLLVM)
162     {
163         version (OSX)
164             return "4";
165         else
166             return "3";
167     }
168     else
169         return "1";
170 }
171 
172 void extractArchive(Config config)
173 {
174     import std.path : buildPath;
175     import std.stdio : writefln;
176     import std.file : mkdirRecurse;
177 
178     auto archivePath = buildPath(downloadPath, llvmArchive(config));
179     auto targetPath = buildPath(downloadPath, "clang");
180 
181     writefln("Extracting %s to %s", archivePath, targetPath);
182 
183     mkdirRecurse(targetPath);
184 
185     version (Posix)
186         execute("tar", "xf", archivePath, "-C", targetPath,
187             "--strip-components=" ~ componentsToStrip(config));
188     else
189         execute("7z", "x", archivePath, "-y", "-o" ~ targetPath);
190 }
191 
192 string llvmVersion()
193 {
194     import std.process : environment;
195 
196     return environment.get("LLVM_VERSION", "10.0.0");
197 }
198 
199 string llvmMajorVersion()
200 {
201     import std..string : split;
202 
203     return llvmVersion.split('.')[0];
204 }
205 
206 string llvmUrl(Config config)
207 {
208     import std.format : format;
209 
210     const baseUrls = config.dstepLLVM ? dstepLLVMUrls : llvmUrls;
211     const baseUrl = baseUrls.tryGet(llvmVersion);
212 
213     return format(baseUrl, llvmVersion, llvmArchive(config));
214 }
215 
216 string llvmArchive(Config config)
217 {
218     if (config.dstepLLVM)
219         return dstepLLVMArchive(config);
220 
221     return archive(llvmArchives, config);
222 }
223 
224 string dstepLLVMArchive(Config config)
225 {
226     return archive(dstepLLVMArchives, config);
227 }
228 
229 string archive(string[string][int] archives, Config config)
230 {
231     return archives.tryGet(config.architecture).tryGet(llvmVersion);
232 }
233 
234 Architecture defaultArchitecture()
235 {
236     version (X86_64)
237         return Architecture.bit64;
238     else version (X86)
239         return Architecture.bit32;
240     else
241         static assert("unsupported architecture");
242 }
243 
244 void execute(const string[] args ...)
245 {
246     import std.process : spawnProcess, wait;
247     import std.array : join;
248 
249     if (spawnProcess(args).wait() != 0)
250         throw new Exception("Failed to execute command: " ~ args.join(' '));
251 }
252 
253 void executeShell(string command)
254 {
255     import std.process : spawnShell, wait;
256 
257     if (spawnShell(command).wait() != 0)
258         throw new Exception("Failed to execute command: " ~ command);
259 }
260 
261 inout(V) tryGet(K, V)(inout(V[K]) aa, K key)
262 {
263     import std.format : format;
264 
265     if (auto value = key in aa)
266         return *value;
267     else
268     {
269         auto message = format("The key '%s' did not exist in the associative " ~
270             "array: %s", key, aa
271         );
272 
273         throw new Exception(message);
274     }
275 }