1 import { join as joinPosix } from "path/posix";
2 import Logger, { LoggerSource } from "../logger.mjs";
3 import { existsSync, readFileSync, rmSync } from "fs";
4 import { homedir } from "os";
10 } from "./gitUtil.mjs";
11 import Settings from "../settings.mjs";
12 import { checkForGit } from "./requirementsUtil.mjs";
13 import { cp } from "fs/promises";
14 import { get } from "https";
19 } from "./downloadHelpers.mjs";
20 import { unknownErrorToString } from "./errorHelper.mjs";
22 const EXAMPLES_REPOSITORY_URL =
23 "https://github.com/raspberrypi/pico-examples.git";
24 const EXAMPLES_JSON_URL =
25 "https://raspberrypi.github.io/pico-vscode/" +
26 `${CURRENT_DATA_VERSION}/examples.json`;
27 const EXAMPLES_GITREF = "7fe60d6b4027771e45d97f207532c41b1d8c5418";
28 const EXAMPLES_TAG = "sdk-2.0.0";
30 export interface Example {
36 supportRiscV: boolean;
40 interface ExamplesFile {
47 supportRiscV: boolean;
51 function buildExamplesPath(): string {
52 return joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "examples");
55 function parseExamplesJson(data: string): Example[] {
57 const examples = JSON.parse(data.toString()) as ExamplesFile;
59 return Object.keys(examples).map(key => ({
60 path: examples[key].path,
61 name: examples[key].name,
62 libPaths: examples[key].libPaths,
63 libNames: examples[key].libNames,
64 boards: examples[key].boards,
65 supportRiscV: examples[key].supportRiscV,
70 LoggerSource.examples,
71 "Failed to parse examples.json.",
72 unknownErrorToString(error)
80 * Downloads the examples list from the internet or loads the included examples.json.
82 * @returns A promise that resolves with an array of Example objects.
84 export async function loadExamples(): Promise<Example[]> {
86 if (!(await isInternetConnected())) {
88 "Error while downloading examples list. " + "No internet connection"
91 const result = await new Promise<Example[]>((resolve, reject) => {
92 // Download the INI file
93 get(EXAMPLES_JSON_URL, response => {
94 if (response.statusCode !== 200) {
97 "Error while downloading examples list. " +
98 `Status code: ${response.statusCode}`
104 // Append data as it arrives
105 response.on("data", chunk => {
109 // Parse the INI data when the download is complete
110 response.on("end", () => {
111 // Resolve with the array of SupportedToolchainVersion
112 resolve(parseExamplesJson(data));
116 response.on("error", error => {
123 LoggerSource.examples,
124 "Successfully downloaded examples list from the internet."
130 LoggerSource.examples,
131 "Failed to download examples:",
132 unknownErrorToString(error)
136 const examplesFile = readFileSync(
137 joinPosix(getDataRoot(), "examples.json")
140 return parseExamplesJson(examplesFile.toString("utf-8"));
143 LoggerSource.examples,
144 "Failed to load included examples.json.",
145 unknownErrorToString(error)
153 export async function setupExample(
156 ): Promise<boolean> {
157 const examplesRepoPath = buildExamplesPath();
158 const absoluteExamplePath = joinPosix(examplesRepoPath, example.path);
160 const settings = Settings.getInstance();
161 if (settings === undefined) {
162 Logger.log("Error: Settings not initialized.");
167 // TODO: this does take about 2s - may be reduced
168 const requirementsCheck = await checkForGit(settings);
169 if (!requirementsCheck) {
173 const gitPath = await getGit(settings);
175 if (existsSync(joinPosix(examplesRepoPath, ".git"))) {
176 const ref = await execAsync(
178 process.env.ComSpec?.endsWith("cmd.exe") ? "/d " : " "
179 }"${examplesRepoPath}" && ${
180 process.env.ComSpec === "powershell.exe" ? "&" : ""
181 }"${gitPath}" rev-parse HEAD`
183 Logger.log(`Examples git ref is ${ref.stdout}\n`);
184 if (ref.stdout.trim() !== EXAMPLES_GITREF) {
185 Logger.log(`Removing old examples repo\n`);
186 rmSync(examplesRepoPath, { recursive: true, force: true });
190 if (!existsSync(examplesRepoPath)) {
191 const result = await sparseCloneRepository(
192 EXAMPLES_REPOSITORY_URL,
203 Logger.log(`Sparse-checkout selected example: ${example.name}`);
204 const result = await sparseCheckout(examplesRepoPath, example.path, gitPath);
209 for (const libPath of example.libPaths) {
210 Logger.log(`Sparse-checkout selected example required path: ${libPath}`);
211 const result = await sparseCheckout(examplesRepoPath, libPath, gitPath);
217 Logger.log(`Copying example from ${absoluteExamplePath} to ${targetPath}`);
218 // TODO: use example.name or example.search key for project folder name?
219 await cp(absoluteExamplePath, joinPosix(targetPath, example.searchKey), {
223 for (let i = 0; i < example.libPaths.length; i++) {
224 const libPath = example.libPaths[i];
225 const libName = example.libNames[i];
226 const absoluteLibPath = joinPosix(examplesRepoPath, libPath);
228 `Copying example required path from ${absoluteLibPath} ` +
229 `to ${targetPath}/${example.searchKey}/${libName}`
231 // TODO: use example.name or example.search key for project folder name?
234 joinPosix(targetPath, example.searchKey, libName),
239 Logger.log("Done copying example.");