Skip to content

Commit df8b50e

Browse files
committed
Add xz decompression support for Heavy Toolchain
1 parent da2211a commit df8b50e

1 file changed

Lines changed: 102 additions & 30 deletions

File tree

Source/Heavy/Toolchain.h

Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#pragma clang diagnostic push
99

1010
#include <juce_gui_basics/juce_gui_basics.h>
11+
#include <xz/src/liblzma/api/lzma.h>
12+
#include <fstream>
1113
#include "Constants.h"
1214

1315
struct Toolchain {
@@ -148,12 +150,14 @@ class ToolchainInstaller final : public Component
148150
String downloadLocation = "https://github.com/plugdata-team/plugdata-heavy-toolchain/releases/download/v" + latestVersion + "/";
149151

150152
#if JUCE_MAC
151-
downloadLocation += "Heavy-MacOS-Universal.zip";
153+
downloadLocation += "Heavy-MacOS-Universal.tar.xz";
152154
#elif JUCE_WINDOWS
153-
downloadLocation += "Heavy-Win64.zip";
155+
downloadLocation += "Heavy-Win64.tar.xz";
154156
#elif JUCE_LINUX && !__aarch64__
155-
downloadLocation += "Heavy-Linux-x64.zip";
157+
downloadLocation += "Heavy-Linux-x64.tar.xz";
156158
#endif
159+
// for testing, you can set a local archive path here
160+
//downloadLocation = "file://Users/timothy/Downloads/Heavy-MacOS-Universal.tar.xz";
157161

158162
instream = URL(downloadLocation).createInputStream(URL::InputStreamOptions(URL::ParameterHandling::inAddress).withConnectionTimeoutMs(10000).withStatusCode(&statusCode));
159163
startThread();
@@ -259,17 +263,105 @@ class ToolchainInstaller final : public Component
259263

260264
startTimer(25);
261265

262-
MemoryInputStream input(toolchainData, false);
263-
ZipFile zip(input);
264-
265266
auto const toolchainDir = ProjectInfo::appDataDir.getChildFile("Toolchain");
266267

267268
if (toolchainDir.exists())
268269
toolchainDir.deleteRecursively();
269270

270-
auto const result = zip.uncompressTo(toolchainDir);
271+
HeapArray<uint8_t> decompressedToolchain;
272+
#if JUCE_LINUX || JUCE_WINDOWS
273+
decompressedToolchain.reserve(800 * 1024 * 1024);
274+
#else
275+
decompressedToolchain.reserve(500 * 1024 * 1024);
276+
#endif
277+
bool failed = false;
278+
{
279+
lzma_stream strm = LZMA_STREAM_INIT;
280+
if (lzma_stream_decoder(&strm, UINT64_MAX, 0) != LZMA_OK) {
281+
failed = true;
282+
}
283+
else {
284+
strm.next_in = reinterpret_cast<const uint8_t*>(toolchainData.getData());
285+
strm.avail_in = toolchainData.getSize();
286+
287+
uint8_t buffer[8192];
288+
lzma_ret ret;
289+
290+
do {
291+
strm.next_out = buffer;
292+
strm.avail_out = sizeof(buffer);
293+
294+
ret = lzma_code(&strm, LZMA_FINISH);
295+
size_t written = sizeof(buffer) - strm.avail_out;
296+
decompressedToolchain.insert(decompressedToolchain.end(), buffer, buffer + written);
297+
298+
if (ret != LZMA_OK && ret != LZMA_STREAM_END) {
299+
failed = true;
300+
lzma_end(&strm);
301+
}
302+
} while (ret != LZMA_STREAM_END);
303+
304+
lzma_end(&strm);
305+
}
306+
}
271307

272-
if (!result.wasOk() || statusCode >= 400) {
308+
// Parse and extract .tar
309+
// TODO: we do this a few times inline, move it into a helper function along with the xz decompression
310+
auto extractTar = [](const uint8_t* data, size_t size, const File& destRoot) -> bool {
311+
size_t offset = 0;
312+
while (offset + 512 <= size) {
313+
const uint8_t* header = data + offset;
314+
if (header[0] == '\0') break; // End of archive
315+
316+
// Get file name
317+
std::string name(reinterpret_cast<const char*>(header), 100);
318+
if (name.empty()) break;
319+
320+
#if !JUCE_WINDOWS
321+
mode_t mode = static_cast<mode_t>(
322+
std::strtoul(reinterpret_cast<const char*>(header + 100), nullptr, 8)
323+
);
324+
bool executable = (mode & 0100) || (mode & 0010) || (mode & 0001);
325+
#endif
326+
// Get file size (octal)
327+
size_t fileSize = std::strtoull(reinterpret_cast<const char*>(header + 124), nullptr, 8);
328+
File outFile = destRoot.getChildFile(String(name));
329+
330+
// Determine type
331+
char typeFlag = header[156];
332+
if (typeFlag == '5') {
333+
outFile.createDirectory();
334+
#if !JUCE_WINDOWS
335+
outFile.setExecutePermission(executable);
336+
#endif
337+
} else if (typeFlag == '0' || typeFlag == '\0') {
338+
outFile.getParentDirectory().createDirectory();
339+
std::ofstream out(outFile.getFullPathName().toRawUTF8(), std::ios::binary);
340+
size_t fileOffset = offset + 512;
341+
out.write(reinterpret_cast<const char*>(data + fileOffset), fileSize);
342+
343+
if (!out.good()) {
344+
out.close();
345+
outFile.deleteFile(); // cleanup partial file
346+
return false;
347+
}
348+
out.close();
349+
#if !JUCE_WINDOWS
350+
outFile.setExecutePermission(executable);
351+
#endif
352+
}
353+
354+
size_t totalEntrySize = 512 + ((fileSize + 511) & ~511); // pad to next 512
355+
offset += totalEntrySize;
356+
}
357+
return true;
358+
};
359+
360+
if(!extractTar(decompressedToolchain.data(), decompressedToolchain.size(), toolchainDir)) {
361+
failed = true;
362+
}
363+
364+
if (failed || statusCode >= 400) {
273365
MessageManager::callAsync([this] {
274366
installButton.topText = "Try Again";
275367
errorMessage = "Error: Could not extract downloaded package";
@@ -278,28 +370,8 @@ class ToolchainInstaller final : public Component
278370
});
279371
return;
280372
}
281-
282-
// Make sure downloaded files have executable permission on unix
283-
#if JUCE_MAC || JUCE_LINUX || JUCE_BSD
284-
285-
auto const& tcPath = Toolchain::dir.getFullPathName();
286-
auto const permissionsScript = String("#!/bin/bash")
287-
+ "\nchmod +x " + tcPath + "/bin/Heavy/Heavy"
288-
+ "\nchmod +x " + tcPath + "/bin/*"
289-
+ "\nchmod +x " + tcPath + "/lib/dpf/utils/generate-ttl.sh"
290-
+ "\nchmod +x " + tcPath + "/arm-none-eabi/bin/*"
291-
+ "\nchmod +x " + tcPath + "/lib/gcc/arm-none-eabi/*/*"
292-
+ "\nchmod +x " + tcPath + "/lib/OwlProgram/Tools/*"
293-
# if JUCE_LINUX
294-
+ "\nchmod +x " + tcPath + "/x86_64-anywhere-linux-gnu/bin/*"
295-
+ "\nchmod +x " + tcPath + "/x86_64-anywhere-linux-gnu/sysroot/sbin/*"
296-
+ "\nchmod +x " + tcPath + "/x86_64-anywhere-linux-gnu/sysroot/usr/bin/*"
297-
# endif
298-
;
299-
300-
Toolchain::startShellScript(permissionsScript);
301-
302-
#elif JUCE_WINDOWS
373+
374+
#if JUCE_WINDOWS
303375
File usbDriverInstaller = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("install-filter.exe");
304376
File driverSpec = Toolchain::dir.getChildFile("etc").getChildFile("usb_driver").getChildFile("DFU_in_FS_Mode.inf");
305377

0 commit comments

Comments
 (0)