mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
chore: make downloadPrebuilt a little more reliable (#20401)
Makes the prebuilt installation a bit more reliable: - Check for 200 - Check for an executable header (MZ, ELF, etc) - Download to a .temp file until we're certain the file is valid - If multiple requests for a tool are made, only run one task
This commit is contained in:
parent
9226207c01
commit
8eda8c0dbd
1 changed files with 57 additions and 2 deletions
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
import { deferred } from "../test_util/std/async/deferred.ts";
|
||||
import {
|
||||
dirname,
|
||||
fromFileUrl,
|
||||
|
@ -115,10 +116,45 @@ const platformDirName = {
|
|||
|
||||
const executableSuffix = Deno.build.os === "windows" ? ".exe" : "";
|
||||
|
||||
async function sanityCheckPrebuiltFile(toolPath) {
|
||||
const stat = await Deno.stat(toolPath);
|
||||
if (stat.size < PREBUILT_MINIMUM_SIZE) {
|
||||
throw new Error(
|
||||
`File size ${stat.size} is less than expected minimum file size ${PREBUILT_MINIMUM_SIZE}`,
|
||||
);
|
||||
}
|
||||
const file = await Deno.open(toolPath, { read: true });
|
||||
const buffer = new Uint8Array(1024);
|
||||
let n = 0;
|
||||
while (n < 1024) {
|
||||
n += await file.read(buffer.subarray(n));
|
||||
}
|
||||
|
||||
// Mac: OK
|
||||
if (buffer[0] == 0xcf && buffer[1] == 0xfa) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows OK
|
||||
if (buffer[0] == "M".charCodeAt(0) && buffer[1] == "Z".charCodeAt(0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Linux OK
|
||||
if (
|
||||
buffer[0] == 0x7f && buffer[1] == "E".charCodeAt(0) &&
|
||||
buffer[2] == "L".charCodeAt(0) && buffer[3] == "F".charCodeAt(0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Invalid executable (header was ${buffer.subarray(0, 16)}`);
|
||||
}
|
||||
|
||||
export async function getPrebuilt(toolName) {
|
||||
const toolPath = getPrebuiltToolPath(toolName);
|
||||
try {
|
||||
await Deno.stat(toolPath);
|
||||
await sanityCheckPrebuiltFile(toolPath);
|
||||
const versionOk = await verifyVersion(toolName);
|
||||
if (!versionOk) {
|
||||
throw new Error("Version mismatch");
|
||||
|
@ -132,6 +168,8 @@ export async function getPrebuilt(toolName) {
|
|||
|
||||
const PREBUILT_PATH = join(ROOT_PATH, "third_party", "prebuilt");
|
||||
const PREBUILT_TOOL_DIR = join(PREBUILT_PATH, platformDirName);
|
||||
const PREBUILT_MINIMUM_SIZE = 16 * 1024;
|
||||
const DOWNLOAD_TASKS = {};
|
||||
|
||||
export function getPrebuiltToolPath(toolName) {
|
||||
return join(PREBUILT_TOOL_DIR, toolName + executableSuffix);
|
||||
|
@ -141,8 +179,15 @@ const downloadUrl =
|
|||
`https://raw.githubusercontent.com/denoland/deno_third_party/69ffd968c0c435f5f9dbba713a92b4fb6a3e2301/prebuilt/${platformDirName}`;
|
||||
|
||||
export async function downloadPrebuilt(toolName) {
|
||||
// Ensure only one download per tool happens at a time
|
||||
if (DOWNLOAD_TASKS[toolName]) {
|
||||
return await DOWNLOAD_TASKS[toolName];
|
||||
}
|
||||
|
||||
const downloadPromise = DOWNLOAD_TASKS[toolName] = deferred();
|
||||
const spinner = wait("Downloading prebuilt tool: " + toolName).start();
|
||||
const toolPath = getPrebuiltToolPath(toolName);
|
||||
const tempFile = `${toolPath}.temp`;
|
||||
|
||||
try {
|
||||
await Deno.mkdir(PREBUILT_TOOL_DIR, { recursive: true });
|
||||
|
@ -150,19 +195,29 @@ export async function downloadPrebuilt(toolName) {
|
|||
const url = `${downloadUrl}/${toolName}${executableSuffix}`;
|
||||
|
||||
const resp = await fetch(url);
|
||||
const file = await Deno.open(toolPath, {
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Non-successful response from ${url}: ${resp.status}`);
|
||||
}
|
||||
|
||||
const file = await Deno.open(tempFile, {
|
||||
create: true,
|
||||
write: true,
|
||||
mode: 0o755,
|
||||
});
|
||||
|
||||
await resp.body.pipeTo(file.writable);
|
||||
spinner.text = `Checking prebuilt tool: ${toolName}`;
|
||||
await sanityCheckPrebuiltFile(tempFile);
|
||||
spinner.text = `Successfully downloaded: ${toolName}`;
|
||||
await Deno.rename(tempFile, toolPath);
|
||||
} catch (e) {
|
||||
spinner.fail();
|
||||
downloadPromise.reject(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
spinner.succeed();
|
||||
downloadPromise.resolve(null);
|
||||
}
|
||||
|
||||
export async function verifyVersion(toolName) {
|
||||
|
|
Loading…
Reference in a new issue