Skip to content
This repository was archived by the owner on Dec 23, 2021. It is now read-only.

Commit 418bcd9

Browse files
authored
Install dependencies for users (#135)
PBI: 32822 Task: 32598 * install dependencies for users * Only show the dialog once if you click yes * Update docs * update develope docs * Add success notifications
1 parent e4e73f2 commit 418bcd9

File tree

9 files changed

+155
-44
lines changed

9 files changed

+155
-44
lines changed

docs/developers-setup.md

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,33 +14,22 @@
1414
(for example it could be found at : `c:\users\<...>\appdata\local\programs\python\python37\lib\site-packages\pip`)
1515
- Run in a console `python -m pip install --upgrade pip`
1616

17-
- Playsound
18-
19-
- Run the command in a console : `pip install playsound`
20-
21-
- pytest
22-
23-
- Run the command in a console : `pip install pytest`
24-
25-
- Pywin32
26-
27-
- Run the command in a console : `pip install pywin32`
28-
29-
- Python-Socketio
30-
31-
- Run the command in a console : `pip install python-socketio`
32-
33-
- Application Insights
34-
35-
- Run the command in a console : `pip install applicationinsights`
36-
37-
- Requests
38-
39-
- Run the command in a console : `pip install requests`
40-
41-
- Application Insights
42-
43-
- Run the command in a console : `pip install applicationinsights`
17+
- Python Modules
18+
19+
- **Note:** On extension activation you will be prompted asking if you want the modules to be automatically installed for you
20+
- Playsound
21+
- Run the command in a console : `pip install playsound`
22+
- pytest
23+
- Run the command in a console : `pip install pytest`
24+
- Pywin32
25+
- **Note:** This is only needed for Windows computers
26+
- Run the command in a console : `pip install pywin32`
27+
- Python-Socketio
28+
- Run the command in a console : `pip install python-socketio`
29+
- Requests
30+
- Run the command in a console : `pip install requests`
31+
- Application Insights
32+
- Run the command in a console : `pip install applicationinsights`
4433

4534
- VS Code
4635

docs/install.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ _Note: You need to install all the dependencies in order to use the extension._
2222
_(Note: the easiest way to do it might be when you install Python, you can select the "Add to PATH" option directly. Otherwise you can search how to insert it manually, but make sure that when you type `python` in a terminal, the command is recognized.)_
2323
- Python VS Code extension (downloaded from VS Code Marketplace)
2424
- **Note:** This extension is installed automatically from the marketplace when you install our extension
25-
- Playsound :
26-
- `python -m pip install --upgrade pip`
27-
- `pip install playsound`
28-
- Pywin32 : `pip install pywin32`
29-
- Python-Socketio : `pip install python-socketio`
30-
- Requests : `pip install requests`
31-
- Application Insights: `pip install applicationinsights`
25+
- Python Modules
26+
- **Note:** On extension activation you will be prompted asking if you want the modules to be automatically installed for you
27+
- Playsound : `pip install playsound`
28+
- Pywin32 :
29+
- **Note:** This is only needed for Windows computers
30+
- `pip install pywin32`
31+
- Python-Socketio : `pip install python-socketio`
32+
- Requests : `pip install requests`
33+
- Application Insights: `pip install applicationinsights`
3234

3335
## How to use the Extension
3436

gulpfile.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ gulp.task("clean", () => {
2929
);
3030
});
3131

32-
const pythonToMove = ["./src/adafruit_circuitplayground/*.*", "./src/*.py"];
32+
const pythonToMove = [
33+
"./src/adafruit_circuitplayground/*.*",
34+
"./src/*.py",
35+
"./src/requirements.txt"
36+
];
3337

3438
gulp.task("python-compile", () => {
3539
// the base option sets the relative root for the set of files,

locales/en/out/constants.i18n.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"dialogResponses.exampleCode": "Example Code on GitHub",
77
"dialogResponses.help": "I need help",
88
"dialogResponses.installPython": "Install from python.org",
9+
"dialogResponses.installNow": "Install Now",
10+
"dialogResponses.dontInstall": "Don't Install",
911
"dialogResponses.tutorials": "Tutorials on Adafruit",
1012
"error.debuggerServerInitFailed": "Warning : The Debugger Server cannot be opened. Please try to free the port {0} if it's already in use or select another one in your Settings 'Pacifica: Debugger Server Port' and start another debug session.\n You can still debug your code but you won't be able to use the Simulator.",
1113
"error.debuggingSessionInProgress": "[ERROR] A debugging session is currently in progress, please stop it before running your code. \n",
@@ -24,14 +26,16 @@
2426
"info.deploySuccess": "\n[INFO] Code successfully deployed\n",
2527
"info.extensionActivated": "Congratulations, your extension Adafruit_Simulator is now active!",
2628
"info.firstTimeWebview": "To reopen the simulator click on the \"Open Simulator\" button on the upper right corner of the text editor, or select the command \"Open Simulator\" from command palette.",
29+
"info.installPythonDependencies": "Do you want us to try and install this extensions dependencies for you?",
2730
"error.invalidFileExtensionDebug": "The file you tried to run isn\\'t a Python file.",
2831
"info.newFile": "New to Python or the Circuit Playground Express? We are here to help!",
2932
"info.redirect": "You are being redirected.",
3033
"info.runningCode": "Running user code",
3134
"info.privacyStatement": "Privacy Statement",
35+
"info.successfulInstall": "Successfully installed Python dependencies.",
3236
"info.thirdPartyWebsite": "By clicking \"Agree and Proceed\" you will be redirected to adafruit.com, a third party website not managed by Microsoft. Please note that your activity on adafruit.com is subject to Adafruit's privacy policy",
3337
"info.welcomeOutputTab": "Welcome to the Adafruit Simulator output tab !\n\n",
3438
"label.webviewPanel": "Adafruit CPX",
3539
"name": "Pacifica Simulator",
3640
"warning.agreeAndRun": "By selecting ‘Agree and Run’, you understand the extension executes Python code on your local computer, which may be a potential security risk."
37-
}
41+
}

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@
165165
"description": "%pacificaExtension.configuration.properties.device%",
166166
"scope": "resource"
167167
},
168+
"pacifica.showDependencyInstall": {
169+
"type": "boolean",
170+
"default": true,
171+
"scope": "resource"
172+
},
168173
"pacifica.debuggerServerPort": {
169174
"type": "number",
170175
"default": 5577,
@@ -330,4 +335,4 @@
330335
"extensionDependencies": [
331336
"ms-python.python"
332337
]
333-
}
338+
}

src/constants.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ const localize: nls.LocalizeFunc = nls.config({
1616
messageFormat: nls.MessageFormat.file
1717
})();
1818

19+
export const CONFIG = {
20+
SHOW_DEPENDENCY_INSTALL: "pacifica.showDependencyInstall"
21+
}
22+
1923
export const CONSTANTS = {
2024
DEBUG_CONFIGURATION_TYPE: "pacifica",
2125
DEPENDENCY_CHECKER: {
26+
PIP3: "pip3",
2227
PYTHON: "python",
2328
PYTHON3: "python3",
2429
PYTHON_LAUNCHER: "py -3"
@@ -89,6 +94,10 @@ export const CONSTANTS = {
8994
)
9095
},
9196
INFO: {
97+
ARE_YOU_SURE: localize(
98+
"info.areYouSure",
99+
"Are you sure you don't want to install the dependencies? The extension can't run without installing it"
100+
),
92101
CLOSED_SERIAL_PORT: (port: string) => {
93102
return localize(
94103
"info.closedSerialPort",
@@ -130,6 +139,10 @@ export const CONSTANTS = {
130139
"info.incorrectFileNameForSimulatorPopup",
131140
'We want your code to work on your actual board as well. Make sure you name your file "code.py" or "main.py" to be able to run your code on an actual physical device'
132141
),
142+
INSTALL_PYTHON_DEPENDENCIES: localize(
143+
"info.installPythonDependencies",
144+
"Do you want us to try and install this extensions dependencies for you?"
145+
),
133146
INVALID_FILE_NAME_DEBUG: localize(
134147
"info.invalidFileNameDebug",
135148
'The file you tried to debug isn\'t named "code.py" or "main.py". Rename your file if you want your code to work on your actual device.'
@@ -156,6 +169,7 @@ export const CONSTANTS = {
156169
),
157170
REDIRECT: localize("info.redirect", "You are being redirected."),
158171
RUNNING_CODE: localize("info.runningCode", "Running user code"),
172+
SUCCESSFUL_INSTALL: localize("info.successfulInstall", "Successfully installed Python dependencies."),
159173
THIRD_PARTY_WEBSITE: localize(
160174
"info.thirdPartyWebsite",
161175
'By clicking "Agree and Proceed" you will be redirected to adafruit.com, a third party website not managed by Microsoft. Please note that your activity on adafruit.com is subject to Adafruit\'s privacy policy'
@@ -298,6 +312,12 @@ export namespace DialogResponses {
298312
export const NO: MessageItem = {
299313
title: localize("dialogResponses.No", "No")
300314
};
315+
export const INSTALL_NOW: MessageItem = {
316+
title: localize("dialogResponses.installNow", "Install Now")
317+
};
318+
export const DONT_INSTALL: MessageItem = {
319+
title: localize("dialogResponses.dontInstall", "Don't Install")
320+
};
301321
export const PRIVACY_STATEMENT: MessageItem = {
302322
title: localize("info.privacyStatement", "Privacy Statement")
303323
};

src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export async function activate(context: vscode.ExtensionContext) {
6262
// Add our library path to settings.json for autocomplete functionality
6363
updatePythonExtraPaths();
6464

65+
await utils.checkPythonDependencies(context)
66+
6567
// Generate cpx.json
6668
utils.generateCPXConfig();
6769
pythonExecutableName = await utils.setPythonExectuableName();

src/extension_utils/dependencyChecker.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface IDependency {
1616
}
1717

1818
const PYTHON3_REGEX = RegExp("^(Python )(3\\.[0-9]+\\.[0-9]+)");
19+
const MINIMUM_PYTHON_VERSION = "3.5.0"
1920

2021
export class DependencyChecker {
2122
constructor() { }
@@ -27,26 +28,36 @@ export class DependencyChecker {
2728
const userOnWin: boolean = userOS.indexOf("win") === 0;
2829

2930
if (
30-
await this.runPythonVersionCommand(CONSTANTS.DEPENDENCY_CHECKER.PYTHON3)
31+
await this.runCommandVersion(CONSTANTS.DEPENDENCY_CHECKER.PYTHON3, MINIMUM_PYTHON_VERSION)
3132
) {
3233
state = true;
3334
dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON3;
3435
} else if (
35-
await this.runPythonVersionCommand(CONSTANTS.DEPENDENCY_CHECKER.PYTHON)
36+
await this.runCommandVersion(CONSTANTS.DEPENDENCY_CHECKER.PYTHON, MINIMUM_PYTHON_VERSION)
3637
) {
3738
state = true;
3839
dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON;
3940
} else if (
4041
userOnWin &&
41-
(await this.runPythonVersionCommand(
42-
CONSTANTS.DEPENDENCY_CHECKER.PYTHON_LAUNCHER
42+
(await this.runCommandVersion(
43+
CONSTANTS.DEPENDENCY_CHECKER.PYTHON_LAUNCHER,
44+
MINIMUM_PYTHON_VERSION
4345
))
4446
) {
4547
state = true;
4648
dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON;
4749
} else {
4850
state = false;
4951
}
52+
} else if (dependencyName === CONSTANTS.DEPENDENCY_CHECKER.PIP3) {
53+
if (
54+
await this.runCommandVersion(CONSTANTS.DEPENDENCY_CHECKER.PIP3)
55+
) {
56+
state = true;
57+
dependencyName = CONSTANTS.DEPENDENCY_CHECKER.PYTHON3;
58+
} else {
59+
state = false;
60+
}
5061
}
5162
return {
5263
payload: {
@@ -56,12 +67,16 @@ export class DependencyChecker {
5667
};
5768
}
5869

59-
private async runPythonVersionCommand(command: string) {
60-
let installed: boolean;
70+
private async runCommandVersion(command: string, versionDependency?: string) {
71+
let installed: boolean = false;
6172
try {
6273
const { stdout } = await exec(command + " --version");
6374
const matches = PYTHON3_REGEX.exec(stdout);
64-
installed = matches ? compareVersions(matches[2], "3.5.0") >= 0 : false;
75+
if (versionDependency) {
76+
installed = matches ? compareVersions(matches[2], versionDependency) >= 0 : false;
77+
} else {
78+
installed = true
79+
}
6580
} catch (err) {
6681
installed = false;
6782
}

src/extension_utils/utils.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import { DependencyChecker } from "./dependencyChecker";
77
import { DeviceContext } from "../deviceContext";
88
import * as vscode from "vscode";
99
import {
10+
CONFIG,
1011
CONSTANTS,
1112
CPX_CONFIG_FILE,
1213
DialogResponses,
1314
USER_CODE_NAMES,
1415
SERVER_INFO
1516
} from "../constants";
1617
import { CPXWorkspace } from "../cpxWorkspace";
18+
import * as cp from "child_process";
19+
import * as util from "util";
20+
const exec = util.promisify(cp.exec);
1721

1822
// tslint:disable-next-line: export-name
1923
export const getPathToScript = (
@@ -160,6 +164,14 @@ export const checkPythonDependency = async () => {
160164
return result.payload;
161165
};
162166

167+
export const checkPipDependency = async () => {
168+
const dependencyChecker: DependencyChecker = new DependencyChecker();
169+
const result = await dependencyChecker.checkDependency(
170+
CONSTANTS.DEPENDENCY_CHECKER.PIP3
171+
);
172+
return result.payload;
173+
}
174+
163175
export const setPythonExectuableName = async () => {
164176
// Find our what command is the PATH for python
165177
let executableName: string = "";
@@ -217,3 +229,61 @@ export const getServerPortConfig = (): number => {
217229
}
218230
return SERVER_INFO.DEFAULT_SERVER_PORT;
219231
};
232+
233+
export const checkConfig = (configName: string): boolean => {
234+
return vscode.workspace.getConfiguration().get(configName) === true;
235+
}
236+
237+
export const checkPythonDependencies = async (context: vscode.ExtensionContext) => {
238+
let hasInstalledDependencies: boolean = false;
239+
if (checkPipDependency() && checkPythonDependency()) {
240+
if (checkConfig(CONFIG.SHOW_DEPENDENCY_INSTALL)) {
241+
hasInstalledDependencies = await promptInstallPythonDependencies(context);
242+
if (hasInstalledDependencies) {
243+
await vscode.workspace.getConfiguration().update(CONFIG.SHOW_DEPENDENCY_INSTALL, false);
244+
}
245+
}
246+
} else {
247+
hasInstalledDependencies = false;
248+
}
249+
return hasInstalledDependencies;
250+
}
251+
252+
253+
export const promptInstallPythonDependencies = (context: vscode.ExtensionContext) => {
254+
return vscode.window.showInformationMessage(
255+
CONSTANTS.INFO.INSTALL_PYTHON_DEPENDENCIES,
256+
DialogResponses.YES,
257+
DialogResponses.NO)
258+
.then((selection: vscode.MessageItem | undefined) => {
259+
if (selection === DialogResponses.YES) {
260+
return installPythonDependencies(context);
261+
} else if (selection === DialogResponses.NO) {
262+
return vscode.window.showInformationMessage(
263+
CONSTANTS.INFO.ARE_YOU_SURE,
264+
DialogResponses.INSTALL_NOW,
265+
DialogResponses.DONT_INSTALL
266+
).then((installChoice: vscode.MessageItem | undefined) => {
267+
if (installChoice === DialogResponses.INSTALL_NOW) {
268+
return installPythonDependencies(context);
269+
} else {
270+
return false;
271+
}
272+
})
273+
}
274+
});
275+
}
276+
277+
export const installPythonDependencies = async (context: vscode.ExtensionContext) => {
278+
let installed: boolean = false;
279+
try {
280+
const requirementsPath: string = getPathToScript(context, "out", "requirements.txt");
281+
const { stdout } = await exec(`pip3 install -r ${requirementsPath}`);
282+
installed = true;
283+
vscode.window.showInformationMessage(CONSTANTS.INFO.SUCCESSFUL_INSTALL)
284+
} catch (err) {
285+
console.error(err);
286+
installed = false;
287+
}
288+
return installed
289+
}

0 commit comments

Comments
 (0)