Skip to content

Commit 39de7a4

Browse files
committed
Added rustup support
Signed-off-by: paulober <[email protected]>
1 parent 0c5673c commit 39de7a4

File tree

5 files changed

+263
-158
lines changed

5 files changed

+263
-158
lines changed

src/commands/compileProject.mts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { EventEmitter } from "events";
33
import { CommandWithResult } from "./command.mjs";
44
import Logger from "../logger.mjs";
55
import Settings, { SettingsKey } from "../settings.mjs";
6-
import { ContextKeys } from "../contextKeys.mjs";
76
import State from "../state.mjs";
87

98
export default class CompileProjectCommand extends CommandWithResult<boolean> {

src/utils/download.mts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ export async function downloadAndInstallArchive(
244244
// Ensure the target directory exists
245245
await mkdir(targetDirectory, { recursive: true });
246246

247-
const archiveExtension = getArchiveExtension(url);
247+
let archiveExtension = getArchiveExtension(url);
248248
if (!archiveExtension) {
249249
Logger.error(
250250
LoggerSource.downloader,
@@ -254,6 +254,19 @@ export async function downloadAndInstallArchive(
254254
return false;
255255
}
256256

257+
// TODO: find and eliminate issue why this is necesarry
258+
if (archiveExtension.length > 6) {
259+
archiveExtension = getArchiveExtension(archiveFileName);
260+
if (!archiveExtension) {
261+
Logger.error(
262+
LoggerSource.downloader,
263+
`Could not determine archive extension for ${archiveFileName}`
264+
);
265+
266+
return false;
267+
}
268+
}
269+
257270
const tmpBasePath = join(tmpdir(), "pico-sdk");
258271
await mkdir(tmpBasePath, { recursive: true });
259272
const archiveFilePath = join(tmpBasePath, archiveFileName);
@@ -566,8 +579,8 @@ export async function downloadAndInstallSDK(
566579
* @param redirectURL An optional redirect URL to download the asset
567580
* from (used to follow redirects recursively)
568581
* @returns A promise that resolves to true if the asset was downloaded and installed successfully
569-
*/
570-
async function downloadAndInstallGithubAsset(
582+
*/ // TODO: do not export
583+
export async function downloadAndInstallGithubAsset(
571584
version: string,
572585
releaseVersion: string,
573586
repo: GithubRepository,

src/utils/githubREST.mts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export enum GithubRepository {
2626
tools = 3,
2727
picotool = 4,
2828
rust = 5,
29+
rsTools = 6,
2930
}
3031

3132
/**
@@ -71,6 +72,8 @@ export function ownerOfRepository(repository: GithubRepository): string {
7172
return "ninja-build";
7273
case GithubRepository.rust:
7374
return "rust-lang";
75+
case GithubRepository.rsTools:
76+
return "paulober";
7477
}
7578
}
7679

@@ -95,6 +98,8 @@ export function repoNameOfRepository(repository: GithubRepository): string {
9598
return "picotool";
9699
case GithubRepository.rust:
97100
return "rust";
101+
case GithubRepository.rsTools:
102+
return "pico-vscode-rs-tools";
98103
}
99104
}
100105

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

324+
export async function getRustToolsReleases(): Promise<string[]> {
325+
return getReleases(GithubRepository.rsTools);
326+
}
327+
319328
/**
320329
* Get the release data for a specific tag from
321330
* the GitHub RESY API.

src/utils/rustUtil.mts

Lines changed: 189 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,20 @@
1-
import { homedir } from "os";
2-
import {
3-
downloadAndInstallArchive,
4-
downloadAndReadFile,
5-
getScriptsRoot,
6-
} from "./download.mjs";
7-
import { getRustReleases } from "./githubREST.mjs";
8-
import { join as joinPosix } from "path/posix";
9-
import {
10-
existsSync,
11-
mkdirSync,
12-
readdirSync,
13-
renameSync,
14-
rmSync,
15-
symlinkSync,
16-
} from "fs";
1+
import { homedir, tmpdir } from "os";
2+
import { downloadAndInstallGithubAsset } from "./download.mjs";
3+
import { getRustToolsReleases, GithubRepository } from "./githubREST.mjs";
4+
import { mkdirSync, renameSync } from "fs";
175
import Logger, { LoggerSource } from "../logger.mjs";
186
import { unknownErrorToString } from "./errorHelper.mjs";
19-
import { env, ProgressLocation, Uri, window } from "vscode";
20-
import type { Progress as GotProgress } from "got";
21-
import { parse as parseToml } from "toml";
7+
import { env, ProgressLocation, Uri, window, workspace } from "vscode";
228
import { promisify } from "util";
23-
import { exec, execSync } from "child_process";
9+
import { exec } from "child_process";
2410
import { dirname, join } from "path";
25-
import { copyFile, mkdir, readdir, rm, stat } from "fs/promises";
26-
import findPython from "./pythonHelper.mjs";
2711

28-
const STABLE_INDEX_DOWNLOAD_URL =
29-
"https://static.rust-lang.org/dist/channel-rust-stable.toml";
12+
/*const STABLE_INDEX_DOWNLOAD_URL =
13+
"https://static.rust-lang.org/dist/channel-rust-stable.toml";*/
3014

3115
const execAsync = promisify(exec);
3216

17+
/*
3318
interface IndexToml {
3419
pkg?: {
3520
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -89,14 +74,15 @@ function computeDownloadLink(release: string): string {
8974
"https://static.rust-lang.org/dist" +
9075
`/rust-${release}-${arch}-${platform}.tar.xz`
9176
);
92-
}
77+
}*/
9378

9479
export async function cargoInstall(
9580
packageName: string,
9681
locked = false
9782
): Promise<boolean> {
9883
const command = process.platform === "win32" ? "cargo.exe" : "cargo";
9984
try {
85+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
10086
const { stdout, stderr } = await execAsync(
10187
`${command} install ${locked ? "--locked " : ""}${packageName}`,
10288
{
@@ -105,11 +91,14 @@ export async function cargoInstall(
10591
);
10692

10793
if (stderr) {
94+
// TODO: find better solution
10895
if (
10996
stderr.toLowerCase().includes("already exists") ||
110-
stderr.toLowerCase().includes("to your path")
97+
stderr.toLowerCase().includes("to your path") ||
98+
stderr.toLowerCase().includes("is already installed") ||
99+
stderr.toLowerCase().includes("yanked in registry")
111100
) {
112-
Logger.warn(
101+
Logger.debug(
113102
LoggerSource.rustUtil,
114103
`Cargo package '${packageName}' is already installed ` +
115104
"or cargo bin not in PATH:",
@@ -132,7 +121,9 @@ export async function cargoInstall(
132121
const msg = unknownErrorToString(error);
133122
if (
134123
msg.toLowerCase().includes("already exists") ||
135-
msg.toLowerCase().includes("to your path")
124+
msg.toLowerCase().includes("to your path") ||
125+
msg.toLowerCase().includes("is already installed") ||
126+
msg.toLowerCase().includes("yanked in registry")
136127
) {
137128
Logger.warn(
138129
LoggerSource.rustUtil,
@@ -314,7 +305,13 @@ export async function checkRustInstallation(): Promise<boolean> {
314305
}
315306
}
316307

317-
export async function installEmbeddedRust(): Promise<boolean> {
308+
/**
309+
* Installs all requirements for embedded Rust development.
310+
* (if required)
311+
*
312+
* @returns {boolean} True if all requirements are met or have been installed, false otherwise.
313+
*/
314+
export async function downloadAndInstallRust(): Promise<boolean> {
318315
/*try {
319316
const rustup = process.platform === "win32" ? "rustup.exe" : "rustup";
320317
const cargo = process.platform === "win32" ? "cargo.exe" : "cargo";
@@ -378,8 +375,8 @@ export async function installEmbeddedRust(): Promise<boolean> {
378375
const result = await cargoInstall(flipLink, false);
379376
if (!result) {
380377
void window.showErrorMessage(
381-
`Failed to install cargo package '${flipLink}'.`,
382-
"Please check the logs."
378+
`Failed to install cargo package '${flipLink}'.` +
379+
"Please check the logs."
383380
);
384381

385382
return false;
@@ -390,8 +387,8 @@ export async function installEmbeddedRust(): Promise<boolean> {
390387
const result2 = await cargoInstall(probeRsTools, true);
391388
if (!result2) {
392389
void window.showErrorMessage(
393-
`Failed to install cargo package '${probeRsTools}'.`,
394-
"Please check the logs."
390+
`Failed to install cargo package '${probeRsTools}'.` +
391+
"Please check the logs."
395392
);
396393

397394
return false;
@@ -402,8 +399,106 @@ export async function installEmbeddedRust(): Promise<boolean> {
402399
const result3 = await cargoInstall(elf2uf2Rs, true);
403400
if (!result3) {
404401
void window.showErrorMessage(
405-
`Failed to install cargo package '${elf2uf2Rs}'.`,
406-
"Please check the logs."
402+
`Failed to install cargo package '${elf2uf2Rs}'.` +
403+
"Please check the logs."
404+
);
405+
406+
return false;
407+
}
408+
409+
// install cargo-generate binary
410+
const result4 = await installCargoGenerate();
411+
if (!result4) {
412+
void window.showErrorMessage(
413+
"Failed to install cargo-generate. Please check the logs."
414+
);
415+
416+
return false;
417+
}
418+
419+
return true;
420+
}
421+
422+
function platformToGithubMatrix(platform: string): string {
423+
switch (platform) {
424+
case "darwin":
425+
return "macos-latest";
426+
case "linux":
427+
return "ubuntu-latest";
428+
case "win32":
429+
return "windows-latest";
430+
default:
431+
throw new Error(`Unsupported platform: ${platform}`);
432+
}
433+
}
434+
435+
function archToGithubMatrix(arch: string): string {
436+
switch (arch) {
437+
case "x64":
438+
return "x86_64";
439+
case "arm64":
440+
return "aarch64";
441+
default:
442+
throw new Error(`Unsupported architecture: ${arch}`);
443+
}
444+
}
445+
446+
async function installCargoGenerate(): Promise<boolean> {
447+
const release = await getRustToolsReleases();
448+
if (!release) {
449+
Logger.error(LoggerSource.rustUtil, "Failed to get Rust tools releases");
450+
451+
return false;
452+
}
453+
454+
const assetName = `cargo-generate-${platformToGithubMatrix(
455+
process.platform
456+
)}-${archToGithubMatrix(process.arch)}.zip`;
457+
458+
const tmpLoc = join(tmpdir(), "pico-vscode-rs");
459+
460+
const result = await downloadAndInstallGithubAsset(
461+
release[0],
462+
release[0],
463+
GithubRepository.rsTools,
464+
tmpLoc,
465+
"cargo-generate.zip",
466+
assetName,
467+
"cargo-generate"
468+
);
469+
470+
if (!result) {
471+
Logger.error(LoggerSource.rustUtil, "Failed to install cargo-generate");
472+
473+
return false;
474+
}
475+
476+
const cargoBin = join(homedir(), ".cargo", "bin");
477+
478+
try {
479+
mkdirSync(cargoBin, { recursive: true });
480+
renameSync(
481+
join(
482+
tmpLoc,
483+
"cargo-generate" + (process.platform === "win32" ? ".exe" : "")
484+
),
485+
join(
486+
cargoBin,
487+
"cargo-generate" + (process.platform === "win32" ? ".exe" : "")
488+
)
489+
);
490+
491+
if (process.platform !== "win32") {
492+
await execAsync(`chmod +x ${join(cargoBin, "cargo-generate")}`, {
493+
windowsHide: true,
494+
});
495+
}
496+
} catch (error) {
497+
Logger.error(
498+
LoggerSource.rustUtil,
499+
`Failed to move cargo-generate to ~/.cargo/bin: ${unknownErrorToString(
500+
error
501+
)}`
407502
);
408503

409504
return false;
@@ -412,4 +507,62 @@ export async function installEmbeddedRust(): Promise<boolean> {
412507
return true;
413508
}
414509

415-
export
510+
export async function generateRustProject(
511+
projectFolder: string,
512+
name: string,
513+
flashMethod: string
514+
): Promise<boolean> {
515+
try {
516+
const valuesFile = join(tmpdir(), "pico-vscode", "values.toml");
517+
await workspace.fs.createDirectory(Uri.file(dirname(valuesFile)));
518+
await workspace.fs.writeFile(
519+
Uri.file(valuesFile),
520+
// TODO: make selectable in UI
521+
Buffer.from(`[values]\nflash_method="${flashMethod}"\n`, "utf-8")
522+
);
523+
524+
// TODO: fix outside function (maybe)
525+
let projectRoot = projectFolder;
526+
if (projectFolder.endsWith(name)) {
527+
projectRoot = projectFolder.slice(0, projectFolder.length - name.length);
528+
}
529+
530+
// cache template and use --path
531+
const command =
532+
"cargo generate --git " +
533+
"https://github.com/rp-rs/rp2040-project-template " +
534+
` --name ${name} --values-file "${valuesFile}" ` +
535+
`--destination "${projectRoot}"`;
536+
537+
const customEnv = { ...process.env };
538+
customEnv["PATH"] += `${process.platform === "win32" ? ";" : ":"}${join(
539+
homedir(),
540+
".cargo",
541+
"bin"
542+
)}`;
543+
// TODO: add timeout
544+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
545+
const { stdout, stderr } = await execAsync(command, {
546+
windowsHide: true,
547+
env: customEnv,
548+
});
549+
550+
if (stderr) {
551+
Logger.error(
552+
LoggerSource.rustUtil,
553+
`Failed to generate Rust project: ${stderr}`
554+
);
555+
556+
return false;
557+
}
558+
} catch (error) {
559+
Logger.error(
560+
LoggerSource.rustUtil,
561+
`Failed to generate Rust project: ${unknownErrorToString(error)}`
562+
);
563+
564+
return false;
565+
}
566+
567+
return true;
568+
}

0 commit comments

Comments
 (0)