From 39de7a4ccc32cebfff3ee3b8ee4cf198addb76e0 Mon Sep 17 00:00:00 2001 From: paulober <44974737+paulober@users.noreply.github.com> Date: Sun, 29 Sep 2024 23:15:32 +0100 Subject: [PATCH] Added rustup support Signed-off-by: paulober <44974737+paulober@users.noreply.github.com> --- src/commands/compileProject.mts | 1 - src/utils/download.mts | 19 ++- src/utils/githubREST.mts | 9 ++ src/utils/rustUtil.mts | 225 +++++++++++++++++++++++----- src/webview/newRustProjectPanel.mts | 167 ++++++--------------- 5 files changed, 263 insertions(+), 158 deletions(-) diff --git a/src/commands/compileProject.mts b/src/commands/compileProject.mts index 0f90a4e..1f2e4e4 100644 --- a/src/commands/compileProject.mts +++ b/src/commands/compileProject.mts @@ -3,7 +3,6 @@ import { EventEmitter } from "events"; import { CommandWithResult } from "./command.mjs"; import Logger from "../logger.mjs"; import Settings, { SettingsKey } from "../settings.mjs"; -import { ContextKeys } from "../contextKeys.mjs"; import State from "../state.mjs"; export default class CompileProjectCommand extends CommandWithResult { diff --git a/src/utils/download.mts b/src/utils/download.mts index b5cc90a..46444b1 100644 --- a/src/utils/download.mts +++ b/src/utils/download.mts @@ -244,7 +244,7 @@ export async function downloadAndInstallArchive( // Ensure the target directory exists await mkdir(targetDirectory, { recursive: true }); - const archiveExtension = getArchiveExtension(url); + let archiveExtension = getArchiveExtension(url); if (!archiveExtension) { Logger.error( LoggerSource.downloader, @@ -254,6 +254,19 @@ export async function downloadAndInstallArchive( 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); @@ -566,8 +579,8 @@ export async function downloadAndInstallSDK( * @param redirectURL An optional redirect URL to download the asset * from (used to follow redirects recursively) * @returns A promise that resolves to true if the asset was downloaded and installed successfully - */ -async function downloadAndInstallGithubAsset( + */ // TODO: do not export +export async function downloadAndInstallGithubAsset( version: string, releaseVersion: string, repo: GithubRepository, diff --git a/src/utils/githubREST.mts b/src/utils/githubREST.mts index f0e0ba0..ab1fee6 100644 --- a/src/utils/githubREST.mts +++ b/src/utils/githubREST.mts @@ -26,6 +26,7 @@ export enum GithubRepository { tools = 3, picotool = 4, rust = 5, + rsTools = 6, } /** @@ -71,6 +72,8 @@ export function ownerOfRepository(repository: GithubRepository): string { return "ninja-build"; case GithubRepository.rust: return "rust-lang"; + case GithubRepository.rsTools: + return "paulober"; } } @@ -95,6 +98,8 @@ export function repoNameOfRepository(repository: GithubRepository): string { return "picotool"; case GithubRepository.rust: return "rust"; + case GithubRepository.rsTools: + return "pico-vscode-rs-tools"; } } @@ -316,6 +321,10 @@ export async function getRustReleases(): Promise { return getReleases(GithubRepository.rust); } +export async function getRustToolsReleases(): Promise { + return getReleases(GithubRepository.rsTools); +} + /** * Get the release data for a specific tag from * the GitHub RESY API. diff --git a/src/utils/rustUtil.mts b/src/utils/rustUtil.mts index f709a7c..e9a244f 100644 --- a/src/utils/rustUtil.mts +++ b/src/utils/rustUtil.mts @@ -1,35 +1,20 @@ -import { homedir } from "os"; -import { - downloadAndInstallArchive, - downloadAndReadFile, - getScriptsRoot, -} from "./download.mjs"; -import { getRustReleases } from "./githubREST.mjs"; -import { join as joinPosix } from "path/posix"; -import { - existsSync, - mkdirSync, - readdirSync, - renameSync, - rmSync, - symlinkSync, -} from "fs"; +import { homedir, tmpdir } from "os"; +import { downloadAndInstallGithubAsset } from "./download.mjs"; +import { getRustToolsReleases, GithubRepository } from "./githubREST.mjs"; +import { mkdirSync, renameSync } from "fs"; import Logger, { LoggerSource } from "../logger.mjs"; import { unknownErrorToString } from "./errorHelper.mjs"; -import { env, ProgressLocation, Uri, window } from "vscode"; -import type { Progress as GotProgress } from "got"; -import { parse as parseToml } from "toml"; +import { env, ProgressLocation, Uri, window, workspace } from "vscode"; import { promisify } from "util"; -import { exec, execSync } from "child_process"; +import { exec } from "child_process"; import { dirname, join } from "path"; -import { copyFile, mkdir, readdir, rm, stat } from "fs/promises"; -import findPython from "./pythonHelper.mjs"; -const STABLE_INDEX_DOWNLOAD_URL = - "https://static.rust-lang.org/dist/channel-rust-stable.toml"; +/*const STABLE_INDEX_DOWNLOAD_URL = + "https://static.rust-lang.org/dist/channel-rust-stable.toml";*/ const execAsync = promisify(exec); +/* interface IndexToml { pkg?: { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -89,7 +74,7 @@ function computeDownloadLink(release: string): string { "https://static.rust-lang.org/dist" + `/rust-${release}-${arch}-${platform}.tar.xz` ); -} +}*/ export async function cargoInstall( packageName: string, @@ -97,6 +82,7 @@ export async function cargoInstall( ): Promise { const command = process.platform === "win32" ? "cargo.exe" : "cargo"; try { + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { stdout, stderr } = await execAsync( `${command} install ${locked ? "--locked " : ""}${packageName}`, { @@ -105,11 +91,14 @@ export async function cargoInstall( ); if (stderr) { + // TODO: find better solution if ( stderr.toLowerCase().includes("already exists") || - stderr.toLowerCase().includes("to your path") + stderr.toLowerCase().includes("to your path") || + stderr.toLowerCase().includes("is already installed") || + stderr.toLowerCase().includes("yanked in registry") ) { - Logger.warn( + Logger.debug( LoggerSource.rustUtil, `Cargo package '${packageName}' is already installed ` + "or cargo bin not in PATH:", @@ -132,7 +121,9 @@ export async function cargoInstall( const msg = unknownErrorToString(error); if ( msg.toLowerCase().includes("already exists") || - msg.toLowerCase().includes("to your path") + msg.toLowerCase().includes("to your path") || + msg.toLowerCase().includes("is already installed") || + msg.toLowerCase().includes("yanked in registry") ) { Logger.warn( LoggerSource.rustUtil, @@ -314,7 +305,13 @@ export async function checkRustInstallation(): Promise { } } -export async function installEmbeddedRust(): Promise { +/** + * Installs all requirements for embedded Rust development. + * (if required) + * + * @returns {boolean} True if all requirements are met or have been installed, false otherwise. + */ +export async function downloadAndInstallRust(): Promise { /*try { const rustup = process.platform === "win32" ? "rustup.exe" : "rustup"; const cargo = process.platform === "win32" ? "cargo.exe" : "cargo"; @@ -378,8 +375,8 @@ export async function installEmbeddedRust(): Promise { const result = await cargoInstall(flipLink, false); if (!result) { void window.showErrorMessage( - `Failed to install cargo package '${flipLink}'.`, - "Please check the logs." + `Failed to install cargo package '${flipLink}'.` + + "Please check the logs." ); return false; @@ -390,8 +387,8 @@ export async function installEmbeddedRust(): Promise { const result2 = await cargoInstall(probeRsTools, true); if (!result2) { void window.showErrorMessage( - `Failed to install cargo package '${probeRsTools}'.`, - "Please check the logs." + `Failed to install cargo package '${probeRsTools}'.` + + "Please check the logs." ); return false; @@ -402,8 +399,106 @@ export async function installEmbeddedRust(): Promise { const result3 = await cargoInstall(elf2uf2Rs, true); if (!result3) { void window.showErrorMessage( - `Failed to install cargo package '${elf2uf2Rs}'.`, - "Please check the logs." + `Failed to install cargo package '${elf2uf2Rs}'.` + + "Please check the logs." + ); + + return false; + } + + // install cargo-generate binary + const result4 = await installCargoGenerate(); + if (!result4) { + void window.showErrorMessage( + "Failed to install cargo-generate. Please check the logs." + ); + + return false; + } + + return true; +} + +function platformToGithubMatrix(platform: string): string { + switch (platform) { + case "darwin": + return "macos-latest"; + case "linux": + return "ubuntu-latest"; + case "win32": + return "windows-latest"; + default: + throw new Error(`Unsupported platform: ${platform}`); + } +} + +function archToGithubMatrix(arch: string): string { + switch (arch) { + case "x64": + return "x86_64"; + case "arm64": + return "aarch64"; + default: + throw new Error(`Unsupported architecture: ${arch}`); + } +} + +async function installCargoGenerate(): Promise { + const release = await getRustToolsReleases(); + if (!release) { + Logger.error(LoggerSource.rustUtil, "Failed to get Rust tools releases"); + + return false; + } + + const assetName = `cargo-generate-${platformToGithubMatrix( + process.platform + )}-${archToGithubMatrix(process.arch)}.zip`; + + const tmpLoc = join(tmpdir(), "pico-vscode-rs"); + + const result = await downloadAndInstallGithubAsset( + release[0], + release[0], + GithubRepository.rsTools, + tmpLoc, + "cargo-generate.zip", + assetName, + "cargo-generate" + ); + + if (!result) { + Logger.error(LoggerSource.rustUtil, "Failed to install cargo-generate"); + + return false; + } + + const cargoBin = join(homedir(), ".cargo", "bin"); + + try { + mkdirSync(cargoBin, { recursive: true }); + renameSync( + join( + tmpLoc, + "cargo-generate" + (process.platform === "win32" ? ".exe" : "") + ), + join( + cargoBin, + "cargo-generate" + (process.platform === "win32" ? ".exe" : "") + ) + ); + + if (process.platform !== "win32") { + await execAsync(`chmod +x ${join(cargoBin, "cargo-generate")}`, { + windowsHide: true, + }); + } + } catch (error) { + Logger.error( + LoggerSource.rustUtil, + `Failed to move cargo-generate to ~/.cargo/bin: ${unknownErrorToString( + error + )}` ); return false; @@ -412,4 +507,62 @@ export async function installEmbeddedRust(): Promise { return true; } -export +export async function generateRustProject( + projectFolder: string, + name: string, + flashMethod: string +): Promise { + try { + const valuesFile = join(tmpdir(), "pico-vscode", "values.toml"); + await workspace.fs.createDirectory(Uri.file(dirname(valuesFile))); + await workspace.fs.writeFile( + Uri.file(valuesFile), + // TODO: make selectable in UI + Buffer.from(`[values]\nflash_method="${flashMethod}"\n`, "utf-8") + ); + + // TODO: fix outside function (maybe) + let projectRoot = projectFolder; + if (projectFolder.endsWith(name)) { + projectRoot = projectFolder.slice(0, projectFolder.length - name.length); + } + + // cache template and use --path + const command = + "cargo generate --git " + + "https://github.com/rp-rs/rp2040-project-template " + + ` --name ${name} --values-file "${valuesFile}" ` + + `--destination "${projectRoot}"`; + + const customEnv = { ...process.env }; + customEnv["PATH"] += `${process.platform === "win32" ? ";" : ":"}${join( + homedir(), + ".cargo", + "bin" + )}`; + // TODO: add timeout + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { stdout, stderr } = await execAsync(command, { + windowsHide: true, + env: customEnv, + }); + + if (stderr) { + Logger.error( + LoggerSource.rustUtil, + `Failed to generate Rust project: ${stderr}` + ); + + return false; + } + } catch (error) { + Logger.error( + LoggerSource.rustUtil, + `Failed to generate Rust project: ${unknownErrorToString(error)}` + ); + + return false; + } + + return true; +} diff --git a/src/webview/newRustProjectPanel.mts b/src/webview/newRustProjectPanel.mts index d463399..16acd81 100644 --- a/src/webview/newRustProjectPanel.mts +++ b/src/webview/newRustProjectPanel.mts @@ -22,8 +22,10 @@ import { import { existsSync, readFileSync } from "fs"; import { join } from "path"; import { unknownErrorToString } from "../utils/errorHelper.mjs"; -import { downloadAndInstallRust } from "../utils/rustUtil.mjs"; -import { cloneRepository, getGit } from "../utils/gitUtil.mjs"; +import { + downloadAndInstallRust, + generateRustProject, +} from "../utils/rustUtil.mjs"; interface SubmitMessageValue { projectName: string; @@ -295,18 +297,13 @@ export class NewRustProjectPanel { increment: 10, }); - const gitPath = await getGit(this._settings); - try { - await workspace.fs.createDirectory(Uri.file(projectFolder)); - - // also create a blink.py in it with a import machine - // TODO: put into const and cache template - const result = await cloneRepository( - "https://github.com/rp-rs/rp2040-project-template.git", - "main", + //await workspace.fs.createDirectory(Uri.file(projectFolder)); + // TODO: add flash method to ui + const result = await generateRustProject( projectFolder, - gitPath + data.projectName, + "probe-rs" ); if (!result) { progress.report({ @@ -314,7 +311,7 @@ export class NewRustProjectPanel { increment: 100, }); void window.showErrorMessage( - `Failed to clone project template to ${projectFolder}` + `Failed to create project folder ${projectFolder}` ); return; @@ -327,124 +324,58 @@ export class NewRustProjectPanel { await workspace.fs.writeFile( Uri.file(join(projectFolder, ".vscode", "extensions.json")), Buffer.from( - JSON.stringify({ - recommendations: [ - "rust-lang.rust-analyzer", - this._settings.getExtensionId(), - ], - }), + JSON.stringify( + { + recommendations: [ + "rust-lang.rust-analyzer", + this._settings.getExtensionId(), + ], + }, + undefined, + 4 + ), "utf-8" ) ); await workspace.fs.writeFile( Uri.file(join(projectFolder, ".vscode", "tasks.json")), Buffer.from( - JSON.stringify({ - version: "2.0.0", - tasks: [ - { - label: "Compile Project", - type: "process", - isBuildCommand: true, - command: "${userHome}/.pico-sdk/rust/latest/bin/cargo", - args: ["build"], - group: { - kind: "build", - isDefault: true, - }, - presentation: { - reveal: "always", - panel: "dedicated", - }, - problemMatcher: "$rustc", - windows: { - command: - "${env:USERPROFILE}/.pico-sdk/rust/latest/bin/cargo.exe", + JSON.stringify( + { + version: "2.0.0", + tasks: [ + { + label: "Compile Project", + type: "process", + isBuildCommand: true, + command: "cargo", args: ["build"], - options: { - env: { - // eslint-disable-next-line @typescript-eslint/naming-convention - PATH: "${env:PATH};${userHome}/.cargo/bin;${userHome}/.pico-sdk/rust/latest/bin;${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/bin/Hostx64/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/10.0.19041.0/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/10.0.19041.0/x64/ucrt", - - // eslint-disable-next-line @typescript-eslint/naming-convention - VSCMD_ARG_HOST_ARCH: "x64", - // eslint-disable-next-line @typescript-eslint/naming-convention - VSCMD_ARG_TGT_ARCH: "x64", - // eslint-disable-next-line @typescript-eslint/naming-convention - VCToolsVersion: "14.41.34120", - // eslint-disable-next-line @typescript-eslint/naming-convention - WindowsSDKVersion: "10.0.19041.0", - // eslint-disable-next-line @typescript-eslint/naming-convention - VCToolsInstallDir: - "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/", - // eslint-disable-next-line @typescript-eslint/naming-convention - WindowsSdkBinPath: - "${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/", - // eslint-disable-next-line @typescript-eslint/naming-convention - INCLUDE: - "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/include;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/ucrt;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/shared;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/um;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/winrt;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/cppwinrt", - // eslint-disable-next-line @typescript-eslint/naming-convention - LIB: "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/lib/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Lib/10.0.19041.0/ucrt/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Lib/10.0.19041.0/um/x64", - }, + group: { + kind: "build", + isDefault: true, }, - }, - options: { - env: { - // eslint-disable-next-line @typescript-eslint/naming-convention - PATH: "${env:PATH}:${userHome}/.cargo/bin:${userHome}/.pico-sdk/rust/latest/bin", + presentation: { + reveal: "always", + panel: "dedicated", }, + problemMatcher: "$rustc", }, - }, - { - label: "Run", - type: "shell", - command: "${userHome}/.pico-sdk/rust/latest/bin/cargo.exe", - args: ["run", "--release"], - group: { - kind: "test", - isDefault: true, - }, - problemMatcher: "$rustc", - windows: { - command: - "${env:USERPROFILE}/.pico-sdk/rust/latest/bin/cargo.exe", + { + label: "Run Project", + type: "shell", + command: "cargo", args: ["run", "--release"], - options: { - env: { - // eslint-disable-next-line @typescript-eslint/naming-convention - PATH: "${env:PATH};${userHome}/.cargo/bin;${userHome}/.pico-sdk/rust/latest/bin;${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/bin/Hostx64/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/10.0.19041.0/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/10.0.19041.0/x64/ucrt", - - // eslint-disable-next-line @typescript-eslint/naming-convention - VSCMD_ARG_HOST_ARCH: "x64", - // eslint-disable-next-line @typescript-eslint/naming-convention - VSCMD_ARG_TGT_ARCH: "x64", - // eslint-disable-next-line @typescript-eslint/naming-convention - VCToolsVersion: "14.41.34120", - // eslint-disable-next-line @typescript-eslint/naming-convention - WindowsSDKVersion: "10.0.19041.0", - // eslint-disable-next-line @typescript-eslint/naming-convention - VCToolsInstallDir: - "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/", - // eslint-disable-next-line @typescript-eslint/naming-convention - WindowsSdkBinPath: - "${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/bin/", - // eslint-disable-next-line @typescript-eslint/naming-convention - INCLUDE: - "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/include;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/ucrt;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/shared;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/um;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/winrt;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Include/10.0.19041.0/cppwinrt", - // eslint-disable-next-line @typescript-eslint/naming-convention - LIB: "${userHome}/.pico-sdk/msvc/latest/VC/Tools/MSVC/14.41.34120/lib/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Lib/10.0.19041.0/ucrt/x64;${userHome}/.pico-sdk/msvc/latest/Windows Kits/10/Lib/10.0.19041.0/um/x64", - }, - }, - }, - options: { - env: { - // eslint-disable-next-line @typescript-eslint/naming-convention - PATH: "${env:PATH}:${userHome}/.cargo/bin:${userHome}/.pico-sdk/rust/latest/bin", + group: { + kind: "test", + isDefault: true, }, + problemMatcher: "$rustc", }, - }, - ], - }), + ], + }, + undefined, + 4 + ), "utf-8" ) );