test
This commit is contained in:
parent
4756250079
commit
aadb1071fc
|
|
@ -0,0 +1 @@
|
|||
custom: https://platformio.org/donate
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
What kind of issue is this?
|
||||
|
||||
- [ ] **Question**.
|
||||
This issue tracker is not the place for questions. If you want to ask how to do something,
|
||||
or to understand why something isn't working the way you expect it to,
|
||||
use [Community Forums](https://community.platformio.org) or [Premium Support](https://platformio.org/support)
|
||||
|
||||
- [ ] **PlatformIO IDE**.
|
||||
All issues related to PlatformIO IDE should be reported to the
|
||||
[PlatformIO IDE for VSCode](https://github.com/platformio/platformio-vscode-ide/issues) repository
|
||||
|
||||
- [ ] **Development Platform or Board**.
|
||||
All issues (building, uploading, adding new boards, etc.) related to PlatformIO development platforms
|
||||
should be reported to appropriate repository related to your hardware
|
||||
https://github.com/topics/platformio-platform
|
||||
|
||||
- [ ] **Feature Request**.
|
||||
Start by telling us what problem you’re trying to solve. Often a solution
|
||||
already exists! Don’t send pull requests to implement new features without first getting our
|
||||
support. Sometimes we leave features out on purpose to keep the project small.
|
||||
|
||||
- [ ] **PlatformIO Core**.
|
||||
If you’ve found a bug, please provide an information below.
|
||||
|
||||
|
||||
*You can erase any parts of this template not applicable to your Issue.*
|
||||
|
||||
------------------------------------------------------------------
|
||||
|
||||
### Configuration
|
||||
|
||||
**Operating system**:
|
||||
|
||||
**PlatformIO Version** (`platformio --version`):
|
||||
|
||||
### Description of problem
|
||||
|
||||
|
||||
#### Steps to Reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
### Actual Results
|
||||
|
||||
|
||||
### Expected Results
|
||||
|
||||
|
||||
### If problems with PlatformIO Build System:
|
||||
|
||||
**The content of `platformio.ini`:**
|
||||
```ini
|
||||
Insert here...
|
||||
```
|
||||
|
||||
**Source file to reproduce issue:**
|
||||
```cpp
|
||||
Insert here...
|
||||
```
|
||||
|
||||
### Additional info
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
name: Core
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, windows-latest, macos-latest]
|
||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
|
||||
- name: Core System Info
|
||||
run: |
|
||||
tox -e py
|
||||
|
||||
- name: Python Lint
|
||||
if: ${{ matrix.python-version != '3.6' }}
|
||||
run: |
|
||||
tox -e lint
|
||||
|
||||
- name: Integration Tests
|
||||
if: ${{ matrix.python-version == '3.9' }}
|
||||
run: |
|
||||
tox -e testcore
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@master
|
||||
if: failure()
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
job_name: '*Core*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
name: Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
- "release/**"
|
||||
|
||||
jobs:
|
||||
deployment:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox wheel
|
||||
|
||||
- name: Deployment Tests
|
||||
env:
|
||||
TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}
|
||||
TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}
|
||||
TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}
|
||||
run: |
|
||||
tox -e testcore
|
||||
|
||||
- name: Build Python source tarball
|
||||
run: python setup.py sdist bdist_wheel
|
||||
|
||||
- name: Publish package to PyPI
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
name: Docs
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
tox -e docs
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@master
|
||||
if: failure()
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
job_name: '*Docs*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
||||
- name: Preserve Docs
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
tar -czvf docs.tar.gz -C docs/_build html rtdpage
|
||||
|
||||
- name: Save artifact
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
path: ./docs.tar.gz
|
||||
|
||||
deploy:
|
||||
name: Deploy Docs
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
DOCS_REPO: platformio/platformio-docs
|
||||
DOCS_DIR: platformio-docs
|
||||
LATEST_DOCS_DIR: latest-docs
|
||||
RELEASE_BUILD: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docs
|
||||
- name: Unpack artifact
|
||||
run: |
|
||||
mkdir ./${{ env.LATEST_DOCS_DIR }}
|
||||
tar -xzf ./docs.tar.gz -C ./${{ env.LATEST_DOCS_DIR }}
|
||||
- name: Delete Artifact
|
||||
uses: geekyeggo/delete-artifact@v2
|
||||
with:
|
||||
name: docs
|
||||
- name: Select Docs type
|
||||
id: get-destination-dir
|
||||
run: |
|
||||
if [[ ${{ env.RELEASE_BUILD }} == true ]]; then
|
||||
echo "::set-output name=dst_dir::stable"
|
||||
else
|
||||
echo "::set-output name=dst_dir::latest"
|
||||
fi
|
||||
- name: Checkout latest Docs
|
||||
continue-on-error: true
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ${{ env.DOCS_REPO }}
|
||||
path: ${{ env.DOCS_DIR }}
|
||||
ref: gh-pages
|
||||
- name: Synchronize Docs
|
||||
run: |
|
||||
rm -rf ${{ env.DOCS_DIR }}/.git
|
||||
rm -rf ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
mkdir -p ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
cp -rf ${{ env.LATEST_DOCS_DIR }}/html/* ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}
|
||||
if [[ ${{ env.RELEASE_BUILD }} == false ]]; then
|
||||
rm -rf ${{ env.DOCS_DIR }}/page
|
||||
mkdir -p ${{ env.DOCS_DIR }}/page
|
||||
cp -rf ${{ env.LATEST_DOCS_DIR }}/rtdpage/* ${{ env.DOCS_DIR }}/page
|
||||
fi
|
||||
- name: Validate Docs
|
||||
run: |
|
||||
if [ -z "$(ls -A ${{ env.DOCS_DIR }})" ]; then
|
||||
echo "Docs folder is empty. Aborting!"
|
||||
exit 1
|
||||
fi
|
||||
- name: Deploy to Github Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
personal_token: ${{ secrets.DEPLOY_GH_DOCS_TOKEN }}
|
||||
external_repository: ${{ env.DOCS_REPO }}
|
||||
publish_dir: ./${{ env.DOCS_DIR }}
|
||||
commit_message: Sync Docs
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
name: Examples
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
PIO_INSTALL_DEVPLATFORM_OWNERNAMES: "platformio"
|
||||
PIO_INSTALL_DEVPLATFORM_NAMES: "aceinna_imu,atmelavr,atmelmegaavr,atmelsam,espressif32,espressif8266,nordicnrf52,raspberrypi,ststm32,teensy"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
|
||||
- name: Run on Linux
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
# Free space
|
||||
sudo apt clean
|
||||
docker rmi $(docker image ls -aq)
|
||||
df -h
|
||||
tox -e testexamples
|
||||
|
||||
- name: Run on macOS
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: |
|
||||
df -h
|
||||
tox -e testexamples
|
||||
|
||||
- name: Run on Windows
|
||||
if: startsWith(matrix.os, 'windows')
|
||||
env:
|
||||
PLATFORMIO_CORE_DIR: C:/pio
|
||||
PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH
|
||||
run: |
|
||||
tox -e testexamples
|
||||
|
||||
- name: Slack Notification
|
||||
uses: homoluctus/slatify@master
|
||||
if: failure()
|
||||
with:
|
||||
type: ${{ job.status }}
|
||||
job_name: '*Examples*'
|
||||
commit: true
|
||||
url: ${{ secrets.SLACK_BUILD_WEBHOOK }}
|
||||
token: ${{ secrets.SLACK_GITHUB_TOKEN }}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
name: Projects
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
project:
|
||||
- marlin:
|
||||
repository: "MarlinFirmware/Marlin"
|
||||
folder: "Marlin"
|
||||
config_dir: "Marlin"
|
||||
env_name: "mega2560"
|
||||
- esphome:
|
||||
repository: "esphome/esphome"
|
||||
folder: "esphome"
|
||||
config_dir: "esphome"
|
||||
env_name: "esp32-arduino"
|
||||
- smartknob:
|
||||
repository: "scottbez1/smartknob"
|
||||
folder: "smartknob"
|
||||
config_dir: "smartknob"
|
||||
env_name: "view"
|
||||
- espurna:
|
||||
repository: "xoseperez/espurna"
|
||||
folder: "espurna"
|
||||
config_dir: "espurna/code"
|
||||
env_name: "nodemcu-lolin"
|
||||
- OpenMQTTGateway:
|
||||
repository: "1technophile/OpenMQTTGateway"
|
||||
folder: "OpenMQTTGateway"
|
||||
config_dir: "OpenMQTTGateway"
|
||||
env_name: "esp32-m5atom-lite"
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
exclude:
|
||||
- os: windows-latest
|
||||
project: {"esphome": "", "repository": "esphome/esphome", "folder": "esphome", "config_dir": "esphome", "env_name": "esp32-arduino"}
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install PlatformIO
|
||||
run: pip install -U .
|
||||
|
||||
- name: Check out ${{ matrix.project.repository }}
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
repository: ${{ matrix.project.repository }}
|
||||
path: ${{ matrix.project.folder }}
|
||||
|
||||
- name: Install ESPHome dependencies
|
||||
# Requires esptool package as it's used in a custom prescript
|
||||
if: ${{ contains(matrix.project.repository, 'esphome') }}
|
||||
run: pip install esptool==3.*
|
||||
|
||||
- name: Compile ${{ matrix.project.repository }}
|
||||
run: pio run -d ${{ matrix.project.config_dir }} -e ${{ matrix.project.env_name }}
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
*.egg-info
|
||||
*.pyc
|
||||
__pycache__
|
||||
.tox
|
||||
docs/_build
|
||||
dist
|
||||
build
|
||||
.cache
|
||||
coverage.xml
|
||||
.coverage
|
||||
htmlcov
|
||||
.pytest_cache
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[submodule "examples"]
|
||||
path = examples
|
||||
url = https://github.com/platformio/platformio-examples.git
|
||||
[submodule "docs"]
|
||||
path = docs
|
||||
url = https://github.com/platformio/platformio-docs.git
|
||||
branch = develop
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[REPORTS]
|
||||
output-format=colorized
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=
|
||||
missing-docstring,
|
||||
duplicate-code,
|
||||
invalid-name,
|
||||
too-few-public-methods,
|
||||
consider-using-f-string,
|
||||
cyclic-import,
|
||||
use-dict-literal
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Code of Conduct
|
||||
|
||||
See https://piolabs.com/legal/code-of-conduct.html
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
Contributing
|
||||
------------
|
||||
|
||||
To get started, <a href="https://cla-assistant.io/platformio/platformio-core">sign the Contributor License Agreement</a>.
|
||||
|
||||
1. Fork the repository on GitHub
|
||||
2. Clone repository `git clone --recursive https://github.com/YourGithubUsername/platformio-core.git`
|
||||
3. Run `pip install tox`
|
||||
4. Go to the root of the PlatformIO Core project where `tox.ini` is located (``cd platformio-core``) and run `tox -e py39`.
|
||||
You can replace `py39` with your own Python version. For example, `py311` means Python 3.11.
|
||||
5. Activate current development environment:
|
||||
|
||||
* Windows: `.tox\py39\Scripts\activate`
|
||||
* Bash/ZSH: `source .tox/py39/bin/activate`
|
||||
* Fish: `source .tox/py39/bin/activate.fish`
|
||||
|
||||
6. Make changes to code, documentation, etc.
|
||||
7. Lint source code `make before-commit`
|
||||
8. Run the tests `make test`
|
||||
9. Build documentation `tox -e docs` (creates a directory _build under docs where you can find the html)
|
||||
10. Commit changes to your forked repository
|
||||
11. Submit a Pull Request on GitHub
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
Release Notes
|
||||
=============
|
||||
|
||||
.. |PIOCONF| replace:: `"platformio.ini" <https://docs.platformio.org/en/latest/projectconf.html>`__ configuration file
|
||||
.. |LIBRARYJSON| replace:: `library.json <https://docs.platformio.org/en/latest/manifests/library-json/index.html>`__
|
||||
.. |LDF| replace:: `LDF <https://docs.platformio.org/en/latest/librarymanager/ldf.html>`__
|
||||
.. |INTERPOLATION| replace:: `Interpolation of Values <https://docs.platformio.org/en/latest/projectconf/interpolation.html>`__
|
||||
.. |UNITTESTING| replace:: `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html>`__
|
||||
.. |DEBUGGING| replace:: `Debugging <https://docs.platformio.org/en/latest/plus/debugging.html>`__
|
||||
|
||||
.. _release_notes_6:
|
||||
|
||||
PlatformIO Core 6
|
||||
-----------------
|
||||
|
||||
Unlock the true potential of embedded software development with
|
||||
PlatformIO's collaborative ecosystem, embracing declarative principles,
|
||||
test-driven methodologies, and modern toolchains for unrivaled success.
|
||||
|
||||
6.1.11 (2023-08-31)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Resolved a possible issue that may cause generated projects for `PlatformIO IDE for VSCode <https://docs.platformio.org/en/latest/integration/ide/vscode.html>`__ to fail to launch a debug session because of a missing "objdump" binary when GDB is not part of the toolchain package
|
||||
* Resolved a regression issue that resulted in the malfunction of the Memory Inspection feature within `PIO Home <https://docs.platformio.org/en/latest/home/index.html>`__
|
||||
|
||||
6.1.10 (2023-08-11)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Resolved an issue that caused generated projects for `PlatformIO IDE for VSCode <https://docs.platformio.org/en/latest/integration/ide/vscode.html>`__ to break when the ``-iprefix`` compiler flag was used
|
||||
* Resolved an issue encountered while utilizing the `pio pkg exec <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_exec.html>`__ command on the Windows platform to execute Python scripts from a package
|
||||
* Implemented a crucial improvement to the `pio run <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html>`__ command, guaranteeing that the ``monitor`` target is not executed if any of the preceding targets, such as ``upload``, encounter failures
|
||||
* `Cppcheck <https://docs.platformio.org/en/latest/plus/check-tools/cppcheck.html>`__ v2.11 with new checks, CLI commands and various analysis improvements
|
||||
* Resolved a critical issue that arose on macOS ARM platforms due to the Python "requests" module, leading to a "ModuleNotFoundError: No module named 'chardet'" (`issue #4702 <https://github.com/platformio/platformio-core/issues/4702>`_)
|
||||
|
||||
6.1.9 (2023-07-06)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Rectified a regression bug that occurred when the ``-include`` flag was passed via the `build_flags <https://docs.platformio.org/en/latest/projectconf/sections/env/options/build/build_flags.html>`__ option as a relative path and subsequently expanded (`issue #4683 <https://github.com/platformio/platformio-core/issues/4683>`_)
|
||||
* Resolved an issue that resulted in unresolved absolute toolchain paths when generating the `Compilation database "compile_commands.json" <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ (`issue #4684 <https://github.com/platformio/platformio-core/issues/4684>`_)
|
||||
|
||||
6.1.8 (2023-07-05)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added a new ``--lint`` option to the `pio project config <https://docs.platformio.org/en/latest/core/userguide/project/cmd_config.html>`__ command, enabling users to efficiently perform linting on the |PIOCONF|
|
||||
* Enhanced the parsing of the |PIOCONF| to provide comprehensive diagnostic information
|
||||
* Expanded the functionality of the |LIBRARYJSON| manifest by allowing the use of the underscore symbol in the `keywords <https://docs.platformio.org/en/latest/manifests/library-json/fields/keywords.html>`__ field
|
||||
* Optimized project integration templates to address the issue of long paths on Windows (`issue #4652 <https://github.com/platformio/platformio-core/issues/4652>`_)
|
||||
* Refactored |UNITTESTING| engine to resolve compiler warnings with "-Wpedantic" option (`pull #4671 <https://github.com/platformio/platformio-core/pull/4671>`_)
|
||||
* Eliminated erroneous warning regarding the use of obsolete PlatformIO Core when downgrading to the stable version (`issue #4664 <https://github.com/platformio/platformio-core/issues/4664>`_)
|
||||
* Updated the `pio project metadata <https://docs.platformio.org/en/latest/core/userguide/project/cmd_metadata.html>`__ command to return C/C++ flags as parsed Unix shell arguments when dumping project build metadata
|
||||
* Resolved a critical issue related to the usage of the ``-include`` flag within the `build_flags <https://docs.platformio.org/en/latest/projectconf/sections/env/options/build/build_flags.html>`__ option, specifically when employing dynamic variables (`issue #4682 <https://github.com/platformio/platformio-core/issues/4682>`_)
|
||||
* Removed PlatformIO IDE for Atom from the documentation as `Atom has been deprecated <https://github.blog/2022-06-08-sunsetting-atom/>`__
|
||||
|
||||
6.1.7 (2023-05-08)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Introduced a new ``--sample-code`` option to the `pio project init <https://docs.platformio.org/en/latest/core/userguide/project/cmd_init.html>`__ command, which allows users to include sample code in the newly created project
|
||||
* Added validation for `project working environment names <https://docs.platformio.org/en/latest/projectconf/sections/env/index.html#working-env-name>`__ to ensure that they only contain lowercase letters ``a-z``, numbers ``0-9``, and special characters ``_`` (underscore) and ``-`` (hyphen)
|
||||
* Added the ability to show a detailed library dependency tree only in `verbose mode <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html#cmdoption-pio-run-v>`__, which can help you understand the relationship between libraries and troubleshoot issues more effectively (`issue #4517 <https://github.com/platformio/platformio-core/issues/4517>`_)
|
||||
* Added the ability to run only the `device monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html>`__ when using the `pio run -t monitor <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html>`__ command, saving you time and resources by skipping the build process
|
||||
* Implemented a new feature to store device monitor logs in the project's ``logs`` folder, making it easier to access and review device monitor logs for your projects (`issue #4596 <https://github.com/platformio/platformio-core/issues/4596>`_)
|
||||
* Improved support for projects located on Windows network drives, including Network Shared Folder, Dropbox, OneDrive, Google Drive, and other similar services (`issue #3417 <https://github.com/platformio/platformio-core/issues/3417>`_)
|
||||
* Improved source file filtering functionality for the `Static Code Analysis <https://docs.platformio.org/en/latest/advanced/static-code-analysis/index.html>`__ feature, making it easier to analyze only the code you need to
|
||||
* Upgraded the build engine to the latest version of SCons (4.5.2) to improve build performance, reliability, and compatibility with other tools and systems (`release notes <https://github.com/SCons/scons/releases/tag/4.5.2>`__)
|
||||
* Implemented a fix for shell injection vulnerabilities when converting INO files to CPP, ensuring your code is safe and secure (`issue #4532 <https://github.com/platformio/platformio-core/issues/4532>`_)
|
||||
* Restored the project generator for the `NetBeans IDE <https://docs.platformio.org/en/latest/integration/ide/netbeans.html>`__, providing you with more flexibility and options for your development workflow
|
||||
* Resolved installation issues with PIO Remote on Raspberry Pi and other small form-factor PCs (`issue #4425 <https://github.com/platformio/platformio-core/issues/4425>`_, `issue #4493 <https://github.com/platformio/platformio-core/issues/4493>`_, `issue #4607 <https://github.com/platformio/platformio-core/issues/4607>`_)
|
||||
* Resolved an issue where the `build_cache_dir <https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/build_cache_dir.html>`__ setting was not being recognized consistently across multiple environments (`issue #4574 <https://github.com/platformio/platformio-core/issues/4574>`_)
|
||||
* Resolved an issue where organization details could not be updated using the `pio org update <https://docs.platformio.org/en/latest/core/userguide/org/cmd_update.html>`__ command
|
||||
* Resolved an issue where the incorrect debugging environment was generated for VSCode in "Auto" mode (`issue #4597 <https://github.com/platformio/platformio-core/issues/4597>`_)
|
||||
* Resolved an issue where native tests would fail if a custom program name was specified (`issue #4546 <https://github.com/platformio/platformio-core/issues/4546>`_)
|
||||
* Resolved an issue where the PlatformIO |DEBUGGING| solution was not escaping the tool installation process into MI2 correctly (`issue #4565 <https://github.com/platformio/platformio-core/issues/4565>`_)
|
||||
* Resolved an issue where multiple targets were not executed sequentially (`issue #4604 <https://github.com/platformio/platformio-core/issues/4604>`_)
|
||||
* Resolved an issue where upgrading PlatformIO Core fails on Windows with Python 3.11 (`issue #4540 <https://github.com/platformio/platformio-core/issues/4540>`_)
|
||||
|
||||
6.1.6 (2023-01-23)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added support for Python 3.11
|
||||
* Added a new `name <https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/generic/description.html>`__ configuration option to customize a project name (`pull #4498 <https://github.com/platformio/platformio-core/pull/4498>`_)
|
||||
* Made assets (templates, ``99-platformio-udev.rules``) part of Python's module (`issue #4458 <https://github.com/platformio/platformio-core/issues/4458>`_)
|
||||
* Updated `Clang-Tidy <https://docs.platformio.org/en/latest/plus/check-tools/clang-tidy.html>`__ check tool to v15.0.5 with new diagnostics and bugfixes
|
||||
* Removed dependency on the "zeroconf" package and install it only when a user lists mDNS devices (issue with zeroconf's LGPL license)
|
||||
* Show the real error message instead of "Can not remove temporary directory" when |PIOCONF| is broken (`issue #4480 <https://github.com/platformio/platformio-core/issues/4480>`_)
|
||||
* Fixed an issue with an incorrect test summary when a testcase name includes a colon (`issue #4508 <https://github.com/platformio/platformio-core/issues/4508>`_)
|
||||
* Fixed an issue when `extends <https://docs.platformio.org/en/latest/projectconf/sections/env/options/advanced/extends.html>`__ did not override options in the right order (`issue #4462 <https://github.com/platformio/platformio-core/issues/4462>`_)
|
||||
* Fixed an issue when `pio pkg list <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_list.html>`__ and `pio pkg uninstall <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_uninstall.html>`__ commands fail if there are circular dependencies in the |LIBRARYJSON| manifests (`issue #4475 <https://github.com/platformio/platformio-core/issues/4475>`_)
|
||||
|
||||
6.1.5 (2022-11-01)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added a new `enable_proxy_strict_ssl <https://docs.platformio.org/en/latest/core/userguide/cmd_settings.html>`__ setting to disable the proxy server certificate verification (`issue #4432 <https://github.com/platformio/platformio-core/issues/4432>`_)
|
||||
* Documented `PlatformIO Core Proxy Configuration <https://docs.platformio.org/en/latest/core/installation/proxy-configuration.html>`__
|
||||
* Speeded up device port finder by avoiding loading board HWIDs from development platforms
|
||||
* Improved caching of build metadata in debug mode
|
||||
* Fixed an issue when `pio pkg install --storage-dir <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html>`__ command requires PlatformIO project (`issue #4410 <https://github.com/platformio/platformio-core/issues/4410>`_)
|
||||
|
||||
6.1.4 (2022-08-12)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added support for accepting the original FileNode environment in a "callback" function when using `Build Middlewares <https://docs.platformio.org/en/latest/scripting/middlewares.html>`__ (`pull #4380 <https://github.com/platformio/platformio-core/pull/4380>`_)
|
||||
* Improved device port finder when using dual channel UART converter (`issue #4367 <https://github.com/platformio/platformio-core/issues/4367>`_)
|
||||
* Improved project dependency resolving when using the `pio project init --ide <https://docs.platformio.org/en/latest/core/userguide/project/cmd_init.html>`__ command
|
||||
* Upgraded build engine to the SCons 4.4.0 (`release notes <https://github.com/SCons/scons/releases/tag/4.4.0>`__)
|
||||
* Keep custom "unwantedRecommendations" when generating projects for VSCode (`issue #4383 <https://github.com/platformio/platformio-core/issues/4383>`_)
|
||||
* Do not resolve project dependencies for the ``cleanall`` target (`issue #4344 <https://github.com/platformio/platformio-core/issues/4344>`_)
|
||||
* Warn about calling "env.BuildSources" in a POST-type script (`issue #4385 <https://github.com/platformio/platformio-core/issues/4385>`_)
|
||||
* Fixed an issue when escaping macros/defines for IDE integration (`issue #4360 <https://github.com/platformio/platformio-core/issues/4360>`_)
|
||||
* Fixed an issue when the "cleanall" target removes dependencies from all working environments (`issue #4386 <https://github.com/platformio/platformio-core/issues/4386>`_)
|
||||
|
||||
6.1.3 (2022-07-18)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fixed a regression bug when opening device monitor without any filters (`issue #4363 <https://github.com/platformio/platformio-core/issues/4363>`_)
|
||||
|
||||
6.1.2 (2022-07-18)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Export a ``PIO_UNIT_TESTING`` macro to the project source files and dependent libraries in the |UNITTESTING| mode
|
||||
* Improved detection of Windows architecture (`issue #4353 <https://github.com/platformio/platformio-core/issues/4353>`_)
|
||||
* Warn about unknown `device monitor filters <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#filters>`__ (`issue #4362 <https://github.com/platformio/platformio-core/issues/4362>`_)
|
||||
* Fixed a regression bug when `libArchive <https://docs.platformio.org/en/latest/manifests/library-json/fields/build/libarchive.html>`__ option declared in the |LIBRARYJSON| manifest was ignored (`issue #4351 <https://github.com/platformio/platformio-core/issues/4351>`_)
|
||||
* Fixed an issue when the `pio pkg publish <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_publish.html>`__ command didn't work with Python 3.6 (`issue #4352 <https://github.com/platformio/platformio-core/issues/4352>`_)
|
||||
|
||||
6.1.1 (2022-07-11)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added new ``monitor_encoding`` project configuration option to configure `Device Monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html>`__ (`issue #4350 <https://github.com/platformio/platformio-core/issues/4350>`_)
|
||||
* Allowed specifying project environments for `pio ci <https://docs.platformio.org/en/latest/core/userguide/cmd_ci.html>`__ command (`issue #4347 <https://github.com/platformio/platformio-core/issues/4347>`_)
|
||||
* Show "TimeoutError" only in the verbose mode when can not find a serial port
|
||||
* Fixed an issue when a serial port was not automatically detected if the board has predefined HWIDs
|
||||
* Fixed an issue with endless scanning of project dependencies (`issue #4349 <https://github.com/platformio/platformio-core/issues/4349>`_)
|
||||
* Fixed an issue with |LDF| when incompatible libraries were used for the working project environment with the missed framework (`pull #4346 <https://github.com/platformio/platformio-core/pull/4346>`_)
|
||||
|
||||
6.1.0 (2022-07-06)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* **Device Manager**
|
||||
|
||||
- Automatically reconnect device monitor if a connection fails
|
||||
- Added new `pio device monitor --no-reconnect <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#cmdoption-pio-device-monitor-no-reconnect>`__ option to disable automatic reconnection
|
||||
- Handle device monitor disconnects more gracefully (`issue #3939 <https://github.com/platformio/platformio-core/issues/3939>`_)
|
||||
- Improved a serial port finder for `Black Magic Probe <https://docs.platformio.org/en/latest/plus/debug-tools/blackmagic.html>`__ (`issue #4023 <https://github.com/platformio/platformio-core/issues/4023>`_)
|
||||
- Improved a serial port finder for a board with predefined HWIDs
|
||||
- Replaced ``monitor_flags`` with independent project configuration options: `monitor_parity <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-parity>`__, `monitor_eol <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-eol>`__, `monitor_raw <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-raw>`__, `monitor_echo <https://docs.platformio.org/en/latest/projectconf/section_env_monitor.html#monitor-echo>`__
|
||||
- Fixed an issue when the monitor filters were not applied in their order (`issue #4320 <https://github.com/platformio/platformio-core/issues/4320>`_)
|
||||
|
||||
* **Unit Testing**
|
||||
|
||||
- Updated "Getting Started" documentation for `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework
|
||||
- Export |UNITTESTING| flags only to the project build environment (``projenv``, files in "src" folder)
|
||||
- Merged the "building" stage with "uploading" for the embedded target (`issue #4307 <https://github.com/platformio/platformio-core/issues/4307>`_)
|
||||
- Do not resolve dependencies from the project "src" folder when the `test_build_src <https://docs.platformio.org/en/latest//projectconf/section_env_test.html#test-build-src>`__ option is not enabled
|
||||
- Do not immediately terminate a testing program when results are received
|
||||
- Fixed an issue when a custom `pio test --project-config <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-c>`__ was not handled properly (`issue #4299 <https://github.com/platformio/platformio-core/issues/4299>`_)
|
||||
- Fixed an issue when testing results were wrong in the verbose mode (`issue #4336 <https://github.com/platformio/platformio-core/issues/4336>`_)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Significantly improved support for `Pre & Post Actions <https://docs.platformio.org/en/latest/scripting/actions.html>`__
|
||||
|
||||
* Allowed to declare actions in the `PRE-type scripts <https://docs.platformio.org/en/latest/scripting/launch_types.html>`__ even if the target is not ready yet
|
||||
* Allowed library maintainers to use Pre & Post Actions in the library `extraScript <https://docs.platformio.org/en/latest/manifests/library-json/fields/build/extrascript.html>`__
|
||||
|
||||
- Documented `Stringification <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#stringification>`__ – converting a macro argument into a string constant (`issue #4310 <https://github.com/platformio/platformio-core/issues/4310>`_)
|
||||
- Added new `pio run --monitor-port <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html#cmdoption-pio-run-monitor-port>`__ option to specify custom device monitor port to the ``monitor`` target (`issue #4337 <https://github.com/platformio/platformio-core/issues/4337>`_)
|
||||
- Added ``env.StringifyMacro(value)`` helper function for the `Advanced Scripting <https://docs.platformio.org/en/latest/scripting/index.html>`__
|
||||
- Allowed to ``Import("projenv")`` in a library extra script (`issue #4305 <https://github.com/platformio/platformio-core/issues/4305>`_)
|
||||
- Fixed an issue when the `build_unflags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-unflags>`__ operation ignores a flag value (`issue #4309 <https://github.com/platformio/platformio-core/issues/4309>`_)
|
||||
- Fixed an issue when the `build_unflags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-unflags>`__ option was not applied to the ``ASPPFLAGS`` scope
|
||||
- Fixed an issue on Windows OS when flags were wrapped to the temporary file while generating the `Compilation database "compile_commands.json" <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__
|
||||
- Fixed an issue with the |LDF| when recursively scanning dependencies in the ``chain`` mode
|
||||
- Fixed a "PermissionError" on Windows when running "clean" or "cleanall" targets (`issue #4331 <https://github.com/platformio/platformio-core/issues/4331>`_)
|
||||
|
||||
* **Package Management**
|
||||
|
||||
- Fixed an issue when library dependencies were installed for the incompatible project environment (`issue #4338 <https://github.com/platformio/platformio-core/issues/4338>`_)
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Warn about incompatible Bash version for the `Shell Completion <https://docs.platformio.org/en/latest/core/userguide/system/completion/index.html>`__ (`issue #4326 <https://github.com/platformio/platformio-core/issues/4326>`_)
|
||||
|
||||
6.0.2 (2022-06-01)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Control |UNITTESTING| verbosity with a new multilevel `pio test -v <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-v>`__ command option (`issue #4276 <https://github.com/platformio/platformio-core/issues/4276>`_)
|
||||
* Follow symbolic links during searching for the unit test suites (`issue #4288 <https://github.com/platformio/platformio-core/issues/4288>`_)
|
||||
* Show a warning when testing an empty project without a test suite (`issue #4278 <https://github.com/platformio/platformio-core/issues/4278>`_)
|
||||
* Improved support for `Asking for input (prompts) <https://docs.platformio.org/en/latest/scripting/examples/asking_for_input.html>`_
|
||||
* Fixed an issue when the `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__ option was applied outside the project scope (`issue #4277 <https://github.com/platformio/platformio-core/issues/4277>`_)
|
||||
* Fixed an issue with debugging assembly files without preprocessor (".s")
|
||||
|
||||
6.0.1 (2022-05-17)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Improved support for the renamed configuration options (`issue #4270 <https://github.com/platformio/platformio-core/issues/4270>`_)
|
||||
* Fixed an issue when calling the built-in `pio device monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html#filters>`__ filters
|
||||
* Fixed an issue when using |INTERPOLATION| and merging str+int options (`issue #4271 <https://github.com/platformio/platformio-core/issues/4271>`_)
|
||||
|
||||
6.0.0 (2022-05-16)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please check the `Migration guide from 5.x to 6.0 <https://docs.platformio.org/en/latest/core/migration.html>`__.
|
||||
|
||||
* **Package Management**
|
||||
|
||||
- New unified Package Management CLI (``pio pkg``):
|
||||
|
||||
* `pio pkg exec <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_exec.html>`_ - run command from package tool (`issue #4163 <https://github.com/platformio/platformio-core/issues/4163>`_)
|
||||
* `pio pkg install <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html>`_ - install the project dependencies or custom packages
|
||||
* `pio pkg list <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_list.html>`__ - list installed packages
|
||||
* `pio pkg outdated <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_outdated.html>`__ - check for project outdated packages
|
||||
* `pio pkg search <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_search.html>`__ - search for packages
|
||||
* `pio pkg show <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_show.html>`__ - show package information
|
||||
* `pio pkg uninstall <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_uninstall.html>`_ - uninstall the project dependencies or custom packages
|
||||
* `pio pkg update <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_update.html>`__ - update the project dependencies or custom packages
|
||||
|
||||
- Package Manifest
|
||||
|
||||
* Added support for `"scripts" <https://docs.platformio.org/en/latest/librarymanager/config.html#scripts>`__ (`issue #485 <https://github.com/platformio/platformio-core/issues/485>`_)
|
||||
* Added support for `multi-licensed <https://docs.platformio.org/en/latest/librarymanager/config.html#license>`__ packages using SPDX Expressions (`issue #4037 <https://github.com/platformio/platformio-core/issues/4037>`_)
|
||||
* Added support for `"dependencies" <https://docs.platformio.org/en/latest/librarymanager/config.html#dependencies>`__ declared in a "tool" package manifest
|
||||
|
||||
- Added support for `symbolic links <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html#local-folder>`__ allowing pointing the local source folder to the Package Manager (`issue #3348 <https://github.com/platformio/platformio-core/issues/3348>`_)
|
||||
- Automatically install dependencies of the local (private) project libraries (`issue #2910 <https://github.com/platformio/platformio-core/issues/2910>`_)
|
||||
- Improved detection of a package type from the tarball archive (`issue #3828 <https://github.com/platformio/platformio-core/issues/3828>`_)
|
||||
- Ignore files according to the patterns declared in ".gitignore" when using the `pio package pack <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_pack.html>`__ command (`issue #4188 <https://github.com/platformio/platformio-core/issues/4188>`_)
|
||||
- Dropped automatic updates of global libraries and development platforms (`issue #4179 <https://github.com/platformio/platformio-core/issues/4179>`_)
|
||||
- Dropped support for the "pythonPackages" field in "platform.json" manifest in favor of `Extra Python Dependencies <https://docs.platformio.org/en/latest/scripting/examples/extra_python_packages.html>`__
|
||||
- Fixed an issue when manually removed dependencies from the |PIOCONF| were not uninstalled from the storage (`issue #3076 <https://github.com/platformio/platformio-core/issues/3076>`_)
|
||||
|
||||
* **Unit Testing**
|
||||
|
||||
- Refactored from scratch |UNITTESTING| solution and its documentation
|
||||
- New: `Test Hierarchy <https://docs.platformio.org/en/latest/advanced/unit-testing/structure.html>`_ (`issue #4135 <https://github.com/platformio/platformio-core/issues/4135>`_)
|
||||
- New: `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework (`issue #4240 <https://github.com/platformio/platformio-core/issues/4240>`_)
|
||||
- New: `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework (`issue #3572 <https://github.com/platformio/platformio-core/issues/3572>`_)
|
||||
- New: `Semihosting <https://docs.platformio.org/en/latest/advanced/unit-testing/semihosting.html>`__ (`issue #3516 <https://github.com/platformio/platformio-core/issues/3516>`_)
|
||||
- New: Hardware `Simulators <https://docs.platformio.org/en/latest/advanced/unit-testing/simulators/index.html>`__ for Unit Testing (QEMU, Renode, SimAVR, and custom solutions)
|
||||
- New: ``test`` `build configuration <https://docs.platformio.org/en/latest/projectconf/build_configurations.html>`__
|
||||
- Added support for a `custom testing framework <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/custom/index.html>`_
|
||||
- Added support for a custom `testing command <https://docs.platformio.org/en/latest/projectconf/section_env_test.html#test-testing-command>`__
|
||||
- Added support for a `custom Unity library <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/custom/examples/custom_unity_library.html>`__ (`issue #3980 <https://github.com/platformio/platformio-core/issues/3980>`_)
|
||||
- Added support for the ``socket://`` and ``rfc2217://`` protocols using `test_port <https://docs.platformio.org/en/latest/projectconf/section_env_test.html#test-port>`__ option (`issue #4229 <https://github.com/platformio/platformio-core/issues/4229>`_)
|
||||
- List available project tests with a new `pio test --list-tests <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-list-tests>`__ option
|
||||
- Pass extra arguments to the testing program with a new `pio test --program-arg <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html#cmdoption-pio-test-a>`__ option (`issue #3132 <https://github.com/platformio/platformio-core/issues/3132>`_)
|
||||
- Generate reports in JUnit and JSON formats using the `pio test <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html>`__ command (`issue #2891 <https://github.com/platformio/platformio-core/issues/2891>`_)
|
||||
- Provide more information when the native program crashed on a host (errored with a non-zero return code) (`issue #3429 <https://github.com/platformio/platformio-core/issues/3429>`_)
|
||||
- Improved automatic detection of a testing serial port (`issue #4076 <https://github.com/platformio/platformio-core/issues/4076>`_)
|
||||
- Fixed an issue when command line parameters (``--ignore``, ``--filter``) do not override values defined in the |PIOCONF| (`issue #3845 <https://github.com/platformio/platformio-core/issues/3845>`_)
|
||||
- Renamed the "test_build_project_src" project configuration option to the `test_build_src <https://docs.platformio.org/en/latest//projectconf/section_env_test.html#test-build-src>`__
|
||||
- Removed the "test_transport" option in favor of the `Custom "unity_config.h" <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/unity.html>`_
|
||||
|
||||
* **Static Code Analysis**
|
||||
|
||||
- Updated analysis tools:
|
||||
|
||||
* `Cppcheck <https://docs.platformio.org/en/latest/plus/check-tools/cppcheck.html>`__ v2.7 with various checker improvements and fixed false positives
|
||||
* `PVS-Studio <https://docs.platformio.org/en/latest/plus/check-tools/pvs-studio.html>`__ v7.18 with improved and updated semantic analysis system
|
||||
|
||||
- Added support for the custom `Clang-Tidy <https://docs.platformio.org/en/latest/plus/check-tools/clang-tidy.html>`__ configuration file (`issue #4186 <https://github.com/platformio/platformio-core/issues/4186>`_)
|
||||
- Added ability to override a tool version using the `platform_packages <https://docs.platformio.org/en/latest/projectconf/section_env_platform.html#platform-packages>`__ option (`issue #3798 <https://github.com/platformio/platformio-core/issues/3798>`_)
|
||||
- Fixed an issue with improper handling of defects that don't specify a source file (`issue #4237 <https://github.com/platformio/platformio-core/issues/4237>`_)
|
||||
|
||||
* **Build System**
|
||||
|
||||
- Show project dependency licenses when building in the verbose mode
|
||||
- Fixed an issue when |LDF| ignores the project `lib_deps <https://docs.platformio.org/en/latest/projectconf/section_env_library.html#lib-deps>`__ while resolving library dependencies (`issue #3598 <https://github.com/platformio/platformio-core/issues/3598>`_)
|
||||
- Fixed an issue with calling an extra script located outside a project (`issue #4220 <https://github.com/platformio/platformio-core/issues/4220>`_)
|
||||
- Fixed an issue when GCC preprocessor was applied to the ".s" assembly files on case-sensitive OS such as Window OS (`issue #3917 <https://github.com/platformio/platformio-core/issues/3917>`_)
|
||||
- Fixed an issue when |LDF| ignores `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__ in the "deep+" mode (`issue #4253 <https://github.com/platformio/platformio-core/issues/4253>`_)
|
||||
|
||||
* **Integration**
|
||||
|
||||
- Added a new build variable (``COMPILATIONDB_INCLUDE_TOOLCHAIN``) to include toolchain paths in the compilation database (`issue #3735 <https://github.com/platformio/platformio-core/issues/3735>`_)
|
||||
- Changed a default path for compilation database `compile_commands.json <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ to the project root
|
||||
- Enhanced integration for Qt Creator (`issue #3046 <https://github.com/platformio/platformio-core/issues/3046>`_)
|
||||
|
||||
* **Project Configuration**
|
||||
|
||||
- Extended |INTERPOLATION| with ``${this}`` pattern (`issue #3953 <https://github.com/platformio/platformio-core/issues/3953>`_)
|
||||
- Embed environment name of the current section in the |PIOCONF| using ``${this.__env__}`` pattern
|
||||
- Renamed the "src_build_flags" project configuration option to the `build_src_flags <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-flags>`__
|
||||
- Renamed the "src_filter" project configuration option to the `build_src_filter <https://docs.platformio.org/en/latest/projectconf/section_env_build.html#build-src-filter>`__
|
||||
|
||||
* **Miscellaneous**
|
||||
|
||||
- Pass extra arguments to the `native <https://docs.platformio.org/en/latest/platforms/native.html>`__ program with a new `pio run --program-arg <https://docs.platformio.org/en/latest/core/userguide/cmd_run.html#cmdoption-pio-run-a>`__ option (`issue #4246 <https://github.com/platformio/platformio-core/issues/4246>`_)
|
||||
- Improved PIO Remote setup on credit-card sized computers (Raspberry Pi, BeagleBon, etc) (`issue #3865 <https://github.com/platformio/platformio-core/issues/3865>`_)
|
||||
- Finally removed all tracks to the Python 2.7, the Python 3.6 is the minimum supported version.
|
||||
|
||||
.. _release_notes_5:
|
||||
|
||||
PlatformIO Core 5
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 5.0 history <https://github.com/platformio/platformio-core/blob/v5.2.5/HISTORY.rst>`__.
|
||||
|
||||
.. _release_notes_4:
|
||||
|
||||
PlatformIO Core 4
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 4.0 history <https://github.com/platformio/platformio-core/blob/v4.3.4/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 3
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 3.0 history <https://github.com/platformio/platformio-core/blob/v3.6.7/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 2
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 2.0 history <https://github.com/platformio/platformio-core/blob/v2.11.2/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core 1
|
||||
-----------------
|
||||
|
||||
See `PlatformIO Core 1.0 history <https://github.com/platformio/platformio-core/blob/v1.5.0/HISTORY.rst>`__.
|
||||
|
||||
PlatformIO Core Preview
|
||||
-----------------------
|
||||
|
||||
See `PlatformIO Core Preview history <https://github.com/platformio/platformio-core/blob/v0.10.2/HISTORY.rst>`__.
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
lint:
|
||||
pylint --rcfile=./.pylintrc ./tests
|
||||
pylint --rcfile=./.pylintrc ./platformio
|
||||
|
||||
isort:
|
||||
isort ./platformio
|
||||
isort ./tests
|
||||
|
||||
format:
|
||||
black ./platformio
|
||||
black ./tests
|
||||
|
||||
test:
|
||||
py.test --verbose --exitfirst -n 6 --dist=loadscope tests --ignore tests/test_examples.py
|
||||
|
||||
before-commit: isort format lint
|
||||
|
||||
clean-docs:
|
||||
rm -rf docs/_build
|
||||
|
||||
clean: clean-docs
|
||||
find . -name \*.pyc -delete
|
||||
find . -name __pycache__ -delete
|
||||
rm -rf .cache
|
||||
rm -rf build
|
||||
rm -rf htmlcov
|
||||
rm -f .coverage
|
||||
|
||||
profile:
|
||||
# Usage $ > make PIOARGS="boards" profile
|
||||
python -m cProfile -o .tox/.tmp/cprofile.prof -m platformio ${PIOARGS}
|
||||
snakeviz .tox/.tmp/cprofile.prof
|
||||
|
||||
pack:
|
||||
python setup.py sdist
|
||||
|
||||
publish:
|
||||
python setup.py sdist upload
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
PlatformIO Core
|
||||
===============
|
||||
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Core/badge.svg
|
||||
:target: https://docs.platformio.org/en/latest/core/index.html
|
||||
:alt: CI Build for PlatformIO Core
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Docs/badge.svg
|
||||
:target: https://docs.platformio.org?utm_source=github&utm_medium=core
|
||||
:alt: CI Build for Docs
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg
|
||||
:target: https://github.com/platformio/platformio-examples
|
||||
:alt: CI Build for dev-platform examples
|
||||
.. image:: https://github.com/platformio/platformio-core/workflows/Projects/badge.svg
|
||||
:target: https://docs.platformio.org/en/latest/tutorials/index.html#projects
|
||||
:alt: CI Build for the Community Projects
|
||||
.. image:: https://img.shields.io/pypi/v/platformio.svg
|
||||
:target: https://pypi.python.org/pypi/platformio/
|
||||
:alt: Latest Version
|
||||
.. image:: https://img.shields.io/badge/PlatformIO-Labs-orange.svg
|
||||
:alt: PlatformIO Labs
|
||||
:target: https://piolabs.com/?utm_source=github&utm_medium=core
|
||||
|
||||
**Quick Links:** `Homepage <https://platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_ |
|
||||
`Registry <https://registry.platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`Project Examples <https://github.com/platformio/platformio-examples/>`__ |
|
||||
`Docs <https://docs.platformio.org?utm_source=github&utm_medium=core>`_ |
|
||||
`Donate <https://platformio.org/donate?utm_source=github&utm_medium=core>`_ |
|
||||
`Contact Us <https://piolabs.com/?utm_source=github&utm_medium=core>`_
|
||||
|
||||
**Social:** `LinkedIn <https://www.linkedin.com/company/platformio/>`_ |
|
||||
`Twitter <https://twitter.com/PlatformIO_Org>`_ |
|
||||
`Facebook <https://www.facebook.com/platformio>`_ |
|
||||
`Community Forums <https://community.platformio.org?utm_source=github&utm_medium=core>`_
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-ide-laptop.png
|
||||
:target: https://platformio.org?utm_source=github&utm_medium=core
|
||||
|
||||
`PlatformIO <https://platformio.org>`_: Your Gateway to Embedded Software Development Excellence.
|
||||
|
||||
Unlock the true potential of embedded software development with
|
||||
PlatformIO's collaborative ecosystem, embracing declarative principles,
|
||||
test-driven methodologies, and modern toolchains for unrivaled success.
|
||||
|
||||
* Open source, maximum permissive Apache 2.0 license
|
||||
* Cross-platform IDE and Unified Debugger
|
||||
* Static Code Analyzer and Remote Unit Testing
|
||||
* Multi-platform and Multi-architecture Build System
|
||||
* Firmware File Explorer and Memory Inspection
|
||||
|
||||
Get Started
|
||||
-----------
|
||||
|
||||
* `What is PlatformIO? <https://docs.platformio.org/en/latest/what-is-platformio.html?utm_source=github&utm_medium=core>`_
|
||||
* `PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_
|
||||
* `PlatformIO Core (CLI) <https://docs.platformio.org/en/latest/core.html?utm_source=github&utm_medium=core>`_
|
||||
* `Project Examples <https://github.com/platformio/platformio-examples?utm_source=github&utm_medium=core>`__
|
||||
|
||||
Solutions
|
||||
---------
|
||||
|
||||
* `Library Management <https://docs.platformio.org/en/latest/librarymanager/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Desktop IDEs Integration <https://docs.platformio.org/en/latest/ide.html?utm_source=github&utm_medium=core>`_
|
||||
* `Continuous Integration <https://docs.platformio.org/en/latest/ci/index.html?utm_source=github&utm_medium=core>`_
|
||||
|
||||
**Advanced**
|
||||
|
||||
* `Debugging <https://docs.platformio.org/en/latest/plus/debugging.html?utm_source=github&utm_medium=core>`_
|
||||
* `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html?utm_source=github&utm_medium=core>`_
|
||||
* `Static Code Analysis <https://docs.platformio.org/en/latest/plus/pio-check.html?utm_source=github&utm_medium=core>`_
|
||||
* `Remote Development <https://docs.platformio.org/en/latest/plus/pio-remote.html?utm_source=github&utm_medium=core>`_
|
||||
|
||||
Registry
|
||||
--------
|
||||
|
||||
* `Libraries <https://registry.platformio.org/search?t=library&utm_source=github&utm_medium=core>`_
|
||||
* `Development Platforms <https://registry.platformio.org/search?t=platform&utm_source=github&utm_medium=core>`_
|
||||
* `Development Tools <https://registry.platformio.org/search?t=tool&utm_source=github&utm_medium=core>`_
|
||||
|
||||
Contributing
|
||||
------------
|
||||
|
||||
See `contributing guidelines <https://github.com/platformio/platformio/blob/develop/CONTRIBUTING.md>`_.
|
||||
|
||||
Telemetry / Privacy Policy
|
||||
--------------------------
|
||||
|
||||
Share minimal diagnostics and usage information to help us make PlatformIO better.
|
||||
It is enabled by default. For more information see:
|
||||
|
||||
* `Telemetry Setting <https://docs.platformio.org/en/latest/userguide/cmd_settings.html?utm_source=github&utm_medium=core#enable-telemetry>`_
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
|
||||
The PlatformIO is licensed under the permissive Apache 2.0 license,
|
||||
so you can use it in both commercial and personal projects with confidence.
|
||||
|
||||
.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg
|
||||
:target: https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md
|
||||
:alt: SWUbanner
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We are committed to ensuring the security and protection of PlatformIO Core.
|
||||
To this end, we support only the following versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 6.1.x | :white_check_mark: |
|
||||
| < 6.1 | :x: |
|
||||
|
||||
Unsupported versions of the PlatformIO Core may have known vulnerabilities or security issues that could compromise the security of our organization's systems and data.
|
||||
Therefore, it is important that all developers use only supported versions of the PlatformIO Core.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take the security of our systems and data very seriously. We encourage responsible disclosure of any vulnerabilities or security issues that you may find in our systems or applications. If you believe you have discovered a vulnerability, please report it to us immediately.
|
||||
|
||||
To report a vulnerability, please send an email to our security team at contact@piolabs.com. Please include as much information as possible, including:
|
||||
|
||||
- A description of the vulnerability and how it can be exploited
|
||||
- Steps to reproduce the vulnerability
|
||||
- Any additional information that can help us understand and reproduce the vulnerability
|
||||
|
||||
Once we receive your report, our security team will acknowledge receipt within 24 hours and will work to validate the reported vulnerability. We will provide periodic updates on the progress of the vulnerability assessment, and will notify you once a fix has been deployed.
|
||||
|
||||
If the vulnerability is accepted, we will work to remediate the issue as quickly as possible. We may also provide credit or recognition to the individual who reported the vulnerability, at our discretion.
|
||||
|
||||
If the vulnerability is declined, we will provide a justification for our decision and may offer guidance on how to improve the report or how to test the system more effectively.
|
||||
|
||||
Please note that we will not take any legal action against individuals who report vulnerabilities in good faith and in accordance with this policy.
|
||||
|
||||
Thank you for helping us keep our systems and data secure.
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
VERSION = (6, 1, 11)
|
||||
__version__ = ".".join([str(s) for s in VERSION])
|
||||
|
||||
__title__ = "platformio"
|
||||
__description__ = (
|
||||
"Your Gateway to Embedded Software Development Excellence. "
|
||||
"Unlock the true potential of embedded software development "
|
||||
"with PlatformIO's collaborative ecosystem, embracing "
|
||||
"declarative principles, test-driven methodologies, and "
|
||||
"modern toolchains for unrivaled success."
|
||||
)
|
||||
__url__ = "https://platformio.org"
|
||||
|
||||
__author__ = "PlatformIO Labs"
|
||||
__email__ = "contact@piolabs.com"
|
||||
|
||||
__license__ = "Apache Software License"
|
||||
__copyright__ = "Copyright 2014-present PlatformIO Labs"
|
||||
|
||||
__accounts_api__ = "https://api.accounts.platformio.org"
|
||||
__registry_mirror_hosts__ = [
|
||||
"registry.platformio.org",
|
||||
"registry.nm1.platformio.org",
|
||||
]
|
||||
__pioremote_endpoint__ = "ssl:host=remote.platformio.org:port=4413"
|
||||
|
||||
__core_packages__ = {
|
||||
"contrib-piohome": "~3.4.2",
|
||||
"contrib-pioremote": "~1.0.0",
|
||||
"tool-scons": "~4.40502.0",
|
||||
"tool-cppcheck": "~1.21100.0",
|
||||
"tool-clangtidy": "~1.150005.0",
|
||||
"tool-pvs-studio": "~7.18.0",
|
||||
}
|
||||
|
||||
__check_internet_hosts__ = [
|
||||
"185.199.110.153", # Github.com
|
||||
"88.198.170.159", # platformio.org
|
||||
"github.com",
|
||||
] + __registry_mirror_hosts__
|
||||
|
||||
__install_requires__ = [
|
||||
# Core requirements
|
||||
"bottle == 0.12.*",
|
||||
"click >=8.0.4, <=8.2",
|
||||
"colorama",
|
||||
"marshmallow == 3.*",
|
||||
"pyelftools == 0.29",
|
||||
"pyserial == 3.5.*", # keep in sync "device/monitor/terminal.py"
|
||||
"requests == 2.*",
|
||||
"semantic_version == 2.10.*",
|
||||
"tabulate == 0.*",
|
||||
] + [
|
||||
# PIO Home requirements
|
||||
"ajsonrpc == 1.2.*",
|
||||
"starlette >=0.19, <0.32",
|
||||
"uvicorn >=0.16, <0.24",
|
||||
"wsproto == 1.*",
|
||||
]
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import click
|
||||
|
||||
from platformio import __version__, exception, maintenance
|
||||
from platformio.cli import PlatformioCLI
|
||||
from platformio.compat import IS_CYGWIN, ensure_python3
|
||||
|
||||
|
||||
@click.command(
|
||||
cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"])
|
||||
)
|
||||
@click.version_option(__version__, prog_name="PlatformIO Core")
|
||||
@click.option("--force", "-f", is_flag=True, help="DEPRECATED", hidden=True)
|
||||
@click.option("--caller", "-c", help="Caller ID (service)")
|
||||
@click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters")
|
||||
@click.pass_context
|
||||
def cli(ctx, force, caller, no_ansi): # pylint: disable=unused-argument
|
||||
try:
|
||||
if (
|
||||
no_ansi
|
||||
or str(
|
||||
os.getenv("PLATFORMIO_NO_ANSI", os.getenv("PLATFORMIO_DISABLE_COLOR"))
|
||||
).lower()
|
||||
== "true"
|
||||
):
|
||||
# pylint: disable=protected-access
|
||||
click._compat.isatty = lambda stream: False
|
||||
elif (
|
||||
str(
|
||||
os.getenv("PLATFORMIO_FORCE_ANSI", os.getenv("PLATFORMIO_FORCE_COLOR"))
|
||||
).lower()
|
||||
== "true"
|
||||
):
|
||||
# pylint: disable=protected-access
|
||||
click._compat.isatty = lambda stream: True
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
maintenance.on_cmd_start(ctx, caller)
|
||||
|
||||
|
||||
@cli.result_callback()
|
||||
@click.pass_context
|
||||
def process_result(*_, **__):
|
||||
maintenance.on_cmd_end()
|
||||
|
||||
|
||||
def configure():
|
||||
if IS_CYGWIN:
|
||||
raise exception.CygwinEnvDetected()
|
||||
|
||||
# https://urllib3.readthedocs.org
|
||||
# /en/latest/security.html#insecureplatformwarning
|
||||
try:
|
||||
import urllib3 # pylint: disable=import-outside-toplevel
|
||||
|
||||
urllib3.disable_warnings()
|
||||
except (AttributeError, ImportError):
|
||||
pass
|
||||
|
||||
# Handle IOError issue with VSCode's Terminal (Windows)
|
||||
click_echo_origin = [click.echo, click.secho]
|
||||
|
||||
def _safe_echo(origin, *args, **kwargs):
|
||||
try:
|
||||
click_echo_origin[origin](*args, **kwargs)
|
||||
except IOError:
|
||||
(sys.stderr.write if kwargs.get("err") else sys.stdout.write)(
|
||||
"%s\n" % (args[0] if args else "")
|
||||
)
|
||||
|
||||
click.echo = lambda *args, **kwargs: _safe_echo(0, *args, **kwargs)
|
||||
click.secho = lambda *args, **kwargs: _safe_echo(1, *args, **kwargs)
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
exit_code = 0
|
||||
prev_sys_argv = sys.argv[:]
|
||||
if argv:
|
||||
assert isinstance(argv, list)
|
||||
sys.argv = argv
|
||||
|
||||
try:
|
||||
ensure_python3(raise_exception=True)
|
||||
configure()
|
||||
cli() # pylint: disable=no-value-for-parameter
|
||||
except SystemExit as exc:
|
||||
if exc.code and str(exc.code).isdigit():
|
||||
exit_code = int(exc.code)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
if not isinstance(exc, exception.ReturnErrorCode):
|
||||
maintenance.on_platformio_exception(exc)
|
||||
error_str = f"{exc.__class__.__name__}: "
|
||||
if isinstance(exc, exception.PlatformioException):
|
||||
error_str += str(exc)
|
||||
else:
|
||||
error_str += traceback.format_exc()
|
||||
error_str += """
|
||||
============================================================
|
||||
|
||||
An unexpected error occurred. Further steps:
|
||||
|
||||
* Verify that you have the latest version of PlatformIO using
|
||||
`python -m pip install -U platformio` command
|
||||
|
||||
* Try to find answer in FAQ Troubleshooting section
|
||||
https://docs.platformio.org/page/faq/index.html
|
||||
|
||||
* Report this problem to the developers
|
||||
https://github.com/platformio/platformio-core/issues
|
||||
|
||||
============================================================
|
||||
"""
|
||||
click.secho(error_str, fg="red", err=True)
|
||||
exit_code = int(str(exc)) if str(exc).isdigit() else 1
|
||||
|
||||
maintenance.on_platformio_exit()
|
||||
sys.argv = prev_sys_argv
|
||||
return exit_code
|
||||
|
||||
|
||||
def debug_gdb_main():
|
||||
return main([sys.argv[0], "debug", "--interface", "gdb"] + sys.argv[1:])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.commands.destroy import account_destroy_cmd
|
||||
from platformio.account.commands.forgot import account_forgot_cmd
|
||||
from platformio.account.commands.login import account_login_cmd
|
||||
from platformio.account.commands.logout import account_logout_cmd
|
||||
from platformio.account.commands.password import account_password_cmd
|
||||
from platformio.account.commands.register import account_register_cmd
|
||||
from platformio.account.commands.show import account_show_cmd
|
||||
from platformio.account.commands.token import account_token_cmd
|
||||
from platformio.account.commands.update import account_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"account",
|
||||
commands=[
|
||||
account_destroy_cmd,
|
||||
account_forgot_cmd,
|
||||
account_login_cmd,
|
||||
account_logout_cmd,
|
||||
account_password_cmd,
|
||||
account_register_cmd,
|
||||
account_show_cmd,
|
||||
account_token_cmd,
|
||||
account_update_cmd,
|
||||
],
|
||||
short_help="Manage PlatformIO account",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from platformio import __accounts_api__, app
|
||||
from platformio.exception import PlatformioException, UserSideException
|
||||
from platformio.http import HTTPClient, HTTPClientError
|
||||
|
||||
|
||||
class AccountError(PlatformioException):
|
||||
MESSAGE = "{0}"
|
||||
|
||||
|
||||
class AccountNotAuthorized(AccountError, UserSideException):
|
||||
MESSAGE = "You are not authorized! Please log in to PlatformIO Account."
|
||||
|
||||
|
||||
class AccountAlreadyAuthorized(AccountError, UserSideException):
|
||||
MESSAGE = "You are already authorized with {0} account."
|
||||
|
||||
|
||||
class AccountClient(HTTPClient): # pylint:disable=too-many-public-methods
|
||||
SUMMARY_CACHE_TTL = 60 * 60 * 24 * 7
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(__accounts_api__)
|
||||
|
||||
@staticmethod
|
||||
def get_refresh_token():
|
||||
try:
|
||||
return app.get_state_item("account").get("auth").get("refresh_token")
|
||||
except Exception as exc:
|
||||
raise AccountNotAuthorized() from exc
|
||||
|
||||
@staticmethod
|
||||
def delete_local_session():
|
||||
app.delete_state_item("account")
|
||||
|
||||
@staticmethod
|
||||
def delete_local_state(key):
|
||||
account = app.get_state_item("account")
|
||||
if not account or key not in account:
|
||||
return
|
||||
del account[key]
|
||||
app.set_state_item("account", account)
|
||||
|
||||
def fetch_json_data(self, *args, **kwargs):
|
||||
try:
|
||||
return super().fetch_json_data(*args, **kwargs)
|
||||
except HTTPClientError as exc:
|
||||
raise AccountError(exc) from exc
|
||||
|
||||
def fetch_authentication_token(self):
|
||||
if os.environ.get("PLATFORMIO_AUTH_TOKEN"):
|
||||
return os.environ.get("PLATFORMIO_AUTH_TOKEN")
|
||||
auth = app.get_state_item("account", {}).get("auth", {})
|
||||
if auth.get("access_token") and auth.get("access_token_expire"):
|
||||
if auth.get("access_token_expire") > time.time():
|
||||
return auth.get("access_token")
|
||||
if auth.get("refresh_token"):
|
||||
try:
|
||||
data = self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/login",
|
||||
headers={
|
||||
"Authorization": "Bearer %s" % auth.get("refresh_token")
|
||||
},
|
||||
)
|
||||
app.set_state_item("account", data)
|
||||
return data.get("auth").get("access_token")
|
||||
except AccountError:
|
||||
self.delete_local_session()
|
||||
raise AccountNotAuthorized()
|
||||
|
||||
def login(self, username, password):
|
||||
try:
|
||||
self.fetch_authentication_token()
|
||||
except: # pylint:disable=bare-except
|
||||
pass
|
||||
else:
|
||||
raise AccountAlreadyAuthorized(
|
||||
app.get_state_item("account", {}).get("email", "")
|
||||
)
|
||||
|
||||
data = self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/login",
|
||||
data={"username": username, "password": password},
|
||||
)
|
||||
app.set_state_item("account", data)
|
||||
return data
|
||||
|
||||
def login_with_code(self, client_id, code, redirect_uri):
|
||||
try:
|
||||
self.fetch_authentication_token()
|
||||
except: # pylint:disable=bare-except
|
||||
pass
|
||||
else:
|
||||
raise AccountAlreadyAuthorized(
|
||||
app.get_state_item("account", {}).get("email", "")
|
||||
)
|
||||
|
||||
result = self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/login/code",
|
||||
data={"client_id": client_id, "code": code, "redirect_uri": redirect_uri},
|
||||
)
|
||||
app.set_state_item("account", result)
|
||||
return result
|
||||
|
||||
def logout(self):
|
||||
refresh_token = self.get_refresh_token()
|
||||
self.delete_local_session()
|
||||
try:
|
||||
self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/logout",
|
||||
data={"refresh_token": refresh_token},
|
||||
)
|
||||
except AccountError:
|
||||
pass
|
||||
return True
|
||||
|
||||
def change_password(self, old_password, new_password):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/password",
|
||||
data={"old_password": old_password, "new_password": new_password},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def registration(
|
||||
self, username, email, password, firstname, lastname
|
||||
): # pylint:disable=too-many-arguments
|
||||
try:
|
||||
self.fetch_authentication_token()
|
||||
except: # pylint:disable=bare-except
|
||||
pass
|
||||
else:
|
||||
raise AccountAlreadyAuthorized(
|
||||
app.get_state_item("account", {}).get("email", "")
|
||||
)
|
||||
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/registration",
|
||||
data={
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password": password,
|
||||
"firstname": firstname,
|
||||
"lastname": lastname,
|
||||
},
|
||||
)
|
||||
|
||||
def auth_token(self, password, regenerate):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/token",
|
||||
data={"password": password, "regenerate": 1 if regenerate else 0},
|
||||
x_with_authorization=True,
|
||||
).get("auth_token")
|
||||
|
||||
def forgot_password(self, username):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/forgot",
|
||||
data={"username": username},
|
||||
)
|
||||
|
||||
def get_profile(self):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/profile",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_profile(self, profile, current_password):
|
||||
profile["current_password"] = current_password
|
||||
self.delete_local_state("summary")
|
||||
response = self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/profile",
|
||||
data=profile,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
return response
|
||||
|
||||
def get_account_info(self, offline=False):
|
||||
account = app.get_state_item("account") or {}
|
||||
if (
|
||||
account.get("summary")
|
||||
and account["summary"].get("expire_at", 0) > time.time()
|
||||
):
|
||||
return account["summary"]
|
||||
if offline and account.get("email"):
|
||||
return {
|
||||
"profile": {
|
||||
"email": account.get("email"),
|
||||
"username": account.get("username"),
|
||||
}
|
||||
}
|
||||
result = self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/summary",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
account["summary"] = dict(
|
||||
profile=result.get("profile"),
|
||||
packages=result.get("packages"),
|
||||
subscriptions=result.get("subscriptions"),
|
||||
user_id=result.get("user_id"),
|
||||
expire_at=int(time.time()) + self.SUMMARY_CACHE_TTL,
|
||||
)
|
||||
app.set_state_item("account", account)
|
||||
return result
|
||||
|
||||
def get_logged_username(self):
|
||||
return self.get_account_info(offline=True).get("profile").get("username")
|
||||
|
||||
def destroy_account(self):
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/account",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def create_org(self, orgname, email, displayname):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs",
|
||||
data={"orgname": orgname, "email": email, "displayname": displayname},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def get_org(self, orgname):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_orgs(self):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs",
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_org(self, orgname, data):
|
||||
return self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
data={k: v for k, v in data.items() if v},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def destroy_org(self, orgname):
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def add_org_owner(self, orgname, username):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_org_owners(self, orgname):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def remove_org_owner(self, orgname, username):
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/owners" % orgname,
|
||||
params={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def create_team(self, orgname, teamname, description):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/teams" % orgname,
|
||||
data={"name": teamname, "description": description},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def destroy_team(self, orgname, teamname):
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def get_team(self, orgname, teamname):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def list_teams(self, orgname):
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v1/orgs/%s/teams" % orgname,
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def update_team(self, orgname, teamname, data):
|
||||
return self.fetch_json_data(
|
||||
"put",
|
||||
"/v1/orgs/%s/teams/%s" % (orgname, teamname),
|
||||
data={k: v for k, v in data.items() if v},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def add_team_member(self, orgname, teamname, username):
|
||||
return self.fetch_json_data(
|
||||
"post",
|
||||
"/v1/orgs/%s/teams/%s/members" % (orgname, teamname),
|
||||
data={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
||||
def remove_team_member(self, orgname, teamname, username):
|
||||
return self.fetch_json_data(
|
||||
"delete",
|
||||
"/v1/orgs/%s/teams/%s/members" % (orgname, teamname),
|
||||
params={"username": username},
|
||||
x_with_authorization=True,
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient, AccountNotAuthorized
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy account")
|
||||
def account_destroy_cmd():
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the %s user account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% client.get_logged_username(),
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_account()
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
click.secho(
|
||||
"User account has been destroyed.",
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("forgot", short_help="Forgot password")
|
||||
@click.option("--username", prompt="Username or email")
|
||||
def account_forgot_cmd(username):
|
||||
client = AccountClient()
|
||||
client.forgot_password(username)
|
||||
click.secho(
|
||||
"If this account is registered, we will send the "
|
||||
"further instructions to your email.",
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("login", short_help="Log in to PlatformIO Account")
|
||||
@click.option("-u", "--username", prompt="Username or email")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
def account_login_cmd(username, password):
|
||||
client = AccountClient()
|
||||
client.login(username, password)
|
||||
click.secho("Successfully logged in!", fg="green")
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("logout", short_help="Log out of PlatformIO Account")
|
||||
def account_logout_cmd():
|
||||
client = AccountClient()
|
||||
client.logout()
|
||||
click.secho("Successfully logged out!", fg="green")
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("password", short_help="Change password")
|
||||
@click.option("--old-password", prompt=True, hide_input=True)
|
||||
@click.option("--new-password", prompt=True, hide_input=True, confirmation_prompt=True)
|
||||
def account_password_cmd(old_password, new_password):
|
||||
client = AccountClient()
|
||||
client.change_password(old_password, new_password)
|
||||
click.secho("Password successfully changed!", fg="green")
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import (
|
||||
validate_email,
|
||||
validate_password,
|
||||
validate_username,
|
||||
)
|
||||
|
||||
|
||||
@click.command("register", short_help="Create new PlatformIO Account")
|
||||
@click.option(
|
||||
"-u",
|
||||
"--username",
|
||||
prompt=True,
|
||||
callback=lambda _, __, value: validate_username(value),
|
||||
)
|
||||
@click.option(
|
||||
"-e", "--email", prompt=True, callback=lambda _, __, value: validate_email(value)
|
||||
)
|
||||
@click.option(
|
||||
"-p",
|
||||
"--password",
|
||||
prompt=True,
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
callback=lambda _, __, value: validate_password(value),
|
||||
)
|
||||
@click.option("--firstname", prompt=True)
|
||||
@click.option("--lastname", prompt=True)
|
||||
def account_register_cmd(username, email, password, firstname, lastname):
|
||||
client = AccountClient()
|
||||
client.registration(username, email, password, firstname, lastname)
|
||||
click.secho(
|
||||
"An account has been successfully created. "
|
||||
"Please check your mail to activate your account and verify your email address.",
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import util
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("show", short_help="PlatformIO Account information")
|
||||
@click.option("--offline", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_show_cmd(offline, json_output):
|
||||
client = AccountClient()
|
||||
info = client.get_account_info(offline)
|
||||
if json_output:
|
||||
click.echo(json.dumps(info))
|
||||
return
|
||||
click.echo()
|
||||
if info.get("profile"):
|
||||
print_profile(info["profile"])
|
||||
if info.get("packages"):
|
||||
print_packages(info["packages"])
|
||||
if info.get("subscriptions"):
|
||||
print_subscriptions(info["subscriptions"])
|
||||
click.echo()
|
||||
|
||||
|
||||
def print_profile(profile):
|
||||
click.secho("Profile", fg="cyan", bold=True)
|
||||
click.echo("=" * len("Profile"))
|
||||
data = []
|
||||
if profile.get("username"):
|
||||
data.append(("Username:", profile["username"]))
|
||||
if profile.get("email"):
|
||||
data.append(("Email:", profile["email"]))
|
||||
if profile.get("firstname"):
|
||||
data.append(("First name:", profile["firstname"]))
|
||||
if profile.get("lastname"):
|
||||
data.append(("Last name:", profile["lastname"]))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_packages(packages):
|
||||
click.echo()
|
||||
click.secho("Packages", fg="cyan")
|
||||
click.echo("=" * len("Packages"))
|
||||
for package in packages:
|
||||
click.echo()
|
||||
click.secho(package.get("name"), bold=True)
|
||||
click.echo("-" * len(package.get("name")))
|
||||
if package.get("description"):
|
||||
click.echo(package.get("description"))
|
||||
data = []
|
||||
expire = "-"
|
||||
if "subscription" in package:
|
||||
expire = util.parse_datetime(
|
||||
package["subscription"].get("end_at")
|
||||
or package["subscription"].get("next_bill_at")
|
||||
).strftime("%Y-%m-%d")
|
||||
data.append(("Expire:", expire))
|
||||
services = []
|
||||
for key in package:
|
||||
if not key.startswith("service."):
|
||||
continue
|
||||
if isinstance(package[key], dict):
|
||||
services.append(package[key].get("title"))
|
||||
else:
|
||||
services.append(package[key])
|
||||
if services:
|
||||
data.append(("Services:", ", ".join(services)))
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
||||
|
||||
def print_subscriptions(subscriptions):
|
||||
click.echo()
|
||||
click.secho("Subscriptions", fg="cyan")
|
||||
click.echo("=" * len("Subscriptions"))
|
||||
for subscription in subscriptions:
|
||||
click.echo()
|
||||
click.secho(subscription.get("product_name"), bold=True)
|
||||
click.echo("-" * len(subscription.get("product_name")))
|
||||
data = [("State:", subscription.get("status"))]
|
||||
begin_at = util.parse_datetime(subscription.get("begin_at")).strftime("%c")
|
||||
data.append(("Start date:", begin_at or "-"))
|
||||
end_at = subscription.get("end_at")
|
||||
if end_at:
|
||||
end_at = util.parse_datetime(subscription.get("end_at")).strftime("%c")
|
||||
data.append(("End date:", end_at or "-"))
|
||||
next_bill_at = subscription.get("next_bill_at")
|
||||
if next_bill_at:
|
||||
next_bill_at = util.parse_datetime(
|
||||
subscription.get("next_bill_at")
|
||||
).strftime("%c")
|
||||
data.append(("Next payment:", next_bill_at or "-"))
|
||||
data.append(
|
||||
("Edit:", click.style(subscription.get("update_url"), fg="blue") or "-")
|
||||
)
|
||||
data.append(
|
||||
("Cancel:", click.style(subscription.get("cancel_url"), fg="blue") or "-")
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("token", short_help="Get or regenerate Authentication Token")
|
||||
@click.option("-p", "--password", prompt=True, hide_input=True)
|
||||
@click.option("--regenerate", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def account_token_cmd(password, regenerate, json_output):
|
||||
client = AccountClient()
|
||||
auth_token = client.auth_token(password, regenerate)
|
||||
if json_output:
|
||||
click.echo(json.dumps({"status": "success", "result": auth_token}))
|
||||
return
|
||||
click.secho("Personal Authentication Token: %s" % auth_token, fg="green")
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient, AccountNotAuthorized
|
||||
from platformio.account.validate import validate_email, validate_username
|
||||
|
||||
|
||||
@click.command("update", short_help="Update profile information")
|
||||
@click.option("--current-password", prompt=True, hide_input=True)
|
||||
@click.option("--username")
|
||||
@click.option("--email")
|
||||
@click.option("--firstname")
|
||||
@click.option("--lastname")
|
||||
def account_update_cmd(current_password, **kwargs):
|
||||
client = AccountClient()
|
||||
profile = client.get_profile()
|
||||
new_profile = profile.copy()
|
||||
if not any(kwargs.values()):
|
||||
for field in profile:
|
||||
new_profile[field] = click.prompt(
|
||||
field.replace("_", " ").capitalize(), default=profile[field]
|
||||
)
|
||||
if field == "email":
|
||||
validate_email(new_profile[field])
|
||||
if field == "username":
|
||||
validate_username(new_profile[field])
|
||||
else:
|
||||
new_profile.update({key: value for key, value in kwargs.items() if value})
|
||||
client.update_profile(new_profile, current_password)
|
||||
click.secho("Profile successfully updated!", fg="green")
|
||||
username_changed = new_profile["username"] != profile["username"]
|
||||
email_changed = new_profile["email"] != profile["email"]
|
||||
if not username_changed and not email_changed:
|
||||
return None
|
||||
try:
|
||||
client.logout()
|
||||
except AccountNotAuthorized:
|
||||
pass
|
||||
if email_changed:
|
||||
click.secho(
|
||||
"Please check your mail to verify your new email address and re-login. ",
|
||||
fg="yellow",
|
||||
)
|
||||
return None
|
||||
click.secho("Please re-login.", fg="yellow")
|
||||
return None
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.org.commands.add import org_add_cmd
|
||||
from platformio.account.org.commands.create import org_create_cmd
|
||||
from platformio.account.org.commands.destroy import org_destroy_cmd
|
||||
from platformio.account.org.commands.list import org_list_cmd
|
||||
from platformio.account.org.commands.remove import org_remove_cmd
|
||||
from platformio.account.org.commands.update import org_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"account",
|
||||
commands=[
|
||||
org_add_cmd,
|
||||
org_create_cmd,
|
||||
org_destroy_cmd,
|
||||
org_list_cmd,
|
||||
org_remove_cmd,
|
||||
org_update_cmd,
|
||||
],
|
||||
short_help="Manage organizations",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("add", short_help="Add a new owner to organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_add_cmd(orgname, username):
|
||||
client = AccountClient()
|
||||
client.add_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The new owner `%s` has been successfully added to the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_email, validate_orgname
|
||||
|
||||
|
||||
@click.command("create", short_help="Create a new organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--email", callback=lambda _, __, value: validate_email(value) if value else value
|
||||
)
|
||||
@click.option(
|
||||
"--displayname",
|
||||
)
|
||||
def org_create_cmd(orgname, email, displayname):
|
||||
client = AccountClient()
|
||||
client.create_org(orgname, email, displayname)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully created." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy organization")
|
||||
@click.argument("orgname")
|
||||
def org_destroy_cmd(orgname):
|
||||
client = AccountClient()
|
||||
click.confirm(
|
||||
"Are you sure you want to delete the `%s` organization account?\n"
|
||||
"Warning! All linked data will be permanently removed and can not be restored."
|
||||
% orgname,
|
||||
abort=True,
|
||||
)
|
||||
client.destroy_org(orgname)
|
||||
return click.secho(
|
||||
"Organization `%s` has been destroyed." % orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("list", short_help="List organizations and their members")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def org_list_cmd(json_output):
|
||||
client = AccountClient()
|
||||
orgs = client.list_orgs()
|
||||
if json_output:
|
||||
return click.echo(json.dumps(orgs))
|
||||
if not orgs:
|
||||
return click.echo("You do not have any organization")
|
||||
for org in orgs:
|
||||
click.echo()
|
||||
click.secho(org.get("orgname"), fg="cyan")
|
||||
click.echo("-" * len(org.get("orgname")))
|
||||
data = []
|
||||
if org.get("displayname"):
|
||||
data.append(("Display Name:", org.get("displayname")))
|
||||
if org.get("email"):
|
||||
data.append(("Email:", org.get("email")))
|
||||
data.append(
|
||||
(
|
||||
"Owners:",
|
||||
", ".join((owner.get("username") for owner in org.get("owners"))),
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("remove", short_help="Remove an owner from organization")
|
||||
@click.argument(
|
||||
"orgname",
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def org_remove_cmd(orgname, username):
|
||||
client = AccountClient()
|
||||
client.remove_org_owner(orgname, username)
|
||||
return click.secho(
|
||||
"The `%s` owner has been successfully removed from the `%s` organization."
|
||||
% (username, orgname),
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_email, validate_orgname
|
||||
|
||||
|
||||
@click.command("update", short_help="Update organization")
|
||||
@click.argument("cur_orgname")
|
||||
@click.option(
|
||||
"--orgname",
|
||||
callback=lambda _, __, value: validate_orgname(value) if value else value,
|
||||
help="A new orgname",
|
||||
)
|
||||
@click.option(
|
||||
"--email",
|
||||
callback=lambda _, __, value: validate_email(value) if value else value,
|
||||
)
|
||||
@click.option("--displayname")
|
||||
def org_update_cmd(cur_orgname, **kwargs):
|
||||
client = AccountClient()
|
||||
org = client.get_org(cur_orgname)
|
||||
new_org = {
|
||||
key: value if value is not None else org[key] for key, value in kwargs.items()
|
||||
}
|
||||
if not any(kwargs.values()):
|
||||
for key in kwargs:
|
||||
new_org[key] = click.prompt(key.capitalize(), default=org[key])
|
||||
if key == "email":
|
||||
validate_email(new_org[key])
|
||||
if key == "orgname":
|
||||
validate_orgname(new_org[key])
|
||||
client.update_org(cur_orgname, new_org)
|
||||
return click.secho(
|
||||
"The organization `%s` has been successfully updated." % cur_orgname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.team.commands.add import team_add_cmd
|
||||
from platformio.account.team.commands.create import team_create_cmd
|
||||
from platformio.account.team.commands.destroy import team_destroy_cmd
|
||||
from platformio.account.team.commands.list import team_list_cmd
|
||||
from platformio.account.team.commands.remove import team_remove_cmd
|
||||
from platformio.account.team.commands.update import team_update_cmd
|
||||
|
||||
|
||||
@click.group(
|
||||
"team",
|
||||
commands=[
|
||||
team_add_cmd,
|
||||
team_create_cmd,
|
||||
team_destroy_cmd,
|
||||
team_list_cmd,
|
||||
team_remove_cmd,
|
||||
team_update_cmd,
|
||||
],
|
||||
short_help="Manage organization teams",
|
||||
)
|
||||
def cli():
|
||||
pass
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("add", short_help="Add a new member to team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.argument(
|
||||
"username",
|
||||
)
|
||||
def team_add_cmd(orgname_teamname, username):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.add_team_member(orgname, teamname, username)
|
||||
return click.secho(
|
||||
"The new member %s has been successfully added to the %s team."
|
||||
% (username, teamname),
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("create", short_help="Create a new team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--description",
|
||||
)
|
||||
def team_create_cmd(orgname_teamname, description):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.create_team(orgname, teamname, description)
|
||||
return click.secho(
|
||||
"The team %s has been successfully created." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("destroy", short_help="Destroy a team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
def team_destroy_cmd(orgname_teamname):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
click.confirm(
|
||||
click.style(
|
||||
"Are you sure you want to destroy the %s team?" % teamname, fg="yellow"
|
||||
),
|
||||
abort=True,
|
||||
)
|
||||
client = AccountClient()
|
||||
client.destroy_team(orgname, teamname)
|
||||
return click.secho(
|
||||
"The team %s has been successfully destroyed." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
|
||||
|
||||
@click.command("list", short_help="List teams")
|
||||
@click.argument("orgname", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def team_list_cmd(orgname, json_output):
|
||||
client = AccountClient()
|
||||
data = {}
|
||||
if not orgname:
|
||||
for item in client.list_orgs():
|
||||
teams = client.list_teams(item.get("orgname"))
|
||||
data[item.get("orgname")] = teams
|
||||
else:
|
||||
teams = client.list_teams(orgname)
|
||||
data[orgname] = teams
|
||||
if json_output:
|
||||
return click.echo(json.dumps(data[orgname] if orgname else data))
|
||||
if not any(data.values()):
|
||||
return click.secho("You do not have any teams.", fg="yellow")
|
||||
for org_name, teams in data.items():
|
||||
for team in teams:
|
||||
click.echo()
|
||||
click.secho("%s:%s" % (org_name, team.get("name")), fg="cyan")
|
||||
click.echo("-" * len("%s:%s" % (org_name, team.get("name"))))
|
||||
table_data = []
|
||||
if team.get("description"):
|
||||
table_data.append(("Description:", team.get("description")))
|
||||
table_data.append(
|
||||
(
|
||||
"Members:",
|
||||
", ".join(
|
||||
(member.get("username") for member in team.get("members"))
|
||||
)
|
||||
if team.get("members")
|
||||
else "-",
|
||||
)
|
||||
)
|
||||
click.echo(tabulate(table_data, tablefmt="plain"))
|
||||
return click.echo()
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname
|
||||
|
||||
|
||||
@click.command("remove", short_help="Remove a member from team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.argument("username")
|
||||
def team_remove_cmd(orgname_teamname, username):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
client.remove_team_member(orgname, teamname, username)
|
||||
return click.secho(
|
||||
"The %s member has been successfully removed from the %s team."
|
||||
% (username, teamname),
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
from platformio.account.client import AccountClient
|
||||
from platformio.account.validate import validate_orgname_teamname, validate_teamname
|
||||
|
||||
|
||||
@click.command("update", short_help="Update team")
|
||||
@click.argument(
|
||||
"orgname_teamname",
|
||||
metavar="ORGNAME:TEAMNAME",
|
||||
callback=lambda _, __, value: validate_orgname_teamname(value),
|
||||
)
|
||||
@click.option(
|
||||
"--name",
|
||||
callback=lambda _, __, value: validate_teamname(value) if value else value,
|
||||
help="A new team name",
|
||||
)
|
||||
@click.option(
|
||||
"--description",
|
||||
)
|
||||
def team_update_cmd(orgname_teamname, **kwargs):
|
||||
orgname, teamname = orgname_teamname.split(":", 1)
|
||||
client = AccountClient()
|
||||
team = client.get_team(orgname, teamname)
|
||||
new_team = {
|
||||
key: value if value is not None else team[key] for key, value in kwargs.items()
|
||||
}
|
||||
if not any(kwargs.values()):
|
||||
for key in kwargs:
|
||||
new_team[key] = click.prompt(key.capitalize(), default=team[key])
|
||||
if key == "name":
|
||||
validate_teamname(new_team[key])
|
||||
client.update_team(orgname, teamname, new_team)
|
||||
return click.secho(
|
||||
"The team %s has been successfully updated." % teamname,
|
||||
fg="green",
|
||||
)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def validate_username(value, field="username"):
|
||||
value = str(value).strip() if value else None
|
||||
if not value or not re.match(
|
||||
r"^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,37}$", value, flags=re.I
|
||||
):
|
||||
raise click.BadParameter(
|
||||
"Invalid %s format. "
|
||||
"%s must contain only alphanumeric characters "
|
||||
"or single hyphens, cannot begin or end with a hyphen, "
|
||||
"and must not be longer than 38 characters."
|
||||
% (field.lower(), field.capitalize())
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_orgname(value):
|
||||
return validate_username(value, "Organization name")
|
||||
|
||||
|
||||
def validate_email(value):
|
||||
value = str(value).strip() if value else None
|
||||
if not value or not re.match(
|
||||
r"^[a-z\d_\.\+\-]+@[a-z\d\-]+\.[a-z\d\-\.]+$", value, flags=re.I
|
||||
):
|
||||
raise click.BadParameter("Invalid email address")
|
||||
return value
|
||||
|
||||
|
||||
def validate_password(value):
|
||||
value = str(value).strip() if value else None
|
||||
if not value or not re.match(r"^(?=.*[a-z])(?=.*\d).{8,}$", value):
|
||||
raise click.BadParameter(
|
||||
"Invalid password format. "
|
||||
"Password must contain at least 8 characters"
|
||||
" including a number and a lowercase letter"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_teamname(value):
|
||||
value = str(value).strip() if value else None
|
||||
if not value or not re.match(
|
||||
r"^[a-z\d](?:[a-z\d]|[\-_ ](?=[a-z\d])){0,19}$", value, flags=re.I
|
||||
):
|
||||
raise click.BadParameter(
|
||||
"Invalid team name format. "
|
||||
"Team name must only contain alphanumeric characters, "
|
||||
"single hyphens, underscores, spaces. It can not "
|
||||
"begin or end with a hyphen or a underscore and must"
|
||||
" not be longer than 20 characters."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def validate_orgname_teamname(value):
|
||||
value = str(value).strip() if value else None
|
||||
if not value or ":" not in value:
|
||||
raise click.BadParameter(
|
||||
"Please specify organization and team name using the following"
|
||||
" format - orgname:teamname. For example, mycompany:DreamTeam"
|
||||
)
|
||||
orgname, teamname = value.split(":", 1)
|
||||
validate_orgname(orgname)
|
||||
validate_teamname(teamname)
|
||||
return value
|
||||
|
|
@ -0,0 +1,293 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import getpass
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from platformio import __version__, exception, fs, proc
|
||||
from platformio.compat import IS_WINDOWS, hashlib_encode_data
|
||||
from platformio.package.lockfile import LockFile
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import get_default_projects_dir
|
||||
|
||||
|
||||
def projects_dir_validate(projects_dir):
|
||||
assert os.path.isdir(projects_dir)
|
||||
return os.path.abspath(projects_dir)
|
||||
|
||||
|
||||
DEFAULT_SETTINGS = {
|
||||
"check_platformio_interval": {
|
||||
"description": "Check for the new PlatformIO Core interval (days)",
|
||||
"value": 7,
|
||||
},
|
||||
"check_prune_system_threshold": {
|
||||
"description": "Check for pruning unnecessary data threshold (megabytes)",
|
||||
"value": 1024,
|
||||
},
|
||||
"enable_cache": {
|
||||
"description": "Enable caching for HTTP API requests",
|
||||
"value": True,
|
||||
},
|
||||
"enable_telemetry": {
|
||||
"description": ("Telemetry service <https://bit.ly/pio-telemetry> (Yes/No)"),
|
||||
"value": True,
|
||||
},
|
||||
"force_verbose": {
|
||||
"description": "Force verbose output when processing environments",
|
||||
"value": False,
|
||||
},
|
||||
"projects_dir": {
|
||||
"description": "Default location for PlatformIO projects (PlatformIO Home)",
|
||||
"value": get_default_projects_dir(),
|
||||
"validator": projects_dir_validate,
|
||||
},
|
||||
"enable_proxy_strict_ssl": {
|
||||
"description": "Verify the proxy server certificate against the list of supplied CAs",
|
||||
"value": True,
|
||||
},
|
||||
}
|
||||
|
||||
SESSION_VARS = {
|
||||
"command_ctx": None,
|
||||
"caller_id": None,
|
||||
"custom_project_conf": None,
|
||||
"pause_telemetry": False,
|
||||
}
|
||||
|
||||
|
||||
def resolve_state_path(conf_option_dir, file_name, ensure_dir_exists=True):
|
||||
state_dir = ProjectConfig.get_instance().get("platformio", conf_option_dir)
|
||||
if ensure_dir_exists and not os.path.isdir(state_dir):
|
||||
os.makedirs(state_dir)
|
||||
return os.path.join(state_dir, file_name)
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, path=None, lock=False):
|
||||
self.path = path
|
||||
self.lock = lock
|
||||
if not self.path:
|
||||
self.path = resolve_state_path("core_dir", "appstate.json")
|
||||
self._storage = {}
|
||||
self._lockfile = None
|
||||
self.modified = False
|
||||
|
||||
def __enter__(self):
|
||||
try:
|
||||
self._lock_state_file()
|
||||
if os.path.isfile(self.path):
|
||||
self._storage = fs.load_json(self.path)
|
||||
assert isinstance(self._storage, dict)
|
||||
except (
|
||||
AssertionError,
|
||||
ValueError,
|
||||
UnicodeDecodeError,
|
||||
exception.InvalidJSONFile,
|
||||
):
|
||||
self._storage = {}
|
||||
return self
|
||||
|
||||
def __exit__(self, type_, value, traceback):
|
||||
if self.modified:
|
||||
try:
|
||||
with open(self.path, mode="w", encoding="utf8") as fp:
|
||||
fp.write(json.dumps(self._storage))
|
||||
except IOError as exc:
|
||||
raise exception.HomeDirPermissionsError(
|
||||
os.path.dirname(self.path)
|
||||
) from exc
|
||||
self._unlock_state_file()
|
||||
|
||||
def _lock_state_file(self):
|
||||
if not self.lock:
|
||||
return
|
||||
self._lockfile = LockFile(self.path)
|
||||
try:
|
||||
self._lockfile.acquire()
|
||||
except IOError as exc:
|
||||
raise exception.HomeDirPermissionsError(os.path.dirname(self.path)) from exc
|
||||
|
||||
def _unlock_state_file(self):
|
||||
if hasattr(self, "_lockfile") and self._lockfile:
|
||||
self._lockfile.release()
|
||||
|
||||
def __del__(self):
|
||||
self._unlock_state_file()
|
||||
|
||||
# Dictionary Proxy
|
||||
|
||||
def as_dict(self):
|
||||
return self._storage
|
||||
|
||||
def keys(self):
|
||||
return self._storage.keys()
|
||||
|
||||
def get(self, key, default=True):
|
||||
return self._storage.get(key, default)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.modified = True
|
||||
return self._storage.update(*args, **kwargs)
|
||||
|
||||
def clear(self):
|
||||
return self._storage.clear()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._storage[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.modified = True
|
||||
self._storage[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
self.modified = True
|
||||
del self._storage[key]
|
||||
|
||||
def __contains__(self, item):
|
||||
return item in self._storage
|
||||
|
||||
|
||||
def sanitize_setting(name, value):
|
||||
if name not in DEFAULT_SETTINGS:
|
||||
raise exception.InvalidSettingName(name)
|
||||
|
||||
defdata = DEFAULT_SETTINGS[name]
|
||||
try:
|
||||
if "validator" in defdata:
|
||||
value = defdata["validator"](value)
|
||||
elif isinstance(defdata["value"], bool):
|
||||
if not isinstance(value, bool):
|
||||
value = str(value).lower() in ("true", "yes", "y", "1")
|
||||
elif isinstance(defdata["value"], int):
|
||||
value = int(value)
|
||||
except Exception as exc:
|
||||
raise exception.InvalidSettingValue(value, name) from exc
|
||||
return value
|
||||
|
||||
|
||||
def get_state_item(name, default=None):
|
||||
with State() as state:
|
||||
return state.get(name, default)
|
||||
|
||||
|
||||
def set_state_item(name, value):
|
||||
with State(lock=True) as state:
|
||||
state[name] = value
|
||||
state.modified = True
|
||||
|
||||
|
||||
def delete_state_item(name):
|
||||
with State(lock=True) as state:
|
||||
if name in state:
|
||||
del state[name]
|
||||
|
||||
|
||||
def get_setting(name):
|
||||
_env_name = "PLATFORMIO_SETTING_%s" % name.upper()
|
||||
if _env_name in os.environ:
|
||||
return sanitize_setting(name, os.getenv(_env_name))
|
||||
|
||||
with State() as state:
|
||||
if "settings" in state and name in state["settings"]:
|
||||
return state["settings"][name]
|
||||
|
||||
return DEFAULT_SETTINGS[name]["value"]
|
||||
|
||||
|
||||
def set_setting(name, value):
|
||||
with State(lock=True) as state:
|
||||
if "settings" not in state:
|
||||
state["settings"] = {}
|
||||
state["settings"][name] = sanitize_setting(name, value)
|
||||
state.modified = True
|
||||
|
||||
|
||||
def reset_settings():
|
||||
with State(lock=True) as state:
|
||||
if "settings" in state:
|
||||
del state["settings"]
|
||||
|
||||
|
||||
def get_session_var(name, default=None):
|
||||
return SESSION_VARS.get(name, default)
|
||||
|
||||
|
||||
def set_session_var(name, value):
|
||||
assert name in SESSION_VARS
|
||||
SESSION_VARS[name] = value
|
||||
|
||||
|
||||
def is_disabled_progressbar():
|
||||
return os.getenv("PLATFORMIO_DISABLE_PROGRESSBAR") == "true"
|
||||
|
||||
|
||||
def get_cid():
|
||||
cid = get_state_item("cid")
|
||||
if cid:
|
||||
return cid
|
||||
uid = None
|
||||
if os.getenv("GITHUB_USER"):
|
||||
uid = os.getenv("GITHUB_USER")
|
||||
elif os.getenv("GITPOD_GIT_USER_NAME"):
|
||||
uid = os.getenv("GITPOD_GIT_USER_NAME")
|
||||
if not uid:
|
||||
uid = uuid.getnode()
|
||||
cid = uuid.UUID(bytes=hashlib.md5(hashlib_encode_data(uid)).digest())
|
||||
cid = str(cid)
|
||||
if IS_WINDOWS or os.getuid() > 0: # pylint: disable=no-member
|
||||
set_state_item("cid", cid)
|
||||
set_state_item("created_at", int(time.time()))
|
||||
return cid
|
||||
|
||||
|
||||
def get_project_id(project_dir):
|
||||
return hashlib.sha1(hashlib_encode_data(project_dir)).hexdigest()
|
||||
|
||||
|
||||
def get_user_agent():
|
||||
data = [
|
||||
"PlatformIO/%s" % __version__,
|
||||
"CI/%d" % int(proc.is_ci()),
|
||||
"Container/%d" % int(proc.is_container()),
|
||||
]
|
||||
if get_session_var("caller_id"):
|
||||
data.append("Caller/%s" % get_session_var("caller_id"))
|
||||
if os.getenv("PLATFORMIO_IDE"):
|
||||
data.append("IDE/%s" % os.getenv("PLATFORMIO_IDE"))
|
||||
data.append("Python/%s" % platform.python_version())
|
||||
data.append("Platform/%s" % platform.platform())
|
||||
if not get_setting("enable_telemetry"):
|
||||
data.append("Telemetry/0")
|
||||
return " ".join(data)
|
||||
|
||||
|
||||
def get_host_id():
|
||||
h = hashlib.sha1(hashlib_encode_data(get_cid()))
|
||||
try:
|
||||
username = getpass.getuser()
|
||||
h.update(hashlib_encode_data(username))
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
def get_host_name():
|
||||
return str(socket.gethostname())[:255]
|
||||
|
|
@ -0,0 +1,487 @@
|
|||
{
|
||||
"$id": "https://example.com/library.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "library.json schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 50,
|
||||
"description": "A name of a library.\nMust be unique in the PlatformIO Registry\nShould be slug style for simplicity, consistency, and compatibility. Example: HelloWorld\nCan contain a-z, digits, and dashes (but not start/end with them)\nConsecutive dashes and [:;/,@<>] chars are not allowed.",
|
||||
"required": true
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"maxLength": 20,
|
||||
"description": "A version of a current library source code. Can contain a-z, digits, dots or dash and should be Semantic Versioning compatible.",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 255,
|
||||
"description": "The field helps users to identify and search for your library with a brief description. Describe the hardware devices (sensors, boards and etc.) which are suitable with it.",
|
||||
"required": true
|
||||
},
|
||||
"keywords": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 255
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Used for search by keyword. Helps to make your library easier to discover without people needing to know its name.\nThe keyword should be lowercased, can contain a-z, digits and dash (but not start/end with them). A list from the keywords can be specified with separator , or declared as Array.",
|
||||
"required": true
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string",
|
||||
"maxLength": 255,
|
||||
"description": "Home page of a library (if is different from repository url).",
|
||||
"required": false
|
||||
},
|
||||
"repository": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"git",
|
||||
"hg",
|
||||
"svn"
|
||||
],
|
||||
"description": "only “git”, “hg” or “svn” are supported"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"description": "if is not specified, default branch will be used. This field will be ignored if tag/release exists with the value of version."
|
||||
}
|
||||
},
|
||||
"description": "The repository in which the source code can be found.",
|
||||
"required": false
|
||||
},
|
||||
"authors": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Full name"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "An author’s contact page"
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "boolean",
|
||||
"description": "Specify “maintainer” status"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"description": "Full name"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "An author’s contact page"
|
||||
},
|
||||
"maintainer": {
|
||||
"type": "boolean",
|
||||
"description": "Specify “maintainer” status"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "An author contact information\nIf authors field is not defined, PlatformIO will try to fetch data from VCS provider (Github, Gitlab, etc) if repository is declared.",
|
||||
"required": false
|
||||
},
|
||||
"license": {
|
||||
"type": "string",
|
||||
"description": "A SPDX license ID or SPDX Expression. You can check the full list of SPDX license IDs (see “Identifier” column).",
|
||||
"required": false
|
||||
},
|
||||
"frameworks": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "espidf, freertos, *, etc'"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "espidf, freertos, *, etc'"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "A list with compatible frameworks. The available framework names are defined in the Frameworks section.\nIf the library is compatible with the all frameworks, then do not declare this field or you use *",
|
||||
"required": false
|
||||
},
|
||||
"platforms": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "atmelavr, espressif8266, *, etc'"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "atmelavr, espressif8266, *, etc'"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "A list with compatible development platforms. The available platform name are defined in Development Platforms section.\nIf the library is compatible with the all platforms, then do not declare this field or use *.\nPlatformIO does not check platforms for compatibility in default mode. See Compatibility Mode for details. If you need a strict checking for compatible platforms for a library, please set libCompatMode to strict.",
|
||||
"required": false
|
||||
},
|
||||
"headers": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "MyLibrary.h"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "FooCore.h, FooFeature.h"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "A list of header files that can be included in a project source files using #include <...> directive.",
|
||||
"required": false
|
||||
},
|
||||
"examples": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"base": {
|
||||
"type": "string"
|
||||
},
|
||||
"files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "A list of example patterns.",
|
||||
"required": "false"
|
||||
},
|
||||
"dependencies": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "an owner name (username) from the PlatformIO Registry"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "library name"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version Requirements or Package Specifications"
|
||||
},
|
||||
"frameworks": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "project compatible Frameworks"
|
||||
},
|
||||
"platforms": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": " project compatible Development Platforms"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"owner": {
|
||||
"type": "string",
|
||||
"description": "an owner name (username) from the PlatformIO Registry"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "library name"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version Requirements or Package Specifications"
|
||||
},
|
||||
"frameworks": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "project compatible Frameworks"
|
||||
},
|
||||
"platforms": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": " project compatible Development Platforms"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "A list of dependent libraries that will be automatically installed.",
|
||||
"required": false
|
||||
},
|
||||
"export": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Export only files that matched declared patterns.\n* - matches everything\n? - matches any single character\n[seq] - matches any character in seq\n[!seq] - matches any character not in seq"
|
||||
},
|
||||
"exclude": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Exclude the directories and files which match with exclude patterns."
|
||||
}
|
||||
},
|
||||
"description": "This option is useful if you need to exclude extra data (test code, docs, images, PDFs, etc). It allows one to reduce the size of the final archive.\nTo check which files will be included in the final packages, please use pio pkg pack command.",
|
||||
"required": false
|
||||
},
|
||||
"scripts": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"postinstall": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "runs a script AFTER the package has been installed.\nRun a custom Python script located in the package “scripts” folder AFTER the package is installed. Please note that you don’t need to specify a Python interpreter for Python scripts"
|
||||
},
|
||||
"preuninstall": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "runs a script BEFORE the package is removed.\nRun a custom Bash script BEFORE the package is uninstalled. The script is declared as a list of command arguments and is located at the root of a package"
|
||||
}
|
||||
},
|
||||
"description": "Execute custom scripts during the special Package Management CLI life cycle events",
|
||||
"required": false
|
||||
},
|
||||
"build": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"flags": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Extra flags to control preprocessing, compilation, assembly, and linking processes. More details build_flags.\nKeep in mind when operating with the -I flag (directories to be searched for header files). The path should be relative to the root directory where the library.json manifest is located."
|
||||
},
|
||||
"unflags": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Remove base/initial flags which were set by development platform. More details build_unflags."
|
||||
},
|
||||
"includeDir": {
|
||||
"type": "string",
|
||||
"description": "Custom directory to be searched for header files. A default value is include and means that folder is located at the root of a library.\nThe Library Dependency Finder (LDF) will pick a library automatically only when a project or other dependent libraries include any header file located in includeDir or srcDir.",
|
||||
"required": false
|
||||
},
|
||||
"srcDir": {
|
||||
"type": "string",
|
||||
"description": "Custom location of library source code. A default value is src and means that folder is located in the root of a library.",
|
||||
"required": "false"
|
||||
},
|
||||
"srcFilter": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"description": "Specify which source files should be included/excluded from build process. The path in filter should be relative to the srcDir option of a library.\nSee syntax for build_src_filter.\nPlease note that you can generate source filter “on-the-fly” using extraScript",
|
||||
"required": false
|
||||
},
|
||||
"extraScript": {
|
||||
"type": "string",
|
||||
"description": "Launch extra script before a build process.",
|
||||
"required": "false"
|
||||
},
|
||||
"libArchive": {
|
||||
"type": "boolean",
|
||||
"description": "Create an archive (*.a, static library) from the object files and link it into a firmware (program). This is default behavior of PlatformIO Build System (\"libArchive\": true).\nSetting \"libArchive\": false will instruct PlatformIO Build System to link object files directly (in-line). This could be useful if you need to override weak symbols defined in framework or other libraries.\nYou can disable library archiving globally using lib_archive option in “platformio.ini” (Project Configuration File).",
|
||||
"required": "false"
|
||||
},
|
||||
"libLDFMode": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"off"
|
||||
],
|
||||
"description": "“Manual mode”, does not process source files of a project and dependencies. Builds only the libraries that are specified in manifests (library.json, module.json) or using lib_deps option."
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"chain"
|
||||
],
|
||||
"description": "[DEFAULT] Parses ALL C/C++ source files of the project and follows only by nested includes (#include ..., chain...) from the libraries. It also parses C, CC, CPP files from libraries which have the same name as included header file. Does not evaluate C/C++ Preprocessor conditional syntax."
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"deep"
|
||||
],
|
||||
"description": "Parses ALL C/C++ source files of the project and parses ALL C/C++ source files of the each found dependency (recursively). Does not evaluate C/C++ Preprocessor conditional syntax."
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"chain+"
|
||||
],
|
||||
"description": "The same behavior as for the chain but evaluates C/C++ Preprocessor conditional syntax."
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"deep+"
|
||||
],
|
||||
"description": "The same behavior as for the deep but evaluates C/C++ Preprocessor conditional syntax."
|
||||
}
|
||||
],
|
||||
"description": "Specify Library Dependency Finder Mode. See Dependency Finder Mode for details.",
|
||||
"required": false
|
||||
},
|
||||
"libCompatMode": {
|
||||
"type": "string",
|
||||
"description": "Specify Library Compatibility Mode. See Compatibility Mode for details.",
|
||||
"required": false
|
||||
},
|
||||
"builder": {
|
||||
"anyOf": [
|
||||
{
|
||||
"enum": [
|
||||
"PlatformIOLibBuilder"
|
||||
],
|
||||
"description": "Default Builder"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"ArduinoLibBuilder"
|
||||
]
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"MbedLibBuilder"
|
||||
]
|
||||
}
|
||||
],
|
||||
"description": "Override default PlatformIOLibBuilder with another builder.",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
#####################################################################################
|
||||
#
|
||||
# INSTALLATION
|
||||
#
|
||||
# Please visit > https://docs.platformio.org/en/latest/core/installation/udev-rules.html
|
||||
#
|
||||
#####################################################################################
|
||||
|
||||
#
|
||||
# Boards
|
||||
#
|
||||
|
||||
# CP210X USB UART
|
||||
ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea[67][013]", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="80a9", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# FT231XS USB UART
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Prolific Technology, Inc. PL2303 Serial Port
|
||||
ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# QinHeng Electronics HL-340 USB-Serial adapter
|
||||
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
# QinHeng Electronics CH9102 USB-Serial adapter
|
||||
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Arduino boards
|
||||
ATTRS{idVendor}=="2341", ATTRS{idProduct}=="[08][023]*", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
ATTRS{idVendor}=="2a03", ATTRS{idProduct}=="[08][02]*", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Arduino SAM-BA
|
||||
ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="6124", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{MTP_NO_PROBE}="1"
|
||||
|
||||
# Digistump boards
|
||||
ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="0753", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Maple with DFU
|
||||
ATTRS{idVendor}=="1eaf", ATTRS{idProduct}=="000[34]", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# USBtiny
|
||||
ATTRS{idProduct}=="0c9f", ATTRS{idVendor}=="1781", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# USBasp V2.0
|
||||
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Teensy boards
|
||||
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1"
|
||||
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", MODE:="0666"
|
||||
KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", MODE:="0666"
|
||||
|
||||
# TI Stellaris Launchpad
|
||||
ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# TI MSP430 Launchpad
|
||||
ATTRS{idVendor}=="0451", ATTRS{idProduct}=="f432", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# GD32V DFU Bootloader
|
||||
ATTRS{idVendor}=="28e9", ATTRS{idProduct}=="0189", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# FireBeetle-ESP32
|
||||
ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7522", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Wio Terminal
|
||||
ATTRS{idVendor}=="2886", ATTRS{idProduct}=="[08]02d", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Raspberry Pi Pico
|
||||
ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="[01]*", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# AIR32F103
|
||||
ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
|
||||
#
|
||||
# Debuggers
|
||||
#
|
||||
|
||||
# Black Magic Probe
|
||||
SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic GDB Server", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic UART Port", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# opendous and estick
|
||||
ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204f", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Original FT232/FT245/FT2232/FT232H/FT4232
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="60[01][104]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# DISTORTEC JTAG-lock-pick Tiny 2
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8220", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# TUMPA, TUMPA Lite
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8a9[89]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# XDS100v2
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="a6d0", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Xverve Signalyzer Tool (DT-USB-ST), Signalyzer LITE (DT-USB-SLITE)
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bca[01]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# TI/Luminary Stellaris Evaluation Board FTDI (several)
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bcd[9a]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# egnite Turtelizer 2
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bdc8", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Section5 ICEbear
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c14[01]", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Amontec JTAGkey and JTAGkey-tiny
|
||||
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="cff8", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# TI ICDI
|
||||
ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# STLink probes
|
||||
ATTRS{idVendor}=="0483", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Hilscher NXHX Boards
|
||||
ATTRS{idVendor}=="0640", ATTRS{idProduct}=="0028", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Hitex probes
|
||||
ATTRS{idVendor}=="0640", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Altera USB Blaster
|
||||
ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Amontec JTAGkey-HiSpeed
|
||||
ATTRS{idVendor}=="0fbb", ATTRS{idProduct}=="1000", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# SEGGER J-Link
|
||||
ATTRS{idVendor}=="1366", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Raisonance RLink
|
||||
ATTRS{idVendor}=="138e", ATTRS{idProduct}=="9000", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Debug Board for Neo1973
|
||||
ATTRS{idVendor}=="1457", ATTRS{idProduct}=="5118", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Olimex probes
|
||||
ATTRS{idVendor}=="15ba", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# USBprog with OpenOCD firmware
|
||||
ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c63", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# TI/Luminary Stellaris In-Circuit Debug Interface (ICDI) Board
|
||||
ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Marvell Sheevaplug
|
||||
ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Keil Software, Inc. ULink
|
||||
ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2710", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# CMSIS-DAP compatible adapters
|
||||
ATTRS{product}=="*CMSIS-DAP*", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
||||
# Atmel AVR Dragon
|
||||
ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2107", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1"
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,250 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
|
||||
import click
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import DEFAULT_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import AllowSubstExceptions # pylint: disable=import-error
|
||||
from SCons.Script import AlwaysBuild # pylint: disable=import-error
|
||||
from SCons.Script import Default # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
from SCons.Script import Import # pylint: disable=import-error
|
||||
from SCons.Script import Variables # pylint: disable=import-error
|
||||
|
||||
from platformio import app, fs
|
||||
from platformio.platform.base import PlatformBase
|
||||
from platformio.proc import get_pythonexe_path
|
||||
from platformio.project.helpers import get_project_dir
|
||||
|
||||
AllowSubstExceptions(NameError)
|
||||
|
||||
# append CLI arguments to build environment
|
||||
clivars = Variables(None)
|
||||
clivars.AddVariables(
|
||||
("BUILD_SCRIPT",),
|
||||
("PROJECT_CONFIG",),
|
||||
("PIOENV",),
|
||||
("PIOTEST_RUNNING_NAME",),
|
||||
("UPLOAD_PORT",),
|
||||
("PROGRAM_ARGS",),
|
||||
)
|
||||
|
||||
DEFAULT_ENV_OPTIONS = dict(
|
||||
tools=[
|
||||
"ar",
|
||||
"cc",
|
||||
"c++",
|
||||
"link",
|
||||
"piohooks",
|
||||
"pioasm",
|
||||
"piobuild",
|
||||
"pioproject",
|
||||
"pioplatform",
|
||||
"piotest",
|
||||
"piotarget",
|
||||
"piolib",
|
||||
"pioupload",
|
||||
"piosize",
|
||||
"pioino",
|
||||
"piomisc",
|
||||
"piointegration",
|
||||
"piomaxlen",
|
||||
],
|
||||
toolpath=[os.path.join(fs.get_source_dir(), "builder", "tools")],
|
||||
variables=clivars,
|
||||
# Propagating External Environment
|
||||
ENV=os.environ,
|
||||
UNIX_TIME=int(time()),
|
||||
BUILD_DIR=os.path.join("$PROJECT_BUILD_DIR", "$PIOENV"),
|
||||
BUILD_SRC_DIR=os.path.join("$BUILD_DIR", "src"),
|
||||
BUILD_TEST_DIR=os.path.join("$BUILD_DIR", "test"),
|
||||
COMPILATIONDB_PATH=os.path.join("$PROJECT_DIR", "compile_commands.json"),
|
||||
LIBPATH=["$BUILD_DIR"],
|
||||
PROGNAME="program",
|
||||
PROGPATH=os.path.join("$BUILD_DIR", "$PROGNAME$PROGSUFFIX"),
|
||||
PROG_PATH="$PROGPATH", # deprecated
|
||||
PYTHONEXE=get_pythonexe_path(),
|
||||
)
|
||||
|
||||
# Declare command verbose messages
|
||||
command_strings = dict(
|
||||
ARCOM="Archiving",
|
||||
LINKCOM="Linking",
|
||||
RANLIBCOM="Indexing",
|
||||
ASCOM="Compiling",
|
||||
ASPPCOM="Compiling",
|
||||
CCCOM="Compiling",
|
||||
CXXCOM="Compiling",
|
||||
)
|
||||
if not int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
for name, value in command_strings.items():
|
||||
DEFAULT_ENV_OPTIONS["%sSTR" % name] = "%s $TARGET" % (value)
|
||||
|
||||
env = DefaultEnvironment(**DEFAULT_ENV_OPTIONS)
|
||||
env.SConscriptChdir(False)
|
||||
|
||||
# Load variables from CLI
|
||||
env.Replace(
|
||||
**{
|
||||
key: PlatformBase.decode_scons_arg(env[key])
|
||||
for key in list(clivars.keys())
|
||||
if key in env
|
||||
}
|
||||
)
|
||||
|
||||
# Setup project optional directories
|
||||
config = env.GetProjectConfig()
|
||||
app.set_session_var("custom_project_conf", config.path)
|
||||
|
||||
env.Replace(
|
||||
PROJECT_DIR=get_project_dir(),
|
||||
PROJECT_CORE_DIR=config.get("platformio", "core_dir"),
|
||||
PROJECT_PACKAGES_DIR=config.get("platformio", "packages_dir"),
|
||||
PROJECT_WORKSPACE_DIR=config.get("platformio", "workspace_dir"),
|
||||
PROJECT_LIBDEPS_DIR=config.get("platformio", "libdeps_dir"),
|
||||
PROJECT_INCLUDE_DIR=config.get("platformio", "include_dir"),
|
||||
PROJECT_SRC_DIR=config.get("platformio", "src_dir"),
|
||||
PROJECTSRC_DIR="$PROJECT_SRC_DIR", # legacy for dev/platform
|
||||
PROJECT_TEST_DIR=config.get("platformio", "test_dir"),
|
||||
PROJECT_DATA_DIR=config.get("platformio", "data_dir"),
|
||||
PROJECTDATA_DIR="$PROJECT_DATA_DIR", # legacy for dev/platform
|
||||
PROJECT_BUILD_DIR=config.get("platformio", "build_dir"),
|
||||
BUILD_TYPE=env.GetBuildType(),
|
||||
BUILD_CACHE_DIR=config.get("platformio", "build_cache_dir"),
|
||||
LIBSOURCE_DIRS=[
|
||||
config.get("platformio", "lib_dir"),
|
||||
os.path.join("$PROJECT_LIBDEPS_DIR", "$PIOENV"),
|
||||
config.get("platformio", "globallib_dir"),
|
||||
],
|
||||
)
|
||||
|
||||
if int(ARGUMENTS.get("ISATTY", 0)):
|
||||
# pylint: disable=protected-access
|
||||
click._compat.isatty = lambda stream: True
|
||||
|
||||
if env.subst("$BUILD_CACHE_DIR"):
|
||||
if not os.path.isdir(env.subst("$BUILD_CACHE_DIR")):
|
||||
os.makedirs(env.subst("$BUILD_CACHE_DIR"))
|
||||
env.CacheDir("$BUILD_CACHE_DIR")
|
||||
|
||||
if not int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
click.echo("Verbose mode can be enabled via `-v, --verbose` option")
|
||||
|
||||
# Dynamically load dependent tools
|
||||
if "compiledb" in COMMAND_LINE_TARGETS:
|
||||
env.Tool("compilation_db")
|
||||
|
||||
if not os.path.isdir(env.subst("$BUILD_DIR")):
|
||||
os.makedirs(env.subst("$BUILD_DIR"))
|
||||
|
||||
env.LoadProjectOptions()
|
||||
env.LoadPioPlatform()
|
||||
|
||||
env.SConsignFile(
|
||||
os.path.join(
|
||||
"$BUILD_CACHE_DIR" if env.subst("$BUILD_CACHE_DIR") else "$BUILD_DIR",
|
||||
".sconsign%d%d" % (sys.version_info[0], sys.version_info[1]),
|
||||
)
|
||||
)
|
||||
|
||||
env.SConscript(env.GetExtraScripts("pre"), exports="env")
|
||||
|
||||
if env.IsCleanTarget():
|
||||
env.CleanProject(fullclean=int(ARGUMENTS.get("FULLCLEAN", 0)))
|
||||
env.Exit(0)
|
||||
|
||||
env.SConscript("$BUILD_SCRIPT")
|
||||
|
||||
if "UPLOAD_FLAGS" in env:
|
||||
env.Prepend(UPLOADERFLAGS=["$UPLOAD_FLAGS"])
|
||||
if env.GetProjectOption("upload_command"):
|
||||
env.Replace(UPLOADCMD=env.GetProjectOption("upload_command"))
|
||||
|
||||
env.SConscript(env.GetExtraScripts("post"), exports="env")
|
||||
|
||||
##############################################################################
|
||||
|
||||
# Checking program size
|
||||
if env.get("SIZETOOL") and not (
|
||||
set(["nobuild", "sizedata"]) & set(COMMAND_LINE_TARGETS)
|
||||
):
|
||||
env.Depends("upload", "checkprogsize")
|
||||
# Replace platform's "size" target with our
|
||||
_new_targets = [t for t in DEFAULT_TARGETS if str(t) != "size"]
|
||||
Default(None)
|
||||
Default(_new_targets)
|
||||
Default("checkprogsize")
|
||||
|
||||
if "compiledb" in COMMAND_LINE_TARGETS:
|
||||
env.Alias("compiledb", env.CompilationDatabase("$COMPILATIONDB_PATH"))
|
||||
|
||||
# Print configured protocols
|
||||
env.AddPreAction(
|
||||
"upload",
|
||||
env.VerboseAction(
|
||||
lambda source, target, env: env.PrintUploadInfo(),
|
||||
"Configuring upload protocol...",
|
||||
),
|
||||
)
|
||||
|
||||
AlwaysBuild(env.Alias("__debug", DEFAULT_TARGETS))
|
||||
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
|
||||
|
||||
env.ProcessDelayedActions()
|
||||
|
||||
##############################################################################
|
||||
|
||||
if "envdump" in COMMAND_LINE_TARGETS:
|
||||
click.echo(env.Dump())
|
||||
env.Exit(0)
|
||||
|
||||
if env.IsIntegrationDump():
|
||||
projenv = None
|
||||
try:
|
||||
Import("projenv")
|
||||
except: # pylint: disable=bare-except
|
||||
projenv = env
|
||||
data = projenv.DumpIntegrationData(env)
|
||||
# dump to file for the further reading by project.helpers.load_build_metadata
|
||||
with open(
|
||||
projenv.subst(os.path.join("$BUILD_DIR", "idedata.json")),
|
||||
mode="w",
|
||||
encoding="utf8",
|
||||
) as fp:
|
||||
json.dump(data, fp)
|
||||
click.echo("\n%s\n" % json.dumps(data)) # pylint: disable=undefined-variable
|
||||
env.Exit(0)
|
||||
|
||||
if "sizedata" in COMMAND_LINE_TARGETS:
|
||||
AlwaysBuild(
|
||||
env.Alias(
|
||||
"sizedata",
|
||||
DEFAULT_TARGETS,
|
||||
env.VerboseAction(env.DumpSizeData, "Generating memory usage report..."),
|
||||
)
|
||||
)
|
||||
|
||||
Default("sizedata")
|
||||
|
||||
# issue #4604: process targets sequentially
|
||||
for index, target in enumerate(
|
||||
[t for t in COMMAND_LINE_TARGETS if not t.startswith("__")][1:]
|
||||
):
|
||||
env.Depends(target, COMMAND_LINE_TARGETS[index])
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import SCons.Tool.asm # pylint: disable=import-error
|
||||
|
||||
#
|
||||
# Resolve https://github.com/platformio/platformio-core/issues/3917
|
||||
# Avoid forcing .S to bare assembly on Windows OS
|
||||
#
|
||||
|
||||
if ".S" in SCons.Tool.asm.ASSuffixes:
|
||||
SCons.Tool.asm.ASSuffixes.remove(".S")
|
||||
if ".S" not in SCons.Tool.asm.ASPPSuffixes:
|
||||
SCons.Tool.asm.ASPPSuffixes.append(".S")
|
||||
|
||||
|
||||
generate = SCons.Tool.asm.generate
|
||||
exists = SCons.Tool.asm.exists
|
||||
|
|
@ -0,0 +1,390 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import sys
|
||||
|
||||
from SCons import Builder, Util # pylint: disable=import-error
|
||||
from SCons.Node import FS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import AlwaysBuild # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
from SCons.Script import SConscript # pylint: disable=import-error
|
||||
|
||||
from platformio import __version__, fs
|
||||
from platformio.compat import IS_MACOS, string_types
|
||||
from platformio.package.version import pepver_to_semver
|
||||
from platformio.proc import where_is_program
|
||||
|
||||
SRC_HEADER_EXT = ["h", "hpp"]
|
||||
SRC_ASM_EXT = ["S", "spp", "SPP", "sx", "s", "asm", "ASM"]
|
||||
SRC_C_EXT = ["c"]
|
||||
SRC_CXX_EXT = ["cc", "cpp", "cxx", "c++"]
|
||||
SRC_BUILD_EXT = SRC_C_EXT + SRC_CXX_EXT + SRC_ASM_EXT
|
||||
SRC_FILTER_DEFAULT = ["+<*>", "-<.git%s>" % os.sep, "-<.svn%s>" % os.sep]
|
||||
|
||||
|
||||
def scons_patched_match_splitext(path, suffixes=None):
|
||||
"""Patch SCons Builder, append $OBJSUFFIX to the end of each target"""
|
||||
tokens = Util.splitext(path)
|
||||
if suffixes and tokens[1] and tokens[1] in suffixes:
|
||||
return (path, tokens[1])
|
||||
return tokens
|
||||
|
||||
|
||||
def GetBuildType(env):
|
||||
modes = []
|
||||
if (
|
||||
set(["__debug", "sizedata"]) # sizedata = for memory inspection
|
||||
& set(COMMAND_LINE_TARGETS)
|
||||
or env.GetProjectOption("build_type") == "debug"
|
||||
):
|
||||
modes.append("debug")
|
||||
if "__test" in COMMAND_LINE_TARGETS or env.GetProjectOption("build_type") == "test":
|
||||
modes.append("test")
|
||||
return "+".join(modes or ["release"])
|
||||
|
||||
|
||||
def BuildProgram(env):
|
||||
env.ProcessProgramDeps()
|
||||
env.ProcessProjectDeps()
|
||||
|
||||
# append into the beginning a main LD script
|
||||
if env.get("LDSCRIPT_PATH") and not any("-Wl,-T" in f for f in env["LINKFLAGS"]):
|
||||
env.Prepend(LINKFLAGS=["-T", env.subst("$LDSCRIPT_PATH")])
|
||||
|
||||
# enable "cyclic reference" for linker
|
||||
if (
|
||||
env.get("LIBS")
|
||||
and env.GetCompilerType() == "gcc"
|
||||
and (env.PioPlatform().is_embedded() or not IS_MACOS)
|
||||
):
|
||||
env.Prepend(_LIBFLAGS="-Wl,--start-group ")
|
||||
env.Append(_LIBFLAGS=" -Wl,--end-group")
|
||||
|
||||
program = env.Program(env.subst("$PROGPATH"), env["PIOBUILDFILES"])
|
||||
env.Replace(PIOMAINPROG=program)
|
||||
|
||||
AlwaysBuild(
|
||||
env.Alias(
|
||||
"checkprogsize",
|
||||
program,
|
||||
env.VerboseAction(env.CheckUploadSize, "Checking size $PIOMAINPROG"),
|
||||
)
|
||||
)
|
||||
|
||||
print("Building in %s mode" % env["BUILD_TYPE"])
|
||||
|
||||
return program
|
||||
|
||||
|
||||
def ProcessProgramDeps(env):
|
||||
def _append_pio_macros():
|
||||
core_version = pepver_to_semver(__version__)
|
||||
env.AppendUnique(
|
||||
CPPDEFINES=[
|
||||
(
|
||||
"PLATFORMIO",
|
||||
int(
|
||||
"{0:02d}{1:02d}{2:02d}".format(
|
||||
core_version.major, core_version.minor, core_version.patch
|
||||
)
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
_append_pio_macros()
|
||||
|
||||
env.PrintConfiguration()
|
||||
|
||||
# process extra flags from board
|
||||
if "BOARD" in env and "build.extra_flags" in env.BoardConfig():
|
||||
env.ProcessFlags(env.BoardConfig().get("build.extra_flags"))
|
||||
|
||||
# apply user flags
|
||||
env.ProcessFlags(env.get("BUILD_FLAGS"))
|
||||
|
||||
# process framework scripts
|
||||
env.BuildFrameworks(env.get("PIOFRAMEWORK"))
|
||||
|
||||
if "debug" in env["BUILD_TYPE"]:
|
||||
env.ConfigureDebugTarget()
|
||||
|
||||
# remove specified flags
|
||||
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
|
||||
|
||||
if "compiledb" in COMMAND_LINE_TARGETS:
|
||||
# Resolve absolute path of toolchain
|
||||
for cmd in ("CC", "CXX", "AS"):
|
||||
if cmd not in env:
|
||||
continue
|
||||
if os.path.isabs(env[cmd]):
|
||||
continue
|
||||
env[cmd] = where_is_program(
|
||||
env.subst("$%s" % cmd), env.subst("${ENV['PATH']}")
|
||||
)
|
||||
|
||||
if env.get("COMPILATIONDB_INCLUDE_TOOLCHAIN"):
|
||||
for scope, includes in env.DumpIntegrationIncludes().items():
|
||||
if scope in ("toolchain",):
|
||||
env.Append(CPPPATH=includes)
|
||||
|
||||
|
||||
def ProcessProjectDeps(env):
|
||||
plb = env.ConfigureProjectLibBuilder()
|
||||
|
||||
# prepend project libs to the beginning of list
|
||||
env.Prepend(LIBS=plb.build())
|
||||
# prepend extra linker related options from libs
|
||||
env.PrependUnique(
|
||||
**{
|
||||
key: plb.env.get(key)
|
||||
for key in ("LIBS", "LIBPATH", "LINKFLAGS")
|
||||
if plb.env.get(key)
|
||||
}
|
||||
)
|
||||
|
||||
if "test" in env["BUILD_TYPE"]:
|
||||
build_files_before_nums = len(env.get("PIOBUILDFILES", []))
|
||||
plb.env.BuildSources(
|
||||
"$BUILD_TEST_DIR", "$PROJECT_TEST_DIR", "$PIOTEST_SRC_FILTER"
|
||||
)
|
||||
if len(env.get("PIOBUILDFILES", [])) - build_files_before_nums < 1:
|
||||
sys.stderr.write(
|
||||
"Error: Nothing to build. Please put your test suites "
|
||||
"to the '%s' folder\n" % env.subst("$PROJECT_TEST_DIR")
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
if "test" not in env["BUILD_TYPE"] or env.GetProjectOption("test_build_src"):
|
||||
plb.env.BuildSources(
|
||||
"$BUILD_SRC_DIR", "$PROJECT_SRC_DIR", env.get("SRC_FILTER")
|
||||
)
|
||||
|
||||
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
|
||||
sys.stderr.write(
|
||||
"Error: Nothing to build. Please put your source code files "
|
||||
"to the '%s' folder\n" % env.subst("$PROJECT_SRC_DIR")
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
|
||||
if not isinstance(flags, list):
|
||||
flags = [flags]
|
||||
result = {}
|
||||
for raw in flags:
|
||||
for key, value in env.ParseFlags(str(raw)).items():
|
||||
if key not in result:
|
||||
result[key] = []
|
||||
result[key].extend(value)
|
||||
|
||||
cppdefines = []
|
||||
for item in result["CPPDEFINES"]:
|
||||
if not Util.is_Sequence(item):
|
||||
cppdefines.append(item)
|
||||
continue
|
||||
name, value = item[:2]
|
||||
if '"' in value:
|
||||
value = value.replace('"', '\\"')
|
||||
elif value.isdigit():
|
||||
value = int(value)
|
||||
elif value.replace(".", "", 1).isdigit():
|
||||
value = float(value)
|
||||
cppdefines.append((name, value))
|
||||
result["CPPDEFINES"] = cppdefines
|
||||
|
||||
# fix relative CPPPATH & LIBPATH
|
||||
for k in ("CPPPATH", "LIBPATH"):
|
||||
for i, p in enumerate(result.get(k, [])):
|
||||
p = env.subst(p)
|
||||
if os.path.isdir(p):
|
||||
result[k][i] = os.path.abspath(p)
|
||||
|
||||
# fix relative path for "-include"
|
||||
for i, f in enumerate(result.get("CCFLAGS", [])):
|
||||
if isinstance(f, tuple) and f[0] == "-include":
|
||||
result["CCFLAGS"][i] = (f[0], env.subst(f[1].get_path()))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def ProcessFlags(env, flags): # pylint: disable=too-many-branches
|
||||
if not flags:
|
||||
return
|
||||
env.Append(**env.ParseFlagsExtended(flags))
|
||||
|
||||
# Cancel any previous definition of name, either built in or
|
||||
# provided with a -U option // Issue #191
|
||||
undefines = [
|
||||
u
|
||||
for u in env.get("CCFLAGS", [])
|
||||
if isinstance(u, string_types) and u.startswith("-U")
|
||||
]
|
||||
if undefines:
|
||||
for undef in undefines:
|
||||
env["CCFLAGS"].remove(undef)
|
||||
if undef[2:] in env["CPPDEFINES"]:
|
||||
env["CPPDEFINES"].remove(undef[2:])
|
||||
env.Append(_CPPDEFFLAGS=" %s" % " ".join(undefines))
|
||||
|
||||
|
||||
def ProcessUnFlags(env, flags):
|
||||
if not flags:
|
||||
return
|
||||
parsed = env.ParseFlagsExtended(flags)
|
||||
unflag_scopes = tuple(set(["ASPPFLAGS"] + list(parsed.keys())))
|
||||
for scope in unflag_scopes:
|
||||
for unflags in parsed.values():
|
||||
for unflag in unflags:
|
||||
for current in list(env.get(scope, [])):
|
||||
conditions = [
|
||||
unflag == current,
|
||||
not isinstance(unflag, (tuple, list))
|
||||
and isinstance(current, (tuple, list))
|
||||
and unflag == current[0],
|
||||
]
|
||||
if any(conditions):
|
||||
env[scope].remove(current)
|
||||
|
||||
|
||||
def StringifyMacro(env, value): # pylint: disable=unused-argument
|
||||
return '\\"%s\\"' % value.replace('"', '\\\\\\"')
|
||||
|
||||
|
||||
def MatchSourceFiles(env, src_dir, src_filter=None, src_exts=None):
|
||||
src_filter = env.subst(src_filter) if src_filter else None
|
||||
src_filter = src_filter or SRC_FILTER_DEFAULT
|
||||
src_exts = src_exts or (SRC_BUILD_EXT + SRC_HEADER_EXT)
|
||||
return fs.match_src_files(env.subst(src_dir), src_filter, src_exts)
|
||||
|
||||
|
||||
def CollectBuildFiles(
|
||||
env, variant_dir, src_dir, src_filter=None, duplicate=False
|
||||
): # pylint: disable=too-many-locals
|
||||
sources = []
|
||||
variants = []
|
||||
|
||||
src_dir = env.subst(src_dir)
|
||||
if src_dir.endswith(os.sep):
|
||||
src_dir = src_dir[:-1]
|
||||
|
||||
for item in env.MatchSourceFiles(src_dir, src_filter, SRC_BUILD_EXT):
|
||||
_reldir = os.path.dirname(item)
|
||||
_src_dir = os.path.join(src_dir, _reldir) if _reldir else src_dir
|
||||
_var_dir = os.path.join(variant_dir, _reldir) if _reldir else variant_dir
|
||||
|
||||
if _var_dir not in variants:
|
||||
variants.append(_var_dir)
|
||||
env.VariantDir(_var_dir, _src_dir, duplicate)
|
||||
|
||||
sources.append(env.File(os.path.join(_var_dir, os.path.basename(item))))
|
||||
|
||||
middlewares = env.get("__PIO_BUILD_MIDDLEWARES")
|
||||
if not middlewares:
|
||||
return sources
|
||||
|
||||
new_sources = []
|
||||
for node in sources:
|
||||
new_node = node
|
||||
for callback, pattern in middlewares:
|
||||
if pattern and not fnmatch.fnmatch(node.srcnode().get_path(), pattern):
|
||||
continue
|
||||
if callback.__code__.co_argcount == 2:
|
||||
new_node = callback(env, new_node)
|
||||
else:
|
||||
new_node = callback(new_node)
|
||||
if not new_node:
|
||||
break
|
||||
if new_node:
|
||||
new_sources.append(new_node)
|
||||
|
||||
return new_sources
|
||||
|
||||
|
||||
def AddBuildMiddleware(env, callback, pattern=None):
|
||||
env.Append(__PIO_BUILD_MIDDLEWARES=[(callback, pattern)])
|
||||
|
||||
|
||||
def BuildFrameworks(env, frameworks):
|
||||
if not frameworks:
|
||||
return
|
||||
|
||||
if "BOARD" not in env:
|
||||
sys.stderr.write(
|
||||
"Please specify `board` in `platformio.ini` to use "
|
||||
"with '%s' framework\n" % ", ".join(frameworks)
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
supported_frameworks = env.BoardConfig().get("frameworks", [])
|
||||
for name in frameworks:
|
||||
if name == "arduino":
|
||||
# Arduino IDE appends .o to the end of filename
|
||||
Builder.match_splitext = scons_patched_match_splitext
|
||||
if "nobuild" not in COMMAND_LINE_TARGETS:
|
||||
env.ConvertInoToCpp()
|
||||
|
||||
if name in supported_frameworks:
|
||||
SConscript(env.GetFrameworkScript(name), exports="env")
|
||||
else:
|
||||
sys.stderr.write("Error: This board doesn't support %s framework!\n" % name)
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def BuildLibrary(env, variant_dir, src_dir, src_filter=None, nodes=None):
|
||||
env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
|
||||
nodes = nodes or env.CollectBuildFiles(variant_dir, src_dir, src_filter)
|
||||
return env.StaticLibrary(env.subst(variant_dir), nodes)
|
||||
|
||||
|
||||
def BuildSources(env, variant_dir, src_dir, src_filter=None):
|
||||
if env.get("PIOMAINPROG"):
|
||||
sys.stderr.write(
|
||||
"Error: The main program is already constructed and the inline "
|
||||
"source files are not allowed. Please use `env.BuildLibrary(...)` "
|
||||
"or PRE-type script instead."
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter)
|
||||
DefaultEnvironment().Append(
|
||||
PIOBUILDFILES=[
|
||||
env.Object(node) if isinstance(node, FS.File) else node for node in nodes
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(GetBuildType)
|
||||
env.AddMethod(BuildProgram)
|
||||
env.AddMethod(ProcessProgramDeps)
|
||||
env.AddMethod(ProcessProjectDeps)
|
||||
env.AddMethod(ParseFlagsExtended)
|
||||
env.AddMethod(ProcessFlags)
|
||||
env.AddMethod(ProcessUnFlags)
|
||||
env.AddMethod(StringifyMacro)
|
||||
env.AddMethod(MatchSourceFiles)
|
||||
env.AddMethod(CollectBuildFiles)
|
||||
env.AddMethod(AddBuildMiddleware)
|
||||
env.AddMethod(BuildFrameworks)
|
||||
env.AddMethod(BuildLibrary)
|
||||
env.AddMethod(BuildSources)
|
||||
return env
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
def AddActionWrapper(handler):
|
||||
def wraps(env, files, action):
|
||||
if not isinstance(files, (list, tuple, set)):
|
||||
files = [files]
|
||||
known_nodes = []
|
||||
unknown_files = []
|
||||
for item in files:
|
||||
nodes = env.arg2nodes(item, env.fs.Entry)
|
||||
if nodes and nodes[0].exists():
|
||||
known_nodes.extend(nodes)
|
||||
else:
|
||||
unknown_files.append(item)
|
||||
if unknown_files:
|
||||
env.Append(**{"_PIO_DELAYED_ACTIONS": [(handler, unknown_files, action)]})
|
||||
if known_nodes:
|
||||
return handler(known_nodes, action)
|
||||
return []
|
||||
|
||||
return wraps
|
||||
|
||||
|
||||
def ProcessDelayedActions(env):
|
||||
for func, nodes, action in env.get("_PIO_DELAYED_ACTIONS", []):
|
||||
func(nodes, action)
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.Replace(**{"_PIO_DELAYED_ACTIONS": []})
|
||||
env.AddMethod(AddActionWrapper(env.AddPreAction), "AddPreAction")
|
||||
env.AddMethod(AddActionWrapper(env.AddPostAction), "AddPostAction")
|
||||
env.AddMethod(ProcessDelayedActions)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import atexit
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio.compat import get_filesystem_encoding, get_locale_encoding
|
||||
|
||||
|
||||
class InoToCPPConverter:
|
||||
PROTOTYPE_RE = re.compile(
|
||||
r"""^(
|
||||
(?:template\<.*\>\s*)? # template
|
||||
([a-z_\d\&]+\*?\s+){1,2} # return type
|
||||
([a-z_\d]+\s*) # name of prototype
|
||||
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments
|
||||
)\s*(\{|;) # must end with `{` or `;`
|
||||
""",
|
||||
re.X | re.M | re.I,
|
||||
)
|
||||
DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I)
|
||||
PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)"
|
||||
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
self._main_ino = None
|
||||
self._safe_encoding = None
|
||||
|
||||
def read_safe_contents(self, path):
|
||||
error_reported = False
|
||||
for encoding in (
|
||||
"utf-8",
|
||||
None,
|
||||
get_filesystem_encoding(),
|
||||
get_locale_encoding(),
|
||||
"latin-1",
|
||||
):
|
||||
try:
|
||||
with io.open(path, encoding=encoding) as fp:
|
||||
contents = fp.read()
|
||||
self._safe_encoding = encoding
|
||||
return contents
|
||||
except UnicodeDecodeError:
|
||||
if not error_reported:
|
||||
error_reported = True
|
||||
click.secho(
|
||||
"Unicode decode error has occurred, please remove invalid "
|
||||
"(non-ASCII or non-UTF8) characters from %s file or convert it to UTF-8"
|
||||
% path,
|
||||
fg="yellow",
|
||||
err=True,
|
||||
)
|
||||
return ""
|
||||
|
||||
def write_safe_contents(self, path, contents):
|
||||
with io.open(
|
||||
path, "w", encoding=self._safe_encoding, errors="backslashreplace"
|
||||
) as fp:
|
||||
return fp.write(contents)
|
||||
|
||||
def is_main_node(self, contents):
|
||||
return self.DETECTMAIN_RE.search(contents)
|
||||
|
||||
def convert(self, nodes):
|
||||
contents = self.merge(nodes)
|
||||
if not contents:
|
||||
return None
|
||||
return self.process(contents)
|
||||
|
||||
def merge(self, nodes):
|
||||
assert nodes
|
||||
lines = []
|
||||
for node in nodes:
|
||||
contents = self.read_safe_contents(node.get_path())
|
||||
_lines = ['# 1 "%s"' % node.get_path().replace("\\", "/"), contents]
|
||||
if self.is_main_node(contents):
|
||||
lines = _lines + lines
|
||||
self._main_ino = node.get_path()
|
||||
else:
|
||||
lines.extend(_lines)
|
||||
|
||||
if not self._main_ino:
|
||||
self._main_ino = nodes[0].get_path()
|
||||
|
||||
return "\n".join(["#include <Arduino.h>"] + lines) if lines else None
|
||||
|
||||
def process(self, contents):
|
||||
out_file = re.sub(r"[\"\'\;]+", "", self._main_ino) + ".cpp"
|
||||
assert self._gcc_preprocess(contents, out_file)
|
||||
contents = self.read_safe_contents(out_file)
|
||||
contents = self._join_multiline_strings(contents)
|
||||
self.write_safe_contents(out_file, self.append_prototypes(contents))
|
||||
return out_file
|
||||
|
||||
def _gcc_preprocess(self, contents, out_file):
|
||||
tmp_path = tempfile.mkstemp()[1]
|
||||
self.write_safe_contents(tmp_path, contents)
|
||||
self.env.Execute(
|
||||
self.env.VerboseAction(
|
||||
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
|
||||
out_file, tmp_path
|
||||
),
|
||||
"Converting " + os.path.basename(out_file[:-4]),
|
||||
)
|
||||
)
|
||||
atexit.register(_delete_file, tmp_path)
|
||||
return os.path.isfile(out_file)
|
||||
|
||||
def _join_multiline_strings(self, contents):
|
||||
if "\\\n" not in contents:
|
||||
return contents
|
||||
newlines = []
|
||||
linenum = 0
|
||||
stropen = False
|
||||
for line in contents.split("\n"):
|
||||
_linenum = self._parse_preproc_line_num(line)
|
||||
if _linenum is not None:
|
||||
linenum = _linenum
|
||||
else:
|
||||
linenum += 1
|
||||
|
||||
if line.endswith("\\"):
|
||||
if line.startswith('"'):
|
||||
stropen = True
|
||||
newlines.append(line[:-1])
|
||||
continue
|
||||
if stropen:
|
||||
newlines[len(newlines) - 1] += line[:-1]
|
||||
continue
|
||||
elif stropen and line.endswith(('",', '";')):
|
||||
newlines[len(newlines) - 1] += line
|
||||
stropen = False
|
||||
newlines.append(
|
||||
'#line %d "%s"' % (linenum, self._main_ino.replace("\\", "/"))
|
||||
)
|
||||
continue
|
||||
|
||||
newlines.append(line)
|
||||
|
||||
return "\n".join(newlines)
|
||||
|
||||
@staticmethod
|
||||
def _parse_preproc_line_num(line):
|
||||
if not line.startswith("#"):
|
||||
return None
|
||||
tokens = line.split(" ", 3)
|
||||
if len(tokens) > 2 and tokens[1].isdigit():
|
||||
return int(tokens[1])
|
||||
return None
|
||||
|
||||
def _parse_prototypes(self, contents):
|
||||
prototypes = []
|
||||
reserved_keywords = set(["if", "else", "while"])
|
||||
for match in self.PROTOTYPE_RE.finditer(contents):
|
||||
if (
|
||||
set([match.group(2).strip(), match.group(3).strip()])
|
||||
& reserved_keywords
|
||||
):
|
||||
continue
|
||||
prototypes.append(match)
|
||||
return prototypes
|
||||
|
||||
def _get_total_lines(self, contents):
|
||||
total = 0
|
||||
if contents.endswith("\n"):
|
||||
contents = contents[:-1]
|
||||
for line in contents.split("\n")[::-1]:
|
||||
linenum = self._parse_preproc_line_num(line)
|
||||
if linenum is not None:
|
||||
return total + linenum
|
||||
total += 1
|
||||
return total
|
||||
|
||||
def append_prototypes(self, contents):
|
||||
prototypes = self._parse_prototypes(contents) or []
|
||||
|
||||
# skip already declared prototypes
|
||||
declared = set(m.group(1).strip() for m in prototypes if m.group(4) == ";")
|
||||
prototypes = [m for m in prototypes if m.group(1).strip() not in declared]
|
||||
|
||||
if not prototypes:
|
||||
return contents
|
||||
|
||||
prototype_names = set(m.group(3).strip() for m in prototypes)
|
||||
split_pos = prototypes[0].start()
|
||||
match_ptrs = re.search(
|
||||
self.PROTOPTRS_TPLRE % ("|".join(prototype_names)),
|
||||
contents[:split_pos],
|
||||
re.M,
|
||||
)
|
||||
if match_ptrs:
|
||||
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
|
||||
|
||||
result = []
|
||||
result.append(contents[:split_pos].strip())
|
||||
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
|
||||
result.append(
|
||||
'#line %d "%s"'
|
||||
% (
|
||||
self._get_total_lines(contents[:split_pos]),
|
||||
self._main_ino.replace("\\", "/"),
|
||||
)
|
||||
)
|
||||
result.append(contents[split_pos:].strip())
|
||||
return "\n".join(result)
|
||||
|
||||
|
||||
def FindInoNodes(env):
|
||||
src_dir = glob.escape(env.subst("$PROJECT_SRC_DIR"))
|
||||
return env.Glob(os.path.join(src_dir, "*.ino")) + env.Glob(
|
||||
os.path.join(src_dir, "*.pde")
|
||||
)
|
||||
|
||||
|
||||
def ConvertInoToCpp(env):
|
||||
ino_nodes = env.FindInoNodes()
|
||||
if not ino_nodes:
|
||||
return
|
||||
c = InoToCPPConverter(env)
|
||||
out_file = c.convert(ino_nodes)
|
||||
|
||||
atexit.register(_delete_file, out_file)
|
||||
|
||||
|
||||
def _delete_file(path):
|
||||
try:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(FindInoNodes)
|
||||
env.AddMethod(ConvertInoToCpp)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import os
|
||||
|
||||
import SCons.Defaults # pylint: disable=import-error
|
||||
import SCons.Subst # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
|
||||
from platformio.proc import exec_command, where_is_program
|
||||
|
||||
|
||||
def IsIntegrationDump(_):
|
||||
return set(["__idedata", "idedata"]) & set(COMMAND_LINE_TARGETS)
|
||||
|
||||
|
||||
def DumpIntegrationIncludes(env):
|
||||
result = dict(build=[], compatlib=[], toolchain=[])
|
||||
|
||||
result["build"].extend(
|
||||
[
|
||||
env.subst("$PROJECT_INCLUDE_DIR"),
|
||||
env.subst("$PROJECT_SRC_DIR"),
|
||||
]
|
||||
)
|
||||
result["build"].extend(
|
||||
[os.path.abspath(env.subst(item)) for item in env.get("CPPPATH", [])]
|
||||
)
|
||||
|
||||
# installed libs
|
||||
for lb in env.GetLibBuilders():
|
||||
result["compatlib"].extend(
|
||||
[os.path.abspath(inc) for inc in lb.get_include_dirs()]
|
||||
)
|
||||
|
||||
# includes from toolchains
|
||||
p = env.PioPlatform()
|
||||
for pkg in p.get_installed_packages(with_optional=False):
|
||||
if p.get_package_type(pkg.metadata.name) != "toolchain":
|
||||
continue
|
||||
toolchain_dir = glob.escape(pkg.path)
|
||||
toolchain_incglobs = [
|
||||
os.path.join(toolchain_dir, "*", "include", "c++", "*"),
|
||||
os.path.join(toolchain_dir, "*", "include", "c++", "*", "*-*-*"),
|
||||
os.path.join(toolchain_dir, "lib", "gcc", "*", "*", "include*"),
|
||||
os.path.join(toolchain_dir, "*", "include*"),
|
||||
]
|
||||
for g in toolchain_incglobs:
|
||||
result["toolchain"].extend([os.path.abspath(inc) for inc in glob.glob(g)])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_gcc_defines(env):
|
||||
items = []
|
||||
try:
|
||||
sysenv = os.environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
result = exec_command(
|
||||
"echo | %s -dM -E -" % env.subst("$CC"), env=sysenv, shell=True
|
||||
)
|
||||
except OSError:
|
||||
return items
|
||||
if result["returncode"] != 0:
|
||||
return items
|
||||
for line in result["out"].split("\n"):
|
||||
tokens = line.strip().split(" ", 2)
|
||||
if not tokens or tokens[0] != "#define":
|
||||
continue
|
||||
if len(tokens) > 2:
|
||||
items.append("%s=%s" % (tokens[1], tokens[2]))
|
||||
else:
|
||||
items.append(tokens[1])
|
||||
return items
|
||||
|
||||
|
||||
def dump_defines(env):
|
||||
defines = []
|
||||
# global symbols
|
||||
for item in SCons.Defaults.processDefines(env.get("CPPDEFINES", [])):
|
||||
item = item.strip()
|
||||
if item:
|
||||
defines.append(env.subst(item).replace('\\"', '"'))
|
||||
|
||||
# special symbol for Atmel AVR MCU
|
||||
if env["PIOPLATFORM"] == "atmelavr":
|
||||
board_mcu = env.get("BOARD_MCU")
|
||||
if not board_mcu and "BOARD" in env:
|
||||
board_mcu = env.BoardConfig().get("build.mcu")
|
||||
if board_mcu:
|
||||
defines.append(
|
||||
str(
|
||||
"__AVR_%s__"
|
||||
% board_mcu.upper()
|
||||
.replace("ATMEGA", "ATmega")
|
||||
.replace("ATTINY", "ATtiny")
|
||||
)
|
||||
)
|
||||
|
||||
# built-in GCC marcos
|
||||
# if env.GetCompilerType() == "gcc":
|
||||
# defines.extend(get_gcc_defines(env))
|
||||
|
||||
return defines
|
||||
|
||||
|
||||
def dump_svd_path(env):
|
||||
svd_path = env.GetProjectOption("debug_svd_path")
|
||||
if svd_path:
|
||||
return os.path.abspath(svd_path)
|
||||
|
||||
if "BOARD" not in env:
|
||||
return None
|
||||
try:
|
||||
svd_path = env.BoardConfig().get("debug.svd_path")
|
||||
assert svd_path
|
||||
except (AssertionError, KeyError):
|
||||
return None
|
||||
# custom path to SVD file
|
||||
if os.path.isfile(svd_path):
|
||||
return svd_path
|
||||
# default file from ./platform/misc/svd folder
|
||||
p = env.PioPlatform()
|
||||
if os.path.isfile(os.path.join(p.get_dir(), "misc", "svd", svd_path)):
|
||||
return os.path.abspath(os.path.join(p.get_dir(), "misc", "svd", svd_path))
|
||||
return None
|
||||
|
||||
|
||||
def _split_flags_string(env, s):
|
||||
args = env.subst_list(s, SCons.Subst.SUBST_CMD)[0]
|
||||
return [str(arg) for arg in args]
|
||||
|
||||
|
||||
def DumpIntegrationData(*args):
|
||||
projenv, globalenv = args[0:2] # pylint: disable=unbalanced-tuple-unpacking
|
||||
data = {
|
||||
"build_type": globalenv.GetBuildType(),
|
||||
"env_name": globalenv["PIOENV"],
|
||||
"libsource_dirs": [
|
||||
globalenv.subst(item) for item in globalenv.GetLibSourceDirs()
|
||||
],
|
||||
"defines": dump_defines(projenv),
|
||||
"includes": projenv.DumpIntegrationIncludes(),
|
||||
"cc_flags": _split_flags_string(projenv, "$CFLAGS $CCFLAGS $CPPFLAGS"),
|
||||
"cxx_flags": _split_flags_string(projenv, "$CXXFLAGS $CCFLAGS $CPPFLAGS"),
|
||||
"cc_path": where_is_program(
|
||||
globalenv.subst("$CC"), globalenv.subst("${ENV['PATH']}")
|
||||
),
|
||||
"cxx_path": where_is_program(
|
||||
globalenv.subst("$CXX"), globalenv.subst("${ENV['PATH']}")
|
||||
),
|
||||
"gdb_path": where_is_program(
|
||||
globalenv.subst("$GDB"), globalenv.subst("${ENV['PATH']}")
|
||||
),
|
||||
"prog_path": globalenv.subst("$PROGPATH"),
|
||||
"svd_path": dump_svd_path(globalenv),
|
||||
"compiler_type": globalenv.GetCompilerType(),
|
||||
"targets": globalenv.DumpTargets(),
|
||||
"extra": dict(
|
||||
flash_images=[
|
||||
{"offset": item[0], "path": globalenv.subst(item[1])}
|
||||
for item in globalenv.get("FLASH_EXTRA_IMAGES", [])
|
||||
]
|
||||
),
|
||||
}
|
||||
for key in ("IDE_EXTRA_DATA", "INTEGRATION_EXTRA_DATA"):
|
||||
data["extra"].update(globalenv.get(key, {}))
|
||||
return data
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env["IDE_EXTRA_DATA"] = {} # legacy support
|
||||
env["INTEGRATION_EXTRA_DATA"] = {}
|
||||
env.AddMethod(IsIntegrationDump)
|
||||
env.AddMethod(DumpIntegrationIncludes)
|
||||
env.AddMethod(DumpIntegrationData)
|
||||
return env
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,96 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
|
||||
from SCons.Platform import TempFileMunge # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Subst import quote_spaces # pylint: disable=import-error
|
||||
|
||||
from platformio.compat import IS_WINDOWS, hashlib_encode_data
|
||||
|
||||
# There are the next limits depending on a platform:
|
||||
# - Windows = 8192
|
||||
# - Unix = 131072
|
||||
# We need ~512 characters for compiler and temporary file paths
|
||||
MAX_LINE_LENGTH = (8192 if IS_WINDOWS else 131072) - 512
|
||||
|
||||
WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)")
|
||||
|
||||
|
||||
def tempfile_arg_esc_func(arg):
|
||||
arg = quote_spaces(arg)
|
||||
if not IS_WINDOWS:
|
||||
return arg
|
||||
# GCC requires double Windows slashes, let's use UNIX separator
|
||||
return WINPATHSEP_RE.sub(r"/\1", arg)
|
||||
|
||||
|
||||
def long_sources_hook(env, sources):
|
||||
_sources = str(sources).replace("\\", "/")
|
||||
if len(str(_sources)) < MAX_LINE_LENGTH:
|
||||
return sources
|
||||
|
||||
# fix space in paths
|
||||
data = []
|
||||
for line in _sources.split(".o "):
|
||||
line = line.strip()
|
||||
if not line.endswith(".o"):
|
||||
line += ".o"
|
||||
data.append('"%s"' % line)
|
||||
|
||||
return '@"%s"' % _file_long_data(env, " ".join(data))
|
||||
|
||||
|
||||
def _file_long_data(env, data):
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
tmp_file = os.path.join(
|
||||
build_dir, "longcmd-%s" % hashlib.md5(hashlib_encode_data(data)).hexdigest()
|
||||
)
|
||||
if os.path.isfile(tmp_file):
|
||||
return tmp_file
|
||||
with open(tmp_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(data)
|
||||
return tmp_file
|
||||
|
||||
|
||||
def exists(env):
|
||||
return "compiledb" not in COMMAND_LINE_TARGETS and not env.IsIntegrationDump()
|
||||
|
||||
|
||||
def generate(env):
|
||||
if not exists(env):
|
||||
return env
|
||||
kwargs = dict(
|
||||
_long_sources_hook=long_sources_hook,
|
||||
TEMPFILE=TempFileMunge,
|
||||
MAXLINELENGTH=MAX_LINE_LENGTH,
|
||||
TEMPFILEARGESCFUNC=tempfile_arg_esc_func,
|
||||
TEMPFILESUFFIX=".tmp",
|
||||
TEMPFILEDIR="$BUILD_DIR",
|
||||
)
|
||||
|
||||
for name in ("LINKCOM", "ASCOM", "ASPPCOM", "CCCOM", "CXXCOM"):
|
||||
kwargs[name] = "${TEMPFILE('%s','$%sSTR')}" % (env.get(name), name)
|
||||
|
||||
kwargs["ARCOM"] = env.get("ARCOM", "").replace(
|
||||
"$SOURCES", "${_long_sources_hook(__env__, SOURCES)}"
|
||||
)
|
||||
env.Replace(**kwargs)
|
||||
|
||||
return env
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.proc import exec_command
|
||||
|
||||
|
||||
@util.memoized()
|
||||
def GetCompilerType(env):
|
||||
if env.subst("$CC").endswith("-gcc"):
|
||||
return "gcc"
|
||||
try:
|
||||
sysenv = os.environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
result = exec_command([env.subst("$CC"), "-v"], env=sysenv)
|
||||
except OSError:
|
||||
return None
|
||||
if result["returncode"] != 0:
|
||||
return None
|
||||
output = "".join([result["out"], result["err"]]).lower()
|
||||
if "clang" in output and "LLVM" in output:
|
||||
return "clang"
|
||||
if "gcc" in output:
|
||||
return "gcc"
|
||||
return None
|
||||
|
||||
|
||||
def GetActualLDScript(env):
|
||||
def _lookup_in_ldpath(script):
|
||||
for d in env.get("LIBPATH", []):
|
||||
path = os.path.join(env.subst(d), script)
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
script = None
|
||||
script_in_next = False
|
||||
for f in env.get("LINKFLAGS", []):
|
||||
raw_script = None
|
||||
if f == "-T":
|
||||
script_in_next = True
|
||||
continue
|
||||
if script_in_next:
|
||||
script_in_next = False
|
||||
raw_script = f
|
||||
elif f.startswith("-Wl,-T"):
|
||||
raw_script = f[6:]
|
||||
else:
|
||||
continue
|
||||
script = env.subst(raw_script.replace('"', "").strip())
|
||||
if os.path.isfile(script):
|
||||
return script
|
||||
path = _lookup_in_ldpath(script)
|
||||
if path:
|
||||
return path
|
||||
|
||||
if script:
|
||||
sys.stderr.write(
|
||||
"Error: Could not find '%s' LD script in LDPATH '%s'\n"
|
||||
% (script, env.subst("$LIBPATH"))
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
if not script and "LDSCRIPT_PATH" in env:
|
||||
path = _lookup_in_ldpath(env["LDSCRIPT_PATH"])
|
||||
if path:
|
||||
return path
|
||||
|
||||
sys.stderr.write("Error: Could not find LD script\n")
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def ConfigureDebugTarget(env):
|
||||
def _cleanup_debug_flags(scope):
|
||||
if scope not in env:
|
||||
return
|
||||
unflags = ["-Os", "-g"]
|
||||
for level in [0, 1, 2, 3]:
|
||||
for flag in ("O", "g", "ggdb"):
|
||||
unflags.append("-%s%d" % (flag, level))
|
||||
env[scope] = [f for f in env.get(scope, []) if f not in unflags]
|
||||
|
||||
env.Append(CPPDEFINES=["__PLATFORMIO_BUILD_DEBUG__"])
|
||||
|
||||
for scope in ("ASFLAGS", "CCFLAGS", "LINKFLAGS"):
|
||||
_cleanup_debug_flags(scope)
|
||||
|
||||
debug_flags = env.ParseFlags(
|
||||
env.get("PIODEBUGFLAGS")
|
||||
if env.get("PIODEBUGFLAGS")
|
||||
and not env.GetProjectOptions(as_dict=True).get("debug_build_flags")
|
||||
else env.GetProjectOption("debug_build_flags")
|
||||
)
|
||||
|
||||
env.MergeFlags(debug_flags)
|
||||
optimization_flags = [
|
||||
f for f in debug_flags.get("CCFLAGS", []) if f.startswith(("-O", "-g"))
|
||||
]
|
||||
|
||||
if optimization_flags:
|
||||
env.AppendUnique(
|
||||
ASFLAGS=[
|
||||
# skip -O flags for assembler
|
||||
f
|
||||
for f in optimization_flags
|
||||
if f.startswith("-g")
|
||||
],
|
||||
LINKFLAGS=optimization_flags,
|
||||
)
|
||||
|
||||
|
||||
def GetExtraScripts(env, scope):
|
||||
items = []
|
||||
for item in env.GetProjectOption("extra_scripts", []):
|
||||
if scope == "post" and ":" not in item:
|
||||
items.append(item)
|
||||
elif item.startswith("%s:" % scope):
|
||||
items.append(item[len(scope) + 1 :])
|
||||
if not items:
|
||||
return items
|
||||
with fs.cd(env.subst("$PROJECT_DIR")):
|
||||
return [os.path.abspath(env.subst(item)) for item in items]
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(GetCompilerType)
|
||||
env.AddMethod(GetActualLDScript)
|
||||
env.AddMethod(ConfigureDebugTarget)
|
||||
env.AddMethod(GetExtraScripts)
|
||||
# bakward-compatibility with Zephyr build script
|
||||
env.AddMethod(ConfigureDebugTarget, "ConfigureDebugFlags")
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
from SCons.Script import DefaultEnvironment # pylint: disable=import-error
|
||||
|
||||
from platformio import fs, util
|
||||
from platformio.compat import IS_MACOS, IS_WINDOWS
|
||||
from platformio.package.meta import PackageItem
|
||||
from platformio.package.version import get_original_version
|
||||
from platformio.platform.exception import UnknownBoard
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
from platformio.project.config import ProjectOptions
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-locals
|
||||
|
||||
|
||||
@util.memoized()
|
||||
def _PioPlatform():
|
||||
env = DefaultEnvironment()
|
||||
return PlatformFactory.from_env(env["PIOENV"], targets=COMMAND_LINE_TARGETS)
|
||||
|
||||
|
||||
def PioPlatform(_):
|
||||
return _PioPlatform()
|
||||
|
||||
|
||||
def BoardConfig(env, board=None):
|
||||
with fs.cd(env.subst("$PROJECT_DIR")):
|
||||
try:
|
||||
p = env.PioPlatform()
|
||||
board = board or env.get("BOARD")
|
||||
assert board, "BoardConfig: Board is not defined"
|
||||
return p.board_config(board)
|
||||
except (AssertionError, UnknownBoard) as exc:
|
||||
sys.stderr.write("Error: %s\n" % str(exc))
|
||||
env.Exit(1)
|
||||
return None
|
||||
|
||||
|
||||
def GetFrameworkScript(env, framework):
|
||||
p = env.PioPlatform()
|
||||
assert p.frameworks and framework in p.frameworks
|
||||
script_path = env.subst(p.frameworks[framework]["script"])
|
||||
if not os.path.isfile(script_path):
|
||||
script_path = os.path.join(p.get_dir(), script_path)
|
||||
return script_path
|
||||
|
||||
|
||||
def LoadPioPlatform(env):
|
||||
p = env.PioPlatform()
|
||||
|
||||
# Ensure real platform name
|
||||
env["PIOPLATFORM"] = p.name
|
||||
|
||||
# Add toolchains and uploaders to $PATH and $*_LIBRARY_PATH
|
||||
for pkg in p.get_installed_packages():
|
||||
type_ = p.get_package_type(pkg.metadata.name)
|
||||
if type_ not in ("toolchain", "uploader", "debugger"):
|
||||
continue
|
||||
env.PrependENVPath(
|
||||
"PATH",
|
||||
os.path.join(pkg.path, "bin")
|
||||
if os.path.isdir(os.path.join(pkg.path, "bin"))
|
||||
else pkg.path,
|
||||
)
|
||||
if (
|
||||
not IS_WINDOWS
|
||||
and os.path.isdir(os.path.join(pkg.path, "lib"))
|
||||
and type_ != "toolchain"
|
||||
):
|
||||
env.PrependENVPath(
|
||||
"DYLD_LIBRARY_PATH" if IS_MACOS else "LD_LIBRARY_PATH",
|
||||
os.path.join(pkg.path, "lib"),
|
||||
)
|
||||
|
||||
# Platform specific LD Scripts
|
||||
if os.path.isdir(os.path.join(p.get_dir(), "ldscripts")):
|
||||
env.Prepend(LIBPATH=[os.path.join(p.get_dir(), "ldscripts")])
|
||||
|
||||
if "BOARD" not in env:
|
||||
return
|
||||
|
||||
# update board manifest with overridden data from INI config
|
||||
board_config = env.BoardConfig()
|
||||
for option, value in env.GetProjectOptions():
|
||||
if not option.startswith("board_"):
|
||||
continue
|
||||
option = option.lower()[6:]
|
||||
try:
|
||||
if isinstance(board_config.get(option), bool):
|
||||
value = str(value).lower() in ("1", "yes", "true")
|
||||
elif isinstance(board_config.get(option), int):
|
||||
value = int(value)
|
||||
except KeyError:
|
||||
pass
|
||||
board_config.update(option, value)
|
||||
|
||||
# load default variables from board config
|
||||
for option_meta in ProjectOptions.values():
|
||||
if not option_meta.buildenvvar or option_meta.buildenvvar in env:
|
||||
continue
|
||||
data_path = (
|
||||
option_meta.name[6:]
|
||||
if option_meta.name.startswith("board_")
|
||||
else option_meta.name.replace("_", ".")
|
||||
)
|
||||
try:
|
||||
env[option_meta.buildenvvar] = board_config.get(data_path)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if "build.ldscript" in board_config:
|
||||
env.Replace(LDSCRIPT_PATH=board_config.get("build.ldscript"))
|
||||
|
||||
|
||||
def PrintConfiguration(env): # pylint: disable=too-many-statements
|
||||
platform = env.PioPlatform()
|
||||
pkg_metadata = PackageItem(platform.get_dir()).metadata
|
||||
board_config = env.BoardConfig() if "BOARD" in env else None
|
||||
|
||||
def _get_configuration_data():
|
||||
return (
|
||||
None
|
||||
if not board_config
|
||||
else [
|
||||
"CONFIGURATION:",
|
||||
"https://docs.platformio.org/page/boards/%s/%s.html"
|
||||
% (platform.name, board_config.id),
|
||||
]
|
||||
)
|
||||
|
||||
def _get_plaform_data():
|
||||
data = [
|
||||
"PLATFORM: %s (%s)"
|
||||
% (
|
||||
platform.title,
|
||||
pkg_metadata.version if pkg_metadata else platform.version,
|
||||
)
|
||||
]
|
||||
if (
|
||||
int(ARGUMENTS.get("PIOVERBOSE", 0))
|
||||
and pkg_metadata
|
||||
and pkg_metadata.spec.external
|
||||
):
|
||||
data.append("(%s)" % pkg_metadata.spec.uri)
|
||||
if board_config:
|
||||
data.extend([">", board_config.get("name")])
|
||||
return data
|
||||
|
||||
def _get_hardware_data():
|
||||
data = ["HARDWARE:"]
|
||||
mcu = env.subst("$BOARD_MCU")
|
||||
f_cpu = env.subst("$BOARD_F_CPU")
|
||||
if mcu:
|
||||
data.append(mcu.upper())
|
||||
if f_cpu:
|
||||
f_cpu = int("".join([c for c in str(f_cpu) if c.isdigit()]))
|
||||
data.append("%dMHz," % (f_cpu / 1000000))
|
||||
if not board_config:
|
||||
return data
|
||||
ram = board_config.get("upload", {}).get("maximum_ram_size")
|
||||
flash = board_config.get("upload", {}).get("maximum_size")
|
||||
data.append(
|
||||
"%s RAM, %s Flash"
|
||||
% (fs.humanize_file_size(ram), fs.humanize_file_size(flash))
|
||||
)
|
||||
return data
|
||||
|
||||
def _get_debug_data():
|
||||
debug_tools = (
|
||||
board_config.get("debug", {}).get("tools") if board_config else None
|
||||
)
|
||||
if not debug_tools:
|
||||
return None
|
||||
data = [
|
||||
"DEBUG:",
|
||||
"Current",
|
||||
"(%s)"
|
||||
% board_config.get_debug_tool_name(env.GetProjectOption("debug_tool")),
|
||||
]
|
||||
onboard = []
|
||||
external = []
|
||||
for key, value in debug_tools.items():
|
||||
if value.get("onboard"):
|
||||
onboard.append(key)
|
||||
else:
|
||||
external.append(key)
|
||||
if onboard:
|
||||
data.extend(["On-board", "(%s)" % ", ".join(sorted(onboard))])
|
||||
if external:
|
||||
data.extend(["External", "(%s)" % ", ".join(sorted(external))])
|
||||
return data
|
||||
|
||||
def _get_packages_data():
|
||||
data = []
|
||||
for item in platform.dump_used_packages():
|
||||
original_version = get_original_version(item["version"])
|
||||
info = "%s @ %s" % (item["name"], item["version"])
|
||||
extra = []
|
||||
if original_version:
|
||||
extra.append(original_version)
|
||||
if "src_url" in item and int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
extra.append(item["src_url"])
|
||||
if extra:
|
||||
info += " (%s)" % ", ".join(extra)
|
||||
data.append(info)
|
||||
if not data:
|
||||
return None
|
||||
return ["PACKAGES:"] + ["\n - %s" % d for d in sorted(data)]
|
||||
|
||||
for data in (
|
||||
_get_configuration_data(),
|
||||
_get_plaform_data(),
|
||||
_get_hardware_data(),
|
||||
_get_debug_data(),
|
||||
_get_packages_data(),
|
||||
):
|
||||
if data and len(data) > 1:
|
||||
print(" ".join(data))
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(PioPlatform)
|
||||
env.AddMethod(BoardConfig)
|
||||
env.AddMethod(GetFrameworkScript)
|
||||
env.AddMethod(LoadPioPlatform)
|
||||
env.AddMethod(PrintConfiguration)
|
||||
return env
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.compat import MISSING
|
||||
from platformio.project.config import ProjectConfig
|
||||
|
||||
|
||||
def GetProjectConfig(env):
|
||||
return ProjectConfig.get_instance(env["PROJECT_CONFIG"])
|
||||
|
||||
|
||||
def GetProjectOptions(env, as_dict=False):
|
||||
return env.GetProjectConfig().items(env=env["PIOENV"], as_dict=as_dict)
|
||||
|
||||
|
||||
def GetProjectOption(env, option, default=MISSING):
|
||||
return env.GetProjectConfig().get("env:" + env["PIOENV"], option, default)
|
||||
|
||||
|
||||
def LoadProjectOptions(env):
|
||||
config = env.GetProjectConfig()
|
||||
section = "env:" + env["PIOENV"]
|
||||
for option in config.options(section):
|
||||
option_meta = config.find_option_meta(section, option)
|
||||
if (
|
||||
not option_meta
|
||||
or not option_meta.buildenvvar
|
||||
or option_meta.buildenvvar in env
|
||||
):
|
||||
continue
|
||||
env[option_meta.buildenvvar] = config.get(section, option)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(GetProjectConfig)
|
||||
env.AddMethod(GetProjectOptions)
|
||||
env.AddMethod(GetProjectOption)
|
||||
env.AddMethod(LoadProjectOptions)
|
||||
return env
|
||||
|
|
@ -0,0 +1,266 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
import json
|
||||
import sys
|
||||
from os import environ, makedirs, remove
|
||||
from os.path import isdir, join, splitdrive
|
||||
|
||||
from elftools.elf.descriptions import describe_sh_flags
|
||||
from elftools.elf.elffile import ELFFile
|
||||
|
||||
from platformio.compat import IS_WINDOWS
|
||||
from platformio.proc import exec_command
|
||||
|
||||
|
||||
def _run_tool(cmd, env, tool_args):
|
||||
sysenv = environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
if not isdir(build_dir):
|
||||
makedirs(build_dir)
|
||||
tmp_file = join(build_dir, "size-data-longcmd.txt")
|
||||
|
||||
with open(tmp_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write("\n".join(tool_args))
|
||||
|
||||
cmd.append("@" + tmp_file)
|
||||
result = exec_command(cmd, env=sysenv)
|
||||
remove(tmp_file)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _get_symbol_locations(env, elf_path, addrs):
|
||||
if not addrs:
|
||||
return {}
|
||||
cmd = [env.subst("$CC").replace("-gcc", "-addr2line"), "-e", elf_path]
|
||||
result = _run_tool(cmd, env, addrs)
|
||||
locations = [line for line in result["out"].split("\n") if line]
|
||||
assert len(addrs) == len(locations)
|
||||
|
||||
return dict(zip(addrs, [loc.strip() for loc in locations]))
|
||||
|
||||
|
||||
def _get_demangled_names(env, mangled_names):
|
||||
if not mangled_names:
|
||||
return {}
|
||||
result = _run_tool(
|
||||
[env.subst("$CC").replace("-gcc", "-c++filt")], env, mangled_names
|
||||
)
|
||||
demangled_names = [line for line in result["out"].split("\n") if line]
|
||||
assert len(mangled_names) == len(demangled_names)
|
||||
|
||||
return dict(
|
||||
zip(
|
||||
mangled_names,
|
||||
[dn.strip().replace("::__FUNCTION__", "") for dn in demangled_names],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _collect_sections_info(env, elffile):
|
||||
sections = {}
|
||||
for section in elffile.iter_sections():
|
||||
if section.is_null() or section.name.startswith(".debug"):
|
||||
continue
|
||||
|
||||
section_type = section["sh_type"]
|
||||
section_flags = describe_sh_flags(section["sh_flags"])
|
||||
section_size = section.data_size
|
||||
|
||||
section_data = {
|
||||
"name": section.name,
|
||||
"size": section_size,
|
||||
"start_addr": section["sh_addr"],
|
||||
"type": section_type,
|
||||
"flags": section_flags,
|
||||
}
|
||||
|
||||
sections[section.name] = section_data
|
||||
sections[section.name]["in_flash"] = env.pioSizeIsFlashSection(section_data)
|
||||
sections[section.name]["in_ram"] = env.pioSizeIsRamSection(section_data)
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
def _collect_symbols_info(env, elffile, elf_path, sections):
|
||||
symbols = []
|
||||
|
||||
symbol_section = elffile.get_section_by_name(".symtab")
|
||||
if symbol_section.is_null():
|
||||
sys.stderr.write("Couldn't find symbol table. Is ELF file stripped?")
|
||||
env.Exit(1)
|
||||
|
||||
sysenv = environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
|
||||
symbol_addrs = []
|
||||
mangled_names = []
|
||||
for s in symbol_section.iter_symbols():
|
||||
symbol_info = s.entry["st_info"]
|
||||
symbol_addr = s["st_value"]
|
||||
symbol_size = s["st_size"]
|
||||
symbol_type = symbol_info["type"]
|
||||
|
||||
if not env.pioSizeIsValidSymbol(s.name, symbol_type, symbol_addr):
|
||||
continue
|
||||
|
||||
symbol = {
|
||||
"addr": symbol_addr,
|
||||
"bind": symbol_info["bind"],
|
||||
"name": s.name,
|
||||
"type": symbol_type,
|
||||
"size": symbol_size,
|
||||
"section": env.pioSizeDetermineSection(sections, symbol_addr),
|
||||
}
|
||||
|
||||
if s.name.startswith("_Z"):
|
||||
mangled_names.append(s.name)
|
||||
|
||||
symbol_addrs.append(hex(symbol_addr))
|
||||
symbols.append(symbol)
|
||||
|
||||
symbol_locations = _get_symbol_locations(env, elf_path, symbol_addrs)
|
||||
demangled_names = _get_demangled_names(env, mangled_names)
|
||||
for symbol in symbols:
|
||||
if symbol["name"].startswith("_Z"):
|
||||
symbol["demangled_name"] = demangled_names.get(symbol["name"])
|
||||
location = symbol_locations.get(hex(symbol["addr"]))
|
||||
if not location or "?" in location:
|
||||
continue
|
||||
if IS_WINDOWS:
|
||||
drive, tail = splitdrive(location)
|
||||
location = join(drive.upper(), tail)
|
||||
symbol["file"] = location
|
||||
symbol["line"] = 0
|
||||
if ":" in location:
|
||||
file_, line = location.rsplit(":", 1)
|
||||
if line.isdigit():
|
||||
symbol["file"] = file_
|
||||
symbol["line"] = int(line)
|
||||
return symbols
|
||||
|
||||
|
||||
def pioSizeDetermineSection(_, sections, symbol_addr):
|
||||
for section, info in sections.items():
|
||||
if not info.get("in_flash", False) and not info.get("in_ram", False):
|
||||
continue
|
||||
if symbol_addr in range(info["start_addr"], info["start_addr"] + info["size"]):
|
||||
return section
|
||||
return "unknown"
|
||||
|
||||
|
||||
def pioSizeIsValidSymbol(_, symbol_name, symbol_type, symbol_address):
|
||||
return symbol_name and symbol_address != 0 and symbol_type != "STT_NOTYPE"
|
||||
|
||||
|
||||
def pioSizeIsRamSection(_, section):
|
||||
return (
|
||||
section.get("type", "") in ("SHT_NOBITS", "SHT_PROGBITS")
|
||||
and section.get("flags", "") == "WA"
|
||||
)
|
||||
|
||||
|
||||
def pioSizeIsFlashSection(_, section):
|
||||
return section.get("type", "") == "SHT_PROGBITS" and "A" in section.get("flags", "")
|
||||
|
||||
|
||||
def pioSizeCalculateFirmwareSize(_, sections):
|
||||
flash_size = ram_size = 0
|
||||
for section_info in sections.values():
|
||||
if section_info.get("in_flash", False):
|
||||
flash_size += section_info.get("size", 0)
|
||||
if section_info.get("in_ram", False):
|
||||
ram_size += section_info.get("size", 0)
|
||||
|
||||
return ram_size, flash_size
|
||||
|
||||
|
||||
def DumpSizeData(_, target, source, env): # pylint: disable=unused-argument
|
||||
data = {"device": {}, "memory": {}, "version": 1}
|
||||
|
||||
board = env.BoardConfig()
|
||||
if board:
|
||||
data["device"] = {
|
||||
"mcu": board.get("build.mcu", ""),
|
||||
"cpu": board.get("build.cpu", ""),
|
||||
"frequency": board.get("build.f_cpu"),
|
||||
"flash": int(board.get("upload.maximum_size", 0)),
|
||||
"ram": int(board.get("upload.maximum_ram_size", 0)),
|
||||
}
|
||||
if data["device"]["frequency"] and data["device"]["frequency"].endswith("L"):
|
||||
data["device"]["frequency"] = int(data["device"]["frequency"][0:-1])
|
||||
|
||||
elf_path = env.subst("$PIOMAINPROG")
|
||||
|
||||
with open(elf_path, "rb") as fp:
|
||||
elffile = ELFFile(fp)
|
||||
|
||||
if not elffile.has_dwarf_info():
|
||||
sys.stderr.write("Elf file doesn't contain DWARF information")
|
||||
env.Exit(1)
|
||||
|
||||
sections = _collect_sections_info(env, elffile)
|
||||
firmware_ram, firmware_flash = env.pioSizeCalculateFirmwareSize(sections)
|
||||
data["memory"]["total"] = {
|
||||
"ram_size": firmware_ram,
|
||||
"flash_size": firmware_flash,
|
||||
"sections": sections,
|
||||
}
|
||||
|
||||
files = {}
|
||||
for symbol in _collect_symbols_info(env, elffile, elf_path, sections):
|
||||
file_path = symbol.get("file") or "unknown"
|
||||
if not files.get(file_path, {}):
|
||||
files[file_path] = {"symbols": [], "ram_size": 0, "flash_size": 0}
|
||||
|
||||
symbol_size = symbol.get("size", 0)
|
||||
section = sections.get(symbol.get("section", ""), {})
|
||||
if not section:
|
||||
continue
|
||||
if section.get("in_ram", False):
|
||||
files[file_path]["ram_size"] += symbol_size
|
||||
if section.get("in_flash", False):
|
||||
files[file_path]["flash_size"] += symbol_size
|
||||
|
||||
files[file_path]["symbols"].append(symbol)
|
||||
|
||||
data["memory"]["files"] = []
|
||||
for k, v in files.items():
|
||||
file_data = {"path": k}
|
||||
file_data.update(v)
|
||||
data["memory"]["files"].append(file_data)
|
||||
|
||||
with open(
|
||||
join(env.subst("$BUILD_DIR"), "sizedata.json"), mode="w", encoding="utf8"
|
||||
) as fp:
|
||||
fp.write(json.dumps(data))
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(pioSizeIsRamSection)
|
||||
env.AddMethod(pioSizeIsFlashSection)
|
||||
env.AddMethod(pioSizeCalculateFirmwareSize)
|
||||
env.AddMethod(pioSizeDetermineSection)
|
||||
env.AddMethod(pioSizeIsValidSymbol)
|
||||
env.AddMethod(DumpSizeData)
|
||||
return env
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
from SCons.Action import Action # pylint: disable=import-error
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from SCons.Script import AlwaysBuild # pylint: disable=import-error
|
||||
|
||||
from platformio import compat, fs
|
||||
|
||||
|
||||
def VerboseAction(_, act, actstr):
|
||||
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
return act
|
||||
return Action(act, actstr)
|
||||
|
||||
|
||||
def IsCleanTarget(env):
|
||||
return env.GetOption("clean")
|
||||
|
||||
|
||||
def CleanProject(env, fullclean=False):
|
||||
def _relpath(path):
|
||||
if compat.IS_WINDOWS:
|
||||
prefix = os.getcwd()[:2].lower()
|
||||
if (
|
||||
":" not in prefix
|
||||
or not path.lower().startswith(prefix)
|
||||
or os.path.relpath(path).startswith("..")
|
||||
):
|
||||
return path
|
||||
return os.path.relpath(path)
|
||||
|
||||
def _clean_dir(path):
|
||||
clean_rel_path = _relpath(path)
|
||||
print(f"Removing {clean_rel_path}")
|
||||
fs.rmtree(path)
|
||||
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
libdeps_dir = env.subst(os.path.join("$PROJECT_LIBDEPS_DIR", "$PIOENV"))
|
||||
if os.path.isdir(build_dir):
|
||||
_clean_dir(build_dir)
|
||||
else:
|
||||
print("Build environment is clean")
|
||||
|
||||
if fullclean and os.path.isdir(libdeps_dir):
|
||||
_clean_dir(libdeps_dir)
|
||||
|
||||
print("Done cleaning")
|
||||
|
||||
|
||||
def AddTarget( # pylint: disable=too-many-arguments
|
||||
env,
|
||||
name,
|
||||
dependencies,
|
||||
actions,
|
||||
title=None,
|
||||
description=None,
|
||||
group="General",
|
||||
always_build=True,
|
||||
):
|
||||
if "__PIO_TARGETS" not in env:
|
||||
env["__PIO_TARGETS"] = {}
|
||||
assert name not in env["__PIO_TARGETS"]
|
||||
env["__PIO_TARGETS"][name] = dict(
|
||||
name=name, title=title, description=description, group=group
|
||||
)
|
||||
target = env.Alias(name, dependencies, actions)
|
||||
if always_build:
|
||||
AlwaysBuild(target)
|
||||
return target
|
||||
|
||||
|
||||
def AddPlatformTarget(env, *args, **kwargs):
|
||||
return env.AddTarget(group="Platform", *args, **kwargs)
|
||||
|
||||
|
||||
def AddCustomTarget(env, *args, **kwargs):
|
||||
return env.AddTarget(group="Custom", *args, **kwargs)
|
||||
|
||||
|
||||
def DumpTargets(env):
|
||||
targets = env.get("__PIO_TARGETS") or {}
|
||||
# pre-fill default targets if embedded dev-platform
|
||||
if env.PioPlatform().is_embedded() and not any(
|
||||
t["group"] == "Platform" for t in targets.values()
|
||||
):
|
||||
targets["upload"] = dict(name="upload", group="Platform", title="Upload")
|
||||
return list(targets.values())
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(VerboseAction)
|
||||
env.AddMethod(IsCleanTarget)
|
||||
env.AddMethod(CleanProject)
|
||||
env.AddMethod(AddTarget)
|
||||
env.AddMethod(AddPlatformTarget)
|
||||
env.AddMethod(AddCustomTarget)
|
||||
env.AddMethod(DumpTargets)
|
||||
return env
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
from platformio.builder.tools import piobuild
|
||||
from platformio.test.result import TestSuite
|
||||
from platformio.test.runners.factory import TestRunnerFactory
|
||||
|
||||
|
||||
def ConfigureTestTarget(env):
|
||||
env.Append(
|
||||
CPPDEFINES=["UNIT_TEST"], # deprecated, use PIO_UNIT_TESTING
|
||||
PIOTEST_SRC_FILTER=[f"+<*.{ext}>" for ext in piobuild.SRC_BUILD_EXT],
|
||||
)
|
||||
env.Prepend(CPPPATH=["$PROJECT_TEST_DIR"])
|
||||
|
||||
if "PIOTEST_RUNNING_NAME" in env:
|
||||
test_name = env["PIOTEST_RUNNING_NAME"]
|
||||
while True:
|
||||
test_name = os.path.dirname(test_name) # parent dir
|
||||
# skip nested tests (user's side issue?)
|
||||
if not test_name or os.path.basename(test_name).startswith("test_"):
|
||||
break
|
||||
env.Prepend(
|
||||
PIOTEST_SRC_FILTER=[
|
||||
f"+<{test_name}{os.path.sep}*.{ext}>"
|
||||
for ext in piobuild.SRC_BUILD_EXT
|
||||
],
|
||||
CPPPATH=[os.path.join("$PROJECT_TEST_DIR", test_name)],
|
||||
)
|
||||
|
||||
env.Prepend(
|
||||
PIOTEST_SRC_FILTER=[f"+<$PIOTEST_RUNNING_NAME{os.path.sep}>"],
|
||||
CPPPATH=[os.path.join("$PROJECT_TEST_DIR", "$PIOTEST_RUNNING_NAME")],
|
||||
)
|
||||
|
||||
test_runner = TestRunnerFactory.new(
|
||||
TestSuite(env["PIOENV"], env.get("PIOTEST_RUNNING_NAME", "*")),
|
||||
env.GetProjectConfig(),
|
||||
)
|
||||
test_runner.configure_build_env(env)
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(ConfigureTestTarget)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from shutil import copyfile
|
||||
from time import sleep
|
||||
|
||||
from SCons.Script import ARGUMENTS # pylint: disable=import-error
|
||||
from serial import Serial, SerialException
|
||||
|
||||
from platformio import exception, fs
|
||||
from platformio.device.finder import SerialPortFinder, find_mbed_disk, is_pattern_port
|
||||
from platformio.device.list.util import list_serial_ports
|
||||
from platformio.proc import exec_command
|
||||
|
||||
|
||||
def FlushSerialBuffer(env, port):
|
||||
s = Serial(env.subst(port))
|
||||
s.flushInput()
|
||||
s.setDTR(False)
|
||||
s.setRTS(False)
|
||||
sleep(0.1)
|
||||
s.setDTR(True)
|
||||
s.setRTS(True)
|
||||
s.close()
|
||||
|
||||
|
||||
def TouchSerialPort(env, port, baudrate):
|
||||
port = env.subst(port)
|
||||
print("Forcing reset using %dbps open/close on port %s" % (baudrate, port))
|
||||
try:
|
||||
s = Serial(port=port, baudrate=baudrate)
|
||||
s.setDTR(False)
|
||||
s.close()
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
sleep(0.4) # DO NOT REMOVE THAT (required by SAM-BA based boards)
|
||||
|
||||
|
||||
def WaitForNewSerialPort(env, before):
|
||||
print("Waiting for the new upload port...")
|
||||
prev_port = env.subst("$UPLOAD_PORT")
|
||||
new_port = None
|
||||
elapsed = 0
|
||||
before = [p["port"] for p in before]
|
||||
while elapsed < 5 and new_port is None:
|
||||
now = [p["port"] for p in list_serial_ports()]
|
||||
for p in now:
|
||||
if p not in before:
|
||||
new_port = p
|
||||
break
|
||||
before = now
|
||||
sleep(0.25)
|
||||
elapsed += 0.25
|
||||
|
||||
if not new_port:
|
||||
for p in now:
|
||||
if prev_port == p:
|
||||
new_port = p
|
||||
break
|
||||
|
||||
try:
|
||||
s = Serial(new_port)
|
||||
s.close()
|
||||
except SerialException:
|
||||
sleep(1)
|
||||
|
||||
if not new_port:
|
||||
sys.stderr.write(
|
||||
"Error: Couldn't find a board on the selected port. "
|
||||
"Check that you have the correct port selected. "
|
||||
"If it is correct, try pressing the board's reset "
|
||||
"button after initiating the upload.\n"
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
return new_port
|
||||
|
||||
|
||||
def AutodetectUploadPort(*args, **kwargs):
|
||||
env = args[0]
|
||||
initial_port = env.subst("$UPLOAD_PORT")
|
||||
upload_protocol = env.subst("$UPLOAD_PROTOCOL")
|
||||
if initial_port and not is_pattern_port(initial_port):
|
||||
print(env.subst("Using manually specified: $UPLOAD_PORT"))
|
||||
return
|
||||
|
||||
if upload_protocol == "mbed" or (
|
||||
"mbed" in env.subst("$PIOFRAMEWORK") and not upload_protocol
|
||||
):
|
||||
env.Replace(UPLOAD_PORT=find_mbed_disk(initial_port))
|
||||
else:
|
||||
try:
|
||||
fs.ensure_udev_rules()
|
||||
except exception.InvalidUdevRules as exc:
|
||||
sys.stderr.write("\n%s\n\n" % exc)
|
||||
env.Replace(
|
||||
UPLOAD_PORT=SerialPortFinder(
|
||||
board_config=env.BoardConfig() if "BOARD" in env else None,
|
||||
upload_protocol=upload_protocol,
|
||||
prefer_gdb_port="blackmagic" in upload_protocol,
|
||||
verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)),
|
||||
).find(initial_port)
|
||||
)
|
||||
|
||||
if env.subst("$UPLOAD_PORT"):
|
||||
print(env.subst("Auto-detected: $UPLOAD_PORT"))
|
||||
else:
|
||||
sys.stderr.write(
|
||||
"Error: Please specify `upload_port` for environment or use "
|
||||
"global `--upload-port` option.\n"
|
||||
"For some development platforms it can be a USB flash "
|
||||
"drive (i.e. /media/<user>/<device name>)\n"
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def UploadToDisk(_, target, source, env):
|
||||
assert "UPLOAD_PORT" in env
|
||||
progname = env.subst("$PROGNAME")
|
||||
for ext in ("bin", "hex"):
|
||||
fpath = os.path.join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext))
|
||||
if not os.path.isfile(fpath):
|
||||
continue
|
||||
copyfile(
|
||||
fpath, os.path.join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext))
|
||||
)
|
||||
print(
|
||||
"Firmware has been successfully uploaded.\n"
|
||||
"(Some boards may require manual hard reset)"
|
||||
)
|
||||
|
||||
|
||||
def CheckUploadSize(_, target, source, env):
|
||||
check_conditions = [
|
||||
env.get("BOARD"),
|
||||
env.get("SIZETOOL") or env.get("SIZECHECKCMD"),
|
||||
]
|
||||
if not all(check_conditions):
|
||||
return
|
||||
program_max_size = int(env.BoardConfig().get("upload.maximum_size", 0))
|
||||
data_max_size = int(env.BoardConfig().get("upload.maximum_ram_size", 0))
|
||||
if program_max_size == 0:
|
||||
return
|
||||
|
||||
def _configure_defaults():
|
||||
env.Replace(
|
||||
SIZECHECKCMD="$SIZETOOL -B -d $SOURCES",
|
||||
SIZEPROGREGEXP=r"^(\d+)\s+(\d+)\s+\d+\s",
|
||||
SIZEDATAREGEXP=r"^\d+\s+(\d+)\s+(\d+)\s+\d+",
|
||||
)
|
||||
|
||||
def _get_size_output():
|
||||
cmd = env.get("SIZECHECKCMD")
|
||||
if not cmd:
|
||||
return None
|
||||
if not isinstance(cmd, list):
|
||||
cmd = cmd.split()
|
||||
cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg]
|
||||
sysenv = os.environ.copy()
|
||||
sysenv["PATH"] = str(env["ENV"]["PATH"])
|
||||
result = exec_command(env.subst(cmd), env=sysenv)
|
||||
if result["returncode"] != 0:
|
||||
return None
|
||||
return result["out"].strip()
|
||||
|
||||
def _calculate_size(output, pattern):
|
||||
if not output or not pattern:
|
||||
return -1
|
||||
size = 0
|
||||
regexp = re.compile(pattern)
|
||||
for line in output.split("\n"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
match = regexp.search(line)
|
||||
if not match:
|
||||
continue
|
||||
size += sum(int(value) for value in match.groups())
|
||||
return size
|
||||
|
||||
def _format_availale_bytes(value, total):
|
||||
percent_raw = float(value) / float(total)
|
||||
blocks_per_progress = 10
|
||||
used_blocks = min(
|
||||
int(round(blocks_per_progress * percent_raw)), blocks_per_progress
|
||||
)
|
||||
return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format(
|
||||
"=" * used_blocks, blocks_per_progress, percent_raw, value, total
|
||||
)
|
||||
|
||||
if not env.get("SIZECHECKCMD") and not env.get("SIZEPROGREGEXP"):
|
||||
_configure_defaults()
|
||||
output = _get_size_output()
|
||||
program_size = _calculate_size(output, env.get("SIZEPROGREGEXP"))
|
||||
data_size = _calculate_size(output, env.get("SIZEDATAREGEXP"))
|
||||
|
||||
print('Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"')
|
||||
if data_max_size and data_size > -1:
|
||||
print("RAM: %s" % _format_availale_bytes(data_size, data_max_size))
|
||||
if program_size > -1:
|
||||
print("Flash: %s" % _format_availale_bytes(program_size, program_max_size))
|
||||
if int(ARGUMENTS.get("PIOVERBOSE", 0)):
|
||||
print(output)
|
||||
|
||||
# raise error
|
||||
# if data_max_size and data_size > data_max_size:
|
||||
# sys.stderr.write(
|
||||
# "Error: The data size (%d bytes) is greater "
|
||||
# "than maximum allowed (%s bytes)\n" % (data_size, data_max_size))
|
||||
# env.Exit(1)
|
||||
if program_size > program_max_size:
|
||||
sys.stderr.write(
|
||||
"Error: The program size (%d bytes) is greater "
|
||||
"than maximum allowed (%s bytes)\n" % (program_size, program_max_size)
|
||||
)
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def PrintUploadInfo(env):
|
||||
configured = env.subst("$UPLOAD_PROTOCOL")
|
||||
available = [configured] if configured else []
|
||||
if "BOARD" in env:
|
||||
available.extend(env.BoardConfig().get("upload", {}).get("protocols", []))
|
||||
if available:
|
||||
print("AVAILABLE: %s" % ", ".join(sorted(set(available))))
|
||||
if configured:
|
||||
print("CURRENT: upload_protocol = %s" % configured)
|
||||
|
||||
|
||||
def exists(_):
|
||||
return True
|
||||
|
||||
|
||||
def generate(env):
|
||||
env.AddMethod(FlushSerialBuffer)
|
||||
env.AddMethod(TouchSerialPort)
|
||||
env.AddMethod(WaitForNewSerialPort)
|
||||
env.AddMethod(AutodetectUploadPort)
|
||||
env.AddMethod(UploadToDisk)
|
||||
env.AddMethod(CheckUploadSize)
|
||||
env.AddMethod(PrintUploadInfo)
|
||||
return env
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import hashlib
|
||||
import os
|
||||
from time import time
|
||||
|
||||
from platformio import app, fs
|
||||
from platformio.compat import hashlib_encode_data
|
||||
from platformio.package.lockfile import LockFile
|
||||
from platformio.project.helpers import get_project_cache_dir
|
||||
|
||||
|
||||
class ContentCache:
|
||||
def __init__(self, namespace=None):
|
||||
self.cache_dir = os.path.join(get_project_cache_dir(), namespace or "content")
|
||||
self._db_path = os.path.join(self.cache_dir, "db.data")
|
||||
self._lockfile = None
|
||||
if not os.path.isdir(self.cache_dir):
|
||||
os.makedirs(self.cache_dir)
|
||||
|
||||
def __enter__(self):
|
||||
# cleanup obsolete items
|
||||
self.delete()
|
||||
return self
|
||||
|
||||
def __exit__(self, type_, value, traceback):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def key_from_args(*args):
|
||||
h = hashlib.sha1()
|
||||
for arg in args:
|
||||
if arg:
|
||||
h.update(hashlib_encode_data(arg))
|
||||
return h.hexdigest()
|
||||
|
||||
def get_cache_path(self, key):
|
||||
assert "/" not in key and "\\" not in key
|
||||
key = str(key)
|
||||
assert len(key) > 3
|
||||
return os.path.join(self.cache_dir, key)
|
||||
|
||||
def get(self, key):
|
||||
cache_path = self.get_cache_path(key)
|
||||
if not os.path.isfile(cache_path):
|
||||
return None
|
||||
with codecs.open(cache_path, "rb", encoding="utf8") as fp:
|
||||
return fp.read()
|
||||
|
||||
def set(self, key, data, valid):
|
||||
if not app.get_setting("enable_cache"):
|
||||
return False
|
||||
cache_path = self.get_cache_path(key)
|
||||
if os.path.isfile(cache_path):
|
||||
self.delete(key)
|
||||
if not data:
|
||||
return False
|
||||
tdmap = {"s": 1, "m": 60, "h": 3600, "d": 86400}
|
||||
assert valid.endswith(tuple(tdmap))
|
||||
expire_time = int(time() + tdmap[valid[-1]] * int(valid[:-1]))
|
||||
|
||||
if not self._lock_dbindex():
|
||||
return False
|
||||
|
||||
if not os.path.isdir(os.path.dirname(cache_path)):
|
||||
os.makedirs(os.path.dirname(cache_path))
|
||||
try:
|
||||
with codecs.open(cache_path, mode="wb", encoding="utf8") as fp:
|
||||
fp.write(data)
|
||||
with open(self._db_path, mode="a", encoding="utf8") as fp:
|
||||
fp.write("%s=%s\n" % (str(expire_time), os.path.basename(cache_path)))
|
||||
except UnicodeError:
|
||||
if os.path.isfile(cache_path):
|
||||
try:
|
||||
os.remove(cache_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return self._unlock_dbindex()
|
||||
|
||||
def delete(self, keys=None):
|
||||
"""Keys=None, delete expired items"""
|
||||
if not os.path.isfile(self._db_path):
|
||||
return None
|
||||
if not keys:
|
||||
keys = []
|
||||
if not isinstance(keys, list):
|
||||
keys = [keys]
|
||||
paths_for_delete = [self.get_cache_path(k) for k in keys]
|
||||
found = False
|
||||
newlines = []
|
||||
with open(self._db_path, encoding="utf8") as fp:
|
||||
for line in fp.readlines():
|
||||
line = line.strip()
|
||||
if "=" not in line:
|
||||
continue
|
||||
expire, fname = line.split("=")
|
||||
path = os.path.join(self.cache_dir, fname)
|
||||
try:
|
||||
if (
|
||||
time() < int(expire)
|
||||
and os.path.isfile(path)
|
||||
and path not in paths_for_delete
|
||||
):
|
||||
newlines.append(line)
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
found = True
|
||||
if os.path.isfile(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
if not os.listdir(os.path.dirname(path)):
|
||||
fs.rmtree(os.path.dirname(path))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if found and self._lock_dbindex():
|
||||
with open(self._db_path, mode="w", encoding="utf8") as fp:
|
||||
fp.write("\n".join(newlines) + "\n")
|
||||
self._unlock_dbindex()
|
||||
|
||||
return True
|
||||
|
||||
def clean(self):
|
||||
if not os.path.isdir(self.cache_dir):
|
||||
return
|
||||
fs.rmtree(self.cache_dir)
|
||||
|
||||
def _lock_dbindex(self):
|
||||
self._lockfile = LockFile(self.cache_dir)
|
||||
try:
|
||||
self._lockfile.acquire()
|
||||
except: # pylint: disable=bare-except
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _unlock_dbindex(self):
|
||||
if self._lockfile:
|
||||
self._lockfile.release()
|
||||
return True
|
||||
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
||||
|
||||
def cleanup_content_cache(namespace=None):
|
||||
with ContentCache(namespace) as cc:
|
||||
cc.clean()
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
|
||||
# pylint: disable=redefined-builtin,too-many-statements
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
from collections import Counter
|
||||
from os.path import dirname, isfile
|
||||
from time import time
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import app, exception, fs, util
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools import CheckToolFactory
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import find_project_dir_above, get_project_dir
|
||||
|
||||
|
||||
@click.command("check", short_help="Static Code Analysis")
|
||||
@click.option("-e", "--environment", multiple=True)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--project-dir",
|
||||
default=os.getcwd,
|
||||
type=click.Path(exists=True, file_okay=True, dir_okay=True, writable=True),
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--project-conf",
|
||||
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
|
||||
)
|
||||
@click.option("--pattern", multiple=True, hidden=True)
|
||||
@click.option("-f", "--src-filters", multiple=True)
|
||||
@click.option("--flags", multiple=True)
|
||||
@click.option(
|
||||
"--severity", multiple=True, type=click.Choice(DefectItem.SEVERITY_LABELS.values())
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True)
|
||||
@click.option("-v", "--verbose", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option(
|
||||
"--fail-on-defect",
|
||||
multiple=True,
|
||||
type=click.Choice(DefectItem.SEVERITY_LABELS.values()),
|
||||
)
|
||||
@click.option("--skip-packages", is_flag=True)
|
||||
def cli(
|
||||
environment,
|
||||
project_dir,
|
||||
project_conf,
|
||||
src_filters,
|
||||
pattern,
|
||||
flags,
|
||||
severity,
|
||||
silent,
|
||||
verbose,
|
||||
json_output,
|
||||
fail_on_defect,
|
||||
skip_packages,
|
||||
):
|
||||
app.set_session_var("custom_project_conf", project_conf)
|
||||
|
||||
# find project directory on upper level
|
||||
if isfile(project_dir):
|
||||
project_dir = find_project_dir_above(project_dir)
|
||||
|
||||
results = []
|
||||
with fs.cd(project_dir):
|
||||
config = ProjectConfig.get_instance(project_conf)
|
||||
config.validate(environment)
|
||||
|
||||
default_envs = config.default_envs()
|
||||
for envname in config.envs():
|
||||
skipenv = any(
|
||||
[
|
||||
environment and envname not in environment,
|
||||
not environment and default_envs and envname not in default_envs,
|
||||
]
|
||||
)
|
||||
|
||||
env_options = config.items(env=envname, as_dict=True)
|
||||
env_dump = []
|
||||
for k, v in env_options.items():
|
||||
if k not in ("platform", "framework", "board"):
|
||||
continue
|
||||
env_dump.append(
|
||||
"%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v)
|
||||
)
|
||||
|
||||
default_src_filters = [
|
||||
"+<%s>" % os.path.basename(config.get("platformio", "src_dir")),
|
||||
"+<%s>" % os.path.basename(config.get("platformio", "include_dir")),
|
||||
]
|
||||
|
||||
src_filters = (
|
||||
src_filters
|
||||
or pattern
|
||||
or env_options.get(
|
||||
"check_src_filters",
|
||||
env_options.get("check_patterns", default_src_filters),
|
||||
)
|
||||
)
|
||||
|
||||
tool_options = dict(
|
||||
verbose=verbose,
|
||||
silent=silent,
|
||||
src_filters=src_filters,
|
||||
flags=flags or env_options.get("check_flags"),
|
||||
severity=[DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH]]
|
||||
if silent
|
||||
else severity or config.get("env:" + envname, "check_severity"),
|
||||
skip_packages=skip_packages or env_options.get("check_skip_packages"),
|
||||
platform_packages=env_options.get("platform_packages"),
|
||||
)
|
||||
|
||||
for tool in config.get("env:" + envname, "check_tool"):
|
||||
if skipenv:
|
||||
results.append({"env": envname, "tool": tool})
|
||||
continue
|
||||
if not silent and not json_output:
|
||||
print_processing_header(tool, envname, env_dump)
|
||||
|
||||
ct = CheckToolFactory.new(
|
||||
tool, project_dir, config, envname, tool_options
|
||||
)
|
||||
|
||||
result = {"env": envname, "tool": tool, "duration": time()}
|
||||
rc = ct.check(
|
||||
on_defect_callback=None
|
||||
if (json_output or verbose)
|
||||
else lambda defect: click.echo(repr(defect))
|
||||
)
|
||||
|
||||
result["defects"] = ct.get_defects()
|
||||
result["duration"] = time() - result["duration"]
|
||||
|
||||
result["succeeded"] = rc == 0
|
||||
if fail_on_defect:
|
||||
result["succeeded"] = rc == 0 and not any(
|
||||
DefectItem.SEVERITY_LABELS[d.severity] in fail_on_defect
|
||||
for d in result["defects"]
|
||||
)
|
||||
result["stats"] = collect_component_stats(result)
|
||||
results.append(result)
|
||||
|
||||
if verbose:
|
||||
click.echo("\n".join(repr(d) for d in result["defects"]))
|
||||
|
||||
if not json_output and not silent:
|
||||
if rc != 0:
|
||||
click.echo(
|
||||
"Error: %s failed to perform check! Please "
|
||||
"examine tool output in verbose mode." % tool
|
||||
)
|
||||
elif not result["defects"]:
|
||||
click.echo("No defects found")
|
||||
print_processing_footer(result)
|
||||
|
||||
if json_output:
|
||||
click.echo(json.dumps(results_to_json(results)))
|
||||
elif not silent:
|
||||
print_check_summary(results, verbose=verbose)
|
||||
|
||||
# Reset custom project config
|
||||
app.set_session_var("custom_project_conf", None)
|
||||
|
||||
command_failed = any(r.get("succeeded") is False for r in results)
|
||||
if command_failed:
|
||||
raise exception.ReturnErrorCode(1)
|
||||
|
||||
|
||||
def results_to_json(raw):
|
||||
results = []
|
||||
for item in raw:
|
||||
if item.get("succeeded") is None:
|
||||
continue
|
||||
item.update(
|
||||
{
|
||||
"succeeded": bool(item.get("succeeded")),
|
||||
"defects": [d.as_dict() for d in item.get("defects", [])],
|
||||
}
|
||||
)
|
||||
results.append(item)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def print_processing_header(tool, envname, envdump):
|
||||
click.echo(
|
||||
"Checking %s > %s (%s)"
|
||||
% (click.style(envname, fg="cyan", bold=True), tool, "; ".join(envdump))
|
||||
)
|
||||
terminal_width = shutil.get_terminal_size().columns
|
||||
click.secho("-" * terminal_width, bold=True)
|
||||
|
||||
|
||||
def print_processing_footer(result):
|
||||
is_failed = not result.get("succeeded")
|
||||
util.print_labeled_bar(
|
||||
"[%s] Took %.2f seconds"
|
||||
% (
|
||||
(
|
||||
click.style("FAILED", fg="red", bold=True)
|
||||
if is_failed
|
||||
else click.style("PASSED", fg="green", bold=True)
|
||||
),
|
||||
result["duration"],
|
||||
),
|
||||
is_error=is_failed,
|
||||
)
|
||||
|
||||
|
||||
def collect_component_stats(result):
|
||||
components = {}
|
||||
|
||||
def _append_defect(component, defect):
|
||||
if not components.get(component):
|
||||
components[component] = Counter()
|
||||
components[component].update({DefectItem.SEVERITY_LABELS[defect.severity]: 1})
|
||||
|
||||
for defect in result.get("defects", []):
|
||||
component = dirname(defect.file) or defect.file
|
||||
_append_defect(component, defect)
|
||||
|
||||
if component.lower().startswith(get_project_dir().lower()):
|
||||
while os.sep in component:
|
||||
component = dirname(component)
|
||||
_append_defect(component, defect)
|
||||
|
||||
return components
|
||||
|
||||
|
||||
def print_defects_stats(results):
|
||||
if not results:
|
||||
return
|
||||
|
||||
component_stats = {}
|
||||
for r in results:
|
||||
for k, v in r.get("stats", {}).items():
|
||||
if not component_stats.get(k):
|
||||
component_stats[k] = Counter()
|
||||
component_stats[k].update(r["stats"][k])
|
||||
|
||||
if not component_stats:
|
||||
return
|
||||
|
||||
severity_labels = list(DefectItem.SEVERITY_LABELS.values())
|
||||
severity_labels.reverse()
|
||||
tabular_data = []
|
||||
for k, v in component_stats.items():
|
||||
tool_defect = [v.get(s, 0) for s in severity_labels]
|
||||
tabular_data.append([k] + tool_defect)
|
||||
|
||||
total = ["Total"] + [sum(d) for d in list(zip(*tabular_data))[1:]]
|
||||
tabular_data.sort()
|
||||
tabular_data.append([]) # Empty line as delimiter
|
||||
tabular_data.append(total)
|
||||
|
||||
headers = ["Component"]
|
||||
headers.extend([label.upper() for label in severity_labels])
|
||||
headers = [click.style(h, bold=True) for h in headers]
|
||||
click.echo(tabulate(tabular_data, headers=headers, numalign="center"))
|
||||
click.echo()
|
||||
|
||||
|
||||
def print_check_summary(results, verbose=False):
|
||||
click.echo()
|
||||
|
||||
tabular_data = []
|
||||
succeeded_nums = 0
|
||||
failed_nums = 0
|
||||
duration = 0
|
||||
|
||||
print_defects_stats(results)
|
||||
|
||||
for result in results:
|
||||
duration += result.get("duration", 0)
|
||||
if result.get("succeeded") is False:
|
||||
failed_nums += 1
|
||||
status_str = click.style("FAILED", fg="red")
|
||||
elif result.get("succeeded") is None:
|
||||
status_str = "IGNORED"
|
||||
if not verbose:
|
||||
continue
|
||||
else:
|
||||
succeeded_nums += 1
|
||||
status_str = click.style("PASSED", fg="green")
|
||||
|
||||
tabular_data.append(
|
||||
(
|
||||
click.style(result["env"], fg="cyan"),
|
||||
result["tool"],
|
||||
status_str,
|
||||
util.humanize_duration_time(result.get("duration")),
|
||||
)
|
||||
)
|
||||
|
||||
click.echo(
|
||||
tabulate(
|
||||
tabular_data,
|
||||
headers=[
|
||||
click.style(s, bold=True)
|
||||
for s in ("Environment", "Tool", "Status", "Duration")
|
||||
],
|
||||
),
|
||||
err=failed_nums,
|
||||
)
|
||||
|
||||
util.print_labeled_bar(
|
||||
"%s%d succeeded in %s"
|
||||
% (
|
||||
"%d failed, " % failed_nums if failed_nums else "",
|
||||
succeeded_nums,
|
||||
util.humanize_duration_time(duration),
|
||||
),
|
||||
is_error=failed_nums,
|
||||
fg="red" if failed_nums else "green",
|
||||
)
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio.exception import PlatformioException
|
||||
from platformio.project.helpers import get_project_dir
|
||||
|
||||
# pylint: disable=too-many-instance-attributes, redefined-builtin
|
||||
# pylint: disable=too-many-arguments
|
||||
|
||||
|
||||
class DefectItem:
|
||||
SEVERITY_HIGH = 1
|
||||
SEVERITY_MEDIUM = 2
|
||||
SEVERITY_LOW = 4
|
||||
SEVERITY_LABELS = {4: "low", 2: "medium", 1: "high"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
severity,
|
||||
category,
|
||||
message,
|
||||
file=None,
|
||||
line=0,
|
||||
column=0,
|
||||
id=None,
|
||||
callstack=None,
|
||||
cwe=None,
|
||||
):
|
||||
assert severity in (self.SEVERITY_HIGH, self.SEVERITY_MEDIUM, self.SEVERITY_LOW)
|
||||
self.severity = severity
|
||||
self.category = category
|
||||
self.message = message
|
||||
self.line = int(line)
|
||||
self.column = int(column)
|
||||
self.callstack = callstack
|
||||
self.cwe = cwe
|
||||
self.id = id
|
||||
self.file = file or "unknown"
|
||||
if file.lower().startswith(get_project_dir().lower()):
|
||||
self.file = os.path.relpath(file, get_project_dir())
|
||||
|
||||
def __repr__(self):
|
||||
defect_color = None
|
||||
if self.severity == self.SEVERITY_HIGH:
|
||||
defect_color = "red"
|
||||
elif self.severity == self.SEVERITY_MEDIUM:
|
||||
defect_color = "yellow"
|
||||
|
||||
format_str = "{file}:{line}: [{severity}:{category}] {message} {id}"
|
||||
return format_str.format(
|
||||
severity=click.style(self.SEVERITY_LABELS[self.severity], fg=defect_color),
|
||||
category=click.style(self.category.lower(), fg=defect_color),
|
||||
file=click.style(self.file, bold=True),
|
||||
message=self.message,
|
||||
line=self.line,
|
||||
id="%s" % "[%s]" % self.id if self.id else "",
|
||||
)
|
||||
|
||||
def __or__(self, defect):
|
||||
return self.severity | defect.severity
|
||||
|
||||
@staticmethod
|
||||
def severity_to_int(label):
|
||||
for key, value in DefectItem.SEVERITY_LABELS.items():
|
||||
if label == value:
|
||||
return key
|
||||
raise PlatformioException("Unknown severity label -> %s" % label)
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"severity": self.SEVERITY_LABELS[self.severity],
|
||||
"category": self.category,
|
||||
"message": self.message,
|
||||
"file": os.path.abspath(self.file),
|
||||
"line": self.line,
|
||||
"column": self.column,
|
||||
"callstack": self.callstack,
|
||||
"id": self.id,
|
||||
"cwe": self.cwe,
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio import exception
|
||||
from platformio.check.tools.clangtidy import ClangtidyCheckTool
|
||||
from platformio.check.tools.cppcheck import CppcheckCheckTool
|
||||
from platformio.check.tools.pvsstudio import PvsStudioCheckTool
|
||||
|
||||
|
||||
class CheckToolFactory:
|
||||
@staticmethod
|
||||
def new(tool, project_dir, config, envname, options):
|
||||
cls = None
|
||||
if tool == "cppcheck":
|
||||
cls = CppcheckCheckTool
|
||||
elif tool == "clangtidy":
|
||||
cls = ClangtidyCheckTool
|
||||
elif tool == "pvs-studio":
|
||||
cls = PvsStudioCheckTool
|
||||
else:
|
||||
raise exception.PlatformioException("Unknown check tool `%s`" % tool)
|
||||
return cls(project_dir, config, envname, options)
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio import fs, proc
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.package.manager.core import get_core_package_dir
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.project.helpers import load_build_metadata
|
||||
|
||||
|
||||
class CheckToolBase: # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, project_dir, config, envname, options):
|
||||
self.config = config
|
||||
self.envname = envname
|
||||
self.options = options
|
||||
self.project_dir = project_dir
|
||||
self.cc_flags = []
|
||||
self.cxx_flags = []
|
||||
self.cpp_includes = []
|
||||
self.cpp_defines = []
|
||||
self.toolchain_defines = []
|
||||
self._tmp_files = []
|
||||
self.cc_path = None
|
||||
self.cxx_path = None
|
||||
self._defects = []
|
||||
self._on_defect_callback = None
|
||||
self._bad_input = False
|
||||
self._load_cpp_data()
|
||||
|
||||
# detect all defects by default
|
||||
if not self.options.get("severity"):
|
||||
self.options["severity"] = [
|
||||
DefectItem.SEVERITY_LOW,
|
||||
DefectItem.SEVERITY_MEDIUM,
|
||||
DefectItem.SEVERITY_HIGH,
|
||||
]
|
||||
# cast to severity by ids
|
||||
self.options["severity"] = [
|
||||
s if isinstance(s, int) else DefectItem.severity_to_int(s)
|
||||
for s in self.options["severity"]
|
||||
]
|
||||
|
||||
def _load_cpp_data(self):
|
||||
data = load_build_metadata(self.project_dir, self.envname)
|
||||
if not data:
|
||||
return
|
||||
self.cc_flags = data.get("cc_flags", [])
|
||||
self.cxx_flags = data.get("cxx_flags", [])
|
||||
self.cpp_includes = self._dump_includes(data.get("includes", {}))
|
||||
self.cpp_defines = data.get("defines", [])
|
||||
self.cc_path = data.get("cc_path")
|
||||
self.cxx_path = data.get("cxx_path")
|
||||
self.toolchain_defines = self._get_toolchain_defines()
|
||||
|
||||
def get_tool_dir(self, pkg_name):
|
||||
for spec in self.options["platform_packages"] or []:
|
||||
spec = PackageSpec(spec)
|
||||
if spec.name == pkg_name:
|
||||
return get_core_package_dir(pkg_name, spec=spec)
|
||||
return get_core_package_dir(pkg_name)
|
||||
|
||||
def get_flags(self, tool):
|
||||
result = []
|
||||
flags = self.options.get("flags") or []
|
||||
for flag in flags:
|
||||
if ":" not in flag or flag.startswith("-"):
|
||||
result.extend([f for f in flag.split(" ") if f])
|
||||
elif flag.startswith("%s:" % tool):
|
||||
result.extend([f for f in flag.split(":", 1)[1].split(" ") if f])
|
||||
|
||||
return result
|
||||
|
||||
def _get_toolchain_defines(self):
|
||||
def _extract_defines(language, includes_file):
|
||||
build_flags = self.cxx_flags if language == "c++" else self.cc_flags
|
||||
defines = []
|
||||
cmd = "echo | %s -x %s %s %s -dM -E -" % (
|
||||
self.cc_path,
|
||||
language,
|
||||
" ".join(
|
||||
[f for f in build_flags if f.startswith(("-m", "-f", "-std"))]
|
||||
),
|
||||
includes_file,
|
||||
)
|
||||
result = proc.exec_command(cmd, shell=True)
|
||||
|
||||
if result["returncode"] != 0:
|
||||
click.echo("Warning: Failed to extract toolchain defines!")
|
||||
if self.options.get("verbose"):
|
||||
click.echo(result["out"])
|
||||
click.echo(result["err"])
|
||||
|
||||
for line in result["out"].split("\n"):
|
||||
tokens = line.strip().split(" ", 2)
|
||||
if not tokens or tokens[0] != "#define":
|
||||
continue
|
||||
if len(tokens) > 2:
|
||||
defines.append("%s=%s" % (tokens[1], tokens[2]))
|
||||
else:
|
||||
defines.append(tokens[1])
|
||||
|
||||
return defines
|
||||
|
||||
incflags_file = self._long_includes_hook(self.cpp_includes)
|
||||
return {lang: _extract_defines(lang, incflags_file) for lang in ("c", "c++")}
|
||||
|
||||
def _create_tmp_file(self, data):
|
||||
with tempfile.NamedTemporaryFile("w", delete=False) as fp:
|
||||
fp.write(data)
|
||||
self._tmp_files.append(fp.name)
|
||||
return fp.name
|
||||
|
||||
def _long_includes_hook(self, includes):
|
||||
data = []
|
||||
for inc in includes:
|
||||
data.append('-I"%s"' % fs.to_unix_path(inc))
|
||||
|
||||
return '@"%s"' % self._create_tmp_file(" ".join(data))
|
||||
|
||||
@staticmethod
|
||||
def _dump_includes(includes_map):
|
||||
result = []
|
||||
for includes in includes_map.values():
|
||||
for include in includes:
|
||||
if include not in result:
|
||||
result.append(include)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def is_flag_set(flag, flags):
|
||||
return any(flag in f for f in flags)
|
||||
|
||||
def get_defects(self):
|
||||
return self._defects
|
||||
|
||||
def configure_command(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def on_tool_output(self, line):
|
||||
line = self.tool_output_filter(line)
|
||||
if not line:
|
||||
return
|
||||
|
||||
defect = self.parse_defect(line)
|
||||
|
||||
if not isinstance(defect, DefectItem):
|
||||
if self.options.get("verbose"):
|
||||
click.echo(line)
|
||||
return
|
||||
|
||||
if defect.severity not in self.options["severity"]:
|
||||
return
|
||||
|
||||
self._defects.append(defect)
|
||||
if self._on_defect_callback:
|
||||
self._on_defect_callback(defect)
|
||||
|
||||
@staticmethod
|
||||
def tool_output_filter(line):
|
||||
return line
|
||||
|
||||
@staticmethod
|
||||
def parse_defect(raw_line):
|
||||
return raw_line
|
||||
|
||||
def clean_up(self):
|
||||
for f in self._tmp_files:
|
||||
if os.path.isfile(f):
|
||||
os.remove(f)
|
||||
|
||||
@staticmethod
|
||||
def is_check_successful(cmd_result):
|
||||
return cmd_result["returncode"] == 0
|
||||
|
||||
def execute_check_cmd(self, cmd):
|
||||
result = proc.exec_command(
|
||||
cmd,
|
||||
stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),
|
||||
stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),
|
||||
)
|
||||
|
||||
if not self.is_check_successful(result):
|
||||
click.echo(
|
||||
"\nError: Failed to execute check command! Exited with code %d."
|
||||
% result["returncode"]
|
||||
)
|
||||
if self.options.get("verbose"):
|
||||
click.echo(result["out"])
|
||||
click.echo(result["err"])
|
||||
self._bad_input = True
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def get_project_target_files(project_dir, src_filters):
|
||||
c_extension = (".c",)
|
||||
cpp_extensions = (".cc", ".cpp", ".cxx", ".ino")
|
||||
header_extensions = (".h", ".hh", ".hpp", ".hxx")
|
||||
|
||||
result = {"c": [], "c++": [], "headers": []}
|
||||
|
||||
def _add_file(path):
|
||||
if path.endswith(header_extensions):
|
||||
result["headers"].append(os.path.abspath(path))
|
||||
elif path.endswith(c_extension):
|
||||
result["c"].append(os.path.abspath(path))
|
||||
elif path.endswith(cpp_extensions):
|
||||
result["c++"].append(os.path.abspath(path))
|
||||
|
||||
src_filters = normalize_src_filters(src_filters)
|
||||
for f in fs.match_src_files(project_dir, src_filters):
|
||||
_add_file(f)
|
||||
|
||||
return result
|
||||
|
||||
def check(self, on_defect_callback=None):
|
||||
self._on_defect_callback = on_defect_callback
|
||||
cmd = self.configure_command()
|
||||
if cmd:
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
|
||||
self.execute_check_cmd(cmd)
|
||||
|
||||
else:
|
||||
if self.options.get("verbose"):
|
||||
click.echo("Error: Couldn't configure command")
|
||||
self._bad_input = True
|
||||
|
||||
self.clean_up()
|
||||
|
||||
return self._bad_input
|
||||
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
||||
|
||||
def normalize_src_filters(src_filters):
|
||||
def _normalize(src_filters):
|
||||
return (
|
||||
src_filters
|
||||
if src_filters.startswith(("+<", "-<"))
|
||||
else "+<%s>" % src_filters
|
||||
)
|
||||
|
||||
if isinstance(src_filters, (list, tuple)):
|
||||
return " ".join([_normalize(f) for f in src_filters])
|
||||
|
||||
return _normalize(src_filters)
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
from os.path import join
|
||||
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
|
||||
|
||||
class ClangtidyCheckTool(CheckToolBase):
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if not self.options.get("verbose") and "[clang-diagnostic-error]" in line:
|
||||
return ""
|
||||
|
||||
if "[CommonOptionsParser]" in line:
|
||||
self._bad_input = True
|
||||
return line
|
||||
|
||||
if any(d in line for d in ("note: ", "error: ", "warning: ")):
|
||||
return line
|
||||
|
||||
return ""
|
||||
|
||||
def parse_defect(self, raw_line): # pylint: disable=arguments-differ
|
||||
match = re.match(r"^(.*):(\d+):(\d+):\s+([^:]+):\s(.+)\[([^]]+)\]$", raw_line)
|
||||
if not match:
|
||||
return raw_line
|
||||
|
||||
file_, line, column, category, message, defect_id = match.groups()
|
||||
|
||||
severity = DefectItem.SEVERITY_LOW
|
||||
if category == "error":
|
||||
severity = DefectItem.SEVERITY_HIGH
|
||||
elif category == "warning":
|
||||
severity = DefectItem.SEVERITY_MEDIUM
|
||||
|
||||
return DefectItem(severity, category, message, file_, line, column, defect_id)
|
||||
|
||||
@staticmethod
|
||||
def is_check_successful(cmd_result):
|
||||
# Note: Clang-Tidy returns 1 for not critical compilation errors,
|
||||
# so 0 and 1 are only acceptable values
|
||||
return cmd_result["returncode"] < 2
|
||||
|
||||
def configure_command(self):
|
||||
tool_path = join(self.get_tool_dir("tool-clangtidy"), "clang-tidy")
|
||||
|
||||
cmd = [tool_path, "--quiet"]
|
||||
flags = self.get_flags("clangtidy")
|
||||
if not (
|
||||
self.is_flag_set("--checks", flags) or self.is_flag_set("--config", flags)
|
||||
):
|
||||
cmd.append("--checks=*")
|
||||
|
||||
project_files = self.get_project_target_files(
|
||||
self.project_dir, self.options["src_filters"]
|
||||
)
|
||||
|
||||
src_files = []
|
||||
for items in project_files.values():
|
||||
src_files.extend(items)
|
||||
|
||||
cmd.extend(flags + src_files + ["--"])
|
||||
cmd.extend(
|
||||
["-D%s" % d for d in self.cpp_defines + self.toolchain_defines["c++"]]
|
||||
)
|
||||
|
||||
includes = []
|
||||
for inc in self.cpp_includes:
|
||||
if self.options.get("skip_packages") and inc.lower().startswith(
|
||||
self.config.get("platformio", "packages_dir").lower()
|
||||
):
|
||||
continue
|
||||
includes.append(inc)
|
||||
|
||||
cmd.extend(["-I%s" % inc for inc in includes])
|
||||
|
||||
return cmd
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio import proc
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
|
||||
|
||||
class CppcheckCheckTool(CheckToolBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._field_delimiter = "<&PIO&>"
|
||||
self._buffer = ""
|
||||
self.defect_fields = [
|
||||
"severity",
|
||||
"message",
|
||||
"file",
|
||||
"line",
|
||||
"column",
|
||||
"callstack",
|
||||
"cwe",
|
||||
"id",
|
||||
]
|
||||
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if (
|
||||
not self.options.get("verbose")
|
||||
and "--suppress=unmatchedSuppression:" in line
|
||||
):
|
||||
return ""
|
||||
|
||||
if any(
|
||||
msg in line
|
||||
for msg in (
|
||||
"No C or C++ source files found",
|
||||
"unrecognized command line option",
|
||||
"there was an internal error",
|
||||
)
|
||||
):
|
||||
self._bad_input = True
|
||||
|
||||
return line
|
||||
|
||||
def parse_defect(self, raw_line): # pylint: disable=arguments-differ
|
||||
if self._field_delimiter not in raw_line:
|
||||
return None
|
||||
|
||||
self._buffer += raw_line
|
||||
if any(f not in self._buffer for f in self.defect_fields):
|
||||
return None
|
||||
|
||||
args = {}
|
||||
for field in self._buffer.split(self._field_delimiter):
|
||||
field = field.strip().replace('"', "")
|
||||
name, value = field.split("=", 1)
|
||||
args[name] = value
|
||||
|
||||
args["category"] = args["severity"]
|
||||
if args["severity"] == "error":
|
||||
args["severity"] = DefectItem.SEVERITY_HIGH
|
||||
elif args["severity"] == "warning":
|
||||
args["severity"] = DefectItem.SEVERITY_MEDIUM
|
||||
else:
|
||||
args["severity"] = DefectItem.SEVERITY_LOW
|
||||
|
||||
# Skip defects found in third-party software, but keep in mind that such defects
|
||||
# might break checking process so defects from project files are not reported
|
||||
breaking_defect_ids = ("preprocessorErrorDirective", "syntaxError")
|
||||
if (
|
||||
args.get("file", "")
|
||||
.lower()
|
||||
.startswith(self.config.get("platformio", "packages_dir").lower())
|
||||
):
|
||||
if args["id"] in breaking_defect_ids:
|
||||
if self.options.get("verbose"):
|
||||
click.echo(
|
||||
"Error: Found a breaking defect '%s' in %s:%s\n"
|
||||
"Please note: check results might not be valid!\n"
|
||||
"Try adding --skip-packages"
|
||||
% (args.get("message"), args.get("file"), args.get("line"))
|
||||
)
|
||||
click.echo()
|
||||
self._bad_input = True
|
||||
self._buffer = ""
|
||||
return None
|
||||
|
||||
self._buffer = ""
|
||||
return DefectItem(**args)
|
||||
|
||||
def configure_command(self, language, src_file): # pylint: disable=arguments-differ
|
||||
tool_path = os.path.join(self.get_tool_dir("tool-cppcheck"), "cppcheck")
|
||||
|
||||
cmd = [
|
||||
tool_path,
|
||||
"--addon-python=%s" % proc.get_pythonexe_path(),
|
||||
"--error-exitcode=3",
|
||||
"--verbose" if self.options.get("verbose") else "--quiet",
|
||||
]
|
||||
|
||||
cmd.append(
|
||||
'--template="%s"'
|
||||
% self._field_delimiter.join(
|
||||
["{0}={{{0}}}".format(f) for f in self.defect_fields]
|
||||
)
|
||||
)
|
||||
|
||||
flags = self.get_flags("cppcheck")
|
||||
if not flags:
|
||||
# by default user can suppress reporting individual defects
|
||||
# directly in code // cppcheck-suppress warningID
|
||||
cmd.append("--inline-suppr")
|
||||
if not self.is_flag_set("--platform", flags):
|
||||
cmd.append("--platform=unspecified")
|
||||
if not self.is_flag_set("--enable", flags):
|
||||
enabled_checks = [
|
||||
"warning",
|
||||
"style",
|
||||
"performance",
|
||||
"portability",
|
||||
"unusedFunction",
|
||||
]
|
||||
cmd.append("--enable=%s" % ",".join(enabled_checks))
|
||||
|
||||
if not self.is_flag_set("--language", flags):
|
||||
cmd.append("--language=" + language)
|
||||
|
||||
build_flags = self.cxx_flags if language == "c++" else self.cc_flags
|
||||
|
||||
if not self.is_flag_set("--std", flags):
|
||||
# Try to guess the standard version from the build flags
|
||||
for flag in build_flags:
|
||||
if "-std" in flag:
|
||||
cmd.append("-" + self.convert_language_standard(flag))
|
||||
|
||||
cmd.extend(
|
||||
["-D%s" % d for d in self.cpp_defines + self.toolchain_defines[language]]
|
||||
)
|
||||
|
||||
cmd.extend(flags)
|
||||
|
||||
cmd.extend(
|
||||
"--include=" + inc
|
||||
for inc in self.get_forced_includes(build_flags, self.cpp_includes)
|
||||
)
|
||||
cmd.append("--includes-file=%s" % self._generate_inc_file())
|
||||
cmd.append('"%s"' % src_file)
|
||||
|
||||
return cmd
|
||||
|
||||
@staticmethod
|
||||
def get_forced_includes(build_flags, includes):
|
||||
def _extract_filepath(flag, include_options, build_flags):
|
||||
path = ""
|
||||
for option in include_options:
|
||||
if not flag.startswith(option):
|
||||
continue
|
||||
if flag.split(option)[1].strip():
|
||||
path = flag.split(option)[1].strip()
|
||||
elif build_flags.index(flag) + 1 < len(build_flags):
|
||||
path = build_flags[build_flags.index(flag) + 1]
|
||||
return path
|
||||
|
||||
def _search_include_dir(filepath, include_paths):
|
||||
for inc_path in include_paths:
|
||||
path = os.path.join(inc_path, filepath)
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return ""
|
||||
|
||||
result = []
|
||||
include_options = ("-include", "-imacros")
|
||||
for f in build_flags:
|
||||
if f.startswith(include_options):
|
||||
filepath = _extract_filepath(f, include_options, build_flags)
|
||||
if not os.path.isabs(filepath):
|
||||
filepath = _search_include_dir(filepath, includes)
|
||||
if os.path.isfile(filepath):
|
||||
result.append(filepath)
|
||||
|
||||
return result
|
||||
|
||||
def _generate_src_file(self, src_files):
|
||||
return self._create_tmp_file("\n".join(src_files))
|
||||
|
||||
def _generate_inc_file(self):
|
||||
result = []
|
||||
for inc in self.cpp_includes:
|
||||
if self.options.get("skip_packages") and inc.lower().startswith(
|
||||
self.config.get("platformio", "packages_dir").lower()
|
||||
):
|
||||
continue
|
||||
result.append(inc)
|
||||
return self._create_tmp_file("\n".join(result))
|
||||
|
||||
def clean_up(self):
|
||||
super().clean_up()
|
||||
|
||||
# delete temporary dump files generated by addons
|
||||
if not self.is_flag_set("--addon", self.get_flags("cppcheck")):
|
||||
return
|
||||
|
||||
for files in self.get_project_target_files(
|
||||
self.project_dir, self.options["src_filters"]
|
||||
).values():
|
||||
for f in files:
|
||||
dump_file = f + ".dump"
|
||||
if os.path.isfile(dump_file):
|
||||
os.remove(dump_file)
|
||||
|
||||
@staticmethod
|
||||
def is_check_successful(cmd_result):
|
||||
# Cppcheck is configured to return '3' if a defect is found
|
||||
return cmd_result["returncode"] in (0, 3)
|
||||
|
||||
@staticmethod
|
||||
def convert_language_standard(flag):
|
||||
cpp_standards_map = {
|
||||
"0x": "11",
|
||||
"1y": "14",
|
||||
"1z": "17",
|
||||
"2a": "20",
|
||||
}
|
||||
|
||||
standard = flag[-2:]
|
||||
# Note: GNU extensions are not supported and converted to regular standards
|
||||
return flag.replace("gnu", "c").replace(
|
||||
standard, cpp_standards_map.get(standard, standard)
|
||||
)
|
||||
|
||||
def check(self, on_defect_callback=None):
|
||||
self._on_defect_callback = on_defect_callback
|
||||
|
||||
project_files = self.get_project_target_files(
|
||||
self.project_dir, self.options["src_filters"]
|
||||
)
|
||||
src_files_scope = ("c", "c++")
|
||||
if not any(project_files[t] for t in src_files_scope):
|
||||
click.echo("Error: Nothing to check.")
|
||||
return True
|
||||
|
||||
for scope, files in project_files.items():
|
||||
if scope not in src_files_scope:
|
||||
continue
|
||||
for src_file in files:
|
||||
cmd = self.configure_command(scope, src_file)
|
||||
if not cmd:
|
||||
self._bad_input = True
|
||||
continue
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
|
||||
self.execute_check_cmd(cmd)
|
||||
|
||||
self.clean_up()
|
||||
|
||||
return self._bad_input
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
# Copyright (c) 2020-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from xml.etree.ElementTree import fromstring
|
||||
|
||||
import click
|
||||
|
||||
from platformio import proc
|
||||
from platformio.check.defect import DefectItem
|
||||
from platformio.check.tools.base import CheckToolBase
|
||||
from platformio.compat import IS_WINDOWS
|
||||
|
||||
|
||||
class PvsStudioCheckTool(CheckToolBase): # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._tmp_dir = tempfile.mkdtemp(prefix="piocheck")
|
||||
self._tmp_preprocessed_file = self._generate_tmp_file_path() + ".i"
|
||||
self._tmp_output_file = self._generate_tmp_file_path() + ".pvs"
|
||||
self._tmp_cfg_file = self._generate_tmp_file_path() + ".cfg"
|
||||
self._tmp_cmd_file = self._generate_tmp_file_path() + ".cmd"
|
||||
self.tool_path = os.path.join(
|
||||
self.get_tool_dir("tool-pvs-studio"),
|
||||
"x64" if IS_WINDOWS else "bin",
|
||||
"pvs-studio",
|
||||
)
|
||||
|
||||
with open(self._tmp_cfg_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(
|
||||
"exclude-path = "
|
||||
+ self.config.get("platformio", "packages_dir").replace("\\", "/")
|
||||
)
|
||||
|
||||
with open(self._tmp_cmd_file, mode="w", encoding="utf8") as fp:
|
||||
fp.write(
|
||||
" ".join(
|
||||
['-I"%s"' % inc.replace("\\", "/") for inc in self.cpp_includes]
|
||||
)
|
||||
)
|
||||
|
||||
def tool_output_filter(self, line): # pylint: disable=arguments-differ
|
||||
if any(
|
||||
err_msg in line.lower()
|
||||
for err_msg in (
|
||||
"license was not entered",
|
||||
"license information is incorrect",
|
||||
)
|
||||
):
|
||||
self._bad_input = True
|
||||
return line
|
||||
|
||||
def _process_defects(self, defects):
|
||||
for defect in defects:
|
||||
if not isinstance(defect, DefectItem):
|
||||
return
|
||||
if defect.severity not in self.options["severity"]:
|
||||
return
|
||||
self._defects.append(defect)
|
||||
if self._on_defect_callback:
|
||||
self._on_defect_callback(defect)
|
||||
|
||||
def _demangle_report(self, output_file):
|
||||
converter_tool = os.path.join(
|
||||
self.get_tool_dir("tool-pvs-studio"),
|
||||
"HtmlGenerator" if IS_WINDOWS else os.path.join("bin", "plog-converter"),
|
||||
)
|
||||
|
||||
cmd = (
|
||||
converter_tool,
|
||||
"-t",
|
||||
"xml",
|
||||
output_file,
|
||||
"-m",
|
||||
"cwe",
|
||||
"-m",
|
||||
"misra",
|
||||
"-a",
|
||||
# Enable all possible analyzers and defect levels
|
||||
"GA:1,2,3;64:1,2,3;OP:1,2,3;CS:1,2,3;MISRA:1,2,3",
|
||||
"--cerr",
|
||||
)
|
||||
|
||||
result = proc.exec_command(cmd)
|
||||
if result["returncode"] != 0:
|
||||
click.echo(result["err"])
|
||||
self._bad_input = True
|
||||
|
||||
return result["err"]
|
||||
|
||||
def parse_defects(self, output_file):
|
||||
defects = []
|
||||
|
||||
report = self._demangle_report(output_file)
|
||||
if not report:
|
||||
self._bad_input = True
|
||||
return []
|
||||
|
||||
try:
|
||||
defects_data = fromstring(report)
|
||||
except: # pylint: disable=bare-except
|
||||
click.echo("Error: Couldn't decode generated report!")
|
||||
self._bad_input = True
|
||||
return []
|
||||
|
||||
for table in defects_data.iter("PVS-Studio_Analysis_Log"):
|
||||
message = table.find("Message").text
|
||||
category = table.find("ErrorType").text
|
||||
line = table.find("Line").text
|
||||
file_ = table.find("File").text
|
||||
defect_id = table.find("ErrorCode").text
|
||||
cwe = table.find("CWECode")
|
||||
cwe_id = None
|
||||
if cwe is not None:
|
||||
cwe_id = cwe.text.lower().replace("cwe-", "")
|
||||
misra = table.find("MISRA")
|
||||
if misra is not None:
|
||||
message += " [%s]" % misra.text
|
||||
|
||||
severity = DefectItem.SEVERITY_LOW
|
||||
if category == "error":
|
||||
severity = DefectItem.SEVERITY_HIGH
|
||||
elif category == "warning":
|
||||
severity = DefectItem.SEVERITY_MEDIUM
|
||||
|
||||
defects.append(
|
||||
DefectItem(
|
||||
severity, category, message, file_, line, id=defect_id, cwe=cwe_id
|
||||
)
|
||||
)
|
||||
|
||||
return defects
|
||||
|
||||
def configure_command(self, src_file): # pylint: disable=arguments-differ
|
||||
if os.path.isfile(self._tmp_output_file):
|
||||
os.remove(self._tmp_output_file)
|
||||
|
||||
if not os.path.isfile(self._tmp_preprocessed_file):
|
||||
click.echo("Error: Missing preprocessed file for '%s'" % src_file)
|
||||
return ""
|
||||
|
||||
cmd = [
|
||||
self.tool_path,
|
||||
"--skip-cl-exe",
|
||||
"yes",
|
||||
"--language",
|
||||
"C" if src_file.endswith(".c") else "C++",
|
||||
"--preprocessor",
|
||||
"gcc",
|
||||
"--cfg",
|
||||
self._tmp_cfg_file,
|
||||
"--source-file",
|
||||
src_file,
|
||||
"--i-file",
|
||||
self._tmp_preprocessed_file,
|
||||
"--output-file",
|
||||
self._tmp_output_file,
|
||||
]
|
||||
|
||||
flags = self.get_flags("pvs-studio")
|
||||
if not self.is_flag_set("--platform", flags):
|
||||
cmd.append("--platform=arm")
|
||||
cmd.extend(flags)
|
||||
|
||||
return cmd
|
||||
|
||||
def _generate_tmp_file_path(self):
|
||||
# pylint: disable=protected-access
|
||||
return os.path.join(self._tmp_dir, next(tempfile._get_candidate_names()))
|
||||
|
||||
def _prepare_preprocessed_file(self, src_file):
|
||||
if os.path.isfile(self._tmp_preprocessed_file):
|
||||
os.remove(self._tmp_preprocessed_file)
|
||||
|
||||
flags = self.cxx_flags
|
||||
compiler = self.cxx_path
|
||||
if src_file.endswith(".c"):
|
||||
flags = self.cc_flags
|
||||
compiler = self.cc_path
|
||||
|
||||
cmd = [
|
||||
compiler,
|
||||
'"%s"' % src_file,
|
||||
"-E",
|
||||
"-o",
|
||||
'"%s"' % self._tmp_preprocessed_file,
|
||||
]
|
||||
cmd.extend([f for f in flags if f])
|
||||
cmd.extend(['"-D%s"' % d.replace('"', '\\"') for d in self.cpp_defines])
|
||||
cmd.append('@"%s"' % self._tmp_cmd_file)
|
||||
|
||||
# Explicitly specify C++ as the language used in .ino files
|
||||
if src_file.endswith(".ino"):
|
||||
cmd.insert(1, "-xc++")
|
||||
|
||||
result = proc.exec_command(" ".join(cmd), shell=True)
|
||||
if result["returncode"] != 0 or result["err"]:
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
click.echo(result["err"])
|
||||
self._bad_input = True
|
||||
|
||||
def clean_up(self):
|
||||
super().clean_up()
|
||||
if os.path.isdir(self._tmp_dir):
|
||||
shutil.rmtree(self._tmp_dir)
|
||||
|
||||
@staticmethod
|
||||
def is_check_successful(cmd_result):
|
||||
return (
|
||||
"license" not in cmd_result["err"].lower() and cmd_result["returncode"] == 0
|
||||
)
|
||||
|
||||
def check(self, on_defect_callback=None):
|
||||
self._on_defect_callback = on_defect_callback
|
||||
for scope, files in self.get_project_target_files(
|
||||
self.project_dir, self.options["src_filters"]
|
||||
).items():
|
||||
if scope not in ("c", "c++"):
|
||||
continue
|
||||
for src_file in files:
|
||||
self._prepare_preprocessed_file(src_file)
|
||||
cmd = self.configure_command(src_file)
|
||||
if self.options.get("verbose"):
|
||||
click.echo(" ".join(cmd))
|
||||
if not cmd:
|
||||
self._bad_input = True
|
||||
continue
|
||||
|
||||
result = self.execute_check_cmd(cmd)
|
||||
if result["returncode"] != 0:
|
||||
continue
|
||||
|
||||
self._process_defects(self.parse_defects(self._tmp_output_file))
|
||||
|
||||
self.clean_up()
|
||||
|
||||
return self._bad_input
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class PlatformioCLI(click.MultiCommand):
|
||||
leftover_args = []
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._pio_root_path = Path(__file__).parent
|
||||
self._pio_cmd_aliases = dict(package="pkg")
|
||||
|
||||
def _find_pio_commands(self):
|
||||
def _to_module_path(p):
|
||||
return (
|
||||
"platformio." + ".".join(p.relative_to(self._pio_root_path).parts)[:-3]
|
||||
)
|
||||
|
||||
result = {}
|
||||
for p in self._pio_root_path.rglob("cli.py"):
|
||||
# skip this module
|
||||
if p.parent == self._pio_root_path:
|
||||
continue
|
||||
cmd_name = p.parent.name
|
||||
result[self._pio_cmd_aliases.get(cmd_name, cmd_name)] = _to_module_path(p)
|
||||
|
||||
# find legacy commands
|
||||
for p in (self._pio_root_path / "commands").iterdir():
|
||||
if p.name.startswith("_"):
|
||||
continue
|
||||
if (p / "command.py").is_file():
|
||||
result[p.name] = _to_module_path(p / "command.py")
|
||||
elif p.name.endswith(".py"):
|
||||
result[p.name[:-3]] = _to_module_path(p)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def in_silence():
|
||||
args = PlatformioCLI.leftover_args
|
||||
return args and any(
|
||||
[
|
||||
args[0] == "debug" and "--interpreter" in " ".join(args),
|
||||
args[0] == "upgrade",
|
||||
"--json-output" in args,
|
||||
"--version" in args,
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def reveal_cmd_path_args(cls, ctx):
|
||||
result = []
|
||||
group = ctx.command
|
||||
args = cls.leftover_args[::]
|
||||
while args:
|
||||
cmd_name = args.pop(0)
|
||||
next_group = group.get_command(ctx, cmd_name)
|
||||
if next_group:
|
||||
group = next_group
|
||||
result.append(cmd_name)
|
||||
if not hasattr(group, "get_command"):
|
||||
break
|
||||
return result
|
||||
|
||||
def invoke(self, ctx):
|
||||
PlatformioCLI.leftover_args = ctx.args
|
||||
if hasattr(ctx, "protected_args"):
|
||||
PlatformioCLI.leftover_args = ctx.protected_args + ctx.args
|
||||
return super().invoke(ctx)
|
||||
|
||||
def list_commands(self, ctx):
|
||||
return sorted(list(self._find_pio_commands()))
|
||||
|
||||
def get_command(self, ctx, cmd_name):
|
||||
commands = self._find_pio_commands()
|
||||
if cmd_name not in commands:
|
||||
return self._handle_obsolate_command(ctx, cmd_name)
|
||||
module = importlib.import_module(commands[cmd_name])
|
||||
return getattr(module, "cli")
|
||||
|
||||
@staticmethod
|
||||
def _handle_obsolate_command(ctx, cmd_name):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
if cmd_name == "init":
|
||||
from platformio.project.commands.init import project_init_cmd
|
||||
|
||||
return project_init_cmd
|
||||
|
||||
if cmd_name == "package":
|
||||
from platformio.package.cli import cli
|
||||
|
||||
return cli
|
||||
|
||||
raise click.UsageError('No such command "%s"' % cmd_name, ctx)
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import shutil
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import fs
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
|
||||
|
||||
@click.command("boards", short_help="Board Explorer")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--installed", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def cli(query, installed, json_output): # pylint: disable=R0912
|
||||
if json_output:
|
||||
return _print_boards_json(query, installed)
|
||||
|
||||
grpboards = {}
|
||||
for board in _get_boards(installed):
|
||||
if query and not any(
|
||||
query.lower() in str(board.get(k, "")).lower()
|
||||
for k in ("id", "name", "mcu", "vendor", "platform", "frameworks")
|
||||
):
|
||||
continue
|
||||
if board["platform"] not in grpboards:
|
||||
grpboards[board["platform"]] = []
|
||||
grpboards[board["platform"]].append(board)
|
||||
|
||||
terminal_width = shutil.get_terminal_size().columns
|
||||
for platform, boards in sorted(grpboards.items()):
|
||||
click.echo("")
|
||||
click.echo("Platform: ", nl=False)
|
||||
click.secho(platform, bold=True)
|
||||
click.echo("=" * terminal_width)
|
||||
print_boards(boards)
|
||||
return True
|
||||
|
||||
|
||||
def print_boards(boards):
|
||||
click.echo(
|
||||
tabulate(
|
||||
[
|
||||
(
|
||||
click.style(b["id"], fg="cyan"),
|
||||
b["mcu"],
|
||||
"%dMHz" % (b["fcpu"] / 1000000),
|
||||
fs.humanize_file_size(b["rom"]),
|
||||
fs.humanize_file_size(b["ram"]),
|
||||
b["name"],
|
||||
)
|
||||
for b in boards
|
||||
],
|
||||
headers=["ID", "MCU", "Frequency", "Flash", "RAM", "Name"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _get_boards(installed=False):
|
||||
pm = PlatformPackageManager()
|
||||
return pm.get_installed_boards() if installed else pm.get_all_boards()
|
||||
|
||||
|
||||
def _print_boards_json(query, installed=False):
|
||||
result = []
|
||||
for board in _get_boards(installed):
|
||||
if query:
|
||||
search_data = "%s %s" % (board["id"], json.dumps(board).lower())
|
||||
if query.lower() not in search_data.lower():
|
||||
continue
|
||||
result.append(board)
|
||||
click.echo(json.dumps(result))
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import click
|
||||
|
||||
from platformio import fs
|
||||
from platformio.exception import CIBuildEnvsEmpty
|
||||
from platformio.project.commands.init import project_init_cmd, validate_boards
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.run.cli import cli as cmd_run
|
||||
|
||||
|
||||
def validate_path(ctx, param, value): # pylint: disable=unused-argument
|
||||
invalid_path = None
|
||||
value = list(value)
|
||||
for i, p in enumerate(value):
|
||||
if p.startswith("~"):
|
||||
value[i] = fs.expanduser(p)
|
||||
value[i] = os.path.abspath(value[i])
|
||||
if not glob.glob(value[i], recursive=True):
|
||||
invalid_path = p
|
||||
break
|
||||
try:
|
||||
assert invalid_path is None
|
||||
return value
|
||||
except AssertionError as exc:
|
||||
raise click.BadParameter("Found invalid path: %s" % invalid_path) from exc
|
||||
|
||||
|
||||
@click.command("ci", short_help="Continuous Integration")
|
||||
@click.argument("src", nargs=-1, callback=validate_path)
|
||||
@click.option("-l", "--lib", multiple=True, callback=validate_path, metavar="DIRECTORY")
|
||||
@click.option("--exclude", multiple=True)
|
||||
@click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards)
|
||||
@click.option(
|
||||
"--build-dir",
|
||||
default=tempfile.mkdtemp,
|
||||
type=click.Path(file_okay=False, dir_okay=True, writable=True),
|
||||
)
|
||||
@click.option("--keep-build-dir", is_flag=True)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--project-conf",
|
||||
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
|
||||
)
|
||||
@click.option("-O", "--project-option", multiple=True)
|
||||
@click.option("-e", "--environment", "environments", multiple=True)
|
||||
@click.option("-v", "--verbose", is_flag=True)
|
||||
@click.pass_context
|
||||
def cli( # pylint: disable=too-many-arguments, too-many-branches
|
||||
ctx,
|
||||
src,
|
||||
lib,
|
||||
exclude,
|
||||
board,
|
||||
build_dir,
|
||||
keep_build_dir,
|
||||
project_conf,
|
||||
project_option,
|
||||
environments,
|
||||
verbose,
|
||||
):
|
||||
if not src and os.getenv("PLATFORMIO_CI_SRC"):
|
||||
src = validate_path(ctx, None, os.getenv("PLATFORMIO_CI_SRC").split(":"))
|
||||
if not src:
|
||||
raise click.BadParameter("Missing argument 'src'")
|
||||
|
||||
try:
|
||||
if not keep_build_dir and os.path.isdir(build_dir):
|
||||
fs.rmtree(build_dir)
|
||||
if not os.path.isdir(build_dir):
|
||||
os.makedirs(build_dir)
|
||||
|
||||
for dir_name, patterns in dict(lib=lib, src=src).items():
|
||||
if not patterns:
|
||||
continue
|
||||
contents = []
|
||||
for p in patterns:
|
||||
contents += glob.glob(p, recursive=True)
|
||||
_copy_contents(os.path.join(build_dir, dir_name), contents)
|
||||
|
||||
if project_conf and os.path.isfile(project_conf):
|
||||
_copy_project_conf(build_dir, project_conf)
|
||||
elif not board:
|
||||
raise CIBuildEnvsEmpty()
|
||||
|
||||
if exclude:
|
||||
_exclude_contents(build_dir, exclude)
|
||||
|
||||
# initialise project
|
||||
ctx.invoke(
|
||||
project_init_cmd,
|
||||
project_dir=build_dir,
|
||||
boards=board,
|
||||
project_options=project_option,
|
||||
)
|
||||
|
||||
# process project
|
||||
ctx.invoke(
|
||||
cmd_run, project_dir=build_dir, environment=environments, verbose=verbose
|
||||
)
|
||||
finally:
|
||||
if not keep_build_dir:
|
||||
fs.rmtree(build_dir)
|
||||
|
||||
|
||||
def _copy_contents(dst_dir, contents): # pylint: disable=too-many-branches
|
||||
items = {"dirs": set(), "files": set()}
|
||||
|
||||
for path in contents:
|
||||
if os.path.isdir(path):
|
||||
items["dirs"].add(path)
|
||||
elif os.path.isfile(path):
|
||||
items["files"].add(path)
|
||||
|
||||
dst_dir_name = os.path.basename(dst_dir)
|
||||
|
||||
if dst_dir_name == "src" and len(items["dirs"]) == 1:
|
||||
if not os.path.isdir(dst_dir):
|
||||
shutil.copytree(list(items["dirs"]).pop(), dst_dir, symlinks=True)
|
||||
else:
|
||||
if not os.path.isdir(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
for d in items["dirs"]:
|
||||
src_dst_dir = os.path.join(dst_dir, os.path.basename(d))
|
||||
if not os.path.isdir(src_dst_dir):
|
||||
shutil.copytree(d, src_dst_dir, symlinks=True)
|
||||
|
||||
if not items["files"]:
|
||||
return
|
||||
|
||||
if dst_dir_name == "lib":
|
||||
dst_dir = os.path.join(dst_dir, tempfile.mkdtemp(dir=dst_dir))
|
||||
|
||||
for f in items["files"]:
|
||||
dst_file = os.path.join(dst_dir, os.path.basename(f))
|
||||
if f == dst_file:
|
||||
continue
|
||||
shutil.copyfile(f, dst_file)
|
||||
|
||||
|
||||
def _exclude_contents(dst_dir, patterns):
|
||||
contents = []
|
||||
for p in patterns:
|
||||
contents += glob.glob(os.path.join(glob.escape(dst_dir), p), recursive=True)
|
||||
for path in contents:
|
||||
path = os.path.abspath(path)
|
||||
if os.path.isdir(path):
|
||||
fs.rmtree(path)
|
||||
elif os.path.isfile(path):
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def _copy_project_conf(build_dir, project_conf):
|
||||
config = ProjectConfig(project_conf, parse_extra=False)
|
||||
if config.has_section("platformio"):
|
||||
config.remove_section("platformio")
|
||||
config.save(os.path.join(build_dir, "platformio.ini"))
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from platformio.device.monitor.filters.base import (
|
||||
DeviceMonitorFilterBase as DeviceMonitorFilter,
|
||||
)
|
||||
|
|
@ -0,0 +1,459 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-branches, too-many-locals
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio import exception, fs
|
||||
from platformio.cli import PlatformioCLI
|
||||
from platformio.package.commands.install import package_install_cmd
|
||||
from platformio.package.commands.list import package_list_cmd
|
||||
from platformio.package.commands.search import package_search_cmd
|
||||
from platformio.package.commands.show import package_show_cmd
|
||||
from platformio.package.commands.uninstall import package_uninstall_cmd
|
||||
from platformio.package.commands.update import package_update_cmd
|
||||
from platformio.package.exception import NotGlobalLibDir
|
||||
from platformio.package.manager.library import LibraryPackageManager
|
||||
from platformio.package.meta import PackageItem, PackageSpec
|
||||
from platformio.proc import is_ci
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import get_project_dir, is_platformio_project
|
||||
|
||||
CTX_META_INPUT_DIRS_KEY = __name__ + ".input_dirs"
|
||||
CTX_META_PROJECT_ENVIRONMENTS_KEY = __name__ + ".project_environments"
|
||||
CTX_META_STORAGE_DIRS_KEY = __name__ + ".storage_dirs"
|
||||
CTX_META_STORAGE_LIBDEPS_KEY = __name__ + ".storage_lib_deps"
|
||||
|
||||
|
||||
def get_project_global_lib_dir():
|
||||
return ProjectConfig.get_instance().get("platformio", "globallib_dir")
|
||||
|
||||
|
||||
def invoke_command(ctx, cmd, **kwargs):
|
||||
input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])
|
||||
project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]
|
||||
for input_dir in input_dirs:
|
||||
cmd_kwargs = kwargs.copy()
|
||||
if is_platformio_project(input_dir):
|
||||
cmd_kwargs["project_dir"] = input_dir
|
||||
cmd_kwargs["environments"] = project_environments
|
||||
else:
|
||||
cmd_kwargs["global"] = True
|
||||
cmd_kwargs["storage_dir"] = input_dir
|
||||
ctx.invoke(cmd, **cmd_kwargs)
|
||||
|
||||
|
||||
@click.group(short_help="Library manager", hidden=True)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--storage-dir",
|
||||
multiple=True,
|
||||
default=None,
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),
|
||||
help="Manage custom library storage",
|
||||
)
|
||||
@click.option(
|
||||
"-g", "--global", is_flag=True, help="Manage global PlatformIO library storage"
|
||||
)
|
||||
@click.option(
|
||||
"-e",
|
||||
"--environment",
|
||||
multiple=True,
|
||||
help=(
|
||||
"Manage libraries for the specific project build environments "
|
||||
"declared in `platformio.ini`"
|
||||
),
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(ctx, **options):
|
||||
in_silence = PlatformioCLI.in_silence()
|
||||
storage_cmds = ("install", "uninstall", "update", "list")
|
||||
# skip commands that don't need storage folder
|
||||
if ctx.invoked_subcommand not in storage_cmds or (
|
||||
len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")
|
||||
):
|
||||
return
|
||||
storage_dirs = list(options["storage_dir"])
|
||||
if options["global"]:
|
||||
storage_dirs.append(get_project_global_lib_dir())
|
||||
if not storage_dirs:
|
||||
if is_platformio_project():
|
||||
storage_dirs = [get_project_dir()]
|
||||
elif is_ci():
|
||||
storage_dirs = [get_project_global_lib_dir()]
|
||||
click.secho(
|
||||
"Warning! Global library storage is used automatically. "
|
||||
"Please use `platformio lib --global %s` command to remove "
|
||||
"this warning." % ctx.invoked_subcommand,
|
||||
fg="yellow",
|
||||
)
|
||||
|
||||
if not storage_dirs:
|
||||
raise NotGlobalLibDir(
|
||||
get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand
|
||||
)
|
||||
|
||||
ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options["environment"]
|
||||
ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = []
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {}
|
||||
for storage_dir in storage_dirs:
|
||||
if not is_platformio_project(storage_dir):
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
continue
|
||||
with fs.cd(storage_dir):
|
||||
config = ProjectConfig.get_instance(
|
||||
os.path.join(storage_dir, "platformio.ini")
|
||||
)
|
||||
config.validate(options["environment"], silent=in_silence)
|
||||
libdeps_dir = config.get("platformio", "libdeps_dir")
|
||||
for env in config.envs():
|
||||
if options["environment"] and env not in options["environment"]:
|
||||
continue
|
||||
storage_dir = os.path.join(libdeps_dir, env)
|
||||
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
|
||||
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(
|
||||
"env:" + env, "lib_deps", []
|
||||
)
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install library")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Save installed libraries into the `platformio.ini` dependency list"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"--interactive",
|
||||
is_flag=True,
|
||||
help="Deprecated! Please use a strict dependency specification (owner/libname)",
|
||||
)
|
||||
@click.option(
|
||||
"-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
|
||||
)
|
||||
@click.pass_context
|
||||
def lib_install( # pylint: disable=too-many-arguments,unused-argument
|
||||
ctx, libraries, save, silent, interactive, force
|
||||
):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg install` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(
|
||||
ctx,
|
||||
package_install_cmd,
|
||||
libraries=libraries,
|
||||
no_save=not save,
|
||||
force=force,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Remove libraries")
|
||||
@click.argument("libraries", nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"--save/--no-save",
|
||||
is_flag=True,
|
||||
default=True,
|
||||
help="Remove libraries from the `platformio.ini` dependency list and save changes"
|
||||
" (enabled by default)",
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.pass_context
|
||||
def lib_uninstall(ctx, libraries, save, silent):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
invoke_command(
|
||||
ctx,
|
||||
package_uninstall_cmd,
|
||||
libraries=libraries,
|
||||
no_save=not save,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed libraries")
|
||||
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_update( # pylint: disable=too-many-arguments
|
||||
ctx, libraries, only_check, dry_run, silent, json_output
|
||||
):
|
||||
only_check = dry_run or only_check
|
||||
if only_check and not json_output:
|
||||
raise exception.UserSideException(
|
||||
"This command is deprecated, please use `pio pkg outdated` instead"
|
||||
)
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg update` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(
|
||||
ctx,
|
||||
package_update_cmd,
|
||||
libraries=libraries,
|
||||
silent=silent,
|
||||
)
|
||||
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
lib_deps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, {}).get(storage_dir, [])
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
lm.set_log_level(logging.WARN if silent else logging.DEBUG)
|
||||
_libraries = libraries or lib_deps or lm.get_installed()
|
||||
|
||||
result = []
|
||||
for library in _libraries:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(library, PackageItem):
|
||||
pkg = library
|
||||
else:
|
||||
spec = PackageSpec(library)
|
||||
pkg = lm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = lm.outdated(pkg, spec)
|
||||
if not outdated.is_outdated(allow_incompatible=True):
|
||||
continue
|
||||
manifest = lm.legacy_load_manifest(pkg)
|
||||
manifest["versionWanted"] = (
|
||||
str(outdated.wanted) if outdated.wanted else None
|
||||
)
|
||||
manifest["versionLatest"] = (
|
||||
str(outdated.latest) if outdated.latest else None
|
||||
)
|
||||
result.append(manifest)
|
||||
|
||||
json_result[storage_dir] = result
|
||||
|
||||
return click.echo(
|
||||
json.dumps(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed libraries")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_list(ctx, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg list` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return invoke_command(ctx, package_list_cmd, only_libraries=True)
|
||||
|
||||
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
|
||||
json_result = {}
|
||||
for storage_dir in storage_dirs:
|
||||
lm = LibraryPackageManager(storage_dir)
|
||||
json_result[storage_dir] = lm.legacy_get_installed()
|
||||
return click.echo(
|
||||
json.dumps(
|
||||
json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for a library")
|
||||
@click.argument("query", required=False, nargs=-1)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option("--page", type=click.INT, default=1)
|
||||
@click.option("--id", multiple=True)
|
||||
@click.option("-o", "--owner", multiple=True)
|
||||
@click.option("-n", "--name", multiple=True)
|
||||
@click.option("-a", "--author", multiple=True)
|
||||
@click.option("-k", "--keyword", multiple=True)
|
||||
@click.option("-f", "--framework", multiple=True)
|
||||
@click.option("-p", "--platform", multiple=True)
|
||||
@click.option("-i", "--header", multiple=True)
|
||||
@click.option(
|
||||
"--noninteractive",
|
||||
is_flag=True,
|
||||
help="Do not prompt, automatically paginate with delay",
|
||||
)
|
||||
@click.pass_context
|
||||
def lib_search( # pylint: disable=unused-argument
|
||||
ctx, query, json_output, page, noninteractive, **filters
|
||||
):
|
||||
if not query:
|
||||
query = []
|
||||
if not isinstance(query, list):
|
||||
query = list(query)
|
||||
|
||||
for key, values in filters.items():
|
||||
for value in values:
|
||||
query.append('%s:"%s"' % (key, value))
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg search` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
query.append("type:library")
|
||||
return ctx.invoke(package_search_cmd, query=" ".join(query), page=page)
|
||||
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
result = regclient.fetch_json_data(
|
||||
"get",
|
||||
"/v2/lib/search",
|
||||
params=dict(query=" ".join(query), page=page),
|
||||
x_cache_valid="1d",
|
||||
)
|
||||
return click.echo(json.dumps(result))
|
||||
|
||||
|
||||
@cli.command("builtin", short_help="List built-in libraries")
|
||||
@click.option("--storage", multiple=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_builtin(storage, json_output):
|
||||
items = LibraryPackageManager.get_builtin_libs(storage)
|
||||
if json_output:
|
||||
return click.echo(json.dumps(items))
|
||||
|
||||
for storage_ in items:
|
||||
if not storage_["items"]:
|
||||
continue
|
||||
click.secho(storage_["name"], fg="green")
|
||||
click.echo("*" * len(storage_["name"]))
|
||||
click.echo()
|
||||
|
||||
for item in sorted(storage_["items"], key=lambda i: i["name"]):
|
||||
print_lib_item(item)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show detailed info about a library")
|
||||
@click.argument("library", metavar="[LIBRARY]")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def lib_show(ctx, library, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg show` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_show_cmd, pkg_type="library", spec=library)
|
||||
|
||||
lm = LibraryPackageManager()
|
||||
lm.set_log_level(logging.ERROR if json_output else logging.DEBUG)
|
||||
lib_id = lm.reveal_registry_package_id(library)
|
||||
regclient = lm.get_registry_client_instance()
|
||||
lib = regclient.fetch_json_data(
|
||||
"get", "/v2/lib/info/%d" % lib_id, x_cache_valid="1h"
|
||||
)
|
||||
return click.echo(json.dumps(lib))
|
||||
|
||||
|
||||
@cli.command("register", short_help="Deprecated")
|
||||
@click.argument("config_url")
|
||||
def lib_register(config_url): # pylint: disable=unused-argument
|
||||
raise exception.UserSideException(
|
||||
"This command is deprecated. Please use `pio pkg publish` command."
|
||||
)
|
||||
|
||||
|
||||
@cli.command("stats", short_help="Library Registry Statistics")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def lib_stats(json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease visit "
|
||||
"https://registry.platformio.org\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return None
|
||||
|
||||
regclient = LibraryPackageManager().get_registry_client_instance()
|
||||
result = regclient.fetch_json_data("get", "/v2/lib/stats", x_cache_valid="1h")
|
||||
return click.echo(json.dumps(result))
|
||||
|
||||
|
||||
def print_lib_item(item):
|
||||
click.secho(item["name"], fg="cyan")
|
||||
click.echo("=" * len(item["name"]))
|
||||
if "id" in item:
|
||||
click.secho("#ID: %d" % item["id"], bold=True)
|
||||
if "description" in item or "url" in item:
|
||||
click.echo(item.get("description", item.get("url", "")))
|
||||
click.echo()
|
||||
|
||||
for key in ("version", "homepage", "license", "keywords"):
|
||||
if key not in item or not item[key]:
|
||||
continue
|
||||
if isinstance(item[key], list):
|
||||
click.echo("%s: %s" % (key.capitalize(), ", ".join(item[key])))
|
||||
else:
|
||||
click.echo("%s: %s" % (key.capitalize(), item[key]))
|
||||
|
||||
for key in ("frameworks", "platforms"):
|
||||
if key not in item:
|
||||
continue
|
||||
click.echo(
|
||||
"Compatible %s: %s"
|
||||
% (
|
||||
key,
|
||||
", ".join(
|
||||
[i["title"] if isinstance(i, dict) else i for i in item[key]]
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if "authors" in item or "authornames" in item:
|
||||
click.echo(
|
||||
"Authors: %s"
|
||||
% ", ".join(
|
||||
item.get(
|
||||
"authornames", [a.get("name", "") for a in item.get("authors", [])]
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "__src_url" in item:
|
||||
click.secho("Source: %s" % item["__src_url"])
|
||||
click.echo()
|
||||
|
|
@ -0,0 +1,391 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import click
|
||||
|
||||
from platformio.exception import UserSideException
|
||||
from platformio.package.commands.install import package_install_cmd
|
||||
from platformio.package.commands.list import package_list_cmd
|
||||
from platformio.package.commands.search import package_search_cmd
|
||||
from platformio.package.commands.show import package_show_cmd
|
||||
from platformio.package.commands.uninstall import package_uninstall_cmd
|
||||
from platformio.package.commands.update import package_update_cmd
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
from platformio.package.meta import PackageItem, PackageSpec
|
||||
from platformio.package.version import get_original_version
|
||||
from platformio.platform.exception import UnknownPlatform
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
|
||||
|
||||
@click.group(short_help="Platform manager", hidden=True)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("search", short_help="Search for development platform")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_search(ctx, query, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg search` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
query = query or ""
|
||||
return ctx.invoke(package_search_cmd, query=f"type:platform {query}".strip())
|
||||
|
||||
platforms = []
|
||||
for platform in _get_registry_platforms():
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = json.dumps(platform)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
platforms.append(
|
||||
_get_registry_platform_data(
|
||||
platform["name"], with_boards=False, expose_packages=False
|
||||
)
|
||||
)
|
||||
click.echo(json.dumps(platforms))
|
||||
return None
|
||||
|
||||
|
||||
@cli.command("frameworks", short_help="List supported frameworks, SDKs")
|
||||
@click.argument("query", required=False)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def platform_frameworks(query, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease visit https://docs.platformio.org"
|
||||
"/en/latest/frameworks/index.html\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return
|
||||
|
||||
regclient = PlatformPackageManager().get_registry_client_instance()
|
||||
frameworks = []
|
||||
for framework in regclient.fetch_json_data(
|
||||
"get", "/v2/frameworks", x_cache_valid="1d"
|
||||
):
|
||||
if query == "all":
|
||||
query = ""
|
||||
search_data = json.dumps(framework)
|
||||
if query and query.lower() not in search_data.lower():
|
||||
continue
|
||||
framework["homepage"] = "https://platformio.org/frameworks/" + framework["name"]
|
||||
framework["platforms"] = [
|
||||
platform["name"]
|
||||
for platform in _get_registry_platforms()
|
||||
if framework["name"] in platform["frameworks"]
|
||||
]
|
||||
frameworks.append(framework)
|
||||
|
||||
frameworks = sorted(frameworks, key=lambda manifest: manifest["name"])
|
||||
click.echo(json.dumps(frameworks))
|
||||
|
||||
|
||||
@cli.command("list", short_help="List installed development platforms")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_list(ctx, json_output):
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg list` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_list_cmd, **{"global": True, "only_platforms": True})
|
||||
|
||||
platforms = []
|
||||
pm = PlatformPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
platforms.append(
|
||||
_get_installed_platform_data(pkg, with_boards=False, expose_packages=False)
|
||||
)
|
||||
|
||||
platforms = sorted(platforms, key=lambda manifest: manifest["name"])
|
||||
click.echo(json.dumps(platforms))
|
||||
return None
|
||||
|
||||
|
||||
@cli.command("show", short_help="Show details about development platform")
|
||||
@click.argument("platform")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_show(ctx, platform, json_output): # pylint: disable=too-many-branches
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg show` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(package_show_cmd, pkg_type="platform", spec=platform)
|
||||
|
||||
data = _get_platform_data(platform)
|
||||
if not data:
|
||||
raise UnknownPlatform(platform)
|
||||
return click.echo(json.dumps(data))
|
||||
|
||||
|
||||
@cli.command("install", short_help="Install new development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
@click.option("--with-package", multiple=True)
|
||||
@click.option("--without-package", multiple=True)
|
||||
@click.option("--skip-default-package", is_flag=True)
|
||||
@click.option("--with-all-packages", is_flag=True)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option(
|
||||
"-f",
|
||||
"--force",
|
||||
is_flag=True,
|
||||
help="Reinstall/redownload dev/platform and its packages if exist",
|
||||
)
|
||||
@click.pass_context
|
||||
def platform_install( # pylint: disable=too-many-arguments
|
||||
ctx,
|
||||
platforms,
|
||||
with_package,
|
||||
without_package,
|
||||
skip_default_package,
|
||||
with_all_packages,
|
||||
silent,
|
||||
force,
|
||||
):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg install` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
ctx.invoke(
|
||||
package_install_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
"skip_dependencies": (
|
||||
not with_all_packages
|
||||
and (with_package or without_package or skip_default_package)
|
||||
),
|
||||
"silent": silent,
|
||||
"force": force,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@cli.command("uninstall", short_help="Uninstall development platform")
|
||||
@click.argument("platforms", nargs=-1, required=True, metavar="[PLATFORM...]")
|
||||
@click.pass_context
|
||||
def platform_uninstall(ctx, platforms):
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg uninstall` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
ctx.invoke(
|
||||
package_uninstall_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@cli.command("update", short_help="Update installed development platforms")
|
||||
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
|
||||
@click.option(
|
||||
"-p", "--only-packages", is_flag=True, help="Update only the platform packages"
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
@click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.pass_context
|
||||
def platform_update( # pylint: disable=too-many-locals, too-many-arguments
|
||||
ctx, platforms, only_check, dry_run, silent, json_output, **_
|
||||
):
|
||||
only_check = dry_run or only_check
|
||||
|
||||
if only_check and not json_output:
|
||||
raise UserSideException(
|
||||
"This command is deprecated, please use `pio pkg outdated` instead"
|
||||
)
|
||||
|
||||
if not json_output:
|
||||
click.secho(
|
||||
"\nWARNING: This command is deprecated and will be removed in "
|
||||
"the next releases. \nPlease use `pio pkg update` instead.\n",
|
||||
fg="yellow",
|
||||
)
|
||||
return ctx.invoke(
|
||||
package_update_cmd,
|
||||
**{
|
||||
"global": True,
|
||||
"platforms": platforms,
|
||||
"silent": silent,
|
||||
},
|
||||
)
|
||||
|
||||
pm = PlatformPackageManager()
|
||||
pm.set_log_level(logging.WARN if silent else logging.DEBUG)
|
||||
platforms = platforms or pm.get_installed()
|
||||
result = []
|
||||
for platform in platforms:
|
||||
spec = None
|
||||
pkg = None
|
||||
if isinstance(platform, PackageItem):
|
||||
pkg = platform
|
||||
else:
|
||||
spec = PackageSpec(platform)
|
||||
pkg = pm.get_package(spec)
|
||||
if not pkg:
|
||||
continue
|
||||
outdated = pm.outdated(pkg, spec)
|
||||
if (
|
||||
not outdated.is_outdated(allow_incompatible=True)
|
||||
and not PlatformFactory.new(pkg).are_outdated_packages()
|
||||
):
|
||||
continue
|
||||
data = _get_installed_platform_data(
|
||||
pkg, with_boards=False, expose_packages=False
|
||||
)
|
||||
if outdated.is_outdated(allow_incompatible=True):
|
||||
data["versionLatest"] = str(outdated.latest) if outdated.latest else None
|
||||
result.append(data)
|
||||
click.echo(json.dumps(result))
|
||||
return True
|
||||
|
||||
|
||||
#
|
||||
# Helpers
|
||||
#
|
||||
|
||||
|
||||
def _get_registry_platforms():
|
||||
regclient = PlatformPackageManager().get_registry_client_instance()
|
||||
return regclient.fetch_json_data("get", "/v2/platforms", x_cache_valid="1d")
|
||||
|
||||
|
||||
def _get_platform_data(*args, **kwargs):
|
||||
try:
|
||||
return _get_installed_platform_data(*args, **kwargs)
|
||||
except UnknownPlatform:
|
||||
return _get_registry_platform_data(*args, **kwargs)
|
||||
|
||||
|
||||
def _get_installed_platform_data(platform, with_boards=True, expose_packages=True):
|
||||
p = PlatformFactory.new(platform)
|
||||
data = dict(
|
||||
name=p.name,
|
||||
title=p.title,
|
||||
description=p.description,
|
||||
version=p.version,
|
||||
homepage=p.homepage,
|
||||
url=p.homepage,
|
||||
repository=p.repository_url,
|
||||
license=p.license,
|
||||
forDesktop=not p.is_embedded(),
|
||||
frameworks=sorted(list(p.frameworks) if p.frameworks else []),
|
||||
packages=list(p.packages) if p.packages else [],
|
||||
)
|
||||
|
||||
# if dump to API
|
||||
# del data['version']
|
||||
# return data
|
||||
|
||||
# overwrite VCS version and add extra fields
|
||||
manifest = PlatformPackageManager().legacy_load_manifest(
|
||||
os.path.dirname(p.manifest_path)
|
||||
)
|
||||
assert manifest
|
||||
for key in manifest:
|
||||
if key == "version" or key.startswith("__"):
|
||||
data[key] = manifest[key]
|
||||
|
||||
if with_boards:
|
||||
data["boards"] = [c.get_brief_data() for c in p.get_boards().values()]
|
||||
|
||||
if not data["packages"] or not expose_packages:
|
||||
return data
|
||||
|
||||
data["packages"] = []
|
||||
installed_pkgs = {
|
||||
pkg.metadata.name: p.pm.load_manifest(pkg) for pkg in p.get_installed_packages()
|
||||
}
|
||||
for name, options in p.packages.items():
|
||||
item = dict(
|
||||
name=name,
|
||||
type=p.get_package_type(name),
|
||||
requirements=options.get("version"),
|
||||
optional=options.get("optional") is True,
|
||||
)
|
||||
if name in installed_pkgs:
|
||||
for key, value in installed_pkgs[name].items():
|
||||
if key not in ("url", "version", "description"):
|
||||
continue
|
||||
item[key] = value
|
||||
if key == "version":
|
||||
item["originalVersion"] = get_original_version(value)
|
||||
data["packages"].append(item)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _get_registry_platform_data( # pylint: disable=unused-argument
|
||||
platform, with_boards=True, expose_packages=True
|
||||
):
|
||||
_data = None
|
||||
for p in _get_registry_platforms():
|
||||
if p["name"] == platform:
|
||||
_data = p
|
||||
break
|
||||
|
||||
if not _data:
|
||||
return None
|
||||
|
||||
data = dict(
|
||||
ownername=_data.get("ownername"),
|
||||
name=_data["name"],
|
||||
title=_data["title"],
|
||||
description=_data["description"],
|
||||
homepage=_data["homepage"],
|
||||
repository=_data["repository"],
|
||||
url=_data["url"],
|
||||
license=_data["license"],
|
||||
forDesktop=_data["forDesktop"],
|
||||
frameworks=_data["frameworks"],
|
||||
packages=_data["packages"],
|
||||
versions=_data.get("versions"),
|
||||
)
|
||||
|
||||
if with_boards:
|
||||
data["boards"] = [
|
||||
board
|
||||
for board in PlatformPackageManager().get_registered_boards()
|
||||
if board["platform"] == _data["name"]
|
||||
]
|
||||
|
||||
return data
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
from tabulate import tabulate
|
||||
|
||||
from platformio import app
|
||||
from platformio.compat import string_types
|
||||
|
||||
|
||||
def format_value(raw):
|
||||
if isinstance(raw, bool):
|
||||
return "Yes" if raw else "No"
|
||||
if isinstance(raw, string_types):
|
||||
return raw
|
||||
return str(raw)
|
||||
|
||||
|
||||
@click.group(short_help="Manage system settings")
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@cli.command("get", short_help="Get existing setting/-s")
|
||||
@click.argument("name", required=False)
|
||||
def settings_get(name):
|
||||
tabular_data = []
|
||||
for key, options in sorted(app.DEFAULT_SETTINGS.items()):
|
||||
if name and name != key:
|
||||
continue
|
||||
raw_value = app.get_setting(key)
|
||||
formatted_value = format_value(raw_value)
|
||||
|
||||
if raw_value != options["value"]:
|
||||
default_formatted_value = format_value(options["value"])
|
||||
formatted_value += "%s" % (
|
||||
"\n" if len(default_formatted_value) > 10 else " "
|
||||
)
|
||||
formatted_value += "[%s]" % click.style(
|
||||
default_formatted_value, fg="yellow"
|
||||
)
|
||||
|
||||
tabular_data.append(
|
||||
(click.style(key, fg="cyan"), formatted_value, options["description"])
|
||||
)
|
||||
|
||||
click.echo(
|
||||
tabulate(
|
||||
tabular_data, headers=["Name", "Current value [Default]", "Description"]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@cli.command("set", short_help="Set new value for the setting")
|
||||
@click.argument("name")
|
||||
@click.argument("value")
|
||||
@click.pass_context
|
||||
def settings_set(ctx, name, value):
|
||||
app.set_setting(name, value)
|
||||
click.secho("The new value for the setting has been set!", fg="green")
|
||||
ctx.invoke(settings_get, name=name)
|
||||
|
||||
|
||||
@cli.command("reset", short_help="Reset settings to default")
|
||||
@click.pass_context
|
||||
def settings_reset(ctx):
|
||||
app.reset_settings()
|
||||
click.secho("The settings have been reseted!", fg="green")
|
||||
ctx.invoke(settings_get)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command(
|
||||
"update",
|
||||
short_help="Update installed platforms, packages and libraries",
|
||||
hidden=True,
|
||||
)
|
||||
@click.option("--core-packages", is_flag=True, help="Update only the core packages")
|
||||
@click.option(
|
||||
"-c",
|
||||
"--only-check",
|
||||
is_flag=True,
|
||||
help="DEPRECATED. Please use `--dry-run` instead",
|
||||
)
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
|
||||
)
|
||||
def cli(*_, **__):
|
||||
click.secho(
|
||||
"This command is deprecated and will be removed in the next releases. \n"
|
||||
"Please use `pio pkg update` instead.",
|
||||
fg="yellow",
|
||||
)
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
|
||||
from platformio import VERSION, __install_requires__, __version__, app, exception
|
||||
from platformio.http import fetch_remote_content
|
||||
from platformio.package.manager.core import update_core_packages
|
||||
from platformio.proc import get_pythonexe_path
|
||||
|
||||
PYPI_JSON_URL = "https://pypi.org/pypi/platformio/json"
|
||||
DEVELOP_ZIP_URL = "https://github.com/platformio/platformio-core/archive/develop.zip"
|
||||
DEVELOP_INIT_SCRIPT_URL = (
|
||||
"https://raw.githubusercontent.com/platformio/platformio-core"
|
||||
"/develop/platformio/__init__.py"
|
||||
)
|
||||
|
||||
|
||||
@click.command("upgrade", short_help="Upgrade PlatformIO Core to the latest version")
|
||||
@click.option("--dev", is_flag=True, help="Use development branch")
|
||||
@click.option("--only-dependencies", is_flag=True)
|
||||
@click.option("--verbose", "-v", is_flag=True)
|
||||
def cli(dev, only_dependencies, verbose):
|
||||
if only_dependencies:
|
||||
return upgrade_pypi_dependencies(verbose)
|
||||
|
||||
update_core_packages()
|
||||
|
||||
if not dev and __version__ == get_latest_version():
|
||||
return click.secho(
|
||||
"You're up-to-date!\nPlatformIO %s is currently the "
|
||||
"newest version available." % __version__,
|
||||
fg="green",
|
||||
)
|
||||
|
||||
click.secho("Please wait while upgrading PlatformIO Core ...", fg="yellow")
|
||||
|
||||
python_exe = get_pythonexe_path()
|
||||
to_develop = dev or not all(c.isdigit() for c in __version__ if c != ".")
|
||||
pkg_spec = DEVELOP_ZIP_URL if to_develop else "platformio"
|
||||
|
||||
try:
|
||||
# PIO Core
|
||||
subprocess.run(
|
||||
[python_exe, "-m", "pip", "install", "--upgrade", pkg_spec],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE if not verbose else None,
|
||||
)
|
||||
|
||||
# PyPI dependencies
|
||||
subprocess.run(
|
||||
[python_exe, "-m", "platformio", "upgrade", "--only-dependencies"],
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
# Check version
|
||||
output = subprocess.run(
|
||||
[python_exe, "-m", "platformio", "--version"],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.decode()
|
||||
assert "version" in output
|
||||
actual_version = output.split("version", 1)[1].strip()
|
||||
click.secho(
|
||||
"PlatformIO has been successfully upgraded to %s" % actual_version,
|
||||
fg="green",
|
||||
)
|
||||
click.echo("Release notes: ", nl=False)
|
||||
click.secho("https://docs.platformio.org/en/latest/history.html", fg="cyan")
|
||||
if app.get_session_var("caller_id"):
|
||||
click.secho(
|
||||
"Warning! Please restart IDE to affect PIO Home changes", fg="yellow"
|
||||
)
|
||||
except (AssertionError, subprocess.CalledProcessError) as exc:
|
||||
click.secho(
|
||||
"\nWarning!!! Could not automatically upgrade the PlatformIO Core.",
|
||||
fg="red",
|
||||
)
|
||||
click.secho(
|
||||
"Please upgrade it manually using the following command:\n",
|
||||
fg="red",
|
||||
)
|
||||
click.secho(f'"{python_exe}" -m pip install -U {pkg_spec}\n', fg="cyan")
|
||||
raise exception.ReturnErrorCode(1) from exc
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def upgrade_pypi_dependencies(verbose):
|
||||
subprocess.run(
|
||||
[
|
||||
get_pythonexe_path(),
|
||||
"-m",
|
||||
"pip",
|
||||
"install",
|
||||
"--upgrade",
|
||||
"pip",
|
||||
*__install_requires__,
|
||||
],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE if not verbose else None,
|
||||
)
|
||||
|
||||
|
||||
def get_latest_version():
|
||||
try:
|
||||
if not str(VERSION[2]).isdigit():
|
||||
try:
|
||||
return get_develop_latest_version()
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
return get_pypi_latest_version()
|
||||
except Exception as exc:
|
||||
raise exception.GetLatestVersionError() from exc
|
||||
|
||||
|
||||
def get_develop_latest_version():
|
||||
version = None
|
||||
content = fetch_remote_content(DEVELOP_INIT_SCRIPT_URL)
|
||||
for line in content.split("\n"):
|
||||
line = line.strip()
|
||||
if not line.startswith("VERSION"):
|
||||
continue
|
||||
match = re.match(r"VERSION\s*=\s*\(([^\)]+)\)", line)
|
||||
if not match:
|
||||
continue
|
||||
version = match.group(1)
|
||||
for c in (" ", "'", '"'):
|
||||
version = version.replace(c, "")
|
||||
version = ".".join(version.split(","))
|
||||
assert version
|
||||
return version
|
||||
|
||||
|
||||
def get_pypi_latest_version():
|
||||
content = fetch_remote_content(PYPI_JSON_URL)
|
||||
return json.loads(content)["info"]["version"]
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=unused-import,no-name-in-module
|
||||
|
||||
import importlib.util
|
||||
import inspect
|
||||
import locale
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
from asyncio import create_task as aio_create_task
|
||||
from asyncio import get_running_loop as aio_get_running_loop
|
||||
else:
|
||||
from asyncio import ensure_future as aio_create_task
|
||||
from asyncio import get_event_loop as aio_get_running_loop
|
||||
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from shlex import join as shlex_join
|
||||
else:
|
||||
|
||||
def shlex_join(split_command):
|
||||
return " ".join(shlex.quote(arg) for arg in split_command)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from asyncio import to_thread as aio_to_thread
|
||||
else:
|
||||
from starlette.concurrency import run_in_threadpool as aio_to_thread
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2 # DO NOT REMOVE IT. ESP8266/ESP32 depend on it
|
||||
IS_CYGWIN = sys.platform.startswith("cygwin")
|
||||
IS_WINDOWS = WINDOWS = sys.platform.startswith("win")
|
||||
IS_MACOS = sys.platform.startswith("darwin")
|
||||
MISSING = object()
|
||||
string_types = (str,)
|
||||
|
||||
|
||||
def is_bytes(x):
|
||||
return isinstance(x, (bytes, memoryview, bytearray))
|
||||
|
||||
|
||||
def isascii(text):
|
||||
if sys.version_info >= (3, 7):
|
||||
return text.isascii()
|
||||
for c in text or "":
|
||||
if ord(c) > 127:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_terminal():
|
||||
try:
|
||||
return sys.stdout.isatty()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return False
|
||||
|
||||
|
||||
def ci_strings_are_equal(a, b):
|
||||
if a == b:
|
||||
return True
|
||||
if not a or not b:
|
||||
return False
|
||||
return a.strip().lower() == b.strip().lower()
|
||||
|
||||
|
||||
def hashlib_encode_data(data):
|
||||
if is_bytes(data):
|
||||
return data
|
||||
if not isinstance(data, string_types):
|
||||
data = str(data)
|
||||
return data.encode()
|
||||
|
||||
|
||||
def load_python_module(name, pathname):
|
||||
spec = importlib.util.spec_from_file_location(name, pathname)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def get_filesystem_encoding():
|
||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||
|
||||
|
||||
def get_locale_encoding():
|
||||
return locale.getpreferredencoding()
|
||||
|
||||
|
||||
def get_object_members(obj, ignore_private=True):
|
||||
members = inspect.getmembers(obj, lambda a: not inspect.isroutine(a))
|
||||
if not ignore_private:
|
||||
return members
|
||||
return {
|
||||
item[0]: item[1]
|
||||
for item in members
|
||||
if not (item[0].startswith("__") and item[0].endswith("__"))
|
||||
}
|
||||
|
||||
|
||||
def ensure_python3(raise_exception=True):
|
||||
compatible = sys.version_info >= (3, 6)
|
||||
if not raise_exception or compatible:
|
||||
return compatible
|
||||
raise UserSideException(
|
||||
"Python 3.6 or later is required for this operation. \n"
|
||||
"Please check a migration guide:\n"
|
||||
"https://docs.platformio.org/en/latest/core/migration.html"
|
||||
"#drop-support-for-python-2-and-3-5"
|
||||
)
|
||||
|
||||
|
||||
def path_to_unicode(path):
|
||||
"""
|
||||
Deprecated: Compatibility with dev-platforms,
|
||||
and custom device monitor filters
|
||||
"""
|
||||
return path
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# pylint: disable=too-many-arguments, too-many-locals
|
||||
# pylint: disable=too-many-branches, too-many-statements
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
import click
|
||||
|
||||
from platformio import app, exception, fs, proc
|
||||
from platformio.compat import IS_WINDOWS
|
||||
from platformio.debug import helpers
|
||||
from platformio.debug.config.factory import DebugConfigFactory
|
||||
from platformio.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.debug.process.gdb import GDBClientProcess
|
||||
from platformio.exception import ReturnErrorCode
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import is_platformio_project
|
||||
from platformio.project.options import ProjectOptions
|
||||
|
||||
|
||||
@click.command(
|
||||
"debug",
|
||||
context_settings=dict(ignore_unknown_options=True),
|
||||
short_help="Unified Debugger",
|
||||
)
|
||||
@click.option(
|
||||
"-d",
|
||||
"--project-dir",
|
||||
default=os.getcwd,
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),
|
||||
)
|
||||
@click.option(
|
||||
"-c",
|
||||
"--project-conf",
|
||||
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
|
||||
)
|
||||
@click.option("--environment", "-e", metavar="<environment>")
|
||||
@click.option("--load-mode", type=ProjectOptions["env.debug_load_mode"].type)
|
||||
@click.option("--verbose", "-v", is_flag=True)
|
||||
@click.option("--interface", type=click.Choice(["gdb"]))
|
||||
@click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED)
|
||||
@click.pass_context
|
||||
def cli(
|
||||
ctx,
|
||||
project_dir,
|
||||
project_conf,
|
||||
environment,
|
||||
load_mode,
|
||||
verbose,
|
||||
interface,
|
||||
__unprocessed,
|
||||
):
|
||||
app.set_session_var("custom_project_conf", project_conf)
|
||||
|
||||
# use env variables from Eclipse or CLion
|
||||
for name in ("CWD", "PWD", "PLATFORMIO_PROJECT_DIR"):
|
||||
if is_platformio_project(project_dir):
|
||||
break
|
||||
if os.getenv(name):
|
||||
project_dir = os.getenv(name)
|
||||
|
||||
with fs.cd(project_dir):
|
||||
project_config = ProjectConfig.get_instance(project_conf)
|
||||
project_config.validate(envs=[environment] if environment else None)
|
||||
env_name = environment or helpers.get_default_debug_env(project_config)
|
||||
|
||||
if not interface:
|
||||
return helpers.predebug_project(
|
||||
ctx, project_dir, project_config, env_name, False, verbose
|
||||
)
|
||||
|
||||
configure_args = (
|
||||
ctx,
|
||||
project_config,
|
||||
env_name,
|
||||
load_mode,
|
||||
verbose,
|
||||
__unprocessed,
|
||||
)
|
||||
if helpers.is_gdbmi_mode():
|
||||
os.environ["PLATFORMIO_DISABLE_PROGRESSBAR"] = "true"
|
||||
stream = helpers.GDBMIConsoleStream()
|
||||
with proc.capture_std_streams(stream):
|
||||
debug_config = _configure(*configure_args)
|
||||
stream.close()
|
||||
else:
|
||||
debug_config = _configure(*configure_args)
|
||||
|
||||
_run(project_dir, debug_config, __unprocessed)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _configure(ctx, project_config, env_name, load_mode, verbose, __unprocessed):
|
||||
platform = PlatformFactory.from_env(env_name, autoinstall=True)
|
||||
debug_config = DebugConfigFactory.new(
|
||||
platform,
|
||||
project_config,
|
||||
env_name,
|
||||
)
|
||||
if "--version" in __unprocessed:
|
||||
raise ReturnErrorCode(
|
||||
subprocess.run(
|
||||
[debug_config.client_executable_path, "--version"], check=True
|
||||
).returncode
|
||||
)
|
||||
|
||||
try:
|
||||
fs.ensure_udev_rules()
|
||||
except exception.InvalidUdevRules as exc:
|
||||
click.echo(str(exc))
|
||||
|
||||
rebuild_prog = False
|
||||
preload = debug_config.load_cmds == ["preload"]
|
||||
load_mode = load_mode or debug_config.load_mode
|
||||
if load_mode == "always":
|
||||
rebuild_prog = preload or not helpers.has_debug_symbols(
|
||||
debug_config.program_path
|
||||
)
|
||||
elif load_mode == "modified":
|
||||
rebuild_prog = helpers.is_prog_obsolete(
|
||||
debug_config.program_path
|
||||
) or not helpers.has_debug_symbols(debug_config.program_path)
|
||||
|
||||
if not (debug_config.program_path and os.path.isfile(debug_config.program_path)):
|
||||
rebuild_prog = True
|
||||
|
||||
if preload or (not rebuild_prog and load_mode != "always"):
|
||||
# don't load firmware through debug server
|
||||
debug_config.load_cmds = []
|
||||
|
||||
if rebuild_prog:
|
||||
click.echo("Preparing firmware for debugging...")
|
||||
helpers.predebug_project(
|
||||
ctx, os.getcwd(), project_config, env_name, preload, verbose
|
||||
)
|
||||
# save SHA sum of newly created prog
|
||||
if load_mode == "modified":
|
||||
helpers.is_prog_obsolete(debug_config.program_path)
|
||||
|
||||
if not os.path.isfile(debug_config.program_path):
|
||||
raise DebugInvalidOptionsError("Program/firmware is missed")
|
||||
|
||||
return debug_config
|
||||
|
||||
|
||||
def _run(project_dir, debug_config, __unprocessed):
|
||||
loop = asyncio.ProactorEventLoop() if IS_WINDOWS else asyncio.get_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
client = GDBClientProcess(project_dir, debug_config)
|
||||
coro = client.run(__unprocessed)
|
||||
try:
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
loop.run_until_complete(coro)
|
||||
if IS_WINDOWS:
|
||||
client.close()
|
||||
# an issue with `asyncio` executor and STIDIN,
|
||||
# it cannot be closed gracefully
|
||||
proc.force_exit()
|
||||
finally:
|
||||
client.close()
|
||||
loop.close()
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from platformio import fs, proc, util
|
||||
from platformio.compat import string_types
|
||||
from platformio.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import load_build_metadata
|
||||
from platformio.project.options import ProjectOptions
|
||||
|
||||
|
||||
class DebugConfigBase: # pylint: disable=too-many-instance-attributes
|
||||
def __init__(self, platform, project_config, env_name, port=None):
|
||||
self.platform = platform
|
||||
self.project_config = project_config
|
||||
self.env_name = env_name
|
||||
self.env_options = project_config.items(env=env_name, as_dict=True)
|
||||
self.build_data = self._load_build_data()
|
||||
|
||||
self.tool_name = None
|
||||
self.board_config = {}
|
||||
self.tool_settings = {}
|
||||
if "board" in self.env_options:
|
||||
self.board_config = platform.board_config(self.env_options["board"])
|
||||
self.tool_name = self.board_config.get_debug_tool_name(
|
||||
self.env_options.get("debug_tool")
|
||||
)
|
||||
self.tool_settings = (
|
||||
self.board_config.get("debug", {})
|
||||
.get("tools", {})
|
||||
.get(self.tool_name, {})
|
||||
)
|
||||
|
||||
self._load_cmds = None
|
||||
self._port = None
|
||||
|
||||
self.port = port
|
||||
self.server = self._configure_server()
|
||||
|
||||
try:
|
||||
platform.configure_debug_session(self)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def cleanup_cmds(items):
|
||||
items = ProjectConfig.parse_multi_values(items)
|
||||
return ["$LOAD_CMDS" if item == "$LOAD_CMD" else item for item in items]
|
||||
|
||||
@property
|
||||
def program_path(self):
|
||||
return self.build_data["prog_path"]
|
||||
|
||||
@property
|
||||
def client_executable_path(self):
|
||||
return self.build_data["gdb_path"]
|
||||
|
||||
@property
|
||||
def load_cmds(self):
|
||||
if self._load_cmds is not None:
|
||||
return self._load_cmds
|
||||
result = self.env_options.get("debug_load_cmds")
|
||||
if not result:
|
||||
result = self.tool_settings.get("load_cmds")
|
||||
if not result:
|
||||
# legacy
|
||||
result = self.tool_settings.get("load_cmd")
|
||||
if not result:
|
||||
result = ProjectOptions["env.debug_load_cmds"].default
|
||||
return self.cleanup_cmds(result)
|
||||
|
||||
@load_cmds.setter
|
||||
def load_cmds(self, cmds):
|
||||
self._load_cmds = cmds
|
||||
|
||||
@property
|
||||
def load_mode(self):
|
||||
result = self.env_options.get("debug_load_mode")
|
||||
if not result:
|
||||
result = self.tool_settings.get("load_mode")
|
||||
return result or ProjectOptions["env.debug_load_mode"].default
|
||||
|
||||
@property
|
||||
def init_break(self):
|
||||
missed = object()
|
||||
result = self.env_options.get("debug_init_break", missed)
|
||||
if result != missed:
|
||||
return result
|
||||
result = None
|
||||
if not result:
|
||||
result = self.tool_settings.get("init_break")
|
||||
return result or ProjectOptions["env.debug_init_break"].default
|
||||
|
||||
@property
|
||||
def init_cmds(self):
|
||||
return self.cleanup_cmds(
|
||||
self.env_options.get("debug_init_cmds", self.tool_settings.get("init_cmds"))
|
||||
)
|
||||
|
||||
@property
|
||||
def extra_cmds(self):
|
||||
return self.cleanup_cmds(
|
||||
self.env_options.get("debug_extra_cmds")
|
||||
) + self.cleanup_cmds(self.tool_settings.get("extra_cmds"))
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
return (
|
||||
self.env_options.get("debug_port", self.tool_settings.get("port"))
|
||||
or self._port
|
||||
)
|
||||
|
||||
@port.setter
|
||||
def port(self, value):
|
||||
self._port = value
|
||||
|
||||
@property
|
||||
def upload_protocol(self):
|
||||
return self.env_options.get(
|
||||
"upload_protocol", self.board_config.get("upload", {}).get("protocol")
|
||||
)
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
return self.env_options.get("debug_speed", self.tool_settings.get("speed"))
|
||||
|
||||
@property
|
||||
def server_ready_pattern(self):
|
||||
return self.env_options.get(
|
||||
"debug_server_ready_pattern", (self.server or {}).get("ready_pattern")
|
||||
)
|
||||
|
||||
def _load_build_data(self):
|
||||
data = load_build_metadata(os.getcwd(), self.env_name, cache=True, debug=True)
|
||||
if not data:
|
||||
raise DebugInvalidOptionsError("Could not load a build configuration")
|
||||
return data
|
||||
|
||||
def _configure_server(self):
|
||||
# user disabled server in platformio.ini
|
||||
if "debug_server" in self.env_options and not self.env_options.get(
|
||||
"debug_server"
|
||||
):
|
||||
return None
|
||||
|
||||
result = None
|
||||
|
||||
# specific server per a system
|
||||
if isinstance(self.tool_settings.get("server", {}), list):
|
||||
for item in self.tool_settings["server"][:]:
|
||||
self.tool_settings["server"] = item
|
||||
if util.get_systype() in item.get("system", []):
|
||||
break
|
||||
|
||||
# user overwrites debug server
|
||||
if self.env_options.get("debug_server"):
|
||||
result = {
|
||||
"cwd": None,
|
||||
"executable": None,
|
||||
"arguments": self.env_options.get("debug_server"),
|
||||
}
|
||||
result["executable"] = result["arguments"][0]
|
||||
result["arguments"] = result["arguments"][1:]
|
||||
elif "server" in self.tool_settings:
|
||||
result = self.tool_settings["server"]
|
||||
server_package = result.get("package")
|
||||
server_package_dir = (
|
||||
self.platform.get_package_dir(server_package)
|
||||
if server_package
|
||||
else None
|
||||
)
|
||||
if server_package and not server_package_dir:
|
||||
self.platform.install_package(server_package)
|
||||
server_package_dir = self.platform.get_package_dir(server_package)
|
||||
result.update(
|
||||
dict(
|
||||
cwd=server_package_dir if server_package else None,
|
||||
executable=result.get("executable"),
|
||||
arguments=[
|
||||
a.replace("$PACKAGE_DIR", server_package_dir)
|
||||
if server_package_dir
|
||||
else a
|
||||
for a in result.get("arguments", [])
|
||||
],
|
||||
)
|
||||
)
|
||||
return self.reveal_patterns(result) if result else None
|
||||
|
||||
def get_init_script(self, debugger):
|
||||
try:
|
||||
return getattr(self, "%s_INIT_SCRIPT" % debugger.upper())
|
||||
except AttributeError as exc:
|
||||
raise NotImplementedError from exc
|
||||
|
||||
def reveal_patterns(self, source, recursive=True):
|
||||
program_path = self.program_path or ""
|
||||
patterns = {
|
||||
"PLATFORMIO_CORE_DIR": self.project_config.get("platformio", "core_dir"),
|
||||
"PYTHONEXE": proc.get_pythonexe_path(),
|
||||
"PROJECT_DIR": os.getcwd(),
|
||||
"PROG_PATH": program_path,
|
||||
"PROG_DIR": os.path.dirname(program_path),
|
||||
"PROG_NAME": os.path.basename(os.path.splitext(program_path)[0]),
|
||||
"DEBUG_PORT": self.port,
|
||||
"UPLOAD_PROTOCOL": self.upload_protocol,
|
||||
"INIT_BREAK": self.init_break or "",
|
||||
"LOAD_CMDS": "\n".join(self.load_cmds or []),
|
||||
}
|
||||
for key, value in patterns.items():
|
||||
if key.endswith(("_DIR", "_PATH")):
|
||||
patterns[key] = fs.to_unix_path(value)
|
||||
|
||||
def _replace(text):
|
||||
for key, value in patterns.items():
|
||||
pattern = "$%s" % key
|
||||
text = text.replace(pattern, value or "")
|
||||
return text
|
||||
|
||||
if isinstance(source, string_types):
|
||||
source = _replace(source)
|
||||
elif isinstance(source, (list, dict)):
|
||||
items = enumerate(source) if isinstance(source, list) else source.items()
|
||||
for key, value in items:
|
||||
if isinstance(value, string_types):
|
||||
source[key] = _replace(value)
|
||||
elif isinstance(value, (list, dict)) and recursive:
|
||||
source[key] = self.reveal_patterns(value, patterns)
|
||||
|
||||
data = json.dumps(source)
|
||||
if any(("$" + key) in data for key in patterns):
|
||||
source = self.reveal_patterns(source, patterns)
|
||||
|
||||
return source
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.debug.config.base import DebugConfigBase
|
||||
from platformio.debug.exception import DebugInvalidOptionsError
|
||||
from platformio.device.finder import SerialPortFinder, is_pattern_port
|
||||
|
||||
|
||||
class BlackmagicDebugConfig(DebugConfigBase):
|
||||
GDB_INIT_SCRIPT = """
|
||||
define pio_reset_halt_target
|
||||
set language c
|
||||
set *0xE000ED0C = 0x05FA0004
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
while ($busy)
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
end
|
||||
set language auto
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
pio_reset_halt_target
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor swdp_scan
|
||||
attach 1
|
||||
set mem inaccessible-by-default off
|
||||
$LOAD_CMDS
|
||||
$INIT_BREAK
|
||||
|
||||
set language c
|
||||
set *0xE000ED0C = 0x05FA0004
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
while ($busy)
|
||||
set $busy = (*0xE000ED0C & 0x4)
|
||||
end
|
||||
set language auto
|
||||
"""
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
# pylint: disable=assignment-from-no-return
|
||||
initial_port = DebugConfigBase.port.fget(self)
|
||||
if initial_port and not is_pattern_port(initial_port):
|
||||
return initial_port
|
||||
port = SerialPortFinder(
|
||||
board_config=self.board_config,
|
||||
upload_protocol=self.tool_name,
|
||||
prefer_gdb_port=True,
|
||||
).find(initial_port)
|
||||
if port:
|
||||
return port
|
||||
raise DebugInvalidOptionsError(
|
||||
"Please specify `debug_port` for the working environment"
|
||||
)
|
||||
|
||||
@port.setter
|
||||
def port(self, value):
|
||||
self._port = value
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import importlib
|
||||
import re
|
||||
|
||||
from platformio.debug.config.generic import GenericDebugConfig
|
||||
from platformio.debug.config.native import NativeDebugConfig
|
||||
|
||||
|
||||
class DebugConfigFactory:
|
||||
@staticmethod
|
||||
def get_clsname(name):
|
||||
name = re.sub(r"[^\da-z\_\-]+", "", name, flags=re.I)
|
||||
return "%sDebugConfig" % name.lower().capitalize()
|
||||
|
||||
@classmethod
|
||||
def new(cls, platform, project_config, env_name):
|
||||
board_config = platform.board_config(
|
||||
project_config.get("env:" + env_name, "board")
|
||||
)
|
||||
tool_name = (
|
||||
board_config.get_debug_tool_name(
|
||||
project_config.get("env:" + env_name, "debug_tool")
|
||||
)
|
||||
if board_config
|
||||
else None
|
||||
)
|
||||
config_cls = None
|
||||
try:
|
||||
mod = importlib.import_module("platformio.debug.config.%s" % tool_name)
|
||||
config_cls = getattr(mod, cls.get_clsname(tool_name))
|
||||
except ModuleNotFoundError:
|
||||
config_cls = (
|
||||
GenericDebugConfig if platform.is_embedded() else NativeDebugConfig
|
||||
)
|
||||
return config_cls(platform, project_config, env_name)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.debug.config.base import DebugConfigBase
|
||||
|
||||
|
||||
class GenericDebugConfig(DebugConfigBase):
|
||||
GDB_INIT_SCRIPT = """
|
||||
define pio_reset_halt_target
|
||||
monitor reset halt
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor reset
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor init
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "port" not in kwargs:
|
||||
kwargs["port"] = ":3333"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.debug.config.base import DebugConfigBase
|
||||
|
||||
|
||||
class JlinkDebugConfig(DebugConfigBase):
|
||||
GDB_INIT_SCRIPT = """
|
||||
define pio_reset_halt_target
|
||||
monitor reset
|
||||
monitor halt
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
monitor clrbp
|
||||
monitor reset
|
||||
monitor go
|
||||
end
|
||||
|
||||
target extended-remote $DEBUG_PORT
|
||||
monitor clrbp
|
||||
monitor speed auto
|
||||
pio_reset_halt_target
|
||||
$LOAD_CMDS
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "port" not in kwargs:
|
||||
kwargs["port"] = ":2331"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def server_ready_pattern(self):
|
||||
return super().server_ready_pattern or ("Waiting for GDB connection")
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.debug.config.base import DebugConfigBase
|
||||
|
||||
|
||||
class MspdebugDebugConfig(DebugConfigBase):
|
||||
GDB_INIT_SCRIPT = """
|
||||
define pio_reset_halt_target
|
||||
end
|
||||
|
||||
define pio_reset_run_target
|
||||
end
|
||||
|
||||
target remote $DEBUG_PORT
|
||||
monitor erase
|
||||
$LOAD_CMDS
|
||||
pio_reset_halt_target
|
||||
$INIT_BREAK
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if "port" not in kwargs:
|
||||
kwargs["port"] = ":2000"
|
||||
super().__init__(*args, **kwargs)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue