Skip to content

Commit

Permalink
Added rustup support
Browse files Browse the repository at this point in the history
Signed-off-by: paulober <[email protected]>
  • Loading branch information
paulober committed Sep 29, 2024
1 parent 0c5673c commit 39de7a4
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 158 deletions.
1 change: 0 additions & 1 deletion src/commands/compileProject.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> {
Expand Down
19 changes: 16 additions & 3 deletions src/utils/download.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions src/utils/githubREST.mts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export enum GithubRepository {
tools = 3,
picotool = 4,
rust = 5,
rsTools = 6,
}

/**
Expand Down Expand Up @@ -71,6 +72,8 @@ export function ownerOfRepository(repository: GithubRepository): string {
return "ninja-build";
case GithubRepository.rust:
return "rust-lang";
case GithubRepository.rsTools:
return "paulober";
}
}

Expand All @@ -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";
}
}

Expand Down Expand Up @@ -316,6 +321,10 @@ export async function getRustReleases(): Promise<string[]> {
return getReleases(GithubRepository.rust);
}

export async function getRustToolsReleases(): Promise<string[]> {
return getReleases(GithubRepository.rsTools);
}

/**
* Get the release data for a specific tag from
* the GitHub RESY API.
Expand Down
225 changes: 189 additions & 36 deletions src/utils/rustUtil.mts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -89,14 +74,15 @@ function computeDownloadLink(release: string): string {
"https://static.rust-lang.org/dist" +
`/rust-${release}-${arch}-${platform}.tar.xz`
);
}
}*/

export async function cargoInstall(
packageName: string,
locked = false
): Promise<boolean> {
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}`,
{
Expand All @@ -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:",
Expand All @@ -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,
Expand Down Expand Up @@ -314,7 +305,13 @@ export async function checkRustInstallation(): Promise<boolean> {
}
}

export async function installEmbeddedRust(): Promise<boolean> {
/**
* 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<boolean> {
/*try {
const rustup = process.platform === "win32" ? "rustup.exe" : "rustup";
const cargo = process.platform === "win32" ? "cargo.exe" : "cargo";
Expand Down Expand Up @@ -378,8 +375,8 @@ export async function installEmbeddedRust(): Promise<boolean> {
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;
Expand All @@ -390,8 +387,8 @@ export async function installEmbeddedRust(): Promise<boolean> {
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;
Expand All @@ -402,8 +399,106 @@ export async function installEmbeddedRust(): Promise<boolean> {
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<boolean> {
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;
Expand All @@ -412,4 +507,62 @@ export async function installEmbeddedRust(): Promise<boolean> {
return true;
}

export
export async function generateRustProject(
projectFolder: string,
name: string,
flashMethod: string
): Promise<boolean> {
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;
}
Loading

0 comments on commit 39de7a4

Please sign in to comment.