## Known Issues
-- If the extension installs Python it can't currently be used due to an issue with dynlib include paths (macOS only)
- Cannot run project generator 2 times in a row without restarting the extension
+- Custom Ninja, Python3 or git paths are not stored in CMakeLists.txt like SDK and Toolchain paths so using them would require to build and configure the project thought the extension
parser.add_argument("-tcVersion", "--toolchainVersion", help="ARM Embeded Toolchain version to use (required)")
parser.add_argument("-np", "--ninjaPath", help="Ninja path")
parser.add_argument("-cmp", "--cmakePath", help="CMake path")
+ parser.add_argument("-cupy", "--customPython", action='store_true', help="Custom python path used to execute the script.")
return parser.parse_args()
# Generates the requested project files, if any
-def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, sdkVersion, toolchainVersion, ninjaPath, cmakePath):
+def generateProjectFiles(projectPath, projectName, sdkPath, projects, debugger, sdkVersion, toolchainVersion, ninjaPath, cmakePath, customPython):
oldCWD = os.getcwd()
"cmake.cmakePath": "{cmakePath}",
"raspberry-pi-pico.cmakeAutoConfigure": true,
"raspberry-pi-pico.cmakePath": "{cmakePath.replace(user_home, "${HOME}") if use_home_var else cmakePath}",
- "raspberry-pi-pico.ninjaPath": "{ninjaPath.replace(user_home, "${HOME}") if use_home_var else ninjaPath}"
-}}
-'''
+ "raspberry-pi-pico.ninjaPath": "{ninjaPath.replace(user_home, "${HOME}") if use_home_var else ninjaPath}"'''
+
+ if customPython:
+ settings += f''',
+ "raspberry-pi-pico.python3Path": "{sys.executable.replace(user_home, "${HOME}") if use_home_var else sys.executable}"'''
+
+ settings += '\n}\n'
# extensions
extensions = f'''{{
params["sdkVersion"],
params["toolchainVersion"],
params["ninjaPath"],
- params["cmakePath"])
+ params["cmakePath"],
+ params["customPython"])
if params['wantBuild']:
if params['wantGUI'] and ENABLE_TK_GUI:
'toolchainVersion': args.toolchainVersion,
'ninjaPath' : args.ninjaPath,
'cmakePath' : args.cmakePath,
+ 'customPython' : args.customPython
}
DoEverything(None, params)
import { exec } from "child_process";
import { workspace, type Uri, window, ProgressLocation } from "vscode";
import { showRequirementsNotMetErrorMessage } from "./requirementsUtil.mjs";
-import { dirname, join, resolve } from "path";
+import { dirname, join } from "path";
import type Settings from "../settings.mjs";
import { HOME_VAR, SettingsKey } from "../settings.mjs";
import { readFileSync } from "fs";
"cmake",
{ nothrow: true }
);
+ console.log(
+ settings.getString(SettingsKey.python3Path)?.replace(HOME_VAR, homedir())
+ );
// TODO: maybe also check for "python" on unix systems
const pythonPath = await which(
settings
.getString(SettingsKey.python3Path)
- ?.replace(HOME_VAR, homedir()) || process.platform === "win32"
- ? "python"
- : "python3",
+ ?.replace(HOME_VAR, homedir()) ||
+ (process.platform === "win32" ? "python" : "python3"),
{ nothrow: true }
);
// TODO: maybe delete the build folder before running cmake so
// all configuration files in build get updates
const customEnv = process.env;
- customEnv["PYTHONHOME"] = pythonPath.includes("/")
+ /*customEnv["PYTHONHOME"] = pythonPath.includes("/")
? resolve(join(dirname(pythonPath), ".."))
- : "";
+ : "";*/
const isWindows = process.platform === "win32";
customEnv[isWindows ? "Path" : "PATH"] = `${
ninjaPath.includes("/") ? dirname(ninjaPath) : ""
const child = exec(
`${
process.platform === "win32" ? "&" : ""
- }"${cmake}" -DCMAKE_BUILD_TYPE=Debug ` +
- `-G Ninja -B ./build "${folder.fsPath}"`,
+ }"${cmake}" -DCMAKE_BUILD_TYPE=Debug ${
+ pythonPath.includes("/")
+ ? `-DPython3_EXECUTABLE="${pythonPath.replaceAll("\\", "/")}" `
+ : ""
+ }` + `-G Ninja -B ./build "${folder.fsPath}"`,
{
env: customEnv,
cwd: folder.fsPath,
redirectURL?: string
): Promise<string | undefined> {
if (
- process.platform === "linux" ||
+ // even tough this function supports downloading python3 on macOS arm64
+ // it doesn't work correctly therefore it's excluded here
+ // use pyenvInstallPython instead
+ process.platform !== "win32" ||
(process.platform === "win32" && process.arch !== "x64")
) {
Logger.log(
// Ensure the target directory exists
await mkdir(targetDirectory, { recursive: true });
- // select download url for platform()_arch()
- const downloadUrl =
- redirectURL ??
- (process.platform === "darwin"
- ? versionBundle.python.macos
- : versionBundle.python.windowsAmd64);
+ // select download url
+ const downloadUrl = versionBundle.python.windowsAmd64;
const tmpBasePath = join(tmpdir(), "pico-sdk");
await mkdir(tmpBasePath, { recursive: true });
const archiveFilePath = join(
tmpBasePath,
- `python-${versionBundle.python.version}.${
- process.platform === "darwin" ? "pkg" : "zip"
- }`
+ `python-${versionBundle.python.version}.zip`
);
return new Promise(resolve => {
// save the file to disk
const fileWriter = createWriteStream(archiveFilePath).on("finish", () => {
+ // doesn't work correctly therefore use pyenvInstallPython instead
+ // TODO: remove unused darwin code-path here
if (process.platform === "darwin") {
const pkgExtractor = new MacOSPythonPkgExtractor(
archiveFilePath,
if (success) {
try {
// create symlink, so the same path can be used as on Windows
- symlinkSync(
- joinPosix(
- settingsTargetDirectory,
- "/Versions/",
- versionBundle.python.version.substring(
- 0,
- versionBundle.python.version.lastIndexOf(".")
- ),
- "bin",
- "python3"
+ const srcPath = joinPosix(
+ settingsTargetDirectory,
+ "/Versions/",
+ versionBundle.python.version.substring(
+ 0,
+ versionBundle.python.version.lastIndexOf(".")
),
+ "bin",
+ "python3"
+ );
+ symlinkSync(
+ srcPath,
// use .exe as python is already used in the directory
join(settingsTargetDirectory, "python.exe"),
"file"
);
+ symlinkSync(
+ srcPath,
+ // use .exe as python is already used in the directory
+ join(settingsTargetDirectory, "python3.exe"),
+ "file"
+ );
} catch {
resolve(undefined);
}
try {
await execAsync(cloneCommand);
- Logger.log(`SDK ${branch} has been cloned and installed.`);
+ Logger.log(`SDK/Pyenv ${branch} has been cloned and installed.`);
return true;
} catch (error) {
- await unlink(targetDirectory);
+ try {
+ await unlink(targetDirectory);
+ } catch {
+ /* */
+ }
const err = error instanceof Error ? error.message : (error as string);
- Logger.log(`Error while cloning SDK: ${err}`);
+ if (err.includes("already exists")) {
+ return true;
+ }
+ Logger.log(`Error while cloning repository: ${err}`);
return false;
}
export const SDK_REPOSITORY_URL = "https://github.com/raspberrypi/pico-sdk.git";
export const NINJA_REPOSITORY_URL = "https://github.com/ninja-build/ninja.git";
export const CMAKE_REPOSITORY_URL = "https://github.com/Kitware/CMake.git";
+export const PYENV_REPOSITORY_URL = "https://github.com/pyenv/pyenv.git";
export async function getSDKReleases(): Promise<GithubRelease[]> {
const octokit = new Octokit();
--- /dev/null
+import { homedir } from "os";
+import { cloneRepository } from "./gitUtil.mjs";
+import { PYENV_REPOSITORY_URL } from "./githubREST.mjs";
+import { join as joinPosix } from "path/posix";
+import { exec } from "child_process";
+import { buildPython3Path } from "./download.mjs";
+import { HOME_VAR } from "../settings.mjs";
+import { existsSync, mkdirSync, symlinkSync } from "fs";
+import { join } from "path";
+
+export function buildPyEnvPath(): string {
+ // TODO: maybe replace . with _
+ return joinPosix(homedir().replaceAll("\\", "/"), ".pico-sdk", "pyenv");
+}
+
+/**
+ * Download pyenv and install it.
+ */
+export async function setupPyenv(): Promise<boolean> {
+ const targetDirectory = buildPyEnvPath();
+ const result = await cloneRepository(
+ PYENV_REPOSITORY_URL,
+ "master",
+ targetDirectory
+ );
+
+ if (!result) {
+ return false;
+ }
+
+ return true;
+}
+
+export async function pyenvInstallPython(
+ version: string
+): Promise<string | null> {
+ const targetDirectory = buildPyEnvPath();
+ const binDirectory = joinPosix(targetDirectory, "bin");
+ const command = `${binDirectory}/pyenv install ${version}`;
+
+ const customEnv = { ...process.env };
+ customEnv["PYENV_ROOT"] = targetDirectory;
+ customEnv[
+ process.platform === "win32" ? "Path" : "PATH"
+ ] = `${binDirectory};${customEnv["PATH"]}`;
+
+ const settingsTarget =
+ `${HOME_VAR}/.pico-sdk` + `/python/${version}/python.exe`;
+ const pythonVersionPath = buildPython3Path(version);
+
+ if (existsSync(pythonVersionPath)) {
+ return settingsTarget;
+ }
+
+ return new Promise(resolve => {
+ exec(
+ command,
+ { env: customEnv, cwd: binDirectory },
+ (error, stdout, stderr) => {
+ if (error) {
+ resolve(null);
+ }
+
+ const versionFolder = joinPosix(targetDirectory, "versions", version);
+ const pyBin = joinPosix(versionFolder, "bin");
+ mkdirSync(pythonVersionPath, { recursive: true });
+ symlinkSync(
+ joinPosix(pyBin, "python3"),
+ joinPosix(pythonVersionPath, "python.exe")
+ );
+ symlinkSync(
+ joinPosix(pyBin, "python3"),
+ joinPosix(pythonVersionPath, "python3exe")
+ );
+
+ resolve(settingsTarget);
+ }
+ );
+ });
+}
commands,
ColorThemeKind,
ProgressLocation,
+ Location,
} from "vscode";
import { type ExecOptions, exec } from "child_process";
import { HOME_VAR } from "../settings.mjs";
import which from "which";
import { homedir } from "os";
import { symlink } from "fs/promises";
+import { pyenvInstallPython, setupPyenv } from "../utils/pyenvUtil.mjs";
interface SubmitMessageValue {
projectName: string;
process.platform === "win32"
) {
switch (data.pythonMode) {
- case 0:
- python3Path = await downloadEmbedPython(
- this._versionBundle
+ case 0: {
+ const versionBundle = this._versionBundle;
+ await window.withProgress(
+ {
+ location: ProgressLocation.Notification,
+ title:
+ "Download and installing Python. This may take a while...",
+ cancellable: false,
+ },
+ async progress => {
+ if (process.platform === "win32") {
+ python3Path = await downloadEmbedPython(
+ versionBundle
+ );
+ } else if (process.platform === "darwin") {
+ const result1 = await setupPyenv();
+ if (!result1) {
+ progress.report({
+ increment: 100,
+ });
+
+ return;
+ }
+ const result = await pyenvInstallPython(
+ versionBundle.python.version
+ );
+
+ if (result !== null) {
+ python3Path = result;
+ }
+ } else {
+ this._logger.error(
+ "Automatic python installation is only supported on Windows and macOS."
+ );
+
+ await window.showErrorMessage(
+ "Automatic python installation is only supported on Windows and macOS."
+ );
+ }
+ progress.report({
+ increment: 100,
+ });
+ }
);
break;
+ }
case 1:
python3Path =
process.platform === "win32" ? "python" : "python3";
};
// add compiler to PATH
const isWindows = process.platform === "win32";
- customEnv["PYTHONHOME"] = pythonExe.includes("/")
+ /*customEnv["PYTHONHOME"] = pythonExe.includes("/")
? resolve(join(dirname(pythonExe), ".."))
- : "";
+ : "";*/
customEnv[isWindows ? "Path" : "PATH"] = `${join(
options.toolchainAndSDK.toolchainPath,
"bin"
options.ninjaExecutable.includes("/")
? `${isWindows ? ";" : ":"}${dirname(options.ninjaExecutable)}`
: ""
- }${
- pythonExe.includes("/")
- ? `${isWindows ? ";" : ":"}${dirname(pythonExe)}`
- : ""
}${isWindows ? ";" : ":"}${customEnv[isWindows ? "Path" : "PATH"]}`;
const command: string = [
`"${options.ninjaExecutable}"`,
"--cmakePath",
`"${options.cmakeExecutable}"`,
- `"${options.name}"`,
+ // set custom python executable path used flag if python executable is not in PATH
+ pythonExe.includes("/") ? `-cupy "${options.name}"` : `"${options.name}"`,
].join(" ");
this._logger.debug(`Executing project generator command: ${command}`);