├── settings.gradle.kts ├── path_settings.PNG ├── src └── main │ ├── resources │ ├── messages │ │ └── MyBundle.properties │ ├── esp32 │ │ └── embedded │ │ │ └── clion │ │ │ └── openocd │ │ │ ├── ocd.png │ │ │ ├── reset.png │ │ │ └── ocd_run.png │ └── META-INF │ │ ├── plugin.xml │ │ └── pluginIcon.svg │ └── java │ └── esp32 │ └── embedded │ └── clion │ └── openocd │ ├── OpenOcdConfigurationType.java │ ├── OpenOcdSettingsState.java │ ├── Informational.java │ ├── OpenOcdSettings.java │ ├── OpenOcdLauncher.java │ ├── OpenOcdConfigurationEditor.java │ ├── OpenOcdComponent.java │ ├── FileChooseInput.java │ └── OpenOcdConfiguration.java ├── configure_openocd_path.PNG ├── configure_debug_settings.PNG ├── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── .github └── issue_template.md ├── .run ├── Run IDE for UI Tests.run.xml ├── Run Plugin Tests.run.xml ├── Run Qodana.run.xml ├── Run IDE with Plugin.run.xml └── Run Plugin Verification.run.xml ├── LICENSE.txt ├── gradle.properties ├── README.md ├── CONTRIBUTING.md ├── USAGE.md ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md └── gradlew /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "OpenOCD + ESP32 Support for embedded development" 2 | -------------------------------------------------------------------------------- /path_settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/path_settings.PNG -------------------------------------------------------------------------------- /src/main/resources/messages/MyBundle.properties: -------------------------------------------------------------------------------- 1 | name = OpenOCD + ESP32 Support for Embedded Development 2 | -------------------------------------------------------------------------------- /configure_openocd_path.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/configure_openocd_path.PNG -------------------------------------------------------------------------------- /configure_debug_settings.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/configure_debug_settings.PNG -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea/ 3 | .intellijPlatform/ 4 | .qodana 5 | build 6 | release.properties 7 | .DS_Store 8 | gradle/.DS_Store 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/esp32/embedded/clion/openocd/ocd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/src/main/resources/esp32/embedded/clion/openocd/ocd.png -------------------------------------------------------------------------------- /src/main/resources/esp32/embedded/clion/openocd/reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/src/main/resources/esp32/embedded/clion/openocd/reset.png -------------------------------------------------------------------------------- /src/main/resources/esp32/embedded/clion/openocd/ocd_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/HEAD/src/main/resources/esp32/embedded/clion/openocd/ocd_run.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | changelog = "2.4.0" 3 | intelliJPlatform = "2.9.0" 4 | kotlin = "2.1.20" 5 | 6 | [plugins] 7 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 8 | intelliJPlatform = { id = "org.jetbrains.intellij.platform", version.ref = "intelliJPlatform" } 9 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 10 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Plugin Version: 16 | - CLion version: 17 | - OpenOCD version: 18 | - GDB Version: 19 | - STM32CubeMX version: 20 | - Toolchain: 21 | - Platform: 22 | - Hardware configuration: 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | de.femtopedia.openocd 4 | OpenOCD + ESP32 Support for Embedded Development 5 | Nico Mexis 6 | 7 | com.intellij.modules.clion 8 | 9 | 10 | 12 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 15 | 17 | true 18 | true 19 | false 20 | 21 | 22 | -------------------------------------------------------------------------------- /.run/Run Plugin Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Run Qodana.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 19 | 21 | true 22 | true 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /.run/Run IDE with Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | SPDX short identifier: MIT 3 | 4 | Further resources on the MIT License 5 | Copyright 2017, Elmot 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 8 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 9 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 10 | persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 15 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 17 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.run/Run Plugin Verification.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | 23 | 25 | 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories 2 | # -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 3 | 4 | pluginGroup = de.femtopedia.openocd 5 | pluginName = OpenOCD + ESP32 Support for embedded development 6 | pluginRepositoryUrl = https://github.com/ThexXTURBOXx/clion-embedded-esp32 7 | pluginVersion = 0.4.4 8 | 9 | # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html 10 | # for insight into build numbers and IntelliJ Platform versions. 11 | pluginSinceBuild = 252 12 | pluginUntilBuild = 253.* 13 | 14 | # See https://jb.gg/intellij-platform-builds-list for available build versions. 15 | platformType = CL 16 | platformVersion = 253.17525-EAP-CANDIDATE-SNAPSHOT 17 | 18 | # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html 19 | # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 20 | platformPlugins = 21 | # Example: platformBundledPlugins = com.intellij.java 22 | platformBundledPlugins = com.intellij.clion, com.intellij.cidr.lang, com.intellij.clion.cmake, com.intellij.nativeDebug 23 | 24 | gradleVersion = 8.14.3 25 | 26 | # Opt-out flag for bundling Kotlin standard library. 27 | # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. 28 | # suppress inspection "UnusedProperty" 29 | kotlin.stdlib.default.dependency = false 30 | 31 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 32 | org.gradle.configuration-cache = true 33 | 34 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 35 | org.gradle.caching = true 36 | 37 | # Assign more memory to Gradle 38 | org.gradle.jvmargs = -Xmx4G 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ESP32 MCU development plugin for JetBrains CLion 2 | ==== 3 | 4 | 5 | This project is a modified version of Elmot's excellent OpenOCD + 6 | STM / ARM CLion plugin modified to work with the ESP32 series of MCU. All thanks go to them! 7 | 8 | This is a plugin to support complete ESP32 development from within CLion. 9 | 10 | If there are any features missing, anything that you'd like to see, or of course bugs, please raise an issue and I'll 11 | try and have a look at it. 12 | 13 | Plugin page 14 | at [Jetbrains Repository](https://plugins.jetbrains.com/plugin/18760-openocd--esp32-support-for-embedded-development). 15 | 16 | ![Screenshot](https://raw.githubusercontent.com/ThexXTURBOXx/clion-embedded-esp32/master/configure_debug_settings.PNG) 17 | 18 | The plugin is able to: 19 | --- 20 | 21 | * Download project binaries via JTAG using *[OpenOCD](https://openocd.org/)* 22 | * Debug project on chip including support for initial breakpoints. 23 | 24 | Disclaimer 25 | --- 26 | 27 | * No warranties, you are using the plugin at your own risk. 28 | * Beware bugs! This is very early version. 29 | 30 | 31 | 32 | License 33 | --- 34 | [MIT](https://github.com/ThexXTURBOXx/clion-embedded-esp32/blob/master/LICENSE.txt) 35 | 36 | How To Use 37 | --- 38 | See [USAGE.md](https://github.com/ThexXTURBOXx/clion-embedded-esp32/blob/master/USAGE.md). 39 | 40 | Contributions 41 | === 42 | First, please have a look at 43 | our [code of conduct](https://github.com/ThexXTURBOXx/clion-embedded-esp32/blob/master/CODE_OF_CONDUCT.md). Well, it's 44 | standard stuff, I believe you won't do wrong things. Then read 45 | our [contribution guide](https://github.com/ThexXTURBOXx/clion-embedded-esp32/blob/master/CONTRIBUTING.md). 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to *clion-embedded-esp32* plugin 2 | 3 | #### **Did you find a bug?** 4 | 5 | * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues). 6 | 7 | * If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 8 | 9 | * Please note your platform (linux, windows, mac), CLion version, OpenOCD version, STM32CubeMX version (if applicable), type of your MCU and/or development board 10 | 11 | #### **Did you write a patch that fixes a bug?** 12 | 13 | * Open a new GitHub pull request with the patch. 14 | 15 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 16 | 17 | #### **Did you fix whitespace, format code, or make a purely cosmetic patch?** 18 | 19 | See above. Please note that formatting fixes may merged, or may be rejected without any explanations. 20 | 21 | #### **Do you intend to add a new feature or change an existing one?** 22 | 23 | * [Open an issue](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/new) on GitHub, add `feature request` label to it. 24 | 25 | #### **Do you have questions about the source code?** 26 | 27 | * [Open an issue](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/new) on GitHub, add `question` label to it. 28 | 29 | #### **Do you want to contribute to the documentation?** 30 | 31 | This is the best contribution to the project. Go right ahead, open a pull request! You can do it [online](https://help.github.com/articles/editing-files-in-another-user-s-repository/) 32 | 33 | Thanks! :heartbeat: 34 | 35 | Elmot 36 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | Purpose 2 | === 3 | 4 | The plugin supports two different, almost unrelated features: 5 | * Downloading and debugging binaries onto MCU chips using [OpenOCD](https://openocd.org/) 6 | 7 | Disclaimer 8 | === 9 | You are doing everything at your own risk. Nor me, nor JetBrains, nor anybody else takes any 10 | responsibility in case of any direct or indirect damages or losses. 11 | 12 | Prerequisites 13 | === 14 | You will need following tools being installed and configured: 15 | 16 | * Compatible hardware. This guide was written for, and tested against the ESP-WROOM32 (DevKit C) and FTDI C232HM-DDHSL JTag USB Adaptor 17 | * [CLion](https://www.jetbrains.com/clion/). This project has been tested against CLion 2018.3. 18 | * ESP32 toolchain installed on your platform as per [the getting started guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started-cmake/index.html) 19 | 20 | Install Plugin 21 | === 22 | In CLion, go to **File -> Settings ... -> Plugins -> Browse repositories ...** and install the plugin **"OpenOCD + ESP32 Support for embedded development"**. 23 | 24 | Running Examples 25 | === 26 | Detailed guide coming soon! 27 | 28 | Basic steps: 29 | 1. Install the ESP32 toolchain for your platform as well as the latest (git master) ESP-IDF (Required for Cmake scripts). Ensure that all of the paths have been set (IDF_PATH etc.) 30 | ![path_settings](path_settings.PNG) 31 | 2. Load an example project (recommended as a base) into Clion. Ensure that the initial Cmake build succeeds. If it fails, there is probably a path issue. 32 | 3. Configure the path to OpenOCD as below (this is for the Windows version of the ESP toolchain) 33 | ![config_open_ocd_path](configure_openocd_path.PNG) 34 | 4. Create a new Run / Debug configuration as below. 35 | ![config_debug_settings](configure_debug_settings.PNG) 36 | 5. Try running / debugging your project! 37 | 38 | Bugs, Feature requests, Questions, and Contribution 39 | === 40 | 41 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) 42 | 43 | Likes and Donations 44 | === 45 | 46 | If you like the plugin, you may :star: the project at github (button at top-right of the page) and at [jetbrains plugins repository](https://plugins.jetbrains.com/plugin/11284). 47 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ESP32 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdConfigurationType.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.configurations.ConfigurationFactory; 4 | import com.intellij.execution.configurations.RunConfiguration; 5 | import com.intellij.execution.configurations.RunConfigurationSingletonPolicy; 6 | import com.intellij.ide.ui.ProductIcons; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.IconLoader; 9 | import com.intellij.openapi.util.NotNullLazyValue; 10 | import com.jetbrains.cidr.cpp.execution.CMakeRunConfigurationType; 11 | import javax.swing.Icon; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * (c) elmot on 29.9.2017. 16 | */ 17 | public class OpenOcdConfigurationType extends CMakeRunConfigurationType { 18 | 19 | private static final String FACTORY_ID = "elmot.embedded.openocd.conf.factory"; 20 | public static final String TYPE_ID = "elmot.embedded.openocd.conf.type"; 21 | public static final NotNullLazyValue ICON = NotNullLazyValue.lazy(() -> { 22 | final Icon icon = IconLoader.findIcon("esp32/embedded/clion/openocd/ocd_run.png", 23 | OpenOcdConfigurationType.class); 24 | return icon == null ? ProductIcons.getInstance().getProductIcon() : icon; 25 | }); 26 | private final ConfigurationFactory factory; 27 | 28 | public OpenOcdConfigurationType() { 29 | super(TYPE_ID, 30 | FACTORY_ID, 31 | "OpenOCD Download & Run (ESP32)", 32 | "Downloads and Runs Embedded Applications using OpenOCD", 33 | ICON 34 | ); 35 | factory = new ConfigurationFactory(this) { 36 | @NotNull 37 | @Override 38 | public RunConfiguration createTemplateConfiguration(@NotNull Project project) { 39 | return new OpenOcdConfiguration(project, factory, ""); 40 | } 41 | 42 | @NotNull 43 | @Override 44 | public RunConfigurationSingletonPolicy getSingletonPolicy() { 45 | return RunConfigurationSingletonPolicy.SINGLE_INSTANCE_ONLY; 46 | } 47 | 48 | @NotNull 49 | @Override 50 | public String getId() { 51 | return FACTORY_ID; 52 | } 53 | }; 54 | } 55 | 56 | @Override 57 | public OpenOcdConfigurationEditor createEditor(@NotNull Project project) { 58 | return new OpenOcdConfigurationEditor(project, getHelper(project)); 59 | } 60 | 61 | @NotNull 62 | @Override 63 | protected OpenOcdConfiguration createRunConfiguration(@NotNull Project project, 64 | @NotNull ConfigurationFactory configurationFactory) { 65 | return new OpenOcdConfiguration(project, factory, ""); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at elijah.mot@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdSettingsState.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.configurations.PathEnvironmentVariableUtil; 4 | import com.intellij.openapi.components.PersistentStateComponent; 5 | import com.intellij.openapi.components.Service; 6 | import com.intellij.openapi.components.State; 7 | import com.intellij.openapi.util.SystemInfo; 8 | import com.intellij.openapi.vfs.VfsUtil; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import java.io.File; 11 | import org.jdesktop.swingx.util.OS; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | /** 16 | * (c) elmot on 21.10.2017. 17 | */ 18 | @Service(Service.Level.PROJECT) 19 | @State(name = "elmot.OpenOcdPlugin") 20 | public final class OpenOcdSettingsState implements PersistentStateComponent { 21 | 22 | public String openOcdHome; 23 | public boolean shippedGdb; 24 | public boolean autoUpdateCmake; 25 | 26 | public OpenOcdSettingsState() { 27 | openOcdHome = defOpenOcdLocation(); 28 | shippedGdb = true; 29 | autoUpdateCmake = false; 30 | } 31 | 32 | public static VirtualFile findOcdScripts(VirtualFile ocdHomeVFile) { 33 | VirtualFile ocdScripts = null; 34 | if (ocdHomeVFile != null) { 35 | ocdScripts = ocdHomeVFile.findFileByRelativePath(OpenOcdComponent.SCRIPTS_PATH_LONG); 36 | if (ocdScripts == null) { 37 | ocdScripts = ocdHomeVFile.findFileByRelativePath(OpenOcdComponent.SCRIPTS_PATH_SHORT); 38 | if (ocdScripts == null) { 39 | ocdScripts = ocdHomeVFile.findFileByRelativePath(OpenOcdComponent.SCRIPTS_PATH_MEDIUM); 40 | } 41 | } 42 | } 43 | return ocdScripts; 44 | } 45 | 46 | @NotNull 47 | @Override 48 | public OpenOcdSettingsState getState() { 49 | return this; 50 | } 51 | 52 | @Override 53 | public void loadState(@NotNull OpenOcdSettingsState state) { 54 | openOcdHome = state.openOcdHome; 55 | shippedGdb = state.shippedGdb; 56 | autoUpdateCmake = state.autoUpdateCmake; 57 | } 58 | 59 | @Override 60 | public void noStateLoaded() { 61 | openOcdHome = defOpenOcdLocation(); 62 | File openocd = findExecutableInPath("openocd"); 63 | if (openocd != null) { 64 | File folder = openocd.getParentFile(); 65 | if (folder != null) { 66 | folder = folder.getParentFile(); 67 | if (folder != null) { 68 | openOcdHome = folder.getAbsolutePath(); 69 | } 70 | } 71 | } 72 | } 73 | 74 | @NotNull 75 | private String defOpenOcdLocation() { 76 | if (!OS.isWindows()) return "/usr"; 77 | VirtualFile defDir = VfsUtil.getUserHomeDir(); 78 | if (defDir != null) { 79 | return defDir.getPath(); 80 | } 81 | return "C:\\"; 82 | } 83 | 84 | @Nullable 85 | private File findExecutableInPath(String name) { 86 | if (SystemInfo.isWindows) { 87 | for (String ext : PathEnvironmentVariableUtil.getWindowsExecutableFileExtensions()) { 88 | File file = PathEnvironmentVariableUtil.findInPath(name + ext); 89 | if (file != null) return null; 90 | } 91 | return null; 92 | } else { 93 | return PathEnvironmentVariableUtil.findInPath(name); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/Informational.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.CommonBundle; 4 | import com.intellij.ide.BrowserUtil; 5 | import com.intellij.openapi.application.ApplicationManager; 6 | import com.intellij.openapi.options.ConfigurationException; 7 | import com.intellij.openapi.options.ShowSettingsUtil; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.ui.MessageType; 10 | import com.intellij.openapi.ui.Messages; 11 | import com.intellij.openapi.wm.ToolWindowId; 12 | import com.intellij.openapi.wm.ToolWindowManager; 13 | import com.intellij.ui.HyperlinkAdapter; 14 | import javax.swing.event.HyperlinkEvent; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | /** 18 | * (c) elmot on 20.10.2017. 19 | */ 20 | public class Informational { 21 | public static final String SETTINGS_PROTOCOL = "settings://"; 22 | public static final String HELP_URL = "https://github.com/ThexXTURBOXx/clion-embedded-esp32/blob/master/USAGE.md"; 23 | 24 | private Informational() { 25 | } 26 | 27 | public static void showSuccessfulDownloadNotification(Project project) { 28 | showMessage(project, MessageType.INFO, "Firmware Download Success"); 29 | } 30 | 31 | public static void showMessage(Project project, MessageType messageType, String message) { 32 | ApplicationManager.getApplication().invokeLater(() -> { 33 | ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); 34 | if (toolWindowManager.canShowNotification(ToolWindowId.RUN)) { 35 | toolWindowManager.notifyByBalloon(ToolWindowId.RUN, messageType, message, 36 | OpenOcdConfigurationType.ICON.getValue(), 37 | new HyperlinkHandler(project) 38 | ); 39 | } 40 | } 41 | ); 42 | } 43 | 44 | public static void showFailedDownloadNotification(Project project) { 45 | showMessage(project, MessageType.ERROR, 46 | "MCU Communication FAILURE.\nCheck OpenOCD configuration and connection.
" + 50 | "Plugin documentation is located here"); 51 | } 52 | 53 | public static void showPluginError(Project project, ConfigurationException e) { 54 | ApplicationManager.getApplication().invokeLater(() -> { 55 | int optionNo = Messages.showDialog(project, e.getLocalizedMessage(), e.getTitle(), 56 | new String[]{Messages.getOkButton(), CommonBundle.settingsAction(), 57 | CommonBundle.getHelpButtonText()}, 58 | 0, Messages.getErrorIcon()); 59 | switch (optionNo) { 60 | case 1 -> ShowSettingsUtil.getInstance().showSettingsDialog(project, OpenOcdSettings.class); 61 | case 2 -> BrowserUtil.browse(HELP_URL); 62 | default -> {//nothing to do 63 | } 64 | } 65 | }); 66 | } 67 | 68 | private static class HyperlinkHandler extends HyperlinkAdapter { 69 | private final Project project; 70 | 71 | public HyperlinkHandler(Project project) { 72 | this.project = project; 73 | } 74 | 75 | @Override 76 | protected void hyperlinkActivated(@NotNull HyperlinkEvent e) { 77 | /*String link = Objects.toString(e.getDescription(), ""); 78 | if (link.toLowerCase().startsWith(SETTINGS_PROTOCOL)) { 79 | try { 80 | String className = link.substring(SETTINGS_PROTOCOL.length()); 81 | ShowSettingsUtil.getInstance().showSettingsDialog(project, (Class) Class.forName(className)); 82 | } catch (ClassNotFoundException ignored) { 83 | } 84 | } else { 85 | BrowserUtil.browse(link); 86 | }*/ 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # OpenOCD + ESP32 Support for embedded development Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.4.4] - 2025-09-16 6 | 7 | ### Fixed 8 | 9 | - Updated to 2025.3 EAP 10 | 11 | ## [0.4.3] - 2025-05-06 12 | 13 | ### Fixed 14 | 15 | - Updated to 2025.2 EAP 16 | 17 | ## [0.4.2] 18 | 19 | ### Fixed 20 | 21 | - Also support 2025.1 EAP 22 | 23 | ## [0.4.1] 24 | 25 | ### Fixed 26 | 27 | - Fix error messages being shown in wrong thread ([#25](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/25)) 28 | 29 | ## [0.4.0] 30 | 31 | ### Added 32 | 33 | - Add additional program parameters ([#22](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/22)) through PR ([#23](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/23)) 34 | 35 | ### Changed 36 | 37 | - Update IntelliJ platform plugin 38 | 39 | ### Fixed 40 | 41 | - Fix OpenOCD launching in wrong thread ([#21](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/21)) through PR ([#24](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/24)) 42 | 43 | ## [0.3.8] 44 | 45 | ### Fixed 46 | 47 | - Updated to 2024.2 EAP 48 | 49 | ## [0.3.7] 50 | 51 | ### Fixed 52 | 53 | - Updated to 2024.1 EAP 54 | 55 | ## [0.3.6] 56 | 57 | ### Fixed 58 | 59 | - Fixed program offsets not properly updating ([#15](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/15)) 60 | 61 | ## [0.3.5] 62 | 63 | ### Fixed 64 | 65 | - Fixed content roots 66 | 67 | ## [0.3.4] 68 | 69 | ### Added 70 | 71 | - Add debugger configuration ([#12](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/12)) 72 | 73 | ## [0.3.3] 74 | 75 | ### Fixed 76 | 77 | - Updated to 2023.3 EAP 78 | - Remove deprecations 79 | 80 | ## [0.3.2] 81 | 82 | ### Fixed 83 | 84 | - Fix root path ([#10](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/10)) 85 | 86 | ## [0.3.1] 87 | 88 | ### Fixed 89 | 90 | - Fix typo when restoring saved run configs 91 | 92 | ## [0.3.0] 93 | 94 | ### Fixed 95 | 96 | - Fix deadlock ([#4](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/4)) through PR ([#9](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/9)) 97 | - Allow not specifying a few config entries 98 | - Support more reset types 99 | - Cleanup (migrate away from obsolete and deprecated entities) 100 | 101 | ## [0.2.4] 102 | 103 | ### Fixed 104 | 105 | - Fix deadlock ([#4](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/4)) through PR ([#7](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/7)) 106 | 107 | ## [0.2.3] 108 | 109 | ### Fixed 110 | 111 | - Fix file path parameter ([#6](https://github.com/ThexXTURBOXx/clion-embedded-esp32/issues/6)) 112 | - Fix icons 113 | - Add ESP32 tag to run configurations to differentiate them better 114 | - Change default command to `program_esp` 115 | - Support new xpack OpenOCD format 116 | - Fix changelog 117 | 118 | ## [0.2.2] 119 | 120 | ### Fixed 121 | 122 | - Updated to 2023.2 EAP 123 | 124 | ## [0.2.1] 125 | 126 | ### Fixed 127 | 128 | - Updated to 2023.1 EAP 129 | 130 | ## [0.2.0] 131 | 132 | ### Added 133 | 134 | - Support for both `program_esp[32]` commands ([#3](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/3)) 135 | - Support for `offset` parameters ([#3](https://github.com/ThexXTURBOXx/clion-embedded-esp32/pull/3)) 136 | - Support for `verify` parameter 137 | 138 | ## [0.1.9] 139 | 140 | ### Fixed 141 | 142 | - Updated to 2022.3 EAP 143 | 144 | ## [0.1.8] 145 | 146 | ### Fixed 147 | 148 | - Updated to 2022.2 EAP 149 | 150 | ## [0.1.7] 151 | 152 | ### Fixed 153 | 154 | - Updated to 2022.1 EAP again 155 | 156 | ## [0.1.6] 157 | 158 | ### Fixed 159 | 160 | - Fixed support for CLion 2020.3 and 2021.1 161 | 162 | ## [0.1.5] 163 | 164 | ### Added 165 | 166 | - Support for CLion 2022.1 EAP 167 | 168 | ## [0.1.4] 169 | 170 | ### Changed 171 | 172 | - Changed the icon 173 | 174 | ## [0.1.3] 175 | 176 | ### Changed 177 | 178 | - Migrate to Gradle 179 | 180 | ### Fixed 181 | 182 | - Fixed `NoClassDefFoundError` (#6) 183 | 184 | ## [0.1.2] 185 | 186 | ### Added 187 | 188 | - Updated for CLion 2019.1+ 189 | 190 | ## [0.1.1] 191 | 192 | ### Added 193 | 194 | - STM32G0 and STM32L5 experimental support added 195 | 196 | [Unreleased]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.4.4...HEAD 197 | [0.4.4]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.4.3...v0.4.4 198 | [0.4.3]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.4.2...v0.4.3 199 | [0.4.2]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.4.1...v0.4.2 200 | [0.4.1]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.4.0...v0.4.1 201 | [0.4.0]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.8...v0.4.0 202 | [0.3.8]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.7...v0.3.8 203 | [0.3.7]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.6...v0.3.7 204 | [0.3.6]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.5...v0.3.6 205 | [0.3.5]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.4...v0.3.5 206 | [0.3.4]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.3...v0.3.4 207 | [0.3.3]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.2...v0.3.3 208 | [0.3.2]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.1...v0.3.2 209 | [0.3.1]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.3.0...v0.3.1 210 | [0.3.0]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.2.4...v0.3.0 211 | [0.2.4]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.2.3...v0.2.4 212 | [0.2.3]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.2.2...v0.2.3 213 | [0.2.2]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.2.1...v0.2.2 214 | [0.2.1]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.2.0...v0.2.1 215 | [0.2.0]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.9...v0.2.0 216 | [0.1.9]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.8...v0.1.9 217 | [0.1.8]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.7...v0.1.8 218 | [0.1.7]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.6...v0.1.7 219 | [0.1.6]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.5...v0.1.6 220 | [0.1.5]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.4...v0.1.5 221 | [0.1.4]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.3...v0.1.4 222 | [0.1.3]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.2...v0.1.3 223 | [0.1.2]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/compare/v0.1.1...v0.1.2 224 | [0.1.1]: https://github.com/ThexXTURBOXx/clion-embedded-esp32/commits/v0.1.1 225 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdSettings.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.vfs.VfsUtil; 7 | import com.intellij.ui.components.JBCheckBox; 8 | import com.intellij.uiDesigner.core.GridConstraints; 9 | import com.intellij.uiDesigner.core.GridLayoutManager; 10 | import com.intellij.uiDesigner.core.Spacer; 11 | import com.jetbrains.cidr.cpp.toolchains.CPPToolchains; 12 | import java.awt.FlowLayout; 13 | import java.io.File; 14 | import java.util.Objects; 15 | import javax.swing.ButtonGroup; 16 | import javax.swing.JComponent; 17 | import javax.swing.JLabel; 18 | import javax.swing.JPanel; 19 | import javax.swing.JRadioButton; 20 | import org.jetbrains.annotations.Nls; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import static com.intellij.uiDesigner.core.GridConstraints.ANCHOR_CENTER; 25 | import static com.intellij.uiDesigner.core.GridConstraints.ANCHOR_WEST; 26 | import static com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL; 27 | import static com.intellij.uiDesigner.core.GridConstraints.FILL_NONE; 28 | import static com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED; 29 | import static com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_WANT_GROW; 30 | 31 | /** 32 | * (c) elmot on 20.10.2017. 33 | */ 34 | public class OpenOcdSettings implements Configurable { 35 | protected final Project project; 36 | private OpenOcdSettingsPanel panel = null; 37 | 38 | public OpenOcdSettings(Project project) { 39 | this.project = project; 40 | } 41 | 42 | @Nls 43 | @Override 44 | public String getDisplayName() { 45 | return "ARM/OpenOCD support"; 46 | } 47 | 48 | @Override 49 | public boolean isModified() { 50 | OpenOcdSettingsState state = project.getService(OpenOcdSettingsState.class); 51 | if (state == null) return true; 52 | return !( 53 | Objects.equals(panel.openOcdHome.getText(), state.openOcdHome) && 54 | panel.shippedRadioButton.isSelected() == state.shippedGdb && 55 | panel.autoUpdateCmake.isSelected() == state.autoUpdateCmake); 56 | } 57 | 58 | @Override 59 | public void apply() throws ConfigurationException { 60 | panel.openOcdHome.validateContent(); 61 | 62 | OpenOcdSettingsState state = project.getService(OpenOcdSettingsState.class); 63 | if (state != null) { 64 | state.openOcdHome = panel.openOcdHome.getText(); 65 | state.shippedGdb = panel.shippedRadioButton.isSelected(); 66 | state.autoUpdateCmake = panel.autoUpdateCmake.isSelected(); 67 | } 68 | } 69 | 70 | @Nullable 71 | @Override 72 | public JComponent createComponent() { 73 | 74 | panel = new OpenOcdSettingsPanel(); 75 | return panel; 76 | } 77 | 78 | @Override 79 | public void reset() { 80 | OpenOcdSettingsState state = project.getService(OpenOcdSettingsState.class); 81 | panel.openOcdHome.setText(state.openOcdHome); 82 | panel.shippedRadioButton.setSelected(state.shippedGdb); 83 | panel.toolchainRadioButton.setSelected(!state.shippedGdb); 84 | panel.updateToolchainGdbName(); 85 | panel.autoUpdateCmake.setSelected(state.autoUpdateCmake); 86 | } 87 | 88 | /** 89 | * (c) elmot on 20.10.2017. 90 | */ 91 | public static class OpenOcdSettingsPanel extends JPanel { 92 | 93 | // private final FileChooseInput boardConfigFile; 94 | private final FileChooseInput openOcdHome; 95 | private final JBCheckBox autoUpdateCmake; 96 | private JRadioButton toolchainRadioButton; 97 | private JRadioButton shippedRadioButton; 98 | 99 | public OpenOcdSettingsPanel() { 100 | super(new GridLayoutManager(6, 3), true); 101 | ((GridLayoutManager) getLayout()).setColumnStretch(1, 10); 102 | openOcdHome = addValueRow(0, new FileChooseInput.OpenOcdHome("OpenOCD Home", VfsUtil.getUserHomeDir())); 103 | 104 | addValueRow(2, "Use GDB", setupGdbButtonGroup()); 105 | 106 | autoUpdateCmake = addValueRow(4, "CMake Project Update", new JBCheckBox("Automatic")); 107 | 108 | add(new Spacer(), new GridConstraints(5, 0, 1, 1, ANCHOR_CENTER, FILL_NONE, 109 | SIZEPOLICY_FIXED, SIZEPOLICY_WANT_GROW, null, null, null)); 110 | } 111 | 112 | @NotNull 113 | protected JPanel setupGdbButtonGroup() { 114 | shippedRadioButton = new JRadioButton("Shipped with CLion"); 115 | 116 | toolchainRadioButton = new JRadioButton(); 117 | updateToolchainGdbName(); 118 | ButtonGroup buttonGroup = new ButtonGroup(); 119 | buttonGroup.add(shippedRadioButton); 120 | buttonGroup.add(toolchainRadioButton); 121 | JPanel gdbPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 122 | 123 | gdbPanel.add(shippedRadioButton); 124 | gdbPanel.add(toolchainRadioButton); 125 | return gdbPanel; 126 | } 127 | 128 | private void updateToolchainGdbName() { 129 | CPPToolchains.Toolchain toolchain = CPPToolchains.getInstance().getDefaultToolchain(); 130 | File debugger = toolchain == null ? null : toolchain.getDebugger().getGdbExecutable(); 131 | if (debugger == null) { 132 | toolchainRadioButton.setText("From Toolchain"); 133 | toolchainRadioButton.setToolTipText(null); 134 | } else { 135 | toolchainRadioButton.setText(String.format("From Toolchain (%s)", debugger.getName())); 136 | toolchainRadioButton.setToolTipText(debugger.getAbsolutePath()); 137 | } 138 | } 139 | 140 | private T addValueRow(int row, @NotNull T component) { 141 | return addValueRow(row, component.getValueName(), component); 142 | } 143 | 144 | private T addValueRow(int row, @Nullable String labelText, @NotNull T component) { 145 | add(component, new GridConstraints(row, 1, 1, 1, ANCHOR_WEST, 146 | FILL_HORIZONTAL, SIZEPOLICY_WANT_GROW, SIZEPOLICY_FIXED, null, null, null)); 147 | if (labelText != null) { 148 | JLabel label = new JLabel(labelText); 149 | add(label, new GridConstraints(row, 0, 1, 1, ANCHOR_WEST, FILL_NONE, 150 | SIZEPOLICY_FIXED, SIZEPOLICY_FIXED, null, null, null)); 151 | label.setLabelFor(component); 152 | } 153 | return component; 154 | } 155 | 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdLauncher.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.configurations.CommandLineState; 5 | import com.intellij.execution.configurations.GeneralCommandLine; 6 | import com.intellij.execution.filters.Filter; 7 | import com.intellij.execution.process.OSProcessHandler; 8 | import com.intellij.execution.process.ProcessEvent; 9 | import com.intellij.execution.process.ProcessHandler; 10 | import com.intellij.execution.process.ProcessListener; 11 | import com.intellij.execution.ui.ExecutionConsole; 12 | import com.intellij.openapi.actionSystem.AnAction; 13 | import com.intellij.openapi.actionSystem.AnActionEvent; 14 | import com.intellij.openapi.application.ApplicationManager; 15 | import com.intellij.openapi.options.ConfigurationException; 16 | import com.intellij.openapi.progress.ProgressManager; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.openapi.util.IconLoader; 19 | import com.intellij.openapi.util.Key; 20 | import com.intellij.openapi.util.ThrowableComputable; 21 | import com.intellij.xdebugger.XDebugProcess; 22 | import com.intellij.xdebugger.XDebugSession; 23 | import com.jetbrains.cidr.cpp.execution.CMakeAppRunConfiguration; 24 | import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration; 25 | import com.jetbrains.cidr.cpp.toolchains.CPPDebugger; 26 | import com.jetbrains.cidr.cpp.toolchains.CPPToolchains; 27 | import com.jetbrains.cidr.execution.CidrLauncher; 28 | import com.jetbrains.cidr.execution.debugger.CidrDebugProcess; 29 | import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager; 30 | import com.jetbrains.cidr.execution.debugger.backend.DebuggerCommandException; 31 | import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver; 32 | import com.jetbrains.cidr.execution.debugger.remote.CidrRemoteDebugParameters; 33 | import com.jetbrains.cidr.execution.debugger.remote.CidrRemoteGDBDebugProcess; 34 | import esp32.embedded.clion.openocd.OpenOcdConfiguration.DownloadType; 35 | import java.io.File; 36 | import java.util.List; 37 | import java.util.concurrent.Future; 38 | import java.util.concurrent.TimeUnit; 39 | import java.util.concurrent.TimeoutException; 40 | import java.util.concurrent.atomic.AtomicReference; 41 | import org.jetbrains.annotations.NotNull; 42 | 43 | /** 44 | * (c) elmot on 19.10.2017. 45 | */ 46 | 47 | class OpenOcdLauncher extends CidrLauncher { 48 | 49 | private static final Key RESTART_KEY = Key.create(OpenOcdLauncher.class.getName() + "#restartAction"); 50 | private final OpenOcdConfiguration openOcdConfiguration; 51 | 52 | OpenOcdLauncher(OpenOcdConfiguration openOcdConfiguration) { 53 | this.openOcdConfiguration = openOcdConfiguration; 54 | } 55 | 56 | @Override 57 | protected ProcessHandler createProcess(@NotNull CommandLineState commandLineState) throws ExecutionException { 58 | File runFile = findRunFile(commandLineState); 59 | findOpenOcdAction(commandLineState.getEnvironment().getProject()).stopOpenOcd(); 60 | try { 61 | GeneralCommandLine commandLine = OpenOcdComponent 62 | .createOcdCommandLine(openOcdConfiguration, 63 | runFile, "reset", true); 64 | OSProcessHandler osProcessHandler = new OSProcessHandler(commandLine); 65 | osProcessHandler.addProcessListener(new ProcessListener() { 66 | @Override 67 | public void processTerminated(@NotNull ProcessEvent event) { 68 | Project project = commandLineState.getEnvironment().getProject(); 69 | if (event.getExitCode() == 0) { 70 | Informational.showSuccessfulDownloadNotification(project); 71 | } else { 72 | Informational.showFailedDownloadNotification(project); 73 | } 74 | } 75 | }); 76 | return osProcessHandler; 77 | } catch (ConfigurationException e) { 78 | Informational.showPluginError(getProject(), e); 79 | throw new ExecutionException(e); 80 | } 81 | } 82 | 83 | @NotNull 84 | @Override 85 | protected CidrDebugProcess createDebugProcess(@NotNull CommandLineState commandLineState, 86 | @NotNull XDebugSession xDebugSession) throws ExecutionException { 87 | Project project = commandLineState.getEnvironment().getProject(); 88 | OpenOcdSettingsState ocdSettings = project.getService(OpenOcdSettingsState.class); 89 | CidrRemoteDebugParameters remoteDebugParameters = new CidrRemoteDebugParameters(); 90 | 91 | remoteDebugParameters.setSymbolFile(findRunFile(commandLineState).getAbsolutePath()); 92 | remoteDebugParameters.setRemoteCommand("tcp:localhost:" + openOcdConfiguration.getGdbPort()); 93 | 94 | CPPToolchains.Toolchain toolchain = openOcdConfiguration.getDebuggerData().getOrCreateDebuggerToolchain(); 95 | if (ocdSettings.shippedGdb) { 96 | toolchain = toolchain.copy(); 97 | File gdbFile = CidrDebuggerPathManager.getBundledGDBBinary(); 98 | String gdbPath = gdbFile.getAbsolutePath(); 99 | CPPDebugger cppDebugger = CPPDebugger.create(CPPDebugger.Kind.CUSTOM_GDB, gdbPath); 100 | toolchain.setDebugger(cppDebugger); 101 | } 102 | CLionGDBDriverConfiguration gdbDriverConfiguration = new CLionGDBDriverConfiguration(getProject(), toolchain); 103 | 104 | xDebugSession.stop(); 105 | 106 | AtomicReference debugProcessRef = new AtomicReference<>(); 107 | ApplicationManager.getApplication().invokeAndWait(() -> { 108 | try { 109 | debugProcessRef.set(new CidrRemoteGDBDebugProcess(gdbDriverConfiguration, 110 | remoteDebugParameters, 111 | xDebugSession, 112 | commandLineState.getConsoleBuilder(), 113 | project1 -> new Filter[0])); 114 | } catch (ExecutionException e) { 115 | throw new RuntimeException(e); 116 | } 117 | }); 118 | CidrRemoteGDBDebugProcess debugProcess = debugProcessRef.get(); 119 | 120 | debugProcess.getProcessHandler().addProcessListener(new ProcessListener() { 121 | @Override 122 | public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) { 123 | findOpenOcdAction(project).stopOpenOcd(); 124 | } 125 | }); 126 | 127 | debugProcess.getProcessHandler().putUserData(RESTART_KEY, 128 | new AnAction("Reset", "MCU reset", 129 | IconLoader.findIcon("esp32/embedded/clion/openocd/reset.png", OpenOcdLauncher.class)) { 130 | @Override 131 | public void actionPerformed(@NotNull AnActionEvent e) { 132 | XDebugSession session = debugProcess.getSession(); 133 | session.pause(); 134 | debugProcess.postCommand(drv -> { 135 | try { 136 | ProgressManager.getInstance().runProcess(() -> { 137 | while (drv.getState() != DebuggerDriver.TargetState.SUSPENDED) { 138 | Thread.yield(); 139 | } 140 | }, null); 141 | drv.executeInterpreterCommand("monitor reset init"); 142 | session.resume(); 143 | } catch (DebuggerCommandException exception) { 144 | Informational.showFailedDownloadNotification(e.getProject()); 145 | } 146 | }); 147 | } 148 | } 149 | ); 150 | 151 | new Thread(() -> { 152 | while (!debugProcess.getCurrentStateMessage().equals("Connected")) { 153 | try { 154 | Thread.onSpinWait(); 155 | } catch (Throwable ignored) { 156 | } 157 | } 158 | 159 | // Connected. Perform initialisation 160 | XDebugSession session = debugProcess.getSession(); 161 | 162 | // Check if we need any init 163 | if (openOcdConfiguration.getResetType().needsInit()) { 164 | session.pause(); 165 | debugProcess.postCommand(drv -> { 166 | try { 167 | ProgressManager.getInstance().runProcess(() -> { 168 | while (drv.getState() != DebuggerDriver.TargetState.SUSPENDED) { 169 | Thread.yield(); 170 | } 171 | }, null); 172 | 173 | // Determine which commands need to be run 174 | if (openOcdConfiguration.getFlushRegs()) { 175 | drv.executeInterpreterCommand("flushregs"); 176 | } 177 | 178 | if (openOcdConfiguration.getInitialBreak() && !openOcdConfiguration.getInitialBreakName().isEmpty()) { 179 | drv.executeInterpreterCommand("thb " + openOcdConfiguration.getInitialBreakName()); 180 | } 181 | 182 | session.resume(); 183 | 184 | } catch (DebuggerCommandException ignored) { 185 | } 186 | }); 187 | } 188 | 189 | }).start(); 190 | 191 | return debugProcess; 192 | } 193 | 194 | @NotNull 195 | private File findRunFile(CommandLineState commandLineState) throws ExecutionException { 196 | String targetProfileName = commandLineState.getExecutionTarget().getDisplayName(); 197 | CMakeAppRunConfiguration.BuildAndRunConfigurations runConfigurations = openOcdConfiguration 198 | .getBuildAndRunConfigurations(targetProfileName); 199 | if (runConfigurations == null) { 200 | throw new ExecutionException("Target is not defined"); 201 | } 202 | File runFile = runConfigurations.getRunFile(getProject()); 203 | if (runFile == null) { 204 | throw new ExecutionException("Run file is not defined for " + runConfigurations); 205 | } 206 | if (!runFile.exists() || !runFile.isFile()) { 207 | throw new ExecutionException("Invalid run file " + runFile.getAbsolutePath()); 208 | } 209 | return runFile; 210 | } 211 | 212 | 213 | @NotNull 214 | @Override 215 | public XDebugProcess startDebugProcess(@NotNull CommandLineState commandLineState, 216 | @NotNull XDebugSession xDebugSession) throws ExecutionException { 217 | 218 | File runFile = null; 219 | if (openOcdConfiguration.getDownloadType() != DownloadType.NONE) { 220 | runFile = findRunFile(commandLineState); 221 | if (openOcdConfiguration.getDownloadType() == DownloadType.UPDATED_ONLY && 222 | OpenOcdComponent.isLatestUploaded(runFile)) { 223 | runFile = null; 224 | } 225 | } 226 | 227 | try { 228 | xDebugSession.stop(); 229 | OpenOcdComponent openOcdComponent = findOpenOcdAction(commandLineState.getEnvironment().getProject()); 230 | openOcdComponent.stopOpenOcd(); 231 | Future downloadResult = openOcdComponent.startOpenOcd(openOcdConfiguration, 232 | runFile); 233 | 234 | ProgressManager progressManager = ProgressManager.getInstance(); 235 | ThrowableComputable process = () -> { 236 | try { 237 | progressManager.getProgressIndicator().setIndeterminate(true); 238 | while (true) { 239 | try { 240 | return downloadResult.get(500, TimeUnit.MILLISECONDS); 241 | } catch (TimeoutException ignored) { 242 | ProgressManager.checkCanceled(); 243 | } 244 | } 245 | } catch (InterruptedException | java.util.concurrent.ExecutionException e) { 246 | throw new ExecutionException(e); 247 | } 248 | }; 249 | String progressTitle = runFile == null ? "Start OpenOCD" : "Firmware Download"; 250 | OpenOcdComponent.Status downloadStatus = progressManager.runProcessWithProgressSynchronously( 251 | process, progressTitle, true, getProject()); 252 | if (downloadStatus == OpenOcdComponent.Status.FLASH_ERROR) { 253 | downloadResult.cancel(true); 254 | throw new ExecutionException("OpenOCD cancelled"); 255 | } 256 | return super.startDebugProcess(commandLineState, xDebugSession); 257 | } catch (ConfigurationException e) { 258 | Informational.showPluginError(getProject(), e); 259 | throw new ExecutionException(e); 260 | } 261 | } 262 | 263 | @Override 264 | protected void collectAdditionalActions(@NotNull CommandLineState state, @NotNull ProcessHandler processHandler, 265 | @NotNull ExecutionConsole console, 266 | @NotNull List actions) throws ExecutionException { 267 | super.collectAdditionalActions(state, processHandler, console, actions); 268 | AnAction restart = processHandler.getUserData(RESTART_KEY); 269 | if (restart != null) { 270 | actions.add(restart); 271 | } 272 | } 273 | 274 | private OpenOcdComponent findOpenOcdAction(Project project) { 275 | return project.getService(OpenOcdComponent.class); 276 | } 277 | 278 | @NotNull 279 | @Override 280 | public Project getProject() { 281 | return openOcdConfiguration.getProject(); 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdConfigurationEditor.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.ui.CommonProgramParametersPanel; 4 | import com.intellij.openapi.module.ModuleManager; 5 | import com.intellij.openapi.options.ConfigurationException; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.roots.ModuleRootManager; 8 | import com.intellij.openapi.vfs.VfsUtil; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.ui.components.fields.ExtendableTextField; 11 | import com.intellij.ui.components.fields.IntegerField; 12 | import com.intellij.util.ui.GridBag; 13 | import com.jetbrains.cidr.cpp.execution.CMakeAppRunConfiguration; 14 | import com.jetbrains.cidr.cpp.execution.CMakeAppRunConfigurationSettingsEditor; 15 | import com.jetbrains.cidr.cpp.execution.CMakeBuildConfigurationHelper; 16 | import com.jetbrains.cidr.cpp.execution.remote.DebuggerData; 17 | import com.jetbrains.cidr.cpp.execution.remote.DebuggersComboBoxWithModel; 18 | import esp32.embedded.clion.openocd.OpenOcdConfiguration.DownloadType; 19 | import esp32.embedded.clion.openocd.OpenOcdConfiguration.ProgramType; 20 | import java.awt.Component; 21 | import java.awt.FlowLayout; 22 | import java.awt.GridLayout; 23 | import java.awt.event.ItemEvent; 24 | import java.util.Objects; 25 | import javax.swing.Box; 26 | import javax.swing.JCheckBox; 27 | import javax.swing.JLabel; 28 | import javax.swing.JPanel; 29 | import org.jdesktop.swingx.JXRadioGroup; 30 | import org.jetbrains.annotations.NotNull; 31 | 32 | public class OpenOcdConfigurationEditor extends CMakeAppRunConfigurationSettingsEditor { 33 | public static final String BOOTLOADER_FILE = "Bootloader file"; 34 | public static final String PART_TABLE_FILE = "Partition Table file"; 35 | private DebuggersComboBoxWithModel debuggers; 36 | private IntegerField gdbPort; 37 | private IntegerField telnetPort; 38 | private ExtendableTextField offset; 39 | private JXRadioGroup resetGroup; 40 | private JCheckBox flushRegsCheck; 41 | private JCheckBox initialBreakpointCheck; 42 | private ExtendableTextField initialBreakpointName; 43 | private FileChooseInput boardConfigFile; 44 | private FileChooseInput interfaceConfigFile; 45 | 46 | private FileChooseInput.BinFile bootloaderFile; 47 | private ExtendableTextField bootloaderOffset; 48 | private FileChooseInput.BinFile partitionTableFile; 49 | private ExtendableTextField partitionTableOffset; 50 | private String openocdHome; 51 | private JXRadioGroup downloadGroup; 52 | 53 | private JXRadioGroup programType; 54 | private JCheckBox appendVerify; 55 | 56 | private ExtendableTextField additionalProgramParameters; 57 | 58 | 59 | public OpenOcdConfigurationEditor(Project project, 60 | @NotNull CMakeBuildConfigurationHelper cMakeBuildConfigurationHelper) { 61 | super(project, cMakeBuildConfigurationHelper); 62 | } 63 | 64 | @Override 65 | protected void applyEditorTo(@NotNull CMakeAppRunConfiguration cMakeAppRunConfiguration) throws ConfigurationException { 66 | super.applyEditorTo(cMakeAppRunConfiguration); 67 | 68 | OpenOcdConfiguration ocdConfiguration = (OpenOcdConfiguration) cMakeAppRunConfiguration; 69 | 70 | String boardConfig = boardConfigFile.getText().trim(); 71 | ocdConfiguration.setBoardConfigFile(boardConfig.isEmpty() ? null : boardConfig); 72 | 73 | String interfaceConfig = interfaceConfigFile.getText().trim(); 74 | ocdConfiguration.setInterfaceConfigFile(interfaceConfig.isEmpty() ? null : interfaceConfig); 75 | 76 | String bootPath = bootloaderFile.getPath().trim(); 77 | ocdConfiguration.setBootBinPath(bootPath.isEmpty() ? null : bootPath); 78 | 79 | String partPath = partitionTableFile.getPath().trim(); 80 | ocdConfiguration.setPartitionBinPath(partPath.isEmpty() ? null : partPath); 81 | 82 | gdbPort.validateContent(); 83 | telnetPort.validateContent(); 84 | 85 | DebuggerData selectedDebugger = debuggers.getSelectedDebugger(); 86 | ocdConfiguration.setDebuggerData(selectedDebugger); 87 | 88 | ocdConfiguration.setGdbPort(gdbPort.getValue()); 89 | ocdConfiguration.setTelnetPort(telnetPort.getValue()); 90 | ocdConfiguration.setDownloadType(downloadGroup.getSelectedValue()); 91 | ocdConfiguration.setProgramType(programType.getSelectedValue()); 92 | ocdConfiguration.setAppendVerify(appendVerify.isSelected()); 93 | ocdConfiguration.setAdditionalProgramParameters(additionalProgramParameters.getText()); 94 | 95 | ocdConfiguration.setOffset(offset.getText()); 96 | ocdConfiguration.setBootOffset(bootloaderOffset.getText()); 97 | ocdConfiguration.setPartitionOffset(partitionTableOffset.getText()); 98 | ocdConfiguration.setResetType(resetGroup.getSelectedValue()); 99 | ocdConfiguration.setFlushRegs(flushRegsCheck.isSelected()); 100 | ocdConfiguration.setInitialBreak(initialBreakpointCheck.isSelected()); 101 | ocdConfiguration.setInitialBreakName(initialBreakpointName.getText()); 102 | } 103 | 104 | @Override 105 | protected void resetEditorFrom(@NotNull CMakeAppRunConfiguration cMakeAppRunConfiguration) { 106 | super.resetEditorFrom(cMakeAppRunConfiguration); 107 | 108 | OpenOcdConfiguration ocd = (OpenOcdConfiguration) cMakeAppRunConfiguration; 109 | 110 | openocdHome = ocd.getProject().getService(OpenOcdSettingsState.class).openOcdHome; 111 | 112 | boardConfigFile.setText(ocd.getBoardConfigFile()); 113 | interfaceConfigFile.setText(ocd.getInterfaceConfigFile()); 114 | 115 | ModuleRootManager manager = ModuleRootManager.getInstance(ModuleManager.getInstance(myProject).getModules()[0]); 116 | String root = Objects.requireNonNull(getContentRoot(manager)).getPath(); 117 | 118 | String bootBinPath = ocd.getBootBinPath(); 119 | if (bootBinPath != null) 120 | bootBinPath = bootBinPath.replaceAll(root + "/", ""); 121 | bootloaderFile.setText(bootBinPath); 122 | 123 | String partitionPath = ocd.getPartitionBinPath(); 124 | if (partitionPath != null) 125 | partitionPath = partitionPath.replaceAll(root + "/", ""); 126 | partitionTableFile.setText(partitionPath); 127 | 128 | debuggers.resetModel(ocd.getDebuggerData()); 129 | 130 | gdbPort.setText(String.valueOf(ocd.getGdbPort())); 131 | 132 | telnetPort.setText(String.valueOf(ocd.getTelnetPort())); 133 | downloadGroup.setSelectedValue(ocd.getDownloadType()); 134 | programType.setSelectedValue(ocd.getProgramType()); 135 | appendVerify.setSelected(ocd.getAppendVerify()); 136 | if (ocd.getAdditionalProgramParameters() != null) { 137 | additionalProgramParameters.setText(ocd.getAdditionalProgramParameters()); 138 | } else { 139 | additionalProgramParameters.setText(""); 140 | } 141 | 142 | offset.setText(ocd.getOffset()); 143 | bootloaderOffset.setText(ocd.getBootOffset()); 144 | partitionTableOffset.setText(ocd.getPartitionOffset()); 145 | resetGroup.setSelectedValue(ocd.getResetType()); 146 | flushRegsCheck.setSelected(ocd.getFlushRegs()); 147 | initialBreakpointCheck.setSelected(ocd.getInitialBreak()); 148 | initialBreakpointName.setText(ocd.getInitialBreakName()); 149 | } 150 | 151 | @Override 152 | protected void createEditorInner(JPanel panel, GridBag gridBag) { 153 | super.createEditorInner(panel, gridBag); 154 | 155 | for (Component component : panel.getComponents()) { 156 | if (component instanceof CommonProgramParametersPanel) { 157 | component.setVisible(false);//todo get rid of this hack 158 | } 159 | } 160 | 161 | this.debuggers = new DebuggersComboBoxWithModel(this.myProject, true, true); 162 | panel.add(new JLabel("Debugger:"), gridBag.nextLine().next()); 163 | panel.add(debuggers.getComponent(), gridBag.next().coverLine()); 164 | 165 | panel.add(new JLabel("Board config file:"), gridBag.nextLine().next()); 166 | boardConfigFile = new FileChooseInput.BoardCfg("Board config", VfsUtil.getUserHomeDir(), 167 | this::getOpenocdHome); 168 | panel.add(boardConfigFile, gridBag.next().coverLine()); 169 | 170 | panel.add(new JLabel("Interface config file:"), gridBag.nextLine().next()); 171 | interfaceConfigFile = new FileChooseInput.InterfaceCfg("Interface config", VfsUtil.getUserHomeDir(), 172 | this::getOpenocdHome); 173 | panel.add(interfaceConfigFile, gridBag.next().coverLine()); 174 | 175 | ModuleRootManager manager = ModuleRootManager.getInstance(ModuleManager.getInstance(myProject).getModules()[0]); 176 | VirtualFile contentRoot = getContentRoot(manager); 177 | 178 | JPanel bootloaderPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 179 | bootloaderPanel.add(new JLabel("Bootloader binary:")); 180 | bootloaderFile = new FileChooseInput.BinFile(BOOTLOADER_FILE, VfsUtil.getUserHomeDir(), contentRoot); 181 | bootloaderPanel.add(bootloaderFile); 182 | 183 | bootloaderPanel.add(new JLabel("Bootloader offset:")); 184 | bootloaderOffset = addOffsetInput(OpenOcdConfiguration.DEF_BOOT_OFFSET); 185 | bootloaderPanel.add(bootloaderOffset); 186 | 187 | panel.add(bootloaderPanel, gridBag.nextLine().next().coverLine()); 188 | 189 | JPanel partitionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 190 | partitionPanel.add(new JLabel("Partition Table binary:")); 191 | partitionTableFile = new FileChooseInput.BinFile(PART_TABLE_FILE, VfsUtil.getUserHomeDir(), contentRoot); 192 | partitionPanel.add(partitionTableFile); 193 | 194 | partitionPanel.add(new JLabel("Partition Table offset:")); 195 | partitionTableOffset = addOffsetInput(OpenOcdConfiguration.DEF_PART_OFFSET); 196 | partitionPanel.add(partitionTableOffset); 197 | 198 | panel.add(partitionPanel, gridBag.nextLine().next().coverLine()); 199 | 200 | panel.add(new JLabel("OpenOCD command:"), gridBag.nextLine().next()); 201 | programType = new JXRadioGroup<>(ProgramType.values()); 202 | panel.add(programType, gridBag.next().coverLine()); 203 | 204 | appendVerify = new JCheckBox("Append verify parameter", OpenOcdConfiguration.DEF_APPEND_VERIFY); 205 | panel.add(appendVerify, gridBag.nextLine().next()); 206 | 207 | panel.add(new JLabel("Additional program parameters:"), gridBag.nextLine().next()); 208 | additionalProgramParameters = new ExtendableTextField(OpenOcdConfiguration.DEF_ADD_PROG_PARAM); 209 | panel.add(additionalProgramParameters, gridBag.next().coverLine()); 210 | 211 | JPanel portsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 212 | 213 | gdbPort = addPortInput(portsPanel, "GDB port", OpenOcdConfiguration.DEF_GDB_PORT); 214 | portsPanel.add(Box.createHorizontalStrut(10)); 215 | 216 | telnetPort = addPortInput(portsPanel, "Telnet port", OpenOcdConfiguration.DEF_TELNET_PORT); 217 | 218 | panel.add(portsPanel, gridBag.nextLine().next().coverLine()); 219 | 220 | panel.add(new JLabel("Download Options"), gridBag.nextLine().next()); 221 | 222 | panel.add(createDownloadSelector(), gridBag.nextLine().coverLine()); 223 | 224 | panel.add(createGDBSettingsSelector(), gridBag.nextLine().coverLine()); 225 | } 226 | 227 | @NotNull 228 | private JPanel createDownloadSelector() { 229 | JPanel downloadPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 230 | GridLayout downloadGrid = new GridLayout(3, 2); 231 | downloadPanel.setLayout(downloadGrid); 232 | 233 | downloadPanel.add(new JLabel("Offset:")); 234 | offset = addOffsetInput(OpenOcdConfiguration.DEF_PROGRAM_OFFSET); 235 | downloadPanel.add(offset); 236 | 237 | downloadPanel.add(new JLabel("Perform:")); 238 | downloadGroup = new JXRadioGroup<>(DownloadType.values()); 239 | downloadPanel.add(downloadGroup); 240 | 241 | downloadPanel.add(new JLabel("Reset:")); 242 | resetGroup = new JXRadioGroup<>(OpenOcdConfiguration.ResetType.values()); 243 | resetGroup.addActionListener(e -> { 244 | if (resetGroup.getSelectedValue().supportsBreakpoints()) { 245 | initialBreakpointCheck.setVisible(true); 246 | if (initialBreakpointCheck.isSelected()) initialBreakpointName.setVisible(true); 247 | } else { 248 | initialBreakpointCheck.setVisible(false); 249 | initialBreakpointName.setVisible(false); 250 | } 251 | }); 252 | downloadPanel.add(resetGroup); 253 | 254 | return downloadPanel; 255 | } 256 | 257 | private VirtualFile getContentRoot(ModuleRootManager manager) { 258 | VirtualFile[] contentRoots = manager.getContentRoots(); 259 | if (contentRoots.length > 0) return contentRoots[0]; 260 | 261 | VirtualFile[] excludeRoots = manager.getExcludeRoots(); 262 | if (excludeRoots.length > 0) return excludeRoots[0].getParent(); 263 | 264 | throw new IllegalStateException("Cannot find content root!"); 265 | } 266 | 267 | private JPanel createGDBSettingsSelector() { 268 | JPanel settingsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 269 | GridLayout settingsGrid = new GridLayout(2, 4); 270 | settingsPanel.setLayout(settingsGrid); 271 | 272 | flushRegsCheck = new JCheckBox("Flush registers", OpenOcdConfiguration.DEF_FLUSH_REGS); 273 | settingsPanel.add(flushRegsCheck); 274 | 275 | settingsPanel.add(new JPanel()); // Placeholder 276 | 277 | initialBreakpointCheck = new JCheckBox("Break on function", OpenOcdConfiguration.DEF_BREAK_FUNCTION); 278 | initialBreakpointCheck.addItemListener(e -> initialBreakpointName.setVisible(e.getStateChange() == ItemEvent.SELECTED)); 279 | settingsPanel.add(initialBreakpointCheck); 280 | 281 | initialBreakpointName = new ExtendableTextField(OpenOcdConfiguration.DEF_BREAK_FUNCTION_NAME); 282 | settingsPanel.add(initialBreakpointName); 283 | 284 | return settingsPanel; 285 | } 286 | 287 | private IntegerField addPortInput(JPanel portsPanel, String label, int defaultValue) { 288 | portsPanel.add(new JLabel(label + ": ")); 289 | IntegerField field = new IntegerField(label, 1024, 65535); 290 | field.setDefaultValue(defaultValue); 291 | field.setColumns(5); 292 | portsPanel.add(field); 293 | return field; 294 | } 295 | 296 | private ExtendableTextField addOffsetInput(String defaultValue) { 297 | ExtendableTextField field = new ExtendableTextField(defaultValue); 298 | field.setColumns(5); 299 | return field; 300 | } 301 | 302 | private String getOpenocdHome() { 303 | return openocdHome; 304 | } 305 | 306 | } 307 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdComponent.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.RunContentExecutor; 5 | import com.intellij.execution.configurations.GeneralCommandLine; 6 | import com.intellij.execution.configurations.PtyCommandLine; 7 | import com.intellij.execution.filters.Filter; 8 | import com.intellij.execution.process.OSProcessHandler; 9 | import com.intellij.execution.process.ProcessEvent; 10 | import com.intellij.execution.process.ProcessListener; 11 | import com.intellij.execution.ui.ConsoleViewContentType; 12 | import com.intellij.execution.util.ExecutionErrorDialog; 13 | import com.intellij.openapi.application.ApplicationManager; 14 | import com.intellij.openapi.components.Service; 15 | import com.intellij.openapi.diagnostic.Logger; 16 | import com.intellij.openapi.editor.colors.EditorColorsManager; 17 | import com.intellij.openapi.editor.colors.EditorColorsScheme; 18 | import com.intellij.openapi.editor.markup.HighlighterLayer; 19 | import com.intellij.openapi.options.ConfigurationException; 20 | import com.intellij.openapi.progress.ProgressManager; 21 | import com.intellij.openapi.project.Project; 22 | import com.intellij.openapi.util.Key; 23 | import com.intellij.openapi.util.text.StringUtil; 24 | import com.intellij.openapi.vfs.LocalFileSystem; 25 | import com.intellij.openapi.vfs.VfsUtil; 26 | import com.intellij.openapi.vfs.VirtualFile; 27 | import java.io.File; 28 | import java.util.Objects; 29 | import java.util.concurrent.CompletableFuture; 30 | import java.util.concurrent.Future; 31 | import org.jdesktop.swingx.util.OS; 32 | import org.jetbrains.annotations.NotNull; 33 | import org.jetbrains.annotations.Nullable; 34 | 35 | @Service(Service.Level.PROJECT) 36 | public final class OpenOcdComponent { 37 | 38 | public static final String SCRIPTS_PATH_SHORT = "scripts"; 39 | public static final String SCRIPTS_PATH_MEDIUM = "openocd/" + SCRIPTS_PATH_SHORT; 40 | public static final String SCRIPTS_PATH_LONG = "share/openocd/" + SCRIPTS_PATH_SHORT; 41 | public static final String BIN_OPENOCD; 42 | private static final Key UPLOAD_LOAD_COUNT_KEY = new Key<>(OpenOcdConfiguration.class.getName() + 43 | "#LAST_DOWNLOAD_MOD_COUNT"); 44 | private static final String ERROR_PREFIX = "Error: "; 45 | private static final String[] IGNORED_STRINGS = { 46 | "clearing lockup after double fault", 47 | "LIB_USB_NOT_SUPPORTED"}; 48 | 49 | private final static String[] FAIL_STRINGS = { 50 | "** Programming Failed **", "communication failure", "** OpenOCD init failed **"}; 51 | private static final String FLASH_SUCCESS_TEXT = "** Program Flash Complete! **"; 52 | private static final Logger LOG = Logger.getInstance(OpenOcdComponent.class); 53 | private static final String ADAPTER_SPEED = "adapter speed"; 54 | 55 | static { 56 | BIN_OPENOCD = "bin/openocd" + (OS.isWindows() ? ".exe" : ""); 57 | } 58 | 59 | private final EditorColorsScheme myColorsScheme; 60 | private OSProcessHandler process; 61 | 62 | public OpenOcdComponent() { 63 | myColorsScheme = EditorColorsManager.getInstance().getGlobalScheme(); 64 | } 65 | 66 | @NotNull 67 | public static GeneralCommandLine createOcdCommandLine(OpenOcdConfiguration config, File fileToLoad, 68 | @Nullable String additionalCommand, boolean shutdown) throws ConfigurationException { 69 | Project project = config.getProject(); 70 | OpenOcdSettingsState ocdSettings = project.getService(OpenOcdSettingsState.class); 71 | if (StringUtil.isEmpty(config.getBoardConfigFile())) { 72 | throw new ConfigurationException("Board Config file is not defined.", "OpenOCD Run Error"); 73 | } 74 | VirtualFile ocdHome = require(LocalFileSystem.getInstance().findFileByPath(ocdSettings.openOcdHome)); 75 | VirtualFile ocdBinary = require(ocdHome.findFileByRelativePath(BIN_OPENOCD)); 76 | File ocdBinaryIo = VfsUtil.virtualToIoFile(ocdBinary); 77 | GeneralCommandLine commandLine = new PtyCommandLine() 78 | .withWorkDirectory(ocdBinaryIo.getParentFile()) 79 | .withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) 80 | .withParameters("-c", "tcl_port disabled") 81 | .withExePath(ocdBinaryIo.getAbsolutePath()); 82 | 83 | VirtualFile ocdScripts = require(OpenOcdSettingsState.findOcdScripts(ocdHome)); 84 | commandLine.addParameters("-s", VfsUtil.virtualToIoFile(ocdScripts).getAbsolutePath()); 85 | if (config.getGdbPort() != OpenOcdConfiguration.DEF_GDB_PORT) { 86 | commandLine.addParameters("-c", "gdb_port " + config.getGdbPort()); 87 | } 88 | if (config.getTelnetPort() != OpenOcdConfiguration.DEF_TELNET_PORT) { 89 | commandLine.addParameters("-c", "telnet_port " + config.getTelnetPort()); 90 | } 91 | 92 | if (!StringUtil.isEmpty(config.getInterfaceConfigFile())) { 93 | commandLine.addParameters("-f", config.getInterfaceConfigFile()); 94 | } 95 | 96 | commandLine.addParameters("-f", config.getBoardConfigFile()); 97 | 98 | if (!StringUtil.isEmpty(config.getBootBinPath())) { 99 | commandLine.addParameters("-c", 100 | config.getProgramType().toString() + " " + config.getBootBinPath() + " " 101 | + config.getBootOffset() + (config.getAppendVerify() ? " verify" : "") + 102 | (config.getAdditionalProgramParameters() != null ? 103 | " " + config.getAdditionalProgramParameters() : "")); 104 | } 105 | if (!StringUtil.isEmpty(config.getPartitionBinPath())) { 106 | commandLine.addParameters("-c", 107 | config.getProgramType().toString() + " " + config.getPartitionBinPath() + " " 108 | + config.getPartitionOffset() + (config.getAppendVerify() ? " verify" : "") + 109 | (config.getAdditionalProgramParameters() != null ? 110 | " " + config.getAdditionalProgramParameters() : "")); 111 | } 112 | 113 | if (fileToLoad != null) { // Program Command 114 | String command = 115 | config.getProgramType().toString() + " " + fileToLoad.getAbsolutePath() 116 | .replace(File.separatorChar, '/') 117 | .replace(".elf", ".bin"); 118 | if (config.getOffset() != null && !config.getOffset().isEmpty()) 119 | command = command + " " + config.getOffset(); 120 | 121 | if (config.getAppendVerify()) { 122 | command += " verify"; 123 | } 124 | 125 | if (config.getAdditionalProgramParameters() != null){ 126 | command += " " + config.getAdditionalProgramParameters(); 127 | } 128 | 129 | commandLine.addParameters("-c", command); 130 | } 131 | 132 | if (additionalCommand != null && !additionalCommand.isEmpty()) 133 | commandLine.addParameters("-c", additionalCommand); 134 | 135 | commandLine.addParameters("-c", config.getResetType().getCommand()); 136 | 137 | commandLine.addParameters("-c", "echo \"" + FLASH_SUCCESS_TEXT + "\""); 138 | 139 | if (shutdown) { 140 | commandLine.addParameters("-c", "shutdown"); 141 | } 142 | return commandLine; 143 | } 144 | 145 | @NotNull 146 | public static VirtualFile require(VirtualFile fileToCheck) throws ConfigurationException { 147 | if (fileToCheck == null) { 148 | openOcdNotFound(); 149 | } 150 | return fileToCheck; 151 | } 152 | 153 | private static void openOcdNotFound() throws ConfigurationException { 154 | throw new ConfigurationException("Please open settings dialog and fix OpenOCD home", "OpenOCD Config " 155 | + "Error"); 156 | } 157 | 158 | public void stopOpenOcd() { 159 | if (process == null || process.isProcessTerminated() || process.isProcessTerminating()) 160 | return; 161 | ProgressManager.getInstance().executeNonCancelableSection(() -> { 162 | process.destroyProcess(); 163 | process.waitFor(1000); 164 | }); 165 | } 166 | 167 | public Future startOpenOcd(OpenOcdConfiguration config, @Nullable File fileToLoad) throws 168 | ConfigurationException { 169 | CompletableFuture ret = new CompletableFuture<>(); 170 | if (config == null) { 171 | ret.obtrudeValue(Status.FLASH_ERROR); 172 | return ret; 173 | } 174 | GeneralCommandLine commandLine = createOcdCommandLine(config, fileToLoad, null, false); 175 | if (process != null && !process.isProcessTerminated()) { 176 | LOG.info("OpenOCD is already run"); 177 | ret.obtrudeValue(Status.FLASH_ERROR); 178 | return ret; 179 | } 180 | VirtualFile virtualFile = fileToLoad != null ? VfsUtil.findFileByIoFile(fileToLoad, true) : null; 181 | Project project = config.getProject(); 182 | try { 183 | process = new OSProcessHandler(commandLine) { 184 | @Override 185 | public boolean isSilentlyDestroyOnClose() { 186 | return true; 187 | } 188 | }; 189 | DownloadFollower downloadFollower = new DownloadFollower(virtualFile); 190 | process.addProcessListener(downloadFollower); 191 | RunContentExecutor openOCDConsole = new RunContentExecutor(project, process) 192 | .withTitle("OpenOCD Console") 193 | .withActivateToolWindow(true) 194 | .withFilter(new ErrorFilter(project)) 195 | .withStop(process::destroyProcess, 196 | () -> !process.isProcessTerminated() && !process.isProcessTerminating()); 197 | 198 | ApplicationManager.getApplication().invokeLater(openOCDConsole::run); 199 | ret.obtrudeValue(null); // Unneeded. Complete anyway. 200 | return downloadFollower; 201 | } catch (ExecutionException e) { 202 | ExecutionErrorDialog.show(e, "OpenOCD Start Failed", project); 203 | ret.obtrudeValue(Status.FLASH_ERROR); 204 | return ret; 205 | } 206 | } 207 | 208 | public enum Status { 209 | FLASH_SUCCESS, 210 | FLASH_WARNING, 211 | FLASH_ERROR, 212 | } 213 | 214 | public enum FlashedStatus { 215 | INITIALIZED, APP_OK; 216 | 217 | private FlashedStatus nextState; 218 | 219 | static { 220 | INITIALIZED.nextState = APP_OK; 221 | APP_OK.nextState = APP_OK; 222 | } 223 | 224 | public FlashedStatus getNextState() { 225 | return nextState; 226 | } 227 | } 228 | 229 | private class ErrorFilter implements Filter { 230 | private final Project project; 231 | 232 | ErrorFilter(Project project) { 233 | this.project = project; 234 | } 235 | 236 | /** 237 | * Filters line by creating an instance of {@link Result}. 238 | * 239 | * @param line The line to be filtered. Note that the line must contain a line 240 | * separator at the end. 241 | * @param entireLength The length of the entire text including the line passed for filtration. 242 | * @return null, if there was no match, otherwise, an instance of {@link Result} 243 | */ 244 | @Nullable 245 | @Override 246 | public Result applyFilter(@NotNull String line, int entireLength) { 247 | if (containsOneOf(line, FAIL_STRINGS)) { 248 | Informational.showFailedDownloadNotification(project); 249 | return new Result(0, line.length(), null, 250 | myColorsScheme.getAttributes(ConsoleViewContentType.ERROR_OUTPUT_KEY)) { 251 | @Override 252 | public int getHighlighterLayer() { 253 | return HighlighterLayer.ERROR; 254 | } 255 | }; 256 | } else if (line.equals(FLASH_SUCCESS_TEXT)) { 257 | Informational.showSuccessfulDownloadNotification(project); 258 | } 259 | return null; 260 | } 261 | } 262 | 263 | private class DownloadFollower extends CompletableFuture implements ProcessListener { 264 | @Nullable 265 | private final VirtualFile vRunFile; 266 | 267 | private FlashedStatus flashedStatusListen = FlashedStatus.INITIALIZED; 268 | 269 | DownloadFollower(@Nullable VirtualFile vRunFile) { 270 | this.vRunFile = vRunFile; 271 | } 272 | 273 | @Override 274 | public void startNotified(@NotNull ProcessEvent event) { 275 | //nothing to do 276 | } 277 | 278 | @Override 279 | public void processTerminated(@NotNull ProcessEvent event) { 280 | try { 281 | if (!isDone()) { 282 | obtrudeValue(Status.FLASH_ERROR); 283 | } 284 | } catch (Exception e) { 285 | obtrudeValue(Status.FLASH_ERROR); 286 | } 287 | } 288 | 289 | @Override 290 | public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDestroyed) { 291 | //nothing to do 292 | } 293 | 294 | @Override 295 | public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { 296 | String text = event.getText().trim(); 297 | if (containsOneOf(text, FAIL_STRINGS)) { 298 | obtrudeValue(Status.FLASH_ERROR); 299 | } else if (vRunFile == null && text.startsWith(ADAPTER_SPEED)) { 300 | obtrudeValue(Status.FLASH_SUCCESS); 301 | } else if (text.contains(FLASH_SUCCESS_TEXT)) { 302 | if (flashedStatusListen == FlashedStatus.APP_OK) { 303 | if (vRunFile != null) { 304 | UPLOAD_LOAD_COUNT_KEY.set(vRunFile, vRunFile.getModificationCount()); 305 | } 306 | obtrudeValue(Status.FLASH_SUCCESS); 307 | } 308 | flashedStatusListen = flashedStatusListen.getNextState(); 309 | } else if (text.startsWith(ERROR_PREFIX) && !containsOneOf(text, IGNORED_STRINGS)) { 310 | obtrudeValue(Status.FLASH_WARNING); 311 | } 312 | } 313 | } 314 | 315 | private boolean containsOneOf(String text, String[] sampleStrings) { 316 | if (text == null || text.isEmpty()) { 317 | return false; 318 | } 319 | for (String sampleString : sampleStrings) { 320 | if (text.contains(sampleString)) return true; 321 | } 322 | return false; 323 | } 324 | 325 | public static boolean isLatestUploaded(File runFile) { 326 | VirtualFile vRunFile = VfsUtil.findFileByIoFile(runFile, true); 327 | Long latestDownloadModCount = UPLOAD_LOAD_COUNT_KEY.get(vRunFile); 328 | return vRunFile != null && Objects.equals(latestDownloadModCount, vRunFile.getModificationCount()); 329 | } 330 | 331 | } 332 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/FileChooseInput.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooser; 4 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 5 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 6 | import com.intellij.openapi.options.ConfigurationException; 7 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 8 | import com.intellij.openapi.util.InvalidDataException; 9 | import com.intellij.openapi.util.SystemInfo; 10 | import com.intellij.openapi.util.text.StringUtil; 11 | import com.intellij.openapi.vfs.LocalFileSystem; 12 | import com.intellij.openapi.vfs.VfsUtil; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import com.intellij.ui.components.JBTextField; 15 | import com.intellij.ui.components.fields.valueEditors.TextFieldValueEditor; 16 | import java.io.File; 17 | import java.util.function.Supplier; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | public abstract class FileChooseInput extends TextFieldWithBrowseButton { 22 | 23 | public static final String BOARD_FOLDER = "board"; 24 | public static final String INTERFACE_FOLDER = "interface"; 25 | public static final String BOOT_BIN_FOLDER = "bootloader"; 26 | public static final String PART_BIN_FOLDER = "partition_table"; 27 | protected final TextFieldValueEditor editor; 28 | private final FileChooserDescriptor fileDescriptor; 29 | 30 | protected FileChooseInput(String valueName, VirtualFile defValue) { 31 | super(new JBTextField()); 32 | 33 | editor = new FileTextFieldValueEditor(valueName, defValue); 34 | fileDescriptor = createFileChooserDescriptor().withFileFilter(this::validateFile); 35 | installPathCompletion(fileDescriptor); 36 | addActionListener(e -> { 37 | VirtualFile virtualFile = null; 38 | String text = getTextField().getText(); 39 | if (text != null && !text.isEmpty()) 40 | try { 41 | virtualFile = parseTextToFile(text); 42 | } catch (InvalidDataException ignored) { 43 | virtualFile = LocalFileSystem.getInstance().findFileByPath(text); 44 | } 45 | if (virtualFile == null) { 46 | virtualFile = getDefaultLocation(); 47 | } 48 | VirtualFile chosenFile = FileChooser.chooseFile(fileDescriptor, null, virtualFile); 49 | if (chosenFile != null) { 50 | getTextField().setText(fileToTextValue(chosenFile)); 51 | } 52 | }); 53 | } 54 | 55 | protected VirtualFile getDefaultLocation() { 56 | return VfsUtil.getUserHomeDir(); 57 | } 58 | 59 | protected String fileToTextValue(VirtualFile file) { 60 | return file.getCanonicalPath(); 61 | } 62 | 63 | protected abstract boolean validateFile(VirtualFile virtualFile); 64 | 65 | protected abstract FileChooserDescriptor createFileChooserDescriptor(); 66 | 67 | public String getValueName() { 68 | return editor.getValueName(); 69 | } 70 | 71 | public void validateContent() throws ConfigurationException { 72 | editor.validateContent(); 73 | } 74 | 75 | @NotNull 76 | protected VirtualFile parseTextToFile(@Nullable String text) { 77 | VirtualFile file = text == null ? editor.getDefaultValue() : 78 | LocalFileSystem.getInstance().findFileByPath(text); 79 | if (file == null || !validateFile(file)) { 80 | throw new InvalidDataException("is invalid"); 81 | } 82 | return file; 83 | } 84 | 85 | public static class BoardCfg extends FileChooseInput { 86 | 87 | private final Supplier ocdHome; 88 | 89 | public BoardCfg(String valueName, VirtualFile defValue, Supplier ocdHome) { 90 | super(valueName, defValue); 91 | this.ocdHome = ocdHome; 92 | } 93 | 94 | @Override 95 | protected VirtualFile getDefaultLocation() { 96 | VirtualFile ocdScripts = findOcdScripts(); 97 | if (ocdScripts != null) { 98 | VirtualFile ocdBoards = ocdScripts.findFileByRelativePath(BOARD_FOLDER); 99 | if (ocdBoards != null) { 100 | return ocdBoards; 101 | } 102 | } 103 | return super.getDefaultLocation(); 104 | } 105 | 106 | @NotNull 107 | @Override 108 | protected VirtualFile parseTextToFile(@Nullable String text) { 109 | VirtualFile file; 110 | if (text == null) { 111 | file = editor.getDefaultValue(); 112 | } else { 113 | file = LocalFileSystem.getInstance().findFileByPath(text); 114 | if (file == null) { 115 | VirtualFile ocdScripts = findOcdScripts(); 116 | if (ocdScripts != null) { 117 | file = ocdScripts.findFileByRelativePath(text); 118 | } 119 | } 120 | } 121 | if (file == null || !validateFile(file)) { 122 | throw new InvalidDataException("is invalid"); 123 | } 124 | return file; 125 | } 126 | 127 | private VirtualFile getOpenOcdHome() { 128 | return LocalFileSystem.getInstance().findFileByPath(ocdHome.get()); 129 | } 130 | 131 | @Override 132 | protected boolean validateFile(VirtualFile virtualFile) { 133 | return virtualFile.exists() && !virtualFile.isDirectory(); 134 | } 135 | 136 | @Override 137 | protected FileChooserDescriptor createFileChooserDescriptor() { 138 | return FileChooserDescriptorFactory.singleFile(); 139 | } 140 | 141 | @Override 142 | protected String fileToTextValue(VirtualFile file) { 143 | String completeFileName = super.fileToTextValue(file); 144 | VirtualFile ocdScripts = findOcdScripts(); 145 | if (ocdScripts != null) { 146 | String relativePath = VfsUtil.getRelativePath(file, ocdScripts); 147 | if (relativePath != null) { 148 | return relativePath; 149 | } 150 | } 151 | return completeFileName; 152 | } 153 | 154 | @Nullable 155 | private VirtualFile findOcdScripts() { 156 | return OpenOcdSettingsState.findOcdScripts(getOpenOcdHome()); 157 | } 158 | 159 | } 160 | 161 | public static class InterfaceCfg extends FileChooseInput { 162 | 163 | private final Supplier ocdHome; 164 | 165 | public InterfaceCfg(String valueName, VirtualFile defValue, Supplier ocdHome) { 166 | super(valueName, defValue); 167 | this.ocdHome = ocdHome; 168 | } 169 | 170 | @Override 171 | protected VirtualFile getDefaultLocation() { 172 | VirtualFile ocdScripts = findOcdScripts(); 173 | if (ocdScripts != null) { 174 | VirtualFile ocdInterfaces = ocdScripts.findFileByRelativePath(INTERFACE_FOLDER); 175 | if (ocdInterfaces != null) { 176 | return ocdInterfaces; 177 | } 178 | } 179 | return super.getDefaultLocation(); 180 | } 181 | 182 | @NotNull 183 | @Override 184 | protected VirtualFile parseTextToFile(@Nullable String text) { 185 | VirtualFile file; 186 | if (text == null) { 187 | file = editor.getDefaultValue(); 188 | } else { 189 | file = LocalFileSystem.getInstance().findFileByPath(text); 190 | if (file == null) { 191 | VirtualFile ocdScripts = findOcdScripts(); 192 | if (ocdScripts != null) { 193 | file = ocdScripts.findFileByRelativePath(text); 194 | } 195 | } 196 | } 197 | if (file == null || !validateFile(file)) { 198 | throw new InvalidDataException("is invalid"); 199 | } 200 | return file; 201 | } 202 | 203 | private VirtualFile getOpenOcdHome() { 204 | return LocalFileSystem.getInstance().findFileByPath(ocdHome.get()); 205 | } 206 | 207 | @Override 208 | protected boolean validateFile(VirtualFile virtualFile) { 209 | return virtualFile.exists() && !virtualFile.isDirectory(); 210 | } 211 | 212 | @Override 213 | protected FileChooserDescriptor createFileChooserDescriptor() { 214 | return FileChooserDescriptorFactory.singleFile(); 215 | } 216 | 217 | @Override 218 | protected String fileToTextValue(VirtualFile file) { 219 | String completeFileName = super.fileToTextValue(file); 220 | VirtualFile ocdScripts = findOcdScripts(); 221 | if (ocdScripts != null) { 222 | String relativePath = VfsUtil.getRelativePath(file, ocdScripts); 223 | if (relativePath != null) { 224 | return relativePath; 225 | } 226 | } 227 | return completeFileName; 228 | } 229 | 230 | @Nullable 231 | private VirtualFile findOcdScripts() { 232 | return OpenOcdSettingsState.findOcdScripts(getOpenOcdHome()); 233 | } 234 | 235 | } 236 | 237 | public static class BinFile extends FileChooseInput { 238 | 239 | private final VirtualFile projectHome; 240 | 241 | public BinFile(String valueName, VirtualFile defValue, VirtualFile projectHome) { 242 | super(valueName, defValue); 243 | this.projectHome = projectHome; 244 | } 245 | 246 | public String getPath() { 247 | String text = getText(); 248 | if (StringUtil.isEmpty(text)) { 249 | return ""; 250 | } 251 | if (new File(text).isAbsolute()) { 252 | return text; 253 | } 254 | // return Objects.requireNonNull(projectHome.findFileByRelativePath(text)).getPath(); 255 | return projectHome.getPath() + "/" + text; 256 | } 257 | 258 | @Override 259 | protected VirtualFile getDefaultLocation() { 260 | if (projectHome != null) { 261 | String valueName = getValueName(); 262 | if (valueName.equals(OpenOcdConfigurationEditor.BOOTLOADER_FILE)) { 263 | VirtualFile bin = projectHome.findFileByRelativePath(BOOT_BIN_FOLDER); 264 | if (bin != null) return bin; 265 | } 266 | if (valueName.equals(OpenOcdConfigurationEditor.PART_TABLE_FILE)) { 267 | VirtualFile bin = projectHome.findFileByRelativePath(PART_BIN_FOLDER); 268 | if (bin != null) return bin; 269 | } 270 | } 271 | return super.getDefaultLocation(); 272 | } 273 | 274 | @NotNull 275 | @Override 276 | protected VirtualFile parseTextToFile(@Nullable String text) { 277 | VirtualFile file; 278 | if (text == null) { 279 | file = editor.getDefaultValue(); 280 | } else { 281 | file = LocalFileSystem.getInstance().findFileByPath(text); 282 | if (file == null) { 283 | if (projectHome != null) { 284 | file = projectHome.findFileByRelativePath(text); 285 | } 286 | } 287 | } 288 | if (file == null || !validateFile(file)) { 289 | throw new InvalidDataException("is invalid"); 290 | } 291 | return file; 292 | } 293 | 294 | @Override 295 | protected boolean validateFile(VirtualFile virtualFile) { 296 | return virtualFile.exists() && !virtualFile.isDirectory(); 297 | } 298 | 299 | @Override 300 | protected FileChooserDescriptor createFileChooserDescriptor() { 301 | if (SystemInfo.isWindows) { 302 | return FileChooserDescriptorFactory.createSingleFileDescriptor("bin"); 303 | } else { 304 | return FileChooserDescriptorFactory.singleFile(); 305 | } 306 | } 307 | 308 | @Override 309 | protected String fileToTextValue(VirtualFile file) { 310 | String completeFileName = super.fileToTextValue(file); 311 | if (projectHome != null) { 312 | String relativePath = VfsUtil.getRelativePath(file, projectHome); 313 | if (relativePath != null) { 314 | return relativePath; 315 | } 316 | } 317 | return completeFileName; 318 | } 319 | } 320 | 321 | // Might be used in future 322 | public static class ExeFile extends FileChooseInput { 323 | 324 | public ExeFile(String valueName, VirtualFile defValue) { 325 | super(valueName, defValue); 326 | } 327 | 328 | @Override 329 | public boolean validateFile(VirtualFile virtualFile) { 330 | return virtualFile.exists() && !virtualFile.isDirectory() 331 | && VfsUtil.virtualToIoFile(virtualFile).canExecute(); 332 | } 333 | 334 | @Override 335 | protected FileChooserDescriptor createFileChooserDescriptor() { 336 | if (SystemInfo.isWindows) { 337 | return FileChooserDescriptorFactory.createSingleFileDescriptor("exe"); 338 | } else { 339 | return FileChooserDescriptorFactory.singleFile(); 340 | } 341 | } 342 | } 343 | 344 | public static class OpenOcdHome extends FileChooseInput { 345 | 346 | public OpenOcdHome(String valueName, VirtualFile defValue) { 347 | super(valueName, defValue); 348 | } 349 | 350 | @Override 351 | public boolean validateFile(VirtualFile virtualFile) { 352 | if (!virtualFile.isDirectory()) return false; 353 | VirtualFile openOcdBinary = virtualFile.findFileByRelativePath(OpenOcdComponent.BIN_OPENOCD); 354 | if (openOcdBinary == null || openOcdBinary.isDirectory() 355 | || !VfsUtil.virtualToIoFile(openOcdBinary).canExecute()) return false; 356 | VirtualFile ocdScripts = OpenOcdSettingsState.findOcdScripts(virtualFile); 357 | if (ocdScripts != null) { 358 | VirtualFile ocdBoard = ocdScripts.findFileByRelativePath(BOARD_FOLDER); 359 | return ocdBoard != null && ocdBoard.isDirectory(); 360 | } 361 | return false; 362 | } 363 | 364 | @Override 365 | protected FileChooserDescriptor createFileChooserDescriptor() { 366 | return FileChooserDescriptorFactory.createSingleFolderDescriptor(); 367 | } 368 | } 369 | 370 | private class FileTextFieldValueEditor extends TextFieldValueEditor { 371 | FileTextFieldValueEditor(String valueName, VirtualFile defValue) { 372 | super(FileChooseInput.this.getTextField(), valueName, defValue); 373 | } 374 | 375 | @NotNull 376 | @Override 377 | public VirtualFile parseValue(@Nullable String text) { 378 | return parseTextToFile(text); 379 | } 380 | 381 | @Override 382 | public String valueToString(@NotNull VirtualFile value) { 383 | return value.getPath(); 384 | } 385 | 386 | @Override 387 | public boolean isValid(@NotNull VirtualFile virtualFile) { 388 | return FileChooseInput.this.validateFile(virtualFile); 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /src/main/java/esp32/embedded/clion/openocd/OpenOcdConfiguration.java: -------------------------------------------------------------------------------- 1 | package esp32.embedded.clion.openocd; 2 | 3 | import com.intellij.execution.Executor; 4 | import com.intellij.execution.configurations.ConfigurationFactory; 5 | import com.intellij.execution.configurations.RuntimeConfigurationException; 6 | import com.intellij.execution.runners.ExecutionEnvironment; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.util.InvalidDataException; 9 | import com.intellij.openapi.util.WriteExternalException; 10 | import com.intellij.openapi.util.text.StringUtil; 11 | import com.jetbrains.cidr.cpp.execution.CMakeAppRunConfiguration; 12 | import com.jetbrains.cidr.cpp.execution.debugger.CLionDebuggerKind; 13 | import com.jetbrains.cidr.cpp.execution.remote.DebuggerData; 14 | import com.jetbrains.cidr.execution.CidrCommandLineState; 15 | import com.jetbrains.cidr.execution.CidrExecutableDataHolder; 16 | import java.util.Objects; 17 | import org.jdom.Element; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | /** 22 | * (c) elmot on 29.9.2017. 23 | */ 24 | public class OpenOcdConfiguration extends CMakeAppRunConfiguration implements CidrExecutableDataHolder { 25 | public static final DebuggerData DEF_DEBUGGER = new DebuggerData(CLionDebuggerKind.Bundled.GDB.INSTANCE); 26 | public static final int DEF_GDB_PORT = 3333; 27 | public static final int DEF_TELNET_PORT = 4444; 28 | public static final DownloadType DEF_DOWNLOAD_TYPE = DownloadType.ALWAYS; 29 | public static final String DEF_PROGRAM_OFFSET = "0x10000"; 30 | public static final String DEF_BOOT_OFFSET = "0x0"; 31 | public static final String DEF_BOOT_BIN_PATH = "build/bootloader/bootloader.bin"; 32 | public static final boolean DEF_BOOT_BIN_PATH_SET = false; 33 | 34 | public static final String DEF_PART_OFFSET = "0x8000"; 35 | public static final String DEF_PART_BIN_PATH = "build/partition_table/partition-table.bin"; 36 | public static final boolean DEF_PART_BIN_PATH_SET = false; 37 | public static final ResetType DEF_RESET_TYPE = ResetType.HALT; 38 | public static final boolean DEF_FLUSH_REGS = true; 39 | public static final boolean DEF_BREAK_FUNCTION = true; 40 | public static final String DEF_BREAK_FUNCTION_NAME = "app_main"; 41 | 42 | public static final ProgramType DEF_PROGRAM_TYPE = ProgramType.PROGRAM_ESP; 43 | public static final boolean DEF_APPEND_VERIFY = true; 44 | public static final String DEF_ADD_PROG_PARAM = ""; 45 | public static final boolean DEF_ADD_PROG_PARAM_SET = false; 46 | 47 | private static final String ATTR_DEBUGGER = "gdb_debugger"; 48 | private static final String ATTR_GDB_PORT = "gdb_port"; 49 | private static final String ATTR_TELNET_PORT = "telnet_port"; 50 | private static final String ATTR_BOARD_CONFIG = "board_config"; 51 | private static final String ATTR_INTERFACE_CONFIG = "interface_config"; 52 | private static final String ATTR_BOOT_PATH_SET_CONFIG = "boot_path_set_cfg"; 53 | private static final String ATTR_BOOT_PATH_CONFIG = "boot_path_cfg"; 54 | private static final String ATTR_BOOT_OFFSET_CONFIG = "boot_offset_cfg"; 55 | 56 | private static final String ATTR_PART_PATH_SET_CONFIG = "part_path_set_cfg"; 57 | private static final String ATTR_PART_PATH_CONFIG = "part_path_cfg"; 58 | private static final String ATTR_PART_OFFSET_CONFIG = "part_offset_cfg"; 59 | private static final String ATTR_PROGRAM_OFFSET_CONFIG = "program_offset_cfg"; 60 | public static final String ATTR_DOWNLOAD_TYPE = "download_type"; 61 | public static final String ATTR_RESET_TYPE = "reset_type"; 62 | public static final String ATTR_FLUSH_REGS = "flush_regs"; 63 | public static final String ATTR_BREAK_FUNCTION = "break"; 64 | public static final String ATTR_BREAK_FUNCTION_NAME = "break_function"; 65 | 66 | public static final String ATTR_PROGRAM_TYPE_CONFIG = "prog_type_cfg"; 67 | public static final String ATTR_APPEND_VERIFY_CONFIG = "app_verify_cfg"; 68 | public static final String ATTR_ADD_PROG_PARAM_CONFIG = "add_prog_param_cfg"; 69 | public static final String ATTR_ADD_PROG_PARAM_SET_CONFIG = "add_prog_param_set_cfg"; 70 | 71 | 72 | private DebuggerData debuggerData = DEF_DEBUGGER; 73 | private int gdbPort = DEF_GDB_PORT; 74 | private int telnetPort = DEF_TELNET_PORT; 75 | private String boardConfigFile; 76 | private String interfaceConfigFile; 77 | private DownloadType downloadType = DEF_DOWNLOAD_TYPE; 78 | private String offset = DEF_PROGRAM_OFFSET; 79 | private ResetType resetType = DEF_RESET_TYPE; 80 | private boolean flushRegs = DEF_FLUSH_REGS; 81 | private boolean initialBreak = DEF_BREAK_FUNCTION; 82 | private String initialBreakName = DEF_BREAK_FUNCTION_NAME; 83 | 84 | private String bootloaderOffset = DEF_BOOT_OFFSET; 85 | private String bootloaderBinPath = DEF_BOOT_BIN_PATH; 86 | private boolean bootloaderBinPathSet = DEF_BOOT_BIN_PATH_SET; 87 | private String partitionOffset = DEF_PART_OFFSET; 88 | private String partitionBinPath = DEF_PART_BIN_PATH; 89 | private boolean partitionBinPathSet = DEF_PART_BIN_PATH_SET; 90 | 91 | private ProgramType programType = DEF_PROGRAM_TYPE; 92 | private boolean appendVerify = DEF_APPEND_VERIFY; 93 | private String additionalProgramParameters = DEF_ADD_PROG_PARAM; 94 | private boolean additionalProgramParametersSet = DEF_ADD_PROG_PARAM_SET; 95 | 96 | public enum DownloadType { 97 | 98 | ALWAYS, 99 | UPDATED_ONLY, 100 | NONE; 101 | 102 | @Override 103 | public String toString() { 104 | return toBeautyString(super.toString()); 105 | } 106 | } 107 | 108 | public enum ProgramType { 109 | PROGRAM_ESP, 110 | PROGRAM_ESP32; 111 | 112 | @Override 113 | public String toString() { 114 | return super.toString().toLowerCase(); 115 | } 116 | } 117 | 118 | public static String toBeautyString(String obj) { 119 | return StringUtil.toTitleCase(obj.toLowerCase().replace("_", " ")); 120 | } 121 | 122 | public enum ResetType { 123 | HALT("init; reset halt", true, true), 124 | RESET("init; reset"), 125 | RUN("init; reset run;"), 126 | INIT("init; reset init;"), 127 | NONE(""); 128 | 129 | @Override 130 | public String toString() { 131 | return toBeautyString(super.toString()); 132 | } 133 | 134 | ResetType(String command) { 135 | this(command, false, false); 136 | } 137 | 138 | ResetType(String command, boolean supportsBreakpoints, boolean needsInit) { 139 | this.command = command; 140 | this.supportsBreakpoints = supportsBreakpoints; 141 | this.needsInit = needsInit; 142 | } 143 | 144 | private final String command; 145 | private final boolean supportsBreakpoints; 146 | private final boolean needsInit; 147 | 148 | public final String getCommand() { 149 | return command; 150 | } 151 | 152 | public final boolean supportsBreakpoints() { 153 | return supportsBreakpoints; 154 | } 155 | 156 | public final boolean needsInit() { 157 | return needsInit; 158 | } 159 | 160 | } 161 | 162 | public OpenOcdConfiguration(Project project, ConfigurationFactory configurationFactory, String targetName) { 163 | super(project, configurationFactory, targetName); 164 | } 165 | 166 | @Nullable 167 | @Override 168 | public CidrCommandLineState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { 169 | return new CidrCommandLineState(environment, new OpenOcdLauncher(this)); 170 | } 171 | 172 | @Override 173 | public void readExternal(@NotNull Element element) throws InvalidDataException { 174 | super.readExternal(element); 175 | boardConfigFile = element.getAttributeValue(ATTR_BOARD_CONFIG); 176 | interfaceConfigFile = element.getAttributeValue(ATTR_INTERFACE_CONFIG); 177 | 178 | Element debuggerElement = element.getChild(ATTR_DEBUGGER); 179 | if (debuggerElement != null) { 180 | debuggerData.readExternal(debuggerElement); 181 | } 182 | 183 | gdbPort = readIntAttr(element, ATTR_GDB_PORT, DEF_GDB_PORT); 184 | telnetPort = readIntAttr(element, ATTR_TELNET_PORT, DEF_TELNET_PORT); 185 | downloadType = readEnumAttr(element, ATTR_DOWNLOAD_TYPE, DownloadType.ALWAYS); 186 | resetType = readEnumAttr(element, ATTR_RESET_TYPE, DEF_RESET_TYPE); 187 | flushRegs = readBoolAttr(element, ATTR_FLUSH_REGS, DEF_FLUSH_REGS); 188 | initialBreak = readBoolAttr(element, ATTR_BREAK_FUNCTION, DEF_BREAK_FUNCTION); 189 | initialBreakName = element.getAttributeValue(ATTR_BREAK_FUNCTION_NAME, null, DEF_BREAK_FUNCTION_NAME); 190 | 191 | offset = element.getAttributeValue(ATTR_PROGRAM_OFFSET_CONFIG, null, DEF_PROGRAM_OFFSET); 192 | 193 | bootloaderBinPathSet = readBoolAttr(element, ATTR_BOOT_PATH_SET_CONFIG, DEF_BOOT_BIN_PATH_SET); 194 | bootloaderBinPath = element.getAttributeValue(ATTR_BOOT_PATH_CONFIG, null, 195 | bootloaderBinPathSet ? null : DEF_BOOT_BIN_PATH); 196 | bootloaderOffset = element.getAttributeValue(ATTR_BOOT_OFFSET_CONFIG, null, DEF_BOOT_OFFSET); 197 | 198 | partitionBinPathSet = readBoolAttr(element, ATTR_PART_PATH_SET_CONFIG, DEF_PART_BIN_PATH_SET); 199 | partitionBinPath = element.getAttributeValue(ATTR_PART_PATH_CONFIG, null, 200 | partitionBinPathSet ? null : DEF_PART_BIN_PATH); 201 | partitionOffset = element.getAttributeValue(ATTR_PART_OFFSET_CONFIG, null, DEF_PART_OFFSET); 202 | 203 | String programTypeStr = element.getAttributeValue(ATTR_PROGRAM_TYPE_CONFIG); 204 | programType = programTypeStr != null ? ProgramType.valueOf(programTypeStr) : DEF_PROGRAM_TYPE; 205 | appendVerify = readBoolAttr(element, ATTR_APPEND_VERIFY_CONFIG, DEF_APPEND_VERIFY); 206 | 207 | additionalProgramParametersSet = readBoolAttr(element, ATTR_ADD_PROG_PARAM_SET_CONFIG, DEF_ADD_PROG_PARAM_SET); 208 | additionalProgramParameters = element.getAttributeValue(ATTR_ADD_PROG_PARAM_CONFIG, null, 209 | additionalProgramParametersSet ? null : DEF_ADD_PROG_PARAM); 210 | } 211 | 212 | private int readIntAttr(@NotNull Element element, String name, int def) { 213 | String s = element.getAttributeValue(name); 214 | if (StringUtil.isEmpty(s)) return def; 215 | return Integer.parseUnsignedInt(s); 216 | } 217 | 218 | @SuppressWarnings("unchecked") 219 | private > T readEnumAttr(@NotNull Element element, String name, T def) { 220 | String s = element.getAttributeValue(name); 221 | if (StringUtil.isEmpty(s)) return def; 222 | try { 223 | return (T) Enum.valueOf(def.getDeclaringClass(), s); 224 | } catch (Throwable t) { 225 | return def; 226 | } 227 | } 228 | 229 | private boolean readBoolAttr(@NotNull Element element, String name, boolean def) { 230 | String s = element.getAttributeValue(name); 231 | if (StringUtil.isEmpty(s)) return def; 232 | try { 233 | return Boolean.parseBoolean(s); 234 | } catch (Throwable t) { 235 | return def; 236 | } 237 | } 238 | 239 | @Override 240 | public void writeExternal(@NotNull Element element) throws WriteExternalException { 241 | super.writeExternal(element); 242 | 243 | Element debuggerElement = new Element(ATTR_DEBUGGER); 244 | element.addContent(debuggerElement); 245 | debuggerData.writeExternal(debuggerElement); 246 | 247 | element.setAttribute(ATTR_GDB_PORT, String.valueOf(gdbPort)); 248 | element.setAttribute(ATTR_TELNET_PORT, String.valueOf(telnetPort)); 249 | if (boardConfigFile != null) { 250 | element.setAttribute(ATTR_BOARD_CONFIG, boardConfigFile); 251 | } 252 | if (interfaceConfigFile != null) { 253 | element.setAttribute(ATTR_INTERFACE_CONFIG, interfaceConfigFile); 254 | } 255 | element.setAttribute(ATTR_DOWNLOAD_TYPE, downloadType.name()); 256 | element.setAttribute(ATTR_RESET_TYPE, resetType.name()); 257 | element.setAttribute(ATTR_FLUSH_REGS, String.valueOf(flushRegs)); 258 | element.setAttribute(ATTR_BREAK_FUNCTION, String.valueOf(initialBreak)); 259 | element.setAttribute(ATTR_BREAK_FUNCTION_NAME, initialBreakName); 260 | 261 | element.setAttribute(ATTR_BOOT_PATH_SET_CONFIG, String.valueOf(bootloaderBinPathSet)); 262 | element.setAttribute(ATTR_BOOT_PATH_CONFIG, bootloaderBinPath == null ? "" : bootloaderBinPath); 263 | element.setAttribute(ATTR_BOOT_OFFSET_CONFIG, Objects.requireNonNullElse(bootloaderOffset, DEF_BOOT_OFFSET)); 264 | 265 | element.setAttribute(ATTR_PART_PATH_SET_CONFIG, String.valueOf(partitionBinPathSet)); 266 | element.setAttribute(ATTR_PART_PATH_CONFIG, partitionBinPath == null ? "" : partitionBinPath); 267 | element.setAttribute(ATTR_PART_OFFSET_CONFIG, Objects.requireNonNullElse(partitionOffset, DEF_PART_OFFSET)); 268 | 269 | element.setAttribute(ATTR_PROGRAM_OFFSET_CONFIG, Objects.requireNonNullElse(offset, DEF_PROGRAM_OFFSET)); 270 | 271 | element.setAttribute(ATTR_PROGRAM_TYPE_CONFIG, programType.name()); 272 | element.setAttribute(ATTR_APPEND_VERIFY_CONFIG, String.valueOf(appendVerify)); 273 | 274 | element.setAttribute(ATTR_ADD_PROG_PARAM_SET_CONFIG, String.valueOf(additionalProgramParametersSet)); 275 | element.setAttribute(ATTR_ADD_PROG_PARAM_CONFIG, additionalProgramParameters == null ? "" : additionalProgramParameters); 276 | } 277 | 278 | @Override 279 | public void checkConfiguration() throws RuntimeConfigurationException { 280 | super.checkConfiguration(); 281 | checkPort(gdbPort); 282 | checkPort(telnetPort); 283 | if (gdbPort == telnetPort) { 284 | throw new RuntimeConfigurationException("Port values should be different"); 285 | } 286 | if (StringUtil.isEmpty(boardConfigFile)) { 287 | throw new RuntimeConfigurationException("Board config file is not defined"); 288 | } 289 | } 290 | 291 | private void checkPort(int port) throws RuntimeConfigurationException { 292 | if (port <= 1024 || port > 65535) 293 | throw new RuntimeConfigurationException("Port value must be in the range [1025...65535]"); 294 | } 295 | 296 | public DebuggerData getDebuggerData() { 297 | return debuggerData; 298 | } 299 | 300 | public void setDebuggerData(DebuggerData debuggerData) { 301 | this.debuggerData = debuggerData; 302 | } 303 | 304 | public int getGdbPort() { 305 | return gdbPort; 306 | } 307 | 308 | public void setGdbPort(int gdbPort) { 309 | this.gdbPort = gdbPort; 310 | } 311 | 312 | public int getTelnetPort() { 313 | return telnetPort; 314 | } 315 | 316 | public void setTelnetPort(int telnetPort) { 317 | this.telnetPort = telnetPort; 318 | } 319 | 320 | public String getBoardConfigFile() { 321 | return boardConfigFile; 322 | } 323 | 324 | public String getInterfaceConfigFile() { 325 | return interfaceConfigFile; 326 | } 327 | 328 | public void setBoardConfigFile(String boardConfigFile) { 329 | this.boardConfigFile = boardConfigFile; 330 | } 331 | 332 | public void setInterfaceConfigFile(String interfaceConfigFile) { 333 | this.interfaceConfigFile = interfaceConfigFile; 334 | } 335 | 336 | 337 | public DownloadType getDownloadType() { 338 | return downloadType; 339 | } 340 | 341 | public void setDownloadType(DownloadType downloadType) { 342 | this.downloadType = downloadType; 343 | } 344 | 345 | public String getOffset() { 346 | return offset; 347 | } 348 | 349 | public void setOffset(String offset) { 350 | this.offset = offset; 351 | } 352 | 353 | public ResetType getResetType() { 354 | return resetType; 355 | } 356 | 357 | public void setResetType(ResetType resetType) { 358 | this.resetType = resetType; 359 | } 360 | 361 | public boolean getFlushRegs() { 362 | return flushRegs; 363 | } 364 | 365 | public void setFlushRegs(boolean flushRegs) { 366 | this.flushRegs = flushRegs; 367 | } 368 | 369 | public boolean getInitialBreak() { 370 | return initialBreak; 371 | } 372 | 373 | public void setInitialBreak(boolean initialBreak) { 374 | this.initialBreak = initialBreak; 375 | } 376 | 377 | public String getInitialBreakName() { 378 | return initialBreakName; 379 | } 380 | 381 | public void setInitialBreakName(String initialBreakName) { 382 | this.initialBreakName = initialBreakName; 383 | } 384 | 385 | public String getBootOffset() { 386 | return bootloaderOffset; 387 | } 388 | 389 | public void setBootOffset(String offset) { 390 | this.bootloaderOffset = offset; 391 | } 392 | 393 | public String getPartitionOffset() { 394 | return partitionOffset; 395 | } 396 | 397 | public void setPartitionOffset(String offset) { 398 | this.partitionOffset = offset; 399 | } 400 | 401 | public String getBootBinPath() { 402 | return bootloaderBinPath; 403 | } 404 | 405 | public void setBootBinPath(String path) { 406 | this.bootloaderBinPath = path; 407 | this.bootloaderBinPathSet = true; 408 | } 409 | 410 | public String getPartitionBinPath() { 411 | return partitionBinPath; 412 | } 413 | 414 | public void setPartitionBinPath(String path) { 415 | this.partitionBinPath = path; 416 | this.partitionBinPathSet = true; 417 | } 418 | 419 | public ProgramType getProgramType() { 420 | return programType; 421 | } 422 | 423 | public void setProgramType(ProgramType programType) { 424 | this.programType = programType; 425 | } 426 | 427 | public boolean getAppendVerify() { 428 | return appendVerify; 429 | } 430 | 431 | public void setAppendVerify(boolean appendVerify) { 432 | this.appendVerify = appendVerify; 433 | } 434 | 435 | public String getAdditionalProgramParameters() { 436 | return additionalProgramParameters; 437 | } 438 | 439 | public void setAdditionalProgramParameters(String additionalProgramParameters) { 440 | this.additionalProgramParameters = additionalProgramParameters; 441 | this.additionalProgramParametersSet = additionalProgramParameters != null; 442 | } 443 | } 444 | --------------------------------------------------------------------------------