Skip to content
This repository was archived by the owner on Oct 1, 2024. It is now read-only.

Autopopulate arduino.path with arduino-cli when a package manager is used #1444

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ The Arduino IDE can be installed the Arduino [download page](https://www.arduino
- *Note:* Arduino IDE `1.8.7` had some breaking changes, causing board package and library installation failures. These failures were corrected in `1.8.8` and later.

### Arduino CLI
The Arduino CLI can be downloaded from the repository's [release page](https://github.com/arduino/arduino-cli/releases/tag/0.13.0)
The Arduino CLI can be downloaded using one of the following methods:
* from the repository's [release page](https://github.com/arduino/arduino-cli/releases/tag/0.13.0) (Note: if you use this, you will have to set `arduino.path` since the CLI does not have a default path.)
* by using `brew install arduino-cli` if you use Homebrew (Mac and Linux)
* by using `choco install arduino-cli` if you use Chocolatey (Windows)
- The extension has only been tested with v0.13.0.
- If you use the CLI you will have to set `arduino.path` since the CLI does not have a default path.
- If you install the CLI without the use of a package manager,

## Installation
Open VS Code and press <kbd>F1</kbd> or <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> *or* <kbd>Cmd</kbd> + <kbd>Shift</kbd> + <kbd>P</kbd> to open command palette, select **Install Extension** and type `vscode-arduino`.
Expand Down
6,357 changes: 2,720 additions & 3,637 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@
"arduino.useArduinoCli": {
"type": "boolean",
"default": false,
"markdownDescription": "Use Arduino CLI installed instead of Arduino IDE. `#arduino.path#` must be set, as there is no default path for 'arduino-cli'. (Requires a restart after change)"
"markdownDescription": "Use Arduino CLI installed instead of Arduino IDE. If this was not installed using a package manager (such as `homebrew`), `#arduino.path#` must be set, as there is no default path for 'arduino-cli'. (Requires a restart after change)"
},
"arduino.path": {
"type": "string",
Expand Down
17 changes: 10 additions & 7 deletions src/arduino/arduinoSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
import * as os from "os";
import * as path from "path";
import * as WinReg from "winreg";
import { IHostPlatform } from "../common/i-host-platform";
import * as util from "../common/util";

import { resolveArduinoPath } from "../common/platform";

import { VscodeSettings } from "./vscodeSettings";

export interface IArduinoSettings {
Expand Down Expand Up @@ -43,14 +42,17 @@ export class ArduinoSettings implements IArduinoSettings {

private _defaultTimestampFormat: string;

public constructor() {
public constructor(private _platform: IHostPlatform) {
}

public async initialize() {
const platform = os.platform();
this._commandPath = VscodeSettings.getInstance().commandPath;
this._useArduinoCli = VscodeSettings.getInstance().useArduinoCli;
await this.tryResolveArduinoPath();
if(this._useArduinoCli && !this._commandPath) {
this._commandPath = 'arduino-cli';
}
await this.tryResolveArduinoPath(this._useArduinoCli);
await this.tryGetDefaultBaudRate();
await this.tryGetDefaultTimestampFormat();
if (platform === "win32") {
Expand Down Expand Up @@ -136,7 +138,8 @@ export class ArduinoSettings implements IArduinoSettings {
public get commandPath(): string {
const platform = os.platform();
if (platform === "darwin") {
return path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), path.normalize(this._commandPath));
let cmdPath = path.join(util.resolveMacArduinoAppPath(this._arduinoPath, this._useArduinoCli), path.normalize(this._commandPath));
return cmdPath;
} else {
return path.join(this._arduinoPath, path.normalize(this._commandPath));
}
Expand Down Expand Up @@ -220,14 +223,14 @@ export class ArduinoSettings implements IArduinoSettings {
}
}

private async tryResolveArduinoPath(): Promise<void> {
private async tryResolveArduinoPath(useArduinoCli = false): Promise<void> {
// Query arduino path sequentially from the following places such as "vscode user settings", "system environment variables",
// "usual software installation directory for each os".
// 1. Search vscode user settings first.
const configValue = VscodeSettings.getInstance().arduinoPath;
if (!configValue || !configValue.trim()) {
// 2 & 3. Resolve arduino path from system environment variables and usual software installation directory.
this._arduinoPath = await Promise.resolve(resolveArduinoPath());
this._arduinoPath = await Promise.resolve(this._platform.resolveArduinoPath(useArduinoCli));
} else {
this._arduinoPath = configValue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/arduino/exampleProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export class ExampleProvider implements vscode.TreeDataProvider<ExampleItem> {
this._exmaples = null;
this._exampleManager.loadExamples().then((examples) => {
this._exmaples = examples;
this._onDidChangeTreeData.fire();
this._onDidChangeTreeData.fire(null);
});
}

Expand Down
3 changes: 2 additions & 1 deletion src/arduinoActivator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ExampleProvider } from "./arduino/exampleProvider";
import { LibraryManager } from "./arduino/libraryManager";
import { ProgrammerManager } from "./arduino/programmerManager";
import ArduinoContext from "./arduinoContext";
import { hostPlatform } from "./common/platform";
import { DeviceContext } from "./deviceContext";

class ArduinoActivator {
Expand All @@ -22,7 +23,7 @@ class ArduinoActivator {
}

this._initializePromise = (async () => {
const arduinoSettings = new ArduinoSettings();
const arduinoSettings = new ArduinoSettings(hostPlatform());
await arduinoSettings.initialize();
const arduinoApp = new ArduinoApp(arduinoSettings);
await arduinoApp.initialize();
Expand Down
4 changes: 3 additions & 1 deletion src/arduinoContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { ArduinoApp } from "./arduino/arduino";
import { BoardManager } from "./arduino/boardManager";
import { hostPlatform } from "./common/platform";
import { DebuggerManager } from "./debug/debuggerManager";
import { DeviceContext } from "./deviceContext";

Expand Down Expand Up @@ -34,7 +35,8 @@ class ArduinoContext {
this._debuggerManager = new DebuggerManager(
DeviceContext.getInstance().extensionPath,
this.arduinoApp.settings,
this.boardManager);
this.boardManager,
hostPlatform());
this._debuggerManager.initialize();
}
return this._debuggerManager;
Expand Down
6 changes: 6 additions & 0 deletions src/common/i-host-platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IHostPlatform {
resolveArduinoPath(useArduinoCli?: boolean): string | Promise<string> | undefined;
validateArduinoPath(arduinoPath: string, useArduinoCli?: boolean);
findFile(fileName: string, cwd: string): string;
getExecutableFileName(fileName: string): string;
}
38 changes: 14 additions & 24 deletions src/common/platform.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from "path";
import { IHostPlatform } from "./i-host-platform";
import { DarwinPlatform } from "./sys/darwin";
import { LinuxPlatform } from "./sys/linux";
import { WindowsPlatform } from "./sys/win32";

export const isWindows = (process.platform === "win32");
export const isMacintosh = (process.platform === "darwin");
export const isLinux = (process.platform === "linux");

/*tslint:disable:no-var-requires*/
const internalSysLib = require(path.join(__dirname, `sys/${process.platform}`));

export function resolveArduinoPath(): string {
return internalSysLib.resolveArduinoPath();
}

export function validateArduinoPath(arduinoPath: string, useArduinoCli = false): boolean {
return internalSysLib.validateArduinoPath(arduinoPath, useArduinoCli);
}

export function findFile(fileName: string, cwd: string): string {
return internalSysLib.findFile(fileName, cwd);
}

export function getExecutableFileName(fileName: string): string {
if (isWindows) {
return `${fileName}.exe`;
export function hostPlatform(): IHostPlatform {
switch(process.platform) {
case 'win32':
return new WindowsPlatform();
case 'darwin':
return new DarwinPlatform();
case 'linux':
return new LinuxPlatform();
default:
return new LinuxPlatform();
}
return fileName;
}
68 changes: 45 additions & 23 deletions src/common/sys/darwin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,59 @@

import * as childProcess from "child_process";
import * as path from "path";
import { IHostPlatform } from "../i-host-platform";
import { directoryExistsSync, fileExistsSync, resolveMacArduinoAppPath } from "../util";

export function resolveArduinoPath(): string {
let result;
export class DarwinPlatform implements IHostPlatform {
public resolveArduinoPath(useArduinoCli?: boolean) {
let result;

const defaultCommonPaths = [path.join(process.env.HOME, "Applications"), "/Applications"];
for (const scanPath of defaultCommonPaths) {
if (directoryExistsSync(path.join(scanPath, "Arduino.app"))) {
result = scanPath;
break;
const appName = useArduinoCli ? 'arduino-cli' : 'Arduino.app'
const defaultCommonPaths = [path.join(process.env.HOME, "Applications"), "/Applications", '/opt/homebrew/bin'];
for (const scanPath of defaultCommonPaths) {
if (directoryExistsSync(path.join(scanPath, appName))) {
result = scanPath;
break;
}
}
if(!result) {
result = this.which(appName);
}
return result || "";
}
return result || "";
}

export function validateArduinoPath(arduinoPath: string, useArduinoCli = false): boolean {
return fileExistsSync(path.join(resolveMacArduinoAppPath(arduinoPath, useArduinoCli), useArduinoCli ? "arduino-cli" : "/Contents/MacOS/Arduino"));
}
public validateArduinoPath(arduinoPath: string, useArduinoCli?: boolean) {
let fileExists = fileExistsSync(path.join(resolveMacArduinoAppPath(arduinoPath, useArduinoCli), useArduinoCli
? "arduino-cli" :
"/Contents/MacOS/Arduino"));
return fileExists;
}

public findFile(fileName: string, cwd: string): string {
let pathString;
try {
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");

export function findFile(fileName: string, cwd: string): string {
let pathString;
try {
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");
if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
}
} catch (ex) {
// Ignore the errors.
}
return pathString;
}

public getExecutableFileName(fileName: string): string {
return fileName;
}

if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
private which(programName: string) {
let location = childProcess.execSync(`which ${programName}`, { encoding: "utf8" }).trim();
if(location === `${programName} not found`) {
return undefined;
}
} catch (ex) {
// Ignore the errors.
return location.substring(0, location.length - programName.length);
}
return pathString;
}
60 changes: 34 additions & 26 deletions src/common/sys/linux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,47 @@

import * as childProcess from "child_process";
import * as path from "path";
import { IHostPlatform } from "../i-host-platform";
import { fileExistsSync } from "../util";

export function resolveArduinoPath(): string {
let pathString;
try {
pathString = childProcess.execSync("readlink -f $(which arduino)", { encoding: "utf8" });
pathString = path.resolve(pathString).trim();
if (fileExistsSync(pathString)) {
pathString = path.dirname(path.resolve(pathString));
export class LinuxPlatform implements IHostPlatform {
public resolveArduinoPath(useArduinoCli?: boolean): string {
let pathString;
try {
const appName = useArduinoCli ? 'arduino-cli' : 'arduino'
pathString = childProcess.execSync(`readlink -f $(which ${appName})`, { encoding: "utf8" });
pathString = path.resolve(pathString).trim();
if (fileExistsSync(pathString)) {
pathString = path.dirname(path.resolve(pathString));
}
} catch (ex) {
// Ignore the errors.
}
} catch (ex) {
// Ignore the errors.

return pathString || "";
}

return pathString || "";
}

export function validateArduinoPath(arduinoPath: string, useArduinoCli = false): boolean {
return fileExistsSync(path.join(arduinoPath, useArduinoCli ? "arduino-cli" : "arduino"));
}
public validateArduinoPath(arduinoPath: string, useArduinoCli?: boolean) {
return fileExistsSync(path.join(arduinoPath, useArduinoCli ? "arduino-cli" : "arduino"));
}

export function findFile(fileName: string, cwd: string): string {
let pathString;
try {
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");
public findFile(fileName: string, cwd: string): string {
let pathString;
try {
pathString = childProcess.execSync(`find ${cwd} -name ${fileName} -type f`, { encoding: "utf8" }).split("\n");

if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
if (pathString && pathString[0] && fileExistsSync(pathString[0].trim())) {
pathString = path.normalize(pathString[0].trim());
} else {
pathString = null;
}
} catch (ex) {
// Ignore the errors.
}
} catch (ex) {
// Ignore the errors.
return pathString;
}

public getExecutableFileName(fileName: string): string {
return fileName;
}
return pathString;
}
Loading