From 7532f6c6dc3fa6a8b4994e1cf5a2f8c75568fd96 Mon Sep 17 00:00:00 2001 From: "Brian A. Ignacio" Date: Mon, 4 Jan 2021 19:08:32 +0800 Subject: [PATCH] Simplify onboarding (VSC-426) (#159) * initial auto window * initial setup window * add tools setup methods * complete one click install * open py esp-idf folders * add prerequisites validation * add codicons and custom page * add initial custom setup page * idf download setup * add esp-idf cancel logic * improve custom ui alignment * idfToolManager simplify check tools cancel dload * progress tools cancel py env install * cancel pkg extract save custom settings * fix manual path issues * test on win 1 * rm duplicate python manual path * 1 click install status window * update status page * check esp-idf container and path on install page * finished status rm custom * status ui update * fix esp-idf url links * check default values env vars * install py reqs command * install py req cmd in schema * fix error message on esp-idf python * enhance download progress ui * Add status step background * improve start ui and errors * improve tools ui status page * add custom setup ui * check settings on extension activate * rm old onboarding * show setup when missing pre requisites * rm test lines * rm testing on checkExistingSetttings * improve setup css align and colors * fix select erroMsg css rm home btn statusPage * reject execChildProcess only on Error * fix childProcess cancelToken * fix icons on vsix * add resolve url loader fix font codicon * update yarn lock * resolve url loader * fix python list linux * add font face to setup webview * update codicon icons ensure idfContainer dir * fix path sep add def idfPath * empty default manual idf path * log download error * initialize versions currentSetupValid * set percentage on idf and tools installed status * update setup docs * refactor setup panel highlight tools folder * update doc paths * rm timeout test setup spaces * fix python path with spaces * add fallback on env idf_path idf_tools_path Co-authored-by: Brian Ignacio --- .github/workflows/codeql-analysis.yml | 58 +- README.md | 20 +- docs/COVERAGE.md | 2 +- docs/ONBOARDING.md | 104 --- docs/SETUP.md | 90 +++ i18n/en/package.i18n.json | 4 +- i18n/es/package.i18n.json | 4 +- i18n/zh-CN/package.i18n.json | 4 +- package.json | 23 +- package.nls.json | 4 +- schema.i18n.json | 21 +- src/{onboarding => }/PyReqLog.ts | 0 src/checkExtensionSettings.ts | 77 +++ src/common/abstractCloning.ts | 60 +- src/downloadManager.ts | 124 ++-- src/examples/ExamplesPanel.ts | 4 +- src/extension.ts | 98 ++- src/idfConfiguration.ts | 6 +- src/idfToolsManager.ts | 255 ++++--- src/installManager.ts | 643 +++++++++--------- src/onboarding/OnboardingPanel.ts | 528 -------------- src/onboarding/espIdfDownload.ts | 356 ---------- src/onboarding/onboardingInit.ts | 61 -- src/onboarding/pythonReqsManager.ts | 186 ----- src/onboarding/toolsInstall.ts | 146 ---- src/onboarding/updateViewMethods.ts | 57 -- src/pythonManager.ts | 531 ++++++++------- src/setup/SetupPanel.ts | 487 +++++++++++++ src/setup/espIdfDownload.ts | 119 ++++ src/setup/espIdfDownloadStep.ts | 102 +++ src/setup/espIdfVersionList.ts | 139 ++++ src/setup/installPyReqs.ts | 105 +++ src/setup/pyReqsInstallStep.ts | 59 ++ src/setup/setupInit.ts | 256 +++++++ src/setup/toolInstall.ts | 66 ++ src/setup/toolsDownloadStep.ts | 64 ++ src/setup/webviewMsgMethods.ts | 86 +++ src/utils.ts | 45 +- src/views/commons/espCommons.scss | 5 +- src/views/onboarding/App.vue | 107 --- src/views/onboarding/Download.vue | 125 ---- src/views/onboarding/GitPyCheck.vue | 118 ---- src/views/onboarding/Home.vue | 80 --- src/views/onboarding/ToolsSetup.vue | 98 --- .../components/ConfigurationTarget.vue | 101 --- .../onboarding/components/IDFDownload.vue | 94 --- src/views/onboarding/components/IDFManual.vue | 109 --- src/views/onboarding/components/ToolCheck.vue | 47 -- .../onboarding/components/ToolDownload.vue | 88 --- .../components/ToolsDownloadStep.vue | 93 --- .../onboarding/components/ToolsManual.vue | 130 ---- .../onboarding/components/ToolsSkipStep.vue | 89 --- src/views/onboarding/main.ts | 239 ------- src/views/onboarding/store/actions.ts | 103 --- src/views/onboarding/store/index.ts | 21 - src/views/onboarding/store/mutations.ts | 286 -------- src/views/onboarding/store/onboarding.ts | 61 -- src/views/onboarding/store/types.ts | 77 --- src/views/setup/App.vue | 122 ++++ src/views/setup/Home.vue | 207 ++++++ src/views/setup/Install.vue | 93 +++ src/views/setup/Status.vue | 297 ++++++++ src/views/setup/ToolsCustom.vue | 130 ++++ src/views/setup/components/IdfDownload.vue | 34 + src/views/setup/components/folderOpen.vue | 66 ++ src/views/setup/components/selectEspIdf.vue | 121 ++++ .../setup/components/selectPyVersion.vue | 95 +++ src/views/setup/components/toolDownload.vue | 95 +++ src/views/setup/components/toolManual.vue | 67 ++ src/views/setup/main.ts | 242 +++++++ src/views/setup/store/index.ts | 392 +++++++++++ .../setup/types.ts} | 53 +- src/vue.shim.d.ts | 8 +- webpack.config.js | 8 +- yarn.lock | 8 +- 75 files changed, 4690 insertions(+), 4413 deletions(-) delete mode 100644 docs/ONBOARDING.md create mode 100644 docs/SETUP.md rename src/{onboarding => }/PyReqLog.ts (100%) create mode 100644 src/checkExtensionSettings.ts delete mode 100644 src/onboarding/OnboardingPanel.ts delete mode 100644 src/onboarding/espIdfDownload.ts delete mode 100644 src/onboarding/onboardingInit.ts delete mode 100644 src/onboarding/pythonReqsManager.ts delete mode 100644 src/onboarding/toolsInstall.ts delete mode 100644 src/onboarding/updateViewMethods.ts create mode 100644 src/setup/SetupPanel.ts create mode 100644 src/setup/espIdfDownload.ts create mode 100644 src/setup/espIdfDownloadStep.ts create mode 100644 src/setup/espIdfVersionList.ts create mode 100644 src/setup/installPyReqs.ts create mode 100644 src/setup/pyReqsInstallStep.ts create mode 100644 src/setup/setupInit.ts create mode 100644 src/setup/toolInstall.ts create mode 100644 src/setup/toolsDownloadStep.ts create mode 100644 src/setup/webviewMsgMethods.ts delete mode 100644 src/views/onboarding/App.vue delete mode 100644 src/views/onboarding/Download.vue delete mode 100644 src/views/onboarding/GitPyCheck.vue delete mode 100644 src/views/onboarding/Home.vue delete mode 100644 src/views/onboarding/ToolsSetup.vue delete mode 100644 src/views/onboarding/components/ConfigurationTarget.vue delete mode 100644 src/views/onboarding/components/IDFDownload.vue delete mode 100644 src/views/onboarding/components/IDFManual.vue delete mode 100644 src/views/onboarding/components/ToolCheck.vue delete mode 100644 src/views/onboarding/components/ToolDownload.vue delete mode 100644 src/views/onboarding/components/ToolsDownloadStep.vue delete mode 100644 src/views/onboarding/components/ToolsManual.vue delete mode 100644 src/views/onboarding/components/ToolsSkipStep.vue delete mode 100644 src/views/onboarding/main.ts delete mode 100644 src/views/onboarding/store/actions.ts delete mode 100644 src/views/onboarding/store/index.ts delete mode 100644 src/views/onboarding/store/mutations.ts delete mode 100644 src/views/onboarding/store/onboarding.ts delete mode 100644 src/views/onboarding/store/types.ts create mode 100644 src/views/setup/App.vue create mode 100644 src/views/setup/Home.vue create mode 100644 src/views/setup/Install.vue create mode 100644 src/views/setup/Status.vue create mode 100644 src/views/setup/ToolsCustom.vue create mode 100644 src/views/setup/components/IdfDownload.vue create mode 100644 src/views/setup/components/folderOpen.vue create mode 100644 src/views/setup/components/selectEspIdf.vue create mode 100644 src/views/setup/components/selectPyVersion.vue create mode 100644 src/views/setup/components/toolDownload.vue create mode 100644 src/views/setup/components/toolManual.vue create mode 100644 src/views/setup/main.ts create mode 100644 src/views/setup/store/index.ts rename src/{onboarding/createOnboardingHtml.ts => views/setup/types.ts} (51%) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index dd3f12575..e990b45e6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [master] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - - cron: '16 7 * * 6' + - cron: "16 7 * * 6" jobs: analyze: @@ -28,40 +28,40 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript', 'python' ] + language: ["javascript", "python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # ℹ️ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/README.md b/README.md index f7b26e1f5..31b7ee433 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,7 @@ espressif logo -ESP-IDF VS Code Extension -=== +# ESP-IDF VS Code Extension Develop and debug applications for Espressif [ESP32](https://espressif.com/en/products/hardware/esp32), [ESP32-S2](https://www.espressif.com/en/products/socs/esp32-s2) chips with [ESP-IDF](https://github.com/espressif/esp-idf) IoT Development Framework. @@ -15,7 +14,7 @@ Develop and debug applications for Espressif [ESP32](https://espressif.com/en/pr The ESP-IDF extension makes it easy to develop, build, flash, monitor and debug your ESP-IDF code, some functionality includes: -- Quick [Configure ESP-IDF extension](https://github.com/espressif/vscode-esp-idf-extension/blob/master/docs/ONBOARDING.md) for first time user to help you download, install and setup ESP-IDF and required tools within Visual Studio Code extension. +- Quick [Configure ESP-IDF extension](./docs/SETUP.md) for first time user to help you download, install and setup ESP-IDF and required tools within Visual Studio Code extension. - Quick prototyping by copying ESP-IDF examples with **ESP-IDF: Show ESP-IDF Examples Projects**. - App tracing when using ESP-IDF Application Level Tracing Library like in [ESP-IDF Application Level Tracing Example](https://github.com/espressif/esp-idf/tree/master/examples/system/app_trace_to_host). - Size analysis of binaries with **ESP-IDF: Size analysis of the binaries**. @@ -29,15 +28,14 @@ The ESP-IDF extension makes it easy to develop, build, flash, monitor and debug ## Prerequisites -There are a few dependencies which needs to be downloaded and installed before you can continue to use the extension. All the other dependencies like ESP-IDF or toolchain will be taken care by the [onboarding](./docs/ONBOARDING.md) process. +There are a few dependencies which needs to be downloaded and installed before you can continue to use the extension. All the other dependencies like ESP-IDF or toolchain will be taken care by the [setup wizard](./docs/SETUP.md) process. - [Python 3.5](https://www.python.org/download/releases/3.5/)+ - [Git](https://git-scm.com/downloads) -- [CMake](https://cmake.org/download) and [Ninja](https://github.com/ninja-build/ninja/releases) for **Linux or MacOS users**. For Windows users, it is part of the onboarding configuration tools intall. +- [CMake](https://cmake.org/download) and [Ninja](https://github.com/ninja-build/ninja/releases) for **Linux or MacOS users**. For Windows users, it is part of the extension setup wizard. > Please note that this extension **only [supports](https://github.com/espressif/esp-idf/blob/master/SUPPORT_POLICY.md)** the release versions of ESP-IDF, you can still use the extension on `master` branch or some other branch, but certain feature might not work fully. - ## Quick Installation Guide There are several ways to install this extension to your VSCode, easiest one is from VSCode Marketplace. However if you are looking to contribute to this project we suggest you to have install in [Source mode](#Build-from-Source-Code). @@ -65,7 +63,7 @@ To install from `.vsix` file, first head to [releases page](https://github.com/e #### Build vsix locally -- Build the Visual Studio Code extension setup with `yarn package` +- Build the Visual Studio Code extension setup with `yarn package`. ## Uninstalling the plugin @@ -80,7 +78,7 @@ To install from `.vsix` file, first head to [releases page](https://github.com/e - Then - Either open Visual Studio Code and create a workspace folder. - Run `code ${YOUR_PROJECT_DIR}` from the command line. -- Press F1 and type **ESP-IDF: Configure ESP-IDF extension** to configure the extension Please take a look at [ONBOARDING](./docs/ONBOARDING.md) for more detail about extension configuration. +- Press F1 and type **ESP-IDF: Configure ESP-IDF extension** to configure the extension Please take a look at [SETUP](./docs/SETUP.md) for more detail about extension configuration. - Press F1 and type **ESP-IDF: Create ESP-IDF project** to generate a template ESP-IDF project. @@ -131,12 +129,12 @@ The **Show Examples Projects** command allows you create a new project using one ## ESP-IDF Configure extension -Initial configuration is done easily by executing **ESP-IDF: Configure ESP-IDF extension** Please take a look at [ONBOARDING](./docs/ONBOARDING.md) for more in-depth detail. This window is always shown when extension is activated which you can disable with `idf.showOnboardingOnInit`. +Initial configuration is done easily by executing **ESP-IDF: Configure ESP-IDF extension** Please take a look at [SETUP](./docs/SETUP.md) for more in-depth detail. This windows helps you setup key Visual Studio Code configurations for this extension to perform included features correctly. This is how the extension uses them: 1. `idf.pythonBinPath` is used to executed python scripts within the extension. In **ESP-IDF: Configure ESP-IDF extension** we first select a system-wide python executable from which to create a python virtual environment and we save the executable from this virtual environment in `idf.pythonBinPath`. All required python packages by ESP-IDF are installed in this virtual environment, if using **ESP-IDF: Configure ESP-IDF extension** -2. `idf.customExtraPaths` is pre-appended to your system environment variable PATH within Visual Studio Code **(not modifying your system environment)** before executing any of our extension commands such as `openocd` or `cmake` (i.e. build your current project) else extension commands will try to use what is already in your system PATH. In **ESP-IDF: Configure ESP-IDF extension** you can download ESP-IDF Tools or skip IDF Tools download and manually enter all required ESP-IDF Tools as explain in [ONBOARDING](./docs/ONBOARDING.md) which will be saved in `idf.customExtraPaths`. +2. `idf.customExtraPaths` is pre-appended to your system environment variable PATH within Visual Studio Code **(not modifying your system environment)** before executing any of our extension commands such as `openocd` or `cmake` (i.e. build your current project) else extension commands will try to use what is already in your system PATH. In **ESP-IDF: Configure ESP-IDF extension** you can download ESP-IDF Tools or skip IDF Tools download and manually enter all required ESP-IDF Tools as explain in [SETUP](./docs/SETUP.md) which will be saved in `idf.customExtraPaths`. 3. `idf.customExtraVars` stores any custom environment variable we use such as OPENOCD_SCRIPTS, which is the openOCD scripts directory used in openocd server startup. We add these variables to visual studio code process environment variables, choosing the extension variable if available, else extension commands will try to use what is already in your system PATH. **This doesn't modify your system environment outside Visual Studio Code.** 4. `idf.adapterTargetName` is used to select the chipset (esp32, esp32 s2, etc.) on which to run our extension commands. 5. `idf.openOcdConfigs` is used to store an array of openOCD scripts directory relative path config files to use with OpenOCD server. (Example: ["interface/ftdi/esp32_devkitj_v1.cfg", "board/esp32-wrover.cfg"]). @@ -279,7 +277,7 @@ We provide editor code coverage highlight and HTML reports for ESP-IDF projects, ## Debugging -Click F5 to start debugging. For correct debug experience, first `build`, `flash` your device and define the correct `idf.customExtraPaths` paths and `idf.customExtraVars` using [ONBOARDING](./docs/ONBOARDING.md). +Click F5 to start debugging. For correct debug experience, first `build`, `flash` your device and define the correct `idf.customExtraPaths` paths and `idf.customExtraVars` using [SETUP](./docs/SETUP.md). When you start debug, an OpenOCD process starts in the background. OpenOCD Output log window is created in Visual Studio Code lower panel. To configure your project's launch.json to debug, please review [DEBUGGING](./docs/DEBUGGING.md). diff --git a/docs/COVERAGE.md b/docs/COVERAGE.md index d4f70a32f..ec0f6107b 100644 --- a/docs/COVERAGE.md +++ b/docs/COVERAGE.md @@ -5,7 +5,7 @@ Your ESP-IDF project should be configured to generate gcda/gcno coverage files using gcov. Please take a look at the [ESP-IDF gcov example](https://github.com/espressif/esp-idf/tree/master/examples/system/gcov) to see how to set up your project. This extension also requires `gcovr` to generate JSON and HTML reports from generated files. This is installed as part of this extension ESP-IDF Debug Adapter Python requirements when following the **ESP-IDF: Configure ESP-IDF extension** command. -Please take a look at [ONBOARDING](./docs/ONBOARDING.md) for more information. +Please take a look at [SETUP](./docs/SETUP.md) for more information. Make sure you had properly configure xtensa toolchain in `idf.customExtraPaths` or in your environment variable PATH since the gcov executable used is `xtensa-esp32-elf-gcov` and `gcovr` exists in the same directory as your `${idf.pythonBinPath}` path. diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md deleted file mode 100644 index 25f6e5fa0..000000000 --- a/docs/ONBOARDING.md +++ /dev/null @@ -1,104 +0,0 @@ -# Onboarding ESP-IDF Extension - -When you start ESP-IDF extension, the onboarding window can help you set up ESP-IDF, ESP-IDF Tools and their Python requirements to easily start working on your projects. - -## Prerequisites - -Install [ESP-IDF Prerequisites](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html#step-1-install-prerequisites) for your operating system. - -For this extension you also need [Python 3.5+](https://www.python.org/download) and [Git](https://www.python.org/downloads). If you are on MacOS and Linux, you also need to preinstall [CMake](https://cmake.org/download) and [Ninja-build](https://github.com/ninja-build/ninja/releases) (and add them to `idf.customExtraPaths` in Step 4). For Windows users, CMake and Ninja are downloaded in step 3 Tools Downloads. - -## Installed ESP-IDF following the Espressif documentation and want to reuse it in the extension. - -If you installed ESP-IDF and ESP-IDF Tools following [ESP-IDF Get Started](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) then you can configure the extension following these steps: - -### Directly modify settings.json - -1. In Visual Studio Code press F1 and type "Preferences: Open Settings (JSON)". This will open your user global settings for Visual Studio Code. You could choose to modify your workspace settings.json if you want these settings for a specific workspace or within a specific project .vscode/settings.json. -2. Your settings.json should look like: - -``` -{ - ..., - "idf.espIdfPath": "path/to/esp-idf", - "idf.customExtraPaths": "UPDATED_PATH", - "idf.customExtraVars": "{\"OPENOCD_SCRIPTS\":\"OPENOCD_FOLDER/share/openocd/scripts\"}", - "idf.pythonBinPath": "PYTHON_INTERPRETER", - "idf.port": "DEVICE_PORT", - "idf.openOcdConfigs": [ - "interface/ftdi/esp32_devkitj_v1.cfg", - "board/esp32-wrover.cfg" - ], -} -``` - -where UPDATED_PATH is the "Updated PATH variable" generated by `$IDF_PATH/export.sh`, PYTHON_INTERPRETER is the "Using Python interpreter in" generated by `$IDF_PATH/export.sh`, the device port is your device serial port (COM1, /dev/cu.usbserial-1433401, etc) and idf.openOcdConfigs are the config files used for OpenOCD for your device (they are relative to OPENOCD_SCRIPTS directory). - -Make sure to install the extension and extension debug adapter Python requirements by running the following commands in your terminal: - -``` -PYTHON_INTERPRETER -m pip install -r EXTENSION_PATH/requirements.txt -``` - -``` -PYTHON_INTERPRETER -m pip install -r EXTENSION_PATH/esp_debug_adapter/requirements.txt -``` - -where EXTENSION_PATH is `%USERPROFILE%\.vscode\extensions\espressif.esp-idf-extension-VERSION` on Windows and `$HOME/.vscode/extensions/espressif.esp-idf-extension-VERSION` on Linux/MacOS. - -### Using Preferences: Open Settings (UI) - -This is the same as previous sections but the name of each configuration setting is the description given in the [Readme ESP-IDF Settings section](./../README.md). - -### Using ESP-IDF: Configure ESP-IDF extension window - -Follow the next section steps without downloading the ESP-IDF Tools. On Step 3, Skip Tools download and manually set them. First set PYTHON_INTERPRETER in the first input, UPDATED_PATH in the second input (make sure CMake and Ninja-build directories are part of these paths) and the correct value for OPENOCD_SCRIPTS (replace \${TOOL_PATH} with OpenOCD directory) as well as other variables shown. - -## How to configure this extension from zero using ESP-IDF: Configure ESP-IDF extension window - -1. Press **START** on the onboarding window and check that git is installed. Also select the Python executable to use in this extension. The python executable is persisted for the current onboarding session to create a python virtual environment in step 3, sub-item 1. - -2. Go to **Configure ESP-IDF** to download ESP-IDF or select an existing ESP-IDF folder in your system. In this window you can select the version you wish to download and the path to install it or you can select ESP-IDF directory, which will be validated. This path is saved as `idf.espIdfPath` which is used to replace system environment variable `IDF_PATH` (inside Visual Studio Code only) before running extension commands. - -3. Go to **Configure ESP-IDF Tools** will allow you to download required ESP-IDF Tools or manually set them. - - 1. If you are downloading them, choose the directory where to install them (Default directory is **\$HOME/.espressif** for Linux/MacOS users or **%USER_PROFILE%\.espressif** for Windows users) which will saved as `idf.toolsPath`. After installing the ESP-IDF Tools, a python virtual environment will be created for ESP-IDF in **\${`idf.toolsPath`}/python_env** and the virtualenv python full path executable will be saved in `idf.pythonBinPath` or `idf.pythonBinPathWin` for Windows users. **This python executable will be used to execute all python dependent commands in this ESP-IDF extension**. After you download the ESP-IDF Tools, their bin folder directories will be set in `idf.customExtraPaths` and their required environment variables in `idf.customExtraVars` (such as OPENOCD_SCRIPTS). - -4. The next step is to verify each installed tool version is correct for the `idf.espIdfPath` ESP-IDF. If you skipped ESP-IDF tools download, you need to manually set each tool bin directory in `idf.customExtraPaths` by typing all required tools executable location separated by ; (Windows users) or : (Linux/MacOS users), the virtual environment python executable absolute path, which is saved in `idf.pythonBinPath` and is used to test all ESP-IDF requirements.txt are fulfilled as well; and the `idf.customExtraVars` with correct values for environment variables such as OPENOCD_SCRIPTS, which is used to find the OpenOCD configuration files specified in `idf.openOcdConfigs`. - -**DO NOT USE ~, \$HOME OR %USERPROFILE%, ENVIRONMENT VARIABLES ARE NOT RESOLVED IN THIS CONFIGURATION SETTINGS.**. You could use `${env:HOME}` to refer to \$HOME (Linux/MacOS) or %HOME% (Windows). - -An example python path for `idf.pythonBinPath` is _/home/myUser/.espressif/python_env/idf4.0_py3.5_env/bin/python_ for MacOS/Linux users or _C:\Users\myUser\.espressif\python_env\idf4.0_py3.5_env\Scripts\python.exe_ for Windows users. - -For example if - -- OpenOCD executable path is _/home/myUser/.espressif/tools/openocd/version/openocd-esp32/bin/openocd_ or _C:\Users\myUser\.espressif\tools\openocd\version\openocd-esp32\bin\openocd_ (Windows) -- XtensaEsp32 executable path is _/home/myUser/.espressif/tools/xtensa-esp32/version/xtensa-esp32/bin/xtensa-esp32-gcc_ or _C:\Users\myUser\.espressif\tools\xtensa\version\xtensa-esp32\bin/xtensa-esp32-gcc_ (Windows) - -you need to set in `idf.customExtraPaths`: - -- _/home/myUser/\.espressif\tools\openocd\version\openocd-esp32\bin:/home/myUser//.espressif/tools/xtensa-esp32/version/xtensa-esp32/bin_ (Linux/MacOS users) or -- _C:\Users\myUser\.espressif\tools\openocd\version\openocd-esp32\bin;C:\Users\myUser\.espressif\tools\xtensa-esp32\version\xtensa-esp32\bin_ (Windows users). - -`idf.customExtraVars` is an object saved in Visual Studio Code's settings.json that looks like this (**Make sure to replace \${TOOL_PATH} with the tool path**): - -``` -{ - ..., - OPENOCD_SCRIPTS: "...", - OTHER_ENV_VAR: "...", - ... -} -``` - -The list of required `idf.customExtraVars` will be shown in the current step (Verify ESP-IDF Tools) and they should be filled for the extension to work properly. - -5. After verifying ESP-IDF tools and required python packages, the extension has been configured correctly. An option to open **ESP-IDF: Show examples** is shown. - -Here is diagram to configure ESP-IDF extension: - -![Onboarding](../media/onboarding.png) - -## Notes - -If you try to download ESP-IDF in a directory where a folder esp-idf already exists an error will be shown. Please delete this esp-idf folder or choose another destination directory. diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 000000000..5c94078fe --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,90 @@ +# ESPRESSIF IDF extension for Visual Studio Code + +When you start ESP-IDF extension, it will try to self-configure by looking for existing ESP-IDF directory in `IDF_PATH` environment variable, `$HOME/esp/esp-idf` on MacOS/Linux and `%USERPROFILE%\esp\esp-idf` or `%USERPROFILE%\Desktop\esp-idf` in Windows. It will look for ESP-IDF Tools and ESP-IDF Python virtual environment in `IDF_TOOLS_PATH` environment variable, `$HOME\.espressif` on MacOS/Linux and `%USERPROFILE%\.espressif` on Windows. + +If ESP-IDF and required ESP-IDF tools are found, these paths will be saved as Visual Studio Code Configuration settings, which are located in `press F1 -> type Preferences: Open Settings (UI)` and `press F1 -> type Preferences: Open Settings (JSON)`. These settings, as described in [README ESP-IDF Specific Settings](../README.md), are `idf.espIdfPath` for IDF_PATH, `idf.customExtraPaths` for ESP-IDF Tools paths to be appended to environment variable PATH, `idf.pythonBinPath` for absolute virtual environment python path and `idf.customExtraVars` for additional environment variables from ESP-IDF tools such as OPENOCD_SCRIPTS. + +If ESP-IDF and tools are not downloaded yet, you can use the [Setup Wizard](#Setup-Wizard) to download them and configure the extension for you or manually configure the extension as explained in [JSON Manual Configuration](#JSON-Manual-Configuration) or [Settings UI Manual Configuration](#UI-Manual-Configuration). + +## Setup Wizard + +In Visual Studio Code press **F1** and type **ESP-IDF: Configure ESP-IDF extension**. + +Setup wizard provides 3 choices: + +- Express install: Fastest option. Choose to either download selected ESP-IDF version or find ESP-IDF in your system, install ESP-IDF Tools and create python virtual environment with required packages on the path specified in `idf.toolsPath` on MacOS/Linux or `idf.toolsPathWin` in Windows, which has a default path value of in `$HOME\.espressif` on MacOS/Linux and `%USERPROFILE%\.espressif` on Windows, respectively. +- Advanced install: Configurable option. Choose to either download selected ESP-IDF version or find ESP-IDF in your system, install ESP-IDF Tools and create python virtual environment with required packages in the chosen location or manually specify each required ESP-IDF tool path. +- Use existing setup: If ESP-IDF is found in `idf.espIdfPath` (or `$HOME/esp/esp-idf` on MacOS/Linux and `%USERPROFILE%\esp\esp-idf` or `%USERPROFILE%\Desktop\esp-idf` in Windows) and ESP-IDF tools in `idf.toolsPath` (or `$HOME\.espressif` on MacOS/Linux and `%USERPROFILE%\.espressif` in Windows) then this option is shown to use an existing setup, install extension and extension debug adapter python requirements. + +After choosing any of the previous options, a status page is displayed showing ESP-IDF, tools and python environment install status. When the setup is finished, a message is shown that "All settings have been configured. You can close this window." + +## JSON Manual Configuration + +1. In Visual Studio Code press **F1** and type **Preferences: Open Settings (JSON)**. This will open your user global settings for Visual Studio Code. You could choose to modify your workspace settings.json if you want these settings for a workspace-specific or folder-specific `.vscode/settings.json`. +2. Your settings.json should look like: + +``` +{ + ..., + "idf.espIdfPath": "path/to/esp-idf", + "idf.customExtraPaths": "UPDATED_PATH", + "idf.customExtraVars": "{\"OPENOCD_SCRIPTS\":\"OPENOCD_FOLDER/share/openocd/scripts\"}", + "idf.pythonBinPath": "PYTHON_INTERPRETER", + "idf.openOcdConfigs": [ + "interface/ftdi/esp32_devkitj_v1.cfg", + "board/esp32-wrover.cfg" + ], + "idf.port": "DEVICE_PORT" +} +``` + +where **UPDATED_PATH** is the "Updated PATH variable" generated by `$IDF_PATH/export.sh`, **PYTHON_INTERPRETER** is the "Using Python interpreter in" value generated by `$IDF_PATH/export.sh`, the DEVICE_PORT is your device serial port (COM1, /dev/cu.usbserial-1433401 or /dev/ttyUSB1 etc) and idf.openOcdConfigs are the config files used for OpenOCD for your device (they are relative to OPENOCD_SCRIPTS directory). + +**DO NOT USE ~, $HOME OR %USERPROFILE%, ENVIRONMENT VARIABLES ARE NOT RESOLVED IN THIS CONFIGURATION SETTINGS. You must use ${env:HOME} instead of \$HOME (Linux/MacOS) or %HOME% (Windows).** + +Make sure to install the extension and extension debug adapter Python requirements by running the following commands in your terminal: + +``` +PYTHON_INTERPRETER -m pip install -r EXTENSION_PATH/requirements.txt +``` + +``` +PYTHON_INTERPRETER -m pip install -r EXTENSION_PATH/esp_debug_adapter/requirements.txt +``` + +where EXTENSION_PATH is `%USERPROFILE%\.vscode\extensions\espressif.esp-idf-extension-VERSION` on Windows and `$HOME/.vscode/extensions/espressif.esp-idf-extension-VERSION` on Linux/MacOS. + +### Example configuration setting values + +An example python path for `idf.pythonBinPath` is `/home/myUser/.espressif/python_env/idf4.0_py3.5_env/bin/python` for MacOS/Linux users or `C:\Users\myUser\.espressif\python_env\idf4.0_py3.5_env\Scripts\python.exe` for Windows users. + +For example if required ESP-IDF Tools are: + +- OpenOCD executable path is `/home/myUser/.espressif/tools/openocd-esp32/version/openocd-esp32/bin/openocd` or `C:\Users\myUser\.espressif\tools\openocd-esp32\version\openocd-esp32\bin\openocd` (Windows) +- XtensaEsp32 executable path is `/home/myUser/.espressif/tools/xtensa-esp32/version/xtensa-esp32/bin/xtensa-esp32-gcc` or `C:\Users\myUser\.espressif\tools\xtensa\version\xtensa-esp32\bin\xtensa-esp32-gcc` (Windows) + +you need to set in `idf.customExtraPaths`: + +- Linux/MacOS users + +``` +/home/myUser/.espressif/tools/openocd/version/openocd-esp32/bin:/home/myUser/.espressif/tools/xtensa-esp32/version/xtensa-esp32/bin +``` + +- Windows users + +``` +C:\Users\myUser\.espressif\tools\openocd-esp32\version\openocd-esp32\bin;C:\Users\myUser\.espressif\tools\xtensa-esp32\version\xtensa-esp32\bin +``` + +`idf.customExtraVars` is an JSON object saved in Visual Studio Code's settings.json (**Make sure to replace \${TOOL_PATH} with the tool path**): + +``` +"idf.customExtraVars": "{\"OPENOCD_SCRIPTS\":\"/home/myUser/.espressif/tools/openocd-esp32/version/openocd-esp32/share/openocd/scripts\"}" +``` + +The list of required `idf.customExtraVars` can be found in `$IDF_PATH/tools/tools.json` (replace **\${TOOL_PATH}** with tool location) and they should be filled for the extension to work properly. + +## UI Manual Configuration + +This is the same as previous sections but the name of each configuration setting is the description given in the [Readme ESP-IDF Settings](./../README.md). You also need to install requirements.txt as shown in the previous section. diff --git a/i18n/en/package.i18n.json b/i18n/en/package.i18n.json index 9f986cfae..c193a1f6e 100644 --- a/i18n/en/package.i18n.json +++ b/i18n/en/package.i18n.json @@ -14,7 +14,7 @@ "espIdf.buildDevice.title": "ESP-IDF: Build your project", "espIdf.flashDevice.title": "ESP-IDF: Flash (UART) your project", "espIdf.monitorDevice.title": "ESP-IDF: Monitor your device", - "espIdf.onboarding.title": "ESP-IDF: Configure ESP-IDF extension", + "espIdf.setup.title": "ESP-IDF: Configure ESP-IDF extension", "espIdf.examples.title": "ESP-IDF: Show Examples Projects", "espIdf.buildFlashMonitor.title": "ESP-IDF: Build, Flash and start a monitor on your device", "espIdf.pickAWorkspaceFolder.title": "ESP-IDF: Pick a workspace folder", @@ -28,6 +28,7 @@ "espIdf.searchInEspIdfDocs.title": "ESP-IDF: Search in documentation...", "espIdf.getEspAdf.title": "ESP-ADF: Install ESP-ADF", "espIdf.getEspMdf.title": "ESP-IDF: Install ESP-MDF", + "espIdf.installPyReqs.title": "ESP-IDF: Install ESP-IDF Python Packages", "espIdf.openDocUrl.title": "ESP-IDF: Open ESP-IDF Documentation...", "espIdf.clearDocsSearchResult.title": "ESP-IDF: Clear ESP-IDF Search results", "espIdf.fullClean.title": "ESP-IDF: Full clean project", @@ -46,7 +47,6 @@ "param.exportPaths": "Paths to be appended to PATH", "param.exportVars": "Variables to be added to system environment variables", "param.useIDFKConfigStyle": "Enable/Disable ESP-IDF style validation for Kconfig files", - "param.showOnboardingOnInit": "Show ESP-IDF Configuration window", "param.coveredLightTheme": "Background color for covered lines in Light theme for ESP-IDF Coverage.", "param.coveredDarkTheme": "Background color for covered lines in Dark theme for ESP-IDF Coverage.", "param.partialLightTheme": "Background color for partially covered lines in Light theme for ESP-IDF Coverage.", diff --git a/i18n/es/package.i18n.json b/i18n/es/package.i18n.json index b64c851dd..2b7340951 100644 --- a/i18n/es/package.i18n.json +++ b/i18n/es/package.i18n.json @@ -14,7 +14,7 @@ "espIdf.buildDevice.title": "ESP-IDF: Construir el proyecto", "espIdf.flashDevice.title": "ESP-IDF: Programar (UART) el dispositivo", "espIdf.monitorDevice.title": "ESP-IDF: Monitorear el dispositivo", - "espIdf.onboarding.title": "ESP-IDF: Configurar la extension ESP-IDF", + "espIdf.setup.title": "ESP-IDF: Configurar la extension ESP-IDF", "espIdf.examples.title": "ESP-IDF: Mostrar ejemplos de proyectos", "espIdf.buildFlashMonitor.title": "ESP-IDF: Construir, Programar y empezar monitor de dispositivo", "espIdf.pickAWorkspaceFolder.title": "ESP-IDF: Elige una carpeta de trabajo", @@ -28,6 +28,7 @@ "espIdf.searchInEspIdfDocs.title": "ESP-IDF: Buscar en la documentación...", "espIdf.getEspAdf.title": "ESP-ADF: Instalar ESP-ADF", "espIdf.getEspMdf.title": "ESP-IDF: Instalar ESP-MDF", + "espIdf.installPyReqs.title": "ESP-IDF: Instalar paquetes Python para ESP-IDF", "espIdf.openDocUrl.title": "ESP-IDF: Abrir documentación ESP-IDF...", "espIdf.clearDocsSearchResult.title": "ESP-IDF: Limpiar resultados de busqueda", "espIdf.fullClean.title": "ESP-IDF: Limpiar el proyecto", @@ -46,7 +47,6 @@ "param.espAdfPath": "Ruta del entorno de trabajo ESP-ADF (ADF_PATH)", "param.espMdfPath": "Ruta del entorno de trabajo ESP-MDF (MDF_PATH)", "param.useIDFKConfigStyle": "Habilitar/Deshabilitar validación de estilo ESP-IDF para archivos Kconfig", - "param.showOnboardingOnInit": "Mostrar ventana de configuración ESP-IDF", "param.coveredLightTheme": "Color de fondo para lineas cubiertas en temas claros por ESP-IDF Coverage.", "param.coveredDarkTheme": "Color de fondo para lineas cubiertas en temas oscuros por ESP-IDF Coverage.", "param.partialLightTheme": "Color de fondo para lineas parcialmente cubiertas en temas claros por ESP-IDF Coverage.", diff --git a/i18n/zh-CN/package.i18n.json b/i18n/zh-CN/package.i18n.json index 2c6f62d92..c6cded352 100644 --- a/i18n/zh-CN/package.i18n.json +++ b/i18n/zh-CN/package.i18n.json @@ -14,7 +14,7 @@ "espIdf.buildDevice.title": "ESP-IDF:构建项目", "espIdf.flashDevice.title": "ESP-IDF:烧写项目 (UART)", "espIdf.monitorDevice.title": "ESP-IDF:监视设备输出", - "espIdf.onboarding.title": "ESP-IDF:配置 ESP-IDF 插件", + "espIdf.setup.title": "ESP-IDF:配置 ESP-IDF 插件", "espIdf.examples.title": "ESP-IDF:展示示例项目", "espIdf.buildFlashMonitor.title": "ESP-IDF:构建、烧写项目并启动监视器", "espIdf.pickAWorkspaceFolder.title": "ESP-IDF:选择工作文件夹", @@ -28,6 +28,7 @@ "espIdf.getCoverageReport.title": "ESP-IDF: 获取项目的HTML覆盖率报告", "espIdf.getEspAdf.title": "ESP-ADF: 安装ESP-ADF", "espIdf.getEspMdf.title": "ESP-ADF: 安装ESP-MDF", + "espIdf.installPyReqs.title": "ESP-IDF: 安装ESP-IDF Python包", "espIdf.openDocUrl.title": "ESP-IDF: 打开ESP-IDF文档...", "espIdf.clearDocsSearchResult.title": "ESP-IDF: 清除ESP-IDF搜索结果", "espIdf.fullClean.title": "ESP-IDF: 全清洁工程", @@ -46,7 +47,6 @@ "param.espAdfPath": "ADF_PATH 的路径", "param.espMdfPath": "MDF_PATH 的路径", "param.useIDFKConfigStyle": "启用/禁用 ESP-IDF Kconfig 文件的样式验证", - "param.showOnboardingOnInit": "显示 ESP-IDF 配置窗口", "param.coveredLightTheme": "ESP-IDF覆盖的光主题覆盖线的背景色", "param.coveredDarkTheme": "用于ESP-IDF覆盖的深色主题覆盖线的背景色", "param.partialLightTheme": "ESP-IDF覆盖的光主题部分覆盖线的背景色.", diff --git a/package.json b/package.json index d0b74f1a8..fb1ad61f9 100644 --- a/package.json +++ b/package.json @@ -69,12 +69,13 @@ "onCommand:espIdf.genCoverage", "onCommand:espIdf.removeCoverage", "onCommand:espIdf.searchInEspIdfDocs", - "onCommand:onboarding.start", + "onCommand:espIdf.setup.start", "onCommand:examples.start", "onCommand:esp.rainmaker.backend.connect", "onCommand:esp.rainmaker.backend.sync", "onCommand:espIdf.getEspAdf", "onCommand:espIdf.getEspMdf", + "onCommand:espIdf.installPyReqs", "onCommand:esp.webview.open.partition-table", "onCommand:espIdf.webview.nvsPartitionEditor", "onView:idfAppTracer", @@ -328,7 +329,7 @@ }, "idf.toolsPath": { "type": "string", - "default": "${env:HOME}/.espressif", + "default": "${env:IDF_TOOLS_PATH}", "description": "%param.toolsPath%", "scope": "resource" }, @@ -352,7 +353,7 @@ }, "idf.toolsPathWin": { "type": "string", - "default": "${env:USERPROFILE}\\.espressif", + "default": "${env:IDF_TOOLS_PATH}", "description": "%param.toolsPath%", "scope": "resource" }, @@ -426,12 +427,6 @@ "scope": "resource", "description": "%param.useIDFKConfigStyle%" }, - "idf.showOnboardingOnInit": { - "type": "boolean", - "default": true, - "scope": "resource", - "description": "%param.showOnboardingOnInit%" - }, "trace.poll_period": { "type": "string", "default": "1", @@ -602,8 +597,8 @@ "title": "%menuconfig.start.title%" }, { - "command": "onboarding.start", - "title": "%espIdf.onboarding.title%" + "command": "espIdf.setup.start", + "title": "%espIdf.setup.title%" }, { "command": "examples.start", @@ -753,6 +748,10 @@ "command": "espIdf.getEspMdf", "title": "%espIdf.getEspMdf.title%" }, + { + "command": "espIdf.installPyReqs", + "title": "%espIdf.installPyReqs.title%" + }, { "command": "esp.webview.open.partition-table", "title": "%esp.webview.open.partition-table.title%", @@ -983,7 +982,7 @@ "axios": "^0.19.2", "del": "^4.1.1", "es6-promisify": "^6.0.0", - "follow-redirects": "^1.6.1", + "follow-redirects": "^1.13.0", "fs-extra": "^8.1.0", "https-proxy-agent": "^3.0.0", "interactjs": "^1.9.18", diff --git a/package.nls.json b/package.nls.json index bb9626d1e..e0e781c69 100644 --- a/package.nls.json +++ b/package.nls.json @@ -14,7 +14,7 @@ "espIdf.buildDevice.title": "ESP-IDF: Build your project", "espIdf.flashDevice.title": "ESP-IDF: Flash (UART) your project", "espIdf.monitorDevice.title": "ESP-IDF: Monitor your device", - "espIdf.onboarding.title": "ESP-IDF: Configure ESP-IDF extension", + "espIdf.setup.title": "ESP-IDF: Configure ESP-IDF extension", "espIdf.examples.title": "ESP-IDF: Show Examples Projects", "espIdf.buildFlashMonitor.title": "ESP-IDF: Build, Flash and start a monitor on your device", "espIdf.pickAWorkspaceFolder.title": "ESP-IDF: Pick a workspace folder", @@ -28,6 +28,7 @@ "espIdf.searchInEspIdfDocs.title": "ESP-IDF: Search in documentation...", "espIdf.getEspAdf.title": "ESP-IDF: Install ESP-ADF", "espIdf.getEspMdf.title": "ESP-IDF: Install ESP-MDF", + "espIdf.installPyReqs.title": "ESP-IDF: Install ESP-IDF Python Packages", "espIdf.openDocUrl.title": "ESP-IDF: Open ESP-IDF Documentation...", "espIdf.clearDocsSearchResult.title": "ESP-IDF: Clear ESP-IDF Search results", "espIdf.fullClean.title": "ESP-IDF: Full clean project", @@ -46,7 +47,6 @@ "param.exportPaths": "Paths to be appended to PATH", "param.exportVars": "Variables to be added to system environment variables", "param.useIDFKConfigStyle": "Enable/Disable ESP-IDF style validation for Kconfig files", - "param.showOnboardingOnInit": "Show ESP-IDF Configuration window", "param.coveredLightTheme": "Background color for covered lines in Light theme for ESP-IDF Coverage.", "param.coveredDarkTheme": "Background color for covered lines in Dark theme for ESP-IDF Coverage.", "param.partialLightTheme": "Background color for partially covered lines in Light theme for ESP-IDF Coverage.", diff --git a/schema.i18n.json b/schema.i18n.json index be2591d31..6fabb9a1d 100644 --- a/schema.i18n.json +++ b/schema.i18n.json @@ -61,20 +61,11 @@ "idfComponentsDataProvider": [ "idfComponentDataProvider.proj_desc_not_found" ], - "idfConfiguration": [ - "idfConfiguration.hasBeenUpdated" - ], - "utils": [ - "utils.currentFolder", - "utils.openComponentTitle" - ] + "idfConfiguration": ["idfConfiguration.hasBeenUpdated"], + "utils": ["utils.currentFolder", "utils.openComponentTitle"] }, "views": { - "menuconfig": [ - "save", - "discard", - "reset" - ] + "menuconfig": ["save", "discard", "reset"] }, "package": [ "espIdf.createFiles.title", @@ -91,7 +82,7 @@ "espIdf.buildDevice.title", "espIdf.flashDevice.title", "espIdf.monitorDevice.title", - "espIdf.onboarding.title", + "espIdf.setup.title", "espIdf.examples.title", "espIdf.buildFlashMonitor.title", "espIdf.pickAWorkspaceFolder.title", @@ -107,6 +98,7 @@ "espIdf.getCoverageReport.title", "espIdf.getEspAdf.title", "espIdf.getEspMdf.title", + "espIdf.installPyReqs.title", "espIdf.openDocUrl.title", "espIdf.clearDocsSearchResult.title", "debug.initConfig.name", @@ -123,7 +115,6 @@ "param.exportPaths", "param.exportVars", "param.useIDFKConfigStyle", - "param.showOnboardingOnInit", "param.coveredLightTheme", "param.coveredDarkTheme", "param.partialLightTheme", @@ -163,4 +154,4 @@ "esp_idf.debuggers.text.description", "esp_idf.gdbinitFile.description" ] -} \ No newline at end of file +} diff --git a/src/onboarding/PyReqLog.ts b/src/PyReqLog.ts similarity index 100% rename from src/onboarding/PyReqLog.ts rename to src/PyReqLog.ts diff --git a/src/checkExtensionSettings.ts b/src/checkExtensionSettings.ts new file mode 100644 index 000000000..2fc661ca5 --- /dev/null +++ b/src/checkExtensionSettings.ts @@ -0,0 +1,77 @@ +/* + * Project: ESP-IDF VSCode Extension + * File Created: Sunday, 10th May 2020 11:33:22 pm + * Copyright 2020 Espressif Systems (Shanghai) CO LTD + *  + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *  + * http://www.apache.org/licenses/LICENSE-2.0 + *  + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import * as vscode from "vscode"; +import { + getSetupInitialValues, + isCurrentInstallValid, + saveSettings, +} from "./setup/setupInit"; +import { Logger } from "./logger/logger"; +import { OutputChannel } from "./logger/outputChannel"; +import { installExtensionPyReqs } from "./pythonManager"; + +export async function checkExtensionSettings(extensionPath: string) { + const isExtensionConfigured = await isCurrentInstallValid(); + if (isExtensionConfigured) { + return; + } + await vscode.window.withProgress( + { + cancellable: false, + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF: Loading initial configuration...", + }, + async ( + progress: vscode.Progress<{ message: string; increment: number }>, + cancelToken: vscode.CancellationToken + ) => { + try { + const setupArgs = await getSetupInitialValues(extensionPath, progress); + if ( + setupArgs.espIdfPath && + setupArgs.espToolsPath && + setupArgs.pyBinPath && + setupArgs.exportedPaths && + setupArgs.exportedVars + ) { + if (!setupArgs.hasPrerequisites) { + vscode.commands.executeCommand("espIdf.setup.start", setupArgs); + } + await installExtensionPyReqs( + setupArgs.pyBinPath, + setupArgs.espToolsPath, + undefined, + OutputChannel.init(), + cancelToken + ); + await saveSettings( + setupArgs.espIdfPath, + setupArgs.pyBinPath, + setupArgs.exportedPaths, + setupArgs.exportedVars + ); + } else if (typeof process.env.WEB_IDE === "undefined") { + vscode.commands.executeCommand("espIdf.setup.start", setupArgs); + } + } catch (error) { + Logger.errorNotify(error.message, error); + vscode.commands.executeCommand("espIdf.setup.start"); + } + } + ); +} diff --git a/src/common/abstractCloning.ts b/src/common/abstractCloning.ts index 68f4a1c8d..d05cf4e74 100644 --- a/src/common/abstractCloning.ts +++ b/src/common/abstractCloning.ts @@ -19,6 +19,7 @@ import { checkGitExists, dirExistPromise } from "../utils"; import { OutputChannel } from "../logger/outputChannel"; import { CancellationToken, Progress, ProgressLocation, window } from "vscode"; import * as idfConf from "../idfConfiguration"; +import { PackageProgress } from "../PackageProgress"; export class AbstractCloning { private cloneProcess: ChildProcess; @@ -41,8 +42,9 @@ export class AbstractCloning { } public downloadByCloning( - progress: Progress<{ message?: string; increment?: number }>, - installDir: string + installDir: string, + pkgProgress?: PackageProgress, + progress?: Progress<{ message?: string; increment?: number }> ) { return new Promise((resolve, reject) => { this.cloneProcess = spawn( @@ -66,14 +68,26 @@ export class AbstractCloning { } const progressRegex = /(\d+)(\.\d+)?%/g; const matches = data.toString().match(progressRegex); - if (progress && matches) { - progress.report({ - message: `Downloading ${matches[matches.length - 1]}`, - }); + if (matches) { + let progressMsg = `Downloading ${matches[matches.length - 1]}`; + if (progress) { + progress.report({ + message: progressMsg, + }); + } + if (pkgProgress) { + pkgProgress.Progress = matches[matches.length - 1]; + } } else if (data.toString().indexOf("Cloning into") !== -1) { - progress.report({ - message: `${data.toString()}`, - }); + let detailMsg = " " + data.toString(); + if (progress) { + progress.report({ + message: `${data.toString()}`, + }); + } + if (pkgProgress) { + pkgProgress.Progress = detailMsg; + } } }); @@ -81,14 +95,26 @@ export class AbstractCloning { OutputChannel.appendLine(data.toString()); const progressRegex = /(\d+)(\.\d+)?%/g; const matches = data.toString().match(progressRegex); - if (progress && matches) { - progress.report({ - message: `Downloading ${matches[matches.length - 1]}`, - }); + if (matches) { + let progressMsg = `Downloading ${matches[matches.length - 1]}`; + if (progress) { + progress.report({ + message: progressMsg, + }); + } + if (pkgProgress) { + pkgProgress.Progress = matches[matches.length - 1]; + } } else if (data.toString().indexOf("Cloning into") !== -1) { - progress.report({ - message: ` ${data.toString()}`, - }); + let detailMsg = " " + data.toString(); + if (progress) { + progress.report({ + message: `${data.toString()}`, + }); + } + if (pkgProgress) { + pkgProgress.Progress = detailMsg; + } } }); @@ -162,7 +188,7 @@ export class AbstractCloning { cancelToken.onCancellationRequested((e) => { this.cancel(); }); - await this.downloadByCloning(progress, installDirPath); + await this.downloadByCloning(installDirPath, undefined, progress); const target = idfConf.readParameter("idf.saveScope"); await idfConf.writeParameter(configurationId, resultingPath, target); Logger.infoNotify(`${this.name} has been installed`); diff --git a/src/downloadManager.ts b/src/downloadManager.ts index 92dd6251d..6d5244f21 100644 --- a/src/downloadManager.ts +++ b/src/downloadManager.ts @@ -42,7 +42,8 @@ export class DownloadManager { public downloadPackages( idfToolsManager: IdfToolsManager, progress: vscode.Progress<{ message?: string; increment?: number }>, - pkgsProgress?: PackageProgress[] + pkgsProgress?: PackageProgress[], + cancelToken?: vscode.CancellationToken ): Promise { return idfToolsManager.getPackageList().then((packages) => { let count: number = 1; @@ -60,7 +61,8 @@ export class DownloadManager { pkg, `${count}/${packages.length}`, progress, - pkgProgressToUse + pkgProgressToUse, + cancelToken ); count += 1; return p; @@ -74,67 +76,75 @@ export class DownloadManager { pkg: IPackage, progressCount: string, progress: vscode.Progress<{ message?: string; increment?: number }>, - pkgProgress?: PackageProgress + pkgProgress?: PackageProgress, + cancelToken?: vscode.CancellationToken ): Promise { progress.report({ message: `Downloading ${progressCount}: ${pkg.name}` }); this.appendChannel(`Downloading ${pkg.description}`); const urlInfoToUse = idfToolsManager.obtainUrlInfoForPlatform(pkg); - await this.downloadPackageWithRetries(pkg, urlInfoToUse, pkgProgress); + await this.downloadPackageWithRetries( + pkg, + urlInfoToUse, + pkgProgress, + cancelToken + ); } public async downloadPackageWithRetries( pkg: IPackage, urlInfoToUse: IFileInfo, - pkgProgress?: PackageProgress + pkgProgress?: PackageProgress, + cancelToken?: vscode.CancellationToken ): Promise { const fileName = utils.fileNameFromUrl(urlInfoToUse.url); const destPath = this.getToolPackagesPath(["dist"]); const absolutePath: string = this.getToolPackagesPath(["dist", fileName]); - - await pathExists(absolutePath).then(async (pkgExists) => { - if (pkgExists) { - await utils - .validateFileSizeAndChecksum( - absolutePath, - urlInfoToUse.sha256, - urlInfoToUse.size - ) - .then(async (checksumEqual) => { - if (checksumEqual) { - pkgProgress.FileMatchChecksum = checksumEqual; - this.appendChannel( - `Found ${pkg.name} in ${this.installPath + path.sep}dist` - ); - pkgProgress.Progress = `100.00%`; - pkgProgress.ProgressDetail = `(${( - urlInfoToUse.size / 1024 - ).toFixed(2)} / - ${(urlInfoToUse.size / 1024).toFixed(2)}) KB`; - return; - } else { - await del(absolutePath); - await this.downloadWithRetries( - urlInfoToUse.url, - destPath, - pkgProgress - ); - } - }); - } else { - await this.downloadWithRetries(urlInfoToUse.url, destPath, pkgProgress); - } - pkgProgress.FileMatchChecksum = await utils.validateFileSizeAndChecksum( + const pkgExists = await pathExists(absolutePath); + if (pkgExists) { + const checksumEqual = await utils.validateFileSizeAndChecksum( absolutePath, urlInfoToUse.sha256, urlInfoToUse.size ); - }); + if (checksumEqual) { + pkgProgress.FileMatchChecksum = checksumEqual; + this.appendChannel( + `Found ${pkg.name} in ${this.installPath + path.sep}dist` + ); + pkgProgress.Progress = `100.00%`; + pkgProgress.ProgressDetail = `(${(urlInfoToUse.size / 1024).toFixed( + 2 + )} / ${(urlInfoToUse.size / 1024).toFixed(2)}) KB`; + return; + } else { + await del(absolutePath, { force: true }); + await this.downloadWithRetries( + urlInfoToUse.url, + destPath, + pkgProgress, + cancelToken + ); + } + } else { + await this.downloadWithRetries( + urlInfoToUse.url, + destPath, + pkgProgress, + cancelToken + ); + } + pkgProgress.FileMatchChecksum = await utils.validateFileSizeAndChecksum( + absolutePath, + urlInfoToUse.sha256, + urlInfoToUse.size + ); } public async downloadWithRetries( urlToUse: string, destPath: string, - pkgProgress: PackageProgress + pkgProgress: PackageProgress, + cancelToken?: vscode.CancellationToken ) { let success: boolean = false; let retryCount: number = 2; @@ -145,13 +155,21 @@ export class DownloadManager { urlToUse, retryCount, destPath, - pkgProgress + pkgProgress, + cancelToken ).catch((pkgError: PackageError) => { - throw pkgError.message; + throw pkgError; }); success = true; } catch (error) { + const errMsg = error.message + ? error.message + : `Error downloading ${urlToUse}`; + Logger.error(errMsg, error); retryCount += 1; + if (cancelToken && cancelToken.isCancellationRequested) { + throw error; + } if (retryCount > MAX_RETRIES) { this.appendChannel("Failed to download " + urlToUse); throw error; @@ -172,7 +190,8 @@ export class DownloadManager { urlString: string, delay: number, destinationPath: string, - pkgProgress?: PackageProgress + pkgProgress?: PackageProgress, + cancelToken?: vscode.CancellationToken ): Promise { const parsedUrl: url.Url = url.parse(urlString); const proxyStrictSSL: any = vscode.workspace @@ -184,10 +203,14 @@ export class DownloadManager { host: parsedUrl.host, path: parsedUrl.pathname, rejectUnauthorized: proxyStrictSSL, - timeout: 3000, }; return new Promise((resolve, reject) => { + if (cancelToken && cancelToken.isCancellationRequested) { + return reject( + new PackageError("Download cancelled by user", "downloadFile") + ); + } let secondsDelay: number = Math.pow(2, delay); if (secondsDelay === 1) { secondsDelay = 0; @@ -329,6 +352,9 @@ export class DownloadManager { const req = https.request(options, handleResponse); req.on("error", (error) => { + error.stack + ? this.appendChannel(error.stack) + : this.appendChannel(error.message); return reject( new PackageError( "HTTP/HTTPS Request error " + urlString, @@ -338,6 +364,14 @@ export class DownloadManager { ) ); }); + if (cancelToken) { + cancelToken.onCancellationRequested(() => { + req.abort(); + return reject( + new PackageError("Download cancelled by user", "downloadFile") + ); + }); + } req.end(); }, secondsDelay * 1000); }); diff --git a/src/examples/ExamplesPanel.ts b/src/examples/ExamplesPanel.ts index de9e3d1e7..f88471fbf 100644 --- a/src/examples/ExamplesPanel.ts +++ b/src/examples/ExamplesPanel.ts @@ -57,13 +57,13 @@ export class ExamplesPlanel { targetFrameworkFolder: string, targetDesc: string ) { - const onBoardingPanelTitle = locDic.localize( + const panelTitle = locDic.localize( "examples.panelName", `${targetDesc} Examples` ); this.panel = vscode.window.createWebviewPanel( ExamplesPlanel.viewType, - onBoardingPanelTitle, + panelTitle, column, { enableScripts: true, diff --git a/src/extension.ts b/src/extension.ts index fec0a5586..55a1f8c9f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -50,8 +50,6 @@ import * as idfConf from "./idfConfiguration"; import { LocDictionary } from "./localizationDictionary"; import { Logger } from "./logger/logger"; import { OutputChannel } from "./logger/outputChannel"; -import { getOnboardingInitialValues } from "./onboarding/onboardingInit"; -import { OnBoardingPanel } from "./onboarding/OnboardingPanel"; import * as utils from "./utils"; import { PreCheck } from "./utils"; import { @@ -84,9 +82,17 @@ import { ESPEFuseManager } from "./efuse"; import { constants, createFileSync, pathExists } from "fs-extra"; import { getEspAdf } from "./espAdf/espAdfDownload"; import { getEspMdf } from "./espMdf/espMdfDownload"; +import { SetupPanel } from "./setup/SetupPanel"; import { TCLClient } from "./espIdf/openOcd/tcl/tclClient"; import { JTAGFlash } from "./flash/jtag"; import { ChangelogViewer } from "./changelog-viewer"; +import { + getSetupInitialValues, + isCurrentInstallValid, + ISetupInitArgs, +} from "./setup/setupInit"; +import { installReqs } from "./pythonManager"; +import { checkExtensionSettings } from "./checkExtensionSettings"; import { CmakeListsEditorPanel } from "./cmake/cmakeEditorPanel"; import { seachInEspDocs } from "./espIdf/documentation/getSearchResults"; import { @@ -855,6 +861,51 @@ export async function activate(context: vscode.ExtensionContext) { ); }); + registerIDFCommand("espIdf.installPyReqs", () => { + return PreCheck.perform([openFolderCheck], async () => { + vscode.window.withProgress( + { + cancellable: true, + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF:", + }, + async ( + progress: vscode.Progress<{ message: string; increment?: number }>, + cancelToken: vscode.CancellationToken + ) => { + try { + const espIdfPath = idfConf.readParameter( + "idf.espIdfPath" + ) as string; + const toolsPath = idfConf.readParameter("idf.toolsPath") as string; + const pyPath = idfConf.readParameter("idf.pythonBinPath") as string; + progress.report({ + message: `Installing ESP-IDF Python Requirements...`, + }); + await installReqs( + espIdfPath, + pyPath, + toolsPath, + undefined, + OutputChannel.init(), + cancelToken + ); + vscode.window.showInformationMessage( + "ESP-IDF Python Requirements has been installed" + ); + } catch (error) { + const msg = error.message + ? error.message + : typeof error === "string" + ? error + : "Error installing Python requirements"; + Logger.errorNotify(msg, error); + } + } + ); + }); + }); + registerIDFCommand("espIdf.getXtensaGdb", () => { return PreCheck.perform([openFolderCheck], async () => { const modifiedEnv = utils.appendIdfAndToolsToPath(); @@ -1009,14 +1060,14 @@ export async function activate(context: vscode.ExtensionContext) { }); }); - registerIDFCommand("onboarding.start", () => { - PreCheck.perform([webIdeCheck], () => { + registerIDFCommand("espIdf.setup.start", (setupArgs?: ISetupInitArgs) => { + PreCheck.perform([webIdeCheck], async () => { try { - if (OnBoardingPanel.isCreatedAndHidden()) { - OnBoardingPanel.createOrShow(context.extensionPath); + if (SetupPanel.isCreatedAndHidden()) { + SetupPanel.createOrShow(context.extensionPath); return; } - vscode.window.withProgress( + await vscode.window.withProgress( { cancellable: false, location: vscode.ProgressLocation.Notification, @@ -1026,14 +1077,10 @@ export async function activate(context: vscode.ExtensionContext) { progress: vscode.Progress<{ message: string; increment: number }> ) => { try { - const onboardingArgs = await getOnboardingInitialValues( - context.extensionPath, - progress - ); - OnBoardingPanel.createOrShow( - context.extensionPath, - onboardingArgs - ); + setupArgs = setupArgs + ? setupArgs + : await getSetupInitialValues(context.extensionPath, progress); + SetupPanel.createOrShow(context.extensionPath, setupArgs); } catch (error) { Logger.errorNotify(error.message, error); } @@ -1318,12 +1365,6 @@ export async function activate(context: vscode.ExtensionContext) { await AppTraceManager.saveConfiguration(); }); }); - const showOnboardingInit = vscode.workspace - .getConfiguration("idf") - .get("showOnboardingOnInit"); - if (showOnboardingInit && typeof process.env.WEB_IDE === "undefined") { - vscode.commands.executeCommand("onboarding.start"); - } registerIDFCommand("esp.rainmaker.backend.connect", async () => { if (RainmakerAPIClient.isLoggedIn()) { @@ -1854,6 +1895,7 @@ export async function activate(context: vscode.ExtensionContext) { Logger.warn(`Failed to handle URI Open, ${uri.toString()}`); }, }); + await checkExtensionSettings(context.extensionPath); } function validateInputForRainmakerDeviceParam( @@ -1997,6 +2039,10 @@ const build = () => { buildTask.building(false); }); try { + const checkToolsAreValid = await isCurrentInstallValid(); + if (!checkToolsAreValid) { + return; + } await buildTask.build(); await TaskManager.runTasks(); if (!cancelToken.isCancellationRequested) { @@ -2090,7 +2136,7 @@ const flash = () => { const flasherArgsJsonPath = path.join(buildPath, "flasher_args.json"); let flashTask: FlashTask; - vscode.window.withProgress( + await vscode.window.withProgress( { cancellable: true, location: vscode.ProgressLocation.Notification, @@ -2105,6 +2151,10 @@ const flash = () => { TaskManager.disposeListeners(); }); try { + const checkToolsAreValid = await isCurrentInstallValid(); + if (!checkToolsAreValid) { + return; + } const model = await createFlashModel( flasherArgsJsonPath, port, @@ -2220,6 +2270,10 @@ const buildFlashAndMonitor = (runMonitor: boolean = true) => { buildTask.building(false); }); try { + const checkToolsAreValid = await isCurrentInstallValid(); + if (!checkToolsAreValid) { + return; + } progress.report({ message: "Building project...", increment: 20 }); await buildTask.build().then(() => { buildTask.building(false); diff --git a/src/idfConfiguration.ts b/src/idfConfiguration.ts index e0b72a2e0..9d01c589e 100644 --- a/src/idfConfiguration.ts +++ b/src/idfConfiguration.ts @@ -194,8 +194,10 @@ export function resolveVariables(configPath: string) { } if (match.indexOf("env:") > 0) { const envVariable = name.substring(name.indexOf("env:") + "env:".length); - const pathInEnv = process.env[envVariable]; - return pathInEnv; + if (Object.keys(process.env).indexOf(envVariable) === -1) { + return ""; + } + return process.env[envVariable]; } if (match.indexOf("workspaceFolder") > 0) { return PreCheck.isWorkspaceFolderOpen() diff --git a/src/idfToolsManager.ts b/src/idfToolsManager.ts index 1b9835b65..1247b44f7 100644 --- a/src/idfToolsManager.ts +++ b/src/idfToolsManager.ts @@ -18,19 +18,45 @@ import { IFileInfo, IPackage } from "./IPackage"; import { PackageError } from "./packageError"; import { PlatformInformation } from "./PlatformInformation"; import * as utils from "./utils"; +import { Logger } from "./logger/logger"; +import { OutputChannel } from "./logger/outputChannel"; +import { readJSON } from "fs-extra"; + +export interface IEspIdfTool { + actual: string; + description: string; + doesToolExist: boolean; + env: {}; + expected: string; + hashResult: boolean; + hasFailed: boolean; + id: string; + name: string; + path: string; + progress: string; + progressDetail: string; +} export class IdfToolsManager { - // toolsJson is expected to be a Javascript Object parsed from tools metadata json - private static toolsManagerChannel: vscode.OutputChannel; + public static async createIdfToolsManager(idfPath: string) { + const platformInfo = await PlatformInformation.GetPlatformInformation(); + const toolsJsonPath = await utils.getToolsJsonPath(idfPath); + const toolsObj = await readJSON(toolsJsonPath); + const idfToolsManager = new IdfToolsManager( + toolsObj, + platformInfo, + OutputChannel.init() + ); + return idfToolsManager; + } + private allPackages: IPackage[]; constructor( private toolsJson: any, private platformInfo: PlatformInformation, - private channel: vscode.OutputChannel - ) { - IdfToolsManager.toolsManagerChannel = channel; - } + private toolsManagerChannel: vscode.OutputChannel + ) {} public getPackageList(onReqPkgs?: string[]): Promise { return new Promise((resolve, reject) => { @@ -86,20 +112,16 @@ export class IdfToolsManager { }); } - public verifyPackages(pathsToVerify: string, onReqPkgs?: string[]): {} { - return this.getPackageList(onReqPkgs).then(async (packages) => { - const promiseArr = {}; - const names = packages.map((pkg) => pkg.name); - const promises = packages.map((pkg) => - this.checkBinariesVersion(pkg, pathsToVerify) - ); - return Promise.all(promises).then((versionExistsArr) => { - names.forEach( - (pkgName, index) => (promiseArr[pkgName] = versionExistsArr[index]) - ); - return promiseArr; - }); - }); + public async verifyPackages(pathsToVerify: string, onReqPkgs?: string[]) { + const pkgs = await this.getPackageList(onReqPkgs); + const promiseArr: { [key: string]: string } = {}; + const names = pkgs.map((pkg) => pkg.name); + const promises = pkgs.map((p) => + this.checkBinariesVersion(p, pathsToVerify) + ); + const versionExistsArr = await Promise.all(promises); + names.forEach((pkgName, i) => (promiseArr[pkgName] = versionExistsArr[i])); + return promiseArr; } public obtainUrlInfoForPlatform(pkg: IPackage): IFileInfo { @@ -145,59 +167,75 @@ export class IdfToolsManager { } public async checkBinariesVersion(pkg: IPackage, pathsToVerify: string) { - if (process.env.PATH.indexOf(pathsToVerify) === -1) { - process.env.PATH = `${pathsToVerify}${path.delimiter}${process.env.PATH}`; + const pathNameInEnv: string = + process.platform === "win32" ? "Path" : "PATH"; + let modifiedPath = process.env[pathNameInEnv]; + if ( + process.env[pathNameInEnv] && + process.env[pathNameInEnv].indexOf(pathsToVerify) === -1 + ) { + modifiedPath = `${pathsToVerify}${path.delimiter}${process.env[pathNameInEnv]}`; } const versionCmd = pkg.version_cmd.join(" "); - return utils - .execChildProcess( + const modifiedEnv = Object.assign({}, process.env); + if ( + modifiedEnv[pathNameInEnv] && + !modifiedEnv[pathNameInEnv].includes(modifiedPath) + ) { + modifiedEnv[pathNameInEnv] = modifiedPath; + } + try { + const binVersionResponse = await utils.execChildProcess( versionCmd, process.cwd(), - IdfToolsManager.toolsManagerChannel - ) - .then((resp) => { - const regexResult = resp.match(pkg.version_regex); - if (regexResult.length > 0) { - if (pkg.version_regex_replace) { - let replaceRegexResult = pkg.version_regex_replace; - for (let i = 0; i < regexResult.length; i++) { - replaceRegexResult = replaceRegexResult.replace( - "\\" + i, - regexResult[i] - ); - } - return replaceRegexResult; + this.toolsManagerChannel, + { + env: modifiedEnv, + maxBuffer: 500 * 1024, + cwd: process.cwd(), + } + ); + const regexResult = binVersionResponse + .toString() + .match(pkg.version_regex); + if (regexResult.length > 0) { + if (pkg.version_regex_replace) { + let replaceRegexResult = pkg.version_regex_replace; + for (let i = 0; i < regexResult.length; i++) { + replaceRegexResult = replaceRegexResult.replace( + "\\" + i, + regexResult[i] + ); } - return regexResult[1]; + return replaceRegexResult; } - return "No match"; - }) - .catch((reason) => { - IdfToolsManager.toolsManagerChannel.appendLine(reason); - return "Error"; - }); + return regexResult[1]; + } + return "No match"; + } catch (error) { + const errMsg = `Error checking ${pkg.name} version`; + this.toolsManagerChannel.appendLine(errMsg); + this.toolsManagerChannel.appendLine(error); + Logger.error(errMsg, error); + return errMsg; + } + } + + public async exportPathsInString(basePath: string, onReqPkgs?: string[]) { + let exportedPaths = await this.exportPathsInArray(basePath, onReqPkgs); + const exportedJoinedPaths = exportedPaths.join(path.delimiter); + return exportedJoinedPaths; } - public async exportPaths(basePath: string, onReqPkgs?: string[]) { - let exportedPaths = ""; + public async exportPathsInArray(basePath: string, onReqPkgs?: string[]) { const pkgs = await this.getPackageList(onReqPkgs); + const exportedPaths: string[] = []; for (const pkg of pkgs) { const versionToUse = this.getVersionToUse(pkg); - let pkgExportedPath: string; - if (pkg.binaries) { - pkgExportedPath = path.join( - basePath, - pkg.name, - versionToUse, - ...pkg.binaries - ); - } else { - pkgExportedPath = path.join(basePath, pkg.name, versionToUse); - } - exportedPaths = - exportedPaths === "" - ? pkgExportedPath - : exportedPaths + path.delimiter + pkgExportedPath; + let pkgExportedPath = pkg.binaries + ? path.join(basePath, pkg.name, versionToUse, ...pkg.binaries) + : path.join(basePath, pkg.name, versionToUse); + exportedPaths.push(pkgExportedPath); } return exportedPaths; } @@ -219,57 +257,70 @@ export class IdfToolsManager { const exportedVars = {}; const pkgs = await this.getPackageList(onReqPkgs); for (const pkg of pkgs) { - Object.keys(pkg.export_vars).forEach((key, index, arr) => { + const pkgVars = this.exportVarsForPkg(pkg, basePath); + Object.keys(pkgVars).forEach((key, index, arr) => { if (Object.keys(exportedVars).indexOf(key) === -1) { - const versionToUse = this.getVersionToUse(pkg); - const toolPath = path.join(basePath, pkg.name, versionToUse); - exportedVars[key] = pkg.export_vars[key].replace( - "${TOOL_PATH}", - toolPath - ); + exportedVars[key] = pkgVars[key]; } }); } return JSON.stringify(exportedVars); } - public async getRequiredToolsInfo() { - return this.getPackageList().then((packages) => { - return packages.map((pkg) => { - const pkgVersionsForPlatform = pkg.versions.filter((version) => { - return ( - Object.getOwnPropertyNames(version).indexOf( - this.platformInfo.platformToUse - ) > -1 - ); - }); - const expectedVersions = pkgVersionsForPlatform.map((p) => p.name); - return { - expected: expectedVersions.join(","), - hashResult: false, - id: pkg.name, - progress: "0.00%", - hasFailed: false, - }; - }); + public exportVarsForPkg(pkg: IPackage, basePath: string) { + const exportedVars = {}; + Object.keys(pkg.export_vars).forEach((key, index, arr) => { + const versionToUse = this.getVersionToUse(pkg); + const toolPath = path.join(basePath, pkg.name, versionToUse); + exportedVars[key] = pkg.export_vars[key].replace( + "${TOOL_PATH}", + toolPath + ); }); + return exportedVars; } - public async checkToolsVersion(pathToVerify: string) { - const versions = await this.verifyPackages(pathToVerify); - const pkgs = await this.getPackageList().then((packages) => { - return packages.map((pkg) => { - const expectedVersions = pkg.versions.map((p) => p.name); - const isToolVersionCorrect = - expectedVersions.indexOf(versions[pkg.name]) > -1; - return { - actual: versions[pkg.name], - doesToolExist: isToolVersionCorrect, - expected: expectedVersions.join(" or \n"), - id: pkg.name, - }; + public async getRequiredToolsInfo(basePath?: string, pathToVerify?: string) { + let versions: { [key: string]: string } = {}; + if (pathToVerify) { + versions = await this.verifyPackages(pathToVerify); + } + const packages = await this.getPackageList(); + const idfToolsList = packages.map((pkg) => { + const pkgVersionsForPlatform = pkg.versions.filter((version) => { + return ( + Object.getOwnPropertyNames(version).indexOf( + this.platformInfo.platformToUse + ) > -1 + ); }); + const expectedVersions = pkgVersionsForPlatform.map((p) => p.name); + const isToolVersionCorrect = + expectedVersions.indexOf(versions[pkg.name]) > -1; + const versionToUse = this.getVersionToUse(pkg); + let pkgExportedPath: string = ""; + let pkgVars = pkg.export_vars; + if (basePath) { + pkgExportedPath = pkg.binaries + ? path.join(basePath, pkg.name, versionToUse, ...pkg.binaries) + : path.join(basePath, pkg.name, versionToUse); + pkgVars = this.exportVarsForPkg(pkg, basePath); + } + return { + actual: versions[pkg.name] || "", + description: pkg.description, + doesToolExist: isToolVersionCorrect, + env: pkgVars, + expected: expectedVersions.join(","), + hashResult: false, + hasFailed: false, + id: pkg.name, + name: pkg.name, + path: pkgExportedPath, + progress: "0.00%", + progressDetail: "", + } as IEspIdfTool; }); - return pkgs; + return idfToolsList; } } diff --git a/src/installManager.ts b/src/installManager.ts index c1b3ddcc9..53e7a0c6f 100644 --- a/src/installManager.ts +++ b/src/installManager.ts @@ -33,9 +33,10 @@ export class InstallManager { return path.resolve(this.installPath, ...toolPackage); } - public installPackages( + public async installPackages( idfToolsManager: IdfToolsManager, - progress: vscode.Progress<{ message?: string; increment?: number }> + progress: vscode.Progress<{ message?: string; increment?: number }>, + cancelToken?: vscode.CancellationToken ): Promise { return idfToolsManager.getPackageList().then((packages) => { let count: number = 1; @@ -53,29 +54,33 @@ export class InstallManager { }); this.appendChannel(`Installing package ${pkg.description}`); const urlToUse = idfToolsManager.obtainUrlInfoForPlatform(pkg); - const parsedUrl = urlToUse.url.split(/\#|\?/)[0].split("."); // Get url file extension + const parsedUrl = urlToUse.url.split(/\#|\?/)[0].split("."); // Get url file extension let p: Promise; if (parsedUrl[parsedUrl.length - 1] === "zip") { - p = this.installZipPackage(idfToolsManager, pkg).then(async () => { - if (pkg.strip_container_dirs) { - await this.promisedStripContainerDirs( - absolutePath, - pkg.strip_container_dirs - ); + p = this.installZipPackage(idfToolsManager, pkg, cancelToken).then( + async () => { + if (pkg.strip_container_dirs) { + await this.promisedStripContainerDirs( + absolutePath, + pkg.strip_container_dirs + ); + } } - }); + ); } else if ( parsedUrl[parsedUrl.length - 2] === "tar" && parsedUrl[parsedUrl.length - 1] === "gz" ) { - p = this.installTarPackage(idfToolsManager, pkg).then(async () => { - if (pkg.strip_container_dirs) { - await this.promisedStripContainerDirs( - absolutePath, - pkg.strip_container_dirs - ); + p = this.installTarPackage(idfToolsManager, pkg, cancelToken).then( + async () => { + if (pkg.strip_container_dirs) { + await this.promisedStripContainerDirs( + absolutePath, + pkg.strip_container_dirs + ); + } } - }); + ); } else { p = new Promise((resolve) => { resolve(); @@ -87,379 +92,343 @@ export class InstallManager { }); } - public installZipFile(zipFilePath: string, destPath: string) { + public async installZipFile( + zipFilePath: string, + destPath: string, + cancelToken?: vscode.CancellationToken + ) { return new Promise(async (resolve, reject) => { - await pathExists(zipFilePath).then((doesZipFileExists) => { - if (!doesZipFileExists) { - return reject(`File ${zipFilePath} doesn't exist.`); + const doesZipFileExists = await pathExists(zipFilePath); + if (!doesZipFileExists) { + return reject(`File ${zipFilePath} doesn't exist.`); + } + yauzl.open(zipFilePath, { lazyEntries: true }, (error, zipfile) => { + if (error) { + return reject( + new PackageError("Zip file error", "InstallZipFile", error) + ); + } + if (cancelToken && cancelToken.isCancellationRequested) { + return reject( + new PackageError("Install cancelled by user", "InstallZipFile") + ); } - yauzl.open(zipFilePath, { lazyEntries: true }, (error, zipfile) => { - if (error) { - return reject( - new PackageError("Zip file error", "InstallZipFile", error) - ); - } - - zipfile.on("end", () => { - return resolve(); - }); - zipfile.on("error", (err) => { - return reject( - new PackageError("Zip File error", "InstallZipFile", err) - ); - }); - zipfile.readEntry(); + zipfile.on("end", () => { + return resolve(); + }); + zipfile.on("error", (err) => { + return reject( + new PackageError("Zip File error", "InstallZipFile", err) + ); + }); - zipfile.on("entry", async (entry: yauzl.Entry) => { - const absolutePath: string = path.resolve(destPath, entry.fileName); - await utils - .dirExistPromise(absolutePath) - .then(async (dirExists) => { - if (dirExists) { - await del(absolutePath, { force: true }).catch((err) => { - this.appendChannel( - `Error deleting previous ${absolutePath}: ${err.message}` + zipfile.readEntry(); + zipfile.on("entry", async (entry: yauzl.Entry) => { + const absolutePath: string = path.resolve(destPath, entry.fileName); + const dirExists = await utils.dirExistPromise(absolutePath); + if (dirExists) { + try { + await del(absolutePath, { force: true }); + } catch (err) { + this.appendChannel( + `Error deleting previous ${absolutePath}: ${err.message}` + ); + return reject( + new PackageError( + "Install folder cant be deleted", + "InstallZipFile", + err, + err.errorCode + ) + ); + } + } + if (entry.fileName.endsWith("/")) { + try { + await ensureDir(absolutePath, { mode: 0o775 }); + zipfile.readEntry(); + } catch (err) { + return reject( + new PackageError( + "Error creating directory", + "InstallZipFile", + err + ) + ); + } + } else { + const exists = await pathExists(absolutePath); + if (!exists) { + zipfile.openReadStream( + entry, + async (err, readStream: fs.ReadStream) => { + if (err) { + return reject( + new PackageError( + "Error reading zip stream", + "InstallZipFile", + err + ) ); + } + readStream.on("error", (openErr) => { return reject( new PackageError( - "Install folder cant be deleted", + "Error in readstream", "InstallZipFile", - err, - err.errorCode + openErr ) ); }); - } - }); - if (entry.fileName.endsWith("/")) { - await ensureDir(absolutePath, { mode: 0o775 }) - .then(() => { - zipfile.readEntry(); - }) - .catch((err) => { - if (err) { + + try { + await ensureDir(path.dirname(absolutePath), { + mode: 0o775, + }); + } catch (mkdirErr) { return reject( new PackageError( "Error creating directory", "InstallZipFile", - err + mkdirErr ) ); } - }); - } else { - await pathExists(absolutePath).then((exists: boolean) => { - if (!exists) { - zipfile.openReadStream( - entry, - async (err, readStream: fs.ReadStream) => { - if (err) { - return reject( - new PackageError( - "Error reading zip stream", - "InstallZipFile", - err - ) - ); - } - readStream.on("error", (openErr) => { - return reject( - new PackageError( - "Error in readstream", - "InstallZipFile", - openErr - ) - ); - }); - - await ensureDir(path.dirname(absolutePath), { - mode: 0o775, - }) - .then(async () => { - const absoluteEntryTmpPath: string = - absolutePath + ".tmp"; - await pathExists(absoluteEntryTmpPath).then( - async (doesTmpPathExists) => { - if (doesTmpPathExists) { - await remove(absoluteEntryTmpPath).catch( - (rmError) => { - return reject( - new PackageError( - `Error unlinking tmp file ${absoluteEntryTmpPath}`, - "InstallZipFile", - rmError - ) - ); - } - ); - } - } - ); - const writeStream: fs.WriteStream = fs.createWriteStream( - absoluteEntryTmpPath, - { mode: 0o755 } - ); - writeStream.on("close", async () => { - try { - await move(absoluteEntryTmpPath, absolutePath); - } catch (closeWriteStreamErr) { - return reject( - new PackageError( - `Error renaming file ${absoluteEntryTmpPath}`, - "InstallZipFile", - closeWriteStreamErr - ) - ); - } - zipfile.readEntry(); - }); - writeStream.on("error", (writeStreamErr) => { - return reject( - new PackageError( - "Error in writeStream", - "InstallZipFile", - writeStreamErr - ) - ); - }); - - readStream.pipe(writeStream); - }) - .catch((mkdirErr) => { - if (mkdirErr) { - reject( - new PackageError( - "Error creating directory", - "InstallZipFile", - mkdirErr - ) - ); - } - }); + const absoluteEntryTmpPath: string = absolutePath + ".tmp"; + const doesTmpPathExists = await pathExists( + absoluteEntryTmpPath + ); + if (doesTmpPathExists) { + try { + await remove(absoluteEntryTmpPath); + } catch (rmError) { + return reject( + new PackageError( + `Error unlinking tmp file ${absoluteEntryTmpPath}`, + "InstallZipFile", + rmError + ) + ); } + } + const writeStream: fs.WriteStream = fs.createWriteStream( + absoluteEntryTmpPath, + { mode: 0o755 } ); - } else { - if (path.extname(absolutePath) !== ".txt") { - this.appendChannel( - `Warning File ${absolutePath} - already exists and was not updated.` + writeStream.on("error", (writeStreamErr) => { + return reject( + new PackageError( + "Error in writeStream", + "InstallZipFile", + writeStreamErr + ) ); - } - zipfile.readEntry(); + }); + writeStream.on("close", async () => { + try { + await move(absoluteEntryTmpPath, absolutePath); + } catch (closeWriteStreamErr) { + return reject( + new PackageError( + `Error renaming file ${absoluteEntryTmpPath}`, + "InstallZipFile", + closeWriteStreamErr + ) + ); + } + zipfile.readEntry(); + }); + readStream.pipe(writeStream); } - }); + ); + } else { + if (path.extname(absolutePath) !== ".txt") { + this.appendChannel( + `Warning File ${absolutePath} + already exists and was not updated.` + ); + } + zipfile.readEntry(); } - }); + } }); }); }); } - public installZipPackage(idfToolsManager: IdfToolsManager, pkg: IPackage) { + public async installZipPackage( + idfToolsManager: IdfToolsManager, + pkg: IPackage, + cancelToken?: vscode.CancellationToken + ) { this.appendChannel(`Installing zip package ${pkg.description}`); + const urlInfo = idfToolsManager.obtainUrlInfoForPlatform(pkg); + const fileName = utils.fileNameFromUrl(urlInfo.url); + const packageFile: string = this.getToolPackagesPath(["dist", fileName]); - return new Promise(async (resolve, reject) => { - const urlInfo = idfToolsManager.obtainUrlInfoForPlatform(pkg); - const fileName = utils.fileNameFromUrl(urlInfo.url); - const packageFile: string = this.getToolPackagesPath(["dist", fileName]); - await pathExists(packageFile).then(async (packageDownloaded) => { - if (packageDownloaded) { - await utils - .validateFileSizeAndChecksum( - packageFile, - urlInfo.sha256, - urlInfo.size - ) - .then(async (isValidFile) => { - if (isValidFile) { - const versionName = idfToolsManager.getVersionToUse(pkg); - const absolutePath: string = this.getToolPackagesPath([ - "tools", - pkg.name, - versionName, - ]); - return await this.installZipFile(packageFile, absolutePath) - .then(() => { - return resolve(); - }) - .catch((reason) => { - return reject(reason); - }); - } else { - this.appendChannel( - `Package ${pkg.description} downloaded file is invalid.` - ); - return reject( - new PackageError( - "Downloaded file invalid", - "InstallZipPackage" - ) - ); - } - }); - } else { - this.appendChannel( - `${pkg.description} downloaded file is not available.` - ); - return reject( - new PackageError( - "Error finding downloaded file", - "InstallZipPackage" - ) - ); - } - }); - }); + const packageDownloaded = await pathExists(packageFile); + if (!packageDownloaded) { + this.appendChannel( + `${pkg.description} downloaded file is not available.` + ); + throw new PackageError( + "Error finding downloaded file", + "InstallZipPackage" + ); + } + const isValidFile = await utils.validateFileSizeAndChecksum( + packageFile, + urlInfo.sha256, + urlInfo.size + ); + if (!isValidFile) { + this.appendChannel( + `Package ${pkg.description} downloaded file is invalid.` + ); + throw new PackageError("Downloaded file invalid", "InstallZipPackage"); + } + const versionName = idfToolsManager.getVersionToUse(pkg); + const absolutePath: string = this.getToolPackagesPath([ + "tools", + pkg.name, + versionName, + ]); + return await this.installZipFile(packageFile, absolutePath, cancelToken); } public installTarPackage( idfToolsManager: IdfToolsManager, - pkg: IPackage + pkg: IPackage, + cancelToken?: vscode.CancellationToken ): Promise { return new Promise(async (resolve, reject) => { const urlInfo = idfToolsManager.obtainUrlInfoForPlatform(pkg); const fileName = utils.fileNameFromUrl(urlInfo.url); const packageFile: string = this.getToolPackagesPath(["dist", fileName]); - await pathExists(packageFile).then(async (packageDownloaded) => { - if (packageDownloaded) { - await utils - .validateFileSizeAndChecksum( - packageFile, - urlInfo.sha256, - urlInfo.size - ) - .then(async (isValidFile) => { - if (isValidFile) { - const versionName = idfToolsManager.getVersionToUse(pkg); - const absolutePath: string = this.getToolPackagesPath([ - "tools", - pkg.name, - versionName, - ]); - await utils - .dirExistPromise(absolutePath) - .then(async (dirExists) => { - if (dirExists) { - await del(absolutePath, { force: true }).catch((err) => { - this.appendChannel( - `Error deleting ${pkg.name} old install: ${err.message}` - ); - return reject( - new PackageError( - "Install folder cant be deleted", - "installTarPackage", - err, - err.errorCode - ) - ); - }); - } - }); - const binPath = pkg.binaries - ? path.join(absolutePath, ...pkg.binaries, pkg.version_cmd[0]) - : path.join(absolutePath, pkg.version_cmd[0]); + if (cancelToken && cancelToken.isCancellationRequested) { + return reject(new Error("Process cancelled by user")); + } - await pathExists(binPath).then((exists) => { - if (!exists) { - this.appendChannel( - `Installing tar.gz package ${pkg.description}` - ); - const extractor = tarfs.extract(absolutePath, { - readable: true, // all dirs and files should be readable - writable: true, // all dirs and files should be writable - }); - extractor.on("error", (err) => { - reject( - new PackageError( - "Extracting gunzipped tar error", - "InstallTarPackage", - err, - err.code - ) - ); - }); - extractor.on("finish", () => { - resolve(); - }); - fs.createReadStream(packageFile) - .pipe(zlib.createGunzip()) - .pipe(extractor); - } else { - this.appendChannel( - `Existing ${pkg.description} found in ${this.installPath}` - ); - resolve(); - } - }); - } else { - this.appendChannel( - `Package ${pkg.description} downloaded file is invalid.` - ); - return reject( - new PackageError( - "Downloaded file invalid", - "InstallTarPackage" - ) - ); - } - }); - } else { + const packageDownloaded = await pathExists(packageFile); + if (!packageDownloaded) { + this.appendChannel( + `Package ${pkg.description} downloaded file not available.` + ); + return reject( + new PackageError("Downloaded file unavailable", "InstallTarPackage") + ); + } + const isValidFile = await utils.validateFileSizeAndChecksum( + packageFile, + urlInfo.sha256, + urlInfo.size + ); + if (!isValidFile) { + this.appendChannel( + `Package ${pkg.description} downloaded file is invalid.` + ); + return reject( + new PackageError("Downloaded file invalid", "InstallTarPackage") + ); + } + const versionName = idfToolsManager.getVersionToUse(pkg); + const absolutePath: string = this.getToolPackagesPath([ + "tools", + pkg.name, + versionName, + ]); + const dirExists = await utils.dirExistPromise(absolutePath); + if (dirExists) { + try { + await del(absolutePath, { force: true }); + } catch (err) { this.appendChannel( - `Package ${pkg.description} downloaded file not available.` + `Error deleting ${pkg.name} old install: ${err.message}` ); return reject( - new PackageError("Downloaded file unavailable", "InstallTarPackage") + new PackageError( + "Install folder cant be deleted", + "installTarPackage", + err, + err.errorCode + ) ); } + } + const binPath = pkg.binaries + ? path.join(absolutePath, ...pkg.binaries, pkg.version_cmd[0]) + : path.join(absolutePath, pkg.version_cmd[0]); + + const binExists = await pathExists(binPath); + if (binExists) { + this.appendChannel( + `Existing ${pkg.description} found in ${this.installPath}` + ); + return resolve(); + } + this.appendChannel(`Installing tar.gz package ${pkg.description}`); + const extractor = tarfs.extract(absolutePath, { + readable: true, // all dirs and files should be readable + writable: true, // all dirs and files should be writable + }); + extractor.on("error", (err) => { + reject( + new PackageError( + "Extracting gunzipped tar error", + "InstallTarPackage", + err, + err.code + ) + ); }); + extractor.on("finish", () => { + return resolve(); + }); + try { + fs.createReadStream(packageFile) + .pipe(zlib.createGunzip()) + .pipe(extractor); + } catch (error) { + return reject(error); + } }); } private appendChannel(text: string): void { OutputChannel.appendLine(text); Logger.info(text); } - private promisedStripContainerDirs(pkgDirPath: string, levels: number) { - return new Promise(async (resolve, reject) => { - const tmpPath = pkgDirPath + ".tmp"; - await utils.dirExistPromise(tmpPath).then(async (exists) => { - if (exists) { - await del(tmpPath, { force: true }); - } - }); - - await move(pkgDirPath, tmpPath); - await ensureDir(pkgDirPath); - let basePath = tmpPath; - // Walk given number of levels down - for (let i = 0; i < levels; i++) { - await utils.readDirPromise(basePath).then(async (files) => { - if (files.length > 1) { - reject(`At level ${i} expected 1 entry, got ${files.length}`); - } - basePath = path.join(basePath, files[0]); - utils.dirExistPromise(basePath).then((isDirectory) => { - if (!isDirectory) { - reject(`At level ${levels[i]}, ${files[0]} is not a directory.`); - } - }); - }); + private async promisedStripContainerDirs(pkgDirPath: string, levels: number) { + const tmpPath = pkgDirPath + ".tmp"; + const exists = await utils.dirExistPromise(tmpPath); + if (exists) { + await del(tmpPath, { force: true }); + } + await move(pkgDirPath, tmpPath); + await ensureDir(pkgDirPath); + let basePath = tmpPath; + // Walk given number of levels down + for (let i = 0; i < levels; i++) { + const files = await utils.readDirPromise(basePath); + if (files.length > 1) { + throw new Error(`At level ${i} expected 1 entry, got ${files.length}`); } - // Get list of directories/files to move - await utils - .readDirPromise(basePath) - .then(async (files) => { - for (const file of files) { - const moveFrom = path.join(basePath, file); - const moveTo = path.join(pkgDirPath, file); - await move(moveFrom, moveTo); - } - }) - .then(async () => { - await del(tmpPath, { force: true }); - resolve(); - }); - }); + basePath = path.join(basePath, files[0]); + const isDirectory = await utils.dirExistPromise(basePath); + if (!isDirectory) { + throw new Error( + `At level ${levels[i]}, ${files[0]} is not a directory.` + ); + } + } + // Get list of directories/files to move + const filesToMove = await utils.readDirPromise(basePath); + for (let file of filesToMove) { + const moveFrom = path.join(basePath, file); + const moveTo = path.join(pkgDirPath, file); + await move(moveFrom, moveTo); + } + await del(tmpPath, { force: true }); } } diff --git a/src/onboarding/OnboardingPanel.ts b/src/onboarding/OnboardingPanel.ts deleted file mode 100644 index dd176b673..000000000 --- a/src/onboarding/OnboardingPanel.ts +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { ensureDir, constants } from "fs-extra"; -import * as path from "path"; -import * as vscode from "vscode"; -import * as idfConf from "../idfConfiguration"; -import { IdfToolsManager } from "../idfToolsManager"; -import { LocDictionary } from "../localizationDictionary"; -import { Logger } from "../logger/logger"; -import { OutputChannel } from "../logger/outputChannel"; -import { PlatformInformation } from "../PlatformInformation"; -import * as utils from "../utils"; -import { createOnboardingHtml } from "./createOnboardingHtml"; -import { downloadInstallIdfVersion, IEspIdfLink } from "./espIdfDownload"; -import { IOnboardingArgs } from "./onboardingInit"; -import { checkPythonRequirements } from "./pythonReqsManager"; -import { downloadToolsInIdfToolsPath } from "./toolsInstall"; - -const locDic = new LocDictionary("OnBoardingPanel"); - -export class OnBoardingPanel { - public static currentPanel: OnBoardingPanel | undefined; - - public static createOrShow( - extensionPath: string, - onboardingArgs?: IOnboardingArgs - ) { - const column = vscode.window.activeTextEditor - ? vscode.window.activeTextEditor.viewColumn - : undefined; - if (OnBoardingPanel.currentPanel) { - OnBoardingPanel.currentPanel.panel.reveal(column); - } else { - OnBoardingPanel.currentPanel = new OnBoardingPanel( - extensionPath, - column || vscode.ViewColumn.One, - onboardingArgs - ); - } - } - - public static postMessage(content: any) { - if (OnBoardingPanel.currentPanel) { - OnBoardingPanel.currentPanel.panel.webview.postMessage(content); - } - } - - public static isCreatedAndHidden(): boolean { - return ( - OnBoardingPanel.currentPanel && - OnBoardingPanel.currentPanel.panel.visible === false - ); - } - - private static readonly viewType = "onboarding"; - private readonly panel: vscode.WebviewPanel; - private disposables: vscode.Disposable[] = []; - private extensionPath: string; - private idfToolsManager: IdfToolsManager; - private confTarget: vscode.ConfigurationTarget = - vscode.ConfigurationTarget.Global; - private selectedWorkspaceFolder: vscode.WorkspaceFolder; - private pythonSystemBinPath: string; - - private constructor( - extensionPath: string, - column: vscode.ViewColumn, - onboardingArgs: IOnboardingArgs - ) { - this.extensionPath = extensionPath; - this.idfToolsManager = onboardingArgs.idfToolsManager; - this.pythonSystemBinPath = onboardingArgs.pythonVersions[0]; - const onBoardingPanelTitle = locDic.localize( - "onboarding.panelName", - "IDF Onboarding" - ); - this.panel = vscode.window.createWebviewPanel( - OnBoardingPanel.viewType, - onBoardingPanelTitle, - column, - { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [ - vscode.Uri.file(path.join(extensionPath, "dist", "views")), - ], - } - ); - const scriptPath = this.panel.webview.asWebviewUri( - vscode.Uri.file( - path.join(extensionPath, "dist", "views", "onboarding-bundle.js") - ) - ); - this.panel.iconPath = utils.getWebViewFavicon(extensionPath); - this.panel.webview.html = createOnboardingHtml(scriptPath); - - this.panel.onDidDispose(() => this.dispose(), null, this.disposables); - - this.panel.webview.onDidReceiveMessage((message) => { - switch (message.command) { - case "command": - this.panel.webview.postMessage({ - command: "command", - value: "exampleValue", - }); - break; - case "updateConfigurationTarget": - switch (message.confTarget) { - case vscode.ConfigurationTarget.Global: - this.confTarget = vscode.ConfigurationTarget.Global; - break; - case vscode.ConfigurationTarget.Workspace: - this.confTarget = vscode.ConfigurationTarget.Workspace; - if (utils.PreCheck.isWorkspaceFolderOpen()) { - this.confTarget = vscode.ConfigurationTarget.Workspace; - } else { - this.panel.webview.postMessage({ - command: "resetConfigurationTarget", - confTarget: vscode.ConfigurationTarget.Global, - }); - vscode.window.showInformationMessage( - "No workspace is open. Please open a workspace first." - ); - } - break; - case vscode.ConfigurationTarget.WorkspaceFolder: - this.confTarget = vscode.ConfigurationTarget.Workspace; - if (utils.PreCheck.isWorkspaceFolderOpen()) { - this.confTarget = vscode.ConfigurationTarget.WorkspaceFolder; - this.selectedWorkspaceFolder = vscode.workspace.workspaceFolders.find( - (f) => f.uri.fsPath === message.workspaceFolder - ); - } else { - this.panel.webview.postMessage({ - command: "resetConfigurationTarget", - confTarget: vscode.ConfigurationTarget.Global, - }); - vscode.window.showInformationMessage( - "No folder is open. Please open a folder first." - ); - } - break; - default: - break; - } - break; - case "checkIdfPath": - if (message.new_value) { - const idfBinaryPath = path.join( - message.new_value, - "tools", - "idf.py" - ); - const doesIdfExist = utils.fileExists(idfBinaryPath); - utils.getEspIdfVersion(message.new_value).then((idfVersion) => { - this.panel.webview.postMessage({ - command: "response_check_idf_version", - version: idfVersion, - }); - }); - this.panel.webview.postMessage({ - command: "response_check_idf_path", - doesIdfExists: doesIdfExist, - }); - } - break; - case "saveNewIdfPath": - if (message.idf_path) { - idfConf.writeParameter( - "idf.espIdfPath", - message.idf_path, - this.confTarget, - this.selectedWorkspaceFolder - ); - this.updateIdfToolsManager(message.idf_path); - } - break; - case "checkIdfToolsForPaths": - if ( - message.custom_paths && - message.custom_vars && - message.py_bin_path - ) { - this.idfToolsManager - .checkToolsVersion(message.custom_paths) - .then((dictTools) => { - const pkgsNotFound = dictTools.filter( - (p) => p.doesToolExist === false - ); - if (pkgsNotFound.length === 0) { - this.panel.webview.postMessage({ - command: "set_tools_check_finish", - }); - } - this.panel.webview.postMessage({ - command: "respond_check_idf_tools_path", - dictToolsExist: dictTools, - }); - const toolsNotFound = dictTools.filter( - (val) => val.doesToolExist === false - ); - if (toolsNotFound.length === 0) { - idfConf.writeParameter( - "idf.customExtraPaths", - message.custom_paths, - this.confTarget, - this.selectedWorkspaceFolder - ); - } - if (!utils.canAccessFile(message.py_bin_path, constants.R_OK)) { - const notAccessMsg = `${message.py_bin_path} is not accesible.`; - vscode.window.showErrorMessage(notAccessMsg); - this.panel.webview.postMessage({ - command: "response_py_req_check", - py_req_log: notAccessMsg, - }); - return; - } - checkPythonRequirements( - this.extensionPath, - this.selectedWorkspaceFolder, - message.py_bin_path - ); - }); - } else { - if (message.py_bin_path === "") { - vscode.window.showInformationMessage( - "Please fill all required inputs" - ); - } - } - break; - case "getRequiredToolsInfo": - this.idfToolsManager.getRequiredToolsInfo().then((requiredTools) => { - this.panel.webview.postMessage({ - command: "reply_required_tools_versions", - requiredToolsVersions: requiredTools, - }); - }); - break; - case "downloadToolsInPath": - if (message.idf_path && message.tools_path) { - utils - .dirExistPromise(message.tools_path) - .then(async (doesDirExists: boolean) => { - if (doesDirExists) { - await idfConf.writeParameter( - "idf.toolsPath", - message.tools_path, - this.confTarget, - this.selectedWorkspaceFolder - ); - } else { - if (message.tools_path.indexOf("~") > -1) { - vscode.window.showInformationMessage( - "Given path can't contain ~, please use absolute path." - ); - return; - } - const selected = await vscode.window.showErrorMessage( - "Specified Directory doesn't exists. Create?", - { modal: true }, - { title: "Yes", isCloseAffordance: false }, - { title: "No", isCloseAffordance: false } - ); - if (selected.title === "Yes") { - await ensureDir(message.tools_path).then(async () => { - await idfConf.writeParameter( - "idf.toolsPath", - message.tools_path, - this.confTarget, - this.selectedWorkspaceFolder - ); - }); - } else { - vscode.window.showInformationMessage( - "Please input a valid IDF Tools Directory" - ); - return; - } - } - downloadToolsInIdfToolsPath( - message.idf_path, - this.idfToolsManager, - message.tools_path, - this.confTarget, - this.selectedWorkspaceFolder, - this.pythonSystemBinPath - ).catch((reason) => { - OutputChannel.appendLine(reason); - Logger.info(reason); - }); - }); - } - break; - case "getExamplesList": - vscode.commands.executeCommand("examples.start"); - break; - case "saveEnvVars": - if (message.custom_vars && message.custom_paths) { - OutputChannel.appendLine(""); - Logger.info(""); - OutputChannel.appendLine( - "The following paths would be added to env PATH" - ); - Logger.info("The following paths would be added to env PATH"); - OutputChannel.appendLine(message.custom_paths); - Logger.info(message.custom_paths); - OutputChannel.appendLine(""); - Logger.info(""); - idfConf.writeParameter( - "idf.customExtraPaths", - message.custom_paths, - this.confTarget, - this.selectedWorkspaceFolder - ); - idfConf.writeParameter( - "idf.customExtraVars", - JSON.stringify(message.custom_vars), - this.confTarget, - this.selectedWorkspaceFolder - ); - } - break; - case "openEspIdfFolder": - vscode.window - .showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - }) - .then((selectedFolder: vscode.Uri[]) => { - if (selectedFolder && selectedFolder.length > 0) { - this.panel.webview.postMessage({ - command: "response_selected_espidf_folder", - selected_folder: selectedFolder[0].fsPath, - }); - } else { - vscode.window.showInformationMessage("No folder selected"); - } - }); - break; - case "openToolsFolder": - vscode.window - .showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - }) - .then((selectedFolder: vscode.Uri[]) => { - if (selectedFolder && selectedFolder.length > 0) { - this.panel.webview.postMessage({ - command: "response_selected_tools_folder", - selected_folder: selectedFolder[0].fsPath, - }); - } else { - vscode.window.showInformationMessage("No folder selected"); - } - }); - break; - case "downloadEspIdfVersion": - if (message.selectedVersion && message.idfPath) { - const idfVersion = message.selectedVersion as IEspIdfLink; - downloadInstallIdfVersion( - idfVersion, - message.idfPath, - this.confTarget, - this.selectedWorkspaceFolder - ).then(async () => { - await this.updateIdfToolsManager( - path.join(message.idfPath, "esp-idf") - ); - }); - } - break; - case "savePythonBinary": - if (message.selectedPyBin) { - if (!utils.fileExists(message.selectedPyBin)) { - vscode.window.showInformationMessage("Python path doesn't exist"); - return; - } - const msg = `Saving ${message.selectedPyBin} to create python virtual environment.`; - Logger.info(msg); - OutputChannel.appendLine(msg); - this.pythonSystemBinPath = message.selectedPyBin; - this.panel.webview.postMessage({ - command: "set_py_sys_path_is_valid", - }); - } - break; - case "saveShowOnboarding": - if (message.showOnboarding !== undefined) { - idfConf.writeParameter( - "idf.showOnboardingOnInit", - message.showOnboarding, - vscode.ConfigurationTarget.Global - ); - Logger.info( - `Show onboarding on extension start? ${message.showOnboarding}` - ); - } - break; - case "requestInitValues": - this.sendInitialValues(onboardingArgs); - break; - default: - break; - } - }); - } - - public dispose() { - OnBoardingPanel.currentPanel = undefined; - this.panel.dispose(); - } - - private async updateIdfToolsManager(newIdfPath: string) { - const platformInfo = await PlatformInformation.GetPlatformInformation(); - const toolsJsonPath = await utils.getToolsJsonPath(newIdfPath); - const toolsJson = JSON.parse(utils.readFileSync(toolsJsonPath)); - this.idfToolsManager = new IdfToolsManager( - toolsJson, - platformInfo, - OutputChannel.init() - ); - } - - private sendInitialValues(onboardingArgs: IOnboardingArgs) { - // Send workspace folders - let selected; - if (utils.PreCheck.isWorkspaceFolderOpen()) { - const folders = vscode.workspace.workspaceFolders.map( - (f) => f.uri.fsPath - ); - selected = vscode.workspace.workspaceFolders[0]; - this.panel.webview.postMessage({ - command: "loadWorkspaceFolders", - folders: folders, - }); - } - // Give initial IDF_PATH - const espIdfPath = idfConf.readParameter("idf.espIdfPath", selected); - this.panel.webview.postMessage({ - command: "load_idf_path", - idf_path: espIdfPath, - }); - // Give initial IDF_TOOLS_PATH - const idfToolsPath = idfConf.readParameter("idf.toolsPath", selected); - this.panel.webview.postMessage({ - command: "load_idf_tools_path", - idf_tools_path: idfToolsPath, - }); - // Give initial download path for idf_path - const idfDownloadPath = - process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; - this.panel.webview.postMessage({ - command: "load_idf_download_path", - idf_path: idfDownloadPath, - }); - - // Initial Python Path - const pyBinPath = idfConf.readParameter("idf.pythonBinPath") as string; - this.panel.webview.postMessage({ - command: "load_python_bin_path", - pythonBinPath: pyBinPath, - }); - - // Show onboarding on extension activate - const showOnboardingOnInit = JSON.parse( - idfConf.readParameter("idf.showOnboardingOnInit") - ); - this.panel.webview.postMessage({ - command: "load_show_onboarding", - show_onboarding_on_init: showOnboardingOnInit, - }); - - this.panel.webview.postMessage({ - command: "load_path_delimiter", - pathDelimiter: path.delimiter, - }); - - // Give initial values of idf.customExtraPaths - const customExtraPaths = idfConf.readParameter("idf.customExtraPaths"); - this.panel.webview.postMessage({ - command: "load_custom_paths", - custom_paths: customExtraPaths, - }); - - // Initial ESP-IDF version list - this.panel.webview.postMessage({ - command: "load_idf_versions", - versions: onboardingArgs.espIdfVersionList, - }); - this.panel.webview.postMessage({ - command: "load_git_version", - gitVersion: onboardingArgs.gitVersion, - }); - this.panel.webview.postMessage({ - command: "load_py_version_list", - pyVersionList: onboardingArgs.pythonVersions, - }); - - const customVars = idfConf.readParameter("idf.customExtraVars"); - if (utils.isJson(customVars)) { - this.panel.webview.postMessage({ - command: "load_custom_paths", - custom_vars: JSON.parse(customVars), - }); - } else { - this.panel.webview.postMessage({ - command: "load_env_vars_def", - env_vars: onboardingArgs.expectedEnvVars, - }); - } - } -} diff --git a/src/onboarding/espIdfDownload.ts b/src/onboarding/espIdfDownload.ts deleted file mode 100644 index e9d55850c..000000000 --- a/src/onboarding/espIdfDownload.ts +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { spawn } from "child_process"; -import * as fs from "fs"; -import { move } from "fs-extra"; -import { EOL, tmpdir } from "os"; -import * as path from "path"; -import { ConfigurationTarget, WorkspaceFolder } from "vscode"; -import { DownloadManager } from "../downloadManager"; -import * as idfConf from "../idfConfiguration"; -import { InstallManager } from "../installManager"; -import { Logger } from "../logger/logger"; -import { OutputChannel } from "../logger/outputChannel"; -import { PackageProgress } from "../PackageProgress"; -import * as utils from "../utils"; -import { OnBoardingPanel } from "./OnboardingPanel"; -import { - sendDownloadEspIdfDetail, - sendDownloadEspIdfPercentage, -} from "./updateViewMethods"; - -export interface IEspIdfLink { - filename: string; - name: string; - url: string; - mirror: string; -} - -export async function downloadInstallIdfVersion( - idfVersion: IEspIdfLink, - destPath: string, - confTarget: ConfigurationTarget, - selectedWorkspaceFolder: WorkspaceFolder -) { - const downloadedZipPath = path.join(destPath, idfVersion.filename); - const extractedDirectory = downloadedZipPath.replace(".zip", ""); - const expectedDirectory = path.join(destPath, "esp-idf"); - await utils.dirExistPromise(destPath).then(async (containerFolderExists) => { - if (!containerFolderExists) { - Logger.infoNotify( - `${destPath} doesn't exists. Please select an existing directory.` - ); - return; - } - await utils - .dirExistPromise(expectedDirectory) - .then(async (espIdfFolderExists) => { - if (espIdfFolderExists) { - OutputChannel.appendLine( - `${expectedDirectory} already exists. Delete it or use another location` - ); - Logger.infoNotify( - `${expectedDirectory} already exists. Delete it or use another location` - ); - return; - } - OnBoardingPanel.postMessage({ - command: "set_selected_download_state", - state: "download", - }); - const downloadManager = new DownloadManager(destPath); - const installManager = new InstallManager(destPath); - const espIdfProgress = new PackageProgress( - idfVersion.name, - sendDownloadEspIdfPercentage, - null, - sendDownloadEspIdfDetail, - null - ); - if ( - idfVersion.filename === "master" || - idfVersion.filename.startsWith("release") - ) { - OutputChannel.appendLine( - `Downloading ESP-IDF ${idfVersion.filename} using git clone...\n` - ); - Logger.info("Downloading ESP-IDF master using git clone...\n"); - await downloadEspIdfByClone( - destPath, - idfVersion.filename, - espIdfProgress - ) - .then(async () => { - OutputChannel.appendLine( - `ESP-IDF ${idfVersion.filename} has been cloned from Github.\n` - ); - Logger.info( - `ESP-IDF ${idfVersion.filename} has been cloned from Github.\n` - ); - espIdfProgress.ProgressDetail = `${idfVersion.name} has been git cloned successfully`; - OnBoardingPanel.postMessage({ - command: "notify_idf_downloaded", - downloadedPath: "master", - }); - OnBoardingPanel.postMessage({ command: "notify_idf_extracted" }); - await idfConf.writeParameter( - "idf.espIdfPath", - expectedDirectory, - confTarget, - selectedWorkspaceFolder - ); - }) - .catch((reason) => { - OutputChannel.appendLine(reason); - Logger.infoNotify(reason); - OnBoardingPanel.postMessage({ - command: "set_selected_download_state", - state: "empty", - }); - }); - } else { - await downloadManager - .downloadWithRetries(idfVersion.url, destPath, espIdfProgress) - .then(async () => { - OutputChannel.appendLine(`Downloaded ${idfVersion.name}.\n`); - Logger.info(`Downloaded ${idfVersion.name}.\n`); - OnBoardingPanel.postMessage({ - command: "notify_idf_downloaded", - downloadedPath: downloadedZipPath, - }); - await installManager - .installZipFile(downloadedZipPath, destPath) - .then(async () => { - OutputChannel.appendLine( - `Extracted ${downloadedZipPath} in ${destPath}.\n` - ); - Logger.info( - `Extracted ${downloadedZipPath} in ${destPath}.\n` - ); - OnBoardingPanel.postMessage({ - command: "notify_idf_extracted", - }); - - // Rename folder esp-idf-{version} to esp-idf - await move(extractedDirectory, expectedDirectory).then(() => { - OutputChannel.appendLine( - `Renamed ${extractedDirectory} in ${expectedDirectory}.\n` - ); - Logger.info( - `Extracted ${extractedDirectory} in ${expectedDirectory}.\n` - ); - OnBoardingPanel.postMessage({ - command: "load_idf_path", - idf_path: expectedDirectory, - }); - }); - await idfConf.writeParameter( - "idf.espIdfPath", - expectedDirectory, - confTarget, - selectedWorkspaceFolder - ); - }); - }) - .catch((reason) => { - OutputChannel.appendLine(reason); - Logger.infoNotify(reason); - OnBoardingPanel.postMessage({ - command: "set_selected_download_state", - state: "empty", - }); - }); - } - }); - }); -} - -export function createEspIdfLinkList(data: Buffer, splitString: string) { - const versionZip = - "https://github.com/espressif/esp-idf/releases/download/IDFZIPFileVersion/esp-idf-IDFZIPFileVersion.zip"; - const mirrorZip = - "https://dl.espressif.com/dl/esp-idf/releases/esp-idf-IDFZIPFileVersion.zip"; - const versionRegex = /\b(IDFZIPFileVersion)\b/g; - const espIdfMasterZip = - "https://github.com/espressif/esp-idf/archive/master.zip"; - const preReleaseRegex = /v.+-rc/g; - const betaRegex = /v.+-beta/g; - - const versionList = data.toString().trim().split(splitString); - const downloadList: IEspIdfLink[] = versionList.map((version) => { - if (version.startsWith("release/")) { - const versionRoot = version.replace("release/", ""); - const versionForRelease = versionList.find((ver) => - ver.startsWith(versionRoot) - ); - if (versionForRelease) { - return { - filename: `esp-idf-${versionForRelease}.zip`, - name: version + " (release branch)", - url: versionZip.replace(versionRegex, versionForRelease), - mirror: mirrorZip.replace(versionRegex, versionForRelease), - }; - } else { - return { - filename: `${version}`, - name: version + " (release branch)", - url: "", - mirror: "", - }; - } - } else if (version.startsWith("v")) { - return { - filename: `esp-idf-${version}.zip`, - name: version + " (release version)", - url: versionZip.replace(versionRegex, version), - mirror: mirrorZip.replace(versionRegex, version), - }; - } else if (preReleaseRegex.test(version)) { - return { - filename: `esp-idf-${version}.zip`, - name: version + " (pre-release version)", - url: versionZip.replace(versionRegex, version), - mirror: mirrorZip.replace(versionRegex, version), - }; - } else if (version === "master") { - return { - filename: `master`, - name: version + " (development branch)", - url: espIdfMasterZip, - mirror: espIdfMasterZip, - }; - } else if (betaRegex.test(version)) { - return { - filename: `esp-idf-${version}.zip`, - name: version + " (beta version)", - url: versionZip.replace(versionRegex, version), - mirror: mirrorZip.replace(versionRegex, version), - }; - } - }); - return downloadList; -} - -export function downloadEspIdfVersionList( - downloadManager: DownloadManager, - extensionPath: string -) { - const idfVersionList = path.join(tmpdir(), "idf_versions.txt"); - const versionsUrl = "https://dl.espressif.com/dl/esp-idf/idf_versions.txt"; - - return new Promise(async (resolve, reject) => { - await downloadManager - .downloadFile(versionsUrl, 0, tmpdir()) - .then((message) => { - Logger.info(message.statusMessage); - fs.readFile(idfVersionList, (err, data) => { - if (err) { - OutputChannel.appendLine( - `Error opening esp-idf version list file. ${err.message}` - ); - reject(err.message); - } - const downloadList: IEspIdfLink[] = createEspIdfLinkList(data, "\n"); - resolve(downloadList); - }); - }) - .catch((err) => { - // reject(err); - const idfVersionListFallBack = path.join( - extensionPath, - "idf_versions.txt" - ); - fs.readFile(idfVersionListFallBack, (error, data) => { - if (error) { - OutputChannel.appendLine( - `Error opening esp-idf fallback version list file. ${err.message}` - ); - reject(err.message); - } - const downloadList: IEspIdfLink[] = createEspIdfLinkList(data, EOL); - resolve(downloadList); - }); - }); - }); -} - -export function downloadEspIdfByClone( - installDirectoryPath: string, - branchName: string, - pkgProgress: PackageProgress -): Promise { - const espIdfGithubRepo = "https://github.com/espressif/esp-idf.git"; - return new Promise((resolve, reject) => { - const gitCloneProcess = spawn( - `git`, - [ - "clone", - "--recursive", - "--progress", - "-b", - branchName, - espIdfGithubRepo, - ], - { cwd: installDirectoryPath } - ); - gitCloneProcess.stderr.on("data", (data) => { - OutputChannel.appendLine(data.toString()); - const errRegex = /\b(Error)\b/g; - if (errRegex.test(data.toString())) { - reject(data.toString()); - } - const progressRegex = /(\d+)(\.\d+)?%/g; - const matches = data.toString().match(progressRegex); - if (pkgProgress && matches) { - pkgProgress.Progress = matches[matches.length - 1]; - } else if (data.toString().indexOf("Cloning into") !== -1) { - pkgProgress.ProgressDetail = " " + data.toString(); - } - }); - - gitCloneProcess.stdout.on("data", (data) => { - OutputChannel.appendLine(data.toString()); - const progressRegex = /(\d+)(\.\d+)?%/g; - const matches = data.toString().match(progressRegex); - if (pkgProgress && matches) { - pkgProgress.Progress = matches[matches.length - 1]; - } else if (data.toString().indexOf("Cloning into") !== -1) { - pkgProgress.ProgressDetail = " " + data.toString(); - } - }); - - gitCloneProcess.on("exit", (code, signal) => { - if (!signal && code !== 0) { - OutputChannel.appendLine(`ESP-IDF master clone has exit with ${code}`); - reject(`ESP-IDF master clone has exit with ${code}`); - } - resolve(); - }); - }); -} - -export async function getEspIdfVersions(extensionPath: string) { - const downloadManager = new DownloadManager(extensionPath); - const versionList = await downloadEspIdfVersionList( - downloadManager, - extensionPath - ); - const manualVersion = { - name: "Find ESP-IDF in your system", - filename: "manual", - } as IEspIdfLink; - versionList.push(manualVersion); - return versionList; -} diff --git a/src/onboarding/onboardingInit.ts b/src/onboarding/onboardingInit.ts deleted file mode 100644 index c4eb240aa..000000000 --- a/src/onboarding/onboardingInit.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { Progress } from "vscode"; -import { IdfToolsManager } from "../idfToolsManager"; -import { OutputChannel } from "../logger/outputChannel"; -import { PlatformInformation } from "../PlatformInformation"; -import * as utils from "../utils"; -import { getEspIdfVersions, IEspIdfLink } from "./espIdfDownload"; -import { getPythonList } from "./pythonReqsManager"; - -export interface IOnboardingArgs { - expectedEnvVars: {}; - espIdfVersionList: IEspIdfLink[]; - gitVersion: string; - idfToolsManager: IdfToolsManager; - pythonVersions: string[]; -} - -export async function getOnboardingInitialValues( - extensionPath: string, - progress: Progress<{ message: string; increment: number }> -) { - const platformInfo = await PlatformInformation.GetPlatformInformation(); - const toolsJsonPath = await utils.getToolsJsonPath(extensionPath); - const toolsJson = JSON.parse(utils.readFileSync(toolsJsonPath)); - progress.report({ - increment: 10, - message: "Loading ESP-IDF Tools information...", - }); - const idfToolsManager = new IdfToolsManager( - toolsJson, - platformInfo, - OutputChannel.init() - ); - progress.report({ increment: 20, message: "Getting ESP-IDF versions..." }); - const espIdfVersionList = await getEspIdfVersions(extensionPath); - progress.report({ increment: 20, message: "Getting Python versions..." }); - const pythonVersions = await getPythonList(extensionPath); - const gitVersion = await utils.checkGitExists(extensionPath); - progress.report({ increment: 20, message: "Preparing onboarding view..." }); - const expectedEnvVars = await idfToolsManager.getListOfReqEnvVars(); - return { - espIdfVersionList, - expectedEnvVars, - gitVersion, - idfToolsManager, - pythonVersions, - } as IOnboardingArgs; -} diff --git a/src/onboarding/pythonReqsManager.ts b/src/onboarding/pythonReqsManager.ts deleted file mode 100644 index 8f4628944..000000000 --- a/src/onboarding/pythonReqsManager.ts +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { pathExists } from "fs-extra"; -import * as path from "path"; -import { ConfigurationTarget, WorkspaceFolder } from "vscode"; -import * as idfConf from "../idfConfiguration"; -import { Logger } from "../logger/logger"; -import { OutputChannel } from "../logger/outputChannel"; -import * as pythonManager from "../pythonManager"; -import * as utils from "../utils"; -import { OnBoardingPanel } from "./OnboardingPanel"; -import { PyReqLog } from "./PyReqLog"; - -export async function checkPythonRequirements( - workingDir: string, - selectedWorkspaceFolder: WorkspaceFolder, - pythonBinPath: string -) { - const canCheck = await checkPythonPipExists(pythonBinPath, workingDir); - if (!canCheck) { - OnBoardingPanel.postMessage({ - command: "response_py_req_check", - py_req_log: "Python or pip have not been found in your environment.", - }); - return; - } - const espIdfPath = idfConf.readParameter( - "idf.espIdfPath", - selectedWorkspaceFolder - ) as string; - const idfToolsPath = idfConf.readParameter( - "idf.toolsPath", - selectedWorkspaceFolder - ) as string; - const requirements = path.join(espIdfPath, "requirements.txt"); - const debugAdapterRequirements = path.join( - utils.extensionContext.extensionPath, - "esp_debug_adapter", - "requirements.txt" - ); - const pythonBin = await pythonManager.getPythonBinToUse( - espIdfPath, - idfToolsPath, - pythonBinPath - ); - await utils - .startPythonReqsProcess(pythonBin, espIdfPath, requirements) - .then(async (pyReqLog) => { - const resultLog = `Checking ESP-IDF Python requirements using ${pythonBinPath}\n${pyReqLog}`; - OutputChannel.appendLine(resultLog); - Logger.info(resultLog); - OnBoardingPanel.postMessage({ - command: "response_py_req_check", - py_req_log: resultLog, - }); - await utils - .startPythonReqsProcess(pythonBin, espIdfPath, debugAdapterRequirements) - .then(async (adapterReqLog) => { - const adapterResultLog = `Checking Debug Adapter requirements using ${pythonBin}\n${adapterReqLog}`; - OutputChannel.appendLine(adapterResultLog); - Logger.info(adapterResultLog); - OnBoardingPanel.postMessage({ - command: "response_py_req_check", - py_req_log: resultLog + adapterResultLog, - }); - if ( - pyReqLog.indexOf("are not satisfied") < 0 && - adapterReqLog.indexOf("are not satisfied") < 0 - ) { - OnBoardingPanel.postMessage({ command: "set_py_setup_finish" }); - } - }); - }) - .catch((reason) => { - if (reason.message) { - Logger.error(reason.message, reason); - OnBoardingPanel.postMessage({ - command: "response_py_req_check", - py_req_log: reason.message, - }); - } else { - OnBoardingPanel.postMessage({ - command: "response_py_req_check", - py_req_log: reason, - }); - } - }); -} - -export async function installPythonRequirements( - espIdfPath: string, - workingDir: string, - confTarget: ConfigurationTarget, - selectedWorkspaceFolder: WorkspaceFolder, - systemPythonPath: string -) { - const canCheck = await checkPythonPipExists(systemPythonPath, workingDir); - if (!canCheck) { - sendPyReqLog("Python or pip have not been found in your environment."); - OutputChannel.appendLine( - "Python or pip have not been found in your environment." - ); - return; - } - const logTracker = new PyReqLog(sendPyReqLog); - return await pythonManager - .installPythonEnv( - espIdfPath, - workingDir, - logTracker, - systemPythonPath, - OutputChannel.init() - ) - .then(async (virtualEnvPythonBin) => { - if (virtualEnvPythonBin) { - await idfConf.writeParameter( - "idf.pythonBinPath", - virtualEnvPythonBin, - confTarget, - selectedWorkspaceFolder - ); - OutputChannel.appendLine("Python requirements has been installed."); - if (logTracker.Log.indexOf("Exception") < 0) { - OnBoardingPanel.postMessage({ command: "set_py_setup_finish" }); - OnBoardingPanel.postMessage({ - command: "load_python_bin_path", - pythonBinPath: virtualEnvPythonBin, - }); - } - return virtualEnvPythonBin; - } else { - OutputChannel.appendLine("Python requirements has not been installed."); - } - }) - .catch((reason) => { - if (reason.message) { - OutputChannel.appendLine(reason.message); - sendPyReqLog(reason.message); - } else { - OutputChannel.appendLine(reason); - sendPyReqLog(reason); - } - }); -} - -export async function checkPythonPipExists( - pythonBinPath: string, - workingDir: string -) { - const pyExists = - pythonBinPath === "python" ? true : await pathExists(pythonBinPath); - const doestPythonExists = await pythonManager.checkPythonExists( - pythonBinPath, - workingDir - ); - const doesPipExists = await pythonManager.checkPipExists( - pythonBinPath, - workingDir - ); - return pyExists && doestPythonExists && doesPipExists; -} - -export async function getPythonList(workingDir: string) { - const pyVersionList = await pythonManager.getPythonBinList(workingDir); - pyVersionList.push("Provide python executable path"); - return pyVersionList; -} - -export function sendPyReqLog(log: string) { - OnBoardingPanel.postMessage({ - command: "response_py_req_install", - py_req_log: log, - }); -} diff --git a/src/onboarding/toolsInstall.ts b/src/onboarding/toolsInstall.ts deleted file mode 100644 index afb6373b9..000000000 --- a/src/onboarding/toolsInstall.ts +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import * as path from "path"; -import * as vscode from "vscode"; -import { DownloadManager } from "../downloadManager"; -import * as idfConf from "../idfConfiguration"; -import { IdfToolsManager } from "../idfToolsManager"; -import { InstallManager } from "../installManager"; -import { Logger } from "../logger/logger"; -import { OutputChannel } from "../logger/outputChannel"; -import { PackageProgress } from "../PackageProgress"; -import { OnBoardingPanel } from "./OnboardingPanel"; -import { installPythonRequirements } from "./pythonReqsManager"; -import { - sendChecksumResult, - sendDownloadDetail, - sendDownloadFailed, - sendDownloadPercentage, -} from "./updateViewMethods"; - -export async function downloadToolsInIdfToolsPath( - espIdfPath: string, - idfToolsManager: IdfToolsManager, - installDir: string, - confTarget: vscode.ConfigurationTarget, - selectedWorkspaceFolder: vscode.WorkspaceFolder, - systemPythonPath: string -) { - // In case IDF tools path is of the form path1:path2, tell the user for single path - const manyPathsInIdfTools = installDir.split(path.delimiter); - if (manyPathsInIdfTools.length > 1) { - Logger.infoNotify("Please introduce a single path"); - return; - } - const downloadManager = new DownloadManager(installDir); - const installManager = new InstallManager(installDir); - await vscode.window.withProgress( - { - cancellable: false, - location: vscode.ProgressLocation.Notification, - title: "ESP-IDF Tools", - }, - async ( - progress: vscode.Progress<{ message: string; increment?: number }> - ) => { - const packagesProgress = await idfToolsManager - .getPackageList() - .then((pkgs) => { - return pkgs.map((pkg) => { - return new PackageProgress( - pkg.name, - sendDownloadPercentage, - sendChecksumResult, - sendDownloadDetail, - sendDownloadFailed - ); - }); - }); - OutputChannel.appendLine(""); - Logger.info(""); - await downloadManager.downloadPackages( - idfToolsManager, - progress, - packagesProgress - ); - OutputChannel.appendLine(""); - Logger.info(""); - await installManager.installPackages(idfToolsManager, progress); - OnBoardingPanel.postMessage({ command: "set_tools_setup_finish" }); - progress.report({ - message: `Installing python virtualenv and ESP-IDF python requirements...`, - }); - OutputChannel.appendLine( - "Installing python virtualenv and ESP-IDF python requirements..." - ); - Logger.info( - "Installing python virtualenv and ESP-IDF python requirements..." - ); - const pyEnvResult = await installPythonRequirements( - espIdfPath, - installDir, - confTarget, - selectedWorkspaceFolder, - systemPythonPath - ); - let exportPaths = await idfToolsManager.exportPaths( - path.join(installDir, "tools") - ); - const pythonBinPath = - pyEnvResult || - (idfConf.readParameter( - "idf.pythonBinPath", - selectedWorkspaceFolder - ) as string); - // Append System Python and Virtual Env Python to PATH - exportPaths = - path.dirname(pythonBinPath) + - path.delimiter + - path.dirname(systemPythonPath) + - path.delimiter + - exportPaths; - const exportVars = await idfToolsManager.exportVars( - path.join(installDir, "tools") - ); - OutputChannel.appendLine(""); - Logger.info(""); - OutputChannel.appendLine( - "The following paths should be added to env PATH" - ); - Logger.info("The following paths should be added to env PATH"); - OutputChannel.appendLine(exportPaths); - Logger.info(exportPaths); - OutputChannel.appendLine(""); - Logger.info(""); - await idfConf.writeParameter( - "idf.customExtraPaths", - exportPaths, - confTarget, - selectedWorkspaceFolder - ); - await idfConf.writeParameter( - "idf.customExtraVars", - exportVars, - confTarget, - selectedWorkspaceFolder - ); - OnBoardingPanel.postMessage({ - command: "load_custom_paths", - custom_vars: JSON.parse(exportVars), - custom_paths: exportPaths, - }); - } - ); -} diff --git a/src/onboarding/updateViewMethods.ts b/src/onboarding/updateViewMethods.ts deleted file mode 100644 index fd5677b22..000000000 --- a/src/onboarding/updateViewMethods.ts +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { OnBoardingPanel } from "./OnboardingPanel"; - -export function sendDownloadPercentage(pkgName, updatedPercentage) { - OnBoardingPanel.postMessage({ - command: "update_pkgs_download_percentage", - updatedPkgDownloadStatus: { id: pkgName, progress: updatedPercentage }, - }); -} - -export function sendChecksumResult(pkgName, updatedChecksumResult) { - OnBoardingPanel.postMessage({ - command: "checksum_result", - isChecksumEqual: { id: pkgName, hashResult: updatedChecksumResult }, - }); -} - -export function sendDownloadDetail(pkgName, updatedDetail) { - OnBoardingPanel.postMessage({ - command: "update_pkg_download_detail", - updatedPkgDownloadDetail: { id: pkgName, progressDetail: updatedDetail }, - }); -} - -export function sendDownloadFailed(pkgName, failedFlag) { - OnBoardingPanel.postMessage({ - command: "set_pkg_download_failed", - updatedPkgFailed: { id: pkgName, hasFailed: failedFlag }, - }); -} - -export function sendDownloadEspIdfPercentage(version, updatedPercentage) { - OnBoardingPanel.postMessage({ - command: "update_espidf_download_percentage", - updatedIdfDownloadStatus: { id: version, progress: updatedPercentage }, - }); -} - -export function sendDownloadEspIdfDetail(version, updatedDetail) { - OnBoardingPanel.postMessage({ - command: "update_espidf_download_detail", - updatedIdfDownloadDetail: { id: version, progressDetail: updatedDetail }, - }); -} diff --git a/src/pythonManager.ts b/src/pythonManager.ts index acc40c661..dcdb60eb5 100644 --- a/src/pythonManager.ts +++ b/src/pythonManager.ts @@ -12,53 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { PyReqLog } from "./PyReqLog"; +import { CancellationToken, OutputChannel } from "vscode"; +import * as utils from "./utils"; import * as del from "del"; import { constants, pathExists } from "fs-extra"; import { EOL } from "os"; -import * as path from "path"; -import { OutputChannel } from "vscode"; import { Logger } from "./logger/logger"; -import { PyReqLog } from "./onboarding/PyReqLog"; -import * as utils from "./utils"; - -export async function getPythonBinToUse( - espDir: string, - idfToolsDir: string, - pythonBinPath: string -): Promise { - return getPythonEnvPath(espDir, idfToolsDir, pythonBinPath).then( - (pyEnvPath) => { - const pythonInEnv = - process.platform === "win32" - ? path.join(pyEnvPath, "Scripts", "python.exe") - : path.join(pyEnvPath, "bin", "python"); - return pathExists(pythonInEnv).then((haveVirtualPython) => { - return haveVirtualPython ? pythonInEnv : pythonBinPath; - }); - } - ); -} +import path from "path"; export async function installPythonEnv( espDir: string, idfToolsDir: string, pyTracker: PyReqLog, pythonBinPath: string, - channel?: OutputChannel + channel?: OutputChannel, + cancelToken?: CancellationToken ) { - // Check if already in virtualenv and install const isInsideVirtualEnv = await utils.execChildProcess( - `"${pythonBinPath}" -c "import sys; print(hasattr(sys, 'real_prefix'))"`, + `"${pythonBinPath}" -c "import sys; print(hasattr(sys, 'real_prefix') or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))"`, idfToolsDir, channel ); if (isInsideVirtualEnv.replace(EOL, "") === "True") { - const ignoreVirtualEnvPythonMsg = `Can't use virtualenv Python ${pythonBinPath}. Please use system wide Python executable.`; - Logger.infoNotify(ignoreVirtualEnvPythonMsg); + const inVenvMsg = `Using existing virtual environment ${pythonBinPath}. Installing Python requirements...`; + pyTracker.Log = inVenvMsg; if (channel) { - channel.appendLine(ignoreVirtualEnvPythonMsg); + channel.appendLine(inVenvMsg); } - return; + await installReqs(espDir, pythonBinPath, idfToolsDir, pyTracker, channel); + return pythonBinPath; } const pyEnvPath = await getPythonEnvPath(espDir, idfToolsDir, pythonBinPath); @@ -67,50 +50,21 @@ export async function installPythonEnv( ? ["Scripts", "python.exe"] : ["bin", "python"]; const virtualEnvPython = path.join(pyEnvPath, ...pyDir); - const requirements = path.join(espDir, "requirements.txt"); - const reqDoesNotExists = " doesn't exist. Make sure the path is correct."; - if (!utils.canAccessFile(requirements, constants.R_OK)) { - Logger.warnNotify(requirements + reqDoesNotExists); - if (channel) { - channel.appendLine(requirements + reqDoesNotExists); - } - return; - } - const extensionRequirements = path.join( - utils.extensionContext.extensionPath, - "requirements.txt" - ); - if (!utils.canAccessFile(extensionRequirements, constants.R_OK)) { - Logger.warnNotify(extensionRequirements + reqDoesNotExists); - if (channel) { - channel.appendLine(extensionRequirements + reqDoesNotExists); - } - return; - } - const debugAdapterRequirements = path.join( - utils.extensionContext.extensionPath, - "esp_debug_adapter", - "requirements.txt" - ); - if (!utils.canAccessFile(debugAdapterRequirements, constants.R_OK)) { - Logger.warnNotify(debugAdapterRequirements + reqDoesNotExists); - if (channel) { - channel.appendLine(debugAdapterRequirements + reqDoesNotExists); - } - return; - } const creatEnvMsg = `Creating a new Python environment in ${pyEnvPath} ...\n`; - const installPyPkgsMsg = `Installing ESP-IDF python packages in ${pyEnvPath} ...\n`; - const installDAPyPkgsMsg = `Installing ESP-IDF Debug Adapter python packages in ${pyEnvPath} ...\n`; if ( pythonBinPath.indexOf(virtualEnvPython) < 0 && utils.fileExists(virtualEnvPython) ) { - await del(pyEnvPath, { force: true }).catch((err) => { - Logger.errorNotify("Error deleting virtualenv files", err); - }); + await installExtensionPyReqs( + virtualEnvPython, + idfToolsDir, + pyTracker, + channel, + cancelToken + ); + return virtualEnvPython; } pyTracker.Log = creatEnvMsg; @@ -118,91 +72,190 @@ export async function installPythonEnv( channel.appendLine(creatEnvMsg); } - const pythonVersion = ( - await utils.execChildProcess( - `"${pythonBinPath}" -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))"`, - espDir - ) - ).replace(/(\n|\r|\r\n)/gm, ""); - let envModule: string; - if (pythonVersion.localeCompare("3.3") >= 0) { - envModule = "venv"; - } else { - envModule = "virtualenv"; - const virtualEnvInstallCmd = `"${pythonBinPath}" -m pip install virtualenv --user`; - const virtualEnvInstallResult = await utils.execChildProcess( - virtualEnvInstallCmd, - idfToolsDir, - channel - ); - pyTracker.Log = virtualEnvInstallResult; - pyTracker.Log = "\n"; - if (channel) { - channel.appendLine(virtualEnvInstallResult + "\n"); + try { + const pythonVersion = ( + await utils.execChildProcess( + `"${pythonBinPath}" -c "import sys; print('{}.{}'.format(sys.version_info.major, sys.version_info.minor))"`, + espDir, + channel, + undefined, + cancelToken + ) + ).replace(/(\n|\r|\r\n)/gm, ""); + envModule = + pythonVersion.localeCompare("3.3") !== -1 ? "venv" : "virtualenv"; + if (envModule.indexOf("virtualenv") !== -1) { + const checkVirtualEnv = await utils.execChildProcess( + `"${pythonBinPath}" -c "import virtualenv"`, + idfToolsDir, + channel, + undefined, + cancelToken + ); + } + } catch (error) { + if (error && error.message.indexOf("ModuleNotFoundError") !== -1) { + await execProcessWithLog( + `"${pythonBinPath}" -m pip install --user virtualenv`, + idfToolsDir, + pyTracker, + channel, + undefined, + cancelToken + ); } } - - const createVirtualEnvResult = await utils.execChildProcess( + await execProcessWithLog( `"${pythonBinPath}" -m ${envModule} "${pyEnvPath}"`, idfToolsDir, - channel + pyTracker, + channel, + undefined, + cancelToken ); - pyTracker.Log = createVirtualEnvResult; - pyTracker.Log = "\n"; - if (channel) { - channel.appendLine(createVirtualEnvResult + "\n"); + await installReqs( + espDir, + virtualEnvPython, + idfToolsDir, + pyTracker, + channel, + cancelToken + ); + return virtualEnvPython; +} + +export async function installReqs( + espDir: string, + virtualEnvPython: string, + idfToolsDir: string, + pyTracker?: PyReqLog, + channel?: OutputChannel, + cancelToken?: CancellationToken +) { + const installPyPkgsMsg = `Installing ESP-IDF python packages in ${virtualEnvPython} ...\n`; + if (pyTracker) { + pyTracker.Log = installPyPkgsMsg; } - pyTracker.Log = installPyPkgsMsg; - // Install wheel and other required packages - const systemReqs = await utils.execChildProcess( + await execProcessWithLog( `"${virtualEnvPython}" -m pip install wheel`, - pyEnvPath, - channel + idfToolsDir, + pyTracker, + channel, + undefined, + cancelToken ); - pyTracker.Log = systemReqs; - pyTracker.Log = "\n"; - if (channel) { - channel.appendLine(systemReqs + "\n"); + const requirements = path.join(espDir, "requirements.txt"); + const reqDoesNotExists = " doesn't exist. Make sure the path is correct."; + if (!utils.canAccessFile(requirements, constants.R_OK)) { + Logger.warnNotify(requirements + reqDoesNotExists); + if (channel) { + channel.appendLine(requirements + reqDoesNotExists); + } + return; } - // ESP-IDF Python Requirements const modifiedEnv = Object.assign({}, process.env); modifiedEnv.IDF_PATH = espDir; - const espIdfReqInstallResult = await utils.execChildProcess( - `"${virtualEnvPython}" -m pip install -r "${requirements}"`, - pyEnvPath, + await execProcessWithLog( + `"${virtualEnvPython}" -m pip install --no-warn-script-location -r "${requirements}"`, + idfToolsDir, + pyTracker, channel, { env: modifiedEnv } ); - pyTracker.Log = espIdfReqInstallResult; - pyTracker.Log = "\n"; + await installExtensionPyReqs( + virtualEnvPython, + idfToolsDir, + pyTracker, + channel, + cancelToken + ); +} + +export async function installExtensionPyReqs( + virtualEnvPython: string, + idfToolsDir: string, + pyTracker?: PyReqLog, + channel?: OutputChannel, + cancelToken?: CancellationToken +) { + const reqDoesNotExists = " doesn't exist. Make sure the path is correct."; + const debugAdapterRequirements = path.join( + utils.extensionContext.extensionPath, + "esp_debug_adapter", + "requirements.txt" + ); + if (!utils.canAccessFile(debugAdapterRequirements, constants.R_OK)) { + Logger.warnNotify(debugAdapterRequirements + reqDoesNotExists); + if (channel) { + channel.appendLine(debugAdapterRequirements + reqDoesNotExists); + } + return; + } + const extensionRequirements = path.join( + utils.extensionContext.extensionPath, + "requirements.txt" + ); + if (!utils.canAccessFile(extensionRequirements, constants.R_OK)) { + Logger.warnNotify(extensionRequirements + reqDoesNotExists); + if (channel) { + channel.appendLine(extensionRequirements + reqDoesNotExists); + } + return; + } + const installDAPyPkgsMsg = `Installing ESP-IDF Debug Adapter python packages in ${virtualEnvPython} ...\n`; + const installExtensionPyPkgsMsg = `Installing ESP-IDF extension python packages in ${virtualEnvPython} ...\n`; + if (pyTracker) { + pyTracker.Log = installExtensionPyPkgsMsg; + } if (channel) { - channel.appendLine(espIdfReqInstallResult + "\n"); + channel.appendLine(installExtensionPyPkgsMsg + "\n"); } - // Extension Python requirements - const extensionInstallResult = await utils.execChildProcess( - `"${virtualEnvPython}" -m pip install -r "${extensionRequirements}"`, - pyEnvPath, - channel + await execProcessWithLog( + `"${virtualEnvPython}" -m pip install --no-warn-script-location -r "${extensionRequirements}"`, + idfToolsDir, + pyTracker, + channel, + undefined, + cancelToken ); - pyTracker.Log = extensionInstallResult; - pyTracker.Log = "\n"; + if (pyTracker) { + pyTracker.Log = installDAPyPkgsMsg; + } if (channel) { - channel.appendLine(extensionInstallResult + "\n"); + channel.appendLine(installDAPyPkgsMsg + "\n"); } - // Debug Adapter Python Requirements - pyTracker.Log = installDAPyPkgsMsg; - const pyDAReqInstallResult = await utils.execChildProcess( - `"${virtualEnvPython}" -m pip install -r "${debugAdapterRequirements}"`, - pyEnvPath, - channel + await execProcessWithLog( + `"${virtualEnvPython}" -m pip install --no-warn-script-location -r "${debugAdapterRequirements}"`, + idfToolsDir, + pyTracker, + channel, + undefined, + cancelToken + ); +} + +export async function execProcessWithLog( + cmd: string, + workDir: string, + pyTracker?: PyReqLog, + channel?: OutputChannel, + opts?: { env: NodeJS.ProcessEnv }, + cancelToken?: CancellationToken +) { + const processResult = await utils.execChildProcess( + cmd, + workDir, + channel, + opts, + cancelToken ); - pyTracker.Log = pyDAReqInstallResult; - pyTracker.Log = "\n"; + if (pyTracker) { + pyTracker.Log = processResult + "\n"; + } if (channel) { - channel.appendLine(pyDAReqInstallResult + "\n"); + channel.appendLine(processResult + "\n"); } - return virtualEnvPython; } export async function getPythonEnvPath( @@ -224,57 +277,53 @@ export async function getPythonEnvPath( } export async function checkPythonExists(pythonBin: string, workingDir: string) { - return await utils - .execChildProcess(`"${pythonBin}" --version`, workingDir) - .then((result) => { - if (result) { - const match = result.match(/Python\s\d+(.\d+)?(.\d+)?/g); - if (match && match.length > 0) { - return true; - } else { - Logger.errorNotify( - "Python is not found in current environment", - Error("") - ); - return false; - } + try { + const versionResult = await utils.execChildProcess( + `"${pythonBin}" --version`, + workingDir + ); + if (versionResult) { + const match = versionResult.match(/Python\s\d+(.\d+)?(.\d+)?/g); + if (match && match.length > 0) { + return true; } - }) - .catch((err) => { - if (err.message) { - const match = err.message.match(/Python\s\d+.\d+.\d+/g); - if (match && match.length > 0) { - return true; - } else { - return false; - } + } + } catch (error) { + if (error && error.message) { + const match = error.message.match(/Python\s\d+.\d+.\d+/g); + if (match && match.length > 0) { + return true; } - Logger.errorNotify("Python is not found in current environment", err); - return false; - }); + } + const newErr = + error && error.message + ? error + : new Error("Python is not found in current environment"); + Logger.errorNotify(newErr.message, newErr); + } + return false; } -export async function checkPipExists(pythonBin: string, workingDir: string) { - return await utils - .execChildProcess(`"${pythonBin}" -m pip --version`, workingDir) - .then((result) => { - if (result) { - const match = result.match(/pip\s\d+(.\d+)?(.\d+)?/g); - if (match && match.length > 0) { - return true; - } else { - Logger.errorNotify( - "Python pip is not found in current environment", - Error("") - ); - return false; - } +export async function checkPipExists(pyBinPath: string, workingDir: string) { + try { + const pipResult = await utils.execChildProcess( + `"${pyBinPath}" -m pip --version`, + workingDir + ); + if (pipResult) { + const match = pipResult.match(/pip\s\d+(.\d+)?(.\d+)?/g); + if (match && match.length > 0) { + return true; } - }) - .catch((err) => { - Logger.errorNotify("Python pip is not found in current environment", err); - return false; - }); + } + } catch (error) { + const newErr = + error && error.message + ? error + : new Error("Pip is not found in current environment"); + Logger.errorNotify(newErr.message, newErr); + } + return false; } export async function getPythonBinList(workingDir: string) { @@ -286,18 +335,19 @@ export async function getPythonBinList(workingDir: string) { } export async function getUnixPythonList(workingDir: string) { - return await utils - .execChildProcess("which -a python python3", workingDir) - .then((result) => { - if (result) { - const resultList = result.trim().split("\n"); - return resultList; - } - }) - .catch((err) => { - Logger.errorNotify("Error looking for python in system", err); - return ["Not found"]; - }); + try { + const pyVersionsStr = await utils.execChildProcess( + "which -a python; which -a python3", + workingDir + ); + if (pyVersionsStr) { + const resultList = pyVersionsStr.trim().split("\n"); + return resultList; + } + } catch (error) { + Logger.errorNotify("Error looking for python in system", error); + return ["Not found"]; + } } export async function getPythonBinListWindows(workingDir: string) { @@ -308,74 +358,57 @@ export async function getPythonBinListWindows(workingDir: string) { "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432NODE\\PYTHON", ]; for (const root of registryRootLocations) { - await utils - .execChildProcess("reg query " + root, workingDir) - .then(async (result) => { - if (result.trim() === "") { - return; + try { + const rootResult = await utils.execChildProcess( + "reg query " + root, + workingDir + ); + if (!rootResult.trim()) { + continue; + } + const companies = rootResult.trim().split("\r\n"); + for (const company of companies) { + if (company.indexOf("PyLauncher") !== -1) { + continue; } - const companies = result.trim().split("\r\n"); - for (const company of companies) { - await utils - .execChildProcess("reg query " + company, workingDir) - .then(async (companyTags) => { - if (companyTags.trim() === "") { - return; - } - if (company.indexOf("PyLauncher") !== -1) { - return; + const companyResult = await utils.execChildProcess( + "reg query " + company, + workingDir + ); + if (!companyResult.trim()) { + continue; + } + const tags = companyResult.trim().split("\r\n"); + const keyValues = await utils.execChildProcess( + "reg query " + tags[tags.length - 1], + workingDir + ); + if (!keyValues.trim()) { + continue; + } + const values = keyValues.trim().split("\r\n"); + for (const val of values) { + if (val.indexOf("InstallPath") !== -1) { + const installPaths = await utils.execChildProcess( + "reg query " + val, + workingDir + ); + const binPaths = installPaths.trim().split("\r\n"); + for (const iPath of binPaths) { + const trimPath = iPath.trim().split(/\s{2,}/); + if (trimPath[0] === "ExecutablePath") { + paths.push(trimPath[trimPath.length - 1]); } - const tags = companyTags.trim().split("\r\n"); - utils - .execChildProcess( - "reg query " + tags[tags.length - 1], - workingDir - ) - .then((keyValues) => { - const values = keyValues.trim().split("\r\n"); - for (const val of values) { - if (val.indexOf("InstallPath") !== -1) { - utils - .execChildProcess("reg query " + val, workingDir) - .then((installPaths) => { - const binPaths = installPaths.trim().split("\r\n"); - for (const iPath of binPaths) { - const trimPath = iPath.trim().split(/\s{2,}/); - if (trimPath[0] === "ExecutablePath") { - paths.push(trimPath[trimPath.length - 1]); - } - } - }) - .catch((err) => { - Logger.error( - "Error looking for python in windows system", - err - ); - }); - } - } - }) - .catch((err) => { - Logger.error( - "Error looking for python in windows system", - err - ); - }); - }) - .catch((err) => { - Logger.error("Error looking for python in windows system", err); - }); + } + } } - }) - .catch((err) => { - Logger.error("Error looking for python in windows", err); - }); + } + } catch (error) { + Logger.error("Error looking for python in windows", error); + } } if (paths.length === 0) { - Logger.error( - "Error looking for python in windows", - new Error("Installed Python not found in registry") - ); + return ["Not found"]; } return paths; } diff --git a/src/setup/SetupPanel.ts b/src/setup/SetupPanel.ts new file mode 100644 index 000000000..711ca7ca6 --- /dev/null +++ b/src/setup/SetupPanel.ts @@ -0,0 +1,487 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { LocDictionary } from "../localizationDictionary"; +import { ISetupInitArgs, saveSettings } from "./setupInit"; +import { + IdfMirror, + IEspIdfLink, + IEspIdfTool, + SetupMode, + StatusType, +} from "../views/setup/types"; +import * as idfConf from "../idfConfiguration"; +import { ensureDir } from "fs-extra"; +import path from "path"; +import vscode from "vscode"; +import { expressInstall } from "./espIdfDownloadStep"; +import { IdfToolsManager } from "../idfToolsManager"; +import { installExtensionPyReqs } from "./installPyReqs"; +import { OutputChannel } from "../logger/outputChannel"; +import { Logger } from "../logger/logger"; +import { createPyReqs } from "./pyReqsInstallStep"; +import { downloadIdfTools } from "./toolsDownloadStep"; + +const locDic = new LocDictionary("SetupPanel"); + +export class SetupPanel { + public static currentPanel: SetupPanel | undefined; + + public static createOrShow( + extensionPath: string, + setupArgs?: ISetupInitArgs + ) { + const column = vscode.window.activeTextEditor + ? vscode.window.activeTextEditor.viewColumn + : vscode.ViewColumn.One; + if (SetupPanel.currentPanel) { + SetupPanel.currentPanel.panel.reveal(column); + } else { + SetupPanel.currentPanel = new SetupPanel( + extensionPath, + column, + setupArgs + ); + } + } + + public static isCreatedAndHidden(): boolean { + return ( + SetupPanel.currentPanel && SetupPanel.currentPanel.panel.visible === false + ); + } + + public static postMessage(content: any) { + if (SetupPanel.currentPanel) { + SetupPanel.currentPanel.panel.webview.postMessage(content); + } + } + + private static readonly viewType = "setupPanel"; + private readonly panel: vscode.WebviewPanel; + private disposables: vscode.Disposable[] = []; + + constructor( + private extensionPath: string, + column: vscode.ViewColumn, + setupArgs: ISetupInitArgs + ) { + const setupPanelTitle = locDic.localize( + "setupPanel.panelName", + "ESP-IDF Setup" + ); + + this.panel = vscode.window.createWebviewPanel( + SetupPanel.viewType, + setupPanelTitle, + column, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [ + vscode.Uri.file(path.join(this.extensionPath, "dist", "views")), + ], + } + ); + this.panel.iconPath = vscode.Uri.file( + path.join(extensionPath, "media", "espressif_icon.png") + ); + + const scriptPath = this.panel.webview.asWebviewUri( + vscode.Uri.file( + path.join(extensionPath, "dist", "views", "setup-bundle.js") + ) + ); + this.panel.webview.html = this.createSetupHtml(scriptPath); + + const espIdfPath = idfConf.readParameter("idf.espIdfPath") as string; + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + const defaultEspIdfPathContainer = path.join(containerPath, "esp"); + const toolsPath = idfConf.readParameter("idf.toolsPath"); + + this.panel.webview.onDidReceiveMessage(async (message) => { + switch (message.command) { + case "checkEspIdfTools": + if (message.espIdf && message.pyPath && message.toolsPath) { + await this.checkRequiredTools(message.espIdf, message.toolsPath); + } + break; + case "installEspIdf": + if ( + message.espIdfContainer && + message.selectedEspIdfVersion && + message.selectedPyPath && + typeof message.manualEspIdfPath !== undefined && + typeof message.mirror !== undefined && + typeof message.setupMode !== undefined + ) { + if (message.espIdfContainer === defaultEspIdfPathContainer) { + await ensureDir(defaultEspIdfPathContainer); + } + await this.autoInstall( + message.selectedEspIdfVersion, + message.selectedPyPath, + message.manualEspIdfPath, + message.espIdfContainer, + message.mirror, + message.setupMode + ); + } + break; + case "installEspIdfTools": + if (message.espIdf && message.pyPath && message.toolsPath) { + await this.installEspIdfTools( + message.espIdf, + message.pyPath, + message.toolsPath + ); + } + break; + case "openEspIdfFolder": + const selectedFolder = await this.openFolder(); + this.panel.webview.postMessage({ + command: "updateEspIdfFolder", + selectedFolder, + }); + break; + case "openEspIdfContainerFolder": + const selectedContainerFolder = await this.openFolder(); + this.panel.webview.postMessage({ + command: "updateEspIdfContainerFolder", + selectedContainerFolder, + }); + break; + case "openEspIdfToolsFolder": + const selectedToolsFolder = await this.openFolder(); + this.panel.webview.postMessage({ + command: "updateEspIdfToolsFolder", + selectedToolsFolder, + }); + break; + case "openPythonPath": + const selectedPyPath = await this.openFile(); + this.panel.webview.postMessage({ + command: "updatePythonPath", + selectedPyPath, + }); + break; + case "requestInitialValues": + const pathSep = path.sep; + this.panel.webview.postMessage({ + command: "initialLoad", + espIdfContainer: defaultEspIdfPathContainer, + espIdf: espIdfPath || setupArgs.espIdfPath, + espToolsPath: toolsPath || setupArgs.espToolsPath, + gitVersion: setupArgs.gitVersion, + hasPrerequisites: setupArgs.hasPrerequisites, + idfVersion: setupArgs.espIdfVersion, + idfVersions: setupArgs.espIdfVersionsList, + pathSep, + pyBinPath: setupArgs.pyBinPath, + pyVersionList: setupArgs.pythonVersions, + toolsResults: setupArgs.toolsResults, + }); + break; + case "saveCustomSettings": + if ( + message.espIdfPath && + message.toolsPath && + message.pyBinPath && + message.tools + ) { + const { exportedPaths, exportedVars } = this.getCustomSetupSettings( + message.tools + ); + this.panel.webview.postMessage({ + command: "updateEspIdfToolsStatus", + status: StatusType.installed, + }); + await this.installPyReqs( + message.espIdfPath, + message.toolsPath, + message.pyBinPath, + exportedPaths, + exportedVars + ); + } + break; + case "usePreviousSettings": + if ( + setupArgs.espIdfPath && + setupArgs.pyBinPath && + setupArgs.exportedPaths && + setupArgs.exportedVars + ) { + this.panel.webview.postMessage({ + command: "updateEspIdfStatus", + status: StatusType.installed, + }); + this.panel.webview.postMessage({ + command: "updateEspIdfToolsStatus", + status: StatusType.installed, + }); + this.panel.webview.postMessage({ + command: "updatePyVEnvStatus", + status: StatusType.started, + }); + this.panel.webview.postMessage({ + command: "goToCustomPage", + installing: true, + page: "/status", + }); + await installExtensionPyReqs( + setupArgs.espToolsPath, + setupArgs.pyBinPath + ); + await saveSettings( + setupArgs.espIdfPath, + setupArgs.pyBinPath, + setupArgs.exportedPaths, + setupArgs.exportedVars + ); + this.panel.webview.postMessage({ + command: "setIsInstalled", + isInstalled: true, + }); + } + break; + default: + break; + } + }); + + this.panel.onDidDispose(() => this.dispose(), null, this.disposables); + } + + setupErrHandler(error: Error) { + const errMsg = error.message ? error.message : "Error during ESP-IDF setup"; + if (errMsg.indexOf("ERROR_EXISTING_ESP_IDF") !== -1) { + SetupPanel.postMessage({ + command: "setEspIdfErrorStatus", + errorMsg: error.message, + }); + } else if (errMsg.indexOf("ERROR_INVALID_PYTHON") !== -1) { + SetupPanel.postMessage({ + command: "setPyExecErrorStatus", + errorMsg: error.message, + }); + } else { + SetupPanel.postMessage({ + command: "setEspIdfErrorStatus", + errorMsg: "", + }); + } + OutputChannel.appendLine(errMsg); + Logger.errorNotify(errMsg, error); + OutputChannel.show(); + SetupPanel.postMessage({ + command: "goToCustomPage", + installing: false, + page: "/autoinstall", + }); + } + + private async autoInstall( + selectedIdfVersion: IEspIdfLink, + pyPath: string, + espIdfPath: string, + idfContainerPath: string, + mirror: IdfMirror, + setupMode: SetupMode + ) { + return await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF Setup:", + cancellable: true, + }, + async ( + progress: vscode.Progress<{ message: string; increment?: number }>, + cancelToken: vscode.CancellationToken + ) => { + try { + await expressInstall( + selectedIdfVersion, + pyPath, + espIdfPath, + idfContainerPath, + mirror, + setupMode, + progress, + cancelToken + ); + } catch (error) { + this.setupErrHandler(error); + } + } + ); + } + + private async checkRequiredTools(idfPath: string, toolsInfo: IEspIdfTool[]) { + const toolsManager = await IdfToolsManager.createIdfToolsManager(idfPath); + const pathToVerify = toolsInfo + .reduce((prev, curr, i) => { + return prev + path.delimiter + curr.path; + }, "") + .slice(1); + + const foundVersions = await toolsManager.verifyPackages(pathToVerify); + const updatedToolsInfo = toolsInfo.map((tool) => { + const isToolVersionCorrect = + tool.expected.indexOf(foundVersions[tool.name]) > -1; + tool.doesToolExist = isToolVersionCorrect; + if (isToolVersionCorrect) { + tool.progress = "100.00%"; + tool.hashResult = true; + } else { + tool.progress = "0.00%"; + tool.hashResult = false; + } + return tool; + }); + this.panel.webview.postMessage({ + command: "setRequiredToolsInfo", + toolsInfo: updatedToolsInfo, + }); + } + + private getCustomSetupSettings(toolsInfo: IEspIdfTool[]) { + const exportedPaths = toolsInfo + .reduce((prev, curr, i) => { + return prev + path.delimiter + curr.path; + }, "") + .slice(1); + const exportedVars = {}; + for (const tool of toolsInfo) { + Object.keys(tool.env).forEach((key, index, arr) => { + if (Object.keys(exportedVars).indexOf(key) !== -1) { + exportedVars[key] = tool.env[key]; + } + }); + } + const exportedVarsStr = JSON.stringify(exportedVars); + return { exportedPaths, exportedVars: exportedVarsStr }; + } + + private async installEspIdfTools( + idfPath: string, + pyPath: string, + toolsPath: string + ) { + return await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF Tools Setup:", + cancellable: true, + }, + async ( + progress: vscode.Progress<{ message: string; increment?: number }>, + cancelToken: vscode.CancellationToken + ) => { + try { + await downloadIdfTools( + idfPath, + toolsPath, + pyPath, + progress, + cancelToken + ); + } catch (error) { + this.setupErrHandler(error); + } + } + ); + } + + private async installPyReqs( + idfPath: string, + toolsPath: string, + pyPath: string, + exportPaths: string, + exportVars: string + ) { + return await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: "ESP-IDF Python Requirements:", + cancellable: true, + }, + async ( + progress: vscode.Progress<{ message: string; increment?: number }>, + cancelToken: vscode.CancellationToken + ) => { + try { + await createPyReqs( + idfPath, + toolsPath, + pyPath, + exportPaths, + exportVars, + progress, + cancelToken + ); + } catch (error) { + this.setupErrHandler(error); + } + } + ); + } + + private async openFolder() { + const selectedFolder = await vscode.window.showOpenDialog({ + canSelectFolders: true, + canSelectFiles: false, + canSelectMany: false, + }); + if (selectedFolder && selectedFolder.length > 0) { + return selectedFolder[0].fsPath; + } else { + vscode.window.showInformationMessage("No folder selected"); + } + } + + private async openFile() { + const selectedFolder = await vscode.window.showOpenDialog({ + canSelectFolders: false, + canSelectFiles: true, + canSelectMany: false, + }); + if (selectedFolder && selectedFolder.length > 0) { + return selectedFolder[0].fsPath; + } else { + vscode.window.showInformationMessage("No folder selected"); + } + } + + private createSetupHtml(scriptPath: vscode.Uri): string { + return ` + + + + + ESP-IDF Setup + + +
+ + + `; + } + + public dispose() { + SetupPanel.currentPanel = undefined; + this.panel.dispose(); + } +} diff --git a/src/setup/espIdfDownload.ts b/src/setup/espIdfDownload.ts new file mode 100644 index 000000000..771cae9c8 --- /dev/null +++ b/src/setup/espIdfDownload.ts @@ -0,0 +1,119 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import path from "path"; +import * as utils from "../utils"; +import { Logger } from "../logger/logger"; +import { OutputChannel } from "../logger/outputChannel"; +import { DownloadManager } from "../downloadManager"; +import { InstallManager } from "../installManager"; +import { PackageProgress } from "../PackageProgress"; +import { IdfMirror, IEspIdfLink } from "../views/setup/types"; +import { + sendEspIdfDownloadDetail, + sendEspIdfDownloadProgress, + sendDownloadedZip, + sendExtractedZip, +} from "./webviewMsgMethods"; +import { ensureDir, move } from "fs-extra"; +import { AbstractCloning } from "../common/abstractCloning"; +import { CancellationToken, Progress } from "vscode"; +import { Disposable } from "vscode-languageclient"; + +export class EspIdfCloning extends AbstractCloning { + constructor(branchName: string) { + super("https://github.com/espressif/esp-idf.git", "ESP-IDF", branchName); + } +} + +export async function downloadInstallIdfVersion( + idfVersion: IEspIdfLink, + destPath: string, + mirror: IdfMirror, + progress?: Progress<{ message: string; increment?: number }>, + cancelToken?: CancellationToken +) { + const downloadedZipPath = path.join(destPath, idfVersion.filename); + const extractedDirectory = downloadedZipPath.replace(".zip", ""); + const expectedDirectory = path.join(destPath, "esp-idf"); + await ensureDir(destPath); + const expectedDirExists = await utils.dirExistPromise(expectedDirectory); + if (expectedDirExists) { + const espExistsMsg = `${expectedDirectory} already exists. Delete it or use another location. (ERROR_EXISTING_ESP_IDF)`; + OutputChannel.appendLine(espExistsMsg); + Logger.infoNotify(espExistsMsg); + throw new Error(espExistsMsg); + } + const downloadManager = new DownloadManager(destPath); + const installManager = new InstallManager(destPath); + const pkgProgress = new PackageProgress( + idfVersion.name, + sendEspIdfDownloadProgress, + null, + sendEspIdfDownloadDetail, + null + ); + pkgProgress.Progress = `0.00%`; + + if ( + idfVersion.filename === "master" || + idfVersion.filename.startsWith("release") + ) { + const downloadByCloneMsg = `Downloading ESP-IDF ${idfVersion.filename} using git clone...\n`; + OutputChannel.appendLine(downloadByCloneMsg); + Logger.info(downloadByCloneMsg); + if (progress) { + progress.report({ message: downloadByCloneMsg }); + } + const espIdfCloning = new EspIdfCloning(idfVersion.filename); + let cancelDisposable: Disposable; + if (cancelToken) { + cancelDisposable = cancelToken.onCancellationRequested(() => { + espIdfCloning.cancel(); + }); + } + await espIdfCloning.downloadByCloning(destPath, pkgProgress); + cancelDisposable.dispose(); + } else { + const downloadByHttpMsg = `Downloading ESP-IDF ${idfVersion.name}...`; + OutputChannel.appendLine(downloadByHttpMsg); + Logger.info(downloadByHttpMsg); + if (progress) { + progress.report({ message: downloadByHttpMsg }); + } + const urlToUse = + mirror === IdfMirror.Github ? idfVersion.url : idfVersion.mirror; + await downloadManager.downloadWithRetries( + urlToUse, + destPath, + pkgProgress, + cancelToken + ); + const downloadedMsg = `Downloaded ${idfVersion.name}.\n`; + OutputChannel.appendLine(downloadedMsg); + Logger.info(downloadedMsg); + sendDownloadedZip(downloadedZipPath); + await installManager.installZipFile( + downloadedZipPath, + destPath, + cancelToken + ); + const extractedMsg = `Extracted ${downloadedZipPath} in ${destPath}.\n`; + OutputChannel.appendLine(extractedMsg); + Logger.info(extractedMsg); + await move(extractedDirectory, expectedDirectory); + sendExtractedZip(expectedDirectory); + } + return expectedDirectory; +} diff --git a/src/setup/espIdfDownloadStep.ts b/src/setup/espIdfDownloadStep.ts new file mode 100644 index 000000000..47c057cac --- /dev/null +++ b/src/setup/espIdfDownloadStep.ts @@ -0,0 +1,102 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as path from "path"; +import * as vscode from "vscode"; +import { checkPythonPipExists } from "./installPyReqs"; +import { SetupPanel } from "./SetupPanel"; +import * as idfConf from "../idfConfiguration"; +import * as utils from "../utils"; +import { + IdfMirror, + IEspIdfLink, + SetupMode, + StatusType, +} from "../views/setup/types"; +import { downloadInstallIdfVersion } from "./espIdfDownload"; +import { Logger } from "../logger/logger"; +import { downloadIdfTools } from "./toolsDownloadStep"; + +export async function expressInstall( + selectedIdfVersion: IEspIdfLink, + pyPath: string, + espIdfPath: string, + idfContainerPath: string, + mirror: IdfMirror, + setupMode: SetupMode, + progress?: vscode.Progress<{ message: string; increment?: number }>, + cancelToken?: vscode.CancellationToken +) { + const pyCheck = await checkPythonPipExists(pyPath, __dirname); + if (!pyCheck) { + const containerNotFoundMsg = `${pyPath} is not valid. (ERROR_INVALID_PYTHON)`; + Logger.infoNotify(containerNotFoundMsg); + throw new Error(containerNotFoundMsg); + } + SetupPanel.postMessage({ + command: "goToCustomPage", + installing: true, + page: "/status", + }); + let idfPath: string; + if (selectedIdfVersion.filename === "manual") { + idfPath = espIdfPath; + } else { + idfPath = await downloadInstallIdfVersion( + selectedIdfVersion, + idfContainerPath, + mirror, + progress, + cancelToken + ); + } + const idfVersion = await utils.getEspIdfVersion(idfPath); + if (idfVersion === "x.x") { + throw new Error("Invalid ESP-IDF"); + } + SetupPanel.postMessage({ + command: "updateEspIdfFolder", + selectedFolder: idfPath, + }); + SetupPanel.postMessage({ + command: "setIdfVersion", + idfVersion, + }); + SetupPanel.postMessage({ + command: "updateEspIdfStatus", + status: StatusType.installed, + }); + SetupPanel.postMessage({ + command: "setEspIdfErrorStatus", + errorMsg: `ESP-IDF is installed in ${idfPath}`, + }); + SetupPanel.postMessage({ + command: "updateEspIdfToolsStatus", + status: StatusType.started, + }); + if (setupMode === SetupMode.advanced) { + SetupPanel.postMessage({ + command: "goToCustomPage", + installing: false, + page: "/custom", + }); + return; + } + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + const toolsPath = + (idfConf.readParameter("idf.toolsPath") as string) || + path.join(containerPath, ".espressif"); + await downloadIdfTools(idfPath, toolsPath, pyPath, progress, cancelToken); +} diff --git a/src/setup/espIdfVersionList.ts b/src/setup/espIdfVersionList.ts new file mode 100644 index 000000000..f7f9defce --- /dev/null +++ b/src/setup/espIdfVersionList.ts @@ -0,0 +1,139 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { DownloadManager } from "../downloadManager"; +import path from "path"; +import { EOL, tmpdir } from "os"; +import { Logger } from "../logger/logger"; +import { readFile } from "fs-extra"; +import { OutputChannel } from "../logger/outputChannel"; +import { IEspIdfLink } from "../views/setup/types"; + +export async function getEspIdfVersions(extensionPath: string) { + const downloadManager = new DownloadManager(extensionPath); + const versionList = await downloadEspIdfVersionList( + downloadManager, + extensionPath + ); + const manualVersion = { + name: "Find ESP-IDF in your system", + filename: "manual", + } as IEspIdfLink; + versionList.push(manualVersion); + return versionList; +} + +export async function downloadEspIdfVersionList( + downloadManager: DownloadManager, + extensionPath: string +) { + try { + const idfVersionList = path.join(tmpdir(), "idf_versions.txt"); + const versionsUrl = "https://dl.espressif.com/dl/esp-idf/idf_versions.txt"; + const downloadMessage = await downloadManager.downloadFile( + versionsUrl, + 0, + tmpdir() + ); + Logger.info(downloadMessage.statusMessage); + const fileContent = await readFile(idfVersionList); + const downloadList: IEspIdfLink[] = createEspIdfLinkList(fileContent, "\n"); + return downloadList; + } catch (error) { + OutputChannel.appendLine( + `Error opening esp-idf version list file. ${error.message}` + ); + try { + const idfVersionListFallBack = path.join( + extensionPath, + "idf_versions.txt" + ); + const fallbackContent = await readFile(idfVersionListFallBack); + const downloadList: IEspIdfLink[] = createEspIdfLinkList( + fallbackContent, + EOL + ); + return downloadList; + } catch (fallbackError) { + OutputChannel.appendLine( + `Error opening esp-idf fallback version list file. ${fallbackError.message}` + ); + } + } +} + +export function createEspIdfLinkList(data: Buffer, splitString: string) { + const versionZip = + "https://github.com/espressif/esp-idf/releases/download/IDFZIPFileVersion/esp-idf-IDFZIPFileVersion.zip"; + const mirrorZip = + "https://dl.espressif.com/dl/esp-idf/releases/esp-idf-IDFZIPFileVersion.zip"; + const versionRegex = /\b(IDFZIPFileVersion)\b/g; + const espIdfMasterZip = + "https://github.com/espressif/esp-idf/archive/master.zip"; + const preReleaseRegex = /v.+-rc/g; + const betaRegex = /v.+-beta/g; + + const versionList = data.toString().trim().split(splitString); + const downloadList: IEspIdfLink[] = versionList.map((version) => { + if (version.startsWith("release/")) { + const versionRoot = version.replace("release/", ""); + const versionForRelease = versionList.find((ver) => + ver.startsWith(versionRoot) + ); + if (versionForRelease) { + return { + filename: `esp-idf-${versionForRelease}.zip`, + name: version + " (release branch)", + url: versionZip.replace(versionRegex, versionForRelease), + mirror: mirrorZip.replace(versionRegex, versionForRelease), + }; + } else { + return { + filename: `${version}`, + name: version + " (release branch)", + url: "", + mirror: "", + }; + } + } else if (version.startsWith("v")) { + return { + filename: `esp-idf-${version}.zip`, + name: version + " (release version)", + url: versionZip.replace(versionRegex, version), + mirror: mirrorZip.replace(versionRegex, version), + }; + } else if (preReleaseRegex.test(version)) { + return { + filename: `esp-idf-${version}.zip`, + name: version + " (pre-release version)", + url: versionZip.replace(versionRegex, version), + mirror: mirrorZip.replace(versionRegex, version), + }; + } else if (version === "master") { + return { + filename: `master`, + name: version + " (development branch)", + url: espIdfMasterZip, + mirror: espIdfMasterZip, + }; + } else if (betaRegex.test(version)) { + return { + filename: `esp-idf-${version}.zip`, + name: version + " (beta version)", + url: versionZip.replace(versionRegex, version), + mirror: mirrorZip.replace(versionRegex, version), + }; + } + }); + return downloadList; +} diff --git a/src/setup/installPyReqs.ts b/src/setup/installPyReqs.ts new file mode 100644 index 000000000..993e35cef --- /dev/null +++ b/src/setup/installPyReqs.ts @@ -0,0 +1,105 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { pathExists } from "fs-extra"; +import * as pythonManager from "../pythonManager"; +import { SetupPanel } from "./SetupPanel"; +import { OutputChannel } from "../logger/outputChannel"; +import { PyReqLog } from "../PyReqLog"; +import { CancellationToken, Progress } from "vscode"; + +export async function installPyReqs( + espIdfPath: string, + workingDir: string, + sysPyBinPath: string, + progress: Progress<{ message: string; increment?: number }>, + cancelToken?: CancellationToken +) { + progress.report({ + message: `Checking Python and pip exists...`, + }); + const canCheck = await checkPythonPipExists(sysPyBinPath, workingDir); + if (!canCheck) { + const msg = "Python or pip have not been found in your environment."; + sendPyReqLog(msg); + OutputChannel.appendLine(msg); + return; + } + const logTracker = new PyReqLog(sendPyReqLog); + progress.report({ + message: `Installing python virtualenv and ESP-IDF python requirements...`, + }); + const virtualEnvPyBin = await pythonManager.installPythonEnv( + espIdfPath, + workingDir, + logTracker, + sysPyBinPath, + OutputChannel.init(), + cancelToken + ); + if (virtualEnvPyBin) { + if (logTracker.Log.indexOf("Exception") < 0) { + OutputChannel.appendLine("Python requirements has been installed"); + SetupPanel.postMessage({ + command: "load_python_bin_path", + pythonBinPath: virtualEnvPyBin, + }); + return virtualEnvPyBin; + } + } + OutputChannel.appendLine("Python requirements has not been installed"); + return; +} + +export async function installExtensionPyReqs( + workingDir: string, + pythonBinPath: string +) { + const logTracker = new PyReqLog(sendPyReqLog); + await pythonManager.installExtensionPyReqs( + pythonBinPath, + workingDir, + logTracker, + OutputChannel.init() + ); +} + +export async function checkPythonPipExists( + pyBinPath: string, + workingDir: string +) { + const pyExists = pyBinPath === "python" ? true : await pathExists(pyBinPath); + const doesPythonExists = await pythonManager.checkPythonExists( + pyBinPath, + workingDir + ); + const doesPipExists = await pythonManager.checkPipExists( + pyBinPath, + workingDir + ); + return pyExists && doesPythonExists && doesPipExists; +} + +export function sendPyReqLog(log: string) { + SetupPanel.postMessage({ + command: "updatePyReqsLog", + pyReqsLog: log, + }); +} + +export async function getPythonList(workingDir: string) { + const pyVersionList = await pythonManager.getPythonBinList(workingDir); + pyVersionList.push("Provide python executable path"); + return pyVersionList; +} diff --git a/src/setup/pyReqsInstallStep.ts b/src/setup/pyReqsInstallStep.ts new file mode 100644 index 000000000..d6c6ffc80 --- /dev/null +++ b/src/setup/pyReqsInstallStep.ts @@ -0,0 +1,59 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as vscode from "vscode"; +import { StatusType } from "../views/setup/types"; +import { installPyReqs } from "./installPyReqs"; +import { SetupPanel } from "./SetupPanel"; +import { saveSettings } from "./setupInit"; + +export async function createPyReqs( + idfPath: string, + toolsPath: string, + pyPath: string, + exportPaths: string, + exportVars: string, + progress: vscode.Progress<{ message: string; increment?: number }>, + cancelToken: vscode.CancellationToken +) { + SetupPanel.postMessage({ + command: "updatePyVEnvStatus", + status: StatusType.started, + }); + SetupPanel.postMessage({ + command: "goToCustomPage", + installing: true, + page: "/status", + }); + const virtualEnvPath = await installPyReqs( + idfPath, + toolsPath, + pyPath, + progress, + cancelToken + ); + await saveSettings(idfPath, virtualEnvPath, exportPaths, exportVars); + SetupPanel.postMessage({ + command: "updatePyVEnvStatus", + status: StatusType.installed, + }); + SetupPanel.postMessage({ + command: "setIsInstalled", + isInstalled: true, + }); + SetupPanel.postMessage({ + command: "setIsIdfInstalling", + installing: false, + }); +} diff --git a/src/setup/setupInit.ts b/src/setup/setupInit.ts new file mode 100644 index 000000000..63792ad19 --- /dev/null +++ b/src/setup/setupInit.ts @@ -0,0 +1,256 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { ConfigurationTarget, Progress, window } from "vscode"; +import { IdfToolsManager, IEspIdfTool } from "../idfToolsManager"; +import * as utils from "../utils"; +import { getEspIdfVersions } from "./espIdfVersionList"; +import { IEspIdfLink } from "../views/setup/types"; +import { getPythonList } from "./installPyReqs"; +import { pathExists } from "fs-extra"; +import path from "path"; +import { getPythonEnvPath } from "../pythonManager"; +import { Logger } from "../logger/logger"; +import * as idfConf from "../idfConfiguration"; + +export interface ISetupInitArgs { + espIdfPath: string; + espIdfVersion: string; + espToolsPath: string; + exportedPaths: string; + exportedVars: string; + espIdfVersionsList: IEspIdfLink[]; + gitVersion: string; + hasPrerequisites: boolean; + pythonVersions: string[]; + toolsResults: IEspIdfTool[]; + pyBinPath: string; +} + +export async function checkPreviousInstall(pythonVersions: string[]) { + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + + const confEspIdfPath = idfConf.readParameter("idf.espIdfPath") as string; + const confToolsPath = idfConf.readParameter("idf.toolsPath") as string; + const toolsPath = + confToolsPath || + process.env.IDF_TOOLS_PATH || + path.join(containerPath, ".espressif"); + let espIdfPath = + confEspIdfPath || + process.env.IDF_PATH || + path.join(containerPath, "esp", "esp-idf"); + let idfPathVersion = await utils.getEspIdfVersion(espIdfPath); + if (idfPathVersion === "x.x" && process.platform === "win32") { + espIdfPath = path.join(process.env.USERPROFILE, "Desktop", "esp-idf"); + idfPathVersion = await utils.getEspIdfVersion(espIdfPath); + } + if (idfPathVersion === "x.x") { + return { + espToolsPath: toolsPath, + }; + } + const idfToolsManager = await IdfToolsManager.createIdfToolsManager( + espIdfPath + ); + + const exportedToolsPaths = await idfToolsManager.exportPathsInString( + path.join(toolsPath, "tools") + ); + const toolsInfo = await idfToolsManager.getRequiredToolsInfo( + path.join(toolsPath, "tools"), + exportedToolsPaths + ); + + const failedToolsResult = toolsInfo.filter((tInfo) => !tInfo.doesToolExist); + if (failedToolsResult.length > 0) { + return { + espIdfPath, + espIdfVersion: idfPathVersion, + espToolsPath: toolsPath, + }; + } + + const exportedVars = await idfToolsManager.exportVars( + path.join(toolsPath, "tools") + ); + + if (!exportedVars) { + return { + espIdfPath, + espIdfVersion: idfPathVersion, + espToolsPath: toolsPath, + exportedToolsPaths, + toolsInfo, + }; + } + + const pyVenvPath = await checkPyVersion( + pythonVersions, + espIdfPath, + toolsPath + ); + + if (!pyVenvPath) { + return { + espIdfPath, + espIdfVersion: idfPathVersion, + espToolsPath: toolsPath, + exportedToolsPaths, + exportedVars, + toolsInfo, + }; + } + + return { + espIdfPath, + espIdfVersion: idfPathVersion, + espToolsPath: toolsPath, + exportedToolsPaths, + exportedVars, + pyVenvPath, + toolsInfo, + }; +} + +export async function checkPyVersion( + pythonVersions: string[], + espIdfPath: string, + toolsDir: string +) { + for (const pyVer of pythonVersions) { + const venvPyFolder = await getPythonEnvPath(espIdfPath, toolsDir, pyVer); + const pythonInEnv = + process.platform === "win32" + ? path.join(venvPyFolder, "Scripts", "python.exe") + : path.join(venvPyFolder, "bin", "python"); + const pyExists = await pathExists(pythonInEnv); + if (!pyExists) { + continue; + } + const requirements = path.join(espIdfPath, "requirements.txt"); + const reqsResults = await utils.startPythonReqsProcess( + pythonInEnv, + espIdfPath, + requirements + ); + if (reqsResults.indexOf("are not satisfied") > -1) { + continue; + } + return pythonInEnv; + } + return; +} + +export async function getSetupInitialValues( + extensionPath: string, + progress: Progress<{ message: string; increment: number }> +) { + progress.report({ increment: 20, message: "Getting ESP-IDF versions..." }); + const espIdfVersionsList = await getEspIdfVersions(extensionPath); + progress.report({ increment: 20, message: "Getting Python versions..." }); + const pythonVersions = await getPythonList(extensionPath); + const gitVersion = await utils.checkGitExists(extensionPath); + + let hasPrerequisites = false; + if (process.platform !== "win32") { + const canAccessCMake = await utils.isBinInPath( + "cmake", + extensionPath, + process.env + ); + const canAccessNinja = await utils.isBinInPath( + "ninja", + extensionPath, + process.env + ); + hasPrerequisites = + gitVersion !== "" && canAccessCMake !== "" && canAccessNinja !== ""; + } else { + hasPrerequisites = gitVersion !== ""; + } + + const setupInitArgs = { + espIdfVersionsList, + gitVersion, + pythonVersions, + hasPrerequisites, + } as ISetupInitArgs; + try { + progress.report({ + increment: 10, + message: "Checking for previous install...", + }); + + // Get initial paths + const prevInstall = await checkPreviousInstall(pythonVersions); + progress.report({ increment: 20, message: "Preparing setup view..." }); + if (prevInstall) { + setupInitArgs.espIdfPath = prevInstall.espIdfPath; + setupInitArgs.espIdfVersion = prevInstall.espIdfVersion; + setupInitArgs.espToolsPath = prevInstall.espToolsPath; + setupInitArgs.exportedPaths = prevInstall.exportedToolsPaths; + setupInitArgs.exportedVars = prevInstall.exportedVars; + setupInitArgs.toolsResults = prevInstall.toolsInfo; + setupInitArgs.pyBinPath = prevInstall.pyVenvPath; + } + } catch (error) { + Logger.error(error.message, error); + } + return setupInitArgs; +} + +export async function isCurrentInstallValid() { + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + const toolsPath = path.join(containerPath, ".espressif"); + const extraPaths = idfConf.readParameter("idf.customExtraPaths") as string; + let espIdfPath = idfConf.readParameter("idf.espIdfPath"); + let idfPathVersion = await utils.getEspIdfVersion(espIdfPath); + if (idfPathVersion === "x.x" && process.platform === "win32") { + espIdfPath = path.join(process.env.USERPROFILE, "Desktop", "esp-idf"); + idfPathVersion = await utils.getEspIdfVersion(espIdfPath); + } + if (idfPathVersion === "x.x") { + return false; + } + const idfToolsManager = await IdfToolsManager.createIdfToolsManager( + espIdfPath + ); + const toolsInfo = await idfToolsManager.getRequiredToolsInfo( + path.join(toolsPath, "tools"), + extraPaths + ); + const failedToolsResult = toolsInfo.filter((tInfo) => !tInfo.doesToolExist); + return failedToolsResult.length === 0; +} + +export async function saveSettings( + espIdfPath: string, + pythonBinPath: string, + exportedPaths: string, + exportedVars: string, + confTarget: ConfigurationTarget = ConfigurationTarget.Global +) { + await idfConf.writeParameter("idf.espIdfPath", espIdfPath, confTarget); + await idfConf.writeParameter("idf.pythonBinPath", pythonBinPath, confTarget); + await idfConf.writeParameter( + "idf.customExtraPaths", + exportedPaths, + confTarget + ); + await idfConf.writeParameter("idf.customExtraVars", exportedVars, confTarget); + window.showInformationMessage("ESP-IDF has been configured"); +} diff --git a/src/setup/toolInstall.ts b/src/setup/toolInstall.ts new file mode 100644 index 000000000..5560d6e59 --- /dev/null +++ b/src/setup/toolInstall.ts @@ -0,0 +1,66 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { delimiter } from "path"; +import { Logger } from "../logger/logger"; +import { OutputChannel } from "../logger/outputChannel"; +import { DownloadManager } from "../downloadManager"; +import { InstallManager } from "../installManager"; +import { IdfToolsManager } from "../idfToolsManager"; +import { PackageProgress } from "../PackageProgress"; +import { + sendPkgDownloadPercentage, + sendPkgChecksumResult, + sendPkgDownloadDetail, + sendPkgDownloadFailed, +} from "./webviewMsgMethods"; +import { CancellationToken, Progress } from "vscode"; + +export async function downloadEspIdfTools( + installDir: string, + idfToolsManager: IdfToolsManager, + progress: Progress<{ message: string; increment?: number }>, + cancelToken?: CancellationToken +) { + const manyPathsInInstallDir = installDir.split(delimiter); + if (manyPathsInInstallDir.length > 1) { + Logger.infoNotify("Introduce a single path"); + return; + } + const downloadManager = new DownloadManager(installDir); + const installManager = new InstallManager(installDir); + + const packages = await idfToolsManager.getPackageList(); + const pkgProgress = packages.map((p) => { + return new PackageProgress( + p.name, + sendPkgDownloadPercentage, + sendPkgChecksumResult, + sendPkgDownloadDetail, + sendPkgDownloadFailed + ); + }); + OutputChannel.appendLine(""); + Logger.info(""); + await downloadManager.downloadPackages( + idfToolsManager, + progress, + pkgProgress, + cancelToken + ); + OutputChannel.appendLine(""); + Logger.info(""); + await installManager.installPackages(idfToolsManager, progress, cancelToken); + OutputChannel.appendLine(""); + Logger.info(""); +} diff --git a/src/setup/toolsDownloadStep.ts b/src/setup/toolsDownloadStep.ts new file mode 100644 index 000000000..d72fc5f02 --- /dev/null +++ b/src/setup/toolsDownloadStep.ts @@ -0,0 +1,64 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as path from "path"; +import * as vscode from "vscode"; +import { IdfToolsManager } from "../idfToolsManager"; +import { SetupPanel } from "./SetupPanel"; +import { downloadEspIdfTools } from "./toolInstall"; +import { StatusType } from "../views/setup/types"; +import { createPyReqs } from "./pyReqsInstallStep"; + +export async function downloadIdfTools( + idfPath: string, + toolsPath: string, + pyPath: string, + progress?: vscode.Progress<{ message: string; increment?: number }>, + cancelToken?: vscode.CancellationToken +) { + SetupPanel.postMessage({ + command: "goToCustomPage", + installing: true, + page: "/status", + }); + const idfToolsManager = await IdfToolsManager.createIdfToolsManager(idfPath); + const exportPaths = await idfToolsManager.exportPathsInString( + path.join(toolsPath, "tools") + ); + const exportVars = await idfToolsManager.exportVars( + path.join(toolsPath, "tools") + ); + const requiredTools = await idfToolsManager.getRequiredToolsInfo( + toolsPath, + exportPaths + ); + SetupPanel.postMessage({ + command: "setRequiredToolsInfo", + toolsInfo: requiredTools, + }); + await downloadEspIdfTools(toolsPath, idfToolsManager, progress, cancelToken); + SetupPanel.postMessage({ + command: "updateEspIdfToolsStatus", + status: StatusType.installed, + }); + await createPyReqs( + idfPath, + toolsPath, + pyPath, + exportPaths, + exportVars, + progress, + cancelToken + ); +} diff --git a/src/setup/webviewMsgMethods.ts b/src/setup/webviewMsgMethods.ts new file mode 100644 index 000000000..a6cce1446 --- /dev/null +++ b/src/setup/webviewMsgMethods.ts @@ -0,0 +1,86 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { SetupPanel } from "./SetupPanel"; + +export function sendEspIdfDownloadProgress( + id: string, + updatedPercentage: string +) { + SetupPanel.postMessage({ + command: "updateIdfDownloadStatusPercentage", + id, + percentage: updatedPercentage, + }); +} + +export function sendEspIdfDownloadDetail(id: string, updatedDetail: string) { + SetupPanel.postMessage({ + command: "updateIdfDownloadStatusDetail", + detail: updatedDetail, + id, + }); +} + +export function sendDownloadedZip(downloadedPath: string) { + SetupPanel.postMessage({ + command: "setEspIdfErrorStatus", + errorMsg: `ESP-IDF is downloaded in ${downloadedPath}`, + }); +} + +export function sendExtractedZip(extractedPath: string) { + SetupPanel.postMessage({ + command: "setEspIdfErrorStatus", + errorMsg: `ESP-IDF is extracted in ${extractedPath}`, + }); +} + +export function sendPkgDownloadPercentage( + pkgName: string, + updatedPercentage: string +) { + SetupPanel.postMessage({ + command: "updatePkgDownloadPercentage", + id: pkgName, + percentage: updatedPercentage, + }); +} + +export function sendPkgChecksumResult( + pkgName: string, + updatedChecksumResult: boolean +) { + SetupPanel.postMessage({ + command: "updatePkgChecksumResult", + id: pkgName, + hashResult: updatedChecksumResult, + }); +} + +export function sendPkgDownloadDetail(pkgName: string, updatedDetail: string) { + SetupPanel.postMessage({ + command: "updatePkgDownloadDetail", + id: pkgName, + progressDetail: updatedDetail, + }); +} + +export function sendPkgDownloadFailed(pkgName: string, failedFlag: boolean) { + SetupPanel.postMessage({ + command: "updatePkgDownloadFailed", + id: pkgName, + hasFailed: failedFlag, + }); +} diff --git a/src/utils.ts b/src/utils.ts index 171ae6923..6ef4225c0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -311,7 +311,8 @@ export function execChildProcess( processStr: string, workingDirectory: string, channel?: vscode.OutputChannel, - opts?: childProcess.ExecOptions + opts?: childProcess.ExecOptions, + cancelToken?: vscode.CancellationToken ): Promise { const execOpts: childProcess.ExecOptions = opts ? opts @@ -324,6 +325,9 @@ export function execChildProcess( processStr, execOpts, (error: Error, stdout: string, stderr: string) => { + if (cancelToken && cancelToken.isCancellationRequested) { + return reject(new Error("Process cancelled by user")); + } if (channel) { let message: string = ""; let err: boolean = false; @@ -332,12 +336,8 @@ export function execChildProcess( } if (stderr && stderr.length > 0) { message += stderr; - err = true; - if (stderr.indexOf("Licensed under GNU GPL v2") !== -1) { - err = false; - } - if (stderr.indexOf("DEPRECATION") !== -1) { - err = false; + if (stderr.indexOf("Error") !== -1) { + err = true; } } if (error) { @@ -354,31 +354,15 @@ export function execChildProcess( if (error.message) { Logger.error(error.message, error); } - reject(error); - return; + return reject(error); } if (stderr && stderr.length > 2) { Logger.error(stderr, new Error(stderr)); - const ignoredMessages = [ - "Licensed under GNU GPL v2", - "DEPRECATION", - "WARNING", - "Cache entry deserialization failed", - `Ignoring pywin32: markers 'platform_system == "Windows"' don't match your environment`, - `Ignoring None: markers 'sys_platform == "win32"' don't match your environment`, - ]; - for (const msg of ignoredMessages) { - if (stderr.indexOf(msg) !== -1) { - resolve(stdout.concat(stderr)); - } - } - if (stderr.trim().endsWith("pip install --upgrade pip' command.")) { - resolve(stdout.concat(stderr)); + if (stderr.indexOf("Error") !== -1) { + return reject(stderr); } - reject(new Error(stderr)); - return; } - resolve(stdout); + return resolve(stdout.concat(stderr)); } ); }); @@ -656,8 +640,13 @@ export function appendIdfAndToolsToPath() { } } + const containerPath = + process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME; + const defaultEspIdfPath = path.join(containerPath, "esp", "esp-idf"); + const idfPathDir = idfConf.readParameter("idf.espIdfPath"); - modifiedEnv.IDF_PATH = idfPathDir || process.env.IDF_PATH; + modifiedEnv.IDF_PATH = + idfPathDir || process.env.IDF_PATH || defaultEspIdfPath; const adfPathDir = idfConf.readParameter("idf.espAdfPath"); modifiedEnv.ADF_PATH = adfPathDir || process.env.ADF_PATH; diff --git a/src/views/commons/espCommons.scss b/src/views/commons/espCommons.scss index ca63e0e9b..63f8b255f 100644 --- a/src/views/commons/espCommons.scss +++ b/src/views/commons/espCommons.scss @@ -93,7 +93,10 @@ button > span.icon > i:hover { .select select:hover, .textarea, .input:hover { - border-color: var(--vscode-inputOption-activeBorder); + border-color: var(--vscode-inputOption-activeBorder) !important; +} +.select:hover::after { + border-color: var(--vscode-focusBorder) !important; } .select select:active, .select select:focus, diff --git a/src/views/onboarding/App.vue b/src/views/onboarding/App.vue deleted file mode 100644 index 1c20fc26a..000000000 --- a/src/views/onboarding/App.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - - - diff --git a/src/views/onboarding/Download.vue b/src/views/onboarding/Download.vue deleted file mode 100644 index 1a3b0045e..000000000 --- a/src/views/onboarding/Download.vue +++ /dev/null @@ -1,125 +0,0 @@ - - - - - diff --git a/src/views/onboarding/GitPyCheck.vue b/src/views/onboarding/GitPyCheck.vue deleted file mode 100644 index 0e9017a6f..000000000 --- a/src/views/onboarding/GitPyCheck.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - - - diff --git a/src/views/onboarding/Home.vue b/src/views/onboarding/Home.vue deleted file mode 100644 index 2a59e96b7..000000000 --- a/src/views/onboarding/Home.vue +++ /dev/null @@ -1,80 +0,0 @@ - - - - - diff --git a/src/views/onboarding/ToolsSetup.vue b/src/views/onboarding/ToolsSetup.vue deleted file mode 100644 index 1fae6f4ea..000000000 --- a/src/views/onboarding/ToolsSetup.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ConfigurationTarget.vue b/src/views/onboarding/components/ConfigurationTarget.vue deleted file mode 100644 index d4066c163..000000000 --- a/src/views/onboarding/components/ConfigurationTarget.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/IDFDownload.vue b/src/views/onboarding/components/IDFDownload.vue deleted file mode 100644 index 585410903..000000000 --- a/src/views/onboarding/components/IDFDownload.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/IDFManual.vue b/src/views/onboarding/components/IDFManual.vue deleted file mode 100644 index b71ec09df..000000000 --- a/src/views/onboarding/components/IDFManual.vue +++ /dev/null @@ -1,109 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ToolCheck.vue b/src/views/onboarding/components/ToolCheck.vue deleted file mode 100644 index 96bcfe2ec..000000000 --- a/src/views/onboarding/components/ToolCheck.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ToolDownload.vue b/src/views/onboarding/components/ToolDownload.vue deleted file mode 100644 index fe388bb74..000000000 --- a/src/views/onboarding/components/ToolDownload.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ToolsDownloadStep.vue b/src/views/onboarding/components/ToolsDownloadStep.vue deleted file mode 100644 index 6d41351d2..000000000 --- a/src/views/onboarding/components/ToolsDownloadStep.vue +++ /dev/null @@ -1,93 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ToolsManual.vue b/src/views/onboarding/components/ToolsManual.vue deleted file mode 100644 index 57b111a0a..000000000 --- a/src/views/onboarding/components/ToolsManual.vue +++ /dev/null @@ -1,130 +0,0 @@ - - - - - diff --git a/src/views/onboarding/components/ToolsSkipStep.vue b/src/views/onboarding/components/ToolsSkipStep.vue deleted file mode 100644 index bbfb59983..000000000 --- a/src/views/onboarding/components/ToolsSkipStep.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/src/views/onboarding/main.ts b/src/views/onboarding/main.ts deleted file mode 100644 index 59b70ebbe..000000000 --- a/src/views/onboarding/main.ts +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -import Vue from "vue"; -import VueRouter from "vue-router"; -// @ts-ignore -import App from "./App.vue"; -// @ts-ignore -import Download from "./Download.vue"; -// @ts-ignore -import GitPyCheck from "./GitPyCheck.vue"; -// @ts-ignore -import Home from "./Home.vue"; -// @ts-ignore -import ToolSetup from "./ToolsSetup.vue"; -import { store } from "./store"; -import IconifyIcon from "@iconify/vue"; -import folderOpen from "@iconify-icons/codicon/folder-opened"; -import folder from "@iconify-icons/codicon/folder"; -import check from "@iconify-icons/codicon/check"; -import close from "@iconify-icons/codicon/close"; -IconifyIcon.addIcon("folder-opened", folderOpen); -IconifyIcon.addIcon("folder", folder); -IconifyIcon.addIcon("check", check); -IconifyIcon.addIcon("close", close); -Vue.component("iconify-icon", IconifyIcon); - -const routes = [ - { path: "/", component: Home }, - { path: "/download", component: Download }, - { path: "/toolsetup", component: ToolSetup }, - { path: "/gitpycheck", component: GitPyCheck }, -]; - -Vue.use(VueRouter); - -const router = new VueRouter({ - routes, - base: __dirname, -}); - -// tslint:disable-next-line: no-unused-expression -new Vue({ - el: "#app", - components: { App }, - store, - data: { - versions: [], - }, - router, - template: "", -}); - -window.addEventListener("message", (event) => { - const message = event.data; - switch (message.command) { - case "load_idf_path": - if (message.idf_path) { - store.commit("setIdfPath", message.idf_path); - } - break; - case "load_idf_tools_path": - if (message.idf_tools_path) { - store.commit("setIdfToolsPath", message.idf_tools_path); - } - break; - case "load_custom_paths": - if (message.custom_paths) { - store.commit("setCustomExtraPaths", message.custom_paths); - } - if (message.custom_vars) { - store.commit("setEnvVars", message.custom_vars); - } - break; - case "load_env_vars_def": - if (message.env_vars) { - store.commit("setEnvVars", message.env_vars); - } - break; - case "load_idf_download_path": - if (message.idf_path) { - store.commit("setIdfDownloadPath", message.idf_path); - } - break; - case "load_idf_versions": - if (message.versions) { - store.commit("setEspIdfVersionList", message.versions); - } - break; - case "load_git_version": - if (message.gitVersion) { - store.commit("setGitVersion", message.gitVersion); - } - break; - case "load_py_version_list": - if (message.pyVersionList) { - store.commit("setPyVersionList", message.pyVersionList); - } - break; - case "notify_idf_downloaded": - if (message.downloadedPath) { - store.commit("setDownloadedZipPath", message.downloadedPath); - } - break; - case "notify_idf_extracted": - store.commit("setIsIDFZipExtracted", true); - break; - case "response_check_idf_path": - store.commit("showIdfPathCheck", true); - store.commit("updateDoesIdfPathExist", message.doesIdfExists); - break; - case "response_check_idf_version": - store.commit("updateIdfVersion", message.version); - break; - case "respond_check_idf_tools_path": - if (message.dictToolsExist) { - store.commit("setShowIdfToolsChecks", true); - store.commit("setToolsCheckResults", message.dictToolsExist); - } - break; - case "load_path_delimiter": - store.commit("setPathDelimiter", message.pathDelimiter); - break; - case "reply_required_tools_versions": - if (message.requiredToolsVersions) { - store.commit("setRequiredToolsVersion", message.requiredToolsVersions); - } - break; - case "update_pkgs_download_percentage": - if (message.updatedPkgDownloadStatus) { - store.commit( - "updatePkgDownloadPercentage", - message.updatedPkgDownloadStatus - ); - } - break; - case "update_espidf_download_percentage": - if (message.updatedIdfDownloadStatus) { - store.commit( - "updateIdfDownloadProgress", - message.updatedIdfDownloadStatus - ); - } - break; - case "set_selected_download_state": - if (message.state) { - store.commit("setSelectedIdfDownloadState", message.state); - } - break; - case "set_tools_check_finish": - store.commit("setToolCheckFinish", true); - break; - case "set_tools_setup_finish": - store.commit("setToolSetupFinish"); - break; - case "checksum_result": - if (message.isChecksumEqual) { - store.commit("updatePkgHashResult", message.isChecksumEqual); - } - break; - case "response_py_req_check": - case "response_py_req_install": - if (message.py_req_log) { - store.commit("setPyLog", message.py_req_log); - } - break; - case "set_py_setup_finish": - store.commit("setPySetupFinish", true); - break; - case "update_pkg_download_detail": - if (message.updatedPkgDownloadDetail) { - store.commit("updateDownloadDetail", message.updatedPkgDownloadDetail); - } - break; - case "update_espidf_download_detail": - if (message.updatedIdfDownloadDetail) { - store.commit( - "updateIdfDownloadDetail", - message.updatedIdfDownloadDetail - ); - } - break; - case "set_pkg_download_failed": - if (message.updatedPkgFailed) { - store.commit("updatePkgFailed", message.updatePkgFailed); - } - break; - case "response_selected_tools_folder": - if (message.selected_folder) { - store.commit("setIdfToolsPath", message.selected_folder); - } - break; - case "response_selected_espidf_folder": - if (message.selected_folder) { - store.commit("setIdfPath", message.selected_folder); - store.commit("setIdfDownloadPath", message.selected_folder); - store.commit("showIdfPathCheck", false); - } - break; - case "load_show_onboarding": - if (message.show_onboarding_on_init !== undefined) { - store.commit( - "setShowOnboardingOnInit", - message.show_onboarding_on_init - ); - } - break; - case "resetConfigurationTarget": - if (message.confTarget) { - store.commit("updateConfTarget", message.confTarget); - } - break; - case "loadWorkspaceFolders": - if (message.folders) { - store.commit("setWorkspaceFolders", message.folders); - } - break; - case "load_python_bin_path": - if (message.pythonBinPath) { - store.commit("setPythonBinPath", message.pythonBinPath); - } - break; - case "set_py_sys_path_is_valid": - store.commit("setPythonSysIsValid", true); - break; - default: - break; - } -}); diff --git a/src/views/onboarding/store/actions.ts b/src/views/onboarding/store/actions.ts deleted file mode 100644 index 4af15fc84..000000000 --- a/src/views/onboarding/store/actions.ts +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { ActionTree } from "vuex"; -import { IState } from "./types"; - -declare var acquireVsCodeApi: any; -let vscode: any; -try { - vscode = acquireVsCodeApi(); -} catch (error) { - // tslint:disable-next-line: no-console - console.error(error); -} - -export const actions: ActionTree = { - checkIdfPath(context) { - vscode.postMessage({ - command: "checkIdfPath", - new_value: context.state.idfPath, - }); - }, - saveIdfPath(context) { - vscode.postMessage({ - command: "saveNewIdfPath", - idf_path: context.state.idfPath, - }); - }, - checkManualExportPaths(context) { - vscode.postMessage({ - command: "checkIdfToolsForPaths", - custom_paths: context.state.customExtraPaths, - custom_vars: context.state.envVars, - py_bin_path: context.state.pyBinPath, - }); - }, - getExamplesList() { - vscode.postMessage({ command: "getExamplesList" }); - }, - getRequiredTools() { - vscode.postMessage({ command: "getRequiredToolsInfo" }); - }, - downloadTools(context) { - vscode.postMessage({ - command: "downloadToolsInPath", - tools_path: context.state.idfToolsPath, - idf_path: context.state.idfPath, - }); - }, - saveCustomPathsEnvVars(context) { - vscode.postMessage({ - command: "saveEnvVars", - custom_paths: context.state.customExtraPaths, - custom_vars: context.state.envVars, - }); - }, - openEspIdfFolder() { - vscode.postMessage({ command: "openEspIdfFolder" }); - }, - openToolsFolder() { - vscode.postMessage({ command: "openToolsFolder" }); - }, - downloadEspIdf(context) { - vscode.postMessage({ - command: "downloadEspIdfVersion", - selectedVersion: context.state.selectedIdfVersion, - idfPath: context.state.idfDownloadPath, - }); - }, - requestInitValues(context) { - vscode.postMessage({ command: "requestInitValues" }); - }, - savePythonToUse(context, pyBinPath) { - vscode.postMessage({ - command: "savePythonBinary", - selectedPyBin: pyBinPath, - }); - }, - updateShowOnboardingOnInit(context, value) { - vscode.postMessage({ - command: "saveShowOnboarding", - showOnboarding: value, - }); - }, - updateConfTarget(context, value) { - vscode.postMessage({ - command: "updateConfigurationTarget", - confTarget: parseInt(value, 10), - workspaceFolder: context.state.selectedWorkspaceFolder, - }); - }, -}; diff --git a/src/views/onboarding/store/index.ts b/src/views/onboarding/store/index.ts deleted file mode 100644 index 806c992ab..000000000 --- a/src/views/onboarding/store/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import Vue from "vue"; -import Vuex from "vuex"; -import { onboarding } from "./onboarding"; - -Vue.use(Vuex); - -export const store = new Vuex.Store(onboarding); diff --git a/src/views/onboarding/store/mutations.ts b/src/views/onboarding/store/mutations.ts deleted file mode 100644 index 61b3f24c8..000000000 --- a/src/views/onboarding/store/mutations.ts +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { MutationTree } from "vuex"; -import { - IEspIdfLink, - IEspIdfStatus, - IState, - IToolStatus, - IToolVersionResult, -} from "./types"; - -export const mutations: MutationTree = { - setCustomExtraPaths(state, newCustomExtraPaths: string) { - const newState = state; - newState.customExtraPaths = newCustomExtraPaths; - Object.assign(state, newState); - }, - setIdfPath(state, newPath: string) { - const newState = state; - newState.idfPath = newPath; - Object.assign(state, newState); - }, - setIdfDownloadPath(state, newPath: string) { - const newState = state; - newState.idfDownloadPath = newPath; - Object.assign(state, newState); - }, - setIsIDFZipExtracted(state, val: boolean) { - const newState = state; - newState.isIDFZipExtracted = val; - Object.assign(state, newState); - }, - showIdfPathCheck(state, areCheckVisible: boolean) { - const newState = state; - newState.showIdfPathCheck = areCheckVisible; - Object.assign(state, newState); - }, - updateDoesIdfPathExist(state, isThereAnIdfBinary: boolean) { - const newState = state; - newState.doesIdfPathExist = isThereAnIdfBinary; - Object.assign(state, newState); - }, - setEnvVars(state, newEnvVarsValues: object) { - const newState = state; - newState.envVars = newEnvVarsValues; - Object.assign(state, newState); - }, - setIdfToolsPath(state, newPath: string) { - const newState = state; - newState.idfToolsPath = newPath; - Object.assign(state, newState); - }, - setGitVersion(state, gitVersion: string) { - const newState = state; - newState.gitVersion = gitVersion; - Object.assign(state, newState); - }, - setPyVersionList(state, pyVersionList: string[]) { - const newState = state; - newState.pyVersionList = pyVersionList; - newState.selectedPythonVersion = pyVersionList[0]; - Object.assign(state, newState); - }, - setPythonBinPath(state, pythonBinPath: string) { - const newState = state; - newState.pyBinPath = pythonBinPath; - Object.assign(state, newState); - }, - setPythonSysIsValid(state, isValid: boolean) { - const newState = state; - newState.pythonSysPathIsValid = isValid; - Object.assign(state, newState); - }, - setPyLog(state, pyLog: string) { - const newState = state; - newState.pyLog = pyLog; - Object.assign(state, newState); - }, - setToolSetupMode(state, setupMode: string) { - const newState = state; - newState.toolsSelectedSetupMode = setupMode; - if (setupMode === "empty") { - newState.isInstallationCompleted = false; - } - Object.assign(state, newState); - }, - setToolCheckFinish(state, isCompleted: boolean) { - const newState = state; - newState.isToolsCheckCompleted = isCompleted; - Object.assign(state, newState); - }, - setToolSetupFinish(state) { - const newState = state; - newState.isInstallationCompleted = true; - Object.assign(state, newState); - }, - setPySetupFinish(state, isPySetupFinish) { - const newState = state; - newState.isPyInstallCompleted = isPySetupFinish; - Object.assign(state, newState); - }, - setShowIdfToolsChecks(state, areChecksVisible: boolean) { - const newState = state; - newState.showIdfToolsChecks = areChecksVisible; - Object.assign(state, newState); - }, - setShowOnboardingOnInit(state, showOnboardingOnInit: boolean) { - const newState = state; - newState.showOnboardingOnInit = showOnboardingOnInit; - Object.assign(state, newState); - }, - setToolsCheckResults(state, checkResults: IToolVersionResult[]) { - const newState = state; - newState.toolsCheckResults = checkResults; - Object.assign(state, newState); - }, - setPathDelimiter(state, pathDelimiter: string) { - const newState = state; - newState.pathDelimiter = pathDelimiter; - Object.assign(state, newState); - }, - setRequiredToolsVersion(state, requiredToolsVersions: IToolStatus[]) { - const newState = state; - newState.requiredToolsVersions = requiredToolsVersions; - Object.assign(state, newState); - }, - setSelectedPythonVersion(state, newPythonSelected) { - const newState = state; - newState.selectedPythonVersion = newPythonSelected; - Object.assign(state, newState); - }, - setEspIdfVersionList(state, idfVersionList: IEspIdfLink[]) { - const newState = state; - newState.idfVersionList = idfVersionList; - newState.selectedIdfVersion = idfVersionList[0]; - newState.idfDownloadStatus = { - id: idfVersionList[0].name, - progress: "0.00%", - progressDetail: "", - }; - Object.assign(state, newState); - }, - setSelectedIdfVersion(state, selectedVersion: IEspIdfLink) { - const newState = state; - newState.selectedIdfVersion = selectedVersion; - newState.idfDownloadStatus = { - id: selectedVersion.name, - progress: "0.00%", - progressDetail: "", - }; - Object.assign(state, newState); - }, - setSelectedIdfDownloadState(state, selectedState: string) { - const newState = state; - newState.idfDownloadState = selectedState; - newState.isIDFZipExtracted = false; - newState.downloadedIdfZipPath = ""; - Object.assign(state, newState); - }, - setSelectedWorkspaceFolder(state, newWorkspaceFolder: string) { - const newState = state; - newState.selectedWorkspaceFolder = newWorkspaceFolder; - Object.assign(state, newState); - }, - setWorkspaceFolders(state, workspaceFolders: string[]) { - const newState = state; - newState.workspaceFolders = workspaceFolders; - newState.selectedWorkspaceFolder = workspaceFolders[0]; - Object.assign(state, newState); - }, - setDownloadedZipPath(state, downloadedIdfZipPath: string) { - const newState = state; - newState.downloadedIdfZipPath = downloadedIdfZipPath; - Object.assign(state, newState); - }, - updateIdfVersion(state, idfVersion) { - const newState = state; - newState.idfVersion = idfVersion; - Object.assign(state, newState); - }, - updatePkgDownloadPercentage(state, pkgUpdatePercentage: IToolStatus) { - const newState = state; - newState.requiredToolsVersions = state.requiredToolsVersions.map( - (pkgInfo) => { - const newPkgPercentage: IToolStatus = { - expected: pkgInfo.expected, - hashResult: pkgInfo.hashResult, - id: pkgInfo.id, - progress: pkgUpdatePercentage.progress, - progressDetail: pkgInfo.progressDetail, - hasFailed: pkgInfo.hasFailed, - }; - return pkgInfo.id === pkgUpdatePercentage.id - ? newPkgPercentage - : pkgInfo; - } - ); - Object.assign(state, newState); - }, - updatePkgHashResult(state, pkgUpdateHashResult: IToolStatus) { - const newState = state; - newState.requiredToolsVersions = state.requiredToolsVersions.map( - (pkgInfo) => { - const newPkg: IToolStatus = { - expected: pkgInfo.expected, - hashResult: pkgUpdateHashResult.hashResult, - id: pkgInfo.id, - progress: pkgInfo.progress, - progressDetail: pkgInfo.progressDetail, - hasFailed: pkgInfo.hasFailed, - }; - return pkgInfo.id === pkgUpdateHashResult.id ? newPkg : pkgInfo; - } - ); - Object.assign(state, newState); - }, - updateDownloadDetail(state, updatedPkgDownloadDetail) { - const newState = state; - newState.requiredToolsVersions = state.requiredToolsVersions.map( - (pkgInfo) => { - const newPkg: IToolStatus = { - expected: pkgInfo.expected, - hashResult: pkgInfo.hashResult, - id: pkgInfo.id, - progress: pkgInfo.progress, - progressDetail: updatedPkgDownloadDetail.progressDetail, - hasFailed: pkgInfo.hasFailed, - }; - return pkgInfo.id === updatedPkgDownloadDetail.id ? newPkg : pkgInfo; - } - ); - Object.assign(state, newState); - }, - updatePkgFailed(state, updatePkgFailed) { - const newState = state; - newState.requiredToolsVersions = state.requiredToolsVersions.map( - (pkgInfo) => { - const newPkg: IToolStatus = { - expected: pkgInfo.expected, - hashResult: pkgInfo.hashResult, - id: pkgInfo.id, - progress: pkgInfo.progress, - progressDetail: pkgInfo.progressDetail, - hasFailed: updatePkgFailed.hasFailed, - }; - return pkgInfo.id === updatePkgFailed.id ? newPkg : pkgInfo; - } - ); - Object.assign(state, newState); - }, - updateConfTarget(state, confTarget) { - const newState = state; - newState.selectedConfTarget = confTarget; - Object.assign(state, newState); - }, - updateIdfDownloadDetail(state, updatedIdfDownloadDetail: IEspIdfStatus) { - const newState = state; - newState.idfDownloadStatus = { - id: newState.idfDownloadStatus.id, - progress: newState.idfDownloadStatus.progress, - progressDetail: updatedIdfDownloadDetail.progressDetail, - }; - Object.assign(state, newState); - }, - updateIdfDownloadProgress(state, updatedIdfDownloadProgress: IEspIdfStatus) { - const newState = state; - newState.idfDownloadStatus = { - id: newState.idfDownloadStatus.id, - progress: updatedIdfDownloadProgress.progress, - progressDetail: newState.idfDownloadStatus.progressDetail, - }; - Object.assign(state, newState); - }, -}; diff --git a/src/views/onboarding/store/onboarding.ts b/src/views/onboarding/store/onboarding.ts deleted file mode 100644 index 92785eba3..000000000 --- a/src/views/onboarding/store/onboarding.ts +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { StoreOptions } from "vuex"; -import { actions } from "./actions"; -import { mutations } from "./mutations"; -import { IState } from "./types"; - -const CONF_TARGET_GLOBAL = 1; - -export const onboardState: IState = { - customExtraPaths: "", - downloadedIdfZipPath: "", - doesIdfPathExist: false, - envVars: {}, - gitVersion: "", - idfDownloadPath: "", - idfDownloadStatus: null, - idfDownloadState: "empty", - isIDFZipExtracted: false, - idfPath: "", - idfVersion: "", - idfVersionList: [], - idfToolsPath: "", - isInstallationCompleted: false, - isPyInstallCompleted: false, - isToolsCheckCompleted: false, - pathDelimiter: "", - pyBinPath: "", - pyLog: "", - pyVersionList: [], - pythonSysPathIsValid: false, - requiredToolsVersions: [], - selectedConfTarget: CONF_TARGET_GLOBAL, - selectedIdfVersion: undefined, - selectedPythonVersion: "", - selectedWorkspaceFolder: "", - showIdfPathCheck: false, - showIdfToolsChecks: false, - showOnboardingOnInit: true, - toolsSelectedSetupMode: "empty", - toolsCheckResults: [], - workspaceFolders: [], -}; - -export const onboarding: StoreOptions = { - actions, - mutations, - state: onboardState, -}; diff --git a/src/views/onboarding/store/types.ts b/src/views/onboarding/store/types.ts deleted file mode 100644 index 50fe21311..000000000 --- a/src/views/onboarding/store/types.ts +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2019 Espressif Systems (Shanghai) CO LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -export interface IToolVersionResult { - actual: string; - doesToolExist: boolean; - expected: string; - id: string; -} - -export interface IToolStatus { - expected: string; - hashResult: boolean; - hasFailed: boolean; - id: string; - progress: string; - progressDetail: string; -} - -export interface IEspIdfLink { - filename: string; - name: string; - mirror: string; - url: string; -} - -export interface IEspIdfStatus { - id: string; - progress: string; - progressDetail: string; -} - -export interface IState { - customExtraPaths: string; - downloadedIdfZipPath: string; - doesIdfPathExist: boolean; - envVars: object; - gitVersion: string; - idfPath: string; - idfDownloadPath: string; - idfDownloadStatus: IEspIdfStatus; - idfDownloadState: string; - idfToolsPath: string; - idfVersion: string; - idfVersionList: IEspIdfLink[]; - isIDFZipExtracted: boolean; - isInstallationCompleted: boolean; - isPyInstallCompleted: boolean; - isToolsCheckCompleted: boolean; - pathDelimiter: string; - pyBinPath: string; - pyLog: string; - pyVersionList: string[]; - pythonSysPathIsValid: boolean; - requiredToolsVersions: IToolStatus[]; - selectedConfTarget: number; - selectedIdfVersion: IEspIdfLink; - selectedPythonVersion: string; - selectedWorkspaceFolder: string; - showIdfPathCheck: boolean; - showIdfToolsChecks: boolean; - showOnboardingOnInit: boolean; - toolsCheckResults: IToolVersionResult[]; - toolsSelectedSetupMode: string; - workspaceFolders: string[]; -} diff --git a/src/views/setup/App.vue b/src/views/setup/App.vue new file mode 100644 index 000000000..059393322 --- /dev/null +++ b/src/views/setup/App.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/views/setup/Home.vue b/src/views/setup/Home.vue new file mode 100644 index 000000000..83c2c21b6 --- /dev/null +++ b/src/views/setup/Home.vue @@ -0,0 +1,207 @@ + + + + + diff --git a/src/views/setup/Install.vue b/src/views/setup/Install.vue new file mode 100644 index 000000000..027e3f4a3 --- /dev/null +++ b/src/views/setup/Install.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/views/setup/Status.vue b/src/views/setup/Status.vue new file mode 100644 index 000000000..cbd6848b3 --- /dev/null +++ b/src/views/setup/Status.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/src/views/setup/ToolsCustom.vue b/src/views/setup/ToolsCustom.vue new file mode 100644 index 000000000..bfba9eb22 --- /dev/null +++ b/src/views/setup/ToolsCustom.vue @@ -0,0 +1,130 @@ + + + + + diff --git a/src/views/setup/components/IdfDownload.vue b/src/views/setup/components/IdfDownload.vue new file mode 100644 index 000000000..f9b57626a --- /dev/null +++ b/src/views/setup/components/IdfDownload.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/src/views/setup/components/folderOpen.vue b/src/views/setup/components/folderOpen.vue new file mode 100644 index 000000000..31db715a1 --- /dev/null +++ b/src/views/setup/components/folderOpen.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/views/setup/components/selectEspIdf.vue b/src/views/setup/components/selectEspIdf.vue new file mode 100644 index 000000000..ed1e4d7e4 --- /dev/null +++ b/src/views/setup/components/selectEspIdf.vue @@ -0,0 +1,121 @@ + + + + + diff --git a/src/views/setup/components/selectPyVersion.vue b/src/views/setup/components/selectPyVersion.vue new file mode 100644 index 000000000..1a75bcd32 --- /dev/null +++ b/src/views/setup/components/selectPyVersion.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/setup/components/toolDownload.vue b/src/views/setup/components/toolDownload.vue new file mode 100644 index 000000000..ffa051cde --- /dev/null +++ b/src/views/setup/components/toolDownload.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/views/setup/components/toolManual.vue b/src/views/setup/components/toolManual.vue new file mode 100644 index 000000000..c77e25f71 --- /dev/null +++ b/src/views/setup/components/toolManual.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/views/setup/main.ts b/src/views/setup/main.ts new file mode 100644 index 000000000..99f5b45bc --- /dev/null +++ b/src/views/setup/main.ts @@ -0,0 +1,242 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import Vue from "vue"; +import VueRouter from "vue-router"; +import { store } from "./store"; +// @ts-ignore +import App from "./App.vue"; +// @ts-ignore +import ToolsCustom from "./ToolsCustom.vue"; +// @ts-ignore +import Home from "./Home.vue"; +// @ts-ignore +import Install from "./Install.vue"; +// @ts-ignore +import Status from "./Status.vue"; +import IconifyIcon from "@iconify/vue"; +import check from "@iconify-icons/codicon/check"; +import close from "@iconify-icons/codicon/close"; +import folder from "@iconify-icons/codicon/folder"; +import folderOpen from "@iconify-icons/codicon/folder-opened"; +import home from "@iconify-icons/codicon/home"; +import loading from "@iconify-icons/codicon/loading"; +IconifyIcon.addIcon("check", check); +IconifyIcon.addIcon("close", close); +IconifyIcon.addIcon("folder", folder); +IconifyIcon.addIcon("folder-opened", folderOpen); +IconifyIcon.addIcon("home", home); +IconifyIcon.addIcon("loading", loading); +Vue.component("iconify-icon", IconifyIcon); + +const routes = [ + { path: "/", component: Home }, + { path: "/autoinstall", component: Install }, + { path: "/custom", component: ToolsCustom }, + { path: "/status", component: Status }, +]; + +Vue.use(VueRouter); + +export const router = new VueRouter({ + routes, + base: __dirname, +}); + +const app = new Vue({ + components: { App }, + data: { + isLoaded: false, + versions: [], + }, + el: "#app", + router, + store, + template: "", +}); + +window.addEventListener("message", (event) => { + const msg = event.data; + switch (msg.command) { + case "goToCustomPage": + if (msg.page) { + app.$router.push(msg.page); + } + if (typeof msg.installing !== "undefined") { + store.commit("setIsIdfInstalling", msg.installing); + } + break; + case "initialLoad": + if (msg.espToolsPath) { + store.commit("setToolsFolder", msg.espToolsPath); + } + if (msg.idfVersions) { + store.commit("setEspIdfVersionList", msg.idfVersions); + } + if (msg.idfVersion) { + store.commit("setIdfVersion", msg.idfVersion); + } + if (msg.pyVersionList) { + store.commit("setPyVersionsList", msg.pyVersionList); + } + if (msg.gitVersion) { + store.commit("setGitVersion", msg.gitVersion); + } + if (msg.espIdf) { + store.commit("setEspIdfPath", msg.espIdf); + } + if (msg.espIdfContainer) { + store.commit("setEspIdfContainerPath", msg.espIdfContainer); + } + if (msg.pyBinPath) { + store.commit("setManualPyPath", msg.pyBinPath); + } + if (msg.toolsResults) { + store.commit("setToolsResult", msg.toolsResults); + } + if (msg.hasPrerequisites) { + store.commit("setHasPrerequisites", msg.hasPrerequisites); + } + if (msg.pathSep) { + store.commit("setPathSep", msg.pathSep); + } + break; + case "setEspIdfErrorStatus": + if (typeof msg.errorMsg !== "undefined") { + store.commit("setEspIdfErrorStatus", msg.errorMsg); + store.commit("setIsIdfInstalling", false); + } + break; + case "setIdfVersion": + if (msg.idfVersion) { + store.commit("setIdfVersion", msg.idfVersion); + } + break; + case "setIsIdfInstalling": + if (typeof msg.installing !== "undefined") { + store.commit("setIsIdfInstalling", msg.installing); + } + break; + case "setIsInstalled": + if (msg.isInstalled) { + store.commit("setIsIdfInstalled", msg.isInstalled); + } + break; + case "setPyExecErrorStatus": + if (msg.errorMsg) { + store.commit("setPyExecErrorStatus", msg.errorMsg); + store.commit("setIsIdfInstalling", false); + } + break; + case "setRequiredToolsInfo": + if (msg.toolsInfo) { + store.commit("setToolsResult", msg.toolsInfo); + } + break; + case "setSetupMode": + if (typeof msg.setupMode !== "undefined") { + store.commit("setSetupMode", msg.setupMode); + } + break; + case "updateEspIdfFolder": + if (msg.selectedFolder) { + store.commit("setEspIdfPath", msg.selectedFolder); + } + break; + case "updateEspIdfContainerFolder": + if (msg.selectedContainerFolder) { + store.commit("setEspIdfContainerPath", msg.selectedContainerFolder); + } + break; + case "updateEspIdfStatus": + if (typeof msg.status !== "undefined") { + store.commit("setStatusEspIdf", msg.status); + } + break; + case "updateEspIdfToolsFolder": + if (msg.selectedToolsFolder) { + store.commit("setToolsFolder", msg.selectedToolsFolder); + } + break; + case "updateEspIdfToolsStatus": + if (msg.status) { + store.commit("setStatusEspIdfTools", msg.status); + } + break; + case "updateIdfDownloadStatusDetail": + if (msg.detail) { + store.commit("setIdfDownloadStatusDetail", msg.detail); + } + if (msg.id) { + store.commit("setIdfDownloadStatusId", msg.id); + } + break; + case "updateIdfDownloadStatusPercentage": + if (msg.percentage) { + store.commit("setIdfDownloadStatusPercentage", msg.percentage); + } + if (msg.id) { + store.commit("setIdfDownloadStatusId", msg.id); + } + break; + case "updatePkgChecksumResult": + if (msg.id && msg.hashResult) { + store.commit("setToolChecksum", { + name: msg.id, + checksum: msg.hashResult, + }); + } + break; + case "updatePkgDownloadDetail": + if (msg.id && msg.progressDetail) { + store.commit("setToolDetail", { + name: msg.id, + detail: msg.progressDetail, + }); + } + break; + case "updatePkgDownloadFailed": + if (msg.id && msg.hasFailed) { + store.commit("setToolFailed", { + name: msg.id, + hasFailed: msg.hasFailed, + }); + } + break; + case "updatePkgDownloadPercentage": + if (msg.id && msg.percentage) { + store.commit("setToolPercentage", { + name: msg.id, + percentage: msg.percentage, + }); + } + break; + case "updatePyReqsLog": + if (msg.pyReqsLog) { + store.commit("setPyReqsLog", msg.pyReqsLog); + } + break; + case "updatePythonPath": + if (msg.selectedPyPath) { + store.commit("setManualPyPath", msg.selectedPyPath); + } + break; + case "updatePyVEnvStatus": + if (msg.status) { + store.commit("setStatusPyVEnv", msg.status); + } + break; + default: + break; + } +}); diff --git a/src/views/setup/store/index.ts b/src/views/setup/store/index.ts new file mode 100644 index 000000000..35490c35a --- /dev/null +++ b/src/views/setup/store/index.ts @@ -0,0 +1,392 @@ +// Copyright 2019 Espressif Systems (Shanghai) CO LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Vue from "vue"; +import { ActionTree, Store, StoreOptions, MutationTree } from "vuex"; +import Vuex from "vuex"; +import { + IdfMirror, + IEspIdfLink, + IEspIdfTool, + IDownload, + SetupMode, + StatusType, +} from "../types"; + +export interface IState { + areToolsValid: boolean; + espIdf: string; + espIdfContainer: string; + espIdfErrorStatus: string; + espIdfVersionList: IEspIdfLink[]; + exportedToolsPaths: string; + exportedVars: string; + gitVersion: string; + hasPrerequisites: boolean; + idfDownloadStatus: IDownload; + idfVersion: string; + isEspIdfValid: boolean; + isIdfInstalling: boolean; + isIdfInstalled: boolean; + manualPythonPath: string; + pathSep: string; + pyExecErrorStatus: string; + pyReqsLog: string; + pyVersionsList: string[]; + selectedEspIdfVersion: IEspIdfLink; + selectedIdfMirror: IdfMirror; + selectedSysPython: string; + setupMode: SetupMode; + statusEspIdf: StatusType; + statusEspIdfTools: StatusType; + statusPyVEnv: StatusType; + toolsFolder: string; + toolsResults: IEspIdfTool[]; +} + +export const setupState: IState = { + areToolsValid: false, + espIdf: "", + espIdfContainer: "", + espIdfErrorStatus: "", + espIdfVersionList: [], + exportedToolsPaths: "", + exportedVars: "", + gitVersion: "", + hasPrerequisites: false, + idfDownloadStatus: { + id: "", + progress: "", + progressDetail: "", + }, + idfVersion: "", + isEspIdfValid: false, + isIdfInstalling: false, + isIdfInstalled: false, + manualPythonPath: "", + pathSep: "/", + pyExecErrorStatus: "", + pyReqsLog: "", + pyVersionsList: [], + selectedEspIdfVersion: { + filename: "", + mirror: "", + name: "", + url: "", + }, + selectedIdfMirror: IdfMirror.Github, + selectedSysPython: "", + setupMode: SetupMode.express, + statusEspIdf: StatusType.started, + statusEspIdfTools: StatusType.pending, + statusPyVEnv: StatusType.pending, + toolsFolder: "", + toolsResults: [], +}; + +declare var acquireVsCodeApi: any; +let vscode: any; +try { + vscode = acquireVsCodeApi(); +} catch (error) { + // tslint:disable-next-line: no-console + console.error(error); +} + +export const actions: ActionTree = { + checkEspIdfTools(context) { + const pyPath = + context.state.selectedSysPython === + context.state.pyVersionsList[context.state.pyVersionsList.length - 1] + ? context.state.manualPythonPath + : context.state.selectedSysPython; + vscode.postMessage({ + command: "checkEspIdfTools", + espIdf: context.state.espIdf, + pyPath, + toolsPath: context.state.toolsResults, + }); + }, + installEspIdf(context) { + const pyPath = + context.state.selectedSysPython === + context.state.pyVersionsList[context.state.pyVersionsList.length - 1] + ? context.state.manualPythonPath + : context.state.selectedSysPython; + vscode.postMessage({ + command: "installEspIdf", + espIdfContainer: context.state.espIdfContainer, + manualEspIdfPath: context.state.espIdf, + mirror: context.state.selectedIdfMirror, + selectedEspIdfVersion: context.state.selectedEspIdfVersion, + selectedPyPath: pyPath, + setupMode: context.state.setupMode, + }); + }, + installEspIdfTools(context) { + const pyPath = + context.state.selectedSysPython === + context.state.pyVersionsList[context.state.pyVersionsList.length - 1] + ? context.state.manualPythonPath + : context.state.selectedSysPython; + vscode.postMessage({ + command: "installEspIdfTools", + espIdf: context.state.espIdf, + pyPath, + toolsPath: context.state.toolsFolder, + }); + }, + openEspIdfFolder() { + vscode.postMessage({ + command: "openEspIdfFolder", + }); + }, + openEspIdfContainerFolder() { + vscode.postMessage({ + command: "openEspIdfContainerFolder", + }); + }, + openEspIdfToolsFolder() { + vscode.postMessage({ + command: "openEspIdfToolsFolder", + }); + }, + openPythonPath() { + vscode.postMessage({ + command: "openPythonPath", + }); + }, + requestInitialValues() { + vscode.postMessage({ + command: "requestInitialValues", + }); + }, + saveCustomSettings(context) { + const pyPath = + context.state.selectedSysPython === + context.state.pyVersionsList[context.state.pyVersionsList.length - 1] + ? context.state.manualPythonPath + : context.state.selectedSysPython; + vscode.postMessage({ + command: "saveCustomSettings", + espIdfPath: context.state.espIdf, + pyBinPath: pyPath, + tools: context.state.toolsResults, + toolsPath: context.state.toolsFolder, + }); + }, + useDefaultSettings() { + vscode.postMessage({ + command: "usePreviousSettings", + }); + }, +}; + +export const mutations: MutationTree = { + setEspIdfPath(state, espIdf: string) { + const newState = state; + newState.espIdf = espIdf; + Object.assign(state, newState); + }, + setEspIdfContainerPath(state, espIdfContainer: string) { + const newState = state; + newState.espIdfContainer = espIdfContainer; + Object.assign(state, newState); + }, + setEspIdfErrorStatus(state, errorStatus: string) { + const newState = state; + newState.espIdfErrorStatus = errorStatus; + Object.assign(state, newState); + }, + setEspIdfVersionList(state, espIdfVersionList: IEspIdfLink[]) { + const newState = state; + newState.espIdfVersionList = espIdfVersionList; + if (espIdfVersionList && espIdfVersionList.length > 0) { + newState.selectedEspIdfVersion = espIdfVersionList[0]; + } + Object.assign(state, newState); + }, + setGitVersion(state, gitVersion) { + const newState = state; + newState.gitVersion = gitVersion; + Object.assign(state, newState); + }, + setHasPrerequisites(state, hasRequisites: boolean) { + const newState = state; + newState.hasPrerequisites = hasRequisites; + Object.assign(state, newState); + }, + setIdfMirror(state, mirrorToUse: IdfMirror) { + const newState = state; + newState.selectedIdfMirror = mirrorToUse; + Object.assign(state, mirrorToUse); + }, + setIdfDownloadStatusId(state, id: string) { + const newState = state; + newState.idfDownloadStatus.id = id; + Object.assign(state, newState); + }, + setIdfDownloadStatusPercentage(state, progress: string) { + const newState = state; + newState.idfDownloadStatus.progress = progress; + Object.assign(state, newState); + }, + setIdfDownloadStatusDetail(state, progressDetail: string) { + const newState = state; + newState.idfDownloadStatus.progressDetail = progressDetail; + Object.assign(state, newState); + }, + setIdfVersion(state, idfVersion) { + const newState = state; + newState.idfVersion = idfVersion; + Object.assign(state, newState); + }, + setIsIdfInstalled(state, isInstalled: boolean) { + const newState = state; + newState.isIdfInstalled = isInstalled; + Object.assign(state, newState); + }, + setIsIdfInstalling(state, isInstalled: boolean) { + const newState = state; + newState.isIdfInstalling = isInstalled; + Object.assign(state, newState); + }, + setManualPyPath(state, manualPyPath) { + const newState = state; + newState.manualPythonPath = manualPyPath; + Object.assign(state, newState); + }, + setPathSep(state, pathSep: string) { + const newState = state; + newState.pathSep = pathSep; + Object.assign(state, newState); + }, + setPyExecErrorStatus(state, errorStatus: string) { + const newState = state; + newState.pyExecErrorStatus = errorStatus; + Object.assign(state, newState); + }, + setPyReqsLog(state, pyReqsLog: string) { + const newState = state; + newState.pyReqsLog = pyReqsLog; + Object.assign(state, newState); + }, + setPyVersionsList(state, pyVersionsList: string[]) { + const newState = state; + newState.pyVersionsList = pyVersionsList; + if (pyVersionsList && pyVersionsList.length > 0) { + newState.selectedSysPython = pyVersionsList[0]; + } + Object.assign(state, newState); + }, + setSelectedEspIdfVersion(state, selectedEspIdfVersion: IEspIdfLink) { + const newState = state; + newState.selectedEspIdfVersion = selectedEspIdfVersion; + newState.idfDownloadStatus.id = selectedEspIdfVersion.name; + Object.assign(state, newState); + }, + setSelectedSysPython(state, selectedSysPython: string) { + const newState = state; + newState.selectedSysPython = selectedSysPython; + Object.assign(state, newState); + }, + setSetupMode(state, setupMode: SetupMode) { + const newState = state; + newState.setupMode = setupMode; + Object.assign(state, newState); + }, + setToolsFolder(state, toolsFolder: string) { + const newState = state; + newState.toolsFolder = toolsFolder; + Object.assign(state, newState); + }, + setToolChecksum(state, toolData: { name: string; checksum: boolean }) { + const newState = state; + for (let i = 0; i < newState.toolsResults.length; i++) { + if (newState.toolsResults[i].name === toolData.name) { + newState.toolsResults[i].hashResult = toolData.checksum; + break; + } + } + Object.assign(state, newState); + }, + setToolDetail(state, toolData: { name: string; detail: string }) { + const newState = state; + for (let i = 0; i < newState.toolsResults.length; i++) { + if (newState.toolsResults[i].name === toolData.name) { + newState.toolsResults[i].progressDetail = toolData.detail; + break; + } + } + Object.assign(state, newState); + }, + setToolFailed(state, toolData: { name: string; hasFailed: boolean }) { + const newState = state; + for (let i = 0; i < newState.toolsResults.length; i++) { + if (newState.toolsResults[i].name === toolData.name) { + newState.toolsResults[i].hasFailed = toolData.hasFailed; + break; + } + } + Object.assign(state, newState); + }, + setToolPercentage(state, toolData: { name: string; percentage: string }) { + const newState = state; + for (let i = 0; i < newState.toolsResults.length; i++) { + if (newState.toolsResults[i].name === toolData.name) { + newState.toolsResults[i].progress = toolData.percentage; + break; + } + } + Object.assign(state, newState); + }, + setToolsResult(state, toolsResults: IEspIdfTool[]) { + const newState = state; + newState.toolsResults = toolsResults; + Object.assign(state, newState); + }, + setStatusEspIdf(state, status: StatusType) { + const newState = state; + newState.statusEspIdf = status; + if (status === StatusType.installed) { + newState.idfDownloadStatus.progress = "100.00%"; + } + Object.assign(state, newState); + }, + setStatusEspIdfTools(state, status: StatusType) { + const newState = state; + newState.statusEspIdfTools = status; + if (status === StatusType.installed) { + for (let i = 0; i < newState.toolsResults.length; i++) { + newState.toolsResults[i].progress = "100.00%"; + } + } + Object.assign(state, newState); + }, + setStatusPyVEnv(state, status: StatusType) { + const newState = state; + newState.statusPyVEnv = status; + Object.assign(state, newState); + }, +}; + +export const setupStore: StoreOptions = { + actions, + mutations, + state: setupState, +}; + +Vue.use(Vuex); + +export const store = new Store(setupStore); diff --git a/src/onboarding/createOnboardingHtml.ts b/src/views/setup/types.ts similarity index 51% rename from src/onboarding/createOnboardingHtml.ts rename to src/views/setup/types.ts index a6e5411f1..a6e8894ff 100644 --- a/src/onboarding/createOnboardingHtml.ts +++ b/src/views/setup/types.ts @@ -12,19 +12,44 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Uri } from "vscode"; +export interface IEspIdfLink { + filename: string; + name: string; + mirror: string; + url: string; +} + +export enum IdfMirror { + Espressif, + Github, +} + +export interface IDownload { + id: string; + progress: string; + progressDetail: string; +} + +export interface IEspIdfTool extends IDownload { + actual: string; + description: string; + doesToolExist: boolean; + env: {}; + expected: string; + hashResult: boolean; + hasFailed: boolean; + name: string; + path: string; +} + +export enum StatusType { + failed, + installed, + pending, + started, +} -export function createOnboardingHtml(scriptPath: Uri): string { - return ` - - - - - Onboarding Setup - - -
- - - `; +export enum SetupMode { + advanced, + express, } diff --git a/src/vue.shim.d.ts b/src/vue.shim.d.ts index abb9e5c54..068448468 100644 --- a/src/vue.shim.d.ts +++ b/src/vue.shim.d.ts @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -declare module "*.vue" { - import Vue from "vue"; - export default Vue; +import VueRouter, { Route } from "vue-router"; +declare module "vue/types/vue" { + interface Vue { + $router: VueRouter; + } } diff --git a/webpack.config.js b/webpack.config.js index 4c8e5708f..b0fd96ccd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -70,13 +70,6 @@ const webViewConfig = { examples: path.resolve(__dirname, "src", "views", "examples", "main.ts"), size: path.resolve(__dirname, "src", "views", "size", "main.ts"), tracing: path.resolve(__dirname, "src", "views", "tracing", "main.ts"), - onboarding: path.resolve( - __dirname, - "src", - "views", - "onboarding", - "main.ts" - ), menuconfig: path.resolve( __dirname, "src", @@ -84,6 +77,7 @@ const webViewConfig = { "menuconfig", "main.ts" ), + setup: path.resolve(__dirname, "src", "views", "setup", "main.ts"), nvsPartitionTable: path.resolve( __dirname, "src", diff --git a/yarn.lock b/yarn.lock index 0f69fc29f..5f4d220e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2371,10 +2371,10 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" -follow-redirects@^1.6.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" - integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg== +follow-redirects@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== for-in@^1.0.1, for-in@^1.0.2: version "1.0.2"