diff --git a/src/utils/download.mts b/src/utils/download.mts index 66413b9..023bc3a 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -258,25 +258,12 @@ export async function downloadAndInstallArchive( if (!archiveExtension) { Logger.error( LoggerSource.downloader, - `Could not determine archive extension for ${url}` + `Could not determine archive extension for ${archiveFileName}` ); return false; } - // TODO: find and eliminate issue why this is necesarry - if (archiveExtension.length > 6) { - archiveExtension = getArchiveExtension(archiveFileName); - if (!archiveExtension) { - Logger.error( - LoggerSource.downloader, - `Could not determine archive extension for ${archiveFileName}` - ); - - return false; - } - } - const tmpBasePath = join(tmpdir(), "pico-sdk"); await mkdir(tmpBasePath, { recursive: true }); const archiveFilePath = join(tmpBasePath, archiveFileName); @@ -538,15 +525,14 @@ export async function downloadAndInstallSDK( ); // Constants taken from the SDK CMakeLists.txt files const TINYUSB_TEST_PATH = joinPosix( - "lib/tinyusb", "src/portable/raspberrypi/rp2040" + "lib/tinyusb", + "src/portable/raspberrypi/rp2040" ); const CYW43_DRIVER_TEST_FILE = joinPosix("lib/cyw43-driver", "src/cyw43.h"); const LWIP_TEST_PATH = joinPosix("lib/lwip", "src/Filelists.cmake"); const BTSTACK_TEST_PATH = joinPosix("lib/btstack", "src/bluetooth.h"); - const MBEDTLS_TEST_PATH = joinPosix("lib/mbedtls", "library/aes.c") - const submoduleChecks = [ - TINYUSB_TEST_PATH - ] + const MBEDTLS_TEST_PATH = joinPosix("lib/mbedtls", "library/aes.c"); + const submoduleChecks = [TINYUSB_TEST_PATH]; if (compareGe(version, "1.4.0")) { submoduleChecks.push(CYW43_DRIVER_TEST_FILE); submoduleChecks.push(LWIP_TEST_PATH); diff --git a/src/utils/rustUtil.mts b/src/utils/rustUtil.mts index 063f841..d52b878 100644 --- a/src/utils/rustUtil.mts +++ b/src/utils/rustUtil.mts @@ -5,7 +5,6 @@ import { env, ProgressLocation, Uri, window } from "vscode"; import { promisify } from "util"; import { exec } from "child_process"; import { join } from "path"; -import { homedir } from "os"; /*const STABLE_INDEX_DOWNLOAD_URL = "https://static.rust-lang.org/dist/channel-rust-stable.toml";*/ @@ -81,33 +80,10 @@ function computeDownloadLink(release: string): string { }*/ export async function cargoInstall( - cargoExecutable: string, packageName: string, - locked = false, - moreEnv: { [key: string]: string } + locked = false ): Promise { - const prefix = process.platform === "win32" ? "&" : ""; - const command = `${prefix}"${cargoExecutable}" install ${ - locked ? "--locked " : "" - }${packageName}`; - - let customEnv = process.env; - customEnv.PATH += `${process.platform === "win32" ? ";" : ":"}${dirname( - cargoExecutable - )}`; - if ("PATH" in moreEnv) { - customEnv.PATH += `${process.platform === "win32" ? ";" : ":"}${ - moreEnv.PATH - }`; - delete moreEnv.PATH; - } - if (moreEnv) { - // TODO: what with duplicates? - customEnv = { - ...customEnv, - ...moreEnv, - }; - } + const command = process.platform === "win32" ? "cargo.exe" : "cargo"; try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { stdout, stderr } = await execAsync( @@ -128,161 +104,79 @@ export async function cargoInstall( ) { Logger.warn( LoggerSource.rustUtil, - `Cargo command '${command}' failed but ignoring:`, + `Cargo package '${packageName}' is already installed ` + + "or cargo bin not in PATH:", msg ); return true; - } else { - Logger.error( - LoggerSource.rustUtil, - `Failed to install cargo command '${command}': ${msg}` - ); - - return false; } + Logger.error( + LoggerSource.rustUtil, + `Failed to install cargo package '${packageName}': ${unknownErrorToString( + error + )}` + ); + + return false; } } -export function detectExistingRustInstallation(): boolean { - const dir = joinPosix(homedir(), ".pico-sdk", "rust"); - - try { - const contents = readdirSync(dir); - - // Check if the directory contains a subdirectory if yes delete it - if (contents.length > 0) { - for (const itemToDelete of contents) { - const itemPath = joinPosix(dir, itemToDelete); - try { - rmSync(itemPath, { recursive: true, force: true }); - } catch (error) { - Logger.debug( - LoggerSource.rustUtil, - "Error deleting existing Rust installation:", - unknownErrorToString(error) - ); - } - } - - return true; +export function calculateRequiredHostTriple(): string { + const arch = process.arch; + const platform = process.platform; + let triple = ""; + if (platform === "win32" && arch === "x64") { + triple = "x86_64-pc-windows-msvc"; + } else if (platform === "darwin") { + if (arch === "x64") { + triple = "x86_64-apple-darwin"; } else { - return false; + triple = "aarch64-apple-darwin"; } - } catch { - return false; + } else if (platform === "linux") { + if (arch === "x64") { + triple = "x86_64-unknown-linux-gnu"; + } else if (arch === "arm64") { + triple = "aarch64-unknown-linux-gnu"; + } else { + throw new Error(`Unsupported architecture: ${arch}`); + } + } else { + throw new Error(`Unsupported platform: ${platform}`); } + + return triple; } -/** - * Merges multiple directories at the top level into a single directory structure. - * - * @param parentDir - The path to the parent directory containing the subdirectories to merge. - */ -async function mergeDirectories(parentDir: string): Promise { - // Get a list of all directories in the parent directory - const directories: string[] = (await readdir(parentDir)).filter(async dir => { - const stats = await stat(join(parentDir, dir)); - - return stats.isDirectory(); - }); - - // Define the subdirectories we want to merge - const subDirs: string[] = ["bin", "etc", "lib", "libexec", "share"]; - - // Create the top-level directories if they do not exist - await Promise.all( - subDirs.map(async subDir => { - const destSubDir: string = join(parentDir, subDir); - try { - await mkdir(destSubDir, { recursive: true }); - } catch { - // Ignore error if directory already exists - } - }) - ); +async function checkHostToolchainInstalled(): Promise { + try { + const hostTriple = calculateRequiredHostTriple(); + const rustup = process.platform === "win32" ? "rustup.exe" : "rustup"; + const { stdout } = await execAsync(`${rustup} toolchain list`, { + windowsHide: true, + }); - // Function to merge directories - const mergeSubDirectories = async ( - srcDir: string, - destDir: string - ): Promise => { - const items: string[] = await readdir(srcDir); - - await Promise.all( - items.map(async item => { - const srcItemPath: string = join(srcDir, item); - const destItemPath: string = join(destDir, item); - - const stats = await stat(srcItemPath); - if (stats.isDirectory()) { - // If the item is a directory, merge it recursively - await mkdir(destItemPath, { recursive: true }); - await mergeSubDirectories(srcItemPath, destItemPath); - } else { - // If it's a file, copy it - await copyFile(srcItemPath, destItemPath); - } - }) + return stdout.includes(hostTriple); + } catch (error) { + Logger.error( + LoggerSource.rustUtil, + `Failed to check for host toolchain: ${unknownErrorToString(error)}` ); - }; - - // Merge the contents of the subdirectories into the top-level structure - await Promise.all( - directories.map(async directory => { - const dirPath: string = join(parentDir, directory); - - await Promise.all( - subDirs.map(async subDir => { - const sourcePath: string = join(dirPath, subDir); - - try { - const stats = await stat(sourcePath); - if (stats.isDirectory()) { - await mergeSubDirectories(sourcePath, join(parentDir, subDir)); - } - } catch { - // Ignore error if directory does not exist - } - }) - ); - }) - ); - - // Remove the old directories after merging their contents - await Promise.all( - directories.map(async directory => { - await rm(join(parentDir, directory), { - recursive: true, - force: true, - }); - }) - ); -} - -// TODO: move task env setup for this into a command -async function installPortableMSVC(): Promise { - const python = await findPython(); - if (!python) { - Logger.error(LoggerSource.rustUtil, "Could not find python"); return false; } - const prefix = process.platform === "win32" ? "&" : ""; - // TODO: ask for license - const command = `${prefix}"${python}" "${joinPosix( - getScriptsRoot(), - "portable-msvc.py" - )}" --accept-license --msvc-version 14.41 --sdk-version 19041`; + // or else check .rustup/toolchains/ for the host toolchain +} +export async function installHostToolchain(): Promise { try { // TODO: maybe listen for stderr // this will automatically take care of having the correct // recommended host toolchain installed except for snap rustup installs const { stdout } = await execAsync("rustup show", { windowsHide: true, - cwd: join(homedir(), ".pico-sdk", "msvc"), }); if (stdout.includes("no active toolchain")) { @@ -295,8 +189,7 @@ async function installPortableMSVC(): Promise { } catch (error) { Logger.error( LoggerSource.rustUtil, - "Failed to install MSVC:", - unknownErrorToString(error) + `Failed to install host toolchain: ${unknownErrorToString(error)}` ); return false; @@ -304,199 +197,97 @@ async function installPortableMSVC(): Promise { } /** - * Download and installs the latest version of Rust. + * Checks for all requirements except targets and cargo packages. * - * @returns A promise that resolves to an object containing the - * paths to the installed `rustc` and `cargo` executables, - * or `undefined` if the installation failed. + * (Cares about UI feedback) + * + * @returns {boolean} True if all requirements are met, false otherwise. */ -export async function downloadAndInstallRust(): Promise { - // TODO: use channel rust stable instead - const rustReleases = await getRustReleases(); - const latestRelease = rustReleases[0]; - - const downloadLink = computeDownloadLink(latestRelease); - const targetDirectory = joinPosix( - homedir(), - ".pico-sdk", - "rust", - latestRelease - ); - - if (existsSync(targetDirectory)) { - Logger.debug( - LoggerSource.rustUtil, - `Latest Rust ${latestRelease} already installed, skipping installation` - ); - - return joinPosix( - targetDirectory, - "bin", - "cargo" + (process.platform === "win32" ? ".exe" : "") - ); - } - - const existingInstallation = detectExistingRustInstallation(); - - // Download and install Rust - let progressState = 0; - const result = await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Downloading and installing Rust", - cancellable: false, - }, - async progress => - downloadAndInstallArchive( - downloadLink, - targetDirectory, - `rust-${latestRelease}.tar.xz`, - "Rust", - undefined, - undefined, - undefined, - (prog: GotProgress) => { - const percent = prog.percent * 100; - progress.report({ increment: percent - progressState }); - progressState = percent; - } - ) - ); - if (!result) { - return undefined; - } - - const index = await downloadAndReadFile(STABLE_INDEX_DOWNLOAD_URL); - if (!index) { - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ - } - - return undefined; - } - Logger.debug(LoggerSource.rustUtil, "Downloaded Rust index file"); - +export async function checkRustInstallation(): Promise { + let rustupOk = false; + let rustcOk = false; + let cargoOk = false; try { - const data: IndexToml = parseToml(index) as IndexToml; + const rustup = process.platform === "win32" ? "rustup.exe" : "rustup"; + await execAsync(`${rustup} --version`, { + windowsHide: true, + }); + rustupOk = true; - const targetTriple = "thumbv6m-none-eabi"; - if ( - data.pkg && - data.pkg["rust-std"] && - data.pkg["rust-std"].target && - data.pkg["rust-std"].target[targetTriple] && - data.pkg["rust-std"].target[targetTriple].available - ) { - const stdDownloadLink = data.pkg["rust-std"].target[targetTriple].xz_url; - progressState = 0; - const targetLabel = `rust-std-${targetTriple}`; - const newTargetDirectory = joinPosix(targetDirectory, targetLabel); + // check rustup toolchain + const result = await checkHostToolchainInstalled(); + if (!result) { + Logger.error(LoggerSource.rustUtil, "Host toolchain not installed"); + + // TODO: make cancelable (Ctrl+C) const result = await window.withProgress( { location: ProgressLocation.Notification, - title: "Downloading and installing Pico Rust target stdlib", - cancellable: false, + title: "Installing Rust toolchain", + cancellable: true, }, - async progress => - downloadAndInstallArchive( - stdDownloadLink, - newTargetDirectory, - `rust-pico-${latestRelease}-std.tar.xz`, - "Rust Pico Standard Library", - undefined, - undefined, - undefined, - (prog: GotProgress) => { - const percent = prog.percent * 100; - progress.report({ increment: percent - progressState }); - progressState = percent; - }, - `rust-std-${latestRelease}-${targetTriple}/${targetLabel}` - ) + async () => installHostToolchain() ); if (!result) { - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ - } - - return; - } - } else { - Logger.error( - LoggerSource.rustUtil, - "Error parsing Rust index file: std not available" - ); + void window.showErrorMessage( + "Failed to install Rust toolchain. " + + "Please install it manually with `rustup show`." + ); - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ + return false; } - - return; } - if ( - data.pkg && - data.pkg["rust-analysis"] && - data.pkg["rust-analysis"].target && - data.pkg["rust-analysis"].target[targetTriple] && - data.pkg["rust-analysis"].target[targetTriple].available - ) { - const stdDownloadLink = - data.pkg["rust-analysis"].target[targetTriple].xz_url; - progressState = 0; - const targetLabel = `rust-analysis-${targetTriple}`; - const newTargetDirectory = joinPosix(targetDirectory, targetLabel); - const result = await window.withProgress( - { - location: ProgressLocation.Notification, - title: "Downloading and installing Pico Rust target analysis library", - cancellable: false, - }, - async progress => - downloadAndInstallArchive( - stdDownloadLink, - newTargetDirectory, - `rust-pico-${latestRelease}-analysis.tar.xz`, - "Rust Pico Analysis Library", - undefined, - undefined, - undefined, - (prog: GotProgress) => { - const percent = prog.percent * 100; - progress.report({ increment: percent - progressState }); - progressState = percent; - }, - `rust-analysis-${latestRelease}-${targetTriple}/${targetLabel}` - ) - ); + const rustc = process.platform === "win32" ? "rustc.exe" : "rustc"; + await execAsync(`${rustc} --version`, { + windowsHide: true, + }); + rustcOk = true; - if (!result) { - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ - } + const cargo = process.platform === "win32" ? "cargo.exe" : "cargo"; + await execAsync(`${cargo} --version`, { + windowsHide: true, + }); + cargoOk = true; - return; - } + return true; + } catch (error) { + Logger.error( + LoggerSource.rustUtil, + `Rust installation check failed: ${unknownErrorToString(error)}` + ); + + if (!rustupOk) { + void window + .showErrorMessage( + "Rustup is not installed. Please install it manually.", + "Install" + ) + .then(result => { + if (result) { + env.openExternal( + Uri.parse("https://www.rust-lang.org/tools/install", true) + ); + } + }); + } else if (!rustcOk) { + void window.showErrorMessage( + "Rustc is not installed. Please install it manually." + ); + } else if (!cargoOk) { + void window.showErrorMessage( + "Cargo is not installed. Please install it manually." + ); } else { - Logger.error( - LoggerSource.rustUtil, - "Error parsing Rust index file: analysis not available" + void window.showErrorMessage( + "Failed to check Rust installation. Please check the logs." ); + } - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ - } + return false; + } +} /** * Installs all requirements for embedded Rust development. @@ -545,11 +336,8 @@ export async function downloadAndInstallRust(): Promise { "Please check the logs." ); - // symlink to latest - const latestPath = joinPosix(homedir(), ".pico-sdk", "rust", "latest"); - if (existsSync(latestPath)) { - rmSync(latestPath, { recursive: true, force: true }); - } + return false; + } // or install probe-rs-tools const probeRsTools = "defmt-print"; @@ -560,11 +348,8 @@ export async function downloadAndInstallRust(): Promise { "Please check the logs." ); - try { - rmSync(targetDirectory, { recursive: true, force: true }); - } catch { - /* */ - } + return false; + } // install elf2uf2-rs const elf2uf2Rs = "elf2uf2-rs";