[
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: https://platformio.org/donate\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "What kind of issue is this?\n\n- [ ] **Question**.\n      This issue tracker is not the place for questions. If you want to ask how to do something,\n      or to understand why something isn't working the way you expect it to, \n      use [Community Forums](https://community.platformio.org) or [Premium Support](https://platformio.org/support)\n\n- [ ] **PlatformIO IDE**.\n      All issues related to PlatformIO IDE should be reported to the\n      [PlatformIO IDE for VSCode](https://github.com/platformio/platformio-vscode-ide/issues) repository\n\n- [ ] **Development Platform or Board**.\n      All issues (building, uploading, adding new boards, etc.) related to PlatformIO development platforms\n      should be reported to appropriate repository related to your hardware\n      https://github.com/topics/platformio-platform\n\n- [ ] **Feature Request**.\n      Start by telling us what problem you’re trying to solve. Often a solution\n      already exists! Don’t send pull requests to implement new features without first getting our\n      support. Sometimes we leave features out on purpose to keep the project small.\n\n- [ ] **PlatformIO Core**.\n      If you’ve found a bug, please provide an information below.\n\n\n*You can erase any parts of this template not applicable to your Issue.*\n\n------------------------------------------------------------------\n\n### Configuration\n\n**Operating system**:\n\n**PlatformIO Version** (`platformio --version`):\n\n### Description of problem\n\n\n#### Steps to Reproduce\n\n1.\n2.\n3.\n\n### Actual Results\n\n\n### Expected Results\n\n\n### If problems with PlatformIO Build System:\n\n**The content of `platformio.ini`:**\n```ini\nInsert here...\n```\n\n**Source file to reproduce issue:**\n```cpp\nInsert here...\n```\n\n### Additional info\n"
  },
  {
    "path": ".github/workflows/core.yml",
    "content": "name: Core\n\non: [push, pull_request]\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        python-version: [\"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    runs-on: ${{ matrix.os }}\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install tox\n\n      - name: Run \"codespell\" on Linux\n        if: startsWith(matrix.os, 'ubuntu')\n        run: |\n          python -m pip install codespell\n          make codespell\n\n      - name: Core System Info\n        run: |\n          tox -e py\n\n      - name: Integration Tests\n        if: ${{ matrix.python-version == '3.11' }}\n        run: |\n          tox -e testcore\n\n      - name: Slack Notification\n        uses: homoluctus/slatify@master\n        if: failure()\n        with:\n          type: ${{ job.status }}\n          job_name: '*Core*'\n          commit: true\n          url: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n          token: ${{ secrets.SLACK_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/deployment.yml",
    "content": "name: Deployment\n\non:\n  push:\n    branches:\n      - \"master\"\n      - \"release/**\"\n\njobs:\n  deployment:\n    runs-on: ubuntu-latest\n    environment: production\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install tox build\n\n      - name: Deployment Tests\n        env:\n          TEST_EMAIL_LOGIN: ${{ secrets.TEST_EMAIL_LOGIN }}\n          TEST_EMAIL_PASSWORD: ${{ secrets.TEST_EMAIL_PASSWORD }}\n          TEST_EMAIL_IMAP_SERVER: ${{ secrets.TEST_EMAIL_IMAP_SERVER }}\n        run: |\n          tox -e testcore\n\n      - name: Build Python distributions\n        run:  python -m build\n\n      - name: Publish package to PyPI\n        if: ${{ github.ref == 'refs/heads/master' }}\n        uses: pypa/gh-action-pypi-publish@release/v1\n        with:\n          user: __token__\n          password: ${{ secrets.PYPI_API_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Docs\n\non: [push, pull_request]\n\njobs:\n  build:\n    name: Build Docs\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install tox\n\n      - name: Build docs\n        run: |\n          tox -e docs\n\n      - name: Slack Notification\n        uses: homoluctus/slatify@master\n        if: failure()\n        with:\n          type: ${{ job.status }}\n          job_name: '*Docs*'\n          commit: true\n          url: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n          token: ${{ secrets.SLACK_GITHUB_TOKEN }}\n\n      - name: Preserve Docs\n        if: ${{ github.event_name == 'push' }}\n        run: |\n          tar -czvf docs.tar.gz -C docs/_build html rtdpage\n\n      - name: Save artifact\n        if: ${{ github.event_name == 'push' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: docs\n          path: ./docs.tar.gz\n\n  deploy:\n    name: Deploy Docs\n    needs: build\n    runs-on: ubuntu-latest\n    env:\n      DOCS_REPO: platformio/platformio-docs\n      DOCS_DIR: platformio-docs\n      LATEST_DOCS_DIR: latest-docs\n      RELEASE_BUILD: ${{ startsWith(github.ref, 'refs/tags/v') }}\n    if: ${{ github.event_name == 'push' }}\n    steps:\n      - name: Download artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: docs\n      - name: Unpack artifact\n        run: |\n          mkdir ./${{ env.LATEST_DOCS_DIR }}\n          tar -xzf ./docs.tar.gz -C ./${{ env.LATEST_DOCS_DIR }}\n      - name: Delete Artifact\n        uses: geekyeggo/delete-artifact@v5\n        with:\n          name: docs\n      - name: Select Docs type\n        id: get-destination-dir\n        run: |\n          if [[ ${{ env.RELEASE_BUILD }} == true ]]; then\n            echo \"::set-output name=dst_dir::stable\"\n          else\n            echo \"::set-output name=dst_dir::latest\"\n          fi\n      - name: Checkout latest Docs\n        continue-on-error: true\n        uses: actions/checkout@v6\n        with:\n          repository: ${{ env.DOCS_REPO }}\n          path: ${{ env.DOCS_DIR }}\n          ref: gh-pages\n      - name: Synchronize Docs\n        run: |\n          rm -rf ${{ env.DOCS_DIR }}/.git\n          rm -rf ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}\n          mkdir -p ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}\n          cp -rf ${{ env.LATEST_DOCS_DIR }}/html/* ${{ env.DOCS_DIR }}/en/${{ steps.get-destination-dir.outputs.dst_dir }}\n          if [[ ${{ env.RELEASE_BUILD }} == false ]]; then\n            rm -rf ${{ env.DOCS_DIR }}/page\n            mkdir -p ${{ env.DOCS_DIR }}/page\n            cp -rf ${{ env.LATEST_DOCS_DIR }}/rtdpage/* ${{ env.DOCS_DIR }}/page\n          fi\n      - name: Validate Docs\n        run: |\n          if [ -z \"$(ls -A ${{ env.DOCS_DIR }})\" ]; then\n             echo \"Docs folder is empty. Aborting!\"\n             exit 1\n          fi\n      - name: Deploy to Github Pages\n        uses: peaceiris/actions-gh-pages@v4\n        with:\n          personal_token: ${{ secrets.DEPLOY_GH_DOCS_TOKEN }}\n          external_repository: ${{ env.DOCS_REPO }}\n          publish_dir: ./${{ env.DOCS_DIR }}\n          commit_message: Sync Docs\n"
  },
  {
    "path": ".github/workflows/examples.yml",
    "content": "name: Examples\n\non: [push, pull_request]\n\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n    runs-on: ${{ matrix.os }}\n    env:\n      PIO_INSTALL_DEVPLATFORM_OWNERNAMES: \"platformio\"\n      PIO_INSTALL_DEVPLATFORM_NAMES: \"aceinna_imu,atmelavr,atmelmegaavr,atmelsam,espressif32,espressif8266,nordicnrf52,raspberrypi,ststm32,teensy\"\n\n    steps:\n      - name: Free Disk Space\n        uses: endersonmenezes/free-disk-space@v3\n        with:\n          remove_android: true\n          remove_dotnet: true\n          remove_haskell: true\n          # Faster cleanup\n          remove_packages_one_command: true\n          rm_cmd: \"rmz\"\n\n      - uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Set up Python\n        uses: actions/setup-python@v6\n        with:\n          python-version: \"3.11\"\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install tox\n\n      - name: Run on Linux\n        if: startsWith(matrix.os, 'ubuntu')\n        run: |\n          # Free space\n          sudo apt clean\n          # docker rmi $(docker image ls -aq)\n          df -h\n          tox -e testexamples\n\n      - name: Run on macOS\n        if: startsWith(matrix.os, 'macos')\n        run: |\n          df -h\n          tox -e testexamples\n\n      - name: Run on Windows\n        if: startsWith(matrix.os, 'windows')\n        env:\n          PLATFORMIO_CORE_DIR: C:/pio\n          PLATFORMIO_WORKSPACE_DIR: C:/pio-workspace/$PROJECT_HASH\n        run: |\n          tox -e testexamples\n\n      - name: Slack Notification\n        uses: homoluctus/slatify@master\n        if: failure()\n        with:\n          type: ${{ job.status }}\n          job_name: '*Examples*'\n          commit: true\n          url: ${{ secrets.SLACK_BUILD_WEBHOOK }}\n          token: ${{ secrets.SLACK_GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/projects.yml",
    "content": "name: Projects\n\non: [push, pull_request]\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        project:\n          - marlin:\n            repository: \"MarlinFirmware/Marlin\"\n            folder: \"Marlin\"\n            config_dir: \"Marlin\"\n            env_name: \"mega2560\"\n          - smartknob:\n            repository: \"scottbez1/smartknob\"\n            folder: \"smartknob\"\n            config_dir: \"smartknob\"\n            env_name: \"view\"\n          - espurna:\n            repository: \"xoseperez/espurna\"\n            folder: \"espurna\"\n            config_dir: \"espurna/code\"\n            env_name: \"nodemcu-lolin\"\n          - OpenMQTTGateway:\n            repository: \"1technophile/OpenMQTTGateway\"\n            folder: \"OpenMQTTGateway\"\n            config_dir: \"OpenMQTTGateway\"\n            env_name: \"esp32-m5atom-lite\"\n        os: [ubuntu-latest, windows-latest, macos-latest]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v6\n        with:\n          python-version: 3.11\n\n      - name: Install PlatformIO\n        run: pip install -U .\n\n      - name: Check out ${{ matrix.project.repository }}\n        uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n          repository: ${{ matrix.project.repository }}\n          path: ${{ matrix.project.folder }}\n\n      - name: Compile ${{ matrix.project.repository }}\n        run: pio run -d ${{ matrix.project.config_dir }} -e ${{ matrix.project.env_name }}\n\n"
  },
  {
    "path": ".gitignore",
    "content": "*.egg-info\n*.pyc\n__pycache__\n.tox\ndocs/_build\ndist\nbuild\n.cache\ncoverage.xml\n.coverage\nhtmlcov\n.pytest_cache\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"examples\"]\n\tpath = examples\n\turl = https://github.com/platformio/platformio-examples.git\n[submodule \"docs\"]\n\tpath = docs\n\turl = https://github.com/platformio/platformio-docs.git\n\tbranch = develop\n"
  },
  {
    "path": ".pylintrc",
    "content": "[REPORTS]\noutput-format=colorized\n\n[MESSAGES CONTROL]\ndisable=\n    missing-docstring,\n    duplicate-code,\n    invalid-name,\n    too-few-public-methods,\n    consider-using-f-string,\n    cyclic-import,\n    use-dict-literal\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nSee https://piolabs.com/legal/code-of-conduct.html\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing\n------------\n\nTo get started, <a href=\"https://cla-assistant.io/platformio/platformio-core\">sign the Contributor License Agreement</a>.\n\n1. Fork the repository on GitHub\n2. Clone repository `git clone --recursive https://github.com/YourGithubUsername/platformio-core.git`\n3. Run `pip install tox`\n4. Go to the root of the PlatformIO Core project where `tox.ini` is located (``cd platformio-core``) and run `tox -e py39`.\n   You can replace `py39` with your own Python version. For example, `py311` means Python 3.11.\n5. Activate current development environment:\n\n   * Windows: `.tox\\py39\\Scripts\\activate`\n   * Bash/ZSH: `source .tox/py39/bin/activate`\n   * Fish: `source .tox/py39/bin/activate.fish`\n\n6. Make changes to code, documentation, etc.\n7. Lint source code `make before-commit`\n8. Run the tests `make test`\n9. Build documentation `tox -e docs` (creates a directory _build under docs where you can find the html)\n10. Commit changes to your forked repository\n11. Submit a Pull Request on GitHub\n"
  },
  {
    "path": "HISTORY.rst",
    "content": "Release Notes\n=============\n\n.. |PIOCONF| replace:: `\"platformio.ini\" <https://docs.platformio.org/en/latest/projectconf.html>`__ configuration file\n.. |LIBRARYJSON| replace:: `library.json <https://docs.platformio.org/en/latest/manifests/library-json/index.html>`__\n.. |LDF| replace:: `LDF <https://docs.platformio.org/en/latest/librarymanager/ldf.html>`__\n.. |INTERPOLATION| replace:: `Interpolation of Values <https://docs.platformio.org/en/latest/projectconf/interpolation.html>`__\n.. |UNITTESTING| replace:: `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html>`__\n.. |DEBUGGING| replace:: `Debugging <https://docs.platformio.org/en/latest/plus/debugging.html>`__\n.. |STATICCODEANALYSIS| replace:: `Static Code Analysis <https://docs.platformio.org/en/latest/advanced/static-code-analysis/index.html>`__\n.. |PIOHOME| replace:: `PIO Home <https://docs.platformio.org/en/latest/home/index.html>`__\n\n.. _release_notes_6:\n\nPlatformIO Core 6\n-----------------\n\nUnlock the true potential of embedded software development with\nPlatformIO's collaborative ecosystem, embracing declarative principles,\ntest-driven methodologies, and modern toolchains for unrivaled success.\n\n6.1.19 (2026-02-04)\n~~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 3.14\n* Upgraded the `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework to version 2.4.12, the `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ to version 1.17.0, and the `Unity <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/unity.html>`__ to version 2.6.1, incorporating the latest features and improvements for enhanced testing capabilities\n* Enhanced compatibility with the CCLS language server, improving integration with editors like `Emacs <https://docs.platformio.org/en/latest/integration/ide/emacs.html>`__, `Sublime Text <https://docs.platformio.org/en/latest/integration/ide/sublimetext.html>`__, and `Vim <https://docs.platformio.org/en/latest/integration/ide/vim.html>`__ (`issue #5186 <https://github.com/platformio/platformio-core/issues/5186>`_)\n* Improved error messages for package installation to make it easier to understand when a package is missing or incompatible (`pull #5336 <https://github.com/platformio/platformio-core/pull/5336>`_).\n* Fixed a regression issue where custom build flags were not properly reflected in the `compile_commands.json <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ file, ensuring accurate compilation database generation\n* Fixed an issue where fully-qualified serial port URLs (e.g., ``rfc2217://host:port``) were incorrectly treated as wildcard patterns (`issue #5225 <https://github.com/platformio/platformio-core/issues/5225>`_)\n* Fixed an issue where the toolchain path in static analysis was not handled correctly if it contained spaces (`pull #5351 <https://github.com/platformio/platformio-core/issues/5351>`_)\n* Fixed installation failure when the executable path contains spaces for ``postinstall`` scripts and handling both list and string command formats (`pull #5366 <https://github.com/platformio/platformio-core/pull/5366>`_)\n* Fixed cleanup of the ``.pio/libdeps`` folder so that leftover libraries are properly removed when the `lib_deps <https://docs.platformio.org/en/latest/projectconf/sections/env/options/library/lib_deps.html>`__ option is empty (`issue #5110 <https://github.com/platformio/platformio-core/issues/5110>`_)\n\n6.1.18 (2025-03-11)\n~~~~~~~~~~~~~~~~~~~\n\n* Resolved a regression issue that prevented |PIOHOME| from opening external links (`issue #5084 <https://github.com/platformio/platformio-core/issues/5084>`_)\n\n6.1.17 (2025-02-13)\n~~~~~~~~~~~~~~~~~~~\n\n* Introduced the `PLATFORMIO_RUN_JOBS <https://docs.platformio.org/en/latest/envvars.html#envvar-PLATFORMIO_RUN_JOBS>`__ environment variable, allowing manual override of the number of parallel build jobs (`issue #5077 <https://github.com/platformio/platformio-core/issues/5077>`_)\n* Added support for ``tar.xz`` tarball dependencies (`pull #4974 <https://github.com/platformio/platformio-core/pull/4974>`_)\n* Ensured that dependencies of private libraries are no longer unnecessarily re-installed, optimizing dependency management and reducing redundant operations (`issue #4987 <https://github.com/platformio/platformio-core/issues/4987>`_)\n* Resolved an issue where the ``compiledb`` target failed to properly escape compiler executable paths containing spaces (`issue #4998 <https://github.com/platformio/platformio-core/issues/4998>`_)\n* Resolved an issue with incorrect path resolution when linking static libraries via the `build_flags <https://docs.platformio.org/en/latest/projectconf/sections/env/options/build/build_flags.html>`__ option (`issue #5004 <https://github.com/platformio/platformio-core/issues/5004>`_)\n* Resolved an issue where the ``--project-dir`` flag did not function correctly with the `pio check <https://docs.platformio.org/en/latest/core/userguide/cmd_check.html>`__ and `pio debug <https://docs.platformio.org/en/latest/core/userguide/cmd_debug.html>`__ commands (`issue #5029 <https://github.com/platformio/platformio-core/issues/5029>`_)\n* Resolved an issue where the |LDF| occasionally excluded bundled platform libraries from the dependency graph (`pull #4941 <https://github.com/platformio/platformio-core/pull/4941>`_)\n\n6.1.16 (2024-09-26)\n~~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 3.13\n* Introduced the `PLATFORMIO_SYSTEM_TYPE <https://docs.platformio.org/en/latest/envvars.html#envvar-PLATFORMIO_SYSTEM_TYPE>`__ environment variable, enabling manual override of the detected system type for greater flexibility and control in custom build environments\n* Enhanced internet connection checks by falling back to HTTPS protocol when HTTP (port 80) fails (`issue #4980 <https://github.com/platformio/platformio-core/issues/4980>`_)\n* Upgraded the build engine to the latest version of SCons (4.8.1) to improve build performance, reliability, and compatibility with other tools and systems (`release notes <https://github.com/SCons/scons/releases/tag/4.8.1>`__)\n* Upgraded the `Doctest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ testing framework to version 2.4.11, the `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/doctest.html>`__ to version 1.15.2, and the `Unity <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/unity.html>`__ to version 2.6.0, incorporating the latest features and improvements for enhanced testing capabilities\n* Corrected an issue where the incorrect public class was imported for the ``DoctestTestRunner`` (`issue #4949 <https://github.com/platformio/platformio-core/issues/4949>`_)\n\n6.1.15 (2024-04-25)\n~~~~~~~~~~~~~~~~~~~\n\n* Resolved an issue where the |LDF| couldn't locate a library dependency declared via version control system repository (`issue #4885 <https://github.com/platformio/platformio-core/issues/4885>`_)\n* Resolved an issue related to the inaccurate detection of the Clang compiler (`pull #4897 <https://github.com/platformio/platformio-core/pull/4897>`_)\n\n6.1.14 (2024-03-21)\n~~~~~~~~~~~~~~~~~~~\n\n* Introduced the ``--json-output`` option to the `pio test <https://docs.platformio.org/en/latest/core/userguide/cmd_test.html>`__ command, enabling users to generate test results in the JSON format\n* Upgraded the build engine to the latest version of SCons (4.7.0) to improve build performance, reliability, and compatibility with other tools and systems (`release notes <https://github.com/SCons/scons/releases/tag/4.7.0>`__)\n* Broadened version support for the ``pyelftools`` dependency, enabling compatibility with lower versions and facilitating integration with a wider range of third-party tools (`issue #4834 <https://github.com/platformio/platformio-core/issues/4834>`_)\n* Addressed an issue where passing a relative path (``--project-dir``) to the `pio project init <https://docs.platformio.org/en/latest/core/userguide/project/cmd_init.html>`__ command resulted in an error (`issue #4847 <https://github.com/platformio/platformio-core/issues/4847>`_)\n* Enhanced |STATICCODEANALYSIS| to accommodate scenarios where custom ``src_dir`` or ``include_dir`` are located outside the project folder (`pull #4874 <https://github.com/platformio/platformio-core/pull/4874>`_)\n* Corrected the validation of ``symlink://`` `package specifications <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html#local-folder>`__ , resolving an issue that caused the package manager to repeatedly reinstall dependencies (`pull #4870 <https://github.com/platformio/platformio-core/pull/4870>`_)\n* Resolved an issue related to the relative package path in the `pio pkg publish <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_publish.html>`__ command\n* Resolved an issue where the |LDF| selected an incorrect library version (`issue #4860 <https://github.com/platformio/platformio-core/issues/4860>`_)\n* Resolved an issue with the ``hexlify`` filter in the `device monitor <https://docs.platformio.org/en/latest/core/userguide/device/cmd_monitor.html>`__ command, ensuring proper representation of characters with Unicode code points higher than 127 (`issue #4732 <https://github.com/platformio/platformio-core/issues/4732>`_)\n\n6.1.13 (2024-01-12)\n~~~~~~~~~~~~~~~~~~~\n\n* Expanded support for SCons variables declared in the legacy format ``${SCONS_VARNAME}`` (`issue #4828 <https://github.com/platformio/platformio-core/issues/4828>`_)\n\n6.1.12 (2024-01-10)\n~~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 3.12\n* Introduced the capability to launch the debug server in a separate process (`issue #4722 <https://github.com/platformio/platformio-core/issues/4722>`_)\n* Introduced a warning during the verification of MCU maximum RAM usage, signaling when the allocated RAM surpasses 100% (`issue #4791 <https://github.com/platformio/platformio-core/issues/4791>`_)\n* Drastically enhanced the speed of project building when operating in verbose mode (`issue #4783 <https://github.com/platformio/platformio-core/issues/4783>`_)\n* Upgraded the build engine to the latest version of SCons (4.6.0) to improve build performance, reliability, and compatibility with other tools and systems (`release notes <https://github.com/SCons/scons/releases/tag/4.6.0>`__)\n* Enhanced the handling of built-in variables in |PIOCONF| during |INTERPOLATION| (`issue #4695 <https://github.com/platformio/platformio-core/issues/4695>`_)\n* Enhanced PIP dependency declarations for improved reliability and extended support to include Python 3.6 (`issue #4819 <https://github.com/platformio/platformio-core/issues/4819>`_)\n* Implemented automatic installation of missing dependencies when utilizing a SOCKS proxy (`issue #4822 <https://github.com/platformio/platformio-core/issues/4822>`_)\n* Implemented a fail-safe mechanism to terminate a debugging session if an unknown CLI option is passed (`issue #4699 <https://github.com/platformio/platformio-core/issues/4699>`_)\n* Rectified an issue where ``${platformio.name}`` erroneously represented ``None`` as the default `project name <https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/generic/name.html>`__ (`issue #4717 <https://github.com/platformio/platformio-core/issues/4717>`_)\n* Resolved an issue where the ``COMPILATIONDB_INCLUDE_TOOLCHAIN`` setting was not correctly applying to private libraries (`issue #4762 <https://github.com/platformio/platformio-core/issues/4762>`_)\n* Resolved an issue where ``get_systype()`` inaccurately returned the architecture when executed within a Docker container on a 64-bit kernel with a 32-bit userspace (`issue #4777 <https://github.com/platformio/platformio-core/issues/4777>`_)\n* Resolved an issue with incorrect handling of the ``check_src_filters`` option when used in multiple environments (`issue #4788 <https://github.com/platformio/platformio-core/issues/4788>`_)\n* Resolved an issue where running `pio project metadata <https://docs.platformio.org/en/latest/core/userguide/project/cmd_metadata.html>`__ resulted in duplicated \"include\" entries (`issue #4723 <https://github.com/platformio/platformio-core/issues/4723>`_)\n* Resolved an issue where native debugging failed on the host machine (`issue #4745 <https://github.com/platformio/platformio-core/issues/4745>`_)\n* Resolved an issue where custom debug configurations were being inadvertently overwritten in VSCode's ``launch.json`` (`issue #4810 <https://github.com/platformio/platformio-core/issues/4810>`_)\n\n6.1.11 (2023-08-31)\n~~~~~~~~~~~~~~~~~~~\n\n* 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\n* Resolved a regression issue that resulted in the malfunction of the Memory Inspection feature within |PIOHOME|\n\n6.1.10 (2023-08-11)\n~~~~~~~~~~~~~~~~~~~\n\n* 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\n* 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\n* 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\n* `Cppcheck <https://docs.platformio.org/en/latest/plus/check-tools/cppcheck.html>`__ v2.11 with new checks, CLI commands and various analysis improvements\n* 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>`_)\n\n6.1.9 (2023-07-06)\n~~~~~~~~~~~~~~~~~~\n\n* 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>`_)\n* 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>`_)\n\n6.1.8 (2023-07-05)\n~~~~~~~~~~~~~~~~~~\n\n* 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|\n* Enhanced the parsing of the |PIOCONF| to provide comprehensive diagnostic information\n* 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\n* Optimized project integration templates to address the issue of long paths on Windows (`issue #4652 <https://github.com/platformio/platformio-core/issues/4652>`_)\n* Refactored |UNITTESTING| engine to resolve compiler warnings with \"-Wpedantic\" option (`pull #4671 <https://github.com/platformio/platformio-core/pull/4671>`_)\n* 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>`_)\n* 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\n* 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>`_)\n* Removed PlatformIO IDE for Atom from the documentation as `Atom has been deprecated <https://github.blog/2022-06-08-sunsetting-atom/>`__\n\n6.1.7 (2023-05-08)\n~~~~~~~~~~~~~~~~~~\n\n* 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\n* 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)\n* 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>`_)\n* 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\n* 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>`_)\n* 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>`_)\n* 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\n* 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>`__)\n* 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>`_)\n* 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\n* 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>`_)\n* 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>`_)\n* 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\n* 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>`_)\n* 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>`_)\n* 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>`_)\n* Resolved an issue where multiple targets were not executed sequentially (`issue #4604 <https://github.com/platformio/platformio-core/issues/4604>`_)\n* Resolved an issue where upgrading PlatformIO Core fails on Windows with Python 3.11 (`issue #4540 <https://github.com/platformio/platformio-core/issues/4540>`_)\n\n6.1.6 (2023-01-23)\n~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 3.11\n* 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>`_)\n* Made assets (templates, ``99-platformio-udev.rules``) part of Python's module (`issue #4458 <https://github.com/platformio/platformio-core/issues/4458>`_)\n* 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\n* Removed dependency on the \"zeroconf\" package and install it only when a user lists mDNS devices (issue with zeroconf's LGPL license)\n* 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>`_)\n* 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>`_)\n* 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>`_)\n* 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>`_)\n\n6.1.5 (2022-11-01)\n~~~~~~~~~~~~~~~~~~\n\n* 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>`_)\n* Documented `PlatformIO Core Proxy Configuration <https://docs.platformio.org/en/latest/core/installation/proxy-configuration.html>`__\n* Speeded up device port finder by avoiding loading board HWIDs from development platforms\n* Improved caching of build metadata in debug mode\n* 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>`_)\n\n6.1.4 (2022-08-12)\n~~~~~~~~~~~~~~~~~~\n\n* 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>`_)\n* Improved device port finder when using dual channel UART converter (`issue #4367 <https://github.com/platformio/platformio-core/issues/4367>`_)\n* Improved project dependency resolving when using the `pio project init --ide <https://docs.platformio.org/en/latest/core/userguide/project/cmd_init.html>`__ command\n* Upgraded build engine to the SCons 4.4.0 (`release notes <https://github.com/SCons/scons/releases/tag/4.4.0>`__)\n* Keep custom \"unwantedRecommendations\" when generating projects for VSCode (`issue #4383 <https://github.com/platformio/platformio-core/issues/4383>`_)\n* Do not resolve project dependencies for the ``cleanall`` target (`issue #4344 <https://github.com/platformio/platformio-core/issues/4344>`_)\n* Warn about calling \"env.BuildSources\" in a POST-type script (`issue #4385 <https://github.com/platformio/platformio-core/issues/4385>`_)\n* Fixed an issue when escaping macros/defines for IDE integration (`issue #4360 <https://github.com/platformio/platformio-core/issues/4360>`_)\n* Fixed an issue when the \"cleanall\" target removes dependencies from all working environments (`issue #4386 <https://github.com/platformio/platformio-core/issues/4386>`_)\n\n6.1.3 (2022-07-18)\n~~~~~~~~~~~~~~~~~~\n\n* Fixed a regression bug when opening device monitor without any filters (`issue #4363 <https://github.com/platformio/platformio-core/issues/4363>`_)\n\n6.1.2 (2022-07-18)\n~~~~~~~~~~~~~~~~~~\n\n* Export a ``PIO_UNIT_TESTING`` macro to the project source files and dependent libraries in the |UNITTESTING| mode\n* Improved detection of Windows architecture (`issue #4353 <https://github.com/platformio/platformio-core/issues/4353>`_)\n* 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>`_)\n* 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>`_)\n* 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>`_)\n\n6.1.1 (2022-07-11)\n~~~~~~~~~~~~~~~~~~\n\n* 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>`_)\n* 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>`_)\n* Show \"TimeoutError\" only in the verbose mode when can not find a serial port\n* Fixed an issue when a serial port was not automatically detected if the board has predefined HWIDs\n* Fixed an issue with endless scanning of project dependencies (`issue #4349 <https://github.com/platformio/platformio-core/issues/4349>`_)\n* 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>`_)\n\n6.1.0 (2022-07-06)\n~~~~~~~~~~~~~~~~~~\n\n* **Device Manager**\n\n  - Automatically reconnect device monitor if a connection fails\n  - 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\n  - Handle device monitor disconnects more gracefully (`issue #3939 <https://github.com/platformio/platformio-core/issues/3939>`_)\n  - 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>`_)\n  - Improved a serial port finder for a board with predefined HWIDs\n  - 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>`__\n  - Fixed an issue when the monitor filters were not applied in their order (`issue #4320 <https://github.com/platformio/platformio-core/issues/4320>`_)\n\n* **Unit Testing**\n\n  - Updated \"Getting Started\" documentation for `GoogleTest <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/googletest.html>`__ testing and mocking framework\n  - Export |UNITTESTING| flags only to the project build environment (``projenv``, files in \"src\" folder)\n  - Merged the \"building\" stage with \"uploading\" for the embedded target (`issue #4307 <https://github.com/platformio/platformio-core/issues/4307>`_)\n  - 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\n  - Do not immediately terminate a testing program when results are received\n  - 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>`_)\n  - Fixed an issue when testing results were wrong in the verbose mode (`issue #4336 <https://github.com/platformio/platformio-core/issues/4336>`_)\n\n* **Build System**\n\n  - Significantly improved support for `Pre & Post Actions <https://docs.platformio.org/en/latest/scripting/actions.html>`__\n\n    * 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\n    * 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>`__\n\n  - 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>`_)\n  - 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>`_)\n  - Added ``env.StringifyMacro(value)`` helper function for the `Advanced Scripting <https://docs.platformio.org/en/latest/scripting/index.html>`__\n  - Allowed to ``Import(\"projenv\")`` in a library extra script (`issue #4305 <https://github.com/platformio/platformio-core/issues/4305>`_)\n  - 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>`_)\n  - 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\n  - 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>`__\n  - Fixed an issue with the |LDF| when recursively scanning dependencies in the ``chain`` mode\n  - Fixed a \"PermissionError\" on Windows when running \"clean\" or \"cleanall\" targets (`issue #4331 <https://github.com/platformio/platformio-core/issues/4331>`_)\n\n* **Package Management**\n\n  - Fixed an issue when library dependencies were installed for the incompatible project environment (`issue #4338 <https://github.com/platformio/platformio-core/issues/4338>`_)\n\n* **Miscellaneous**\n\n  - 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>`_)\n\n6.0.2 (2022-06-01)\n~~~~~~~~~~~~~~~~~~\n\n* 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>`_)\n* Follow symbolic links during searching for the unit test suites (`issue #4288 <https://github.com/platformio/platformio-core/issues/4288>`_)\n* Show a warning when testing an empty project without a test suite (`issue #4278 <https://github.com/platformio/platformio-core/issues/4278>`_)\n* Improved support for `Asking for input (prompts) <https://docs.platformio.org/en/latest/scripting/examples/asking_for_input.html>`_\n* 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>`_)\n* Fixed an issue with debugging assembly files without preprocessor (\".s\")\n\n6.0.1 (2022-05-17)\n~~~~~~~~~~~~~~~~~~\n\n* Improved support for the renamed configuration options (`issue #4270 <https://github.com/platformio/platformio-core/issues/4270>`_)\n* 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\n* Fixed an issue when using |INTERPOLATION| and merging str+int options (`issue #4271 <https://github.com/platformio/platformio-core/issues/4271>`_)\n\n6.0.0 (2022-05-16)\n~~~~~~~~~~~~~~~~~~\n\nPlease check the `Migration guide from 5.x to 6.0 <https://docs.platformio.org/en/latest/core/migration.html>`__.\n\n* **Package Management**\n\n  - New unified Package Management CLI (``pio pkg``):\n\n    * `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>`_)\n    * `pio pkg install <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_install.html>`_ - install the project dependencies or custom packages\n    * `pio pkg list <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_list.html>`__ - list installed packages\n    * `pio pkg outdated <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_outdated.html>`__ - check for project outdated packages\n    * `pio pkg search <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_search.html>`__ - search for packages\n    * `pio pkg show <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_show.html>`__ - show package information\n    * `pio pkg uninstall <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_uninstall.html>`_ - uninstall the project dependencies or custom packages\n    * `pio pkg update <https://docs.platformio.org/en/latest/core/userguide/pkg/cmd_update.html>`__ - update the project dependencies or custom packages\n\n  - Package Manifest\n\n    * Added support for `\"scripts\" <https://docs.platformio.org/en/latest/librarymanager/config.html#scripts>`__ (`issue #485 <https://github.com/platformio/platformio-core/issues/485>`_)\n    * 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>`_)\n    * Added support for `\"dependencies\" <https://docs.platformio.org/en/latest/librarymanager/config.html#dependencies>`__ declared in a \"tool\" package manifest\n\n  - 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>`_)\n  - Automatically install dependencies of the local (private) project libraries (`issue #2910 <https://github.com/platformio/platformio-core/issues/2910>`_)\n  - Improved detection of a package type from the tarball archive (`issue #3828 <https://github.com/platformio/platformio-core/issues/3828>`_)\n  - 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>`_)\n  - Dropped automatic updates of global libraries and development platforms (`issue #4179 <https://github.com/platformio/platformio-core/issues/4179>`_)\n  - 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>`__\n  - 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>`_)\n\n* **Unit Testing**\n\n  - Refactored from scratch |UNITTESTING| solution and its documentation\n  - New: `Test Hierarchy <https://docs.platformio.org/en/latest/advanced/unit-testing/structure.html>`_ (`issue #4135 <https://github.com/platformio/platformio-core/issues/4135>`_)\n  - 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>`_)\n  - 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>`_)\n  - New: `Semihosting <https://docs.platformio.org/en/latest/advanced/unit-testing/semihosting.html>`__ (`issue #3516 <https://github.com/platformio/platformio-core/issues/3516>`_)\n  - New: Hardware `Simulators <https://docs.platformio.org/en/latest/advanced/unit-testing/simulators/index.html>`__ for Unit Testing (QEMU, Renode, SimAVR, and custom solutions)\n  - New: ``test`` `build configuration <https://docs.platformio.org/en/latest/projectconf/build_configurations.html>`__\n  - Added support for a `custom testing framework <https://docs.platformio.org/en/latest/advanced/unit-testing/frameworks/custom/index.html>`_\n  - Added support for a custom `testing command <https://docs.platformio.org/en/latest/projectconf/section_env_test.html#test-testing-command>`__\n  - 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>`_)\n  - 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>`_)\n  - 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\n  - 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>`_)\n  - 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>`_)\n  - 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>`_)\n  - Improved automatic detection of a testing serial port (`issue #4076 <https://github.com/platformio/platformio-core/issues/4076>`_)\n  - 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>`_)\n  - 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>`__\n  - 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>`_\n\n* **Static Code Analysis**\n\n  - Updated analysis tools:\n\n    * `Cppcheck <https://docs.platformio.org/en/latest/plus/check-tools/cppcheck.html>`__ v2.7 with various checker improvements and fixed false positives\n    * `PVS-Studio <https://docs.platformio.org/en/latest/plus/check-tools/pvs-studio.html>`__ v7.18 with improved and updated semantic analysis system\n\n  - 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>`_)\n  - 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>`_)\n  - 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>`_)\n\n* **Build System**\n\n  - Show project dependency licenses when building in the verbose mode\n  - 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>`_)\n  - Fixed an issue with calling an extra script located outside a project (`issue #4220 <https://github.com/platformio/platformio-core/issues/4220>`_)\n  - 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>`_)\n  - 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>`_)\n\n* **Integration**\n\n  - 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>`_)\n  - Changed a default path for compilation database `compile_commands.json <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ to the project root\n  - Enhanced integration for Qt Creator (`issue #3046 <https://github.com/platformio/platformio-core/issues/3046>`_)\n\n* **Project Configuration**\n\n  - Extended |INTERPOLATION| with ``${this}`` pattern (`issue #3953 <https://github.com/platformio/platformio-core/issues/3953>`_)\n  - Embed environment name of the current section in the |PIOCONF| using ``${this.__env__}`` pattern\n  - 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>`__\n  - 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>`__\n\n* **Miscellaneous**\n\n  - 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>`_)\n  - Improved PIO Remote setup on credit-card sized computers (Raspberry Pi, BeagleBon, etc) (`issue #3865 <https://github.com/platformio/platformio-core/issues/3865>`_)\n  - Finally removed all tracks to the Python 2.7, the Python 3.6 is the minimum supported version.\n\n.. _release_notes_5:\n\nPlatformIO Core 5\n-----------------\n\nSee `PlatformIO Core 5.0 history <https://github.com/platformio/platformio-core/blob/v5.2.5/HISTORY.rst>`__.\n\n.. _release_notes_4:\n\nPlatformIO Core 4\n-----------------\n\nSee `PlatformIO Core 4.0 history <https://github.com/platformio/platformio-core/blob/v4.3.4/HISTORY.rst>`__.\n\nPlatformIO Core 3\n-----------------\n\nSee `PlatformIO Core 3.0 history <https://github.com/platformio/platformio-core/blob/v3.6.7/HISTORY.rst>`__.\n\nPlatformIO Core 2\n-----------------\n\nSee `PlatformIO Core 2.0 history <https://github.com/platformio/platformio-core/blob/v2.11.2/HISTORY.rst>`__.\n\nPlatformIO Core 1\n-----------------\n\nSee `PlatformIO Core 1.0 history <https://github.com/platformio/platformio-core/blob/v1.5.0/HISTORY.rst>`__.\n\nPlatformIO Core Preview\n-----------------------\n\nSee `PlatformIO Core Preview history <https://github.com/platformio/platformio-core/blob/v0.10.2/HISTORY.rst>`__.\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction,\nand distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by\nthe copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all\nother entities that control, are controlled by, or are under common\ncontrol with that entity. For the purposes of this definition,\n\"control\" means (i) the power, direct or indirect, to cause the\ndirection or management of such entity, whether by contract or\notherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity\nexercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications,\nincluding but not limited to software source code, documentation\nsource, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical\ntransformation or translation of a Source form, including but\nnot limited to compiled object code, generated documentation,\nand conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or\nObject form, made available under the License, as indicated by a\ncopyright notice that is included in or attached to the work\n(an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object\nform, that is based on (or derived from) the Work and for which the\neditorial revisions, annotations, elaborations, or other modifications\nrepresent, as a whole, an original work of authorship. For the purposes\nof this License, Derivative Works shall not include works that remain\nseparable from, or merely link (or bind by name) to the interfaces of,\nthe Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including\nthe original version of the Work and any modifications or additions\nto that Work or Derivative Works thereof, that is intentionally\nsubmitted to Licensor for inclusion in the Work by the copyright owner\nor by an individual or Legal Entity authorized to submit on behalf of\nthe copyright owner. For the purposes of this definition, \"submitted\"\nmeans any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems,\nand issue tracking systems that are managed by, or on behalf of, the\nLicensor for the purpose of discussing and improving the Work, but\nexcluding communication that is conspicuously marked or otherwise\ndesignated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\non behalf of whom a Contribution has been received by Licensor and\nsubsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\ncopyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the\nWork and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n(except as stated in this section) patent license to make, have made,\nuse, offer to sell, sell, import, and otherwise transfer the Work,\nwhere such license applies only to those patent claims licensable\nby such Contributor that are necessarily infringed by their\nContribution(s) alone or by combination of their Contribution(s)\nwith the Work to which such Contribution(s) was submitted. If You\ninstitute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work\nor a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses\ngranted to You under this License for that Work shall terminate\nas of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\nWork or Derivative Works thereof in any medium, with or without\nmodifications, and in Source or Object form, provided that You\nmeet the following conditions:\n\n(a) You must give any other recipients of the Work or\n  Derivative Works a copy of this License; and\n\n(b) You must cause any modified files to carry prominent notices\n  stating that You changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works\n  that You distribute, all copyright, patent, trademark, and\n  attribution notices from the Source form of the Work,\n  excluding those notices that do not pertain to any part of\n  the Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its\n  distribution, then any Derivative Works that You distribute must\n  include a readable copy of the attribution notices contained\n  within such NOTICE file, excluding those notices that do not\n  pertain to any part of the Derivative Works, in at least one\n  of the following places: within a NOTICE text file distributed\n  as part of the Derivative Works; within the Source form or\n  documentation, if provided along with the Derivative Works; or,\n  within a display generated by the Derivative Works, if and\n  wherever such third-party notices normally appear. The contents\n  of the NOTICE file are for informational purposes only and\n  do not modify the License. You may add Your own attribution\n  notices within Derivative Works that You distribute, alongside\n  or as an addendum to the NOTICE text from the Work, provided\n  that such additional attribution notices cannot be construed\n  as modifying the License.\n\nYou may add Your own copyright statement to Your modifications and\nmay provide additional or different license terms and conditions\nfor use, reproduction, or distribution of Your modifications, or\nfor any such Derivative Works as a whole, provided Your use,\nreproduction, and distribution of the Work otherwise complies with\nthe conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\nany Contribution intentionally submitted for inclusion in the Work\nby You to the Licensor shall be under the terms and conditions of\nthis License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify\nthe terms of any separate license agreement you may have executed\nwith Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\nnames, trademarks, service marks, or product names of the Licensor,\nexcept as required for reasonable and customary use in describing the\norigin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\nagreed to in writing, Licensor provides the Work (and each\nContributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied, including, without limitation, any warranties or conditions\nof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\nPARTICULAR PURPOSE. You are solely responsible for determining the\nappropriateness of using or redistributing the Work and assume any\nrisks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise,\nunless required by applicable law (such as deliberate and grossly\nnegligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special,\nincidental, or consequential damages of any character arising as a\nresult of this License or out of the use or inability to use the\nWork (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses), even if such Contributor\nhas been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\nthe Work or Derivative Works thereof, You may choose to offer,\nand charge a fee for, acceptance of support, warranty, indemnity,\nor other liability obligations and/or rights consistent with this\nLicense. However, in accepting such obligations, You may act only\non Your own behalf and on Your sole responsibility, not on behalf\nof any other Contributor, and only if You agree to indemnify,\ndefend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason\nof your accepting any such warranty or additional liability.\n"
  },
  {
    "path": "Makefile",
    "content": "lint:\n\tpylint --rcfile=./.pylintrc ./tests\n\tpylint --rcfile=./.pylintrc ./platformio\n\nisort:\n\tisort ./platformio\n\tisort ./tests\n\nformat:\n\tblack ./platformio\n\tblack ./tests\n\ncodespell:\n\tcodespell --skip \"./build,./docs/_build\" -L \"AtLeast,TRE,ans,dout,homestate,ser\"\n\ntest:\n\tpytest --verbose --exitfirst -n 6 --dist=loadscope tests --ignore tests/test_examples.py\n\nbefore-commit: codespell isort format lint\n\nclean-docs:\n\trm -rf docs/_build\n\nclean: clean-docs\n\tfind . -name \\*.pyc -delete\n\tfind . -name __pycache__ -delete\n\trm -rf .cache\n\trm -rf build\n\trm -rf htmlcov\n\trm -f .coverage\n\nprofile:\n\t# Usage $ > make PIOARGS=\"boards\" profile\n\tpython -m cProfile -o .tox/.tmp/cprofile.prof -m platformio ${PIOARGS}\n\tsnakeviz .tox/.tmp/cprofile.prof\n\npack:\n\tpython setup.py sdist\n\npublish:\n\tpython setup.py sdist upload\n"
  },
  {
    "path": "README.rst",
    "content": "PlatformIO Core\n===============\n\n.. image:: https://github.com/platformio/platformio-core/workflows/Core/badge.svg\n    :target: https://docs.platformio.org/en/latest/core/index.html\n    :alt:  CI Build for PlatformIO Core\n.. image:: https://github.com/platformio/platformio-core/workflows/Docs/badge.svg\n    :target: https://docs.platformio.org?utm_source=github&utm_medium=core\n    :alt:  CI Build for Docs\n.. image:: https://github.com/platformio/platformio-core/workflows/Examples/badge.svg\n    :target: https://github.com/platformio/platformio-examples\n    :alt:  CI Build for dev-platform examples\n.. image:: https://github.com/platformio/platformio-core/workflows/Projects/badge.svg\n    :target: https://docs.platformio.org/en/latest/tutorials/index.html#projects\n    :alt:  CI Build for the Community Projects\n.. image:: https://img.shields.io/pypi/v/platformio.svg\n    :target: https://pypi.python.org/pypi/platformio/\n    :alt: Latest Version\n.. image:: https://img.shields.io/badge/PlatformIO-Labs-orange.svg\n   :alt: PlatformIO Labs\n   :target: https://piolabs.com/?utm_source=github&utm_medium=core\n\n**Quick Links:** `Homepage <https://platformio.org?utm_source=github&utm_medium=core>`_ |\n`PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_ |\n`Registry <https://registry.platformio.org?utm_source=github&utm_medium=core>`_ |\n`Project Examples <https://github.com/platformio/platformio-examples/>`__ |\n`Docs <https://docs.platformio.org?utm_source=github&utm_medium=core>`_ |\n`Donate <https://platformio.org/donate?utm_source=github&utm_medium=core>`_ |\n`Contact Us <https://piolabs.com/?utm_source=github&utm_medium=core>`_\n\n**Social:** `LinkedIn <https://www.linkedin.com/company/platformio/>`_ |\n`Twitter <https://twitter.com/PlatformIO_Org>`_ |\n`Facebook <https://www.facebook.com/platformio>`_ |\n`Community Forums <https://community.platformio.org?utm_source=github&utm_medium=core>`_\n\n.. image:: https://raw.githubusercontent.com/platformio/platformio-web/develop/app/images/platformio-ide-laptop.png\n    :target: https://platformio.org?utm_source=github&utm_medium=core\n\n`PlatformIO <https://platformio.org>`_: Your Gateway to Embedded Software Development Excellence.\n\nUnlock the true potential of embedded software development with\nPlatformIO's collaborative ecosystem, embracing declarative principles,\ntest-driven methodologies, and modern toolchains for unrivaled success.\n\n* Open source, maximum permissive Apache 2.0 license\n* Cross-platform IDE and Unified Debugger\n* Static Code Analyzer and Remote Unit Testing\n* Multi-platform and Multi-architecture Build System\n* Firmware File Explorer and Memory Inspection\n\nGet Started\n-----------\n\n* `What is PlatformIO? <https://docs.platformio.org/en/latest/what-is-platformio.html?utm_source=github&utm_medium=core>`_\n* `PlatformIO IDE <https://platformio.org/platformio-ide?utm_source=github&utm_medium=core>`_\n* `PlatformIO Core (CLI) <https://docs.platformio.org/en/latest/core.html?utm_source=github&utm_medium=core>`_\n* `Project Examples <https://github.com/platformio/platformio-examples?utm_source=github&utm_medium=core>`__\n\nSolutions\n---------\n\n* `Library Management <https://docs.platformio.org/en/latest/librarymanager/index.html?utm_source=github&utm_medium=core>`_\n* `Desktop IDEs Integration <https://docs.platformio.org/en/latest/ide.html?utm_source=github&utm_medium=core>`_\n* `Continuous Integration <https://docs.platformio.org/en/latest/ci/index.html?utm_source=github&utm_medium=core>`_\n\n**Advanced**\n\n* `Debugging <https://docs.platformio.org/en/latest/plus/debugging.html?utm_source=github&utm_medium=core>`_\n* `Unit Testing <https://docs.platformio.org/en/latest/advanced/unit-testing/index.html?utm_source=github&utm_medium=core>`_\n* `Static Code Analysis <https://docs.platformio.org/en/latest/plus/pio-check.html?utm_source=github&utm_medium=core>`_\n* `Remote Development <https://docs.platformio.org/en/latest/plus/pio-remote.html?utm_source=github&utm_medium=core>`_\n\nRegistry\n--------\n\n* `Libraries <https://registry.platformio.org/search?t=library&utm_source=github&utm_medium=core>`_\n* `Development Platforms <https://registry.platformio.org/search?t=platform&utm_source=github&utm_medium=core>`_\n* `Development Tools <https://registry.platformio.org/search?t=tool&utm_source=github&utm_medium=core>`_\n\nContributing\n------------\n\nSee `contributing guidelines <https://github.com/platformio/platformio/blob/develop/CONTRIBUTING.md>`_.\n\nTelemetry / Privacy Policy\n--------------------------\n\nShare minimal diagnostics and usage information to help us make PlatformIO better.\nIt is enabled by default. For more information see:\n\n* `Telemetry Setting <https://docs.platformio.org/en/latest/userguide/cmd_settings.html?utm_source=github&utm_medium=core#enable-telemetry>`_\n\nLicense\n-------\n\nCopyright (c) 2014-present PlatformIO <contact@platformio.org>\n\nThe PlatformIO is licensed under the permissive Apache 2.0 license,\nso you can use it in both commercial and personal projects with confidence.\n\n.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg\n    :target: https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md\n    :alt:  SWUbanner"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nWe are committed to ensuring the security and protection of PlatformIO Core. \nTo this end, we support only the following versions:\n\n| Version | Supported          |\n| ------- | ------------------ |\n| 6.1.x   | :white_check_mark: |\n| < 6.1   | :x:                |\n\nUnsupported versions of the PlatformIO Core may have known vulnerabilities or security issues that could compromise the security of our organization's systems and data. \nTherefore, it is important that all developers use only supported versions of the PlatformIO Core.\n\n## Reporting a Vulnerability\n\nWe 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.\n\nTo report a vulnerability, please send an email to our security team at contact@piolabs.com. Please include as much information as possible, including:\n\n- A description of the vulnerability and how it can be exploited\n- Steps to reproduce the vulnerability\n- Any additional information that can help us understand and reproduce the vulnerability\n\nOnce 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.\n\nIf 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.\n\nIf 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.\n\nPlease note that we will not take any legal action against individuals who report vulnerabilities in good faith and in accordance with this policy.\n\nThank you for helping us keep our systems and data secure.\n"
  },
  {
    "path": "platformio/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nVERSION = (6, 1, \"20a2\")\n__version__ = \".\".join([str(s) for s in VERSION])\n\n__title__ = \"platformio\"\n__description__ = (\n    \"Your Gateway to Embedded Software Development Excellence. \"\n    \"Unlock the true potential of embedded software development \"\n    \"with PlatformIO's collaborative ecosystem, embracing \"\n    \"declarative principles, test-driven methodologies, and \"\n    \"modern toolchains for unrivaled success.\"\n)\n__url__ = \"https://platformio.org\"\n\n__author__ = \"PlatformIO Labs\"\n__email__ = \"contact@piolabs.com\"\n\n__license__ = \"Apache Software License\"\n__copyright__ = \"Copyright 2014-present PlatformIO Labs\"\n\n__accounts_api__ = \"https://api.accounts.platformio.org\"\n__registry_mirror_hosts__ = [\n    \"registry.platformio.org\",\n    \"registry.nm1.platformio.org\",\n]\n__pioremote_endpoint__ = \"ssl:host=remote.platformio.org:port=4413\"\n\n__check_internet_hosts__ = [\n    \"185.199.110.153\",  # Github.com\n    \"88.198.170.159\",  # platformio.org\n    \"github.com\",\n] + __registry_mirror_hosts__\n"
  },
  {
    "path": "platformio/__main__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\nimport traceback\n\nimport click\n\nfrom platformio import __version__, exception, maintenance\nfrom platformio.cli import PlatformioCLI\nfrom platformio.compat import IS_CYGWIN, ensure_python3\n\n\n@click.command(\n    cls=PlatformioCLI, context_settings=dict(help_option_names=[\"-h\", \"--help\"])\n)\n@click.version_option(__version__, prog_name=\"PlatformIO Core\")\n@click.option(\"--force\", \"-f\", is_flag=True, help=\"DEPRECATED\", hidden=True)\n@click.option(\"--caller\", \"-c\", help=\"Caller ID (service)\")\n@click.option(\"--no-ansi\", is_flag=True, help=\"Do not print ANSI control characters\")\n@click.pass_context\ndef cli(ctx, force, caller, no_ansi):  # pylint: disable=unused-argument\n    try:\n        if (\n            no_ansi\n            or str(\n                os.getenv(\"PLATFORMIO_NO_ANSI\", os.getenv(\"PLATFORMIO_DISABLE_COLOR\"))\n            ).lower()\n            == \"true\"\n        ):\n            # pylint: disable=protected-access\n            click._compat.isatty = lambda stream: False\n        elif (\n            str(\n                os.getenv(\"PLATFORMIO_FORCE_ANSI\", os.getenv(\"PLATFORMIO_FORCE_COLOR\"))\n            ).lower()\n            == \"true\"\n        ):\n            # pylint: disable=protected-access\n            click._compat.isatty = lambda stream: True\n    except:  # pylint: disable=bare-except\n        pass\n\n    maintenance.on_cmd_start(ctx, caller)\n\n\n@cli.result_callback()\n@click.pass_context\ndef process_result(*_, **__):\n    maintenance.on_cmd_end()\n\n\ndef configure():\n    if IS_CYGWIN:\n        raise exception.CygwinEnvDetected()\n\n    # https://urllib3.readthedocs.org\n    # /en/latest/security.html#insecureplatformwarning\n    try:\n        import urllib3  # pylint: disable=import-outside-toplevel\n\n        urllib3.disable_warnings()\n    except (AttributeError, ImportError):\n        pass\n\n    # Handle IOError issue with VSCode's Terminal (Windows)\n    click_echo_origin = [click.echo, click.secho]\n\n    def _safe_echo(origin, *args, **kwargs):\n        try:\n            click_echo_origin[origin](*args, **kwargs)\n        except IOError:\n            (sys.stderr.write if kwargs.get(\"err\") else sys.stdout.write)(\n                \"%s\\n\" % (args[0] if args else \"\")\n            )\n\n    click.echo = lambda *args, **kwargs: _safe_echo(0, *args, **kwargs)\n    click.secho = lambda *args, **kwargs: _safe_echo(1, *args, **kwargs)\n\n\ndef main(argv=None):\n    exit_code = 0\n    prev_sys_argv = sys.argv[:]\n    if argv:\n        assert isinstance(argv, list)\n        sys.argv = argv\n\n    try:\n        ensure_python3(raise_exception=True)\n        configure()\n        cli()  # pylint: disable=no-value-for-parameter\n    except SystemExit as exc:\n        if exc.code and str(exc.code).isdigit():\n            exit_code = int(exc.code)\n    except Exception as exc:  # pylint: disable=broad-except\n        if not isinstance(exc, exception.ReturnErrorCode):\n            maintenance.on_platformio_exception(exc)\n            error_str = f\"{exc.__class__.__name__}: \"\n            if isinstance(exc, exception.PlatformioException):\n                error_str += str(exc)\n            else:\n                error_str += traceback.format_exc()\n                error_str += \"\"\"\n============================================================\n\nAn unexpected error occurred. Further steps:\n\n* Verify that you have the latest version of PlatformIO using\n  `python -m pip install -U platformio` command\n\n* Try to find answer in FAQ Troubleshooting section\n  https://docs.platformio.org/page/faq/index.html\n\n* Report this problem to the developers\n  https://github.com/platformio/platformio-core/issues\n\n============================================================\n\"\"\"\n            click.secho(error_str, fg=\"red\", err=True)\n        exit_code = int(str(exc)) if str(exc).isdigit() else 1\n\n    maintenance.on_platformio_exit()\n    sys.argv = prev_sys_argv\n    return exit_code\n\n\ndef debug_gdb_main():\n    return main([sys.argv[0], \"debug\", \"--interface\", \"gdb\"] + sys.argv[1:])\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "platformio/account/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.commands.destroy import account_destroy_cmd\nfrom platformio.account.commands.forgot import account_forgot_cmd\nfrom platformio.account.commands.login import account_login_cmd\nfrom platformio.account.commands.logout import account_logout_cmd\nfrom platformio.account.commands.password import account_password_cmd\nfrom platformio.account.commands.register import account_register_cmd\nfrom platformio.account.commands.show import account_show_cmd\nfrom platformio.account.commands.token import account_token_cmd\nfrom platformio.account.commands.update import account_update_cmd\n\n\n@click.group(\n    \"account\",\n    commands=[\n        account_destroy_cmd,\n        account_forgot_cmd,\n        account_login_cmd,\n        account_logout_cmd,\n        account_password_cmd,\n        account_register_cmd,\n        account_show_cmd,\n        account_token_cmd,\n        account_update_cmd,\n    ],\n    short_help=\"Manage PlatformIO account\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/account/client.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport time\n\nfrom platformio import __accounts_api__, app\nfrom platformio.exception import PlatformioException, UserSideException\nfrom platformio.http import HTTPClient, HTTPClientError\n\n\nclass AccountError(PlatformioException):\n    MESSAGE = \"{0}\"\n\n\nclass AccountNotAuthorized(AccountError, UserSideException):\n    MESSAGE = \"You are not authorized! Please log in to PlatformIO Account.\"\n\n\nclass AccountAlreadyAuthorized(AccountError, UserSideException):\n    MESSAGE = \"You are already authorized with {0} account.\"\n\n\nclass AccountClient(HTTPClient):  # pylint:disable=too-many-public-methods\n    SUMMARY_CACHE_TTL = 60 * 60 * 24 * 7\n\n    def __init__(self):\n        super().__init__(__accounts_api__)\n\n    @staticmethod\n    def get_refresh_token():\n        try:\n            return app.get_state_item(\"account\").get(\"auth\").get(\"refresh_token\")\n        except Exception as exc:\n            raise AccountNotAuthorized() from exc\n\n    @staticmethod\n    def delete_local_session():\n        app.delete_state_item(\"account\")\n\n    @staticmethod\n    def delete_local_state(key):\n        account = app.get_state_item(\"account\")\n        if not account or key not in account:\n            return\n        del account[key]\n        app.set_state_item(\"account\", account)\n\n    def fetch_json_data(self, *args, **kwargs):\n        try:\n            return super().fetch_json_data(*args, **kwargs)\n        except HTTPClientError as exc:\n            raise AccountError(exc) from exc\n\n    def fetch_authentication_token(self):\n        if os.environ.get(\"PLATFORMIO_AUTH_TOKEN\"):\n            return os.environ.get(\"PLATFORMIO_AUTH_TOKEN\")\n        auth = app.get_state_item(\"account\", {}).get(\"auth\", {})\n        if auth.get(\"access_token\") and auth.get(\"access_token_expire\"):\n            if auth.get(\"access_token_expire\") > time.time():\n                return auth.get(\"access_token\")\n            if auth.get(\"refresh_token\"):\n                try:\n                    data = self.fetch_json_data(\n                        \"post\",\n                        \"/v1/login\",\n                        headers={\n                            \"Authorization\": \"Bearer %s\" % auth.get(\"refresh_token\")\n                        },\n                    )\n                    app.set_state_item(\"account\", data)\n                    return data.get(\"auth\").get(\"access_token\")\n                except AccountError:\n                    self.delete_local_session()\n        raise AccountNotAuthorized()\n\n    def login(self, username, password):\n        try:\n            self.fetch_authentication_token()\n        except:  # pylint:disable=bare-except\n            pass\n        else:\n            raise AccountAlreadyAuthorized(\n                app.get_state_item(\"account\", {}).get(\"email\", \"\")\n            )\n\n        data = self.fetch_json_data(\n            \"post\",\n            \"/v1/login\",\n            data={\"username\": username, \"password\": password},\n        )\n        app.set_state_item(\"account\", data)\n        return data\n\n    def login_with_code(self, client_id, code, redirect_uri):\n        try:\n            self.fetch_authentication_token()\n        except:  # pylint:disable=bare-except\n            pass\n        else:\n            raise AccountAlreadyAuthorized(\n                app.get_state_item(\"account\", {}).get(\"email\", \"\")\n            )\n\n        result = self.fetch_json_data(\n            \"post\",\n            \"/v1/login/code\",\n            data={\"client_id\": client_id, \"code\": code, \"redirect_uri\": redirect_uri},\n        )\n        app.set_state_item(\"account\", result)\n        return result\n\n    def logout(self):\n        refresh_token = self.get_refresh_token()\n        self.delete_local_session()\n        try:\n            self.fetch_json_data(\n                \"post\",\n                \"/v1/logout\",\n                data={\"refresh_token\": refresh_token},\n            )\n        except AccountError:\n            pass\n        return True\n\n    def change_password(self, old_password, new_password):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/password\",\n            data={\"old_password\": old_password, \"new_password\": new_password},\n            x_with_authorization=True,\n        )\n\n    def registration(\n        self, username, email, password, firstname, lastname\n    ):  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        try:\n            self.fetch_authentication_token()\n        except:  # pylint:disable=bare-except\n            pass\n        else:\n            raise AccountAlreadyAuthorized(\n                app.get_state_item(\"account\", {}).get(\"email\", \"\")\n            )\n\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/registration\",\n            data={\n                \"username\": username,\n                \"email\": email,\n                \"password\": password,\n                \"firstname\": firstname,\n                \"lastname\": lastname,\n            },\n        )\n\n    def auth_token(self, password, regenerate):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/token\",\n            data={\"password\": password, \"regenerate\": 1 if regenerate else 0},\n            x_with_authorization=True,\n        ).get(\"auth_token\")\n\n    def forgot_password(self, username):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/forgot\",\n            data={\"username\": username},\n        )\n\n    def get_profile(self):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/profile\",\n            x_with_authorization=True,\n        )\n\n    def update_profile(self, profile, current_password):\n        profile[\"current_password\"] = current_password\n        self.delete_local_state(\"summary\")\n        response = self.fetch_json_data(\n            \"put\",\n            \"/v1/profile\",\n            data=profile,\n            x_with_authorization=True,\n        )\n        return response\n\n    def get_account_info(self, offline=False):\n        account = app.get_state_item(\"account\") or {}\n        if (\n            account.get(\"summary\")\n            and account[\"summary\"].get(\"expire_at\", 0) > time.time()\n        ):\n            return account[\"summary\"]\n        if offline and account.get(\"email\"):\n            return {\n                \"profile\": {\n                    \"email\": account.get(\"email\"),\n                    \"username\": account.get(\"username\"),\n                }\n            }\n        result = self.fetch_json_data(\n            \"get\",\n            \"/v1/summary\",\n            x_with_authorization=True,\n        )\n        account[\"summary\"] = dict(\n            profile=result.get(\"profile\"),\n            packages=result.get(\"packages\"),\n            subscriptions=result.get(\"subscriptions\"),\n            user_id=result.get(\"user_id\"),\n            expire_at=int(time.time()) + self.SUMMARY_CACHE_TTL,\n        )\n        app.set_state_item(\"account\", account)\n        return result\n\n    def get_logged_username(self):\n        return self.get_account_info(offline=True).get(\"profile\").get(\"username\")\n\n    def destroy_account(self):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v1/account\",\n            x_with_authorization=True,\n        )\n\n    def create_org(self, orgname, email, displayname):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/orgs\",\n            data={\"orgname\": orgname, \"email\": email, \"displayname\": displayname},\n            x_with_authorization=True,\n        )\n\n    def get_org(self, orgname):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/orgs/%s\" % orgname,\n            x_with_authorization=True,\n        )\n\n    def list_orgs(self):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/orgs\",\n            x_with_authorization=True,\n        )\n\n    def update_org(self, orgname, data):\n        return self.fetch_json_data(\n            \"put\",\n            \"/v1/orgs/%s\" % orgname,\n            data={k: v for k, v in data.items() if v},\n            x_with_authorization=True,\n        )\n\n    def destroy_org(self, orgname):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v1/orgs/%s\" % orgname,\n            x_with_authorization=True,\n        )\n\n    def add_org_owner(self, orgname, username):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/orgs/%s/owners\" % orgname,\n            data={\"username\": username},\n            x_with_authorization=True,\n        )\n\n    def list_org_owners(self, orgname):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/orgs/%s/owners\" % orgname,\n            x_with_authorization=True,\n        )\n\n    def remove_org_owner(self, orgname, username):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v1/orgs/%s/owners\" % orgname,\n            params={\"username\": username},\n            x_with_authorization=True,\n        )\n\n    def create_team(self, orgname, teamname, description):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/orgs/%s/teams\" % orgname,\n            data={\"name\": teamname, \"description\": description},\n            x_with_authorization=True,\n        )\n\n    def destroy_team(self, orgname, teamname):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v1/orgs/%s/teams/%s\" % (orgname, teamname),\n            x_with_authorization=True,\n        )\n\n    def get_team(self, orgname, teamname):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/orgs/%s/teams/%s\" % (orgname, teamname),\n            x_with_authorization=True,\n        )\n\n    def list_teams(self, orgname):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v1/orgs/%s/teams\" % orgname,\n            x_with_authorization=True,\n        )\n\n    def update_team(self, orgname, teamname, data):\n        return self.fetch_json_data(\n            \"put\",\n            \"/v1/orgs/%s/teams/%s\" % (orgname, teamname),\n            data={k: v for k, v in data.items() if v},\n            x_with_authorization=True,\n        )\n\n    def add_team_member(self, orgname, teamname, username):\n        return self.fetch_json_data(\n            \"post\",\n            \"/v1/orgs/%s/teams/%s/members\" % (orgname, teamname),\n            data={\"username\": username},\n            x_with_authorization=True,\n        )\n\n    def remove_team_member(self, orgname, teamname, username):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v1/orgs/%s/teams/%s/members\" % (orgname, teamname),\n            params={\"username\": username},\n            x_with_authorization=True,\n        )\n"
  },
  {
    "path": "platformio/account/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/commands/destroy.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient, AccountNotAuthorized\n\n\n@click.command(\"destroy\", short_help=\"Destroy account\")\ndef account_destroy_cmd():\n    client = AccountClient()\n    click.confirm(\n        \"Are you sure you want to delete the %s user account?\\n\"\n        \"Warning! All linked data will be permanently removed and can not be restored.\"\n        % client.get_logged_username(),\n        abort=True,\n    )\n    client.destroy_account()\n    try:\n        client.logout()\n    except AccountNotAuthorized:\n        pass\n    click.secho(\n        \"User account has been destroyed.\",\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/commands/forgot.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"forgot\", short_help=\"Forgot password\")\n@click.option(\"--username\", prompt=\"Username or email\")\ndef account_forgot_cmd(username):\n    client = AccountClient()\n    client.forgot_password(username)\n    click.secho(\n        \"If this account is registered, we will send the \"\n        \"further instructions to your email.\",\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/commands/login.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"login\", short_help=\"Log in to PlatformIO Account\")\n@click.option(\"-u\", \"--username\", prompt=\"Username or email\")\n@click.option(\"-p\", \"--password\", prompt=True, hide_input=True)\ndef account_login_cmd(username, password):\n    client = AccountClient()\n    client.login(username, password)\n    click.secho(\"Successfully logged in!\", fg=\"green\")\n"
  },
  {
    "path": "platformio/account/commands/logout.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"logout\", short_help=\"Log out of PlatformIO Account\")\ndef account_logout_cmd():\n    client = AccountClient()\n    client.logout()\n    click.secho(\"Successfully logged out!\", fg=\"green\")\n"
  },
  {
    "path": "platformio/account/commands/password.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"password\", short_help=\"Change password\")\n@click.option(\"--old-password\", prompt=True, hide_input=True)\n@click.option(\"--new-password\", prompt=True, hide_input=True, confirmation_prompt=True)\ndef account_password_cmd(old_password, new_password):\n    client = AccountClient()\n    client.change_password(old_password, new_password)\n    click.secho(\"Password successfully changed!\", fg=\"green\")\n"
  },
  {
    "path": "platformio/account/commands/register.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import (\n    validate_email,\n    validate_password,\n    validate_username,\n)\n\n\n@click.command(\"register\", short_help=\"Create new PlatformIO Account\")\n@click.option(\n    \"-u\",\n    \"--username\",\n    prompt=True,\n    callback=lambda _, __, value: validate_username(value),\n)\n@click.option(\n    \"-e\", \"--email\", prompt=True, callback=lambda _, __, value: validate_email(value)\n)\n@click.option(\n    \"-p\",\n    \"--password\",\n    prompt=True,\n    hide_input=True,\n    confirmation_prompt=True,\n    callback=lambda _, __, value: validate_password(value),\n)\n@click.option(\"--firstname\", prompt=True)\n@click.option(\"--lastname\", prompt=True)\ndef account_register_cmd(username, email, password, firstname, lastname):\n    client = AccountClient()\n    client.registration(username, email, password, firstname, lastname)\n    click.secho(\n        \"An account has been successfully created. \"\n        \"Please check your mail to activate your account and verify your email address.\",\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/commands/show.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import util\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"show\", short_help=\"PlatformIO Account information\")\n@click.option(\"--offline\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\ndef account_show_cmd(offline, json_output):\n    client = AccountClient()\n    info = client.get_account_info(offline)\n    if json_output:\n        click.echo(json.dumps(info))\n        return\n    click.echo()\n    if info.get(\"profile\"):\n        print_profile(info[\"profile\"])\n    if info.get(\"packages\"):\n        print_packages(info[\"packages\"])\n    if info.get(\"subscriptions\"):\n        print_subscriptions(info[\"subscriptions\"])\n    click.echo()\n\n\ndef print_profile(profile):\n    click.secho(\"Profile\", fg=\"cyan\", bold=True)\n    click.echo(\"=\" * len(\"Profile\"))\n    data = []\n    if profile.get(\"username\"):\n        data.append((\"Username:\", profile[\"username\"]))\n    if profile.get(\"email\"):\n        data.append((\"Email:\", profile[\"email\"]))\n    if profile.get(\"firstname\"):\n        data.append((\"First name:\", profile[\"firstname\"]))\n    if profile.get(\"lastname\"):\n        data.append((\"Last name:\", profile[\"lastname\"]))\n    click.echo(tabulate(data, tablefmt=\"plain\"))\n\n\ndef print_packages(packages):\n    click.echo()\n    click.secho(\"Packages\", fg=\"cyan\")\n    click.echo(\"=\" * len(\"Packages\"))\n    for package in packages:\n        click.echo()\n        click.secho(package.get(\"name\"), bold=True)\n        click.echo(\"-\" * len(package.get(\"name\")))\n        if package.get(\"description\"):\n            click.echo(package.get(\"description\"))\n        data = []\n        expire = \"-\"\n        if \"subscription\" in package:\n            expire = util.parse_datetime(\n                package[\"subscription\"].get(\"end_at\")\n                or package[\"subscription\"].get(\"next_bill_at\")\n            ).strftime(\"%Y-%m-%d\")\n        data.append((\"Expire:\", expire))\n        services = []\n        for key in package:\n            if not key.startswith(\"service.\"):\n                continue\n            if isinstance(package[key], dict):\n                services.append(package[key].get(\"title\"))\n            else:\n                services.append(package[key])\n        if services:\n            data.append((\"Services:\", \", \".join(services)))\n        click.echo(tabulate(data, tablefmt=\"plain\"))\n\n\ndef print_subscriptions(subscriptions):\n    click.echo()\n    click.secho(\"Subscriptions\", fg=\"cyan\")\n    click.echo(\"=\" * len(\"Subscriptions\"))\n    for subscription in subscriptions:\n        click.echo()\n        click.secho(subscription.get(\"product_name\"), bold=True)\n        click.echo(\"-\" * len(subscription.get(\"product_name\")))\n        data = [(\"State:\", subscription.get(\"status\"))]\n        begin_at = util.parse_datetime(subscription.get(\"begin_at\")).strftime(\"%c\")\n        data.append((\"Start date:\", begin_at or \"-\"))\n        end_at = subscription.get(\"end_at\")\n        if end_at:\n            end_at = util.parse_datetime(subscription.get(\"end_at\")).strftime(\"%c\")\n        data.append((\"End date:\", end_at or \"-\"))\n        next_bill_at = subscription.get(\"next_bill_at\")\n        if next_bill_at:\n            next_bill_at = util.parse_datetime(\n                subscription.get(\"next_bill_at\")\n            ).strftime(\"%c\")\n        data.append((\"Next payment:\", next_bill_at or \"-\"))\n        data.append(\n            (\"Edit:\", click.style(subscription.get(\"update_url\"), fg=\"blue\") or \"-\")\n        )\n        data.append(\n            (\"Cancel:\", click.style(subscription.get(\"cancel_url\"), fg=\"blue\") or \"-\")\n        )\n        click.echo(tabulate(data, tablefmt=\"plain\"))\n"
  },
  {
    "path": "platformio/account/commands/token.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"token\", short_help=\"Get or regenerate Authentication Token\")\n@click.option(\"-p\", \"--password\", prompt=True, hide_input=True)\n@click.option(\"--regenerate\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\ndef account_token_cmd(password, regenerate, json_output):\n    client = AccountClient()\n    auth_token = client.auth_token(password, regenerate)\n    if json_output:\n        click.echo(json.dumps({\"status\": \"success\", \"result\": auth_token}))\n        return\n    click.secho(\"Personal Authentication Token: %s\" % auth_token, fg=\"green\")\n"
  },
  {
    "path": "platformio/account/commands/update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient, AccountNotAuthorized\nfrom platformio.account.validate import validate_email, validate_username\n\n\n@click.command(\"update\", short_help=\"Update profile information\")\n@click.option(\"--current-password\", prompt=True, hide_input=True)\n@click.option(\"--username\")\n@click.option(\"--email\")\n@click.option(\"--firstname\")\n@click.option(\"--lastname\")\ndef account_update_cmd(current_password, **kwargs):\n    client = AccountClient()\n    profile = client.get_profile()\n    new_profile = profile.copy()\n    if not any(kwargs.values()):\n        for field in profile:\n            new_profile[field] = click.prompt(\n                field.replace(\"_\", \" \").capitalize(), default=profile[field]\n            )\n            if field == \"email\":\n                validate_email(new_profile[field])\n            if field == \"username\":\n                validate_username(new_profile[field])\n    else:\n        new_profile.update({key: value for key, value in kwargs.items() if value})\n    client.update_profile(new_profile, current_password)\n    click.secho(\"Profile successfully updated!\", fg=\"green\")\n    username_changed = new_profile[\"username\"] != profile[\"username\"]\n    email_changed = new_profile[\"email\"] != profile[\"email\"]\n    if not username_changed and not email_changed:\n        return None\n    try:\n        client.logout()\n    except AccountNotAuthorized:\n        pass\n    if email_changed:\n        click.secho(\n            \"Please check your mail to verify your new email address and re-login. \",\n            fg=\"yellow\",\n        )\n        return None\n    click.secho(\"Please re-login.\", fg=\"yellow\")\n    return None\n"
  },
  {
    "path": "platformio/account/org/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/org/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.org.commands.add import org_add_cmd\nfrom platformio.account.org.commands.create import org_create_cmd\nfrom platformio.account.org.commands.destroy import org_destroy_cmd\nfrom platformio.account.org.commands.list import org_list_cmd\nfrom platformio.account.org.commands.remove import org_remove_cmd\nfrom platformio.account.org.commands.update import org_update_cmd\n\n\n@click.group(\n    \"account\",\n    commands=[\n        org_add_cmd,\n        org_create_cmd,\n        org_destroy_cmd,\n        org_list_cmd,\n        org_remove_cmd,\n        org_update_cmd,\n    ],\n    short_help=\"Manage organizations\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/account/org/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/org/commands/add.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"add\", short_help=\"Add a new owner to organization\")\n@click.argument(\n    \"orgname\",\n)\n@click.argument(\n    \"username\",\n)\ndef org_add_cmd(orgname, username):\n    client = AccountClient()\n    client.add_org_owner(orgname, username)\n    return click.secho(\n        \"The new owner `%s` has been successfully added to the `%s` organization.\"\n        % (username, orgname),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/org/commands/create.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_email, validate_orgname\n\n\n@click.command(\"create\", short_help=\"Create a new organization\")\n@click.argument(\n    \"orgname\",\n    callback=lambda _, __, value: validate_orgname(value),\n)\n@click.option(\n    \"--email\", callback=lambda _, __, value: validate_email(value) if value else value\n)\n@click.option(\n    \"--displayname\",\n)\ndef org_create_cmd(orgname, email, displayname):\n    client = AccountClient()\n    client.create_org(orgname, email, displayname)\n    return click.secho(\n        \"The organization `%s` has been successfully created.\" % orgname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/org/commands/destroy.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"destroy\", short_help=\"Destroy organization\")\n@click.argument(\"orgname\")\ndef org_destroy_cmd(orgname):\n    client = AccountClient()\n    click.confirm(\n        \"Are you sure you want to delete the `%s` organization account?\\n\"\n        \"Warning! All linked data will be permanently removed and can not be restored.\"\n        % orgname,\n        abort=True,\n    )\n    client.destroy_org(orgname)\n    return click.secho(\n        \"Organization `%s` has been destroyed.\" % orgname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/org/commands/list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"list\", short_help=\"List organizations and their members\")\n@click.option(\"--json-output\", is_flag=True)\ndef org_list_cmd(json_output):\n    client = AccountClient()\n    orgs = client.list_orgs()\n    if json_output:\n        return click.echo(json.dumps(orgs))\n    if not orgs:\n        return click.echo(\"You do not have any organization\")\n    for org in orgs:\n        click.echo()\n        click.secho(org.get(\"orgname\"), fg=\"cyan\")\n        click.echo(\"-\" * len(org.get(\"orgname\")))\n        data = []\n        if org.get(\"displayname\"):\n            data.append((\"Display Name:\", org.get(\"displayname\")))\n        if org.get(\"email\"):\n            data.append((\"Email:\", org.get(\"email\")))\n        data.append(\n            (\n                \"Owners:\",\n                \", \".join((owner.get(\"username\") for owner in org.get(\"owners\"))),\n            )\n        )\n        click.echo(tabulate(data, tablefmt=\"plain\"))\n    return click.echo()\n"
  },
  {
    "path": "platformio/account/org/commands/remove.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"remove\", short_help=\"Remove an owner from organization\")\n@click.argument(\n    \"orgname\",\n)\n@click.argument(\n    \"username\",\n)\ndef org_remove_cmd(orgname, username):\n    client = AccountClient()\n    client.remove_org_owner(orgname, username)\n    return click.secho(\n        \"The `%s` owner has been successfully removed from the `%s` organization.\"\n        % (username, orgname),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/org/commands/update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_email, validate_orgname\n\n\n@click.command(\"update\", short_help=\"Update organization\")\n@click.argument(\"cur_orgname\")\n@click.option(\n    \"--orgname\",\n    callback=lambda _, __, value: validate_orgname(value) if value else value,\n    help=\"A new orgname\",\n)\n@click.option(\n    \"--email\",\n    callback=lambda _, __, value: validate_email(value) if value else value,\n)\n@click.option(\"--displayname\")\ndef org_update_cmd(cur_orgname, **kwargs):\n    client = AccountClient()\n    org = client.get_org(cur_orgname)\n    new_org = {\n        key: value if value is not None else org[key] for key, value in kwargs.items()\n    }\n    if not any(kwargs.values()):\n        for key in kwargs:\n            new_org[key] = click.prompt(key.capitalize(), default=org[key])\n            if key == \"email\":\n                validate_email(new_org[key])\n            if key == \"orgname\":\n                validate_orgname(new_org[key])\n    client.update_org(cur_orgname, new_org)\n    return click.secho(\n        \"The organization `%s` has been successfully updated.\" % cur_orgname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/team/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/team/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.team.commands.add import team_add_cmd\nfrom platformio.account.team.commands.create import team_create_cmd\nfrom platformio.account.team.commands.destroy import team_destroy_cmd\nfrom platformio.account.team.commands.list import team_list_cmd\nfrom platformio.account.team.commands.remove import team_remove_cmd\nfrom platformio.account.team.commands.update import team_update_cmd\n\n\n@click.group(\n    \"team\",\n    commands=[\n        team_add_cmd,\n        team_create_cmd,\n        team_destroy_cmd,\n        team_list_cmd,\n        team_remove_cmd,\n        team_update_cmd,\n    ],\n    short_help=\"Manage organization teams\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/account/team/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/account/team/commands/add.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_orgname_teamname\n\n\n@click.command(\"add\", short_help=\"Add a new member to team\")\n@click.argument(\n    \"orgname_teamname\",\n    metavar=\"ORGNAME:TEAMNAME\",\n    callback=lambda _, __, value: validate_orgname_teamname(value),\n)\n@click.argument(\n    \"username\",\n)\ndef team_add_cmd(orgname_teamname, username):\n    orgname, teamname = orgname_teamname.split(\":\", 1)\n    client = AccountClient()\n    client.add_team_member(orgname, teamname, username)\n    return click.secho(\n        \"The new member %s has been successfully added to the %s team.\"\n        % (username, teamname),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/team/commands/create.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_orgname_teamname\n\n\n@click.command(\"create\", short_help=\"Create a new team\")\n@click.argument(\n    \"orgname_teamname\",\n    metavar=\"ORGNAME:TEAMNAME\",\n    callback=lambda _, __, value: validate_orgname_teamname(value),\n)\n@click.option(\n    \"--description\",\n)\ndef team_create_cmd(orgname_teamname, description):\n    orgname, teamname = orgname_teamname.split(\":\", 1)\n    client = AccountClient()\n    client.create_team(orgname, teamname, description)\n    return click.secho(\n        \"The team %s has been successfully created.\" % teamname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/team/commands/destroy.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_orgname_teamname\n\n\n@click.command(\"destroy\", short_help=\"Destroy a team\")\n@click.argument(\n    \"orgname_teamname\",\n    metavar=\"ORGNAME:TEAMNAME\",\n    callback=lambda _, __, value: validate_orgname_teamname(value),\n)\ndef team_destroy_cmd(orgname_teamname):\n    orgname, teamname = orgname_teamname.split(\":\", 1)\n    click.confirm(\n        click.style(\n            \"Are you sure you want to destroy the %s team?\" % teamname, fg=\"yellow\"\n        ),\n        abort=True,\n    )\n    client = AccountClient()\n    client.destroy_team(orgname, teamname)\n    return click.secho(\n        \"The team %s has been successfully destroyed.\" % teamname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/team/commands/list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio.account.client import AccountClient\n\n\n@click.command(\"list\", short_help=\"List teams\")\n@click.argument(\"orgname\", required=False)\n@click.option(\"--json-output\", is_flag=True)\ndef team_list_cmd(orgname, json_output):\n    client = AccountClient()\n    data = {}\n    if not orgname:\n        for item in client.list_orgs():\n            teams = client.list_teams(item.get(\"orgname\"))\n            data[item.get(\"orgname\")] = teams\n    else:\n        teams = client.list_teams(orgname)\n        data[orgname] = teams\n    if json_output:\n        return click.echo(json.dumps(data[orgname] if orgname else data))\n    if not any(data.values()):\n        return click.secho(\"You do not have any teams.\", fg=\"yellow\")\n    for org_name, teams in data.items():\n        for team in teams:\n            click.echo()\n            click.secho(\"%s:%s\" % (org_name, team.get(\"name\")), fg=\"cyan\")\n            click.echo(\"-\" * len(\"%s:%s\" % (org_name, team.get(\"name\"))))\n            table_data = []\n            if team.get(\"description\"):\n                table_data.append((\"Description:\", team.get(\"description\")))\n            table_data.append(\n                (\n                    \"Members:\",\n                    (\n                        \", \".join(\n                            (member.get(\"username\") for member in team.get(\"members\"))\n                        )\n                        if team.get(\"members\")\n                        else \"-\"\n                    ),\n                )\n            )\n            click.echo(tabulate(table_data, tablefmt=\"plain\"))\n    return click.echo()\n"
  },
  {
    "path": "platformio/account/team/commands/remove.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_orgname_teamname\n\n\n@click.command(\"remove\", short_help=\"Remove a member from team\")\n@click.argument(\n    \"orgname_teamname\",\n    metavar=\"ORGNAME:TEAMNAME\",\n    callback=lambda _, __, value: validate_orgname_teamname(value),\n)\n@click.argument(\"username\")\ndef team_remove_cmd(orgname_teamname, username):\n    orgname, teamname = orgname_teamname.split(\":\", 1)\n    client = AccountClient()\n    client.remove_team_member(orgname, teamname, username)\n    return click.secho(\n        \"The %s member has been successfully removed from the %s team.\"\n        % (username, teamname),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/team/commands/update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.account.validate import validate_orgname_teamname, validate_teamname\n\n\n@click.command(\"update\", short_help=\"Update team\")\n@click.argument(\n    \"orgname_teamname\",\n    metavar=\"ORGNAME:TEAMNAME\",\n    callback=lambda _, __, value: validate_orgname_teamname(value),\n)\n@click.option(\n    \"--name\",\n    callback=lambda _, __, value: validate_teamname(value) if value else value,\n    help=\"A new team name\",\n)\n@click.option(\n    \"--description\",\n)\ndef team_update_cmd(orgname_teamname, **kwargs):\n    orgname, teamname = orgname_teamname.split(\":\", 1)\n    client = AccountClient()\n    team = client.get_team(orgname, teamname)\n    new_team = {\n        key: value if value is not None else team[key] for key, value in kwargs.items()\n    }\n    if not any(kwargs.values()):\n        for key in kwargs:\n            new_team[key] = click.prompt(key.capitalize(), default=team[key])\n            if key == \"name\":\n                validate_teamname(new_team[key])\n    client.update_team(orgname, teamname, new_team)\n    return click.secho(\n        \"The team %s has been successfully updated.\" % teamname,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/account/validate.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\n\nimport click\n\n\ndef validate_username(value, field=\"username\"):\n    value = str(value).strip() if value else None\n    if not value or not re.match(\n        r\"^[a-z\\d](?:[a-z\\d]|-(?=[a-z\\d])){0,37}$\", value, flags=re.I\n    ):\n        raise click.BadParameter(\n            \"Invalid %s format. \"\n            \"%s must contain only alphanumeric characters \"\n            \"or single hyphens, cannot begin or end with a hyphen, \"\n            \"and must not be longer than 38 characters.\"\n            % (field.lower(), field.capitalize())\n        )\n    return value\n\n\ndef validate_orgname(value):\n    return validate_username(value, \"Organization name\")\n\n\ndef validate_email(value):\n    value = str(value).strip() if value else None\n    if not value or not re.match(\n        r\"^[a-z\\d_\\.\\+\\-]+@[a-z\\d\\-]+\\.[a-z\\d\\-\\.]+$\", value, flags=re.I\n    ):\n        raise click.BadParameter(\"Invalid email address\")\n    return value\n\n\ndef validate_password(value):\n    value = str(value).strip() if value else None\n    if not value or not re.match(r\"^(?=.*[a-z])(?=.*\\d).{8,}$\", value):\n        raise click.BadParameter(\n            \"Invalid password format. \"\n            \"Password must contain at least 8 characters\"\n            \" including a number and a lowercase letter\"\n        )\n    return value\n\n\ndef validate_teamname(value):\n    value = str(value).strip() if value else None\n    if not value or not re.match(\n        r\"^[a-z\\d](?:[a-z\\d]|[\\-_ ](?=[a-z\\d])){0,19}$\", value, flags=re.I\n    ):\n        raise click.BadParameter(\n            \"Invalid team name format. \"\n            \"Team name must only contain alphanumeric characters, \"\n            \"single hyphens, underscores, spaces. It can not \"\n            \"begin or end with a hyphen or a underscore and must\"\n            \" not be longer than 20 characters.\"\n        )\n    return value\n\n\ndef validate_orgname_teamname(value):\n    value = str(value).strip() if value else None\n    if not value or \":\" not in value:\n        raise click.BadParameter(\n            \"Please specify organization and team name using the following\"\n            \" format - orgname:teamname. For example, mycompany:DreamTeam\"\n        )\n    orgname, teamname = value.split(\":\", 1)\n    validate_orgname(orgname)\n    validate_teamname(teamname)\n    return value\n"
  },
  {
    "path": "platformio/app.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport getpass\nimport hashlib\nimport json\nimport os\nimport platform\nimport socket\nimport time\nimport uuid\n\nfrom platformio import __version__, exception, fs, proc\nfrom platformio.compat import IS_WINDOWS, hashlib_encode_data\nfrom platformio.package.lockfile import LockFile\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import get_default_projects_dir\n\n\ndef projects_dir_validate(projects_dir):\n    assert os.path.isdir(projects_dir)\n    return os.path.abspath(projects_dir)\n\n\nDEFAULT_SETTINGS = {\n    \"check_platformio_interval\": {\n        \"description\": \"Check for the new PlatformIO Core interval (days)\",\n        \"value\": 7,\n    },\n    \"check_prune_system_threshold\": {\n        \"description\": \"Check for pruning unnecessary data threshold (megabytes)\",\n        \"value\": 1024,\n    },\n    \"enable_cache\": {\n        \"description\": \"Enable caching for HTTP API requests\",\n        \"value\": True,\n    },\n    \"enable_telemetry\": {\n        \"description\": (\"Telemetry service <https://bit.ly/pio-telemetry> (Yes/No)\"),\n        \"value\": True,\n    },\n    \"force_verbose\": {\n        \"description\": \"Force verbose output when processing environments\",\n        \"value\": False,\n    },\n    \"projects_dir\": {\n        \"description\": \"Default location for PlatformIO projects (PlatformIO Home)\",\n        \"value\": get_default_projects_dir(),\n        \"validator\": projects_dir_validate,\n    },\n    \"enable_proxy_strict_ssl\": {\n        \"description\": \"Verify the proxy server certificate against the list of supplied CAs\",\n        \"value\": True,\n    },\n}\n\nSESSION_VARS = {\n    \"command_ctx\": None,\n    \"caller_id\": None,\n    \"custom_project_conf\": None,\n    \"pause_telemetry\": False,\n}\n\n\ndef resolve_state_path(conf_option_dir, file_name, ensure_dir_exists=True):\n    state_dir = ProjectConfig.get_instance().get(\"platformio\", conf_option_dir)\n    if ensure_dir_exists and not os.path.isdir(state_dir):\n        os.makedirs(state_dir)\n    return os.path.join(state_dir, file_name)\n\n\nclass State:\n    def __init__(self, path=None, lock=False):\n        self.path = path\n        self.lock = lock\n        if not self.path:\n            self.path = resolve_state_path(\"core_dir\", \"appstate.json\")\n        self._storage = {}\n        self._lockfile = None\n        self.modified = False\n\n    def __enter__(self):\n        try:\n            self._lock_state_file()\n            if os.path.isfile(self.path):\n                self._storage = fs.load_json(self.path)\n            assert isinstance(self._storage, dict)\n        except (\n            AssertionError,\n            ValueError,\n            UnicodeDecodeError,\n            exception.InvalidJSONFile,\n        ):\n            self._storage = {}\n        return self\n\n    def __exit__(self, type_, value, traceback):\n        if self.modified:\n            try:\n                with open(self.path, mode=\"w\", encoding=\"utf8\") as fp:\n                    fp.write(json.dumps(self._storage))\n            except IOError as exc:\n                raise exception.HomeDirPermissionsError(\n                    os.path.dirname(self.path)\n                ) from exc\n        self._unlock_state_file()\n\n    def _lock_state_file(self):\n        if not self.lock:\n            return\n        self._lockfile = LockFile(self.path)\n        try:\n            self._lockfile.acquire()\n        except IOError as exc:\n            raise exception.HomeDirPermissionsError(os.path.dirname(self.path)) from exc\n\n    def _unlock_state_file(self):\n        if hasattr(self, \"_lockfile\") and self._lockfile:\n            self._lockfile.release()\n\n    def __del__(self):\n        self._unlock_state_file()\n\n    # Dictionary Proxy\n\n    def as_dict(self):\n        return self._storage\n\n    def keys(self):\n        return self._storage.keys()\n\n    def get(self, key, default=True):\n        return self._storage.get(key, default)\n\n    def update(self, *args, **kwargs):\n        self.modified = True\n        return self._storage.update(*args, **kwargs)\n\n    def clear(self):\n        return self._storage.clear()\n\n    def __getitem__(self, key):\n        return self._storage[key]\n\n    def __setitem__(self, key, value):\n        self.modified = True\n        self._storage[key] = value\n\n    def __delitem__(self, key):\n        self.modified = True\n        del self._storage[key]\n\n    def __contains__(self, item):\n        return item in self._storage\n\n\ndef sanitize_setting(name, value):\n    if name not in DEFAULT_SETTINGS:\n        raise exception.InvalidSettingName(name)\n\n    defdata = DEFAULT_SETTINGS[name]\n    try:\n        if \"validator\" in defdata:\n            value = defdata[\"validator\"](value)\n        elif isinstance(defdata[\"value\"], bool):\n            if not isinstance(value, bool):\n                value = str(value).lower() in (\"true\", \"yes\", \"y\", \"1\")\n        elif isinstance(defdata[\"value\"], int):\n            value = int(value)\n    except Exception as exc:\n        raise exception.InvalidSettingValue(value, name) from exc\n    return value\n\n\ndef get_state_item(name, default=None):\n    with State() as state:\n        return state.get(name, default)\n\n\ndef set_state_item(name, value):\n    with State(lock=True) as state:\n        state[name] = value\n        state.modified = True\n\n\ndef delete_state_item(name):\n    with State(lock=True) as state:\n        if name in state:\n            del state[name]\n\n\ndef get_setting(name):\n    _env_name = \"PLATFORMIO_SETTING_%s\" % name.upper()\n    if _env_name in os.environ:\n        return sanitize_setting(name, os.getenv(_env_name))\n\n    with State() as state:\n        if \"settings\" in state and name in state[\"settings\"]:\n            return state[\"settings\"][name]\n\n    return DEFAULT_SETTINGS[name][\"value\"]\n\n\ndef set_setting(name, value):\n    with State(lock=True) as state:\n        if \"settings\" not in state:\n            state[\"settings\"] = {}\n        state[\"settings\"][name] = sanitize_setting(name, value)\n        state.modified = True\n\n\ndef reset_settings():\n    with State(lock=True) as state:\n        if \"settings\" in state:\n            del state[\"settings\"]\n\n\ndef get_session_var(name, default=None):\n    return SESSION_VARS.get(name, default)\n\n\ndef set_session_var(name, value):\n    assert name in SESSION_VARS\n    SESSION_VARS[name] = value\n\n\ndef is_disabled_progressbar():\n    return os.getenv(\"PLATFORMIO_DISABLE_PROGRESSBAR\") == \"true\"\n\n\ndef get_cid():\n    cid = get_state_item(\"cid\")\n    if cid:\n        return cid\n    uid = None\n    if os.getenv(\"GITHUB_USER\"):\n        uid = os.getenv(\"GITHUB_USER\")\n    elif os.getenv(\"GITPOD_GIT_USER_NAME\"):\n        uid = os.getenv(\"GITPOD_GIT_USER_NAME\")\n    if not uid:\n        uid = uuid.getnode()\n    cid = uuid.UUID(bytes=hashlib.md5(hashlib_encode_data(uid)).digest())\n    cid = str(cid)\n    if IS_WINDOWS or os.getuid() > 0:  # pylint: disable=no-member\n        set_state_item(\"cid\", cid)\n        set_state_item(\"created_at\", int(time.time()))\n    return cid\n\n\ndef get_project_id(project_dir):\n    return hashlib.sha1(hashlib_encode_data(project_dir)).hexdigest()\n\n\ndef get_user_agent():\n    data = [\n        \"PlatformIO/%s\" % __version__,\n        \"CI/%d\" % int(proc.is_ci()),\n        \"Container/%d\" % int(proc.is_container()),\n    ]\n    if get_session_var(\"caller_id\"):\n        data.append(\"Caller/%s\" % get_session_var(\"caller_id\"))\n    if os.getenv(\"PLATFORMIO_IDE\"):\n        data.append(\"IDE/%s\" % os.getenv(\"PLATFORMIO_IDE\"))\n    data.append(\"Python/%s\" % platform.python_version())\n    data.append(\"Platform/%s\" % platform.platform())\n    if not get_setting(\"enable_telemetry\"):\n        data.append(\"Telemetry/0\")\n    return \" \".join(data)\n\n\ndef get_host_id():\n    h = hashlib.sha1(hashlib_encode_data(get_cid()))\n    try:\n        username = getpass.getuser()\n        h.update(hashlib_encode_data(username))\n    except:  # pylint: disable=bare-except\n        pass\n    return h.hexdigest()\n\n\ndef get_host_name():\n    return str(socket.gethostname())[:255]\n"
  },
  {
    "path": "platformio/assets/schema/library.json",
    "content": "{\r\n  \"$id\": \"https://example.com/library.json\",\r\n  \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\r\n  \"title\": \"library.json schema\",\r\n  \"type\": \"object\",\r\n  \"properties\": {\r\n    \"name\": {\r\n      \"type\": \"string\",\r\n      \"maxLength\": 50,\r\n      \"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.\",\r\n      \"required\": true\r\n    },\r\n    \"version\": {\r\n      \"type\": \"string\",\r\n      \"maxLength\": 20,\r\n      \"description\": \"A version of a current library source code. Can contain a-z, digits, dots or dash and should be Semantic Versioning compatible.\",\r\n      \"required\": true\r\n    },\r\n    \"description\": {\r\n      \"type\": \"string\",\r\n      \"maxLength\": 255,\r\n      \"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.\",\r\n      \"required\": true\r\n    },\r\n    \"keywords\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"string\",\r\n          \"maxLength\": 255\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\",\r\n            \"maxLength\": 255\r\n          }\r\n        }\r\n      ],\r\n      \"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.\",\r\n      \"required\": true\r\n    },\r\n    \"homepage\": {\r\n      \"type\": \"string\",\r\n      \"maxLength\": 255,\r\n      \"description\": \"Home page of a library (if is different from repository url).\",\r\n      \"required\": false\r\n    },\r\n    \"repository\": {\r\n      \"type\": \"object\",\r\n      \"properties\": {\r\n        \"type\": {\r\n          \"enum\": [\r\n            \"git\",\r\n            \"hg\",\r\n            \"svn\"\r\n          ],\r\n          \"description\": \"only “git”, “hg” or “svn” are supported\"\r\n        },\r\n        \"url\": {\r\n          \"type\": \"string\"\r\n        },\r\n        \"branch\": {\r\n          \"type\": \"string\",\r\n          \"description\": \"if is not specified, default branch will be used. This field will be ignored if tag/release exists with the value of version.\"\r\n        }\r\n      },\r\n      \"description\": \"The repository in which the source code can be found.\",\r\n      \"required\": false\r\n    },\r\n    \"authors\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"object\",\r\n          \"properties\": {\r\n            \"name\": {\r\n              \"type\": \"string\",\r\n              \"required\": true,\r\n              \"description\": \"Full name\"\r\n            },\r\n            \"email\": {\r\n              \"type\": \"string\"\r\n            },\r\n            \"url\": {\r\n              \"type\": \"string\",\r\n              \"description\": \"An author’s contact page\"\r\n            },\r\n            \"maintainer\": {\r\n              \"type\": \"boolean\",\r\n              \"description\": \"Specify “maintainer” status\"\r\n            }\r\n          }\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"object\",\r\n            \"properties\": {\r\n              \"name\": {\r\n                \"type\": \"string\",\r\n                \"required\": true,\r\n                \"description\": \"Full name\"\r\n              },\r\n              \"email\": {\r\n                \"type\": \"string\"\r\n              },\r\n              \"url\": {\r\n                \"type\": \"string\",\r\n                \"description\": \"An author’s contact page\"\r\n              },\r\n              \"maintainer\": {\r\n                \"type\": \"boolean\",\r\n                \"description\": \"Specify “maintainer” status\"\r\n              }\r\n            }\r\n          }\r\n        }\r\n      ],\r\n      \"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.\",\r\n      \"required\": false\r\n    },\r\n    \"license\": {\r\n      \"type\": \"string\",\r\n      \"description\": \"A SPDX license ID or SPDX Expression. You can check the full list of SPDX license IDs (see “Identifier” column).\",\r\n      \"required\": false\r\n    },\r\n    \"frameworks\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"string\",\r\n          \"description\": \"espidf, freertos, *, etc'\"\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\",\r\n            \"description\": \"espidf, freertos, *, etc'\"\r\n          }\r\n        }\r\n      ],\r\n      \"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 *\",\r\n      \"required\": false\r\n    },\r\n    \"platforms\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"string\",\r\n          \"description\": \"atmelavr, espressif8266, *, etc'\"\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\",\r\n            \"description\": \"atmelavr, espressif8266, *, etc'\"\r\n          }\r\n        }\r\n      ],\r\n      \"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.\",\r\n      \"required\": false\r\n    },\r\n    \"headers\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"string\",\r\n          \"description\": \"MyLibrary.h\"\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\",\r\n            \"description\": \"FooCore.h, FooFeature.h\"\r\n          }\r\n        }\r\n      ],\r\n      \"description\": \"A list of header files that can be included in a project source files using #include <...> directive.\",\r\n      \"required\": false\r\n    },\r\n    \"examples\": {\r\n      \"type\": \"array\",\r\n      \"items\": {\r\n        \"type\": \"object\",\r\n        \"properties\": {\r\n          \"name\": {\r\n            \"type\": \"string\"\r\n          },\r\n          \"base\": {\r\n            \"type\": \"string\"\r\n          },\r\n          \"files\": {\r\n            \"type\": \"array\",\r\n            \"items\": {\r\n              \"type\": \"string\"\r\n            }\r\n          }\r\n        }\r\n      },\r\n      \"description\": \"A list of example patterns.\",\r\n      \"required\": \"false\"\r\n    },\r\n    \"dependencies\": {\r\n      \"anyOf\": [\r\n        {\r\n          \"type\": \"object\",\r\n          \"properties\": {\r\n            \"owner\": {\r\n              \"type\": \"string\",\r\n              \"description\": \"an owner name (username) from the PlatformIO Registry\"\r\n            },\r\n            \"name\": {\r\n              \"type\": \"string\",\r\n              \"description\": \"library name\"\r\n            },\r\n            \"version\": {\r\n              \"type\": \"string\",\r\n              \"description\": \"Version Requirements or Package Specifications\"\r\n            },\r\n            \"frameworks\": {\r\n              \"anyOf\": [\r\n                {\r\n                  \"type\": \"string\"\r\n                },\r\n                {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                }\r\n              ],\r\n              \"description\": \"project compatible Frameworks\"\r\n            },\r\n            \"platforms\": {\r\n              \"anyOf\": [\r\n                {\r\n                  \"type\": \"string\"\r\n                },\r\n                {\r\n                  \"type\": \"array\",\r\n                  \"items\": {\r\n                    \"type\": \"string\"\r\n                  }\r\n                }\r\n              ],\r\n              \"description\": \" project compatible Development Platforms\"\r\n            }\r\n          }\r\n        },\r\n        {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"object\",\r\n            \"properties\": {\r\n              \"owner\": {\r\n                \"type\": \"string\",\r\n                \"description\": \"an owner name (username) from the PlatformIO Registry\"\r\n              },\r\n              \"name\": {\r\n                \"type\": \"string\",\r\n                \"description\": \"library name\"\r\n              },\r\n              \"version\": {\r\n                \"type\": \"string\",\r\n                \"description\": \"Version Requirements or Package Specifications\"\r\n              },\r\n              \"frameworks\": {\r\n                \"anyOf\": [\r\n                  {\r\n                    \"type\": \"string\"\r\n                  },\r\n                  {\r\n                    \"type\": \"array\",\r\n                    \"items\": {\r\n                      \"type\": \"string\"\r\n                    }\r\n                  }\r\n                ],\r\n                \"description\": \"project compatible Frameworks\"\r\n              },\r\n              \"platforms\": {\r\n                \"anyOf\": [\r\n                  {\r\n                    \"type\": \"string\"\r\n                  },\r\n                  {\r\n                    \"type\": \"array\",\r\n                    \"items\": {\r\n                      \"type\": \"string\"\r\n                    }\r\n                  }\r\n                ],\r\n                \"description\": \" project compatible Development Platforms\"\r\n              }\r\n            }\r\n          }\r\n        }\r\n      ],\r\n      \"description\": \"A list of dependent libraries that will be automatically installed.\",\r\n      \"required\": false\r\n    },\r\n    \"export\": {\r\n      \"type\": \"object\",\r\n      \"properties\": {\r\n        \"include\": {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\"\r\n          },\r\n          \"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\"\r\n        },\r\n        \"exclude\": {\r\n          \"type\": \"array\",\r\n          \"items\": {\r\n            \"type\": \"string\"\r\n          },\r\n          \"description\": \"Exclude the directories and files which match with exclude patterns.\"\r\n        }\r\n      },\r\n      \"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.\",\r\n      \"required\": false\r\n    },\r\n    \"scripts\": {\r\n      \"type\": \"object\",\r\n      \"properties\": {\r\n        \"postinstall\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"type\": \"string\"\r\n            },\r\n            {\r\n              \"type\": \"array\",\r\n              \"items\": {\r\n                \"type\": \"string\"\r\n              }\r\n            }\r\n          ],\r\n          \"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\"\r\n        },\r\n        \"preuninstall\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"type\": \"string\"\r\n            },\r\n            {\r\n              \"type\": \"array\",\r\n              \"items\": {\r\n                \"type\": \"string\"\r\n              }\r\n            }\r\n          ],\r\n          \"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\"\r\n        }\r\n      },\r\n      \"description\": \"Execute custom scripts during the special Package Management CLI life cycle events\",\r\n      \"required\": false\r\n    },\r\n    \"build\": {\r\n      \"type\": \"object\",\r\n      \"properties\": {\r\n        \"flags\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"type\": \"string\"\r\n            },\r\n            {\r\n              \"type\": \"array\",\r\n              \"items\": {\r\n                \"type\": \"string\"\r\n              }\r\n            }\r\n          ],\r\n          \"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.\"\r\n        },\r\n        \"unflags\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"type\": \"string\"\r\n            },\r\n            {\r\n              \"type\": \"array\",\r\n              \"items\": {\r\n                \"type\": \"string\"\r\n              }\r\n            }\r\n          ],\r\n          \"description\": \"Remove base/initial flags which were set by development platform. More details build_unflags.\"\r\n        },\r\n        \"includeDir\": {\r\n          \"type\": \"string\",\r\n          \"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.\",\r\n          \"required\": false\r\n        },\r\n        \"srcDir\": {\r\n          \"type\": \"string\",\r\n          \"description\": \"Custom location of library source code. A default value is src and means that folder is located in the root of a library.\",\r\n          \"required\": \"false\"\r\n        },\r\n        \"srcFilter\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"type\": \"string\"\r\n            },\r\n            {\r\n              \"type\": \"array\",\r\n              \"items\": {\r\n                \"type\": \"string\"\r\n              }\r\n            }\r\n          ],\r\n          \"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\",\r\n          \"required\": false\r\n        },\r\n        \"extraScript\": {\r\n          \"type\": \"string\",\r\n          \"description\": \"Launch extra script before a build process.\",\r\n          \"required\": \"false\"\r\n        },\r\n        \"libArchive\": {\r\n          \"type\": \"boolean\",\r\n          \"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).\",\r\n          \"required\": \"false\"\r\n        },\r\n        \"libLDFMode\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"enum\": [\r\n                \"off\"\r\n              ],\r\n              \"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.\"\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"chain\"\r\n              ],\r\n              \"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.\"\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"deep\"\r\n              ],\r\n              \"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.\"\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"chain+\"\r\n              ],\r\n              \"description\": \"The same behavior as for the chain but evaluates C/C++ Preprocessor conditional syntax.\"\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"deep+\"\r\n              ],\r\n              \"description\": \"The same behavior as for the deep but evaluates C/C++ Preprocessor conditional syntax.\"\r\n            }\r\n          ],\r\n          \"description\": \"Specify Library Dependency Finder Mode. See Dependency Finder Mode for details.\",\r\n          \"required\": false\r\n        },\r\n        \"libCompatMode\": {\r\n          \"type\": \"string\",\r\n          \"description\": \"Specify Library Compatibility Mode. See Compatibility Mode for details.\",\r\n          \"required\": false\r\n        },\r\n        \"builder\": {\r\n          \"anyOf\": [\r\n            {\r\n              \"enum\": [\r\n                \"PlatformIOLibBuilder\"\r\n              ],\r\n              \"description\": \"Default Builder\"\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"ArduinoLibBuilder\"\r\n              ]\r\n            },\r\n            {\r\n              \"enum\": [\r\n                \"MbedLibBuilder\"\r\n              ]\r\n            }\r\n          ],\r\n          \"description\": \"Override default PlatformIOLibBuilder with another builder.\",\r\n          \"required\": false\r\n        }\r\n      },\r\n      \"required\": false\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "platformio/assets/system/99-platformio-udev.rules",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n#####################################################################################\n#\n# INSTALLATION\n#\n# Please visit > https://docs.platformio.org/en/latest/core/installation/udev-rules.html\n#\n#####################################################################################\n\n#\n# Boards\n#\n\n# CP210X USB UART\nATTRS{idVendor}==\"10c4\", ATTRS{idProduct}==\"ea[67][013]\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\nATTRS{idVendor}==\"10c4\", ATTRS{idProduct}==\"80a9\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# FT231XS USB UART\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"6015\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# FX2348N USB UART\nATTRS{idVendor}==\"0843\", ATTRS{idProduct}==\"5740\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Prolific Technology, Inc. PL2303 Serial Port\nATTRS{idVendor}==\"067b\", ATTRS{idProduct}==\"2303\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# QinHeng Electronics HL-340 USB-Serial adapter\nATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"7523\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n# QinHeng Electronics CH343 USB-Serial adapter\nATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d3\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n# QinHeng Electronics CH9102 USB-Serial adapter\nATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"55d4\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Arduino boards\nATTRS{idVendor}==\"2341\", ATTRS{idProduct}==\"[08][023]*\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\nATTRS{idVendor}==\"2a03\", ATTRS{idProduct}==\"[08][02]*\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Arduino SAM-BA\nATTRS{idVendor}==\"03eb\", ATTRS{idProduct}==\"6124\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{MTP_NO_PROBE}=\"1\"\n\n# Digistump boards\nATTRS{idVendor}==\"16d0\", ATTRS{idProduct}==\"0753\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Maple with DFU\nATTRS{idVendor}==\"1eaf\", ATTRS{idProduct}==\"000[34]\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# USBtiny\nATTRS{idProduct}==\"0c9f\", ATTRS{idVendor}==\"1781\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# USBasp V2.0\nATTRS{idVendor}==\"16c0\", ATTRS{idProduct}==\"05dc\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Teensy boards\nATTRS{idVendor}==\"16c0\", ATTRS{idProduct}==\"04[789B]?\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\nATTRS{idVendor}==\"16c0\", ATTRS{idProduct}==\"04[789A]?\", ENV{MTP_NO_PROBE}=\"1\"\nSUBSYSTEMS==\"usb\", ATTRS{idVendor}==\"16c0\", ATTRS{idProduct}==\"04[789ABCD]?\", MODE:=\"0666\"\nKERNEL==\"ttyACM*\", ATTRS{idVendor}==\"16c0\", ATTRS{idProduct}==\"04[789B]?\", MODE:=\"0666\"\n\n# TI Stellaris Launchpad\nATTRS{idVendor}==\"1cbe\", ATTRS{idProduct}==\"00fd\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# TI MSP430 Launchpad\nATTRS{idVendor}==\"0451\", ATTRS{idProduct}==\"f432\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# GD32V DFU Bootloader\nATTRS{idVendor}==\"28e9\", ATTRS{idProduct}==\"0189\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# FireBeetle-ESP32\nATTRS{idVendor}==\"1a86\", ATTRS{idProduct}==\"7522\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Wio Terminal\nATTRS{idVendor}==\"2886\", ATTRS{idProduct}==\"[08]02d\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Raspberry Pi Pico\nATTRS{idVendor}==\"2e8a\", ATTRS{idProduct}==\"[01]*\", MODE:=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# AIR32F103\nATTRS{idVendor}==\"0d28\", ATTRS{idProduct}==\"0204\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# STM32 virtual COM port\nATTRS{idVendor}==\"0483\", ATTRS{idProduct}==\"5740\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n#\n# Debuggers\n#\n\n# Black Magic Probe\nSUBSYSTEM==\"tty\", ATTRS{interface}==\"Black Magic GDB Server\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\nSUBSYSTEM==\"tty\", ATTRS{interface}==\"Black Magic UART Port\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# opendous and estick\nATTRS{idVendor}==\"03eb\", ATTRS{idProduct}==\"204f\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Original FT232/FT245/FT2232/FT232H/FT4232\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"60[01][104]\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# DISTORTEC JTAG-lock-pick Tiny 2\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"8220\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# TUMPA, TUMPA Lite\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"8a9[89]\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# XDS100v2\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"a6d0\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Xverve Signalyzer Tool (DT-USB-ST), Signalyzer LITE (DT-USB-SLITE)\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"bca[01]\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# TI/Luminary Stellaris Evaluation Board FTDI (several)\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"bcd[9a]\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# egnite Turtelizer 2\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"bdc8\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Section5 ICEbear\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"c14[01]\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Amontec JTAGkey and JTAGkey-tiny\nATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"cff8\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# TI ICDI\nATTRS{idVendor}==\"0451\", ATTRS{idProduct}==\"c32a\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# STLink probes\nATTRS{idVendor}==\"0483\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Hilscher NXHX Boards\nATTRS{idVendor}==\"0640\", ATTRS{idProduct}==\"0028\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Hitex probes\nATTRS{idVendor}==\"0640\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Altera USB Blaster\nATTRS{idVendor}==\"09fb\", ATTRS{idProduct}==\"6001\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Amontec JTAGkey-HiSpeed\nATTRS{idVendor}==\"0fbb\", ATTRS{idProduct}==\"1000\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# SEGGER J-Link\nATTRS{idVendor}==\"1366\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Raisonance RLink\nATTRS{idVendor}==\"138e\", ATTRS{idProduct}==\"9000\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Debug Board for Neo1973\nATTRS{idVendor}==\"1457\", ATTRS{idProduct}==\"5118\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Olimex probes\nATTRS{idVendor}==\"15ba\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# USBprog with OpenOCD firmware\nATTRS{idVendor}==\"1781\", ATTRS{idProduct}==\"0c63\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# TI/Luminary Stellaris In-Circuit Debug Interface (ICDI) Board\nATTRS{idVendor}==\"1cbe\", ATTRS{idProduct}==\"00fd\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Marvell Sheevaplug\nATTRS{idVendor}==\"9e88\", ATTRS{idProduct}==\"9e8f\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Keil Software, Inc. ULink\nATTRS{idVendor}==\"c251\", ATTRS{idProduct}==\"2710\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# CMSIS-DAP compatible adapters\nATTRS{product}==\"*CMSIS-DAP*\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Atmel AVR Dragon\nATTRS{idVendor}==\"03eb\", ATTRS{idProduct}==\"2107\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Espressif USB JTAG/serial debug unit\nATTRS{idVendor}==\"303a\", ATTRS{idProduct}==\"1001\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n\n# Zephyr framework USB CDC-ACM\nATTRS{idVendor}==\"2fe3\", ATTRS{idProduct}==\"0100\", MODE=\"0666\", ENV{ID_MM_DEVICE_IGNORE}=\"1\", ENV{ID_MM_PORT_IGNORE}=\"1\"\n"
  },
  {
    "path": "platformio/builder/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/builder/main.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport sys\nfrom time import time\n\nimport click\nfrom SCons.Script import ARGUMENTS  # pylint: disable=import-error\nfrom SCons.Script import COMMAND_LINE_TARGETS  # pylint: disable=import-error\nfrom SCons.Script import DEFAULT_TARGETS  # pylint: disable=import-error\nfrom SCons.Script import AllowSubstExceptions  # pylint: disable=import-error\nfrom SCons.Script import AlwaysBuild  # pylint: disable=import-error\nfrom SCons.Script import Default  # pylint: disable=import-error\nfrom SCons.Script import DefaultEnvironment  # pylint: disable=import-error\nfrom SCons.Script import Import  # pylint: disable=import-error\nfrom SCons.Script import Variables  # pylint: disable=import-error\n\nfrom platformio import app, fs\nfrom platformio.platform.base import PlatformBase\nfrom platformio.proc import get_pythonexe_path\nfrom platformio.project.helpers import get_project_dir\n\nAllowSubstExceptions(NameError)\n\n# append CLI arguments to build environment\nclivars = Variables(None)\nclivars.AddVariables(\n    (\"BUILD_SCRIPT\",),\n    (\"PROJECT_CONFIG\",),\n    (\"PIOENV\",),\n    (\"PIOTEST_RUNNING_NAME\",),\n    (\"UPLOAD_PORT\",),\n    (\"PROGRAM_ARGS\",),\n)\n\nDEFAULT_ENV_OPTIONS = dict(\n    tools=[\n        \"ar\",\n        \"cc\",\n        \"c++\",\n        \"link\",\n        \"piohooks\",\n        \"pioasm\",\n        \"piobuild\",\n        \"pioproject\",\n        \"pioplatform\",\n        \"piotest\",\n        \"piotarget\",\n        \"piolib\",\n        \"pioupload\",\n        \"piosize\",\n        \"pioino\",\n        \"piomisc\",\n        \"piointegration\",\n        \"piomaxlen\",\n    ],\n    toolpath=[os.path.join(fs.get_source_dir(), \"builder\", \"tools\")],\n    variables=clivars,\n    # Propagating External Environment\n    ENV=os.environ,\n    UNIX_TIME=int(time()),\n    BUILD_DIR=os.path.join(\"$PROJECT_BUILD_DIR\", \"$PIOENV\"),\n    BUILD_SRC_DIR=os.path.join(\"$BUILD_DIR\", \"src\"),\n    BUILD_TEST_DIR=os.path.join(\"$BUILD_DIR\", \"test\"),\n    COMPILATIONDB_PATH=os.path.join(\"$PROJECT_DIR\", \"compile_commands.json\"),\n    LIBPATH=[\"$BUILD_DIR\"],\n    PROGNAME=\"program\",\n    PROGPATH=os.path.join(\"$BUILD_DIR\", \"$PROGNAME$PROGSUFFIX\"),\n    PROG_PATH=\"$PROGPATH\",  # deprecated\n    PYTHONEXE=get_pythonexe_path(),\n)\n\n# Declare command verbose messages\ncommand_strings = dict(\n    ARCOM=\"Archiving\",\n    LINKCOM=\"Linking\",\n    RANLIBCOM=\"Indexing\",\n    ASCOM=\"Compiling\",\n    ASPPCOM=\"Compiling\",\n    CCCOM=\"Compiling\",\n    CXXCOM=\"Compiling\",\n)\nif not int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n    for name, value in command_strings.items():\n        DEFAULT_ENV_OPTIONS[\"%sSTR\" % name] = \"%s $TARGET\" % (value)\n\nenv = DefaultEnvironment(**DEFAULT_ENV_OPTIONS)\nenv.SConscriptChdir(False)\n\n# Load variables from CLI\nenv.Replace(\n    **{\n        key: PlatformBase.decode_scons_arg(env[key])\n        for key in list(clivars.keys())\n        if key in env\n    }\n)\n\n# Setup project optional directories\nconfig = env.GetProjectConfig()\napp.set_session_var(\"custom_project_conf\", config.path)\n\nenv.Replace(\n    PROJECT_DIR=get_project_dir(),\n    PROJECT_CORE_DIR=config.get(\"platformio\", \"core_dir\"),\n    PROJECT_PACKAGES_DIR=config.get(\"platformio\", \"packages_dir\"),\n    PROJECT_WORKSPACE_DIR=config.get(\"platformio\", \"workspace_dir\"),\n    PROJECT_LIBDEPS_DIR=config.get(\"platformio\", \"libdeps_dir\"),\n    PROJECT_INCLUDE_DIR=config.get(\"platformio\", \"include_dir\"),\n    PROJECT_SRC_DIR=config.get(\"platformio\", \"src_dir\"),\n    PROJECTSRC_DIR=\"$PROJECT_SRC_DIR\",  # legacy for dev/platform\n    PROJECT_TEST_DIR=config.get(\"platformio\", \"test_dir\"),\n    PROJECT_DATA_DIR=config.get(\"platformio\", \"data_dir\"),\n    PROJECTDATA_DIR=\"$PROJECT_DATA_DIR\",  # legacy for dev/platform\n    PROJECT_BUILD_DIR=config.get(\"platformio\", \"build_dir\"),\n    BUILD_TYPE=env.GetBuildType(),\n    BUILD_CACHE_DIR=config.get(\"platformio\", \"build_cache_dir\"),\n    LIBSOURCE_DIRS=[\n        config.get(\"platformio\", \"lib_dir\"),\n        os.path.join(\"$PROJECT_LIBDEPS_DIR\", \"$PIOENV\"),\n        config.get(\"platformio\", \"globallib_dir\"),\n    ],\n)\n\nif int(ARGUMENTS.get(\"ISATTY\", 0)):\n    # pylint: disable=protected-access\n    click._compat.isatty = lambda stream: True\n\nif env.subst(\"$BUILD_CACHE_DIR\"):\n    if not os.path.isdir(env.subst(\"$BUILD_CACHE_DIR\")):\n        os.makedirs(env.subst(\"$BUILD_CACHE_DIR\"))\n    env.CacheDir(\"$BUILD_CACHE_DIR\")\n\nif not int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n    click.echo(\"Verbose mode can be enabled via `-v, --verbose` option\")\n\nif not os.path.isdir(env.subst(\"$BUILD_DIR\")):\n    os.makedirs(env.subst(\"$BUILD_DIR\"))\n\n# Dynamically load dependent tools\nif \"compiledb\" in COMMAND_LINE_TARGETS:\n    env.Tool(\"compilation_db\")\n\nenv.LoadProjectOptions()\nenv.LoadPioPlatform()\n\nenv.SConsignFile(\n    os.path.join(\n        \"$BUILD_CACHE_DIR\" if env.subst(\"$BUILD_CACHE_DIR\") else \"$BUILD_DIR\",\n        \".sconsign%d%d\" % (sys.version_info[0], sys.version_info[1]),\n    )\n)\n\nenv.SConscript(env.GetExtraScripts(\"pre\"), exports=\"env\")\n\nif env.IsCleanTarget():\n    env.CleanProject(fullclean=int(ARGUMENTS.get(\"FULLCLEAN\", 0)))\n    env.Exit(0)\n\nenv.SConscript(\"$BUILD_SCRIPT\")\n\nif \"UPLOAD_FLAGS\" in env:\n    env.Prepend(UPLOADERFLAGS=[\"$UPLOAD_FLAGS\"])\nif env.GetProjectOption(\"upload_command\"):\n    env.Replace(UPLOADCMD=env.GetProjectOption(\"upload_command\"))\n\nenv.SConscript(env.GetExtraScripts(\"post\"), exports=\"env\")\n\n##############################################################################\n\n# Checking program size\nif env.get(\"SIZETOOL\") and not (\n    set([\"nobuild\", \"sizedata\"]) & set(COMMAND_LINE_TARGETS)\n):\n    env.Depends(\"upload\", \"checkprogsize\")\n    # Replace platform's \"size\" target with our\n    _new_targets = [t for t in DEFAULT_TARGETS if str(t) != \"size\"]\n    Default(None)\n    Default(_new_targets)\n    Default(\"checkprogsize\")\n\nif \"compiledb\" in COMMAND_LINE_TARGETS:\n    env.Alias(\"compiledb\", env.CompilationDatabase(\"$COMPILATIONDB_PATH\"))\n\n# Print configured protocols\nenv.AddPreAction(\n    \"upload\",\n    env.VerboseAction(\n        lambda source, target, env: env.PrintUploadInfo(),\n        \"Configuring upload protocol...\",\n    ),\n)\n\nAlwaysBuild(env.Alias(\"__debug\", DEFAULT_TARGETS))\nAlwaysBuild(env.Alias(\"__test\", DEFAULT_TARGETS))\n\nenv.ProcessDelayedActions()\n\n##############################################################################\n\nif \"envdump\" in COMMAND_LINE_TARGETS:\n    click.echo(env.Dump())\n    env.Exit(0)\n\nif env.IsIntegrationDump():\n    projenv = None\n    try:\n        Import(\"projenv\")\n    except:  # pylint: disable=bare-except\n        projenv = env\n    data = projenv.DumpIntegrationData(env)\n    # dump to file for the further reading by project.helpers.load_build_metadata\n    with open(\n        projenv.subst(os.path.join(\"$BUILD_DIR\", \"idedata.json\")),\n        mode=\"w\",\n        encoding=\"utf8\",\n    ) as fp:\n        json.dump(data, fp)\n    click.echo(\"\\n%s\\n\" % json.dumps(data))  # pylint: disable=undefined-variable\n    env.Exit(0)\n\nif \"sizedata\" in COMMAND_LINE_TARGETS:\n    AlwaysBuild(\n        env.Alias(\n            \"sizedata\",\n            DEFAULT_TARGETS,\n            env.VerboseAction(env.DumpSizeData, \"Generating memory usage report...\"),\n        )\n    )\n\n    Default(\"sizedata\")\n\n# issue #4604: process targets sequentially\nfor index, target in enumerate(\n    [t for t in COMMAND_LINE_TARGETS if not t.startswith(\"__\")][1:]\n):\n    env.Depends(target, COMMAND_LINE_TARGETS[index])\n"
  },
  {
    "path": "platformio/builder/tools/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/builder/tools/pioasm.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport SCons.Tool.asm  # pylint: disable=import-error\n\n#\n# Resolve https://github.com/platformio/platformio-core/issues/3917\n# Avoid forcing .S to bare assembly on Windows OS\n#\n\nif \".S\" in SCons.Tool.asm.ASSuffixes:\n    SCons.Tool.asm.ASSuffixes.remove(\".S\")\nif \".S\" not in SCons.Tool.asm.ASPPSuffixes:\n    SCons.Tool.asm.ASPPSuffixes.append(\".S\")\n\n\ngenerate = SCons.Tool.asm.generate\nexists = SCons.Tool.asm.exists\n"
  },
  {
    "path": "platformio/builder/tools/piobuild.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport fnmatch\nimport os\nimport sys\n\nfrom SCons import Builder, Util  # pylint: disable=import-error\nfrom SCons.Node import FS  # pylint: disable=import-error\nfrom SCons.Script import COMMAND_LINE_TARGETS  # pylint: disable=import-error\nfrom SCons.Script import AlwaysBuild  # pylint: disable=import-error\nfrom SCons.Script import DefaultEnvironment  # pylint: disable=import-error\nfrom SCons.Script import SConscript  # pylint: disable=import-error\n\nfrom platformio import __version__, fs\nfrom platformio.compat import IS_MACOS, string_types\nfrom platformio.package.version import pepver_to_semver\nfrom platformio.proc import where_is_program\n\nSRC_HEADER_EXT = [\"h\", \"hpp\"]\nSRC_ASM_EXT = [\"S\", \"spp\", \"SPP\", \"sx\", \"s\", \"asm\", \"ASM\"]\nSRC_C_EXT = [\"c\"]\nSRC_CXX_EXT = [\"cc\", \"cpp\", \"cxx\", \"c++\"]\nSRC_BUILD_EXT = SRC_C_EXT + SRC_CXX_EXT + SRC_ASM_EXT\nSRC_FILTER_DEFAULT = [\"+<*>\", \"-<.git%s>\" % os.sep, \"-<.svn%s>\" % os.sep]\n\n\ndef scons_patched_match_splitext(path, suffixes=None):\n    \"\"\"Patch SCons Builder, append $OBJSUFFIX to the end of each target\"\"\"\n    tokens = Util.splitext(path)\n    if suffixes and tokens[1] and tokens[1] in suffixes:\n        return (path, tokens[1])\n    return tokens\n\n\ndef GetBuildType(env):\n    modes = []\n    if (\n        set([\"__debug\", \"sizedata\"])  # sizedata = for memory inspection\n        & set(COMMAND_LINE_TARGETS)\n        or env.GetProjectOption(\"build_type\") == \"debug\"\n    ):\n        modes.append(\"debug\")\n    if \"__test\" in COMMAND_LINE_TARGETS or env.GetProjectOption(\"build_type\") == \"test\":\n        modes.append(\"test\")\n    return \", \".join(modes or [\"release\"])\n\n\ndef BuildProgram(env):\n    env.ProcessProgramDeps()\n    env.ProcessCompileDbToolchainOption()\n    env.ProcessProjectDeps()\n\n    # append into the beginning a main LD script\n    if env.get(\"LDSCRIPT_PATH\") and not any(\"-Wl,-T\" in f for f in env[\"LINKFLAGS\"]):\n        env.Prepend(LINKFLAGS=[\"-T\", env.subst(\"$LDSCRIPT_PATH\")])\n\n    # enable \"cyclic reference\" for linker\n    if (\n        env.get(\"LIBS\")\n        and env.GetCompilerType() == \"gcc\"\n        and (env.PioPlatform().is_embedded() or not IS_MACOS)\n    ):\n        env.Prepend(_LIBFLAGS=\"-Wl,--start-group \")\n        env.Append(_LIBFLAGS=\" -Wl,--end-group\")\n\n    program = env.Program(env.subst(\"$PROGPATH\"), env[\"PIOBUILDFILES\"])\n    env.Replace(PIOMAINPROG=program)\n\n    AlwaysBuild(\n        env.Alias(\n            \"checkprogsize\",\n            program,\n            env.VerboseAction(env.CheckUploadSize, \"Checking size $PIOMAINPROG\"),\n        )\n    )\n\n    print(\"Building in %s mode\" % env[\"BUILD_TYPE\"])\n\n    return program\n\n\ndef ProcessProgramDeps(env):\n    def _append_pio_macros():\n        core_version = pepver_to_semver(__version__)\n        env.AppendUnique(\n            CPPDEFINES=[\n                (\n                    \"PLATFORMIO\",\n                    int(\n                        \"{0:02d}{1:02d}{2:02d}\".format(\n                            core_version.major, core_version.minor, core_version.patch\n                        )\n                    ),\n                )\n            ]\n        )\n\n    _append_pio_macros()\n\n    env.PrintConfiguration()\n\n    # process extra flags from board\n    if \"BOARD\" in env and \"build.extra_flags\" in env.BoardConfig():\n        env.ProcessFlags(env.BoardConfig().get(\"build.extra_flags\"))\n\n    # apply user flags\n    env.ProcessFlags(env.get(\"BUILD_FLAGS\"))\n\n    # process framework scripts\n    env.BuildFrameworks(env.get(\"PIOFRAMEWORK\"))\n\n    if \"debug\" in env[\"BUILD_TYPE\"]:\n        env.ConfigureDebugTarget()\n\n    # remove specified flags\n    env.ProcessUnFlags(env.get(\"BUILD_UNFLAGS\"))\n\n\ndef ProcessCompileDbToolchainOption(env):\n    if \"compiledb\" not in COMMAND_LINE_TARGETS:\n        return\n\n    # Resolve absolute path of toolchain\n    for cmd in (\"CC\", \"CXX\", \"AS\"):\n        if cmd not in env:\n            continue\n        if os.path.isabs(env[cmd]) or '\"' in env[cmd]:\n            continue\n        env[cmd] = where_is_program(env.subst(\"$%s\" % cmd), env.subst(\"${ENV['PATH']}\"))\n        if \" \" in env[cmd]:  # issue #4998: Space in compilator path\n            env[cmd] = f'\"{env[cmd]}\"'\n\n    if env.get(\"COMPILATIONDB_INCLUDE_TOOLCHAIN\"):\n        print(\"Warning! `COMPILATIONDB_INCLUDE_TOOLCHAIN` is scoping\")\n        for scope, includes in env.DumpIntegrationIncludes().items():\n            if scope in (\"toolchain\",):\n                env.Append(CPPPATH=includes)\n\n\ndef ProcessProjectDeps(env):\n    plb = env.ConfigureProjectLibBuilder()\n\n    # prepend project libs to the beginning of list\n    env.Prepend(LIBS=plb.build())\n    # prepend extra linker related options from libs\n    env.PrependUnique(\n        **{\n            key: plb.env.get(key)\n            for key in (\"LIBS\", \"LIBPATH\", \"LINKFLAGS\")\n            if plb.env.get(key)\n        }\n    )\n\n    if \"test\" in env[\"BUILD_TYPE\"]:\n        build_files_before_nums = len(env.get(\"PIOBUILDFILES\", []))\n        plb.env.BuildSources(\n            \"$BUILD_TEST_DIR\", \"$PROJECT_TEST_DIR\", \"$PIOTEST_SRC_FILTER\"\n        )\n        if len(env.get(\"PIOBUILDFILES\", [])) - build_files_before_nums < 1:\n            sys.stderr.write(\n                \"Error: Nothing to build. Please put your test suites \"\n                \"to the '%s' folder\\n\" % env.subst(\"$PROJECT_TEST_DIR\")\n            )\n            env.Exit(1)\n\n    if \"test\" not in env[\"BUILD_TYPE\"] or env.GetProjectOption(\"test_build_src\"):\n        plb.env.BuildSources(\n            \"$BUILD_SRC_DIR\", \"$PROJECT_SRC_DIR\", env.get(\"SRC_FILTER\")\n        )\n\n    if not env.get(\"PIOBUILDFILES\") and not COMMAND_LINE_TARGETS:\n        sys.stderr.write(\n            \"Error: Nothing to build. Please put your source code files \"\n            \"to the '%s' folder\\n\" % env.subst(\"$PROJECT_SRC_DIR\")\n        )\n        env.Exit(1)\n\n\ndef ParseFlagsExtended(env, flags):  # pylint: disable=too-many-branches\n    if not isinstance(flags, list):\n        flags = [flags]\n    result = {}\n    for raw in flags:\n        for key, value in env.ParseFlags(str(raw)).items():\n            if key not in result:\n                result[key] = []\n            result[key].extend(value)\n\n    cppdefines = []\n    for item in result[\"CPPDEFINES\"]:\n        if not Util.is_Sequence(item):\n            cppdefines.append(item)\n            continue\n        name, value = item[:2]\n        if '\"' in value:\n            value = value.replace('\"', '\\\\\"')\n        elif value.isdigit():\n            value = int(value)\n        elif value.replace(\".\", \"\", 1).isdigit():\n            value = float(value)\n        cppdefines.append((name, value))\n    result[\"CPPDEFINES\"] = cppdefines\n\n    # fix relative CPPPATH & LIBPATH\n    for k in (\"CPPPATH\", \"LIBPATH\"):\n        for i, p in enumerate(result.get(k, [])):\n            p = env.subst(p)\n            if os.path.isdir(p):\n                result[k][i] = os.path.abspath(p)\n\n    # fix relative LIBs\n    for i, l in enumerate(result.get(\"LIBS\", [])):\n        if isinstance(l, FS.File):\n            result[\"LIBS\"][i] = os.path.abspath(l.get_path())\n\n    # fix relative path for \"-include\"\n    for i, f in enumerate(result.get(\"CCFLAGS\", [])):\n        if isinstance(f, tuple) and f[0] == \"-include\":\n            result[\"CCFLAGS\"][i] = (f[0], env.subst(f[1].get_path()))\n\n    return result\n\n\ndef ProcessFlags(env, flags):  # pylint: disable=too-many-branches\n    if not flags:\n        return\n    env.Append(**env.ParseFlagsExtended(flags))\n\n    # Cancel any previous definition of name, either built in or\n    # provided with a -U option // Issue #191\n    undefines = [\n        u\n        for u in env.get(\"CCFLAGS\", [])\n        if isinstance(u, string_types) and u.startswith(\"-U\")\n    ]\n    if undefines:\n        for undef in undefines:\n            env[\"CCFLAGS\"].remove(undef)\n            if undef[2:] in env[\"CPPDEFINES\"]:\n                env[\"CPPDEFINES\"].remove(undef[2:])\n        env.Append(_CPPDEFFLAGS=\" %s\" % \" \".join(undefines))\n\n\ndef ProcessUnFlags(env, flags):\n    if not flags:\n        return\n    parsed = env.ParseFlagsExtended(flags)\n    unflag_scopes = tuple(set([\"ASPPFLAGS\"] + list(parsed.keys())))\n    for scope in unflag_scopes:\n        for unflags in parsed.values():\n            for unflag in unflags:\n                for current in list(env.get(scope, [])):\n                    conditions = [\n                        unflag == current,\n                        not isinstance(unflag, (tuple, list))\n                        and isinstance(current, (tuple, list))\n                        and unflag == current[0],\n                    ]\n                    if any(conditions):\n                        env[scope].remove(current)\n\n\ndef StringifyMacro(env, value):  # pylint: disable=unused-argument\n    return '\\\\\"%s\\\\\"' % value.replace('\"', '\\\\\\\\\\\\\"')\n\n\ndef MatchSourceFiles(env, src_dir, src_filter=None, src_exts=None):\n    src_filter = env.subst(src_filter) if src_filter else None\n    src_filter = src_filter or SRC_FILTER_DEFAULT\n    src_exts = src_exts or (SRC_BUILD_EXT + SRC_HEADER_EXT)\n    return fs.match_src_files(env.subst(src_dir), src_filter, src_exts)\n\n\ndef CollectBuildFiles(\n    env, variant_dir, src_dir, src_filter=None, duplicate=False\n):  # pylint: disable=too-many-locals\n    sources = []\n    variants = []\n\n    src_dir = env.subst(src_dir)\n    if src_dir.endswith(os.sep):\n        src_dir = src_dir[:-1]\n\n    for item in env.MatchSourceFiles(src_dir, src_filter, SRC_BUILD_EXT):\n        _reldir = os.path.dirname(item)\n        _src_dir = os.path.join(src_dir, _reldir) if _reldir else src_dir\n        _var_dir = os.path.join(variant_dir, _reldir) if _reldir else variant_dir\n\n        if _var_dir not in variants:\n            variants.append(_var_dir)\n            env.VariantDir(_var_dir, _src_dir, duplicate)\n\n        sources.append(env.File(os.path.join(_var_dir, os.path.basename(item))))\n\n    middlewares = env.get(\"__PIO_BUILD_MIDDLEWARES\")\n    if not middlewares:\n        return sources\n\n    new_sources = []\n    for node in sources:\n        new_node = node\n        for callback, pattern in middlewares:\n            if pattern and not fnmatch.fnmatch(node.srcnode().get_path(), pattern):\n                continue\n            if callback.__code__.co_argcount == 2:\n                new_node = callback(env, new_node)\n            else:\n                new_node = callback(new_node)\n            if not new_node:\n                break\n        if new_node:\n            new_sources.append(new_node)\n\n    return new_sources\n\n\ndef AddBuildMiddleware(env, callback, pattern=None):\n    env.Append(__PIO_BUILD_MIDDLEWARES=[(callback, pattern)])\n\n\ndef BuildFrameworks(env, frameworks):\n    if not frameworks:\n        return\n\n    if \"BOARD\" not in env:\n        sys.stderr.write(\n            \"Please specify `board` in `platformio.ini` to use \"\n            \"with '%s' framework\\n\" % \", \".join(frameworks)\n        )\n        env.Exit(1)\n\n    supported_frameworks = env.BoardConfig().get(\"frameworks\", [])\n    for name in frameworks:\n        if name == \"arduino\":\n            # Arduino IDE appends .o to the end of filename\n            Builder.match_splitext = scons_patched_match_splitext\n            if \"nobuild\" not in COMMAND_LINE_TARGETS:\n                env.ConvertInoToCpp()\n\n        if name in supported_frameworks:\n            SConscript(env.GetFrameworkScript(name), exports=\"env\")\n        else:\n            sys.stderr.write(\"Error: This board doesn't support %s framework!\\n\" % name)\n            env.Exit(1)\n\n\ndef BuildLibrary(env, variant_dir, src_dir, src_filter=None, nodes=None):\n    env.ProcessUnFlags(env.get(\"BUILD_UNFLAGS\"))\n    nodes = nodes or env.CollectBuildFiles(variant_dir, src_dir, src_filter)\n    return env.StaticLibrary(env.subst(variant_dir), nodes)\n\n\ndef BuildSources(env, variant_dir, src_dir, src_filter=None):\n    if env.get(\"PIOMAINPROG\"):\n        sys.stderr.write(\n            \"Error: The main program is already constructed and the inline \"\n            \"source files are not allowed. Please use `env.BuildLibrary(...)` \"\n            \"or PRE-type script instead.\"\n        )\n        env.Exit(1)\n\n    nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter)\n    DefaultEnvironment().Append(\n        PIOBUILDFILES=[\n            env.Object(node) if isinstance(node, FS.File) else node for node in nodes\n        ]\n    )\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(GetBuildType)\n    env.AddMethod(BuildProgram)\n    env.AddMethod(ProcessProgramDeps)\n    env.AddMethod(ProcessCompileDbToolchainOption)\n    env.AddMethod(ProcessProjectDeps)\n    env.AddMethod(ParseFlagsExtended)\n    env.AddMethod(ProcessFlags)\n    env.AddMethod(ProcessUnFlags)\n    env.AddMethod(StringifyMacro)\n    env.AddMethod(MatchSourceFiles)\n    env.AddMethod(CollectBuildFiles)\n    env.AddMethod(AddBuildMiddleware)\n    env.AddMethod(BuildFrameworks)\n    env.AddMethod(BuildLibrary)\n    env.AddMethod(BuildSources)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piohooks.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\ndef AddActionWrapper(handler):\n    def wraps(env, files, action):\n        if not isinstance(files, (list, tuple, set)):\n            files = [files]\n        known_nodes = []\n        unknown_files = []\n        for item in files:\n            nodes = env.arg2nodes(item, env.fs.Entry)\n            if nodes and nodes[0].exists():\n                known_nodes.extend(nodes)\n            else:\n                unknown_files.append(item)\n        if unknown_files:\n            env.Append(**{\"_PIO_DELAYED_ACTIONS\": [(handler, unknown_files, action)]})\n        if known_nodes:\n            return handler(known_nodes, action)\n        return []\n\n    return wraps\n\n\ndef ProcessDelayedActions(env):\n    for func, nodes, action in env.get(\"_PIO_DELAYED_ACTIONS\", []):\n        func(nodes, action)\n\n\ndef generate(env):\n    env.Replace(**{\"_PIO_DELAYED_ACTIONS\": []})\n    env.AddMethod(AddActionWrapper(env.AddPreAction), \"AddPreAction\")\n    env.AddMethod(AddActionWrapper(env.AddPostAction), \"AddPostAction\")\n    env.AddMethod(ProcessDelayedActions)\n\n\ndef exists(_):\n    return True\n"
  },
  {
    "path": "platformio/builder/tools/pioino.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport atexit\nimport glob\nimport io\nimport os\nimport re\nimport tempfile\n\nimport click\n\nfrom platformio.compat import get_filesystem_encoding, get_locale_encoding\n\n\nclass InoToCPPConverter:\n    PROTOTYPE_RE = re.compile(\n        r\"\"\"^(\n        (?:template\\<.*\\>\\s*)?      # template\n        ([a-z_\\d\\&]+\\*?\\s+){1,2}    # return type\n        ([a-z_\\d]+\\s*)              # name of prototype\n        \\([a-z_,\\.\\*\\&\\[\\]\\s\\d]*\\)  # arguments\n        )\\s*(\\{|;)                  # must end with `{` or `;`\n        \"\"\",\n        re.X | re.M | re.I,\n    )\n    DETECTMAIN_RE = re.compile(r\"void\\s+(setup|loop)\\s*\\(\", re.M | re.I)\n    PROTOPTRS_TPLRE = r\"\\([^&\\(]*&(%s)[^\\)]*\\)\"\n\n    def __init__(self, env):\n        self.env = env\n        self._main_ino = None\n        self._safe_encoding = None\n\n    def read_safe_contents(self, path):\n        error_reported = False\n        for encoding in (\n            \"utf-8\",\n            None,\n            get_filesystem_encoding(),\n            get_locale_encoding(),\n            \"latin-1\",\n        ):\n            try:\n                with io.open(path, encoding=encoding) as fp:\n                    contents = fp.read()\n                    self._safe_encoding = encoding\n                    return contents\n            except UnicodeDecodeError:\n                if not error_reported:\n                    error_reported = True\n                    click.secho(\n                        \"Unicode decode error has occurred, please remove invalid \"\n                        \"(non-ASCII or non-UTF8) characters from %s file or convert it to UTF-8\"\n                        % path,\n                        fg=\"yellow\",\n                        err=True,\n                    )\n        return \"\"\n\n    def write_safe_contents(self, path, contents):\n        with io.open(\n            path, \"w\", encoding=self._safe_encoding, errors=\"backslashreplace\"\n        ) as fp:\n            return fp.write(contents)\n\n    def is_main_node(self, contents):\n        return self.DETECTMAIN_RE.search(contents)\n\n    def convert(self, nodes):\n        contents = self.merge(nodes)\n        if not contents:\n            return None\n        return self.process(contents)\n\n    def merge(self, nodes):\n        assert nodes\n        lines = []\n        for node in nodes:\n            contents = self.read_safe_contents(node.get_path())\n            _lines = ['# 1 \"%s\"' % node.get_path().replace(\"\\\\\", \"/\"), contents]\n            if self.is_main_node(contents):\n                lines = _lines + lines\n                self._main_ino = node.get_path()\n            else:\n                lines.extend(_lines)\n\n        if not self._main_ino:\n            self._main_ino = nodes[0].get_path()\n\n        return \"\\n\".join([\"#include <Arduino.h>\"] + lines) if lines else None\n\n    def process(self, contents):\n        out_file = re.sub(r\"[\\\"\\'\\;]+\", \"\", self._main_ino) + \".cpp\"\n        assert self._gcc_preprocess(contents, out_file)\n        contents = self.read_safe_contents(out_file)\n        contents = self._join_multiline_strings(contents)\n        self.write_safe_contents(out_file, self.append_prototypes(contents))\n        return out_file\n\n    def _gcc_preprocess(self, contents, out_file):\n        tmp_path = tempfile.mkstemp()[1]\n        self.write_safe_contents(tmp_path, contents)\n        self.env.Execute(\n            self.env.VerboseAction(\n                '$CXX -o \"{0}\" -x c++ -fpreprocessed -dD -E \"{1}\"'.format(\n                    out_file, tmp_path\n                ),\n                \"Converting \" + os.path.basename(out_file[:-4]),\n            )\n        )\n        atexit.register(_delete_file, tmp_path)\n        return os.path.isfile(out_file)\n\n    def _join_multiline_strings(self, contents):\n        if \"\\\\\\n\" not in contents:\n            return contents\n        newlines = []\n        linenum = 0\n        stropen = False\n        for line in contents.split(\"\\n\"):\n            _linenum = self._parse_preproc_line_num(line)\n            if _linenum is not None:\n                linenum = _linenum\n            else:\n                linenum += 1\n\n            if line.endswith(\"\\\\\"):\n                if line.startswith('\"'):\n                    stropen = True\n                    newlines.append(line[:-1])\n                    continue\n                if stropen:\n                    newlines[len(newlines) - 1] += line[:-1]\n                    continue\n            elif stropen and line.endswith(('\",', '\";')):\n                newlines[len(newlines) - 1] += line\n                stropen = False\n                newlines.append(\n                    '#line %d \"%s\"' % (linenum, self._main_ino.replace(\"\\\\\", \"/\"))\n                )\n                continue\n\n            newlines.append(line)\n\n        return \"\\n\".join(newlines)\n\n    @staticmethod\n    def _parse_preproc_line_num(line):\n        if not line.startswith(\"#\"):\n            return None\n        tokens = line.split(\" \", 3)\n        if len(tokens) > 2 and tokens[1].isdigit():\n            return int(tokens[1])\n        return None\n\n    def _parse_prototypes(self, contents):\n        prototypes = []\n        reserved_keywords = set([\"if\", \"else\", \"while\"])\n        for match in self.PROTOTYPE_RE.finditer(contents):\n            if (\n                set([match.group(2).strip(), match.group(3).strip()])\n                & reserved_keywords\n            ):\n                continue\n            prototypes.append(match)\n        return prototypes\n\n    def _get_total_lines(self, contents):\n        total = 0\n        if contents.endswith(\"\\n\"):\n            contents = contents[:-1]\n        for line in contents.split(\"\\n\")[::-1]:\n            linenum = self._parse_preproc_line_num(line)\n            if linenum is not None:\n                return total + linenum\n            total += 1\n        return total\n\n    def append_prototypes(self, contents):\n        prototypes = self._parse_prototypes(contents) or []\n\n        # skip already declared prototypes\n        declared = set(m.group(1).strip() for m in prototypes if m.group(4) == \";\")\n        prototypes = [m for m in prototypes if m.group(1).strip() not in declared]\n\n        if not prototypes:\n            return contents\n\n        prototype_names = set(m.group(3).strip() for m in prototypes)\n        split_pos = prototypes[0].start()\n        match_ptrs = re.search(\n            self.PROTOPTRS_TPLRE % (\"|\".join(prototype_names)),\n            contents[:split_pos],\n            re.M,\n        )\n        if match_ptrs:\n            split_pos = contents.rfind(\"\\n\", 0, match_ptrs.start()) + 1\n\n        result = []\n        result.append(contents[:split_pos].strip())\n        result.append(\"%s;\" % \";\\n\".join([m.group(1) for m in prototypes]))\n        result.append(\n            '#line %d \"%s\"'\n            % (\n                self._get_total_lines(contents[:split_pos]),\n                self._main_ino.replace(\"\\\\\", \"/\"),\n            )\n        )\n        result.append(contents[split_pos:].strip())\n        return \"\\n\".join(result)\n\n\ndef FindInoNodes(env):\n    src_dir = glob.escape(env.subst(\"$PROJECT_SRC_DIR\"))\n    return env.Glob(os.path.join(src_dir, \"*.ino\")) + env.Glob(\n        os.path.join(src_dir, \"*.pde\")\n    )\n\n\ndef ConvertInoToCpp(env):\n    ino_nodes = env.FindInoNodes()\n    if not ino_nodes:\n        return\n    c = InoToCPPConverter(env)\n    out_file = c.convert(ino_nodes)\n\n    atexit.register(_delete_file, out_file)\n\n\ndef _delete_file(path):\n    try:\n        if os.path.isfile(path):\n            os.remove(path)\n    except:  # pylint: disable=bare-except\n        pass\n\n\ndef generate(env):\n    env.AddMethod(FindInoNodes)\n    env.AddMethod(ConvertInoToCpp)\n\n\ndef exists(_):\n    return True\n"
  },
  {
    "path": "platformio/builder/tools/piointegration.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport glob\nimport os\n\nimport SCons.Defaults  # pylint: disable=import-error\nimport SCons.Subst  # pylint: disable=import-error\nfrom SCons.Script import COMMAND_LINE_TARGETS  # pylint: disable=import-error\n\nfrom platformio.proc import exec_command, where_is_program\n\n\ndef IsIntegrationDump(_):\n    return set([\"__idedata\", \"idedata\"]) & set(COMMAND_LINE_TARGETS)\n\n\ndef DumpIntegrationIncludes(env):\n    result = dict(build=[], compatlib=[], toolchain=[])\n\n    # `env`(project) CPPPATH\n    result[\"build\"].extend(\n        [os.path.abspath(env.subst(item)) for item in env.get(\"CPPPATH\", [])]\n    )\n\n    # installed libs\n    for lb in env.GetLibBuilders():\n        result[\"compatlib\"].extend(\n            [os.path.abspath(inc) for inc in lb.get_include_dirs()]\n        )\n\n    # includes from toolchains\n    p = env.PioPlatform()\n    for pkg in p.get_installed_packages(with_optional=False):\n        if p.get_package_type(pkg.metadata.name) != \"toolchain\":\n            continue\n        toolchain_dir = glob.escape(pkg.path)\n        toolchain_incglobs = [\n            os.path.join(toolchain_dir, \"*\", \"include\", \"c++\", \"*\"),\n            os.path.join(toolchain_dir, \"*\", \"include\", \"c++\", \"*\", \"*-*-*\"),\n            os.path.join(toolchain_dir, \"lib\", \"gcc\", \"*\", \"*\", \"include*\"),\n            os.path.join(toolchain_dir, \"*\", \"include*\"),\n        ]\n        for g in toolchain_incglobs:\n            result[\"toolchain\"].extend([os.path.abspath(inc) for inc in glob.glob(g)])\n\n    return result\n\n\ndef get_gcc_defines(env):\n    items = []\n    try:\n        sysenv = os.environ.copy()\n        sysenv[\"PATH\"] = str(env[\"ENV\"][\"PATH\"])\n        result = exec_command(\n            \"echo | %s -dM -E -\" % env.subst(\"$CC\"), env=sysenv, shell=True\n        )\n    except OSError:\n        return items\n    if result[\"returncode\"] != 0:\n        return items\n    for line in result[\"out\"].split(\"\\n\"):\n        tokens = line.strip().split(\" \", 2)\n        if not tokens or tokens[0] != \"#define\":\n            continue\n        if len(tokens) > 2:\n            items.append(\"%s=%s\" % (tokens[1], tokens[2]))\n        else:\n            items.append(tokens[1])\n    return items\n\n\ndef dump_defines(env):\n    defines = []\n    # global symbols\n    for item in SCons.Defaults.processDefines(env.get(\"CPPDEFINES\", [])):\n        item = item.strip()\n        if item:\n            defines.append(env.subst(item).replace('\\\\\"', '\"'))\n\n    # special symbol for Atmel AVR MCU\n    if env[\"PIOPLATFORM\"] == \"atmelavr\":\n        board_mcu = env.get(\"BOARD_MCU\")\n        if not board_mcu and \"BOARD\" in env:\n            board_mcu = env.BoardConfig().get(\"build.mcu\")\n        if board_mcu:\n            defines.append(\n                str(\n                    \"__AVR_%s__\"\n                    % board_mcu.upper()\n                    .replace(\"ATMEGA\", \"ATmega\")\n                    .replace(\"ATTINY\", \"ATtiny\")\n                )\n            )\n\n    # built-in GCC marcos\n    # if env.GetCompilerType() == \"gcc\":\n    #     defines.extend(get_gcc_defines(env))\n\n    return defines\n\n\ndef dump_svd_path(env):\n    svd_path = env.GetProjectOption(\"debug_svd_path\")\n    if svd_path:\n        return os.path.abspath(svd_path)\n\n    if \"BOARD\" not in env:\n        return None\n    try:\n        svd_path = env.BoardConfig().get(\"debug.svd_path\")\n        assert svd_path\n    except (AssertionError, KeyError):\n        return None\n    # custom path to SVD file\n    if os.path.isfile(svd_path):\n        return svd_path\n    # default file from ./platform/misc/svd folder\n    p = env.PioPlatform()\n    if os.path.isfile(os.path.join(p.get_dir(), \"misc\", \"svd\", svd_path)):\n        return os.path.abspath(os.path.join(p.get_dir(), \"misc\", \"svd\", svd_path))\n    return None\n\n\ndef _split_flags_string(env, s):\n    args = env.subst_list(s, SCons.Subst.SUBST_CMD)[0]\n    return [str(arg) for arg in args]\n\n\ndef DumpIntegrationData(*args):\n    projenv, globalenv = args[0:2]  # pylint: disable=unbalanced-tuple-unpacking\n    data = {\n        \"build_type\": globalenv.GetBuildType(),\n        \"env_name\": globalenv[\"PIOENV\"],\n        \"libsource_dirs\": [\n            globalenv.subst(item) for item in globalenv.GetLibSourceDirs()\n        ],\n        \"defines\": dump_defines(projenv),\n        \"includes\": projenv.DumpIntegrationIncludes(),\n        \"cc_flags\": _split_flags_string(projenv, \"$CFLAGS $CCFLAGS $CPPFLAGS\"),\n        \"cxx_flags\": _split_flags_string(projenv, \"$CXXFLAGS $CCFLAGS $CPPFLAGS\"),\n        \"cc_path\": where_is_program(\n            globalenv.subst(\"$CC\"), globalenv.subst(\"${ENV['PATH']}\")\n        ),\n        \"cxx_path\": where_is_program(\n            globalenv.subst(\"$CXX\"), globalenv.subst(\"${ENV['PATH']}\")\n        ),\n        \"gdb_path\": where_is_program(\n            globalenv.subst(\"$GDB\"), globalenv.subst(\"${ENV['PATH']}\")\n        ),\n        \"prog_path\": globalenv.subst(\"$PROGPATH\"),\n        \"svd_path\": dump_svd_path(globalenv),\n        \"compiler_type\": globalenv.GetCompilerType(),\n        \"targets\": globalenv.DumpTargets(),\n        \"extra\": dict(\n            flash_images=[\n                {\"offset\": item[0], \"path\": globalenv.subst(item[1])}\n                for item in globalenv.get(\"FLASH_EXTRA_IMAGES\", [])\n            ]\n        ),\n    }\n    for key in (\"IDE_EXTRA_DATA\", \"INTEGRATION_EXTRA_DATA\"):\n        data[\"extra\"].update(globalenv.get(key, {}))\n    return data\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env[\"IDE_EXTRA_DATA\"] = {}  # legacy support\n    env[\"INTEGRATION_EXTRA_DATA\"] = {}\n    env.AddMethod(IsIntegrationDump)\n    env.AddMethod(DumpIntegrationIncludes)\n    env.AddMethod(DumpIntegrationData)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piolib.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-instance-attributes, too-many-public-methods\n# pylint: disable=assignment-from-no-return, unused-argument, too-many-lines\n\nimport hashlib\nimport io\nimport os\nimport re\nimport sys\n\nimport click\nimport SCons.Scanner  # pylint: disable=import-error\nfrom SCons.Script import ARGUMENTS  # pylint: disable=import-error\nfrom SCons.Script import DefaultEnvironment  # pylint: disable=import-error\n\nfrom platformio import exception, fs\nfrom platformio.builder.tools import piobuild\nfrom platformio.compat import IS_WINDOWS, hashlib_encode_data, string_types\nfrom platformio.http import HTTPClientError, InternetConnectionError\nfrom platformio.package.exception import (\n    MissingPackageManifestError,\n    UnknownPackageError,\n)\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manifest.parser import (\n    ManifestParserError,\n    ManifestParserFactory,\n)\nfrom platformio.package.meta import PackageCompatibility, PackageItem, PackageSpec\nfrom platformio.project.options import ProjectOptions\n\n\nclass LibBuilderFactory:\n    @staticmethod\n    def new(env, path, verbose=int(ARGUMENTS.get(\"PIOVERBOSE\", 0))):\n        clsname = \"UnknownLibBuilder\"\n        if os.path.isfile(os.path.join(path, \"library.json\")):\n            clsname = \"PlatformIOLibBuilder\"\n        else:\n            used_frameworks = LibBuilderFactory.get_used_frameworks(env, path)\n            common_frameworks = set(env.get(\"PIOFRAMEWORK\", [])) & set(used_frameworks)\n            if common_frameworks:\n                clsname = \"%sLibBuilder\" % list(common_frameworks)[0].capitalize()\n            elif used_frameworks:\n                clsname = \"%sLibBuilder\" % used_frameworks[0].capitalize()\n\n        obj = globals()[clsname](env, path, verbose=verbose)\n\n        # Handle PlatformIOLibBuilder.manifest.build.builder\n        # pylint: disable=protected-access\n        if isinstance(obj, PlatformIOLibBuilder) and obj._manifest.get(\"build\", {}).get(\n            \"builder\"\n        ):\n            obj = globals()[obj._manifest.get(\"build\", {}).get(\"builder\")](\n                env, path, verbose=verbose\n            )\n\n        assert isinstance(obj, LibBuilderBase)\n        return obj\n\n    @staticmethod\n    def get_used_frameworks(env, path):\n        if any(\n            os.path.isfile(os.path.join(path, fname))\n            for fname in (\"library.properties\", \"keywords.txt\")\n        ):\n            return [\"arduino\"]\n\n        if os.path.isfile(os.path.join(path, \"module.json\")):\n            return [\"mbed\"]\n\n        include_re = re.compile(\n            r'^#include\\s+(<|\")(Arduino|mbed)\\.h(<|\")', flags=re.MULTILINE\n        )\n\n        # check source files\n        for root, _, files in os.walk(path, followlinks=True):\n            if \"mbed_lib.json\" in files:\n                return [\"mbed\"]\n            for fname in files:\n                if not fs.path_endswith_ext(\n                    fname, piobuild.SRC_BUILD_EXT + piobuild.SRC_HEADER_EXT\n                ):\n                    continue\n                with io.open(\n                    os.path.join(root, fname), encoding=\"utf8\", errors=\"ignore\"\n                ) as fp:\n                    content = fp.read()\n                if not content:\n                    continue\n                if \"Arduino.h\" in content and include_re.search(content):\n                    return [\"arduino\"]\n                if \"mbed.h\" in content and include_re.search(content):\n                    return [\"mbed\"]\n        return []\n\n\nclass LibBuilderBase:\n    CLASSIC_SCANNER = SCons.Scanner.C.CScanner()\n    CCONDITIONAL_SCANNER = SCons.Scanner.C.CConditionalScanner()\n    # Max depth of nested includes:\n    # -1 = unlimited\n    # 0 - disabled nesting\n    # >0 - number of allowed nested includes\n    CCONDITIONAL_SCANNER_DEPTH = 99\n    PARSE_SRC_BY_H_NAME = True\n\n    _INCLUDE_DIRS_CACHE = None\n\n    def __init__(self, env, path, manifest=None, verbose=False):\n        self.env = env.Clone()\n        self.envorigin = env.Clone()\n        self.path = os.path.abspath(env.subst(path))\n        self.verbose = verbose\n\n        try:\n            self._manifest = manifest if manifest else self.load_manifest()\n        except ManifestParserError:\n            click.secho(\n                \"Warning! Ignoring broken library manifest in \" + self.path, fg=\"yellow\"\n            )\n            self._manifest = {}\n\n        self.is_dependent = False\n        self.is_built = False\n        self.depbuilders = []\n\n        self._deps_are_processed = False\n        self._circular_deps = []\n        self._processed_search_files = []\n\n        # pass a macro to the projenv + libs\n        if \"test\" in env[\"BUILD_TYPE\"]:\n            self.env.Append(CPPDEFINES=[\"PIO_UNIT_TESTING\"])\n\n        # reset source filter, could be overridden with extra script\n        self.env[\"SRC_FILTER\"] = \"\"\n\n        # process extra options and append to build environment\n        self.process_extra_options()\n\n    def __repr__(self):\n        return \"%s(%r)\" % (self.__class__, self.path)\n\n    def __contains__(self, child_path):\n        return self.is_common_builder(self.path, child_path)\n\n    def is_common_builder(self, root_path, child_path):\n        if IS_WINDOWS:\n            root_path = root_path.lower()\n            child_path = child_path.lower()\n        if root_path == child_path:\n            return True\n        if (\n            os.path.commonprefix([root_path + os.path.sep, child_path])\n            == root_path + os.path.sep\n        ):\n            return True\n        # try to resolve paths\n        root_path = os.path.realpath(root_path)\n        child_path = os.path.realpath(child_path)\n        return (\n            os.path.commonprefix([root_path + os.path.sep, child_path])\n            == root_path + os.path.sep\n        )\n\n    @property\n    def name(self):\n        return self._manifest.get(\"name\", os.path.basename(self.path))\n\n    @property\n    def version(self):\n        return self._manifest.get(\"version\")\n\n    @property\n    def dependent(self):\n        \"\"\"Backward compatibility with ESP-IDF\"\"\"\n        return self.is_dependent\n\n    @property\n    def dependencies(self):\n        return self._manifest.get(\"dependencies\")\n\n    @property\n    def src_filter(self):\n        return piobuild.SRC_FILTER_DEFAULT + [\n            \"-<example%s>\" % os.sep,\n            \"-<examples%s>\" % os.sep,\n            \"-<test%s>\" % os.sep,\n            \"-<tests%s>\" % os.sep,\n        ]\n\n    @property\n    def include_dir(self):\n        for name in (\"include\", \"Include\"):\n            d = os.path.join(self.path, name)\n            if os.path.isdir(d):\n                return d\n        return None\n\n    @property\n    def src_dir(self):\n        for name in (\"src\", \"Src\"):\n            d = os.path.join(self.path, name)\n            if os.path.isdir(d):\n                return d\n        return self.path\n\n    def get_include_dirs(self):\n        items = []\n        include_dir = self.include_dir\n        if include_dir:\n            items.append(include_dir)\n        items.append(self.src_dir)\n        return items\n\n    @property\n    def build_dir(self):\n        lib_hash = hashlib.sha1(hashlib_encode_data(self.path)).hexdigest()[:3]\n        return os.path.join(\n            \"$BUILD_DIR\", \"lib%s\" % lib_hash, os.path.basename(self.path)\n        )\n\n    @property\n    def build_flags(self):\n        return None\n\n    @property\n    def build_unflags(self):\n        return None\n\n    @property\n    def extra_script(self):\n        return None\n\n    @property\n    def lib_archive(self):\n        return self.env.GetProjectOption(\"lib_archive\")\n\n    @property\n    def lib_ldf_mode(self):\n        return self.env.GetProjectOption(\"lib_ldf_mode\")\n\n    @staticmethod\n    def validate_ldf_mode(mode):\n        ldf_modes = ProjectOptions[\"env.lib_ldf_mode\"].type.choices\n        if isinstance(mode, string_types):\n            mode = mode.strip().lower()\n        if mode in ldf_modes:\n            return mode\n        try:\n            return ldf_modes[int(mode)]\n        except (IndexError, ValueError):\n            pass\n        return ProjectOptions[\"env.lib_ldf_mode\"].default\n\n    @property\n    def lib_compat_mode(self):\n        return self.env.GetProjectOption(\"lib_compat_mode\")\n\n    @staticmethod\n    def validate_compat_mode(mode):\n        compat_modes = ProjectOptions[\"env.lib_compat_mode\"].type.choices\n        if isinstance(mode, string_types):\n            mode = mode.strip().lower()\n        if mode in compat_modes:\n            return mode\n        try:\n            return compat_modes[int(mode)]\n        except (IndexError, ValueError):\n            pass\n        return ProjectOptions[\"env.lib_compat_mode\"].default\n\n    def is_platforms_compatible(self, platforms):\n        return True\n\n    def is_frameworks_compatible(self, frameworks):\n        return True\n\n    def load_manifest(self):\n        return {}\n\n    def process_extra_options(self):\n        with fs.cd(self.path):\n            self.env.ProcessFlags(self.build_flags)\n            if self.extra_script:\n                self.env.SConscriptChdir(True)\n                self.env.SConscript(\n                    os.path.abspath(self.extra_script),\n                    exports={\"env\": self.env, \"pio_lib_builder\": self},\n                )\n                self.env.SConscriptChdir(False)\n            self.env.ProcessUnFlags(self.build_unflags)\n\n    def process_dependencies(self):\n        if not self.dependencies or self._deps_are_processed:\n            return\n        self._deps_are_processed = True\n        for dependency in self.dependencies:\n            found = False\n            for lb in self.env.GetLibBuilders():\n                if not lb.is_dependency_compatible(dependency):\n                    continue\n                found = True\n                if lb not in self.depbuilders:\n                    self.depend_on(lb)\n                break\n\n            if not found and self.verbose:\n                sys.stderr.write(\n                    \"Warning: Ignored `%s` dependency for `%s` \"\n                    \"library\\n\" % (dependency[\"name\"], self.name)\n                )\n\n    def is_dependency_compatible(self, dependency):\n        pkg = PackageItem(self.path)\n        qualifiers = {\"name\": self.name, \"version\": self.version}\n        if pkg.metadata:\n            qualifiers = {\"name\": pkg.metadata.name, \"version\": pkg.metadata.version}\n            if pkg.metadata.spec and pkg.metadata.spec.owner:\n                qualifiers[\"owner\"] = pkg.metadata.spec.owner\n        dep_qualifiers = {\n            k: v for k, v in dependency.items() if k in (\"owner\", \"name\", \"version\")\n        }\n        if (\n            \"version\" in dep_qualifiers\n            and not PackageSpec(dep_qualifiers[\"version\"]).requirements\n        ):\n            del dep_qualifiers[\"version\"]\n        return PackageCompatibility.from_dependency(dep_qualifiers).is_compatible(\n            PackageCompatibility(**qualifiers)\n        )\n\n    def get_search_files(self):\n        return [\n            os.path.join(self.src_dir, item)\n            for item in self.env.MatchSourceFiles(\n                self.src_dir, self.src_filter, piobuild.SRC_BUILD_EXT\n            )\n        ]\n\n    def get_implicit_includes(  # pylint: disable=too-many-branches\n        self, search_files=None\n    ):\n        # all include directories\n        if not LibBuilderBase._INCLUDE_DIRS_CACHE:\n            LibBuilderBase._INCLUDE_DIRS_CACHE = [\n                self.env.Dir(d)\n                for d in ProjectAsLibBuilder(\n                    self.envorigin, \"$PROJECT_DIR\", export_projenv=False\n                ).get_include_dirs()\n            ]\n            for lb in self.env.GetLibBuilders():\n                LibBuilderBase._INCLUDE_DIRS_CACHE.extend(\n                    [self.env.Dir(d) for d in lb.get_include_dirs()]\n                )\n\n        # append self include directories\n        include_dirs = [self.env.Dir(d) for d in self.get_include_dirs()]\n        include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE)\n\n        result = []\n        search_files = search_files or []\n        while search_files:\n            node = self.env.File(search_files.pop(0))\n            if node.get_abspath() in self._processed_search_files:\n                continue\n            self._processed_search_files.append(node.get_abspath())\n\n            try:\n                assert \"+\" in self.lib_ldf_mode\n                candidates = LibBuilderBase.CCONDITIONAL_SCANNER(\n                    node,\n                    self.env,\n                    tuple(include_dirs),\n                    depth=self.CCONDITIONAL_SCANNER_DEPTH,\n                )\n\n            except Exception as exc:  # pylint: disable=broad-except\n                if self.verbose and \"+\" in self.lib_ldf_mode:\n                    sys.stderr.write(\n                        \"Warning! Classic Pre Processor is used for `%s`, \"\n                        \"advanced has failed with `%s`\\n\" % (node.get_abspath(), exc)\n                    )\n                candidates = LibBuilderBase.CLASSIC_SCANNER(\n                    node, self.env, tuple(include_dirs)\n                )\n\n            # print(node.get_abspath(), [c.get_abspath() for c in candidates])\n            for item in candidates:\n                item_path = item.get_abspath()\n                # process internal files recursively\n                if (\n                    item_path not in self._processed_search_files\n                    and item_path not in search_files\n                    and item_path in self\n                ):\n                    search_files.append(item_path)\n                if item not in result:\n                    result.append(item)\n                if not self.PARSE_SRC_BY_H_NAME:\n                    continue\n                if not fs.path_endswith_ext(item_path, piobuild.SRC_HEADER_EXT):\n                    continue\n                item_fname = item_path[: item_path.rindex(\".\")]\n                for ext in piobuild.SRC_C_EXT + piobuild.SRC_CXX_EXT:\n                    if not os.path.isfile(\"%s.%s\" % (item_fname, ext)):\n                        continue\n                    item_c_node = self.env.File(\"%s.%s\" % (item_fname, ext))\n                    if item_c_node not in result:\n                        result.append(item_c_node)\n\n        return result\n\n    def search_deps_recursive(self, search_files=None):\n        self.process_dependencies()\n\n        # when LDF is disabled\n        if self.lib_ldf_mode == \"off\":\n            return\n\n        if self.lib_ldf_mode.startswith(\"deep\"):\n            search_files = self.get_search_files()\n\n        lib_inc_map = {}\n        for inc in self.get_implicit_includes(search_files):\n            inc_path = inc.get_abspath()\n            for lb in self.env.GetLibBuilders():\n                if inc_path in lb:\n                    if lb not in lib_inc_map:\n                        lib_inc_map[lb] = []\n                    lib_inc_map[lb].append(inc_path)\n                    break\n\n        for lb, lb_search_files in lib_inc_map.items():\n            self.depend_on(lb, search_files=lb_search_files)\n\n    def depend_on(self, lb, search_files=None, recursive=True):\n        def _already_depends(_lb):\n            if self in _lb.depbuilders:\n                return True\n            for __lb in _lb.depbuilders:\n                if _already_depends(__lb):\n                    return True\n            return False\n\n        # assert isinstance(lb, LibBuilderBase)\n        if self != lb:\n            if _already_depends(lb):\n                if self.verbose:\n                    sys.stderr.write(\n                        \"Warning! Circular dependencies detected \"\n                        \"between `%s` and `%s`\\n\" % (self.path, lb.path)\n                    )\n                self._circular_deps.append(lb)\n            elif lb not in self.depbuilders:\n                self.depbuilders.append(lb)\n                lb.is_dependent = True\n                LibBuilderBase._INCLUDE_DIRS_CACHE = None\n\n        if recursive:\n            lb.search_deps_recursive(search_files)\n\n    def build(self):\n        libs = []\n        shared_scopes = (\"CPPPATH\", \"LIBPATH\", \"LIBS\", \"LINKFLAGS\")\n        for lb in self.depbuilders:\n            libs.extend(lb.build())\n            # copy shared information to self env\n            self.env.PrependUnique(\n                **{\n                    scope: lb.env.get(scope)\n                    for scope in shared_scopes\n                    if lb.env.get(scope)\n                }\n            )\n\n        for lb in self._circular_deps:\n            self.env.PrependUnique(CPPPATH=lb.get_include_dirs())\n\n        if self.is_built:\n            return libs\n        self.is_built = True\n\n        self.env.PrependUnique(CPPPATH=self.get_include_dirs())\n        self.env.ProcessCompileDbToolchainOption()\n\n        if self.lib_ldf_mode == \"off\":\n            for lb in self.env.GetLibBuilders():\n                if self == lb or not lb.is_built:\n                    continue\n                self.env.PrependUnique(\n                    **{\n                        scope: lb.env.get(scope)\n                        for scope in shared_scopes\n                        if lb.env.get(scope)\n                    }\n                )\n\n        do_not_archive = not self.lib_archive\n        if not do_not_archive:\n            nodes = self.env.CollectBuildFiles(\n                self.build_dir, self.src_dir, self.src_filter\n            )\n            if nodes:\n                libs.append(\n                    self.env.BuildLibrary(\n                        self.build_dir, self.src_dir, self.src_filter, nodes\n                    )\n                )\n            else:\n                do_not_archive = True\n        if do_not_archive:\n            self.env.BuildSources(self.build_dir, self.src_dir, self.src_filter)\n\n        return libs\n\n\nclass UnknownLibBuilder(LibBuilderBase):\n    pass\n\n\nclass ArduinoLibBuilder(LibBuilderBase):\n    def load_manifest(self):\n        manifest_path = os.path.join(self.path, \"library.properties\")\n        if not os.path.isfile(manifest_path):\n            return {}\n        return ManifestParserFactory.new_from_file(manifest_path).as_dict()\n\n    @property\n    def include_dir(self):\n        if not all(\n            os.path.isdir(os.path.join(self.path, d)) for d in (\"include\", \"src\")\n        ):\n            return None\n        return os.path.join(self.path, \"include\")\n\n    def get_include_dirs(self):\n        include_dirs = super().get_include_dirs()\n        if os.path.isdir(os.path.join(self.path, \"src\")):\n            return include_dirs\n        if os.path.isdir(os.path.join(self.path, \"utility\")):\n            include_dirs.append(os.path.join(self.path, \"utility\"))\n        return include_dirs\n\n    @property\n    def src_filter(self):\n        src_dir = os.path.join(self.path, \"src\")\n        if os.path.isdir(src_dir):\n            # pylint: disable=no-member\n            src_filter = LibBuilderBase.src_filter.fget(self)\n            for root, _, files in os.walk(src_dir, followlinks=True):\n                found = False\n                for fname in files:\n                    if fname.lower().endswith(\"asm\"):\n                        found = True\n                        break\n                if not found:\n                    continue\n                rel_path = root.replace(src_dir, \"\")\n                if rel_path.startswith(os.path.sep):\n                    rel_path = rel_path[1:] + os.path.sep\n                src_filter.append(\"-<%s*.[aA][sS][mM]>\" % rel_path)\n            return src_filter\n\n        src_filter = []\n        is_utility = os.path.isdir(os.path.join(self.path, \"utility\"))\n        for ext in piobuild.SRC_BUILD_EXT + piobuild.SRC_HEADER_EXT:\n            # arduino ide ignores files with .asm or .ASM extensions\n            if ext.lower() == \"asm\":\n                continue\n            src_filter.append(\"+<*.%s>\" % ext)\n            if is_utility:\n                src_filter.append(\"+<utility%s*.%s>\" % (os.path.sep, ext))\n        return src_filter\n\n    @property\n    def dependencies(self):\n        # do not include automatically all libraries for build\n        # chain+ will decide later\n        return None\n\n    @property\n    def lib_ldf_mode(self):\n        # pylint: disable=no-member\n        if not self._manifest.get(\"dependencies\"):\n            return LibBuilderBase.lib_ldf_mode.fget(self)\n        missing = object()\n        global_value = self.env.GetProjectConfig().getraw(\n            \"env:\" + self.env[\"PIOENV\"], \"lib_ldf_mode\", missing\n        )\n        if global_value != missing:\n            return LibBuilderBase.lib_ldf_mode.fget(self)\n        # automatically enable C++ Preprocessing in runtime\n        # (Arduino IDE has this behavior)\n        return \"chain+\"\n\n    def is_frameworks_compatible(self, frameworks):\n        return PackageCompatibility(frameworks=frameworks).is_compatible(\n            PackageCompatibility(frameworks=[\"arduino\", \"energia\"])\n        )\n\n    def is_platforms_compatible(self, platforms):\n        return PackageCompatibility(platforms=platforms).is_compatible(\n            PackageCompatibility(platforms=self._manifest.get(\"platforms\"))\n        )\n\n    @property\n    def build_flags(self):\n        ldflags = [\n            LibBuilderBase.build_flags.fget(self),  # pylint: disable=no-member\n            self._manifest.get(\"ldflags\"),\n        ]\n        if self._manifest.get(\"precompiled\") in (\"true\", \"full\"):\n            # add to LDPATH {build.mcu} folder\n            board_config = self.env.BoardConfig()\n            for key in (\"build.mcu\", \"build.cpu\"):\n                libpath = os.path.join(self.src_dir, board_config.get(key, \"\"))\n                if not os.path.isdir(libpath):\n                    continue\n                self.env.PrependUnique(LIBPATH=libpath)\n                break\n        ldflags = [flag for flag in ldflags if flag]  # remove empty\n        return \" \".join(ldflags) if ldflags else None\n\n\nclass MbedLibBuilder(LibBuilderBase):\n    def load_manifest(self):\n        manifest_path = os.path.join(self.path, \"module.json\")\n        if not os.path.isfile(manifest_path):\n            return {}\n        return ManifestParserFactory.new_from_file(manifest_path).as_dict()\n\n    @property\n    def src_dir(self):\n        if os.path.isdir(os.path.join(self.path, \"source\")):\n            return os.path.join(self.path, \"source\")\n        return LibBuilderBase.src_dir.fget(self)  # pylint: disable=no-member\n\n    def get_include_dirs(self):\n        include_dirs = super().get_include_dirs()\n        if self.path not in include_dirs:\n            include_dirs.append(self.path)\n\n        # library with module.json\n        for p in self._manifest.get(\"extraIncludes\", []):\n            include_dirs.append(os.path.join(self.path, p))\n\n        # old mbed library without manifest, add to CPPPATH all folders\n        if not self._manifest:\n            for root, _, __ in os.walk(self.path):\n                part = root.replace(self.path, \"\").lower()\n                if any(s in part for s in (\"%s.\" % os.path.sep, \"test\", \"example\")):\n                    continue\n                if root not in include_dirs:\n                    include_dirs.append(root)\n\n        return include_dirs\n\n    def is_frameworks_compatible(self, frameworks):\n        return PackageCompatibility(frameworks=frameworks).is_compatible(\n            PackageCompatibility(frameworks=[\"mbed\"])\n        )\n\n    def process_extra_options(self):\n        self._process_mbed_lib_confs()\n        return super().process_extra_options()\n\n    def _process_mbed_lib_confs(self):\n        mbed_lib_paths = [\n            os.path.join(root, \"mbed_lib.json\")\n            for root, _, files in os.walk(self.path)\n            if \"mbed_lib.json\" in files\n        ]\n        if not mbed_lib_paths:\n            return None\n\n        mbed_config_path = None\n        for p in self.env.get(\"CPPPATH\"):\n            mbed_config_path = os.path.join(self.env.subst(p), \"mbed_config.h\")\n            if os.path.isfile(mbed_config_path):\n                break\n            mbed_config_path = None\n        if not mbed_config_path:\n            return None\n\n        macros = {}\n        for mbed_lib_path in mbed_lib_paths:\n            macros.update(self._mbed_lib_conf_parse_macros(mbed_lib_path))\n\n        self._mbed_conf_append_macros(mbed_config_path, macros)\n        return True\n\n    @staticmethod\n    def _mbed_normalize_macro(macro):\n        name = macro\n        value = None\n        if \"=\" in macro:\n            name, value = macro.split(\"=\", 1)\n        return dict(name=name, value=value)\n\n    def _mbed_lib_conf_parse_macros(self, mbed_lib_path):\n        macros = {}\n        cppdefines = str(self.env.Flatten(self.env.subst(\"$CPPDEFINES\")))\n        manifest = fs.load_json(mbed_lib_path)\n\n        # default macros\n        for macro in manifest.get(\"macros\", []):\n            macro = self._mbed_normalize_macro(macro)\n            macros[macro[\"name\"]] = macro\n\n        # configuration items\n        for key, options in manifest.get(\"config\", {}).items():\n            if \"value\" not in options:\n                continue\n            macros[key] = dict(\n                name=options.get(\"macro_name\"), value=options.get(\"value\")\n            )\n\n        # overrode items per target\n        for target, options in manifest.get(\"target_overrides\", {}).items():\n            if target != \"*\" and \"TARGET_\" + target not in cppdefines:\n                continue\n            for macro in options.get(\"target.macros_add\", []):\n                macro = self._mbed_normalize_macro(macro)\n                macros[macro[\"name\"]] = macro\n            for key, value in options.items():\n                if not key.startswith(\"target.\") and key in macros:\n                    macros[key][\"value\"] = value\n\n        # normalize macro names\n        for key, macro in macros.items():\n            if not macro[\"name\"]:\n                macro[\"name\"] = key\n                if \".\" not in macro[\"name\"]:\n                    macro[\"name\"] = \"%s.%s\" % (manifest.get(\"name\"), macro[\"name\"])\n                macro[\"name\"] = re.sub(\n                    r\"[^a-z\\d]+\", \"_\", macro[\"name\"], flags=re.I\n                ).upper()\n                macro[\"name\"] = \"MBED_CONF_\" + macro[\"name\"]\n            if isinstance(macro[\"value\"], bool):\n                macro[\"value\"] = 1 if macro[\"value\"] else 0\n\n        return {macro[\"name\"]: macro[\"value\"] for macro in macros.values()}\n\n    def _mbed_conf_append_macros(self, mbed_config_path, macros):\n        lines = []\n        with open(mbed_config_path, encoding=\"utf8\") as fp:\n            for line in fp.readlines():\n                line = line.strip()\n                if line == \"#endif\":\n                    lines.append(\"// PlatformIO Library Dependency Finder (LDF)\")\n                    lines.extend(\n                        [\n                            \"#define %s %s\" % (name, value if value is not None else \"\")\n                            for name, value in macros.items()\n                        ]\n                    )\n                    lines.append(\"\")\n                if not line.startswith(\"#define\"):\n                    lines.append(line)\n                    continue\n                tokens = line.split()\n                if len(tokens) < 2 or tokens[1] not in macros:\n                    lines.append(line)\n        lines.append(\"\")\n        with open(mbed_config_path, mode=\"w\", encoding=\"utf8\") as fp:\n            fp.write(\"\\n\".join(lines))\n\n\nclass PlatformIOLibBuilder(LibBuilderBase):\n    def load_manifest(self):\n        manifest_path = os.path.join(self.path, \"library.json\")\n        if not os.path.isfile(manifest_path):\n            return {}\n        return ManifestParserFactory.new_from_file(manifest_path).as_dict()\n\n    def _has_arduino_manifest(self):\n        return os.path.isfile(os.path.join(self.path, \"library.properties\"))\n\n    @property\n    def include_dir(self):\n        if \"includeDir\" in self._manifest.get(\"build\", {}):\n            with fs.cd(self.path):\n                return os.path.abspath(self._manifest.get(\"build\").get(\"includeDir\"))\n        return LibBuilderBase.include_dir.fget(self)  # pylint: disable=no-member\n\n    def get_include_dirs(self):\n        include_dirs = super().get_include_dirs()\n\n        # backwards compatibility with PlatformIO 2.0\n        if (\n            \"build\" not in self._manifest\n            and self._has_arduino_manifest()\n            and not os.path.isdir(os.path.join(self.path, \"src\"))\n            and os.path.isdir(os.path.join(self.path, \"utility\"))\n        ):\n            include_dirs.append(os.path.join(self.path, \"utility\"))\n\n        for path in self.env.get(\"CPPPATH\", []):\n            if path not in include_dirs and path not in self.envorigin.get(\n                \"CPPPATH\", []\n            ):\n                include_dirs.append(self.env.subst(path))\n\n        return include_dirs\n\n    @property\n    def src_dir(self):\n        if \"srcDir\" in self._manifest.get(\"build\", {}):\n            with fs.cd(self.path):\n                return os.path.abspath(self._manifest.get(\"build\").get(\"srcDir\"))\n        return LibBuilderBase.src_dir.fget(self)  # pylint: disable=no-member\n\n    @property\n    def src_filter(self):\n        # pylint: disable=no-member\n        if \"srcFilter\" in self._manifest.get(\"build\", {}):\n            return self._manifest.get(\"build\").get(\"srcFilter\")\n        if self.env[\"SRC_FILTER\"]:\n            return self.env[\"SRC_FILTER\"]\n        if self._has_arduino_manifest():\n            return ArduinoLibBuilder.src_filter.fget(self)\n        return LibBuilderBase.src_filter.fget(self)\n\n    @property\n    def build_flags(self):\n        if \"flags\" in self._manifest.get(\"build\", {}):\n            return self._manifest.get(\"build\").get(\"flags\")\n        return LibBuilderBase.build_flags.fget(self)  # pylint: disable=no-member\n\n    @property\n    def build_unflags(self):\n        if \"unflags\" in self._manifest.get(\"build\", {}):\n            return self._manifest.get(\"build\").get(\"unflags\")\n        return LibBuilderBase.build_unflags.fget(self)  # pylint: disable=no-member\n\n    @property\n    def extra_script(self):\n        if \"extraScript\" in self._manifest.get(\"build\", {}):\n            return self._manifest.get(\"build\").get(\"extraScript\")\n        return LibBuilderBase.extra_script.fget(self)  # pylint: disable=no-member\n\n    @property\n    def lib_archive(self):\n        missing = object()\n        global_value = self.env.GetProjectConfig().getraw(\n            \"env:\" + self.env[\"PIOENV\"], \"lib_archive\", missing\n        )\n        if global_value != missing:\n            return self.env.GetProjectConfig().get(\n                \"env:\" + self.env[\"PIOENV\"], \"lib_archive\"\n            )\n        # pylint: disable=no-member\n        return self._manifest.get(\"build\", {}).get(\n            \"libArchive\", LibBuilderBase.lib_archive.fget(self)\n        )\n\n    @property\n    def lib_ldf_mode(self):\n        # pylint: disable=no-member\n        return self.validate_ldf_mode(\n            self._manifest.get(\"build\", {}).get(\n                \"libLDFMode\", LibBuilderBase.lib_ldf_mode.fget(self)\n            )\n        )\n\n    @property\n    def lib_compat_mode(self):\n        # pylint: disable=no-member\n        return self.validate_compat_mode(\n            self._manifest.get(\"build\", {}).get(\n                \"libCompatMode\", LibBuilderBase.lib_compat_mode.fget(self)\n            )\n        )\n\n    def is_platforms_compatible(self, platforms):\n        return PackageCompatibility(platforms=platforms).is_compatible(\n            PackageCompatibility(platforms=self._manifest.get(\"platforms\"))\n        )\n\n    def is_frameworks_compatible(self, frameworks):\n        return PackageCompatibility(frameworks=frameworks).is_compatible(\n            PackageCompatibility(frameworks=self._manifest.get(\"frameworks\"))\n        )\n\n\nclass ProjectAsLibBuilder(LibBuilderBase):\n    def __init__(self, env, *args, **kwargs):\n        export_projenv = kwargs.get(\"export_projenv\", True)\n        if \"export_projenv\" in kwargs:\n            del kwargs[\"export_projenv\"]\n        # backup original value, will be reset in base.__init__\n        project_src_filter = env.get(\"SRC_FILTER\")\n        super().__init__(env, *args, **kwargs)\n        self.env[\"SRC_FILTER\"] = project_src_filter\n        if export_projenv:\n            env.Export(dict(projenv=self.env))\n\n    def __contains__(self, child_path):\n        for root_path in (self.include_dir, self.src_dir, self.test_dir):\n            if root_path and self.is_common_builder(root_path, child_path):\n                return True\n        return False\n\n    @property\n    def include_dir(self):\n        include_dir = self.env.subst(\"$PROJECT_INCLUDE_DIR\")\n        return include_dir if os.path.isdir(include_dir) else None\n\n    @property\n    def src_dir(self):\n        return self.env.subst(\"$PROJECT_SRC_DIR\")\n\n    @property\n    def test_dir(self):\n        return self.env.subst(\"$PROJECT_TEST_DIR\")\n\n    def get_search_files(self):\n        items = []\n        build_type = self.env[\"BUILD_TYPE\"]\n        # project files\n        if \"test\" not in build_type or self.env.GetProjectOption(\"test_build_src\"):\n            items.extend(super().get_search_files())\n        # test files\n        if \"test\" in build_type:\n            items.extend(\n                [\n                    os.path.join(\"$PROJECT_TEST_DIR\", item)\n                    for item in self.env.MatchSourceFiles(\n                        \"$PROJECT_TEST_DIR\", \"$PIOTEST_SRC_FILTER\"\n                    )\n                ]\n            )\n        return items\n\n    @property\n    def lib_ldf_mode(self):\n        mode = LibBuilderBase.lib_ldf_mode.fget(self)  # pylint: disable=no-member\n        if not mode.startswith(\"chain\"):\n            return mode\n        # parse all project files\n        return \"deep+\" if \"+\" in mode else \"deep\"\n\n    @property\n    def src_filter(self):\n        # pylint: disable=no-member\n        return self.env.get(\"SRC_FILTER\") or LibBuilderBase.src_filter.fget(self)\n\n    @property\n    def build_flags(self):\n        # pylint: disable=no-member\n        return self.env.get(\"SRC_BUILD_FLAGS\") or LibBuilderBase.build_flags.fget(self)\n\n    @property\n    def dependencies(self):\n        return self.env.GetProjectOption(\"lib_deps\", [])\n\n    def process_extra_options(self):\n        with fs.cd(self.path):\n            self.env.ProcessFlags(self.build_flags)\n            self.env.ProcessUnFlags(self.build_unflags)\n\n    def install_dependencies(self):\n        def _is_builtin(spec):\n            for lb in self.env.GetLibBuilders():\n                if lb.name == spec:\n                    return True\n            return False\n\n        not_found_specs = []\n        for spec in self.dependencies:\n            # check if built-in library\n            if _is_builtin(spec):\n                continue\n\n            found = False\n            for storage_dir in self.env.GetLibSourceDirs():\n                lm = LibraryPackageManager(storage_dir)\n                if lm.get_package(spec):\n                    found = True\n                    break\n            if not found:\n                not_found_specs.append(spec)\n\n        did_install = False\n        lm = LibraryPackageManager(\n            self.env.subst(os.path.join(\"$PROJECT_LIBDEPS_DIR\", \"$PIOENV\"))\n        )\n        for spec in not_found_specs:\n            try:\n                lm.install(spec)\n                did_install = True\n            except (\n                HTTPClientError,\n                UnknownPackageError,\n                InternetConnectionError,\n            ) as exc:\n                click.secho(\"Warning! %s\" % exc, fg=\"yellow\")\n\n        # reset cache\n        if did_install:\n            DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=None)\n\n    def process_dependencies(self):  # pylint: disable=too-many-branches\n        found_lbs = []\n        for spec in self.dependencies:\n            found = False\n            for storage_dir in self.env.GetLibSourceDirs():\n                if found:\n                    break\n                lm = LibraryPackageManager(storage_dir)\n                pkg = lm.get_package(spec)\n                if not pkg:\n                    continue\n                for lb in self.env.GetLibBuilders():\n                    if pkg.path != lb.path:\n                        continue\n                    if lb not in self.depbuilders:\n                        self.depend_on(lb, recursive=False)\n                        found_lbs.append(lb)\n                    found = True\n                    break\n            if found:\n                continue\n\n            # look for built-in libraries by a name\n            # which don't have package manifest\n            for lb in self.env.GetLibBuilders():\n                if lb.name != spec:\n                    continue\n                if lb not in self.depbuilders:\n                    self.depend_on(lb)\n                found = True\n                break\n\n        # process library dependencies\n        for lb in found_lbs:\n            lb.search_deps_recursive()\n\n    def build(self):\n        self.is_built = True  # do not build Project now\n        result = super().build()\n        self.env.PrependUnique(CPPPATH=self.get_include_dirs())\n        return result\n\n\ndef GetLibSourceDirs(env):\n    items = env.GetProjectOption(\"lib_extra_dirs\", [])\n    items.extend(env[\"LIBSOURCE_DIRS\"])\n    return [\n        env.subst(fs.expanduser(item) if item.startswith(\"~\") else item)\n        for item in items\n    ]\n\n\ndef IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get(\"PIOVERBOSE\", 0))):\n    compat_mode = lb.lib_compat_mode\n    if lb.name in env.GetProjectOption(\"lib_ignore\", []):\n        if verbose:\n            sys.stderr.write(\"Ignored library %s\\n\" % lb.path)\n        return None\n    if compat_mode == \"strict\" and not lb.is_platforms_compatible(env[\"PIOPLATFORM\"]):\n        if verbose:\n            sys.stderr.write(\"Platform incompatible library %s\\n\" % lb.path)\n        return False\n    if compat_mode in (\"soft\", \"strict\") and not lb.is_frameworks_compatible(\n        env.get(\"PIOFRAMEWORK\", \"__noframework__\")\n    ):\n        if verbose:\n            sys.stderr.write(\"Framework incompatible library %s\\n\" % lb.path)\n        return False\n    return True\n\n\ndef GetLibBuilders(_):  # pylint: disable=too-many-branches\n    env = DefaultEnvironment()\n    if env.get(\"__PIO_LIB_BUILDERS\", None) is not None:\n        return sorted(\n            env[\"__PIO_LIB_BUILDERS\"],\n            key=lambda lb: 0 if lb.is_dependent else 1,\n        )\n\n    env.Replace(__PIO_LIB_BUILDERS=[])\n\n    verbose = int(ARGUMENTS.get(\"PIOVERBOSE\", 0))\n    found_incompat = False\n\n    for storage_dir in env.GetLibSourceDirs():\n        storage_dir = os.path.abspath(storage_dir)\n        if not os.path.isdir(storage_dir):\n            continue\n        for item in sorted(os.listdir(storage_dir)):\n            lib_dir = os.path.join(storage_dir, item)\n            if item == \"__cores__\":\n                continue\n            if LibraryPackageManager.is_symlink(lib_dir):\n                lib_dir, _ = LibraryPackageManager.resolve_symlink(lib_dir)\n            if not lib_dir or not os.path.isdir(lib_dir):\n                continue\n            try:\n                lb = LibBuilderFactory.new(env, lib_dir)\n            except exception.InvalidJSONFile:\n                if verbose:\n                    sys.stderr.write(\n                        \"Skip library with broken manifest: %s\\n\" % lib_dir\n                    )\n                continue\n            if env.IsCompatibleLibBuilder(lb):\n                env.Append(__PIO_LIB_BUILDERS=[lb])\n            else:\n                found_incompat = True\n\n    for lb in env.get(\"EXTRA_LIB_BUILDERS\", []):\n        if env.IsCompatibleLibBuilder(lb):\n            env.Append(__PIO_LIB_BUILDERS=[lb])\n        else:\n            found_incompat = True\n\n    if verbose and found_incompat:\n        sys.stderr.write(\n            'More details about \"Library Compatibility Mode\": '\n            \"https://docs.platformio.org/page/librarymanager/ldf.html#\"\n            \"ldf-compat-mode\\n\"\n        )\n\n    return env[\"__PIO_LIB_BUILDERS\"]\n\n\ndef ConfigureProjectLibBuilder(env):\n    _pm_storage = {}\n\n    def _get_lib_license(pkg):\n        storage_dir = os.path.dirname(os.path.dirname(pkg.path))\n        if storage_dir not in _pm_storage:\n            _pm_storage[storage_dir] = LibraryPackageManager(storage_dir)\n        try:\n            return (_pm_storage[storage_dir].load_manifest(pkg) or {}).get(\"license\")\n        except MissingPackageManifestError:\n            pass\n        return None\n\n    def _correct_found_libs(lib_builders):\n        # build full dependency graph\n        found_lbs = [lb for lb in lib_builders if lb.is_dependent]\n        for lb in lib_builders:\n            if lb in found_lbs:\n                lb.search_deps_recursive(lb.get_search_files())\n        # refill found libs after recursive search\n        found_lbs = [lb for lb in lib_builders if lb.is_dependent]\n        for lb in lib_builders:\n            for deplb in lb.depbuilders[:]:\n                if deplb not in found_lbs:\n                    lb.depbuilders.remove(deplb)\n\n    def _print_deps_tree(root, level=0):\n        margin = \"|   \" * (level)\n        for lb in root.depbuilders:\n            title = lb.name\n            pkg = PackageItem(lb.path)\n            if pkg.metadata:\n                title += \" @ %s\" % pkg.metadata.version\n            elif lb.version:\n                title += \" @ %s\" % lb.version\n            click.echo(\"%s|-- %s\" % (margin, title), nl=False)\n            if int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n                click.echo(\n                    \" (License: %s, \" % (_get_lib_license(pkg) or \"Unknown\"), nl=False\n                )\n                if pkg.metadata and pkg.metadata.spec.external:\n                    click.echo(\"URI: %s, \" % pkg.metadata.spec.uri, nl=False)\n                click.echo(\"Path: %s\" % lb.path, nl=False)\n                click.echo(\")\", nl=False)\n            click.echo(\"\")\n            if lb.verbose and lb.depbuilders:\n                _print_deps_tree(lb, level + 1)\n\n    project = ProjectAsLibBuilder(env, \"$PROJECT_DIR\")\n\n    if \"test\" in env[\"BUILD_TYPE\"]:\n        project.env.ConfigureTestTarget()\n\n    ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project)  # pylint: disable=no-member\n\n    click.echo(\"LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf\")\n    click.echo(\n        \"LDF Modes: Finder ~ %s, Compatibility ~ %s\"\n        % (ldf_mode, project.lib_compat_mode)\n    )\n\n    project.install_dependencies()\n\n    lib_builders = env.GetLibBuilders()\n    click.echo(\"Found %d compatible libraries\" % len(lib_builders))\n\n    click.echo(\"Scanning dependencies...\")\n    project.search_deps_recursive()\n\n    if ldf_mode.startswith(\"chain\") and project.depbuilders:\n        _correct_found_libs(lib_builders)\n\n    if project.depbuilders:\n        click.echo(\"Dependency Graph\")\n        _print_deps_tree(project)\n    else:\n        click.echo(\"No dependencies\")\n\n    return project\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(GetLibSourceDirs)\n    env.AddMethod(IsCompatibleLibBuilder)\n    env.AddMethod(GetLibBuilders)\n    env.AddMethod(ConfigureProjectLibBuilder)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piomaxlen.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport os\nimport re\n\nfrom SCons.Platform import TempFileMunge  # pylint: disable=import-error\nfrom SCons.Script import COMMAND_LINE_TARGETS  # pylint: disable=import-error\nfrom SCons.Subst import quote_spaces  # pylint: disable=import-error\n\nfrom platformio.compat import IS_WINDOWS, hashlib_encode_data\n\n# There are the next limits depending on a platform:\n# - Windows = 8191\n# - Unix    = 131072\n# We need ~512 characters for compiler and temporary file paths\nMAX_LINE_LENGTH = (8191 if IS_WINDOWS else 131072) - 512\n\nWINPATHSEP_RE = re.compile(r\"\\\\([^\\\"'\\\\]|$)\")\n\n\ndef tempfile_arg_esc_func(arg):\n    arg = quote_spaces(arg)\n    if not IS_WINDOWS:\n        return arg\n    # GCC requires double Windows slashes, let's use UNIX separator\n    return WINPATHSEP_RE.sub(r\"/\\1\", arg)\n\n\ndef long_sources_hook(env, sources):\n    _sources = str(sources).replace(\"\\\\\", \"/\")\n    if len(str(_sources)) < MAX_LINE_LENGTH:\n        return sources\n\n    # fix space in paths\n    data = []\n    for line in _sources.split(\".o \"):\n        line = line.strip()\n        if not line.endswith(\".o\"):\n            line += \".o\"\n        data.append('\"%s\"' % line)\n\n    return '@\"%s\"' % _file_long_data(env, \" \".join(data))\n\n\ndef _file_long_data(env, data):\n    build_dir = env.subst(\"$BUILD_DIR\")\n    if not os.path.isdir(build_dir):\n        os.makedirs(build_dir)\n    tmp_file = os.path.join(\n        build_dir, \"longcmd-%s\" % hashlib.md5(hashlib_encode_data(data)).hexdigest()\n    )\n    if os.path.isfile(tmp_file):\n        return tmp_file\n    with open(tmp_file, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(data)\n    return tmp_file\n\n\ndef exists(env):\n    return \"compiledb\" not in COMMAND_LINE_TARGETS and not env.IsIntegrationDump()\n\n\ndef generate(env):\n    if not exists(env):\n        return env\n    kwargs = dict(\n        _long_sources_hook=long_sources_hook,\n        TEMPFILE=TempFileMunge,\n        MAXLINELENGTH=MAX_LINE_LENGTH,\n        TEMPFILEARGESCFUNC=tempfile_arg_esc_func,\n        TEMPFILESUFFIX=\".tmp\",\n        TEMPFILEDIR=\"$BUILD_DIR\",\n    )\n\n    for name in (\"LINKCOM\", \"ASCOM\", \"ASPPCOM\", \"CCCOM\", \"CXXCOM\"):\n        kwargs[name] = \"${TEMPFILE('%s','$%sSTR')}\" % (env.get(name), name)\n\n    kwargs[\"ARCOM\"] = env.get(\"ARCOM\", \"\").replace(\n        \"$SOURCES\", \"${_long_sources_hook(__env__, SOURCES)}\"\n    )\n    env.Replace(**kwargs)\n\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piomisc.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\n\nfrom platformio import fs, util\nfrom platformio.proc import exec_command\n\n\n@util.memoized()\ndef GetCompilerType(env):  # pylint: disable=too-many-return-statements\n    CC = env.subst(\"$CC\")\n    if CC.endswith(\"-gcc\"):\n        return \"gcc\"\n    if os.path.basename(CC) == \"clang\":\n        return \"clang\"\n    try:\n\n        sysenv = os.environ.copy()\n        sysenv[\"PATH\"] = str(env[\"ENV\"][\"PATH\"])\n        result = exec_command([CC, \"-v\"], env=sysenv)\n    except OSError:\n        return None\n    if result[\"returncode\"] != 0:\n        return None\n    output = \"\".join([result[\"out\"], result[\"err\"]]).lower()\n    if \"clang version\" in output:\n        return \"clang\"\n    if \"gcc\" in output:\n        return \"gcc\"\n    return None\n\n\ndef GetActualLDScript(env):\n    def _lookup_in_ldpath(script):\n        for d in env.get(\"LIBPATH\", []):\n            path = os.path.join(env.subst(d), script)\n            if os.path.isfile(path):\n                return path\n        return None\n\n    script = None\n    script_in_next = False\n    for f in env.get(\"LINKFLAGS\", []):\n        raw_script = None\n        if f == \"-T\":\n            script_in_next = True\n            continue\n        if script_in_next:\n            script_in_next = False\n            raw_script = f\n        elif f.startswith(\"-Wl,-T\"):\n            raw_script = f[6:]\n        else:\n            continue\n        script = env.subst(raw_script.replace('\"', \"\").strip())\n        if os.path.isfile(script):\n            return script\n        path = _lookup_in_ldpath(script)\n        if path:\n            return path\n\n    if script:\n        sys.stderr.write(\n            \"Error: Could not find '%s' LD script in LDPATH '%s'\\n\"\n            % (script, env.subst(\"$LIBPATH\"))\n        )\n        env.Exit(1)\n\n    if not script and \"LDSCRIPT_PATH\" in env:\n        path = _lookup_in_ldpath(env[\"LDSCRIPT_PATH\"])\n        if path:\n            return path\n\n    sys.stderr.write(\"Error: Could not find LD script\\n\")\n    env.Exit(1)\n\n\ndef ConfigureDebugTarget(env):\n    def _cleanup_debug_flags(scope):\n        if scope not in env:\n            return\n        unflags = [\"-Os\", \"-g\"]\n        for level in [0, 1, 2, 3]:\n            for flag in (\"O\", \"g\", \"ggdb\"):\n                unflags.append(\"-%s%d\" % (flag, level))\n        env[scope] = [f for f in env.get(scope, []) if f not in unflags]\n\n    env.Append(CPPDEFINES=[\"__PLATFORMIO_BUILD_DEBUG__\"])\n\n    for scope in (\"ASFLAGS\", \"CCFLAGS\", \"LINKFLAGS\"):\n        _cleanup_debug_flags(scope)\n\n    debug_flags = env.ParseFlags(\n        env.get(\"PIODEBUGFLAGS\")\n        if env.get(\"PIODEBUGFLAGS\")\n        and not env.GetProjectOptions(as_dict=True).get(\"debug_build_flags\")\n        else env.GetProjectOption(\"debug_build_flags\")\n    )\n\n    env.MergeFlags(debug_flags)\n    optimization_flags = [\n        f for f in debug_flags.get(\"CCFLAGS\", []) if f.startswith((\"-O\", \"-g\"))\n    ]\n\n    if optimization_flags:\n        env.AppendUnique(\n            ASFLAGS=[\n                # skip -O flags for assembler\n                f\n                for f in optimization_flags\n                if f.startswith(\"-g\")\n            ],\n            LINKFLAGS=optimization_flags,\n        )\n\n\ndef GetExtraScripts(env, scope):\n    items = []\n    for item in env.GetProjectOption(\"extra_scripts\", []):\n        if scope == \"post\" and \":\" not in item:\n            items.append(item)\n        elif item.startswith(\"%s:\" % scope):\n            items.append(item[len(scope) + 1 :])\n    if not items:\n        return items\n    with fs.cd(env.subst(\"$PROJECT_DIR\")):\n        return [os.path.abspath(env.subst(item)) for item in items]\n\n\ndef generate(env):\n    env.AddMethod(GetCompilerType)\n    env.AddMethod(GetActualLDScript)\n    env.AddMethod(ConfigureDebugTarget)\n    env.AddMethod(GetExtraScripts)\n    # backward-compatibility with Zephyr build script\n    env.AddMethod(ConfigureDebugTarget, \"ConfigureDebugFlags\")\n\n\ndef exists(_):\n    return True\n"
  },
  {
    "path": "platformio/builder/tools/pioplatform.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\n\nfrom SCons.Script import ARGUMENTS  # pylint: disable=import-error\nfrom SCons.Script import COMMAND_LINE_TARGETS  # pylint: disable=import-error\nfrom SCons.Script import DefaultEnvironment  # pylint: disable=import-error\n\nfrom platformio import fs, util\nfrom platformio.compat import IS_MACOS, IS_WINDOWS\nfrom platformio.package.meta import PackageItem\nfrom platformio.package.version import get_original_version\nfrom platformio.platform.exception import UnknownBoard\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectOptions\n\n# pylint: disable=too-many-branches, too-many-locals\n\n\n@util.memoized()\ndef _PioPlatform():\n    env = DefaultEnvironment()\n    return PlatformFactory.from_env(env[\"PIOENV\"], targets=COMMAND_LINE_TARGETS)\n\n\ndef PioPlatform(_):\n    return _PioPlatform()\n\n\ndef BoardConfig(env, board=None):\n    with fs.cd(env.subst(\"$PROJECT_DIR\")):\n        try:\n            p = env.PioPlatform()\n            board = board or env.get(\"BOARD\")\n            assert board, \"BoardConfig: Board is not defined\"\n            return p.board_config(board)\n        except (AssertionError, UnknownBoard) as exc:\n            sys.stderr.write(\"Error: %s\\n\" % str(exc))\n            env.Exit(1)\n    return None\n\n\ndef GetFrameworkScript(env, framework):\n    p = env.PioPlatform()\n    assert p.frameworks and framework in p.frameworks\n    script_path = env.subst(p.frameworks[framework][\"script\"])\n    if not os.path.isfile(script_path):\n        script_path = os.path.join(p.get_dir(), script_path)\n    return script_path\n\n\ndef LoadPioPlatform(env):\n    p = env.PioPlatform()\n\n    # Ensure real platform name\n    env[\"PIOPLATFORM\"] = p.name\n\n    # Add toolchains and uploaders to $PATH and $*_LIBRARY_PATH\n    for pkg in p.get_installed_packages():\n        type_ = p.get_package_type(pkg.metadata.name)\n        if type_ not in (\"toolchain\", \"uploader\", \"debugger\"):\n            continue\n        env.PrependENVPath(\n            \"PATH\",\n            (\n                os.path.join(pkg.path, \"bin\")\n                if os.path.isdir(os.path.join(pkg.path, \"bin\"))\n                else pkg.path\n            ),\n        )\n        if (\n            not IS_WINDOWS\n            and os.path.isdir(os.path.join(pkg.path, \"lib\"))\n            and type_ != \"toolchain\"\n        ):\n            env.PrependENVPath(\n                \"DYLD_LIBRARY_PATH\" if IS_MACOS else \"LD_LIBRARY_PATH\",\n                os.path.join(pkg.path, \"lib\"),\n            )\n\n    # Platform specific LD Scripts\n    if os.path.isdir(os.path.join(p.get_dir(), \"ldscripts\")):\n        env.Prepend(LIBPATH=[os.path.join(p.get_dir(), \"ldscripts\")])\n\n    if \"BOARD\" not in env:\n        return\n\n    # update board manifest with overridden data from INI config\n    board_config = env.BoardConfig()\n    for option, value in env.GetProjectOptions():\n        if not option.startswith(\"board_\"):\n            continue\n        option = option.lower()[6:]\n        try:\n            if isinstance(board_config.get(option), bool):\n                value = str(value).lower() in (\"1\", \"yes\", \"true\")\n            elif isinstance(board_config.get(option), int):\n                value = int(value)\n        except KeyError:\n            pass\n        board_config.update(option, value)\n\n    # load default variables from board config\n    for option_meta in ProjectOptions.values():\n        if not option_meta.buildenvvar or option_meta.buildenvvar in env:\n            continue\n        data_path = (\n            option_meta.name[6:]\n            if option_meta.name.startswith(\"board_\")\n            else option_meta.name.replace(\"_\", \".\")\n        )\n        try:\n            env[option_meta.buildenvvar] = board_config.get(data_path)\n        except KeyError:\n            pass\n\n    if \"build.ldscript\" in board_config:\n        env.Replace(LDSCRIPT_PATH=board_config.get(\"build.ldscript\"))\n\n\ndef PrintConfiguration(env):  # pylint: disable=too-many-statements\n    platform = env.PioPlatform()\n    pkg_metadata = PackageItem(platform.get_dir()).metadata\n    board_config = env.BoardConfig() if \"BOARD\" in env else None\n\n    def _get_configuration_data():\n        return (\n            None\n            if not board_config\n            else [\n                \"CONFIGURATION:\",\n                \"https://docs.platformio.org/page/boards/%s/%s.html\"\n                % (platform.name, board_config.id),\n            ]\n        )\n\n    def _get_plaform_data():\n        data = [\n            \"PLATFORM: %s (%s)\"\n            % (\n                platform.title,\n                pkg_metadata.version if pkg_metadata else platform.version,\n            )\n        ]\n        if (\n            int(ARGUMENTS.get(\"PIOVERBOSE\", 0))\n            and pkg_metadata\n            and pkg_metadata.spec.external\n        ):\n            data.append(\"(%s)\" % pkg_metadata.spec.uri)\n        if board_config:\n            data.extend([\">\", board_config.get(\"name\")])\n        return data\n\n    def _get_hardware_data():\n        data = [\"HARDWARE:\"]\n        mcu = env.subst(\"$BOARD_MCU\")\n        f_cpu = env.subst(\"$BOARD_F_CPU\")\n        if mcu:\n            data.append(mcu.upper())\n        if f_cpu:\n            f_cpu = int(\"\".join([c for c in str(f_cpu) if c.isdigit()]))\n            data.append(\"%dMHz,\" % (f_cpu / 1000000))\n        if not board_config:\n            return data\n        ram = board_config.get(\"upload\", {}).get(\"maximum_ram_size\")\n        flash = board_config.get(\"upload\", {}).get(\"maximum_size\")\n        data.append(\n            \"%s RAM, %s Flash\"\n            % (fs.humanize_file_size(ram), fs.humanize_file_size(flash))\n        )\n        return data\n\n    def _get_debug_data():\n        debug_tools = (\n            board_config.get(\"debug\", {}).get(\"tools\") if board_config else None\n        )\n        if not debug_tools:\n            return None\n        data = [\n            \"DEBUG:\",\n            \"Current\",\n            \"(%s)\"\n            % board_config.get_debug_tool_name(env.GetProjectOption(\"debug_tool\")),\n        ]\n        onboard = []\n        external = []\n        for key, value in debug_tools.items():\n            if value.get(\"onboard\"):\n                onboard.append(key)\n            else:\n                external.append(key)\n        if onboard:\n            data.extend([\"On-board\", \"(%s)\" % \", \".join(sorted(onboard))])\n        if external:\n            data.extend([\"External\", \"(%s)\" % \", \".join(sorted(external))])\n        return data\n\n    def _get_packages_data():\n        data = []\n        for item in platform.dump_used_packages():\n            original_version = get_original_version(item[\"version\"])\n            info = \"%s @ %s\" % (item[\"name\"], item[\"version\"])\n            extra = []\n            if original_version:\n                extra.append(original_version)\n            if \"src_url\" in item and int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n                extra.append(item[\"src_url\"])\n            if extra:\n                info += \" (%s)\" % \", \".join(extra)\n            data.append(info)\n        if not data:\n            return None\n        return [\"PACKAGES:\"] + [\"\\n - %s\" % d for d in sorted(data)]\n\n    for data in (\n        _get_configuration_data(),\n        _get_plaform_data(),\n        _get_hardware_data(),\n        _get_debug_data(),\n        _get_packages_data(),\n    ):\n        if data and len(data) > 1:\n            print(\" \".join(data))\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(PioPlatform)\n    env.AddMethod(BoardConfig)\n    env.AddMethod(GetFrameworkScript)\n    env.AddMethod(LoadPioPlatform)\n    env.AddMethod(PrintConfiguration)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/pioproject.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.compat import MISSING\nfrom platformio.project.config import ProjectConfig\n\n\ndef GetProjectConfig(env):\n    return ProjectConfig.get_instance(env[\"PROJECT_CONFIG\"])\n\n\ndef GetProjectOptions(env, as_dict=False):\n    return env.GetProjectConfig().items(env=env[\"PIOENV\"], as_dict=as_dict)\n\n\ndef GetProjectOption(env, option, default=MISSING):\n    return env.GetProjectConfig().get(\"env:\" + env[\"PIOENV\"], option, default)\n\n\ndef LoadProjectOptions(env):\n    config = env.GetProjectConfig()\n    section = \"env:\" + env[\"PIOENV\"]\n    for option in config.options(section):\n        option_meta = config.find_option_meta(section, option)\n        if (\n            not option_meta\n            or not option_meta.buildenvvar\n            or option_meta.buildenvvar in env\n        ):\n            continue\n        env[option_meta.buildenvvar] = config.get(section, option)\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(GetProjectConfig)\n    env.AddMethod(GetProjectOptions)\n    env.AddMethod(GetProjectOption)\n    env.AddMethod(LoadProjectOptions)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piosize.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-locals\n\nimport json\nimport sys\nfrom os import environ, makedirs, remove\nfrom os.path import isdir, join, splitdrive\n\nfrom elftools.elf.descriptions import describe_sh_flags\nfrom elftools.elf.elffile import ELFFile\n\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.proc import exec_command\n\n\ndef _run_tool(cmd, env, tool_args):\n    sysenv = environ.copy()\n    sysenv[\"PATH\"] = str(env[\"ENV\"][\"PATH\"])\n\n    build_dir = env.subst(\"$BUILD_DIR\")\n    if not isdir(build_dir):\n        makedirs(build_dir)\n    tmp_file = join(build_dir, \"size-data-longcmd.txt\")\n\n    with open(tmp_file, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(\"\\n\".join(tool_args))\n\n    cmd.append(\"@\" + tmp_file)\n    result = exec_command(cmd, env=sysenv)\n    remove(tmp_file)\n\n    return result\n\n\ndef _get_symbol_locations(env, elf_path, addrs):\n    if not addrs:\n        return {}\n    cmd = [env.subst(\"$CC\").replace(\"-gcc\", \"-addr2line\"), \"-e\", elf_path]\n    result = _run_tool(cmd, env, addrs)\n    locations = [line for line in result[\"out\"].split(\"\\n\") if line]\n    assert len(addrs) == len(locations)\n\n    return dict(zip(addrs, [loc.strip() for loc in locations]))\n\n\ndef _get_demangled_names(env, mangled_names):\n    if not mangled_names:\n        return {}\n    result = _run_tool(\n        [env.subst(\"$CC\").replace(\"-gcc\", \"-c++filt\")], env, mangled_names\n    )\n    demangled_names = [line for line in result[\"out\"].split(\"\\n\") if line]\n    assert len(mangled_names) == len(demangled_names)\n\n    return dict(\n        zip(\n            mangled_names,\n            [dn.strip().replace(\"::__FUNCTION__\", \"\") for dn in demangled_names],\n        )\n    )\n\n\ndef _collect_sections_info(env, elffile):\n    sections = {}\n    for section in elffile.iter_sections():\n        if section.is_null() or section.name.startswith(\".debug\"):\n            continue\n\n        section_type = section[\"sh_type\"]\n        section_flags = describe_sh_flags(section[\"sh_flags\"])\n        section_size = section.data_size\n\n        section_data = {\n            \"name\": section.name,\n            \"size\": section_size,\n            \"start_addr\": section[\"sh_addr\"],\n            \"type\": section_type,\n            \"flags\": section_flags,\n        }\n\n        sections[section.name] = section_data\n        sections[section.name][\"in_flash\"] = env.pioSizeIsFlashSection(section_data)\n        sections[section.name][\"in_ram\"] = env.pioSizeIsRamSection(section_data)\n\n    return sections\n\n\ndef _collect_symbols_info(env, elffile, elf_path, sections):\n    symbols = []\n\n    symbol_section = elffile.get_section_by_name(\".symtab\")\n    if symbol_section.is_null():\n        sys.stderr.write(\"Couldn't find symbol table. Is ELF file stripped?\")\n        env.Exit(1)\n\n    sysenv = environ.copy()\n    sysenv[\"PATH\"] = str(env[\"ENV\"][\"PATH\"])\n\n    symbol_addrs = []\n    mangled_names = []\n    for s in symbol_section.iter_symbols():\n        symbol_info = s.entry[\"st_info\"]\n        symbol_addr = s[\"st_value\"]\n        symbol_size = s[\"st_size\"]\n        symbol_type = symbol_info[\"type\"]\n\n        if not env.pioSizeIsValidSymbol(s.name, symbol_type, symbol_addr):\n            continue\n\n        symbol = {\n            \"addr\": symbol_addr,\n            \"bind\": symbol_info[\"bind\"],\n            \"name\": s.name,\n            \"type\": symbol_type,\n            \"size\": symbol_size,\n            \"section\": env.pioSizeDetermineSection(sections, symbol_addr),\n        }\n\n        if s.name.startswith(\"_Z\"):\n            mangled_names.append(s.name)\n\n        symbol_addrs.append(hex(symbol_addr))\n        symbols.append(symbol)\n\n    symbol_locations = _get_symbol_locations(env, elf_path, symbol_addrs)\n    demangled_names = _get_demangled_names(env, mangled_names)\n    for symbol in symbols:\n        if symbol[\"name\"].startswith(\"_Z\"):\n            symbol[\"demangled_name\"] = demangled_names.get(symbol[\"name\"])\n        location = symbol_locations.get(hex(symbol[\"addr\"]))\n        if not location or \"?\" in location:\n            continue\n        if IS_WINDOWS:\n            drive, tail = splitdrive(location)\n            location = join(drive.upper(), tail)\n        symbol[\"file\"] = location\n        symbol[\"line\"] = 0\n        if \":\" in location:\n            file_, line = location.rsplit(\":\", 1)\n            if line.isdigit():\n                symbol[\"file\"] = file_\n                symbol[\"line\"] = int(line)\n    return symbols\n\n\ndef pioSizeDetermineSection(_, sections, symbol_addr):\n    for section, info in sections.items():\n        if not info.get(\"in_flash\", False) and not info.get(\"in_ram\", False):\n            continue\n        if symbol_addr in range(info[\"start_addr\"], info[\"start_addr\"] + info[\"size\"]):\n            return section\n    return \"unknown\"\n\n\ndef pioSizeIsValidSymbol(_, symbol_name, symbol_type, symbol_address):\n    return symbol_name and symbol_address != 0 and symbol_type != \"STT_NOTYPE\"\n\n\ndef pioSizeIsRamSection(_, section):\n    return (\n        section.get(\"type\", \"\") in (\"SHT_NOBITS\", \"SHT_PROGBITS\")\n        and section.get(\"flags\", \"\") == \"WA\"\n    )\n\n\ndef pioSizeIsFlashSection(_, section):\n    return section.get(\"type\", \"\") == \"SHT_PROGBITS\" and \"A\" in section.get(\"flags\", \"\")\n\n\ndef pioSizeCalculateFirmwareSize(_, sections):\n    flash_size = ram_size = 0\n    for section_info in sections.values():\n        if section_info.get(\"in_flash\", False):\n            flash_size += section_info.get(\"size\", 0)\n        if section_info.get(\"in_ram\", False):\n            ram_size += section_info.get(\"size\", 0)\n\n    return ram_size, flash_size\n\n\ndef DumpSizeData(_, target, source, env):  # pylint: disable=unused-argument\n    data = {\"device\": {}, \"memory\": {}, \"version\": 1}\n\n    board = env.BoardConfig()\n    if board:\n        data[\"device\"] = {\n            \"mcu\": board.get(\"build.mcu\", \"\"),\n            \"cpu\": board.get(\"build.cpu\", \"\"),\n            \"frequency\": board.get(\"build.f_cpu\"),\n            \"flash\": int(board.get(\"upload.maximum_size\", 0)),\n            \"ram\": int(board.get(\"upload.maximum_ram_size\", 0)),\n        }\n        if data[\"device\"][\"frequency\"] and data[\"device\"][\"frequency\"].endswith(\"L\"):\n            data[\"device\"][\"frequency\"] = int(data[\"device\"][\"frequency\"][0:-1])\n\n    elf_path = env.subst(\"$PIOMAINPROG\")\n\n    with open(elf_path, \"rb\") as fp:\n        elffile = ELFFile(fp)\n\n        if not elffile.has_dwarf_info():\n            sys.stderr.write(\"Elf file doesn't contain DWARF information\")\n            env.Exit(1)\n\n        sections = _collect_sections_info(env, elffile)\n        firmware_ram, firmware_flash = env.pioSizeCalculateFirmwareSize(sections)\n        data[\"memory\"][\"total\"] = {\n            \"ram_size\": firmware_ram,\n            \"flash_size\": firmware_flash,\n            \"sections\": sections,\n        }\n\n        files = {}\n        for symbol in _collect_symbols_info(env, elffile, elf_path, sections):\n            file_path = symbol.get(\"file\") or \"unknown\"\n            if not files.get(file_path, {}):\n                files[file_path] = {\"symbols\": [], \"ram_size\": 0, \"flash_size\": 0}\n\n            symbol_size = symbol.get(\"size\", 0)\n            section = sections.get(symbol.get(\"section\", \"\"), {})\n            if not section:\n                continue\n            if section.get(\"in_ram\", False):\n                files[file_path][\"ram_size\"] += symbol_size\n            if section.get(\"in_flash\", False):\n                files[file_path][\"flash_size\"] += symbol_size\n\n            files[file_path][\"symbols\"].append(symbol)\n\n        data[\"memory\"][\"files\"] = []\n        for k, v in files.items():\n            file_data = {\"path\": k}\n            file_data.update(v)\n            data[\"memory\"][\"files\"].append(file_data)\n\n    with open(\n        join(env.subst(\"$BUILD_DIR\"), \"sizedata.json\"), mode=\"w\", encoding=\"utf8\"\n    ) as fp:\n        fp.write(json.dumps(data))\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(pioSizeIsRamSection)\n    env.AddMethod(pioSizeIsFlashSection)\n    env.AddMethod(pioSizeCalculateFirmwareSize)\n    env.AddMethod(pioSizeDetermineSection)\n    env.AddMethod(pioSizeIsValidSymbol)\n    env.AddMethod(DumpSizeData)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piotarget.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom SCons.Action import Action  # pylint: disable=import-error\nfrom SCons.Script import ARGUMENTS  # pylint: disable=import-error\nfrom SCons.Script import AlwaysBuild  # pylint: disable=import-error\n\nfrom platformio import compat, fs\n\n\ndef VerboseAction(_, act, actstr):\n    if int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n        return act\n    return Action(act, actstr)\n\n\ndef IsCleanTarget(env):\n    return env.GetOption(\"clean\")\n\n\ndef CleanProject(env, fullclean=False):\n    def _relpath(path):\n        if compat.IS_WINDOWS:\n            prefix = os.getcwd()[:2].lower()\n            if (\n                \":\" not in prefix\n                or not path.lower().startswith(prefix)\n                or os.path.relpath(path).startswith(\"..\")\n            ):\n                return path\n        return os.path.relpath(path)\n\n    def _clean_dir(path):\n        clean_rel_path = _relpath(path)\n        print(f\"Removing {clean_rel_path}\")\n        fs.rmtree(path)\n\n    build_dir = env.subst(\"$BUILD_DIR\")\n    libdeps_dir = env.subst(os.path.join(\"$PROJECT_LIBDEPS_DIR\", \"$PIOENV\"))\n    if os.path.isdir(build_dir):\n        _clean_dir(build_dir)\n    else:\n        print(\"Build environment is clean\")\n\n    if fullclean and os.path.isdir(libdeps_dir):\n        _clean_dir(libdeps_dir)\n\n    print(\"Done cleaning\")\n\n\ndef AddTarget(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n    env,\n    name,\n    dependencies,\n    actions,\n    title=None,\n    description=None,\n    group=\"General\",\n    always_build=True,\n):\n    if \"__PIO_TARGETS\" not in env:\n        env[\"__PIO_TARGETS\"] = {}\n    assert name not in env[\"__PIO_TARGETS\"]\n    env[\"__PIO_TARGETS\"][name] = dict(\n        name=name, title=title, description=description, group=group\n    )\n    target = env.Alias(name, dependencies, actions)\n    if always_build:\n        AlwaysBuild(target)\n    return target\n\n\ndef AddPlatformTarget(env, *args, **kwargs):\n    return env.AddTarget(group=\"Platform\", *args, **kwargs)\n\n\ndef AddCustomTarget(env, *args, **kwargs):\n    return env.AddTarget(group=\"Custom\", *args, **kwargs)\n\n\ndef DumpTargets(env):\n    targets = env.get(\"__PIO_TARGETS\") or {}\n    # pre-fill default targets if embedded dev-platform\n    if env.PioPlatform().is_embedded() and not any(\n        t[\"group\"] == \"Platform\" for t in targets.values()\n    ):\n        targets[\"upload\"] = dict(name=\"upload\", group=\"Platform\", title=\"Upload\")\n    return list(targets.values())\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(VerboseAction)\n    env.AddMethod(IsCleanTarget)\n    env.AddMethod(CleanProject)\n    env.AddMethod(AddTarget)\n    env.AddMethod(AddPlatformTarget)\n    env.AddMethod(AddCustomTarget)\n    env.AddMethod(DumpTargets)\n    return env\n"
  },
  {
    "path": "platformio/builder/tools/piotest.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio.builder.tools import piobuild\nfrom platformio.test.result import TestSuite\nfrom platformio.test.runners.factory import TestRunnerFactory\n\n\ndef ConfigureTestTarget(env):\n    env.Append(\n        CPPDEFINES=[\"UNIT_TEST\"],  # deprecated, use PIO_UNIT_TESTING\n        PIOTEST_SRC_FILTER=[f\"+<*.{ext}>\" for ext in piobuild.SRC_BUILD_EXT],\n    )\n    env.Prepend(CPPPATH=[\"$PROJECT_TEST_DIR\"])\n\n    if \"PIOTEST_RUNNING_NAME\" in env:\n        test_name = env[\"PIOTEST_RUNNING_NAME\"]\n        while True:\n            test_name = os.path.dirname(test_name)  # parent dir\n            # skip nested tests (user's side issue?)\n            if not test_name or os.path.basename(test_name).startswith(\"test_\"):\n                break\n            env.Prepend(\n                PIOTEST_SRC_FILTER=[\n                    f\"+<{test_name}{os.path.sep}*.{ext}>\"\n                    for ext in piobuild.SRC_BUILD_EXT\n                ],\n                CPPPATH=[os.path.join(\"$PROJECT_TEST_DIR\", test_name)],\n            )\n\n        env.Prepend(\n            PIOTEST_SRC_FILTER=[f\"+<$PIOTEST_RUNNING_NAME{os.path.sep}>\"],\n            CPPPATH=[os.path.join(\"$PROJECT_TEST_DIR\", \"$PIOTEST_RUNNING_NAME\")],\n        )\n\n    test_runner = TestRunnerFactory.new(\n        TestSuite(env[\"PIOENV\"], env.get(\"PIOTEST_RUNNING_NAME\", \"*\")),\n        env.GetProjectConfig(),\n    )\n    test_runner.configure_build_env(env)\n\n\ndef generate(env):\n    env.AddMethod(ConfigureTestTarget)\n\n\ndef exists(_):\n    return True\n"
  },
  {
    "path": "platformio/builder/tools/pioupload.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport os\nimport re\nimport sys\nfrom shutil import copyfile\nfrom time import sleep\n\nfrom SCons.Script import ARGUMENTS  # pylint: disable=import-error\nfrom serial import Serial, SerialException\n\nfrom platformio import exception, fs\nfrom platformio.device.finder import SerialPortFinder, find_mbed_disk, is_pattern_port\nfrom platformio.device.list.util import list_serial_ports\nfrom platformio.proc import exec_command\n\n\ndef FlushSerialBuffer(env, port):\n    s = Serial(env.subst(port))\n    s.flushInput()\n    s.setDTR(False)\n    s.setRTS(False)\n    sleep(0.1)\n    s.setDTR(True)\n    s.setRTS(True)\n    s.close()\n\n\ndef TouchSerialPort(env, port, baudrate):\n    port = env.subst(port)\n    print(\"Forcing reset using %dbps open/close on port %s\" % (baudrate, port))\n    try:\n        s = Serial(port=port, baudrate=baudrate)\n        s.setDTR(False)\n        s.close()\n    except:  # pylint: disable=bare-except\n        pass\n    sleep(0.4)  # DO NOT REMOVE THAT (required by SAM-BA based boards)\n\n\ndef WaitForNewSerialPort(env, before):\n    print(\"Waiting for the new upload port...\")\n    prev_port = env.subst(\"$UPLOAD_PORT\")\n    new_port = None\n    elapsed = 0\n    before = [p[\"port\"] for p in before]\n    while elapsed < 5 and new_port is None:\n        now = [p[\"port\"] for p in list_serial_ports()]\n        for p in now:\n            if p not in before:\n                new_port = p\n                break\n        before = now\n        sleep(0.25)\n        elapsed += 0.25\n\n    if not new_port:\n        for p in now:\n            if prev_port == p:\n                new_port = p\n                break\n\n    try:\n        s = Serial(new_port)\n        s.close()\n    except SerialException:\n        sleep(1)\n\n    if not new_port:\n        sys.stderr.write(\n            \"Error: Couldn't find a board on the selected port. \"\n            \"Check that you have the correct port selected. \"\n            \"If it is correct, try pressing the board's reset \"\n            \"button after initiating the upload.\\n\"\n        )\n        env.Exit(1)\n\n    return new_port\n\n\ndef AutodetectUploadPort(*args, **kwargs):\n    env = args[0]\n    initial_port = env.subst(\"$UPLOAD_PORT\")\n    upload_protocol = env.subst(\"$UPLOAD_PROTOCOL\")\n    if initial_port and not is_pattern_port(initial_port):\n        print(env.subst(\"Using manually specified: $UPLOAD_PORT\"))\n        return\n\n    if upload_protocol == \"mbed\" or (\n        \"mbed\" in env.subst(\"$PIOFRAMEWORK\") and not upload_protocol\n    ):\n        env.Replace(UPLOAD_PORT=find_mbed_disk(initial_port))\n    else:\n        try:\n            fs.ensure_udev_rules()\n        except exception.InvalidUdevRules as exc:\n            sys.stderr.write(\"\\n%s\\n\\n\" % exc)\n        env.Replace(\n            UPLOAD_PORT=SerialPortFinder(\n                board_config=env.BoardConfig() if \"BOARD\" in env else None,\n                upload_protocol=upload_protocol,\n                prefer_gdb_port=\"blackmagic\" in upload_protocol,\n                verbose=int(ARGUMENTS.get(\"PIOVERBOSE\", 0)),\n            ).find(initial_port)\n        )\n\n    if env.subst(\"$UPLOAD_PORT\"):\n        print(env.subst(\"Auto-detected: $UPLOAD_PORT\"))\n    else:\n        sys.stderr.write(\n            \"Error: Please specify `upload_port` for environment or use \"\n            \"global `--upload-port` option.\\n\"\n            \"For some development platforms it can be a USB flash \"\n            \"drive (i.e. /media/<user>/<device name>)\\n\"\n        )\n        env.Exit(1)\n\n\ndef UploadToDisk(_, target, source, env):\n    assert \"UPLOAD_PORT\" in env\n    progname = env.subst(\"$PROGNAME\")\n    for ext in (\"bin\", \"hex\"):\n        fpath = os.path.join(env.subst(\"$BUILD_DIR\"), \"%s.%s\" % (progname, ext))\n        if not os.path.isfile(fpath):\n            continue\n        copyfile(\n            fpath, os.path.join(env.subst(\"$UPLOAD_PORT\"), \"%s.%s\" % (progname, ext))\n        )\n    print(\n        \"Firmware has been successfully uploaded.\\n\"\n        \"(Some boards may require manual hard reset)\"\n    )\n\n\ndef CheckUploadSize(_, target, source, env):\n    check_conditions = [\n        env.get(\"BOARD\"),\n        env.get(\"SIZETOOL\") or env.get(\"SIZECHECKCMD\"),\n    ]\n    if not all(check_conditions):\n        return\n    program_max_size = int(env.BoardConfig().get(\"upload.maximum_size\", 0))\n    data_max_size = int(env.BoardConfig().get(\"upload.maximum_ram_size\", 0))\n    if program_max_size == 0:\n        return\n\n    def _configure_defaults():\n        env.Replace(\n            SIZECHECKCMD=\"$SIZETOOL -B -d $SOURCES\",\n            SIZEPROGREGEXP=r\"^(\\d+)\\s+(\\d+)\\s+\\d+\\s\",\n            SIZEDATAREGEXP=r\"^\\d+\\s+(\\d+)\\s+(\\d+)\\s+\\d+\",\n        )\n\n    def _get_size_output():\n        cmd = env.get(\"SIZECHECKCMD\")\n        if not cmd:\n            return None\n        if not isinstance(cmd, list):\n            cmd = cmd.split()\n        cmd = [arg.replace(\"$SOURCES\", str(source[0])) for arg in cmd if arg]\n        sysenv = os.environ.copy()\n        sysenv[\"PATH\"] = str(env[\"ENV\"][\"PATH\"])\n        result = exec_command(env.subst(cmd), env=sysenv)\n        if result[\"returncode\"] != 0:\n            return None\n        return result[\"out\"].strip()\n\n    def _calculate_size(output, pattern):\n        if not output or not pattern:\n            return -1\n        size = 0\n        regexp = re.compile(pattern)\n        for line in output.split(\"\\n\"):\n            line = line.strip()\n            if not line:\n                continue\n            match = regexp.search(line)\n            if not match:\n                continue\n            size += sum(int(value) for value in match.groups())\n        return size\n\n    def _format_availale_bytes(value, total):\n        percent_raw = float(value) / float(total)\n        blocks_per_progress = 10\n        used_blocks = min(\n            int(round(blocks_per_progress * percent_raw)), blocks_per_progress\n        )\n        return \"[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)\".format(\n            \"=\" * used_blocks, blocks_per_progress, percent_raw, value, total\n        )\n\n    if not env.get(\"SIZECHECKCMD\") and not env.get(\"SIZEPROGREGEXP\"):\n        _configure_defaults()\n    output = _get_size_output()\n    program_size = _calculate_size(output, env.get(\"SIZEPROGREGEXP\"))\n    data_size = _calculate_size(output, env.get(\"SIZEDATAREGEXP\"))\n\n    print('Advanced Memory Usage is available via \"PlatformIO Home > Project Inspect\"')\n    if data_max_size and data_size > -1:\n        print(\"RAM:   %s\" % _format_availale_bytes(data_size, data_max_size))\n    if program_size > -1:\n        print(\"Flash: %s\" % _format_availale_bytes(program_size, program_max_size))\n    if int(ARGUMENTS.get(\"PIOVERBOSE\", 0)):\n        print(output)\n\n    if data_max_size and data_size > data_max_size:\n        sys.stderr.write(\n            \"Warning! The data size (%d bytes) is greater \"\n            \"than maximum allowed (%s bytes)\\n\" % (data_size, data_max_size)\n        )\n    if program_size > program_max_size:\n        sys.stderr.write(\n            \"Error: The program size (%d bytes) is greater \"\n            \"than maximum allowed (%s bytes)\\n\" % (program_size, program_max_size)\n        )\n        env.Exit(1)\n\n\ndef PrintUploadInfo(env):\n    configured = env.subst(\"$UPLOAD_PROTOCOL\")\n    available = [configured] if configured else []\n    if \"BOARD\" in env:\n        available.extend(env.BoardConfig().get(\"upload\", {}).get(\"protocols\", []))\n    if available:\n        print(\"AVAILABLE: %s\" % \", \".join(sorted(set(available))))\n    if configured:\n        print(\"CURRENT: upload_protocol = %s\" % configured)\n\n\ndef exists(_):\n    return True\n\n\ndef generate(env):\n    env.AddMethod(FlushSerialBuffer)\n    env.AddMethod(TouchSerialPort)\n    env.AddMethod(WaitForNewSerialPort)\n    env.AddMethod(AutodetectUploadPort)\n    env.AddMethod(UploadToDisk)\n    env.AddMethod(CheckUploadSize)\n    env.AddMethod(PrintUploadInfo)\n    return env\n"
  },
  {
    "path": "platformio/cache.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport os\nfrom time import time\n\nfrom platformio import app, fs\nfrom platformio.compat import hashlib_encode_data\nfrom platformio.package.lockfile import LockFile\nfrom platformio.project.helpers import get_project_cache_dir\n\n\nclass ContentCache:\n    def __init__(self, namespace=None):\n        self.cache_dir = os.path.join(get_project_cache_dir(), namespace or \"content\")\n        self._db_path = os.path.join(self.cache_dir, \"db.data\")\n        self._lockfile = None\n        if not os.path.isdir(self.cache_dir):\n            os.makedirs(self.cache_dir)\n\n    def __enter__(self):\n        # cleanup obsolete items\n        self.delete()\n        return self\n\n    def __exit__(self, type_, value, traceback):\n        pass\n\n    @staticmethod\n    def key_from_args(*args):\n        h = hashlib.sha1()\n        for arg in args:\n            if arg:\n                h.update(hashlib_encode_data(arg))\n        return h.hexdigest()\n\n    def get_cache_path(self, key):\n        assert \"/\" not in key and \"\\\\\" not in key\n        key = str(key)\n        assert len(key) > 3\n        return os.path.join(self.cache_dir, key)\n\n    def get(self, key):\n        cache_path = self.get_cache_path(key)\n        if not os.path.isfile(cache_path):\n            return None\n        with open(cache_path, \"r\", encoding=\"utf8\") as fp:\n            return fp.read()\n\n    def set(self, key, data, valid):\n        if not app.get_setting(\"enable_cache\"):\n            return False\n        cache_path = self.get_cache_path(key)\n        if os.path.isfile(cache_path):\n            self.delete(key)\n        if not data:\n            return False\n        tdmap = {\"s\": 1, \"m\": 60, \"h\": 3600, \"d\": 86400}\n        assert valid.endswith(tuple(tdmap))\n        expire_time = int(time() + tdmap[valid[-1]] * int(valid[:-1]))\n\n        if not self._lock_dbindex():\n            return False\n\n        if not os.path.isdir(os.path.dirname(cache_path)):\n            os.makedirs(os.path.dirname(cache_path))\n        try:\n            with open(cache_path, mode=\"w\", encoding=\"utf8\") as fp:\n                fp.write(data)\n            with open(self._db_path, mode=\"a\", encoding=\"utf8\") as fp:\n                fp.write(\"%s=%s\\n\" % (str(expire_time), os.path.basename(cache_path)))\n        except UnicodeError:\n            if os.path.isfile(cache_path):\n                try:\n                    os.remove(cache_path)\n                except OSError:\n                    pass\n\n        return self._unlock_dbindex()\n\n    def delete(self, keys=None):\n        \"\"\"Keys=None, delete expired items\"\"\"\n        if not os.path.isfile(self._db_path):\n            return None\n        if not keys:\n            keys = []\n        if not isinstance(keys, list):\n            keys = [keys]\n        paths_for_delete = [self.get_cache_path(k) for k in keys]\n        found = False\n        newlines = []\n        with open(self._db_path, encoding=\"utf8\") as fp:\n            for line in fp.readlines():\n                line = line.strip()\n                if \"=\" not in line:\n                    continue\n                expire, fname = line.split(\"=\")\n                path = os.path.join(self.cache_dir, fname)\n                try:\n                    if (\n                        time() < int(expire)\n                        and os.path.isfile(path)\n                        and path not in paths_for_delete\n                    ):\n                        newlines.append(line)\n                        continue\n                except ValueError:\n                    pass\n                found = True\n                if os.path.isfile(path):\n                    try:\n                        os.remove(path)\n                        if not os.listdir(os.path.dirname(path)):\n                            fs.rmtree(os.path.dirname(path))\n                    except OSError:\n                        pass\n\n        if found and self._lock_dbindex():\n            with open(self._db_path, mode=\"w\", encoding=\"utf8\") as fp:\n                fp.write(\"\\n\".join(newlines) + \"\\n\")\n            self._unlock_dbindex()\n\n        return True\n\n    def clean(self):\n        if not os.path.isdir(self.cache_dir):\n            return\n        fs.rmtree(self.cache_dir)\n\n    def _lock_dbindex(self):\n        self._lockfile = LockFile(self.cache_dir)\n        try:\n            self._lockfile.acquire()\n        except:  # pylint: disable=bare-except\n            return False\n\n        return True\n\n    def _unlock_dbindex(self):\n        if self._lockfile:\n            self._lockfile.release()\n        return True\n\n\n#\n# Helpers\n#\n\n\ndef cleanup_content_cache(namespace=None):\n    with ContentCache(namespace) as cc:\n        cc.clean()\n"
  },
  {
    "path": "platformio/check/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/check/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-arguments,too-many-locals,too-many-branches\n# pylint: disable=redefined-builtin,too-many-statements\n\nimport json\nimport os\nimport shutil\nfrom collections import Counter\nfrom time import time\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import app, exception, fs, util\nfrom platformio.check.defect import DefectItem\nfrom platformio.check.tools import CheckToolFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import find_project_dir_above, get_project_dir\n\n\n@click.command(\"check\", short_help=\"Static Code Analysis\")\n@click.option(\"-e\", \"--environment\", multiple=True)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=True, dir_okay=True, writable=True),\n)\n@click.option(\n    \"-c\",\n    \"--project-conf\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),\n)\n@click.option(\"--pattern\", multiple=True, hidden=True)\n@click.option(\"-f\", \"--src-filters\", multiple=True)\n@click.option(\"--flags\", multiple=True)\n@click.option(\n    \"--severity\", multiple=True, type=click.Choice(DefectItem.SEVERITY_LABELS.values())\n)\n@click.option(\"-s\", \"--silent\", is_flag=True)\n@click.option(\"-v\", \"--verbose\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\n@click.option(\n    \"--fail-on-defect\",\n    multiple=True,\n    type=click.Choice(DefectItem.SEVERITY_LABELS.values()),\n)\n@click.option(\"--skip-packages\", is_flag=True)\ndef cli(  # pylint: disable=too-many-positional-arguments\n    environment,\n    project_dir,\n    project_conf,\n    src_filters,\n    pattern,\n    flags,\n    severity,\n    silent,\n    verbose,\n    json_output,\n    fail_on_defect,\n    skip_packages,\n):\n    app.set_session_var(\"custom_project_conf\", project_conf)\n\n    # find project directory on upper level\n    if os.path.isfile(project_dir):\n        project_dir = find_project_dir_above(project_dir)\n\n    results = []\n    with fs.cd(project_dir):\n        config = ProjectConfig.get_instance(project_conf)\n        config.validate(environment)\n\n        default_envs = config.default_envs()\n        for envname in config.envs():\n            skipenv = any(\n                [\n                    environment and envname not in environment,\n                    not environment and default_envs and envname not in default_envs,\n                ]\n            )\n\n            env_options = config.items(env=envname, as_dict=True)\n            env_dump = []\n            for k, v in env_options.items():\n                if k not in (\"platform\", \"framework\", \"board\"):\n                    continue\n                env_dump.append(\n                    \"%s: %s\" % (k, \", \".join(v) if isinstance(v, list) else v)\n                )\n\n            default_src_filters = []\n            for d in (\n                config.get(\"platformio\", \"src_dir\"),\n                config.get(\"platformio\", \"include_dir\"),\n            ):\n                try:\n                    default_src_filters.append(\"+<%s>\" % os.path.relpath(d))\n                except ValueError as exc:\n                    # On Windows if sources are located on a different logical drive\n                    if not json_output and not silent:\n                        click.echo(\n                            \"Error: Project cannot be analyzed! The project folder `%s`\"\n                            \" is located on a different logical drive\\n\" % d\n                        )\n                    raise exception.ReturnErrorCode(1) from exc\n\n            env_src_filters = (\n                src_filters\n                or pattern\n                or env_options.get(\n                    \"check_src_filters\",\n                    env_options.get(\"check_patterns\", default_src_filters),\n                )\n            )\n\n            tool_options = dict(\n                verbose=verbose,\n                silent=silent,\n                src_filters=env_src_filters,\n                flags=flags or env_options.get(\"check_flags\"),\n                severity=(\n                    [DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH]]\n                    if silent\n                    else severity or config.get(\"env:\" + envname, \"check_severity\")\n                ),\n                skip_packages=skip_packages or env_options.get(\"check_skip_packages\"),\n                platform_packages=env_options.get(\"platform_packages\"),\n            )\n\n            for tool in config.get(\"env:\" + envname, \"check_tool\"):\n                if skipenv:\n                    results.append({\"env\": envname, \"tool\": tool})\n                    continue\n                if not silent and not json_output:\n                    print_processing_header(tool, envname, env_dump)\n\n                ct = CheckToolFactory.new(\n                    tool, os.getcwd(), config, envname, tool_options\n                )\n\n                result = {\"env\": envname, \"tool\": tool, \"duration\": time()}\n                rc = ct.check(\n                    on_defect_callback=(\n                        None\n                        if (json_output or verbose)\n                        else lambda defect: click.echo(repr(defect))\n                    )\n                )\n\n                result[\"defects\"] = ct.get_defects()\n                result[\"duration\"] = time() - result[\"duration\"]\n\n                result[\"succeeded\"] = rc == 0\n                if fail_on_defect:\n                    result[\"succeeded\"] = rc == 0 and not any(\n                        DefectItem.SEVERITY_LABELS[d.severity] in fail_on_defect\n                        for d in result[\"defects\"]\n                    )\n                result[\"stats\"] = collect_component_stats(result)\n                results.append(result)\n\n                if verbose:\n                    click.echo(\"\\n\".join(repr(d) for d in result[\"defects\"]))\n\n                if not json_output and not silent:\n                    if rc != 0:\n                        click.echo(\n                            \"Error: %s failed to perform check! Please \"\n                            \"examine tool output in verbose mode.\" % tool\n                        )\n                    elif not result[\"defects\"]:\n                        click.echo(\"No defects found\")\n                    print_processing_footer(result)\n\n        if json_output:\n            click.echo(json.dumps(results_to_json(results)))\n        elif not silent:\n            print_check_summary(results, verbose=verbose)\n\n    # Reset custom project config\n    app.set_session_var(\"custom_project_conf\", None)\n\n    command_failed = any(r.get(\"succeeded\") is False for r in results)\n    if command_failed:\n        raise exception.ReturnErrorCode(1)\n\n\ndef results_to_json(raw):\n    results = []\n    for item in raw:\n        if item.get(\"succeeded\") is None:\n            continue\n        item.update(\n            {\n                \"succeeded\": bool(item.get(\"succeeded\")),\n                \"defects\": [d.as_dict() for d in item.get(\"defects\", [])],\n            }\n        )\n        results.append(item)\n\n    return results\n\n\ndef print_processing_header(tool, envname, envdump):\n    click.echo(\n        \"Checking %s > %s (%s)\"\n        % (click.style(envname, fg=\"cyan\", bold=True), tool, \"; \".join(envdump))\n    )\n    terminal_width = shutil.get_terminal_size().columns\n    click.secho(\"-\" * terminal_width, bold=True)\n\n\ndef print_processing_footer(result):\n    is_failed = not result.get(\"succeeded\")\n    util.print_labeled_bar(\n        \"[%s] Took %.2f seconds\"\n        % (\n            (\n                click.style(\"FAILED\", fg=\"red\", bold=True)\n                if is_failed\n                else click.style(\"PASSED\", fg=\"green\", bold=True)\n            ),\n            result[\"duration\"],\n        ),\n        is_error=is_failed,\n    )\n\n\ndef collect_component_stats(result):\n    components = {}\n\n    def _append_defect(component, defect):\n        if not components.get(component):\n            components[component] = Counter()\n        components[component].update({DefectItem.SEVERITY_LABELS[defect.severity]: 1})\n\n    for defect in result.get(\"defects\", []):\n        component = os.path.dirname(defect.file) or defect.file\n        _append_defect(component, defect)\n\n        if component.lower().startswith(get_project_dir().lower()):\n            while os.sep in component:\n                component = os.path.dirname(component)\n                _append_defect(component, defect)\n\n    return components\n\n\ndef print_defects_stats(results):\n    if not results:\n        return\n\n    component_stats = {}\n    for r in results:\n        for k, v in r.get(\"stats\", {}).items():\n            if not component_stats.get(k):\n                component_stats[k] = Counter()\n            component_stats[k].update(r[\"stats\"][k])\n\n    if not component_stats:\n        return\n\n    severity_labels = list(DefectItem.SEVERITY_LABELS.values())\n    severity_labels.reverse()\n    tabular_data = []\n    for k, v in component_stats.items():\n        tool_defect = [v.get(s, 0) for s in severity_labels]\n        tabular_data.append([k] + tool_defect)\n\n    total = [\"Total\"] + [sum(d) for d in list(zip(*tabular_data))[1:]]\n    tabular_data.sort()\n    tabular_data.append([])  # Empty line as delimiter\n    tabular_data.append(total)\n\n    headers = [\"Component\"]\n    headers.extend([label.upper() for label in severity_labels])\n    headers = [click.style(h, bold=True) for h in headers]\n    click.echo(tabulate(tabular_data, headers=headers, numalign=\"center\"))\n    click.echo()\n\n\ndef print_check_summary(results, verbose=False):\n    click.echo()\n\n    tabular_data = []\n    succeeded_nums = 0\n    failed_nums = 0\n    duration = 0\n\n    print_defects_stats(results)\n\n    for result in results:\n        duration += result.get(\"duration\", 0)\n        if result.get(\"succeeded\") is False:\n            failed_nums += 1\n            status_str = click.style(\"FAILED\", fg=\"red\")\n        elif result.get(\"succeeded\") is None:\n            status_str = \"IGNORED\"\n            if not verbose:\n                continue\n        else:\n            succeeded_nums += 1\n            status_str = click.style(\"PASSED\", fg=\"green\")\n\n        tabular_data.append(\n            (\n                click.style(result[\"env\"], fg=\"cyan\"),\n                result[\"tool\"],\n                status_str,\n                util.humanize_duration_time(result.get(\"duration\")),\n            )\n        )\n\n    click.echo(\n        tabulate(\n            tabular_data,\n            headers=[\n                click.style(s, bold=True)\n                for s in (\"Environment\", \"Tool\", \"Status\", \"Duration\")\n            ],\n        ),\n        err=failed_nums,\n    )\n\n    util.print_labeled_bar(\n        \"%s%d succeeded in %s\"\n        % (\n            \"%d failed, \" % failed_nums if failed_nums else \"\",\n            succeeded_nums,\n            util.humanize_duration_time(duration),\n        ),\n        is_error=failed_nums,\n        fg=\"red\" if failed_nums else \"green\",\n    )\n"
  },
  {
    "path": "platformio/check/defect.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\n\nfrom platformio.exception import PlatformioException\nfrom platformio.project.helpers import get_project_dir\n\n# pylint: disable=too-many-instance-attributes, redefined-builtin\n# pylint: disable=too-many-arguments\n\n\nclass DefectItem:\n    SEVERITY_HIGH = 1\n    SEVERITY_MEDIUM = 2\n    SEVERITY_LOW = 4\n    SEVERITY_LABELS = {4: \"low\", 2: \"medium\", 1: \"high\"}\n\n    def __init__(  # pylint: disable=too-many-positional-arguments\n        self,\n        severity,\n        category,\n        message,\n        file=None,\n        line=0,\n        column=0,\n        id=None,\n        callstack=None,\n        cwe=None,\n    ):\n        assert severity in (self.SEVERITY_HIGH, self.SEVERITY_MEDIUM, self.SEVERITY_LOW)\n        self.severity = severity\n        self.category = category\n        self.message = message\n        self.line = int(line)\n        self.column = int(column)\n        self.callstack = callstack\n        self.cwe = cwe\n        self.id = id\n        self.file = file or \"unknown\"\n        if file.lower().startswith(get_project_dir().lower()):\n            self.file = os.path.relpath(file, get_project_dir())\n\n    def __repr__(self):\n        defect_color = None\n        if self.severity == self.SEVERITY_HIGH:\n            defect_color = \"red\"\n        elif self.severity == self.SEVERITY_MEDIUM:\n            defect_color = \"yellow\"\n\n        format_str = \"{file}:{line}: [{severity}:{category}] {message} {id}\"\n        return format_str.format(\n            severity=click.style(self.SEVERITY_LABELS[self.severity], fg=defect_color),\n            category=click.style(self.category.lower(), fg=defect_color),\n            file=click.style(self.file, bold=True),\n            message=self.message,\n            line=self.line,\n            id=\"%s\" % \"[%s]\" % self.id if self.id else \"\",\n        )\n\n    def __or__(self, defect):\n        return self.severity | defect.severity\n\n    @staticmethod\n    def severity_to_int(label):\n        for key, value in DefectItem.SEVERITY_LABELS.items():\n            if label == value:\n                return key\n        raise PlatformioException(\"Unknown severity label -> %s\" % label)\n\n    def as_dict(self):\n        return {\n            \"severity\": self.SEVERITY_LABELS[self.severity],\n            \"category\": self.category,\n            \"message\": self.message,\n            \"file\": os.path.abspath(self.file),\n            \"line\": self.line,\n            \"column\": self.column,\n            \"callstack\": self.callstack,\n            \"id\": self.id,\n            \"cwe\": self.cwe,\n        }\n"
  },
  {
    "path": "platformio/check/tools/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio import exception\nfrom platformio.check.tools.clangtidy import ClangtidyCheckTool\nfrom platformio.check.tools.cppcheck import CppcheckCheckTool\nfrom platformio.check.tools.pvsstudio import PvsStudioCheckTool\n\n\nclass CheckToolFactory:\n    @staticmethod\n    def new(tool, project_dir, config, envname, options):\n        cls = None\n        if tool == \"cppcheck\":\n            cls = CppcheckCheckTool\n        elif tool == \"clangtidy\":\n            cls = ClangtidyCheckTool\n        elif tool == \"pvs-studio\":\n            cls = PvsStudioCheckTool\n        else:\n            raise exception.PlatformioException(\"Unknown check tool `%s`\" % tool)\n        return cls(project_dir, config, envname, options)\n"
  },
  {
    "path": "platformio/check/tools/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport tempfile\n\nimport click\n\nfrom platformio import fs, proc\nfrom platformio.check.defect import DefectItem\nfrom platformio.package.manager.core import get_core_package_dir\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.helpers import load_build_metadata\n\n\nclass CheckToolBase:  # pylint: disable=too-many-instance-attributes\n    def __init__(self, project_dir, config, envname, options):\n        self.config = config\n        self.envname = envname\n        self.options = options\n        self.project_dir = project_dir\n        self.cc_flags = []\n        self.cxx_flags = []\n        self.cpp_includes = []\n        self.cpp_defines = []\n        self.toolchain_defines = []\n        self._tmp_files = []\n        self.cc_path = None\n        self.cxx_path = None\n        self._defects = []\n        self._on_defect_callback = None\n        self._bad_input = False\n        self._load_cpp_data()\n\n        # detect all defects by default\n        if not self.options.get(\"severity\"):\n            self.options[\"severity\"] = [\n                DefectItem.SEVERITY_LOW,\n                DefectItem.SEVERITY_MEDIUM,\n                DefectItem.SEVERITY_HIGH,\n            ]\n        # cast to severity by ids\n        self.options[\"severity\"] = [\n            s if isinstance(s, int) else DefectItem.severity_to_int(s)\n            for s in self.options[\"severity\"]\n        ]\n\n    def _load_cpp_data(self):\n        data = load_build_metadata(self.project_dir, self.envname)\n        if not data:\n            return\n        self.cc_flags = data.get(\"cc_flags\", [])\n        self.cxx_flags = data.get(\"cxx_flags\", [])\n        self.cpp_includes = self._dump_includes(data.get(\"includes\", {}))\n        self.cpp_defines = data.get(\"defines\", [])\n        self.cc_path = data.get(\"cc_path\")\n        self.cxx_path = data.get(\"cxx_path\")\n        self.toolchain_defines = self._get_toolchain_defines()\n\n    def get_tool_dir(self, pkg_name):\n        for spec in self.options[\"platform_packages\"] or []:\n            spec = PackageSpec(spec)\n            if spec.name == pkg_name:\n                return get_core_package_dir(pkg_name, spec=spec)\n        return get_core_package_dir(pkg_name)\n\n    def get_flags(self, tool):\n        result = []\n        flags = self.options.get(\"flags\") or []\n        for flag in flags:\n            if \":\" not in flag or flag.startswith(\"-\"):\n                result.extend([f for f in flag.split(\" \") if f])\n            elif flag.startswith(\"%s:\" % tool):\n                result.extend([f for f in flag.split(\":\", 1)[1].split(\" \") if f])\n\n        return result\n\n    def _get_toolchain_defines(self):\n        def _extract_defines(language, includes_file):\n            build_flags = self.cxx_flags if language == \"c++\" else self.cc_flags\n            defines = []\n            cmd = 'echo | \"%s\" -x %s %s %s -dM -E -' % (\n                self.cc_path,\n                language,\n                \" \".join(\n                    [f for f in build_flags if f.startswith((\"-m\", \"-f\", \"-std\"))]\n                ),\n                includes_file,\n            )\n            result = proc.exec_command(cmd, shell=True)\n\n            if result[\"returncode\"] != 0:\n                click.echo(\"Warning: Failed to extract toolchain defines!\")\n                if self.options.get(\"verbose\"):\n                    click.echo(result[\"out\"])\n                    click.echo(result[\"err\"])\n\n            for line in result[\"out\"].split(\"\\n\"):\n                tokens = line.strip().split(\" \", 2)\n                if not tokens or tokens[0] != \"#define\":\n                    continue\n                if len(tokens) > 2:\n                    defines.append(\"%s=%s\" % (tokens[1], tokens[2]))\n                else:\n                    defines.append(tokens[1])\n\n            return defines\n\n        incflags_file = self._long_includes_hook(self.cpp_includes)\n        return {lang: _extract_defines(lang, incflags_file) for lang in (\"c\", \"c++\")}\n\n    def _create_tmp_file(self, data):\n        with tempfile.NamedTemporaryFile(\"w\", delete=False) as fp:\n            fp.write(data)\n            self._tmp_files.append(fp.name)\n            return fp.name\n\n    def _long_includes_hook(self, includes):\n        data = []\n        for inc in includes:\n            data.append('-I\"%s\"' % fs.to_unix_path(inc))\n\n        return '@\"%s\"' % self._create_tmp_file(\" \".join(data))\n\n    @staticmethod\n    def _dump_includes(includes_map):\n        result = []\n        for includes in includes_map.values():\n            for include in includes:\n                if include not in result:\n                    result.append(include)\n        return result\n\n    @staticmethod\n    def is_flag_set(flag, flags):\n        return any(flag in f for f in flags)\n\n    def get_defects(self):\n        return self._defects\n\n    def configure_command(self):\n        raise NotImplementedError\n\n    def on_tool_output(self, line):\n        line = self.tool_output_filter(line)\n        if not line:\n            return\n\n        defect = self.parse_defect(line)\n\n        if not isinstance(defect, DefectItem):\n            if self.options.get(\"verbose\"):\n                click.echo(line)\n            return\n\n        if defect.severity not in self.options[\"severity\"]:\n            return\n\n        self._defects.append(defect)\n        if self._on_defect_callback:\n            self._on_defect_callback(defect)\n\n    @staticmethod\n    def tool_output_filter(line):\n        return line\n\n    @staticmethod\n    def parse_defect(raw_line):\n        return raw_line\n\n    def clean_up(self):\n        for f in self._tmp_files:\n            if os.path.isfile(f):\n                os.remove(f)\n\n    @staticmethod\n    def is_check_successful(cmd_result):\n        return cmd_result[\"returncode\"] == 0\n\n    def execute_check_cmd(self, cmd):\n        result = proc.exec_command(\n            cmd,\n            stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),\n            stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),\n        )\n\n        if not self.is_check_successful(result):\n            click.echo(\n                \"\\nError: Failed to execute check command! Exited with code %d.\"\n                % result[\"returncode\"]\n            )\n            if self.options.get(\"verbose\"):\n                click.echo(result[\"out\"])\n                click.echo(result[\"err\"])\n            self._bad_input = True\n\n        return result\n\n    @staticmethod\n    def get_project_target_files(project_dir, src_filters):\n        c_extension = (\".c\",)\n        cpp_extensions = (\".cc\", \".cpp\", \".cxx\", \".ino\")\n        header_extensions = (\".h\", \".hh\", \".hpp\", \".hxx\")\n\n        result = {\"c\": [], \"c++\": [], \"headers\": []}\n\n        def _add_file(path):\n            if path.endswith(header_extensions):\n                result[\"headers\"].append(os.path.abspath(path))\n            elif path.endswith(c_extension):\n                result[\"c\"].append(os.path.abspath(path))\n            elif path.endswith(cpp_extensions):\n                result[\"c++\"].append(os.path.abspath(path))\n\n        src_filters = normalize_src_filters(src_filters)\n        for f in fs.match_src_files(project_dir, src_filters):\n            _add_file(f)\n\n        return result\n\n    def check(self, on_defect_callback=None):\n        self._on_defect_callback = on_defect_callback\n        cmd = self.configure_command()\n        if cmd:\n            if self.options.get(\"verbose\"):\n                click.echo(\" \".join(cmd))\n\n            self.execute_check_cmd(cmd)\n\n        else:\n            if self.options.get(\"verbose\"):\n                click.echo(\"Error: Couldn't configure command\")\n            self._bad_input = True\n\n        self.clean_up()\n\n        return self._bad_input\n\n\n#\n# Helpers\n#\n\n\ndef normalize_src_filters(src_filters):\n    def _normalize(src_filters):\n        return (\n            src_filters\n            if src_filters.startswith((\"+<\", \"-<\"))\n            else \"+<%s>\" % src_filters\n        )\n\n    if isinstance(src_filters, (list, tuple)):\n        return \" \".join([_normalize(f) for f in src_filters])\n\n    return _normalize(src_filters)\n"
  },
  {
    "path": "platformio/check/tools/clangtidy.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nfrom os.path import join\n\nfrom platformio.check.defect import DefectItem\nfrom platformio.check.tools.base import CheckToolBase\n\n\nclass ClangtidyCheckTool(CheckToolBase):\n    def tool_output_filter(self, line):  # pylint: disable=arguments-differ\n        if not self.options.get(\"verbose\") and \"[clang-diagnostic-error]\" in line:\n            return \"\"\n\n        if \"[CommonOptionsParser]\" in line:\n            self._bad_input = True\n            return line\n\n        if any(d in line for d in (\"note: \", \"error: \", \"warning: \")):\n            return line\n\n        return \"\"\n\n    def parse_defect(self, raw_line):  # pylint: disable=arguments-differ\n        match = re.match(r\"^(.*):(\\d+):(\\d+):\\s+([^:]+):\\s(.+)\\[([^]]+)\\]$\", raw_line)\n        if not match:\n            return raw_line\n\n        file_, line, column, category, message, defect_id = match.groups()\n\n        severity = DefectItem.SEVERITY_LOW\n        if category == \"error\":\n            severity = DefectItem.SEVERITY_HIGH\n        elif category == \"warning\":\n            severity = DefectItem.SEVERITY_MEDIUM\n\n        return DefectItem(severity, category, message, file_, line, column, defect_id)\n\n    @staticmethod\n    def is_check_successful(cmd_result):\n        # Note: Clang-Tidy returns 1 for not critical compilation errors,\n        # so 0 and 1 are only acceptable values\n        return cmd_result[\"returncode\"] < 2\n\n    def configure_command(self):\n        tool_path = join(self.get_tool_dir(\"tool-clangtidy\"), \"clang-tidy\")\n\n        cmd = [tool_path, \"--quiet\"]\n        flags = self.get_flags(\"clangtidy\")\n        if not (\n            self.is_flag_set(\"--checks\", flags) or self.is_flag_set(\"--config\", flags)\n        ):\n            cmd.append(\"--checks=*\")\n\n        project_files = self.get_project_target_files(\n            self.project_dir, self.options[\"src_filters\"]\n        )\n\n        src_files = []\n        for items in project_files.values():\n            src_files.extend(items)\n\n        cmd.extend(flags + src_files + [\"--\"])\n        cmd.extend(\n            [\"-D%s\" % d for d in self.cpp_defines + self.toolchain_defines[\"c++\"]]\n        )\n\n        includes = []\n        for inc in self.cpp_includes:\n            if self.options.get(\"skip_packages\") and inc.lower().startswith(\n                self.config.get(\"platformio\", \"packages_dir\").lower()\n            ):\n                continue\n            includes.append(inc)\n\n        cmd.extend([\"-I%s\" % inc for inc in includes])\n\n        return cmd\n"
  },
  {
    "path": "platformio/check/tools/cppcheck.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\n\nfrom platformio import proc\nfrom platformio.check.defect import DefectItem\nfrom platformio.check.tools.base import CheckToolBase\n\n\nclass CppcheckCheckTool(CheckToolBase):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._field_delimiter = \"<&PIO&>\"\n        self._buffer = \"\"\n        self.defect_fields = [\n            \"severity\",\n            \"message\",\n            \"file\",\n            \"line\",\n            \"column\",\n            \"callstack\",\n            \"cwe\",\n            \"id\",\n        ]\n\n    def tool_output_filter(self, line):  # pylint: disable=arguments-differ\n        if (\n            not self.options.get(\"verbose\")\n            and \"--suppress=unmatchedSuppression:\" in line\n        ):\n            return \"\"\n\n        if any(\n            msg in line\n            for msg in (\n                \"No C or C++ source files found\",\n                \"unrecognized command line option\",\n                \"there was an internal error\",\n            )\n        ):\n            self._bad_input = True\n\n        return line\n\n    def parse_defect(self, raw_line):  # pylint: disable=arguments-differ\n        if self._field_delimiter not in raw_line:\n            return None\n\n        self._buffer += raw_line\n        if any(f not in self._buffer for f in self.defect_fields):\n            return None\n\n        args = {}\n        for field in self._buffer.split(self._field_delimiter):\n            field = field.strip().replace('\"', \"\")\n            name, value = field.split(\"=\", 1)\n            args[name] = value\n\n        args[\"category\"] = args[\"severity\"]\n        if args[\"severity\"] == \"error\":\n            args[\"severity\"] = DefectItem.SEVERITY_HIGH\n        elif args[\"severity\"] == \"warning\":\n            args[\"severity\"] = DefectItem.SEVERITY_MEDIUM\n        else:\n            args[\"severity\"] = DefectItem.SEVERITY_LOW\n\n        # Skip defects found in third-party software, but keep in mind that such defects\n        # might break checking process so defects from project files are not reported\n        breaking_defect_ids = (\"preprocessorErrorDirective\", \"syntaxError\")\n        if (\n            args.get(\"file\", \"\")\n            .lower()\n            .startswith(self.config.get(\"platformio\", \"packages_dir\").lower())\n        ):\n            if args[\"id\"] in breaking_defect_ids:\n                if self.options.get(\"verbose\"):\n                    click.echo(\n                        \"Error: Found a breaking defect '%s' in %s:%s\\n\"\n                        \"Please note: check results might not be valid!\\n\"\n                        \"Try adding --skip-packages\"\n                        % (args.get(\"message\"), args.get(\"file\"), args.get(\"line\"))\n                    )\n                    click.echo()\n                self._bad_input = True\n            self._buffer = \"\"\n            return None\n\n        self._buffer = \"\"\n        return DefectItem(**args)\n\n    def configure_command(self, language, src_file):  # pylint: disable=arguments-differ\n        tool_path = os.path.join(self.get_tool_dir(\"tool-cppcheck\"), \"cppcheck\")\n\n        cmd = [\n            tool_path,\n            \"--addon-python=%s\" % proc.get_pythonexe_path(),\n            \"--error-exitcode=3\",\n            \"--verbose\" if self.options.get(\"verbose\") else \"--quiet\",\n        ]\n\n        cmd.append(\n            '--template=\"%s\"'\n            % self._field_delimiter.join(\n                [\"{0}={{{0}}}\".format(f) for f in self.defect_fields]\n            )\n        )\n\n        flags = self.get_flags(\"cppcheck\")\n        if not flags:\n            # by default user can suppress reporting individual defects\n            # directly in code // cppcheck-suppress warningID\n            cmd.append(\"--inline-suppr\")\n        if not self.is_flag_set(\"--platform\", flags):\n            cmd.append(\"--platform=unspecified\")\n        if not self.is_flag_set(\"--enable\", flags):\n            enabled_checks = [\n                \"warning\",\n                \"style\",\n                \"performance\",\n                \"portability\",\n                \"unusedFunction\",\n            ]\n            cmd.append(\"--enable=%s\" % \",\".join(enabled_checks))\n\n        if not self.is_flag_set(\"--language\", flags):\n            cmd.append(\"--language=\" + language)\n\n        build_flags = self.cxx_flags if language == \"c++\" else self.cc_flags\n\n        if not self.is_flag_set(\"--std\", flags):\n            # Try to guess the standard version from the build flags\n            for flag in build_flags:\n                if \"-std\" in flag:\n                    cmd.append(\"-\" + self.convert_language_standard(flag))\n\n        cmd.extend(\n            [\"-D%s\" % d for d in self.cpp_defines + self.toolchain_defines[language]]\n        )\n\n        cmd.extend(flags)\n\n        cmd.extend(\n            \"--include=\" + inc\n            for inc in self.get_forced_includes(build_flags, self.cpp_includes)\n        )\n        cmd.append(\"--includes-file=%s\" % self._generate_inc_file())\n        cmd.append('\"%s\"' % src_file)\n\n        return cmd\n\n    @staticmethod\n    def get_forced_includes(build_flags, includes):\n        def _extract_filepath(flag, include_options, build_flags):\n            path = \"\"\n            for option in include_options:\n                if not flag.startswith(option):\n                    continue\n                if flag.split(option)[1].strip():\n                    path = flag.split(option)[1].strip()\n                elif build_flags.index(flag) + 1 < len(build_flags):\n                    path = build_flags[build_flags.index(flag) + 1]\n            return path\n\n        def _search_include_dir(filepath, include_paths):\n            for inc_path in include_paths:\n                path = os.path.join(inc_path, filepath)\n                if os.path.isfile(path):\n                    return path\n            return \"\"\n\n        result = []\n        include_options = (\"-include\", \"-imacros\")\n        for f in build_flags:\n            if f.startswith(include_options):\n                filepath = _extract_filepath(f, include_options, build_flags)\n                if not os.path.isabs(filepath):\n                    filepath = _search_include_dir(filepath, includes)\n                if os.path.isfile(filepath):\n                    result.append(filepath)\n\n        return result\n\n    def _generate_src_file(self, src_files):\n        return self._create_tmp_file(\"\\n\".join(src_files))\n\n    def _generate_inc_file(self):\n        result = []\n        for inc in self.cpp_includes:\n            if self.options.get(\"skip_packages\") and inc.lower().startswith(\n                self.config.get(\"platformio\", \"packages_dir\").lower()\n            ):\n                continue\n            result.append(inc)\n        return self._create_tmp_file(\"\\n\".join(result))\n\n    def clean_up(self):\n        super().clean_up()\n\n        # delete temporary dump files generated by addons\n        if not self.is_flag_set(\"--addon\", self.get_flags(\"cppcheck\")):\n            return\n\n        for files in self.get_project_target_files(\n            self.project_dir, self.options[\"src_filters\"]\n        ).values():\n            for f in files:\n                dump_file = f + \".dump\"\n                if os.path.isfile(dump_file):\n                    os.remove(dump_file)\n\n    @staticmethod\n    def is_check_successful(cmd_result):\n        # Cppcheck is configured to return '3' if a defect is found\n        return cmd_result[\"returncode\"] in (0, 3)\n\n    @staticmethod\n    def convert_language_standard(flag):\n        cpp_standards_map = {\n            \"0x\": \"11\",\n            \"1y\": \"14\",\n            \"1z\": \"17\",\n            \"2a\": \"20\",\n        }\n\n        standard = flag[-2:]\n        # Note: GNU extensions are not supported and converted to regular standards\n        return flag.replace(\"gnu\", \"c\").replace(\n            standard, cpp_standards_map.get(standard, standard)\n        )\n\n    def check(self, on_defect_callback=None):\n        self._on_defect_callback = on_defect_callback\n\n        project_files = self.get_project_target_files(\n            self.project_dir, self.options[\"src_filters\"]\n        )\n        src_files_scope = (\"c\", \"c++\")\n        if not any(project_files[t] for t in src_files_scope):\n            click.echo(\"Error: Nothing to check.\")\n            return True\n\n        for scope, files in project_files.items():\n            if scope not in src_files_scope:\n                continue\n            for src_file in files:\n                cmd = self.configure_command(scope, src_file)\n                if not cmd:\n                    self._bad_input = True\n                    continue\n                if self.options.get(\"verbose\"):\n                    click.echo(\" \".join(cmd))\n\n                self.execute_check_cmd(cmd)\n\n        self.clean_up()\n\n        return self._bad_input\n"
  },
  {
    "path": "platformio/check/tools/pvsstudio.py",
    "content": "# Copyright (c) 2020-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport tempfile\nfrom xml.etree.ElementTree import fromstring\n\nimport click\n\nfrom platformio import proc\nfrom platformio.check.defect import DefectItem\nfrom platformio.check.tools.base import CheckToolBase\nfrom platformio.compat import IS_WINDOWS\n\n\nclass PvsStudioCheckTool(CheckToolBase):  # pylint: disable=too-many-instance-attributes\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._tmp_dir = tempfile.mkdtemp(prefix=\"piocheck\")\n        self._tmp_preprocessed_file = self._generate_tmp_file_path() + \".i\"\n        self._tmp_output_file = self._generate_tmp_file_path() + \".pvs\"\n        self._tmp_cfg_file = self._generate_tmp_file_path() + \".cfg\"\n        self._tmp_cmd_file = self._generate_tmp_file_path() + \".cmd\"\n        self.tool_path = os.path.join(\n            self.get_tool_dir(\"tool-pvs-studio\"),\n            \"x64\" if IS_WINDOWS else \"bin\",\n            \"pvs-studio\",\n        )\n\n        with open(self._tmp_cfg_file, mode=\"w\", encoding=\"utf8\") as fp:\n            fp.write(\n                \"exclude-path = \"\n                + self.config.get(\"platformio\", \"packages_dir\").replace(\"\\\\\", \"/\")\n            )\n\n        with open(self._tmp_cmd_file, mode=\"w\", encoding=\"utf8\") as fp:\n            fp.write(\n                \" \".join(\n                    ['-I\"%s\"' % inc.replace(\"\\\\\", \"/\") for inc in self.cpp_includes]\n                )\n            )\n\n    def tool_output_filter(self, line):  # pylint: disable=arguments-differ\n        if any(\n            err_msg in line.lower()\n            for err_msg in (\n                \"license was not entered\",\n                \"license information is incorrect\",\n            )\n        ):\n            self._bad_input = True\n        return line\n\n    def _process_defects(self, defects):\n        for defect in defects:\n            if not isinstance(defect, DefectItem):\n                return\n            if defect.severity not in self.options[\"severity\"]:\n                return\n            self._defects.append(defect)\n            if self._on_defect_callback:\n                self._on_defect_callback(defect)\n\n    def _demangle_report(self, output_file):\n        converter_tool = os.path.join(\n            self.get_tool_dir(\"tool-pvs-studio\"),\n            \"HtmlGenerator\" if IS_WINDOWS else os.path.join(\"bin\", \"plog-converter\"),\n        )\n\n        cmd = (\n            converter_tool,\n            \"-t\",\n            \"xml\",\n            output_file,\n            \"-m\",\n            \"cwe\",\n            \"-m\",\n            \"misra\",\n            \"-a\",\n            # Enable all possible analyzers and defect levels\n            \"GA:1,2,3;64:1,2,3;OP:1,2,3;CS:1,2,3;MISRA:1,2,3\",\n            \"--cerr\",\n        )\n\n        result = proc.exec_command(cmd)\n        if result[\"returncode\"] != 0:\n            click.echo(result[\"err\"])\n            self._bad_input = True\n\n        return result[\"err\"]\n\n    def parse_defects(self, output_file):\n        defects = []\n\n        report = self._demangle_report(output_file)\n        if not report:\n            self._bad_input = True\n            return []\n\n        try:\n            defects_data = fromstring(report)\n        except:  # pylint: disable=bare-except\n            click.echo(\"Error: Couldn't decode generated report!\")\n            self._bad_input = True\n            return []\n\n        for table in defects_data.iter(\"PVS-Studio_Analysis_Log\"):\n            message = table.find(\"Message\").text\n            category = table.find(\"ErrorType\").text\n            line = table.find(\"Line\").text\n            file_ = table.find(\"File\").text\n            defect_id = table.find(\"ErrorCode\").text\n            cwe = table.find(\"CWECode\")\n            cwe_id = None\n            if cwe is not None:\n                cwe_id = cwe.text.lower().replace(\"cwe-\", \"\")\n            misra = table.find(\"MISRA\")\n            if misra is not None:\n                message += \" [%s]\" % misra.text\n\n            severity = DefectItem.SEVERITY_LOW\n            if category == \"error\":\n                severity = DefectItem.SEVERITY_HIGH\n            elif category == \"warning\":\n                severity = DefectItem.SEVERITY_MEDIUM\n\n            defects.append(\n                DefectItem(\n                    severity, category, message, file_, line, id=defect_id, cwe=cwe_id\n                )\n            )\n\n        return defects\n\n    def configure_command(self, src_file):  # pylint: disable=arguments-differ\n        if os.path.isfile(self._tmp_output_file):\n            os.remove(self._tmp_output_file)\n\n        if not os.path.isfile(self._tmp_preprocessed_file):\n            click.echo(\"Error: Missing preprocessed file for '%s'\" % src_file)\n            return \"\"\n\n        cmd = [\n            self.tool_path,\n            \"--skip-cl-exe\",\n            \"yes\",\n            \"--language\",\n            \"C\" if src_file.endswith(\".c\") else \"C++\",\n            \"--preprocessor\",\n            \"gcc\",\n            \"--cfg\",\n            self._tmp_cfg_file,\n            \"--source-file\",\n            src_file,\n            \"--i-file\",\n            self._tmp_preprocessed_file,\n            \"--output-file\",\n            self._tmp_output_file,\n        ]\n\n        flags = self.get_flags(\"pvs-studio\")\n        if not self.is_flag_set(\"--platform\", flags):\n            cmd.append(\"--platform=arm\")\n        cmd.extend(flags)\n\n        return cmd\n\n    def _generate_tmp_file_path(self):\n        # pylint: disable=protected-access\n        return os.path.join(self._tmp_dir, next(tempfile._get_candidate_names()))\n\n    def _prepare_preprocessed_file(self, src_file):\n        if os.path.isfile(self._tmp_preprocessed_file):\n            os.remove(self._tmp_preprocessed_file)\n\n        flags = self.cxx_flags\n        compiler = self.cxx_path\n        if src_file.endswith(\".c\"):\n            flags = self.cc_flags\n            compiler = self.cc_path\n\n        cmd = [\n            '\"%s\"' % compiler,\n            '\"%s\"' % src_file,\n            \"-E\",\n            \"-o\",\n            '\"%s\"' % self._tmp_preprocessed_file,\n        ]\n        cmd.extend([f for f in flags if f])\n        cmd.extend(['\"-D%s\"' % d.replace('\"', '\\\\\"') for d in self.cpp_defines])\n        cmd.append('@\"%s\"' % self._tmp_cmd_file)\n\n        # Explicitly specify C++ as the language used in .ino files\n        if src_file.endswith(\".ino\"):\n            cmd.insert(1, \"-xc++\")\n\n        result = proc.exec_command(\" \".join(cmd), shell=True)\n        if result[\"returncode\"] != 0 or result[\"err\"]:\n            if self.options.get(\"verbose\"):\n                click.echo(\" \".join(cmd))\n            click.echo(result[\"err\"])\n            self._bad_input = True\n\n    def clean_up(self):\n        super().clean_up()\n        if os.path.isdir(self._tmp_dir):\n            shutil.rmtree(self._tmp_dir)\n\n    @staticmethod\n    def is_check_successful(cmd_result):\n        return (\n            \"license\" not in cmd_result[\"err\"].lower() and cmd_result[\"returncode\"] == 0\n        )\n\n    def check(self, on_defect_callback=None):\n        self._on_defect_callback = on_defect_callback\n        for scope, files in self.get_project_target_files(\n            self.project_dir, self.options[\"src_filters\"]\n        ).items():\n            if scope not in (\"c\", \"c++\"):\n                continue\n            for src_file in files:\n                self._prepare_preprocessed_file(src_file)\n                cmd = self.configure_command(src_file)\n                if self.options.get(\"verbose\"):\n                    click.echo(\" \".join(cmd))\n                if not cmd:\n                    self._bad_input = True\n                    continue\n\n                result = self.execute_check_cmd(cmd)\n                if result[\"returncode\"] != 0:\n                    continue\n\n                self._process_defects(self.parse_defects(self._tmp_output_file))\n\n        self.clean_up()\n\n        return self._bad_input\n"
  },
  {
    "path": "platformio/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport importlib\nfrom pathlib import Path\n\nimport click\n\n\nclass PlatformioCLI(click.Group):\n    leftover_args = []\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._pio_root_path = Path(__file__).parent\n        self._pio_cmd_aliases = dict(package=\"pkg\")\n\n    def _find_pio_commands(self):\n        def _to_module_path(p):\n            return (\n                \"platformio.\" + \".\".join(p.relative_to(self._pio_root_path).parts)[:-3]\n            )\n\n        result = {}\n        for p in self._pio_root_path.rglob(\"cli.py\"):\n            # skip this module\n            if p.parent == self._pio_root_path:\n                continue\n            cmd_name = p.parent.name\n            result[self._pio_cmd_aliases.get(cmd_name, cmd_name)] = _to_module_path(p)\n\n        # find legacy commands\n        for p in (self._pio_root_path / \"commands\").iterdir():\n            if p.name.startswith(\"_\"):\n                continue\n            if (p / \"command.py\").is_file():\n                result[p.name] = _to_module_path(p / \"command.py\")\n            elif p.name.endswith(\".py\"):\n                result[p.name[:-3]] = _to_module_path(p)\n\n        return result\n\n    @staticmethod\n    def in_silence():\n        args = PlatformioCLI.leftover_args\n        return args and any(\n            [\n                args[0] == \"debug\" and \"--interpreter\" in \" \".join(args),\n                args[0] == \"upgrade\",\n                \"--json-output\" in args,\n                \"--version\" in args,\n            ]\n        )\n\n    @classmethod\n    def reveal_cmd_path_args(cls, ctx):\n        result = []\n        group = ctx.command\n        args = cls.leftover_args[::]\n        while args:\n            cmd_name = args.pop(0)\n            next_group = group.get_command(ctx, cmd_name)\n            if next_group:\n                group = next_group\n                result.append(cmd_name)\n            if not hasattr(group, \"get_command\"):\n                break\n        return result\n\n    def invoke(self, ctx):\n        PlatformioCLI.leftover_args = ctx.args\n        if hasattr(ctx, \"protected_args\"):\n            PlatformioCLI.leftover_args = ctx.protected_args + ctx.args\n        return super().invoke(ctx)\n\n    def list_commands(self, ctx):  # pylint: disable=unused-argument\n        return sorted(list(self._find_pio_commands()))\n\n    def get_command(self, ctx, cmd_name):\n        commands = self._find_pio_commands()\n        if cmd_name not in commands:\n            return self._handle_obsolate_command(ctx, cmd_name)\n        module = importlib.import_module(commands[cmd_name])\n        return getattr(module, \"cli\")\n\n    @staticmethod\n    def _handle_obsolate_command(ctx, cmd_name):\n        # pylint: disable=import-outside-toplevel\n        if cmd_name == \"init\":\n            from platformio.project.commands.init import project_init_cmd\n\n            return project_init_cmd\n\n        if cmd_name == \"package\":\n            from platformio.package.cli import cli\n\n            return cli\n\n        raise click.UsageError('No such command \"%s\"' % cmd_name, ctx)\n"
  },
  {
    "path": "platformio/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/commands/boards.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport shutil\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.package.manager.platform import PlatformPackageManager\n\n\n@click.command(\"boards\", short_help=\"Board Explorer\")\n@click.argument(\"query\", required=False)\n@click.option(\"--installed\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\ndef cli(query, installed, json_output):  # pylint: disable=R0912\n    if json_output:\n        return _print_boards_json(query, installed)\n\n    grpboards = {}\n    for board in _get_boards(installed):\n        if query and not any(\n            query.lower() in str(board.get(k, \"\")).lower()\n            for k in (\"id\", \"name\", \"mcu\", \"vendor\", \"platform\", \"frameworks\")\n        ):\n            continue\n        if board[\"platform\"] not in grpboards:\n            grpboards[board[\"platform\"]] = []\n        grpboards[board[\"platform\"]].append(board)\n\n    terminal_width = shutil.get_terminal_size().columns\n    for platform, boards in sorted(grpboards.items()):\n        click.echo(\"\")\n        click.echo(\"Platform: \", nl=False)\n        click.secho(platform, bold=True)\n        click.echo(\"=\" * terminal_width)\n        print_boards(boards)\n    return True\n\n\ndef print_boards(boards):\n    click.echo(\n        tabulate(\n            [\n                (\n                    click.style(b[\"id\"], fg=\"cyan\"),\n                    b[\"mcu\"],\n                    \"%dMHz\" % (b[\"fcpu\"] / 1000000),\n                    fs.humanize_file_size(b[\"rom\"]),\n                    fs.humanize_file_size(b[\"ram\"]),\n                    b[\"name\"],\n                )\n                for b in boards\n            ],\n            headers=[\"ID\", \"MCU\", \"Frequency\", \"Flash\", \"RAM\", \"Name\"],\n        )\n    )\n\n\ndef _get_boards(installed=False):\n    pm = PlatformPackageManager()\n    return pm.get_installed_boards() if installed else pm.get_all_boards()\n\n\ndef _print_boards_json(query, installed=False):\n    result = []\n    for board in _get_boards(installed):\n        if query:\n            search_data = \"%s %s\" % (board[\"id\"], json.dumps(board).lower())\n            if query.lower() not in search_data.lower():\n                continue\n        result.append(board)\n    click.echo(json.dumps(result))\n"
  },
  {
    "path": "platformio/commands/ci.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport glob\nimport os\nimport shutil\nimport tempfile\n\nimport click\n\nfrom platformio import fs\nfrom platformio.exception import CIBuildEnvsEmpty\nfrom platformio.project.commands.init import project_init_cmd, validate_boards\nfrom platformio.project.config import ProjectConfig\nfrom platformio.run.cli import cli as cmd_run\n\n\ndef validate_path(ctx, param, value):  # pylint: disable=unused-argument\n    invalid_path = None\n    value = list(value)\n    for i, p in enumerate(value):\n        if p.startswith(\"~\"):\n            value[i] = fs.expanduser(p)\n        value[i] = os.path.abspath(value[i])\n        if not glob.glob(value[i], recursive=True):\n            invalid_path = p\n            break\n    try:\n        assert invalid_path is None\n        return value\n    except AssertionError as exc:\n        raise click.BadParameter(\"Found invalid path: %s\" % invalid_path) from exc\n\n\n@click.command(\"ci\", short_help=\"Continuous Integration\")\n@click.argument(\"src\", nargs=-1, callback=validate_path)\n@click.option(\"-l\", \"--lib\", multiple=True, callback=validate_path, metavar=\"DIRECTORY\")\n@click.option(\"--exclude\", multiple=True)\n@click.option(\"-b\", \"--board\", multiple=True, metavar=\"ID\", callback=validate_boards)\n@click.option(\n    \"--build-dir\",\n    default=tempfile.mkdtemp,\n    type=click.Path(file_okay=False, dir_okay=True, writable=True),\n)\n@click.option(\"--keep-build-dir\", is_flag=True)\n@click.option(\n    \"-c\",\n    \"--project-conf\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),\n)\n@click.option(\"-O\", \"--project-option\", multiple=True)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"-v\", \"--verbose\", is_flag=True)\n@click.pass_context\ndef cli(  # pylint: disable=too-many-arguments,too-many-positional-arguments, too-many-branches\n    ctx,\n    src,\n    lib,\n    exclude,\n    board,\n    build_dir,\n    keep_build_dir,\n    project_conf,\n    project_option,\n    environments,\n    verbose,\n):\n    if not src and os.getenv(\"PLATFORMIO_CI_SRC\"):\n        src = validate_path(ctx, None, os.getenv(\"PLATFORMIO_CI_SRC\").split(\":\"))\n    if not src:\n        raise click.BadParameter(\"Missing argument 'src'\")\n\n    try:\n        if not keep_build_dir and os.path.isdir(build_dir):\n            fs.rmtree(build_dir)\n        if not os.path.isdir(build_dir):\n            os.makedirs(build_dir)\n\n        for dir_name, patterns in dict(lib=lib, src=src).items():\n            if not patterns:\n                continue\n            contents = []\n            for p in patterns:\n                contents += glob.glob(p, recursive=True)\n            _copy_contents(os.path.join(build_dir, dir_name), contents)\n\n        if project_conf and os.path.isfile(project_conf):\n            _copy_project_conf(build_dir, project_conf)\n        elif not board:\n            raise CIBuildEnvsEmpty()\n\n        if exclude:\n            _exclude_contents(build_dir, exclude)\n\n        # initialise project\n        ctx.invoke(\n            project_init_cmd,\n            project_dir=build_dir,\n            boards=board,\n            project_options=project_option,\n        )\n\n        # process project\n        ctx.invoke(\n            cmd_run, project_dir=build_dir, environment=environments, verbose=verbose\n        )\n    finally:\n        if not keep_build_dir:\n            fs.rmtree(build_dir)\n\n\ndef _copy_contents(dst_dir, contents):  # pylint: disable=too-many-branches\n    items = {\"dirs\": set(), \"files\": set()}\n\n    for path in contents:\n        if os.path.isdir(path):\n            items[\"dirs\"].add(path)\n        elif os.path.isfile(path):\n            items[\"files\"].add(path)\n\n    dst_dir_name = os.path.basename(dst_dir)\n\n    if dst_dir_name == \"src\" and len(items[\"dirs\"]) == 1:\n        if not os.path.isdir(dst_dir):\n            shutil.copytree(list(items[\"dirs\"]).pop(), dst_dir, symlinks=True)\n    else:\n        if not os.path.isdir(dst_dir):\n            os.makedirs(dst_dir)\n        for d in items[\"dirs\"]:\n            src_dst_dir = os.path.join(dst_dir, os.path.basename(d))\n            if not os.path.isdir(src_dst_dir):\n                shutil.copytree(d, src_dst_dir, symlinks=True)\n\n    if not items[\"files\"]:\n        return\n\n    if dst_dir_name == \"lib\":\n        dst_dir = os.path.join(dst_dir, tempfile.mkdtemp(dir=dst_dir))\n\n    for f in items[\"files\"]:\n        dst_file = os.path.join(dst_dir, os.path.basename(f))\n        if f == dst_file:\n            continue\n        shutil.copyfile(f, dst_file)\n\n\ndef _exclude_contents(dst_dir, patterns):\n    contents = []\n    for p in patterns:\n        contents += glob.glob(os.path.join(glob.escape(dst_dir), p), recursive=True)\n    for path in contents:\n        path = os.path.abspath(path)\n        if os.path.isdir(path):\n            fs.rmtree(path)\n        elif os.path.isfile(path):\n            os.remove(path)\n\n\ndef _copy_project_conf(build_dir, project_conf):\n    config = ProjectConfig(project_conf, parse_extra=False)\n    if config.has_section(\"platformio\"):\n        config.remove_section(\"platformio\")\n    config.save(os.path.join(build_dir, \"platformio.ini\"))\n"
  },
  {
    "path": "platformio/commands/device/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-import\nfrom platformio.device.monitor.filters.base import (\n    DeviceMonitorFilterBase as DeviceMonitorFilter,\n)\n"
  },
  {
    "path": "platformio/commands/lib.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-branches, too-many-locals\n\nimport json\nimport logging\nimport os\n\nimport click\n\nfrom platformio import exception, fs\nfrom platformio.cli import PlatformioCLI\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.list import package_list_cmd\nfrom platformio.package.commands.search import package_search_cmd\nfrom platformio.package.commands.show import package_show_cmd\nfrom platformio.package.commands.uninstall import package_uninstall_cmd\nfrom platformio.package.commands.update import package_update_cmd\nfrom platformio.package.exception import NotGlobalLibDir\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.meta import PackageItem, PackageSpec\nfrom platformio.proc import is_ci\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import get_project_dir, is_platformio_project\n\nCTX_META_INPUT_DIRS_KEY = __name__ + \".input_dirs\"\nCTX_META_PROJECT_ENVIRONMENTS_KEY = __name__ + \".project_environments\"\nCTX_META_STORAGE_DIRS_KEY = __name__ + \".storage_dirs\"\nCTX_META_STORAGE_LIBDEPS_KEY = __name__ + \".storage_lib_deps\"\n\n\ndef get_project_global_lib_dir():\n    return ProjectConfig.get_instance().get(\"platformio\", \"globallib_dir\")\n\n\ndef invoke_command(ctx, cmd, **kwargs):\n    input_dirs = ctx.meta.get(CTX_META_INPUT_DIRS_KEY, [])\n    project_environments = ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY]\n    for input_dir in input_dirs:\n        cmd_kwargs = kwargs.copy()\n        if is_platformio_project(input_dir):\n            cmd_kwargs[\"project_dir\"] = input_dir\n            cmd_kwargs[\"environments\"] = project_environments\n        else:\n            cmd_kwargs[\"global\"] = True\n            cmd_kwargs[\"storage_dir\"] = input_dir\n        ctx.invoke(cmd, **cmd_kwargs)\n\n\n@click.group(short_help=\"Library manager\", hidden=True)\n@click.option(\n    \"-d\",\n    \"--storage-dir\",\n    multiple=True,\n    default=None,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n    help=\"Manage custom library storage\",\n)\n@click.option(\n    \"-g\", \"--global\", is_flag=True, help=\"Manage global PlatformIO library storage\"\n)\n@click.option(\n    \"-e\",\n    \"--environment\",\n    multiple=True,\n    help=(\n        \"Manage libraries for the specific project build environments \"\n        \"declared in `platformio.ini`\"\n    ),\n)\n@click.pass_context\ndef cli(ctx, **options):\n    in_silence = PlatformioCLI.in_silence()\n    storage_cmds = (\"install\", \"uninstall\", \"update\", \"list\")\n    # skip commands that don't need storage folder\n    if ctx.invoked_subcommand not in storage_cmds or (\n        len(ctx.args) == 2 and ctx.args[1] in (\"-h\", \"--help\")\n    ):\n        return\n    storage_dirs = list(options[\"storage_dir\"])\n    if options[\"global\"]:\n        storage_dirs.append(get_project_global_lib_dir())\n    if not storage_dirs:\n        if is_platformio_project():\n            storage_dirs = [get_project_dir()]\n        elif is_ci():\n            storage_dirs = [get_project_global_lib_dir()]\n            click.secho(\n                \"Warning! Global library storage is used automatically. \"\n                \"Please use `platformio lib --global %s` command to remove \"\n                \"this warning.\" % ctx.invoked_subcommand,\n                fg=\"yellow\",\n            )\n\n    if not storage_dirs:\n        raise NotGlobalLibDir(\n            get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand\n        )\n\n    ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options[\"environment\"]\n    ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs\n    ctx.meta[CTX_META_STORAGE_DIRS_KEY] = []\n    ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {}\n    for storage_dir in storage_dirs:\n        if not is_platformio_project(storage_dir):\n            ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)\n            continue\n        with fs.cd(storage_dir):\n            config = ProjectConfig.get_instance(\n                os.path.join(storage_dir, \"platformio.ini\")\n            )\n            config.validate(options[\"environment\"], silent=in_silence)\n            libdeps_dir = config.get(\"platformio\", \"libdeps_dir\")\n            for env in config.envs():\n                if options[\"environment\"] and env not in options[\"environment\"]:\n                    continue\n                storage_dir = os.path.join(libdeps_dir, env)\n                ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)\n                ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(\n                    \"env:\" + env, \"lib_deps\", []\n                )\n\n\n@cli.command(\"install\", short_help=\"Install library\")\n@click.argument(\"libraries\", required=False, nargs=-1, metavar=\"[LIBRARY...]\")\n@click.option(\n    \"--save/--no-save\",\n    is_flag=True,\n    default=True,\n    help=\"Save installed libraries into the `platformio.ini` dependency list\"\n    \" (enabled by default)\",\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\n@click.option(\n    \"--interactive\",\n    is_flag=True,\n    help=\"Deprecated! Please use a strict dependency specification (owner/libname)\",\n)\n@click.option(\n    \"-f\", \"--force\", is_flag=True, help=\"Reinstall/redownload library if exists\"\n)\n@click.pass_context\ndef lib_install(  # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument\n    ctx, libraries, save, silent, interactive, force\n):\n    click.secho(\n        \"\\nWARNING: This command is deprecated and will be removed in \"\n        \"the next releases. \\nPlease use `pio pkg install` instead.\\n\",\n        fg=\"yellow\",\n    )\n    return invoke_command(\n        ctx,\n        package_install_cmd,\n        libraries=libraries,\n        no_save=not save,\n        force=force,\n        silent=silent,\n    )\n\n\n@cli.command(\"uninstall\", short_help=\"Remove libraries\")\n@click.argument(\"libraries\", nargs=-1, metavar=\"[LIBRARY...]\")\n@click.option(\n    \"--save/--no-save\",\n    is_flag=True,\n    default=True,\n    help=\"Remove libraries from the `platformio.ini` dependency list and save changes\"\n    \" (enabled by default)\",\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\n@click.pass_context\ndef lib_uninstall(ctx, libraries, save, silent):\n    click.secho(\n        \"\\nWARNING: This command is deprecated and will be removed in \"\n        \"the next releases. \\nPlease use `pio pkg uninstall` instead.\\n\",\n        fg=\"yellow\",\n    )\n    invoke_command(\n        ctx,\n        package_uninstall_cmd,\n        libraries=libraries,\n        no_save=not save,\n        silent=silent,\n    )\n\n\n@cli.command(\"update\", short_help=\"Update installed libraries\")\n@click.argument(\"libraries\", required=False, nargs=-1, metavar=\"[LIBRARY...]\")\n@click.option(\n    \"-c\",\n    \"--only-check\",\n    is_flag=True,\n    help=\"DEPRECATED. Please use `--dry-run` instead\",\n)\n@click.option(\n    \"--dry-run\", is_flag=True, help=\"Do not update, only check for the new versions\"\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef lib_update(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n    ctx, libraries, only_check, dry_run, silent, json_output\n):\n    only_check = dry_run or only_check\n    if only_check and not json_output:\n        raise exception.UserSideException(\n            \"This command is deprecated, please use `pio pkg outdated` instead\"\n        )\n\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg update` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return invoke_command(\n            ctx,\n            package_update_cmd,\n            libraries=libraries,\n            silent=silent,\n        )\n\n    storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]\n    json_result = {}\n    for storage_dir in storage_dirs:\n        lib_deps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, {}).get(storage_dir, [])\n        lm = LibraryPackageManager(storage_dir)\n        lm.set_log_level(logging.WARN if silent else logging.DEBUG)\n        _libraries = libraries or lib_deps or lm.get_installed()\n\n        result = []\n        for library in _libraries:\n            spec = None\n            pkg = None\n            if isinstance(library, PackageItem):\n                pkg = library\n            else:\n                spec = PackageSpec(library)\n                pkg = lm.get_package(spec)\n            if not pkg:\n                continue\n            outdated = lm.outdated(pkg, spec)\n            if not outdated.is_outdated(allow_incompatible=True):\n                continue\n            manifest = lm.legacy_load_manifest(pkg)\n            manifest[\"versionWanted\"] = (\n                str(outdated.wanted) if outdated.wanted else None\n            )\n            manifest[\"versionLatest\"] = (\n                str(outdated.latest) if outdated.latest else None\n            )\n            result.append(manifest)\n\n        json_result[storage_dir] = result\n\n    return click.echo(\n        json.dumps(\n            json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result\n        )\n    )\n\n\n@cli.command(\"list\", short_help=\"List installed libraries\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef lib_list(ctx, json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg list` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return invoke_command(ctx, package_list_cmd, only_libraries=True)\n\n    storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]\n    json_result = {}\n    for storage_dir in storage_dirs:\n        lm = LibraryPackageManager(storage_dir)\n        json_result[storage_dir] = lm.legacy_get_installed()\n    return click.echo(\n        json.dumps(\n            json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result\n        )\n    )\n\n\n@cli.command(\"search\", short_help=\"Search for a library\")\n@click.argument(\"query\", required=False, nargs=-1)\n@click.option(\"--json-output\", is_flag=True)\n@click.option(\"--page\", type=click.INT, default=1)\n@click.option(\"--id\", multiple=True)\n@click.option(\"-o\", \"--owner\", multiple=True)\n@click.option(\"-n\", \"--name\", multiple=True)\n@click.option(\"-a\", \"--author\", multiple=True)\n@click.option(\"-k\", \"--keyword\", multiple=True)\n@click.option(\"-f\", \"--framework\", multiple=True)\n@click.option(\"-p\", \"--platform\", multiple=True)\n@click.option(\"-i\", \"--header\", multiple=True)\n@click.option(\n    \"--noninteractive\",\n    is_flag=True,\n    help=\"Do not prompt, automatically paginate with delay\",\n)\n@click.pass_context\ndef lib_search(  # pylint: disable=unused-argument\n    ctx, query, json_output, page, noninteractive, **filters\n):\n    if not query:\n        query = []\n    if not isinstance(query, list):\n        query = list(query)\n\n    for key, values in filters.items():\n        for value in values:\n            query.append('%s:\"%s\"' % (key, value))\n\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg search` instead.\\n\",\n            fg=\"yellow\",\n        )\n        query.append(\"type:library\")\n        return ctx.invoke(package_search_cmd, query=\" \".join(query), page=page)\n\n    regclient = LibraryPackageManager().get_registry_client_instance()\n    result = regclient.fetch_json_data(\n        \"get\",\n        \"/v2/lib/search\",\n        params=dict(query=\" \".join(query), page=page),\n        x_cache_valid=\"1d\",\n    )\n    return click.echo(json.dumps(result))\n\n\n@cli.command(\"builtin\", short_help=\"List built-in libraries\")\n@click.option(\"--storage\", multiple=True)\n@click.option(\"--json-output\", is_flag=True)\ndef lib_builtin(storage, json_output):\n    items = LibraryPackageManager.get_builtin_libs(storage)\n    if json_output:\n        return click.echo(json.dumps(items))\n\n    for storage_ in items:\n        if not storage_[\"items\"]:\n            continue\n        click.secho(storage_[\"name\"], fg=\"green\")\n        click.echo(\"*\" * len(storage_[\"name\"]))\n        click.echo()\n\n        for item in sorted(storage_[\"items\"], key=lambda i: i[\"name\"]):\n            print_lib_item(item)\n\n    return True\n\n\n@cli.command(\"show\", short_help=\"Show detailed info about a library\")\n@click.argument(\"library\", metavar=\"[LIBRARY]\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef lib_show(ctx, library, json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg show` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return ctx.invoke(package_show_cmd, pkg_type=\"library\", spec=library)\n\n    lm = LibraryPackageManager()\n    lm.set_log_level(logging.ERROR if json_output else logging.DEBUG)\n    lib_id = lm.reveal_registry_package_id(library)\n    regclient = lm.get_registry_client_instance()\n    lib = regclient.fetch_json_data(\n        \"get\", \"/v2/lib/info/%d\" % lib_id, x_cache_valid=\"1h\"\n    )\n    return click.echo(json.dumps(lib))\n\n\n@cli.command(\"register\", short_help=\"Deprecated\")\n@click.argument(\"config_url\")\ndef lib_register(config_url):  # pylint: disable=unused-argument\n    raise exception.UserSideException(\n        \"This command is deprecated. Please use `pio pkg publish` command.\"\n    )\n\n\n@cli.command(\"stats\", short_help=\"Library Registry Statistics\")\n@click.option(\"--json-output\", is_flag=True)\ndef lib_stats(json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease visit \"\n            \"https://registry.platformio.org\\n\",\n            fg=\"yellow\",\n        )\n        return None\n\n    regclient = LibraryPackageManager().get_registry_client_instance()\n    result = regclient.fetch_json_data(\"get\", \"/v2/lib/stats\", x_cache_valid=\"1h\")\n    return click.echo(json.dumps(result))\n\n\ndef print_lib_item(item):\n    click.secho(item[\"name\"], fg=\"cyan\")\n    click.echo(\"=\" * len(item[\"name\"]))\n    if \"id\" in item:\n        click.secho(\"#ID: %d\" % item[\"id\"], bold=True)\n    if \"description\" in item or \"url\" in item:\n        click.echo(item.get(\"description\", item.get(\"url\", \"\")))\n    click.echo()\n\n    for key in (\"version\", \"homepage\", \"license\", \"keywords\"):\n        if key not in item or not item[key]:\n            continue\n        if isinstance(item[key], list):\n            click.echo(\"%s: %s\" % (key.capitalize(), \", \".join(item[key])))\n        else:\n            click.echo(\"%s: %s\" % (key.capitalize(), item[key]))\n\n    for key in (\"frameworks\", \"platforms\"):\n        if key not in item:\n            continue\n        click.echo(\n            \"Compatible %s: %s\"\n            % (\n                key,\n                \", \".join(\n                    [i[\"title\"] if isinstance(i, dict) else i for i in item[key]]\n                ),\n            )\n        )\n\n    if \"authors\" in item or \"authornames\" in item:\n        click.echo(\n            \"Authors: %s\"\n            % \", \".join(\n                item.get(\n                    \"authornames\", [a.get(\"name\", \"\") for a in item.get(\"authors\", [])]\n                )\n            )\n        )\n\n    if \"__src_url\" in item:\n        click.secho(\"Source: %s\" % item[\"__src_url\"])\n    click.echo()\n"
  },
  {
    "path": "platformio/commands/platform.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport logging\nimport os\n\nimport click\n\nfrom platformio.exception import UserSideException\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.list import package_list_cmd\nfrom platformio.package.commands.search import package_search_cmd\nfrom platformio.package.commands.show import package_show_cmd\nfrom platformio.package.commands.uninstall import package_uninstall_cmd\nfrom platformio.package.commands.update import package_update_cmd\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.meta import PackageItem, PackageSpec\nfrom platformio.package.version import get_original_version\nfrom platformio.platform.exception import UnknownPlatform\nfrom platformio.platform.factory import PlatformFactory\n\n\n@click.group(short_help=\"Platform manager\", hidden=True)\ndef cli():\n    pass\n\n\n@cli.command(\"search\", short_help=\"Search for development platform\")\n@click.argument(\"query\", required=False)\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef platform_search(ctx, query, json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg search` instead.\\n\",\n            fg=\"yellow\",\n        )\n        query = query or \"\"\n        return ctx.invoke(package_search_cmd, query=f\"type:platform {query}\".strip())\n\n    platforms = []\n    for platform in _get_registry_platforms():\n        if query == \"all\":\n            query = \"\"\n        search_data = json.dumps(platform)\n        if query and query.lower() not in search_data.lower():\n            continue\n        platforms.append(\n            _get_registry_platform_data(\n                platform[\"name\"], with_boards=False, expose_packages=False\n            )\n        )\n    click.echo(json.dumps(platforms))\n    return None\n\n\n@cli.command(\"frameworks\", short_help=\"List supported frameworks, SDKs\")\n@click.argument(\"query\", required=False)\n@click.option(\"--json-output\", is_flag=True)\ndef platform_frameworks(query, json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease visit https://docs.platformio.org\"\n            \"/en/latest/frameworks/index.html\\n\",\n            fg=\"yellow\",\n        )\n        return\n\n    regclient = PlatformPackageManager().get_registry_client_instance()\n    frameworks = []\n    for framework in regclient.fetch_json_data(\n        \"get\", \"/v2/frameworks\", x_cache_valid=\"1d\"\n    ):\n        if query == \"all\":\n            query = \"\"\n        search_data = json.dumps(framework)\n        if query and query.lower() not in search_data.lower():\n            continue\n        framework[\"homepage\"] = \"https://platformio.org/frameworks/\" + framework[\"name\"]\n        framework[\"platforms\"] = [\n            platform[\"name\"]\n            for platform in _get_registry_platforms()\n            if framework[\"name\"] in platform[\"frameworks\"]\n        ]\n        frameworks.append(framework)\n\n    frameworks = sorted(frameworks, key=lambda manifest: manifest[\"name\"])\n    click.echo(json.dumps(frameworks))\n\n\n@cli.command(\"list\", short_help=\"List installed development platforms\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef platform_list(ctx, json_output):\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg list` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return ctx.invoke(package_list_cmd, **{\"global\": True, \"only_platforms\": True})\n\n    platforms = []\n    pm = PlatformPackageManager()\n    for pkg in pm.get_installed():\n        platforms.append(\n            _get_installed_platform_data(pkg, with_boards=False, expose_packages=False)\n        )\n\n    platforms = sorted(platforms, key=lambda manifest: manifest[\"name\"])\n    click.echo(json.dumps(platforms))\n    return None\n\n\n@cli.command(\"show\", short_help=\"Show details about development platform\")\n@click.argument(\"platform\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef platform_show(ctx, platform, json_output):  # pylint: disable=too-many-branches\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg show` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return ctx.invoke(package_show_cmd, pkg_type=\"platform\", spec=platform)\n\n    data = _get_platform_data(platform)\n    if not data:\n        raise UnknownPlatform(platform)\n    return click.echo(json.dumps(data))\n\n\n@cli.command(\"install\", short_help=\"Install new development platform\")\n@click.argument(\"platforms\", nargs=-1, required=True, metavar=\"[PLATFORM...]\")\n@click.option(\"--with-package\", multiple=True)\n@click.option(\"--without-package\", multiple=True)\n@click.option(\"--skip-default-package\", is_flag=True)\n@click.option(\"--with-all-packages\", is_flag=True)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\n@click.option(\n    \"-f\",\n    \"--force\",\n    is_flag=True,\n    help=\"Reinstall/redownload dev/platform and its packages if exist\",\n)\n@click.pass_context\ndef platform_install(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n    ctx,\n    platforms,\n    with_package,\n    without_package,\n    skip_default_package,\n    with_all_packages,\n    silent,\n    force,\n):\n    click.secho(\n        \"\\nWARNING: This command is deprecated and will be removed in \"\n        \"the next releases. \\nPlease use `pio pkg install` instead.\\n\",\n        fg=\"yellow\",\n    )\n    ctx.invoke(\n        package_install_cmd,\n        **{\n            \"global\": True,\n            \"platforms\": platforms,\n            \"skip_dependencies\": (\n                not with_all_packages\n                and (with_package or without_package or skip_default_package)\n            ),\n            \"silent\": silent,\n            \"force\": force,\n        },\n    )\n\n\n@cli.command(\"uninstall\", short_help=\"Uninstall development platform\")\n@click.argument(\"platforms\", nargs=-1, required=True, metavar=\"[PLATFORM...]\")\n@click.pass_context\ndef platform_uninstall(ctx, platforms):\n    click.secho(\n        \"\\nWARNING: This command is deprecated and will be removed in \"\n        \"the next releases. \\nPlease use `pio pkg uninstall` instead.\\n\",\n        fg=\"yellow\",\n    )\n    ctx.invoke(\n        package_uninstall_cmd,\n        **{\n            \"global\": True,\n            \"platforms\": platforms,\n        },\n    )\n\n\n@cli.command(\"update\", short_help=\"Update installed development platforms\")\n@click.argument(\"platforms\", nargs=-1, required=False, metavar=\"[PLATFORM...]\")\n@click.option(\n    \"-p\", \"--only-packages\", is_flag=True, help=\"Update only the platform packages\"\n)\n@click.option(\n    \"-c\",\n    \"--only-check\",\n    is_flag=True,\n    help=\"DEPRECATED. Please use `--dry-run` instead\",\n)\n@click.option(\n    \"--dry-run\", is_flag=True, help=\"Do not update, only check for the new versions\"\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_context\ndef platform_update(  # pylint: disable=too-many-locals,too-many-arguments,too-many-positional-arguments\n    ctx, platforms, only_check, dry_run, silent, json_output, **_\n):\n    only_check = dry_run or only_check\n\n    if only_check and not json_output:\n        raise UserSideException(\n            \"This command is deprecated, please use `pio pkg outdated` instead\"\n        )\n\n    if not json_output:\n        click.secho(\n            \"\\nWARNING: This command is deprecated and will be removed in \"\n            \"the next releases. \\nPlease use `pio pkg update` instead.\\n\",\n            fg=\"yellow\",\n        )\n        return ctx.invoke(\n            package_update_cmd,\n            **{\n                \"global\": True,\n                \"platforms\": platforms,\n                \"silent\": silent,\n            },\n        )\n\n    pm = PlatformPackageManager()\n    pm.set_log_level(logging.WARN if silent else logging.DEBUG)\n    platforms = platforms or pm.get_installed()\n    result = []\n    for platform in platforms:\n        spec = None\n        pkg = None\n        if isinstance(platform, PackageItem):\n            pkg = platform\n        else:\n            spec = PackageSpec(platform)\n            pkg = pm.get_package(spec)\n        if not pkg:\n            continue\n        outdated = pm.outdated(pkg, spec)\n        if (\n            not outdated.is_outdated(allow_incompatible=True)\n            and not PlatformFactory.new(pkg).are_outdated_packages()\n        ):\n            continue\n        data = _get_installed_platform_data(\n            pkg, with_boards=False, expose_packages=False\n        )\n        if outdated.is_outdated(allow_incompatible=True):\n            data[\"versionLatest\"] = str(outdated.latest) if outdated.latest else None\n        result.append(data)\n    click.echo(json.dumps(result))\n    return True\n\n\n#\n# Helpers\n#\n\n\ndef _get_registry_platforms():\n    regclient = PlatformPackageManager().get_registry_client_instance()\n    return regclient.fetch_json_data(\"get\", \"/v2/platforms\", x_cache_valid=\"1d\")\n\n\ndef _get_platform_data(*args, **kwargs):\n    try:\n        return _get_installed_platform_data(*args, **kwargs)\n    except UnknownPlatform:\n        return _get_registry_platform_data(*args, **kwargs)\n\n\ndef _get_installed_platform_data(platform, with_boards=True, expose_packages=True):\n    p = PlatformFactory.new(platform)\n    data = dict(\n        name=p.name,\n        title=p.title,\n        description=p.description,\n        version=p.version,\n        homepage=p.homepage,\n        url=p.homepage,\n        repository=p.repository_url,\n        license=p.license,\n        forDesktop=not p.is_embedded(),\n        frameworks=sorted(list(p.frameworks) if p.frameworks else []),\n        packages=list(p.packages) if p.packages else [],\n    )\n\n    # if dump to API\n    # del data['version']\n    # return data\n\n    # overwrite VCS version and add extra fields\n    manifest = PlatformPackageManager().legacy_load_manifest(\n        os.path.dirname(p.manifest_path)\n    )\n    assert manifest\n    for key in manifest:\n        if key == \"version\" or key.startswith(\"__\"):\n            data[key] = manifest[key]\n\n    if with_boards:\n        data[\"boards\"] = [c.get_brief_data() for c in p.get_boards().values()]\n\n    if not data[\"packages\"] or not expose_packages:\n        return data\n\n    data[\"packages\"] = []\n    installed_pkgs = {\n        pkg.metadata.name: p.pm.load_manifest(pkg) for pkg in p.get_installed_packages()\n    }\n    for name, options in p.packages.items():\n        item = dict(\n            name=name,\n            type=p.get_package_type(name),\n            requirements=options.get(\"version\"),\n            optional=options.get(\"optional\") is True,\n        )\n        if name in installed_pkgs:\n            for key, value in installed_pkgs[name].items():\n                if key not in (\"url\", \"version\", \"description\"):\n                    continue\n                item[key] = value\n                if key == \"version\":\n                    item[\"originalVersion\"] = get_original_version(value)\n        data[\"packages\"].append(item)\n\n    return data\n\n\ndef _get_registry_platform_data(  # pylint: disable=unused-argument\n    platform, with_boards=True, expose_packages=True\n):\n    _data = None\n    for p in _get_registry_platforms():\n        if p[\"name\"] == platform:\n            _data = p\n            break\n\n    if not _data:\n        return None\n\n    data = dict(\n        ownername=_data.get(\"ownername\"),\n        name=_data[\"name\"],\n        title=_data[\"title\"],\n        description=_data[\"description\"],\n        homepage=_data[\"homepage\"],\n        repository=_data[\"repository\"],\n        url=_data[\"url\"],\n        license=_data[\"license\"],\n        forDesktop=_data[\"forDesktop\"],\n        frameworks=_data[\"frameworks\"],\n        packages=_data[\"packages\"],\n        versions=_data.get(\"versions\"),\n    )\n\n    if with_boards:\n        data[\"boards\"] = [\n            board\n            for board in PlatformPackageManager().get_registered_boards()\n            if board[\"platform\"] == _data[\"name\"]\n        ]\n\n    return data\n"
  },
  {
    "path": "platformio/commands/settings.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import app\nfrom platformio.compat import string_types\n\n\ndef format_value(raw):\n    if isinstance(raw, bool):\n        return \"Yes\" if raw else \"No\"\n    if isinstance(raw, string_types):\n        return raw\n    return str(raw)\n\n\n@click.group(short_help=\"Manage system settings\")\ndef cli():\n    pass\n\n\n@cli.command(\"get\", short_help=\"Get existing setting/-s\")\n@click.argument(\"name\", required=False)\ndef settings_get(name):\n    tabular_data = []\n    for key, options in sorted(app.DEFAULT_SETTINGS.items()):\n        if name and name != key:\n            continue\n        raw_value = app.get_setting(key)\n        formatted_value = format_value(raw_value)\n\n        if raw_value != options[\"value\"]:\n            default_formatted_value = format_value(options[\"value\"])\n            formatted_value += \"%s\" % (\n                \"\\n\" if len(default_formatted_value) > 10 else \" \"\n            )\n            formatted_value += \"[%s]\" % click.style(\n                default_formatted_value, fg=\"yellow\"\n            )\n\n        tabular_data.append(\n            (click.style(key, fg=\"cyan\"), formatted_value, options[\"description\"])\n        )\n\n    click.echo(\n        tabulate(\n            tabular_data, headers=[\"Name\", \"Current value [Default]\", \"Description\"]\n        )\n    )\n\n\n@cli.command(\"set\", short_help=\"Set new value for the setting\")\n@click.argument(\"name\")\n@click.argument(\"value\")\n@click.pass_context\ndef settings_set(ctx, name, value):\n    app.set_setting(name, value)\n    click.secho(\"The new value for the setting has been set!\", fg=\"green\")\n    ctx.invoke(settings_get, name=name)\n\n\n@cli.command(\"reset\", short_help=\"Reset settings to default\")\n@click.pass_context\ndef settings_reset(ctx):\n    app.reset_settings()\n    click.secho(\"The settings have been reset!\", fg=\"green\")\n    ctx.invoke(settings_get)\n"
  },
  {
    "path": "platformio/commands/update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\n\n@click.command(\n    \"update\",\n    short_help=\"Update installed platforms, packages and libraries\",\n    hidden=True,\n)\n@click.option(\"--core-packages\", is_flag=True, help=\"Update only the core packages\")\n@click.option(\n    \"-c\",\n    \"--only-check\",\n    is_flag=True,\n    help=\"DEPRECATED. Please use `--dry-run` instead\",\n)\n@click.option(\n    \"--dry-run\", is_flag=True, help=\"Do not update, only check for the new versions\"\n)\ndef cli(*_, **__):\n    click.secho(\n        \"This command is deprecated and will be removed in the next releases. \\n\"\n        \"Please use `pio pkg update` instead.\",\n        fg=\"yellow\",\n    )\n"
  },
  {
    "path": "platformio/commands/upgrade.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport re\nimport subprocess\n\nimport click\n\nfrom platformio import VERSION, __version__, app, exception\nfrom platformio.dependencies import get_pip_dependencies\nfrom platformio.http import fetch_remote_content\nfrom platformio.package.manager.core import update_core_packages\nfrom platformio.proc import get_pythonexe_path\n\nPYPI_JSON_URL = \"https://pypi.org/pypi/platformio/json\"\nDEVELOP_ZIP_URL = \"https://github.com/platformio/platformio-core/archive/develop.zip\"\nDEVELOP_INIT_SCRIPT_URL = (\n    \"https://raw.githubusercontent.com/platformio/platformio-core\"\n    \"/develop/platformio/__init__.py\"\n)\n\n\n@click.command(\"upgrade\", short_help=\"Upgrade PlatformIO Core to the latest version\")\n@click.option(\"--dev\", is_flag=True, help=\"Use development branch\")\n@click.option(\"--only-dependencies\", is_flag=True)\n@click.option(\"--verbose\", \"-v\", is_flag=True)\ndef cli(dev, only_dependencies, verbose):\n    if only_dependencies:\n        return upgrade_pip_dependencies(verbose)\n\n    update_core_packages()\n\n    if not dev and __version__ == get_latest_version():\n        return click.secho(\n            \"You're up-to-date!\\nPlatformIO %s is currently the \"\n            \"newest version available.\" % __version__,\n            fg=\"green\",\n        )\n\n    click.secho(\"Please wait while upgrading PlatformIO Core ...\", fg=\"yellow\")\n\n    python_exe = get_pythonexe_path()\n    to_develop = dev or not all(c.isdigit() for c in __version__ if c != \".\")\n    pkg_spec = DEVELOP_ZIP_URL if to_develop else \"platformio\"\n\n    try:\n        # PIO Core\n        subprocess.run(\n            [python_exe, \"-m\", \"pip\", \"install\", \"--upgrade\", pkg_spec],\n            check=True,\n            stdout=subprocess.PIPE if not verbose else None,\n        )\n\n        # PyPI dependencies\n        subprocess.run(\n            [python_exe, \"-m\", \"platformio\", \"upgrade\", \"--only-dependencies\"],\n            check=False,\n            stdout=subprocess.PIPE,\n        )\n\n        # Check version\n        output = subprocess.run(\n            [python_exe, \"-m\", \"platformio\", \"--version\"],\n            check=True,\n            stdout=subprocess.PIPE,\n        ).stdout.decode()\n        assert \"version\" in output\n        actual_version = output.split(\"version\", 1)[1].strip()\n        click.secho(\n            \"PlatformIO has been successfully upgraded to %s\" % actual_version,\n            fg=\"green\",\n        )\n        click.echo(\"Release notes: \", nl=False)\n        click.secho(\"https://docs.platformio.org/en/latest/history.html\", fg=\"cyan\")\n        if app.get_session_var(\"caller_id\"):\n            click.secho(\n                \"Warning! Please restart IDE to affect PIO Home changes\", fg=\"yellow\"\n            )\n    except (AssertionError, subprocess.CalledProcessError) as exc:\n        click.secho(\n            \"\\nWarning!!! Could not automatically upgrade the PlatformIO Core.\",\n            fg=\"red\",\n        )\n        click.secho(\n            \"Please upgrade it manually using the following command:\\n\",\n            fg=\"red\",\n        )\n        click.secho(f'\"{python_exe}\" -m pip install -U {pkg_spec}\\n', fg=\"cyan\")\n        raise exception.ReturnErrorCode(1) from exc\n\n    return True\n\n\ndef upgrade_pip_dependencies(verbose):\n    subprocess.run(\n        [\n            get_pythonexe_path(),\n            \"-m\",\n            \"pip\",\n            \"install\",\n            \"--upgrade\",\n            \"pip\",\n            *get_pip_dependencies(),\n        ],\n        check=True,\n        stdout=subprocess.PIPE if not verbose else None,\n    )\n\n\ndef get_latest_version():\n    try:\n        if not str(VERSION[2]).isdigit():\n            try:\n                return get_develop_latest_version()\n            except:  # pylint: disable=bare-except\n                pass\n        return get_pypi_latest_version()\n    except Exception as exc:\n        raise exception.GetLatestVersionError() from exc\n\n\ndef get_develop_latest_version():\n    version = None\n    content = fetch_remote_content(DEVELOP_INIT_SCRIPT_URL)\n    for line in content.split(\"\\n\"):\n        line = line.strip()\n        if not line.startswith(\"VERSION\"):\n            continue\n        match = re.match(r\"VERSION\\s*=\\s*\\(([^\\)]+)\\)\", line)\n        if not match:\n            continue\n        version = match.group(1)\n        for c in (\" \", \"'\", '\"'):\n            version = version.replace(c, \"\")\n        version = \".\".join(version.split(\",\"))\n    assert version\n    return version\n\n\ndef get_pypi_latest_version():\n    content = fetch_remote_content(PYPI_JSON_URL)\n    return json.loads(content)[\"info\"][\"version\"]\n"
  },
  {
    "path": "platformio/compat.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-import,no-name-in-module\n\nimport importlib.util\nimport inspect\nimport locale\nimport os\nimport shlex\nimport sys\n\nfrom platformio.exception import UserSideException\n\nif sys.version_info >= (3, 7):\n    from asyncio import create_task as aio_create_task\n    from asyncio import get_running_loop as aio_get_running_loop\nelse:\n    from asyncio import ensure_future as aio_create_task\n    from asyncio import get_event_loop as aio_get_running_loop\n\n\nif sys.version_info >= (3, 8):\n    from shlex import join as shlex_join\nelse:\n\n    def shlex_join(split_command):\n        return \" \".join(shlex.quote(arg) for arg in split_command)\n\n\nif sys.version_info >= (3, 9):\n    from asyncio import to_thread as aio_to_thread\nelse:\n    try:\n        from starlette.concurrency import run_in_threadpool as aio_to_thread\n    except ImportError:\n        pass\n\n\nPY2 = sys.version_info[0] == 2  # DO NOT REMOVE IT. ESP8266/ESP32 depend on it\nPY36 = sys.version_info[0:2] == (3, 6)\nIS_CYGWIN = sys.platform.startswith(\"cygwin\")\nIS_WINDOWS = WINDOWS = sys.platform.startswith(\"win\")\nIS_MACOS = sys.platform.startswith(\"darwin\")\nMISSING = object()\nstring_types = (str,)\n\n\ndef is_bytes(x):\n    return isinstance(x, (bytes, memoryview, bytearray))\n\n\ndef isascii(text):\n    if sys.version_info >= (3, 7):\n        return text.isascii()\n    for c in text or \"\":\n        if ord(c) > 127:\n            return False\n    return True\n\n\ndef is_terminal():\n    try:\n        return sys.stdout.isatty()\n    except Exception:  # pylint: disable=broad-except\n        return False\n\n\ndef ci_strings_are_equal(a, b):\n    if a == b:\n        return True\n    if not a or not b:\n        return False\n    return a.strip().lower() == b.strip().lower()\n\n\ndef hashlib_encode_data(data):\n    if is_bytes(data):\n        return data\n    if not isinstance(data, string_types):\n        data = str(data)\n    return data.encode()\n\n\ndef load_python_module(name, pathname):\n    spec = importlib.util.spec_from_file_location(name, pathname)\n    module = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(module)\n    return module\n\n\ndef get_filesystem_encoding():\n    return sys.getfilesystemencoding() or sys.getdefaultencoding()\n\n\ndef get_locale_encoding():\n    return locale.getpreferredencoding()\n\n\ndef get_object_members(obj, ignore_private=True):\n    members = inspect.getmembers(obj, lambda a: not inspect.isroutine(a))\n    if not ignore_private:\n        return members\n    return {\n        item[0]: item[1]\n        for item in members\n        if not (item[0].startswith(\"__\") and item[0].endswith(\"__\"))\n    }\n\n\ndef ensure_python3(raise_exception=True):\n    compatible = sys.version_info >= (3, 6)\n    if not raise_exception or compatible:\n        return compatible\n    raise UserSideException(\n        \"Python 3.6 or later is required for this operation. \\n\"\n        \"Please check a migration guide:\\n\"\n        \"https://docs.platformio.org/en/latest/core/migration.html\"\n        \"#drop-support-for-python-2-and-3-5\"\n    )\n\n\ndef path_to_unicode(path):\n    \"\"\"\n    Deprecated: Compatibility with dev-platforms,\n    and custom device monitor filters\n    \"\"\"\n    return path\n\n\ndef is_proxy_set(socks=False):\n    for var in (\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\"):\n        value = os.getenv(var, os.getenv(var.lower()))\n        if not value or (socks and not value.startswith(\"socks5://\")):\n            continue\n        return True\n    return False\n\n\ndef click_launch(url, wait=False, locate=False) -> int:\n    return _click_open_url(url, wait=wait, locate=locate)\n\n\ndef _click_open_url(  # pylint: disable=too-many-branches, too-many-return-statements, consider-using-with, import-outside-toplevel, unspecified-encoding\n    url, wait=False, locate=False\n):\n    \"\"\"\n    Issue https://github.com/pallets/click/issues/2868\n    Keep in sync with https://github.com/pallets/click/blob/main/src/click/_termui_impl.py\n    \"\"\"\n    import subprocess\n\n    def _unquote_file(url) -> str:\n        from urllib.parse import unquote\n\n        if url.startswith(\"file://\"):\n            url = unquote(url[7:])\n\n        return url\n\n    if IS_MACOS:\n        args = [\"open\"]\n        if wait:\n            args.append(\"-W\")\n        if locate:\n            args.append(\"-R\")\n        args.append(_unquote_file(url))\n        null = open(\"/dev/null\", \"w\")\n        try:\n            return subprocess.Popen(args, stderr=null).wait()\n        finally:\n            null.close()\n    elif IS_WINDOWS:\n        if locate:\n            url = _unquote_file(url)\n            args = [\"explorer\", f\"/select,{url}\"]\n        else:\n            args = [\"start\"]\n            if wait:\n                args.append(\"/WAIT\")\n            args.append(\"\")\n            args.append(url)\n        try:\n            return subprocess.call(args, shell=True)\n        except OSError:\n            # Command not found\n            return 127\n    elif IS_CYGWIN:\n        if locate:\n            url = _unquote_file(url)\n            args = [\"cygstart\", os.path.dirname(url)]\n        else:\n            args = [\"cygstart\"]\n            if wait:\n                args.append(\"-w\")\n            args.append(url)\n        try:\n            return subprocess.call(args)\n        except OSError:\n            # Command not found\n            return 127\n\n    try:\n        if locate:\n            url = os.path.dirname(_unquote_file(url)) or \".\"\n        else:\n            url = _unquote_file(url)\n        c = subprocess.Popen([\"xdg-open\", url])\n        if wait:\n            return c.wait()\n        return 0\n    except OSError:\n        if url.startswith((\"http://\", \"https://\")) and not locate and not wait:\n            import webbrowser\n\n            webbrowser.open(url)\n            return 0\n        return 1\n"
  },
  {
    "path": "platformio/debug/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/debug/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-arguments, too-many-locals\n# pylint: disable=too-many-branches, too-many-statements\n\nimport asyncio\nimport os\nimport signal\nimport subprocess\n\nimport click\n\nfrom platformio import app, exception, fs, proc\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.debug import helpers\nfrom platformio.debug.config.factory import DebugConfigFactory\nfrom platformio.debug.exception import DebugInvalidOptionsError\nfrom platformio.debug.process.gdb import GDBClientProcess\nfrom platformio.exception import ReturnErrorCode\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import is_platformio_project\nfrom platformio.project.options import ProjectOptions\n\n\n@click.command(\n    \"debug\",\n    context_settings=dict(ignore_unknown_options=True),\n    short_help=\"Unified Debugger\",\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n)\n@click.option(\n    \"-c\",\n    \"--project-conf\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),\n)\n@click.option(\"--environment\", \"-e\", metavar=\"<environment>\")\n@click.option(\"--load-mode\", type=ProjectOptions[\"env.debug_load_mode\"].type)\n@click.option(\"--verbose\", \"-v\", is_flag=True)\n@click.option(\"--interface\", type=click.Choice([\"gdb\"]))\n@click.argument(\"client_extra_args\", nargs=-1, type=click.UNPROCESSED)\n@click.pass_context\ndef cli(  # pylint: disable=too-many-positional-arguments\n    ctx,\n    project_dir,\n    project_conf,\n    environment,\n    load_mode,\n    verbose,\n    interface,\n    client_extra_args,\n):\n    app.set_session_var(\"custom_project_conf\", project_conf)\n\n    if not interface and client_extra_args:\n        raise click.UsageError(\"Please specify debugging interface\")\n\n    # use env variables from Eclipse or CLion\n    for name in (\"CWD\", \"PWD\", \"PLATFORMIO_PROJECT_DIR\"):\n        if is_platformio_project(project_dir):\n            break\n        if os.getenv(name):\n            project_dir = os.getenv(name)\n\n    with fs.cd(project_dir):\n        project_config = ProjectConfig.get_instance(project_conf)\n        project_config.validate(envs=[environment] if environment else None)\n        env_name = environment or helpers.get_default_debug_env(project_config)\n\n        if not interface:\n            return helpers.predebug_project(\n                ctx, os.getcwd(), project_config, env_name, False, verbose\n            )\n\n        configure_args = (\n            ctx,\n            project_config,\n            env_name,\n            load_mode,\n            verbose,\n            client_extra_args,\n        )\n        if helpers.is_gdbmi_mode():\n            os.environ[\"PLATFORMIO_DISABLE_PROGRESSBAR\"] = \"true\"\n            stream = helpers.GDBMIConsoleStream()\n            with proc.capture_std_streams(stream):\n                debug_config = _configure(*configure_args)\n            stream.close()\n        else:\n            debug_config = _configure(*configure_args)\n\n        _run(os.getcwd(), debug_config, client_extra_args)\n\n    return None\n\n\ndef _configure(\n    ctx, project_config, env_name, load_mode, verbose, client_extra_args\n):  # pylint: disable=too-many-positional-arguments\n    platform = PlatformFactory.from_env(env_name, autoinstall=True)\n    debug_config = DebugConfigFactory.new(\n        platform,\n        project_config,\n        env_name,\n    )\n    if \"--version\" in client_extra_args:\n        raise ReturnErrorCode(\n            subprocess.run(\n                [debug_config.client_executable_path, \"--version\"], check=True\n            ).returncode\n        )\n\n    try:\n        fs.ensure_udev_rules()\n    except exception.InvalidUdevRules as exc:\n        click.echo(str(exc))\n\n    rebuild_prog = False\n    preload = debug_config.load_cmds == [\"preload\"]\n    load_mode = load_mode or debug_config.load_mode\n    if load_mode == \"always\":\n        rebuild_prog = preload or not helpers.has_debug_symbols(\n            debug_config.program_path\n        )\n    elif load_mode == \"modified\":\n        rebuild_prog = helpers.is_prog_obsolete(\n            debug_config.program_path\n        ) or not helpers.has_debug_symbols(debug_config.program_path)\n\n    if not (debug_config.program_path and os.path.isfile(debug_config.program_path)):\n        rebuild_prog = True\n\n    if preload or (not rebuild_prog and load_mode != \"always\"):\n        # don't load firmware through debug server\n        debug_config.load_cmds = []\n\n    if rebuild_prog:\n        click.echo(\"Preparing firmware for debugging...\")\n        helpers.predebug_project(\n            ctx, os.getcwd(), project_config, env_name, preload, verbose\n        )\n        # save SHA sum of newly created prog\n        if load_mode == \"modified\":\n            helpers.is_prog_obsolete(debug_config.program_path)\n\n    if not os.path.isfile(debug_config.program_path):\n        raise DebugInvalidOptionsError(\"Program/firmware is missed\")\n\n    return debug_config\n\n\ndef _run(project_dir, debug_config, client_extra_args):\n    try:\n        loop = asyncio.ProactorEventLoop() if IS_WINDOWS else asyncio.get_event_loop()\n    except RuntimeError:\n        loop = asyncio.new_event_loop()\n    asyncio.set_event_loop(loop)\n\n    client = GDBClientProcess(project_dir, debug_config)\n    coro = client.run(client_extra_args)\n    try:\n        signal.signal(signal.SIGINT, signal.SIG_IGN)\n        loop.run_until_complete(coro)\n        if IS_WINDOWS:\n            client.close()\n            # an issue with `asyncio` executor and STIDIN,\n            # it cannot be closed gracefully\n            proc.force_exit()\n    finally:\n        client.close()\n        loop.close()\n"
  },
  {
    "path": "platformio/debug/config/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/debug/config/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\n\nfrom platformio import fs, proc, util\nfrom platformio.compat import string_types\nfrom platformio.debug.exception import DebugInvalidOptionsError\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import load_build_metadata\nfrom platformio.project.options import ProjectOptions\n\n\nclass DebugConfigBase:  # pylint: disable=too-many-instance-attributes\n    DEFAULT_PORT = None\n\n    def __init__(self, platform, project_config, env_name):\n        self.platform = platform\n        self.project_config = project_config\n        self.env_name = env_name\n        self.env_options = project_config.items(env=env_name, as_dict=True)\n        self.build_data = self._load_build_data()\n\n        self.tool_name = None\n        self.board_config = {}\n        self.tool_settings = {}\n        if \"board\" in self.env_options:\n            self.board_config = platform.board_config(self.env_options[\"board\"])\n            self.tool_name = self.board_config.get_debug_tool_name(\n                self.env_options.get(\"debug_tool\")\n            )\n            self.tool_settings = (\n                self.board_config.get(\"debug\", {})\n                .get(\"tools\", {})\n                .get(self.tool_name, {})\n            )\n\n        self._load_cmds = None\n        self._port = None\n\n        self.server = self._configure_server()\n\n        try:\n            platform.configure_debug_session(self)\n        except NotImplementedError:\n            pass\n\n    @staticmethod\n    def cleanup_cmds(items):\n        items = ProjectConfig.parse_multi_values(items)\n        return [\"$LOAD_CMDS\" if item == \"$LOAD_CMD\" else item for item in items]\n\n    @property\n    def program_path(self):\n        return self.build_data[\"prog_path\"]\n\n    @property\n    def client_executable_path(self):\n        return self.build_data[\"gdb_path\"]\n\n    @property\n    def load_cmds(self):\n        if self._load_cmds is not None:\n            return self._load_cmds\n        result = self.env_options.get(\"debug_load_cmds\")\n        if not result:\n            result = self.tool_settings.get(\"load_cmds\")\n        if not result:\n            # legacy\n            result = self.tool_settings.get(\"load_cmd\")\n        if not result:\n            result = ProjectOptions[\"env.debug_load_cmds\"].default\n        return self.cleanup_cmds(result)\n\n    @load_cmds.setter\n    def load_cmds(self, cmds):\n        self._load_cmds = cmds\n\n    @property\n    def load_mode(self):\n        result = self.env_options.get(\"debug_load_mode\")\n        if not result:\n            result = self.tool_settings.get(\"load_mode\")\n        return result or ProjectOptions[\"env.debug_load_mode\"].default\n\n    @property\n    def init_break(self):\n        missed = object()\n        result = self.env_options.get(\"debug_init_break\", missed)\n        if result != missed:\n            return result\n        result = None\n        if not result:\n            result = self.tool_settings.get(\"init_break\")\n        return result or ProjectOptions[\"env.debug_init_break\"].default\n\n    @property\n    def init_cmds(self):\n        return self.cleanup_cmds(\n            self.env_options.get(\"debug_init_cmds\", self.tool_settings.get(\"init_cmds\"))\n        )\n\n    @property\n    def extra_cmds(self):\n        return self.cleanup_cmds(\n            self.env_options.get(\"debug_extra_cmds\")\n        ) + self.cleanup_cmds(self.tool_settings.get(\"extra_cmds\"))\n\n    @property\n    def port(self):\n        return (\n            self._port\n            or self.env_options.get(\"debug_port\")\n            or self.tool_settings.get(\"port\")\n            or self.DEFAULT_PORT\n        )\n\n    @port.setter\n    def port(self, value):\n        self._port = value\n\n    @property\n    def upload_protocol(self):\n        return self.env_options.get(\n            \"upload_protocol\", self.board_config.get(\"upload\", {}).get(\"protocol\")\n        )\n\n    @property\n    def speed(self):\n        return self.env_options.get(\"debug_speed\", self.tool_settings.get(\"speed\"))\n\n    @property\n    def server_ready_pattern(self):\n        return self.env_options.get(\n            \"debug_server_ready_pattern\", (self.server or {}).get(\"ready_pattern\")\n        )\n\n    def _load_build_data(self):\n        data = load_build_metadata(\n            os.getcwd(), self.env_name, cache=True, build_type=\"debug\"\n        )\n        if not data:\n            raise DebugInvalidOptionsError(\"Could not load a build configuration\")\n        return data\n\n    def _configure_server(self):\n        # user disabled server in platformio.ini\n        if \"debug_server\" in self.env_options and not self.env_options.get(\n            \"debug_server\"\n        ):\n            return None\n\n        result = None\n\n        # specific server per a system\n        if isinstance(self.tool_settings.get(\"server\", {}), list):\n            for item in self.tool_settings[\"server\"][:]:\n                self.tool_settings[\"server\"] = item\n                if util.get_systype() in item.get(\"system\", []):\n                    break\n\n        # user overwrites debug server\n        if self.env_options.get(\"debug_server\"):\n            result = {\n                \"cwd\": None,\n                \"executable\": None,\n                \"arguments\": self.env_options.get(\"debug_server\"),\n            }\n            result[\"executable\"] = result[\"arguments\"][0]\n            result[\"arguments\"] = result[\"arguments\"][1:]\n        elif \"server\" in self.tool_settings:\n            result = self.tool_settings[\"server\"]\n            server_package = result.get(\"package\")\n            server_package_dir = (\n                self.platform.get_package_dir(server_package)\n                if server_package\n                else None\n            )\n            if server_package and not server_package_dir:\n                self.platform.install_package(server_package)\n                server_package_dir = self.platform.get_package_dir(server_package)\n            result.update(\n                dict(\n                    cwd=server_package_dir if server_package else None,\n                    executable=result.get(\"executable\"),\n                    arguments=[\n                        (\n                            a.replace(\"$PACKAGE_DIR\", server_package_dir)\n                            if server_package_dir\n                            else a\n                        )\n                        for a in result.get(\"arguments\", [])\n                    ],\n                )\n            )\n        return self.reveal_patterns(result) if result else None\n\n    def get_init_script(self, debugger):\n        try:\n            return getattr(self, \"%s_INIT_SCRIPT\" % debugger.upper())\n        except AttributeError as exc:\n            raise NotImplementedError from exc\n\n    def reveal_patterns(self, source, recursive=True):\n        program_path = self.program_path or \"\"\n        patterns = {\n            \"PLATFORMIO_CORE_DIR\": self.project_config.get(\"platformio\", \"core_dir\"),\n            \"PYTHONEXE\": proc.get_pythonexe_path(),\n            \"PROJECT_DIR\": os.getcwd(),\n            \"PROG_PATH\": program_path,\n            \"PROG_DIR\": os.path.dirname(program_path),\n            \"PROG_NAME\": os.path.basename(os.path.splitext(program_path)[0]),\n            \"DEBUG_PORT\": self.port,\n            \"UPLOAD_PROTOCOL\": self.upload_protocol,\n            \"INIT_BREAK\": self.init_break or \"\",\n            \"LOAD_CMDS\": \"\\n\".join(self.load_cmds or []),\n        }\n        for key, value in patterns.items():\n            if key.endswith((\"_DIR\", \"_PATH\")):\n                patterns[key] = fs.to_unix_path(value)\n\n        def _replace(text):\n            for key, value in patterns.items():\n                pattern = \"$%s\" % key\n                text = text.replace(pattern, value or \"\")\n            return text\n\n        if isinstance(source, string_types):\n            source = _replace(source)\n        elif isinstance(source, (list, dict)):\n            items = enumerate(source) if isinstance(source, list) else source.items()\n            for key, value in items:\n                if isinstance(value, string_types):\n                    source[key] = _replace(value)\n                elif isinstance(value, (list, dict)) and recursive:\n                    source[key] = self.reveal_patterns(value, patterns)\n\n        data = json.dumps(source)\n        if any((\"$\" + key) in data for key in patterns):\n            source = self.reveal_patterns(source, patterns)\n\n        return source\n"
  },
  {
    "path": "platformio/debug/config/blackmagic.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\nfrom platformio.debug.exception import DebugInvalidOptionsError\nfrom platformio.device.finder import SerialPortFinder, is_pattern_port\n\n\nclass BlackmagicDebugConfig(DebugConfigBase):\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\n    set language c\n    set *0xE000ED0C = 0x05FA0004\n    set $busy = (*0xE000ED0C & 0x4)\n    while ($busy)\n        set $busy = (*0xE000ED0C & 0x4)\n    end\n    set language auto\nend\n\ndefine pio_reset_run_target\n    pio_reset_halt_target\nend\n\ntarget extended-remote $DEBUG_PORT\nmonitor swdp_scan\nattach 1\nset mem inaccessible-by-default off\n$LOAD_CMDS\n$INIT_BREAK\n\nset language c\nset *0xE000ED0C = 0x05FA0004\nset $busy = (*0xE000ED0C & 0x4)\nwhile ($busy)\n    set $busy = (*0xE000ED0C & 0x4)\nend\nset language auto\n\"\"\"\n\n    @property\n    def port(self):\n        # pylint: disable=assignment-from-no-return\n        initial_port = DebugConfigBase.port.fget(self)\n        if initial_port and not is_pattern_port(initial_port):\n            return initial_port\n        port = SerialPortFinder(\n            board_config=self.board_config,\n            upload_protocol=self.tool_name,\n            prefer_gdb_port=True,\n        ).find(initial_port)\n        if port:\n            return port\n        raise DebugInvalidOptionsError(\n            \"Please specify `debug_port` for the working environment\"\n        )\n\n    @port.setter\n    def port(self, value):\n        self._port = value\n"
  },
  {
    "path": "platformio/debug/config/factory.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport importlib\nimport re\n\nfrom platformio.debug.config.generic import GenericDebugConfig\nfrom platformio.debug.config.native import NativeDebugConfig\n\n\nclass DebugConfigFactory:\n    @staticmethod\n    def get_clsname(name):\n        name = re.sub(r\"[^\\da-z\\_\\-]+\", \"\", name, flags=re.I)\n        return \"%sDebugConfig\" % name.lower().capitalize()\n\n    @classmethod\n    def new(cls, platform, project_config, env_name):\n        board_id = project_config.get(\"env:\" + env_name, \"board\")\n        config_cls = None\n        tool_name = None\n        if board_id:\n            tool_name = platform.board_config(\n                project_config.get(\"env:\" + env_name, \"board\")\n            ).get_debug_tool_name(project_config.get(\"env:\" + env_name, \"debug_tool\"))\n        try:\n            mod = importlib.import_module(\"platformio.debug.config.%s\" % tool_name)\n            config_cls = getattr(mod, cls.get_clsname(tool_name))\n        except ModuleNotFoundError:\n            config_cls = (\n                GenericDebugConfig if platform.is_embedded() else NativeDebugConfig\n            )\n        return config_cls(platform, project_config, env_name)\n"
  },
  {
    "path": "platformio/debug/config/generic.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass GenericDebugConfig(DebugConfigBase):\n    DEFAULT_PORT = \":3333\"\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\n    monitor reset halt\nend\n\ndefine pio_reset_run_target\n    monitor reset\nend\n\ntarget extended-remote $DEBUG_PORT\nmonitor init\n$LOAD_CMDS\npio_reset_halt_target\n$INIT_BREAK\n\"\"\"\n"
  },
  {
    "path": "platformio/debug/config/jlink.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass JlinkDebugConfig(DebugConfigBase):\n    DEFAULT_PORT = \":2331\"\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\n    monitor reset\n    monitor halt\nend\n\ndefine pio_reset_run_target\n    monitor clrbp\n    monitor reset\n    monitor go\nend\n\ntarget extended-remote $DEBUG_PORT\nmonitor clrbp\nmonitor speed auto\npio_reset_halt_target\n$LOAD_CMDS\n$INIT_BREAK\n\"\"\"\n\n    @property\n    def server_ready_pattern(self):\n        return super().server_ready_pattern or (\"Waiting for GDB connection\")\n"
  },
  {
    "path": "platformio/debug/config/mspdebug.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass MspdebugDebugConfig(DebugConfigBase):\n    DEFAULT_PORT = \":2000\"\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\nend\n\ndefine pio_reset_run_target\nend\n\ntarget remote $DEBUG_PORT\nmonitor erase\n$LOAD_CMDS\npio_reset_halt_target\n$INIT_BREAK\n\"\"\"\n"
  },
  {
    "path": "platformio/debug/config/native.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass NativeDebugConfig(DebugConfigBase):\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\nend\n\ndefine pio_reset_run_target\nend\n\ndefine pio_restart_target\nend\n\n$INIT_BREAK\n\"\"\" + (\"set startup-with-shell off\" if not IS_WINDOWS else \"\")\n"
  },
  {
    "path": "platformio/debug/config/qemu.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass QemuDebugConfig(DebugConfigBase):\n    DEFAULT_PORT = \":1234\"\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\n    monitor system_reset\nend\n\ndefine pio_reset_run_target\n    monitor system_reset\nend\n\ntarget extended-remote $DEBUG_PORT\n$LOAD_CMDS\npio_reset_halt_target\n$INIT_BREAK\n\"\"\"\n"
  },
  {
    "path": "platformio/debug/config/renode.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.debug.config.base import DebugConfigBase\n\n\nclass RenodeDebugConfig(DebugConfigBase):\n    DEFAULT_PORT = \":3333\"\n    GDB_INIT_SCRIPT = \"\"\"\ndefine pio_reset_halt_target\n    monitor machine Reset\n    $LOAD_CMDS\n    monitor start\nend\n\ndefine pio_reset_run_target\n    pio_reset_halt_target\nend\n\ntarget extended-remote $DEBUG_PORT\n$LOAD_CMDS\n$INIT_BREAK\nmonitor start\n\"\"\"\n\n    @property\n    def server_ready_pattern(self):\n        return super().server_ready_pattern or (\n            \"GDB server with all CPUs started on port\"\n        )\n"
  },
  {
    "path": "platformio/debug/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.exception import PlatformioException, UserSideException\n\n\nclass DebugError(PlatformioException):\n    pass\n\n\nclass DebugSupportError(DebugError, UserSideException):\n    MESSAGE = (\n        \"Currently, PlatformIO does not support debugging for `{0}`.\\n\"\n        \"Please request support at https://github.com/platformio/\"\n        \"platformio-core/issues \\nor visit -> https://docs.platformio.org\"\n        \"/page/plus/debugging.html\"\n    )\n\n\nclass DebugInvalidOptionsError(DebugError, UserSideException):\n    pass\n\n\nclass DebugInitError(DebugError, UserSideException):\n    pass\n"
  },
  {
    "path": "platformio/debug/helpers.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport sys\nimport time\nfrom hashlib import sha1\nfrom io import BytesIO\n\nfrom platformio.cli import PlatformioCLI\nfrom platformio.compat import is_bytes\nfrom platformio.debug.exception import DebugInvalidOptionsError\nfrom platformio.run.cli import cli as cmd_run\nfrom platformio.run.cli import print_processing_header\nfrom platformio.test.helpers import list_test_names\nfrom platformio.test.result import TestSuite\nfrom platformio.test.runners.base import TestRunnerOptions\nfrom platformio.test.runners.factory import TestRunnerFactory\n\n\nclass GDBMIConsoleStream(BytesIO):  # pylint: disable=too-few-public-methods\n    STDOUT = sys.stdout\n\n    def write(self, text):\n        self.STDOUT.write(escape_gdbmi_stream(\"~\", text))\n        self.STDOUT.flush()\n\n\ndef is_gdbmi_mode():\n    return \"--interpreter\" in \" \".join(PlatformioCLI.leftover_args)\n\n\ndef escape_gdbmi_stream(prefix, stream):\n    bytes_stream = False\n    if is_bytes(stream):\n        bytes_stream = True\n        stream = stream.decode()\n\n    if not stream:\n        return b\"\" if bytes_stream else \"\"\n\n    ends_nl = stream.endswith(\"\\n\")\n    stream = re.sub(r\"\\\\+\", \"\\\\\\\\\\\\\\\\\", stream)\n    stream = stream.replace('\"', '\\\\\"')\n    stream = stream.replace(\"\\n\", \"\\\\n\")\n    stream = '%s\"%s\"' % (prefix, stream)\n    if ends_nl:\n        stream += \"\\n\"\n\n    return stream.encode() if bytes_stream else stream\n\n\ndef get_default_debug_env(config):\n    default_envs = config.default_envs()\n    all_envs = config.envs()\n    for env in default_envs:\n        if config.get(\"env:\" + env, \"build_type\") == \"debug\":\n            return env\n    for env in all_envs:\n        if config.get(\"env:\" + env, \"build_type\") == \"debug\":\n            return env\n    return default_envs[0] if default_envs else all_envs[0]\n\n\ndef predebug_project(\n    ctx, project_dir, project_config, env_name, preload, verbose\n):  # pylint: disable=too-many-arguments,too-many-positional-arguments\n    debug_testname = project_config.get(\"env:\" + env_name, \"debug_test\")\n    if debug_testname:\n        test_names = list_test_names(project_config)\n        if debug_testname not in test_names:\n            raise DebugInvalidOptionsError(\n                \"Unknown test name `%s`. Valid names are `%s`\"\n                % (debug_testname, \", \".join(test_names))\n            )\n        print_processing_header(env_name, project_config, verbose)\n        test_runner = TestRunnerFactory.new(\n            TestSuite(env_name, debug_testname),\n            project_config,\n            TestRunnerOptions(\n                verbose=3 if verbose else 0,\n                without_building=False,\n                without_debugging=False,\n                without_uploading=not preload,\n                without_testing=True,\n            ),\n        )\n        test_runner.start(ctx)\n    else:\n        ctx.invoke(\n            cmd_run,\n            project_dir=project_dir,\n            project_conf=project_config.path,\n            environment=[env_name],\n            target=[\"__debug\"] + ([\"upload\"] if preload else []),\n            verbose=verbose,\n        )\n\n    if preload:\n        time.sleep(5)\n\n\ndef has_debug_symbols(prog_path):\n    if not os.path.isfile(prog_path):\n        return False\n    matched = {\n        b\".debug_info\": False,\n        b\".debug_abbrev\": False,\n        b\" -Og\": False,\n        b\" -g\": False,\n        # b\"__PLATFORMIO_BUILD_DEBUG__\": False,\n    }\n    with open(prog_path, \"rb\") as fp:\n        last_data = b\"\"\n        while True:\n            data = fp.read(1024)\n            if not data:\n                break\n            for pattern, found in matched.items():\n                if found:\n                    continue\n                if pattern in last_data + data:\n                    matched[pattern] = True\n            last_data = data\n    return all(matched.values())\n\n\ndef is_prog_obsolete(prog_path):\n    prog_hash_path = prog_path + \".sha1\"\n    if not os.path.isfile(prog_path):\n        return True\n    shasum = sha1()\n    with open(prog_path, \"rb\") as fp:\n        while True:\n            data = fp.read(1024)\n            if not data:\n                break\n            shasum.update(data)\n    new_digest = shasum.hexdigest()\n    old_digest = None\n    if os.path.isfile(prog_hash_path):\n        with open(prog_hash_path, encoding=\"utf8\") as fp:\n            old_digest = fp.read()\n    if new_digest == old_digest:\n        return False\n    with open(prog_hash_path, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(new_digest)\n    return True\n"
  },
  {
    "path": "platformio/debug/process/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/debug/process/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport asyncio\nimport signal\nimport subprocess\nimport sys\nimport time\n\nfrom platformio.compat import (\n    IS_WINDOWS,\n    aio_create_task,\n    aio_get_running_loop,\n    get_locale_encoding,\n)\n\n\nclass DebugSubprocessProtocol(asyncio.SubprocessProtocol):\n    def __init__(self, factory):\n        self.factory = factory\n        self._is_exited = False\n\n    def connection_made(self, transport):\n        self.factory.connection_made(transport)\n\n    def pipe_data_received(self, fd, data):\n        pipe_to_cb = [\n            self.factory.stdin_data_received,\n            self.factory.stdout_data_received,\n            self.factory.stderr_data_received,\n        ]\n        pipe_to_cb[fd](data)\n\n    def connection_lost(self, exc):\n        self.process_exited()\n\n    def process_exited(self):\n        if self._is_exited:\n            return\n        self.factory.process_exited()\n        self._is_exited = True\n\n\nclass DebugBaseProcess:\n    STDOUT_CHUNK_SIZE = 2048\n    LOG_FILE = None\n\n    def __init__(self):\n        self.transport = None\n        self._is_running = False\n        self._last_activity = 0\n        self._exit_future = None\n        self._stdin_read_task = None\n        self._std_encoding = get_locale_encoding()\n\n    async def spawn(self, *args, **kwargs):\n        wait_until_exit = False\n        if \"wait_until_exit\" in kwargs:\n            wait_until_exit = kwargs[\"wait_until_exit\"]\n            del kwargs[\"wait_until_exit\"]\n        for pipe in (\"stdin\", \"stdout\", \"stderr\"):\n            if pipe not in kwargs:\n                kwargs[pipe] = subprocess.PIPE\n        loop = aio_get_running_loop()\n        await loop.subprocess_exec(\n            lambda: DebugSubprocessProtocol(self), *args, **kwargs\n        )\n        if wait_until_exit:\n            self._exit_future = loop.create_future()\n            await self._exit_future\n\n    def is_running(self):\n        return self._is_running\n\n    def connection_made(self, transport):\n        self._is_running = True\n        self.transport = transport\n\n    def connect_stdin_pipe(self):\n        self._stdin_read_task = aio_create_task(self._read_stdin_pipe())\n\n    async def _read_stdin_pipe(self):\n        loop = aio_get_running_loop()\n        if IS_WINDOWS:\n            while True:\n                self.stdin_data_received(\n                    await loop.run_in_executor(None, sys.stdin.buffer.readline)\n                )\n        else:\n            reader = asyncio.StreamReader()\n            protocol = asyncio.StreamReaderProtocol(reader)\n            await loop.connect_read_pipe(lambda: protocol, sys.stdin)\n            while True:\n                self.stdin_data_received(await reader.readline())\n\n    def stdin_data_received(self, data):\n        self._last_activity = time.time()\n        if self.LOG_FILE:\n            with open(self.LOG_FILE, \"ab\") as fp:\n                fp.write(data)\n\n    def stdout_data_received(self, data):\n        self._last_activity = time.time()\n        if self.LOG_FILE:\n            with open(self.LOG_FILE, \"ab\") as fp:\n                fp.write(data)\n        while data:\n            chunk = data[: self.STDOUT_CHUNK_SIZE]\n            print(chunk.decode(self._std_encoding, \"replace\"), end=\"\", flush=True)\n            data = data[self.STDOUT_CHUNK_SIZE :]\n\n    def stderr_data_received(self, data):\n        self._last_activity = time.time()\n        if self.LOG_FILE:\n            with open(self.LOG_FILE, \"ab\") as fp:\n                fp.write(data)\n        print(\n            data.decode(self._std_encoding, \"replace\"),\n            end=\"\",\n            file=sys.stderr,\n            flush=True,\n        )\n\n    def process_exited(self):\n        self._is_running = False\n        self._last_activity = time.time()\n        # Allow terminating via SIGINT/CTRL+C\n        signal.signal(signal.SIGINT, signal.default_int_handler)\n        if self._stdin_read_task:\n            self._stdin_read_task.cancel()\n            self._stdin_read_task = None\n        if self._exit_future:\n            self._exit_future.set_result(True)\n            self._exit_future = None\n\n    def terminate(self):\n        if not self.is_running() or not self.transport:\n            return\n        try:\n            self.transport.kill()\n            self.transport.close()\n        except:  # pylint: disable=bare-except\n            pass\n"
  },
  {
    "path": "platformio/debug/process/client.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport os\nimport signal\nimport tempfile\n\nfrom platformio import fs, proc\nfrom platformio.cache import ContentCache\nfrom platformio.compat import IS_WINDOWS, hashlib_encode_data\nfrom platformio.debug.process.base import DebugBaseProcess\nfrom platformio.debug.process.server import DebugServerProcess\nfrom platformio.project.helpers import get_project_cache_dir\n\n\nclass DebugClientProcess(DebugBaseProcess):\n    def __init__(self, project_dir, debug_config):\n        super().__init__()\n        self.project_dir = project_dir\n        self.debug_config = debug_config\n\n        self._server_process = None\n        self._session_id = None\n\n        if not os.path.isdir(get_project_cache_dir()):\n            os.makedirs(get_project_cache_dir())\n        self.working_dir = tempfile.mkdtemp(\n            dir=get_project_cache_dir(), prefix=\".piodebug-\"\n        )\n\n        self._target_is_running = False\n        self._errors_buffer = b\"\"\n\n    async def run(self):\n        session_hash = (\n            self.debug_config.client_executable_path + self.debug_config.program_path\n        )\n        self._session_id = hashlib.sha1(hashlib_encode_data(session_hash)).hexdigest()\n        self._kill_previous_session()\n\n        if self.debug_config.server:\n            self._server_process = DebugServerProcess(self.debug_config)\n            self.debug_config.port = await self._server_process.run()\n\n    def connection_made(self, transport):\n        super().connection_made(transport)\n        self._lock_session(transport.get_pid())\n        # Disable SIGINT and allow GDB's Ctrl+C interrupt\n        signal.signal(signal.SIGINT, lambda *args, **kwargs: None)\n        self.connect_stdin_pipe()\n\n    def process_exited(self):\n        if self._server_process:\n            self._server_process.terminate()\n        super().process_exited()\n\n    def close(self):\n        self._unlock_session()\n        if self.working_dir and os.path.isdir(self.working_dir):\n            fs.rmtree(self.working_dir)\n\n    def __del__(self):\n        self.close()\n\n    def _kill_previous_session(self):\n        assert self._session_id\n        pid = None\n        with ContentCache() as cc:\n            pid = cc.get(self._session_id)\n            cc.delete(self._session_id)\n        if not pid:\n            return\n        if IS_WINDOWS:\n            kill = [\"Taskkill\", \"/PID\", pid, \"/F\"]\n        else:\n            kill = [\"kill\", pid]\n        try:\n            proc.exec_command(kill)\n        except:  # pylint: disable=bare-except\n            pass\n\n    def _lock_session(self, pid):\n        if not self._session_id:\n            return\n        with ContentCache() as cc:\n            cc.set(self._session_id, str(pid), \"1h\")\n\n    def _unlock_session(self):\n        if not self._session_id:\n            return\n        with ContentCache() as cc:\n            cc.delete(self._session_id)\n"
  },
  {
    "path": "platformio/debug/process/gdb.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport signal\nimport time\n\nfrom platformio import telemetry\nfrom platformio.compat import aio_get_running_loop, is_bytes\nfrom platformio.debug import helpers\nfrom platformio.debug.exception import DebugInitError\nfrom platformio.debug.process.client import DebugClientProcess\n\n\nclass GDBClientProcess(DebugClientProcess):\n    PIO_SRC_NAME = \".pioinit\"\n    INIT_COMPLETED_BANNER = \"PlatformIO: Initialization completed\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._target_is_running = False\n        self._errors_buffer = b\"\"\n\n    async def run(self, extra_args):  # pylint: disable=arguments-differ\n        await super().run()\n\n        self.generate_init_script(os.path.join(self.working_dir, self.PIO_SRC_NAME))\n        gdb_path = self.debug_config.client_executable_path or \"gdb\"\n        # start GDB client\n        args = [\n            gdb_path,\n            \"-q\",\n            \"--directory\",\n            self.working_dir,\n            \"--directory\",\n            self.project_dir,\n            \"-l\",\n            \"10\",\n        ]\n        args.extend(list(extra_args or []))\n        gdb_data_dir = self._get_data_dir(gdb_path)\n        if gdb_data_dir:\n            args.extend([\"--data-directory\", gdb_data_dir])\n        args.append(self.debug_config.program_path)\n\n        await self.spawn(*args, cwd=self.project_dir, wait_until_exit=True)\n\n    @staticmethod\n    def _get_data_dir(gdb_path):\n        if \"msp430\" in gdb_path:\n            return None\n        gdb_data_dir = os.path.abspath(\n            os.path.join(os.path.dirname(gdb_path), \"..\", \"share\", \"gdb\")\n        )\n        return gdb_data_dir if os.path.isdir(gdb_data_dir) else None\n\n    def generate_init_script(self, dst):\n        # default GDB init commands depending on debug tool\n        commands = self.debug_config.get_init_script(\"gdb\").split(\"\\n\")\n\n        if self.debug_config.init_cmds:\n            commands = self.debug_config.init_cmds\n        commands.extend(self.debug_config.extra_cmds)\n\n        if not any(\"define pio_reset_run_target\" in cmd for cmd in commands):\n            commands = [\n                \"define pio_reset_run_target\",\n                \"   echo Warning! Undefined pio_reset_run_target command\\\\n\",\n                \"   monitor reset\",\n                \"end\",\n            ] + commands\n        if not any(\"define pio_reset_halt_target\" in cmd for cmd in commands):\n            commands = [\n                \"define pio_reset_halt_target\",\n                \"   echo Warning! Undefined pio_reset_halt_target command\\\\n\",\n                \"   monitor reset halt\",\n                \"end\",\n            ] + commands\n        if not any(\"define pio_restart_target\" in cmd for cmd in commands):\n            commands += [\n                \"define pio_restart_target\",\n                \"   pio_reset_halt_target\",\n                \"   $INIT_BREAK\",\n                \"   %s\" % (\"continue\" if self.debug_config.init_break else \"next\"),\n                \"end\",\n            ]\n\n        banner = [\n            \"echo PlatformIO Unified Debugger -> https://bit.ly/pio-debug\\\\n\",\n            \"echo PlatformIO: debug_tool = %s\\\\n\" % self.debug_config.tool_name,\n            \"echo PlatformIO: Initializing remote target...\\\\n\",\n        ]\n        footer = [\"echo %s\\\\n\" % self.INIT_COMPLETED_BANNER]\n        commands = banner + commands + footer\n\n        with open(dst, mode=\"w\", encoding=\"utf8\") as fp:\n            fp.write(\"\\n\".join(self.debug_config.reveal_patterns(commands)))\n\n    def stdin_data_received(self, data):\n        super().stdin_data_received(data)\n        if b\"-exec-run\" in data:\n            if self._target_is_running:\n                token, _ = data.split(b\"-\", 1)\n                self.stdout_data_received(token + b\"^running\\n\")\n                return\n            if self.debug_config.platform.is_embedded():\n                data = data.replace(b\"-exec-run\", b\"-exec-continue\")\n\n        if b\"-exec-continue\" in data:\n            self._target_is_running = True\n        if b\"-gdb-exit\" in data or data.strip() in (b\"q\", b\"quit\"):\n            # Allow terminating via SIGINT/CTRL+C\n            signal.signal(signal.SIGINT, signal.default_int_handler)\n            self.transport.get_pipe_transport(0).write(b\"pio_reset_run_target\\n\")\n        self.transport.get_pipe_transport(0).write(data)\n\n    def stdout_data_received(self, data):\n        super().stdout_data_received(data)\n        self._handle_error(data)\n        # go to init break automatically\n        if self.INIT_COMPLETED_BANNER.encode() in data:\n            telemetry.log_debug_started(self.debug_config)\n            self._auto_exec_continue()\n\n    def console_log(self, msg):\n        if helpers.is_gdbmi_mode():\n            msg = helpers.escape_gdbmi_stream(\"~\", msg)\n        self.stdout_data_received(msg if is_bytes(msg) else msg.encode())\n\n    def _auto_exec_continue(self):\n        auto_exec_delay = 0.5  # in seconds\n        if self._last_activity > (time.time() - auto_exec_delay):\n            aio_get_running_loop().call_later(0.1, self._auto_exec_continue)\n            return\n\n        if not self.debug_config.init_break or self._target_is_running:\n            return\n\n        self.console_log(\n            \"PlatformIO: Resume the execution to `debug_init_break = %s`\\n\"\n            % self.debug_config.init_break\n        )\n        self.console_log(\n            \"PlatformIO: More configuration options -> https://bit.ly/pio-debug\\n\"\n        )\n        if self.debug_config.platform.is_embedded():\n            self.transport.get_pipe_transport(0).write(\n                b\"0-exec-continue\\n\" if helpers.is_gdbmi_mode() else b\"continue\\n\"\n            )\n        else:\n            self.transport.get_pipe_transport(0).write(\n                b\"0-exec-run\\n\" if helpers.is_gdbmi_mode() else b\"run\\n\"\n            )\n        self._target_is_running = True\n\n    def stderr_data_received(self, data):\n        super().stderr_data_received(data)\n        self._handle_error(data)\n\n    def _handle_error(self, data):\n        self._errors_buffer = (self._errors_buffer + data)[-8192:]  # keep last 8 KBytes\n        if not (\n            self.PIO_SRC_NAME.encode() in self._errors_buffer\n            and b\"Error in sourced\" in self._errors_buffer\n        ):\n            return\n        telemetry.log_debug_exception(\n            DebugInitError(self._errors_buffer.decode()), self.debug_config\n        )\n        self.transport.close()\n"
  },
  {
    "path": "platformio/debug/process/server.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport asyncio\nimport os\nimport re\nimport time\n\nfrom platformio import fs\nfrom platformio.compat import IS_MACOS, IS_WINDOWS\nfrom platformio.debug.exception import DebugInvalidOptionsError\nfrom platformio.debug.helpers import escape_gdbmi_stream, is_gdbmi_mode\nfrom platformio.debug.process.base import DebugBaseProcess\nfrom platformio.proc import where_is_program\n\n\nclass DebugServerProcess(DebugBaseProcess):\n    STD_BUFFER_SIZE = 1024\n\n    def __init__(self, debug_config):\n        super().__init__()\n        self.debug_config = debug_config\n        self._ready = False\n        self._std_buffer = {\"out\": b\"\", \"err\": b\"\"}\n\n    async def run(self):  # pylint: disable=too-many-branches\n        server = self.debug_config.server\n        if not server:\n            return None\n        server_executable = server[\"executable\"]\n        if not server_executable:\n            return None\n        if server[\"cwd\"]:\n            server_executable = os.path.join(server[\"cwd\"], server_executable)\n        if (\n            IS_WINDOWS\n            and not server_executable.endswith(\".exe\")\n            and os.path.isfile(server_executable + \".exe\")\n        ):\n            server_executable = server_executable + \".exe\"\n\n        if not os.path.isfile(server_executable):\n            server_executable = where_is_program(server_executable)\n        if not os.path.isfile(server_executable):\n            raise DebugInvalidOptionsError(\n                \"Could not launch Debug Server '%s'. Please check that it \"\n                \"is installed and is included in a system PATH\\n\"\n                \"See https://docs.platformio.org/page/plus/debugging.html\"\n                % server_executable\n            )\n\n        openocd_pipe_allowed = all(\n            [\n                not self.debug_config.env_options.get(\n                    \"debug_port\", self.debug_config.tool_settings.get(\"port\")\n                ),\n                \"gdb\" in self.debug_config.client_executable_path,\n                \"openocd\" in server_executable,\n            ]\n        )\n        if openocd_pipe_allowed:\n            args = []\n            if server[\"cwd\"]:\n                args.extend([\"-s\", server[\"cwd\"]])\n            args.extend(\n                [\"-c\", \"gdb_port pipe; tcl_port disabled; telnet_port disabled\"]\n            )\n            args.extend(server[\"arguments\"])\n            str_args = \" \".join(\n                [arg if arg.startswith(\"-\") else '\"%s\"' % arg for arg in args]\n            )\n            return fs.to_unix_path('| \"%s\" %s' % (server_executable, str_args))\n\n        env = os.environ.copy()\n        # prepend server \"lib\" folder to LD path\n        if (\n            not IS_WINDOWS\n            and server[\"cwd\"]\n            and os.path.isdir(os.path.join(server[\"cwd\"], \"lib\"))\n        ):\n            ld_key = \"DYLD_LIBRARY_PATH\" if IS_MACOS else \"LD_LIBRARY_PATH\"\n            env[ld_key] = os.path.join(server[\"cwd\"], \"lib\")\n            if os.environ.get(ld_key):\n                env[ld_key] = \"%s:%s\" % (env[ld_key], os.environ.get(ld_key))\n        # prepend BIN to PATH\n        if server[\"cwd\"] and os.path.isdir(os.path.join(server[\"cwd\"], \"bin\")):\n            env[\"PATH\"] = \"%s%s%s\" % (\n                os.path.join(server[\"cwd\"], \"bin\"),\n                os.pathsep,\n                os.environ.get(\"PATH\", os.environ.get(\"Path\", \"\")),\n            )\n\n        await self.spawn(\n            *([server_executable] + server[\"arguments\"]), cwd=server[\"cwd\"], env=env\n        )\n        await self._wait_until_ready()\n\n        return self.debug_config.port\n\n    async def _wait_until_ready(self):\n        ready_pattern = self.debug_config.server_ready_pattern\n        timeout = 60 if ready_pattern else 10\n        elapsed = 0\n        delay = 0.5\n        auto_ready_delay = 0.5\n        while not self._ready and self.is_running() and elapsed < timeout:\n            await asyncio.sleep(delay)\n            if not ready_pattern:\n                self._ready = self._last_activity < (time.time() - auto_ready_delay)\n            elapsed += delay\n\n    def _check_ready_by_pattern(self, data):\n        if self._ready:\n            return self._ready\n        ready_pattern = self.debug_config.server_ready_pattern\n        if ready_pattern:\n            if ready_pattern.startswith(\"^\"):\n                self._ready = re.match(\n                    ready_pattern,\n                    data.decode(\"utf-8\", \"ignore\"),\n                )\n            else:\n                self._ready = ready_pattern.encode() in data\n        return self._ready\n\n    def stdout_data_received(self, data):\n        super().stdout_data_received(\n            escape_gdbmi_stream(\"@\", data) if is_gdbmi_mode() else data\n        )\n        self._std_buffer[\"out\"] += data\n        self._check_ready_by_pattern(self._std_buffer[\"out\"])\n        self._std_buffer[\"out\"] = self._std_buffer[\"out\"][-1 * self.STD_BUFFER_SIZE :]\n\n    def stderr_data_received(self, data):\n        super().stderr_data_received(data)\n        self._std_buffer[\"err\"] += data\n        self._check_ready_by_pattern(self._std_buffer[\"err\"])\n        self._std_buffer[\"err\"] = self._std_buffer[\"err\"][-1 * self.STD_BUFFER_SIZE :]\n"
  },
  {
    "path": "platformio/dependencies.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.compat import is_proxy_set\n\n\ndef get_core_dependencies():\n    return {\n        \"contrib-piohome\": \"~3.4.2\",\n        \"contrib-pioremote\": \"~1.0.0\",\n        \"tool-scons\": \"~4.40801.0\",\n        \"tool-cppcheck\": \"~1.21100.0\",\n        \"tool-clangtidy\": \"~1.150005.0\",\n        \"tool-pvs-studio\": \"~7.18.0\",\n    }\n\n\ndef get_pip_dependencies():\n    core = [\n        \"bottle == 0.13.*\",\n        \"click >=8.0.4, <8.4\",  # click 9.0 removes 'protected_args' attribute\n        \"colorama\",\n        \"marshmallow == 3.*\",\n        \"pyelftools >=0.27, <1\",\n        \"pyserial == 3.5.*\",  # keep in sync \"device/monitor/terminal.py\"\n        \"requests%s == 2.*\" % (\"[socks]\" if is_proxy_set(socks=True) else \"\"),\n        \"semantic_version == 2.10.*\",\n        \"tabulate == 0.*\",\n    ]\n\n    home = [\n        # PIO Home requirements\n        \"ajsonrpc == 1.2.*\",\n        \"starlette >=0.19, <0.53\",\n        \"uvicorn >=0.16, <0.42\",\n        \"wsproto == 1.*\",\n    ]\n\n    extra = []\n    # issue #4702; Broken \"requests/charset_normalizer\" on macOS ARM\n    extra.append(\n        'chardet >= 3.0.2,<6; platform_system == \"Darwin\" and \"arm\" in platform_machine'\n    )\n\n    # issue 4614: urllib3 v2.0 only supports OpenSSL 1.1.1+\n    try:\n        import ssl  # pylint: disable=import-outside-toplevel\n\n        if ssl.OPENSSL_VERSION.startswith(\"OpenSSL \") and ssl.OPENSSL_VERSION_INFO < (\n            1,\n            1,\n            1,\n        ):\n            extra.append(\"urllib3<2\")\n    except ImportError:\n        pass\n\n    return core + home + extra\n"
  },
  {
    "path": "platformio/device/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/device/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.device.list.command import device_list_cmd\nfrom platformio.device.monitor.command import device_monitor_cmd\n\n\n@click.group(\n    \"device\",\n    commands=[\n        device_list_cmd,\n        device_monitor_cmd,\n    ],\n    short_help=\"Device manager & Serial/Socket monitor\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/device/finder.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom fnmatch import fnmatch\nfrom functools import lru_cache\n\nimport click\nimport serial\n\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.device.list.util import list_logical_devices, list_serial_ports\nfrom platformio.fs import get_platformio_udev_rules_path\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.util import retry\n\nBLACK_MAGIC_HWIDS = [\n    \"1D50:6018\",\n]\n\n\ndef parse_udev_rules_hwids(path):\n    result = []\n    with open(path, mode=\"r\", encoding=\"utf8\") as fp:\n        for line in fp.readlines():\n            line = line.strip()\n            if not line or line.startswith(\"#\"):\n                continue\n            attrs = {}\n            for attr in line.split(\",\"):\n                attr = attr.replace(\"==\", \"=\").replace('\"', \"\").strip()\n                if \"=\" not in attr:\n                    continue\n                name, value = attr.split(\"=\", 1)\n                attrs[name] = value\n            hwid = \"%s:%s\" % (\n                attrs.get(\"ATTRS{idVendor}\", \"*\"),\n                attrs.get(\"ATTRS{idProduct}\", \"*\"),\n            )\n            if hwid != \"*:*\":\n                result.append(hwid.upper())\n    return result\n\n\ndef is_pattern_port(port):\n    if not port:\n        return False\n    return set([\"*\", \"?\", \"[\", \"]\"]) & set(port)\n\n\ndef find_mbed_disk(initial_port):\n    msdlabels = (\"mbed\", \"nucleo\", \"frdm\", \"microbit\")\n    for item in list_logical_devices():\n        if item[\"path\"].startswith(\"/net\"):\n            continue\n        if (\n            initial_port\n            and is_pattern_port(initial_port)\n            and not fnmatch(item[\"path\"], initial_port)\n        ):\n            continue\n        mbed_pages = [os.path.join(item[\"path\"], n) for n in (\"mbed.htm\", \"mbed.html\")]\n        if any(os.path.isfile(p) for p in mbed_pages):\n            return item[\"path\"]\n        if item[\"name\"] and any(l in item[\"name\"].lower() for l in msdlabels):\n            return item[\"path\"]\n    return None\n\n\ndef is_serial_port_ready(port, timeout=1):\n    try:\n        serial.Serial(port, timeout=timeout).close()\n        return True\n    except:  # pylint: disable=bare-except\n        pass\n    return False\n\n\nclass SerialPortFinder:\n    def __init__(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self,\n        board_config=None,\n        upload_protocol=None,\n        ensure_ready=False,\n        prefer_gdb_port=False,\n        timeout=2,\n        verbose=False,\n    ):\n        self.board_config = board_config\n        self.upload_protocol = upload_protocol\n        self.ensure_ready = ensure_ready\n        self.prefer_gdb_port = prefer_gdb_port\n        self.timeout = timeout\n        self.verbose = verbose\n\n    @staticmethod\n    def normalize_board_hwid(value):\n        if isinstance(value, (list, tuple)):\n            value = (\"%s:%s\" % (value[0], value[1])).replace(\"0x\", \"\")\n        return value.upper()\n\n    @staticmethod\n    def match_serial_port(pattern):\n        for item in list_serial_ports():\n            if fnmatch(item[\"port\"], pattern):\n                return item[\"port\"]\n        return None\n\n    @staticmethod\n    def match_device_hwid(patterns):\n        if not patterns:\n            return None\n        for item in list_serial_ports(as_objects=True):\n            if not item.vid or not item.pid:\n                continue\n            hwid = \"{:04X}:{:04X}\".format(item.vid, item.pid)\n            for pattern in patterns:\n                if fnmatch(hwid, pattern):\n                    return item\n        return None\n\n    def find(self, initial_port=None):\n        if initial_port:\n            # Treat any URL (contains '://') as a literal port\n            if \"://\" in initial_port:\n                return initial_port\n            # Otherwise fall back to existing wildcard logic\n            if not is_pattern_port(initial_port):\n                return initial_port\n            return self.match_serial_port(initial_port)\n\n        if self.upload_protocol and self.upload_protocol.startswith(\"blackmagic\"):\n            return self._find_blackmagic_port()\n\n        device = None\n        if self.board_config and self.board_config.get(\"build.hwids\", []):\n            device = self._find_board_device()\n        if not device:\n            device = self._find_known_device()\n        if device:\n            return self._reveal_device_port(device)\n\n        # pick the best PID:VID USB device\n        port = best_port = None\n        for item in list_serial_ports():\n            if self.ensure_ready and not is_serial_port_ready(item[\"port\"]):\n                continue\n            port = item[\"port\"]\n            if \"VID:PID\" in item[\"hwid\"]:\n                best_port = port\n        return best_port or port\n\n    def _reveal_device_port(self, device):\n        candidates = []\n        for item in list_serial_ports(as_objects=True):\n            if item.vid == device.vid and item.pid == device.pid:\n                candidates.append(item)\n        if len(candidates) <= 1:\n            return device.device\n        for item in candidates:\n            if (\"GDB\" if self.prefer_gdb_port else \"UART\") in item.description:\n                return item.device\n        candidates = sorted(candidates, key=lambda item: item.device)\n        # first port is GDB? BlackMagic, ESP-Prog\n        return candidates[0 if self.prefer_gdb_port else -1].device\n\n    def _find_blackmagic_port(self):\n        device = self.match_device_hwid(BLACK_MAGIC_HWIDS)\n        if not device:\n            return None\n        port = self._reveal_device_port(device)\n        if IS_WINDOWS and port.startswith(\"COM\") and len(port) > 4:\n            return \"\\\\\\\\.\\\\%s\" % port\n        return port\n\n    def _find_board_device(self):\n        hwids = [\n            self.normalize_board_hwid(hwid)\n            for hwid in self.board_config.get(\"build.hwids\", [])\n        ]\n        try:\n\n            @retry(timeout=self.timeout)\n            def wrapper():\n                device = self.match_device_hwid(hwids)\n                if device:\n                    return device\n                raise retry.RetryNextException()\n\n            return wrapper()\n        except retry.RetryStopException:\n            pass\n\n        if self.verbose:\n            click.secho(\n                \"TimeoutError: Could not automatically find serial port \"\n                \"for the `%s` board based on the declared HWIDs=%s\"\n                % (self.board_config.get(\"name\", \"unknown\"), hwids),\n                fg=\"yellow\",\n                err=True,\n            )\n\n        return None\n\n    def _find_known_device(self):\n        hwids = list(BLACK_MAGIC_HWIDS)\n\n        # load from UDEV rules\n        udev_rules_path = get_platformio_udev_rules_path()\n        if os.path.isfile(udev_rules_path):\n            hwids.extend(parse_udev_rules_hwids(udev_rules_path))\n\n        @lru_cache(maxsize=1)\n        def _fetch_hwids_from_platforms():\n            \"\"\"load from installed dev-platforms\"\"\"\n            result = []\n            for platform in PlatformPackageManager().get_installed():\n                p = PlatformFactory.new(platform)\n                for board_config in p.get_boards().values():\n                    for board_hwid in board_config.get(\"build.hwids\", []):\n                        board_hwid = self.normalize_board_hwid(board_hwid)\n                        if board_hwid not in result:\n                            result.append(board_hwid)\n            return result\n\n        try:\n\n            @retry(timeout=self.timeout)\n            def wrapper():\n                device = self.match_device_hwid(hwids)\n                if not device:\n                    device = self.match_device_hwid(_fetch_hwids_from_platforms())\n                if device:\n                    return device\n                raise retry.RetryNextException()\n\n            return wrapper()\n        except retry.RetryStopException:\n            pass\n\n        if self.verbose:\n            click.secho(\n                \"TimeoutError: Could not automatically find serial port \"\n                \"based on the known UART bridges\",\n                fg=\"yellow\",\n                err=True,\n            )\n\n        return None\n"
  },
  {
    "path": "platformio/device/list/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/device/list/command.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\n\nfrom platformio.device.list.util import (\n    list_logical_devices,\n    list_mdns_services,\n    list_serial_ports,\n)\n\n\n@click.command(\"list\", short_help=\"List devices\")\n@click.option(\"--serial\", is_flag=True, help=\"List serial ports, default\")\n@click.option(\"--logical\", is_flag=True, help=\"List logical devices\")\n@click.option(\"--mdns\", is_flag=True, help=\"List multicast DNS services\")\n@click.option(\"--json-output\", is_flag=True)\ndef device_list_cmd(  # pylint: disable=too-many-branches\n    serial, logical, mdns, json_output\n):\n    if not logical and not mdns:\n        serial = True\n    data = {}\n    if serial:\n        data[\"serial\"] = list_serial_ports()\n    if logical:\n        data[\"logical\"] = list_logical_devices()\n    if mdns:\n        data[\"mdns\"] = list_mdns_services()\n\n    single_key = list(data)[0] if len(list(data)) == 1 else None\n\n    if json_output:\n        return click.echo(json.dumps(data[single_key] if single_key else data))\n\n    titles = {\n        \"serial\": \"Serial Ports\",\n        \"logical\": \"Logical Devices\",\n        \"mdns\": \"Multicast DNS Services\",\n    }\n\n    for key, value in data.items():\n        if not single_key:\n            click.secho(titles[key], bold=True)\n            click.echo(\"=\" * len(titles[key]))\n\n        if key == \"serial\":\n            for item in value:\n                click.secho(item[\"port\"], fg=\"cyan\")\n                click.echo(\"-\" * len(item[\"port\"]))\n                click.echo(\"Hardware ID: %s\" % item[\"hwid\"])\n                click.echo(\"Description: %s\" % item[\"description\"])\n                click.echo(\"\")\n\n        if key == \"logical\":\n            for item in value:\n                click.secho(item[\"path\"], fg=\"cyan\")\n                click.echo(\"-\" * len(item[\"path\"]))\n                click.echo(\"Name: %s\" % item[\"name\"])\n                click.echo(\"\")\n\n        if key == \"mdns\":\n            for item in value:\n                click.secho(item[\"name\"], fg=\"cyan\")\n                click.echo(\"-\" * len(item[\"name\"]))\n                click.echo(\"Type: %s\" % item[\"type\"])\n                click.echo(\"IP: %s\" % item[\"ip\"])\n                click.echo(\"Port: %s\" % item[\"port\"])\n                if item[\"properties\"]:\n                    click.echo(\n                        \"Properties: %s\"\n                        % (\n                            \"; \".join(\n                                [\n                                    \"%s=%s\" % (k, v)\n                                    for k, v in item[\"properties\"].items()\n                                ]\n                            )\n                        )\n                    )\n                click.echo(\"\")\n\n        if single_key:\n            click.echo(\"\")\n\n    return True\n"
  },
  {
    "path": "platformio/device/list/util.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport re\nimport time\nfrom glob import glob\n\nfrom platformio import __version__, exception, proc\nfrom platformio.compat import IS_MACOS, IS_WINDOWS\n\n\ndef list_serial_ports(filter_hwid=False, as_objects=False):\n    try:\n        # pylint: disable=import-outside-toplevel\n        from serial.tools.list_ports import comports\n    except ImportError as exc:\n        raise exception.GetSerialPortsError(os.name) from exc\n\n    if as_objects:\n        return comports()\n\n    result = []\n    for p, d, h in comports():\n        if not p:\n            continue\n        if not filter_hwid or \"VID:PID\" in h:\n            result.append({\"port\": p, \"description\": d, \"hwid\": h})\n\n    if filter_hwid:\n        return result\n\n    # fix for PySerial\n    if not result and IS_MACOS:\n        for p in glob(\"/dev/tty.*\"):\n            result.append({\"port\": p, \"description\": \"n/a\", \"hwid\": \"n/a\"})\n    return result\n\n\ndef list_logical_devices():\n    items = []\n    if IS_WINDOWS:\n        try:\n            result = proc.exec_command(\n                [\"wmic\", \"logicaldisk\", \"get\", \"name,VolumeName\"]\n            ).get(\"out\", \"\")\n            devicenamere = re.compile(r\"^([A-Z]{1}\\:)\\s*(\\S+)?\")\n            for line in result.split(\"\\n\"):\n                match = devicenamere.match(line.strip())\n                if not match:\n                    continue\n                items.append({\"path\": match.group(1) + \"\\\\\", \"name\": match.group(2)})\n            return items\n        except WindowsError:  # pylint: disable=undefined-variable\n            pass\n        # try \"fsutil\"\n        result = proc.exec_command([\"fsutil\", \"fsinfo\", \"drives\"]).get(\"out\", \"\")\n        for device in re.findall(r\"[A-Z]:\\\\\", result):\n            items.append({\"path\": device, \"name\": None})\n        return items\n\n    result = proc.exec_command([\"df\"]).get(\"out\")\n    devicenamere = re.compile(r\"^/.+\\d+\\%\\s+([a-z\\d\\-_/]+)$\", flags=re.I)\n    for line in result.split(\"\\n\"):\n        match = devicenamere.match(line.strip())\n        if not match:\n            continue\n        items.append({\"path\": match.group(1), \"name\": os.path.basename(match.group(1))})\n    return items\n\n\ndef list_mdns_services():\n    try:\n        import zeroconf  # pylint: disable=import-outside-toplevel\n    except ImportError:\n        result = proc.exec_command(\n            [proc.get_pythonexe_path(), \"-m\", \"pip\", \"install\", \"zeroconf\"]\n        )\n        if result.get(\"returncode\") != 0:\n            print(result.get(\"err\"))\n        import zeroconf  # pylint: disable=import-outside-toplevel\n\n    class mDNSListener:\n        def __init__(self):\n            self._zc = zeroconf.Zeroconf(interfaces=zeroconf.InterfaceChoice.All)\n            self._found_types = []\n            self._found_services = []\n\n        def __enter__(self):\n            zeroconf.ServiceBrowser(\n                self._zc,\n                [\n                    \"_http._tcp.local.\",\n                    \"_hap._tcp.local.\",\n                    \"_services._dns-sd._udp.local.\",\n                ],\n                self,\n            )\n            return self\n\n        def __exit__(self, etype, value, traceback):\n            self._zc.close()\n\n        def add_service(self, zc, type_, name):\n            try:\n                assert zeroconf.service_type_name(name)\n                assert str(name)\n            except (AssertionError, UnicodeError, zeroconf.BadTypeInNameException):\n                return\n            if name not in self._found_types:\n                self._found_types.append(name)\n                zeroconf.ServiceBrowser(self._zc, name, self)\n            if type_ in self._found_types:\n                s = zc.get_service_info(type_, name)\n                if s:\n                    self._found_services.append(s)\n\n        def remove_service(self, zc, type_, name):\n            pass\n\n        def update_service(self, zc, type_, name):\n            pass\n\n        def get_services(self):\n            return self._found_services\n\n    items = []\n    with mDNSListener() as mdns:\n        time.sleep(3)\n        for service in mdns.get_services():\n            properties = None\n            if service.properties:\n                try:\n                    properties = {\n                        k.decode(\"utf8\"): (\n                            v.decode(\"utf8\") if isinstance(v, bytes) else v\n                        )\n                        for k, v in service.properties.items()\n                    }\n                    json.dumps(properties)\n                except UnicodeDecodeError:\n                    properties = None\n\n            items.append(\n                {\n                    \"type\": service.type,\n                    \"name\": service.name,\n                    \"ip\": \", \".join(service.parsed_addresses()),\n                    \"port\": service.port,\n                    \"properties\": properties,\n                }\n            )\n    return items\n"
  },
  {
    "path": "platformio/device/monitor/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/device/monitor/command.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\n\nimport click\n\nfrom platformio import exception, fs\nfrom platformio.device.finder import SerialPortFinder\nfrom platformio.device.monitor.filters.base import register_filters\nfrom platformio.device.monitor.terminal import get_available_filters, start_terminal\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import NotPlatformIOProjectError\nfrom platformio.project.options import ProjectOptions\n\n\n@click.command(\"monitor\", short_help=\"Monitor device (Serial/Socket)\")\n@click.option(\"--port\", \"-p\", help=\"Port, a number or a device name\")\n@click.option(\n    \"-b\",\n    \"--baud\",\n    type=ProjectOptions[\"env.monitor_speed\"].type,\n    help=\"Set baud/speed [default=%d]\" % ProjectOptions[\"env.monitor_speed\"].default,\n)\n@click.option(\n    \"--parity\",\n    type=ProjectOptions[\"env.monitor_parity\"].type,\n    help=\"Enable parity checking [default=%s]\"\n    % ProjectOptions[\"env.monitor_parity\"].default,\n)\n@click.option(\"--rtscts\", is_flag=True, help=\"Enable RTS/CTS flow control\")\n@click.option(\"--xonxoff\", is_flag=True, help=\"Enable software flow control\")\n@click.option(\n    \"--rts\",\n    type=ProjectOptions[\"env.monitor_rts\"].type,\n    help=\"Set initial RTS line state\",\n)\n@click.option(\n    \"--dtr\",\n    type=ProjectOptions[\"env.monitor_dtr\"].type,\n    help=\"Set initial DTR line state\",\n)\n@click.option(\"--echo\", is_flag=True, help=\"Enable local echo\")\n@click.option(\n    \"--encoding\",\n    help=(\n        \"Set the encoding for the serial port \"\n        \"(e.g. hexlify, Latin-1, UTF-8) [default=%s]\"\n        % ProjectOptions[\"env.monitor_encoding\"].default\n    ),\n)\n@click.option(\n    \"-f\",\n    \"--filter\",\n    \"filters\",\n    multiple=True,\n    help=\"Apply filters/text transformations\",\n)\n@click.option(\n    \"--eol\",\n    type=ProjectOptions[\"env.monitor_eol\"].type,\n    help=\"End of line mode [default=%s]\" % ProjectOptions[\"env.monitor_eol\"].default,\n)\n@click.option(\"--raw\", is_flag=True, help=ProjectOptions[\"env.monitor_raw\"].description)\n@click.option(\n    \"--exit-char\",\n    type=int,\n    default=3,\n    show_default=True,\n    help=\"ASCII code of special character that is used to exit \"\n    \"the application [default=3 (Ctrl+C)]\",\n)\n@click.option(\n    \"--menu-char\",\n    type=int,\n    default=20,\n    help=\"ASCII code of special character that is used to \"\n    \"control terminal (menu) [default=20 (DEC)]\",\n)\n@click.option(\n    \"--quiet\",\n    is_flag=True,\n    help=\"Diagnostics: suppress non-error messages\",\n)\n@click.option(\n    \"--no-reconnect\",\n    is_flag=True,\n    help=\"Disable automatic reconnection if the established connection fails\",\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\n    \"-e\",\n    \"--environment\",\n    help=\"Load configuration from `platformio.ini` and the specified environment\",\n)\ndef device_monitor_cmd(**options):\n    with fs.cd(options[\"project_dir\"]):\n        platform = None\n        project_options = {}\n        try:\n            project_options = get_project_options(options[\"environment\"])\n            if \"platform\" in project_options:\n                platform = PlatformFactory.new(project_options[\"platform\"])\n        except NotPlatformIOProjectError:\n            pass\n\n        options = apply_project_monitor_options(options, project_options)\n        register_filters(platform=platform, options=options)\n        options[\"port\"] = SerialPortFinder(\n            board_config=(\n                platform.board_config(project_options.get(\"board\"))\n                if platform and project_options.get(\"board\")\n                else None\n            ),\n            upload_protocol=project_options.get(\"upload_protocol\"),\n            ensure_ready=True,\n        ).find(initial_port=options[\"port\"])\n\n        if options[\"menu_char\"] == options[\"exit_char\"]:\n            raise exception.UserSideException(\n                \"--exit-char can not be the same as --menu-char\"\n            )\n\n        # check for unknown filters\n        if options[\"filters\"]:\n            known_filters = set(get_available_filters())\n            unknown_filters = set(options[\"filters\"]) - known_filters\n            if unknown_filters:\n                options[\"filters\"] = list(known_filters & set(options[\"filters\"]))\n                click.secho(\n                    (\"Warning! Skipping unknown filters `%s`. Known filters are `%s`\")\n                    % (\", \".join(unknown_filters), \", \".join(sorted(known_filters))),\n                    fg=\"yellow\",\n                )\n\n        start_terminal(options)\n\n\ndef get_project_options(environment=None):\n    config = ProjectConfig.get_instance()\n    config.validate(envs=[environment] if environment else None)\n    environment = environment or config.get_default_env()\n    return config.items(env=environment, as_dict=True)\n\n\ndef apply_project_monitor_options(initial_options, project_options):\n    for option_meta in ProjectOptions.values():\n        if option_meta.group != \"monitor\":\n            continue\n        cli_key = option_meta.name.split(\"_\", 1)[1]\n        if cli_key == \"speed\":\n            cli_key = \"baud\"\n        # value set from CLI, skip overriding\n        if initial_options[cli_key] not in (None, (), []) and (\n            option_meta.type != click.BOOL or f\"--{cli_key}\" in sys.argv[1:]\n        ):\n            continue\n        initial_options[cli_key] = project_options.get(\n            option_meta.name, option_meta.default\n        )\n    return initial_options\n"
  },
  {
    "path": "platformio/device/monitor/filters/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/device/monitor/filters/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport inspect\nimport os\n\nfrom serial.tools import miniterm\n\nfrom platformio.compat import get_object_members, load_python_module\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.project.config import ProjectConfig\n\n\nclass DeviceMonitorFilterBase(miniterm.Transform):\n    def __init__(self, options=None):\n        \"\"\"Called by PlatformIO to pass context\"\"\"\n        super().__init__()\n\n        self.options = options or {}\n        self.project_dir = self.options.get(\"project_dir\")\n        self.environment = self.options.get(\"environment\")\n        self._running_terminal = None\n\n        self.config = ProjectConfig.get_instance()\n        if not self.environment:\n            default_envs = self.config.default_envs()\n            if default_envs:\n                self.environment = default_envs[0]\n            elif self.config.envs():\n                self.environment = self.config.envs()[0]\n\n    def __call__(self):\n        \"\"\"Called by the miniterm library when the filter is actually used\"\"\"\n        return self\n\n    @property\n    def NAME(self):\n        raise NotImplementedError(\"Please declare NAME attribute for the filter class\")\n\n    def set_running_terminal(self, terminal):\n        self._running_terminal = terminal\n\n    def get_running_terminal(self):\n        return self._running_terminal\n\n\ndef register_filters(platform=None, options=None):\n    # issue #4556: remove default colorize filter\n    miniterm.TRANSFORMATIONS.pop(\"colorize\", None)\n\n    # project filters\n    load_monitor_filters(\n        ProjectConfig.get_instance().get(\"platformio\", \"monitor_dir\"),\n        prefix=\"filter_\",\n        options=options,\n    )\n    # platform filters\n    if platform:\n        load_monitor_filters(\n            os.path.join(platform.get_dir(), \"monitor\"),\n            prefix=\"filter_\",\n            options=options,\n        )\n    # load package filters\n    pm = ToolPackageManager()\n    for pkg in pm.get_installed():\n        load_monitor_filters(\n            os.path.join(pkg.path, \"monitor\"), prefix=\"filter_\", options=options\n        )\n    # default filters\n    load_monitor_filters(os.path.dirname(__file__), options=options)\n\n\ndef load_monitor_filters(monitor_dir, prefix=None, options=None):\n    if not os.path.isdir(monitor_dir):\n        return\n    for name in os.listdir(monitor_dir):\n        if (prefix and not name.startswith(prefix)) or not name.endswith(\".py\"):\n            continue\n        path = os.path.join(monitor_dir, name)\n        if not os.path.isfile(path):\n            continue\n        load_monitor_filter(path, options)\n\n\ndef load_monitor_filter(path, options=None):\n    name = os.path.basename(path)\n    name = name[: name.find(\".\")]\n    module = load_python_module(\"platformio.device.monitor.filters.%s\" % name, path)\n    for cls in get_object_members(module).values():\n        if (\n            not inspect.isclass(cls)\n            or not issubclass(cls, DeviceMonitorFilterBase)\n            or cls == DeviceMonitorFilterBase\n        ):\n            continue\n        obj = cls(options)\n        miniterm.TRANSFORMATIONS[obj.NAME] = obj\n    return True\n"
  },
  {
    "path": "platformio/device/monitor/filters/hexlify.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport serial\n\nfrom platformio.device.monitor.filters.base import DeviceMonitorFilterBase\n\n\nclass Hexlify(DeviceMonitorFilterBase):\n    NAME = \"hexlify\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._counter = 0\n\n    def set_running_terminal(self, terminal):\n        # force to Latin-1, issue #4732\n        if terminal.input_encoding == \"UTF-8\":\n            terminal.set_rx_encoding(\"Latin-1\")\n        super().set_running_terminal(terminal)\n\n    def rx(self, text):\n        result = \"\"\n        for c in serial.iterbytes(text):\n            if (self._counter % 16) == 0:\n                result += \"\\n{:04X} | \".format(self._counter)\n            asciicode = ord(c)\n            if asciicode <= 255:\n                result += \"{:02X} \".format(asciicode)\n            else:\n                result += \"?? \"\n            self._counter += 1\n        return result\n"
  },
  {
    "path": "platformio/device/monitor/filters/log2file.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport io\nimport os\nfrom datetime import datetime\n\nfrom platformio.device.monitor.filters.base import DeviceMonitorFilterBase\n\n\nclass LogToFile(DeviceMonitorFilterBase):\n    NAME = \"log2file\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._log_fp = None\n\n    def __call__(self):\n        if not os.path.isdir(\"logs\"):\n            os.makedirs(\"logs\")\n        log_file_name = os.path.join(\n            \"logs\", \"device-monitor-%s.log\" % datetime.now().strftime(\"%y%m%d-%H%M%S\")\n        )\n        print(\"--- Logging an output to %s\" % os.path.abspath(log_file_name))\n        # pylint: disable=consider-using-with\n        self._log_fp = io.open(log_file_name, \"w\", encoding=\"utf-8\")\n        return self\n\n    def __del__(self):\n        if self._log_fp:\n            self._log_fp.close()\n\n    def rx(self, text):\n        self._log_fp.write(text)\n        self._log_fp.flush()\n        return text\n"
  },
  {
    "path": "platformio/device/monitor/filters/send_on_enter.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.device.monitor.filters.base import DeviceMonitorFilterBase\n\n\nclass SendOnEnter(DeviceMonitorFilterBase):\n    NAME = \"send_on_enter\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._buffer = \"\"\n\n        if self.options.get(\"eol\") == \"CR\":\n            self._eol = \"\\r\"\n        elif self.options.get(\"eol\") == \"LF\":\n            self._eol = \"\\n\"\n        else:\n            self._eol = \"\\r\\n\"\n\n    def tx(self, text):\n        self._buffer += text\n        if self._buffer.endswith(self._eol):\n            text = self._buffer\n            self._buffer = \"\"\n            return text\n        return \"\"\n"
  },
  {
    "path": "platformio/device/monitor/filters/time.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom datetime import datetime\n\nfrom platformio.device.monitor.filters.base import DeviceMonitorFilterBase\n\n\nclass Timestamp(DeviceMonitorFilterBase):\n    NAME = \"time\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._line_started = False\n\n    def rx(self, text):\n        if self._line_started and \"\\n\" not in text:\n            return text\n        timestamp = datetime.now().strftime(\"%H:%M:%S.%f\")[:-3]\n        if not self._line_started:\n            self._line_started = True\n            text = \"%s > %s\" % (timestamp, text)\n        if text.endswith(\"\\n\"):\n            self._line_started = False\n            return text[:-1].replace(\"\\n\", \"\\n%s > \" % timestamp) + \"\\n\"\n        return text.replace(\"\\n\", \"\\n%s > \" % timestamp)\n"
  },
  {
    "path": "platformio/device/monitor/terminal.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport signal\nimport sys\nimport threading\n\nimport click\nimport serial\nfrom serial.tools import miniterm\n\nfrom platformio.exception import UserSideException\n\n\nclass Terminal(miniterm.Miniterm):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.pio_unexpected_exception = None\n\n    def reader(self):\n        try:\n            super().reader()\n        except Exception as exc:  # pylint: disable=broad-except\n            self.pio_unexpected_exception = exc\n\n    def writer(self):\n        try:\n            super().writer()\n        except Exception as exc:  # pylint: disable=broad-except\n            self.pio_unexpected_exception = exc\n\n\ndef get_available_filters():\n    return sorted(miniterm.TRANSFORMATIONS.keys())\n\n\ndef start_terminal(options):\n    retries = 0\n    is_port_valid = False\n    while True:\n        term = None\n        try:\n            term = new_terminal(options)\n            is_port_valid = True\n            options[\"port\"] = term.serial.name\n            if retries:\n                click.echo(\"\\t Connected!\", err=True)\n            elif not options[\"quiet\"]:\n                print_terminal_settings(term)\n            retries = 0  # reset\n            term.start()\n            try:\n                term.join(True)\n            except KeyboardInterrupt:\n                pass\n            term.join()\n\n            # cleanup\n            term.console.cleanup()\n\n            # restore original standard streams\n            sys.stdin = sys.__stdin__\n            sys.stdout = sys.__stdout__\n            sys.stderr = sys.__stderr__\n\n            term.close()\n\n            if term.pio_unexpected_exception:\n                click.secho(\n                    \"Disconnected (%s)\" % term.pio_unexpected_exception,\n                    fg=\"red\",\n                    err=True,\n                )\n                if not options[\"no_reconnect\"]:\n                    raise UserSideException(term.pio_unexpected_exception)\n\n            return\n        except UserSideException as exc:\n            if not is_port_valid:\n                raise exc\n            if not retries:\n                click.echo(\"Reconnecting to %s \" % options[\"port\"], err=True, nl=False)\n                signal.signal(signal.SIGINT, signal.SIG_DFL)\n            else:\n                click.echo(\".\", err=True, nl=False)\n            retries += 1\n            threading.Event().wait(retries / 2)\n\n\ndef new_terminal(options):\n    term = Terminal(\n        new_serial_instance(options),\n        echo=options[\"echo\"],\n        eol=options[\"eol\"].lower(),\n        filters=list(reversed(options[\"filters\"] or [\"default\"])),\n    )\n    term.exit_character = chr(options[\"exit_char\"])\n    term.menu_character = chr(options[\"menu_char\"])\n    term.raw = options[\"raw\"]\n    term.set_rx_encoding(options[\"encoding\"])\n    term.set_tx_encoding(options[\"encoding\"])\n    for ts in (term.tx_transformations, term.rx_transformations):\n        for t in ts:\n            try:\n                t.set_running_terminal(term)\n            except AttributeError:\n                pass\n    return term\n\n\ndef print_terminal_settings(terminal):\n    click.echo(\n        \"--- Terminal on {p.name} | \"\n        \"{p.baudrate} {p.bytesize}-{p.parity}-{p.stopbits}\".format(p=terminal.serial)\n    )\n    click.echo(\n        \"--- Available filters and text transformations: %s\"\n        % \", \".join(get_available_filters())\n    )\n    click.echo(\"--- More details at https://bit.ly/pio-monitor-filters\")\n    click.echo(\n        \"--- Quit: {} | Menu: {} | Help: {} followed by {}\".format(\n            miniterm.key_description(terminal.exit_character),\n            miniterm.key_description(terminal.menu_character),\n            miniterm.key_description(terminal.menu_character),\n            miniterm.key_description(\"\\x08\"),\n        )\n    )\n\n\ndef new_serial_instance(options):  # pylint: disable=too-many-branches\n    serial_instance = None\n    port = options[\"port\"]\n    while serial_instance is None:\n        # no port given on command line -> ask user now\n        if port is None or port == \"-\":\n            try:\n                port = miniterm.ask_for_port()\n            except KeyboardInterrupt as exc:\n                click.echo(\"\", err=True)\n                raise UserSideException(\"User aborted and port is not given\") from exc\n            if not port:\n                raise UserSideException(\"Port is not given\")\n        try:\n            serial_instance = serial.serial_for_url(\n                port,\n                options[\"baud\"],\n                parity=options[\"parity\"],\n                rtscts=options[\"rtscts\"],\n                xonxoff=options[\"xonxoff\"],\n                do_not_open=True,\n            )\n\n            if not hasattr(serial_instance, \"cancel_read\"):\n                # enable timeout for alive flag polling if cancel_read is not available\n                serial_instance.timeout = 1\n\n            if options[\"dtr\"] is not None:\n                if not options[\"quiet\"]:\n                    click.echo(\n                        \"--- forcing DTR {}\".format(\n                            \"active\" if options[\"dtr\"] else \"inactive\"\n                        )\n                    )\n                serial_instance.dtr = options[\"dtr\"]\n\n            if options[\"rts\"] is not None:\n                if not options[\"quiet\"]:\n                    click.echo(\n                        \"--- forcing RTS {}\".format(\n                            \"active\" if options[\"rts\"] else \"inactive\"\n                        )\n                    )\n                serial_instance.rts = options[\"rts\"]\n\n            if isinstance(serial_instance, serial.Serial):\n                serial_instance.exclusive = True\n\n            serial_instance.open()\n        except serial.SerialException as exc:\n            raise UserSideException(exc) from exc\n\n    return serial_instance\n"
  },
  {
    "path": "platformio/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nclass PlatformioException(Exception):\n    MESSAGE = None\n\n    def __str__(self):  # pragma: no cover\n        if self.MESSAGE:\n            # pylint: disable=not-an-iterable\n            return self.MESSAGE.format(*self.args)\n\n        return super().__str__()\n\n\nclass ReturnErrorCode(PlatformioException):\n    MESSAGE = \"{0}\"\n\n\nclass UserSideException(PlatformioException):\n    pass\n\n\nclass AbortedByUser(UserSideException):\n    MESSAGE = \"Aborted by user\"\n\n\n#\n# UDEV Rules\n#\n\n\nclass InvalidUdevRules(UserSideException):\n    pass\n\n\nclass MissedUdevRules(InvalidUdevRules):\n    MESSAGE = (\n        \"Warning! Please install `99-platformio-udev.rules`. \\nMore details: \"\n        \"https://docs.platformio.org/en/latest/core/installation/udev-rules.html\"\n    )\n\n\nclass OutdatedUdevRules(InvalidUdevRules):\n    MESSAGE = (\n        \"Warning! Your `{0}` are outdated. Please update or reinstall them.\"\n        \"\\nMore details: \"\n        \"https://docs.platformio.org/en/latest/core/installation/udev-rules.html\"\n    )\n\n\n#\n# Misc\n#\n\n\nclass GetSerialPortsError(PlatformioException):\n    MESSAGE = \"No implementation for your platform ('{0}') available\"\n\n\nclass GetLatestVersionError(PlatformioException):\n    MESSAGE = \"Can not retrieve the latest PlatformIO version\"\n\n\nclass InvalidSettingName(UserSideException):\n    MESSAGE = \"Invalid setting with the name '{0}'\"\n\n\nclass InvalidSettingValue(UserSideException):\n    MESSAGE = \"Invalid value '{0}' for the setting '{1}'\"\n\n\nclass InvalidJSONFile(ValueError, UserSideException):\n    MESSAGE = \"Could not load broken JSON: {0}\"\n\n\nclass CIBuildEnvsEmpty(UserSideException):\n    MESSAGE = (\n        \"Can't find PlatformIO build environments.\\n\"\n        \"Please specify `--board` or path to `platformio.ini` with \"\n        \"predefined environments using `--project-conf` option\"\n    )\n\n\nclass HomeDirPermissionsError(UserSideException):\n    MESSAGE = (\n        \"The directory `{0}` or its parent directory is not owned by the \"\n        \"current user and PlatformIO can not store configuration data.\\n\"\n        \"Please check the permissions and owner of that directory.\\n\"\n        \"Otherwise, please remove manually `{0}` directory and PlatformIO \"\n        \"will create new from the current user.\"\n    )\n\n\nclass CygwinEnvDetected(PlatformioException):\n    MESSAGE = (\n        \"PlatformIO does not work within Cygwin environment. \"\n        \"Use native Terminal instead.\"\n    )\n"
  },
  {
    "path": "platformio/fs.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport glob\nimport hashlib\nimport io\nimport json\nimport os\nimport re\nimport shutil\nimport stat\nimport sys\n\nimport click\n\nfrom platformio import exception\nfrom platformio.compat import IS_WINDOWS\n\n\nclass cd:\n    def __init__(self, new_path):\n        self.new_path = new_path\n        self.prev_path = os.getcwd()\n\n    def __enter__(self):\n        os.chdir(self.new_path)\n\n    def __exit__(self, etype, value, traceback):\n        os.chdir(self.prev_path)\n\n\ndef get_source_dir():\n    curpath = os.path.abspath(__file__)\n    if not os.path.isfile(curpath):\n        for p in sys.path:\n            if os.path.isfile(os.path.join(p, __file__)):\n                curpath = os.path.join(p, __file__)\n                break\n    return os.path.dirname(curpath)\n\n\ndef get_assets_dir():\n    return os.path.join(get_source_dir(), \"assets\")\n\n\ndef load_json(file_path):\n    try:\n        with open(file_path, mode=\"r\", encoding=\"utf8\") as f:\n            return json.load(f)\n    except ValueError as exc:\n        raise exception.InvalidJSONFile(file_path) from exc\n\n\ndef humanize_file_size(filesize):\n    base = 1024\n    unit = 0\n    suffix = \"B\"\n    filesize = float(filesize)\n    if filesize < base:\n        return \"%d%s\" % (filesize, suffix)\n    for i, suffix in enumerate(\"KMGTPEZY\"):\n        unit = base ** (i + 2)\n        if filesize >= unit:\n            continue\n        if filesize % (base ** (i + 1)):\n            return \"%.2f%sB\" % ((base * filesize / unit), suffix)\n        break\n    return \"%d%sB\" % ((base * filesize / unit), suffix)\n\n\ndef calculate_file_hashsum(algorithm, path):\n    h = hashlib.new(algorithm)\n    with io.open(path, \"rb\", buffering=0) as fp:\n        while True:\n            chunk = fp.read(io.DEFAULT_BUFFER_SIZE)\n            if not chunk:\n                break\n            h.update(chunk)\n    return h.hexdigest()\n\n\ndef calculate_folder_size(path):\n    assert os.path.isdir(path)\n    result = 0\n    for root, __, files in os.walk(path):\n        for f in files:\n            file_path = os.path.join(root, f)\n            if not os.path.islink(file_path):\n                result += os.path.getsize(file_path)\n    return result\n\n\ndef get_platformio_udev_rules_path():\n    return os.path.abspath(\n        os.path.join(get_assets_dir(), \"system\", \"99-platformio-udev.rules\")\n    )\n\n\ndef ensure_udev_rules():\n    from platformio.util import get_systype  # pylint: disable=import-outside-toplevel\n\n    def _rules_to_set(rules_path):\n        result = set()\n        with open(rules_path, encoding=\"utf8\") as fp:\n            for line in fp.readlines():\n                line = line.strip()\n                if not line or line.startswith(\"#\"):\n                    continue\n                result.add(line)\n        return result\n\n    if \"linux\" not in get_systype():\n        return None\n    installed_rules = [\n        \"/etc/udev/rules.d/99-platformio-udev.rules\",\n        \"/lib/udev/rules.d/99-platformio-udev.rules\",\n    ]\n    if not any(os.path.isfile(p) for p in installed_rules):\n        raise exception.MissedUdevRules\n\n    origin_path = get_platformio_udev_rules_path()\n    if not os.path.isfile(origin_path):\n        return None\n\n    origin_rules = _rules_to_set(origin_path)\n    for rules_path in installed_rules:\n        if not os.path.isfile(rules_path):\n            continue\n        current_rules = _rules_to_set(rules_path)\n        if not origin_rules <= current_rules:\n            raise exception.OutdatedUdevRules(rules_path)\n\n    return True\n\n\ndef path_endswith_ext(path, extensions):\n    if not isinstance(extensions, (list, tuple)):\n        extensions = [extensions]\n    for ext in extensions:\n        if path.endswith(\".\" + ext):\n            return True\n    return False\n\n\ndef match_src_files(src_dir, src_filter=None, src_exts=None, followlinks=True):\n    def _add_candidate(items, item, src_dir):\n        if not src_exts or path_endswith_ext(item, src_exts):\n            items.add(os.path.relpath(item, src_dir))\n\n    def _find_candidates(pattern):\n        candidates = set()\n        for item in glob.glob(\n            os.path.join(glob.escape(src_dir), pattern), recursive=True\n        ):\n            if not os.path.isdir(item):\n                _add_candidate(candidates, item, src_dir)\n                continue\n            for root, dirs, files in os.walk(item, followlinks=followlinks):\n                for d in dirs if not followlinks else []:\n                    if os.path.islink(os.path.join(root, d)):\n                        _add_candidate(candidates, os.path.join(root, d), src_dir)\n                for f in files:\n                    _add_candidate(candidates, os.path.join(root, f), src_dir)\n        return candidates\n\n    src_filter = src_filter or \"\"\n    if isinstance(src_filter, (list, tuple)):\n        src_filter = \" \".join(src_filter)\n\n    result = set()\n    # correct fs directory separator\n    src_filter = src_filter.replace(\"/\", os.sep).replace(\"\\\\\", os.sep)\n    for action, pattern in re.findall(r\"(\\+|\\-)<([^>]+)>\", src_filter):\n        candidates = _find_candidates(pattern)\n        if action == \"+\":\n            result |= candidates\n        else:\n            result -= candidates\n    return sorted(list(result))\n\n\ndef to_unix_path(path):\n    if not IS_WINDOWS or not path:\n        return path\n    return path.replace(\"\\\\\", \"/\")\n\n\ndef expanduser(path):\n    \"\"\"\n    Be compatible with Python 3.8, on Windows skip HOME and check for USERPROFILE\n    \"\"\"\n    if not IS_WINDOWS or not path.startswith(\"~\") or \"USERPROFILE\" not in os.environ:\n        return os.path.expanduser(path)\n    return os.environ[\"USERPROFILE\"] + path[1:]\n\n\ndef change_filemtime(path, mtime):\n    os.utime(path, (mtime, mtime))\n\n\ndef rmtree(path):\n    def _onexc(func, path, _):\n        try:\n            st_mode = os.stat(path).st_mode\n            if st_mode & stat.S_IREAD:\n                os.chmod(path, st_mode | stat.S_IWRITE)\n            func(path)\n        except Exception as exc:  # pylint: disable=broad-except\n            click.secho(\n                \"%s \\nPlease manually remove the file `%s`\" % (str(exc), path),\n                fg=\"red\",\n                err=True,\n            )\n\n    # pylint: disable=unexpected-keyword-arg, deprecated-argument\n    if sys.version_info < (3, 12):\n        return shutil.rmtree(path, onerror=_onexc)\n    return shutil.rmtree(path, onexc=_onexc)\n"
  },
  {
    "path": "platformio/home/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/home/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport mimetypes\nimport socket\n\nimport click\n\nfrom platformio.compat import IS_WINDOWS, click_launch\nfrom platformio.home.run import run_server\nfrom platformio.package.manager.core import get_core_package_dir\n\n\n@click.command(\"home\", short_help=\"GUI to manage PlatformIO\")\n@click.option(\"--port\", type=int, default=8008, help=\"HTTP port, default=8008\")\n@click.option(\n    \"--host\",\n    default=\"127.0.0.1\",\n    help=(\n        \"HTTP host, default=127.0.0.1. You can open PIO Home for inbound \"\n        \"connections with --host=0.0.0.0\"\n    ),\n)\n@click.option(\"--no-open\", is_flag=True)\n@click.option(\n    \"--shutdown-timeout\",\n    default=0,\n    type=int,\n    help=(\n        \"Automatically shutdown server on timeout (in seconds) when no clients \"\n        \"are connected. Default is 0 which means never auto shutdown\"\n    ),\n)\n@click.option(\n    \"--session-id\",\n    help=(\n        \"A unique session identifier to keep PIO Home isolated from other instances \"\n        \"and protect from 3rd party access\"\n    ),\n)\ndef cli(port, host, no_open, shutdown_timeout, session_id):\n    # hook for `platformio-node-helpers`\n    if host == \"__do_not_start__\":\n        # download all dependent packages\n        get_core_package_dir(\"contrib-piohome\")\n        return\n\n    # Ensure PIO Home mimetypes are known\n    mimetypes.add_type(\"text/html\", \".html\")\n    mimetypes.add_type(\"text/css\", \".css\")\n    mimetypes.add_type(\"application/javascript\", \".js\")\n\n    home_url = \"http://%s:%d%s\" % (\n        host,\n        port,\n        (\"/session/%s/\" % session_id) if session_id else \"/\",\n    )\n    click.echo(\n        \"\\n\".join(\n            [\n                \"\",\n                \"  ___I_\",\n                \" /\\\\-_--\\\\   PlatformIO Home\",\n                \"/  \\\\_-__\\\\\",\n                \"|[]| [] |  %s\" % home_url,\n                \"|__|____|__%s\" % (\"_\" * len(home_url)),\n            ]\n        )\n    )\n    click.echo(\"\")\n    click.echo(\"Open PlatformIO Home in your browser by this URL => %s\" % home_url)\n\n    if is_port_used(host, port):\n        click.secho(\n            \"PlatformIO Home server is already started in another process.\", fg=\"yellow\"\n        )\n        if not no_open:\n            click_launch(home_url)\n        return\n\n    run_server(\n        host=host,\n        port=port,\n        no_open=no_open,\n        shutdown_timeout=shutdown_timeout,\n        home_url=home_url,\n    )\n\n\ndef is_port_used(host, port):\n    socket.setdefaulttimeout(1)\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    if IS_WINDOWS:\n        try:\n            s.bind((host, port))\n            s.close()\n            return False\n        except (OSError, socket.error):\n            pass\n    else:\n        try:\n            s.connect((host, port))\n            s.close()\n        except socket.error:\n            return False\n\n    return True\n"
  },
  {
    "path": "platformio/home/rpc/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/home/rpc/handlers/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/home/rpc/handlers/account.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom ajsonrpc.core import JSONRPC20DispatchException\n\nfrom platformio.account.client import AccountClient\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\n\n\nclass AccountRPC(BaseRPCHandler):\n    @staticmethod\n    def call_client(method, *args, **kwargs):\n        try:\n            client = AccountClient()\n            return getattr(client, method)(*args, **kwargs)\n        except Exception as exc:  # pylint: disable=bare-except\n            raise JSONRPC20DispatchException(\n                code=5000, message=\"PIO Account Call Error\", data=str(exc)\n            ) from exc\n"
  },
  {
    "path": "platformio/home/rpc/handlers/app.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pathlib import Path\n\nfrom platformio import __version__, app, fs, util\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.project.helpers import is_platformio_project\n\n\nclass AppRPC(BaseRPCHandler):\n    IGNORE_STORAGE_KEYS = [\n        \"cid\",\n        \"coreVersion\",\n        \"coreSystype\",\n        \"coreCaller\",\n        \"coreSettings\",\n        \"homeDir\",\n        \"projectsDir\",\n    ]\n\n    @staticmethod\n    def load_state():\n        with app.State(\n            app.resolve_state_path(\"core_dir\", \"homestate.json\"), lock=True\n        ) as state:\n            storage = state.get(\"storage\", {})\n\n            # base data\n            caller_id = app.get_session_var(\"caller_id\")\n            storage[\"cid\"] = app.get_cid()\n            storage[\"coreVersion\"] = __version__\n            storage[\"coreSystype\"] = util.get_systype()\n            storage[\"coreCaller\"] = str(caller_id).lower() if caller_id else None\n            storage[\"coreSettings\"] = {\n                name: {\n                    \"description\": data[\"description\"],\n                    \"default_value\": data[\"value\"],\n                    \"value\": app.get_setting(name),\n                }\n                for name, data in app.DEFAULT_SETTINGS.items()\n            }\n\n            storage[\"homeDir\"] = fs.expanduser(\"~\")\n            storage[\"projectsDir\"] = storage[\"coreSettings\"][\"projects_dir\"][\"value\"]\n\n            # skip non-existing recent projects\n            storage[\"recentProjects\"] = list(\n                set(\n                    str(Path(p).resolve())\n                    for p in storage.get(\"recentProjects\", [])\n                    if is_platformio_project(p)\n                )\n            )\n\n            state[\"storage\"] = storage\n            state.modified = False  # skip saving extra fields\n            return state.as_dict()\n\n    @staticmethod\n    def get_state():\n        return AppRPC.load_state()\n\n    @staticmethod\n    def save_state(state):\n        with app.State(\n            app.resolve_state_path(\"core_dir\", \"homestate.json\"), lock=True\n        ) as s:\n            s.clear()\n            s.update(state)\n            storage = s.get(\"storage\", {})\n            for k in AppRPC.IGNORE_STORAGE_KEYS:\n                if k in storage:\n                    del storage[k]\n        return True\n"
  },
  {
    "path": "platformio/home/rpc/handlers/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nclass BaseRPCHandler:\n    factory = None\n"
  },
  {
    "path": "platformio/home/rpc/handlers/ide.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport time\nfrom pathlib import Path\n\nfrom ajsonrpc.core import JSONRPC20DispatchException\n\nfrom platformio.compat import aio_get_running_loop\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\n\n\nclass IDERPC(BaseRPCHandler):\n    COMMAND_TIMEOUT = 1.5  # in seconds\n\n    def __init__(self):\n        self._ide_queue = []\n        self._cmd_queue = {}\n\n    async def listen_commands(self):\n        f = aio_get_running_loop().create_future()\n        self._ide_queue.append(f)\n        self._process_commands()\n        return await f\n\n    async def send_command(self, command, params=None):\n        cmd_id = f\"ide-{command}-{time.time()}\"\n        self._cmd_queue[cmd_id] = {\n            \"method\": command,\n            \"params\": params,\n            \"time\": time.time(),\n            \"future\": aio_get_running_loop().create_future(),\n        }\n        self._process_commands()\n        # in case if IDE agent has not been started\n        aio_get_running_loop().call_later(\n            self.COMMAND_TIMEOUT + 0.1, self._process_commands\n        )\n        return await self._cmd_queue[cmd_id][\"future\"]\n\n    def on_command_result(self, cmd_id, value):\n        if cmd_id not in self._cmd_queue:\n            return False\n        if self._cmd_queue[cmd_id][\"method\"] == \"get_pio_project_dirs\":\n            value = [str(Path(p).resolve()) for p in value]\n        self._cmd_queue[cmd_id][\"future\"].set_result(value)\n        del self._cmd_queue[cmd_id]\n        return True\n\n    def _process_commands(self):\n        for cmd_id in list(self._cmd_queue):\n            cmd_data = self._cmd_queue[cmd_id]\n            if cmd_data[\"future\"].done():\n                del self._cmd_queue[cmd_id]\n                continue\n\n            if (\n                not self._ide_queue\n                and (time.time() - cmd_data[\"time\"]) > self.COMMAND_TIMEOUT\n            ):\n                cmd_data[\"future\"].set_exception(\n                    JSONRPC20DispatchException(\n                        code=4005, message=\"PIO Home IDE agent is not started\"\n                    )\n                )\n                continue\n\n            while self._ide_queue:\n                self._ide_queue.pop().set_result(\n                    {\n                        \"id\": cmd_id,\n                        \"method\": cmd_data[\"method\"],\n                        \"params\": cmd_data[\"params\"],\n                    }\n                )\n"
  },
  {
    "path": "platformio/home/rpc/handlers/misc.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport time\n\nfrom platformio.cache import ContentCache\nfrom platformio.compat import aio_create_task\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.home.rpc.handlers.os import OSRPC\n\n\nclass MiscRPC(BaseRPCHandler):\n    async def load_latest_tweets(self, data_url):\n        cache_key = ContentCache.key_from_args(data_url, \"tweets\")\n        cache_valid = \"180d\"\n        with ContentCache() as cc:\n            cache_data = cc.get(cache_key)\n            if cache_data:\n                cache_data = json.loads(cache_data)\n                # automatically update cache in background every 12 hours\n                if cache_data[\"time\"] < (time.time() - (3600 * 12)):\n                    aio_create_task(\n                        self._preload_latest_tweets(data_url, cache_key, cache_valid)\n                    )\n                return cache_data[\"result\"]\n\n        return await self._preload_latest_tweets(data_url, cache_key, cache_valid)\n\n    @staticmethod\n    async def _preload_latest_tweets(data_url, cache_key, cache_valid):\n        result = json.loads((await OSRPC.fetch_content(data_url)))\n        with ContentCache() as cc:\n            cc.set(\n                cache_key,\n                json.dumps({\"time\": int(time.time()), \"result\": result}),\n                cache_valid,\n            )\n        return result\n"
  },
  {
    "path": "platformio/home/rpc/handlers/os.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport glob\nimport io\nimport os\nimport shutil\nfrom functools import cmp_to_key\n\nfrom platformio import fs\nfrom platformio.cache import ContentCache\nfrom platformio.compat import aio_to_thread, click_launch\nfrom platformio.device.list.util import list_logical_devices\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.http import HTTPSession, ensure_internet_on\n\n\nclass HTTPAsyncSession(HTTPSession):\n    async def request(  # pylint: disable=signature-differs,invalid-overridden-method\n        self, *args, **kwargs\n    ):\n        func = super().request\n        return await aio_to_thread(func, *args, **kwargs)\n\n\nclass OSRPC(BaseRPCHandler):\n    _http_session = None\n\n    @classmethod\n    async def fetch_content(cls, url, data=None, headers=None, cache_valid=None):\n        if not headers:\n            headers = {\n                \"User-Agent\": (\n                    \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) \"\n                    \"AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 \"\n                    \"Safari/603.3.8\"\n                )\n            }\n        cache_key = ContentCache.key_from_args(url, data) if cache_valid else None\n        with ContentCache() as cc:\n            if cache_key:\n                result = cc.get(cache_key)\n                if result is not None:\n                    return result\n\n        # check internet before and resolve issue with 60 seconds timeout\n        ensure_internet_on(raise_exception=True)\n\n        if not cls._http_session:\n            cls._http_session = HTTPAsyncSession()\n\n        if data:\n            r = await cls._http_session.post(url, data=data, headers=headers)\n        else:\n            r = await cls._http_session.get(url, headers=headers)\n\n        r.raise_for_status()\n        result = r.text\n        if cache_valid:\n            with ContentCache() as cc:\n                cc.set(cache_key, result, cache_valid)\n        return result\n\n    async def request_content(self, uri, data=None, headers=None, cache_valid=None):\n        if uri.startswith(\"http\"):\n            return await self.fetch_content(uri, data, headers, cache_valid)\n        local_path = uri[7:] if uri.startswith(\"file://\") else uri\n        with io.open(local_path, encoding=\"utf-8\") as fp:\n            return fp.read()\n        return None\n\n    @staticmethod\n    def open_url(url):\n        return click_launch(url)\n\n    @staticmethod\n    def reveal_file(path):\n        return click_launch(path, locate=True)\n\n    @staticmethod\n    def open_file(path):\n        return click_launch(path)\n\n    @staticmethod\n    def call_path_module_func(name, args, **kwargs):\n        return getattr(os.path, name)(*args, **kwargs)\n\n    @staticmethod\n    def get_path_separator():\n        return os.sep\n\n    @staticmethod\n    def is_file(path):\n        return os.path.isfile(path)\n\n    @staticmethod\n    def is_dir(path):\n        return os.path.isdir(path)\n\n    @staticmethod\n    def make_dirs(path):\n        return os.makedirs(path)\n\n    @staticmethod\n    def get_file_mtime(path):\n        return os.path.getmtime(path)\n\n    @staticmethod\n    def rename(src, dst):\n        return os.rename(src, dst)\n\n    @staticmethod\n    def copy(src, dst):\n        return shutil.copytree(src, dst, symlinks=True)\n\n    @staticmethod\n    def glob(pathnames, root=None):\n        if not isinstance(pathnames, list):\n            pathnames = [pathnames]\n        result = set()\n        for pathname in pathnames:\n            result |= set(\n                glob.glob(\n                    os.path.join(root, pathname) if root else pathname, recursive=True\n                )\n            )\n        return list(result)\n\n    @staticmethod\n    def list_dir(path):\n        def _cmp(x, y):\n            if x[1] and not y[1]:\n                return -1\n            if not x[1] and y[1]:\n                return 1\n            if x[0].lower() > y[0].lower():\n                return 1\n            if x[0].lower() < y[0].lower():\n                return -1\n            return 0\n\n        items = []\n        if path.startswith(\"~\"):\n            path = fs.expanduser(path)\n        if not os.path.isdir(path):\n            return items\n        for item in os.listdir(path):\n            try:\n                item_is_dir = os.path.isdir(os.path.join(path, item))\n                if item_is_dir:\n                    os.listdir(os.path.join(path, item))\n                items.append((item, item_is_dir))\n            except OSError:\n                pass\n        return sorted(items, key=cmp_to_key(_cmp))\n\n    @staticmethod\n    def get_logical_devices():\n        return list_logical_devices()\n"
  },
  {
    "path": "platformio/home/rpc/handlers/piocore.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport asyncio\nimport functools\nimport io\nimport json\nimport os\nimport sys\nimport threading\n\nimport click\nfrom ajsonrpc.core import JSONRPC20DispatchException\n\nfrom platformio import __main__, __version__, app, fs, proc, util\nfrom platformio.compat import (\n    IS_WINDOWS,\n    aio_create_task,\n    aio_get_running_loop,\n    aio_to_thread,\n    get_locale_encoding,\n    is_bytes,\n)\nfrom platformio.exception import PlatformioException\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\n\n\nclass PIOCoreProtocol(asyncio.SubprocessProtocol):\n    def __init__(self, exit_future, on_data_callback=None):\n        self.exit_future = exit_future\n        self.on_data_callback = on_data_callback\n        self.stdout = \"\"\n        self.stderr = \"\"\n        self._is_exited = False\n        self._encoding = get_locale_encoding()\n\n    def pipe_data_received(self, fd, data):\n        data = data.decode(self._encoding, \"replace\")\n        pipe = [\"stdin\", \"stdout\", \"stderr\"][fd]\n        if pipe == \"stdout\":\n            self.stdout += data\n        if pipe == \"stderr\":\n            self.stderr += data\n        if self.on_data_callback:\n            self.on_data_callback(pipe=pipe, data=data)\n\n    def connection_lost(self, exc):\n        self.process_exited()\n\n    def process_exited(self):\n        if self._is_exited:\n            return\n        self.exit_future.set_result(True)\n        self._is_exited = True\n\n\nclass MultiThreadingStdStream:\n    def __init__(self, parent_stream):\n        self._buffers = {threading.get_ident(): parent_stream}\n\n    def __getattr__(self, name):\n        thread_id = threading.get_ident()\n        self._ensure_thread_buffer(thread_id)\n        return getattr(self._buffers[thread_id], name)\n\n    def _ensure_thread_buffer(self, thread_id):\n        if thread_id not in self._buffers:\n            self._buffers[thread_id] = io.StringIO()\n\n    def write(self, value):\n        thread_id = threading.get_ident()\n        self._ensure_thread_buffer(thread_id)\n        return self._buffers[thread_id].write(\n            value.decode() if is_bytes(value) else value\n        )\n\n    def get_value_and_reset(self):\n        result = \"\"\n        try:\n            result = self.getvalue()\n            self.seek(0)\n            self.truncate(0)\n        except AttributeError:\n            pass\n        return result\n\n\n@util.memoized(expire=\"60s\")\ndef get_core_fullpath():\n    return proc.where_is_program(\"platformio\" + (\".exe\" if IS_WINDOWS else \"\"))\n\n\nclass PIOCoreRPC(BaseRPCHandler):\n    @staticmethod\n    def version():\n        return __version__\n\n    async def exec(self, args, options=None):\n        loop = aio_get_running_loop()\n        exit_future = loop.create_future()\n        data_callback = functools.partial(\n            self._on_exec_data_received, exec_options=options\n        )\n        if args[0] != \"--caller\" and app.get_session_var(\"caller_id\"):\n            args = [\"--caller\", app.get_session_var(\"caller_id\")] + args\n        transport, protocol = await loop.subprocess_exec(\n            lambda: PIOCoreProtocol(exit_future, data_callback),\n            get_core_fullpath(),\n            *args,\n            stdin=None,\n            **options.get(\"spawn\", {}),\n        )\n        await exit_future\n        transport.close()\n        return {\n            \"stdout\": protocol.stdout,\n            \"stderr\": protocol.stderr,\n            \"returncode\": transport.get_returncode(),\n        }\n\n    def _on_exec_data_received(self, exec_options, pipe, data):\n        notification_method = exec_options.get(f\"{pipe}NotificationMethod\")\n        if not notification_method:\n            return\n        aio_create_task(\n            self.factory.notify_clients(\n                method=notification_method,\n                params=[data],\n                actor=\"frontend\",\n            )\n        )\n\n    @staticmethod\n    def setup_multithreading_std_streams():\n        if isinstance(sys.stdout, MultiThreadingStdStream):\n            return\n        PIOCoreRPC.thread_stdout = MultiThreadingStdStream(sys.stdout)\n        PIOCoreRPC.thread_stderr = MultiThreadingStdStream(sys.stderr)\n        sys.stdout = PIOCoreRPC.thread_stdout\n        sys.stderr = PIOCoreRPC.thread_stderr\n\n    @staticmethod\n    async def call(args, options=None):\n        for i, arg in enumerate(args):\n            if not isinstance(arg, str):\n                args[i] = str(arg)\n\n        options = options or {}\n        to_json = \"--json-output\" in args\n\n        try:\n            if options.get(\"force_subprocess\"):\n                result = await PIOCoreRPC._call_subprocess(args, options)\n                return PIOCoreRPC._process_result(result, to_json)\n            result = await PIOCoreRPC._call_inline(args, options)\n            try:\n                return PIOCoreRPC._process_result(result, to_json)\n            except ValueError:\n                # fall-back to subprocess method\n                result = await PIOCoreRPC._call_subprocess(args, options)\n                return PIOCoreRPC._process_result(result, to_json)\n        except Exception as exc:  # pylint: disable=bare-except\n            raise JSONRPC20DispatchException(\n                code=5000, message=\"PIO Core Call Error\", data=str(exc)\n            ) from exc\n\n    @staticmethod\n    async def _call_subprocess(args, options):\n        result = await aio_to_thread(\n            proc.exec_command,\n            [get_core_fullpath()] + args,\n            cwd=options.get(\"cwd\") or os.getcwd(),\n        )\n        return (result[\"out\"], result[\"err\"], result[\"returncode\"])\n\n    @staticmethod\n    async def _call_inline(args, options):\n        PIOCoreRPC.setup_multithreading_std_streams()\n\n        def _thread_safe_call(args, cwd):\n            with fs.cd(cwd):\n                exit_code = __main__.main([\"-c\"] + args)\n            return (\n                PIOCoreRPC.thread_stdout.get_value_and_reset(),\n                PIOCoreRPC.thread_stderr.get_value_and_reset(),\n                exit_code,\n            )\n\n        return await aio_to_thread(\n            _thread_safe_call, args=args, cwd=options.get(\"cwd\") or os.getcwd()\n        )\n\n    @staticmethod\n    def _process_result(result, to_json=False):\n        out, err, code = result\n        if out and is_bytes(out):\n            out = out.decode(get_locale_encoding())\n        if err and is_bytes(err):\n            err = err.decode(get_locale_encoding())\n        text = (\"%s\\n\\n%s\" % (out, err)).strip()\n        if code != 0:\n            raise PlatformioException(text)\n        if not to_json:\n            return text\n        try:\n            return json.loads(out)\n        except ValueError as exc:\n            click.secho(\"%s => `%s`\" % (exc, out), fg=\"red\", err=True)\n            # if PIO Core prints unhandled warnings\n            for line in out.split(\"\\n\"):\n                line = line.strip()\n                if not line:\n                    continue\n                try:\n                    return json.loads(line)\n                except ValueError:\n                    pass\n            raise exc\n"
  },
  {
    "path": "platformio/home/rpc/handlers/platform.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os.path\n\nfrom platformio.compat import aio_to_thread\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manifest.parser import ManifestParserFactory\nfrom platformio.package.meta import PackageSpec\nfrom platformio.platform.factory import PlatformFactory\n\n\nclass PlatformRPC(BaseRPCHandler):\n    async def fetch_platforms(self, search_query=None, page=0, force_installed=False):\n        if force_installed:\n            return {\n                \"items\": await aio_to_thread(\n                    self._load_installed_platforms, search_query\n                )\n            }\n\n        search_result = await self.factory.manager.dispatcher[\"registry.call_client\"](\n            method=\"list_packages\",\n            query=search_query,\n            qualifiers={\n                \"types\": [\"platform\"],\n            },\n            page=page,\n        )\n        return {\n            \"page\": search_result[\"page\"],\n            \"limit\": search_result[\"limit\"],\n            \"total\": search_result[\"total\"],\n            \"items\": [\n                {\n                    \"id\": item[\"id\"],\n                    \"ownername\": item[\"owner\"][\"username\"],\n                    \"name\": item[\"name\"],\n                    \"version\": item[\"version\"][\"name\"],\n                    \"description\": item[\"description\"],\n                    \"tier\": item[\"tier\"],\n                }\n                for item in search_result[\"items\"]\n            ],\n        }\n\n    @staticmethod\n    def _load_installed_platforms(search_query=None):\n        search_query = (search_query or \"\").strip()\n\n        def _matchSearchQuery(p):\n            content_blocks = [p.name, p.title, p.description]\n            if p.frameworks:\n                content_blocks.append(\" \".join(p.frameworks.keys()))\n            for board in p.get_boards().values():\n                board_data = board.get_brief_data()\n                for key in (\"id\", \"mcu\", \"vendor\"):\n                    content_blocks.append(board_data.get(key))\n            return search_query in \" \".join(content_blocks)\n\n        items = []\n        pm = PlatformPackageManager()\n        for pkg in pm.get_installed():\n            p = PlatformFactory.new(pkg)\n            if search_query and not _matchSearchQuery(p):\n                continue\n            items.append(\n                {\n                    \"__pkg_path\": pkg.path,\n                    \"ownername\": pkg.metadata.spec.owner if pkg.metadata.spec else None,\n                    \"name\": p.name,\n                    \"version\": str(pkg.metadata.version),\n                    \"title\": p.title,\n                    \"description\": p.description,\n                }\n            )\n        return items\n\n    async def fetch_boards(self, platform_spec):\n        spec = PackageSpec(platform_spec)\n        if spec.owner:\n            return await self.factory.manager.dispatcher[\"registry.call_client\"](\n                method=\"get_package\",\n                typex=\"platform\",\n                owner=spec.owner,\n                name=spec.name,\n                extra_path=\"/boards\",\n            )\n        return await aio_to_thread(self._load_installed_boards, spec)\n\n    @staticmethod\n    def _load_installed_boards(platform_spec):\n        p = PlatformFactory.new(platform_spec)\n        return sorted(\n            [b.get_brief_data() for b in p.get_boards().values()],\n            key=lambda item: item[\"name\"],\n        )\n\n    async def fetch_examples(self, platform_spec):\n        spec = PackageSpec(platform_spec)\n        if spec.owner:\n            return await self.factory.manager.dispatcher[\"registry.call_client\"](\n                method=\"get_package\",\n                typex=\"platform\",\n                owner=spec.owner,\n                name=spec.name,\n                extra_path=\"/examples\",\n            )\n        return await aio_to_thread(self._load_installed_examples, spec)\n\n    @staticmethod\n    def _load_installed_examples(platform_spec):\n        platform = PlatformFactory.new(platform_spec)\n        platform_dir = platform.get_dir()\n        parser = ManifestParserFactory.new_from_dir(platform_dir)\n        result = parser.as_dict().get(\"examples\") or []\n        for example in result:\n            example[\"files\"] = [\n                {\n                    \"path\": item,\n                    \"url\": (\n                        \"file://%s\"\n                        + os.path.join(platform_dir, \"examples\", example[\"name\"], item)\n                    ),\n                }\n                for item in example[\"files\"]\n            ]\n        return result\n"
  },
  {
    "path": "platformio/home/rpc/handlers/project.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport time\nfrom pathlib import Path\n\nimport semantic_version\nfrom ajsonrpc.core import JSONRPC20DispatchException\n\nfrom platformio import app, exception, fs\nfrom platformio.home.rpc.handlers.app import AppRPC\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.home.rpc.handlers.piocore import PIOCoreRPC\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import ProjectError\nfrom platformio.project.helpers import get_project_dir, is_platformio_project\nfrom platformio.project.integration.generator import ProjectGenerator\nfrom platformio.project.options import get_config_options_schema\n\n\nclass ProjectRPC(BaseRPCHandler):\n    @staticmethod\n    def config_call(init_kwargs, method, *args):\n        assert isinstance(init_kwargs, dict)\n        assert \"path\" in init_kwargs\n        if os.path.isdir(init_kwargs[\"path\"]):\n            project_dir = init_kwargs[\"path\"]\n            init_kwargs[\"path\"] = os.path.join(init_kwargs[\"path\"], \"platformio.ini\")\n        elif os.path.isfile(init_kwargs[\"path\"]):\n            project_dir = os.path.dirname(init_kwargs[\"path\"])\n        else:\n            project_dir = get_project_dir()\n        with fs.cd(project_dir):\n            return getattr(ProjectConfig(**init_kwargs), method)(*args)\n\n    @staticmethod\n    def config_load(path):\n        return ProjectConfig(\n            path, parse_extra=False, expand_interpolations=False\n        ).as_tuple()\n\n    @staticmethod\n    def config_dump(path, data):\n        config = ProjectConfig(path, parse_extra=False, expand_interpolations=False)\n        config.update(data, clear=True)\n        return config.save()\n\n    @staticmethod\n    def config_update_description(path, text):\n        config = ProjectConfig(path, parse_extra=False, expand_interpolations=False)\n        if not config.has_section(\"platformio\"):\n            config.add_section(\"platformio\")\n        if text:\n            config.set(\"platformio\", \"description\", text)\n        else:\n            if config.has_option(\"platformio\", \"description\"):\n                config.remove_option(\"platformio\", \"description\")\n            if not config.options(\"platformio\"):\n                config.remove_section(\"platformio\")\n        return config.save()\n\n    @staticmethod\n    def get_config_schema():\n        return get_config_options_schema()\n\n    @staticmethod\n    def get_projects():\n        def _get_project_data():\n            data = {\"boards\": [], \"envLibdepsDirs\": [], \"libExtraDirs\": []}\n            config = ProjectConfig()\n            data[\"envs\"] = config.envs()\n            data[\"description\"] = config.get(\"platformio\", \"description\")\n            data[\"libExtraDirs\"].extend(config.get(\"platformio\", \"lib_extra_dirs\", []))\n\n            libdeps_dir = config.get(\"platformio\", \"libdeps_dir\")\n            for section in config.sections():\n                if not section.startswith(\"env:\"):\n                    continue\n                data[\"envLibdepsDirs\"].append(os.path.join(libdeps_dir, section[4:]))\n                if config.has_option(section, \"board\"):\n                    data[\"boards\"].append(config.get(section, \"board\"))\n                data[\"libExtraDirs\"].extend(config.get(section, \"lib_extra_dirs\", []))\n\n            # skip non existing folders and resolve full path\n            for key in (\"envLibdepsDirs\", \"libExtraDirs\"):\n                data[key] = [\n                    fs.expanduser(d) if d.startswith(\"~\") else os.path.abspath(d)\n                    for d in data[key]\n                    if os.path.isdir(d)\n                ]\n\n            return data\n\n        def _path_to_name(path):\n            return (os.path.sep).join(path.split(os.path.sep)[-2:])\n\n        result = []\n        pm = PlatformPackageManager()\n        for project_dir in AppRPC.load_state()[\"storage\"][\"recentProjects\"]:\n            if not os.path.isdir(project_dir):\n                continue\n            data = {}\n            boards = []\n            try:\n                with fs.cd(project_dir):\n                    data = _get_project_data()\n            except ProjectError:\n                continue\n\n            for board_id in data.get(\"boards\", []):\n                name = board_id\n                try:\n                    name = pm.board_config(board_id)[\"name\"]\n                except exception.PlatformioException:\n                    pass\n                boards.append({\"id\": board_id, \"name\": name})\n\n            result.append(\n                {\n                    \"path\": project_dir,\n                    \"name\": _path_to_name(project_dir),\n                    \"modified\": int(os.path.getmtime(project_dir)),\n                    \"boards\": boards,\n                    \"description\": data.get(\"description\"),\n                    \"envs\": data.get(\"envs\", []),\n                    \"envLibStorages\": [\n                        {\"name\": os.path.basename(d), \"path\": d}\n                        for d in data.get(\"envLibdepsDirs\", [])\n                    ],\n                    \"extraLibStorages\": [\n                        {\"name\": _path_to_name(d), \"path\": d}\n                        for d in data.get(\"libExtraDirs\", [])\n                    ],\n                }\n            )\n        return result\n\n    @staticmethod\n    def get_project_examples():\n        result = []\n        pm = PlatformPackageManager()\n        for pkg in pm.get_installed():\n            examples_dir = os.path.join(pkg.path, \"examples\")\n            if not os.path.isdir(examples_dir):\n                continue\n            items = []\n            for project_dir, _, __ in os.walk(examples_dir):\n                project_description = None\n                try:\n                    config = ProjectConfig(os.path.join(project_dir, \"platformio.ini\"))\n                    config.validate(silent=True)\n                    project_description = config.get(\"platformio\", \"description\")\n                except ProjectError:\n                    continue\n\n                path_tokens = project_dir.split(os.path.sep)\n                items.append(\n                    {\n                        \"name\": \"/\".join(\n                            path_tokens[path_tokens.index(\"examples\") + 1 :]\n                        ),\n                        \"path\": project_dir,\n                        \"description\": project_description,\n                    }\n                )\n            manifest = pm.load_manifest(pkg)\n            result.append(\n                {\n                    \"platform\": {\n                        \"title\": manifest[\"title\"],\n                        \"version\": manifest[\"version\"],\n                    },\n                    \"items\": sorted(items, key=lambda item: item[\"name\"]),\n                }\n            )\n        return sorted(result, key=lambda data: data[\"platform\"][\"title\"])\n\n    async def init(self, board, framework, project_dir):\n        assert project_dir\n        if not os.path.isdir(project_dir):\n            os.makedirs(project_dir)\n        args = [\"init\", \"--board\", board, \"--sample-code\"]\n        if framework:\n            args.extend([\"--project-option\", \"framework = %s\" % framework])\n        ide = app.get_session_var(\"caller_id\")\n        if ide in ProjectGenerator.get_supported_ides():\n            args.extend([\"--ide\", ide])\n        await PIOCoreRPC.call(\n            args, options={\"cwd\": project_dir, \"force_subprocess\": True}\n        )\n        return project_dir\n\n    @staticmethod\n    async def import_arduino(board, use_arduino_libs, arduino_project_dir):\n        board = str(board)\n        # don't import PIO Project\n        if is_platformio_project(arduino_project_dir):\n            return arduino_project_dir\n\n        is_arduino_project = any(\n            os.path.isfile(\n                os.path.join(\n                    arduino_project_dir,\n                    \"%s.%s\" % (os.path.basename(arduino_project_dir), ext),\n                )\n            )\n            for ext in (\"ino\", \"pde\")\n        )\n        if not is_arduino_project:\n            raise JSONRPC20DispatchException(\n                code=4000, message=\"Not an Arduino project: %s\" % arduino_project_dir\n            )\n\n        state = AppRPC.load_state()\n        project_dir = os.path.join(\n            state[\"storage\"][\"projectsDir\"], time.strftime(\"%y%m%d-%H%M%S-\") + board\n        )\n        if not os.path.isdir(project_dir):\n            os.makedirs(project_dir)\n        args = [\"init\", \"--board\", board]\n        args.extend([\"--project-option\", \"framework = arduino\"])\n        if use_arduino_libs:\n            args.extend(\n                [\"--project-option\", \"lib_extra_dirs = ~/Documents/Arduino/libraries\"]\n            )\n        ide = app.get_session_var(\"caller_id\")\n        if ide in ProjectGenerator.get_supported_ides():\n            args.extend([\"--ide\", ide])\n        await PIOCoreRPC.call(\n            args, options={\"cwd\": project_dir, \"force_subprocess\": True}\n        )\n        with fs.cd(project_dir):\n            config = ProjectConfig()\n            src_dir = config.get(\"platformio\", \"src_dir\")\n            if os.path.isdir(src_dir):\n                fs.rmtree(src_dir)\n            shutil.copytree(arduino_project_dir, src_dir, symlinks=True)\n        return project_dir\n\n    @staticmethod\n    async def import_pio(project_dir):\n        if not project_dir or not is_platformio_project(project_dir):\n            raise JSONRPC20DispatchException(\n                code=4001, message=\"Not an PlatformIO project: %s\" % project_dir\n            )\n        new_project_dir = os.path.join(\n            AppRPC.load_state()[\"storage\"][\"projectsDir\"],\n            time.strftime(\"%y%m%d-%H%M%S-\") + os.path.basename(project_dir),\n        )\n        shutil.copytree(project_dir, new_project_dir, symlinks=True)\n\n        args = [\"init\"]\n        ide = app.get_session_var(\"caller_id\")\n        if ide in ProjectGenerator.get_supported_ides():\n            args.extend([\"--ide\", ide])\n        await PIOCoreRPC.call(\n            args, options={\"cwd\": new_project_dir, \"force_subprocess\": True}\n        )\n        return new_project_dir\n\n    async def init_v2(self, configuration, options=None):\n        project_dir = os.path.join(configuration[\"location\"], configuration[\"name\"])\n        if not os.path.isdir(project_dir):\n            os.makedirs(project_dir)\n\n        envclone = os.environ.copy()\n        envclone[\"PLATFORMIO_FORCE_ANSI\"] = \"true\"\n        options = options or {}\n        options[\"spawn\"] = {\"env\": envclone, \"cwd\": project_dir}\n\n        args = [\"project\", \"init\"]\n        ide = app.get_session_var(\"caller_id\")\n        if ide in ProjectGenerator.get_supported_ides():\n            args.extend([\"--ide\", ide])\n\n        if configuration.get(\"example\"):\n            await self.factory.notify_clients(\n                method=options.get(\"stdoutNotificationMethod\"),\n                params=[\"Copying example files...\\n\"],\n                actor=\"frontend\",\n            )\n            await self._pre_init_example(configuration, project_dir)\n        else:\n            args.extend(self._pre_init_empty(configuration))\n\n        return await self.factory.manager.dispatcher[\"core.exec\"](args, options=options)\n\n    @staticmethod\n    def _pre_init_empty(configuration):\n        project_options = []\n        platform = configuration[\"platform\"]\n        board_id = configuration.get(\"board\", {}).get(\"id\")\n        env_name = board_id or platform[\"name\"]\n        if configuration.get(\"description\"):\n            project_options.append((\"description\", configuration.get(\"description\")))\n        try:\n            v = semantic_version.Version(platform.get(\"version\"))\n            assert not v.prerelease\n            project_options.append(\n                (\"platform\", \"{name} @ ^{version}\".format(**platform))\n            )\n        except (AssertionError, ValueError):\n            project_options.append(\n                (\"platform\", \"{name} @ {version}\".format(**platform))\n            )\n        if board_id:\n            project_options.append((\"board\", board_id))\n        if configuration.get(\"framework\"):\n            project_options.append((\"framework\", configuration[\"framework\"][\"name\"]))\n\n        args = [\"-e\", env_name, \"--sample-code\"]\n        for name, value in project_options:\n            args.extend([\"-O\", f\"{name}={value}\"])\n        return args\n\n    async def _pre_init_example(self, configuration, project_dir):\n        for item in configuration[\"example\"][\"files\"]:\n            p = Path(project_dir).joinpath(item[\"path\"])\n            if not p.parent.is_dir():\n                p.parent.mkdir(parents=True)\n            p.write_text(\n                await self.factory.manager.dispatcher[\"os.request_content\"](\n                    item[\"url\"]\n                ),\n                encoding=\"utf-8\",\n            )\n        return []\n\n    @staticmethod\n    def configuration(project_dir, env):\n        assert is_platformio_project(project_dir)\n        with fs.cd(project_dir):\n            config = ProjectConfig(os.path.join(project_dir, \"platformio.ini\"))\n            platform = PlatformFactory.from_env(env, autoinstall=True)\n            platform_pkg = PlatformPackageManager().get_package(platform.get_dir())\n            board_id = config.get(f\"env:{env}\", \"board\", None)\n\n            # frameworks\n            frameworks = []\n            for name in config.get(f\"env:{env}\", \"framework\", []):\n                if name not in platform.frameworks:\n                    continue\n                f_pkg_name = platform.frameworks[name].get(\"package\")\n                if not f_pkg_name:\n                    continue\n                f_pkg = platform.get_package(f_pkg_name)\n                if not f_pkg:\n                    continue\n                f_manifest = platform.pm.load_manifest(f_pkg)\n                frameworks.append(\n                    dict(\n                        name=name,\n                        title=f_manifest.get(\"title\"),\n                        version=str(f_pkg.metadata.version),\n                    )\n                )\n\n            return dict(\n                platform=dict(\n                    ownername=(\n                        platform_pkg.metadata.spec.owner\n                        if platform_pkg.metadata.spec\n                        else None\n                    ),\n                    name=platform.name,\n                    title=platform.title,\n                    version=str(platform_pkg.metadata.version),\n                ),\n                board=(\n                    platform.board_config(board_id).get_brief_data()\n                    if board_id\n                    else None\n                ),\n                frameworks=frameworks or None,\n            )\n"
  },
  {
    "path": "platformio/home/rpc/handlers/registry.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom ajsonrpc.core import JSONRPC20DispatchException\n\nfrom platformio.compat import aio_to_thread\nfrom platformio.home.rpc.handlers.base import BaseRPCHandler\nfrom platformio.registry.client import RegistryClient\n\n\nclass RegistryRPC(BaseRPCHandler):\n    @staticmethod\n    async def call_client(method, *args, **kwargs):\n        try:\n            client = RegistryClient()\n            return await aio_to_thread(getattr(client, method), *args, **kwargs)\n        except Exception as exc:  # pylint: disable=bare-except\n            raise JSONRPC20DispatchException(\n                code=5000, message=\"Registry Call Error\", data=str(exc)\n            ) from exc\n"
  },
  {
    "path": "platformio/home/rpc/server.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom urllib.parse import parse_qs\n\nimport ajsonrpc.utils\nimport click\nfrom ajsonrpc.core import JSONRPC20Error, JSONRPC20Request\nfrom ajsonrpc.dispatcher import Dispatcher\nfrom ajsonrpc.manager import AsyncJSONRPCResponseManager, JSONRPC20Response\nfrom starlette.endpoints import WebSocketEndpoint\n\nfrom platformio.compat import aio_create_task, aio_get_running_loop\nfrom platformio.http import InternetConnectionError\nfrom platformio.proc import force_exit\n\n# Remove this line when PR is merged\n# https://github.com/pavlov99/ajsonrpc/pull/22\najsonrpc.utils.is_invalid_params = lambda: False\n\n\nclass JSONRPCServerFactoryBase:\n    connection_nums = 0\n    shutdown_timer = None\n\n    def __init__(self, shutdown_timeout=0):\n        self.shutdown_timeout = shutdown_timeout\n        self.manager = AsyncJSONRPCResponseManager(\n            Dispatcher(), is_server_error_verbose=True\n        )\n        self._clients = {}\n\n    def __call__(self, *args, **kwargs):\n        raise NotImplementedError\n\n    def add_object_handler(self, handler, namespace):\n        handler.factory = self\n        self.manager.dispatcher.add_object(handler, prefix=\"%s.\" % namespace)\n\n    def on_client_connect(self, connection, actor=None):\n        self._clients[connection] = {\"actor\": actor}\n        self.connection_nums += 1\n        if self.shutdown_timer:\n            self.shutdown_timer.cancel()\n            self.shutdown_timer = None\n\n    def on_client_disconnect(self, connection):\n        if connection in self._clients:\n            del self._clients[connection]\n        self.connection_nums -= 1\n        if self.connection_nums < 1:\n            self.connection_nums = 0\n\n        if self.connection_nums == 0:\n            self.shutdown_by_timeout()\n\n    async def on_shutdown(self):\n        pass\n\n    def shutdown_by_timeout(self):\n        if self.shutdown_timeout < 1:\n            return\n\n        def _auto_shutdown_server():\n            click.echo(\"Automatically shutdown server on timeout\")\n            force_exit()\n\n        self.shutdown_timer = aio_get_running_loop().call_later(\n            self.shutdown_timeout, _auto_shutdown_server\n        )\n\n    async def notify_clients(self, method, params=None, actor=None):\n        for client, options in self._clients.items():\n            if actor and options[\"actor\"] != actor:\n                continue\n            request = JSONRPC20Request(method, params, is_notification=True)\n            await client.send_text(self.manager.serialize(request.body))\n        return True\n\n\nclass WebSocketJSONRPCServerFactory(JSONRPCServerFactoryBase):\n    def __call__(self, *args, **kwargs):\n        ws = WebSocketJSONRPCServer(*args, **kwargs)\n        ws.factory = self\n        return ws\n\n\nclass WebSocketJSONRPCServer(WebSocketEndpoint):\n    encoding = \"text\"\n    factory: WebSocketJSONRPCServerFactory = None\n\n    async def on_connect(self, websocket):\n        await websocket.accept()\n        qs = parse_qs(self.scope.get(\"query_string\", b\"\"))\n        actors = qs.get(b\"actor\")\n        self.factory.on_client_connect(  # pylint: disable=no-member\n            websocket, actor=actors[0].decode() if actors else None\n        )\n\n    async def on_receive(self, websocket, data):\n        aio_create_task(self._handle_rpc(websocket, data))\n\n    async def on_disconnect(self, websocket, close_code):\n        self.factory.on_client_disconnect(websocket)  # pylint: disable=no-member\n\n    async def _handle_rpc(self, websocket, data):\n        # pylint: disable=no-member\n        response = await self.factory.manager.get_response_for_payload(data)\n        if response.error and response.error.data:\n            click.secho(\"Error: %s\" % response.error.data, fg=\"red\", err=True)\n            if InternetConnectionError.MESSAGE in response.error.data:\n                response = JSONRPC20Response(\n                    id=response.id,\n                    error=JSONRPC20Error(\n                        code=4008,\n                        message=\"No Internet Connection\",\n                        data=response.error.data,\n                    ),\n                )\n        await websocket.send_text(self.factory.manager.serialize(response.body))\n"
  },
  {
    "path": "platformio/home/run.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom urllib.parse import urlparse\n\nimport click\nimport uvicorn\nfrom starlette.applications import Starlette\nfrom starlette.middleware import Middleware\nfrom starlette.responses import PlainTextResponse\nfrom starlette.routing import Mount, Route, WebSocketRoute\nfrom starlette.staticfiles import StaticFiles\nfrom starlette.status import HTTP_403_FORBIDDEN\n\nfrom platformio.compat import aio_get_running_loop\nfrom platformio.exception import PlatformioException\nfrom platformio.home.rpc.handlers.account import AccountRPC\nfrom platformio.home.rpc.handlers.app import AppRPC\nfrom platformio.home.rpc.handlers.ide import IDERPC\nfrom platformio.home.rpc.handlers.misc import MiscRPC\nfrom platformio.home.rpc.handlers.os import OSRPC\nfrom platformio.home.rpc.handlers.piocore import PIOCoreRPC\nfrom platformio.home.rpc.handlers.platform import PlatformRPC\nfrom platformio.home.rpc.handlers.project import ProjectRPC\nfrom platformio.home.rpc.handlers.registry import RegistryRPC\nfrom platformio.home.rpc.server import WebSocketJSONRPCServerFactory\nfrom platformio.package.manager.core import get_core_package_dir\nfrom platformio.proc import force_exit\n\n\nclass ShutdownMiddleware:\n    def __init__(self, app):\n        self.app = app\n\n    async def __call__(self, scope, receive, send):\n        if scope[\"type\"] == \"http\" and b\"__shutdown__\" in scope.get(\"query_string\", \"\"):\n            await shutdown_server()\n        await self.app(scope, receive, send)\n\n\nasync def shutdown_server(_=None):\n    aio_get_running_loop().call_later(0.5, force_exit)\n    return PlainTextResponse(\"Server has been shutdown!\")\n\n\nasync def protected_page(_):\n    return PlainTextResponse(\n        \"Protected PlatformIO Home session\", status_code=HTTP_403_FORBIDDEN\n    )\n\n\ndef run_server(host, port, no_open, shutdown_timeout, home_url):\n    contrib_dir = get_core_package_dir(\"contrib-piohome\")\n    if not os.path.isdir(contrib_dir):\n        raise PlatformioException(\"Invalid path to PIO Home Contrib\")\n\n    ws_rpc_factory = WebSocketJSONRPCServerFactory(shutdown_timeout)\n    ws_rpc_factory.add_object_handler(AccountRPC(), namespace=\"account\")\n    ws_rpc_factory.add_object_handler(AppRPC(), namespace=\"app\")\n    ws_rpc_factory.add_object_handler(IDERPC(), namespace=\"ide\")\n    ws_rpc_factory.add_object_handler(MiscRPC(), namespace=\"misc\")\n    ws_rpc_factory.add_object_handler(OSRPC(), namespace=\"os\")\n    ws_rpc_factory.add_object_handler(PIOCoreRPC(), namespace=\"core\")\n    ws_rpc_factory.add_object_handler(ProjectRPC(), namespace=\"project\")\n    ws_rpc_factory.add_object_handler(PlatformRPC(), namespace=\"platform\")\n    ws_rpc_factory.add_object_handler(RegistryRPC(), namespace=\"registry\")\n\n    path = urlparse(home_url).path\n    routes = [\n        WebSocketRoute(path + \"wsrpc\", ws_rpc_factory, name=\"wsrpc\"),\n        Route(path + \"__shutdown__\", shutdown_server, methods=[\"POST\"]),\n        Mount(path, StaticFiles(directory=contrib_dir, html=True), name=\"static\"),\n    ]\n    if path != \"/\":\n        routes.append(Route(\"/\", protected_page))\n\n    uvicorn.run(\n        Starlette(\n            middleware=[Middleware(ShutdownMiddleware)],\n            routes=routes,\n            on_startup=[\n                lambda: click.echo(\n                    \"PIO Home has been started. Press Ctrl+C to shutdown.\"\n                ),\n                lambda: None if no_open else click.launch(home_url),\n            ],\n        ),\n        host=host,\n        port=port,\n        log_level=\"warning\",\n    )\n"
  },
  {
    "path": "platformio/http.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport socket\nfrom urllib.parse import urljoin\n\nimport requests.adapters\nfrom urllib3.util.retry import Retry\n\nfrom platformio import __check_internet_hosts__, app, util\nfrom platformio.cache import ContentCache, cleanup_content_cache\nfrom platformio.compat import is_proxy_set\nfrom platformio.exception import PlatformioException, UserSideException\n\n__default_requests_timeout__ = (10, None)  # (connect, read)\n\n\nclass HTTPClientError(UserSideException):\n    def __init__(self, message, response=None):\n        super().__init__()\n        self.message = message\n        self.response = response\n\n    def __str__(self):  # pragma: no cover\n        return self.message\n\n\nclass InternetConnectionError(UserSideException):\n    MESSAGE = (\n        \"You are not connected to the Internet.\\n\"\n        \"PlatformIO needs the Internet connection to\"\n        \" download dependent packages or to work with PlatformIO Account.\"\n    )\n\n\nclass HTTPSession(requests.Session):\n    def __init__(self, *args, **kwargs):\n        self._x_base_url = kwargs.pop(\"x_base_url\") if \"x_base_url\" in kwargs else None\n        super().__init__(*args, **kwargs)\n        self.headers.update({\"User-Agent\": app.get_user_agent()})\n        try:\n            self.verify = app.get_setting(\"enable_proxy_strict_ssl\")\n        except PlatformioException:\n            self.verify = True\n\n    def request(  # pylint: disable=signature-differs,arguments-differ\n        self, method, url, *args, **kwargs\n    ):\n        # print(\"HTTPSession::request\", self._x_base_url, method, url, args, kwargs)\n        if \"timeout\" not in kwargs:\n            kwargs[\"timeout\"] = __default_requests_timeout__\n        return super().request(\n            method,\n            (\n                url\n                if url.startswith(\"http\") or not self._x_base_url\n                else urljoin(self._x_base_url, url)\n            ),\n            *args,\n            **kwargs\n        )\n\n\nclass HTTPSessionIterator:\n    def __init__(self, endpoints):\n        if not isinstance(endpoints, list):\n            endpoints = [endpoints]\n        self.endpoints = endpoints\n        self.endpoints_iter = iter(endpoints)\n        # https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html\n        self.retry = Retry(\n            total=5,\n            backoff_factor=1,  # [0, 2, 4, 8, 16] secs\n            # method_whitelist=list(Retry.DEFAULT_METHOD_WHITELIST) + [\"POST\"],\n            status_forcelist=[413, 500, 502, 503, 504],\n        )\n\n    def __iter__(self):  # pylint: disable=non-iterator-returned\n        return self\n\n    def __next__(self):\n        base_url = next(self.endpoints_iter)\n        session = HTTPSession(x_base_url=base_url)\n        adapter = requests.adapters.HTTPAdapter(max_retries=self.retry)\n        session.mount(base_url, adapter)\n        return session\n\n\nclass HTTPClient:\n    def __init__(self, endpoints):\n        self._session_iter = HTTPSessionIterator(endpoints)\n        self._session = None\n        self._next_session()\n\n    def __del__(self):\n        if not self._session:\n            return\n        try:\n            self._session.close()\n        except:  # pylint: disable=bare-except\n            pass\n        self._session = None\n\n    def _next_session(self):\n        if self._session:\n            self._session.close()\n        self._session = next(self._session_iter)\n\n    @util.throttle(500)\n    def send_request(self, method, path, **kwargs):\n        # check Internet before and resolve issue with 60 seconds timeout\n        ensure_internet_on(raise_exception=True)\n\n        headers = kwargs.get(\"headers\", {})\n        with_authorization = (\n            kwargs.pop(\"x_with_authorization\")\n            if \"x_with_authorization\" in kwargs\n            else False\n        )\n        if with_authorization and \"Authorization\" not in headers:\n            # pylint: disable=import-outside-toplevel\n            from platformio.account.client import AccountClient\n\n            headers[\"Authorization\"] = (\n                \"Bearer %s\" % AccountClient().fetch_authentication_token()\n            )\n        kwargs[\"headers\"] = headers\n\n        while True:\n            try:\n                return getattr(self._session, method)(path, **kwargs)\n            except requests.exceptions.RequestException as exc:\n                try:\n                    self._next_session()\n                except Exception as exc2:\n                    raise HTTPClientError(str(exc2)) from exc\n\n    def fetch_json_data(self, method, path, **kwargs):\n        if method not in (\"get\", \"head\", \"options\"):\n            cleanup_content_cache(\"http\")\n        cache_valid = kwargs.pop(\"x_cache_valid\") if \"x_cache_valid\" in kwargs else None\n        if not cache_valid:\n            return self._parse_json_response(self.send_request(method, path, **kwargs))\n        cache_key = ContentCache.key_from_args(\n            method, path, kwargs.get(\"params\"), kwargs.get(\"data\")\n        )\n        with ContentCache(\"http\") as cc:\n            result = cc.get(cache_key)\n            if result is not None:\n                try:\n                    return json.loads(result)\n                except json.JSONDecodeError:\n                    pass\n            response = self.send_request(method, path, **kwargs)\n            data = self._parse_json_response(response)\n            cc.set(cache_key, response.text, cache_valid)\n            return data\n\n    @staticmethod\n    def _parse_json_response(response, expected_codes=(200, 201, 202)):\n        if response.status_code in expected_codes:\n            try:\n                return response.json()\n            except ValueError:\n                pass\n        try:\n            message = response.json()[\"message\"]\n        except (KeyError, ValueError):\n            message = response.text\n        raise HTTPClientError(message, response)\n\n\n#\n# Helpers\n#\n\n\n@util.memoized(expire=\"10s\")\ndef _internet_on():\n    timeout = 2\n    use_proxy = is_proxy_set()\n    socket.setdefaulttimeout(timeout)\n    for host in __check_internet_hosts__:\n        try:\n            if use_proxy:\n                requests.get(\"http://%s\" % host, allow_redirects=False, timeout=timeout)\n                return True\n            # try to resolve `host` for both AF_INET and AF_INET6, and then try to connect\n            # to all possible addresses (IPv4 and IPv6) in turn until a connection succeeds:\n            s = socket.create_connection((host, 80))\n            s.close()\n            return True\n        except:  # pylint: disable=bare-except\n            pass\n\n    # falling back to HTTPs, issue #4980\n    for host in __check_internet_hosts__:\n        try:\n            requests.get(\"https://%s\" % host, allow_redirects=False, timeout=timeout)\n        except requests.exceptions.RequestException:\n            pass\n        return True\n\n    return False\n\n\ndef ensure_internet_on(raise_exception=False):\n    result = _internet_on()\n    if raise_exception and not result:\n        raise InternetConnectionError()\n    return result\n\n\ndef fetch_remote_content(*args, **kwargs):\n    with HTTPSession() as s:\n        r = s.get(*args, **kwargs)\n        r.raise_for_status()\n        r.close()\n        return r.text\n"
  },
  {
    "path": "platformio/maintenance.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nfrom time import time\n\nimport click\nimport semantic_version\n\nfrom platformio import __version__, app, exception, fs, telemetry\nfrom platformio.cache import cleanup_content_cache\nfrom platformio.cli import PlatformioCLI\nfrom platformio.commands.upgrade import get_latest_version\nfrom platformio.http import HTTPClientError, InternetConnectionError, ensure_internet_on\nfrom platformio.package.manager.core import update_core_packages\nfrom platformio.package.version import pepver_to_semver\nfrom platformio.system.prune import calculate_unnecessary_system_data\n\n\ndef on_cmd_start(ctx, caller):\n    app.set_session_var(\"command_ctx\", ctx)\n    set_caller(caller)\n    telemetry.on_cmd_start(ctx)\n    if PlatformioCLI.in_silence():\n        return\n    after_upgrade(ctx)\n\n\ndef on_cmd_end():\n    if PlatformioCLI.in_silence():\n        return\n\n    try:\n        check_platformio_upgrade()\n        check_prune_system()\n    except (\n        HTTPClientError,\n        InternetConnectionError,\n        exception.GetLatestVersionError,\n    ):\n        click.secho(\n            \"Failed to check for PlatformIO upgrades. \"\n            \"Please check your Internet connection.\",\n            fg=\"red\",\n        )\n\n\ndef on_platformio_exception(exc):\n    telemetry.log_exception(exc)\n\n\ndef on_platformio_exit():\n    telemetry.on_exit()\n\n\ndef set_caller(caller=None):\n    caller = caller or os.getenv(\"PLATFORMIO_CALLER\")\n    if not caller:\n        if os.getenv(\"CODESPACES\"):\n            caller = \"codespaces\"\n        elif os.getenv(\"VSCODE_PID\") or os.getenv(\"VSCODE_NLS_CONFIG\"):\n            caller = \"vscode\"\n        elif os.getenv(\"GITPOD_WORKSPACE_ID\") or os.getenv(\"GITPOD_WORKSPACE_URL\"):\n            caller = \"gitpod\"\n    if caller:\n        app.set_session_var(\"caller_id\", caller)\n\n\nclass Upgrader:\n    def __init__(self, from_version, to_version):\n        self.from_version = from_version\n        self.to_version = to_version\n        self._upgraders = [\n            (semantic_version.Version(\"6.1.8-a.1\"), self._appstate_migration),\n        ]\n\n    def run(self, ctx):\n        if self.from_version > self.to_version:\n            return True\n\n        result = [True]\n        for version, callback in self._upgraders:\n            if self.from_version >= version or self.to_version < version:\n                continue\n            result.append(callback(ctx))\n\n        return all(result)\n\n    @staticmethod\n    def _appstate_migration(_):\n        state_path = app.resolve_state_path(\"core_dir\", \"appstate.json\")\n        if not os.path.isfile(state_path):\n            return True\n        app.delete_state_item(\"telemetry\")\n        created_at = app.get_state_item(\"created_at\", None)\n        if not created_at:\n            state_stat = os.stat(state_path)\n            app.set_state_item(\n                \"created_at\",\n                int(\n                    state_stat.st_birthtime\n                    if hasattr(state_stat, \"st_birthtime\")\n                    else state_stat.st_ctime\n                ),\n            )\n        return True\n\n\ndef after_upgrade(ctx):\n    terminal_width = shutil.get_terminal_size().columns\n    last_version_str = app.get_state_item(\"last_version\", \"0.0.0\")\n    if last_version_str == __version__:\n        return None\n\n    if last_version_str == \"0.0.0\":\n        app.set_state_item(\"last_version\", __version__)\n        return print_welcome_banner()\n\n    last_version = pepver_to_semver(last_version_str)\n    current_version = pepver_to_semver(__version__)\n\n    if last_version > current_version and not last_version.prerelease:\n        click.secho(\"*\" * terminal_width, fg=\"yellow\")\n        click.secho(\n            \"Obsolete PIO Core v%s is used (previous was %s)\"\n            % (__version__, last_version_str),\n            fg=\"yellow\",\n        )\n        click.secho(\"Please remove multiple PIO Cores from a system:\", fg=\"yellow\")\n        click.secho(\n            \"https://docs.platformio.org/en/latest/core\"\n            \"/installation/troubleshooting.html\",\n            fg=\"cyan\",\n        )\n        click.secho(\"*\" * terminal_width, fg=\"yellow\")\n        return None\n\n    click.secho(\"Please wait while upgrading PlatformIO...\", fg=\"yellow\")\n\n    # Update PlatformIO's Core packages\n    cleanup_content_cache(\"http\")\n    update_core_packages()\n\n    u = Upgrader(last_version, current_version)\n    if u.run(ctx):\n        app.set_state_item(\"last_version\", __version__)\n        click.secho(\n            \"PlatformIO has been successfully upgraded to %s!\\n\" % __version__,\n            fg=\"green\",\n        )\n        telemetry.log_event(\n            \"pio_upgrade_core\",\n            {\n                \"label\": \"%s > %s\" % (last_version_str, __version__),\n                \"from_version\": last_version_str,\n                \"to_version\": __version__,\n            },\n        )\n\n    return print_welcome_banner()\n\n\ndef print_welcome_banner():\n    terminal_width = shutil.get_terminal_size().columns\n    click.echo(\"*\" * terminal_width)\n    click.echo(\"If you like %s, please:\" % (click.style(\"PlatformIO\", fg=\"cyan\")))\n    click.echo(\n        \"- %s it on GitHub > %s\"\n        % (\n            click.style(\"star\", fg=\"cyan\"),\n            click.style(\"https://github.com/platformio/platformio-core\", fg=\"cyan\"),\n        )\n    )\n    click.echo(\n        \"- %s us on LinkedIn to stay up-to-date \"\n        \"on the latest project news > %s\"\n        % (\n            click.style(\"follow\", fg=\"cyan\"),\n            click.style(\"https://www.linkedin.com/company/platformio/\", fg=\"cyan\"),\n        )\n    )\n    if not os.getenv(\"PLATFORMIO_IDE\"):\n        click.echo(\n            \"- %s PlatformIO IDE for embedded development > %s\"\n            % (\n                click.style(\"try\", fg=\"cyan\"),\n                click.style(\"https://platformio.org/platformio-ide\", fg=\"cyan\"),\n            )\n        )\n\n    click.echo(\"*\" * terminal_width)\n    click.echo(\"\")\n\n\ndef check_platformio_upgrade():\n    interval = int(app.get_setting(\"check_platformio_interval\")) * 3600 * 24\n    check_state = app.get_state_item(\"last_check\", {})\n    last_checked_time = check_state.get(\"platformio_upgrade\", 0)\n    if (time() - interval) < last_checked_time:\n        return\n\n    check_state[\"platformio_upgrade\"] = int(time())\n    app.set_state_item(\"last_check\", check_state)\n    if not last_checked_time:\n        return\n\n    ensure_internet_on(raise_exception=True)\n\n    # Update PlatformIO Core packages\n    update_core_packages()\n\n    latest_version = get_latest_version()\n    if pepver_to_semver(latest_version) <= pepver_to_semver(__version__):\n        return\n\n    terminal_width = shutil.get_terminal_size().columns\n\n    click.echo(\"\")\n    click.echo(\"*\" * terminal_width)\n    click.secho(\n        \"There is a new version %s of PlatformIO available.\\n\"\n        \"Please upgrade it via `\" % latest_version,\n        fg=\"yellow\",\n        nl=False,\n    )\n    if os.path.join(\"Cellar\", \"platformio\") in fs.get_source_dir():\n        click.secho(\"brew update && brew upgrade\", fg=\"cyan\", nl=False)\n        click.secho(\"` command.\", fg=\"yellow\")\n    else:\n        click.secho(\"platformio upgrade\", fg=\"cyan\", nl=False)\n        click.secho(\"` or `\", fg=\"yellow\", nl=False)\n        click.secho(\"python -m pip install -U platformio\", fg=\"cyan\", nl=False)\n        click.secho(\"` command.\", fg=\"yellow\")\n    click.secho(\"Changes: \", fg=\"yellow\", nl=False)\n    click.secho(\"https://docs.platformio.org/en/latest/history.html\", fg=\"cyan\")\n    click.echo(\"*\" * terminal_width)\n    click.echo(\"\")\n\n\ndef check_prune_system():\n    interval = 30 * 3600 * 24  # 1 time per month\n    check_state = app.get_state_item(\"last_check\", {})\n    last_checked_time = check_state.get(\"prune_system\", 0)\n    if (time() - interval) < last_checked_time:\n        return\n\n    check_state[\"prune_system\"] = int(time())\n    app.set_state_item(\"last_check\", check_state)\n\n    if not last_checked_time:\n        return\n\n    threshold_mb = int(app.get_setting(\"check_prune_system_threshold\") or 0)\n    if threshold_mb <= 0:\n        return\n\n    unnecessary_size = calculate_unnecessary_system_data()\n    if (unnecessary_size / 1024) < threshold_mb:\n        return\n\n    terminal_width = shutil.get_terminal_size().columns\n    click.echo()\n    click.echo(\"*\" * terminal_width)\n    click.secho(\n        \"We found %s of unnecessary PlatformIO system data (temporary files, \"\n        \"unnecessary packages, etc.).\\nUse `pio system prune --dry-run` to list \"\n        \"them or `pio system prune` to save disk space.\"\n        % fs.humanize_file_size(unnecessary_size),\n        fg=\"yellow\",\n    )\n"
  },
  {
    "path": "platformio/package/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/package/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.package.commands.exec import package_exec_cmd\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.list import package_list_cmd\nfrom platformio.package.commands.outdated import package_outdated_cmd\nfrom platformio.package.commands.pack import package_pack_cmd\nfrom platformio.package.commands.publish import package_publish_cmd\nfrom platformio.package.commands.search import package_search_cmd\nfrom platformio.package.commands.show import package_show_cmd\nfrom platformio.package.commands.uninstall import package_uninstall_cmd\nfrom platformio.package.commands.unpublish import package_unpublish_cmd\nfrom platformio.package.commands.update import package_update_cmd\n\n\n@click.group(\n    \"pkg\",\n    commands=[\n        package_exec_cmd,\n        package_install_cmd,\n        package_list_cmd,\n        package_outdated_cmd,\n        package_pack_cmd,\n        package_publish_cmd,\n        package_search_cmd,\n        package_show_cmd,\n        package_uninstall_cmd,\n        package_unpublish_cmd,\n        package_update_cmd,\n    ],\n    short_help=\"Unified Package Manager\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/package/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/package/commands/exec.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport subprocess\n\nimport click\n\nfrom platformio.compat import IS_MACOS, IS_WINDOWS\nfrom platformio.exception import ReturnErrorCode, UserSideException\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.proc import get_pythonexe_path, where_is_program\n\n\n@click.command(\"exec\", short_help=\"Run command from package tool\")\n@click.option(\"-p\", \"--package\", metavar=\"SPECIFICATION\")\n@click.option(\"-c\", \"--call\", metavar=\"<cmd> [args...]\")\n@click.argument(\"args\", nargs=-1, type=click.UNPROCESSED)\n@click.pass_obj\ndef package_exec_cmd(obj, package, call, args):\n    if not call and not args:\n        raise click.BadArgumentUsage(\"Please provide command name\")\n    pkg = None\n    if package:\n        pm = ToolPackageManager()\n        pkg = pm.get_package(package)\n        if not pkg:\n            pkg = pm.install(package)\n    else:\n        executable = args[0] if args else call.split(\" \")[0]\n        pkg = find_pkg_by_executable(executable)\n        if not pkg:\n            raise UserSideException(\n                \"Could not find a package with '%s' executable file\" % executable\n            )\n\n    click.echo(\n        \"Using %s package\"\n        % click.style(\"%s@%s\" % (pkg.metadata.name, pkg.metadata.version), fg=\"cyan\")\n    )\n\n    inject_pkg_to_environ(pkg)\n    os.environ[\"PIO_PYTHON_EXE\"] = get_pythonexe_path()\n\n    # inject current python interpreter on Windows\n    if args and args[0].endswith(\".py\"):\n        args = [os.environ[\"PIO_PYTHON_EXE\"]] + list(args)\n        if not os.path.exists(args[1]):\n            args[1] = where_is_program(args[1])\n\n    result = None\n    try:\n        run_options = dict(shell=call is not None, env=os.environ)\n        force_click_stream = (obj or {}).get(\"force_click_stream\")\n        if force_click_stream:\n            run_options.update(stdout=subprocess.PIPE, stderr=subprocess.STDOUT)\n        result = subprocess.run(  # pylint: disable=subprocess-run-check\n            call or args, **run_options\n        )\n        if force_click_stream:\n            click.echo(result.stdout.decode().strip(), err=result.returncode != 0)\n    except Exception as exc:\n        raise UserSideException(exc) from exc\n\n    if result and result.returncode != 0:\n        raise ReturnErrorCode(result.returncode)\n\n\ndef find_pkg_by_executable(executable):\n    exes = [executable]\n    if IS_WINDOWS and not executable.endswith(\".exe\"):\n        exes.append(f\"{executable}.exe\")\n    for pkg in ToolPackageManager().get_installed():\n        for exe in exes:\n            if os.path.exists(os.path.join(pkg.path, exe)) or os.path.exists(\n                os.path.join(pkg.path, \"bin\", exe)\n            ):\n                return pkg\n    return None\n\n\ndef inject_pkg_to_environ(pkg):\n    bin_dir = os.path.join(pkg.path, \"bin\")\n    lib_dir = os.path.join(pkg.path, \"lib\")\n\n    paths = [bin_dir, pkg.path] if os.path.isdir(bin_dir) else [pkg.path]\n    if os.environ.get(\"PATH\"):\n        paths.append(os.environ.get(\"PATH\"))\n    os.environ[\"PATH\"] = os.pathsep.join(paths)\n\n    if IS_WINDOWS or not os.path.isdir(lib_dir) or \"toolchain\" in pkg.metadata.name:\n        return\n\n    lib_path_key = \"DYLD_LIBRARY_PATH\" if IS_MACOS else \"LD_LIBRARY_PATH\"\n    lib_paths = [lib_dir]\n    if os.environ.get(lib_path_key):\n        lib_paths.append(os.environ.get(lib_path_key))\n    os.environ[lib_path_key] = os.pathsep.join(lib_paths)\n"
  },
  {
    "path": "platformio/package/commands/install.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\nfrom pathlib import Path\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.manager.core import get_core_package_dir\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageCompatibility, PackageSpec\nfrom platformio.platform.exception import UnknownPlatform\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.savedeps import pkg_to_save_spec, save_project_dependencies\nfrom platformio.test.result import TestSuite\nfrom platformio.test.runners.factory import TestRunnerFactory\n\n\n@click.command(\n    \"install\", short_help=\"Install the project dependencies or custom packages\"\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"-p\", \"--platform\", \"platforms\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-t\", \"--tool\", \"tools\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-l\", \"--library\", \"libraries\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\n    \"--no-save\",\n    is_flag=True,\n    help=\"Prevent saving specified packages to `platformio.ini`\",\n)\n@click.option(\"--skip-dependencies\", is_flag=True, help=\"Skip package dependencies\")\n@click.option(\"-g\", \"--global\", is_flag=True, help=\"Install package globally\")\n@click.option(\n    \"--storage-dir\",\n    default=None,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n    help=\"Custom Package Manager storage for global packages\",\n)\n@click.option(\"-f\", \"--force\", is_flag=True, help=\"Reinstall package if it exists\")\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\ndef package_install_cmd(**options):\n    if options.get(\"global\") or options.get(\"storage_dir\"):\n        install_global_dependencies(options)\n    else:\n        install_project_dependencies(options)\n\n\ndef install_global_dependencies(options):\n    pm = PlatformPackageManager(options.get(\"storage_dir\"))\n    tm = ToolPackageManager(options.get(\"storage_dir\"))\n    lm = LibraryPackageManager(options.get(\"storage_dir\"))\n    for obj in (pm, tm, lm):\n        obj.set_log_level(logging.WARN if options.get(\"silent\") else logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        pm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n    for spec in options.get(\"tools\"):\n        tm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n    for spec in options.get(\"libraries\", []):\n        lm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n\n\ndef install_project_dependencies(options):\n    environments = options[\"environments\"]\n    with fs.cd(options[\"project_dir\"]):\n        config = ProjectConfig.get_instance()\n        config.validate(environments)\n        for env in config.envs():\n            if environments and env not in environments:\n                continue\n            if not options.get(\"silent\"):\n                click.echo(\"Resolving %s dependencies...\" % click.style(env, fg=\"cyan\"))\n            already_up_to_date = not install_project_env_dependencies(env, options)\n            if not options.get(\"silent\") and already_up_to_date:\n                click.secho(\"Already up-to-date.\", fg=\"green\")\n\n\ndef install_project_env_dependencies(project_env, options=None):\n    \"\"\"Used in `pio run` -> Processor\"\"\"\n    options = options or {}\n    installed_conds = []\n    # custom platforms\n    if options.get(\"platforms\"):\n        installed_conds.append(\n            _install_project_env_custom_platforms(project_env, options)\n        )\n    # custom tools\n    if options.get(\"tools\"):\n        installed_conds.append(_install_project_env_custom_tools(project_env, options))\n    # custom libraries\n    if options.get(\"libraries\"):\n        installed_conds.append(\n            _install_project_env_custom_libraries(project_env, options)\n        )\n    # declared dependencies\n    if not installed_conds:\n        installed_conds = [\n            _install_project_env_platform(project_env, options),\n            _install_project_env_libraries(project_env, options),\n        ]\n    return any(installed_conds)\n\n\ndef _install_project_env_platform(project_env, options):\n    config = ProjectConfig.get_instance()\n    pm = PlatformPackageManager()\n    if options.get(\"silent\"):\n        pm.set_log_level(logging.WARN)\n    spec = config.get(f\"env:{project_env}\", \"platform\")\n    if not spec:\n        return False\n    already_up_to_date = not options.get(\"force\")\n    if not pm.get_package(spec):\n        already_up_to_date = False\n    PlatformPackageManager().install(\n        spec,\n        project_env=project_env,\n        project_targets=options.get(\"project_targets\"),\n        skip_dependencies=options.get(\"skip_dependencies\"),\n        force=options.get(\"force\"),\n    )\n    # ensure SCons is installed\n    get_core_package_dir(\"tool-scons\")\n    return not already_up_to_date\n\n\ndef _install_project_env_custom_platforms(project_env, options):\n    already_up_to_date = not options.get(\"force\")\n    pm = PlatformPackageManager()\n    if not options.get(\"silent\"):\n        pm.set_log_level(logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        if not pm.get_package(spec):\n            already_up_to_date = False\n        pm.install(\n            spec,\n            project_env=project_env,\n            project_targets=options.get(\"project_targets\"),\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n    return not already_up_to_date\n\n\ndef _install_project_env_custom_tools(project_env, options):\n    already_up_to_date = not options.get(\"force\")\n    tm = ToolPackageManager()\n    if not options.get(\"silent\"):\n        tm.set_log_level(logging.DEBUG)\n    specs_to_save = []\n    for tool in options.get(\"tools\"):\n        spec = PackageSpec(tool)\n        if not tm.get_package(spec):\n            already_up_to_date = False\n        pkg = tm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n        specs_to_save.append(pkg_to_save_spec(pkg, spec))\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"platform_packages\",\n            action=\"add\",\n            environments=[project_env],\n        )\n    return not already_up_to_date\n\n\ndef _install_project_env_libraries(project_env, options):\n    already_up_to_date = not options.get(\"force\")\n    config = ProjectConfig.get_instance()\n\n    compatibility_qualifiers = {}\n    if config.get(f\"env:{project_env}\", \"platform\", None):\n        try:\n            p = PlatformFactory.new(config.get(f\"env:{project_env}\", \"platform\"))\n            compatibility_qualifiers[\"platforms\"] = [p.name]\n        except UnknownPlatform:\n            pass\n        if config.get(f\"env:{project_env}\", \"framework\"):\n            compatibility_qualifiers[\"frameworks\"] = config.get(\n                f\"env:{project_env}\", \"framework\"\n            )\n\n    env_lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env),\n        compatibility=(\n            PackageCompatibility(**compatibility_qualifiers)\n            if compatibility_qualifiers\n            else None\n        ),\n    )\n    private_lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"lib_dir\"))\n    )\n    if options.get(\"silent\"):\n        env_lm.set_log_level(logging.WARN)\n        private_lm.set_log_level(logging.WARN)\n\n    lib_deps = config.get(f\"env:{project_env}\", \"lib_deps\")\n    if \"__test\" in options.get(\"project_targets\", []):\n        test_runner = TestRunnerFactory.new(\n            TestSuite(project_env, options.get(\"piotest_running_name\", \"*\")), config\n        )\n        lib_deps.extend(test_runner.EXTRA_LIB_DEPS or [])\n\n    _uninstall_project_unused_libdeps(env_lm, lib_deps)\n\n    for library in lib_deps:\n        spec = PackageSpec(library)\n        # skip built-in dependencies\n        if not spec.external and not spec.owner:\n            continue\n        if not env_lm.get_package(spec):\n            already_up_to_date = False\n        env_lm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n\n    # install dependencies from the private libraries\n    for pkg in private_lm.get_installed():\n        _install_project_private_library_deps(pkg, private_lm, env_lm, options)\n\n    return not already_up_to_date\n\n\ndef _uninstall_project_unused_libdeps(lm, lib_deps):\n    lib_deps = set(lib_deps)\n    storage_dir = Path(lm.package_dir)\n    if not lib_deps:\n        if storage_dir.exists():\n            fs.rmtree(str(storage_dir))\n        return\n    integrity_dat = storage_dir / \"integrity.dat\"\n    if integrity_dat.is_file():\n        prev_lib_deps = set(\n            integrity_dat.read_text(encoding=\"utf-8\").strip().split(\"\\n\")\n        )\n        if lib_deps == prev_lib_deps:\n            return\n        if lm.log.getEffectiveLevel() < logging.WARN:\n            click.secho(\"Removing unused dependencies...\")\n        for spec in set(prev_lib_deps) - set(lib_deps):\n            try:\n                lm.uninstall(spec)\n            except UnknownPackageError:\n                pass\n    if not storage_dir.is_dir():\n        storage_dir.mkdir(parents=True)\n    integrity_dat.write_text(\"\\n\".join(lib_deps), encoding=\"utf-8\")\n\n\ndef _install_project_private_library_deps(private_pkg, private_lm, env_lm, options):\n    for dependency in private_lm.get_pkg_dependencies(private_pkg) or []:\n        spec = private_lm.dependency_to_spec(dependency)\n        # skip built-in dependencies\n        if not spec.external and not spec.owner:\n            continue\n        pkg = private_lm.get_package(spec)\n        if (\n            not pkg\n            and not private_lm.get_package(spec)\n            and not env_lm.get_package(spec)\n        ):\n            pkg = env_lm.install(\n                spec,\n                skip_dependencies=True,\n                force=options.get(\"force\"),\n            )\n        if not pkg:\n            continue\n        _install_project_private_library_deps(pkg, private_lm, env_lm, options)\n\n\ndef _install_project_env_custom_libraries(project_env, options):\n    already_up_to_date = not options.get(\"force\")\n    config = ProjectConfig.get_instance()\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if not options.get(\"silent\"):\n        lm.set_log_level(logging.DEBUG)\n    specs_to_save = []\n    for library in options.get(\"libraries\") or []:\n        spec = PackageSpec(library)\n        if not lm.get_package(spec):\n            already_up_to_date = False\n        pkg = lm.install(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n            force=options.get(\"force\"),\n        )\n        specs_to_save.append(pkg_to_save_spec(pkg, spec))\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"lib_deps\",\n            action=\"add\",\n            environments=[project_env],\n        )\n    return not already_up_to_date\n"
  },
  {
    "path": "platformio/package/commands/list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom typing import List\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageItem, PackageSpec\nfrom platformio.platform.exception import UnknownPlatform\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\n\n\n@click.command(\"list\", short_help=\"List installed packages\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"-p\", \"--platform\", \"platforms\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-t\", \"--tool\", \"tools\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-l\", \"--library\", \"libraries\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-g\", \"--global\", is_flag=True, help=\"List globally installed packages\")\n@click.option(\n    \"--storage-dir\",\n    default=None,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n    help=\"Custom Package Manager storage for global packages\",\n)\n@click.option(\"--only-platforms\", is_flag=True, help=\"List only platform packages\")\n@click.option(\"--only-tools\", is_flag=True, help=\"List only tool packages\")\n@click.option(\"--only-libraries\", is_flag=True, help=\"List only library packages\")\n@click.option(\"-v\", \"--verbose\", is_flag=True)\ndef package_list_cmd(**options):\n    if options.get(\"global\"):\n        list_global_packages(options)\n    else:\n        list_project_packages(options)\n\n\ndef humanize_package(pkg, spec=None, verbose=False):\n    if spec and not isinstance(spec, PackageSpec):\n        spec = PackageSpec(spec)\n    data = [\n        click.style(pkg.metadata.name, fg=\"cyan\"),\n        click.style(f\"@ {str(pkg.metadata.version)}\", bold=True),\n    ]\n    extra_data = [\"required: %s\" % (spec.humanize() if spec else \"Any\")]\n    if verbose:\n        extra_data.append(pkg.path)\n    data.append(\"(%s)\" % \", \".join(extra_data))\n    return \" \".join(data)\n\n\ndef print_dependency_tree(pm, specs=None, filter_specs=None, level=0, verbose=False):\n    filtered_pkgs = [\n        pm.get_package(spec) for spec in filter_specs or [] if pm.get_package(spec)\n    ]\n    candidates = {}\n    if specs:\n        for spec in specs:\n            pkg = pm.get_package(spec)\n            if not pkg:\n                continue\n            candidates[pkg.path] = (pkg, spec)\n    else:\n        candidates = {pkg.path: (pkg, pkg.metadata.spec) for pkg in pm.get_installed()}\n    if not candidates:\n        return\n    candidates = sorted(candidates.values(), key=lambda item: item[0].metadata.name)\n\n    for index, (pkg, spec) in enumerate(candidates):\n        if filtered_pkgs and not _pkg_tree_contains(pm, pkg, filtered_pkgs):\n            continue\n        printed_pkgs = pm.memcache_get(\"__printed_pkgs\", [])\n        if printed_pkgs and pkg.path in printed_pkgs:\n            continue\n        printed_pkgs.append(pkg.path)\n        pm.memcache_set(\"__printed_pkgs\", printed_pkgs)\n\n        click.echo(\n            \"%s%s %s\"\n            % (\n                \"│   \" * level,\n                \"├──\" if index < len(candidates) - 1 else \"└──\",\n                humanize_package(\n                    pkg,\n                    spec=spec,\n                    verbose=verbose,\n                ),\n            )\n        )\n\n        dependencies = pm.get_pkg_dependencies(pkg)\n        if dependencies:\n            print_dependency_tree(\n                pm,\n                specs=[pm.dependency_to_spec(item) for item in dependencies],\n                filter_specs=filter_specs,\n                level=level + 1,\n                verbose=verbose,\n            )\n\n\ndef _pkg_tree_contains(pm, root: PackageItem, children: List[PackageItem]):\n    if root in children:\n        return True\n    for dependency in pm.get_pkg_dependencies(root) or []:\n        pkg = pm.get_package(pm.dependency_to_spec(dependency))\n        if pkg and _pkg_tree_contains(pm, pkg, children):\n            return True\n    return False\n\n\ndef list_global_packages(options):\n    data = [\n        (\"platforms\", PlatformPackageManager(options.get(\"storage_dir\"))),\n        (\"tools\", ToolPackageManager(options.get(\"storage_dir\"))),\n        (\"libraries\", LibraryPackageManager(options.get(\"storage_dir\"))),\n    ]\n    only_packages = any(\n        options.get(typex) or options.get(f\"only_{typex}\") for (typex, _) in data\n    )\n    for typex, pm in data:\n        skip_conds = [\n            only_packages\n            and not options.get(typex)\n            and not options.get(f\"only_{typex}\"),\n            not pm.get_installed(),\n        ]\n        if any(skip_conds):\n            continue\n        click.secho(typex.capitalize(), bold=True)\n        print_dependency_tree(\n            pm, filter_specs=options.get(typex), verbose=options.get(\"verbose\")\n        )\n        click.echo()\n\n\ndef list_project_packages(options):\n    environments = options[\"environments\"]\n    only_packages = any(\n        options.get(typex) or options.get(f\"only_{typex}\")\n        for typex in (\"platforms\", \"tools\", \"libraries\")\n    )\n    only_platform_packages = any(\n        options.get(typex) or options.get(f\"only_{typex}\")\n        for typex in (\"platforms\", \"tools\")\n    )\n    only_library_packages = options.get(\"libraries\") or options.get(\"only_libraries\")\n\n    with fs.cd(options[\"project_dir\"]):\n        config = ProjectConfig.get_instance()\n        config.validate(environments)\n        for env in config.envs():\n            if environments and env not in environments:\n                continue\n            click.echo(\"Resolving %s dependencies...\" % click.style(env, fg=\"cyan\"))\n            found = False\n            if not only_packages or only_platform_packages:\n                _found = print_project_env_platform_packages(env, options)\n                found = found or _found\n            if not only_packages or only_library_packages:\n                _found = print_project_env_library_packages(env, options)\n                found = found or _found\n            if not found:\n                click.echo(\"No packages\")\n            if (not environments and len(config.envs()) > 1) or len(environments) > 1:\n                click.echo()\n\n\ndef print_project_env_platform_packages(project_env, options):\n    try:\n        p = PlatformFactory.from_env(project_env)\n    except UnknownPlatform:\n        return None\n    click.echo(\n        \"Platform %s\"\n        % (\n            humanize_package(\n                PlatformPackageManager().get_package(p.get_dir()),\n                p.config.get(f\"env:{project_env}\", \"platform\"),\n                verbose=options.get(\"verbose\"),\n            )\n        )\n    )\n    print_dependency_tree(\n        p.pm,\n        specs=[p.get_package_spec(name) for name in p.packages],\n        filter_specs=options.get(\"tools\"),\n    )\n    click.echo()\n    return True\n\n\ndef print_project_env_library_packages(project_env, options):\n    config = ProjectConfig.get_instance()\n    lib_deps = config.get(f\"env:{project_env}\", \"lib_deps\")\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if not lib_deps or not lm.get_installed():\n        return None\n    click.echo(\"Libraries\")\n    print_dependency_tree(\n        lm,\n        lib_deps,\n        filter_specs=options.get(\"libraries\"),\n        verbose=options.get(\"verbose\"),\n    )\n    return True\n"
  },
  {
    "path": "platformio/package/commands/outdated.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\n\n\nclass OutdatedCandidate:\n    def __init__(self, pm, pkg, spec, envs=None):\n        self.pm = pm\n        self.pkg = pkg\n        self.spec = spec\n        self.envs = envs or []\n        self.outdated = None\n        if not isinstance(self.envs, list):\n            self.envs = [self.envs]\n\n    def __eq__(self, other):\n        return all(\n            [\n                self.pm.package_dir == other.pm.package_dir,\n                self.pkg == other.pkg,\n                self.spec == other.spec,\n            ]\n        )\n\n    def check(self):\n        self.outdated = self.pm.outdated(self.pkg, self.spec)\n\n    def is_outdated(self):\n        if not self.outdated:\n            self.check()\n        return self.outdated.is_outdated(allow_incompatible=self.pm.pkg_type != \"tool\")\n\n\n@click.command(\"outdated\", short_help=\"Check for outdated packages\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\ndef package_outdated_cmd(project_dir, environments):\n    with fs.cd(project_dir):\n        candidates = fetch_outdated_candidates(environments, with_progress=True)\n        print_outdated_candidates(candidates)\n\n\ndef print_outdated_candidates(candidates):\n    if not candidates:\n        click.secho(\"Everything is up-to-date!\", fg=\"green\")\n        return\n    tabulate_data = [\n        (\n            click.style(\n                candidate.pkg.metadata.name,\n                fg=get_candidate_update_color(candidate.outdated),\n            ),\n            candidate.outdated.current,\n            candidate.outdated.wanted,\n            click.style(candidate.outdated.latest, fg=\"cyan\"),\n            candidate.pm.pkg_type.capitalize(),\n            \", \".join(set(candidate.envs)),\n        )\n        for candidate in candidates\n    ]\n    click.echo()\n    click.secho(\"Semantic Versioning color legend:\", bold=True)\n    click.echo(\n        tabulate(\n            [\n                (\n                    click.style(\"<Major Update>\", fg=\"red\"),\n                    \"backward-incompatible updates\",\n                ),\n                (\n                    click.style(\"<Minor Update>\", fg=\"yellow\"),\n                    \"backward-compatible features\",\n                ),\n                (\n                    click.style(\"<Patch Update>\", fg=\"green\"),\n                    \"backward-compatible bug fixes\",\n                ),\n            ],\n            tablefmt=\"plain\",\n        )\n    )\n    click.echo()\n    click.echo(\n        tabulate(\n            tabulate_data,\n            headers=[\"Package\", \"Current\", \"Wanted\", \"Latest\", \"Type\", \"Environments\"],\n        )\n    )\n\n\ndef get_candidate_update_color(outdated):\n    if outdated.update_increment_type == outdated.UPDATE_INCREMENT_MAJOR:\n        return \"red\"\n    if outdated.update_increment_type == outdated.UPDATE_INCREMENT_MINOR:\n        return \"yellow\"\n    if outdated.update_increment_type == outdated.UPDATE_INCREMENT_PATCH:\n        return \"green\"\n    return None\n\n\ndef fetch_outdated_candidates(environments, with_progress=False):\n    candidates = []\n    config = ProjectConfig.get_instance()\n    config.validate(environments)\n\n    def _add_candidate(data):\n        new_candidate = OutdatedCandidate(\n            data[\"pm\"], data[\"pkg\"], data[\"spec\"], data[\"env\"]\n        )\n        for candidate in candidates:\n            if candidate == new_candidate:\n                candidate.envs.append(data[\"env\"])\n                return\n        candidates.append(new_candidate)\n\n    # platforms\n    for item in find_platform_candidates(config, environments):\n        _add_candidate(item)\n        # platform package dependencies\n        for dep_item in find_platform_dependency_candidates(item[\"env\"]):\n            _add_candidate(dep_item)\n\n    # libraries\n    for item in find_library_candidates(config, environments):\n        _add_candidate(item)\n\n    result = []\n    if not with_progress:\n        for candidate in candidates:\n            if candidate.is_outdated():\n                result.append(candidate)\n        return result\n\n    with click.progressbar(candidates, label=\"Checking\") as pb:\n        for candidate in pb:\n            if candidate.is_outdated():\n                result.append(candidate)\n    return result\n\n\ndef find_platform_candidates(config, environments):\n    result = []\n    pm = PlatformPackageManager()\n    for env in config.envs():\n        platform = config.get(f\"env:{env}\", \"platform\", None)\n        if not platform or (environments and env not in environments):\n            continue\n        spec = PackageSpec(platform)\n        pkg = pm.get_package(spec)\n        if not pkg:\n            continue\n        result.append(dict(env=env, pm=pm, pkg=pkg, spec=spec))\n    return result\n\n\ndef find_platform_dependency_candidates(env):\n    result = []\n    p = PlatformFactory.from_env(env)\n    for pkg in p.get_installed_packages():\n        result.append(\n            dict(\n                env=env,\n                pm=p.pm,\n                pkg=pkg,\n                spec=p.get_package_spec(pkg.metadata.name),\n            )\n        )\n    return sorted(result, key=lambda item: item[\"pkg\"].metadata.name)\n\n\ndef find_library_candidates(config, environments):\n    result = []\n    for env in config.envs():\n        if environments and env not in environments:\n            continue\n        package_dir = os.path.join(config.get(\"platformio\", \"libdeps_dir\") or \"\", env)\n        lib_deps = [\n            item for item in config.get(f\"env:{env}\", \"lib_deps\", []) if \"/\" in item\n        ]\n        if not os.path.isdir(package_dir) or not lib_deps:\n            continue\n        pm = LibraryPackageManager(package_dir)\n        for lib in lib_deps:\n            spec = PackageSpec(lib)\n            pkg = pm.get_package(spec)\n            if not pkg:\n                continue\n            result.append(dict(env=env, pm=pm, pkg=pkg, spec=spec))\n    return sorted(result, key=lambda item: item[\"pkg\"].metadata.name)\n"
  },
  {
    "path": "platformio/package/commands/pack.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\n\nfrom platformio.package.manifest.parser import ManifestParserFactory\nfrom platformio.package.manifest.schema import ManifestSchema, ManifestValidationError\nfrom platformio.package.pack import PackagePacker\n\n\n@click.command(\"pack\", short_help=\"Create a tarball from a package\")\n@click.argument(\n    \"package\",\n    default=os.getcwd,\n    metavar=\"<source directory, tar.gz or zip>\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=True),\n)\n@click.option(\n    \"-o\", \"--output\", help=\"A destination path (folder or a full path to file)\"\n)\ndef package_pack_cmd(package, output):\n    p = PackagePacker(package)\n    archive_path = p.pack(output)\n    # validate manifest\n    try:\n        ManifestSchema().load_manifest(\n            ManifestParserFactory.new_from_archive(archive_path).as_dict()\n        )\n    except ManifestValidationError as exc:\n        os.remove(archive_path)\n        raise exc\n    click.secho('Wrote a tarball to \"%s\"' % archive_path, fg=\"green\")\n"
  },
  {
    "path": "platformio/package/commands/publish.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport tarfile\nimport tempfile\nfrom datetime import datetime\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.account.client import AccountClient\nfrom platformio.compat import isascii\nfrom platformio.exception import UserSideException\nfrom platformio.package.manifest.parser import ManifestParserFactory\nfrom platformio.package.manifest.schema import ManifestSchema\nfrom platformio.package.meta import PackageType\nfrom platformio.package.pack import PackagePacker\nfrom platformio.package.unpack import FileUnpacker, TARArchiver\nfrom platformio.registry.client import RegistryClient\n\n\ndef validate_datetime(ctx, param, value):  # pylint: disable=unused-argument\n    if not value:\n        return value\n    try:\n        datetime.strptime(value, \"%Y-%m-%d %H:%M:%S\")\n    except ValueError as exc:\n        raise click.BadParameter(exc)\n    return value\n\n\n@click.command(\"publish\", short_help=\"Publish a package to the registry\")\n@click.argument(\n    \"package\",\n    default=os.getcwd,\n    metavar=\"<source directory, tar.gz or zip>\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=True),\n)\n@click.option(\n    \"--owner\",\n    help=\"PIO Account username (can be organization username). \"\n    \"Default is set to a username of the authorized PIO Account\",\n)\n@click.option(\n    \"--type\",\n    \"typex\",\n    type=click.Choice(list(PackageType.items().values())),\n    help=\"Custom package type\",\n)\n@click.option(\n    \"--released-at\",\n    callback=validate_datetime,\n    help=\"Custom release date and time in the next format (UTC): 2014-06-13 17:08:52\",\n)\n@click.option(\"--private\", is_flag=True, help=\"Restricted access (not a public)\")\n@click.option(\n    \"--notify/--no-notify\",\n    default=True,\n    help=\"Notify by email when package is processed\",\n)\n@click.option(\n    \"--no-interactive\",\n    is_flag=True,\n    help=\"Do not show interactive prompt\",\n)\n@click.option(\n    \"--non-interactive\",\n    is_flag=True,\n    help=\"Do not show interactive prompt\",\n    hidden=True,\n)\ndef package_publish_cmd(  # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals\n    package, owner, typex, released_at, private, notify, no_interactive, non_interactive\n):\n    click.secho(\"Preparing a package...\", fg=\"cyan\")\n    package = os.path.abspath(package)\n    no_interactive = no_interactive or non_interactive\n    owner = owner or AccountClient().get_logged_username()\n    do_not_pack = (\n        not os.path.isdir(package)\n        and isinstance(FileUnpacker.new_archiver(package), TARArchiver)\n        and PackageType.from_archive(package)\n    )\n    archive_path = None\n    with tempfile.TemporaryDirectory() as tmp_dir:  # pylint: disable=no-member\n        # publish .tar.gz instantly without repacking\n        if do_not_pack:\n            archive_path = package\n        else:\n            with fs.cd(tmp_dir):\n                p = PackagePacker(package)\n                archive_path = p.pack()\n\n        typex = typex or PackageType.from_archive(archive_path)\n        manifest = ManifestSchema().load_manifest(\n            ManifestParserFactory.new_from_archive(archive_path).as_dict()\n        )\n        name = manifest.get(\"name\")\n        version = manifest.get(\"version\")\n        data = [\n            (\"Type:\", typex),\n            (\"Owner:\", owner),\n            (\"Name:\", name),\n            (\"Version:\", version),\n            (\"Size:\", fs.humanize_file_size(os.path.getsize(archive_path))),\n        ]\n        if manifest.get(\"system\"):\n            data.insert(len(data) - 1, (\"System:\", \", \".join(manifest.get(\"system\"))))\n        click.echo(tabulate(data, tablefmt=\"plain\"))\n\n        # check files containing non-ascii chars\n        check_archive_file_names(archive_path)\n\n        # look for duplicates\n        check_package_duplicates(owner, typex, name, version, manifest.get(\"system\"))\n\n        if not no_interactive:\n            click.confirm(\n                \"Are you sure you want to publish the %s %s to the registry?\\n\"\n                % (\n                    typex,\n                    click.style(\n                        \"%s/%s@%s\" % (owner, name, version),\n                        fg=\"cyan\",\n                    ),\n                ),\n                abort=True,\n            )\n\n        click.secho(\n            \"The package publishing may take some time depending \"\n            \"on your Internet connection and the package size.\",\n            fg=\"yellow\",\n        )\n        click.echo(\"Publishing...\")\n        response = RegistryClient().publish_package(\n            owner, typex, archive_path, released_at, private, notify\n        )\n        if not do_not_pack:\n            os.remove(archive_path)\n        click.secho(response.get(\"message\"), fg=\"green\")\n\n\ndef check_archive_file_names(archive_path):\n    with tarfile.open(archive_path, mode=\"r:gz\") as tf:\n        for name in tf.getnames():\n            if not isascii(name) or not name.isprintable():\n                click.secho(\n                    f\"Warning! The `{name}` file contains non-ASCII chars and can \"\n                    \"lead to the unpacking issues on a user machine\",\n                    fg=\"yellow\",\n                )\n\n\ndef check_package_duplicates(\n    owner, type, name, version, system\n):  # pylint: disable=redefined-builtin\n    found = False\n    items = (\n        RegistryClient()\n        .list_packages(qualifiers=dict(types=[type], names=[name]))\n        .get(\"items\")\n    )\n    if not items:\n        return True\n    # duplicated version by owner / system\n    found = False\n    for item in items:\n        if item[\"owner\"][\"username\"] != owner or item[\"version\"][\"name\"] != version:\n            continue\n        if not system:\n            found = True\n            break\n        published_systems = []\n        for f in item[\"version\"][\"files\"]:\n            published_systems.extend(f.get(\"system\", []))\n        found = set(system).issubset(set(published_systems))\n    if found:\n        raise UserSideException(\n            \"The package `%s/%s@%s` is already published in the registry\"\n            % (owner, name, version)\n        )\n    other_owners = [\n        item[\"owner\"][\"username\"]\n        for item in items\n        if item[\"owner\"][\"username\"] != owner\n    ]\n    if other_owners:\n        click.secho(\n            \"\\nWarning! A package with the name `%s` is already published by the next \"\n            \"owners: %s\\n\" % (name, \", \".join(other_owners)),\n            fg=\"yellow\",\n        )\n    return True\n"
  },
  {
    "path": "platformio/package/commands/search.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport math\n\nimport click\n\nfrom platformio import util\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"search\", short_help=\"Search for packages\")\n@click.argument(\"query\")\n@click.option(\"-p\", \"--page\", type=click.IntRange(min=1))\n@click.option(\n    \"-s\",\n    \"--sort\",\n    type=click.Choice([\"relevance\", \"popularity\", \"trending\", \"added\", \"updated\"]),\n)\ndef package_search_cmd(query, page, sort):\n    client = RegistryClient()\n    result = client.list_packages(query, page=page, sort=sort)\n    if not result[\"total\"]:\n        click.secho(\"Nothing has been found by your request\", fg=\"yellow\")\n        click.echo(\n            \"Try a less-specific search or use truncation (or wildcard) operator *\"\n        )\n        return\n    print_search_result(result)\n\n\ndef print_search_result(result):\n    click.echo(\n        \"Found %d packages (page %d of %d)\"\n        % (\n            result[\"total\"],\n            result[\"page\"],\n            math.ceil(result[\"total\"] / result[\"limit\"]),\n        )\n    )\n    for item in result[\"items\"]:\n        click.echo()\n        print_search_item(item)\n\n\ndef print_search_item(item):\n    click.echo(\n        \"%s/%s\"\n        % (\n            click.style(item[\"owner\"][\"username\"], fg=\"cyan\"),\n            click.style(item[\"name\"], fg=\"cyan\", bold=True),\n        )\n    )\n    click.echo(\n        \"%s • %s • Published on %s\"\n        % (\n            (\n                item[\"type\"].capitalize()\n                if item[\"tier\"] == \"community\"\n                else click.style(\n                    (\"%s %s\" % (item[\"tier\"], item[\"type\"])).title(), bold=True\n                )\n            ),\n            item[\"version\"][\"name\"],\n            util.parse_datetime(item[\"version\"][\"released_at\"]).strftime(\"%c\"),\n        )\n    )\n    click.echo(item[\"description\"])\n"
  },
  {
    "path": "platformio/package/commands/show.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom urllib.parse import quote\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs, util\nfrom platformio.exception import UserSideException\nfrom platformio.package.manager._registry import PackageManagerRegistryMixin\nfrom platformio.package.meta import PackageSpec, PackageType\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"show\", short_help=\"Show package information\")\n@click.argument(\"spec\", metavar=\"[<owner>/]<pkg>[@<version>]\")\n@click.option(\n    \"-t\",\n    \"--type\",\n    \"pkg_type\",\n    type=click.Choice(list(PackageType.items().values())),\n    help=\"Package type\",\n)\ndef package_show_cmd(spec, pkg_type):\n    spec = PackageSpec(spec)\n    data = fetch_package_data(spec, pkg_type)\n    if not data:\n        raise UserSideException(\n            \"Could not find '%s' package in the PlatormIO Registry\" % spec.humanize()\n        )\n\n    click.echo()\n    click.echo(\n        \"%s/%s\"\n        % (\n            click.style(data[\"owner\"][\"username\"], fg=\"cyan\"),\n            click.style(data[\"name\"], fg=\"cyan\", bold=True),\n        )\n    )\n    click.echo(\n        \"%s • %s • %s • Published on %s\"\n        % (\n            data[\"type\"].capitalize(),\n            data[\"version\"][\"name\"],\n            \"Private\" if data.get(\"private\") else \"Public\",\n            util.parse_datetime(data[\"version\"][\"released_at\"]).strftime(\"%c\"),\n        )\n    )\n\n    #  Description\n    click.echo()\n    click.echo(data[\"description\"])\n\n    # Extra info\n    click.echo()\n    fields = [\n        (\"homepage\", \"Homepage\"),\n        (\"repository_url\", \"Repository\"),\n        (\"license\", \"License\"),\n        (\"popularity_rank\", \"Popularity\"),\n        (\"stars_count\", \"Stars\"),\n        (\"examples_count\", \"Examples\"),\n        (\"version.unpacked_size\", \"Installed Size\"),\n        (\"dependents_count\", \"Used By\"),\n        (\"dependencies_count\", \"Dependencies\"),\n        (\"platforms\", \"Compatible Platforms\"),\n        (\"frameworks\", \"Compatible Frameworks\"),\n        (\"keywords\", \"Keywords\"),\n    ]\n    type_plural = \"libraries\" if data[\"type\"] == \"library\" else (data[\"type\"] + \"s\")\n    extra = [\n        (\n            \"Registry\",\n            click.style(\n                \"https://registry.platformio.org/%s/%s/%s\"\n                % (type_plural, data[\"owner\"][\"username\"], quote(data[\"name\"])),\n                fg=\"blue\",\n            ),\n        )\n    ]\n    for key, title in fields:\n        if \".\" in key:\n            k1, k2 = key.split(\".\")\n            value = data.get(k1, {}).get(k2)\n        else:\n            value = data.get(key)\n        if not value:\n            continue\n        if isinstance(value, list):\n            value = \", \".join(value)\n        elif key.endswith(\"_size\"):\n            value = fs.humanize_file_size(value)\n        extra.append((title, value))\n    click.echo(tabulate(extra))\n\n    # Versions\n    click.echo(\"\")\n    table = tabulate(\n        [\n            (\n                version[\"name\"],\n                fs.humanize_file_size(max(f[\"size\"] for f in version[\"files\"])),\n                util.parse_datetime(version[\"released_at\"]),\n            )\n            for version in data[\"versions\"]\n        ],\n        headers=[\"Version\", \"Size\", \"Published\"],\n    )\n    click.echo(table)\n    click.echo(\"\")\n\n\ndef fetch_package_data(spec, pkg_type=None):\n    assert isinstance(spec, PackageSpec)\n    client = RegistryClient()\n    if pkg_type and spec.owner and spec.name:\n        return client.get_package(\n            pkg_type, spec.owner, spec.name, version=spec.requirements\n        )\n    qualifiers = {}\n    if spec.id:\n        qualifiers[\"ids\"] = str(spec.id)\n    if spec.name:\n        qualifiers[\"names\"] = spec.name.lower()\n    if pkg_type:\n        qualifiers[\"types\"] = pkg_type\n    if spec.owner:\n        qualifiers[\"owners\"] = spec.owner.lower()\n    packages = client.list_packages(qualifiers=qualifiers)[\"items\"]\n    if not packages:\n        return None\n    if len(packages) > 1:\n        PackageManagerRegistryMixin.print_multi_package_issue(\n            click.echo, packages, spec\n        )\n        return None\n    return client.get_package(\n        packages[0][\"type\"],\n        packages[0][\"owner\"][\"username\"],\n        packages[0][\"name\"],\n        version=spec.requirements,\n    )\n"
  },
  {
    "path": "platformio/package/commands/uninstall.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.savedeps import pkg_to_save_spec, save_project_dependencies\n\n\n@click.command(\n    \"uninstall\", short_help=\"Uninstall the project dependencies or custom packages\"\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"-p\", \"--platform\", \"platforms\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-t\", \"--tool\", \"tools\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-l\", \"--library\", \"libraries\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\n    \"--no-save\",\n    is_flag=True,\n    help=\"Prevent removing specified packages from `platformio.ini`\",\n)\n@click.option(\"--skip-dependencies\", is_flag=True, help=\"Skip package dependencies\")\n@click.option(\"-g\", \"--global\", is_flag=True, help=\"Uninstall global packages\")\n@click.option(\n    \"--storage-dir\",\n    default=None,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n    help=\"Custom Package Manager storage for global packages\",\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\ndef package_uninstall_cmd(**options):\n    if options.get(\"global\"):\n        uninstall_global_dependencies(options)\n    else:\n        uninstall_project_dependencies(options)\n\n\ndef uninstall_global_dependencies(options):\n    pm = PlatformPackageManager(options.get(\"storage_dir\"))\n    tm = ToolPackageManager(options.get(\"storage_dir\"))\n    lm = LibraryPackageManager(options.get(\"storage_dir\"))\n    for obj in (pm, tm, lm):\n        obj.set_log_level(logging.WARN if options.get(\"silent\") else logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        pm.uninstall(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n    for spec in options.get(\"tools\"):\n        tm.uninstall(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n    for spec in options.get(\"libraries\", []):\n        lm.uninstall(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n\n\ndef uninstall_project_dependencies(options):\n    environments = options[\"environments\"]\n    with fs.cd(options[\"project_dir\"]):\n        config = ProjectConfig.get_instance()\n        config.validate(environments)\n        for env in config.envs():\n            if environments and env not in environments:\n                continue\n            if not options[\"silent\"]:\n                click.echo(\"Resolving %s dependencies...\" % click.style(env, fg=\"cyan\"))\n            already_up_to_date = not uninstall_project_env_dependencies(env, options)\n            if not options[\"silent\"] and already_up_to_date:\n                click.secho(\"Already up-to-date.\", fg=\"green\")\n\n\ndef uninstall_project_env_dependencies(project_env, options=None):\n    options = options or {}\n    uninstalled_conds = []\n    # custom platforms\n    if options.get(\"platforms\"):\n        uninstalled_conds.append(\n            _uninstall_project_env_custom_platforms(project_env, options)\n        )\n    # custom tools\n    if options.get(\"tools\"):\n        uninstalled_conds.append(\n            _uninstall_project_env_custom_tools(project_env, options)\n        )\n    # custom libraries\n    if options.get(\"libraries\"):\n        uninstalled_conds.append(\n            _uninstall_project_env_custom_libraries(project_env, options)\n        )\n    # declared dependencies\n    if not uninstalled_conds:\n        uninstalled_conds = [\n            _uninstall_project_env_platform(project_env, options),\n            _uninstall_project_env_libraries(project_env, options),\n        ]\n    return any(uninstalled_conds)\n\n\ndef _uninstall_project_env_platform(project_env, options):\n    config = ProjectConfig.get_instance()\n    pm = PlatformPackageManager()\n    if options.get(\"silent\"):\n        pm.set_log_level(logging.WARN)\n    spec = config.get(f\"env:{project_env}\", \"platform\")\n    if not spec:\n        return None\n    already_up_to_date = True\n    if not pm.get_package(spec):\n        return None\n    PlatformPackageManager().uninstall(\n        spec,\n        project_env=project_env,\n        skip_dependencies=options.get(\"skip_dependencies\"),\n    )\n    return not already_up_to_date\n\n\ndef _uninstall_project_env_custom_platforms(project_env, options):\n    already_up_to_date = True\n    pm = PlatformPackageManager()\n    if not options.get(\"silent\"):\n        pm.set_log_level(logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        if pm.get_package(spec):\n            already_up_to_date = False\n        pm.uninstall(\n            spec,\n            project_env=project_env,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n    return not already_up_to_date\n\n\ndef _uninstall_project_env_custom_tools(project_env, options):\n    already_up_to_date = True\n    tm = ToolPackageManager()\n    if not options.get(\"silent\"):\n        tm.set_log_level(logging.DEBUG)\n    specs_to_save = []\n    for tool in options.get(\"tools\"):\n        spec = PackageSpec(tool)\n        if tm.get_package(spec):\n            already_up_to_date = False\n        pkg = tm.uninstall(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n        specs_to_save.append(pkg_to_save_spec(pkg, spec))\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"platform_packages\",\n            action=\"remove\",\n            environments=[project_env],\n        )\n    return not already_up_to_date\n\n\ndef _uninstall_project_env_libraries(project_env, options):\n    already_up_to_date = True\n    config = ProjectConfig.get_instance()\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if options.get(\"silent\"):\n        lm.set_log_level(logging.WARN)\n    for library in config.get(f\"env:{project_env}\", \"lib_deps\"):\n        spec = PackageSpec(library)\n        # skip built-in dependencies\n        if not spec.external and not spec.owner:\n            continue\n        if lm.get_package(spec):\n            already_up_to_date = False\n            lm.uninstall(\n                spec,\n                skip_dependencies=options.get(\"skip_dependencies\"),\n            )\n    return not already_up_to_date\n\n\ndef _uninstall_project_env_custom_libraries(project_env, options):\n    already_up_to_date = True\n    config = ProjectConfig.get_instance()\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if not options.get(\"silent\"):\n        lm.set_log_level(logging.DEBUG)\n    specs_to_save = []\n    for library in options.get(\"libraries\") or []:\n        spec = PackageSpec(library)\n        if lm.get_package(spec):\n            already_up_to_date = False\n        pkg = lm.uninstall(\n            spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n        specs_to_save.append(pkg_to_save_spec(pkg, spec))\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"lib_deps\",\n            action=\"remove\",\n            environments=[project_env],\n        )\n    return not already_up_to_date\n"
  },
  {
    "path": "platformio/package/commands/unpublish.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.account.client import AccountClient\nfrom platformio.package.meta import PackageSpec, PackageType\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"unpublish\", short_help=\"Remove a pushed package from the registry\")\n@click.argument(\n    \"package\", required=True, metavar=\"[<organization>/]<pkgname>[@<version>]\"\n)\n@click.option(\n    \"--type\",\n    type=click.Choice(list(PackageType.items().values())),\n    default=\"library\",\n    help=\"Package type, default is set to `library`\",\n)\n@click.option(\n    \"--undo\",\n    is_flag=True,\n    help=\"Undo a remove, putting a version back into the registry\",\n)\ndef package_unpublish_cmd(package, type, undo):  # pylint: disable=redefined-builtin\n    spec = PackageSpec(package)\n    response = RegistryClient().unpublish_package(\n        owner=spec.owner or AccountClient().get_logged_username(),\n        type=type,\n        name=spec.name,\n        version=str(spec.requirements),\n        undo=undo,\n    )\n    click.secho(response.get(\"message\"), fg=\"green\")\n"
  },
  {
    "path": "platformio/package/commands/update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.savedeps import pkg_to_save_spec, save_project_dependencies\n\n\n@click.command(\n    \"update\", short_help=\"Update the project dependencies or custom packages\"\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"-p\", \"--platform\", \"platforms\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-t\", \"--tool\", \"tools\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\"-l\", \"--library\", \"libraries\", metavar=\"SPECIFICATION\", multiple=True)\n@click.option(\n    \"--no-save\",\n    is_flag=True,\n    help=\"Prevent saving specified packages to `platformio.ini`\",\n)\n@click.option(\"--skip-dependencies\", is_flag=True, help=\"Skip package dependencies\")\n@click.option(\"-g\", \"--global\", is_flag=True, help=\"Update global packages\")\n@click.option(\n    \"--storage-dir\",\n    default=None,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n    help=\"Custom Package Manager storage for global packages\",\n)\n@click.option(\"-s\", \"--silent\", is_flag=True, help=\"Suppress progress reporting\")\ndef package_update_cmd(**options):\n    if options.get(\"global\"):\n        update_global_dependencies(options)\n    else:\n        update_project_dependencies(options)\n\n\ndef update_global_dependencies(options):\n    pm = PlatformPackageManager(options.get(\"storage_dir\"))\n    tm = ToolPackageManager(options.get(\"storage_dir\"))\n    lm = LibraryPackageManager(options.get(\"storage_dir\"))\n    for obj in (pm, tm, lm):\n        obj.set_log_level(logging.WARN if options.get(\"silent\") else logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        pm.update(\n            from_spec=spec,\n            to_spec=spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n    for spec in options.get(\"tools\"):\n        tm.update(\n            from_spec=spec,\n            to_spec=spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n    for spec in options.get(\"libraries\", []):\n        lm.update(\n            from_spec=spec,\n            to_spec=spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n\n\ndef update_project_dependencies(options):\n    environments = options[\"environments\"]\n    with fs.cd(options[\"project_dir\"]):\n        config = ProjectConfig.get_instance()\n        config.validate(environments)\n        for env in config.envs():\n            if environments and env not in environments:\n                continue\n            if not options[\"silent\"]:\n                click.echo(\"Resolving %s dependencies...\" % click.style(env, fg=\"cyan\"))\n            already_up_to_date = not update_project_env_dependencies(env, options)\n            if not options[\"silent\"] and already_up_to_date:\n                click.secho(\"Already up-to-date.\", fg=\"green\")\n\n\ndef update_project_env_dependencies(project_env, options=None):\n    options = options or {}\n    updated_conds = []\n    # custom platforms\n    if options.get(\"platforms\"):\n        updated_conds.append(_update_project_env_custom_platforms(project_env, options))\n    # custom tools\n    if options.get(\"tools\"):\n        updated_conds.append(_update_project_env_custom_tools(project_env, options))\n    # custom libraries\n    if options.get(\"libraries\"):\n        updated_conds.append(_update_project_env_custom_libraries(project_env, options))\n    # declared dependencies\n    if not updated_conds:\n        updated_conds = [\n            _update_project_env_platform(project_env, options),\n            _update_project_env_libraries(project_env, options),\n        ]\n    return any(updated_conds)\n\n\ndef _update_project_env_platform(project_env, options):\n    config = ProjectConfig.get_instance()\n    pm = PlatformPackageManager()\n    if options.get(\"silent\"):\n        pm.set_log_level(logging.WARN)\n    spec = config.get(f\"env:{project_env}\", \"platform\")\n    if not spec:\n        return None\n    cur_pkg = pm.get_package(spec)\n    if not cur_pkg:\n        return None\n    new_pkg = PlatformPackageManager().update(\n        cur_pkg,\n        to_spec=spec,\n        project_env=project_env,\n        skip_dependencies=options.get(\"skip_dependencies\"),\n    )\n    return cur_pkg != new_pkg\n\n\ndef _update_project_env_custom_platforms(project_env, options):\n    already_up_to_date = True\n    pm = PlatformPackageManager()\n    if not options.get(\"silent\"):\n        pm.set_log_level(logging.DEBUG)\n    for spec in options.get(\"platforms\"):\n        cur_pkg = pm.get_package(spec)\n        new_pkg = pm.update(\n            cur_pkg,\n            to_spec=spec,\n            project_env=project_env,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n        if cur_pkg != new_pkg:\n            already_up_to_date = False\n    return not already_up_to_date\n\n\ndef _update_project_env_custom_tools(project_env, options):\n    already_up_to_date = True\n    tm = ToolPackageManager()\n    if not options.get(\"silent\"):\n        tm.set_log_level(logging.DEBUG)\n\n    specs_to_save = []\n    for tool in options.get(\"tools\"):\n        spec = PackageSpec(tool)\n        cur_pkg = tm.get_package(spec)\n        new_pkg = tm.update(\n            cur_pkg,\n            to_spec=spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n        if cur_pkg != new_pkg:\n            already_up_to_date = False\n        specs_to_save.append(pkg_to_save_spec(new_pkg, spec))\n\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"platform_packages\",\n            action=\"add\",\n            environments=[project_env],\n        )\n\n    return not already_up_to_date\n\n\ndef _update_project_env_libraries(project_env, options):\n    already_up_to_date = True\n    config = ProjectConfig.get_instance()\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if options.get(\"silent\"):\n        lm.set_log_level(logging.WARN)\n    for library in config.get(f\"env:{project_env}\", \"lib_deps\"):\n        spec = PackageSpec(library)\n        # skip built-in dependencies\n        if not spec.external and not spec.owner:\n            continue\n        cur_pkg = lm.get_package(spec)\n        if cur_pkg:\n            new_pkg = lm.update(\n                cur_pkg,\n                to_spec=spec,\n                skip_dependencies=options.get(\"skip_dependencies\"),\n            )\n            if cur_pkg != new_pkg:\n                already_up_to_date = False\n    return not already_up_to_date\n\n\ndef _update_project_env_custom_libraries(project_env, options):\n    already_up_to_date = True\n    config = ProjectConfig.get_instance()\n    lm = LibraryPackageManager(\n        os.path.join(config.get(\"platformio\", \"libdeps_dir\"), project_env)\n    )\n    if not options.get(\"silent\"):\n        lm.set_log_level(logging.DEBUG)\n\n    specs_to_save = []\n    for library in options.get(\"libraries\") or []:\n        spec = PackageSpec(library)\n        cur_pkg = lm.get_package(spec)\n        new_pkg = lm.update(\n            cur_pkg,\n            to_spec=spec,\n            skip_dependencies=options.get(\"skip_dependencies\"),\n        )\n        if cur_pkg != new_pkg:\n            already_up_to_date = False\n        specs_to_save.append(pkg_to_save_spec(new_pkg, spec))\n\n    if not options.get(\"no_save\") and specs_to_save:\n        save_project_dependencies(\n            os.getcwd(),\n            specs_to_save,\n            scope=\"lib_deps\",\n            action=\"add\",\n            environments=[project_env],\n        )\n\n    return not already_up_to_date\n"
  },
  {
    "path": "platformio/package/download.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport io\nfrom email.utils import parsedate\nfrom os.path import getsize, join\nfrom time import mktime\n\nimport click\n\nfrom platformio import fs\nfrom platformio.compat import is_terminal\nfrom platformio.http import HTTPSession\nfrom platformio.package.exception import PackageException\n\n\nclass FileDownloader:\n    def __init__(self, url, dest_dir=None):\n        self._http_session = HTTPSession()\n        self._http_response = None\n        # make connection\n        self._http_response = self._http_session.get(\n            url,\n            stream=True,\n        )\n        if self._http_response.status_code not in (200, 203):\n            raise PackageException(\n                \"Got the unrecognized status code '{0}' when downloaded {1}\".format(\n                    self._http_response.status_code, url\n                )\n            )\n\n        disposition = self._http_response.headers.get(\"content-disposition\")\n        if disposition and \"filename=\" in disposition:\n            self._fname = (\n                disposition[disposition.index(\"filename=\") + 9 :]\n                .replace('\"', \"\")\n                .replace(\"'\", \"\")\n            )\n        else:\n            self._fname = [p for p in url.split(\"/\") if p][-1]\n        self._fname = str(self._fname)\n        self._destination = self._fname\n        if dest_dir:\n            self.set_destination(join(dest_dir, self._fname))\n\n    def set_destination(self, destination):\n        self._destination = destination\n\n    def get_filepath(self):\n        return self._destination\n\n    def get_lmtime(self):\n        return self._http_response.headers.get(\"last-modified\")\n\n    def get_size(self):\n        if \"content-length\" not in self._http_response.headers:\n            return -1\n        return int(self._http_response.headers[\"content-length\"])\n\n    def start(self, with_progress=True, silent=False):\n        label = \"Downloading\"\n        file_size = self.get_size()\n        itercontent = self._http_response.iter_content(\n            chunk_size=io.DEFAULT_BUFFER_SIZE\n        )\n        try:\n            with open(self._destination, \"wb\") as fp:\n                if file_size == -1 or not with_progress or silent:\n                    if not silent:\n                        click.echo(f\"{label}...\")\n                    for chunk in itercontent:\n                        fp.write(chunk)\n\n                elif not is_terminal():\n                    click.echo(f\"{label} 0%\", nl=False)\n                    print_percent_step = 10\n                    printed_percents = 0\n                    downloaded_size = 0\n                    for chunk in itercontent:\n                        fp.write(chunk)\n                        downloaded_size += len(chunk)\n                        if (downloaded_size / file_size * 100) >= (\n                            printed_percents + print_percent_step\n                        ):\n                            printed_percents += print_percent_step\n                            click.echo(f\" {printed_percents}%\", nl=False)\n                    click.echo(\"\")\n\n                else:\n                    with click.progressbar(\n                        length=file_size,\n                        iterable=itercontent,\n                        label=label,\n                        update_min_steps=min(\n                            256 * 1024, file_size / 100\n                        ),  # every 256Kb or less\n                    ) as pb:\n                        for chunk in pb:\n                            pb.update(len(chunk))\n                            fp.write(chunk)\n        finally:\n            self._http_response.close()\n            self._http_session.close()\n\n        if self.get_lmtime():\n            self._preserve_filemtime(self.get_lmtime())\n\n        return True\n\n    def verify(self, checksum=None):\n        _dlsize = getsize(self._destination)\n        if self.get_size() != -1 and _dlsize != self.get_size():\n            raise PackageException(\n                (\n                    \"The size ({0:d} bytes) of downloaded file '{1}' \"\n                    \"is not equal to remote size ({2:d} bytes)\"\n                ).format(_dlsize, self._fname, self.get_size())\n            )\n        if not checksum:\n            return True\n\n        checksum_len = len(checksum)\n        hash_algo = None\n        if checksum_len == 32:\n            hash_algo = \"md5\"\n        elif checksum_len == 40:\n            hash_algo = \"sha1\"\n        elif checksum_len == 64:\n            hash_algo = \"sha256\"\n\n        if not hash_algo:\n            raise PackageException(\n                \"Could not determine checksum algorithm by %s\" % checksum\n            )\n\n        dl_checksum = fs.calculate_file_hashsum(hash_algo, self._destination)\n        if checksum.lower() != dl_checksum.lower():\n            raise PackageException(\n                \"The checksum '{0}' of the downloaded file '{1}' \"\n                \"does not match to the remote '{2}'\".format(\n                    dl_checksum, self._fname, checksum\n                )\n            )\n        return True\n\n    def _preserve_filemtime(self, lmdate):\n        lmtime = mktime(parsedate(lmdate))\n        fs.change_filemtime(self._destination, lmtime)\n\n    def __del__(self):\n        self._http_session.close()\n        if self._http_response:\n            self._http_response.close()\n"
  },
  {
    "path": "platformio/package/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.exception import UserSideException\n\n\nclass PackageException(UserSideException):\n    pass\n\n\nclass ManifestException(PackageException):\n    pass\n\n\nclass UnknownManifestError(ManifestException):\n    pass\n\n\nclass ManifestParserError(ManifestException):\n    pass\n\n\nclass ManifestValidationError(ManifestException):\n    def __init__(self, messages, data, valid_data):\n        super().__init__()\n        self.messages = messages\n        self.data = data\n        self.valid_data = valid_data\n\n    def __str__(self):\n        return (\n            \"Invalid manifest fields: %s. \\nPlease check specification -> \"\n            \"https://docs.platformio.org/page/librarymanager/config.html\"\n            % self.messages\n        )\n\n\nclass MissingPackageManifestError(ManifestException):\n    MESSAGE = \"Could not find one of '{0}' manifest files in the package\"\n\n\nclass UnknownPackageError(PackageException):\n    MESSAGE = \"Could not find the package with '{0}' requirements\"\n\n\nclass IncompatiblePackageError(UnknownPackageError):\n    MESSAGE = (\n        \"Could not find a version of the package with '{0}' requirements \"\n        \"compatible with the '{1}' system\"\n    )\n\n\nclass NotGlobalLibDir(PackageException):\n    MESSAGE = (\n        \"The `{0}` is not a PlatformIO project.\\n\\n\"\n        \"To manage libraries in global storage `{1}`,\\n\"\n        \"please use `platformio lib --global {2}` or specify custom storage \"\n        \"`platformio lib --storage-dir /path/to/storage/ {2}`.\\n\"\n        \"Check `platformio lib --help` for details.\"\n    )\n"
  },
  {
    "path": "platformio/package/lockfile.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom time import sleep, time\n\nfrom platformio.exception import UserSideException\n\nLOCKFILE_TIMEOUT = 3600  # in seconds, 1 hour\nLOCKFILE_DELAY = 0.2\n\nLOCKFILE_INTERFACE_FCNTL = 1\nLOCKFILE_INTERFACE_MSVCRT = 2\n\ntry:\n    import fcntl\n\n    LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_FCNTL\nexcept ImportError:\n    try:\n        import msvcrt\n\n        LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_MSVCRT\n    except ImportError:\n        LOCKFILE_CURRENT_INTERFACE = None\n\n\nclass LockFileExists(UserSideException):\n    pass\n\n\nclass LockFileTimeoutError(UserSideException):\n    pass\n\n\nclass LockFile:\n    def __init__(self, path, timeout=LOCKFILE_TIMEOUT, delay=LOCKFILE_DELAY):\n        self.timeout = timeout\n        self.delay = delay\n        self._lock_path = os.path.abspath(path) + \".lock\"\n        self._fp = None\n\n    def _lock(self):\n        if not LOCKFILE_CURRENT_INTERFACE and os.path.exists(self._lock_path):\n            # remove stale lock\n            if time() - os.path.getmtime(self._lock_path) > 10:\n                try:\n                    os.remove(self._lock_path)\n                except:  # pylint: disable=bare-except\n                    pass\n            else:\n                raise LockFileExists\n\n        self._fp = open(  # pylint: disable=consider-using-with\n            self._lock_path, mode=\"w\", encoding=\"utf8\"\n        )\n        try:\n            if LOCKFILE_CURRENT_INTERFACE == LOCKFILE_INTERFACE_FCNTL:\n                fcntl.flock(self._fp.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)\n            elif LOCKFILE_CURRENT_INTERFACE == LOCKFILE_INTERFACE_MSVCRT:\n                msvcrt.locking(  # pylint: disable=used-before-assignment\n                    self._fp.fileno(), msvcrt.LK_NBLCK, 1\n                )\n        except (BlockingIOError, IOError) as exc:\n            self._fp.close()\n            self._fp = None\n            raise LockFileExists from exc\n        return True\n\n    def _unlock(self):\n        if not self._fp:\n            return\n        if LOCKFILE_CURRENT_INTERFACE == LOCKFILE_INTERFACE_FCNTL:\n            fcntl.flock(self._fp.fileno(), fcntl.LOCK_UN)\n        elif LOCKFILE_CURRENT_INTERFACE == LOCKFILE_INTERFACE_MSVCRT:\n            msvcrt.locking(self._fp.fileno(), msvcrt.LK_UNLCK, 1)\n        self._fp.close()\n        self._fp = None\n\n    def acquire(self):\n        elapsed = 0\n        while elapsed < self.timeout:\n            try:\n                return self._lock()\n            except LockFileExists:\n                sleep(self.delay)\n                elapsed += self.delay\n\n        raise LockFileTimeoutError()\n\n    def release(self):\n        self._unlock()\n        if os.path.exists(self._lock_path):\n            try:\n                os.remove(self._lock_path)\n            except:  # pylint: disable=bare-except\n                pass\n\n    def __enter__(self):\n        self.acquire()\n\n    def __exit__(self, type_, value, traceback):\n        self.release()\n\n    def __del__(self):\n        self.release()\n"
  },
  {
    "path": "platformio/package/manager/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/package/manager/_download.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport logging\nimport os\nimport tempfile\nimport time\n\nimport click\n\nfrom platformio import app, compat, util\nfrom platformio.package.download import FileDownloader\nfrom platformio.package.lockfile import LockFile\n\n\nclass PackageManagerDownloadMixin:\n    DOWNLOAD_CACHE_EXPIRE = 86400 * 30  # keep package in a local cache for 1 month\n\n    def compute_download_path(self, *args):\n        request_hash = hashlib.new(\"sha1\")\n        for arg in args:\n            request_hash.update(compat.hashlib_encode_data(arg))\n        dl_path = os.path.join(self.get_download_dir(), request_hash.hexdigest())\n        return dl_path\n\n    def get_download_usagedb_path(self):\n        return os.path.join(self.get_download_dir(), \"usage.db\")\n\n    def set_download_utime(self, path, utime=None):\n        with app.State(self.get_download_usagedb_path(), lock=True) as state:\n            state[os.path.basename(path)] = int(time.time() if not utime else utime)\n\n    @util.memoized(DOWNLOAD_CACHE_EXPIRE)\n    def cleanup_expired_downloads(self, _=None):\n        with app.State(self.get_download_usagedb_path(), lock=True) as state:\n            # remove outdated\n            for fname in list(state.keys()):\n                if state[fname] > (time.time() - self.DOWNLOAD_CACHE_EXPIRE):\n                    continue\n                del state[fname]\n                dl_path = os.path.join(self.get_download_dir(), fname)\n                if os.path.isfile(dl_path):\n                    os.remove(dl_path)\n\n    def download(self, url, checksum=None):\n        silent = not self.log.isEnabledFor(logging.INFO)\n        dl_path = self.compute_download_path(url, checksum or \"\")\n        if os.path.isfile(dl_path):\n            self.set_download_utime(dl_path)\n            return dl_path\n\n        with_progress = not app.is_disabled_progressbar()\n        tmp_fd, tmp_path = tempfile.mkstemp(dir=self.get_download_dir())\n        try:\n            with LockFile(dl_path):\n                try:\n                    fd = FileDownloader(url)\n                    fd.set_destination(tmp_path)\n                    fd.start(with_progress=with_progress, silent=silent)\n                except IOError as exc:\n                    raise_error = not silent\n                    if with_progress:\n                        try:\n                            fd = FileDownloader(url)\n                            fd.set_destination(tmp_path)\n                            fd.start(with_progress=False, silent=silent)\n                        except IOError:\n                            raise_error = True\n                    if raise_error:\n                        self.log.error(\n                            click.style(\n                                \"Error: Please read https://bit.ly/package-manager-ioerror\",\n                                fg=\"red\",\n                            )\n                        )\n                        raise exc\n            if checksum:\n                fd.verify(checksum)\n            os.close(tmp_fd)\n            os.rename(tmp_path, dl_path)\n        finally:\n            if os.path.isfile(tmp_path):\n                os.close(tmp_fd)\n                os.remove(tmp_path)\n\n        assert os.path.isfile(dl_path)\n        self.set_download_utime(dl_path)\n        return dl_path\n"
  },
  {
    "path": "platformio/package/manager/_install.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport os\nimport shutil\nimport tempfile\n\nimport click\n\nfrom platformio import app, compat, fs, util\nfrom platformio.package.exception import PackageException, UnknownPackageError\nfrom platformio.package.meta import PackageCompatibility, PackageItem\nfrom platformio.package.unpack import FileUnpacker\nfrom platformio.package.vcsclient import VCSClientFactory\n\n\nclass PackageManagerInstallMixin:\n    _INSTALL_HISTORY = None  # avoid circle dependencies\n\n    @staticmethod\n    def unpack(src, dst):\n        with_progress = not app.is_disabled_progressbar()\n        try:\n            with FileUnpacker(src) as fu:\n                return fu.unpack(dst, with_progress=with_progress)\n        except IOError as exc:\n            if not with_progress:\n                raise exc\n            with FileUnpacker(src) as fu:\n                return fu.unpack(dst, with_progress=False)\n\n    def install(self, spec, skip_dependencies=False, force=False):\n        try:\n            self.lock()\n            pkg = self._install(spec, skip_dependencies=skip_dependencies, force=force)\n            self.memcache_reset()\n            self.cleanup_expired_downloads()\n            return pkg\n        finally:\n            self.unlock()\n\n    def _install(\n        self,\n        spec,\n        skip_dependencies=False,\n        force=False,\n        compatibility: PackageCompatibility = None,\n    ):\n        spec = self.ensure_spec(spec)\n\n        # avoid circle dependencies\n        if not self._INSTALL_HISTORY:\n            self._INSTALL_HISTORY = {}\n        if not force and spec in self._INSTALL_HISTORY:\n            return self._INSTALL_HISTORY[spec]\n\n        # check if package is already installed\n        pkg = self.get_package(spec)\n\n        # if a forced installation\n        if pkg and force:\n            self.uninstall(pkg)\n            pkg = None\n\n        if pkg:\n            # avoid RecursionError for circular_dependencies\n            self._INSTALL_HISTORY[spec] = pkg\n\n            self.log.debug(\n                click.style(\n                    \"{name}@{version} is already installed\".format(\n                        **pkg.metadata.as_dict()\n                    ),\n                    fg=\"yellow\",\n                )\n            )\n            # ensure package dependencies are installed\n            if not skip_dependencies:\n                self.install_dependencies(pkg, print_header=False)\n            return pkg\n\n        self.log.info(\"Installing %s\" % click.style(spec.humanize(), fg=\"cyan\"))\n\n        if spec.external:\n            pkg = self.install_from_uri(spec.uri, spec)\n        else:\n            pkg = self.install_from_registry(\n                spec,\n                search_qualifiers=(\n                    compatibility.to_search_qualifiers(\n                        [\"platforms\", \"frameworks\", \"authors\"]\n                    )\n                    if compatibility\n                    else None\n                ),\n            )\n\n        if not pkg or not pkg.metadata:\n            raise PackageException(\n                \"Could not install package '%s' for '%s' system\"\n                % (spec.humanize(), util.get_systype())\n            )\n\n        self.call_pkg_script(pkg, \"postinstall\")\n\n        self.log.info(\n            click.style(\n                \"{name}@{version} has been installed!\".format(**pkg.metadata.as_dict()),\n                fg=\"green\",\n            )\n        )\n\n        self.memcache_reset()\n        # avoid RecursionError for circular_dependencies\n        self._INSTALL_HISTORY[spec] = pkg\n\n        if not skip_dependencies:\n            self.install_dependencies(pkg)\n\n        return pkg\n\n    def install_dependencies(self, pkg, print_header=True):\n        assert isinstance(pkg, PackageItem)\n        dependencies = self.get_pkg_dependencies(pkg)\n        if not dependencies:\n            return\n        if print_header:\n            self.log.info(\"Resolving dependencies...\")\n        for dependency in dependencies:\n            try:\n                self.install_dependency(dependency)\n            except UnknownPackageError:\n                if dependency.get(\"owner\"):\n                    self.log.warning(\n                        click.style(\n                            \"Warning! Could not install `%s` dependency \"\n                            \"for the`%s` package\" % (dependency, pkg.metadata.name),\n                            fg=\"yellow\",\n                        )\n                    )\n\n    def install_dependency(self, dependency):\n        dependency_compatibility = PackageCompatibility.from_dependency(dependency)\n        if self.compatibility and not dependency_compatibility.is_compatible(\n            self.compatibility\n        ):\n            self.log.debug(\n                click.style(\n                    \"Skip incompatible `%s` dependency with `%s`\"\n                    % (dependency, self.compatibility),\n                    fg=\"yellow\",\n                )\n            )\n            return None\n        return self._install(\n            spec=self.dependency_to_spec(dependency),\n            compatibility=dependency_compatibility,\n        )\n\n    def install_from_uri(self, uri, spec, checksum=None):\n        spec = self.ensure_spec(spec)\n\n        if spec.symlink:\n            return self.install_symlink(spec)\n\n        tmp_dir = tempfile.mkdtemp(prefix=\"pkg-installing-\", dir=self.get_tmp_dir())\n        vcs = None\n        try:\n            if uri.startswith(\"file://\"):\n                _uri = uri[7:]\n                if os.path.isfile(_uri):\n                    self.unpack(_uri, tmp_dir)\n                else:\n                    fs.rmtree(tmp_dir)\n                    shutil.copytree(_uri, tmp_dir, symlinks=True)\n            elif uri.startswith((\"http://\", \"https://\")):\n                dl_path = self.download(uri, checksum)\n                assert os.path.isfile(dl_path)\n                self.unpack(dl_path, tmp_dir)\n            else:\n                vcs = VCSClientFactory.new(tmp_dir, uri)\n                assert vcs.export()\n\n            root_dir = self.find_pkg_root(tmp_dir, spec)\n            pkg_item = PackageItem(\n                root_dir,\n                self.build_metadata(\n                    root_dir, spec, vcs.get_current_revision() if vcs else None\n                ),\n            )\n            pkg_item.dump_meta()\n            return self._install_tmp_pkg(pkg_item)\n        finally:\n            if os.path.isdir(tmp_dir):\n                try:\n                    fs.rmtree(tmp_dir)\n                except:  # pylint: disable=bare-except\n                    pass\n\n    def _install_tmp_pkg(self, tmp_pkg):\n        assert isinstance(tmp_pkg, PackageItem)\n        # validate package version and declared requirements\n        if (\n            tmp_pkg.metadata.spec.requirements\n            and tmp_pkg.metadata.version not in tmp_pkg.metadata.spec.requirements\n        ):\n            raise PackageException(\n                \"Package version %s doesn't satisfy requirements %s based on %s\"\n                % (\n                    tmp_pkg.metadata.version,\n                    tmp_pkg.metadata.spec.requirements,\n                    tmp_pkg.metadata,\n                )\n            )\n        dst_pkg = PackageItem(\n            os.path.join(self.package_dir, tmp_pkg.get_safe_dirname())\n        )\n\n        # what to do with existing package?\n        action = \"overwrite\"\n        if tmp_pkg.metadata.spec.has_custom_name():\n            action = \"overwrite\"\n            dst_pkg = PackageItem(\n                os.path.join(self.package_dir, tmp_pkg.metadata.spec.name)\n            )\n        elif dst_pkg.metadata:\n            if dst_pkg.metadata.spec.external:\n                if dst_pkg.metadata.spec.uri != tmp_pkg.metadata.spec.uri:\n                    action = \"detach-existing\"\n            elif (\n                dst_pkg.metadata.version != tmp_pkg.metadata.version\n                or dst_pkg.metadata.spec.owner != tmp_pkg.metadata.spec.owner\n            ):\n                action = (\n                    \"detach-existing\"\n                    if tmp_pkg.metadata.version > dst_pkg.metadata.version\n                    else \"detach-new\"\n                )\n\n        def _cleanup_dir(path):\n            if os.path.isdir(path):\n                fs.rmtree(path)\n\n        if action == \"detach-existing\":\n            target_dirname = \"%s@%s\" % (\n                tmp_pkg.get_safe_dirname(),\n                dst_pkg.metadata.version,\n            )\n            if dst_pkg.metadata.spec.uri:\n                target_dirname = \"%s@src-%s\" % (\n                    tmp_pkg.get_safe_dirname(),\n                    hashlib.md5(\n                        compat.hashlib_encode_data(dst_pkg.metadata.spec.uri)\n                    ).hexdigest(),\n                )\n            # move existing into the new place\n            pkg_dir = os.path.join(self.package_dir, target_dirname)\n            _cleanup_dir(pkg_dir)\n            shutil.copytree(dst_pkg.path, pkg_dir, symlinks=True)\n            # move new source to the destination location\n            _cleanup_dir(dst_pkg.path)\n            shutil.copytree(tmp_pkg.path, dst_pkg.path, symlinks=True)\n            return PackageItem(dst_pkg.path)\n\n        if action == \"detach-new\":\n            target_dirname = \"%s@%s\" % (\n                tmp_pkg.get_safe_dirname(),\n                tmp_pkg.metadata.version,\n            )\n            if tmp_pkg.metadata.spec.external:\n                target_dirname = \"%s@src-%s\" % (\n                    tmp_pkg.get_safe_dirname(),\n                    hashlib.md5(\n                        compat.hashlib_encode_data(tmp_pkg.metadata.spec.uri)\n                    ).hexdigest(),\n                )\n            pkg_dir = os.path.join(self.package_dir, target_dirname)\n            _cleanup_dir(pkg_dir)\n            shutil.copytree(tmp_pkg.path, pkg_dir, symlinks=True)\n            return PackageItem(pkg_dir)\n\n        # otherwise, overwrite existing\n        _cleanup_dir(dst_pkg.path)\n        shutil.copytree(tmp_pkg.path, dst_pkg.path, symlinks=True)\n        return PackageItem(dst_pkg.path)\n"
  },
  {
    "path": "platformio/package/manager/_legacy.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio import fs\nfrom platformio.package.meta import PackageItem, PackageSpec\n\n\nclass PackageManagerLegacyMixin:\n    def build_legacy_spec(self, pkg_dir):\n        # find src manifest\n        src_manifest_name = \".piopkgmanager.json\"\n        src_manifest_path = None\n        for name in os.listdir(pkg_dir):\n            if not os.path.isfile(os.path.join(pkg_dir, name, src_manifest_name)):\n                continue\n            src_manifest_path = os.path.join(pkg_dir, name, src_manifest_name)\n            break\n\n        if src_manifest_path:\n            src_manifest = fs.load_json(src_manifest_path)\n            return PackageSpec(\n                name=src_manifest.get(\"name\"),\n                uri=src_manifest.get(\"url\"),\n                requirements=src_manifest.get(\"requirements\"),\n            )\n\n        # fall back to a package manifest\n        manifest = self.load_manifest(pkg_dir)\n        return PackageSpec(name=manifest.get(\"name\"))\n\n    def legacy_load_manifest(self, pkg):\n        if not isinstance(pkg, PackageItem):\n            assert os.path.isdir(pkg)\n            pkg = PackageItem(pkg)\n        manifest = self.load_manifest(pkg)\n        manifest[\"__pkg_dir\"] = pkg.path\n        for key in (\"name\", \"version\"):\n            if not manifest.get(key):\n                manifest[key] = str(getattr(pkg.metadata, key))\n        if pkg.metadata and pkg.metadata.spec and pkg.metadata.spec.external:\n            manifest[\"__src_url\"] = pkg.metadata.spec.uri\n            manifest[\"version\"] = str(pkg.metadata.version)\n        if pkg.metadata and pkg.metadata.spec.owner:\n            manifest[\"ownername\"] = pkg.metadata.spec.owner\n        return manifest\n\n    def legacy_get_installed(self):\n        return [self.legacy_load_manifest(pkg) for pkg in self.get_installed()]\n"
  },
  {
    "path": "platformio/package/manager/_registry.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport time\n\nimport click\n\nfrom platformio import util\nfrom platformio.package.exception import IncompatiblePackageError, UnknownPackageError\nfrom platformio.package.meta import PackageSpec, PackageType\nfrom platformio.package.version import cast_version_to_semver\nfrom platformio.registry.client import RegistryClient\nfrom platformio.registry.mirror import RegistryFileMirrorIterator\n\n\nclass PackageManagerRegistryMixin:\n    def install_from_registry(self, spec, search_qualifiers=None):\n        package = version = None\n        if spec.owner and spec.name and not search_qualifiers:\n            package = self.fetch_registry_package(spec)\n            if not package:\n                raise UnknownPackageError(spec.humanize())\n            version = self.pick_best_registry_version(package[\"versions\"], spec)\n        elif spec.id or spec.name:\n            packages = self.search_registry_packages(spec, search_qualifiers)\n            if not packages:\n                raise UnknownPackageError(spec.humanize())\n            if len(packages) > 1:\n                self.print_multi_package_issue(self.log.warning, packages, spec)\n            package, version = self.find_best_registry_version(packages, spec)\n\n        if not package or not version:\n            raise UnknownPackageError(spec.humanize())\n\n        pkgfile = self.pick_compatible_pkg_file(version[\"files\"]) if version else None\n        if not pkgfile:\n            if self.pkg_type == PackageType.TOOL:\n                raise IncompatiblePackageError(spec.humanize(), util.get_systype())\n            raise UnknownPackageError(spec.humanize())\n\n        for url, checksum in RegistryFileMirrorIterator(pkgfile[\"download_url\"]):\n            try:\n                return self.install_from_uri(\n                    url,\n                    PackageSpec(\n                        owner=package[\"owner\"][\"username\"],\n                        id=package[\"id\"],\n                        name=package[\"name\"],\n                    ),\n                    checksum or pkgfile[\"checksum\"][\"sha256\"],\n                )\n            except Exception as exc:  # pylint: disable=broad-except\n                self.log.warning(\n                    click.style(\"Warning! Package Mirror: %s\" % exc, fg=\"yellow\")\n                )\n                self.log.warning(\n                    click.style(\"Looking for another mirror...\", fg=\"yellow\")\n                )\n\n        return None\n\n    def get_registry_client_instance(self):\n        if not self._registry_client:\n            self._registry_client = RegistryClient()\n        return self._registry_client\n\n    def search_registry_packages(self, spec, qualifiers=None):\n        assert isinstance(spec, PackageSpec)\n        qualifiers = qualifiers or {}\n        if spec.id:\n            qualifiers[\"ids\"] = str(spec.id)\n        else:\n            qualifiers[\"types\"] = self.pkg_type\n            qualifiers[\"names\"] = spec.name.lower()\n            if spec.owner:\n                qualifiers[\"owners\"] = spec.owner.lower()\n        return self.get_registry_client_instance().list_packages(qualifiers=qualifiers)[\n            \"items\"\n        ]\n\n    def fetch_registry_package(self, spec):\n        assert isinstance(spec, PackageSpec)\n        result = None\n        regclient = self.get_registry_client_instance()\n        if spec.owner and spec.name:\n            result = regclient.get_package(self.pkg_type, spec.owner, spec.name)\n        if not result and (spec.id or (spec.name and not spec.owner)):\n            packages = self.search_registry_packages(spec)\n            if packages:\n                result = regclient.get_package(\n                    self.pkg_type, packages[0][\"owner\"][\"username\"], packages[0][\"name\"]\n                )\n        if not result:\n            raise UnknownPackageError(spec.humanize())\n        return result\n\n    def reveal_registry_package_id(self, spec):\n        spec = self.ensure_spec(spec)\n        if spec.id:\n            return spec.id\n        packages = self.search_registry_packages(spec)\n        if not packages:\n            raise UnknownPackageError(spec.humanize())\n        if len(packages) > 1:\n            self.print_multi_package_issue(self.log.warning, packages, spec)\n            self.log.info(\"\")\n        return packages[0][\"id\"]\n\n    @staticmethod\n    def print_multi_package_issue(print_func, packages, spec):\n        print_func(\n            click.style(\n                \"Warning! More than one package has been found by \", fg=\"yellow\"\n            )\n            + click.style(spec.humanize(), fg=\"cyan\")\n            + click.style(\" requirements:\", fg=\"yellow\")\n        )\n\n        for item in packages:\n            print_func(\n                \" - {owner}/{name}@{version}\".format(\n                    owner=click.style(item[\"owner\"][\"username\"], fg=\"cyan\"),\n                    name=item[\"name\"],\n                    version=item[\"version\"][\"name\"],\n                )\n            )\n        print_func(\n            click.style(\n                \"Please specify detailed REQUIREMENTS using package owner and version \"\n                \"(shown above) to avoid name conflicts\",\n                fg=\"yellow\",\n            )\n        )\n\n    def find_best_registry_version(self, packages, spec):\n        for package in packages:\n            # find compatible version within the latest package versions\n            version = self.pick_best_registry_version([package[\"version\"]], spec)\n            if version:\n                return (package, version)\n\n            # if the custom version requirements, check ALL package versions\n            version = self.pick_best_registry_version(\n                self.fetch_registry_package(\n                    PackageSpec(\n                        id=package[\"id\"],\n                        owner=package[\"owner\"][\"username\"],\n                        name=package[\"name\"],\n                    )\n                ).get(\"versions\"),\n                spec,\n            )\n            if version:\n                return (package, version)\n            time.sleep(1)\n        return (None, None)\n\n    def get_compatible_registry_versions(self, versions, spec=None, custom_system=None):\n        assert not spec or isinstance(spec, PackageSpec)\n        result = []\n        for version in versions:\n            semver = cast_version_to_semver(version[\"name\"])\n            if spec and spec.requirements and semver not in spec.requirements:\n                continue\n            if not any(\n                self.is_system_compatible(f.get(\"system\"), custom_system=custom_system)\n                for f in version[\"files\"]\n            ):\n                continue\n            result.append(version)\n        return result\n\n    def pick_best_registry_version(self, versions, spec=None, custom_system=None):\n        best = None\n        for version in self.get_compatible_registry_versions(\n            versions, spec, custom_system\n        ):\n            semver = cast_version_to_semver(version[\"name\"])\n            if not best or (semver > cast_version_to_semver(best[\"name\"])):\n                best = version\n        return best\n\n    def pick_compatible_pkg_file(self, version_files, custom_system=None):\n        for item in version_files:\n            if self.is_system_compatible(\n                item.get(\"system\"), custom_system=custom_system\n            ):\n                return item\n        return None\n"
  },
  {
    "path": "platformio/package/manager/_symlink.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\n\nfrom platformio import fs\nfrom platformio.package.exception import PackageException\nfrom platformio.package.meta import PackageItem, PackageSpec\n\n\nclass PackageManagerSymlinkMixin:\n    @staticmethod\n    def is_symlink(path):\n        return path and path.endswith(\".pio-link\") and os.path.isfile(path)\n\n    @classmethod\n    def resolve_symlink(cls, path):\n        assert cls.is_symlink(path)\n        data = fs.load_json(path)\n        spec = PackageSpec(**data[\"spec\"])\n        assert spec.symlink\n        pkg_dir = spec.uri[10:]\n        if not os.path.isabs(pkg_dir):\n            pkg_dir = os.path.normpath(os.path.join(data[\"cwd\"], pkg_dir))\n        return (pkg_dir if os.path.isdir(pkg_dir) else None, spec)\n\n    def get_symlinked_package(self, path):\n        pkg_dir, spec = self.resolve_symlink(path)\n        if not pkg_dir:\n            return None\n        pkg = PackageItem(os.path.realpath(pkg_dir))\n        if not pkg.metadata:\n            pkg.metadata = self.build_metadata(pkg.path, spec)\n        return pkg\n\n    def install_symlink(self, spec):\n        assert spec.symlink\n        pkg_dir = spec.uri[10:]\n        if not os.path.isdir(pkg_dir):\n            raise PackageException(\n                f\"Can not create a symbolic link for `{pkg_dir}`, not a directory\"\n            )\n        link_path = os.path.join(\n            self.package_dir,\n            \"%s.pio-link\" % (spec.name or os.path.basename(os.path.abspath(pkg_dir))),\n        )\n        with open(link_path, mode=\"w\", encoding=\"utf-8\") as fp:\n            json.dump(dict(cwd=os.getcwd(), spec=spec.as_dict()), fp)\n        return self.get_symlinked_package(link_path)\n\n    def uninstall_symlink(self, spec):\n        assert spec.symlink\n        for name in os.listdir(self.package_dir):\n            path = os.path.join(self.package_dir, name)\n            if not self.is_symlink(path):\n                continue\n            pkg = self.get_symlinked_package(path)\n            if pkg.metadata.spec.uri == spec.uri:\n                os.remove(path)\n"
  },
  {
    "path": "platformio/package/manager/_uninstall.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.meta import PackageItem, PackageSpec\n\n\nclass PackageManagerUninstallMixin:\n    def uninstall(self, spec, skip_dependencies=False):\n        try:\n            self.lock()\n            return self._uninstall(spec, skip_dependencies)\n        finally:\n            self.unlock()\n\n    def _uninstall(self, spec, skip_dependencies=False):\n        pkg = self.get_package(spec)\n        if not pkg or not pkg.metadata:\n            raise UnknownPackageError(spec)\n\n        uninstalled_pkgs = self.memcache_get(\"__uninstalled_pkgs\", [])\n        if uninstalled_pkgs and pkg.path in uninstalled_pkgs:\n            return pkg\n        uninstalled_pkgs.append(pkg.path)\n        self.memcache_set(\"__uninstalled_pkgs\", uninstalled_pkgs)\n\n        self.log.info(\n            \"Removing %s @ %s\"\n            % (click.style(pkg.metadata.name, fg=\"cyan\"), pkg.metadata.version)\n        )\n\n        self.call_pkg_script(pkg, \"preuninstall\")\n\n        # firstly, remove dependencies\n        if not skip_dependencies:\n            self.uninstall_dependencies(pkg)\n\n        if pkg.metadata.spec.symlink:\n            self.uninstall_symlink(pkg.metadata.spec)\n        elif os.path.islink(pkg.path):\n            os.unlink(pkg.path)\n        else:\n            fs.rmtree(pkg.path)\n        self.memcache_reset()\n\n        # unfix detached-package with the same name\n        detached_pkg = self.get_package(PackageSpec(name=pkg.metadata.name))\n        if (\n            detached_pkg\n            and \"@\" in detached_pkg.path\n            and not os.path.isdir(\n                os.path.join(self.package_dir, detached_pkg.get_safe_dirname())\n            )\n        ):\n            shutil.move(\n                detached_pkg.path,\n                os.path.join(self.package_dir, detached_pkg.get_safe_dirname()),\n            )\n            self.memcache_reset()\n\n        self.log.info(\n            click.style(\n                \"{name}@{version} has been removed!\".format(**pkg.metadata.as_dict()),\n                fg=\"green\",\n            )\n        )\n\n        return pkg\n\n    def uninstall_dependencies(self, pkg):\n        assert isinstance(pkg, PackageItem)\n        dependencies = self.get_pkg_dependencies(pkg)\n        if not dependencies:\n            return\n        self.log.info(\"Removing dependencies...\")\n        for dependency in dependencies:\n            pkg = self.get_package(self.dependency_to_spec(dependency))\n            if not pkg:\n                continue\n            self._uninstall(pkg)\n"
  },
  {
    "path": "platformio/package/manager/_update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\n\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.meta import PackageItem, PackageOutdatedResult, PackageSpec\nfrom platformio.package.vcsclient import VCSBaseException, VCSClientFactory\n\n\nclass PackageManagerUpdateMixin:\n    def outdated(self, pkg, spec=None):\n        assert isinstance(pkg, PackageItem)\n        assert pkg.metadata\n\n        if spec and not isinstance(spec, PackageSpec):\n            spec = PackageSpec(spec)\n\n        if not os.path.isdir(pkg.path):\n            return PackageOutdatedResult(current=pkg.metadata.version)\n\n        # skip detached package to a specific version\n        detached_conditions = [\n            \"@\" in pkg.path,\n            pkg.metadata.spec and not pkg.metadata.spec.external,\n            not spec,\n        ]\n        if all(detached_conditions):\n            return PackageOutdatedResult(current=pkg.metadata.version, detached=True)\n\n        latest = None\n        wanted = None\n        if pkg.metadata.spec.external:\n            latest = self._fetch_vcs_latest_version(pkg)\n        else:\n            try:\n                reg_pkg = self.fetch_registry_package(pkg.metadata.spec)\n                latest = (\n                    self.pick_best_registry_version(reg_pkg[\"versions\"]) or {}\n                ).get(\"name\")\n                if spec:\n                    wanted = (\n                        self.pick_best_registry_version(reg_pkg[\"versions\"], spec) or {}\n                    ).get(\"name\")\n                    if not wanted:  # wrong library\n                        latest = None\n            except UnknownPackageError:\n                pass\n\n        return PackageOutdatedResult(\n            current=pkg.metadata.version, latest=latest, wanted=wanted\n        )\n\n    def _fetch_vcs_latest_version(self, pkg):\n        vcs = None\n        try:\n            vcs = VCSClientFactory.new(pkg.path, pkg.metadata.spec.uri, silent=True)\n        except VCSBaseException:\n            return None\n        if not vcs.can_be_updated:\n            return None\n\n        vcs_revision = vcs.get_latest_revision()\n        if not vcs_revision:\n            return None\n\n        return str(\n            self.build_metadata(\n                pkg.path, pkg.metadata.spec, vcs_revision=vcs_revision\n            ).version\n        )\n\n    def update(\n        self,\n        from_spec,\n        to_spec=None,\n        skip_dependencies=False,\n    ):\n        pkg = self.get_package(from_spec)\n        if not pkg or not pkg.metadata:\n            raise UnknownPackageError(from_spec)\n\n        outdated = self.outdated(pkg, to_spec)\n        if not outdated.is_outdated(allow_incompatible=False):\n            self.log.debug(\n                click.style(\n                    \"{name}@{version} is already up-to-date\".format(\n                        **pkg.metadata.as_dict()\n                    ),\n                    fg=\"yellow\",\n                )\n            )\n            return pkg\n\n        self.log.info(\n            \"Updating %s @ %s\"\n            % (click.style(pkg.metadata.name, fg=\"cyan\"), pkg.metadata.version)\n        )\n        try:\n            self.lock()\n            return self._update(pkg, outdated, skip_dependencies)\n        finally:\n            self.unlock()\n\n    def _update(self, pkg, outdated, skip_dependencies=False):\n        if pkg.metadata.spec.external:\n            vcs = VCSClientFactory.new(pkg.path, pkg.metadata.spec.uri)\n            assert vcs.update()\n            pkg.metadata.version = self._fetch_vcs_latest_version(pkg)\n            pkg.dump_meta()\n            return pkg\n\n        # uninstall existing version\n        self.uninstall(pkg, skip_dependencies=True)\n\n        return self.install(\n            PackageSpec(\n                id=pkg.metadata.spec.id,\n                owner=pkg.metadata.spec.owner,\n                name=pkg.metadata.spec.name,\n                requirements=outdated.wanted or outdated.latest,\n            ),\n            skip_dependencies=skip_dependencies,\n        )\n"
  },
  {
    "path": "platformio/package/manager/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport logging\nimport os\nimport subprocess\nfrom datetime import datetime\n\nimport click\nimport semantic_version\n\nfrom platformio import fs, util\nfrom platformio.cli import PlatformioCLI\nfrom platformio.compat import ci_strings_are_equal\nfrom platformio.package.exception import ManifestException, MissingPackageManifestError\nfrom platformio.package.lockfile import LockFile\nfrom platformio.package.manager._download import PackageManagerDownloadMixin\nfrom platformio.package.manager._install import PackageManagerInstallMixin\nfrom platformio.package.manager._legacy import PackageManagerLegacyMixin\nfrom platformio.package.manager._registry import PackageManagerRegistryMixin\nfrom platformio.package.manager._symlink import PackageManagerSymlinkMixin\nfrom platformio.package.manager._uninstall import PackageManagerUninstallMixin\nfrom platformio.package.manager._update import PackageManagerUpdateMixin\nfrom platformio.package.manifest.parser import ManifestParserFactory\nfrom platformio.package.meta import (\n    PackageItem,\n    PackageMetadata,\n    PackageSpec,\n    PackageType,\n)\nfrom platformio.proc import get_pythonexe_path\nfrom platformio.project.helpers import get_project_cache_dir\n\n\nclass ClickLoggingHandler(logging.Handler):\n    def emit(self, record):\n        click.echo(self.format(record))\n\n\nclass BasePackageManager(  # pylint: disable=too-many-public-methods,too-many-instance-attributes\n    PackageManagerDownloadMixin,\n    PackageManagerRegistryMixin,\n    PackageManagerSymlinkMixin,\n    PackageManagerInstallMixin,\n    PackageManagerUninstallMixin,\n    PackageManagerUpdateMixin,\n    PackageManagerLegacyMixin,\n):\n    _MEMORY_CACHE = {}\n\n    def __init__(self, pkg_type, package_dir, compatibility=None):\n        self.pkg_type = pkg_type\n        self.package_dir = package_dir\n        self.compatibility = compatibility\n        self.log = self._setup_logger()\n\n        self._MEMORY_CACHE = {}\n        self._lockfile = None\n        self._download_dir = None\n        self._tmp_dir = None\n        self._registry_client = None\n\n    def __repr__(self):\n        return (\n            f\"{self.__class__.__name__} <type={self.pkg_type} \"\n            f\"package_dir={self.package_dir}>\"\n        )\n\n    def _setup_logger(self):\n        logger = logging.getLogger(str(self.__class__.__name__).replace(\"Package\", \" \"))\n        logger.setLevel(logging.INFO)\n        formatter = logging.Formatter(\"%(name)s: %(message)s\")\n        sh = ClickLoggingHandler()\n        sh.setFormatter(formatter)\n        logger.handlers.clear()\n        logger.addHandler(sh)\n        return logger\n\n    def set_log_level(self, level):\n        self.log.setLevel(level)\n\n    def lock(self):\n        if self._lockfile:\n            return\n        self.ensure_dir_exists(os.path.dirname(self.package_dir))\n        self._lockfile = LockFile(self.package_dir)\n        self.ensure_dir_exists(self.package_dir)\n        self._lockfile.acquire()\n\n    def unlock(self):\n        if hasattr(self, \"_lockfile\") and self._lockfile:\n            self._lockfile.release()\n            self._lockfile = None\n\n    def __del__(self):\n        self.unlock()\n\n    def memcache_get(self, key, default=None):\n        return self._MEMORY_CACHE.get(key, default)\n\n    def memcache_set(self, key, value):\n        self._MEMORY_CACHE[key] = value\n\n    def memcache_reset(self):\n        self._MEMORY_CACHE.clear()\n\n    @staticmethod\n    def is_system_compatible(value, custom_system=None):\n        if not value or \"*\" in value:\n            return True\n        return util.items_in_list(value, custom_system or util.get_systype())\n\n    @staticmethod\n    def ensure_dir_exists(path):\n        if not os.path.isdir(path):\n            os.makedirs(path)\n        assert os.path.isdir(path)\n        return path\n\n    @staticmethod\n    def ensure_spec(spec):\n        return spec if isinstance(spec, PackageSpec) else PackageSpec(spec)\n\n    @property\n    def manifest_names(self):\n        raise NotImplementedError\n\n    def get_download_dir(self):\n        if not self._download_dir:\n            self._download_dir = self.ensure_dir_exists(\n                os.path.join(get_project_cache_dir(), \"downloads\")\n            )\n        return self._download_dir\n\n    def get_tmp_dir(self):\n        if not self._tmp_dir:\n            self._tmp_dir = self.ensure_dir_exists(\n                os.path.join(get_project_cache_dir(), \"tmp\")\n            )\n        return self._tmp_dir\n\n    def find_pkg_root(self, path, spec):  # pylint: disable=unused-argument\n        if self.manifest_exists(path):\n            return path\n        for root, _, _ in os.walk(path):\n            if self.manifest_exists(root):\n                return root\n        raise MissingPackageManifestError(\", \".join(self.manifest_names))\n\n    def get_manifest_path(self, pkg_dir):\n        if not os.path.isdir(pkg_dir):\n            return None\n        for name in self.manifest_names:\n            manifest_path = os.path.join(pkg_dir, name)\n            if os.path.isfile(manifest_path):\n                return manifest_path\n        return None\n\n    def manifest_exists(self, pkg_dir):\n        return self.get_manifest_path(pkg_dir)\n\n    def load_manifest(self, src):\n        path = src.path if isinstance(src, PackageItem) else src\n        cache_key = \"load_manifest-%s\" % path\n        result = self.memcache_get(cache_key)\n        if result:\n            return result\n        candidates = (\n            [os.path.join(path, name) for name in self.manifest_names]\n            if os.path.isdir(path)\n            else [path]\n        )\n        for item in candidates:\n            if not os.path.isfile(item):\n                continue\n            try:\n                result = ManifestParserFactory.new_from_file(item).as_dict()\n                self.memcache_set(cache_key, result)\n                return result\n            except ManifestException as exc:\n                if not PlatformioCLI.in_silence():\n                    self.log.warning(click.style(str(exc), fg=\"yellow\"))\n        raise MissingPackageManifestError(\", \".join(self.manifest_names))\n\n    @staticmethod\n    def generate_rand_version():\n        return datetime.now().strftime(\"0.0.0+%Y%m%d%H%M%S\")\n\n    def build_metadata(self, pkg_dir, spec, vcs_revision=None):\n        manifest = self.load_manifest(pkg_dir)\n        metadata = PackageMetadata(\n            type=self.pkg_type,\n            name=manifest.get(\"name\"),\n            version=manifest.get(\"version\"),\n            spec=spec,\n        )\n        if not metadata.name or spec.has_custom_name():\n            metadata.name = spec.name\n        if vcs_revision:\n            metadata.version = \"%s+sha.%s\" % (\n                metadata.version if metadata.version else \"0.0.0\",\n                vcs_revision,\n            )\n        if not metadata.version:\n            metadata.version = self.generate_rand_version()\n        return metadata\n\n    def get_installed(self):  # pylint: disable=too-many-branches\n        if not os.path.isdir(self.package_dir):\n            return []\n\n        cache_key = \"get_installed\"\n        if self.memcache_get(cache_key):\n            return self.memcache_get(cache_key)\n\n        result = []\n        for name in sorted(os.listdir(self.package_dir)):\n            if name.startswith(\"_tmp_installing\"):  # legacy tmp folder\n                continue\n            pkg = None\n            path = os.path.join(self.package_dir, name)\n            if os.path.isdir(path):\n                pkg = PackageItem(path)\n            elif self.is_symlink(path):\n                pkg = self.get_symlinked_package(path)\n            if not pkg:\n                continue\n            if not pkg.metadata:\n                try:\n                    spec = self.build_legacy_spec(pkg.path)\n                    pkg.metadata = self.build_metadata(pkg.path, spec)\n                except MissingPackageManifestError:\n                    pass\n            if not pkg.metadata:\n                continue\n            if self.pkg_type == PackageType.TOOL:\n                try:\n                    if not self.is_system_compatible(\n                        self.load_manifest(pkg).get(\"system\")\n                    ):\n                        continue\n                except MissingPackageManifestError:\n                    pass\n            result.append(pkg)\n\n        self.memcache_set(cache_key, result)\n        return result\n\n    def get_package(self, spec):\n        if isinstance(spec, PackageItem):\n            return spec\n        spec = self.ensure_spec(spec)\n        best = None\n        for pkg in self.get_installed():\n            if not self.test_pkg_spec(pkg, spec):\n                continue\n            assert isinstance(pkg.metadata.version, semantic_version.Version)\n            if spec.requirements and pkg.metadata.version not in spec.requirements:\n                continue\n            if not best or (pkg.metadata.version > best.metadata.version):\n                best = pkg\n        return best\n\n    @staticmethod\n    def test_pkg_spec(pkg, spec):\n        # \"id\" mismatch\n        if spec.id and spec.id != pkg.metadata.spec.id:\n            return False\n\n        # external \"URL\" mismatch\n        if spec.external:\n            # local/symlinked folder mismatch\n            check_conds = [\n                os.path.abspath(spec.uri) == os.path.abspath(pkg.path),\n                spec.uri.startswith(\"file://\")\n                and os.path.abspath(pkg.path) == os.path.abspath(spec.uri[7:]),\n                spec.uri.startswith(\"symlink://\")\n                and os.path.abspath(pkg.path) == os.path.abspath(spec.uri[10:]),\n            ]\n            if any(check_conds):\n                return True\n            if spec.uri != pkg.metadata.spec.uri:\n                return False\n\n        # \"owner\" mismatch\n        elif spec.owner and not ci_strings_are_equal(\n            spec.owner, pkg.metadata.spec.owner\n        ):\n            return False\n\n        # \"name\" mismatch\n        elif not spec.id and not ci_strings_are_equal(spec.name, pkg.metadata.name):\n            return False\n\n        return True\n\n    def get_pkg_dependencies(self, pkg):\n        return self.load_manifest(pkg).get(\"dependencies\")\n\n    @staticmethod\n    def dependency_to_spec(dependency):\n        return PackageSpec(\n            owner=dependency.get(\"owner\"),\n            name=dependency.get(\"name\"),\n            requirements=dependency.get(\"version\"),\n        )\n\n    def call_pkg_script(self, pkg, event):\n        manifest = None\n        try:\n            manifest = self.load_manifest(pkg)\n        except MissingPackageManifestError:\n            pass\n        scripts = (manifest or {}).get(\"scripts\")\n        if not scripts or not isinstance(scripts, dict):\n            return\n        cmd = scripts.get(event)\n        if not cmd:\n            return\n        shell = False\n        if not isinstance(cmd, list):\n            # issue #5366: workaround when command passed as string without spaces\n            if \" \" in cmd:\n                shell = True\n            cmd = [cmd]\n        os.environ[\"PIO_PYTHON_EXE\"] = get_pythonexe_path()\n        with fs.cd(pkg.path):\n            if os.path.isfile(cmd[0]) and cmd[0].endswith(\".py\"):\n                cmd = [os.environ[\"PIO_PYTHON_EXE\"]] + cmd\n            subprocess.run(\n                \" \".join(cmd) if shell else cmd,\n                cwd=pkg.path,\n                shell=shell,\n                env=os.environ,\n                check=True,\n            )\n"
  },
  {
    "path": "platformio/package/manager/core.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio import exception\nfrom platformio.dependencies import get_core_dependencies\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\n\n\ndef get_installed_core_packages():\n    result = []\n    pm = ToolPackageManager()\n    for name, requirements in get_core_dependencies().items():\n        spec = PackageSpec(owner=\"platformio\", name=name, requirements=requirements)\n        pkg = pm.get_package(spec)\n        if pkg:\n            result.append(pkg)\n    return result\n\n\ndef get_core_package_dir(name, spec=None, auto_install=True):\n    if name not in get_core_dependencies():\n        raise exception.PlatformioException(\"Please upgrade PlatformIO Core\")\n    pm = ToolPackageManager()\n    spec = spec or PackageSpec(\n        owner=\"platformio\", name=name, requirements=get_core_dependencies()[name]\n    )\n    pkg = pm.get_package(spec)\n    if pkg:\n        return pkg.path\n    if not auto_install:\n        return None\n    assert pm.install(spec)\n    remove_unnecessary_core_packages()\n    return pm.get_package(spec).path\n\n\ndef update_core_packages():\n    pm = ToolPackageManager()\n    for name, requirements in get_core_dependencies().items():\n        spec = PackageSpec(owner=\"platformio\", name=name, requirements=requirements)\n        try:\n            pm.update(spec, spec)\n        except UnknownPackageError:\n            pass\n    remove_unnecessary_core_packages()\n    return True\n\n\ndef remove_unnecessary_core_packages(dry_run=False):\n    candidates = []\n    pm = ToolPackageManager()\n    best_pkg_versions = {}\n\n    for name, requirements in get_core_dependencies().items():\n        spec = PackageSpec(owner=\"platformio\", name=name, requirements=requirements)\n        pkg = pm.get_package(spec)\n        if not pkg:\n            continue\n        # pylint: disable=no-member\n        best_pkg_versions[pkg.metadata.name] = pkg.metadata.version\n\n    for pkg in pm.get_installed():\n        skip_conds = [\n            os.path.isfile(os.path.join(pkg.path, \".piokeep\")),\n            pkg.metadata.spec.owner != \"platformio\",\n            pkg.metadata.name not in best_pkg_versions,\n            pkg.metadata.name in best_pkg_versions\n            and pkg.metadata.version == best_pkg_versions[pkg.metadata.name],\n        ]\n        if not any(skip_conds):\n            candidates.append(pkg)\n\n    if dry_run:\n        return candidates\n\n    for pkg in candidates:\n        pm.uninstall(pkg)\n\n    return candidates\n"
  },
  {
    "path": "platformio/package/manager/library.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\n\nfrom platformio import util\nfrom platformio.package.exception import MissingPackageManifestError\nfrom platformio.package.manager.base import BasePackageManager\nfrom platformio.package.meta import PackageSpec, PackageType\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\n\n\nclass LibraryPackageManager(BasePackageManager):  # pylint: disable=too-many-ancestors\n    def __init__(self, package_dir=None, **kwargs):\n        super().__init__(\n            PackageType.LIBRARY,\n            package_dir\n            or ProjectConfig.get_instance().get(\"platformio\", \"globallib_dir\"),\n            **kwargs\n        )\n\n    @property\n    def manifest_names(self):\n        return PackageType.get_manifest_map()[PackageType.LIBRARY]\n\n    def find_pkg_root(self, path, spec):\n        try:\n            return super().find_pkg_root(path, spec)\n        except MissingPackageManifestError:\n            pass\n        assert isinstance(spec, PackageSpec)\n\n        root_dir = self.find_library_root(path)\n\n        # automatically generate library manifest\n        with open(\n            os.path.join(root_dir, \"library.json\"), mode=\"w\", encoding=\"utf8\"\n        ) as fp:\n            json.dump(\n                dict(\n                    name=spec.name,\n                    version=self.generate_rand_version(),\n                ),\n                fp,\n                indent=2,\n            )\n\n        return root_dir\n\n    @staticmethod\n    def find_library_root(path):\n        root_dir_signs = set([\"include\", \"Include\", \"inc\", \"Inc\", \"src\", \"Src\"])\n        root_file_signs = set(\n            [\n                \"conanfile.py\",  # Conan-based library\n                \"CMakeLists.txt\",  # CMake-based library\n            ]\n        )\n        for root, dirs, files in os.walk(path):\n            if not files and len(dirs) == 1:\n                continue\n            if set(root_dir_signs) & set(dirs):\n                return root\n            if set(root_file_signs) & set(files):\n                return root\n            for fname in files:\n                if fname.endswith((\".c\", \".cpp\", \".h\", \".hpp\", \".S\")):\n                    return root\n        return path\n\n    def install_dependency(self, dependency):\n        spec = self.dependency_to_spec(dependency)\n        # skip built-in dependencies\n        not_builtin_conds = [spec.external, spec.owner]\n        if not any(not_builtin_conds):\n            not_builtin_conds.append(not self.is_builtin_lib(spec.name))\n        if any(not_builtin_conds):\n            return super().install_dependency(dependency)\n        return None\n\n    @staticmethod\n    @util.memoized(expire=\"60s\")\n    def get_builtin_libs(storage_names=None):\n        # pylint: disable=import-outside-toplevel\n        from platformio.package.manager.platform import PlatformPackageManager\n\n        items = []\n        storage_names = storage_names or []\n        pm = PlatformPackageManager()\n        for pkg in pm.get_installed():\n            p = PlatformFactory.new(pkg)\n            for storage in p.get_lib_storages():\n                if storage_names and storage[\"name\"] not in storage_names:\n                    continue\n                lm = LibraryPackageManager(storage[\"path\"])\n                items.append(\n                    {\n                        \"name\": storage[\"name\"],\n                        \"path\": storage[\"path\"],\n                        \"items\": lm.legacy_get_installed(),\n                    }\n                )\n        return items\n\n    @classmethod\n    def is_builtin_lib(cls, name):\n        for storage in cls.get_builtin_libs():\n            for lib in storage[\"items\"]:\n                if lib.get(\"name\") == name:\n                    return True\n        return False\n"
  },
  {
    "path": "platformio/package/manager/platform.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio import util\nfrom platformio.http import HTTPClientError, InternetConnectionError\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.manager.base import BasePackageManager\nfrom platformio.package.manager.core import get_installed_core_packages\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageType\nfrom platformio.platform.exception import IncompatiblePlatform, UnknownBoard\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\n\n\nclass PlatformPackageManager(BasePackageManager):  # pylint: disable=too-many-ancestors\n    def __init__(self, package_dir=None):\n        self.config = ProjectConfig.get_instance()\n        super().__init__(\n            PackageType.PLATFORM,\n            package_dir or self.config.get(\"platformio\", \"platforms_dir\"),\n        )\n\n    @property\n    def manifest_names(self):\n        return PackageType.get_manifest_map()[PackageType.PLATFORM]\n\n    def install(  # pylint: disable=arguments-differ,too-many-arguments,too-many-positional-arguments\n        self,\n        spec,\n        skip_dependencies=False,\n        force=False,\n        project_env=None,\n        project_targets=None,\n    ):\n        already_installed = self.get_package(spec)\n        pkg = super().install(spec, force=force, skip_dependencies=True)\n        try:\n            p = PlatformFactory.new(pkg)\n            # set logging level for underlying tool manager\n            p.pm.set_log_level(self.log.getEffectiveLevel())\n            p.ensure_engine_compatible()\n        except IncompatiblePlatform as exc:\n            super().uninstall(pkg, skip_dependencies=True)\n            raise exc\n        if project_env:\n            p.configure_project_packages(project_env, project_targets)\n        if not skip_dependencies:\n            p.install_required_packages(force=force)\n        if not already_installed:\n            p.on_installed()\n        return pkg\n\n    def uninstall(  # pylint: disable=arguments-differ\n        self, spec, skip_dependencies=False, project_env=None\n    ):\n        pkg = self.get_package(spec)\n        if not pkg or not pkg.metadata:\n            raise UnknownPackageError(spec)\n        p = PlatformFactory.new(pkg)\n        # set logging level for underlying tool manager\n        p.pm.set_log_level(self.log.getEffectiveLevel())\n        if project_env:\n            p.configure_project_packages(project_env)\n        if not skip_dependencies:\n            p.uninstall_packages()\n        assert super().uninstall(pkg, skip_dependencies=True)\n        p.on_uninstalled()\n        return pkg\n\n    def update(  # pylint: disable=arguments-differ\n        self,\n        from_spec,\n        to_spec=None,\n        skip_dependencies=False,\n        project_env=None,\n    ):\n        pkg = self.get_package(from_spec)\n        if not pkg or not pkg.metadata:\n            raise UnknownPackageError(from_spec)\n        pkg = super().update(\n            from_spec,\n            to_spec,\n        )\n        p = PlatformFactory.new(pkg)\n        # set logging level for underlying tool manager\n        p.pm.set_log_level(self.log.getEffectiveLevel())\n        if project_env:\n            p.configure_project_packages(project_env)\n        if not skip_dependencies:\n            p.update_packages()\n        return pkg\n\n    @util.memoized(expire=\"5s\")\n    def get_installed_boards(self):\n        boards = []\n        for pkg in self.get_installed():\n            p = PlatformFactory.new(pkg)\n            for config in p.get_boards().values():\n                board = config.get_brief_data()\n                if board not in boards:\n                    boards.append(board)\n        return boards\n\n    def get_registered_boards(self):\n        return self.get_registry_client_instance().fetch_json_data(\n            \"get\", \"/v2/boards\", x_cache_valid=\"1d\"\n        )\n\n    def get_all_boards(self):\n        boards = self.get_installed_boards()\n        know_boards = [\"%s:%s\" % (b[\"platform\"], b[\"id\"]) for b in boards]\n        try:\n            for board in self.get_registered_boards():\n                key = \"%s:%s\" % (board[\"platform\"], board[\"id\"])\n                if key not in know_boards:\n                    boards.append(board)\n        except (HTTPClientError, InternetConnectionError):\n            pass\n        return sorted(boards, key=lambda b: b[\"name\"])\n\n    def board_config(self, id_, platform=None):\n        for manifest in self.get_installed_boards():\n            if manifest[\"id\"] == id_ and (\n                not platform or manifest[\"platform\"] == platform\n            ):\n                return manifest\n        for manifest in self.get_registered_boards():\n            if manifest[\"id\"] == id_ and (\n                not platform or manifest[\"platform\"] == platform\n            ):\n                return manifest\n        raise UnknownBoard(id_)\n\n\n#\n# Helpers\n#\n\n\ndef remove_unnecessary_platform_packages(dry_run=False):\n    candidates = []\n    required = set()\n    core_packages = get_installed_core_packages()\n    for platform in PlatformPackageManager().get_installed():\n        p = PlatformFactory.new(platform)\n        for pkg in p.get_installed_packages(with_optional_versions=True):\n            required.add(pkg)\n\n    pm = ToolPackageManager()\n    for pkg in pm.get_installed():\n        skip_conds = [\n            pkg.metadata.spec.uri,\n            os.path.isfile(os.path.join(pkg.path, \".piokeep\")),\n            pkg in required,\n            pkg in core_packages,\n        ]\n        if not any(skip_conds):\n            candidates.append(pkg)\n\n    if dry_run:\n        return candidates\n\n    for pkg in candidates:\n        pm.uninstall(pkg)\n\n    return candidates\n"
  },
  {
    "path": "platformio/package/manager/tool.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.package.manager.base import BasePackageManager\nfrom platformio.package.meta import PackageType\nfrom platformio.project.config import ProjectConfig\n\n\nclass ToolPackageManager(BasePackageManager):  # pylint: disable=too-many-ancestors\n    def __init__(self, package_dir=None):\n        super().__init__(\n            PackageType.TOOL,\n            package_dir\n            or ProjectConfig.get_instance().get(\"platformio\", \"packages_dir\"),\n        )\n\n    @property\n    def manifest_names(self):\n        return PackageType.get_manifest_map()[PackageType.TOOL]\n"
  },
  {
    "path": "platformio/package/manifest/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/package/manifest/parser.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport inspect\nimport io\nimport json\nimport os\nimport re\nimport tarfile\nfrom urllib.parse import urlparse\n\nfrom platformio import util\nfrom platformio.compat import get_object_members, string_types\nfrom platformio.http import fetch_remote_content\nfrom platformio.package.exception import ManifestParserError, UnknownManifestError\nfrom platformio.project.helpers import is_platformio_project\n\n\nclass ManifestFileType:\n    PLATFORM_JSON = \"platform.json\"\n    LIBRARY_JSON = \"library.json\"\n    LIBRARY_PROPERTIES = \"library.properties\"\n    MODULE_JSON = \"module.json\"\n    PACKAGE_JSON = \"package.json\"\n\n    @classmethod\n    def items(cls):\n        return get_object_members(cls)\n\n    @classmethod\n    def from_uri(cls, uri):\n        for t in sorted(cls.items().values()):\n            if uri.endswith(t):\n                return t\n        return None\n\n    @classmethod\n    def from_dir(cls, path):\n        for t in sorted(cls.items().values()):\n            if os.path.isfile(os.path.join(path, t)):\n                return t\n        return None\n\n\nclass ManifestParserFactory:\n    @staticmethod\n    def read_manifest_contents(path):\n        last_err = None\n        for encoding in (\"utf-8\", \"latin-1\"):\n            try:\n                with io.open(path, encoding=encoding) as fp:\n                    return fp.read()\n            except UnicodeDecodeError as exc:\n                last_err = exc\n        raise last_err\n\n    @classmethod\n    def new_from_file(cls, path, remote_url=False):\n        if not path or not os.path.isfile(path):\n            raise UnknownManifestError(\"Manifest file does not exist %s\" % path)\n        type_from_uri = ManifestFileType.from_uri(path)\n        if not type_from_uri:\n            raise UnknownManifestError(\"Unknown manifest file type %s\" % path)\n        return ManifestParserFactory.new(\n            cls.read_manifest_contents(path), type_from_uri, remote_url\n        )\n\n    @classmethod\n    def new_from_dir(cls, path, remote_url=None):\n        assert os.path.isdir(path), \"Invalid directory %s\" % path\n\n        type_from_uri = ManifestFileType.from_uri(remote_url) if remote_url else None\n        if type_from_uri and os.path.isfile(os.path.join(path, type_from_uri)):\n            return ManifestParserFactory.new(\n                cls.read_manifest_contents(os.path.join(path, type_from_uri)),\n                type_from_uri,\n                remote_url=remote_url,\n                package_dir=path,\n            )\n\n        type_from_dir = ManifestFileType.from_dir(path)\n        if not type_from_dir:\n            raise UnknownManifestError(\n                \"Unknown manifest file type in %s directory\" % path\n            )\n        return ManifestParserFactory.new(\n            cls.read_manifest_contents(os.path.join(path, type_from_dir)),\n            type_from_dir,\n            remote_url=remote_url,\n            package_dir=path,\n        )\n\n    @staticmethod\n    def new_from_url(remote_url):\n        content = fetch_remote_content(remote_url)\n        return ManifestParserFactory.new(\n            content,\n            ManifestFileType.from_uri(remote_url) or ManifestFileType.LIBRARY_JSON,\n            remote_url,\n        )\n\n    @staticmethod\n    def new_from_archive(path):\n        assert path.endswith(\"tar.gz\")\n        with tarfile.open(path, mode=\"r:gz\") as tf:\n            for t in sorted(ManifestFileType.items().values()):\n                for member in (t, \"./\" + t):\n                    try:\n                        return ManifestParserFactory.new(\n                            tf.extractfile(member).read().decode(), t\n                        )\n                    except KeyError:\n                        pass\n        raise UnknownManifestError(\"Unknown manifest file type in %s archive\" % path)\n\n    @staticmethod\n    def new(  # pylint: disable=redefined-builtin\n        contents, type, remote_url=None, package_dir=None\n    ):\n        for _, cls in globals().items():\n            if (\n                inspect.isclass(cls)\n                and issubclass(cls, BaseManifestParser)\n                and cls != BaseManifestParser\n                and cls.manifest_type == type\n            ):\n                return cls(contents, remote_url, package_dir)\n        raise UnknownManifestError(\"Unknown manifest file type %s\" % type)\n\n\nclass BaseManifestParser:\n    def __init__(self, contents, remote_url=None, package_dir=None):\n        self.remote_url = remote_url\n        self.package_dir = package_dir\n        try:\n            self._data = self.parse(contents)\n        except Exception as exc:\n            raise ManifestParserError(\"Could not parse manifest -> %s\" % exc) from exc\n\n        self._data = self.normalize_repository(self._data)\n        self._data = self.parse_examples(self._data)\n\n        # remove None fields\n        for key in list(self._data.keys()):\n            if self._data[key] is None:\n                del self._data[key]\n\n    def parse(self, contents):\n        raise NotImplementedError\n\n    def as_dict(self):\n        return self._data\n\n    @staticmethod\n    def str_to_list(value, sep=\",\", lowercase=False, unique=False):\n        if isinstance(value, string_types):\n            value = value.split(sep)\n        assert isinstance(value, list)\n        result = []\n        for item in value:\n            item = item.strip()\n            if not item:\n                continue\n            if lowercase:\n                item = item.lower()\n            if unique and item in result:\n                continue\n            result.append(item)\n        return result\n\n    @staticmethod\n    def cleanup_author(author):\n        assert isinstance(author, dict)\n        if author.get(\"email\"):\n            author[\"email\"] = re.sub(r\"\\s+[aA][tT]\\s+\", \"@\", author[\"email\"])\n            if \"@\" not in author[\"email\"]:\n                author[\"email\"] = None\n        for key in list(author.keys()):\n            if author[key] is None:\n                del author[key]\n        return author\n\n    @staticmethod\n    def parse_author_name_and_email(raw):\n        if raw == \"None\" or \"://\" in raw:\n            return (None, None)\n        name = raw\n        email = None\n        ldel = \"<\"\n        rdel = \">\"\n        if ldel in raw and rdel in raw:\n            name = raw[: raw.index(ldel)]\n            email = raw[raw.index(ldel) + 1 : raw.index(rdel)]\n        if \"(\" in name:\n            name = name.split(\"(\")[0]\n        return (name.strip(), email.strip() if email else None)\n\n    @staticmethod\n    def normalize_repository(data):\n        url = (data.get(\"repository\") or {}).get(\"url\")\n        if not url or \"://\" not in url:\n            return data\n        url_attrs = urlparse(url)\n        if url_attrs.netloc not in (\"github.com\", \"bitbucket.org\", \"gitlab.com\"):\n            return data\n        url = \"https://%s%s\" % (url_attrs.netloc, url_attrs.path)\n        if url.endswith(\"/\"):\n            url = url[:-1]\n        if not url.endswith(\".git\"):\n            url += \".git\"\n        data[\"repository\"][\"url\"] = url\n        return data\n\n    def parse_examples(self, data):\n        examples = data.get(\"examples\")\n        if (\n            not examples\n            or not isinstance(examples, list)\n            or not all(isinstance(v, dict) for v in examples)\n        ):\n            data[\"examples\"] = None\n        if not data[\"examples\"] and self.package_dir:\n            data[\"examples\"] = self.parse_examples_from_dir(self.package_dir)\n        if \"examples\" in data and not data[\"examples\"]:\n            del data[\"examples\"]\n        return data\n\n    @staticmethod\n    def parse_examples_from_dir(package_dir):\n        assert os.path.isdir(package_dir)\n        examples_dir = os.path.join(package_dir, \"examples\")\n        if not os.path.isdir(examples_dir):\n            examples_dir = os.path.join(package_dir, \"Examples\")\n            if not os.path.isdir(examples_dir):\n                return None\n\n        allowed_exts = (\n            \".c\",\n            \".cc\",\n            \".cpp\",\n            \".h\",\n            \".hpp\",\n            \".asm\",\n            \".ASM\",\n            \".s\",\n            \".S\",\n            \".ino\",\n            \".pde\",\n        )\n\n        result = {}\n        last_pio_project = None\n        for root, _, files in os.walk(examples_dir):\n            # skip hidden files, symlinks, and folders\n            files = [\n                f\n                for f in files\n                if not f.startswith(\".\") and not os.path.islink(os.path.join(root, f))\n            ]\n            if os.path.basename(root).startswith(\".\") or not files:\n                continue\n\n            if is_platformio_project(root):\n                last_pio_project = root\n                result[last_pio_project] = dict(\n                    name=os.path.relpath(root, examples_dir),\n                    base=os.path.relpath(root, package_dir),\n                    files=files,\n                )\n                continue\n            if last_pio_project:\n                if root.startswith(last_pio_project):\n                    result[last_pio_project][\"files\"].extend(\n                        [\n                            os.path.relpath(os.path.join(root, f), last_pio_project)\n                            for f in files\n                        ]\n                    )\n                    continue\n                last_pio_project = None\n\n            matched_files = [f for f in files if f.endswith(allowed_exts)]\n            if not matched_files:\n                continue\n            result[root] = dict(\n                name=(\n                    \"Examples\"\n                    if root == examples_dir\n                    else os.path.relpath(root, examples_dir)\n                ),\n                base=os.path.relpath(root, package_dir),\n                files=matched_files,\n            )\n\n        result = list(result.values())\n\n        # normalize example names\n        for item in result:\n            item[\"name\"] = item[\"name\"].replace(os.path.sep, \"/\")\n            item[\"name\"] = re.sub(r\"[^a-z\\d\\d\\-\\_/]+\", \"_\", item[\"name\"], flags=re.I)\n\n        return result or None\n\n\nclass LibraryJsonManifestParser(BaseManifestParser):\n    manifest_type = ManifestFileType.LIBRARY_JSON\n\n    def parse(self, contents):\n        data = json.loads(contents)\n        data = self._process_renamed_fields(data)\n\n        # normalize Union[str, list] fields\n        for k in (\"keywords\", \"platforms\", \"frameworks\"):\n            if k in data:\n                data[k] = self.str_to_list(\n                    data[k], sep=\",\", lowercase=True, unique=True\n                )\n\n        if \"headers\" in data:\n            data[\"headers\"] = self.str_to_list(data[\"headers\"], sep=\",\", unique=True)\n        if \"authors\" in data:\n            data[\"authors\"] = self._parse_authors(data[\"authors\"])\n        if \"platforms\" in data:\n            data[\"platforms\"] = self._fix_platforms(data[\"platforms\"]) or None\n        if \"export\" in data:\n            data[\"export\"] = self._parse_export(data[\"export\"])\n        if \"dependencies\" in data:\n            data[\"dependencies\"] = self._parse_dependencies(data[\"dependencies\"])\n\n        return data\n\n    @staticmethod\n    def _process_renamed_fields(data):\n        if \"url\" in data:\n            data[\"homepage\"] = data[\"url\"]\n            del data[\"url\"]\n\n        for key in (\"include\", \"exclude\"):\n            if key not in data:\n                continue\n            if \"export\" not in data:\n                data[\"export\"] = {}\n            data[\"export\"][key] = data[key]\n            del data[key]\n\n        return data\n\n    def _parse_authors(self, raw):\n        if not raw:\n            return None\n        # normalize Union[dict, list] fields\n        if not isinstance(raw, list):\n            raw = [raw]\n        return [self.cleanup_author(author) for author in raw]\n\n    @staticmethod\n    def _fix_platforms(items):\n        assert isinstance(items, list)\n        if \"espressif\" in items:\n            items[items.index(\"espressif\")] = \"espressif8266\"\n        return items\n\n    @staticmethod\n    def _parse_export(raw):\n        if not isinstance(raw, dict):\n            return None\n        result = {}\n        for k in (\"include\", \"exclude\"):\n            if not raw.get(k):\n                continue\n            result[k] = raw[k] if isinstance(raw[k], list) else [raw[k]]\n        return result\n\n    @staticmethod\n    def _parse_dependencies(raw):\n        # compatibility with legacy dependency format\n        if isinstance(raw, dict) and \"name\" in raw:\n            raw = [raw]\n\n        if isinstance(raw, dict):\n            result = []\n            for name, version in raw.items():\n                if \"/\" in name:\n                    owner, name = name.split(\"/\", 1)\n                    result.append(dict(owner=owner, name=name, version=version))\n                else:\n                    result.append(dict(name=name, version=version))\n            return result\n\n        if isinstance(raw, list):\n            for i, dependency in enumerate(raw):\n                if isinstance(dependency, dict):\n                    for k, v in dependency.items():\n                        if k not in (\"platforms\", \"frameworks\", \"authors\"):\n                            continue\n                        raw[i][k] = util.items_to_list(v)\n                else:\n                    raw[i] = {\"name\": dependency}\n            return raw\n        raise ManifestParserError(\n            \"Invalid dependencies format, should be list or dictionary\"\n        )\n\n\nclass ModuleJsonManifestParser(BaseManifestParser):\n    manifest_type = ManifestFileType.MODULE_JSON\n\n    def parse(self, contents):\n        data = json.loads(contents)\n        data[\"frameworks\"] = [\"mbed\"]\n        data[\"platforms\"] = [\"*\"]\n        data[\"export\"] = {\"exclude\": [\"tests\", \"test\", \"*.doxyfile\", \"*.pdf\"]}\n        if \"author\" in data:\n            data[\"authors\"] = self._parse_authors(data.get(\"author\"))\n            del data[\"author\"]\n        if \"licenses\" in data:\n            data[\"license\"] = self._parse_license(data.get(\"licenses\"))\n            del data[\"licenses\"]\n        if \"dependencies\" in data:\n            data[\"dependencies\"] = self._parse_dependencies(data[\"dependencies\"])\n        if \"keywords\" in data:\n            data[\"keywords\"] = self.str_to_list(\n                data[\"keywords\"], sep=\",\", lowercase=True, unique=True\n            )\n        return data\n\n    def _parse_authors(self, raw):\n        if not raw:\n            return None\n        result = []\n        for author in raw.split(\",\"):\n            name, email = self.parse_author_name_and_email(author)\n            if not name:\n                continue\n            result.append(self.cleanup_author(dict(name=name, email=email)))\n        return result\n\n    @staticmethod\n    def _parse_license(raw):\n        if not raw or not isinstance(raw, list):\n            return None\n        return raw[0].get(\"type\")\n\n    @staticmethod\n    def _parse_dependencies(raw):\n        if isinstance(raw, dict):\n            return [\n                dict(name=name, version=version, frameworks=[\"mbed\"])\n                for name, version in raw.items()\n            ]\n        raise ManifestParserError(\"Invalid dependencies format, should be a dictionary\")\n\n\nclass LibraryPropertiesManifestParser(BaseManifestParser):\n    manifest_type = ManifestFileType.LIBRARY_PROPERTIES\n\n    def parse(self, contents):\n        data = self._parse_properties(contents)\n        repository = self._parse_repository(data)\n        homepage = data.get(\"url\") or None\n        if repository and repository[\"url\"] == homepage:\n            homepage = None\n        data.update(\n            dict(\n                frameworks=[\"arduino\"],\n                homepage=homepage,\n                repository=repository or None,\n                description=self._parse_description(data),\n                platforms=self._parse_platforms(data) or None,\n                keywords=self._parse_keywords(data) or None,\n                export=self._parse_export(),\n            )\n        )\n        if \"includes\" in data:\n            data[\"headers\"] = self.str_to_list(data[\"includes\"], sep=\",\", unique=True)\n        if \"author\" in data:\n            data[\"authors\"] = self._parse_authors(data)\n            for key in (\"author\", \"maintainer\"):\n                if key in data:\n                    del data[key]\n        if \"depends\" in data:\n            data[\"dependencies\"] = self._parse_dependencies(data[\"depends\"])\n        return data\n\n    @staticmethod\n    def _parse_properties(contents):\n        data = {}\n        for line in contents.splitlines():\n            line = line.strip()\n            if not line or \"=\" not in line:\n                continue\n            # skip comments\n            if line.startswith(\"#\"):\n                continue\n            key, value = line.split(\"=\", 1)\n            if not value.strip():\n                continue\n            data[key.strip()] = value.strip()\n        return data\n\n    @staticmethod\n    def _parse_description(properties):\n        lines = []\n        for k in (\"sentence\", \"paragraph\"):\n            if k in properties and properties[k] not in lines:\n                lines.append(properties[k])\n        if len(lines) == 2:\n            if not lines[0].endswith(\".\"):\n                lines[0] += \".\"\n            if len(lines[0]) + len(lines[1]) >= 1000:\n                del lines[1]\n        return \" \".join(lines)\n\n    def _parse_keywords(self, properties):\n        return self.str_to_list(\n            re.split(\n                r\"[\\s/]+\",\n                properties.get(\"category\", \"\"),\n            ),\n            lowercase=True,\n            unique=True,\n        )\n\n    def _parse_platforms(self, properties):\n        result = []\n        platforms_map = {\n            \"avr\": \"atmelavr\",\n            \"sam\": \"atmelsam\",\n            \"samd\": \"atmelsam\",\n            \"esp8266\": \"espressif8266\",\n            \"esp32\": \"espressif32\",\n            \"arc32\": \"intel_arc32\",\n            \"stm32\": \"ststm32\",\n            \"nrf52\": \"nordicnrf52\",\n            \"rp2040\": \"raspberrypi\",\n        }\n        for arch in properties.get(\"architectures\", \"\").split(\",\"):\n            if \"particle-\" in arch:\n                raise ManifestParserError(\"Particle is not supported yet\")\n            arch = arch.strip()\n            if not arch:\n                continue\n            if arch == \"*\":\n                return [\"*\"]\n            if arch in platforms_map:\n                result.append(platforms_map[arch])\n        return self.str_to_list(result, lowercase=True, unique=True)\n\n    def _parse_authors(self, properties):\n        if \"author\" not in properties:\n            return None\n        authors = []\n        for author in properties[\"author\"].split(\",\"):\n            name, email = self.parse_author_name_and_email(author)\n            if not name:\n                continue\n            authors.append(self.cleanup_author(dict(name=name, email=email)))\n        for author in properties.get(\"maintainer\", \"\").split(\",\"):\n            name, email = self.parse_author_name_and_email(author)\n            if not name:\n                continue\n            found = False\n            for item in authors:\n                if item.get(\"name\", \"\").lower() != name.lower():\n                    continue\n                found = True\n                item[\"maintainer\"] = True\n                # pylint: disable=unsupported-membership-test\n                if not item.get(\"email\") and email and \"@\" in email:\n                    item[\"email\"] = email\n            if not found:\n                authors.append(\n                    self.cleanup_author(dict(name=name, email=email, maintainer=True))\n                )\n        return authors\n\n    def _parse_repository(self, properties):\n        if self.remote_url:\n            url_attrs = urlparse(self.remote_url)\n            repo_path_tokens = url_attrs.path[1:].split(\"/\")[:-1]\n            if \"github\" in url_attrs.netloc:\n                return dict(\n                    type=\"git\",\n                    url=\"https://github.com/\" + \"/\".join(repo_path_tokens[:2]),\n                )\n            if \"raw\" in repo_path_tokens:\n                return dict(\n                    type=\"git\",\n                    url=\"https://%s/%s\"\n                    % (\n                        url_attrs.netloc,\n                        \"/\".join(repo_path_tokens[: repo_path_tokens.index(\"raw\")]),\n                    ),\n                )\n        if properties.get(\"url\", \"\").startswith(\"https://github.com\"):\n            return dict(type=\"git\", url=properties[\"url\"])\n        return None\n\n    def _parse_export(self):\n        include = None\n        if self.remote_url:\n            url_attrs = urlparse(self.remote_url)\n            repo_path_tokens = url_attrs.path[1:].split(\"/\")[:-1]\n            if \"github\" in url_attrs.netloc:\n                include = \"/\".join(repo_path_tokens[3:]) or None\n            elif \"raw\" in repo_path_tokens:\n                include = (\n                    \"/\".join(repo_path_tokens[repo_path_tokens.index(\"raw\") + 2 :])\n                    or None\n                )\n        if include:\n            return dict(include=[include])\n        return None\n\n    @staticmethod\n    def _parse_dependencies(raw):\n        result = []\n        for item in raw.split(\",\"):\n            item = item.strip()\n            if not item:\n                continue\n            if item.endswith(\")\") and \"(\" in item:\n                name, version = item.split(\"(\")\n                result.append(\n                    dict(\n                        name=name.strip(),\n                        version=version[:-1].strip(),\n                        frameworks=[\"arduino\"],\n                    )\n                )\n            else:\n                result.append(dict(name=item, frameworks=[\"arduino\"]))\n        return result\n\n\nclass PlatformJsonManifestParser(BaseManifestParser):\n    manifest_type = ManifestFileType.PLATFORM_JSON\n\n    def parse(self, contents):\n        data = json.loads(contents)\n        if \"keywords\" in data:\n            data[\"keywords\"] = self.str_to_list(\n                data[\"keywords\"], sep=\",\", lowercase=True, unique=True\n            )\n        if \"frameworks\" in data:\n            data[\"frameworks\"] = (\n                self.str_to_list(\n                    list(data[\"frameworks\"].keys()), lowercase=True, unique=True\n                )\n                if isinstance(data[\"frameworks\"], dict)\n                else None\n            )\n        if \"packages\" in data:\n            data[\"dependencies\"] = self._parse_dependencies(data[\"packages\"])\n        return data\n\n    @staticmethod\n    def _parse_dependencies(raw):\n        result = []\n        for name, opts in raw.items():\n            item = {\"name\": name}\n            for k in (\"owner\", \"version\"):\n                if k in opts:\n                    item[k] = opts[k]\n            result.append(item)\n        return result\n\n\nclass PackageJsonManifestParser(BaseManifestParser):\n    manifest_type = ManifestFileType.PACKAGE_JSON\n\n    def parse(self, contents):\n        data = json.loads(contents)\n        if \"keywords\" in data:\n            data[\"keywords\"] = self.str_to_list(\n                data[\"keywords\"], sep=\",\", lowercase=True, unique=True\n            )\n        data = self._parse_system(data)\n        data = self._parse_homepage(data)\n        data = self._parse_repository(data)\n        return data\n\n    def _parse_system(self, data):\n        if \"system\" not in data:\n            return data\n        if data[\"system\"] in (\"*\", [\"*\"], \"all\"):\n            del data[\"system\"]\n            return data\n        data[\"system\"] = self.str_to_list(data[\"system\"], lowercase=True, unique=True)\n        return data\n\n    @staticmethod\n    def _parse_homepage(data):\n        if \"url\" in data:\n            data[\"homepage\"] = data[\"url\"]\n            del data[\"url\"]\n        return data\n\n    @staticmethod\n    def _parse_repository(data):\n        if isinstance(data.get(\"repository\", {}), dict):\n            return data\n        data[\"repository\"] = dict(type=\"git\", url=str(data[\"repository\"]))\n        if data[\"repository\"][\"url\"].startswith((\"github:\", \"gitlab:\", \"bitbucket:\")):\n            data[\"repository\"][\"url\"] = \"https://{0}.com/{1}\".format(\n                *(data[\"repository\"][\"url\"].split(\":\", 1))\n            )\n        return data\n"
  },
  {
    "path": "platformio/package/manifest/schema.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-ancestors\n\nimport json\nimport re\n\nimport marshmallow\nimport requests\nimport semantic_version\nfrom marshmallow import Schema, ValidationError, fields, validate, validates\n\nfrom platformio.http import fetch_remote_content\nfrom platformio.package.exception import ManifestValidationError\nfrom platformio.util import memoized\n\n\nclass BaseSchema(Schema):\n    class Meta:\n        unknown = marshmallow.EXCLUDE  # pylint: disable=no-member\n\n    def load_manifest(self, data):\n        return self.load(data)\n\n    def handle_error(self, error, data, **_):  # pylint: disable=arguments-differ\n        raise ManifestValidationError(\n            error.messages,\n            data,\n            error.valid_data if hasattr(error, \"valid_data\") else error.data,\n        )\n\n\nclass StrictSchema(BaseSchema):\n    def handle_error(self, error, data, **_):  # pylint: disable=arguments-differ\n        # skip broken records\n        if self.many:\n            error.valid_data = [\n                item for idx, item in enumerate(data) if idx not in error.messages\n            ]\n        else:\n            error.valid_data = None\n        raise error\n\n\nclass StrictListField(fields.List):\n    def _deserialize(  # pylint: disable=arguments-differ\n        self, value, attr, data, **kwargs\n    ):\n        try:\n            return super()._deserialize(value, attr, data, **kwargs)\n        except ValidationError as exc:\n            if exc.data:\n                exc.data = [item for item in exc.data if item is not None]\n            raise exc\n\n\nclass AuthorSchema(StrictSchema):\n    name = fields.Str(required=True, validate=validate.Length(min=1, max=100))\n    email = fields.Email(validate=validate.Length(min=1, max=50))\n    maintainer = fields.Bool(dump_default=False)\n    url = fields.Url(validate=validate.Length(min=1, max=255))\n\n\nclass RepositorySchema(StrictSchema):\n    type = fields.Str(\n        required=True,\n        validate=validate.OneOf(\n            [\"git\", \"hg\", \"svn\"],\n            error=\"Invalid repository type, please use one of [git, hg, svn]\",\n        ),\n    )\n    url = fields.Str(required=True, validate=validate.Length(min=1, max=255))\n    branch = fields.Str(validate=validate.Length(min=1, max=50))\n\n\nclass DependencySchema(StrictSchema):\n    owner = fields.Str(validate=validate.Length(min=1, max=100))\n    name = fields.Str(required=True, validate=validate.Length(min=1, max=100))\n    version = fields.Str(validate=validate.Length(min=1, max=100))\n    authors = StrictListField(fields.Str(validate=validate.Length(min=1, max=50)))\n    platforms = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^([a-z\\d\\-_]+|\\*)$\", error=\"Only [a-z0-9-_*] chars are allowed\"\n                ),\n            ]\n        )\n    )\n    frameworks = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^([a-z\\d\\-_]+|\\*)$\", error=\"Only [a-z0-9-_*] chars are allowed\"\n                ),\n            ]\n        )\n    )\n\n\nclass ExportSchema(BaseSchema):\n    include = StrictListField(fields.Str)\n    exclude = StrictListField(fields.Str)\n\n\nclass ExampleSchema(StrictSchema):\n    name = fields.Str(\n        required=True,\n        validate=[\n            validate.Length(min=1, max=255),\n            validate.Regexp(\n                r\"^[a-zA-Z\\d\\-\\_/\\. ]+$\",\n                error=\"Only [a-zA-Z0-9-_/. ] chars are allowed\",\n            ),\n        ],\n    )\n    base = fields.Str(required=True)\n    files = StrictListField(fields.Str, required=True)\n\n\n# Fields\n\n\nclass ScriptField(fields.Field):\n    def _deserialize(self, value, attr, data, **kwargs):\n        if isinstance(value, (str, list)):\n            return value\n        raise ValidationError(\n            \"Script value must be a command (string) or list of arguments\"\n        )\n\n\n# Scheme\n\n\nclass ManifestSchema(BaseSchema):\n    # Required fields\n    name = fields.Str(\n        required=True,\n        validate=[\n            validate.Length(min=1, max=100),\n            validate.Regexp(\n                r\"^[^:;/,@\\<\\>]+$\", error=\"The next chars [:;/,@<>] are not allowed\"\n            ),\n        ],\n    )\n    version = fields.Str(required=True, validate=validate.Length(min=1, max=50))\n\n    # Optional fields\n\n    authors = fields.Nested(AuthorSchema, many=True)\n    description = fields.Str(validate=validate.Length(min=1, max=1000))\n    homepage = fields.Url(validate=validate.Length(min=1, max=255))\n    license = fields.Str(validate=validate.Length(min=1, max=255))\n    repository = fields.Nested(RepositorySchema)\n    dependencies = fields.Nested(DependencySchema, many=True)\n    scripts = fields.Dict(\n        keys=fields.Str(validate=validate.OneOf([\"postinstall\", \"preuninstall\"])),\n        values=ScriptField(),\n    )\n\n    # library.json\n    export = fields.Nested(ExportSchema)\n    examples = fields.Nested(ExampleSchema, many=True)\n    downloadUrl = fields.Url(validate=validate.Length(min=1, max=255))\n\n    keywords = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^[a-z\\d\\-_\\+\\. ]+$\", error=\"Only [a-z0-9+_-. ] chars are allowed\"\n                ),\n            ]\n        )\n    )\n    platforms = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^([a-z\\d\\-_]+|\\*)$\", error=\"Only [a-z0-9-_*] chars are allowed\"\n                ),\n            ]\n        )\n    )\n    frameworks = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^([a-z\\d\\-_]+|\\*)$\", error=\"Only [a-z0-9-_*] chars are allowed\"\n                ),\n            ]\n        )\n    )\n    headers = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=255),\n            ]\n        )\n    )\n\n    # platform.json specific\n    title = fields.Str(validate=validate.Length(min=1, max=100))\n\n    # package.json specific\n    system = StrictListField(\n        fields.Str(\n            validate=[\n                validate.Length(min=1, max=50),\n                validate.Regexp(\n                    r\"^[a-z\\d\\-_]+$\", error=\"Only [a-z0-9-_] chars are allowed\"\n                ),\n            ]\n        )\n    )\n\n    @validates(\"version\")\n    def validate_version(self, value):\n        try:\n            value = str(value)\n            assert \".\" in value\n            # check leading zeros\n            try:\n                semantic_version.Version(value)\n            except ValueError as exc:\n                if \"Invalid leading zero\" in str(exc):\n                    raise exc\n            semantic_version.Version.coerce(value)\n        except (AssertionError, ValueError) as exc:\n            raise ValidationError(\n                \"Invalid semantic versioning format, see https://semver.org/\"\n            ) from exc\n\n    @validates(\"license\")\n    def validate_license(self, value):\n        try:\n            spdx = self.load_spdx_licenses()\n        except requests.exceptions.RequestException as exc:\n            raise ValidationError(\n                \"Could not load SPDX licenses for validation\"\n            ) from exc\n        known_ids = set(item.get(\"licenseId\") for item in spdx.get(\"licenses\", []))\n        if value in known_ids:\n            return True\n        # parse license expression\n        # https://spdx.github.io/spdx-spec/SPDX-license-expressions/\n        package_ids = [\n            item.strip()\n            for item in re.sub(r\"(\\s+(?:OR|AND|WITH)\\s+|[\\(\\)])\", \" \", value).split(\" \")\n            if item.strip()\n        ]\n        if known_ids >= set(package_ids):\n            return True\n        raise ValidationError(\n            \"Invalid SPDX license identifier. See valid identifiers at \"\n            \"https://spdx.org/licenses/\"\n        )\n\n    @staticmethod\n    @memoized(expire=\"1h\")\n    def load_spdx_licenses():\n        version = \"3.28.0\"\n        spdx_data_url = (\n            \"https://raw.githubusercontent.com/spdx/license-list-data/\"\n            f\"v{version}/json/licenses.json\"\n        )\n        return json.loads(fetch_remote_content(spdx_data_url))\n"
  },
  {
    "path": "platformio/package/meta.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport re\nimport tarfile\nfrom binascii import crc32\nfrom urllib.parse import urlparse\n\nimport semantic_version\n\nfrom platformio import fs\nfrom platformio.compat import get_object_members, hashlib_encode_data, string_types\nfrom platformio.package.manifest.parser import ManifestFileType\nfrom platformio.package.version import SemanticVersionError, cast_version_to_semver\nfrom platformio.util import items_in_list\n\n\nclass PackageType:\n    LIBRARY = \"library\"\n    PLATFORM = \"platform\"\n    TOOL = \"tool\"\n\n    @classmethod\n    def items(cls):\n        return get_object_members(cls)\n\n    @classmethod\n    def get_manifest_map(cls):\n        return {\n            cls.PLATFORM: (ManifestFileType.PLATFORM_JSON,),\n            cls.LIBRARY: (\n                ManifestFileType.LIBRARY_JSON,\n                ManifestFileType.LIBRARY_PROPERTIES,\n                ManifestFileType.MODULE_JSON,\n            ),\n            cls.TOOL: (ManifestFileType.PACKAGE_JSON,),\n        }\n\n    @classmethod\n    def from_archive(cls, path):\n        assert path.endswith(\"tar.gz\")\n        manifest_map = cls.get_manifest_map()\n        with tarfile.open(path, mode=\"r:gz\") as tf:\n            for t in sorted(cls.items().values()):\n                for manifest in manifest_map[t]:\n                    try:\n                        if tf.getmember(manifest):\n                            return t\n                    except KeyError:\n                        pass\n        return None\n\n\nclass PackageCompatibility:\n    KNOWN_QUALIFIERS = (\n        \"owner\",\n        \"name\",\n        \"version\",\n        \"platforms\",\n        \"frameworks\",\n        \"authors\",\n    )\n\n    @classmethod\n    def from_dependency(cls, dependency):\n        assert isinstance(dependency, dict)\n        qualifiers = {\n            key: value\n            for key, value in dependency.items()\n            if key in cls.KNOWN_QUALIFIERS\n        }\n        return PackageCompatibility(**qualifiers)\n\n    def __init__(self, **kwargs):\n        self.qualifiers = {}\n        for key, value in kwargs.items():\n            if key not in self.KNOWN_QUALIFIERS:\n                raise ValueError(\n                    \"Unknown package compatibility qualifier -> `%s`\" % key\n                )\n            self.qualifiers[key] = value\n\n    def __repr__(self):\n        return \"PackageCompatibility <%s>\" % self.qualifiers\n\n    def to_search_qualifiers(self, fields=None):\n        result = {}\n        for name, value in self.qualifiers.items():\n            if not fields or name in fields:\n                result[name] = value\n        return result\n\n    def is_compatible(self, other):\n        assert isinstance(other, PackageCompatibility)\n        for key, current_value in self.qualifiers.items():\n            other_value = other.qualifiers.get(key)\n            if not current_value or not other_value:\n                continue\n            if any(isinstance(v, list) for v in (current_value, other_value)):\n                if not items_in_list(current_value, other_value):\n                    return False\n                continue\n            if key == \"version\":\n                if not self._compare_versions(current_value, other_value):\n                    return False\n                continue\n            if current_value != other_value:\n                return False\n        return True\n\n    def _compare_versions(self, current, other):\n        if current == other:\n            return True\n        try:\n            version = (\n                other\n                if isinstance(other, semantic_version.Version)\n                else cast_version_to_semver(other)\n            )\n            return version in semantic_version.SimpleSpec(current)\n        except ValueError:\n            pass\n        return False\n\n\nclass PackageOutdatedResult:\n    UPDATE_INCREMENT_MAJOR = \"major\"\n    UPDATE_INCREMENT_MINOR = \"minor\"\n    UPDATE_INCREMENT_PATCH = \"patch\"\n\n    def __init__(self, current, latest=None, wanted=None, detached=False):\n        self.current = current\n        self.latest = latest\n        self.wanted = wanted\n        self.detached = detached\n\n    def __repr__(self):\n        return (\n            \"PackageOutdatedResult <current={current} latest={latest} wanted={wanted} \"\n            \"detached={detached}>\".format(\n                current=self.current,\n                latest=self.latest,\n                wanted=self.wanted,\n                detached=self.detached,\n            )\n        )\n\n    def __setattr__(self, name, value):\n        if (\n            value\n            and name in (\"current\", \"latest\", \"wanted\")\n            and not isinstance(value, semantic_version.Version)\n        ):\n            value = cast_version_to_semver(str(value))\n        return super().__setattr__(name, value)\n\n    @property\n    def update_increment_type(self):\n        if not self.current or not self.latest:\n            return None\n        patch_conds = [\n            self.current.major == self.latest.major,\n            self.current.minor == self.latest.minor,\n        ]\n        if all(patch_conds):\n            return self.UPDATE_INCREMENT_PATCH\n        minor_conds = [\n            self.current.major == self.latest.major,\n            self.current.major > 0,\n        ]\n        if all(minor_conds):\n            return self.UPDATE_INCREMENT_MINOR\n        return self.UPDATE_INCREMENT_MAJOR\n\n    def is_outdated(self, allow_incompatible=False):\n        if self.detached or not self.latest or self.current == self.latest:\n            return False\n        if allow_incompatible:\n            return self.current != self.latest\n        if self.wanted:\n            return self.current != self.wanted\n        return True\n\n\nclass PackageSpec:  # pylint: disable=too-many-instance-attributes\n    def __init__(  # pylint: disable=redefined-builtin,too-many-arguments,too-many-positional-arguments\n        self, raw=None, owner=None, id=None, name=None, requirements=None, uri=None\n    ):\n        self._requirements = None\n        self.owner = owner\n        self.id = id\n        self.name = name\n        self.uri = uri\n        self.raw = raw\n        if requirements:\n            try:\n                self.requirements = requirements\n            except SemanticVersionError as exc:\n                if not self.name or self.uri or self.raw:\n                    raise exc\n                self.raw = \"%s=%s\" % (self.name, requirements)\n        self._name_is_custom = False\n        self._parse(self.raw)\n\n    def __eq__(self, other):\n        return all(\n            [\n                self.owner == other.owner,\n                self.id == other.id,\n                self.name == other.name,\n                self.requirements == other.requirements,\n                self.uri == other.uri,\n            ]\n        )\n\n    def __hash__(self):\n        return crc32(\n            hashlib_encode_data(\n                \"%s-%s-%s-%s-%s\"\n                % (self.owner, self.id, self.name, self.requirements, self.uri)\n            )\n        )\n\n    def __repr__(self):\n        return (\n            \"PackageSpec <owner={owner} id={id} name={name} \"\n            \"requirements={requirements} uri={uri}>\".format(**self.as_dict())\n        )\n\n    @property\n    def external(self):\n        return bool(self.uri)\n\n    @property\n    def symlink(self):\n        return self.uri and self.uri.startswith(\"symlink://\")\n\n    @property\n    def requirements(self):\n        return self._requirements\n\n    @requirements.setter\n    def requirements(self, value):\n        if not value:\n            self._requirements = None\n            return\n        try:\n            self._requirements = (\n                value\n                if isinstance(value, semantic_version.SimpleSpec)\n                else semantic_version.SimpleSpec(str(value))\n            )\n        except ValueError as exc:\n            raise SemanticVersionError(exc) from exc\n\n    def humanize(self):\n        result = \"\"\n        if self.uri:\n            result = self.uri\n        elif self.name:\n            if self.owner:\n                result = self.owner + \"/\"\n            result += self.name\n        elif self.id:\n            result = \"id:%d\" % self.id\n        if self.requirements:\n            result += \" @ \" + str(self.requirements)\n        return result\n\n    def has_custom_name(self):\n        return self._name_is_custom\n\n    def as_dict(self):\n        return dict(\n            owner=self.owner,\n            id=self.id,\n            name=self.name,\n            requirements=str(self.requirements) if self.requirements else None,\n            uri=self.uri,\n        )\n\n    def as_dependency(self):\n        if self.uri:\n            return self.raw or self.uri\n        result = \"\"\n        if self.name:\n            result = \"%s/%s\" % (self.owner, self.name) if self.owner else self.name\n        elif self.id:\n            result = str(self.id)\n        assert result\n        if self.requirements:\n            result = \"%s@%s\" % (result, self.requirements)\n        return result\n\n    def _parse(self, raw):\n        if raw is None:\n            return\n        if not isinstance(raw, string_types):\n            raw = str(raw)\n        raw = raw.strip()\n\n        parsers = (\n            self._parse_local_file,\n            self._parse_requirements,\n            self._parse_custom_name,\n            self._parse_id,\n            self._parse_owner,\n            self._parse_uri,\n        )\n        for parser in parsers:\n            if raw is None:\n                break\n            raw = parser(raw)\n\n        # if name is not custom, parse it from URI\n        if not self.name and self.uri:\n            self.name = self._parse_name_from_uri(self.uri)\n        elif raw:\n            # the leftover is a package name\n            self.name = raw\n\n    @staticmethod\n    def _parse_local_file(raw):\n        if raw.startswith((\"file://\", \"symlink://\")) or not any(\n            c in raw for c in (\"/\", \"\\\\\")\n        ):\n            return raw\n        if os.path.exists(raw):\n            return \"file://%s\" % raw\n        return raw\n\n    def _parse_requirements(self, raw):\n        if \"@\" not in raw or raw.startswith((\"file://\", \"symlink://\")):\n            return raw\n        tokens = raw.rsplit(\"@\", 1)\n        if any(s in tokens[1] for s in (\":\", \"/\")):\n            return raw\n        self.requirements = tokens[1].strip()\n        return tokens[0].strip()\n\n    def _parse_custom_name(self, raw):\n        if \"=\" not in raw or raw.startswith(\"id=\"):\n            return raw\n        tokens = raw.split(\"=\", 1)\n        if \"/\" in tokens[0]:\n            return raw\n        self.name = tokens[0].strip()\n        self._name_is_custom = True\n        return tokens[1].strip()\n\n    def _parse_id(self, raw):\n        if raw.isdigit():\n            self.id = int(raw)\n            return None\n        if raw.startswith(\"id=\"):\n            return self._parse_id(raw[3:])\n        return raw\n\n    def _parse_owner(self, raw):\n        if raw.count(\"/\") != 1 or \"@\" in raw:\n            return raw\n        tokens = raw.split(\"/\", 1)\n        self.owner = tokens[0].strip()\n        self.name = tokens[1].strip()\n        return None\n\n    def _parse_uri(self, raw):\n        if not any(s in raw for s in (\"@\", \":\", \"/\")):\n            return raw\n        self.uri = raw.strip()\n        parts = urlparse(self.uri)\n\n        # if local file or valid URI with scheme vcs+protocol://\n        if (\n            parts.scheme in (\"file\", \"symlink://\")\n            or \"+\" in parts.scheme\n            or self.uri.startswith(\"git+\")\n        ):\n            return None\n\n        # parse VCS\n        git_conditions = [\n            parts.path.endswith(\".git\"),\n            # Handle GitHub URL (https://github.com/user/package)\n            parts.netloc in (\"github.com\", \"gitlab.com\", \"bitbucket.com\")\n            and not parts.path.endswith((\".zip\", \".tar.gz\", \".tar.xz\")),\n        ]\n        hg_conditions = [\n            # Handle Developer Mbed URL\n            # (https://developer.mbed.org/users/user/code/package/)\n            # (https://os.mbed.com/users/user/code/package/)\n            parts.netloc\n            in (\"mbed.com\", \"os.mbed.com\", \"developer.mbed.org\")\n        ]\n        if any(git_conditions):\n            self.uri = \"git+\" + self.uri\n        elif any(hg_conditions):\n            self.uri = \"hg+\" + self.uri\n\n        return None\n\n    @staticmethod\n    def _parse_name_from_uri(uri):\n        if uri.endswith(\"/\"):\n            uri = uri[:-1]\n        stop_chars = [\"#\", \"?\"]\n        if uri.startswith((\"file://\", \"symlink://\")):\n            stop_chars.append(\"@\")  # detached path\n        for c in stop_chars:\n            if c in uri:\n                uri = uri[: uri.index(c)]\n\n        # parse real repository name from Github\n        parts = urlparse(uri)\n        if parts.netloc == \"github.com\" and parts.path.count(\"/\") > 2:\n            return parts.path.split(\"/\")[2]\n\n        name = os.path.basename(uri)\n        if \".\" in name:\n            return name.split(\".\", 1)[0].strip()\n        return name\n\n\nclass PackageMetadata:\n    def __init__(  # pylint: disable=redefined-builtin\n        self, type, name, version, spec=None\n    ):\n        # assert type in PackageType.items().values()\n        if spec:\n            assert isinstance(spec, PackageSpec)\n        self.type = type\n        self.name = name\n        self._version = None\n        self.version = version\n        self.spec = spec\n\n    def __repr__(self):\n        return (\n            \"PackageMetadata <type={type} name={name} version={version} \"\n            \"spec={spec}\".format(**self.as_dict())\n        )\n\n    def __eq__(self, other):\n        return all(\n            [\n                self.type == other.type,\n                self.name == other.name,\n                self.version == other.version,\n                self.spec == other.spec,\n            ]\n        )\n\n    @property\n    def version(self):\n        return self._version\n\n    @version.setter\n    def version(self, value):\n        if not value:\n            self._version = None\n            return\n        self._version = (\n            value\n            if isinstance(value, semantic_version.Version)\n            else cast_version_to_semver(value)\n        )\n\n    def as_dict(self):\n        return dict(\n            type=self.type,\n            name=self.name,\n            version=str(self.version),\n            spec=self.spec.as_dict() if self.spec else None,\n        )\n\n    def dump(self, path):\n        with open(path, mode=\"w\", encoding=\"utf8\") as fp:\n            return json.dump(self.as_dict(), fp)\n\n    @staticmethod\n    def load(path):\n        data = fs.load_json(path)\n        if data[\"spec\"]:\n            # legacy support for Core<5.3 packages\n            if \"url\" in data[\"spec\"]:\n                data[\"spec\"][\"uri\"] = data[\"spec\"][\"url\"]\n                del data[\"spec\"][\"url\"]\n            data[\"spec\"] = PackageSpec(**data[\"spec\"])\n        return PackageMetadata(**data)\n\n\nclass PackageItem:\n    METAFILE_NAME = \".piopm\"\n\n    def __init__(self, path, metadata=None):\n        self.path = path\n        self.metadata = metadata\n        if not self.metadata and self.exists():\n            self.metadata = self.load_meta()\n\n    def __repr__(self):\n        return \"PackageItem <path={path} metadata={metadata}\".format(\n            path=self.path, metadata=self.metadata\n        )\n\n    def __eq__(self, other):\n        conds = [\n            (\n                os.path.realpath(self.path) == os.path.realpath(other.path)\n                if self.path and other.path\n                else self.path == other.path\n            ),\n            self.metadata == other.metadata,\n        ]\n        return all(conds)\n\n    def __hash__(self):\n        return hash(os.path.realpath(self.path))\n\n    def exists(self):\n        return os.path.isdir(self.path)\n\n    def get_safe_dirname(self):\n        assert self.metadata\n        return re.sub(r\"[^\\da-z\\_\\-\\. ]\", \"_\", self.metadata.name, flags=re.I)\n\n    def get_metafile_locations(self):\n        return [\n            os.path.join(self.path, \".git\"),\n            os.path.join(self.path, \".hg\"),\n            os.path.join(self.path, \".svn\"),\n            self.path,\n        ]\n\n    def load_meta(self):\n        assert self.exists()\n        for location in self.get_metafile_locations():\n            manifest_path = os.path.join(location, self.METAFILE_NAME)\n            if os.path.isfile(manifest_path):\n                return PackageMetadata.load(manifest_path)\n        return None\n\n    def dump_meta(self):\n        assert self.exists()\n        location = None\n        for location in self.get_metafile_locations():\n            if os.path.isdir(location):\n                break\n        assert location\n        return self.metadata.dump(os.path.join(location, self.METAFILE_NAME))\n"
  },
  {
    "path": "platformio/package/pack.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport re\nimport shutil\nimport tarfile\nimport tempfile\n\nfrom platformio import fs\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.package.exception import PackageException, UserSideException\nfrom platformio.package.manifest.parser import (\n    LibraryPropertiesManifestParser,\n    ManifestFileType,\n    ManifestParserFactory,\n)\nfrom platformio.package.manifest.schema import ManifestSchema\nfrom platformio.package.meta import PackageItem\nfrom platformio.package.unpack import FileUnpacker\n\n\nclass PackagePacker:\n    INCLUDE_DEFAULT = list(ManifestFileType.items().values()) + [\n        \"README\",\n        \"README.md\",\n        \"README.rst\",\n        \"LICENSE\",\n    ]\n    EXCLUDE_DEFAULT = [\n        # PlatformIO internal files\n        PackageItem.METAFILE_NAME,\n        \".pio/\",\n        \"**/.pio/\",\n        # Hidden files\n        \"._*\",\n        \"__*\",\n        \".DS_Store\",\n        \".vscode\",\n        \"**/.vscode/\",\n        \".cache\",\n        \"**/.cache\",\n        \"**/__pycache__\",\n        \"**/*.pyc\",\n        # VCS\n        \".git/\",\n        \".hg/\",\n        \".svn/\",\n    ]\n    EXCLUDE_EXTRA = [\n        # Tests\n        \"test\",\n        \"tests\",\n        # Docs\n        \"doc\",\n        \"docs\",\n        \"mkdocs\",\n        \"doxygen\",\n        \"*.doxyfile\",\n        \"html\",\n        \"media\",\n        \"**/*.[pP][dD][fF]\",\n        \"**/*.[dD][oO][cC]\",\n        \"**/*.[dD][oO][cC][xX]\",\n        \"**/*.[pP][pP][tT]\",\n        \"**/*.[pP][pP][tT][xX]\",\n        \"**/*.[xX][lL][sS]\",\n        \"**/*.[xX][lL][sS][xX]\",\n        \"**/*.[dD][oO][xX]\",\n        \"**/*.[hH][tT][mM]\",\n        \"**/*.[hH][tT][mM][lL]\",\n        \"**/*.[tT][eE][xX]\",\n        \"**/*.[jJ][sS]\",\n        \"**/*.[cC][sS][sS]\",\n        # Binary files\n        \"**/*.[jJ][pP][gG]\",\n        \"**/*.[jJ][pP][eE][gG]\",\n        \"**/*.[pP][nN][gG]\",\n        \"**/*.[gG][iI][fF]\",\n        \"**/*.[sS][vV][gG]\",\n        \"**/*.[zZ][iI][pP]\",\n        \"**/*.[gG][zZ]\",\n        \"**/*.3[gG][pP]\",\n        \"**/*.[mM][oO][vV]\",\n        \"**/*.[mM][pP][34]\",\n        \"**/*.[pP][sS][dD]\",\n        \"**/*.[wW][aA][wW]\",\n        \"**/*.sqlite\",\n    ]\n    EXCLUDE_LIBRARY_EXTRA = [\n        \"assets\",\n        \"extra\",\n        \"extras\",\n        \"resources\",\n        \"**/build/\",\n        \"**/*.flat\",\n        \"**/*.[jJ][aA][rR]\",\n        \"**/*.[eE][xX][eE]\",\n        \"**/*.[bB][iI][nN]\",\n        \"**/*.[hH][eE][xX]\",\n        \"**/*.[dD][bB]\",\n        \"**/*.[dD][aA][tT]\",\n        \"**/*.[dD][lL][lL]\",\n    ]\n\n    def __init__(self, package, manifest_uri=None):\n        self.package = package\n        self.manifest_uri = manifest_uri\n        self.manifest_parser = None\n\n    @staticmethod\n    def get_archive_name(name, version, system=None):\n        return re.sub(\n            r\"[^\\da-zA-Z\\-\\._\\+]+\",\n            \"\",\n            \"{name}{system}-{version}.tar.gz\".format(\n                name=name,\n                system=(\"-\" + system) if system else \"\",\n                version=version,\n            ),\n        )\n\n    @staticmethod\n    def load_gitignore_filters(path):\n        result = []\n        with open(path, encoding=\"utf8\") as fp:\n            for line in fp.readlines():\n                line = line.strip()\n                if not line or line.startswith((\"#\")):\n                    continue\n                if line.startswith(\"!\"):\n                    result.append(f\"+<{line[1:]}>\")\n                else:\n                    result.append(f\"-<{line}>\")\n        return result\n\n    def pack(self, dst=None):\n        tmp_dir = tempfile.mkdtemp()\n        try:\n            src = self.package\n\n            # if zip/tar.gz -> unpack to tmp dir\n            if not os.path.isdir(src):\n                if IS_WINDOWS:\n                    raise UserSideException(\n                        \"Packaging from an archive does not work on Windows OS. Please \"\n                        \"extract data from `%s` manually and pack a folder instead\"\n                        % src\n                    )\n                with FileUnpacker(src) as fu:\n                    assert fu.unpack(tmp_dir, silent=True)\n                src = tmp_dir\n\n            src = self.find_source_root(src)\n            self.manifest_parser = ManifestParserFactory.new_from_dir(src)\n            manifest = ManifestSchema().load_manifest(self.manifest_parser.as_dict())\n            filename = self.get_archive_name(\n                manifest[\"name\"],\n                manifest[\"version\"],\n                manifest[\"system\"][0] if \"system\" in manifest else None,\n            )\n\n            if not dst:\n                dst = os.path.join(os.getcwd(), filename)\n            elif os.path.isdir(dst):\n                dst = os.path.join(dst, filename)\n\n            return self.create_tarball(src, dst, manifest)\n        finally:\n            shutil.rmtree(tmp_dir)\n\n    def find_source_root(self, src):\n        if self.manifest_uri:\n            mp = (\n                ManifestParserFactory.new_from_file(self.manifest_uri[5:])\n                if self.manifest_uri.startswith(\"file:\")\n                else ManifestParserFactory.new_from_url(self.manifest_uri)\n            )\n            manifest = ManifestSchema().load_manifest(mp.as_dict())\n            include = manifest.get(\"export\", {}).get(\"include\", [])\n            if len(include) == 1:\n                if not os.path.isdir(os.path.join(src, include[0])):\n                    raise PackageException(\n                        \"Non existing `include` directory `%s` in a package\"\n                        % include[0]\n                    )\n                return os.path.join(src, include[0])\n\n        for root, _, __ in os.walk(src):\n            if ManifestFileType.from_dir(root):\n                return root\n\n        return src\n\n    def create_tarball(self, src, dst, manifest):\n        include = manifest.get(\"export\", {}).get(\"include\")\n        exclude = manifest.get(\"export\", {}).get(\"exclude\")\n        # remap root\n        if (\n            include\n            and len(include) == 1\n            and os.path.isdir(os.path.join(src, include[0]))\n        ):\n            src = os.path.join(src, include[0])\n            with open(\n                os.path.join(src, \"library.json\"), mode=\"w\", encoding=\"utf8\"\n            ) as fp:\n                manifest_updated = manifest.copy()\n                del manifest_updated[\"export\"][\"include\"]\n                json.dump(manifest_updated, fp, indent=2, ensure_ascii=False)\n            include = None\n\n        src_filters = self.compute_src_filters(src, include, exclude)\n        with tarfile.open(dst, \"w:gz\") as tar:\n            for f in fs.match_src_files(src, src_filters, followlinks=False):\n                tar.add(os.path.join(src, f), f)\n        return dst\n\n    def compute_src_filters(self, src, include, exclude):\n        exclude_extra = self.EXCLUDE_EXTRA[:]\n        # extend with library extra filters\n        if any(\n            os.path.isfile(os.path.join(src, name))\n            for name in (\n                ManifestFileType.LIBRARY_JSON,\n                ManifestFileType.LIBRARY_PROPERTIES,\n                ManifestFileType.MODULE_JSON,\n            )\n        ):\n            exclude_extra.extend(self.EXCLUDE_LIBRARY_EXTRA)\n\n        result = [\"+<%s>\" % p for p in include or [\"*\", \".*\"]]\n        result += [\"-<%s>\" % p for p in self.EXCLUDE_DEFAULT]\n        # exclude items declared in manifest\n        result += [\"-<%s>\" % p for p in exclude or []]\n\n        # apply extra excludes if no custom \"export\" field in manifest\n        if (not include and not exclude) or isinstance(\n            self.manifest_parser, LibraryPropertiesManifestParser\n        ):\n            result += [\"-<%s>\" % p for p in exclude_extra]\n            if os.path.exists(os.path.join(src, \".gitignore\")):\n                result += self.load_gitignore_filters(os.path.join(src, \".gitignore\"))\n\n        # always include manifests and relevant files\n        result += [\"+<%s>\" % p for p in self.INCLUDE_DEFAULT]\n        return result\n"
  },
  {
    "path": "platformio/package/unpack.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\nfrom tarfile import open as tarfile_open\nfrom time import mktime\nfrom zipfile import ZipFile\n\nimport click\n\nfrom platformio import fs\nfrom platformio.compat import is_terminal\nfrom platformio.package.exception import PackageException\n\n\nclass ExtractArchiveItemError(PackageException):\n    MESSAGE = (\n        \"Could not extract `{0}` to `{1}`. Try to disable antivirus \"\n        \"tool or check this solution -> https://bit.ly/faq-package-manager\"\n    )\n\n\nclass BaseArchiver:\n    def __init__(self, arhfileobj):\n        self._afo = arhfileobj\n\n    def get_items(self):\n        raise NotImplementedError()\n\n    def get_item_filename(self, item):\n        raise NotImplementedError()\n\n    def is_link(self, item):\n        raise NotImplementedError()\n\n    def extract_item(self, item, dest_dir):\n        self._afo.extract(item, dest_dir)\n        self.after_extract(item, dest_dir)\n\n    def after_extract(self, item, dest_dir):\n        pass\n\n    def close(self):\n        self._afo.close()\n\n\nclass TARArchiver(BaseArchiver):\n    def __init__(self, archpath):\n        super().__init__(tarfile_open(archpath))  # pylint: disable=consider-using-with\n\n    def get_items(self):\n        return self._afo.getmembers()\n\n    def get_item_filename(self, item):\n        return item.name\n\n    @staticmethod\n    def is_link(item):  # pylint: disable=arguments-differ\n        return item.islnk() or item.issym()\n\n    @staticmethod\n    def resolve_path(path):\n        return os.path.realpath(os.path.abspath(path))\n\n    def is_bad_path(self, path, base):\n        return not self.resolve_path(os.path.join(base, path)).startswith(base)\n\n    def is_bad_link(self, item, base):\n        return not self.resolve_path(\n            os.path.join(os.path.join(base, os.path.dirname(item.name)), item.linkname)\n        ).startswith(base)\n\n    def extract_item(self, item, dest_dir):\n        if sys.version_info >= (3, 12):\n            self._afo.extract(item, dest_dir, filter=\"data\")\n            return self.after_extract(item, dest_dir)\n\n        # apply custom security logic\n        dest_dir = self.resolve_path(dest_dir)\n        bad_conds = [\n            self.is_bad_path(item.name, dest_dir),\n            self.is_link(item) and self.is_bad_link(item, dest_dir),\n        ]\n        if any(bad_conds):\n            return click.secho(\n                \"Blocked insecure item `%s` from TAR archive\" % item.name,\n                fg=\"red\",\n                err=True,\n            )\n        return super().extract_item(item, dest_dir)\n\n\nclass ZIPArchiver(BaseArchiver):\n    def __init__(self, archpath):\n        super().__init__(ZipFile(archpath))  # pylint: disable=consider-using-with\n\n    @staticmethod\n    def preserve_permissions(item, dest_dir):\n        attrs = item.external_attr >> 16\n        if attrs:\n            os.chmod(os.path.join(dest_dir, item.filename), attrs)\n\n    @staticmethod\n    def preserve_mtime(item, dest_dir):\n        fs.change_filemtime(\n            os.path.join(dest_dir, item.filename),\n            mktime(tuple(item.date_time) + tuple([0, 0, 0])),\n        )\n\n    @staticmethod\n    def is_link(_):  # pylint: disable=arguments-differ\n        return False\n\n    def get_items(self):\n        return self._afo.infolist()\n\n    def get_item_filename(self, item):\n        return item.filename\n\n    def after_extract(self, item, dest_dir):\n        self.preserve_permissions(item, dest_dir)\n        self.preserve_mtime(item, dest_dir)\n\n\nclass FileUnpacker:\n    def __init__(self, path):\n        self.path = path\n        self._archiver = None\n\n    def __enter__(self):\n        self._archiver = self.new_archiver(self.path)\n        return self\n\n    def __exit__(self, *args):\n        if self._archiver:\n            self._archiver.close()\n\n    @staticmethod\n    def new_archiver(path):\n        magic_map = {\n            b\"\\x1f\\x8b\\x08\": TARArchiver,\n            b\"\\x42\\x5a\\x68\": TARArchiver,\n            b\"\\xfd\\x37\\x7a\\x58\\x5a\\x00\": TARArchiver,\n            b\"\\x50\\x4b\\x03\\x04\": ZIPArchiver,\n        }\n        magic_len = max(len(k) for k in magic_map)\n        with open(path, \"rb\") as fp:\n            data = fp.read(magic_len)\n            for magic, archiver in magic_map.items():\n                if data.startswith(magic):\n                    return archiver(path)\n        raise PackageException(\"Unknown archive type '%s'\" % path)\n\n    def unpack(\n        self, dest_dir=None, with_progress=True, check_unpacked=True, silent=False\n    ):  # pylint: disable=too-many-branches\n        assert self._archiver\n        label = \"Unpacking\"\n        items = self._archiver.get_items()\n        if not dest_dir:\n            dest_dir = os.getcwd()\n\n        if not with_progress or silent:\n            if not silent:\n                click.echo(f\"{label}...\")\n            for item in items:\n                self._archiver.extract_item(item, dest_dir)\n        elif not is_terminal():\n            click.echo(f\"{label} 0%\", nl=False)\n            print_percent_step = 10\n            printed_percents = 0\n            unpacked_nums = 0\n            for item in items:\n                self._archiver.extract_item(item, dest_dir)\n                unpacked_nums += 1\n                if (unpacked_nums / len(items) * 100) >= (\n                    printed_percents + print_percent_step\n                ):\n                    printed_percents += print_percent_step\n                    click.echo(f\" {printed_percents}%\", nl=False)\n            click.echo(\"\")\n        else:\n            with click.progressbar(\n                items,\n                label=label,\n                update_min_steps=min(50, len(items) / 100),  # every 50 files or less\n            ) as pb:\n                for item in pb:\n                    self._archiver.extract_item(item, dest_dir)\n\n        if not check_unpacked:\n            return True\n\n        # check on disk\n        for item in self._archiver.get_items():\n            filename = self._archiver.get_item_filename(item)\n            item_path = os.path.join(dest_dir, filename)\n            try:\n                if not self._archiver.is_link(item) and not os.path.exists(item_path):\n                    raise ExtractArchiveItemError(filename, dest_dir)\n            except NotImplementedError:\n                pass\n        return True\n"
  },
  {
    "path": "platformio/package/vcsclient.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport subprocess\nfrom urllib.parse import urlparse\n\nfrom platformio import proc\nfrom platformio.exception import UserSideException\n\n\nclass VCSBaseException(UserSideException):\n    pass\n\n\nclass VCSClientFactory:\n    @staticmethod\n    def new(src_dir, remote_url, silent=False):\n        result = urlparse(remote_url)\n        type_ = result.scheme\n        tag = None\n        if not type_ and remote_url.startswith(\"git+\"):\n            type_ = \"git\"\n            remote_url = remote_url[4:]\n        elif \"+\" in result.scheme:\n            type_, _ = result.scheme.split(\"+\", 1)\n            remote_url = remote_url[len(type_) + 1 :]\n        if \"#\" in remote_url:\n            remote_url, tag = remote_url.rsplit(\"#\", 1)\n        if not type_:\n            raise VCSBaseException(\"VCS: Unknown repository type %s\" % remote_url)\n        try:\n            obj = globals()[\"%sClient\" % type_.capitalize()](\n                src_dir, remote_url, tag, silent\n            )\n            assert isinstance(obj, VCSClientBase)\n            return obj\n        except (KeyError, AssertionError) as exc:\n            raise VCSBaseException(\n                \"VCS: Unknown repository type %s\" % remote_url\n            ) from exc\n\n\nclass VCSClientBase:\n    command = None\n\n    def __init__(self, src_dir, remote_url=None, tag=None, silent=False):\n        self.src_dir = src_dir\n        self.remote_url = remote_url\n        self.tag = tag\n        self.silent = silent\n        self.check_client()\n\n    def check_client(self):\n        try:\n            assert self.command\n            if self.silent:\n                self.get_cmd_output([\"--version\"])\n            else:\n                assert self.run_cmd([\"--version\"])\n        except (AssertionError, OSError) as exc:\n            raise VCSBaseException(\n                \"VCS: `%s` client is not installed in your system\" % self.command\n            ) from exc\n        return True\n\n    @property\n    def storage_dir(self):\n        return os.path.join(self.src_dir, \".\" + self.command)\n\n    def export(self):\n        raise NotImplementedError\n\n    def update(self):\n        raise NotImplementedError\n\n    @property\n    def can_be_updated(self):\n        return not self.tag\n\n    def get_current_revision(self):\n        raise NotImplementedError\n\n    def get_latest_revision(self):\n        return None if self.can_be_updated else self.get_current_revision()\n\n    def run_cmd(self, args, **kwargs):\n        args = [self.command] + args\n        if \"cwd\" not in kwargs:\n            kwargs[\"cwd\"] = self.src_dir\n        if \"env\" not in kwargs:\n            kwargs[\"env\"] = os.environ\n        try:\n            subprocess.check_call(args, **kwargs)\n            return True\n        except subprocess.CalledProcessError as exc:\n            raise VCSBaseException(\n                \"VCS: Could not process command %s\" % exc.cmd\n            ) from exc\n\n    def get_cmd_output(self, args, **kwargs):\n        args = [self.command] + args\n        if \"cwd\" not in kwargs:\n            kwargs[\"cwd\"] = self.src_dir\n        result = proc.exec_command(args, **kwargs)\n        if result[\"returncode\"] == 0:\n            return result[\"out\"].strip()\n        raise VCSBaseException(\n            \"VCS: Could not receive an output from `%s` command (%s)\" % (args, result)\n        )\n\n\nclass GitClient(VCSClientBase):\n    command = \"git\"\n    _configured = False\n\n    def __init__(self, *args, **kwargs):\n        self.configure()\n        super().__init__(*args, **kwargs)\n\n    @classmethod\n    def configure(cls):\n        if cls._configured:\n            return True\n        cls._configured = True\n        try:\n            result = proc.exec_command([cls.command, \"--exec-path\"])\n            if result[\"returncode\"] != 0:\n                return False\n            path = result[\"out\"].strip()\n            if path:\n                proc.append_env_path(\"PATH\", path)\n                return True\n        except (subprocess.CalledProcessError, FileNotFoundError):\n            pass\n        return False\n\n    def check_client(self):\n        try:\n            return VCSClientBase.check_client(self)\n        except UserSideException as exc:\n            raise UserSideException(\n                \"Please install Git client from https://git-scm.com/downloads\"\n            ) from exc\n\n    def get_branches(self):\n        output = self.get_cmd_output([\"branch\"])\n        output = output.replace(\"*\", \"\")  # fix active branch\n        return [b.strip() for b in output.split(\"\\n\")]\n\n    def get_current_branch(self):\n        output = self.get_cmd_output([\"branch\"])\n        for line in output.split(\"\\n\"):\n            line = line.strip()\n            if line.startswith(\"*\"):\n                branch = line[1:].strip()\n                if branch != \"(no branch)\":\n                    return branch\n        return None\n\n    def get_tags(self):\n        output = self.get_cmd_output([\"tag\", \"-l\"])\n        return [t.strip() for t in output.split(\"\\n\")]\n\n    @staticmethod\n    def is_commit_id(text):\n        return text and re.match(r\"[0-9a-f]{7,}$\", text) is not None\n\n    @property\n    def can_be_updated(self):\n        return not self.tag or not self.is_commit_id(self.tag)\n\n    def export(self):\n        is_commit = self.is_commit_id(self.tag)\n        args = [\"clone\", \"--recursive\"]\n        if not self.tag or not is_commit:\n            args += [\"--depth\", \"1\"]\n            if self.tag:\n                args += [\"--branch\", self.tag]\n        args += [self.remote_url, self.src_dir]\n        assert self.run_cmd(args, cwd=os.getcwd())\n        if is_commit:\n            assert self.run_cmd([\"reset\", \"--hard\", self.tag])\n            return self.run_cmd(\n                [\"submodule\", \"update\", \"--init\", \"--recursive\", \"--force\"]\n            )\n        return True\n\n    def update(self):\n        args = [\"pull\", \"--recurse-submodules\"]\n        return self.run_cmd(args)\n\n    def get_current_revision(self):\n        return self.get_cmd_output([\"rev-parse\", \"--short\", \"HEAD\"])\n\n    def get_latest_revision(self):\n        if not self.can_be_updated:\n            return self.get_current_revision()\n        branch = self.get_current_branch()\n        if not branch:\n            return None\n\n        branch_ref = f\"refs/heads/{branch}\"\n        result = self.get_cmd_output([\"ls-remote\", self.remote_url, branch_ref])\n        if not result:\n            return None\n\n        for line in result.split(\"\\n\"):\n            sha, ref = line.strip().split(\"\\t\")\n            if ref == branch_ref:\n                return sha[:7]\n\n        return None\n\n\nclass HgClient(VCSClientBase):\n    command = \"hg\"\n\n    def export(self):\n        args = [\"clone\"]\n        if self.tag:\n            args.extend([\"--updaterev\", self.tag])\n        args.extend([self.remote_url, self.src_dir])\n        return self.run_cmd(args)\n\n    def update(self):\n        args = [\"pull\", \"--update\"]\n        return self.run_cmd(args)\n\n    def get_current_revision(self):\n        return self.get_cmd_output([\"identify\", \"--id\"])\n\n    def get_latest_revision(self):\n        if not self.can_be_updated:\n            return self.get_latest_revision()\n        return self.get_cmd_output([\"identify\", \"--id\", self.remote_url])\n\n\nclass SvnClient(VCSClientBase):\n    command = \"svn\"\n\n    def export(self):\n        args = [\"checkout\"]\n        if self.tag:\n            args.extend([\"--revision\", self.tag])\n        args.extend([self.remote_url, self.src_dir])\n        return self.run_cmd(args)\n\n    def update(self):\n        args = [\"update\"]\n        return self.run_cmd(args)\n\n    def get_current_revision(self):\n        output = self.get_cmd_output(\n            [\"info\", \"--non-interactive\", \"--trust-server-cert\", \"-r\", \"HEAD\"]\n        )\n        for line in output.split(\"\\n\"):\n            line = line.strip()\n            if line.startswith(\"Revision:\"):\n                return line.split(\":\", 1)[1].strip()\n        raise VCSBaseException(\"Could not detect current SVN revision\")\n"
  },
  {
    "path": "platformio/package/version.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\n\nimport semantic_version\n\nfrom platformio.exception import UserSideException\n\n\nclass SemanticVersionError(UserSideException):\n    pass\n\n\ndef cast_version_to_semver(value, force=True, raise_exception=False):\n    assert value\n    try:\n        return semantic_version.Version(value)\n    except ValueError:\n        pass\n    if force:\n        try:\n            return semantic_version.Version.coerce(value)\n        except ValueError:\n            pass\n    if raise_exception:\n        raise SemanticVersionError(\"Invalid SemVer version %s\" % value)\n    # parse commit hash\n    if re.match(r\"^[\\da-f]+$\", value, flags=re.I):\n        return semantic_version.Version(\"0.0.0+sha.\" + value)\n    return semantic_version.Version(\"0.0.0+\" + value)\n\n\ndef pepver_to_semver(pepver):\n    return cast_version_to_semver(\n        re.sub(r\"(\\.\\d+)\\.?(dev|a|b|rc|post)\", r\"\\1-\\2.\", pepver, count=1)\n    )\n\n\ndef get_original_version(version):\n    if version.count(\".\") != 2:\n        return None\n    _, raw = version.split(\".\")[:2]\n    if int(raw) <= 99:\n        return None\n    if int(raw) <= 9999:\n        return \"%s.%s\" % (raw[:-2], int(raw[-2:]))\n    return \"%s.%s.%s\" % (raw[:-4], int(raw[-4:-2]), int(raw[-2:]))\n"
  },
  {
    "path": "platformio/platform/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/platform/_packages.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.package.meta import PackageSpec\n\n\nclass PlatformPackagesMixin:\n    def get_package_spec(self, name, version=None):\n        return PackageSpec(\n            owner=self.packages[name].get(\"owner\"),\n            name=name,\n            requirements=version or self.packages[name].get(\"version\"),\n        )\n\n    def get_package(self, name, spec=None):\n        if not name:\n            return None\n        return self.pm.get_package(spec or self.get_package_spec(name))\n\n    def get_package_dir(self, name):\n        pkg = self.get_package(name)\n        return pkg.path if pkg else None\n\n    def get_package_version(self, name):\n        pkg = self.get_package(name)\n        return str(pkg.metadata.version) if pkg else None\n\n    def get_installed_packages(self, with_optional=True, with_optional_versions=False):\n        result = []\n        for name, options in dict(sorted(self.packages.items())).items():\n            if not with_optional and options.get(\"optional\"):\n                continue\n            versions = [options.get(\"version\")]\n            if with_optional_versions:\n                versions.extend(options.get(\"optionalVersions\", []))\n            for version in versions:\n                if not version:\n                    continue\n                pkg = self.get_package(name, self.get_package_spec(name, version))\n                if pkg:\n                    result.append(pkg)\n        return result\n\n    def dump_used_packages(self):\n        result = []\n        for name, options in self.packages.items():\n            if options.get(\"optional\"):\n                continue\n            pkg = self.get_package(name)\n            if not pkg or not pkg.metadata:\n                continue\n            item = {\"name\": pkg.metadata.name, \"version\": str(pkg.metadata.version)}\n            if pkg.metadata.spec.external:\n                item[\"src_url\"] = pkg.metadata.spec.uri\n            result.append(item)\n        return result\n\n    def install_package(self, name, spec=None, force=False):\n        return self.pm.install(spec or self.get_package_spec(name), force=force)\n\n    def install_required_packages(self, force=False):\n        for name, options in self.packages.items():\n            if options.get(\"optional\"):\n                continue\n            self.install_package(name, force=force)\n\n    def uninstall_packages(self):\n        for pkg in self.get_installed_packages():\n            self.pm.uninstall(pkg)\n\n    def update_packages(self):\n        for pkg in self.get_installed_packages():\n            self.pm.update(pkg, to_spec=self.get_package_spec(pkg.metadata.name))\n\n    def are_outdated_packages(self):\n        for pkg in self.get_installed_packages():\n            if self.pm.outdated(\n                pkg, self.get_package_spec(pkg.metadata.name)\n            ).is_outdated(allow_incompatible=False):\n                return True\n        return False\n"
  },
  {
    "path": "platformio/platform/_run.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport base64\nimport json\nimport os\nimport re\nimport sys\nfrom urllib.parse import quote\n\nimport click\n\nfrom platformio import app, fs, proc, telemetry\nfrom platformio.compat import hashlib_encode_data\nfrom platformio.package.manager.core import get_core_package_dir\nfrom platformio.platform.exception import BuildScriptNotFound\nfrom platformio.run.helpers import KNOWN_CLEAN_TARGETS, KNOWN_FULLCLEAN_TARGETS\n\n\nclass PlatformRunMixin:\n    LINE_ERROR_RE = re.compile(r\"(^|\\s+)error:?\\s+\", re.I)\n\n    @staticmethod\n    def encode_scons_arg(value):\n        if isinstance(value, (list, tuple, dict)):\n            value = json.dumps(value)\n        return base64.urlsafe_b64encode(hashlib_encode_data(value)).decode()\n\n    @staticmethod\n    def decode_scons_arg(data):\n        value = base64.urlsafe_b64decode(data).decode()\n        if value.startswith((\"[\", \"{\")):\n            value = json.loads(value)\n        return value\n\n    def run(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self, variables, targets, silent, verbose, jobs\n    ):\n        assert isinstance(variables, dict)\n        assert isinstance(targets, list)\n\n        self.ensure_engine_compatible()\n\n        self.silent = silent\n        self.verbose = verbose or app.get_setting(\"force_verbose\")\n\n        if \"build_script\" not in variables:\n            variables[\"build_script\"] = self.get_build_script()\n        if not os.path.isfile(variables[\"build_script\"]):\n            raise BuildScriptNotFound(variables[\"build_script\"])\n\n        telemetry.log_platform_run(self, self.config, variables[\"pioenv\"], targets)\n        result = self._run_scons(variables, targets, jobs)\n\n        assert \"returncode\" in result\n\n        return result\n\n    def _run_scons(self, variables, targets, jobs):\n        scons_dir = get_core_package_dir(\"tool-scons\")\n        args = [\n            proc.get_pythonexe_path(),\n            os.path.join(scons_dir, \"scons.py\"),\n            \"-Q\",\n            \"--warn=no-no-parallel-support\",\n            \"--jobs\",\n            str(jobs),\n            \"--sconstruct\",\n            os.path.join(fs.get_source_dir(), \"builder\", \"main.py\"),\n        ]\n        args.append(\"PIOVERBOSE=%d\" % int(self.verbose))\n        # pylint: disable=protected-access\n        args.append(\"ISATTY=%d\" % int(click._compat.isatty(sys.stdout)))\n        # encode and append variables\n        for key, value in variables.items():\n            args.append(\"%s=%s\" % (key.upper(), self.encode_scons_arg(value)))\n\n        if set(KNOWN_CLEAN_TARGETS + KNOWN_FULLCLEAN_TARGETS) & set(targets):\n            args.append(\"--clean\")\n            args.append(\n                \"FULLCLEAN=%d\"\n                % (1 if set(KNOWN_FULLCLEAN_TARGETS) & set(targets) else 0)\n            )\n        elif targets:\n            args.extend(targets)\n\n        # force SCons output to Unicode\n        os.environ[\"PYTHONIOENCODING\"] = \"utf-8\"\n\n        if targets and \"menuconfig\" in targets:\n            return proc.exec_command(\n                args, stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin\n            )\n\n        if click._compat.isatty(sys.stdout):\n\n            def _write_and_flush(stream, data):\n                try:\n                    stream.write(data)\n                    stream.flush()\n                except IOError:\n                    pass\n\n            return proc.exec_command(\n                args,\n                stdout=proc.BuildAsyncPipe(\n                    line_callback=self._on_stdout_line,\n                    data_callback=lambda data: (\n                        None if self.silent else _write_and_flush(sys.stdout, data)\n                    ),\n                ),\n                stderr=proc.BuildAsyncPipe(\n                    line_callback=self._on_stderr_line,\n                    data_callback=lambda data: _write_and_flush(sys.stderr, data),\n                ),\n            )\n\n        return proc.exec_command(\n            args,\n            stdout=proc.LineBufferedAsyncPipe(line_callback=self._on_stdout_line),\n            stderr=proc.LineBufferedAsyncPipe(line_callback=self._on_stderr_line),\n        )\n\n    def _on_stdout_line(self, line):\n        if \"`buildprog' is up to date.\" in line:\n            return\n        self._echo_line(line, level=1)\n\n    def _on_stderr_line(self, line):\n        is_error = self.LINE_ERROR_RE.search(line) is not None\n        self._echo_line(line, level=3 if is_error else 2)\n\n        a_pos = line.find(\"fatal error:\")\n        b_pos = line.rfind(\": No such file or directory\")\n        if a_pos == -1 or b_pos == -1:\n            return\n        self._echo_missed_dependency(line[a_pos + 12 : b_pos].strip())\n\n    def _echo_line(self, line, level):\n        if line.startswith(\"scons: \"):\n            line = line[7:]\n        assert 1 <= level <= 3\n        if self.silent and (level < 2 or not line):\n            return\n        fg = (None, \"yellow\", \"red\")[level - 1]\n        if level == 1 and \"is up to date\" in line:\n            fg = \"green\"\n        click.secho(line, fg=fg, err=level > 1, nl=False)\n\n    @staticmethod\n    def _echo_missed_dependency(filename):\n        if \"/\" in filename or not filename.endswith((\".h\", \".hpp\")):\n            return\n        banner = \"\"\"\n{dots}\n* Looking for {filename_styled} dependency? Check our library registry!\n*\n* CLI  > platformio lib search \"header:{filename}\"\n* Web  > {link}\n*\n{dots}\n\"\"\".format(\n            filename=filename,\n            filename_styled=click.style(filename, fg=\"cyan\"),\n            link=click.style(\n                \"https://registry.platformio.org/search?q=header:%s\"\n                % quote(filename, safe=\"\"),\n                fg=\"blue\",\n            ),\n            dots=\"*\" * (56 + len(filename)),\n        )\n        click.echo(banner, err=True)\n"
  },
  {
    "path": "platformio/platform/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport semantic_version\n\nfrom platformio import __version__, fs\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.version import pepver_to_semver\nfrom platformio.platform._packages import PlatformPackagesMixin\nfrom platformio.platform._run import PlatformRunMixin\nfrom platformio.platform.board import PlatformBoardConfig\nfrom platformio.platform.exception import IncompatiblePlatform, UnknownBoard\nfrom platformio.project.config import ProjectConfig\n\n\nclass PlatformBase(  # pylint: disable=too-many-instance-attributes,too-many-public-methods\n    PlatformPackagesMixin, PlatformRunMixin\n):\n    CORE_SEMVER = pepver_to_semver(__version__)\n    _BOARDS_CACHE = {}\n\n    def __init__(self, manifest_path):\n        self.manifest_path = manifest_path\n        self.project_env = None  # set by factory.from_env(env)\n        self.silent = False\n        self.verbose = False\n\n        self._manifest = fs.load_json(manifest_path)\n        self._BOARDS_CACHE = {}\n        self._custom_packages = None\n\n        self.config = ProjectConfig.get_instance()\n        self.pm = ToolPackageManager(self.config.get(\"platformio\", \"packages_dir\"))\n\n    @property\n    def name(self):\n        return self._manifest[\"name\"]\n\n    @property\n    def title(self):\n        return self._manifest[\"title\"]\n\n    @property\n    def description(self):\n        return self._manifest[\"description\"]\n\n    @property\n    def version(self):\n        return self._manifest[\"version\"]\n\n    @property\n    def homepage(self):\n        return self._manifest.get(\"homepage\")\n\n    @property\n    def repository_url(self):\n        return self._manifest.get(\"repository\", {}).get(\"url\")\n\n    @property\n    def license(self):\n        return self._manifest.get(\"license\")\n\n    @property\n    def frameworks(self):\n        return self._manifest.get(\"frameworks\")\n\n    @property\n    def engines(self):\n        return self._manifest.get(\"engines\")\n\n    @property\n    def manifest(self):\n        return self._manifest\n\n    @property\n    def packages(self):\n        packages = self._manifest.get(\"packages\", {})\n        for item in self._custom_packages or []:\n            name = item\n            version = \"*\"\n            if \"@\" in item:\n                name, version = item.split(\"@\", 1)\n            spec = self.pm.ensure_spec(name)\n            options = {\"version\": version.strip(), \"optional\": False}\n            if spec.owner:\n                options[\"owner\"] = spec.owner\n            if spec.name not in packages:\n                packages[spec.name] = {}\n            packages[spec.name].update(**options)\n        return packages\n\n    def ensure_engine_compatible(self):\n        if not self.engines or \"platformio\" not in self.engines:\n            return True\n        core_spec = semantic_version.SimpleSpec(self.engines[\"platformio\"])\n        if self.CORE_SEMVER in core_spec:\n            return True\n        # PIO Core 6 is compatible with dev-platforms for PIO Core 2.0, 3.0, 4.0\n        if any(\n            semantic_version.Version.coerce(str(v)) in core_spec for v in (2, 3, 4, 5)\n        ):\n            return True\n        raise IncompatiblePlatform(self.name, str(self.CORE_SEMVER), str(core_spec))\n\n    def get_dir(self):\n        return os.path.dirname(self.manifest_path)\n\n    def get_build_script(self):\n        main_script = os.path.join(self.get_dir(), \"builder\", \"main.py\")\n        if os.path.isfile(main_script):\n            return main_script\n        raise NotImplementedError()\n\n    def is_embedded(self):\n        for opts in self.packages.values():\n            if opts.get(\"type\") == \"uploader\":\n                return True\n        return False\n\n    def get_boards(self, id_=None):\n        def _append_board(board_id, manifest_path):\n            config = PlatformBoardConfig(manifest_path)\n            if \"platform\" in config and config.get(\"platform\") != self.name:\n                return\n            if \"platforms\" in config and self.name not in config.get(\"platforms\"):\n                return\n            config.manifest[\"platform\"] = self.name\n            self._BOARDS_CACHE[board_id] = config\n\n        bdirs = [\n            self.config.get(\"platformio\", \"boards_dir\"),\n            os.path.join(self.config.get(\"platformio\", \"core_dir\"), \"boards\"),\n            os.path.join(self.get_dir(), \"boards\"),\n        ]\n\n        if id_ is None:\n            for boards_dir in bdirs:\n                if not os.path.isdir(boards_dir):\n                    continue\n                for item in sorted(os.listdir(boards_dir)):\n                    _id = item[:-5]\n                    if not item.endswith(\".json\") or _id in self._BOARDS_CACHE:\n                        continue\n                    _append_board(_id, os.path.join(boards_dir, item))\n        else:\n            if id_ not in self._BOARDS_CACHE:\n                for boards_dir in bdirs:\n                    if not os.path.isdir(boards_dir):\n                        continue\n                    manifest_path = os.path.join(boards_dir, \"%s.json\" % id_)\n                    if os.path.isfile(manifest_path):\n                        _append_board(id_, manifest_path)\n                        break\n            if id_ not in self._BOARDS_CACHE:\n                raise UnknownBoard(id_)\n        return self._BOARDS_CACHE[id_] if id_ else self._BOARDS_CACHE\n\n    def board_config(self, id_):\n        assert id_\n        return self.get_boards(id_)\n\n    def get_package_type(self, name):\n        return self.packages[name].get(\"type\")\n\n    def configure_project_packages(self, env, targets=None):\n        options = self.config.items(env=env, as_dict=True)\n        if \"framework\" in options:\n            # support PIO Core 3.0 dev/platforms\n            options[\"pioframework\"] = options[\"framework\"]\n        # override user custom packages\n        self._custom_packages = options.get(\"platform_packages\")\n        self.configure_default_packages(options, targets or [])\n\n    def configure_default_packages(self, options, targets):\n        # enable used frameworks\n        for framework in options.get(\"framework\", []):\n            if not self.frameworks:\n                continue\n            framework = framework.lower().strip()\n            if not framework or framework not in self.frameworks:\n                continue\n            _pkg_name = self.frameworks[framework].get(\"package\")\n            if _pkg_name:\n                self.packages[_pkg_name][\"optional\"] = False\n\n        # enable upload tools for upload targets\n        if any([\"upload\" in t for t in targets] + [\"program\" in targets]):\n            for name, opts in self.packages.items():\n                if opts.get(\"type\") == \"uploader\":\n                    self.packages[name][\"optional\"] = False\n                # skip all packages in \"nobuild\" mode\n                # allow only upload tools and frameworks\n                elif \"nobuild\" in targets and opts.get(\"type\") != \"framework\":\n                    self.packages[name][\"optional\"] = True\n\n    def configure_debug_session(self, debug_config):\n        raise NotImplementedError\n\n    def generate_sample_code(self, project_config, environment):\n        raise NotImplementedError\n\n    def on_installed(self):\n        pass\n\n    def on_uninstalled(self):\n        pass\n\n    def get_lib_storages(self):\n        storages = {}\n        for opts in (self.frameworks or {}).values():\n            if \"package\" not in opts:\n                continue\n            pkg = self.get_package(opts[\"package\"])\n            if not pkg or not os.path.isdir(os.path.join(pkg.path, \"libraries\")):\n                continue\n            libs_dir = os.path.join(pkg.path, \"libraries\")\n            storages[libs_dir] = opts[\"package\"]\n            libcores_dir = os.path.join(libs_dir, \"__cores__\")\n            if not os.path.isdir(libcores_dir):\n                continue\n            for item in os.listdir(libcores_dir):\n                libcore_dir = os.path.join(libcores_dir, item)\n                if not os.path.isdir(libcore_dir):\n                    continue\n                storages[libcore_dir] = \"%s-core-%s\" % (opts[\"package\"], item)\n\n        return [dict(name=name, path=path) for path, name in storages.items()]\n"
  },
  {
    "path": "platformio/platform/board.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio import fs, util\nfrom platformio.compat import MISSING\nfrom platformio.debug.exception import DebugInvalidOptionsError, DebugSupportError\nfrom platformio.exception import InvalidJSONFile, UserSideException\nfrom platformio.platform.exception import InvalidBoardManifest\n\n\nclass PlatformBoardConfig:\n    def __init__(self, manifest_path):\n        self._id = os.path.basename(manifest_path)[:-5]\n        assert os.path.isfile(manifest_path)\n        self.manifest_path = manifest_path\n        try:\n            self._manifest = fs.load_json(manifest_path)\n        except InvalidJSONFile as exc:\n            raise InvalidBoardManifest(manifest_path) from exc\n        if not set([\"name\", \"url\", \"vendor\"]) <= set(self._manifest):\n            raise UserSideException(\n                \"Please specify name, url and vendor fields for \" + manifest_path\n            )\n\n    def get(self, path, default=MISSING):\n        try:\n            value = self._manifest\n            for k in path.split(\".\"):\n                value = value[k]\n            return value\n        except KeyError:\n            if default != MISSING:\n                return default\n        raise KeyError(\"Invalid board option '%s'\" % path)\n\n    def update(self, path, value):\n        newdict = None\n        for key in path.split(\".\")[::-1]:\n            if newdict is None:\n                newdict = {key: value}\n            else:\n                newdict = {key: newdict}\n        util.merge_dicts(self._manifest, newdict)\n\n    def __contains__(self, key):\n        try:\n            self.get(key)\n            return True\n        except KeyError:\n            return False\n\n    @property\n    def id(self):\n        return self._id\n\n    @property\n    def id_(self):\n        return self.id\n\n    @property\n    def manifest(self):\n        return self._manifest\n\n    def get_brief_data(self):\n        result = {\n            \"id\": self.id,\n            \"name\": self._manifest[\"name\"],\n            \"platform\": self._manifest.get(\"platform\"),\n            \"mcu\": self._manifest.get(\"build\", {}).get(\"mcu\", \"\").upper(),\n            \"fcpu\": int(\n                \"\".join(\n                    [\n                        c\n                        for c in str(self._manifest.get(\"build\", {}).get(\"f_cpu\", \"0L\"))\n                        if c.isdigit()\n                    ]\n                )\n            ),\n            \"ram\": self._manifest.get(\"upload\", {}).get(\"maximum_ram_size\", 0),\n            \"rom\": self._manifest.get(\"upload\", {}).get(\"maximum_size\", 0),\n            \"frameworks\": self._manifest.get(\"frameworks\"),\n            \"vendor\": self._manifest[\"vendor\"],\n            \"url\": self._manifest[\"url\"],\n        }\n        if self._manifest.get(\"connectivity\"):\n            result[\"connectivity\"] = self._manifest.get(\"connectivity\")\n        debug = self.get_debug_data()\n        if debug:\n            result[\"debug\"] = debug\n        return result\n\n    def get_debug_data(self):\n        if not self._manifest.get(\"debug\", {}).get(\"tools\"):\n            return None\n        tools = {}\n        for name, options in self._manifest[\"debug\"][\"tools\"].items():\n            tools[name] = {}\n            for key, value in options.items():\n                if key in (\"default\", \"onboard\") and value:\n                    tools[name][key] = value\n        return {\"tools\": tools}\n\n    def get_debug_tool_name(self, custom=None):\n        debug_tools = self._manifest.get(\"debug\", {}).get(\"tools\")\n        tool_name = custom\n        if tool_name == \"custom\":\n            return tool_name\n        if not debug_tools:\n            raise DebugSupportError(self._manifest[\"name\"])\n        if tool_name:\n            if tool_name in debug_tools:\n                return tool_name\n            raise DebugInvalidOptionsError(\n                \"Unknown debug tool `%s`. Please use one of `%s` or `custom`\"\n                % (tool_name, \", \".join(sorted(list(debug_tools))))\n            )\n\n        # automatically select best tool\n        data = {\"default\": [], \"onboard\": [], \"external\": []}\n        for key, value in debug_tools.items():\n            if value.get(\"default\"):\n                data[\"default\"].append(key)\n            elif value.get(\"onboard\"):\n                data[\"onboard\"].append(key)\n            data[\"external\"].append(key)\n\n        for key, value in data.items():\n            if not value:\n                continue\n            return sorted(value)[0]\n\n        assert any(item for item in data)\n"
  },
  {
    "path": "platformio/platform/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.exception import UserSideException\n\n\nclass PlatformException(UserSideException):\n    pass\n\n\nclass UnknownPlatform(PlatformException):\n    MESSAGE = \"Unknown development platform '{0}'\"\n\n\nclass IncompatiblePlatform(PlatformException):\n    MESSAGE = (\n        \"Development platform '{0}' is not compatible with PlatformIO Core v{1} and \"\n        \"depends on PlatformIO Core {2}.\\n\"\n    )\n\n\nclass UnknownBoard(PlatformException):\n    MESSAGE = \"Unknown board ID '{0}'\"\n\n\nclass InvalidBoardManifest(PlatformException):\n    MESSAGE = \"Invalid board JSON manifest '{0}'\"\n\n\nclass UnknownFramework(PlatformException):\n    MESSAGE = \"Unknown framework '{0}'\"\n\n\nclass BuildScriptNotFound(PlatformException):\n    MESSAGE = \"Invalid path '{0}' to build script\"\n"
  },
  {
    "path": "platformio/platform/factory.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport sys\n\nfrom platformio import fs\nfrom platformio.compat import load_python_module\nfrom platformio.package.meta import PackageItem\nfrom platformio.platform import base\nfrom platformio.platform.exception import UnknownPlatform\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import UndefinedEnvPlatformError\n\n\nclass PlatformFactory:\n    @staticmethod\n    def get_clsname(name):\n        name = re.sub(r\"[^\\da-z\\_]+\", \"\", name, flags=re.I)\n        return \"%sPlatform\" % name.lower().capitalize()\n\n    @staticmethod\n    def load_platform_module(name, path):\n        # backward compatibility with the legacy dev-platforms\n        sys.modules[\"platformio.managers.platform\"] = base\n        try:\n            return load_python_module(\"platformio.platform.%s\" % name, path)\n        except ImportError as exc:\n            raise UnknownPlatform(name) from exc\n\n    @classmethod\n    def new(cls, pkg_or_spec, autoinstall=False) -> base.PlatformBase:\n        # pylint: disable=import-outside-toplevel\n        from platformio.package.manager.platform import PlatformPackageManager\n\n        platform_dir = None\n        platform_name = None\n        if isinstance(pkg_or_spec, PackageItem):\n            platform_dir = pkg_or_spec.path\n            platform_name = pkg_or_spec.metadata.name\n        elif isinstance(pkg_or_spec, (str, bytes)) and os.path.isdir(pkg_or_spec):\n            platform_dir = pkg_or_spec\n        else:\n            pkg = PlatformPackageManager().get_package(pkg_or_spec)\n            if pkg:\n                platform_dir = pkg.path\n                platform_name = pkg.metadata.name\n\n        if not platform_dir or not os.path.isfile(\n            os.path.join(platform_dir, \"platform.json\")\n        ):\n            if autoinstall:\n                return cls.new(\n                    PlatformPackageManager().install(\n                        pkg_or_spec, skip_dependencies=True\n                    )\n                )\n            raise UnknownPlatform(pkg_or_spec)\n\n        if not platform_name:\n            platform_name = fs.load_json(os.path.join(platform_dir, \"platform.json\"))[\n                \"name\"\n            ]\n\n        platform_cls = None\n        if os.path.isfile(os.path.join(platform_dir, \"platform.py\")):\n            platform_cls = getattr(\n                cls.load_platform_module(\n                    platform_name, os.path.join(platform_dir, \"platform.py\")\n                ),\n                cls.get_clsname(platform_name),\n            )\n        else:\n            platform_cls = type(\n                str(cls.get_clsname(platform_name)), (base.PlatformBase,), {}\n            )\n\n        _instance = platform_cls(os.path.join(platform_dir, \"platform.json\"))\n        assert isinstance(_instance, base.PlatformBase)\n        return _instance\n\n    @classmethod\n    def from_env(cls, env, targets=None, autoinstall=False):\n        config = ProjectConfig.get_instance()\n        spec = config.get(f\"env:{env}\", \"platform\", None)\n        if not spec:\n            raise UndefinedEnvPlatformError(env)\n        p = cls.new(spec, autoinstall=autoinstall)\n        p.project_env = env\n        p.configure_project_packages(env, targets)\n        return p\n"
  },
  {
    "path": "platformio/proc.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport subprocess\nimport sys\nfrom contextlib import contextmanager\nfrom threading import Thread\n\nfrom platformio import exception\nfrom platformio.compat import (\n    IS_WINDOWS,\n    get_filesystem_encoding,\n    get_locale_encoding,\n    string_types,\n)\n\n\nclass AsyncPipeBase:\n    def __init__(self):\n        self._fd_read, self._fd_write = os.pipe()\n        self._pipe_reader = os.fdopen(\n            self._fd_read, encoding=\"utf-8\", errors=\"backslashreplace\"\n        )\n        self._buffer = \"\"\n        self._thread = Thread(target=self.run)\n        self._thread.start()\n\n    def get_buffer(self):\n        return self._buffer\n\n    def fileno(self):\n        return self._fd_write\n\n    def run(self):\n        try:\n            self.do_reading()\n        except (KeyboardInterrupt, SystemExit, IOError):\n            self.close()\n\n    def do_reading(self):\n        raise NotImplementedError()\n\n    def close(self):\n        self._buffer = \"\"\n        os.close(self._fd_write)\n        self._thread.join()\n\n\nclass BuildAsyncPipe(AsyncPipeBase):\n    def __init__(self, line_callback, data_callback):\n        self.line_callback = line_callback\n        self.data_callback = data_callback\n        super().__init__()\n\n    def do_reading(self):\n        line = \"\"\n        print_immediately = False\n\n        for char in iter(lambda: self._pipe_reader.read(1), \"\"):\n            # self._buffer += char\n\n            if line and char.strip() and line[-3:] == (char * 3):\n                print_immediately = True\n\n            if print_immediately:\n                # leftover bytes\n                if line:\n                    self.data_callback(line)\n                    line = \"\"\n                self.data_callback(char)\n                if char == \"\\n\":\n                    print_immediately = False\n            else:\n                line += char\n                if char != \"\\n\":\n                    continue\n                self.line_callback(line)\n                line = \"\"\n\n        self._pipe_reader.close()\n\n\nclass LineBufferedAsyncPipe(AsyncPipeBase):\n    def __init__(self, line_callback):\n        self.line_callback = line_callback\n        super().__init__()\n\n    def do_reading(self):\n        for line in iter(self._pipe_reader.readline, \"\"):\n            self._buffer += line\n            self.line_callback(line)\n        self._pipe_reader.close()\n\n\ndef exec_command(*args, **kwargs):\n    result = {\"out\": None, \"err\": None, \"returncode\": None}\n\n    default = dict(stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    default.update(kwargs)\n    kwargs = default\n\n    with subprocess.Popen(*args, **kwargs) as p:\n        try:\n            result[\"out\"], result[\"err\"] = p.communicate()\n            result[\"returncode\"] = p.returncode\n        except KeyboardInterrupt as exc:\n            raise exception.AbortedByUser() from exc\n        finally:\n            for s in (\"stdout\", \"stderr\"):\n                if isinstance(kwargs[s], AsyncPipeBase):\n                    kwargs[s].close()  # pylint: disable=no-member\n\n    for s in (\"stdout\", \"stderr\"):\n        if isinstance(kwargs[s], AsyncPipeBase):\n            result[s[3:]] = kwargs[s].get_buffer()  # pylint: disable=no-member\n\n    for key, value in result.items():\n        if isinstance(value, bytes):\n            try:\n                result[key] = value.decode(\n                    get_locale_encoding() or get_filesystem_encoding()\n                )\n            except UnicodeDecodeError:\n                result[key] = value.decode(\"latin-1\")\n        if value and isinstance(value, string_types):\n            result[key] = value.strip()\n\n    return result\n\n\n@contextmanager\ndef capture_std_streams(stdout, stderr=None):\n    _stdout = sys.stdout\n    _stderr = sys.stderr\n    sys.stdout = stdout\n    sys.stderr = stderr or stdout\n    yield\n    sys.stdout = _stdout\n    sys.stderr = _stderr\n\n\ndef is_ci():\n    return os.getenv(\"CI\", \"\").lower() == \"true\"\n\n\ndef is_container():\n    if os.path.exists(\"/.dockerenv\"):\n        return True\n    if not os.path.isfile(\"/proc/1/cgroup\"):\n        return False\n    with open(\"/proc/1/cgroup\", encoding=\"utf8\") as fp:\n        return \":/docker/\" in fp.read()\n\n\ndef get_pythonexe_path():\n    return os.environ.get(\"PYTHONEXEPATH\", os.path.normpath(sys.executable))\n\n\ndef copy_pythonpath_to_osenv():\n    _PYTHONPATH = []\n    if \"PYTHONPATH\" in os.environ:\n        _PYTHONPATH = os.environ.get(\"PYTHONPATH\").split(os.pathsep)\n    for p in os.sys.path:\n        conditions = [p not in _PYTHONPATH]\n        if not IS_WINDOWS:\n            conditions.append(\n                os.path.isdir(os.path.join(p, \"click\"))\n                or os.path.isdir(os.path.join(p, \"platformio\"))\n            )\n        if all(conditions):\n            _PYTHONPATH.append(p)\n    os.environ[\"PYTHONPATH\"] = os.pathsep.join(_PYTHONPATH)\n\n\ndef where_is_program(program, envpath=None):\n    env = os.environ.copy()\n    if envpath:\n        env[\"PATH\"] = envpath\n\n    # look up in $PATH\n    for bin_dir in env.get(\"PATH\", \"\").split(os.pathsep):\n        if os.path.isfile(os.path.join(bin_dir, program)):\n            return os.path.join(bin_dir, program)\n        if IS_WINDOWS and os.path.isfile(os.path.join(bin_dir, \"%s.exe\" % program)):\n            return os.path.join(bin_dir, \"%s.exe\" % program)\n\n    # try OS's built-in commands\n    try:\n        result = exec_command([\"where\" if IS_WINDOWS else \"which\", program], env=env)\n        if result[\"returncode\"] == 0 and os.path.isfile(result[\"out\"].strip()):\n            return result[\"out\"].strip()\n    except OSError:\n        pass\n\n    return program\n\n\ndef append_env_path(name, value):\n    cur_value = os.environ.get(name) or \"\"\n    if cur_value and value in cur_value.split(os.pathsep):\n        return cur_value\n    os.environ[name] = os.pathsep.join([cur_value, value])\n    return os.environ[name]\n\n\ndef force_exit(code=0):\n    os._exit(code)  # pylint: disable=protected-access\n"
  },
  {
    "path": "platformio/project/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/project/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.project.commands.config import project_config_cmd\nfrom platformio.project.commands.init import project_init_cmd\nfrom platformio.project.commands.metadata import project_metadata_cmd\n\n\n@click.group(\n    \"project\",\n    commands=[\n        project_config_cmd,\n        project_init_cmd,\n        project_metadata_cmd,\n    ],\n    short_help=\"Project Manager\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/project/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/project/commands/config.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import NotPlatformIOProjectError\nfrom platformio.project.helpers import is_platformio_project\n\n\n@click.command(\"config\", short_help=\"Show computed configuration\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"--lint\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\ndef project_config_cmd(project_dir, lint, json_output):\n    if not is_platformio_project(project_dir):\n        raise NotPlatformIOProjectError(project_dir)\n    with fs.cd(project_dir):\n        if lint:\n            return lint_configuration(json_output)\n        return print_configuration(json_output)\n\n\ndef print_configuration(json_output=False):\n    config = ProjectConfig.get_instance()\n    if json_output:\n        return click.echo(config.to_json())\n    click.echo(\n        \"Computed project configuration for %s\" % click.style(os.getcwd(), fg=\"cyan\")\n    )\n    for section, options in config.as_tuple():\n        click.secho(section, fg=\"cyan\")\n        click.echo(\"-\" * len(section))\n        click.echo(\n            tabulate(\n                [\n                    (name, \"=\", \"\\n\".join(value) if isinstance(value, list) else value)\n                    for name, value in options\n                ],\n                tablefmt=\"plain\",\n            )\n        )\n        click.echo()\n    return None\n\n\ndef lint_configuration(json_output=False):\n    result = ProjectConfig.lint()\n    errors = result[\"errors\"]\n    warnings = result[\"warnings\"]\n    if json_output:\n        return click.echo(result)\n    if not errors and not warnings:\n        return click.secho(\n            'The \"platformio.ini\" configuration file is free from linting errors.',\n            fg=\"green\",\n        )\n    if errors:\n        click.echo(\n            tabulate(\n                [\n                    (\n                        click.style(error[\"type\"], fg=\"red\"),\n                        error[\"message\"],\n                        (\n                            error.get(\"source\", \"\") + (f\":{error.get('lineno')}\")\n                            if \"lineno\" in error\n                            else \"\"\n                        ),\n                    )\n                    for error in errors\n                ],\n                tablefmt=\"plain\",\n            )\n        )\n    if warnings:\n        click.echo(\n            tabulate(\n                [\n                    (click.style(\"Warning\", fg=\"yellow\"), warning)\n                    for warning in warnings\n                ],\n                tablefmt=\"plain\",\n            )\n        )\n    return None\n"
  },
  {
    "path": "platformio/project/commands/init.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=line-too-long,too-many-arguments,too-many-locals\n\n\nimport json\nimport os\n\nimport click\n\nfrom platformio import fs\nfrom platformio.package.commands.install import install_project_dependencies\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.platform.exception import UnknownBoard\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import UndefinedEnvPlatformError\nfrom platformio.project.helpers import is_platformio_project\nfrom platformio.project.integration.generator import ProjectGenerator\nfrom platformio.project.options import ProjectOptions\n\n\ndef validate_boards(ctx, param, value):  # pylint: disable=unused-argument\n    pm = PlatformPackageManager()\n    for id_ in value:\n        try:\n            pm.board_config(id_)\n        except UnknownBoard as exc:\n            raise click.BadParameter(\n                \"`%s`. Please search for board ID using `platformio boards` \"\n                \"command\" % id_\n            ) from exc\n    return value\n\n\n@click.command(\"init\", short_help=\"Initialize a project or update existing\")\n@click.option(\n    \"--project-dir\",\n    \"-d\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n)\n@click.option(\n    \"-b\", \"--board\", \"boards\", multiple=True, metavar=\"ID\", callback=validate_boards\n)\n@click.option(\"--ide\", type=click.Choice(ProjectGenerator.get_supported_ides()))\n@click.option(\"-e\", \"--environment\", help=\"Update existing environment\")\n@click.option(\n    \"-O\",\n    \"--project-option\",\n    \"project_options\",\n    multiple=True,\n    help=\"A `name=value` pair\",\n)\n@click.option(\"--sample-code\", is_flag=True)\n@click.option(\"--no-install-dependencies\", is_flag=True)\n@click.option(\"--env-prefix\", default=\"\")\n@click.option(\"-s\", \"--silent\", is_flag=True)\ndef project_init_cmd(  # pylint: disable=too-many-positional-arguments\n    project_dir,\n    boards,\n    ide,\n    environment,\n    project_options,\n    sample_code,\n    no_install_dependencies,\n    env_prefix,\n    silent,\n):\n    project_dir = os.path.abspath(project_dir)\n    is_new_project = not is_platformio_project(project_dir)\n    if is_new_project:\n        if not silent:\n            print_header(project_dir)\n        init_base_project(project_dir)\n\n    with fs.cd(project_dir):\n        if environment:\n            update_project_env(environment, project_options)\n        elif boards:\n            update_board_envs(project_dir, boards, project_options, env_prefix)\n\n        generator = None\n        config = ProjectConfig.get_instance(os.path.join(project_dir, \"platformio.ini\"))\n        if ide:\n            config.validate()\n            # init generator and pick the best env if user didn't specify\n            generator = ProjectGenerator(config, environment, ide, boards)\n            if not environment:\n                environment = generator.env_name\n\n        # resolve project dependencies\n        if not no_install_dependencies and (environment or boards):\n            install_project_dependencies(\n                options=dict(\n                    project_dir=project_dir,\n                    environments=[environment] if environment else [],\n                    silent=silent,\n                )\n            )\n\n        if environment and sample_code:\n            init_sample_code(config, environment)\n\n        if generator:\n            if not silent:\n                click.echo(\n                    \"Updating metadata for the %s IDE...\" % click.style(ide, fg=\"cyan\")\n                )\n            generator.generate()\n\n        if is_new_project:\n            init_cvs_ignore()\n\n    if not silent:\n        print_footer(is_new_project)\n\n\ndef print_header(project_dir):\n    click.echo(\"The following files/directories have been created in \", nl=False)\n    try:\n        click.secho(project_dir, fg=\"cyan\")\n    except UnicodeEncodeError:\n        click.secho(json.dumps(project_dir), fg=\"cyan\")\n    click.echo(\"%s - Put project header files here\" % click.style(\"include\", fg=\"cyan\"))\n    click.echo(\n        \"%s - Put project specific (private) libraries here\"\n        % click.style(\"lib\", fg=\"cyan\")\n    )\n    click.echo(\"%s - Put project source files here\" % click.style(\"src\", fg=\"cyan\"))\n    click.echo(\n        \"%s - Project Configuration File\" % click.style(\"platformio.ini\", fg=\"cyan\")\n    )\n\n\ndef print_footer(is_new_project):\n    action = \"initialized\" if is_new_project else \"updated\"\n    return click.secho(\n        f\"Project has been successfully {action}!\",\n        fg=\"green\",\n    )\n\n\ndef init_base_project(project_dir):\n    with fs.cd(project_dir):\n        config = ProjectConfig()\n        config.save()\n        dir_to_readme = [\n            (config.get(\"platformio\", \"src_dir\"), None),\n            (config.get(\"platformio\", \"include_dir\"), init_include_readme),\n            (config.get(\"platformio\", \"lib_dir\"), init_lib_readme),\n            (config.get(\"platformio\", \"test_dir\"), init_test_readme),\n        ]\n        for path, cb in dir_to_readme:\n            if os.path.isdir(path):\n                continue\n            os.makedirs(path)\n            if cb:\n                cb(path)\n\n\ndef init_include_readme(include_dir):\n    with open(os.path.join(include_dir, \"README\"), mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(\n            \"\"\"\nThis directory is intended for project header files.\n\nA header file is a file containing C declarations and macro definitions\nto be shared between several project source files. You request the use of a\nheader file in your project source file (C, C++, etc) located in `src` folder\nby including it, with the C preprocessing directive `#include'.\n\n```src/main.c\n\n#include \"header.h\"\n\nint main (void)\n{\n ...\n}\n```\n\nIncluding a header file produces the same results as copying the header file\ninto each source file that needs it. Such copying would be time-consuming\nand error-prone. With a header file, the related declarations appear\nin only one place. If they need to be changed, they can be changed in one\nplace, and programs that include the header file will automatically use the\nnew version when next recompiled. The header file eliminates the labor of\nfinding and changing all the copies as well as the risk that a failure to\nfind one copy will result in inconsistencies within a program.\n\nIn C, the convention is to give header files names that end with `.h'.\n\nRead more about using header files in official GCC documentation:\n\n* Include Syntax\n* Include Operation\n* Once-Only Headers\n* Computed Includes\n\nhttps://gcc.gnu.org/onlinedocs/cpp/Header-Files.html\n\"\"\",\n        )\n\n\ndef init_lib_readme(lib_dir):\n    with open(os.path.join(lib_dir, \"README\"), mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(\n            \"\"\"\nThis directory is intended for project specific (private) libraries.\nPlatformIO will compile them to static libraries and link into the executable file.\n\nThe source code of each library should be placed in a separate directory\n(\"lib/your_library_name/[Code]\").\n\nFor example, see the structure of the following example libraries `Foo` and `Bar`:\n\n|--lib\n|  |\n|  |--Bar\n|  |  |--docs\n|  |  |--examples\n|  |  |--src\n|  |     |- Bar.c\n|  |     |- Bar.h\n|  |  |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html\n|  |\n|  |--Foo\n|  |  |- Foo.c\n|  |  |- Foo.h\n|  |\n|  |- README --> THIS FILE\n|\n|- platformio.ini\n|--src\n   |- main.c\n\nExample contents of `src/main.c` using Foo and Bar:\n```\n#include <Foo.h>\n#include <Bar.h>\n\nint main (void)\n{\n  ...\n}\n\n```\n\nThe PlatformIO Library Dependency Finder will find automatically dependent\nlibraries by scanning project source files.\n\nMore information about PlatformIO Library Dependency Finder\n- https://docs.platformio.org/page/librarymanager/ldf.html\n\"\"\",\n        )\n\n\ndef init_test_readme(test_dir):\n    with open(os.path.join(test_dir, \"README\"), mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(\n            \"\"\"\nThis directory is intended for PlatformIO Test Runner and project tests.\n\nUnit Testing is a software testing method by which individual units of\nsource code, sets of one or more MCU program modules together with associated\ncontrol data, usage procedures, and operating procedures, are tested to\ndetermine whether they are fit for use. Unit testing finds problems early\nin the development cycle.\n\nMore information about PlatformIO Unit Testing:\n- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html\n\"\"\",\n        )\n\n\ndef init_cvs_ignore():\n    conf_path = \".gitignore\"\n    if os.path.isfile(conf_path):\n        return\n    with open(conf_path, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(\".pio\\n\")\n\n\ndef update_board_envs(project_dir, boards, extra_project_options, env_prefix):\n    config = ProjectConfig(\n        os.path.join(project_dir, \"platformio.ini\"), parse_extra=False\n    )\n    used_boards = []\n    for section in config.sections():\n        cond = [section.startswith(\"env:\"), config.has_option(section, \"board\")]\n        if all(cond):\n            used_boards.append(config.get(section, \"board\"))\n\n    pm = PlatformPackageManager()\n    modified = False\n    for id_ in boards:\n        board_config = pm.board_config(id_)\n        if id_ in used_boards:\n            continue\n        used_boards.append(id_)\n        modified = True\n\n        envopts = {\"platform\": board_config[\"platform\"], \"board\": id_}\n        # find default framework for board\n        frameworks = board_config.get(\"frameworks\")\n        if frameworks:\n            envopts[\"framework\"] = frameworks[0]\n\n        for item in extra_project_options:\n            if \"=\" not in item:\n                continue\n            _name, _value = item.split(\"=\", 1)\n            envopts[_name.strip()] = _value.strip()\n\n        section = \"env:%s%s\" % (env_prefix, id_)\n        config.add_section(section)\n\n        for option, value in envopts.items():\n            config.set(section, option, value)\n\n    if modified:\n        config.save()\n\n\ndef update_project_env(environment, extra_project_options=None):\n    if not extra_project_options:\n        return\n    env_section = \"env:%s\" % environment\n    option_to_sections = {\"platformio\": [], env_section: []}\n    for item in extra_project_options:\n        assert \"=\" in item\n        name, value = item.split(\"=\", 1)\n        name = name.strip()\n        destination = env_section\n        for option in ProjectOptions.values():\n            if option.scope in option_to_sections and option.name == name:\n                destination = option.scope\n                break\n        option_to_sections[destination].append((name, value.strip()))\n\n    config = ProjectConfig(\n        \"platformio.ini\", parse_extra=False, expand_interpolations=False\n    )\n    for section, options in option_to_sections.items():\n        if not options:\n            continue\n        if not config.has_section(section):\n            config.add_section(section)\n        for name, value in options:\n            config.set(section, name, value)\n\n    config.save()\n\n\ndef init_sample_code(config, environment):\n    try:\n        p = PlatformFactory.from_env(environment)\n        return p.generate_sample_code(config, environment)\n    except (NotImplementedError, UndefinedEnvPlatformError):\n        pass\n\n    framework = config.get(f\"env:{environment}\", \"framework\", None)\n    if framework != [\"arduino\"]:\n        return None\n    main_content = \"\"\"\n#include <Arduino.h>\n\n// put function declarations here:\nint myFunction(int, int);\n\nvoid setup() {\n  // put your setup code here, to run once:\n  int result = myFunction(2, 3);\n}\n\nvoid loop() {\n  // put your main code here, to run repeatedly:\n}\n\n// put function definitions here:\nint myFunction(int x, int y) {\n  return x + y;\n}\n\"\"\"\n    is_cpp_project = p.name not in [\"intel_mcs51\", \"ststm8\"]\n    src_dir = config.get(\"platformio\", \"src_dir\")\n    main_path = os.path.join(src_dir, \"main.%s\" % (\"cpp\" if is_cpp_project else \"c\"))\n    if os.path.isfile(main_path):\n        return None\n    if not os.path.isdir(src_dir):\n        os.makedirs(src_dir)\n    with open(main_path, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(main_content.strip())\n    return True\n"
  },
  {
    "path": "platformio/project/commands/metadata.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.package.commands.install import install_project_dependencies\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import load_build_metadata\n\n\n@click.command(\n    \"metadata\", short_help=\"Dump metadata intended for IDE extensions/plugins\"\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\"-e\", \"--environment\", \"environments\", multiple=True)\n@click.option(\"--json-output\", is_flag=True)\n@click.option(\"--json-output-path\", type=click.Path())\ndef project_metadata_cmd(project_dir, environments, json_output, json_output_path):\n    project_dir = os.path.abspath(project_dir)\n    with fs.cd(project_dir):\n        config = ProjectConfig.get_instance()\n        config.validate(environments)\n        environments = list(environments or config.envs())\n        build_metadata = load_build_metadata(project_dir, environments)\n\n    if not json_output:\n        install_project_dependencies(\n            options=dict(\n                project_dir=project_dir,\n                environments=environments,\n            )\n        )\n        click.echo()\n\n    if json_output or json_output_path:\n        if json_output_path:\n            if os.path.isdir(json_output_path):\n                json_output_path = os.path.join(json_output_path, \"metadata.json\")\n            with open(json_output_path, mode=\"w\", encoding=\"utf8\") as fp:\n                json.dump(build_metadata, fp)\n            click.secho(f\"Saved metadata to the {json_output_path}\", fg=\"green\")\n        if json_output:\n            click.echo(json.dumps(build_metadata))\n        return\n\n    for envname, metadata in build_metadata.items():\n        click.echo(\"Environment: \" + click.style(envname, fg=\"cyan\", bold=True))\n        click.echo(\"=\" * (13 + len(envname)))\n        click.echo(\n            tabulate(\n                [\n                    (click.style(name, bold=True), \"=\", json.dumps(value, indent=2))\n                    for name, value in metadata.items()\n                ],\n                tablefmt=\"plain\",\n            )\n        )\n        click.echo()\n\n    return\n"
  },
  {
    "path": "platformio/project/config.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport configparser\nimport glob\nimport hashlib\nimport json\nimport os\nimport re\nimport time\n\nimport click\n\nfrom platformio import fs\nfrom platformio.compat import MISSING, hashlib_encode_data, string_types\nfrom platformio.project import exception\nfrom platformio.project.options import ProjectOptions\n\nCONFIG_HEADER = \"\"\"\n; PlatformIO Project Configuration File\n;\n;   Build options: build flags, source filter\n;   Upload options: custom upload port, speed and extra flags\n;   Library options: dependencies, extra library storages\n;   Advanced options: extra scripting\n;\n; Please visit documentation for the other options and examples\n; https://docs.platformio.org/page/projectconf.html\n\"\"\"\n\n\nclass ProjectConfigBase:\n    ENVNAME_RE = re.compile(r\"^[a-z\\d\\_\\-]+$\", flags=re.I)\n    INLINE_COMMENT_RE = re.compile(r\"\\s+;.*$\")\n    VARTPL_RE = re.compile(r\"\\$\\{(?:([^\\.\\}\\()]+)\\.)?([^\\}]+)\\}\")\n\n    BUILTIN_VARS = {\n        \"PROJECT_DIR\": lambda: os.getcwd(),  # pylint: disable=unnecessary-lambda\n        \"PROJECT_HASH\": lambda: \"%s-%s\"\n        % (\n            os.path.basename(os.getcwd()),\n            hashlib.sha1(hashlib_encode_data(os.getcwd())).hexdigest()[:10],\n        ),\n        \"UNIX_TIME\": lambda: str(int(time.time())),\n    }\n\n    CUSTOM_OPTION_PREFIXES = (\"custom_\", \"board_\")\n\n    expand_interpolations = True\n    warnings = []\n\n    _parser = None\n    _parsed = []\n\n    @staticmethod\n    def parse_multi_values(items):\n        result = []\n        if not items:\n            return result\n        if not isinstance(items, (list, tuple)):\n            items = items.split(\"\\n\" if \"\\n\" in items else \", \")\n        for item in items:\n            item = item.strip()\n            # comment\n            if not item or item.startswith((\";\", \"#\")):\n                continue\n            if \";\" in item:\n                item = ProjectConfigBase.INLINE_COMMENT_RE.sub(\"\", item).strip()\n            result.append(item)\n        return result\n\n    @staticmethod\n    def get_default_path():\n        from platformio import app  # pylint: disable=import-outside-toplevel\n\n        return app.get_session_var(\"custom_project_conf\") or os.path.join(\n            os.getcwd(), \"platformio.ini\"\n        )\n\n    def __init__(self, path=None, parse_extra=True, expand_interpolations=True):\n        path = self.get_default_path() if path is None else path\n        self.path = path\n        self.expand_interpolations = expand_interpolations\n        self.warnings = []\n        self._parsed = []\n        self._parser = configparser.ConfigParser(inline_comment_prefixes=(\"#\", \";\"))\n        if path and os.path.isfile(path):\n            self.read(path, parse_extra)\n\n        self._maintain_renamed_options()\n\n    def __getattr__(self, name):\n        return getattr(self._parser, name)\n\n    def read(self, path, parse_extra=True):\n        if path in self._parsed:\n            return\n        self._parsed.append(path)\n        try:\n            self._parser.read(path, \"utf-8\")\n        except configparser.Error as exc:\n            raise exception.InvalidProjectConfError(path, str(exc)) from exc\n\n        if not parse_extra:\n            return\n\n        # load extra configs\n        for pattern in self.get(\"platformio\", \"extra_configs\", []):\n            if pattern.startswith(\"~\"):\n                pattern = fs.expanduser(pattern)\n            for item in glob.glob(pattern, recursive=True):\n                self.read(item)\n\n    def _maintain_renamed_options(self):\n        renamed_options = {}\n        for option in ProjectOptions.values():\n            if option.oldnames:\n                renamed_options.update({name: option.name for name in option.oldnames})\n\n        for section in self._parser.sections():\n            scope = self.get_section_scope(section)\n            if scope not in (\"platformio\", \"env\"):\n                continue\n            for option in self._parser.options(section):\n                if option in renamed_options:\n                    self.warnings.append(\n                        \"`%s` configuration option in section [%s] is \"\n                        \"deprecated and will be removed in the next release! \"\n                        \"Please use `%s` instead\"\n                        % (option, section, renamed_options[option])\n                    )\n                    # # rename on-the-fly\n                    # self._parser.set(\n                    #     section,\n                    #     renamed_options[option],\n                    #     self._parser.get(section, option),\n                    # )\n                    # self._parser.remove_option(section, option)\n                    continue\n\n                # unknown\n                unknown_conditions = [\n                    (\"%s.%s\" % (scope, option)) not in ProjectOptions,\n                    scope != \"env\"\n                    or not option.startswith(self.CUSTOM_OPTION_PREFIXES),\n                ]\n                if all(unknown_conditions):\n                    self.warnings.append(\n                        \"Ignore unknown configuration option `%s` \"\n                        \"in section [%s]\" % (option, section)\n                    )\n        return True\n\n    @staticmethod\n    def get_section_scope(section):\n        assert section\n        return section.split(\":\", 1)[0] if \":\" in section else section\n\n    def walk_options(self, root_section):\n        extends_queue = (\n            [\"env\", root_section] if root_section.startswith(\"env:\") else [root_section]\n        )\n        extends_done = []\n        while extends_queue:\n            section = extends_queue.pop()\n            extends_done.append(section)\n            if not self._parser.has_section(section):\n                continue\n            for option in self._parser.options(section):\n                yield (section, option)\n            if self._parser.has_option(section, \"extends\"):\n                extends_queue.extend(\n                    self.parse_multi_values(self._parser.get(section, \"extends\"))\n                )\n\n    def options(self, section=None, env=None):\n        result = []\n        assert section or env\n        if not section:\n            section = \"env:\" + env\n\n        if not self.expand_interpolations:\n            return self._parser.options(section)\n\n        for _, option in self.walk_options(section):\n            if option not in result:\n                result.append(option)\n\n        # handle system environment variables\n        scope = self.get_section_scope(section)\n        for option_meta in ProjectOptions.values():\n            if option_meta.scope != scope or option_meta.name in result:\n                continue\n            if option_meta.sysenvvar and option_meta.sysenvvar in os.environ:\n                result.append(option_meta.name)\n\n        return result\n\n    def has_option(self, section, option):\n        if self._parser.has_option(section, option):\n            return True\n        return option in self.options(section)\n\n    def items(self, section=None, env=None, as_dict=False):\n        assert section or env\n        if not section:\n            section = \"env:\" + env\n        if as_dict:\n            return {\n                option: self.get(section, option) for option in self.options(section)\n            }\n        return [(option, self.get(section, option)) for option in self.options(section)]\n\n    def set(self, section, option, value):\n        if value is None:\n            value = \"\"\n        if isinstance(value, (list, tuple)):\n            value = \"\\n\".join(value)\n        elif isinstance(value, bool):\n            value = \"yes\" if value else \"no\"\n        elif isinstance(value, (int, float)):\n            value = str(value)\n        # start multi-line value from a new line\n        if \"\\n\" in value and not value.startswith(\"\\n\"):\n            value = \"\\n\" + value\n        self._parser.set(section, option, value)\n\n    def resolve_renamed_option(self, section, old_name):\n        scope = self.get_section_scope(section)\n        if scope not in (\"platformio\", \"env\"):\n            return None\n        for option_meta in ProjectOptions.values():\n            if (\n                option_meta.oldnames\n                and option_meta.scope == scope\n                and old_name in option_meta.oldnames\n            ):\n                return option_meta.name\n        return None\n\n    def find_option_meta(self, section, option):\n        scope = self.get_section_scope(section)\n        if scope not in (\"platformio\", \"env\"):\n            return None\n        option_meta = ProjectOptions.get(\"%s.%s\" % (scope, option))\n        if option_meta:\n            return option_meta\n        for option_meta in ProjectOptions.values():\n            if option_meta.scope == scope and option in (option_meta.oldnames or []):\n                return option_meta\n        return None\n\n    def _traverse_for_value(self, section, option, option_meta=None):\n        for _section, _option in self.walk_options(section):\n            if _option == option or (\n                option_meta\n                and (\n                    option_meta.name == _option\n                    or _option in (option_meta.oldnames or [])\n                )\n            ):\n                return self._parser.get(_section, _option)\n        return MISSING\n\n    def getraw(\n        self, section, option, default=MISSING\n    ):  # pylint: disable=too-many-branches\n        if not self.expand_interpolations:\n            return self._parser.get(section, option)\n\n        option_meta = self.find_option_meta(section, option)\n        value = self._traverse_for_value(section, option, option_meta)\n\n        if not option_meta:\n            if value == MISSING:\n                value = (\n                    default if default != MISSING else self._parser.get(section, option)\n                )\n            return self._expand_interpolations(section, option, value)\n\n        if option_meta.sysenvvar:\n            envvar_value = os.getenv(option_meta.sysenvvar)\n            if not envvar_value and option_meta.oldnames:\n                for oldoption in option_meta.oldnames:\n                    envvar_value = os.getenv(\"PLATFORMIO_\" + oldoption.upper())\n                    if envvar_value:\n                        break\n            if envvar_value and option_meta.multiple:\n                if value == MISSING:\n                    value = \"\"\n                value += (\"\\n\" if value else \"\") + envvar_value\n            elif envvar_value:\n                value = envvar_value\n\n        if value == MISSING:\n            value = default if default != MISSING else option_meta.default\n        if callable(value):\n            value = value()\n        if value == MISSING:\n            return None\n\n        return self._expand_interpolations(section, option, value)\n\n    def _expand_interpolations(self, section, option, value):\n        if not value or not isinstance(value, string_types) or not \"$\" in value:\n            return value\n\n        # legacy support for variables delclared without \"${}\"\n        legacy_vars = [\"PROJECT_HASH\"]\n        stop = False\n        while not stop:\n            stop = True\n            for name in legacy_vars:\n                x = value.find(f\"${name}\")\n                if x < 0 or value[x - 1] == \"$\":\n                    continue\n                value = \"%s${%s}%s\" % (value[:x], name, value[x + len(name) + 1 :])\n                stop = False\n                warn_msg = (\n                    \"Invalid variable declaration. Please use \"\n                    f\"`${{{name}}}` instead of `${name}`\"\n                )\n                if warn_msg not in self.warnings:\n                    self.warnings.append(warn_msg)\n\n        if not all([\"${\" in value, \"}\" in value]):\n            return value\n        return self.VARTPL_RE.sub(\n            lambda match: self._re_interpolation_handler(section, option, match), value\n        )\n\n    def _re_interpolation_handler(self, parent_section, parent_option, match):\n        section, option = match.group(1), match.group(2)\n\n        # handle built-in variables\n        if section is None:\n            if option in self.BUILTIN_VARS:\n                return self.BUILTIN_VARS[option]()\n            # SCons variables\n            return f\"${{{option}}}\"\n\n        # handle system environment variables\n        if section == \"sysenv\":\n            return os.getenv(option)\n\n        # handle ${this.*}\n        if section == \"this\":\n            section = parent_section\n            if option == \"__env__\":\n                if not parent_section.startswith(\"env:\"):\n                    raise exception.ProjectOptionValueError(\n                        f\"`${{this.__env__}}` is called from the `{parent_section}` \"\n                        \"section that is not valid PlatformIO environment. Please \"\n                        f\"check `{parent_option}` option in the `{section}` section\"\n                    )\n                return parent_section[4:]\n\n        # handle nested calls\n        try:\n            value = self.get(section, option)\n        except RecursionError as exc:\n            raise exception.ProjectOptionValueError(\n                f\"Infinite recursion has been detected for `{option}` \"\n                f\"option in the `{section}` section\"\n            ) from exc\n        if isinstance(value, list):\n            return \"\\n\".join(value)\n        return str(value)\n\n    def get(self, section, option, default=MISSING):\n        value = None\n        try:\n            value = self.getraw(section, option, default)\n        except configparser.Error as exc:\n            raise exception.InvalidProjectConfError(self.path, str(exc))\n\n        option_meta = self.find_option_meta(section, option)\n        if not option_meta:\n            return value\n\n        if option_meta.validate:\n            value = option_meta.validate(value)\n        if option_meta.multiple:\n            value = self.parse_multi_values(value or [])\n        try:\n            return self.cast_to(value, option_meta.type)\n        except click.BadParameter as exc:\n            if not self.expand_interpolations:\n                return value\n            raise exception.ProjectOptionValueError(\n                \"%s for `%s` option in the `%s` section (%s)\"\n                % (exc.format_message(), option, section, option_meta.description)\n            )\n\n    @staticmethod\n    def cast_to(value, to_type):\n        items = value\n        if not isinstance(value, (list, tuple)):\n            items = [value]\n        items = [\n            to_type(item) if isinstance(to_type, click.ParamType) else item\n            for item in items\n        ]\n        return items if isinstance(value, (list, tuple)) else items[0]\n\n    def envs(self):\n        return [s[4:] for s in self._parser.sections() if s.startswith(\"env:\")]\n\n    def default_envs(self):\n        return self.get(\"platformio\", \"default_envs\", [])\n\n    def get_default_env(self):\n        default_envs = self.default_envs()\n        if default_envs:\n            return default_envs[0]\n        envs = self.envs()\n        return envs[0] if envs else None\n\n    def validate(self, envs=None, silent=False):\n        if not os.path.isfile(self.path):\n            raise exception.NotPlatformIOProjectError(os.path.dirname(self.path))\n\n        known_envs = set(self.envs())\n\n        # check envs\n        if not known_envs:\n            raise exception.ProjectEnvsNotAvailableError()\n        unknown_envs = set(list(envs or []) + self.default_envs()) - known_envs\n        if unknown_envs:\n            raise exception.UnknownEnvNamesError(\n                \", \".join(unknown_envs), \", \".join(known_envs)\n            )\n\n        for env in known_envs:\n            # check envs names\n            if not self.ENVNAME_RE.match(env):\n                raise exception.InvalidEnvNameError(env)\n\n            # check simultaneous use of `monitor_raw` and `monitor_filters`\n            if self.get(f\"env:{env}\", \"monitor_raw\", False) and self.get(\n                f\"env:{env}\", \"monitor_filters\", None\n            ):\n                self.warnings.append(\n                    \"The `monitor_raw` and `monitor_filters` options cannot be \"\n                    f\"used simultaneously for the `{env}` environment in the \"\n                    \"`platformio.ini` file. The `monitor_filters` option will \"\n                    \"be disabled to avoid conflicts.\"\n                )\n\n        if not silent:\n            for warning in self.warnings:\n                click.secho(\"Warning! %s\" % warning, fg=\"yellow\")\n\n        return True\n\n\nclass ProjectConfigLintMixin:\n    @classmethod\n    def lint(cls, path=None):\n        errors = []\n        warnings = []\n        try:\n            config = cls.get_instance(path)\n            config.validate(silent=True)\n            warnings = config.warnings  # in case \"as_tuple\" fails\n            config.as_tuple()\n            warnings = config.warnings\n        except Exception as exc:  # pylint: disable=broad-exception-caught\n            if exc.__cause__ is not None:\n                exc = exc.__cause__\n\n            item = {\"type\": exc.__class__.__name__, \"message\": str(exc)}\n            for attr in (\"lineno\", \"source\"):\n                if hasattr(exc, attr):\n                    item[attr] = getattr(exc, attr)\n\n            if item[\"type\"] == \"ParsingError\" and hasattr(exc, \"errors\"):\n                for lineno, line in getattr(exc, \"errors\"):\n                    errors.append(\n                        {\n                            \"type\": item[\"type\"],\n                            \"message\": f\"Parsing error: {line}\",\n                            \"lineno\": lineno,\n                            \"source\": item[\"source\"],\n                        }\n                    )\n            else:\n                errors.append(item)\n        return {\"errors\": errors, \"warnings\": warnings}\n\n\nclass ProjectConfigDirsMixin:\n    def get_optional_dir(self, name):\n        \"\"\"\n        Deprecated, used by platformio-node-helpers.project.observer.fetchLibDirs\n        PlatformIO IDE for Atom depends on platformio-node-helpers@~7.2.0\n        PIO Home 3.0 Project Inspection depends on it\n        \"\"\"\n        return self.get(\"platformio\", f\"{name}_dir\")\n\n\nclass ProjectConfig(ProjectConfigBase, ProjectConfigLintMixin, ProjectConfigDirsMixin):\n    _instances = {}\n\n    @staticmethod\n    def get_instance(path=None):\n        path = ProjectConfig.get_default_path() if path is None else path\n        mtime = os.path.getmtime(path) if os.path.isfile(path) else 0\n        instance = ProjectConfig._instances.get(path)\n        if instance and instance[\"mtime\"] != mtime:\n            instance = None\n        if not instance:\n            instance = {\"mtime\": mtime, \"config\": ProjectConfig(path)}\n            ProjectConfig._instances[path] = instance\n        return instance[\"config\"]\n\n    def __repr__(self):\n        return \"<ProjectConfig %s>\" % (self.path or \"in-memory\")\n\n    def as_tuple(self):\n        return [(s, self.items(s)) for s in self.sections()]\n\n    def to_json(self):\n        return json.dumps(self.as_tuple())\n\n    def update(self, data, clear=False):\n        assert isinstance(data, list)\n        if clear:\n            self._parser = configparser.ConfigParser()\n        for section, options in data:\n            if not self._parser.has_section(section):\n                self._parser.add_section(section)\n            for option, value in options:\n                self.set(section, option, value)\n\n    def save(self, path=None):\n        path = path or self.path\n        if path in self._instances:\n            del self._instances[path]\n        with open(path or self.path, mode=\"w+\", encoding=\"utf8\") as fp:\n            fp.write(CONFIG_HEADER.strip() + \"\\n\\n\")\n            self._parser.write(fp)\n            fp.seek(0)\n            contents = fp.read()\n            fp.seek(0)\n            fp.truncate()\n            fp.write(contents.strip() + \"\\n\")\n        return True\n"
  },
  {
    "path": "platformio/project/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.exception import PlatformioException, UserSideException\n\n\nclass ProjectError(PlatformioException):\n    pass\n\n\nclass NotPlatformIOProjectError(ProjectError, UserSideException):\n    MESSAGE = (\n        \"Not a PlatformIO project. `platformio.ini` file has not been \"\n        \"found in current working directory ({0}). To initialize new project \"\n        \"please use `platformio project init` command\"\n    )\n\n\nclass InvalidProjectConfError(ProjectError, UserSideException):\n    MESSAGE = \"Invalid '{0}' (project configuration file): '{1}'\"\n\n\nclass UndefinedEnvPlatformError(ProjectError, UserSideException):\n    MESSAGE = \"Please specify platform for '{0}' environment\"\n\n\nclass ProjectEnvsNotAvailableError(ProjectError, UserSideException):\n    MESSAGE = \"Please setup environments in `platformio.ini` file\"\n\n\nclass UnknownEnvNamesError(ProjectError, UserSideException):\n    MESSAGE = \"Unknown environment names '{0}'. Valid names are '{1}'\"\n\n\nclass InvalidEnvNameError(ProjectError, UserSideException):\n    MESSAGE = (\n        \"Invalid environment name '{0}'. The name can contain \"\n        \"alphanumeric, underscore, and hyphen characters (a-z, 0-9, -, _)\"\n    )\n\n\nclass ProjectOptionValueError(ProjectError, UserSideException):\n    pass\n"
  },
  {
    "path": "platformio/project/helpers.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport subprocess\nfrom hashlib import sha1\n\nfrom click.testing import CliRunner\n\nfrom platformio import __version__, exception, fs\nfrom platformio.compat import IS_MACOS, IS_WINDOWS, hashlib_encode_data\nfrom platformio.project.config import ProjectConfig\n\n\ndef get_project_dir():\n    return os.getcwd()\n\n\ndef is_platformio_project(project_dir=None):\n    if not project_dir:\n        project_dir = get_project_dir()\n    return os.path.isfile(os.path.join(project_dir, \"platformio.ini\"))\n\n\ndef find_project_dir_above(path):\n    if os.path.isfile(path):\n        path = os.path.dirname(path)\n    if is_platformio_project(path):\n        return path\n    if os.path.isdir(os.path.dirname(path)):\n        return find_project_dir_above(os.path.dirname(path))\n    return None\n\n\ndef get_project_watch_lib_dirs():\n    \"\"\"Used by platformio-node-helpers.project.observer.fetchLibDirs\"\"\"\n    config = ProjectConfig.get_instance()\n    result = [\n        config.get(\"platformio\", \"globallib_dir\"),\n        config.get(\"platformio\", \"lib_dir\"),\n    ]\n    libdeps_dir = config.get(\"platformio\", \"libdeps_dir\")\n    if not os.path.isdir(libdeps_dir):\n        return result\n    for d in os.listdir(libdeps_dir):\n        if os.path.isdir(os.path.join(libdeps_dir, d)):\n            result.append(os.path.join(libdeps_dir, d))\n    return result\n\n\nget_project_all_lib_dirs = get_project_watch_lib_dirs\n\n\ndef get_project_cache_dir():\n    \"\"\"Deprecated, use ProjectConfig.get(\"platformio\", \"cache_dir\") instead\"\"\"\n    return ProjectConfig.get_instance().get(\"platformio\", \"cache_dir\")\n\n\ndef get_default_projects_dir():\n    docs_dir = os.path.join(fs.expanduser(\"~\"), \"Documents\")\n    try:\n        assert IS_WINDOWS\n        import ctypes.wintypes  # pylint: disable=import-outside-toplevel\n\n        buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)\n        ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, buf)\n        docs_dir = buf.value\n    except:  # pylint: disable=bare-except\n        if not IS_MACOS:\n            try:\n                docs_dir = (\n                    subprocess.check_output([\"xdg-user-dir\", \"DOCUMENTS\"])\n                    .decode(\"utf-8\")\n                    .strip()\n                )\n            except FileNotFoundError:  # command not found\n                pass\n    return os.path.join(docs_dir, \"PlatformIO\", \"Projects\")\n\n\ndef compute_project_checksum(config):\n    # rebuild when PIO Core version changes\n    checksum = sha1(hashlib_encode_data(__version__))\n\n    # configuration file state\n    config_data = config.to_json()\n    if IS_WINDOWS:\n        # issue #4600: fix drive letter\n        config_data = re.sub(\n            r\"([A-Z]):\\\\\",\n            lambda match: \"%s:\\\\\" % match.group(1).lower(),\n            config_data,\n            flags=re.I,\n        )\n    checksum.update(hashlib_encode_data(config_data))\n\n    # project file structure\n    check_suffixes = (\".c\", \".cc\", \".cpp\", \".h\", \".hpp\", \".s\", \".S\")\n    for d in (\n        config.get(\"platformio\", \"include_dir\"),\n        config.get(\"platformio\", \"src_dir\"),\n        config.get(\"platformio\", \"lib_dir\"),\n    ):\n        if not os.path.isdir(d):\n            continue\n        chunks = []\n        for root, _, files in os.walk(d):\n            for f in files:\n                path = os.path.join(root, f)\n                if path.endswith(check_suffixes):\n                    chunks.append(path)\n        if not chunks:\n            continue\n        chunks_to_str = \",\".join(sorted(chunks))\n        if IS_WINDOWS:  # case insensitive OS\n            chunks_to_str = chunks_to_str.lower()\n        checksum.update(hashlib_encode_data(chunks_to_str))\n\n    return checksum.hexdigest()\n\n\ndef load_build_metadata(project_dir, env_or_envs, cache=False, build_type=None):\n    assert env_or_envs\n    env_names = env_or_envs\n    if not isinstance(env_names, list):\n        env_names = [env_names]\n\n    with fs.cd(project_dir):\n        result = _get_cached_build_metadata(env_names) if cache else {}\n        # incompatible build-type data\n        for env_name in list(result.keys()):\n            if build_type is None:\n                build_type = ProjectConfig.get_instance().get(\n                    f\"env:{env_name}\", \"build_type\"\n                )\n            if result[env_name].get(\"build_type\", \"\") != build_type:\n                del result[env_name]\n        missed_env_names = set(env_names) - set(result.keys())\n        if missed_env_names:\n            result.update(\n                _load_build_metadata(project_dir, missed_env_names, build_type)\n            )\n\n    if not isinstance(env_or_envs, list) and env_or_envs in result:\n        return result[env_or_envs]\n    return result or None\n\n\n# Backward compatibility with dev-platforms\nload_project_ide_data = load_build_metadata\n\n\ndef _load_build_metadata(project_dir, env_names, build_type=None):\n    # pylint: disable=import-outside-toplevel\n    from platformio import app\n    from platformio.run.cli import cli as cmd_run\n\n    args = [\"--project-dir\", project_dir, \"--target\", \"__idedata\"]\n    if build_type == \"debug\":\n        args.extend([\"--target\", \"__debug\"])\n    # if build_type == \"test\":\n    #     args.extend([\"--target\", \"__test\"])\n    for name in env_names:\n        args.extend([\"-e\", name])\n    app.set_session_var(\"pause_telemetry\", True)\n    result = CliRunner().invoke(cmd_run, args)\n    app.set_session_var(\"pause_telemetry\", False)\n    if result.exit_code != 0 and not isinstance(\n        result.exception, exception.ReturnErrorCode\n    ):\n        raise result.exception\n    if '\"includes\":' not in result.output:\n        raise exception.UserSideException(result.output)\n    return _get_cached_build_metadata(env_names)\n\n\ndef _get_cached_build_metadata(env_names):\n    build_dir = ProjectConfig.get_instance().get(\"platformio\", \"build_dir\")\n    result = {}\n    for env_name in env_names:\n        if not os.path.isfile(os.path.join(build_dir, env_name, \"idedata.json\")):\n            continue\n        result[env_name] = fs.load_json(\n            os.path.join(build_dir, env_name, \"idedata.json\")\n        )\n    return result\n"
  },
  {
    "path": "platformio/project/integration/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/project/integration/generator.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport sys\n\nimport bottle\n\nfrom platformio import fs, util\nfrom platformio.debug.helpers import get_default_debug_env\nfrom platformio.proc import where_is_program\nfrom platformio.project.helpers import load_build_metadata\n\n\nclass ProjectGenerator:\n    def __init__(self, config, env_name, ide, boards=None):\n        self.config = config\n        self.project_dir = os.path.dirname(config.path)\n        self.forced_env_name = env_name\n        self.env_name = str(env_name or self.get_best_envname(boards))\n        self.ide = str(ide)\n\n    def get_best_envname(self, boards=None):\n        envname = None\n        default_envs = self.config.default_envs()\n        if default_envs:\n            envname = default_envs[0]\n            if not boards:\n                return envname\n\n        for env in self.config.envs():\n            if not boards:\n                return env\n            if not envname:\n                envname = env\n            items = self.config.items(env=env, as_dict=True)\n            if \"board\" in items and items.get(\"board\") in boards:\n                return env\n        return envname\n\n    @staticmethod\n    def get_ide_tpls_dir():\n        return os.path.join(os.path.dirname(__file__), \"tpls\")\n\n    @classmethod\n    def get_supported_ides(cls):\n        tpls_dir = cls.get_ide_tpls_dir()\n        return sorted(\n            [\n                name\n                for name in os.listdir(tpls_dir)\n                if os.path.isdir(os.path.join(tpls_dir, name))\n            ]\n        )\n\n    @staticmethod\n    def filter_includes(includes_map, ignore_scopes=None, to_unix_path=True):\n        ignore_scopes = ignore_scopes or []\n        result = []\n        for scope, includes in includes_map.items():\n            if scope in ignore_scopes:\n                continue\n            for include in includes:\n                if to_unix_path:\n                    include = fs.to_unix_path(include)\n                if include not in result:\n                    result.append(include)\n        return result\n\n    def _load_tplvars(self):\n        tpl_vars = {\n            \"config\": self.config,\n            \"systype\": util.get_systype(),\n            \"project_name\": self.config.get(\n                \"platformio\", \"name\", os.path.basename(self.project_dir)\n            ),\n            \"project_dir\": self.project_dir,\n            \"forced_env_name\": self.forced_env_name,\n            \"default_debug_env_name\": get_default_debug_env(self.config),\n            \"env_name\": self.env_name,\n            \"user_home_dir\": os.path.abspath(fs.expanduser(\"~\")),\n            \"platformio_path\": (\n                sys.argv[0]\n                if os.path.isfile(sys.argv[0])\n                else where_is_program(\"platformio\")\n            ),\n            \"env_path\": os.getenv(\"PATH\"),\n            \"env_pathsep\": os.pathsep,\n        }\n\n        # default env configuration\n        tpl_vars.update(self.config.items(env=self.env_name, as_dict=True))\n        # build data\n        tpl_vars.update(load_build_metadata(self.project_dir, self.env_name) or {})\n\n        with fs.cd(self.project_dir):\n            tpl_vars.update(\n                {\n                    \"src_files\": self.get_src_files(),\n                    \"project_src_dir\": self.config.get(\"platformio\", \"src_dir\"),\n                    \"project_lib_dir\": self.config.get(\"platformio\", \"lib_dir\"),\n                    \"project_test_dir\": self.config.get(\"platformio\", \"test_dir\"),\n                    \"project_libdeps_dir\": os.path.join(\n                        self.config.get(\"platformio\", \"libdeps_dir\"), self.env_name\n                    ),\n                }\n            )\n\n        for key, value in tpl_vars.items():\n            if key.endswith((\"_path\", \"_dir\")):\n                tpl_vars[key] = fs.to_unix_path(value)\n        for key in (\"src_files\", \"libsource_dirs\"):\n            if key not in tpl_vars:\n                continue\n            tpl_vars[key] = [fs.to_unix_path(inc) for inc in tpl_vars[key]]\n\n        tpl_vars[\"to_unix_path\"] = fs.to_unix_path\n        tpl_vars[\"filter_includes\"] = self.filter_includes\n        return tpl_vars\n\n    def get_src_files(self):\n        result = []\n        with fs.cd(self.project_dir):\n            for root, _, files in os.walk(self.config.get(\"platformio\", \"src_dir\")):\n                for f in files:\n                    result.append(\n                        os.path.relpath(os.path.join(os.path.abspath(root), f))\n                    )\n        return result\n\n    def get_tpls(self):\n        tpls = []\n        ide_tpls_dir = os.path.join(self.get_ide_tpls_dir(), self.ide)\n        for root, _, files in os.walk(ide_tpls_dir):\n            for f in files:\n                if not f.endswith(\".tpl\"):\n                    continue\n                _relpath = root.replace(ide_tpls_dir, \"\")\n                if _relpath.startswith(os.sep):\n                    _relpath = _relpath[1:]\n                tpls.append((_relpath, os.path.join(root, f)))\n        return tpls\n\n    def generate(self):\n        tpl_vars = self._load_tplvars()\n        for tpl_relpath, tpl_path in self.get_tpls():\n            dst_dir = self.project_dir\n            if tpl_relpath:\n                dst_dir = os.path.join(self.project_dir, tpl_relpath)\n                if not os.path.isdir(dst_dir):\n                    os.makedirs(dst_dir)\n            file_name = os.path.basename(tpl_path)[:-4]\n            contents = self._render_tpl(tpl_path, tpl_vars)\n            self._merge_contents(os.path.join(dst_dir, file_name), contents)\n\n    @staticmethod\n    def _render_tpl(tpl_path, tpl_vars):\n        with open(tpl_path, \"r\", encoding=\"utf8\") as fp:\n            return bottle.template(fp.read(), **tpl_vars)\n\n    @staticmethod\n    def _merge_contents(dst_path, contents):\n        if os.path.basename(dst_path) == \".gitignore\" and os.path.isfile(dst_path):\n            return\n        with open(dst_path, \"w\", encoding=\"utf8\") as fp:\n            fp.write(contents)\n"
  },
  {
    "path": "platformio/project/integration/tpls/clion/.gitignore.tpl",
    "content": ".pio\n"
  },
  {
    "path": "platformio/project/integration/tpls/codeblocks/platformio.cbp.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>\n<CodeBlocks_project_file>\n\t<FileVersion major=\"1\" minor=\"6\" />\n\t<Project>\n\t\t<Option title=\"{{project_name}}\" />\n\t\t<Option makefile_is_custom=\"1\" />\n\t\t<Option pch_mode=\"2\" />\n\t\t<Option compiler=\"gcc\" />\n\t\t<MakeCommands>\n\t\t\t<Build command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t<CompileFile command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t<Clean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t<DistClean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t<AskRebuildNeeded command=\"1\" />\n\t\t\t<SilentBuild command=\"{{platformio_path}} -f -c codeblocks run &gt; $(CMD_NULL)\" />\n\t\t</MakeCommands>\n\t\t<Build>\n\t\t\t<Target title=\"Debug\">\n\t\t\t\t<Option type=\"4\" />\n\t\t\t\t<Option compiler=\"gcc\" />\n\t\t\t\t<Option parameters=\"-f -c codeblocks run -t upload\" />\n\t\t\t\t<Option host_application=\"{{platformio_path}}\" />\n\t\t\t\t<Option run_host_application_in_terminal=\"1\" />\n\t\t\t\t<Option use_console_runner=\"0\" />\n\t\t\t\t<MakeCommands>\n\t\t\t\t\t<Build command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t\t\t<CompileFile command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t\t\t<Clean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t\t\t<DistClean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t\t\t<AskRebuildNeeded command=\"1\" />\n\t\t\t\t\t<SilentBuild command=\"{{platformio_path}} -f -c codeblocks run &gt; $(CMD_NULL)\" />\n\t\t\t\t</MakeCommands>\n\t\t\t</Target>\n\t\t\t<Target title=\"Release\">\n\t\t\t\t<Option type=\"4\" />\n\t\t\t\t<Option compiler=\"gcc\" />\n\t\t\t\t<Option parameters=\"-f -c codeblocks run -t upload\" />\n\t\t\t\t<Option host_application=\"{{platformio_path}}\" />\n\t\t\t\t<Option run_host_application_in_terminal=\"1\" />\n\t\t\t\t<Option use_console_runner=\"0\" />\n\t\t\t\t<MakeCommands>\n\t\t\t\t\t<Build command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t\t\t<CompileFile command=\"{{platformio_path}} -f -c codeblocks run\" />\n\t\t\t\t\t<Clean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t\t\t<DistClean command=\"{{platformio_path}} -f -c codeblocks run -t clean\" />\n\t\t\t\t\t<AskRebuildNeeded command=\"1\" />\n\t\t\t\t\t<SilentBuild command=\"{{platformio_path}} -f -c codeblocks run &gt; $(CMD_NULL)\" />\n\t\t\t\t</MakeCommands>\n\t\t\t</Target>\n\t\t</Build>\n\t\t<Compiler>\n\t\t\t% for define in defines:\n\t\t\t<Add option=\"-D{{define}}\"/>\n\t\t\t% end\n\t\t\t% for include in filter_includes(includes):\n\t\t\t<Add directory=\"{{include}}\"/>\n\t\t\t% end\n\t\t</Compiler>\n\t\t<Unit filename=\"platformio.ini\" />\n\t\t% for file in src_files:\n\t\t<Unit filename=\"{{file}}\"></Unit>\n\t\t% end\n\t</Project>\n</CodeBlocks_project_file>\n"
  },
  {
    "path": "platformio/project/integration/tpls/eclipse/.cproject.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<?fileVersion 4.0.0?><cproject storage_type_id=\"org.eclipse.cdt.core.XmlProjectDescriptionStorage\">\n\t<storageModule moduleId=\"org.eclipse.cdt.core.settings\">\n\t\t<cconfiguration id=\"0.910961921\">\n\t\t\t<storageModule buildSystemId=\"org.eclipse.cdt.managedbuilder.core.configurationDataProvider\" id=\"0.910961921\" moduleId=\"org.eclipse.cdt.core.settings\" name=\"Default\">\n\t\t\t\t<externalSettings/>\n\t\t\t\t<extensions>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.ELF\" point=\"org.eclipse.cdt.core.BinaryParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.VCErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GmakeErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.CWDLocator\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GCCErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GASErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GLDErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t</extensions>\n\t\t\t</storageModule>\n\t\t\t<storageModule moduleId=\"cdtBuildSystem\" version=\"4.0.0\">\n\t\t\t\t<configuration artifactName=\"{{project_name}}\" buildProperties=\"\" description=\"\" id=\"0.910961921\" name=\"Default\" parent=\"org.eclipse.cdt.build.core.prefbase.cfg\">\n\t\t\t\t\t<folderInfo id=\"0.910961921.\" name=\"/\" resourcePath=\"\">\n\t\t\t\t\t\t<toolChain id=\"org.eclipse.cdt.build.core.prefbase.toolchain.952979152\" name=\"No ToolChain\" resourceTypeBasedDiscovery=\"false\" superClass=\"org.eclipse.cdt.build.core.prefbase.toolchain\">\n\t\t\t\t\t\t\t<targetPlatform binaryParser=\"org.eclipse.cdt.core.ELF\" id=\"org.eclipse.cdt.build.core.prefbase.toolchain.952979152.52310970\" name=\"\"/>\n\t\t\t\t\t\t\t<builder arguments=\"-f -c eclipse\" cleanBuildTarget=\"run --target clean\" command=\"platformio\" id=\"org.eclipse.cdt.build.core.settings.default.builder.1519453406\" incrementalBuildTarget=\"run\" keepEnvironmentInBuildfile=\"false\" managedBuildOn=\"false\" name=\"Gnu Make Builder\" superClass=\"org.eclipse.cdt.build.core.settings.default.builder\"/>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.libs.1409095472\" name=\"holder for library settings\" superClass=\"org.eclipse.cdt.build.core.settings.holder.libs\"/>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.1624502120\" name=\"Assembly\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.239157887\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% cleaned_includes = filter_includes(includes, [\"toolchain\"])\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.922107295\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.149990277\" languageId=\"org.eclipse.cdt.core.assembly\" languageName=\"Assembly\" sourceContentType=\"org.eclipse.cdt.core.asmSource\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.54121539\" name=\"GNU C++\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.1096940598\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n                                    % end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.1198905600\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.762536863\" languageId=\"org.eclipse.cdt.core.g++\" languageName=\"GNU C++\" sourceContentType=\"org.eclipse.cdt.core.cxxSource,org.eclipse.cdt.core.cxxHeader\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.1310559623\" name=\"GNU C\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.41298875\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n                                    % end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.884639970\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.549319812\" languageId=\"org.eclipse.cdt.core.gcc\" languageName=\"GNU C\" sourceContentType=\"org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t</toolChain>\n\t\t\t\t\t</folderInfo>\n\t\t\t\t</configuration>\n\t\t\t</storageModule>\n\t\t\t<storageModule moduleId=\"org.eclipse.cdt.core.externalSettings\"/>\n\t\t</cconfiguration>\n\t\t<cconfiguration id=\"0.910961921.1363900502\">\n\t\t\t<storageModule buildSystemId=\"org.eclipse.cdt.managedbuilder.core.configurationDataProvider\" id=\"0.910961921.1363900502\" moduleId=\"org.eclipse.cdt.core.settings\" name=\"Debug\">\n\t\t\t\t<externalSettings/>\n\t\t\t\t<extensions>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.ELF\" point=\"org.eclipse.cdt.core.BinaryParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.VCErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GmakeErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.CWDLocator\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GCCErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GASErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t\t<extension id=\"org.eclipse.cdt.core.GLDErrorParser\" point=\"org.eclipse.cdt.core.ErrorParser\"/>\n\t\t\t\t</extensions>\n\t\t\t</storageModule>\n\t\t\t<storageModule moduleId=\"cdtBuildSystem\" version=\"4.0.0\">\n\t\t\t\t<configuration artifactName=\"mbed\" buildProperties=\"\" description=\"\" id=\"0.910961921.1363900502\" name=\"Debug\" parent=\"org.eclipse.cdt.build.core.prefbase.cfg\">\n\t\t\t\t\t<folderInfo id=\"0.910961921.1363900502.\" name=\"/\" resourcePath=\"\">\n\t\t\t\t\t\t<toolChain id=\"org.eclipse.cdt.build.core.prefbase.toolchain.2116690625\" name=\"No ToolChain\" resourceTypeBasedDiscovery=\"false\" superClass=\"org.eclipse.cdt.build.core.prefbase.toolchain\">\n\t\t\t\t\t\t\t<targetPlatform binaryParser=\"org.eclipse.cdt.core.ELF\" id=\"org.eclipse.cdt.build.core.prefbase.toolchain.2116690625.848954921\" name=\"\"/>\n\t\t\t\t\t\t\t<builder arguments=\"-f -c eclipse debug\" cleanBuildTarget=\"run --target clean\" command=\"platformio\" enableCleanBuild=\"false\" id=\"org.eclipse.cdt.build.core.settings.default.builder.985867833\" incrementalBuildTarget=\"\" keepEnvironmentInBuildfile=\"false\" managedBuildOn=\"false\" name=\"Gnu Make Builder\" superClass=\"org.eclipse.cdt.build.core.settings.default.builder\"/>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.libs.1855678035\" name=\"holder for library settings\" superClass=\"org.eclipse.cdt.build.core.settings.holder.libs\"/>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.30528994\" name=\"Assembly\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.794801023\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.1743427839\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.919136836\" languageId=\"org.eclipse.cdt.core.assembly\" languageName=\"Assembly\" sourceContentType=\"org.eclipse.cdt.core.asmSource\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.1146422798\" name=\"GNU C++\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.650084869\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" useByScannerDiscovery=\"false\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.2055633423\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" useByScannerDiscovery=\"false\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.445650141\" languageId=\"org.eclipse.cdt.core.g++\" languageName=\"GNU C++\" sourceContentType=\"org.eclipse.cdt.core.cxxSource,org.eclipse.cdt.core.cxxHeader\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t\t<tool id=\"org.eclipse.cdt.build.core.settings.holder.1637357529\" name=\"GNU C\" superClass=\"org.eclipse.cdt.build.core.settings.holder\">\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.incpaths.1246337321\" name=\"Include Paths\" superClass=\"org.eclipse.cdt.build.core.settings.holder.incpaths\" useByScannerDiscovery=\"false\" valueType=\"includePath\">\n\t\t\t\t\t\t\t\t\t% for include in cleaned_includes:\n                                    % if include.startswith(user_home_dir):\n                                    % if \"windows\" in systype:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${USERPROFILE}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"${HOME}{{include.replace(user_home_dir, '')}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n                                    % else:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{include}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<option id=\"org.eclipse.cdt.build.core.settings.holder.symbols.2122043341\" name=\"Symbols\" superClass=\"org.eclipse.cdt.build.core.settings.holder.symbols\" useByScannerDiscovery=\"false\" valueType=\"definedSymbols\">\n\t\t\t\t\t\t\t\t\t% for define in defines:\n\t\t\t\t\t\t\t\t\t<listOptionValue builtIn=\"false\" value=\"{{define}}\"/>\n\t\t\t\t\t\t\t\t\t% end\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t\t<inputType id=\"org.eclipse.cdt.build.core.settings.holder.inType.207004812\" languageId=\"org.eclipse.cdt.core.gcc\" languageName=\"GNU C\" sourceContentType=\"org.eclipse.cdt.core.cSource,org.eclipse.cdt.core.cHeader\" superClass=\"org.eclipse.cdt.build.core.settings.holder.inType\"/>\n\t\t\t\t\t\t\t</tool>\n\t\t\t\t\t\t</toolChain>\n\t\t\t\t\t</folderInfo>\n\t\t\t\t</configuration>\n\t\t\t</storageModule>\n\t\t\t<storageModule moduleId=\"org.eclipse.cdt.core.externalSettings\"/>\n\t\t</cconfiguration>\n\t</storageModule>\n\t<storageModule moduleId=\"cdtBuildSystem\" version=\"4.0.0\">\n\t\t<project id=\"{{project_name}}.null.189551033\" name=\"{{project_name}}\"/>\n\t</storageModule>\n\t<storageModule moduleId=\"scannerConfiguration\">\n\t\t<autodiscovery enabled=\"true\" problemReportingEnabled=\"true\" selectedProfileId=\"\"/>\n\t\t<scannerConfigBuildInfo instanceId=\"0.910961921\">\n\t\t\t<autodiscovery enabled=\"true\" problemReportingEnabled=\"true\" selectedProfileId=\"\"/>\n\t\t</scannerConfigBuildInfo>\n\t</storageModule>\n\t<storageModule moduleId=\"org.eclipse.cdt.core.LanguageSettingsProviders\"/>\n\t<storageModule moduleId=\"refreshScope\" versionNumber=\"2\">\n\t\t<configuration configurationName=\"Default\">\n\t\t\t<resource resourceType=\"PROJECT\" workspacePath=\"/{{env_name}}\"/>\n\t\t</configuration>\n\t</storageModule>\n\t<storageModule moduleId=\"org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings\"/>\n\t<storageModule moduleId=\"org.eclipse.cdt.make.core.buildtargets\">\n\t\t<buildTargets>\n\t\t\t<target name=\"PlatformIO: Upload using Programmer\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --target program</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Upload SPIFFS image\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --target uploadfs</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Build\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Verbose Build\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --verbose</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Upload\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --target upload</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Verbose Upload\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --target upload --verbose</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Clean\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>run --target clean</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Test\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>test</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Remote\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>remote run --target upload</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Rebuild C/C++ Project Index\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>init --ide eclipse</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: List Devices\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>device list</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Update Project Libraries\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>lib update</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Update All\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>update</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t\t<target name=\"PlatformIO: Upgrade Core\" path=\"\" targetID=\"org.eclipse.cdt.build.MakeTargetBuilder\">\n\t\t\t\t<buildCommand>platformio</buildCommand>\n\t\t\t\t<buildArguments>-f -c eclipse</buildArguments>\n\t\t\t\t<buildTarget>upgrade</buildTarget>\n\t\t\t\t<stopOnError>true</stopOnError>\n\t\t\t\t<useDefaultCommand>false</useDefaultCommand>\n\t\t\t\t<runAllBuilders>false</runAllBuilders>\n\t\t\t</target>\n\t\t</buildTargets>\n\t</storageModule>\n</cproject>\n"
  },
  {
    "path": "platformio/project/integration/tpls/eclipse/.project.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>{{project_name}}</name>\n\t<comment></comment>\n\t<projects>\n\t</projects>\n\t<buildSpec>\n\t\t<buildCommand>\n\t\t\t<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>\n\t\t\t<triggers>clean,full,incremental,</triggers>\n\t\t\t<arguments>\n\t\t\t</arguments>\n\t\t</buildCommand>\n\t\t<buildCommand>\n\t\t\t<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>\n\t\t\t<triggers>full,incremental,</triggers>\n\t\t\t<arguments>\n\t\t\t</arguments>\n\t\t</buildCommand>\n\t</buildSpec>\n\t<natures>\n\t\t<nature>org.eclipse.cdt.core.cnature</nature>\n\t\t<nature>org.eclipse.cdt.core.ccnature</nature>\n\t\t<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>\n\t\t<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>\n\t</natures>\n</projectDescription>\n"
  },
  {
    "path": "platformio/project/integration/tpls/eclipse/.settings/PlatformIO Debugger.launch.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<launchConfiguration type=\"org.eclipse.cdt.launch.applicationLaunchType\">\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.AUTO_SOLIB\" value=\"true\"/>\n<listAttribute key=\"org.eclipse.cdt.dsf.gdb.AUTO_SOLIB_LIST\"/>\n<stringAttribute key=\"org.eclipse.cdt.dsf.gdb.DEBUG_NAME\" value=\"piodebuggdb\"/>\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.DEBUG_ON_FORK\" value=\"false\"/>\n<stringAttribute key=\"org.eclipse.cdt.dsf.gdb.GDB_INIT\" value=\".pioinit\"/>\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.NON_STOP\" value=\"false\"/>\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.REVERSE\" value=\"false\"/>\n<stringAttribute key=\"org.eclipse.cdt.dsf.gdb.REVERSE_MODE\" value=\"UseSoftTrace\"/>\n<listAttribute key=\"org.eclipse.cdt.dsf.gdb.SOLIB_PATH\"/>\n<stringAttribute key=\"org.eclipse.cdt.dsf.gdb.TRACEPOINT_MODE\" value=\"TP_NORMAL_ONLY\"/>\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.UPDATE_THREADLIST_ON_SUSPEND\" value=\"false\"/>\n<booleanAttribute key=\"org.eclipse.cdt.dsf.gdb.internal.ui.launching.LocalApplicationCDebuggerTab.DEFAULTS_SET\" value=\"true\"/>\n<intAttribute key=\"org.eclipse.cdt.launch.ATTR_BUILD_BEFORE_LAUNCH_ATTR\" value=\"1\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.COREFILE_PATH\" value=\"\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.DEBUGGER_ID\" value=\"gdb\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.DEBUGGER_REGISTER_GROUPS\" value=\"\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.DEBUGGER_START_MODE\" value=\"run\"/>\n<booleanAttribute key=\"org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN\" value=\"false\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.DEBUGGER_STOP_AT_MAIN_SYMBOL\" value=\"\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.PROGRAM_NAME\" value=\"{{prog_path}}\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.PROJECT_ATTR\" value=\"{{project_name}}\"/>\n<booleanAttribute key=\"org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_AUTO_ATTR\" value=\"false\"/>\n<stringAttribute key=\"org.eclipse.cdt.launch.PROJECT_BUILD_CONFIG_ID_ATTR\" value=\"0.910961921.1363900502\"/>\n<listAttribute key=\"org.eclipse.debug.core.MAPPED_RESOURCE_PATHS\">\n<listEntry value=\"/{{project_name}}\"/>\n</listAttribute>\n<listAttribute key=\"org.eclipse.debug.core.MAPPED_RESOURCE_TYPES\">\n<listEntry value=\"4\"/>\n</listAttribute>\n<listAttribute key=\"org.eclipse.debug.ui.favoriteGroups\">\n<listEntry value=\"org.eclipse.debug.ui.launchGroup.debug\"/>\n</listAttribute>\n<stringAttribute key=\"org.eclipse.dsf.launch.MEMORY_BLOCKS\" value=\"&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;memoryBlockExpressionList context=&quot;reserved-for-future-use&quot;/&gt;&#10;\"/>\n<stringAttribute key=\"process_factory_id\" value=\"org.eclipse.cdt.dsf.gdb.GdbProcessFactory\"/>\n<stringAttribute key=\"saved_expressions&lt;seperator&gt;Unknown\" value=\"0x55f4\"/>\n</launchConfiguration>\n"
  },
  {
    "path": "platformio/project/integration/tpls/eclipse/.settings/language.settings.xml.tpl",
    "content": "% cxx_stds = [arg for arg in cxx_flags if arg.startswith(\"-std=\")]\n% cxx_std = cxx_stds[-1] if cxx_stds else \"\"\n%\n% if cxx_path.startswith(user_home_dir):\n% if \"windows\" in systype:\n%    cxx_path = \"${USERPROFILE}\" + cxx_path.replace(user_home_dir, \"\")\n% else:\n%    cxx_path = \"${HOME}\" + cxx_path.replace(user_home_dir, \"\")\n% end\n% end\n%\n<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<project>\n\t<configuration id=\"0.910961921\" name=\"Default\">\n\t\t<extension point=\"org.eclipse.cdt.core.LanguageSettingsProvider\">\n\t\t\t<provider copy-of=\"extension\" id=\"org.eclipse.cdt.ui.UserLanguageSettingsProvider\"/>\n\t\t\t<provider-reference id=\"org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider\" ref=\"shared-provider\"/>\n\t\t\t<provider-reference id=\"org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider\" ref=\"shared-provider\"/>\n\t\t\t<provider class=\"org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector\" console=\"false\" env-hash=\"1291887707783033084\" id=\"org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector\" keep-relative-paths=\"false\" name=\"CDT Cross GCC Built-in Compiler Settings\" parameter=\"{{ cxx_path }} ${FLAGS} {{ cxx_std }} -E -P -v -dD &quot;${INPUTS}&quot;\" prefer-non-shared=\"true\">\n\t\t\t\t<language-scope id=\"org.eclipse.cdt.core.gcc\"/>\n\t\t\t\t<language-scope id=\"org.eclipse.cdt.core.g++\"/>\n\t\t\t</provider>\n\t\t</extension>\n\t</configuration>\n\t<configuration id=\"0.910961921.1363900502\" name=\"Debug\">\n\t\t<extension point=\"org.eclipse.cdt.core.LanguageSettingsProvider\">\n\t\t\t<provider copy-of=\"extension\" id=\"org.eclipse.cdt.ui.UserLanguageSettingsProvider\"/>\n\t\t\t<provider-reference id=\"org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider\" ref=\"shared-provider\"/>\n\t\t\t<provider-reference id=\"org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider\" ref=\"shared-provider\"/>\n\t\t\t<provider class=\"org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector\" console=\"false\" env-hash=\"1291887707783033084\" id=\"org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector\" keep-relative-paths=\"false\" name=\"CDT Cross GCC Built-in Compiler Settings\" parameter=\"{{ cxx_path }} ${FLAGS} {{ cxx_std }} -E -P -v -dD &quot;${INPUTS}&quot;\" prefer-non-shared=\"true\">\n\t\t\t\t<language-scope id=\"org.eclipse.cdt.core.gcc\"/>\n\t\t\t\t<language-scope id=\"org.eclipse.cdt.core.g++\"/>\n\t\t\t</provider>\n\t\t</extension>\n\t</configuration>\n</project>\n"
  },
  {
    "path": "platformio/project/integration/tpls/eclipse/.settings/org.eclipse.cdt.core.prefs.tpl",
    "content": "eclipse.preferences.version=1\nenvironment/project/0.910961921/PATH/delimiter={{env_pathsep.replace(\":\", \"\\\\:\")}}\nenvironment/project/0.910961921/PATH/operation=replace\nenvironment/project/0.910961921/PATH/value={{env_path.replace(\":\", \"\\\\:\")}}${PathDelimiter}${PATH}\nenvironment/project/0.910961921/append=true\nenvironment/project/0.910961921/appendContributed=true\nenvironment/project/0.910961921.1363900502/PATH/delimiter={{env_pathsep.replace(\":\", \"\\\\:\")}}\nenvironment/project/0.910961921.1363900502/PATH/operation=replace\nenvironment/project/0.910961921.1363900502/PATH/value={{env_path.replace(\":\", \"\\\\:\")}}${PathDelimiter}${PATH}\nenvironment/project/0.910961921.1363900502/append=true\nenvironment/project/0.910961921.1363900502/appendContributed=true"
  },
  {
    "path": "platformio/project/integration/tpls/emacs/.ccls.tpl",
    "content": "% from platformio.compat import shlex_join\n%\n{{ cc_path }}\n\n{{\"%c\"}} {{ shlex_join(cc_flags) }}\n{{\"%cpp\"}} {{ shlex_join(cxx_flags) }}\n\n% for include in filter_includes(includes):\n-I{{ !include }}\n% end\n\n% for define in defines:\n-D{{ !define }}\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/emacs/.gitignore.tpl",
    "content": ".pio\n.clang_complete\n.ccls\n"
  },
  {
    "path": "platformio/project/integration/tpls/netbeans/nbproject/configurations.xml.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configurationDescriptor version=\"97\">\n  <logicalFolder name=\"root\" displayName=\"root\" projectFiles=\"true\" kind=\"ROOT\">\n    <df root=\".\" name=\"0\">\n      <in>platformio.ini</in>\n    </df>\n    <logicalFolder name=\"ExternalFiles\"\n                   displayName=\"Important Files\"\n                   projectFiles=\"false\"\n                   kind=\"IMPORTANT_FILES_FOLDER\">\n      <itemPath>nbproject/private/launcher.properties</itemPath>\n    </logicalFolder>\n  </logicalFolder>\n  <sourceFolderFilter>^(nbproject|.pio)$</sourceFolderFilter>\n  <sourceRootList>\n    <Elem>.</Elem>\n  </sourceRootList>\n  <projectmakefile></projectmakefile>\n  <confs>\n    <conf name=\"Default\" type=\"0\">\n      <toolsSet>\n        <compilerSet>default</compilerSet>\n        <dependencyChecking>false</dependencyChecking>\n        <rebuildPropChanged>false</rebuildPropChanged>\n      </toolsSet>\n      <codeAssistance>\n        <buildAnalyzer>true</buildAnalyzer>\n        <includeAdditional>true</includeAdditional>\n      </codeAssistance>\n      <makefileType>\n        <makeTool>\n          <buildCommandWorkingDir>.</buildCommandWorkingDir>\n          <buildCommand>\"{{platformio_path}}\" -f -c netbeans run</buildCommand>\n          <cleanCommand>\"{{platformio_path}}\" -f -c netbeans run --target clean</cleanCommand>\n          <executablePath></executablePath>\n          <cTool>\n            % cleaned_includes = filter_includes(includes)\n            <incDir>\n              <pElem>src</pElem>\n              % for include in cleaned_includes:\n              <pElem>{{include}}</pElem>\n              % end\n            </incDir>\n            <preprocessorList>\n              % for define in defines:\n                <Elem>{{define}}</Elem>\n              % end\n            </preprocessorList>\n          </cTool>\n          <ccTool>\n            <incDir>\n              <pElem>src</pElem>\n              % for include in cleaned_includes:\n              <pElem>{{include}}</pElem>\n              % end\n            </incDir>\n            <preprocessorList>\n              % for define in defines:\n                <Elem>{{define}}</Elem>\n              % end\n            </preprocessorList>\n          </ccTool>\n        </makeTool>\n        <preBuild>\n          <preBuildCommandWorkingDir>.</preBuildCommandWorkingDir>\n          <preBuildCommand></preBuildCommand>\n        </preBuild>\n      </makefileType>\n      <item path=\"platformio.ini\" ex=\"false\" tool=\"3\" flavor2=\"0\">\n      </item>\n    </conf>\n  </confs>\n</configurationDescriptor>\n"
  },
  {
    "path": "platformio/project/integration/tpls/netbeans/nbproject/private/configurations.xml.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configurationDescriptor version=\"97\">\n  <logicalFolder name=\"root\" displayName=\"root\" projectFiles=\"true\" kind=\"ROOT\">\n    <df root=\".\" name=\"0\">\n      <df name=\"lib\">\n      </df>\n      <df name=\"src\">\n      </df>\n      <in>platformio.ini</in>\n    </df>\n  </logicalFolder>\n  <projectmakefile></projectmakefile>\n  <confs>\n    <conf name=\"Default\" type=\"0\">\n      <toolsSet>\n        <developmentServer>localhost</developmentServer>\n        <platform>{{4 if \"darwin\" in systype else 2 if \"linux\" in systype else 3}}</platform>\n      </toolsSet>\n      <compile>\n        <compiledirpicklist>\n          <compiledirpicklistitem>.</compiledirpicklistitem>\n          <compiledirpicklistitem>${AUTO_FOLDER}</compiledirpicklistitem>\n        </compiledirpicklist>\n        <compiledir>${AUTO_FOLDER}</compiledir>\n        <compilecommandpicklist>\n          <compilecommandpicklistitem>${MAKE} ${ITEM_NAME}.o</compilecommandpicklistitem>\n          <compilecommandpicklistitem>${AUTO_COMPILE}</compilecommandpicklistitem>\n        </compilecommandpicklist>\n        <compilecommand>${AUTO_COMPILE}</compilecommand>\n      </compile>\n      <dbx_gdbdebugger version=\"1\">\n        <gdb_pathmaps>\n        </gdb_pathmaps>\n        <gdb_interceptlist>\n          <gdbinterceptoptions gdb_all=\"false\" gdb_unhandled=\"true\" gdb_unexpected=\"true\"/>\n        </gdb_interceptlist>\n        <gdb_options>\n          <DebugOptions>\n          </DebugOptions>\n        </gdb_options>\n        <gdb_buildfirst gdb_buildfirst_overriden=\"false\" gdb_buildfirst_old=\"false\"/>\n      </dbx_gdbdebugger>\n      <nativedebugger version=\"1\">\n        <engine>gdb</engine>\n      </nativedebugger>\n      <runprofile version=\"9\">\n        <runcommandpicklist>\n          <runcommandpicklistitem>\"${OUTPUT_PATH}\"</runcommandpicklistitem>\n          <runcommandpicklistitem>{{platformio_path}} -f -c netbeans run --target upload</runcommandpicklistitem>\n        </runcommandpicklist>\n        <runcommand>{{platformio_path}} -f -c netbeans run --target upload</runcommand>\n        <rundir>.</rundir>\n        <buildfirst>false</buildfirst>\n        <terminal-type>0</terminal-type>\n        <remove-instrumentation>0</remove-instrumentation>\n        <environment>\n        </environment>\n      </runprofile>\n    </conf>\n  </confs>\n</configurationDescriptor>\n"
  },
  {
    "path": "platformio/project/integration/tpls/netbeans/nbproject/private/launcher.properties.tpl",
    "content": "# Launchers File syntax:\n#\n# [Must-have property line] \n# launcher1.runCommand=<Run Command>\n# [Optional extra properties] \n# launcher1.displayName=<Display Name, runCommand by default>\n# launcher1.buildCommand=<Build Command, Build Command specified in project properties by default>\n# launcher1.runDir=<Run Directory, ${PROJECT_DIR} by default>\n# launcher1.symbolFiles=<Symbol Files loaded by debugger, ${OUTPUT_PATH} by default>\n# launcher1.env.<Environment variable KEY>=<Environment variable VALUE>\n# (If this value is quoted with ` it is handled as a native command which execution result will become the value)\n# [Common launcher properties]\n# common.runDir=<Run Directory>\n# (This value is overwritten by a launcher specific runDir value if the latter exists)\n# common.env.<Environment variable KEY>=<Environment variable VALUE>\n# (Environment variables from common launcher are merged with launcher specific variables)\n# common.symbolFiles=<Symbol Files loaded by debugger>\n# (This value is overwritten by a launcher specific symbolFiles value if the latter exists)\n#\n# In runDir, symbolFiles and env fields you can use these macros:\n# ${PROJECT_DIR}    -   project directory absolute path\n# ${OUTPUT_PATH}    -   linker output path (relative to project directory path)\n# ${OUTPUT_BASENAME}-   linker output filename\n# ${TESTDIR}        -   test files directory (relative to project directory path)\n# ${OBJECTDIR}      -   object files directory (relative to project directory path)\n# ${CND_DISTDIR}    -   distribution directory (relative to project directory path)\n# ${CND_BUILDDIR}   -   build directory (relative to project directory path)\n# ${CND_PLATFORM}   -   platform name\n# ${CND_CONF}       -   configuration name\n# ${CND_DLIB_EXT}   -   dynamic library extension\n#\n# All the project launchers must be listed in the file!\n#\n# launcher1.runCommand=...\n# launcher2.runCommand=...\n# ...\n# common.runDir=...\n# common.env.KEY=VALUE\n\n# launcher1.runCommand=<type your run command here>"
  },
  {
    "path": "platformio/project/integration/tpls/netbeans/nbproject/private/private.xml.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project-private xmlns=\"http://www.netbeans.org/ns/project-private/1\">\n    <code-assistance-data xmlns=\"http://www.netbeans.org/ns/make-project-private/1\">\n        <code-model-enabled>true</code-model-enabled>\n    </code-assistance-data>\n    <data xmlns=\"http://www.netbeans.org/ns/make-project-private/1\">\n        <activeConfTypeElem>0</activeConfTypeElem>\n        <activeConfIndexElem>0</activeConfIndexElem>\n    </data>\n</project-private>\n"
  },
  {
    "path": "platformio/project/integration/tpls/netbeans/nbproject/project.xml.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://www.netbeans.org/ns/project/1\">\n    <type>org.netbeans.modules.cnd.makeproject</type>\n    <configuration>\n        <data xmlns=\"http://www.netbeans.org/ns/make-project/1\">\n            <name>{{project_name}}</name>\n            <c-extensions/>\n            <cpp-extensions/>\n            <header-extensions/>\n            <sourceEncoding>UTF-8</sourceEncoding>\n            <make-dep-projects/>\n            <sourceRootList>\n                <sourceRootElem>.</sourceRootElem>\n            </sourceRootList>\n            <confList>\n                <confElem>\n                    <name>Default</name>\n                    <type>0</type>\n                </confElem>\n            </confList>\n            <formatting>\n                <project-formatting-style>false</project-formatting-style>\n            </formatting>\n        </data>\n    </configuration>\n</project>\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/.gitignore.tpl",
    "content": ".pio\n.qtc_clangd\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/Makefile.tpl",
    "content": "all:\n\tplatformio -c qtcreator run\n\n# regenerate project files to reflect platformio.ini changes\nproject-update:\n\t@echo \"This will overwrite project metadata files.  Are you sure? [y/N] \" \\\n\t    && read ans && [ $${ans:-'N'} = 'y' ]\n\tplatformio project init --ide qtcreator\n\n# forward any other target (clean, build, etc.) to pio run\n{{'%'}}:\n\tplatformio -c qtcreator run --target $*\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.cflags.tpl",
    "content": "% from platformio.compat import shlex_join\n%\n{{shlex_join(cc_flags).replace('-mlongcalls', '-mlong-calls')}}\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.config.tpl",
    "content": "% for define in defines:\n% tokens = define.split(\"=\", 1)\n% if len(tokens) > 1:\n#define {{tokens[0].strip()}} {{!tokens[1].strip()}}\n% else:\n#define {{define}}\n% end\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.creator.tpl",
    "content": "[General]\n\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.cxxflags.tpl",
    "content": "% from platformio.compat import shlex_join\n%\n{{shlex_join(cxx_flags).replace('-mlongcalls', '-mlong-calls')}}\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.files.tpl",
    "content": "Makefile\nplatformio.ini\n.gitignore\n% for file in src_files:\n{{file}}\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/qtcreator/platformio.includes.tpl",
    "content": "./\n% for include in filter_includes(includes):\n{{include}}\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/sublimetext/.ccls.tpl",
    "content": "% from platformio.compat import shlex_join\n%\n{{ cc_path }}\n\n{{\"%c\"}} {{ shlex_join(cc_flags) }}\n{{\"%cpp\"}} {{ shlex_join(cxx_flags) }}\n\n% for include in filter_includes(includes):\n-I{{ !include }}\n% end\n\n% for define in defines:\n-D{{ !define }}\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/sublimetext/platformio.sublime-project.tpl",
    "content": "{\n\t\"build_systems\":\n\t[\n\t\t{\n\t\t\t\"cmd\":\n\t\t\t[\n\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\"run\"\n\t\t\t],\n\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\"name\": \"PlatformIO\",\n\t\t\t\"variants\":\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"run\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Build\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"run\",\n\t\t\t\t\t\t\"--target\",\n\t\t\t\t\t\t\"upload\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Upload\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"run\",\n\t\t\t\t\t\t\"--target\",\n\t\t\t\t\t\t\"clean\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Clean\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"test\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Test\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"run\",\n\t\t\t\t\t\t\"--target\",\n\t\t\t\t\t\t\"uploadfs\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Upload SPIFFS image\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"update\"\n\t\t\t\t\t],\n\t\t\t\t\t\"file_regex\": \"^(..[^:\\n]*):([0-9]+):?([0-9]+)?:? (.*)$\",\n\t\t\t\t\t\"name\": \"Update platforms and libraries\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"cmd\":\n\t\t\t\t\t[\n\t\t\t\t\t\t\"{{ platformio_path }}\",\n\t\t\t\t\t\t\"-c\", \"sublimetext\",\n\t\t\t\t\t\t\"upgrade\"\n\t\t\t\t\t],\n\t\t\t\t\t\"name\": \"Upgrade PlatformIO Core\"\n\t\t\t\t}\n\t\t\t],\n\t\t\t\"working_dir\": \"${project_path:${folder}}\",\n\t\t\t\"selector\": \"source.c, source.c++\"\n\t\t}\n\t],\n\t\"folders\":\n\t[\n\t\t{\n\t\t\t\"path\": \".\"\n\t\t}\n\t],\n    \"settings\":\n    {\n         \"sublimegdb_workingdir\": \"{{project_dir}}\",\n         \"sublimegdb_exec_cmd\": \"\",\n         \"sublimegdb_commandline\": \"{{ platformio_path }} -f -c sublimetext debug --interface=gdb --interpreter=mi -x .pioinit\"\n\n    }\n}\n"
  },
  {
    "path": "platformio/project/integration/tpls/vim/.ccls.tpl",
    "content": "% from platformio.compat import shlex_join\n%\n{{ cc_path }}\n\n{{\"%c\"}} {{ shlex_join(cc_flags) }}\n{{\"%cpp\"}} {{ shlex_join(cxx_flags) }}\n\n% for include in filter_includes(includes):\n-I{{ !include }}\n% end\n\n% for define in defines:\n-D{{ !define }}\n% end\n"
  },
  {
    "path": "platformio/project/integration/tpls/vim/.gitignore.tpl",
    "content": ".pio\n.clang_complete\n.gcc-flags.json\n.ccls\n"
  },
  {
    "path": "platformio/project/integration/tpls/visualstudio/platformio.vcxproj.filters.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"4.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <ItemGroup>\n    <Filter Include=\"Source Files\">\n      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>\n      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;ino;pde</Extensions>\n    </Filter>\n    <Filter Include=\"Header Files\">\n      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>\n      <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>\n    </Filter>\n  </ItemGroup>\n  <ItemGroup>\n    <None Include=\"platformio.ini\" />\n  </ItemGroup>\n  % for file in src_files:\n  <ItemGroup>\n    % if any(file.endswith(\".%s\" % e) for e in (\"h\", \"hh\", \"hpp\", \"inc\")):\n    <ClInclude Include=\"{{file}}\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    % else:\n    <ClCompile Include=\"{{file}}\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    %end\n  </ItemGroup>\n  % end\n</Project>\n"
  },
  {
    "path": "platformio/project/integration/tpls/visualstudio/platformio.vcxproj.tpl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project DefaultTargets=\"Build\" ToolsVersion=\"12.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup>\n    <Path>{{env_path}}</Path>\n  </PropertyGroup>\n  <ItemGroup Label=\"ProjectConfigurations\">\n    <ProjectConfiguration Include=\"Debug|Win32\">\n      <Configuration>Debug</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n    <ProjectConfiguration Include=\"Release|Win32\">\n      <Configuration>Release</Configuration>\n      <Platform>Win32</Platform>\n    </ProjectConfiguration>\n  </ItemGroup>\n  <PropertyGroup Label=\"Globals\">\n    <ProjectGuid>{0FA9C3A8-452B-41EF-A418-9102B170F49F}</ProjectGuid>\n    <Keyword>MakeFileProj</Keyword>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Makefile</ConfigurationType>\n    <UseDebugLibraries>true</UseDebugLibraries>\n    <PlatformToolset>v120</PlatformToolset>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\" Label=\"Configuration\">\n    <ConfigurationType>Makefile</ConfigurationType>\n    <UseDebugLibraries>false</UseDebugLibraries>\n    <PlatformToolset>v120</PlatformToolset>\n  </PropertyGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />\n  <ImportGroup Label=\"ExtensionSettings\">\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <ImportGroup Label=\"PropertySheets\" Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />\n  </ImportGroup>\n  <PropertyGroup Label=\"UserMacros\" />\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|Win32'\">\n    <NMakeBuildCommandLine>platformio -f -c visualstudio run</NMakeBuildCommandLine>\n    <NMakeCleanCommandLine>platformio -f -c visualstudio run --target clean</NMakeCleanCommandLine>\n    <NMakePreprocessorDefinitions>{{!\";\".join(defines)}}</NMakePreprocessorDefinitions>\n    % cleaned_includes = filter_includes(includes)\n    <NMakeIncludeSearchPath>{{\";\".join([\"$(HOMEDRIVE)$(HOMEPATH)%s\" % i.replace(user_home_dir, \"\") if i.startswith(user_home_dir) else i for i in cleaned_includes])}}</NMakeIncludeSearchPath>\n  </PropertyGroup>\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Release|Win32'\">\n    <NMakeBuildCommandLine>platformio run</NMakeBuildCommandLine>\n    <NMakeCleanCommandLine>platformio run --target clean</NMakeCleanCommandLine>\n    <NMakePreprocessorDefinitions>{{!\";\".join(defines)}}</NMakePreprocessorDefinitions>\n    <NMakeIncludeSearchPath>{{\";\".join([\"$(HOMEDRIVE)$(HOMEPATH)%s\" % i.replace(user_home_dir, \"\") if i.startswith(user_home_dir) else i for i in cleaned_includes])}}</NMakeIncludeSearchPath>\n  </PropertyGroup>\n  <ItemDefinitionGroup>\n  </ItemDefinitionGroup>\n  <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.targets\" />\n  <ImportGroup Label=\"ExtensionTargets\">\n  </ImportGroup>\n  <ItemGroup>\n    <None Include=\"platformio.ini\" />\n  </ItemGroup>\n  % for file in src_files:\n  <ItemGroup>\n    % if any(file.endswith(\".%s\" % e) for e in (\"h\", \"hh\", \"hpp\", \"inc\")):\n    <ClInclude Include=\"{{file}}\">\n      <Filter>Header Files</Filter>\n    </ClInclude>\n    % else:\n    <ClCompile Include=\"{{file}}\">\n      <Filter>Source Files</Filter>\n    </ClCompile>\n    %end\n  </ItemGroup>\n  % end\n</Project>\n"
  },
  {
    "path": "platformio/project/integration/tpls/vscode/.gitignore.tpl",
    "content": ".pio\n.vscode/.browse.c_cpp.db*\n.vscode/c_cpp_properties.json\n.vscode/launch.json\n.vscode/ipch\n"
  },
  {
    "path": "platformio/project/integration/tpls/vscode/.vscode/c_cpp_properties.json.tpl",
    "content": "% import os\n% import platform\n%\n% systype = platform.system().lower()\n%\n% cpp_standards_remap = {\n%   \"c++0x\": \"c++11\",\n%   \"c++1y\": \"c++14\",\n%   \"c++1z\": \"c++17\",\n%   \"c++2a\": \"c++20\",\n%   \"c++2b\": \"c++23\",\n%   \"gnu++0x\": \"gnu++11\",\n%   \"gnu++1y\": \"gnu++14\",\n%   \"gnu++1z\": \"gnu++17\",\n%   \"gnu++2a\": \"gnu++20\",\n%   \"gnu++2b\": \"gnu++23\"\n% }\n%\n% def _escape(text):\n%   return to_unix_path(text).replace('\"', '\\\\\"')\n% end\n%\n% def filter_args(args, allowed, ignore=None):\n%   if not allowed:\n%     return []\n%   end\n%\n%   ignore = ignore or []\n%   result = []\n%   i = 0\n%   length = len(args)\n%   while(i < length):\n%     if any(args[i].startswith(f) for f in allowed) and not any(\n%           args[i].startswith(f) for f in ignore):\n%       result.append(args[i])\n%       if i + 1 < length and not args[i + 1].startswith(\"-\"):\n%         i += 1\n%         result.append(args[i])\n%       end\n%     end\n%     i += 1\n%   end\n%   return result\n% end\n%\n% def _find_abs_path(inc, inc_paths):\n%   for path in inc_paths:\n%     if os.path.isfile(os.path.join(path, inc)):\n%       return os.path.join(path, inc)\n%     end\n%   end\n%   return inc\n% end\n%\n% def _find_forced_includes(flags, inc_paths):\n%   result = []\n%   include_args = (\"-include\", \"-imacros\")\n%   for f in filter_args(flags, include_args):\n%     for arg in include_args:\n%       inc = \"\"\n%       if f.startswith(arg) and f.split(arg)[1].strip():\n%         inc = f.split(arg)[1].strip()\n%       elif not f.startswith(\"-\"):\n%         inc = f\n%       end\n%       if inc:\n%         result.append(_find_abs_path(inc, inc_paths))\n%         break\n%       end\n%     end\n%   end\n%   return result\n% end\n%\n% cleaned_includes = filter_includes(includes, [\"toolchain\"])\n%\n% cc_stds = [arg[5:] for arg in cc_flags if arg.startswith(\"-std=\")]\n% cxx_stds = [arg[5:] for arg in cxx_flags if arg.startswith(\"-std=\")]\n% forced_includes = _find_forced_includes(\n%   filter_args(cc_flags, [\"-include\", \"-imacros\"]), cleaned_includes)\n%\n//\n// !!! WARNING !!! AUTO-GENERATED FILE!\n// PLEASE DO NOT MODIFY IT AND USE \"platformio.ini\":\n// https://docs.platformio.org/page/projectconf/section_env_build.html#build-flags\n//\n{\n    \"configurations\": [\n        {\n            \"name\": \"PlatformIO\",\n            \"includePath\": [\n% for include in cleaned_includes:\n                \"{{ include }}\",\n% end\n                \"\"\n            ],\n            \"browse\": {\n                \"limitSymbolsToIncludedHeaders\": true,\n                \"path\": [\n% for include in cleaned_includes:\n                    \"{{ include }}\",\n% end\n                    \"\"\n                ]\n            },\n            \"defines\": [\n% for define in defines:\n                \"{{! _escape(define) }}\",\n% end\n                \"\"\n            ],\n% if cc_stds:\n            \"cStandard\": \"{{ cc_stds[-1] }}\",\n% end\n% if cxx_stds:\n            \"cppStandard\": \"{{ cpp_standards_remap.get(cxx_stds[-1], cxx_stds[-1]) }}\",\n% end\n% if forced_includes:\n            \"forcedInclude\": [\n% for include in forced_includes:\n                \"{{ include }}\",\n% end\n                \"\"\n            ],\n% end\n            \"compilerPath\": \"{{ cc_path }}\",\n            \"compilerArgs\": [\n% for flag in [\n%     f for f in filter_args(cc_flags, [\"-m\", \"-i\", \"@\"], [\"-include\", \"-imacros\"])\n% ]:\n                \"{{ flag }}\",\n% end\n                \"\"\n            ]\n        }\n    ],\n    \"version\": 4\n}\n"
  },
  {
    "path": "platformio/project/integration/tpls/vscode/.vscode/extensions.json.tpl",
    "content": "% import json\n% import os\n% import re\n%\n% recommendations = set([\"platformio.platformio-ide\"])\n% unwantedRecommendations = set([\"ms-vscode.cpptools-extension-pack\"])\n% previous_json = os.path.join(project_dir, \".vscode\", \"extensions.json\")\n% if os.path.isfile(previous_json):\n%   fp = open(previous_json)\n%   contents = re.sub(r\"^\\s*//.*$\", \"\", fp.read(), flags=re.M).strip()\n%   fp.close()\n%   if contents:\n%       try:\n%           data = json.loads(contents)\n%           recommendations |= set(data.get(\"recommendations\", []))\n%           unwantedRecommendations |= set(data.get(\"unwantedRecommendations\", []))\n%       except ValueError:\n%           pass\n%       end\n%   end\n% end\n{\n    // See http://go.microsoft.com/fwlink/?LinkId=827846\n    // for the documentation about the extensions.json format\n    \"recommendations\": [\n% for i, item in enumerate(sorted(recommendations)):\n        \"{{ item }}\"{{ (\",\" if (i + 1) < len(recommendations) else \"\") }}\n% end\n    ],\n    \"unwantedRecommendations\": [\n% for i, item in enumerate(sorted(unwantedRecommendations)):\n        \"{{ item }}\"{{ (\",\" if (i + 1) < len(unwantedRecommendations) else \"\") }}\n% end\n    ]\n}\n"
  },
  {
    "path": "platformio/project/integration/tpls/vscode/.vscode/launch.json.tpl",
    "content": "% import json\n% import os\n%\n% def _escape(text):\n%   return text.replace('\"', '\\\"')\n% end\n%\n% def _escape_path(path):\n%   return path.replace('\\\\\\\\', '/').replace('\\\\', '/').replace('\"', '\\\\\"')\n% end\n%\n% def get_pio_configurations():\n%  predebug = {\n%    \"type\": \"platformio-debug\",\n%    \"request\": \"launch\",\n%    \"name\": \"PIO Debug (skip Pre-Debug)\",\n%    \"executable\": _escape_path(prog_path),\n%    \"projectEnvName\": env_name if forced_env_name else default_debug_env_name,\n%    \"toolchainBinDir\": _escape_path(os.path.dirname(cc_path)),\n%    \"internalConsoleOptions\": \"openOnSessionStart\",\n%  }\n%\n%  if svd_path:\n%    predebug[\"svdPath\"] = _escape_path(svd_path)\n%  end\n%  debug = predebug.copy()\n%  debug[\"name\"] = \"PIO Debug\"\n%  debug[\"preLaunchTask\"] = {\n%    \"type\": \"PlatformIO\",\n%    \"task\": (\"Pre-Debug (%s)\" % env_name) if len(config.envs()) > 1 and forced_env_name else \"Pre-Debug\",\n%  }\n%  noloading = predebug.copy()\n%  noloading[\"name\"] = \"PIO Debug (without uploading)\"\n%  noloading[\"loadMode\"] = \"manual\"\n%  return [debug, predebug, noloading]\n% end\n%\n% def _remove_comments(lines):\n%  data = \"\"\n%  for line in lines:\n%    line = line.strip()\n%    if not line.startswith(\"//\"):\n%      data += line\n%    end\n%  end\n%  return data\n% end\n%\n% def _contains_custom_configurations(launch_config):\n%  pio_config_names = [\n%    c[\"name\"]\n%    for c in get_pio_configurations()\n%  ]\n%  return any(\n%    c.get(\"type\", \"\") != \"platformio-debug\"\n%    or c.get(\"name\", \"\") in pio_config_names\n%    for c in launch_config.get(\"configurations\", [])\n%  )\n% end\n%\n% def _remove_pio_configurations(launch_config):\n%  if \"configurations\" not in launch_config:\n%    return launch_config\n%  end\n%\n%  pio_config_names = [\n%    c[\"name\"]\n%    for c in get_pio_configurations()\n%  ]\n%  external_configurations = [\n%    c\n%    for c in launch_config[\"configurations\"]\n%    if c.get(\"type\", \"\") != \"platformio-debug\" or c.get(\"name\", \"\") not in pio_config_names\n%  ]\n%\n%  launch_config[\"configurations\"] = external_configurations\n%  return launch_config\n% end\n%\n% def get_launch_configuration():\n%  launch_config = {\"version\": \"0.2.0\", \"configurations\": []}\n%  launch_file = os.path.join(project_dir, \".vscode\", \"launch.json\")\n%  if os.path.isfile(launch_file):\n%    with open(launch_file, \"r\", encoding=\"utf8\") as fp:\n%      launch_data = _remove_comments(fp.readlines())\n%      try:\n%        prev_config = json.loads(launch_data)\n%        if _contains_custom_configurations(prev_config):\n%          launch_config = _remove_pio_configurations(prev_config)\n%        end\n%      except:\n%        pass\n%      end\n%    end\n%  end\n%  launch_config[\"configurations\"].extend(get_pio_configurations())\n%  return launch_config\n% end\n%\n// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY\n//\n// PlatformIO Debugging Solution\n//\n// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html\n// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html\n\n{{ json.dumps(get_launch_configuration(), indent=4, ensure_ascii=False) }}\n"
  },
  {
    "path": "platformio/project/options.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=redefined-builtin, too-many-arguments\n\nimport os\nfrom collections import OrderedDict\n\nimport click\n\nfrom platformio import fs\nfrom platformio.compat import IS_WINDOWS\n\n\nclass ConfigOption:  # pylint: disable=too-many-instance-attributes,too-many-positional-arguments\n    def __init__(\n        self,\n        scope,\n        group,\n        name,\n        description,\n        type=str,\n        multiple=False,\n        sysenvvar=None,\n        buildenvvar=None,\n        oldnames=None,\n        default=None,\n        validate=None,\n    ):\n        self.scope = scope\n        self.group = group\n        self.name = name\n        self.description = description\n        self.type = type\n        self.multiple = multiple\n        self.sysenvvar = sysenvvar\n        self.buildenvvar = buildenvvar\n        self.oldnames = oldnames\n        self.default = default\n        self.validate = validate\n\n    def as_dict(self):\n        result = dict(\n            scope=self.scope,\n            group=self.group,\n            name=self.name,\n            description=self.description,\n            type=\"string\",\n            multiple=self.multiple,\n            sysenvvar=self.sysenvvar,\n            default=self.default() if callable(self.default) else self.default,\n        )\n        if isinstance(self.type, click.ParamType):\n            result[\"type\"] = self.type.name\n        if isinstance(self.type, (click.IntRange, click.FloatRange)):\n            result[\"min\"] = self.type.min\n            result[\"max\"] = self.type.max\n        if isinstance(self.type, click.Choice):\n            result[\"choices\"] = self.type.choices\n        return result\n\n\ndef ConfigPlatformioOption(*args, **kwargs):\n    return ConfigOption(\"platformio\", *args, **kwargs)\n\n\ndef ConfigEnvOption(*args, **kwargs):\n    return ConfigOption(\"env\", *args, **kwargs)\n\n\ndef validate_dir(path):\n    if not path:\n        return path\n    # if not all values expanded, ignore validation\n    if \"${\" in path and \"}\" in path:\n        return path\n    if path.startswith(\"~\"):\n        path = fs.expanduser(path)\n    return os.path.abspath(path)\n\n\ndef get_default_core_dir():\n    path = os.path.join(fs.expanduser(\"~\"), \".platformio\")\n    if IS_WINDOWS:\n        win_core_dir = os.path.splitdrive(path)[0] + \"\\\\.platformio\"\n        if os.path.isdir(win_core_dir):\n            return win_core_dir\n    return path\n\n\nProjectOptions = OrderedDict(\n    [\n        (\"%s.%s\" % (option.scope, option.name), option)\n        for option in [\n            #\n            # [platformio]\n            #\n            ConfigPlatformioOption(\n                group=\"generic\",\n                name=\"name\",\n                description=\"A project name\",\n                default=lambda: os.path.basename(os.getcwd()),\n            ),\n            ConfigPlatformioOption(\n                group=\"generic\",\n                name=\"description\",\n                description=\"Describe a project with a short information\",\n            ),\n            ConfigPlatformioOption(\n                group=\"generic\",\n                name=\"default_envs\",\n                description=(\n                    \"Configure a list with environments which PlatformIO should \"\n                    \"process by default\"\n                ),\n                oldnames=[\"env_default\"],\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_DEFAULT_ENVS\",\n            ),\n            ConfigPlatformioOption(\n                group=\"generic\",\n                name=\"extra_configs\",\n                description=(\n                    \"Extend main configuration with the extra configuration files\"\n                ),\n                multiple=True,\n            ),\n            # Dirs\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"core_dir\",\n                description=(\n                    \"PlatformIO Core location where it keeps installed development \"\n                    \"platforms, packages, global libraries, \"\n                    \"and other internal information\"\n                ),\n                oldnames=[\"home_dir\"],\n                sysenvvar=\"PLATFORMIO_CORE_DIR\",\n                default=get_default_core_dir,\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"globallib_dir\",\n                description=(\n                    \"A library folder/storage where PlatformIO Library Dependency \"\n                    \"Finder (LDF) looks for global libraries\"\n                ),\n                sysenvvar=\"PLATFORMIO_GLOBALLIB_DIR\",\n                default=os.path.join(\"${platformio.core_dir}\", \"lib\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"platforms_dir\",\n                description=(\n                    \"A location where PlatformIO Core keeps installed development \"\n                    \"platforms\"\n                ),\n                sysenvvar=\"PLATFORMIO_PLATFORMS_DIR\",\n                default=os.path.join(\"${platformio.core_dir}\", \"platforms\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"packages_dir\",\n                description=(\n                    \"A location where PlatformIO Core keeps installed packages\"\n                ),\n                sysenvvar=\"PLATFORMIO_PACKAGES_DIR\",\n                default=os.path.join(\"${platformio.core_dir}\", \"packages\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"cache_dir\",\n                description=(\n                    \"A location where PlatformIO Core stores caching information \"\n                    \"(requests to PlatformIO Registry, downloaded packages and \"\n                    \"other service information)\"\n                ),\n                sysenvvar=\"PLATFORMIO_CACHE_DIR\",\n                default=os.path.join(\"${platformio.core_dir}\", \".cache\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"build_cache_dir\",\n                description=(\n                    \"A location where PlatformIO Core keeps derived files from a \"\n                    \"build system (objects, firmwares, ELFs) and caches them between \"\n                    \"build environments\"\n                ),\n                sysenvvar=\"PLATFORMIO_BUILD_CACHE_DIR\",\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"workspace_dir\",\n                description=(\n                    \"A path to a project workspace directory where PlatformIO keeps \"\n                    \"by default compiled objects, static libraries, firmwares, and \"\n                    \"external library dependencies\"\n                ),\n                sysenvvar=\"PLATFORMIO_WORKSPACE_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \".pio\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"build_dir\",\n                description=(\n                    \"PlatformIO Build System uses this folder for project environments\"\n                    \" to store compiled object files, static libraries, firmwares, \"\n                    \"and other cached information\"\n                ),\n                sysenvvar=\"PLATFORMIO_BUILD_DIR\",\n                default=os.path.join(\"${platformio.workspace_dir}\", \"build\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"libdeps_dir\",\n                description=(\n                    \"Internal storage where Library Manager will install project \"\n                    \"dependencies declared via `lib_deps` option\"\n                ),\n                sysenvvar=\"PLATFORMIO_LIBDEPS_DIR\",\n                default=os.path.join(\"${platformio.workspace_dir}\", \"libdeps\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"include_dir\",\n                description=(\n                    \"A default location for project header files. PlatformIO Build \"\n                    \"System automatically adds this path to CPPPATH scope\"\n                ),\n                sysenvvar=\"PLATFORMIO_INCLUDE_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"include\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"src_dir\",\n                description=(\n                    \"A default location where PlatformIO Build System looks for the \"\n                    \"project C/C++ source files\"\n                ),\n                sysenvvar=\"PLATFORMIO_SRC_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"src\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"lib_dir\",\n                description=\"A storage for the custom/private project libraries\",\n                sysenvvar=\"PLATFORMIO_LIB_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"lib\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"data_dir\",\n                description=(\n                    \"A data directory to store contents which can be uploaded to \"\n                    \"file system (SPIFFS, etc.)\"\n                ),\n                sysenvvar=\"PLATFORMIO_DATA_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"data\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"test_dir\",\n                description=(\n                    \"A location where PlatformIO Unit Testing engine looks for \"\n                    \"test source files\"\n                ),\n                sysenvvar=\"PLATFORMIO_TEST_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"test\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"boards_dir\",\n                description=\"A storage for custom board manifests\",\n                sysenvvar=\"PLATFORMIO_BOARDS_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"boards\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"monitor_dir\",\n                description=\"A storage for custom monitor filters\",\n                sysenvvar=\"PLATFORMIO_MONITOR_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"monitor\"),\n                validate=validate_dir,\n            ),\n            ConfigPlatformioOption(\n                group=\"directory\",\n                name=\"shared_dir\",\n                description=(\n                    \"A location which PlatformIO Remote Development service uses to \"\n                    \"synchronize extra files between remote machines\"\n                ),\n                sysenvvar=\"PLATFORMIO_SHARED_DIR\",\n                default=os.path.join(\"${PROJECT_DIR}\", \"shared\"),\n                validate=validate_dir,\n            ),\n            #\n            # [env]\n            #\n            # Platform\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"platform\",\n                description=\"A name or specification of development platform\",\n                buildenvvar=\"PIOPLATFORM\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"platform_packages\",\n                description=\"Custom packages and specifications\",\n                multiple=True,\n            ),\n            # Board\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"board\",\n                description=\"A board ID\",\n                buildenvvar=\"BOARD\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"framework\",\n                description=\"A list of project dependent frameworks\",\n                multiple=True,\n                buildenvvar=\"PIOFRAMEWORK\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"board_build.mcu\",\n                description=\"A custom board MCU\",\n                oldnames=[\"board_mcu\"],\n                buildenvvar=\"BOARD_MCU\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"board_build.f_cpu\",\n                description=\"A custom MCU frequency\",\n                oldnames=[\"board_f_cpu\"],\n                buildenvvar=\"BOARD_F_CPU\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"board_build.f_flash\",\n                description=\"A custom flash frequency\",\n                oldnames=[\"board_f_flash\"],\n                buildenvvar=\"BOARD_F_FLASH\",\n            ),\n            ConfigEnvOption(\n                group=\"platform\",\n                name=\"board_build.flash_mode\",\n                description=\"A custom flash mode\",\n                oldnames=[\"board_flash_mode\"],\n                buildenvvar=\"BOARD_FLASH_MODE\",\n            ),\n            # Build\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"build_type\",\n                description=\"Project build configuration\",\n                type=click.Choice([\"release\", \"test\", \"debug\"]),\n                default=\"release\",\n            ),\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"build_flags\",\n                description=(\n                    \"Custom build flags/options for preprocessing, compilation, \"\n                    \"assembly, and linking processes\"\n                ),\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_BUILD_FLAGS\",\n                buildenvvar=\"BUILD_FLAGS\",\n            ),\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"build_src_flags\",\n                oldnames=[\"src_build_flags\"],\n                description=(\n                    \"The same as `build_flags` but configures flags the only for \"\n                    \"project source files in the `src` folder\"\n                ),\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_BUILD_SRC_FLAGS\",\n                buildenvvar=\"SRC_BUILD_FLAGS\",\n            ),\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"build_unflags\",\n                description=\"A list with flags/option which should be removed\",\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_BUILD_UNFLAGS\",\n                buildenvvar=\"BUILD_UNFLAGS\",\n            ),\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"build_src_filter\",\n                oldnames=[\"src_filter\"],\n                description=(\n                    \"Control which source files from the `src` folder should \"\n                    \"be included/excluded from a build process\"\n                ),\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_BUILD_SRC_FILTER\",\n                buildenvvar=\"SRC_FILTER\",\n                default=\"+<*> -<.git/> -<.svn/>\",\n            ),\n            ConfigEnvOption(\n                group=\"build\",\n                name=\"targets\",\n                description=\"A custom list of targets for PlatformIO Build System\",\n                multiple=True,\n            ),\n            # Upload\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_port\",\n                description=(\n                    \"An upload port which `uploader` tool uses for a firmware flashing\"\n                ),\n                sysenvvar=\"PLATFORMIO_UPLOAD_PORT\",\n                buildenvvar=\"UPLOAD_PORT\",\n            ),\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_protocol\",\n                description=\"A protocol that `uploader` tool uses to talk to a board\",\n                buildenvvar=\"UPLOAD_PROTOCOL\",\n            ),\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_speed\",\n                description=(\n                    \"A connection speed (baud rate) which `uploader` tool uses when \"\n                    \"sending firmware to a board\"\n                ),\n                type=click.INT,\n                buildenvvar=\"UPLOAD_SPEED\",\n            ),\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_flags\",\n                description=\"An extra flags for `uploader` tool\",\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_UPLOAD_FLAGS\",\n                buildenvvar=\"UPLOAD_FLAGS\",\n            ),\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_resetmethod\",\n                description=\"A custom reset method\",\n                buildenvvar=\"UPLOAD_RESETMETHOD\",\n            ),\n            ConfigEnvOption(\n                group=\"upload\",\n                name=\"upload_command\",\n                description=(\n                    \"A custom upload command which overwrites a default from \"\n                    \"development platform\"\n                ),\n                buildenvvar=\"UPLOADCMD\",\n            ),\n            # Monitor\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_port\",\n                description=\"A port, a number or a device name\",\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_speed\",\n                description=\"A monitor speed (baud rate)\",\n                type=click.INT,\n                oldnames=[\"monitor_baud\"],\n                default=9600,\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_parity\",\n                description=\"A monitor parity checking\",\n                type=click.Choice([\"N\", \"E\", \"O\", \"S\", \"M\"]),\n                default=\"N\",\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_filters\",\n                description=(\n                    \"Apply the filters and text transformations to monitor output\"\n                ),\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_rts\",\n                description=\"A monitor initial RTS line state\",\n                type=click.IntRange(0, 1),\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_dtr\",\n                description=\"A monitor initial DTR line state\",\n                type=click.IntRange(0, 1),\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_eol\",\n                description=\"A monitor end of line mode\",\n                type=click.Choice([\"CR\", \"LF\", \"CRLF\"]),\n                default=\"CRLF\",\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_raw\",\n                description=\"Disable encodings/transformations of device output\",\n                type=click.BOOL,\n                default=False,\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_echo\",\n                description=\"Enable a monitor local echo\",\n                type=click.BOOL,\n                default=False,\n            ),\n            ConfigEnvOption(\n                group=\"monitor\",\n                name=\"monitor_encoding\",\n                description=\"Custom encoding (e.g. hexlify, Latin-1, UTF-8)\",\n                default=\"UTF-8\",\n            ),\n            # Library\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_deps\",\n                description=(\n                    \"A list of project library dependencies which should be installed \"\n                    \"automatically before a build process\"\n                ),\n                oldnames=[\"lib_use\", \"lib_force\", \"lib_install\"],\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_ignore\",\n                description=(\n                    \"A list of library names which should be ignored by \"\n                    \"Library Dependency Finder (LDF)\"\n                ),\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_extra_dirs\",\n                description=(\n                    \"A list of extra directories/storages where Library Dependency \"\n                    \"Finder (LDF) will look for dependencies\"\n                ),\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_LIB_EXTRA_DIRS\",\n            ),\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_ldf_mode\",\n                description=(\n                    \"Control how Library Dependency Finder (LDF) should analyze \"\n                    \"dependencies (`#include` directives)\"\n                ),\n                type=click.Choice([\"off\", \"chain\", \"deep\", \"chain+\", \"deep+\"]),\n                default=\"chain\",\n            ),\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_compat_mode\",\n                description=(\n                    \"Configure a strictness (compatibility mode by frameworks, \"\n                    \"development platforms) of Library Dependency Finder (LDF)\"\n                ),\n                type=click.Choice([\"off\", \"soft\", \"strict\"]),\n                default=\"soft\",\n            ),\n            ConfigEnvOption(\n                group=\"library\",\n                name=\"lib_archive\",\n                description=(\n                    \"Create an archive (`*.a`, static library) from the object files \"\n                    \"and link it into a firmware (program)\"\n                ),\n                type=click.BOOL,\n                default=True,\n            ),\n            # Check\n            ConfigEnvOption(\n                group=\"check\",\n                name=\"check_tool\",\n                description=\"A list of check tools used for analysis\",\n                type=click.Choice([\"cppcheck\", \"clangtidy\", \"pvs-studio\"]),\n                multiple=True,\n                default=[\"cppcheck\"],\n            ),\n            ConfigEnvOption(\n                group=\"check\",\n                name=\"check_src_filters\",\n                oldnames=[\"check_patterns\"],\n                description=(\n                    \"Configure a list of target files or directories for checking\"\n                ),\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"check\",\n                name=\"check_flags\",\n                description=\"An extra flags to be passed to a check tool\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"check\",\n                name=\"check_severity\",\n                description=\"List of defect severity types for analysis\",\n                multiple=True,\n                type=click.Choice([\"low\", \"medium\", \"high\"]),\n                default=[\"low\", \"medium\", \"high\"],\n            ),\n            ConfigEnvOption(\n                group=\"check\",\n                name=\"check_skip_packages\",\n                description=\"Skip checking includes from packages directory\",\n                type=click.BOOL,\n                default=False,\n            ),\n            # Test\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_framework\",\n                description=\"A unit testing framework\",\n                type=click.Choice([\"doctest\", \"googletest\", \"unity\", \"custom\"]),\n                default=\"unity\",\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_filter\",\n                description=\"Process tests where the name matches specified patterns\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_ignore\",\n                description=\"Ignore tests where the name matches specified patterns\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_port\",\n                description=\"A serial port to communicate with a target device\",\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_speed\",\n                description=\"A connection speed (baud rate) to communicate with \"\n                \"a target device\",\n                type=click.INT,\n                default=115200,\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_build_src\",\n                oldnames=[\"test_build_project_src\"],\n                description=\"Build main source code in pair with a test code\",\n                type=click.BOOL,\n                default=False,\n            ),\n            ConfigEnvOption(\n                group=\"test\",\n                name=\"test_testing_command\",\n                multiple=True,\n                description=(\n                    \"A custom testing command that runs test cases \"\n                    \"and returns results to the standard output\"\n                ),\n            ),\n            # Debug\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_tool\",\n                description=\"A name of debugging tool\",\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_build_flags\",\n                description=(\n                    \"Custom debug flags/options for preprocessing, compilation, \"\n                    \"assembly, and linking processes\"\n                ),\n                multiple=True,\n                default=[\"-Og\", \"-g2\", \"-ggdb2\"],\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_init_break\",\n                description=(\n                    \"An initial breakpoint that makes program stop whenever a \"\n                    \"certain point in the program is reached\"\n                ),\n                default=\"tbreak main\",\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_init_cmds\",\n                description=\"Initial commands to be passed to a back-end debugger\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_extra_cmds\",\n                description=\"An extra commands to be passed to a back-end debugger\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_load_cmds\",\n                description=(\n                    \"A list of commands to be used to load program/firmware \"\n                    \"to a target device\"\n                ),\n                oldnames=[\"debug_load_cmd\"],\n                multiple=True,\n                default=[\"load\"],\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_load_mode\",\n                description=(\n                    \"Allows one to control when PlatformIO should load debugging \"\n                    \"firmware to the end target\"\n                ),\n                type=click.Choice([\"always\", \"modified\", \"manual\"]),\n                default=\"always\",\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_server\",\n                description=\"Allows one to setup a custom debugging server\",\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_port\",\n                description=(\n                    \"A debugging port of a remote target (a serial device or \"\n                    \"network address)\"\n                ),\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_speed\",\n                description=\"A debug adapter speed (JTAG speed)\",\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_svd_path\",\n                description=(\n                    \"A custom path to SVD file which contains information about \"\n                    \"device peripherals\"\n                ),\n                type=click.Path(exists=True, file_okay=True, dir_okay=False),\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_server_ready_pattern\",\n                description=(\n                    \"A pattern to determine when debugging server is ready \"\n                    \"for an incoming connection\"\n                ),\n            ),\n            ConfigEnvOption(\n                group=\"debug\",\n                name=\"debug_test\",\n                description=(\"A name of a unit test to be debugged\"),\n            ),\n            # Advanced\n            ConfigEnvOption(\n                group=\"advanced\",\n                name=\"extends\",\n                description=(\n                    \"Inherit configuration from other sections or build environments\"\n                ),\n                multiple=True,\n            ),\n            ConfigEnvOption(\n                group=\"advanced\",\n                name=\"extra_scripts\",\n                description=\"A list of PRE and POST extra scripts\",\n                oldnames=[\"extra_script\"],\n                multiple=True,\n                sysenvvar=\"PLATFORMIO_EXTRA_SCRIPTS\",\n            ),\n        ]\n    ]\n)\n\n\ndef get_config_options_schema():\n    return [opt.as_dict() for opt in ProjectOptions.values()]\n"
  },
  {
    "path": "platformio/project/savedeps.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom platformio.compat import ci_strings_are_equal\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import InvalidProjectConfError\n\n\ndef pkg_to_save_spec(pkg, user_spec):\n    assert isinstance(user_spec, PackageSpec)\n    if user_spec.external:\n        return user_spec\n    return PackageSpec(\n        owner=pkg.metadata.spec.owner,\n        name=pkg.metadata.spec.name,\n        requirements=user_spec.requirements\n        or (\n            (\"^%s\" % pkg.metadata.version)\n            if not pkg.metadata.version.build\n            else pkg.metadata.version\n        ),\n    )\n\n\ndef save_project_dependencies(\n    project_dir, specs, scope, action=\"add\", environments=None\n):\n    config = ProjectConfig.get_instance(os.path.join(project_dir, \"platformio.ini\"))\n    config.validate(environments)\n    for env in config.envs():\n        if environments and env not in environments:\n            continue\n        config.expand_interpolations = False\n        candidates = []\n        try:\n            candidates = _ignore_deps_by_specs(config.get(\"env:\" + env, scope), specs)\n        except InvalidProjectConfError:\n            pass\n        if action == \"add\":\n            candidates.extend(spec.as_dependency() for spec in specs)\n        if candidates:\n            result = []\n            for item in candidates:\n                item = item.strip()\n                if item and item not in result:\n                    result.append(item)\n            config.set(\"env:\" + env, scope, result)\n        elif config.has_option(\"env:\" + env, scope):\n            config.remove_option(\"env:\" + env, scope)\n    config.save()\n\n\ndef _ignore_deps_by_specs(deps, specs):\n    result = []\n    for dep in deps:\n        ignore_conditions = []\n        depspec = PackageSpec(dep)\n        if depspec.external:\n            ignore_conditions.append(depspec in specs)\n        else:\n            for spec in specs:\n                if depspec.owner:\n                    ignore_conditions.append(\n                        ci_strings_are_equal(depspec.owner, spec.owner)\n                        and ci_strings_are_equal(depspec.name, spec.name)\n                    )\n                else:\n                    ignore_conditions.append(\n                        ci_strings_are_equal(depspec.name, spec.name)\n                    )\n        if not any(ignore_conditions):\n            result.append(dep)\n    return result\n"
  },
  {
    "path": "platformio/public.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-import\n\nfrom platformio.device.list.util import list_logical_devices, list_serial_ports\nfrom platformio.device.monitor.filters.base import DeviceMonitorFilterBase\nfrom platformio.fs import to_unix_path\nfrom platformio.platform.base import PlatformBase\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.helpers import get_project_watch_lib_dirs, load_build_metadata\nfrom platformio.project.options import get_config_options_schema\nfrom platformio.test.result import TestCase, TestCaseSource, TestStatus\nfrom platformio.test.runners.base import TestRunnerBase\nfrom platformio.test.runners.doctest import DoctestTestRunner\nfrom platformio.test.runners.googletest import GoogletestTestRunner\nfrom platformio.test.runners.unity import UnityTestRunner\nfrom platformio.util import get_systype\n"
  },
  {
    "path": "platformio/registry/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/registry/access/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/registry/access/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.registry.access.commands.grant import access_grant_cmd\nfrom platformio.registry.access.commands.list import access_list_cmd\nfrom platformio.registry.access.commands.private import access_private_cmd\nfrom platformio.registry.access.commands.public import access_public_cmd\nfrom platformio.registry.access.commands.revoke import access_revoke_cmd\n\n\n@click.group(\n    \"access\",\n    commands=[\n        access_grant_cmd,\n        access_list_cmd,\n        access_private_cmd,\n        access_public_cmd,\n        access_revoke_cmd,\n    ],\n    short_help=\"Manage resource access\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/registry/access/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/registry/access/commands/grant.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.registry.access.validate import validate_client, validate_urn\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"grant\", short_help=\"Grant access\")\n@click.argument(\"level\", type=click.Choice([\"admin\", \"maintainer\", \"guest\"]))\n@click.argument(\n    \"client\",\n    metavar=\"[<ORGNAME:TEAMNAME>|<USERNAME>]\",\n    callback=lambda _, __, value: validate_client(value),\n)\n@click.argument(\n    \"urn\",\n    callback=lambda _, __, value: validate_urn(value),\n)\n@click.option(\"--urn-type\", type=click.Choice([\"prn:reg:pkg\"]), default=\"prn:reg:pkg\")\ndef access_grant_cmd(level, client, urn, urn_type):  # pylint: disable=unused-argument\n    reg_client = RegistryClient()\n    reg_client.grant_access_for_resource(urn=urn, client=client, level=level)\n    return click.secho(\n        \"Access for resource %s has been granted for %s\" % (urn, client),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/registry/access/commands/list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"list\", short_help=\"List published resources\")\n@click.argument(\"owner\", required=False)\n@click.option(\"--urn-type\", type=click.Choice([\"prn:reg:pkg\"]), default=\"prn:reg:pkg\")\n@click.option(\"--json-output\", is_flag=True)\ndef access_list_cmd(owner, urn_type, json_output):  # pylint: disable=unused-argument\n    reg_client = RegistryClient()\n    resources = reg_client.list_resources(owner=owner)\n    if json_output:\n        return click.echo(json.dumps(resources))\n    if not resources:\n        return click.secho(\"You do not have any resources.\", fg=\"yellow\")\n    for resource in resources:\n        click.echo()\n        click.secho(resource.get(\"name\"), fg=\"cyan\")\n        click.echo(\"-\" * len(resource.get(\"name\")))\n        table_data = []\n        table_data.append((\"URN:\", resource.get(\"urn\")))\n        table_data.append((\"Owner:\", resource.get(\"owner\")))\n        table_data.append(\n            (\n                \"Access:\",\n                (\n                    click.style(\"Private\", fg=\"red\")\n                    if resource.get(\"private\", False)\n                    else \"Public\"\n                ),\n            )\n        )\n        table_data.append(\n            (\n                \"Access level(s):\",\n                \", \".join(\n                    (level.capitalize() for level in resource.get(\"access_levels\"))\n                ),\n            )\n        )\n        click.echo(tabulate(table_data, tablefmt=\"plain\"))\n    return click.echo()\n"
  },
  {
    "path": "platformio/registry/access/commands/private.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.registry.access.validate import validate_urn\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"private\", short_help=\"Make resource private\")\n@click.argument(\n    \"urn\",\n    callback=lambda _, __, value: validate_urn(value),\n)\n@click.option(\"--urn-type\", type=click.Choice([\"prn:reg:pkg\"]), default=\"prn:reg:pkg\")\ndef access_private_cmd(urn, urn_type):  # pylint: disable=unused-argument\n    client = RegistryClient()\n    client.update_resource(urn=urn, private=1)\n    return click.secho(\n        \"The resource %s has been successfully updated.\" % urn,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/registry/access/commands/public.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.registry.access.validate import validate_urn\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"public\", short_help=\"Make resource public\")\n@click.argument(\n    \"urn\",\n    callback=lambda _, __, value: validate_urn(value),\n)\n@click.option(\"--urn-type\", type=click.Choice([\"prn:reg:pkg\"]), default=\"prn:reg:pkg\")\ndef access_public_cmd(urn, urn_type):  # pylint: disable=unused-argument\n    client = RegistryClient()\n    client.update_resource(urn=urn, private=0)\n    return click.secho(\n        \"The resource %s has been successfully updated.\" % urn,\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/registry/access/commands/revoke.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.registry.access.validate import validate_client, validate_urn\nfrom platformio.registry.client import RegistryClient\n\n\n@click.command(\"revoke\", short_help=\"Revoke access\")\n@click.argument(\n    \"client\",\n    metavar=\"[ORGNAME:TEAMNAME|USERNAME]\",\n    callback=lambda _, __, value: validate_client(value),\n)\n@click.argument(\n    \"urn\",\n    callback=lambda _, __, value: validate_urn(value),\n)\n@click.option(\"--urn-type\", type=click.Choice([\"prn:reg:pkg\"]), default=\"prn:reg:pkg\")\ndef access_revoke_cmd(client, urn, urn_type):  # pylint: disable=unused-argument\n    reg_client = RegistryClient()\n    reg_client.revoke_access_from_resource(urn=urn, client=client)\n    return click.secho(\n        \"Access for resource %s has been revoked for %s\" % (urn, client),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/registry/access/validate.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\n\nimport click\n\nfrom platformio.account.validate import validate_orgname_teamname, validate_username\n\n\ndef validate_urn(value):\n    value = str(value).strip()\n    if not re.match(r\"^prn:reg:pkg:(\\d+):(\\w+)$\", value, flags=re.I):\n        raise click.BadParameter(\"Invalid URN format.\")\n    return value\n\n\ndef validate_client(value):\n    if \":\" in value:\n        validate_orgname_teamname(value)\n    else:\n        validate_username(value)\n    return value\n"
  },
  {
    "path": "platformio/registry/client.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-arguments\n\nfrom platformio import __registry_mirror_hosts__, fs\nfrom platformio.account.client import AccountClient, AccountError\nfrom platformio.http import HTTPClient, HTTPClientError\n\n\nclass RegistryClient(HTTPClient):\n    def __init__(self):\n        endpoints = [f\"https://api.{host}\" for host in __registry_mirror_hosts__]\n        super().__init__(endpoints)\n\n    @staticmethod\n    def allowed_private_packages():\n        private_permissions = set(\n            [\n                \"service.registry.publish-private-tool\",\n                \"service.registry.publish-private-platform\",\n                \"service.registry.publish-private-library\",\n            ]\n        )\n        try:\n            info = AccountClient().get_account_info() or {}\n            for item in info.get(\"packages\", []):\n                if set(item.keys()) & private_permissions:\n                    return True\n        except AccountError:\n            pass\n        return False\n\n    def publish_package(  # pylint: disable=redefined-builtin, too-many-positional-arguments\n        self, owner, type, archive_path, released_at=None, private=False, notify=True\n    ):\n        with open(archive_path, \"rb\") as fp:\n            return self.fetch_json_data(\n                \"post\",\n                \"/v3/packages/%s/%s\" % (owner, type),\n                params={\n                    \"private\": 1 if private else 0,\n                    \"notify\": 1 if notify else 0,\n                    \"released_at\": released_at,\n                },\n                headers={\n                    \"Content-Type\": \"application/octet-stream\",\n                    \"X-PIO-Content-SHA256\": fs.calculate_file_hashsum(\n                        \"sha256\", archive_path\n                    ),\n                },\n                data=fp,\n                x_with_authorization=True,\n            )\n\n    def unpublish_package(  # pylint: disable=redefined-builtin, too-many-positional-arguments\n        self, owner, type, name, version=None, undo=False\n    ):\n        path = \"/v3/packages/%s/%s/%s\" % (owner, type, name)\n        if version:\n            path += \"/\" + version\n        return self.fetch_json_data(\n            \"delete\", path, params={\"undo\": 1 if undo else 0}, x_with_authorization=True\n        )\n\n    def update_resource(self, urn, private):\n        return self.fetch_json_data(\n            \"put\",\n            \"/v3/resources/%s\" % urn,\n            data={\"private\": int(private)},\n            x_with_authorization=True,\n        )\n\n    def grant_access_for_resource(self, urn, client, level):\n        return self.fetch_json_data(\n            \"put\",\n            \"/v3/resources/%s/access\" % urn,\n            data={\"client\": client, \"level\": level},\n            x_with_authorization=True,\n        )\n\n    def revoke_access_from_resource(self, urn, client):\n        return self.fetch_json_data(\n            \"delete\",\n            \"/v3/resources/%s/access\" % urn,\n            data={\"client\": client},\n            x_with_authorization=True,\n        )\n\n    def list_resources(self, owner):\n        return self.fetch_json_data(\n            \"get\",\n            \"/v3/resources\",\n            params={\"owner\": owner} if owner else None,\n            x_cache_valid=\"1h\",\n            x_with_authorization=True,\n        )\n\n    def list_packages(self, query=None, qualifiers=None, page=None, sort=None):\n        search_query = []\n        if qualifiers:\n            valid_qualifiers = (\n                \"authors\",\n                \"keywords\",\n                \"frameworks\",\n                \"platforms\",\n                \"headers\",\n                \"ids\",\n                \"names\",\n                \"owners\",\n                \"types\",\n            )\n            assert set(qualifiers.keys()) <= set(valid_qualifiers)\n            for name, values in qualifiers.items():\n                for value in set(\n                    values if isinstance(values, (list, tuple)) else [values]\n                ):\n                    search_query.append('%s:\"%s\"' % (name[:-1], value))\n        if query:\n            search_query.append(query)\n        params = dict(query=\" \".join(search_query))\n        if page:\n            params[\"page\"] = int(page)\n        if sort:\n            params[\"sort\"] = sort\n        return self.fetch_json_data(\n            \"get\",\n            \"/v3/search\",\n            params=params,\n            x_cache_valid=\"1h\",\n            x_with_authorization=self.allowed_private_packages(),\n        )\n\n    def get_package(\n        self, typex, owner, name, version=None, extra_path=None\n    ):  # pylint: disable=too-many-positional-arguments\n        try:\n            return self.fetch_json_data(\n                \"get\",\n                \"/v3/packages/{owner}/{type}/{name}{extra_path}\".format(\n                    type=typex,\n                    owner=owner.lower(),\n                    name=name.lower(),\n                    extra_path=extra_path or \"\",\n                ),\n                params=dict(version=version) if version else None,\n                x_cache_valid=\"1h\",\n                x_with_authorization=self.allowed_private_packages(),\n            )\n        except HTTPClientError as exc:\n            if exc.response is not None and exc.response.status_code == 404:\n                return None\n            raise exc\n"
  },
  {
    "path": "platformio/registry/mirror.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nfrom urllib.parse import urlparse\n\nfrom platformio import __registry_mirror_hosts__\nfrom platformio.cache import ContentCache\nfrom platformio.exception import UserSideException\nfrom platformio.http import HTTPClient\nfrom platformio.registry.client import RegistryClient\n\n\nclass RegistryFileMirrorIterator:\n    HTTP_CLIENT_INSTANCES = {}\n\n    def __init__(self, download_url):\n        self.download_url = download_url\n        self._url_parts = urlparse(download_url)\n        self._mirror = \"%s://%s\" % (self._url_parts.scheme, self._url_parts.netloc)\n        self._visited_mirrors = []\n\n    def __iter__(self):  # pylint: disable=non-iterator-returned\n        return self\n\n    def __next__(self):\n        cache_key = ContentCache.key_from_args(\n            \"head\", self.download_url, self._visited_mirrors\n        )\n        with ContentCache(\"http\") as cc:\n            result = cc.get(cache_key)\n            if result is not None:\n                try:\n                    headers = json.loads(result)\n                    return (\n                        headers[\"Location\"],\n                        headers[\"X-PIO-Content-SHA256\"],\n                    )\n                except (ValueError, KeyError):\n                    pass\n\n            http = self.get_http_client()\n            response = http.send_request(\n                \"head\",\n                self._url_parts.path,\n                allow_redirects=False,\n                params=(\n                    dict(bypass=\",\".join(self._visited_mirrors))\n                    if self._visited_mirrors\n                    else None\n                ),\n                x_with_authorization=RegistryClient.allowed_private_packages(),\n            )\n            if response.status_code == 429:\n                raise UserSideException(\n                    \"Download limit exceeded. Try again in 24 hours. \"\n                    \"If this persists, contact <contact@platformio.org>\"\n                )\n            stop_conditions = [\n                response.status_code not in (302, 307),\n                not response.headers.get(\"Location\"),\n                not response.headers.get(\"X-PIO-Mirror\"),\n                response.headers.get(\"X-PIO-Mirror\") in self._visited_mirrors,\n            ]\n            if any(stop_conditions):\n                raise StopIteration\n            self._visited_mirrors.append(response.headers.get(\"X-PIO-Mirror\"))\n            cc.set(\n                cache_key,\n                json.dumps(\n                    {\n                        \"Location\": response.headers.get(\"Location\"),\n                        \"X-PIO-Content-SHA256\": response.headers.get(\n                            \"X-PIO-Content-SHA256\"\n                        ),\n                    }\n                ),\n                \"1h\",\n            )\n            return (\n                response.headers.get(\"Location\"),\n                response.headers.get(\"X-PIO-Content-SHA256\"),\n            )\n\n    def get_http_client(self):\n        if self._mirror not in RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES:\n            endpoints = [self._mirror]\n            for host in __registry_mirror_hosts__:\n                endpoint = f\"https://dl.{host}\"\n                if endpoint not in endpoints:\n                    endpoints.append(endpoint)\n            RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror] = HTTPClient(\n                endpoints\n            )\n        return RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror]\n"
  },
  {
    "path": "platformio/remote/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/remote/ac/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/remote/ac/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom twisted.internet import defer  # pylint: disable=import-error\nfrom twisted.spread import pb  # pylint: disable=import-error\n\n\nclass AsyncCommandBase:\n    MAX_BUFFER_SIZE = 1024 * 1024  # 1Mb\n\n    def __init__(self, options=None, on_end_callback=None):\n        self.options = options or {}\n        self.on_end_callback = on_end_callback\n        self._buffer = b\"\"\n        self._return_code = None\n        self._d = None\n        self._paused = False\n\n        try:\n            self.start()\n        except Exception as exc:\n            raise pb.Error(str(exc)) from exc\n\n    @property\n    def id(self):\n        return id(self)\n\n    def pause(self):\n        self._paused = True\n        self.stop()\n\n    def unpause(self):\n        self._paused = False\n        self.start()\n\n    def start(self):\n        raise NotImplementedError\n\n    def stop(self):\n        self.transport.loseConnection()  # pylint: disable=no-member\n\n    def _ac_ended(self):\n        if self.on_end_callback:\n            self.on_end_callback()\n        if not self._d or self._d.called:\n            self._d = None\n            return\n        if self._buffer:\n            self._d.callback(self._buffer)\n        else:\n            self._d.callback(None)\n\n    def _ac_ondata(self, data):\n        self._buffer += data\n        if len(self._buffer) > self.MAX_BUFFER_SIZE:\n            self._buffer = self._buffer[-1 * self.MAX_BUFFER_SIZE :]\n        if self._paused:\n            return\n        if self._d and not self._d.called:\n            self._d.callback(self._buffer)\n            self._buffer = b\"\"\n\n    def ac_read(self):\n        if self._buffer:\n            result = self._buffer\n            self._buffer = b\"\"\n            return result\n        if self._return_code is None:\n            self._d = defer.Deferred()\n            return self._d\n        return None\n\n    def ac_write(self, data):\n        self.transport.write(data)  # pylint: disable=no-member\n        return len(data)\n\n    def ac_close(self):\n        self.stop()\n        return self._return_code\n"
  },
  {
    "path": "platformio/remote/ac/process.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom twisted.internet import protocol, reactor  # pylint: disable=import-error\n\nfrom platformio.remote.ac.base import AsyncCommandBase\n\n\nclass ProcessAsyncCmd(protocol.ProcessProtocol, AsyncCommandBase):\n    def start(self):\n        env = dict(os.environ).copy()\n        env.update({\"PLATFORMIO_FORCE_ANSI\": \"true\"})\n        reactor.spawnProcess(\n            self, self.options[\"executable\"], self.options[\"args\"], env\n        )\n\n    def outReceived(self, data):\n        self._ac_ondata(data)\n\n    def errReceived(self, data):\n        self._ac_ondata(data)\n\n    def processExited(self, reason):\n        self._return_code = reason.value.exitCode\n\n    def processEnded(self, reason):\n        if self._return_code is None:\n            self._return_code = reason.value.exitCode\n        self._ac_ended()\n"
  },
  {
    "path": "platformio/remote/ac/psync.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport zlib\nfrom io import BytesIO\n\nfrom platformio.remote.ac.base import AsyncCommandBase\nfrom platformio.remote.projectsync import PROJECT_SYNC_STAGE, ProjectSync\n\n\nclass ProjectSyncAsyncCmd(AsyncCommandBase):\n    def __init__(self, *args, **kwargs):\n        self.psync = None\n        self._upstream = None\n        super().__init__(*args, **kwargs)\n\n    def start(self):\n        project_dir = os.path.join(\n            self.options[\"agent_working_dir\"], \"projects\", self.options[\"id\"]\n        )\n        self.psync = ProjectSync(project_dir)\n        for name in self.options[\"items\"]:\n            self.psync.add_item(os.path.join(project_dir, name), name)\n\n    def stop(self):\n        self.psync = None\n        self._upstream = None\n        self._return_code = PROJECT_SYNC_STAGE.COMPLETED.value\n\n    def ac_write(self, data):\n        stage = PROJECT_SYNC_STAGE.lookupByValue(data.get(\"stage\"))\n\n        if stage is PROJECT_SYNC_STAGE.DBINDEX:\n            self.psync.rebuild_dbindex()\n            return zlib.compress(json.dumps(self.psync.get_dbindex()).encode())\n\n        if stage is PROJECT_SYNC_STAGE.DELETE:\n            return self.psync.delete_dbindex(\n                json.loads(zlib.decompress(data[\"dbindex\"]))\n            )\n\n        if stage is PROJECT_SYNC_STAGE.UPLOAD:\n            if not self._upstream:\n                self._upstream = BytesIO()\n            self._upstream.write(data[\"chunk\"])\n            if self._upstream.tell() == data[\"total\"]:\n                self.psync.decompress_items(self._upstream)\n                self._upstream = None\n                return PROJECT_SYNC_STAGE.EXTRACTED.value\n\n            return PROJECT_SYNC_STAGE.UPLOAD.value\n\n        return None\n"
  },
  {
    "path": "platformio/remote/ac/serial.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom time import sleep\n\nfrom twisted.internet import protocol, reactor  # pylint: disable=import-error\nfrom twisted.internet.serialport import SerialPort  # pylint: disable=import-error\n\nfrom platformio.remote.ac.base import AsyncCommandBase\n\n\nclass SerialPortAsyncCmd(protocol.Protocol, AsyncCommandBase):\n    def start(self):\n        SerialPort(\n            self,\n            reactor=reactor,\n            **{\n                \"deviceNameOrPortNumber\": self.options[\"port\"],\n                \"baudrate\": self.options[\"baud\"],\n                \"parity\": self.options[\"parity\"],\n                \"rtscts\": 1 if self.options[\"rtscts\"] else 0,\n                \"xonxoff\": 1 if self.options[\"xonxoff\"] else 0,\n            }\n        )\n\n    def connectionMade(self):\n        self.reset_device()\n        if self.options.get(\"rts\", None) is not None:\n            self.transport.setRTS(self.options.get(\"rts\"))\n        if self.options.get(\"dtr\", None) is not None:\n            self.transport.setDTR(self.options.get(\"dtr\"))\n\n    def reset_device(self):\n        self.transport.flushInput()\n        self.transport.setDTR(False)\n        self.transport.setRTS(False)\n        sleep(0.1)\n        self.transport.setDTR(True)\n        self.transport.setRTS(True)\n        sleep(0.1)\n\n    def dataReceived(self, data):\n        self._ac_ondata(data)\n\n    def connectionLost(self, reason):  # pylint: disable=unused-argument\n        if self._paused:\n            return\n        self._return_code = 0\n        self._ac_ended()\n"
  },
  {
    "path": "platformio/remote/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=too-many-arguments, import-outside-toplevel\n# pylint: disable=inconsistent-return-statements\n\nimport os\nimport subprocess\nimport sys\nimport threading\nfrom site import addsitedir\nfrom tempfile import mkdtemp\nfrom time import sleep\n\nimport click\n\nfrom platformio import fs, proc\nfrom platformio.device.monitor.command import (\n    apply_project_monitor_options,\n    device_monitor_cmd,\n    get_project_options,\n)\nfrom platformio.package.manager.core import get_core_package_dir\nfrom platformio.project.exception import NotPlatformIOProjectError\nfrom platformio.project.options import ProjectOptions\nfrom platformio.run.cli import cli as cmd_run\nfrom platformio.test.cli import cli as test_cmd\n\n\n@click.group(\"remote\", short_help=\"Remote Development\")\n@click.option(\"-a\", \"--agent\", multiple=True)\n@click.pass_context\ndef cli(ctx, agent):\n    ctx.obj = agent\n    # inject twisted dependencies\n    contrib_dir = get_core_package_dir(\"contrib-pioremote\")\n    if contrib_dir not in sys.path:\n        addsitedir(contrib_dir)\n        sys.path.insert(0, contrib_dir)\n\n\n@cli.group(\"agent\", short_help=\"Start a new agent or list active\")\ndef remote_agent():\n    pass\n\n\n@remote_agent.command(\"start\", short_help=\"Start agent\")\n@click.option(\"-n\", \"--name\")\n@click.option(\"-s\", \"--share\", multiple=True, metavar=\"E-MAIL\")\n@click.option(\n    \"-d\",\n    \"--working-dir\",\n    envvar=\"PLATFORMIO_REMOTE_AGENT_DIR\",\n    type=click.Path(file_okay=False, dir_okay=True, writable=True),\n)\ndef remote_agent_start(name, share, working_dir):\n    from platformio.remote.client.agent_service import RemoteAgentService\n\n    RemoteAgentService(name, share, working_dir).connect()\n\n\n@remote_agent.command(\"list\", short_help=\"List active agents\")\ndef remote_agent_list():\n    from platformio.remote.client.agent_list import AgentListClient\n\n    AgentListClient().connect()\n\n\n@cli.command(\"update\", short_help=\"Update installed Platforms, Packages and Libraries\")\n@click.option(\n    \"-c\",\n    \"--only-check\",\n    is_flag=True,\n    help=\"DEPRECATED. Please use `--dry-run` instead\",\n)\n@click.option(\n    \"--dry-run\", is_flag=True, help=\"Do not update, only check for the new versions\"\n)\n@click.pass_obj\ndef remote_update(agents, only_check, dry_run):\n    from platformio.remote.client.update_core import UpdateCoreClient\n\n    UpdateCoreClient(\"update\", agents, dict(only_check=only_check or dry_run)).connect()\n\n\n@cli.command(\"run\", short_help=\"Process project environments remotely\")\n@click.option(\"-e\", \"--environment\", multiple=True)\n@click.option(\"-t\", \"--target\", multiple=True)\n@click.option(\"--upload-port\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=True, dir_okay=True, writable=True),\n)\n@click.option(\"--disable-auto-clean\", is_flag=True)\n@click.option(\"-r\", \"--force-remote\", is_flag=True)\n@click.option(\"-s\", \"--silent\", is_flag=True)\n@click.option(\"-v\", \"--verbose\", is_flag=True)\n@click.pass_obj\n@click.pass_context\ndef remote_run(  # pylint: disable=too-many-positional-arguments\n    ctx,\n    agents,\n    environment,\n    target,\n    upload_port,\n    project_dir,\n    disable_auto_clean,\n    force_remote,\n    silent,\n    verbose,\n):\n    from platformio.remote.client.run_or_test import RunOrTestClient\n\n    cr = RunOrTestClient(\n        \"run\",\n        agents,\n        dict(\n            environment=environment,\n            target=target,\n            upload_port=upload_port,\n            project_dir=project_dir,\n            disable_auto_clean=disable_auto_clean,\n            force_remote=force_remote,\n            silent=silent,\n            verbose=verbose,\n        ),\n    )\n    if force_remote:\n        return cr.connect()\n\n    click.secho(\"Building project locally\", bold=True)\n    local_targets = []\n    if \"clean\" in target:\n        local_targets = [\"clean\"]\n    elif set([\"buildfs\", \"uploadfs\", \"uploadfsota\"]) & set(target):\n        local_targets = [\"buildfs\"]\n    else:\n        local_targets = [\"checkprogsize\", \"buildprog\"]\n    ctx.invoke(\n        cmd_run,\n        environment=environment,\n        target=local_targets,\n        project_dir=project_dir,\n        # disable_auto_clean=True,\n        silent=silent,\n        verbose=verbose,\n    )\n\n    if any([\"upload\" in t for t in target] + [\"program\" in target]):\n        click.secho(\"Uploading firmware remotely\", bold=True)\n        cr.options[\"target\"] += (\"nobuild\",)\n        cr.options[\"disable_auto_clean\"] = True\n        cr.connect()\n\n    return True\n\n\n@cli.command(\"test\", short_help=\"Remote Unit Testing\")\n@click.option(\"--environment\", \"-e\", multiple=True, metavar=\"<environment>\")\n@click.option(\n    \"--filter\",\n    \"-f\",\n    multiple=True,\n    metavar=\"<pattern>\",\n    help=\"Filter tests by a pattern\",\n)\n@click.option(\n    \"--ignore\",\n    \"-i\",\n    multiple=True,\n    metavar=\"<pattern>\",\n    help=\"Ignore tests by a pattern\",\n)\n@click.option(\"--upload-port\")\n@click.option(\"--test-port\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n)\n@click.option(\"-r\", \"--force-remote\", is_flag=True)\n@click.option(\"--without-building\", is_flag=True)\n@click.option(\"--without-uploading\", is_flag=True)\n@click.option(\"--verbose\", \"-v\", is_flag=True)\n@click.pass_obj\n@click.pass_context\ndef remote_test(  # pylint: disable=redefined-builtin,too-many-positional-arguments\n    ctx,\n    agents,\n    environment,\n    filter,\n    ignore,\n    upload_port,\n    test_port,\n    project_dir,\n    force_remote,\n    without_building,\n    without_uploading,\n    verbose,\n):\n    from platformio.remote.client.run_or_test import RunOrTestClient\n\n    cr = RunOrTestClient(\n        \"test\",\n        agents,\n        dict(\n            environment=environment,\n            filter=filter,\n            ignore=ignore,\n            upload_port=upload_port,\n            test_port=test_port,\n            project_dir=project_dir,\n            force_remote=force_remote,\n            without_building=without_building,\n            without_uploading=without_uploading,\n            verbose=verbose,\n        ),\n    )\n    if force_remote:\n        return cr.connect()\n\n    click.secho(\"Building project locally\", bold=True)\n\n    ctx.invoke(\n        test_cmd,\n        environment=environment,\n        filter=filter,\n        ignore=ignore,\n        project_dir=project_dir,\n        without_uploading=True,\n        without_testing=True,\n        verbose=verbose,\n    )\n\n    click.secho(\"Testing project remotely\", bold=True)\n    cr.options[\"without_building\"] = True\n    cr.connect()\n\n    return True\n\n\n@cli.group(\"device\", short_help=\"Monitor remote device or list existing\")\ndef remote_device():\n    pass\n\n\n@remote_device.command(\"list\", short_help=\"List remote devices\")\n@click.option(\"--json-output\", is_flag=True)\n@click.pass_obj\ndef device_list(agents, json_output):\n    from platformio.remote.client.device_list import DeviceListClient\n\n    DeviceListClient(agents, json_output).connect()\n\n\n@remote_device.command(\"monitor\", short_help=\"Monitor remote device\")\n@click.option(\"--port\", \"-p\", help=\"Port, a number or a device name\")\n@click.option(\n    \"-b\",\n    \"--baud\",\n    type=ProjectOptions[\"env.monitor_speed\"].type,\n    help=\"Set baud/speed [default=%d]\" % ProjectOptions[\"env.monitor_speed\"].default,\n)\n@click.option(\n    \"--parity\",\n    type=ProjectOptions[\"env.monitor_parity\"].type,\n    help=\"Set parity [default=%s]\" % ProjectOptions[\"env.monitor_parity\"].default,\n)\n@click.option(\"--rtscts\", is_flag=True, help=\"Enable RTS/CTS flow control\")\n@click.option(\"--xonxoff\", is_flag=True, help=\"Enable software flow control\")\n@click.option(\n    \"--rts\",\n    type=ProjectOptions[\"env.monitor_rts\"].type,\n    help=\"Set initial RTS line state\",\n)\n@click.option(\n    \"--dtr\",\n    type=ProjectOptions[\"env.monitor_dtr\"].type,\n    help=\"Set initial DTR line state\",\n)\n@click.option(\"--echo\", is_flag=True, help=\"Enable local echo\")\n@click.option(\n    \"--encoding\",\n    default=\"UTF-8\",\n    show_default=True,\n    help=\"Set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8)\",\n)\n@click.option(\n    \"-f\",\n    \"--filter\",\n    \"filters\",\n    multiple=True,\n    help=\"Apply filters/text transformations\",\n)\n@click.option(\n    \"--eol\",\n    type=ProjectOptions[\"env.monitor_eol\"].type,\n    help=\"End of line mode [default=%s]\" % ProjectOptions[\"env.monitor_eol\"].default,\n)\n@click.option(\"--raw\", is_flag=True, help=ProjectOptions[\"env.monitor_raw\"].description)\n@click.option(\n    \"--exit-char\",\n    type=int,\n    default=3,\n    show_default=True,\n    help=\"ASCII code of special character that is used to exit \"\n    \"the application [default=3 (Ctrl+C)]\",\n)\n@click.option(\n    \"--menu-char\",\n    type=int,\n    default=20,\n    help=\"ASCII code of special character that is used to \"\n    \"control terminal (menu) [default=20 (DEC)]\",\n)\n@click.option(\n    \"--quiet\",\n    is_flag=True,\n    help=\"Diagnostics: suppress non-error messages\",\n)\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True),\n)\n@click.option(\n    \"-e\",\n    \"--environment\",\n    help=\"Load configuration from `platformio.ini` and specified environment\",\n)\n@click.option(\n    \"--sock\",\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n)\n@click.pass_obj\n@click.pass_context\ndef device_monitor(ctx, agents, **kwargs):\n    from platformio.remote.client.device_monitor import DeviceMonitorClient\n\n    if kwargs[\"sock\"]:\n        return DeviceMonitorClient(agents, **kwargs).connect()\n\n    project_options = {}\n    try:\n        with fs.cd(kwargs[\"project_dir\"]):\n            project_options = get_project_options(kwargs[\"environment\"])\n    except NotPlatformIOProjectError:\n        pass\n\n    kwargs = apply_project_monitor_options(kwargs, project_options)\n\n    def _tx_target(sock_dir):\n        subcmd_argv = [\"remote\"]\n        for agent in agents:\n            subcmd_argv.extend([\"--agent\", agent])\n        subcmd_argv.extend([\"device\", \"monitor\"])\n        subcmd_argv.extend(project_options_to_monitor_argv(kwargs))\n        subcmd_argv.extend([\"--sock\", sock_dir])\n        subprocess.call([proc.where_is_program(\"platformio\")] + subcmd_argv)\n\n    sock_dir = mkdtemp(suffix=\"pio\")\n    sock_file = os.path.join(sock_dir, \"sock\")\n    try:\n        t = threading.Thread(target=_tx_target, args=(sock_dir,))\n        t.start()\n        while t.is_alive() and not os.path.isfile(sock_file):\n            sleep(0.1)\n        if not t.is_alive():\n            return\n        with open(sock_file, encoding=\"utf8\") as fp:\n            kwargs[\"port\"] = fp.read()\n        kwargs[\"no_reconnect\"] = True\n        ctx.invoke(device_monitor_cmd, **kwargs)\n        t.join(2)\n    finally:\n        fs.rmtree(sock_dir)\n\n    return True\n\n\ndef project_options_to_monitor_argv(cli_options):\n    result = []\n    for item in cli_options[\"filters\"] or []:\n        result.extend([\"--filter\", item])\n    for k, v in cli_options.items():\n        if v is None or k == \"filters\":\n            continue\n        k = \"--\" + k.replace(\"_\", \"-\")\n        if isinstance(v, bool):\n            if v:\n                result.append(k)\n        elif isinstance(v, tuple):\n            for i in v:\n                result.extend([k, i])\n        else:\n            result.extend([k, str(v)])\n    return result\n"
  },
  {
    "path": "platformio/remote/client/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/remote/client/agent_list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom datetime import datetime\n\nimport click\n\nfrom platformio.remote.client.base import RemoteClientBase\n\n\nclass AgentListClient(RemoteClientBase):\n    def agent_pool_ready(self):\n        d = self.agentpool.callRemote(\"list\", True)\n        d.addCallback(self._cbResult)\n        d.addErrback(self.cb_global_error)\n\n    def _cbResult(self, result):\n        for item in result:\n            click.secho(item[\"name\"], fg=\"cyan\")\n            click.echo(\"-\" * len(item[\"name\"]))\n            click.echo(\"ID: %s\" % item[\"id\"])\n            click.echo(\n                \"Started: %s\"\n                % datetime.fromtimestamp(item[\"started\"]).strftime(\"%Y-%m-%d %H:%M:%S\")\n            )\n            click.echo(\"\")\n        self.disconnect()\n"
  },
  {
    "path": "platformio/remote/client/agent_service.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nfrom twisted.logger import LogLevel  # pylint: disable=import-error\nfrom twisted.spread import pb  # pylint: disable=import-error\n\nfrom platformio import proc\nfrom platformio.device.list.util import list_serial_ports\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import NotPlatformIOProjectError\nfrom platformio.remote.ac.process import ProcessAsyncCmd\nfrom platformio.remote.ac.psync import ProjectSyncAsyncCmd\nfrom platformio.remote.ac.serial import SerialPortAsyncCmd\nfrom platformio.remote.client.base import RemoteClientBase\n\n\nclass RemoteAgentService(RemoteClientBase):\n    def __init__(self, name, share, working_dir=None):\n        RemoteClientBase.__init__(self)\n        self.log_level = LogLevel.info\n        self.working_dir = working_dir or os.path.join(\n            ProjectConfig.get_instance().get(\"platformio\", \"core_dir\"), \"remote\"\n        )\n        if not os.path.isdir(self.working_dir):\n            os.makedirs(self.working_dir)\n        if name:\n            self.name = str(name)[:50]\n        self.join_options.update(\n            {\"agent\": True, \"share\": [s.lower().strip()[:50] for s in share]}\n        )\n\n        self._acs = {}\n\n    def agent_pool_ready(self):\n        pass\n\n    def cb_disconnected(self, reason):\n        for ac in self._acs.values():\n            ac.ac_close()\n        RemoteClientBase.cb_disconnected(self, reason)\n\n    def remote_acread(self, ac_id):\n        self.log.debug(\"Async Read: {id}\", id=ac_id)\n        if ac_id not in self._acs:\n            raise pb.Error(\"Invalid Async Identifier\")\n        return self._acs[ac_id].ac_read()\n\n    def remote_acwrite(self, ac_id, data):\n        self.log.debug(\"Async Write: {id}\", id=ac_id)\n        if ac_id not in self._acs:\n            raise pb.Error(\"Invalid Async Identifier\")\n        return self._acs[ac_id].ac_write(data)\n\n    def remote_acclose(self, ac_id):\n        self.log.debug(\"Async Close: {id}\", id=ac_id)\n        if ac_id not in self._acs:\n            raise pb.Error(\"Invalid Async Identifier\")\n        return_code = self._acs[ac_id].ac_close()\n        del self._acs[ac_id]\n        return return_code\n\n    def remote_cmd(self, cmd, options):\n        self.log.info(\"Remote command received: {cmd}\", cmd=cmd)\n        self.log.debug(\"Command options: {options!r}\", options=options)\n        callback = \"_process_cmd_%s\" % cmd.replace(\".\", \"_\")\n        return getattr(self, callback)(options)\n\n    def _defer_async_cmd(self, ac, pass_agent_name=True):\n        self._acs[ac.id] = ac\n        if pass_agent_name:\n            return (self.id, ac.id, self.name)\n        return (self.id, ac.id)\n\n    def _process_cmd_device_list(self, _):\n        return (self.name, list_serial_ports())\n\n    def _process_cmd_device_monitor(self, options):\n        if not options[\"port\"]:\n            for item in list_serial_ports():\n                if \"VID:PID\" in item[\"hwid\"]:\n                    options[\"port\"] = item[\"port\"]\n                    break\n\n        # terminate opened monitors\n        if options[\"port\"]:\n            for ac in list(self._acs.values()):\n                if (\n                    isinstance(ac, SerialPortAsyncCmd)\n                    and ac.options[\"port\"] == options[\"port\"]\n                ):\n                    self.log.info(\n                        \"Terminate previously opened monitor at {port}\",\n                        port=options[\"port\"],\n                    )\n                    ac.ac_close()\n                    del self._acs[ac.id]\n\n        if not options[\"port\"]:\n            raise pb.Error(\"Please specify serial port using `--port` option\")\n        self.log.info(\"Starting serial monitor at {port}\", port=options[\"port\"])\n\n        return self._defer_async_cmd(SerialPortAsyncCmd(options), pass_agent_name=False)\n\n    def _process_cmd_psync(self, options):\n        for ac in list(self._acs.values()):\n            if (\n                isinstance(ac, ProjectSyncAsyncCmd)\n                and ac.options[\"id\"] == options[\"id\"]\n            ):\n                self.log.info(\"Terminate previous Project Sync process\")\n                ac.ac_close()\n                del self._acs[ac.id]\n\n        options[\"agent_working_dir\"] = self.working_dir\n        return self._defer_async_cmd(\n            ProjectSyncAsyncCmd(options), pass_agent_name=False\n        )\n\n    def _process_cmd_run(self, options):\n        return self._process_cmd_run_or_test(\"run\", options)\n\n    def _process_cmd_test(self, options):\n        return self._process_cmd_run_or_test(\"test\", options)\n\n    def _process_cmd_run_or_test(  # pylint: disable=too-many-locals,too-many-branches,too-many-statements\n        self, command, options\n    ):\n        assert options and \"project_id\" in options\n        project_dir = os.path.join(self.working_dir, \"projects\", options[\"project_id\"])\n        origin_pio_ini = os.path.join(project_dir, \"platformio.ini\")\n        back_pio_ini = os.path.join(project_dir, \"platformio.ini.bak\")\n\n        # remove insecure project options\n        try:\n            conf = ProjectConfig(origin_pio_ini)\n            if os.path.isfile(back_pio_ini):\n                os.remove(back_pio_ini)\n            os.rename(origin_pio_ini, back_pio_ini)\n            # cleanup\n            if conf.has_section(\"platformio\"):\n                for opt in conf.options(\"platformio\"):\n                    if opt.endswith(\"_dir\"):\n                        conf.remove_option(\"platformio\", opt)\n            else:\n                conf.add_section(\"platformio\")\n            conf.set(\"platformio\", \"build_dir\", \".pio/build\")\n            conf.save(origin_pio_ini)\n\n            # restore A/M times\n            os.utime(\n                origin_pio_ini,\n                (os.path.getatime(back_pio_ini), os.path.getmtime(back_pio_ini)),\n            )\n        except NotPlatformIOProjectError as exc:\n            raise pb.Error(str(exc)) from exc\n\n        cmd_args = [\"platformio\", \"--force\", command, \"-d\", project_dir]\n        for env in options.get(\"environment\", []):\n            cmd_args.extend([\"-e\", env])\n        for target in options.get(\"target\", []):\n            cmd_args.extend([\"-t\", target])\n        for filter_ in options.get(\"filter\", []):\n            cmd_args.extend([\"-f\", filter_])\n        for ignore in options.get(\"ignore\", []):\n            cmd_args.extend([\"-i\", ignore])\n        if options.get(\"upload_port\", False):\n            cmd_args.extend([\"--upload-port\", options.get(\"upload_port\")])\n        if options.get(\"test_port\", False):\n            cmd_args.extend([\"--test-port\", options.get(\"test_port\")])\n        if options.get(\"disable_auto_clean\", False):\n            cmd_args.append(\"--disable-auto-clean\")\n        if options.get(\"without_building\", False):\n            cmd_args.append(\"--without-building\")\n        if options.get(\"without_uploading\", False):\n            cmd_args.append(\"--without-uploading\")\n        if options.get(\"silent\", False):\n            cmd_args.append(\"-s\")\n        if options.get(\"verbose\", False):\n            cmd_args.append(\"-v\")\n\n        paused_acs = []\n        for ac in self._acs.values():\n            if not isinstance(ac, SerialPortAsyncCmd):\n                continue\n            self.log.info(\"Pause active monitor at {port}\", port=ac.options[\"port\"])\n            ac.pause()\n            paused_acs.append(ac)\n\n        def _cb_on_end():\n            if os.path.isfile(back_pio_ini):\n                if os.path.isfile(origin_pio_ini):\n                    os.remove(origin_pio_ini)\n                os.rename(back_pio_ini, origin_pio_ini)\n            for ac in paused_acs:\n                ac.unpause()\n                self.log.info(\n                    \"Unpause active monitor at {port}\", port=ac.options[\"port\"]\n                )\n\n        return self._defer_async_cmd(\n            ProcessAsyncCmd(\n                {\"executable\": proc.where_is_program(\"platformio\"), \"args\": cmd_args},\n                on_end_callback=_cb_on_end,\n            )\n        )\n\n    def _process_cmd_update(self, options):\n        cmd_args = [\"platformio\", \"--force\", \"update\"]\n        if options.get(\"only_check\"):\n            cmd_args.append(\"--only-check\")\n        return self._defer_async_cmd(\n            ProcessAsyncCmd(\n                {\"executable\": proc.where_is_program(\"platformio\"), \"args\": cmd_args}\n            )\n        )\n"
  },
  {
    "path": "platformio/remote/client/async_base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\nfrom twisted.spread import pb  # pylint: disable=import-error\n\nfrom platformio.remote.client.base import RemoteClientBase\n\n\nclass AsyncClientBase(RemoteClientBase):\n    def __init__(self, command, agents, options):\n        RemoteClientBase.__init__(self)\n        self.command = command\n        self.agents = agents\n        self.options = options\n\n        self._acs_total = 0\n        self._acs_ended = 0\n\n    def agent_pool_ready(self):\n        pass\n\n    def cb_async_result(self, result):\n        if self._acs_total == 0:\n            self._acs_total = len(result)\n        for success, value in result:\n            if not success:\n                raise pb.Error(value)\n            self.acread_data(*value)\n\n    def acread_data(self, agent_id, ac_id, agent_name=None):\n        d = self.agentpool.callRemote(\"acread\", agent_id, ac_id)\n        d.addCallback(self.cb_acread_result, agent_id, ac_id, agent_name)\n        d.addErrback(self.cb_global_error)\n\n    def cb_acread_result(self, result, agent_id, ac_id, agent_name):\n        if result is None:\n            self.acclose(agent_id, ac_id)\n        else:\n            if self._acs_total > 1 and agent_name:\n                click.echo(\"[%s] \" % agent_name, nl=False)\n            click.echo(result, nl=False)\n            self.acread_data(agent_id, ac_id, agent_name)\n\n    def acclose(self, agent_id, ac_id):\n        d = self.agentpool.callRemote(\"acclose\", agent_id, ac_id)\n        d.addCallback(self.cb_acclose_result)\n        d.addErrback(self.cb_global_error)\n\n    def cb_acclose_result(self, exit_code):\n        self._acs_ended += 1\n        if self._acs_ended != self._acs_total:\n            return\n        self.disconnect(exit_code)\n"
  },
  {
    "path": "platformio/remote/client/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom datetime import datetime\nfrom time import time\n\nimport click\nfrom twisted.internet import defer, endpoints, reactor  # pylint: disable=import-error\nfrom twisted.logger import ILogObserver  # pylint: disable=import-error\nfrom twisted.logger import Logger  # pylint: disable=import-error\nfrom twisted.logger import LogLevel  # pylint: disable=import-error\nfrom twisted.logger import formatEvent  # pylint: disable=import-error\nfrom twisted.python import failure  # pylint: disable=import-error\nfrom twisted.spread import pb  # pylint: disable=import-error\nfrom zope.interface import provider  # pylint: disable=import-error\n\nfrom platformio import __pioremote_endpoint__, __version__, app, exception, maintenance\nfrom platformio.remote.factory.client import RemoteClientFactory\nfrom platformio.remote.factory.ssl import SSLContextFactory\n\n\nclass RemoteClientBase(  # pylint: disable=too-many-instance-attributes\n    pb.Referenceable\n):\n    PING_DELAY = 60\n    PING_MAX_FAILURES = 3\n    DEBUG = False\n\n    def __init__(self):\n        self.log_level = LogLevel.warn\n        self.log = Logger(namespace=\"remote\", observer=self._log_observer)\n        self.id = app.get_host_id()\n        self.name = app.get_host_name()\n        self.join_options = {\"corever\": __version__}\n        self.perspective = None\n        self.agentpool = None\n\n        self._ping_id = 0\n        self._ping_caller = None\n        self._ping_counter = 0\n        self._reactor_stopped = False\n        self._exit_code = 0\n\n    @provider(ILogObserver)\n    def _log_observer(self, event):\n        if not self.DEBUG and (\n            event[\"log_namespace\"] != self.log.namespace\n            or self.log_level > event[\"log_level\"]\n        ):\n            return\n        msg = formatEvent(event)\n        click.echo(\n            \"%s [%s] %s\"\n            % (\n                datetime.fromtimestamp(event[\"log_time\"]).strftime(\"%Y-%m-%d %H:%M:%S\"),\n                event[\"log_level\"].name,\n                msg,\n            )\n        )\n\n    def connect(self):\n        self.log.info(\"Name: {name}\", name=self.name)\n        self.log.info(\"Connecting to PlatformIO Remote Development Cloud\")\n\n        # pylint: disable=protected-access\n        proto, options = endpoints._parse(__pioremote_endpoint__)\n        proto = proto[0]\n\n        factory = RemoteClientFactory()\n        factory.remote_client = self\n        factory.sslContextFactory = None\n        if proto == \"ssl\":\n            factory.sslContextFactory = SSLContextFactory(options[\"host\"])\n            reactor.connectSSL(\n                options[\"host\"],\n                int(options[\"port\"]),\n                factory,\n                factory.sslContextFactory,\n            )\n        elif proto == \"tcp\":\n            reactor.connectTCP(options[\"host\"], int(options[\"port\"]), factory)\n        else:\n            raise exception.PlatformioException(\"Unknown PIO Remote Cloud protocol\")\n        reactor.run()\n\n        if self._exit_code != 0:\n            raise exception.ReturnErrorCode(self._exit_code)\n\n    def cb_client_authorization_failed(self, err):\n        msg = \"Bad account credentials\"\n        if err.check(pb.Error):\n            msg = err.getErrorMessage()\n        self.log.error(msg)\n        self.disconnect(exit_code=1)\n\n    def cb_client_authorization_made(self, perspective):\n        self.log.info(\"Successfully authorized\")\n        self.perspective = perspective\n        d = perspective.callRemote(\"join\", self.id, self.name, self.join_options)\n        d.addCallback(self._cb_client_join_made)\n        d.addErrback(self.cb_global_error)\n\n    def _cb_client_join_made(self, result):\n        code = result[0]\n        if code == 1:\n            self.agentpool = result[1]\n            self.agent_pool_ready()\n            self.restart_ping()\n        elif code == 2:\n            self.remote_service(*result[1:])\n\n    def remote_service(self, command, options):\n        if command == \"disconnect\":\n            self.log.error(\n                \"PIO Remote Cloud disconnected: {msg}\", msg=options.get(\"message\")\n            )\n            self.disconnect()\n\n    def restart_ping(self, reset_counter=True):\n        # stop previous ping callers\n        self.stop_ping(reset_counter)\n        self._ping_caller = reactor.callLater(self.PING_DELAY, self._do_ping)\n\n    def _do_ping(self):\n        self._ping_counter += 1\n        self._ping_id = int(time())\n        d = self.perspective.callRemote(\"service\", \"ping\", {\"id\": self._ping_id})\n        d.addCallback(self._cb_pong)\n        d.addErrback(self._cb_pong)\n\n    def stop_ping(self, reset_counter=True):\n        if reset_counter:\n            self._ping_counter = 0\n        if not self._ping_caller or not self._ping_caller.active():\n            return\n        self._ping_caller.cancel()\n        self._ping_caller = None\n\n    def _cb_pong(self, result):\n        if not isinstance(result, failure.Failure) and self._ping_id == result:\n            self.restart_ping()\n            return\n        if self._ping_counter >= self.PING_MAX_FAILURES:\n            self.stop_ping()\n            self.perspective.broker.transport.loseConnection()\n        else:\n            self.restart_ping(reset_counter=False)\n\n    def agent_pool_ready(self):\n        raise NotImplementedError\n\n    def disconnect(self, exit_code=None):\n        self.stop_ping()\n        if exit_code is not None:\n            self._exit_code = exit_code\n        if reactor.running and not self._reactor_stopped:\n            self._reactor_stopped = True\n            reactor.stop()\n\n    def cb_disconnected(self, _):\n        self.stop_ping()\n        self.perspective = None\n        self.agentpool = None\n\n    def cb_global_error(self, err):\n        if err.check(pb.PBConnectionLost, defer.CancelledError):\n            return\n\n        msg = err.getErrorMessage()\n        if err.check(pb.DeadReferenceError):\n            msg = \"Remote Client has been terminated\"\n        elif \"PioAgentNotStartedError\" in str(err.type):\n            msg = (\n                \"Could not find active agents. Please start it before on \"\n                \"a remote machine using `pio remote agent start` command.\\n\"\n                \"See http://docs.platformio.org/page/plus/pio-remote.html\"\n            )\n        else:\n            maintenance.on_platformio_exception(Exception(err.type))\n        click.secho(msg, fg=\"red\", err=True)\n        self.disconnect(exit_code=1)\n"
  },
  {
    "path": "platformio/remote/client/device_list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nimport click\n\nfrom platformio.remote.client.base import RemoteClientBase\n\n\nclass DeviceListClient(RemoteClientBase):\n    def __init__(self, agents, json_output):\n        RemoteClientBase.__init__(self)\n        self.agents = agents\n        self.json_output = json_output\n\n    def agent_pool_ready(self):\n        d = self.agentpool.callRemote(\"cmd\", self.agents, \"device.list\")\n        d.addCallback(self._cbResult)\n        d.addErrback(self.cb_global_error)\n\n    def _cbResult(self, result):\n        data = {}\n        for success, value in result:\n            if not success:\n                click.secho(value, fg=\"red\", err=True)\n                continue\n            agent_name, devlist = value\n            data[agent_name] = devlist\n\n        if self.json_output:\n            click.echo(json.dumps(data))\n        else:\n            for agent_name, devlist in data.items():\n                click.echo(\"Agent %s\" % click.style(agent_name, fg=\"cyan\", bold=True))\n                click.echo(\"=\" * (6 + len(agent_name)))\n                for item in devlist:\n                    click.secho(item[\"port\"], fg=\"cyan\")\n                    click.echo(\"-\" * len(item[\"port\"]))\n                    click.echo(\"Hardware ID: %s\" % item[\"hwid\"])\n                    click.echo(\"Description: %s\" % item[\"description\"])\n                    click.echo(\"\")\n        self.disconnect()\n"
  },
  {
    "path": "platformio/remote/client/device_monitor.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom fnmatch import fnmatch\n\nimport click\nfrom twisted.internet import protocol, reactor, task  # pylint: disable=import-error\nfrom twisted.spread import pb  # pylint: disable=import-error\n\nfrom platformio.remote.client.base import RemoteClientBase\n\n\nclass SMBridgeProtocol(protocol.Protocol):\n    def connectionMade(self):\n        self.factory.add_client(self)\n\n    def connectionLost(self, reason):  # pylint: disable=unused-argument\n        self.factory.remove_client(self)\n\n    def dataReceived(self, data):\n        self.factory.send_to_server(data)\n\n\nclass SMBridgeFactory(protocol.ServerFactory):\n    def __init__(self, cdm):\n        self.cdm = cdm\n        self._clients = []\n\n    def buildProtocol(self, addr):  # pylint: disable=unused-argument\n        p = SMBridgeProtocol()\n        p.factory = self  # pylint: disable=attribute-defined-outside-init\n        return p\n\n    def add_client(self, client):\n        self.cdm.log.debug(\"SMBridge: Client connected\")\n        self._clients.append(client)\n        self.cdm.acread_data()\n\n    def remove_client(self, client):\n        self.cdm.log.debug(\"SMBridge: Client disconnected\")\n        self._clients.remove(client)\n        if not self._clients:\n            self.cdm.client_terminal_stopped()\n\n    def has_clients(self):\n        return len(self._clients)\n\n    def send_to_clients(self, data):\n        if not self._clients:\n            return None\n        for client in self._clients:\n            client.transport.write(data)\n        return len(data)\n\n    def send_to_server(self, data):\n        self.cdm.acwrite_data(data)\n\n\nclass DeviceMonitorClient(  # pylint: disable=too-many-instance-attributes\n    RemoteClientBase\n):\n    MAX_BUFFER_SIZE = 1024 * 1024\n\n    def __init__(self, agents, **kwargs):\n        RemoteClientBase.__init__(self)\n        self.agents = agents\n        self.cmd_options = kwargs\n\n        self._bridge_factory = SMBridgeFactory(self)\n        self._agent_id = None\n        self._ac_id = None\n        self._d_acread = None\n        self._d_acwrite = None\n        self._acwrite_buffer = b\"\"\n\n    def agent_pool_ready(self):\n        d = task.deferLater(\n            reactor, 1, self.agentpool.callRemote, \"cmd\", self.agents, \"device.list\"\n        )\n        d.addCallback(self._cb_device_list)\n        d.addErrback(self.cb_global_error)\n\n    def _cb_device_list(self, result):\n        devices = []\n        hwid_devindexes = []\n        for success, value in result:\n            if not success:\n                click.secho(value, fg=\"red\", err=True)\n                continue\n            agent_name, ports = value\n            for item in ports:\n                if \"VID:PID\" in item[\"hwid\"]:\n                    hwid_devindexes.append(len(devices))\n                devices.append((agent_name, item))\n\n        if len(result) == 1 and self.cmd_options[\"port\"]:\n            if set([\"*\", \"?\", \"[\", \"]\"]) & set(self.cmd_options[\"port\"]):\n                for agent, item in devices:\n                    if fnmatch(item[\"port\"], self.cmd_options[\"port\"]):\n                        return self.start_remote_monitor(agent, item[\"port\"])\n            return self.start_remote_monitor(result[0][1][0], self.cmd_options[\"port\"])\n\n        device = None\n        if len(hwid_devindexes) == 1:\n            device = devices[hwid_devindexes[0]]\n        else:\n            click.echo(\"Available ports:\")\n            for i, device in enumerate(devices):\n                click.echo(\n                    \"{index}. {host}{port} \\t{description}\".format(\n                        index=i + 1,\n                        host=device[0] + \":\" if len(result) > 1 else \"\",\n                        port=device[1][\"port\"],\n                        description=(\n                            device[1][\"description\"]\n                            if device[1][\"description\"] != \"n/a\"\n                            else \"\"\n                        ),\n                    )\n                )\n            device_index = click.prompt(\n                \"Please choose a port (number in the list above)\",\n                type=click.Choice([str(i + 1) for i, _ in enumerate(devices)]),\n            )\n            device = devices[int(device_index) - 1]\n\n        self.start_remote_monitor(device[0], device[1][\"port\"])\n\n        return None\n\n    def start_remote_monitor(self, agent, port):\n        options = {\"port\": port}\n        for key in (\"baud\", \"parity\", \"rtscts\", \"xonxoff\", \"rts\", \"dtr\"):\n            options[key] = self.cmd_options[key]\n\n        click.echo(\n            \"Starting Serial Monitor on {host}:{port}\".format(\n                host=agent, port=options[\"port\"]\n            )\n        )\n        d = self.agentpool.callRemote(\"cmd\", [agent], \"device.monitor\", options)\n        d.addCallback(self.cb_async_result)\n        d.addErrback(self.cb_global_error)\n\n    def cb_async_result(self, result):\n        if len(result) != 1:\n            raise pb.Error(\"Invalid response from Remote Cloud\")\n        success, value = result[0]\n        if not success:\n            raise pb.Error(value)\n\n        reconnected = self._agent_id is not None\n        self._agent_id, self._ac_id = value\n\n        if reconnected:\n            self.acread_data(force=True)\n            self.acwrite_data(\"\", force=True)\n            return\n\n        # start bridge\n        port = reactor.listenTCP(0, self._bridge_factory)\n        address = port.getHost()\n        self.log.debug(\"Serial Bridge is started on {address!r}\", address=address)\n        if \"sock\" in self.cmd_options:\n            with open(\n                os.path.join(self.cmd_options[\"sock\"], \"sock\"),\n                mode=\"w\",\n                encoding=\"utf8\",\n            ) as fp:\n                fp.write(\"socket://localhost:%d\" % address.port)\n\n    def client_terminal_stopped(self):\n        try:\n            d = self.agentpool.callRemote(\"acclose\", self._agent_id, self._ac_id)\n            d.addCallback(lambda r: self.disconnect())\n            d.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n    def acread_data(self, force=False):\n        if force and self._d_acread:\n            self._d_acread.cancel()\n            self._d_acread = None\n\n        if (\n            self._d_acread and not self._d_acread.called\n        ) or not self._bridge_factory.has_clients():\n            return\n\n        try:\n            self._d_acread = self.agentpool.callRemote(\n                \"acread\", self._agent_id, self._ac_id\n            )\n            self._d_acread.addCallback(self.cb_acread_result)\n            self._d_acread.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n    def cb_acread_result(self, result):\n        if result is None:\n            self.disconnect(exit_code=1)\n        else:\n            self._bridge_factory.send_to_clients(result)\n            self.acread_data()\n\n    def acwrite_data(self, data, force=False):\n        if force and self._d_acwrite:\n            self._d_acwrite.cancel()\n            self._d_acwrite = None\n\n        self._acwrite_buffer += data\n        if len(self._acwrite_buffer) > self.MAX_BUFFER_SIZE:\n            self._acwrite_buffer = self._acwrite_buffer[-1 * self.MAX_BUFFER_SIZE :]\n        if (self._d_acwrite and not self._d_acwrite.called) or not self._acwrite_buffer:\n            return\n\n        data = self._acwrite_buffer\n        self._acwrite_buffer = b\"\"\n        try:\n            d = self.agentpool.callRemote(\"acwrite\", self._agent_id, self._ac_id, data)\n            d.addCallback(self.cb_acwrite_result)\n            d.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n    def cb_acwrite_result(self, result):\n        assert result > 0\n        if self._acwrite_buffer:\n            self.acwrite_data(b\"\")\n"
  },
  {
    "path": "platformio/remote/client/run_or_test.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport json\nimport os\nimport zlib\nfrom io import BytesIO\n\nfrom twisted.spread import pb  # pylint: disable=import-error\n\nfrom platformio import fs\nfrom platformio.compat import hashlib_encode_data\nfrom platformio.project.config import ProjectConfig\nfrom platformio.remote.client.async_base import AsyncClientBase\nfrom platformio.remote.projectsync import PROJECT_SYNC_STAGE, ProjectSync\n\n\nclass RunOrTestClient(AsyncClientBase):\n    MAX_ARCHIVE_SIZE = 50 * 1024 * 1024  # 50Mb\n    UPLOAD_CHUNK_SIZE = 256 * 1024  # 256Kb\n\n    PSYNC_SRC_EXTS = [\n        \"c\",\n        \"cpp\",\n        \"S\",\n        \"spp\",\n        \"SPP\",\n        \"sx\",\n        \"s\",\n        \"asm\",\n        \"ASM\",\n        \"h\",\n        \"hpp\",\n        \"ipp\",\n        \"ino\",\n        \"pde\",\n        \"json\",\n        \"properties\",\n    ]\n\n    PSYNC_SKIP_DIRS = (\".git\", \".svn\", \".hg\", \"example\", \"examples\", \"test\", \"tests\")\n\n    def __init__(self, *args, **kwargs):\n        AsyncClientBase.__init__(self, *args, **kwargs)\n        self.project_id = self.generate_project_id(self.options[\"project_dir\"])\n        self.psync = ProjectSync(self.options[\"project_dir\"])\n\n    def generate_project_id(self, path):\n        h = hashlib.sha1(hashlib_encode_data(self.id))\n        h.update(hashlib_encode_data(path))\n        return \"%s-%s\" % (os.path.basename(path), h.hexdigest())\n\n    def add_project_items(self, psync):\n        with fs.cd(self.options[\"project_dir\"]):\n            cfg = ProjectConfig.get_instance(\n                os.path.join(self.options[\"project_dir\"], \"platformio.ini\")\n            )\n            psync.add_item(cfg.path, \"platformio.ini\")\n            psync.add_item(cfg.get(\"platformio\", \"shared_dir\"), \"shared\")\n            psync.add_item(cfg.get(\"platformio\", \"boards_dir\"), \"boards\")\n\n            if self.options[\"force_remote\"]:\n                self._add_project_source_items(cfg, psync)\n            else:\n                self._add_project_binary_items(cfg, psync)\n\n            if self.command == \"test\":\n                psync.add_item(cfg.get(\"platformio\", \"test_dir\"), \"test\")\n\n    def _add_project_source_items(self, cfg, psync):\n        psync.add_item(cfg.get(\"platformio\", \"lib_dir\"), \"lib\")\n        psync.add_item(\n            cfg.get(\"platformio\", \"include_dir\"),\n            \"include\",\n            cb_filter=self._cb_tarfile_filter,\n        )\n        psync.add_item(\n            cfg.get(\"platformio\", \"src_dir\"), \"src\", cb_filter=self._cb_tarfile_filter\n        )\n        if set([\"buildfs\", \"uploadfs\", \"uploadfsota\"]) & set(\n            self.options.get(\"target\", [])\n        ):\n            psync.add_item(cfg.get(\"platformio\", \"data_dir\"), \"data\")\n\n    @staticmethod\n    def _add_project_binary_items(cfg, psync):\n        build_dir = cfg.get(\"platformio\", \"build_dir\")\n        for env_name in os.listdir(build_dir):\n            env_dir = os.path.join(build_dir, env_name)\n            if not os.path.isdir(env_dir):\n                continue\n            for fname in os.listdir(env_dir):\n                bin_file = os.path.join(env_dir, fname)\n                bin_exts = (\".elf\", \".bin\", \".hex\", \".eep\", \"program\")\n                if os.path.isfile(bin_file) and fname.endswith(bin_exts):\n                    psync.add_item(\n                        bin_file, os.path.join(\".pio\", \"build\", env_name, fname)\n                    )\n\n    def _cb_tarfile_filter(self, path):\n        if (\n            os.path.isdir(path)\n            and os.path.basename(path).lower() in self.PSYNC_SKIP_DIRS\n        ):\n            return None\n        if os.path.isfile(path) and not self.is_file_with_exts(\n            path, self.PSYNC_SRC_EXTS\n        ):\n            return None\n        return path\n\n    @staticmethod\n    def is_file_with_exts(path, exts):\n        if path.endswith(tuple(\".%s\" % e for e in exts)):\n            return True\n        return False\n\n    def agent_pool_ready(self):\n        self.psync_init()\n\n    def psync_init(self):\n        self.add_project_items(self.psync)\n        d = self.agentpool.callRemote(\n            \"cmd\",\n            self.agents,\n            \"psync\",\n            dict(id=self.project_id, items=[i[1] for i in self.psync.get_items()]),\n        )\n        d.addCallback(self.cb_psync_init_result)\n        d.addErrback(self.cb_global_error)\n\n        # build db index while wait for result from agent\n        self.psync.rebuild_dbindex()\n\n    def cb_psync_init_result(self, result):\n        self._acs_total = len(result)\n        for success, value in result:\n            if not success:\n                raise pb.Error(value)\n            agent_id, ac_id = value\n            try:\n                d = self.agentpool.callRemote(\n                    \"acwrite\",\n                    agent_id,\n                    ac_id,\n                    dict(stage=PROJECT_SYNC_STAGE.DBINDEX.value),\n                )\n                d.addCallback(self.cb_psync_dbindex_result, agent_id, ac_id)\n                d.addErrback(self.cb_global_error)\n            except (AttributeError, pb.DeadReferenceError):\n                self.disconnect(exit_code=1)\n\n    def cb_psync_dbindex_result(self, result, agent_id, ac_id):\n        result = set(json.loads(zlib.decompress(result)))\n        dbindex = set(self.psync.get_dbindex())\n        delete = list(result - dbindex)\n        delta = list(dbindex - result)\n\n        self.log.debug(\n            \"PSync: stats, total={total}, delete={delete}, delta={delta}\",\n            total=len(dbindex),\n            delete=len(delete),\n            delta=len(delta),\n        )\n\n        if not delete and not delta:\n            return self.psync_finalize(agent_id, ac_id)\n        if not delete:\n            return self.psync_upload(agent_id, ac_id, delta)\n\n        try:\n            d = self.agentpool.callRemote(\n                \"acwrite\",\n                agent_id,\n                ac_id,\n                dict(\n                    stage=PROJECT_SYNC_STAGE.DELETE.value,\n                    dbindex=zlib.compress(json.dumps(delete).encode()),\n                ),\n            )\n            d.addCallback(self.cb_psync_delete_result, agent_id, ac_id, delta)\n            d.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n        return None\n\n    def cb_psync_delete_result(self, result, agent_id, ac_id, dbindex):\n        assert result\n        self.psync_upload(agent_id, ac_id, dbindex)\n\n    def psync_upload(self, agent_id, ac_id, dbindex):\n        assert dbindex\n        fileobj = BytesIO()\n        compressed = self.psync.compress_items(fileobj, dbindex, self.MAX_ARCHIVE_SIZE)\n        fileobj.seek(0)\n        self.log.debug(\n            \"PSync: upload project, size={size}\", size=len(fileobj.getvalue())\n        )\n        self.psync_upload_chunk(\n            agent_id, ac_id, list(set(dbindex) - set(compressed)), fileobj\n        )\n\n    def psync_upload_chunk(self, agent_id, ac_id, dbindex, fileobj):\n        offset = fileobj.tell()\n        total = fileobj.seek(0, os.SEEK_END)\n        # unwind\n        fileobj.seek(offset)\n        chunk = fileobj.read(self.UPLOAD_CHUNK_SIZE)\n        assert chunk\n        try:\n            d = self.agentpool.callRemote(\n                \"acwrite\",\n                agent_id,\n                ac_id,\n                dict(\n                    stage=PROJECT_SYNC_STAGE.UPLOAD.value,\n                    chunk=chunk,\n                    length=len(chunk),\n                    total=total,\n                ),\n            )\n            d.addCallback(\n                self.cb_psync_upload_chunk_result, agent_id, ac_id, dbindex, fileobj\n            )\n            d.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n    def cb_psync_upload_chunk_result(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self, result, agent_id, ac_id, dbindex, fileobj\n    ):\n        result = PROJECT_SYNC_STAGE.lookupByValue(result)\n        self.log.debug(\"PSync: upload chunk result {r}\", r=str(result))\n        assert result & (PROJECT_SYNC_STAGE.UPLOAD | PROJECT_SYNC_STAGE.EXTRACTED)\n        if result is PROJECT_SYNC_STAGE.EXTRACTED:\n            if dbindex:\n                self.psync_upload(agent_id, ac_id, dbindex)\n            else:\n                self.psync_finalize(agent_id, ac_id)\n        else:\n            self.psync_upload_chunk(agent_id, ac_id, dbindex, fileobj)\n\n    def psync_finalize(self, agent_id, ac_id):\n        try:\n            d = self.agentpool.callRemote(\"acclose\", agent_id, ac_id)\n            d.addCallback(self.cb_psync_completed_result, agent_id)\n            d.addErrback(self.cb_global_error)\n        except (AttributeError, pb.DeadReferenceError):\n            self.disconnect(exit_code=1)\n\n    def cb_psync_completed_result(self, result, agent_id):\n        assert PROJECT_SYNC_STAGE.lookupByValue(result)\n        options = self.options.copy()\n        del options[\"project_dir\"]\n        options[\"project_id\"] = self.project_id\n        d = self.agentpool.callRemote(\"cmd\", [agent_id], self.command, options)\n        d.addCallback(self.cb_async_result)\n        d.addErrback(self.cb_global_error)\n"
  },
  {
    "path": "platformio/remote/client/update_core.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.remote.client.async_base import AsyncClientBase\n\n\nclass UpdateCoreClient(AsyncClientBase):\n    def agent_pool_ready(self):\n        d = self.agentpool.callRemote(\"cmd\", self.agents, self.command, self.options)\n        d.addCallback(self.cb_async_result)\n        d.addErrback(self.cb_global_error)\n"
  },
  {
    "path": "platformio/remote/factory/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/remote/factory/client.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom twisted.cred import credentials  # pylint: disable=import-error\nfrom twisted.internet import defer, protocol, reactor  # pylint: disable=import-error\nfrom twisted.spread import pb  # pylint: disable=import-error\n\nfrom platformio.account.client import AccountClient\nfrom platformio.app import get_host_id\n\n\nclass RemoteClientFactory(pb.PBClientFactory, protocol.ReconnectingClientFactory):\n    def clientConnectionMade(self, broker):\n        if self.sslContextFactory and not self.sslContextFactory.certificate_verified:\n            self.remote_client.log.error(\n                \"A remote cloud could not prove that its security certificate is \"\n                \"from {host}. This may cause a misconfiguration or an attacker \"\n                \"intercepting your connection.\",\n                host=self.sslContextFactory.host,\n            )\n            return self.remote_client.disconnect()\n        pb.PBClientFactory.clientConnectionMade(self, broker)\n        protocol.ReconnectingClientFactory.resetDelay(self)\n        self.remote_client.log.info(\"Successfully connected\")\n        self.remote_client.log.info(\"Authenticating\")\n\n        auth_token = None\n        try:\n            auth_token = AccountClient().fetch_authentication_token()\n        except Exception as exc:  # pylint:disable=broad-except\n            d = defer.Deferred()\n            d.addErrback(self.clientAuthorizationFailed)\n            d.errback(pb.Error(exc))\n            return d\n\n        d = self.login(\n            credentials.UsernamePassword(\n                auth_token.encode(),\n                get_host_id().encode(),\n            ),\n            client=self.remote_client,\n        )\n        d.addCallback(self.remote_client.cb_client_authorization_made)\n        d.addErrback(self.clientAuthorizationFailed)\n        return d\n\n    def clientAuthorizationFailed(self, err):\n        AccountClient.delete_local_session()\n        self.remote_client.cb_client_authorization_failed(err)\n\n    def clientConnectionFailed(self, connector, reason):\n        self.remote_client.log.warn(\n            \"Could not connect to PIO Remote Cloud. Reconnecting...\"\n        )\n        self.remote_client.cb_disconnected(reason)\n        protocol.ReconnectingClientFactory.clientConnectionFailed(\n            self, connector, reason\n        )\n\n    def clientConnectionLost(  # pylint: disable=arguments-differ\n        self, connector, unused_reason\n    ):\n        if not reactor.running:\n            self.remote_client.log.info(\"Successfully disconnected\")\n            return\n        self.remote_client.log.warn(\n            \"Connection is lost to PIO Remote Cloud. Reconnecting\"\n        )\n        pb.PBClientFactory.clientConnectionLost(\n            self, connector, unused_reason, reconnecting=1\n        )\n        self.remote_client.cb_disconnected(unused_reason)\n        protocol.ReconnectingClientFactory.clientConnectionLost(\n            self, connector, unused_reason\n        )\n"
  },
  {
    "path": "platformio/remote/factory/ssl.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport certifi\nfrom OpenSSL import SSL  # pylint: disable=import-error\nfrom twisted.internet import ssl  # pylint: disable=import-error\n\n\nclass SSLContextFactory(ssl.ClientContextFactory):\n    def __init__(self, host):\n        self.host = host\n        self.certificate_verified = False\n\n    def getContext(self):\n        ctx = super().getContext()\n        ctx.set_verify(\n            SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, self.verifyHostname\n        )\n        ctx.load_verify_locations(certifi.where())\n        return ctx\n\n    def verifyHostname(  # pylint: disable=unused-argument,too-many-arguments,too-many-positional-arguments\n        self, connection, x509, errno, depth, status\n    ):\n        cn = x509.get_subject().commonName\n        if cn.startswith(\"*\"):\n            cn = cn[1:]\n        if self.host.endswith(cn):\n            self.certificate_verified = True\n        return status\n"
  },
  {
    "path": "platformio/remote/projectsync.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport tarfile\nfrom binascii import crc32\nfrom os.path import getmtime, getsize, isdir, isfile, join\n\ntry:\n    from twisted.python import constants  # pylint: disable=import-error\nexcept ImportError:\n    # https://docs.twisted.org/en/twisted-16.5.0/core/howto/constants.html\n    import constantly as constants  # pylint: disable=import-error\n\nfrom platformio.compat import hashlib_encode_data\n\n\nclass PROJECT_SYNC_STAGE(constants.Flags):\n    INIT = constants.FlagConstant()\n    DBINDEX = constants.FlagConstant()\n    DELETE = constants.FlagConstant()\n    UPLOAD = constants.FlagConstant()\n    EXTRACTED = constants.FlagConstant()\n    COMPLETED = constants.FlagConstant()\n\n\nclass ProjectSync:\n    def __init__(self, path):\n        self.path = path\n        if not isdir(self.path):\n            os.makedirs(self.path)\n        self.items = []\n        self._db = {}\n\n    def add_item(self, path, relpath, cb_filter=None):\n        self.items.append((path, relpath, cb_filter))\n\n    def get_items(self):\n        return self.items\n\n    def rebuild_dbindex(self):\n        self._db = {}\n        for path, relpath, cb_filter in self.items:\n            if cb_filter and not cb_filter(path):\n                continue\n            self._insert_to_db(path, relpath)\n            if not isdir(path):\n                continue\n            for root, _, files in os.walk(path, followlinks=True):\n                for name in files:\n                    self._insert_to_db(\n                        join(root, name), join(relpath, root[len(path) + 1 :], name)\n                    )\n\n    def _insert_to_db(self, path, relpath):\n        if not isfile(path):\n            return\n        index_hash = \"%s-%s-%s\" % (relpath, getmtime(path), getsize(path))\n        index = crc32(hashlib_encode_data(index_hash))\n        self._db[index] = (path, relpath)\n\n    def get_dbindex(self):\n        return list(self._db.keys())\n\n    def delete_dbindex(self, dbindex):\n        for index in dbindex:\n            if index not in self._db:\n                continue\n            path = self._db[index][0]\n            if isfile(path):\n                os.remove(path)\n            del self._db[index]\n        self.delete_empty_folders()\n        return True\n\n    def delete_empty_folders(self):\n        deleted = False\n        for item in self.items:\n            if not isdir(item[0]):\n                continue\n            for root, dirs, files in os.walk(item[0]):\n                if not dirs and not files and root != item[0]:\n                    deleted = True\n                    os.rmdir(root)\n        if deleted:\n            return self.delete_empty_folders()\n\n        return True\n\n    def compress_items(self, fileobj, dbindex, max_size):\n        compressed = []\n        total_size = 0\n        tar_opts = dict(fileobj=fileobj, mode=\"w:gz\", bufsize=0, dereference=True)\n        with tarfile.open(**tar_opts) as tgz:\n            for index in dbindex:\n                compressed.append(index)\n                if index not in self._db:\n                    continue\n                path, relpath = self._db[index]\n                tgz.add(path, relpath)\n                total_size += getsize(path)\n                if total_size > max_size:\n                    break\n        return compressed\n\n    def decompress_items(self, fileobj):\n        fileobj.seek(0)\n        with tarfile.open(fileobj=fileobj, mode=\"r:gz\") as tgz:\n            tgz.extractall(self.path)\n        return True\n"
  },
  {
    "path": "platformio/run/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/run/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport operator\nimport os\nimport shutil\nfrom multiprocessing import cpu_count\nfrom time import time\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import app, exception, fs, util\nfrom platformio.device.monitor.command import device_monitor_cmd\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import ProjectError\nfrom platformio.project.helpers import find_project_dir_above, load_build_metadata\nfrom platformio.run.helpers import clean_build_dir\nfrom platformio.run.processor import EnvironmentProcessor\nfrom platformio.test.runners.base import CTX_META_TEST_IS_RUNNING\n\n# pylint: disable=too-many-arguments,too-many-locals,too-many-branches\n\ntry:\n    SYSTEM_CPU_COUNT = cpu_count()\nexcept NotImplementedError:\n    SYSTEM_CPU_COUNT = 1\n\nDEFAULT_JOB_NUMS = int(os.getenv(\"PLATFORMIO_RUN_JOBS\", SYSTEM_CPU_COUNT))\n\n\n@click.command(\"run\", short_help=\"Run project targets (build, upload, clean, etc.)\")\n@click.option(\"-e\", \"--environment\", multiple=True)\n@click.option(\"-t\", \"--target\", multiple=True)\n@click.option(\"--upload-port\")\n@click.option(\"--monitor-port\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=True, dir_okay=True, writable=True),\n)\n@click.option(\n    \"-c\",\n    \"--project-conf\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),\n)\n@click.option(\n    \"-j\",\n    \"--jobs\",\n    type=int,\n    default=DEFAULT_JOB_NUMS,\n    help=(\n        \"Allow N jobs at once. \"\n        \"Default is a number of CPUs in a system (N=%d)\" % DEFAULT_JOB_NUMS\n    ),\n)\n@click.option(\n    \"-a\",\n    \"--program-arg\",\n    \"program_args\",\n    multiple=True,\n    help=\"A program argument (multiple are allowed)\",\n)\n@click.option(\"--disable-auto-clean\", is_flag=True)\n@click.option(\"--list-targets\", is_flag=True)\n@click.option(\"-s\", \"--silent\", is_flag=True)\n@click.option(\"-v\", \"--verbose\", is_flag=True)\n@click.pass_context\ndef cli(  # pylint: disable=too-many-positional-arguments\n    ctx,\n    environment,\n    target,\n    upload_port,\n    monitor_port,\n    project_dir,\n    project_conf,\n    jobs,\n    program_args,\n    disable_auto_clean,\n    list_targets,\n    silent,\n    verbose,\n):\n    app.set_session_var(\"custom_project_conf\", project_conf)\n\n    # find project directory on upper level\n    if os.path.isfile(project_dir):\n        project_dir = find_project_dir_above(project_dir)\n\n    targets = list(target) if target else []\n    del target\n    only_monitor = targets == [\"monitor\"]\n    is_test_running = CTX_META_TEST_IS_RUNNING in ctx.meta\n    command_failed = False\n\n    with fs.cd(project_dir):\n        config = ProjectConfig.get_instance(project_conf)\n        config.validate(environment)\n\n        if list_targets:\n            return print_target_list(list(environment) or config.envs())\n\n        # clean obsolete build dir\n        if not only_monitor and not disable_auto_clean:\n            build_dir = config.get(\"platformio\", \"build_dir\")\n            try:\n                clean_build_dir(build_dir, config)\n            except ProjectError as exc:\n                raise exc\n            except:  # pylint: disable=bare-except\n                click.secho(\n                    \"Can not remove temporary directory `%s`. Please remove \"\n                    \"it manually to avoid build issues\" % build_dir,\n                    fg=\"yellow\",\n                )\n\n        default_envs = config.default_envs()\n        results = []\n        for env in config.envs():\n            skipenv = any(\n                [\n                    environment and env not in environment,\n                    not environment and default_envs and env not in default_envs,\n                ]\n            )\n            if skipenv:\n                results.append({\"env\": env})\n                continue\n\n            # print empty line between multi environment project\n            if not silent and any(r.get(\"succeeded\") is not None for r in results):\n                click.echo()\n\n            results.append(\n                process_env(\n                    ctx,\n                    env,\n                    config,\n                    targets,\n                    upload_port,\n                    monitor_port,\n                    jobs,\n                    program_args,\n                    is_test_running,\n                    silent,\n                    verbose,\n                )\n            )\n        command_failed = any(r.get(\"succeeded\") is False for r in results)\n        if (\n            not is_test_running\n            and not only_monitor\n            and (command_failed or not silent)\n            and len(results) > 1\n        ):\n            print_processing_summary(results, verbose)\n\n    # Reset custom project config\n    app.set_session_var(\"custom_project_conf\", None)\n\n    if command_failed:\n        raise exception.ReturnErrorCode(1)\n\n    return True\n\n\ndef process_env(  # pylint: disable=too-many-positional-arguments\n    ctx,\n    name,\n    config,\n    targets,\n    upload_port,\n    monitor_port,\n    jobs,\n    program_args,\n    is_test_running,\n    silent,\n    verbose,\n):\n    if not is_test_running and not silent:\n        print_processing_header(name, config, verbose)\n\n    targets = targets or config.get(f\"env:{name}\", \"targets\", [])\n    only_monitor = targets == [\"monitor\"]\n    result = {\"env\": name, \"duration\": time(), \"succeeded\": True}\n\n    if not only_monitor:\n        result[\"succeeded\"] = EnvironmentProcessor(\n            ctx,\n            name,\n            config,\n            [t for t in targets if t != \"monitor\"],\n            upload_port,\n            jobs,\n            program_args,\n            silent,\n            verbose,\n        ).process()\n\n    if result[\"succeeded\"] and \"monitor\" in targets and \"nobuild\" not in targets:\n        ctx.invoke(\n            device_monitor_cmd,\n            port=monitor_port,\n            environment=name,\n        )\n\n    result[\"duration\"] = time() - result[\"duration\"]\n\n    # print footer on error or when is not unit testing\n    if (\n        not is_test_running\n        and not only_monitor\n        and (not silent or not result[\"succeeded\"])\n    ):\n        print_processing_footer(result)\n\n    return result\n\n\ndef print_processing_header(env, config, verbose=False):\n    env_dump = []\n    for k, v in config.items(env=env):\n        if verbose or k in (\"platform\", \"framework\", \"board\"):\n            env_dump.append(\"%s: %s\" % (k, \", \".join(v) if isinstance(v, list) else v))\n    click.echo(\n        \"Processing %s (%s)\"\n        % (click.style(env, fg=\"cyan\", bold=True), \"; \".join(env_dump))\n    )\n    terminal_width = shutil.get_terminal_size().columns\n    click.secho(\"-\" * terminal_width, bold=True)\n\n\ndef print_processing_footer(result):\n    is_failed = not result.get(\"succeeded\")\n    util.print_labeled_bar(\n        \"[%s] Took %.2f seconds\"\n        % (\n            (\n                click.style(\"FAILED\", fg=\"red\", bold=True)\n                if is_failed\n                else click.style(\"SUCCESS\", fg=\"green\", bold=True)\n            ),\n            result[\"duration\"],\n        ),\n        is_error=is_failed,\n    )\n\n\ndef print_processing_summary(results, verbose=False):\n    tabular_data = []\n    succeeded_nums = 0\n    failed_nums = 0\n    duration = 0\n\n    for result in results:\n        duration += result.get(\"duration\", 0)\n        if result.get(\"succeeded\") is False:\n            failed_nums += 1\n            status_str = click.style(\"FAILED\", fg=\"red\")\n        elif result.get(\"succeeded\") is None:\n            if not verbose:\n                continue\n            status_str = \"IGNORED\"\n        else:\n            succeeded_nums += 1\n            status_str = click.style(\"SUCCESS\", fg=\"green\")\n\n        tabular_data.append(\n            (\n                click.style(result[\"env\"], fg=\"cyan\"),\n                status_str,\n                util.humanize_duration_time(result.get(\"duration\")),\n            )\n        )\n\n    click.echo()\n    click.echo(\n        tabulate(\n            tabular_data,\n            headers=[\n                click.style(s, bold=True) for s in (\"Environment\", \"Status\", \"Duration\")\n            ],\n        ),\n        err=failed_nums,\n    )\n\n    util.print_labeled_bar(\n        \"%s%d succeeded in %s\"\n        % (\n            \"%d failed, \" % failed_nums if failed_nums else \"\",\n            succeeded_nums,\n            util.humanize_duration_time(duration),\n        ),\n        is_error=failed_nums,\n        fg=\"red\" if failed_nums else \"green\",\n    )\n\n\ndef print_target_list(envs):\n    tabular_data = []\n    for env, data in load_build_metadata(os.getcwd(), envs).items():\n        tabular_data.extend(\n            sorted(\n                [\n                    (\n                        click.style(env, fg=\"cyan\"),\n                        t[\"group\"],\n                        click.style(t.get(\"name\"), fg=\"yellow\"),\n                        t[\"title\"],\n                        t.get(\"description\"),\n                    )\n                    for t in data.get(\"targets\", [])\n                ],\n                key=operator.itemgetter(1, 2),\n            )\n        )\n        tabular_data.append((None, None, None, None, None))\n    click.echo(\n        tabulate(\n            tabular_data,\n            headers=[\n                click.style(s, bold=True)\n                for s in (\"Environment\", \"Group\", \"Name\", \"Title\", \"Description\")\n            ],\n        ),\n    )\n"
  },
  {
    "path": "platformio/run/helpers.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom os import makedirs\nfrom os.path import isdir, isfile, join\n\nfrom platformio import fs\nfrom platformio.project.helpers import compute_project_checksum, get_project_dir\n\nKNOWN_CLEAN_TARGETS = (\"clean\",)\nKNOWN_FULLCLEAN_TARGETS = (\"cleanall\", \"fullclean\")\nKNOWN_ALLCLEAN_TARGETS = KNOWN_CLEAN_TARGETS + KNOWN_FULLCLEAN_TARGETS\n\n\ndef clean_build_dir(build_dir, config):\n    # remove legacy \".pioenvs\" folder\n    legacy_build_dir = join(get_project_dir(), \".pioenvs\")\n    if isdir(legacy_build_dir) and legacy_build_dir != build_dir:\n        fs.rmtree(legacy_build_dir)\n\n    checksum_file = join(build_dir, \"project.checksum\")\n    checksum = compute_project_checksum(config)\n\n    if isdir(build_dir):\n        # check project structure\n        if isfile(checksum_file):\n            with open(checksum_file, encoding=\"utf8\") as fp:\n                if fp.read() == checksum:\n                    return\n        fs.rmtree(build_dir)\n\n    makedirs(build_dir)\n    with open(checksum_file, mode=\"w\", encoding=\"utf8\") as fp:\n        fp.write(checksum)\n"
  },
  {
    "path": "platformio/run/processor.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.package.commands.install import install_project_env_dependencies\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.exception import UndefinedEnvPlatformError\nfrom platformio.run.helpers import KNOWN_ALLCLEAN_TARGETS\nfrom platformio.test.runners.base import CTX_META_TEST_RUNNING_NAME\n\n# pylint: disable=too-many-instance-attributes\n\n\nclass EnvironmentProcessor:\n    def __init__(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self,\n        cmd_ctx,\n        name,\n        config,\n        targets,\n        upload_port,\n        jobs,\n        program_args,\n        silent,\n        verbose,\n    ):\n        self.cmd_ctx = cmd_ctx\n        self.name = name\n        self.config = config\n        self.targets = targets\n        self.upload_port = upload_port\n        self.jobs = jobs\n        self.program_args = program_args\n        self.silent = silent\n        self.verbose = verbose\n        self.options = config.items(env=name, as_dict=True)\n\n    def get_build_variables(self):\n        variables = dict(\n            pioenv=self.name,\n            project_config=self.config.path,\n            program_args=self.program_args,\n        )\n\n        if CTX_META_TEST_RUNNING_NAME in self.cmd_ctx.meta:\n            variables[\"piotest_running_name\"] = self.cmd_ctx.meta[\n                CTX_META_TEST_RUNNING_NAME\n            ]\n\n        if self.upload_port:\n            # override upload port with a custom from CLI\n            variables[\"upload_port\"] = self.upload_port\n        return variables\n\n    def process(self):\n        if \"platform\" not in self.options:\n            raise UndefinedEnvPlatformError(self.name)\n\n        build_vars = self.get_build_variables()\n        is_clean = set(KNOWN_ALLCLEAN_TARGETS) & set(self.targets)\n        build_targets = [t for t in self.targets if t not in KNOWN_ALLCLEAN_TARGETS]\n\n        # pre-clean\n        if is_clean:\n            result = PlatformFactory.from_env(\n                self.name, targets=self.targets, autoinstall=True\n            ).run(build_vars, self.targets, self.silent, self.verbose, self.jobs)\n            if not build_targets:\n                return result[\"returncode\"] == 0\n\n        install_project_env_dependencies(\n            self.name,\n            {\n                \"project_targets\": self.targets,\n                \"piotest_running_name\": build_vars.get(\"piotest_running_name\"),\n            },\n        )\n        result = PlatformFactory.from_env(\n            self.name, targets=build_targets, autoinstall=True\n        ).run(build_vars, build_targets, self.silent, self.verbose, self.jobs)\n        return result[\"returncode\"] == 0\n"
  },
  {
    "path": "platformio/system/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/system/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.system.commands.completion import system_completion_cmd\nfrom platformio.system.commands.info import system_info_cmd\nfrom platformio.system.commands.prune import system_prune_cmd\n\n\n@click.group(\n    \"system\",\n    commands=[\n        system_completion_cmd,\n        system_info_cmd,\n        system_prune_cmd,\n    ],\n    short_help=\"Miscellaneous system commands\",\n)\ndef cli():\n    pass\n"
  },
  {
    "path": "platformio/system/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/system/commands/completion.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.system.completion import (\n    ShellType,\n    get_completion_install_path,\n    install_completion_code,\n    uninstall_completion_code,\n)\n\n\n@click.group(\"completion\", short_help=\"Shell completion support\")\ndef system_completion_cmd():\n    pass\n\n\n@system_completion_cmd.command(\n    \"install\", short_help=\"Install shell completion files/code\"\n)\n@click.argument(\"shell\", type=click.Choice([t.value for t in ShellType]))\n@click.option(\n    \"--path\",\n    type=click.Path(file_okay=True, dir_okay=False, readable=True),\n    help=\"Custom installation path of the code to be evaluated by the shell. \"\n    \"The standard installation path is used by default.\",\n)\ndef system_completion_install(shell, path):\n    shell = ShellType(shell)\n    path = path or get_completion_install_path(shell)\n    install_completion_code(shell, path)\n    click.echo(\n        \"PlatformIO CLI completion has been installed for %s shell to %s \\n\"\n        \"Please restart a current shell session.\"\n        % (click.style(shell.name, fg=\"cyan\"), click.style(path, fg=\"blue\"))\n    )\n\n\n@system_completion_cmd.command(\n    \"uninstall\", short_help=\"Uninstall shell completion files/code\"\n)\n@click.argument(\"shell\", type=click.Choice([t.value for t in ShellType]))\n@click.option(\n    \"--path\",\n    type=click.Path(file_okay=True, dir_okay=False, readable=True),\n    help=\"Custom installation path of the code to be evaluated by the shell. \"\n    \"The standard installation path is used by default.\",\n)\ndef system_completion_uninstall(shell, path):\n    shell = ShellType(shell)\n    path = path or get_completion_install_path(shell)\n    uninstall_completion_code(shell, path)\n    click.echo(\n        \"PlatformIO CLI completion has been uninstalled for %s shell from %s \\n\"\n        \"Please restart a current shell session.\"\n        % (click.style(shell.name, fg=\"cyan\"), click.style(path, fg=\"blue\"))\n    )\n"
  },
  {
    "path": "platformio/system/commands/info.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport json\nimport platform\nimport sys\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import __version__, compat, proc, util\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.project.config import ProjectConfig\n\n\n@click.command(\"info\", short_help=\"Display system-wide information\")\n@click.option(\"--json-output\", is_flag=True)\ndef system_info_cmd(json_output):\n    project_config = ProjectConfig()\n    data = {}\n    data[\"core_version\"] = {\"title\": \"PlatformIO Core\", \"value\": __version__}\n    data[\"python_version\"] = {\n        \"title\": \"Python\",\n        \"value\": \"{0}.{1}.{2}-{3}.{4}\".format(*list(sys.version_info)),\n    }\n    data[\"system\"] = {\"title\": \"System Type\", \"value\": util.get_systype()}\n    data[\"platform\"] = {\"title\": \"Platform\", \"value\": platform.platform(terse=True)}\n    data[\"filesystem_encoding\"] = {\n        \"title\": \"File System Encoding\",\n        \"value\": compat.get_filesystem_encoding(),\n    }\n    data[\"locale_encoding\"] = {\n        \"title\": \"Locale Encoding\",\n        \"value\": compat.get_locale_encoding(),\n    }\n    data[\"core_dir\"] = {\n        \"title\": \"PlatformIO Core Directory\",\n        \"value\": project_config.get(\"platformio\", \"core_dir\"),\n    }\n    data[\"platformio_exe\"] = {\n        \"title\": \"PlatformIO Core Executable\",\n        \"value\": proc.where_is_program(\n            \"platformio.exe\" if compat.IS_WINDOWS else \"platformio\"\n        ),\n    }\n    data[\"python_exe\"] = {\n        \"title\": \"Python Executable\",\n        \"value\": proc.get_pythonexe_path(),\n    }\n    data[\"global_lib_nums\"] = {\n        \"title\": \"Global Libraries\",\n        \"value\": len(LibraryPackageManager().get_installed()),\n    }\n    data[\"dev_platform_nums\"] = {\n        \"title\": \"Development Platforms\",\n        \"value\": len(PlatformPackageManager().get_installed()),\n    }\n    data[\"package_tool_nums\"] = {\n        \"title\": \"Tools & Toolchains\",\n        \"value\": len(\n            ToolPackageManager(\n                project_config.get(\"platformio\", \"packages_dir\")\n            ).get_installed()\n        ),\n    }\n    click.echo(\n        json.dumps(data)\n        if json_output\n        else tabulate([(item[\"title\"], item[\"value\"]) for item in data.values()])\n    )\n"
  },
  {
    "path": "platformio/system/commands/prune.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio import fs\nfrom platformio.system.prune import (\n    prune_cached_data,\n    prune_core_packages,\n    prune_platform_packages,\n)\n\n\n@click.command(\"prune\", short_help=\"Remove unused data\")\n@click.option(\"--force\", \"-f\", is_flag=True, help=\"Do not prompt for confirmation\")\n@click.option(\n    \"--dry-run\", is_flag=True, help=\"Do not prune, only show data that will be removed\"\n)\n@click.option(\"--cache\", is_flag=True, help=\"Prune only cached data\")\n@click.option(\n    \"--core-packages\", is_flag=True, help=\"Prune only unnecessary core packages\"\n)\n@click.option(\n    \"--platform-packages\",\n    is_flag=True,\n    help=\"Prune only unnecessary development platform packages\",\n)\ndef system_prune_cmd(force, dry_run, cache, core_packages, platform_packages):\n    if dry_run:\n        click.secho(\n            \"Dry run mode (do not prune, only show data that will be removed)\",\n            fg=\"yellow\",\n        )\n        click.echo()\n\n    reclaimed_cache = 0\n    reclaimed_core_packages = 0\n    reclaimed_platform_packages = 0\n    prune_all = not any([cache, core_packages, platform_packages])\n\n    if cache or prune_all:\n        reclaimed_cache = prune_cached_data(force, dry_run)\n        click.echo()\n\n    if core_packages or prune_all:\n        reclaimed_core_packages = prune_core_packages(force, dry_run)\n        click.echo()\n\n    if platform_packages or prune_all:\n        reclaimed_platform_packages = prune_platform_packages(force, dry_run)\n        click.echo()\n\n    click.secho(\n        \"Total reclaimed space: %s\"\n        % fs.humanize_file_size(\n            reclaimed_cache + reclaimed_core_packages + reclaimed_platform_packages\n        ),\n        fg=\"green\",\n    )\n"
  },
  {
    "path": "platformio/system/completion.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport subprocess\nfrom enum import Enum\n\nimport click\n\nfrom platformio.compat import IS_MACOS\n\n\nclass ShellType(Enum):\n    FISH = \"fish\"\n    ZSH = \"zsh\"\n    BASH = \"bash\"\n\n\ndef get_bash_version():\n    output = subprocess.run(\n        [\"bash\", \"--version\"], check=True, stdout=subprocess.PIPE\n    ).stdout.decode()\n    match = re.search(r\"version\\s+(\\d+)\\.(\\d+)\", output, re.IGNORECASE)\n    if match:\n        return (int(match.group(1)), int(match.group(2)))\n    return (0, 0)\n\n\ndef get_completion_install_path(shell):\n    home_dir = os.path.expanduser(\"~\")\n    prog_name = click.get_current_context().find_root().info_name\n    if shell == ShellType.FISH:\n        return os.path.join(\n            home_dir, \".config\", \"fish\", \"completions\", \"%s.fish\" % prog_name\n        )\n    if shell == ShellType.ZSH:\n        return os.path.join(home_dir, \".zshrc\")\n    if shell == ShellType.BASH:\n        return os.path.join(home_dir, \".bash_completion\")\n    raise click.ClickException(\"%s is not supported.\" % shell)\n\n\ndef get_completion_code(shell):\n    if shell == ShellType.FISH:\n        return \"eval (env _PIO_COMPLETE=fish_source pio)\"\n    if shell == ShellType.ZSH:\n        code = \"autoload -Uz compinit\\ncompinit\\n\" if IS_MACOS else \"\"\n        return code + 'eval \"$(_PIO_COMPLETE=zsh_source pio)\"'\n    if shell == ShellType.BASH:\n        return 'eval \"$(_PIO_COMPLETE=bash_source pio)\"'\n    raise click.ClickException(\"%s is not supported.\" % shell)\n\n\ndef is_completion_code_installed(shell, path):\n    if shell == ShellType.FISH or not os.path.exists(path):\n        return False\n    with open(path, encoding=\"utf8\") as fp:\n        return get_completion_code(shell) in fp.read()\n\n\ndef install_completion_code(shell, path):\n    if shell == ShellType.BASH and get_bash_version() < (4, 4):\n        raise click.ClickException(\"The minimal supported Bash version is 4.4\")\n    if is_completion_code_installed(shell, path):\n        return None\n    append = shell != ShellType.FISH\n    with open(path, mode=\"a\" if append else \"w\", encoding=\"utf8\") as fp:\n        if append:\n            fp.write(\"\\n\\n# Begin: PlatformIO Core completion support\\n\")\n        fp.write(get_completion_code(shell))\n        if append:\n            fp.write(\"\\n# End: PlatformIO Core completion support\\n\\n\")\n    return True\n\n\ndef uninstall_completion_code(shell, path):\n    if not os.path.exists(path):\n        return True\n    if shell == ShellType.FISH:\n        os.remove(path)\n        return True\n\n    with open(path, \"r+\", encoding=\"utf8\") as fp:\n        contents = fp.read()\n        fp.seek(0)\n        fp.truncate()\n        fp.write(contents.replace(get_completion_code(shell), \"\"))\n\n    return True\n"
  },
  {
    "path": "platformio/system/prune.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom operator import itemgetter\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import fs\nfrom platformio.package.manager.core import remove_unnecessary_core_packages\nfrom platformio.package.manager.platform import remove_unnecessary_platform_packages\nfrom platformio.project.helpers import get_project_cache_dir\n\n\ndef prune_cached_data(force=False, dry_run=False, silent=False):\n    reclaimed_space = 0\n    if not silent:\n        click.secho(\"Prune cached data:\", bold=True)\n        click.echo(\" - cached API requests\")\n        click.echo(\" - cached package downloads\")\n        click.echo(\" - temporary data\")\n    cache_dir = get_project_cache_dir()\n    if os.path.isdir(cache_dir):\n        reclaimed_space += fs.calculate_folder_size(cache_dir)\n        if not dry_run:\n            if not force:\n                click.confirm(\"Do you want to continue?\", abort=True)\n            fs.rmtree(cache_dir)\n    if not silent:\n        click.secho(\"Space on disk: %s\" % fs.humanize_file_size(reclaimed_space))\n    return reclaimed_space\n\n\ndef prune_core_packages(force=False, dry_run=False, silent=False):\n    if not silent:\n        click.secho(\"Prune unnecessary core packages:\", bold=True)\n    return _prune_packages(force, dry_run, silent, remove_unnecessary_core_packages)\n\n\ndef prune_platform_packages(force=False, dry_run=False, silent=False):\n    if not silent:\n        click.secho(\"Prune unnecessary development platform packages:\", bold=True)\n    return _prune_packages(force, dry_run, silent, remove_unnecessary_platform_packages)\n\n\ndef _prune_packages(force, dry_run, silent, handler):\n    if not silent:\n        click.echo(\"Calculating...\")\n    items = [\n        (\n            pkg,\n            fs.calculate_folder_size(pkg.path),\n        )\n        for pkg in handler(dry_run=True)\n    ]\n    items = sorted(items, key=itemgetter(1), reverse=True)\n    reclaimed_space = sum(item[1] for item in items)\n    if items and not silent:\n        click.echo(\n            tabulate(\n                [\n                    (\n                        pkg.metadata.spec.humanize(),\n                        str(pkg.metadata.version),\n                        fs.humanize_file_size(size),\n                    )\n                    for (pkg, size) in items\n                ],\n                headers=[\"Package\", \"Version\", \"Size\"],\n            )\n        )\n    if not dry_run:\n        if not force:\n            click.confirm(\"Do you want to continue?\", abort=True)\n        handler(dry_run=False)\n    if not silent:\n        click.secho(\"Space on disk: %s\" % fs.humanize_file_size(reclaimed_space))\n    return reclaimed_space\n\n\ndef calculate_unnecessary_system_data():\n    return (\n        prune_cached_data(force=True, dry_run=True, silent=True)\n        + prune_core_packages(force=True, dry_run=True, silent=True)\n        + prune_platform_packages(force=True, dry_run=True, silent=True)\n    )\n"
  },
  {
    "path": "platformio/telemetry.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport atexit\nimport os\nimport queue\nimport re\nimport sys\nimport threading\nimport time\nimport traceback\nfrom collections import deque\n\nimport requests\n\nfrom platformio import __title__, __version__, app, exception, fs, util\nfrom platformio.cli import PlatformioCLI\nfrom platformio.debug.config.base import DebugConfigBase\nfrom platformio.http import HTTPSession\nfrom platformio.proc import is_ci\n\nKEEP_MAX_REPORTS = 100\nSEND_MAX_EVENTS = 25\n\n\nclass MeasurementProtocol:\n    def __init__(self, events=None):\n        self.client_id = app.get_cid()\n        self._events = events or []\n        self._user_properties = {}\n\n        self.set_user_property(\"systype\", util.get_systype())\n        created_at = app.get_state_item(\"created_at\", None)\n        if created_at:\n            self.set_user_property(\"created_at\", int(created_at))\n\n    @staticmethod\n    def event_to_dict(name, params, timestamp=None):\n        event = {\"name\": name, \"params\": params}\n        if timestamp is not None:\n            event[\"timestamp\"] = timestamp\n        return event\n\n    def set_user_property(self, name, value):\n        self._user_properties[name] = value\n\n    def add_event(self, name, params):\n        self._events.append(self.event_to_dict(name, params))\n\n    def to_payload(self):\n        return {\n            \"client_id\": self.client_id,\n            \"user_properties\": self._user_properties,\n            \"events\": self._events,\n        }\n\n\n@util.singleton\nclass TelemetryLogger:\n    def __init__(self):\n        self._events = deque()\n\n        self._sender_thread = None\n        self._sender_queue = queue.Queue()\n        self._sender_terminated = False\n\n        self._http_session = HTTPSession()\n        self._http_offline = False\n\n    def close(self):\n        self._http_session.close()\n\n    def log_event(self, name, params, timestamp=None, instant_sending=False):\n        if not app.get_setting(\"enable_telemetry\") or app.get_session_var(\n            \"pause_telemetry\"\n        ):\n            return None\n        timestamp = timestamp or int(time.time())\n        self._events.append(\n            MeasurementProtocol.event_to_dict(name, params, timestamp=timestamp)\n        )\n        if self._http_offline:  # if network is off-line\n            return False\n        if instant_sending:\n            self.send()\n        return True\n\n    def send(self):\n        if not self._events or self._sender_terminated:\n            return\n        if not self._sender_thread:\n            self._sender_thread = threading.Thread(\n                target=self._sender_worker, daemon=True\n            )\n            self._sender_thread.start()\n        while self._events:\n            events = []\n            try:\n                while len(events) < SEND_MAX_EVENTS:\n                    events.append(self._events.popleft())\n            except IndexError:\n                pass\n            self._sender_queue.put(events)\n\n    def _sender_worker(self):\n        while True:\n            if self._sender_terminated:\n                return\n            try:\n                events = self._sender_queue.get()\n                if not self._commit_events(events):\n                    self._events.extend(events)\n                self._sender_queue.task_done()\n            except (queue.Empty, ValueError):\n                pass\n\n    def _commit_events(self, events):\n        if self._http_offline:\n            return False\n        mp = MeasurementProtocol(events)\n        payload = mp.to_payload()\n        # print(\"_commit_payload\", payload)\n        try:\n            r = self._http_session.post(\n                \"https://collector.platformio.org/collect\",\n                json=payload,\n                timeout=(2, 5),  # connect, read\n            )\n            r.raise_for_status()\n            return True\n        except requests.exceptions.HTTPError as exc:\n            # skip Bad Request\n            if exc.response.status_code >= 400 and exc.response.status_code < 500:\n                return True\n        except:  # pylint: disable=bare-except\n            pass\n        self._http_offline = True\n        return False\n\n    def terminate_sender(self):\n        self._sender_terminated = True\n\n    def is_sending(self):\n        return self._sender_queue.unfinished_tasks\n\n    def get_unsent_events(self):\n        result = list(self._events)\n        try:\n            while True:\n                result.extend(self._sender_queue.get_nowait())\n        except queue.Empty:\n            pass\n        return result\n\n\ndef log_event(name, params, instant_sending=False):\n    TelemetryLogger().log_event(name, params, instant_sending=instant_sending)\n\n\ndef on_cmd_start(cmd_ctx):\n    process_postponed_logs()\n    log_command(cmd_ctx)\n\n\ndef on_exit():\n    TelemetryLogger().send()\n\n\ndef log_command(ctx):\n    params = {\n        \"path_args\": PlatformioCLI.reveal_cmd_path_args(ctx),\n    }\n    if is_ci():\n        params[\"ci_actor\"] = resolve_ci_actor() or \"Unknown\"\n    log_event(\"cmd_run\", params)\n\n\ndef resolve_ci_actor():\n    known_cis = (\n        \"GITHUB_ACTIONS\",\n        \"TRAVIS\",\n        \"APPVEYOR\",\n        \"GITLAB_CI\",\n        \"CIRCLECI\",\n        \"SHIPPABLE\",\n        \"DRONE\",\n    )\n    for name in known_cis:\n        if os.getenv(name, \"false\").lower() == \"true\":\n            return name\n    return None\n\n\ndef dump_project_env_params(config, env, platform):\n    non_sensitive_data = [\n        \"platform\",\n        \"framework\",\n        \"board\",\n        \"upload_protocol\",\n        \"check_tool\",\n        \"debug_tool\",\n        \"test_framework\",\n    ]\n    section = f\"env:{env}\"\n    params = {\n        option: config.get(section, option)\n        for option in non_sensitive_data\n        if config.has_option(section, option)\n    }\n    params[\"pid\"] = app.get_project_id(os.path.dirname(config.path))\n    params[\"platform_name\"] = platform.name\n    params[\"platform_version\"] = platform.version\n    return params\n\n\ndef log_platform_run(platform, project_config, project_env, targets=None):\n    params = dump_project_env_params(project_config, project_env, platform)\n    if targets:\n        params[\"targets\"] = targets\n    log_event(\"platform_run\", params, instant_sending=True)\n\n\ndef log_exception(exc):\n    skip_conditions = [\n        isinstance(exc, cls)\n        for cls in (\n            IOError,\n            exception.ReturnErrorCode,\n            exception.UserSideException,\n        )\n    ]\n    skip_conditions.append(not isinstance(exc, Exception))\n    if any(skip_conditions):\n        return\n    is_fatal = any(\n        [\n            not isinstance(exc, exception.PlatformioException),\n            \"Error\" in exc.__class__.__name__,\n        ]\n    )\n\n    def _strip_module_path(match):\n        module_path = match.group(1).replace(fs.get_source_dir() + os.sep, \"\")\n        sp_folder_name = \"site-packages\"\n        sp_pos = module_path.find(sp_folder_name)\n        if sp_pos != -1:\n            module_path = module_path[sp_pos + len(sp_folder_name) + 1 :]\n        module_path = fs.to_unix_path(module_path)\n        return f'File \"{module_path}\",'\n\n    trace = re.sub(\n        r'File \"([^\"]+)\",',\n        _strip_module_path,\n        traceback.format_exc(),\n        flags=re.MULTILINE,\n    )\n\n    params = {\n        \"name\": exc.__class__.__name__,\n        \"description\": str(exc),\n        \"traceback\": trace,\n        \"cmd_args\": sys.argv[1:],\n        \"is_fatal\": is_fatal,\n    }\n    log_event(\"exception\", params)\n\n\ndef log_debug_started(debug_config: DebugConfigBase):\n    log_event(\n        \"debug_started\",\n        dump_project_env_params(\n            debug_config.project_config, debug_config.env_name, debug_config.platform\n        ),\n    )\n\n\ndef log_debug_exception(exc, debug_config: DebugConfigBase):\n    # cleanup sensitive information, such as paths\n    description = fs.to_unix_path(str(exc))\n    description = re.sub(\n        r'(^|\\s+|\")(?:[a-z]\\:)?((/[^\"/]+)+)(\\s+|\"|$)',\n        lambda m: \" %s \" % os.path.join(*m.group(2).split(\"/\")[-2:]),\n        description,\n        re.I | re.M,\n    )\n    params = {\n        \"name\": exc.__class__.__name__,\n        \"description\": description.strip(),\n    }\n    params.update(\n        dump_project_env_params(\n            debug_config.project_config, debug_config.env_name, debug_config.platform\n        )\n    )\n    log_event(\"debug_exception\", params)\n\n\n@atexit.register\ndef _finalize():\n    timeout = 1000  # msec\n    elapsed = 0\n    telemetry = TelemetryLogger()\n    telemetry.terminate_sender()\n    try:\n        while elapsed < timeout:\n            if not telemetry.is_sending():\n                break\n            time.sleep(0.2)\n            elapsed += 200\n    except KeyboardInterrupt:\n        pass\n    postpone_events(telemetry.get_unsent_events())\n    telemetry.close()\n\n\ndef load_postponed_events():\n    state_path = app.resolve_state_path(\n        \"cache_dir\", \"telemetry.json\", ensure_dir_exists=False\n    )\n    if not os.path.isfile(state_path):\n        return []\n    with app.State(state_path) as state:\n        return state.get(\"events\", [])\n\n\ndef save_postponed_events(events):\n    state_path = app.resolve_state_path(\"cache_dir\", \"telemetry.json\")\n    if not events:\n        try:\n            if os.path.isfile(state_path):\n                os.remove(state_path)\n        except:  # pylint: disable=bare-except\n            pass\n        return None\n    with app.State(state_path, lock=True) as state:\n        state[\"events\"] = events\n        state.modified = True\n    return True\n\n\ndef postpone_events(events):\n    if not events:\n        return None\n    postponed_events = load_postponed_events() or []\n    timestamp = int(time.time())\n    for event in events:\n        if \"timestamp\" not in event:\n            event[\"timestamp\"] = timestamp\n        postponed_events.append(event)\n    save_postponed_events(postponed_events[KEEP_MAX_REPORTS * -1 :])\n    return True\n\n\ndef process_postponed_logs():\n    events = load_postponed_events()\n    if not events:\n        return None\n    save_postponed_events([])  # clean\n    telemetry = TelemetryLogger()\n    for event in events:\n        if set([\"name\", \"params\", \"timestamp\"]) <= set(event.keys()):\n            telemetry.log_event(\n                event[\"name\"],\n                event[\"params\"],\n                timestamp=event[\"timestamp\"],\n                instant_sending=False,\n            )\n    telemetry.send()\n    return True\n"
  },
  {
    "path": "platformio/test/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/test/cli.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport subprocess\n\nimport click\n\nfrom platformio import app, exception, fs, util\nfrom platformio.project.config import ProjectConfig\nfrom platformio.test.helpers import list_test_suites\nfrom platformio.test.reports.base import TestReportFactory\nfrom platformio.test.result import TestResult, TestStatus\nfrom platformio.test.runners.base import TestRunnerOptions\nfrom platformio.test.runners.factory import TestRunnerFactory\n\n\n@click.command(\"test\", short_help=\"Unit Testing\")\n@click.option(\"--environment\", \"-e\", multiple=True)\n@click.option(\n    \"--filter\",\n    \"-f\",\n    multiple=True,\n    metavar=\"PATTERN\",\n    help=\"Filter tests by a pattern\",\n)\n@click.option(\n    \"--ignore\",\n    \"-i\",\n    multiple=True,\n    metavar=\"PATTERN\",\n    help=\"Ignore tests by a pattern\",\n)\n@click.option(\"--upload-port\")\n@click.option(\"--test-port\")\n@click.option(\n    \"-d\",\n    \"--project-dir\",\n    default=os.getcwd,\n    type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True),\n)\n@click.option(\n    \"-c\",\n    \"--project-conf\",\n    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),\n)\n@click.option(\"--without-building\", is_flag=True)\n@click.option(\"--without-uploading\", is_flag=True)\n@click.option(\"--without-testing\", is_flag=True)\n@click.option(\"--no-reset\", is_flag=True)\n@click.option(\n    \"--monitor-rts\",\n    default=None,\n    type=click.IntRange(0, 1),\n    help=\"Set initial RTS line state for Serial Monitor\",\n)\n@click.option(\n    \"--monitor-dtr\",\n    default=None,\n    type=click.IntRange(0, 1),\n    help=\"Set initial DTR line state for Serial Monitor\",\n)\n@click.option(\n    \"-a\",\n    \"--program-arg\",\n    \"program_args\",\n    multiple=True,\n    help=\"A program argument (multiple are allowed)\",\n)\n@click.option(\"--list-tests\", is_flag=True)\n@click.option(\"--json-output\", is_flag=True)\n@click.option(\"--json-output-path\", type=click.Path())\n@click.option(\"--junit-output-path\", type=click.Path())\n@click.option(\n    \"--verbose\",\n    \"-v\",\n    count=True,\n    help=\"Increase verbosity level, maximum is 3 levels (-vvv), see docs for details\",\n)\n@click.pass_context\ndef cli(  # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals,redefined-builtin\n    ctx,\n    environment,\n    ignore,\n    filter,\n    upload_port,\n    test_port,\n    project_dir,\n    project_conf,\n    without_building,\n    without_uploading,\n    without_testing,\n    no_reset,\n    monitor_rts,\n    monitor_dtr,\n    program_args,\n    list_tests,\n    json_output,\n    json_output_path,\n    junit_output_path,\n    verbose,\n):\n    app.set_session_var(\"custom_project_conf\", project_conf)\n\n    with fs.cd(project_dir):\n        project_config = ProjectConfig.get_instance(project_conf)\n        project_config.validate(envs=environment)\n\n        test_result = TestResult(project_dir)\n        test_suites = list_test_suites(\n            project_config, environments=environment, filters=filter, ignores=ignore\n        )\n        test_names = sorted(set(s.test_name for s in test_suites))\n\n        if not verbose:\n            click.echo(\"Verbosity level can be increased via `-v, -vv, or -vvv` option\")\n        click.secho(\"Collected %d tests\" % len(test_names), bold=True, nl=not verbose)\n        if verbose:\n            click.echo(\" (%s)\" % \", \".join(test_names))\n\n        for test_suite in test_suites:\n            test_result.add_suite(test_suite)\n            if list_tests or test_suite.is_finished():  # skipped by user\n                continue\n            runner = TestRunnerFactory.new(\n                test_suite,\n                project_config,\n                TestRunnerOptions(\n                    verbose=verbose,\n                    without_building=without_building,\n                    without_uploading=without_uploading,\n                    without_testing=without_testing,\n                    upload_port=upload_port,\n                    test_port=test_port,\n                    no_reset=no_reset,\n                    monitor_rts=monitor_rts,\n                    monitor_dtr=monitor_dtr,\n                    program_args=program_args,\n                ),\n            )\n            click.echo()\n            print_suite_header(test_suite)\n            runner.start(ctx)\n            print_suite_footer(test_suite)\n\n    stdout_report = TestReportFactory.new(\"stdout\", test_result)\n    stdout_report.generate(verbose=verbose or list_tests)\n\n    for output_format, output_path in [\n        (\"json\", subprocess.STDOUT if json_output else None),\n        (\"json\", json_output_path),\n        (\"junit\", junit_output_path),\n    ]:\n        if not output_path:\n            continue\n        custom_report = TestReportFactory.new(output_format, test_result)\n        custom_report.generate(output_path=output_path, verbose=True)\n\n    # Reset custom project config\n    app.set_session_var(\"custom_project_conf\", None)\n\n    if test_result.is_errored or test_result.get_status_nums(TestStatus.FAILED):\n        raise exception.ReturnErrorCode(1)\n\n\ndef print_suite_header(test_suite):\n    click.echo(\n        \"Processing %s in %s environment\"\n        % (\n            click.style(test_suite.test_name, fg=\"yellow\", bold=True),\n            click.style(test_suite.env_name, fg=\"cyan\", bold=True),\n        )\n    )\n    terminal_width = shutil.get_terminal_size().columns\n    click.secho(\"-\" * terminal_width, bold=True)\n\n\ndef print_suite_footer(test_suite):\n    is_error = test_suite.status in (TestStatus.FAILED, TestStatus.ERRORED)\n    util.print_labeled_bar(\n        \"%s [%s] Took %.2f seconds\"\n        % (\n            click.style(\n                \"%s:%s\" % (test_suite.env_name, test_suite.test_name), bold=True\n            ),\n            (\n                click.style(test_suite.status.name, fg=\"red\", bold=True)\n                if is_error\n                else click.style(\"PASSED\", fg=\"green\", bold=True)\n            ),\n            test_suite.duration,\n        ),\n        is_error=is_error,\n        sep=\"-\",\n    )\n"
  },
  {
    "path": "platformio/test/exception.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.exception import PlatformioException, UserSideException\n\n\nclass UnitTestError(PlatformioException):\n    pass\n\n\nclass TestDirNotExistsError(UnitTestError, UserSideException):\n    MESSAGE = (\n        \"A test folder '{0}' does not exist.\\nPlease create 'test' \"\n        \"directory in the project root and put a test suite.\\n\"\n        \"More details about Unit \"\n        \"Testing: https://docs.platformio.org/en/latest/advanced/\"\n        \"unit-testing/index.html\"\n    )\n\n\nclass UnitTestSuiteError(UnitTestError):\n    pass\n"
  },
  {
    "path": "platformio/test/helpers.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nfrom fnmatch import fnmatch\n\nfrom platformio.test.exception import TestDirNotExistsError\nfrom platformio.test.result import TestSuite\n\n\ndef list_test_names(project_config):\n    test_dir = project_config.get(\"platformio\", \"test_dir\")\n    if not os.path.isdir(test_dir):\n        raise TestDirNotExistsError(test_dir)\n    names = []\n    for root, _, __ in os.walk(test_dir, followlinks=True):\n        if not os.path.basename(root).startswith(\"test_\"):\n            continue\n        names.append(os.path.relpath(root, test_dir).replace(\"\\\\\", \"/\"))\n    if not names:\n        names = [\"*\"]\n    return names\n\n\ndef list_test_suites(project_config, environments, filters, ignores):\n    result = []\n    test_dir = project_config.get(\"platformio\", \"test_dir\")\n    default_envs = project_config.default_envs()\n    test_names = list_test_names(project_config)\n    for env_name in project_config.envs():\n        for test_name in test_names:\n            # filter and ignore patterns\n            patterns = dict(filter=list(filters), ignore=list(ignores))\n            for key, value in patterns.items():\n                if value:  # overridden from CLI\n                    continue\n                patterns[key].extend(  # pylint: disable=unnecessary-dict-index-lookup\n                    project_config.get(f\"env:{env_name}\", f\"test_{key}\", [])\n                )\n\n            skip_conditions = [\n                environments and env_name not in environments,\n                not environments and default_envs and env_name not in default_envs,\n                test_name != \"*\"\n                and patterns[\"filter\"]\n                and not any(fnmatch(test_name, p) for p in patterns[\"filter\"]),\n                test_name != \"*\"\n                and any(fnmatch(test_name, p) for p in patterns[\"ignore\"]),\n            ]\n            result.append(\n                TestSuite(\n                    env_name,\n                    test_name,\n                    finished=any(skip_conditions),\n                    test_dir=os.path.abspath(\n                        test_dir\n                        if test_name == \"*\"\n                        else os.path.join(test_dir, test_name)\n                    ),\n                )\n            )\n    return result\n"
  },
  {
    "path": "platformio/test/reports/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/test/reports/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport importlib\n\nfrom platformio.test.result import TestResult\n\n\nclass TestReportBase:\n    def __init__(self, test_result):\n        self.test_result = test_result\n\n    def generate(self, output_path, verbose):\n        raise NotImplementedError()\n\n\nclass TestReportFactory:\n    @staticmethod\n    def new(format, test_result) -> TestReportBase:  # pylint: disable=redefined-builtin\n        assert isinstance(test_result, TestResult)\n        mod = importlib.import_module(f\"platformio.test.reports.{format}\")\n        report_cls = getattr(mod, \"%sTestReport\" % format.lower().capitalize())\n        return report_cls(test_result)\n"
  },
  {
    "path": "platformio/test/reports/json.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport datetime\nimport json\nimport os\nimport subprocess\n\nimport click\n\nfrom platformio.test.reports.base import TestReportBase\nfrom platformio.test.result import TestStatus\n\n\nclass JsonTestReport(TestReportBase):\n    def generate(self, output_path, verbose=False):\n        if output_path == subprocess.STDOUT:\n            return click.echo(\"\\n\\n\" + json.dumps(self.to_json()))\n\n        if os.path.isdir(output_path):\n            output_path = os.path.join(\n                output_path,\n                \"pio-test-report-%s-%s.json\"\n                % (\n                    os.path.basename(self.test_result.project_dir),\n                    datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\"),\n                ),\n            )\n\n        with open(output_path, mode=\"w\", encoding=\"utf8\") as fp:\n            json.dump(self.to_json(), fp)\n\n        if verbose:\n            click.secho(f\"Saved JSON report to the {output_path}\", fg=\"green\")\n\n        return True\n\n    def to_json(self):\n        result = dict(\n            version=\"1.0\",\n            project_dir=self.test_result.project_dir,\n            duration=self.test_result.duration,\n            testcase_nums=self.test_result.case_nums,\n            error_nums=self.test_result.get_status_nums(TestStatus.ERRORED),\n            failure_nums=self.test_result.get_status_nums(TestStatus.FAILED),\n            skipped_nums=self.test_result.get_status_nums(TestStatus.SKIPPED),\n            test_suites=[],\n        )\n        for test_suite in self.test_result.suites:\n            result[\"test_suites\"].append(self.test_suite_to_json(test_suite))\n        return result\n\n    def test_suite_to_json(self, test_suite):\n        result = dict(\n            env_name=test_suite.env_name,\n            test_name=test_suite.test_name,\n            test_dir=test_suite.test_dir,\n            status=test_suite.status.name,\n            duration=test_suite.duration,\n            timestamp=(\n                datetime.datetime.fromtimestamp(test_suite.timestamp).strftime(\n                    \"%Y-%m-%dT%H:%M:%S\"\n                )\n                if test_suite.timestamp\n                else None\n            ),\n            testcase_nums=len(test_suite.cases),\n            error_nums=test_suite.get_status_nums(TestStatus.ERRORED),\n            failure_nums=test_suite.get_status_nums(TestStatus.FAILED),\n            skipped_nums=test_suite.get_status_nums(TestStatus.SKIPPED),\n            test_cases=[],\n        )\n        for test_case in test_suite.cases:\n            result[\"test_cases\"].append(self.test_case_to_json(test_case))\n        return result\n\n    @staticmethod\n    def test_case_to_json(test_case):\n        result = dict(\n            name=test_case.name,\n            status=test_case.status.name,\n            message=test_case.message,\n            stdout=test_case.stdout,\n            duration=test_case.duration,\n            exception=None,\n            source=None,\n        )\n        if test_case.exception:\n            result[\"exception\"] = \"%s: %s\" % (\n                test_case.exception.__class__.__name__,\n                test_case.exception,\n            )\n        if test_case.source:\n            result[\"source\"] = dict(\n                file=test_case.source.filename, line=test_case.source.line\n            )\n        return result\n"
  },
  {
    "path": "platformio/test/reports/junit.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport datetime\nimport os\nimport xml.etree.ElementTree as ET\n\nimport click\n\nfrom platformio import __version__\nfrom platformio.test.reports.base import TestReportBase\nfrom platformio.test.result import TestStatus\n\n\nclass JunitTestReport(TestReportBase):\n    def generate(self, output_path, verbose=False):\n        if os.path.isdir(output_path):\n            output_path = os.path.join(\n                output_path,\n                \"pio-test-report-%s-%s-junit.xml\"\n                % (\n                    os.path.basename(self.test_result.project_dir),\n                    datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\"),\n                ),\n            )\n\n        with open(output_path, mode=\"wb\") as fp:\n            self.build_xml_tree().write(fp, encoding=\"utf8\")\n\n        if verbose:\n            click.secho(f\"Saved JUnit report to the {output_path}\", fg=\"green\")\n\n    def build_xml_tree(self):\n        root = ET.Element(\"testsuites\")\n        root.set(\"name\", self.test_result.project_dir)\n        root.set(\"platformio_version\", __version__)\n        root.set(\"tests\", str(self.test_result.case_nums))\n        root.set(\"errors\", str(self.test_result.get_status_nums(TestStatus.ERRORED)))\n        root.set(\"failures\", str(self.test_result.get_status_nums(TestStatus.FAILED)))\n        root.set(\"time\", str(self.test_result.duration))\n        for suite in self.test_result.suites:\n            root.append(self.build_testsuite_node(suite))\n        return ET.ElementTree(root)\n\n    def build_testsuite_node(self, test_suite):\n        element = ET.Element(\"testsuite\")\n        element.set(\"name\", f\"{test_suite.env_name}:{test_suite.test_name}\")\n        element.set(\"tests\", str(len(test_suite.cases)))\n        element.set(\"errors\", str(test_suite.get_status_nums(TestStatus.ERRORED)))\n        element.set(\"failures\", str(test_suite.get_status_nums(TestStatus.FAILED)))\n        element.set(\"skipped\", str(test_suite.get_status_nums(TestStatus.SKIPPED)))\n        element.set(\"time\", str(test_suite.duration))\n        if test_suite.timestamp:\n            element.set(\n                \"timestamp\",\n                datetime.datetime.fromtimestamp(test_suite.timestamp).strftime(\n                    \"%Y-%m-%dT%H:%M:%S\"\n                ),\n            )\n        for test_case in test_suite.cases:\n            element.append(self.build_testcase_node(test_case))\n        return element\n\n    def build_testcase_node(self, test_case):\n        element = ET.Element(\"testcase\")\n        element.set(\"name\", str(test_case.name))\n        element.set(\"time\", str(test_case.duration))\n        element.set(\"status\", str(test_case.status.name))\n        if test_case.source:\n            element.set(\"file\", test_case.source.filename)\n            element.set(\"line\", str(test_case.source.line))\n        if test_case.status == TestStatus.SKIPPED:\n            element.append(ET.Element(\"skipped\"))\n        elif test_case.status == TestStatus.ERRORED:\n            element.append(self.build_testcase_error_node(test_case))\n        elif test_case.status == TestStatus.FAILED:\n            element.append(self.build_testcase_failure_node(test_case))\n        return element\n\n    @staticmethod\n    def build_testcase_error_node(test_case):\n        element = ET.Element(\"error\")\n        element.set(\"type\", test_case.exception.__class__.__name__)\n        element.set(\"message\", str(test_case.exception))\n        if test_case.stdout:\n            element.text = test_case.stdout\n        return element\n\n    @staticmethod\n    def build_testcase_failure_node(test_case):\n        element = ET.Element(\"failure\")\n        if test_case.message:\n            element.set(\"message\", test_case.message)\n        if test_case.stdout:\n            element.text = test_case.stdout\n        return element\n"
  },
  {
    "path": "platformio/test/reports/stdout.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\nfrom tabulate import tabulate\n\nfrom platformio import util\nfrom platformio.test.reports.base import TestReportBase\nfrom platformio.test.result import TestStatus\n\n\nclass StdoutTestReport(TestReportBase):\n    def generate(self, verbose=False):  # pylint: disable=arguments-differ\n        click.echo()\n\n        tabular_data = []\n        failed_nums = self.test_result.get_status_nums(TestStatus.FAILED)\n        skipped_nums = self.test_result.get_status_nums(TestStatus.SKIPPED)\n        is_error = failed_nums > 0 or self.test_result.is_errored\n\n        for test_suite in self.test_result.suites:\n            if not verbose and test_suite.status == TestStatus.SKIPPED:\n                continue\n            status_str = test_suite.status.name\n            if test_suite.status in (TestStatus.FAILED, TestStatus.ERRORED):\n                status_str = click.style(status_str, fg=\"red\")\n            elif test_suite.status == TestStatus.PASSED:\n                status_str = click.style(status_str, fg=\"green\")\n\n            tabular_data.append(\n                (\n                    click.style(test_suite.env_name, fg=\"cyan\"),\n                    test_suite.test_name,\n                    status_str,\n                    util.humanize_duration_time(test_suite.duration or None),\n                )\n            )\n\n        if tabular_data:\n            util.print_labeled_bar(\n                \"SUMMARY\",\n                is_error=is_error,\n                fg=\"red\" if is_error else \"green\",\n            )\n            click.echo(\n                tabulate(\n                    tabular_data,\n                    headers=[\n                        click.style(s, bold=True)\n                        for s in (\"Environment\", \"Test\", \"Status\", \"Duration\")\n                    ],\n                ),\n                err=is_error,\n            )\n\n        if failed_nums:\n            self.print_failed_test_cases()\n\n        util.print_labeled_bar(\n            \"%d test cases: %s%s%d succeeded in %s\"\n            % (\n                self.test_result.case_nums,\n                (\"%d failed, \" % failed_nums) if failed_nums else \"\",\n                (\"%d skipped, \" % skipped_nums) if skipped_nums else \"\",\n                self.test_result.get_status_nums(TestStatus.PASSED),\n                util.humanize_duration_time(self.test_result.duration),\n            ),\n            is_error=is_error,\n            fg=\"red\" if is_error else \"green\",\n        )\n\n    def print_failed_test_cases(self):\n        click.echo()\n        for test_suite in self.test_result.suites:\n            if test_suite.status != TestStatus.FAILED:\n                continue\n            util.print_labeled_bar(\n                click.style(\n                    f\"{test_suite.env_name}:{test_suite.test_name}\", bold=True, fg=\"red\"\n                ),\n                is_error=True,\n                sep=\"_\",\n            )\n            for test_case in test_suite.cases:\n                if test_case.status != TestStatus.FAILED:\n                    continue\n                click.echo((test_case.stdout or \"\").strip())\n                click.echo()\n"
  },
  {
    "path": "platformio/test/result.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport enum\nimport functools\nimport operator\nimport time\n\nimport click\n\n\nclass TestStatus(enum.Enum):\n    PASSED = enum.auto()\n    FAILED = enum.auto()\n    SKIPPED = enum.auto()\n    WARNED = enum.auto()\n    ERRORED = enum.auto()\n\n    @classmethod\n    def from_string(cls, value: str):\n        value = value.lower()\n        if value.startswith((\"failed\", \"fail\")):\n            return cls.FAILED\n        if value.startswith((\"passed\", \"pass\", \"success\", \"ok\")):\n            return cls.PASSED\n        if value.startswith((\"skipped\", \"skip\", \"ignore\", \"ignored\")):\n            return cls.SKIPPED\n        if value.startswith(\"WARNING\"):\n            return cls.WARNED\n        raise ValueError(f\"Unknown test status `{value}`\")\n\n    def to_ansi_color(self):\n        if self == TestStatus.FAILED:\n            return \"red\"\n        if self == TestStatus.PASSED:\n            return \"green\"\n        return \"yellow\"\n\n\nclass TestCaseSource:\n    def __init__(self, filename, line=None):\n        self.filename = filename\n        self.line = line\n\n\nclass TestCase:\n    def __init__(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self,\n        name,\n        status,\n        message=None,\n        stdout=None,\n        source=None,\n        duration=0,\n        exception=None,\n    ):\n        assert isinstance(status, TestStatus)\n        if status == TestStatus.ERRORED:\n            assert isinstance(exception, Exception)\n        self.name = name.strip()\n        self.status = status\n        self.message = message\n        self.stdout = stdout\n        self.source = source\n        self.duration = duration\n        self.exception = exception\n\n    def humanize(self):\n        parts = []\n        if self.source:\n            parts.append(\"%s:%d: \" % (self.source.filename, self.source.line))\n        parts.append(self.name)\n        if self.message:\n            parts.append(\": \" + self.message)\n        parts.extend(\n            [\n                \"\\t\",\n                \"[%s]\" % click.style(self.status.name, fg=self.status.to_ansi_color()),\n            ]\n        )\n        return \"\".join(parts)\n\n\nclass TestSuite:\n    def __init__(self, env_name, test_name, finished=False, test_dir=None):\n        self.env_name = env_name\n        self.test_name = test_name\n        self.test_dir = test_dir\n        self.timestamp = 0\n        self.duration = 0\n        self._cases = []\n        self._finished = finished\n\n    @property\n    def cases(self):\n        return self._cases\n\n    @property\n    def status(self):\n        for s in (TestStatus.ERRORED, TestStatus.FAILED):\n            if self.get_status_nums(s):\n                return s\n        if self._cases and any(c.status == TestStatus.PASSED for c in self._cases):\n            return TestStatus.PASSED\n        return TestStatus.SKIPPED\n\n    def get_status_nums(self, status):\n        return len([True for c in self._cases if c.status == status])\n\n    def add_case(self, case: TestCase):\n        assert isinstance(case, TestCase)\n        self._cases.append(case)\n\n    def is_finished(self):\n        return self._finished\n\n    def on_start(self):\n        self.timestamp = time.time()\n\n    def on_finish(self):\n        if self.is_finished():\n            return\n        self._finished = True\n        self.duration = time.time() - self.timestamp\n\n\nclass TestResult:\n    def __init__(self, project_dir):\n        self.project_dir = project_dir\n        self._suites = []\n\n    @property\n    def suites(self):\n        return self._suites\n\n    def add_suite(self, suite):\n        assert isinstance(suite, TestSuite)\n        self._suites.append(suite)\n\n    @property\n    def duration(self):\n        return functools.reduce(operator.add, [s.duration for s in self._suites])\n\n    @property\n    def case_nums(self):\n        return functools.reduce(operator.add, [len(s.cases) for s in self._suites])\n\n    @property\n    def is_errored(self):\n        return any(s.status == TestStatus.ERRORED for s in self._suites)\n\n    def get_status_nums(self, status):\n        return functools.reduce(\n            operator.add, [s.get_status_nums(status) for s in self._suites]\n        )\n"
  },
  {
    "path": "platformio/test/runners/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/test/runners/base.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.exception import ReturnErrorCode\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.test.exception import UnitTestSuiteError\nfrom platformio.test.result import TestCase, TestStatus\nfrom platformio.test.runners.readers.native import NativeTestOutputReader\nfrom platformio.test.runners.readers.serial import SerialTestOutputReader\n\nCTX_META_TEST_IS_RUNNING = __name__ + \".test_running\"\nCTX_META_TEST_RUNNING_NAME = __name__ + \".test_running_name\"\n\n\nclass TestRunnerOptions:  # pylint: disable=too-many-instance-attributes\n    def __init__(  # pylint: disable=too-many-arguments,too-many-positional-arguments\n        self,\n        verbose=0,\n        without_building=False,\n        without_uploading=False,\n        without_testing=False,\n        without_debugging=True,\n        upload_port=None,\n        test_port=None,\n        no_reset=False,\n        monitor_rts=None,\n        monitor_dtr=None,\n        program_args=None,\n    ):\n        self.verbose = verbose\n        self.without_building = without_building\n        self.without_uploading = without_uploading\n        self.without_testing = without_testing\n        self.without_debugging = without_debugging\n        self.upload_port = upload_port\n        self.test_port = test_port\n        self.no_reset = no_reset\n        self.monitor_rts = monitor_rts\n        self.monitor_dtr = monitor_dtr\n        self.program_args = program_args\n\n\nclass TestRunnerBase:\n    NAME = None\n    EXTRA_LIB_DEPS = None\n    TESTCASE_PARSE_RE = None\n\n    def __init__(self, test_suite, project_config, options=None):\n        self.test_suite = test_suite\n        self.options = options\n        self.project_config = project_config\n        self.platform = PlatformFactory.from_env(\n            self.test_suite.env_name,\n            autoinstall=True,\n        )\n        self.cmd_ctx = None\n        self._testing_output_buffer = \"\"\n\n    @property\n    def name(self):\n        return self.__class__.__name__.replace(\"TestRunner\", \"\").lower()\n\n    def get_test_speed(self):\n        return int(\n            self.project_config.get(f\"env:{self.test_suite.env_name}\", \"test_speed\")\n        )\n\n    def get_test_port(self):\n        return self.options.test_port or self.project_config.get(\n            f\"env:{self.test_suite.env_name}\", \"test_port\"\n        )\n\n    def start(self, cmd_ctx):\n        # setup command context\n        self.cmd_ctx = cmd_ctx\n        self.cmd_ctx.meta[CTX_META_TEST_IS_RUNNING] = True\n        if self.test_suite.test_name != \"*\":\n            self.cmd_ctx.meta[CTX_META_TEST_RUNNING_NAME] = self.test_suite.test_name\n\n        self.test_suite.on_start()\n        try:\n            self.setup()\n            for stage in (\"building\", \"uploading\", \"testing\"):\n                getattr(self, f\"stage_{stage}\")()\n                if self.options.verbose:\n                    click.echo()\n        except Exception as exc:  # pylint: disable=broad-except\n            click.secho(str(exc), fg=\"red\", err=True)\n            self.test_suite.add_case(\n                TestCase(\n                    name=f\"{self.test_suite.env_name}:{self.test_suite.test_name}\",\n                    status=TestStatus.ERRORED,\n                    exception=exc,\n                )\n            )\n        finally:\n            self.test_suite.on_finish()\n            self.teardown()\n\n    def setup(self):\n        pass\n\n    def stage_building(self):\n        if self.options.without_building:\n            return None\n        # run \"building\" once at the \"uploading\" stage for the embedded target\n        if not self.options.without_uploading and self.platform.is_embedded():\n            return None\n        click.secho(\"Building...\", bold=True)\n        targets = [\"__test\"]\n        if not self.options.without_debugging:\n            targets.append(\"__debug\")\n        if self.platform.is_embedded():\n            targets.append(\"checkprogsize\")\n        try:\n            return self.run_project_targets(targets)\n        except ReturnErrorCode as exc:\n            raise UnitTestSuiteError(\n                \"Building stage has failed, see errors above. \"\n                \"Use `pio test -vvv` option to enable verbose output.\"\n            ) from exc\n\n    def stage_uploading(self):\n        is_embedded = self.platform.is_embedded()\n        if self.options.without_uploading or not is_embedded:\n            return None\n        click.secho(\n            \"Building & Uploading...\" if is_embedded else \"Uploading...\", bold=True\n        )\n        targets = [\"upload\"]\n        if self.options.without_building:\n            targets.append(\"nobuild\")\n        else:\n            targets.append(\"__test\")\n        if not self.options.without_debugging:\n            targets.append(\"__debug\")\n        try:\n            return self.run_project_targets(targets)\n        except ReturnErrorCode as exc:\n            raise UnitTestSuiteError(\n                \"Uploading stage has failed, see errors above. \"\n                \"Use `pio test -vvv` option to enable verbose output.\"\n            ) from exc\n\n    def stage_testing(self):\n        if self.options.without_testing:\n            return None\n        click.secho(\"Testing...\", bold=True)\n        test_port = self.get_test_port()\n        native_conds = [\n            not self.platform.is_embedded()\n            and (not test_port or \"://\" not in test_port),\n            self.project_config.get(\n                f\"env:{self.test_suite.env_name}\", \"test_testing_command\"\n            ),\n        ]\n        reader = (\n            NativeTestOutputReader(self)\n            if any(native_conds)\n            else SerialTestOutputReader(self)\n        )\n        return reader.begin()\n\n    def teardown(self):\n        pass\n\n    def run_project_targets(self, targets):\n        # pylint: disable=import-outside-toplevel\n        from platformio.run.cli import cli as run_cmd\n\n        assert self.cmd_ctx\n        return self.cmd_ctx.invoke(\n            run_cmd,\n            project_conf=self.project_config.path,\n            upload_port=self.options.upload_port,\n            verbose=self.options.verbose > 2,\n            silent=self.options.verbose < 2,\n            environment=[self.test_suite.env_name],\n            disable_auto_clean=\"nobuild\" in targets,\n            target=targets,\n        )\n\n    def configure_build_env(self, env):\n        \"\"\"\n        Configure SCons build environment\n        Called in \"builder/tools/piotest\" tool\n        \"\"\"\n        return env\n\n    def on_testing_data_output(self, data):\n        if isinstance(data, bytes):\n            data = data.decode(\"utf8\", \"ignore\")\n        self._testing_output_buffer += data\n        self._testing_output_buffer = self._testing_output_buffer.replace(\"\\r\", \"\")\n        while \"\\n\" in self._testing_output_buffer:\n            nl_pos = self._testing_output_buffer.index(\"\\n\")\n            line = self._testing_output_buffer[: nl_pos + 1]\n            self._testing_output_buffer = self._testing_output_buffer[nl_pos + 1 :]\n            self.on_testing_line_output(line)\n\n    def on_testing_line_output(self, line):\n        click.echo(line, nl=False)\n"
  },
  {
    "path": "platformio/test/runners/doctest.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport click\n\nfrom platformio.test.result import TestCase, TestCaseSource, TestStatus\nfrom platformio.test.runners.base import TestRunnerBase\n\n\nclass DoctestTestCaseParser:\n    def __init__(self):\n        self._tmp_tc = None\n        self._name_tokens = []\n\n    def parse(self, line):\n        if self.is_divider(line):\n            return self._on_divider()\n        if not self._tmp_tc or line.strip().startswith(\"[doctest]\"):\n            return None\n\n        self._tmp_tc.stdout += line\n        line = line.strip()\n\n        # source\n        if not self._tmp_tc.source and line:\n            self._tmp_tc.source = self.parse_source(line)\n            return None\n\n        # name\n        if not self._tmp_tc.name:\n            if line:\n                self._name_tokens.append(line)\n                return None\n            self._tmp_tc.name = self.parse_name(self._name_tokens)\n            return None\n\n        if self._tmp_tc.status != TestStatus.FAILED:\n            self._parse_assert(line)\n\n        return None\n\n    @staticmethod\n    def is_divider(line):\n        line = line.strip()\n        return line.startswith(\"===\") and line.endswith(\"===\")\n\n    def _on_divider(self):\n        test_case = None\n        if self._tmp_tc:\n            test_case = TestCase(\n                name=self._tmp_tc.name.strip(),\n                status=self._tmp_tc.status,\n                message=(self._tmp_tc.message or \"\").strip() or None,\n                source=self._tmp_tc.source,\n                stdout=self._tmp_tc.stdout.strip(),\n            )\n\n        self._tmp_tc = TestCase(\"\", TestStatus.PASSED, stdout=\"\")\n        self._name_tokens = []\n        return test_case\n\n    @staticmethod\n    def parse_source(line):\n        if not line.endswith(\":\"):\n            return None\n        filename, line = line[:-1].rsplit(\":\", 1)\n        return TestCaseSource(filename, int(line))\n\n    @staticmethod\n    def parse_name(tokens):\n        cleaned_tokens = []\n        for token in tokens:\n            if token.startswith(\"TEST \") and \":\" in token:\n                token = token[token.index(\":\") + 1 :]\n            cleaned_tokens.append(token.strip())\n        return \"/\".join(cleaned_tokens)\n\n    def _parse_assert(self, line):\n        status_tokens = [\n            (TestStatus.FAILED, \"ERROR\"),\n            (TestStatus.FAILED, \"FATAL ERROR\"),\n            (TestStatus.WARNED, \"WARNING\"),\n        ]\n        for status, token in status_tokens:\n            index = line.find(\": %s:\" % token)\n            if index == -1:\n                continue\n            self._tmp_tc.status = status\n            self._tmp_tc.message = line[index + len(token) + 3 :].strip() or None\n\n\nclass DoctestTestRunner(TestRunnerBase):\n    EXTRA_LIB_DEPS = [\"doctest/doctest@^2.4.12\"]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._tc_parser = DoctestTestCaseParser()\n\n    def on_testing_line_output(self, line):\n        if self.options.verbose:\n            click.echo(line, nl=False)\n\n        test_case = self._tc_parser.parse(line)\n        if test_case:\n            self.test_suite.add_case(test_case)\n            if not self.options.verbose:\n                click.echo(test_case.humanize())\n\n        if \"[doctest] Status:\" in line:\n            self.test_suite.on_finish()\n"
  },
  {
    "path": "platformio/test/runners/factory.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport importlib\nimport os\nimport re\n\nfrom platformio.compat import load_python_module\nfrom platformio.exception import UserSideException\nfrom platformio.project.config import ProjectConfig\nfrom platformio.test.result import TestSuite\nfrom platformio.test.runners.base import TestRunnerBase, TestRunnerOptions\n\n\nclass TestRunnerFactory:\n    @staticmethod\n    def get_clsname(name):\n        name = re.sub(r\"[^\\da-z\\_\\-]+\", \"\", name, flags=re.I)\n        return \"%sTestRunner\" % name.lower().capitalize()\n\n    @classmethod\n    def new(cls, test_suite, project_config, options=None) -> TestRunnerBase:\n        assert isinstance(test_suite, TestSuite)\n        assert isinstance(project_config, ProjectConfig)\n        if options:\n            assert isinstance(options, TestRunnerOptions)\n        test_framework = project_config.get(\n            f\"env:{test_suite.env_name}\", \"test_framework\"\n        )\n        module_name = f\"platformio.test.runners.{test_framework}\"\n        runner_cls = None\n        if test_framework == \"custom\":\n            test_dir = project_config.get(\"platformio\", \"test_dir\")\n            custom_runner_path = os.path.join(test_dir, \"test_custom_runner.py\")\n            test_name = test_suite.test_name if test_suite.test_name != \"*\" else None\n            while test_name:\n                if os.path.isfile(\n                    os.path.join(test_dir, test_name, \"test_custom_runner.py\")\n                ):\n                    custom_runner_path = os.path.join(\n                        test_dir, test_name, \"test_custom_runner.py\"\n                    )\n                    break\n                test_name = os.path.dirname(test_name)  # parent dir\n\n            try:\n                mod = load_python_module(module_name, custom_runner_path)\n            except (FileNotFoundError, ImportError) as exc:\n                raise UserSideException(\n                    \"Could not find custom test runner \"\n                    f\"by this path -> {custom_runner_path}\"\n                ) from exc\n        else:\n            mod = importlib.import_module(module_name)\n        runner_cls = getattr(mod, cls.get_clsname(test_framework))\n        return runner_cls(test_suite, project_config, options)\n"
  },
  {
    "path": "platformio/test/runners/googletest.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\n\nimport click\n\nfrom platformio.test.result import TestCase, TestCaseSource, TestStatus\nfrom platformio.test.runners.base import TestRunnerBase\n\n\nclass GoogletestTestCaseParser:\n    # Examples:\n    # [ RUN      ] FooTest.Bar\n    # ...\n    # [  FAILED  ] FooTest.Bar (0 ms)\n    STATUS__NAME_RE = r\"^\\[\\s+(?P<status>[A-Z]+)\\s+\\]\\s+(?P<name>[^\\(\\s]+)\"\n\n    # Examples:\n    # [ RUN      ] FooTest.Bar\n    # test/test_gtest/test_main.cpp:26: Failure\n    # Y:\\core\\examples\\unit-testing\\googletest\\test\\test_gtest\\test_main.cpp:26: Failure\n    SOURCE_MESSAGE_RE = r\"^(?P<source_file>.+):(?P<source_line>\\d+):(?P<message>.*)$\"\n\n    def __init__(self):\n        self._tmp_tc = None\n\n    def parse(self, line):\n        if self._tmp_tc:\n            self._tmp_tc.stdout += line\n        return self._parse_test_case(line)\n\n    def _parse_test_case(self, line):\n        status, name = self._parse_status_and_name(line)\n        if status == \"RUN\":\n            self._tmp_tc = TestCase(name, TestStatus.PASSED, stdout=line)\n            return None\n        if not status or not self._tmp_tc:\n            return None\n        source, message = self._parse_source_and_message(self._tmp_tc.stdout)\n        test_case = TestCase(\n            name=self._tmp_tc.name,\n            status=TestStatus.from_string(status),\n            message=message,\n            source=source,\n            stdout=self._tmp_tc.stdout.strip(),\n        )\n        self._tmp_tc = None\n        return test_case\n\n    def _parse_status_and_name(self, line):\n        result = (None, None)\n        line = line.strip()\n        if not line.startswith(\"[\"):\n            return result\n        match = re.search(self.STATUS__NAME_RE, line)\n        if not match:\n            return result\n        return match.group(\"status\"), match.group(\"name\")\n\n    def _parse_source_and_message(self, stdout):\n        for line in stdout.split(\"\\n\"):\n            line = line.strip()\n            if not line:\n                continue\n            match = re.search(self.SOURCE_MESSAGE_RE, line)\n            if not match:\n                continue\n            return (\n                TestCaseSource(\n                    match.group(\"source_file\"), int(match.group(\"source_line\"))\n                ),\n                (match.group(\"message\") or \"\").strip() or None,\n            )\n        return (None, None)\n\n\nclass GoogletestTestRunner(TestRunnerBase):\n    EXTRA_LIB_DEPS = [\"google/googletest@^1.17.0\"]\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._tc_parser = GoogletestTestCaseParser()\n        os.environ[\"GTEST_COLOR\"] = \"no\"  # disable ANSI symbols\n\n    def on_testing_line_output(self, line):\n        if self.options.verbose:\n            click.echo(line, nl=False)\n\n        test_case = self._tc_parser.parse(line)\n        if test_case:\n            self.test_suite.add_case(test_case)\n            if not self.options.verbose:\n                click.echo(test_case.humanize())\n\n        if \"Global test environment tear-down\" in line:\n            self.test_suite.on_finish()\n"
  },
  {
    "path": "platformio/test/runners/readers/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "platformio/test/runners/readers/native.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport asyncio\nimport os\nimport signal\nimport subprocess\nimport time\n\nfrom platformio.compat import (\n    IS_WINDOWS,\n    aio_get_running_loop,\n    get_filesystem_encoding,\n    get_locale_encoding,\n)\nfrom platformio.project.helpers import load_build_metadata\nfrom platformio.test.exception import UnitTestError\n\nEXITING_TIMEOUT = 5  # seconds\n\n\nclass ProgramProcessProtocol(asyncio.SubprocessProtocol):\n    def __init__(self, test_runner, exit_future):\n        self.test_runner = test_runner\n        self.exit_future = exit_future\n        self._exit_timer = None\n\n    def pipe_data_received(self, _, data):\n        try:\n            data = data.decode(get_locale_encoding() or get_filesystem_encoding())\n        except UnicodeDecodeError:\n            data = data.decode(\"latin-1\")\n        self.test_runner.on_testing_data_output(data)\n        if self.test_runner.test_suite.is_finished():\n            self._exit_timer = aio_get_running_loop().call_later(\n                EXITING_TIMEOUT, self._stop_testing\n            )\n\n    def process_exited(self):\n        self._stop_testing()\n\n    def _stop_testing(self):\n        if not self.exit_future.done():\n            self.exit_future.set_result(True)\n        if self._exit_timer:\n            self._exit_timer.cancel()\n\n\nclass NativeTestOutputReader:\n    def __init__(self, test_runner):\n        self.test_runner = test_runner\n        self.aio_loop = (\n            asyncio.ProactorEventLoop() if IS_WINDOWS else asyncio.new_event_loop()\n        )\n        asyncio.set_event_loop(self.aio_loop)\n\n    def get_testing_command(self):\n        custom_testing_command = self.test_runner.project_config.get(\n            f\"env:{self.test_runner.test_suite.env_name}\", \"test_testing_command\"\n        )\n        if custom_testing_command:\n            return custom_testing_command\n        build_dir = self.test_runner.project_config.get(\"platformio\", \"build_dir\")\n        cmd = [\n            os.path.join(\n                build_dir,\n                self.test_runner.test_suite.env_name,\n                \"program.exe\" if IS_WINDOWS else \"program\",\n            )\n        ]\n        # if user changed PROGNAME\n        if not os.path.exists(cmd[0]):\n            build_data = load_build_metadata(\n                os.getcwd(), self.test_runner.test_suite.env_name, cache=True\n            )\n            if build_data:\n                cmd[0] = build_data[\"prog_path\"]\n        if self.test_runner.options.program_args:\n            cmd.extend(self.test_runner.options.program_args)\n        return cmd\n\n    async def gather_results(self):\n        exit_future = asyncio.Future(loop=self.aio_loop)\n        transport, _ = await self.aio_loop.subprocess_exec(\n            lambda: ProgramProcessProtocol(self.test_runner, exit_future),\n            *self.get_testing_command(),\n            stdin=None,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n        )\n        await exit_future\n        last_return_code = transport.get_returncode()\n        transport.close()\n\n        # wait until subprocess will be killed\n        start = time.time()\n        while (\n            start > (time.time() - EXITING_TIMEOUT)\n            and transport.get_returncode() is None\n        ):\n            await asyncio.sleep(0.5)\n\n        if last_return_code:\n            self.raise_for_status(last_return_code)\n\n    @staticmethod\n    def raise_for_status(return_code):\n        try:\n            sig = signal.Signals(abs(return_code))\n            try:\n                signal_description = signal.strsignal(sig)\n            except AttributeError:\n                signal_description = \"\"\n            raise UnitTestError(\n                f\"Program received signal {sig.name} ({signal_description})\"\n            )\n        except ValueError as exc:\n            raise UnitTestError(\"Program errored with %d code\" % return_code) from exc\n\n    def begin(self):\n        try:\n            self.aio_loop.run_until_complete(self.gather_results())\n        finally:\n            self.aio_loop.run_until_complete(self.aio_loop.shutdown_asyncgens())\n            self.aio_loop.close()\n"
  },
  {
    "path": "platformio/test/runners/readers/serial.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom time import sleep\n\nimport click\nimport serial\n\nfrom platformio.device.finder import SerialPortFinder\nfrom platformio.exception import UserSideException\n\n\nclass SerialTestOutputReader:\n    SERIAL_TIMEOUT = 600\n\n    def __init__(self, test_runner):\n        self.test_runner = test_runner\n\n    def begin(self):\n        click.echo(\n            \"If you don't see any output for the first 10 secs, \"\n            \"please reset board (press reset button)\"\n        )\n        click.echo()\n\n        try:\n            ser = serial.serial_for_url(\n                self.resolve_test_port(),\n                do_not_open=True,\n                baudrate=self.test_runner.get_test_speed(),\n                timeout=self.SERIAL_TIMEOUT,\n            )\n            ser.rts = self.test_runner.options.monitor_rts\n            ser.dtr = self.test_runner.options.monitor_dtr\n            ser.open()\n        except serial.SerialException as exc:\n            click.secho(str(exc), fg=\"red\", err=True)\n            return\n\n        if not self.test_runner.options.no_reset:\n            ser.flushInput()\n            ser.setDTR(False)\n            ser.setRTS(False)\n            sleep(0.1)\n            ser.setDTR(True)\n            ser.setRTS(True)\n            sleep(0.1)\n\n        while not self.test_runner.test_suite.is_finished():\n            self.test_runner.on_testing_data_output(ser.read(ser.in_waiting or 1))\n        ser.close()\n\n    def resolve_test_port(self):\n        project_options = self.test_runner.project_config.items(\n            env=self.test_runner.test_suite.env_name, as_dict=True\n        )\n        port = SerialPortFinder(\n            board_config=self.test_runner.platform.board_config(\n                project_options[\"board\"]\n            ),\n            upload_protocol=project_options.get(\"upload_protocol\"),\n            ensure_ready=True,\n            verbose=self.test_runner.options.verbose,\n        ).find(initial_port=self.test_runner.get_test_port())\n        if port:\n            return port\n        raise UserSideException(\n            \"Please specify `test_port` for environment or use \"\n            \"global `--test-port` option.\"\n        )\n"
  },
  {
    "path": "platformio/test/runners/unity.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport string\nfrom pathlib import Path\n\nimport click\n\nfrom platformio.test.exception import UnitTestSuiteError\nfrom platformio.test.result import TestCase, TestCaseSource, TestStatus\nfrom platformio.test.runners.base import TestRunnerBase\nfrom platformio.util import strip_ansi_codes\n\n\nclass UnityTestRunner(TestRunnerBase):\n    EXTRA_LIB_DEPS = [\"throwtheswitch/Unity@^2.6.1\"]\n\n    # Examples:\n    # test/test_foo.cpp:44:test_function_foo:FAIL: Expected 32 Was 33\n    # test/group/test_foo/test_main.cpp:5:test::dummy:FAIL: Expression Evaluated To FALSE\n    TESTCASE_PARSE_RE = re.compile(\n        r\"(?P<source_file>[^:]+):(?P<source_line>\\d+):(?P<name>[^\\s]+):\"\n        r\"(?P<status>PASS|IGNORE|FAIL)(:\\s*(?P<message>.+)$)?\"\n    )\n\n    UNITY_CONFIG_H = \"\"\"\n#ifndef UNITY_CONFIG_H\n#define UNITY_CONFIG_H\n\n#ifndef NULL\n#ifndef __cplusplus\n#define NULL (void*)0\n#else\n#define NULL 0\n#endif\n#endif\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\nvoid unityOutputStart(unsigned long);\nvoid unityOutputChar(unsigned int);\nvoid unityOutputFlush(void);\nvoid unityOutputComplete(void);\n\n#define UNITY_OUTPUT_START()    unityOutputStart((unsigned long) $baudrate)\n#define UNITY_OUTPUT_CHAR(c)    unityOutputChar(c)\n#define UNITY_OUTPUT_FLUSH()    unityOutputFlush()\n#define UNITY_OUTPUT_COMPLETE() unityOutputComplete()\n\n#ifdef __cplusplus\n}\n#endif /* extern \"C\" */\n\n#endif /* UNITY_CONFIG_H */\n\n\"\"\"\n\n    UNITY_CONFIG_C = \"\"\"\n#include <unity_config.h>\n\n#if !defined(UNITY_WEAK_ATTRIBUTE) && !defined(UNITY_WEAK_PRAGMA)\n#   if defined(__GNUC__) || defined(__ghs__) /* __GNUC__ includes clang */\n#       if !(defined(__WIN32__) && defined(__clang__)) && !defined(__TMS470__)\n#           define UNITY_WEAK_ATTRIBUTE __attribute__((weak))\n#       endif\n#   endif\n#endif\n\n#ifdef __cplusplus\nextern \"C\"\n{\n#endif\n\n#ifdef UNITY_WEAK_ATTRIBUTE\n    UNITY_WEAK_ATTRIBUTE void setUp(void) { }\n    UNITY_WEAK_ATTRIBUTE void tearDown(void) { }\n    UNITY_WEAK_ATTRIBUTE void suiteSetUp(void) { }\n    UNITY_WEAK_ATTRIBUTE int suiteTearDown(int num_failures) { return num_failures; }\n#elif defined(UNITY_WEAK_PRAGMA)\n    #pragma weak setUp\n    void setUp(void) { }\n    #pragma weak tearDown\n    void tearDown(void) { }\n    #pragma weak suiteSetUp\n    void suiteSetUp(void) { }\n    #pragma weak suiteTearDown\n    int suiteTearDown(int num_failures) { return num_failures; }\n#endif\n\n#ifdef __cplusplus\n}\n#endif /* extern \"C\" */\n\n$framework_config_code\n    \"\"\"\n\n    UNITY_FRAMEWORK_CONFIG = dict(\n        native=dict(\n            code=\"\"\"\n#include <stdio.h>\nvoid unityOutputStart(unsigned long baudrate) { (void) baudrate; }\nvoid unityOutputChar(unsigned int c) { putchar(c); }\nvoid unityOutputFlush(void) { fflush(stdout); }\nvoid unityOutputComplete(void) { }\n        \"\"\",\n            language=\"c\",\n        ),\n        arduino=dict(\n            code=\"\"\"\n#include <Arduino.h>\nvoid unityOutputStart(unsigned long baudrate) { Serial.begin(baudrate); }\nvoid unityOutputChar(unsigned int c) { Serial.write(c); }\nvoid unityOutputFlush(void) { Serial.flush(); }\nvoid unityOutputComplete(void) { Serial.end(); }\n        \"\"\",\n            language=\"cpp\",\n        ),\n        mbed=dict(\n            code=\"\"\"\n#include <mbed.h>\n#if MBED_MAJOR_VERSION == 6\nUnbufferedSerial pc(USBTX, USBRX);\n#else\nRawSerial pc(USBTX, USBRX);\n#endif\nvoid unityOutputStart(unsigned long baudrate) { pc.baud(baudrate); }\nvoid unityOutputChar(unsigned int c) {\n#if MBED_MAJOR_VERSION == 6\n    pc.write(&c, 1);\n#else\n    pc.putc(c);\n#endif\n}\nvoid unityOutputFlush(void) { }\nvoid unityOutputComplete(void) { }\n        \"\"\",\n            language=\"cpp\",\n        ),\n        espidf=dict(\n            code=\"\"\"\n#include <stdio.h>\nvoid unityOutputStart(unsigned long baudrate) { (void) baudrate; }\nvoid unityOutputChar(unsigned int c) { putchar(c); }\nvoid unityOutputFlush(void) { fflush(stdout); }\nvoid unityOutputComplete(void) { }\n        \"\"\",\n            language=\"c\",\n        ),\n        zephyr=dict(\n            code=\"\"\"\n#include <sys/printk.h>\nvoid unityOutputStart(unsigned long baudrate) { (void) baudrate; }\nvoid unityOutputChar(unsigned int c) { printk(\"%c\", c); }\nvoid unityOutputFlush(void) { }\nvoid unityOutputComplete(void) { }\n        \"\"\",\n            language=\"c\",\n        ),\n        legacy_custom_transport=dict(\n            code=\"\"\"\n#include <unittest_transport.h>\nvoid unityOutputStart(unsigned long baudrate) { unittest_uart_begin(); }\nvoid unityOutputChar(unsigned int c) { unittest_uart_putchar(c); }\nvoid unityOutputFlush(void) { unittest_uart_flush(); }\nvoid unityOutputComplete(void) { unittest_uart_end(); }\n        \"\"\",\n            language=\"cpp\",\n        ),\n    )\n\n    def get_unity_framework_config(self):\n        if not self.platform.is_embedded():\n            return self.UNITY_FRAMEWORK_CONFIG[\"native\"]\n        if (\n            self.project_config.get(\n                f\"env:{self.test_suite.env_name}\", \"test_transport\", None\n            )\n            == \"custom\"\n        ):\n            framework = \"legacy_custom_transport\"\n        else:\n            framework = (\n                self.project_config.get(f\"env:{self.test_suite.env_name}\", \"framework\")\n                or [None]\n            )[0]\n        if framework and framework in self.UNITY_FRAMEWORK_CONFIG:\n            return self.UNITY_FRAMEWORK_CONFIG[framework]\n        raise UnitTestSuiteError(\n            f\"Could not find Unity configuration for the `{framework}` framework.\\n\"\n            \"Learn how to create a custom Unity configuration at\"\n            \"https://docs.platformio.org/en/latest/advanced/\"\n            \"unit-testing/frameworks/unity.html\"\n        )\n\n    def configure_build_env(self, env):\n        env.Append(CPPDEFINES=[\"UNITY_INCLUDE_CONFIG_H\"])\n        if self.custom_unity_config_exists():\n            return env\n        env.Replace(\n            UNITY_CONFIG_DIR=os.path.join(\"$BUILD_DIR\", \"unity_config\"),\n            BUILD_UNITY_CONFIG_DIR=os.path.join(\"$BUILD_DIR\", \"unity_config_build\"),\n        )\n        env.Prepend(CPPPATH=[\"$UNITY_CONFIG_DIR\"])\n        self.generate_unity_extras(env.subst(\"$UNITY_CONFIG_DIR\"))\n        env.BuildSources(\"$BUILD_UNITY_CONFIG_DIR\", \"$UNITY_CONFIG_DIR\")\n        return env\n\n    def custom_unity_config_exists(self):\n        test_dir = self.project_config.get(\"platformio\", \"test_dir\")\n        config_fname = \"unity_config.h\"\n        if os.path.isfile(os.path.join(test_dir, config_fname)):\n            return True\n        test_name = (\n            self.test_suite.test_name if self.test_suite.test_name != \"*\" else None\n        )\n        while test_name:\n            if os.path.isfile(os.path.join(test_dir, test_name, config_fname)):\n                return True\n            test_name = os.path.dirname(test_name)  # parent dir\n        return False\n\n    def generate_unity_extras(self, dst_dir):\n        dst_dir = Path(dst_dir)\n        if not dst_dir.is_dir():\n            dst_dir.mkdir(parents=True)\n        unity_h = dst_dir / \"unity_config.h\"\n        if not unity_h.is_file():\n            unity_h.write_text(\n                string.Template(self.UNITY_CONFIG_H)\n                .substitute(baudrate=self.get_test_speed())\n                .strip()\n                + \"\\n\",\n                encoding=\"utf8\",\n            )\n        framework_config = self.get_unity_framework_config()\n        unity_c = dst_dir / (\"unity_config.%s\" % framework_config.get(\"language\", \"c\"))\n        if not unity_c.is_file():\n            unity_c.write_text(\n                string.Template(self.UNITY_CONFIG_C)\n                .substitute(framework_config_code=framework_config[\"code\"])\n                .strip()\n                + \"\\n\",\n                encoding=\"utf8\",\n            )\n\n    def on_testing_line_output(self, line):\n        if self.options.verbose:\n            click.echo(line, nl=False)\n        line = strip_ansi_codes(line or \"\").strip()\n        if not line:\n            return\n\n        test_case = self.parse_test_case(line)\n        if test_case:\n            self.test_suite.add_case(test_case)\n            if not self.options.verbose:\n                click.echo(test_case.humanize())\n\n        if all(s in line for s in (\"Tests\", \"Failures\", \"Ignored\")):\n            self.test_suite.on_finish()\n\n    def parse_test_case(self, line):\n        if not self.TESTCASE_PARSE_RE:\n            raise NotImplementedError()\n        line = line.strip()\n        if not line:\n            return None\n        match = self.TESTCASE_PARSE_RE.search(line)\n        if not match:\n            return None\n        data = match.groupdict()\n        source = None\n        if \"source_file\" in data:\n            source = TestCaseSource(\n                filename=data[\"source_file\"], line=int(data.get(\"source_line\"))\n            )\n        return TestCase(\n            name=data.get(\"name\").strip(),\n            status=TestStatus.from_string(data.get(\"status\")),\n            message=(data.get(\"message\") or \"\").strip() or None,\n            stdout=line,\n            source=source,\n        )\n"
  },
  {
    "path": "platformio/util.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport datetime\nimport functools\nimport math\nimport os\nimport platform\nimport re\nimport shutil\nimport time\n\nimport click\n\nfrom platformio import __version__\n\n# pylint: disable=unused-import\nfrom platformio.device.list.util import list_serial_ports as get_serial_ports\nfrom platformio.fs import cd, load_json\nfrom platformio.proc import exec_command\n\n# pylint: enable=unused-import\n\n# also export list_serial_ports as get_serialports to be\n# backward compatibility with arduinosam versions 3.9.0 to 3.5.0 (and possibly others)\nget_serialports = get_serial_ports\n\n\nclass memoized:\n    def __init__(self, expire=0):\n        expire = str(expire)\n        if expire.isdigit():\n            expire = \"%ss\" % int((int(expire) / 1000))\n        tdmap = {\"s\": 1, \"m\": 60, \"h\": 3600, \"d\": 86400}\n        assert expire.endswith(tuple(tdmap))\n        self.expire = int(tdmap[expire[-1]] * int(expire[:-1]))\n        self.cache = {}\n\n    def __call__(self, func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            key = str(args) + str(kwargs)\n            if key not in self.cache or (\n                self.expire > 0 and self.cache[key][0] < time.time() - self.expire\n            ):\n                self.cache[key] = (time.time(), func(*args, **kwargs))\n            return self.cache[key][1]\n\n        wrapper.reset = self._reset\n        return wrapper\n\n    def _reset(self):\n        self.cache.clear()\n\n\nclass throttle:\n    def __init__(self, threshold):\n        self.threshold = threshold  # milliseconds\n        self.last = 0\n\n    def __call__(self, func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            diff = int(round((time.time() - self.last) * 1000))\n            if diff < self.threshold:\n                time.sleep((self.threshold - diff) * 0.001)\n            self.last = time.time()\n            return func(*args, **kwargs)\n\n        return wrapper\n\n\n# Retry: Begin\n\n\nclass RetryException(Exception):\n    pass\n\n\nclass RetryNextException(RetryException):\n    pass\n\n\nclass RetryStopException(RetryException):\n    pass\n\n\nclass retry:\n    RetryNextException = RetryNextException\n    RetryStopException = RetryStopException\n\n    def __init__(self, timeout=0, step=0.25):\n        self.timeout = timeout\n        self.step = step\n\n    def __call__(self, func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            elapsed = 0\n            while True:\n                try:\n                    return func(*args, **kwargs)\n                except self.RetryNextException:\n                    pass\n                if elapsed >= self.timeout:\n                    raise self.RetryStopException()\n                elapsed += self.step\n                time.sleep(self.step)\n\n        return wrapper\n\n\n# Retry: End\n\n\ndef singleton(cls):\n    \"\"\"From PEP-318 http://www.python.org/dev/peps/pep-0318/#examples\"\"\"\n    _instances = {}\n\n    def get_instance(*args, **kwargs):\n        if cls not in _instances:\n            _instances[cls] = cls(*args, **kwargs)\n        return _instances[cls]\n\n    return get_instance\n\n\ndef get_systype():\n    # allow manual override, eg. for\n    # windows on arm64 systems with emulated x86\n    if \"PLATFORMIO_SYSTEM_TYPE\" in os.environ:\n        return os.environ.get(\"PLATFORMIO_SYSTEM_TYPE\")\n\n    system = platform.system().lower()\n    arch = platform.machine().lower()\n    if system == \"windows\":\n        if not arch:  # issue #4353\n            arch = \"x86_\" + platform.architecture()[0]\n        if \"x86\" in arch:\n            arch = \"amd64\" if \"64\" in arch else \"x86\"\n    if arch == \"aarch64\" and platform.architecture()[0] == \"32bit\":\n        arch = \"armv7l\"\n    return \"%s_%s\" % (system, arch) if arch else system\n\n\ndef pioversion_to_intstr():\n    \"\"\"Legacy for  framework-zephyr/scripts/platformio/platformio-build-pre.py\"\"\"\n    vermatch = re.match(r\"^([\\d\\.]+)\", __version__)\n    assert vermatch\n    return [int(i) for i in vermatch.group(1).split(\".\")[:3]]\n\n\ndef items_to_list(items):\n    if isinstance(items, list):\n        return items\n    return [i.strip() for i in items.split(\",\") if i.strip()]\n\n\ndef items_in_list(needle, haystack):\n    needle = items_to_list(needle)\n    haystack = items_to_list(haystack)\n    if \"*\" in needle or \"*\" in haystack:\n        return True\n    return set(needle) & set(haystack)\n\n\ndef parse_datetime(datestr):\n    assert \"T\" in datestr and \"Z\" in datestr\n    return datetime.datetime.strptime(datestr, \"%Y-%m-%dT%H:%M:%SZ\")\n\n\ndef merge_dicts(d1, d2, path=None):\n    if path is None:\n        path = []\n    for key in d2:\n        if key in d1 and isinstance(d1[key], dict) and isinstance(d2[key], dict):\n            merge_dicts(d1[key], d2[key], path + [str(key)])\n        else:\n            d1[key] = d2[key]\n    return d1\n\n\ndef print_labeled_bar(label, is_error=False, fg=None, sep=\"=\"):\n    terminal_width = shutil.get_terminal_size().columns\n    width = len(click.unstyle(label))\n    half_line = sep * int((terminal_width - width - 2) / 2)\n    click.secho(\"%s %s %s\" % (half_line, label, half_line), fg=fg, err=is_error)\n\n\ndef humanize_duration_time(duration):\n    if duration is None:\n        return duration\n    duration = duration * 1000\n    tokens = []\n    for multiplier in (3600000, 60000, 1000, 1):\n        fraction = math.floor(duration / multiplier)\n        tokens.append(int(round(duration) if multiplier == 1 else fraction))\n        duration -= fraction * multiplier\n    return \"{:02d}:{:02d}:{:02d}.{:03d}\".format(*tokens)\n\n\ndef strip_ansi_codes(text):\n    # pylint: disable=protected-access\n    return click._compat.strip_ansi(text)\n"
  },
  {
    "path": "scripts/docspregen.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nimport os\nimport sys\nimport tempfile\nfrom urllib.parse import ParseResult, urlparse, urlunparse\n\nsys.path.append(\"..\")\n\nimport click  # noqa: E402\n\nfrom platformio import fs  # noqa: E402\nfrom platformio.package.manager.platform import PlatformPackageManager  # noqa: E402\nfrom platformio.platform.factory import PlatformFactory  # noqa: E402\n\n\nRST_COPYRIGHT = \"\"\"..  Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n       http://www.apache.org/licenses/LICENSE-2.0\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n\"\"\"\n\nSKIP_DEBUG_TOOLS = [\"esp-bridge\", \"esp-builtin\", \"dfu\"]\n\nSTATIC_FRAMEWORK_DATA = {\n    \"arduino\": {\n        \"title\": \"Arduino\",\n        \"description\": (\n            \"Arduino Wiring-based Framework allows writing cross-platform software \"\n            \"to control devices attached to a wide range of Arduino boards to \"\n            \"create all kinds of creative coding, interactive objects, spaces \"\n            \"or physical experiences.\"\n        ),\n    },\n    \"cmsis\": {\n        \"title\": \"CMSIS\",\n        \"description\": (\n            \"Vendor-independent hardware abstraction layer for the Cortex-M processor series\"\n        ),\n    },\n    \"freertos\": {\n        \"title\": \"FreeRTOS\",\n        \"description\": (\n            \"FreeRTOS is a real-time operating system kernel for embedded devices \"\n            \"that has been ported to 40 microcontroller platforms.\"\n        ),\n    },\n}\n\nDOCS_ROOT_DIR = os.path.realpath(\n    os.path.join(os.path.dirname(os.path.realpath(__file__)), \"..\", \"docs\")\n)\nREGCLIENT = PlatformPackageManager().get_registry_client_instance()\n\n\ndef reg_package_url(type_, owner, name):\n    if type_ == \"library\":\n        type_ = \"libraries\"\n    else:\n        type_ += \"s\"\n    return f\"https://registry.platformio.org/{type_}/{owner}/{name}\"\n\n\ndef campaign_url(url, source=\"platformio.org\", medium=\"docs\"):\n    data = urlparse(url)\n    query = data.query\n    if query:\n        query += \"&\"\n    query += \"utm_source=%s&utm_medium=%s\" % (source, medium)\n    return urlunparse(\n        ParseResult(\n            data.scheme, data.netloc, data.path, data.params, query, data.fragment\n        )\n    )\n\n\ndef install_platforms():\n    print(\"Installing platforms...\")\n    page = 1\n    pm = PlatformPackageManager()\n    while True:\n        result = REGCLIENT.list_packages(qualifiers=dict(types=[\"platform\"]), page=page)\n        for item in result[\"items\"]:\n            spec = \"%s/%s\" % (item[\"owner\"][\"username\"], item[\"name\"])\n            skip_conds = [\n                item[\"owner\"][\"username\"] != \"platformio\",\n                item[\"tier\"] == \"community\",\n            ]\n            if all(skip_conds):\n                click.secho(\"Skip community platform: %s\" % spec, fg=\"yellow\")\n                continue\n            pm.install(spec, skip_dependencies=True)\n        page += 1\n        if not result[\"items\"] or result[\"page\"] * result[\"limit\"] >= result[\"total\"]:\n            break\n\n\n@functools.cache\ndef get_frameworks():\n    items = {}\n    for platform in PlatformPackageManager().get_installed():\n        p = PlatformFactory.new(platform)\n        for name, options in (p.frameworks or {}).items():\n            if name in items:\n                continue\n            if name in STATIC_FRAMEWORK_DATA:\n                items[name] = dict(\n                    name=name,\n                    title=STATIC_FRAMEWORK_DATA[name][\"title\"],\n                    description=STATIC_FRAMEWORK_DATA[name][\"description\"],\n                )\n                continue\n            title = options.get(\"title\") or name.title()\n            description = options.get(\"description\")\n            if \"package\" in options:\n                regdata = REGCLIENT.get_package(\n                    \"tool\",\n                    p.packages[options[\"package\"]].get(\"owner\", \"platformio\"),\n                    options[\"package\"],\n                )\n                title = regdata[\"title\"] or title\n                description = regdata[\"description\"]\n            items[name] = dict(name=name, title=title, description=description)\n    return sorted(items.values(), key=lambda item: item[\"name\"])\n\n\ndef is_compat_platform_and_framework(platform, framework):\n    p = PlatformFactory.new(platform)\n    return framework in (p.frameworks or {}).keys()\n\n\ndef generate_boards_table(boards, skip_columns=None):\n    columns = [\n        (\"Name\", \":ref:`board_{platform}_{id}`\"),\n        (\"Platform\", \":ref:`platform_{platform}`\"),\n        (\"Debug\", \"{debug}\"),\n        (\"MCU\", \"{mcu}\"),\n        (\"Frequency\", \"{f_cpu}MHz\"),\n        (\"Flash\", \"{rom}\"),\n        (\"RAM\", \"{ram}\"),\n    ]\n    lines = []\n    lines.append(\n        \"\"\"\n.. list-table::\n    :header-rows:  1\n\"\"\"\n    )\n\n    # add header\n    for name, template in columns:\n        if skip_columns and name in skip_columns:\n            continue\n        prefix = \"    * - \" if name == \"Name\" else \"      - \"\n        lines.append(prefix + name)\n\n    for data in sorted(boards, key=lambda item: item[\"name\"]):\n        has_onboard_debug = data.get(\"debug\") and any(\n            t.get(\"onboard\") for (_, t) in data[\"debug\"][\"tools\"].items()\n        )\n        debug = \"No\"\n        if has_onboard_debug:\n            debug = \"On-board\"\n        elif data.get(\"debug\"):\n            debug = \"External\"\n\n        variables = dict(\n            id=data[\"id\"],\n            name=data[\"name\"],\n            platform=data[\"platform\"],\n            debug=debug,\n            mcu=data[\"mcu\"].upper(),\n            f_cpu=int(data[\"fcpu\"] / 1000000.0),\n            ram=fs.humanize_file_size(data[\"ram\"]),\n            rom=fs.humanize_file_size(data[\"rom\"]),\n        )\n\n        for name, template in columns:\n            if skip_columns and name in skip_columns:\n                continue\n            prefix = \"    * - \" if name == \"Name\" else \"      - \"\n            lines.append(prefix + template.format(**variables))\n\n    if lines:\n        lines.append(\"\")\n\n    return lines\n\n\ndef generate_frameworks_contents(frameworks):\n    if not frameworks:\n        return []\n    lines = []\n    lines.append(\n        \"\"\"\nFrameworks\n----------\n.. list-table::\n    :header-rows:  1\n\n    * - Name\n      - Description\"\"\"\n    )\n    known = set()\n    for framework in get_frameworks():\n        known.add(framework[\"name\"])\n        if framework[\"name\"] not in frameworks:\n            continue\n        lines.append(\n            \"\"\"\n    * - :ref:`framework_{name}`\n      - {description}\"\"\".format(\n                **framework\n            )\n        )\n    if set(frameworks) - known:\n        click.secho(\"Unknown frameworks %s \" % (set(frameworks) - known), fg=\"red\")\n    return lines\n\n\ndef generate_platforms_contents(platforms):\n    if not platforms:\n        return []\n    lines = []\n    lines.append(\n        \"\"\"\nPlatforms\n---------\n.. list-table::\n    :header-rows:  1\n\n    * - Name\n      - Description\"\"\"\n    )\n\n    for name in sorted(platforms):\n        p = PlatformFactory.new(name)\n        lines.append(\n            \"\"\"\n    * - :ref:`platform_{name}`\n      - {description}\"\"\".format(\n                name=p.name, description=p.description\n            )\n        )\n    return lines\n\n\ndef generate_debug_contents(boards, skip_board_columns=None, extra_rst=None):\n    if not skip_board_columns:\n        skip_board_columns = []\n    skip_board_columns.append(\"Debug\")\n    lines = []\n    onboard_debug = [\n        b\n        for b in boards\n        if b.get(\"debug\")\n        and any(t.get(\"onboard\") for (_, t) in b[\"debug\"][\"tools\"].items())\n    ]\n    external_debug = [b for b in boards if b.get(\"debug\") and b not in onboard_debug]\n    if not onboard_debug and not external_debug:\n        return lines\n\n    lines.append(\n        \"\"\"\nDebugging\n---------\n\n:ref:`piodebug` - \"1-click\" solution for debugging with a zero configuration.\n\n.. contents::\n    :local:\n\"\"\"\n    )\n    if extra_rst:\n        lines.append(\".. include:: %s\" % extra_rst)\n\n    lines.append(\n        \"\"\"\nTools & Debug Probes\n~~~~~~~~~~~~~~~~~~~~\n\nSupported debugging tools are listed in \"Debug\" column. For more detailed\ninformation, please scroll table by horizontal.\nYou can switch between debugging :ref:`debugging_tools` using\n:ref:`projectconf_debug_tool` option in :ref:`projectconf`.\n\n.. warning::\n    You will need to install debug tool drivers depending on your system.\n    Please click on compatible debug tool below for the further instructions.\n\"\"\"\n    )\n\n    if onboard_debug:\n        lines.append(\n            \"\"\"\nOn-Board Debug Tools\n^^^^^^^^^^^^^^^^^^^^\n\nBoards listed below have on-board debug probe and **ARE READY** for debugging!\nYou do not need to use/buy external debug probe.\n\"\"\"\n        )\n        lines.extend(\n            generate_boards_table(onboard_debug, skip_columns=skip_board_columns)\n        )\n    if external_debug:\n        lines.append(\n            \"\"\"\nExternal Debug Tools\n^^^^^^^^^^^^^^^^^^^^\n\nBoards listed below are compatible with :ref:`piodebug` but **DEPEND ON**\nexternal debug probe. They **ARE NOT READY** for debugging.\nPlease click on board name for the further details.\n\"\"\"\n        )\n        lines.extend(\n            generate_boards_table(external_debug, skip_columns=skip_board_columns)\n        )\n    return lines\n\n\ndef generate_packages(platform, packages, is_embedded):\n    if not packages:\n        return\n    lines = []\n    lines.append(\n        \"\"\"\nPackages\n--------\n\"\"\"\n    )\n    lines.append(\n        \"\"\".. list-table::\n    :header-rows:  1\n\n    * - Name\n      - Description\"\"\"\n    )\n    for name, options in dict(sorted(packages.items())).items():\n        if name == \"toolchain-gccarmnoneeab\":  # aceinna typo fix\n            name = name + \"i\"\n        package = REGCLIENT.get_package(\n            \"tool\", options.get(\"owner\", \"platformio\"), name\n        )\n        lines.append(\n            \"\"\"\n    * - `{name} <{url}>`__\n      - {description}\"\"\".format(\n                name=package[\"name\"],\n                url=reg_package_url(\n                    \"tool\", package[\"owner\"][\"username\"], package[\"name\"]\n                ),\n                description=package[\"description\"],\n            )\n        )\n\n    if is_embedded:\n        lines.append(\n            \"\"\"\n.. warning::\n    **Linux Users**:\n\n        * Install \"udev\" rules :ref:`platformio_udev_rules`\n        * Raspberry Pi users, please read this article\n          `Enable serial port on Raspberry Pi <https://hallard.me/enable-serial-port-on-raspberry-pi/>`__.\n\"\"\"\n        )\n\n        if platform == \"teensy\":\n            lines.append(\n                \"\"\"\n    **Windows Users:**\n\n        Teensy programming uses only Windows built-in HID\n        drivers. When Teensy is programmed to act as a USB Serial device,\n        Windows XP, Vista, 7 and 8 require `this serial driver\n        <http://www.pjrc.com/teensy/serial_install.exe>`_\n        is needed to access the COM port your program uses. No special driver\n        installation is necessary on Windows 10.\n\"\"\"\n            )\n        else:\n            lines.append(\n                \"\"\"\n    **Windows Users:**\n\n        Please check that you have a correctly installed USB driver from board\n        manufacturer\n\"\"\"\n            )\n\n    return \"\\n\".join(lines)\n\n\ndef generate_platform(pkg, rst_dir):\n    owner = pkg.metadata.spec.owner\n    name = pkg.metadata.name\n    print(\"Processing platform: %s\" % name)\n\n    compatible_boards = [\n        board\n        for board in PlatformPackageManager().get_installed_boards()\n        if name == board[\"platform\"]\n    ]\n\n    lines = []\n    lines.append(RST_COPYRIGHT)\n\n    p = PlatformFactory.new(name)\n    assert p.repository_url.endswith(\".git\")\n    github_url = p.repository_url[:-4]\n    registry_url = reg_package_url(\"platform\", owner, name)\n\n    lines.append(\".. _platform_%s:\" % name)\n    lines.append(\"\")\n\n    lines.append(p.title)\n    lines.append(\"=\" * len(p.title))\n    lines.append(\"\")\n    lines.append(\":Registry:\")\n    lines.append(\"  `%s <%s>`__\" % (registry_url, registry_url))\n    lines.append(\":Configuration:\")\n    lines.append(\"  :ref:`projectconf_env_platform` = ``%s/%s``\" % (owner, name))\n    lines.append(\"\")\n    lines.append(p.description)\n    lines.append(\n        \"\"\"\nFor more detailed information please visit `vendor site <%s>`_.\"\"\"\n        % campaign_url(p.homepage)\n    )\n    lines.append(\n        \"\"\"\n.. contents:: Contents\n    :local:\n    :depth: 1\n\"\"\"\n    )\n\n    #\n    # Extra\n    #\n    if os.path.isfile(os.path.join(rst_dir, \"%s_extra.rst\" % name)):\n        lines.append(\".. include:: %s_extra.rst\" % p.name)\n\n    #\n    # Examples\n    #\n    lines.append(\n        \"\"\"\nExamples\n--------\n\nExamples are listed from `%s development platform repository <%s>`_:\n\"\"\"\n        % (p.title, campaign_url(\"%s/tree/master/examples\" % github_url))\n    )\n    examples_dir = os.path.join(p.get_dir(), \"examples\")\n    if os.path.isdir(examples_dir):\n        for eitem in os.listdir(examples_dir):\n            example_dir = os.path.join(examples_dir, eitem)\n            if not os.path.isdir(example_dir) or not os.listdir(example_dir):\n                continue\n            url = \"%s/tree/master/examples/%s\" % (github_url, eitem)\n            lines.append(\"* `%s <%s>`_\" % (eitem, campaign_url(url)))\n\n    #\n    # Debugging\n    #\n    if compatible_boards:\n        lines.extend(\n            generate_debug_contents(\n                compatible_boards,\n                skip_board_columns=[\"Platform\"],\n                extra_rst=\"%s_debug.rst\" % name\n                if os.path.isfile(os.path.join(rst_dir, \"%s_debug.rst\" % name))\n                else None,\n            )\n        )\n\n    #\n    # Development version of dev/platform\n    #\n    lines.append(\n        \"\"\"\nStable and upstream versions\n----------------------------\n\nYou can switch between `stable releases <{github_url}/releases>`__\nof {title} development platform and the latest upstream version using\n:ref:`projectconf_env_platform` option in :ref:`projectconf` as described below.\n\nStable\n~~~~~~\n\n.. code-block:: ini\n\n    ; Latest stable version, NOT recommended\n    ; Pin the version as shown below\n    [env:latest_stable]\n    platform = {name}\n    {board}\n    ; Specific version\n    [env:custom_stable]\n    platform = {name}@x.y.z\n    {board}\nUpstream\n~~~~~~~~\n\n.. code-block:: ini\n\n    [env:upstream_develop]\n    platform = {github_url}.git\n    {board}\"\"\".format(\n            name=p.name,\n            title=p.title,\n            github_url=github_url,\n            board=\"board = ...\\n\" if p.is_embedded() else \"\",\n        )\n    )\n\n    #\n    # Packages\n    #\n    _packages_content = generate_packages(name, p.packages, p.is_embedded())\n    if _packages_content:\n        lines.append(_packages_content)\n\n    #\n    # Frameworks\n    #\n    compatible_frameworks = []\n    for framework in get_frameworks():\n        if is_compat_platform_and_framework(name, framework[\"name\"]):\n            compatible_frameworks.append(framework[\"name\"])\n    lines.extend(generate_frameworks_contents(compatible_frameworks))\n\n    #\n    # Boards\n    #\n    if compatible_boards:\n        vendors = {}\n        for board in compatible_boards:\n            if board[\"vendor\"] not in vendors:\n                vendors[board[\"vendor\"]] = []\n            vendors[board[\"vendor\"]].append(board)\n\n        lines.append(\n            \"\"\"\nBoards\n------\n\n.. note::\n    * You can list pre-configured boards by :ref:`cmd_boards` command\n    * For more detailed ``board`` information please scroll the tables below by\n      horizontally.\n\"\"\"\n        )\n\n        for vendor, boards in sorted(vendors.items()):\n            lines.append(str(vendor))\n            lines.append(\"~\" * len(vendor))\n            lines.extend(generate_boards_table(boards, skip_columns=[\"Platform\"]))\n\n    return \"\\n\".join(lines)\n\n\ndef update_platform_docs():\n    platforms_dir = os.path.join(DOCS_ROOT_DIR, \"platforms\")\n    for pkg in PlatformPackageManager().get_installed():\n        rst_path = os.path.join(platforms_dir, \"%s.rst\" % pkg.metadata.name)\n        with open(rst_path, \"w\") as f:\n            f.write(generate_platform(pkg, platforms_dir))\n\n\ndef generate_framework(type_, framework, rst_dir=None):\n    print(\"Processing framework: %s\" % type_)\n\n    compatible_platforms = [\n        pkg\n        for pkg in PlatformPackageManager().get_installed()\n        if is_compat_platform_and_framework(pkg.metadata.name, type_)\n    ]\n    compatible_boards = [\n        board\n        for board in PlatformPackageManager().get_installed_boards()\n        if type_ in board[\"frameworks\"]\n    ]\n\n    lines = []\n\n    lines.append(RST_COPYRIGHT)\n    lines.append(\".. _framework_%s:\" % type_)\n    lines.append(\"\")\n\n    lines.append(framework[\"title\"])\n    lines.append(\"=\" * len(framework[\"title\"]))\n    lines.append(\"\")\n    lines.append(\":Configuration:\")\n    lines.append(\"  :ref:`projectconf_env_framework` = ``%s``\" % type_)\n    lines.append(\"\")\n    lines.append(framework[\"description\"])\n    lines.append(\n        \"\"\"\n.. contents:: Contents\n    :local:\n    :depth: 1\"\"\"\n    )\n\n    # Extra\n    if os.path.isfile(os.path.join(rst_dir, \"%s_extra.rst\" % type_)):\n        lines.append(\".. include:: %s_extra.rst\" % type_)\n\n    if compatible_platforms:\n        # Platforms\n        lines.extend(\n            generate_platforms_contents(\n                [pkg.metadata.name for pkg in compatible_platforms]\n            )\n        )\n\n        # examples\n        lines.append(\n            \"\"\"\nExamples\n--------\n\"\"\"\n        )\n        for pkg in compatible_platforms:\n            p = PlatformFactory.new(pkg)\n            lines.append(\n                \"* `%s for %s <%s>`_\"\n                % (\n                    framework[\"title\"],\n                    p.title,\n                    campaign_url(\"%s/tree/master/examples\" % p.repository_url[:-4]),\n                )\n            )\n\n    #\n    # Debugging\n    #\n    if compatible_boards:\n        lines.extend(\n            generate_debug_contents(\n                compatible_boards,\n                extra_rst=\"%s_debug.rst\" % type_\n                if os.path.isfile(os.path.join(rst_dir, \"%s_debug.rst\" % type_))\n                else None,\n            )\n        )\n\n    #\n    # Boards\n    #\n    if compatible_boards:\n        vendors = {}\n        for board in compatible_boards:\n            if board[\"vendor\"] not in vendors:\n                vendors[board[\"vendor\"]] = []\n            vendors[board[\"vendor\"]].append(board)\n        lines.append(\n            \"\"\"\nBoards\n------\n\n.. note::\n    * You can list pre-configured boards by :ref:`cmd_boards` command\n    * For more detailed ``board`` information please scroll the tables below by horizontally.\n\"\"\"\n        )\n        for vendor, boards in sorted(vendors.items()):\n            lines.append(str(vendor))\n            lines.append(\"~\" * len(vendor))\n            lines.extend(generate_boards_table(boards))\n    return \"\\n\".join(lines)\n\n\ndef update_framework_docs():\n    frameworks_dir = os.path.join(DOCS_ROOT_DIR, \"frameworks\")\n    for framework in get_frameworks():\n        name = framework[\"name\"]\n        rst_path = os.path.join(frameworks_dir, \"%s.rst\" % name)\n        with open(rst_path, \"w\") as f:\n            f.write(generate_framework(name, framework, frameworks_dir))\n\n\ndef update_boards():\n    print(\"Updating boards...\")\n    lines = []\n\n    lines.append(RST_COPYRIGHT)\n    lines.append(\".. _boards:\")\n    lines.append(\"\")\n\n    lines.append(\"Boards\")\n    lines.append(\"======\")\n\n    lines.append(\n        \"\"\"\nRapid Embedded Development, Continuous and IDE integration in a few\nsteps with PlatformIO thanks to built-in project generator for the most\npopular embedded boards and IDEs.\n\n.. note::\n    * You can list pre-configured boards by :ref:`cmd_boards` command\n    * For more detailed ``board`` information please scroll tables below by horizontal.\n\"\"\"\n    )\n\n    platforms = {}\n    installed_boards = PlatformPackageManager().get_installed_boards()\n    for data in installed_boards:\n        platform = data[\"platform\"]\n        if platform in platforms:\n            platforms[platform].append(data)\n        else:\n            platforms[platform] = [data]\n\n    for platform, boards in sorted(platforms.items()):\n        p = PlatformFactory.new(platform)\n        lines.append(p.title)\n        lines.append(\"-\" * len(p.title))\n        lines.append(\n            \"\"\"\n.. toctree::\n    :maxdepth: 1\n        \"\"\"\n        )\n        for board in sorted(boards, key=lambda item: item[\"name\"]):\n            lines.append(\"    %s/%s\" % (platform, board[\"id\"]))\n        lines.append(\"\")\n\n    emboards_rst = os.path.join(DOCS_ROOT_DIR, \"boards\", \"index.rst\")\n    with open(emboards_rst, \"w\") as f:\n        f.write(\"\\n\".join(lines))\n\n    # individual board page\n    for data in installed_boards:\n        rst_path = os.path.join(\n            DOCS_ROOT_DIR, \"boards\", data[\"platform\"], \"%s.rst\" % data[\"id\"]\n        )\n        if not os.path.isdir(os.path.dirname(rst_path)):\n            os.makedirs(os.path.dirname(rst_path))\n        update_embedded_board(rst_path, data)\n\n\ndef update_embedded_board(rst_path, board):\n    platform = PlatformFactory.new(board[\"platform\"])\n    board_config = platform.board_config(board[\"id\"])\n\n    board_manifest_url = platform.repository_url\n    assert board_manifest_url\n    if board_manifest_url.endswith(\".git\"):\n        board_manifest_url = board_manifest_url[:-4]\n    board_manifest_url += \"/blob/master/boards/%s.json\" % board[\"id\"]\n\n    variables = dict(\n        id=board[\"id\"],\n        name=board[\"name\"],\n        platform=board[\"platform\"],\n        platform_description=platform.description,\n        url=campaign_url(board[\"url\"]),\n        mcu=board_config.get(\"build\", {}).get(\"mcu\", \"\"),\n        mcu_upper=board[\"mcu\"].upper(),\n        f_cpu=board[\"fcpu\"],\n        f_cpu_mhz=int(int(board[\"fcpu\"]) / 1000000),\n        ram=fs.humanize_file_size(board[\"ram\"]),\n        rom=fs.humanize_file_size(board[\"rom\"]),\n        vendor=board[\"vendor\"],\n        board_manifest_url=board_manifest_url,\n        upload_protocol=board_config.get(\"upload.protocol\", \"\"),\n    )\n\n    lines = [RST_COPYRIGHT]\n    lines.append(\".. _board_{platform}_{id}:\".format(**variables))\n    lines.append(\"\")\n    lines.append(board[\"name\"])\n    lines.append(\"=\" * len(board[\"name\"]))\n    lines.append(\n        \"\"\"\n.. contents::\n\nHardware\n--------\n\nPlatform :ref:`platform_{platform}`: {platform_description}\n\n.. list-table::\n\n  * - **Microcontroller**\n    - {mcu_upper}\n  * - **Frequency**\n    - {f_cpu_mhz:d}MHz\n  * - **Flash**\n    - {rom}\n  * - **RAM**\n    - {ram}\n  * - **Vendor**\n    - `{vendor} <{url}>`__\n\"\"\".format(\n            **variables\n        )\n    )\n\n    #\n    # Configuration\n    #\n    lines.append(\n        \"\"\"\nConfiguration\n-------------\n\nPlease use ``{id}`` ID for :ref:`projectconf_env_board` option in :ref:`projectconf`:\n\n.. code-block:: ini\n\n  [env:{id}]\n  platform = {platform}\n  board = {id}\n\nYou can override default {name} settings per build environment using\n``board_***`` option, where ``***`` is a JSON object path from\nboard manifest `{id}.json <{board_manifest_url}>`_. For example,\n``board_build.mcu``, ``board_build.f_cpu``, etc.\n\n.. code-block:: ini\n\n  [env:{id}]\n  platform = {platform}\n  board = {id}\n\n  ; change microcontroller\n  board_build.mcu = {mcu}\n\n  ; change MCU frequency\n  board_build.f_cpu = {f_cpu}L\n\"\"\".format(\n            **variables\n        )\n    )\n\n    #\n    # Uploading\n    #\n    upload_protocols = board_config.get(\"upload.protocols\", [])\n    if len(upload_protocols) > 1:\n        lines.append(\n            \"\"\"\nUploading\n---------\n%s supports the following uploading protocols:\n\"\"\"\n            % board[\"name\"]\n        )\n        for protocol in sorted(upload_protocols):\n            lines.append(\"* ``%s``\" % protocol)\n        lines.append(\n            \"\"\"\nDefault protocol is ``%s``\"\"\"\n            % variables[\"upload_protocol\"]\n        )\n        lines.append(\n            \"\"\"\nYou can change upload protocol using :ref:`projectconf_upload_protocol` option:\n\n.. code-block:: ini\n\n  [env:{id}]\n  platform = {platform}\n  board = {id}\n\n  upload_protocol = {upload_protocol}\n\"\"\".format(\n                **variables\n            )\n        )\n\n    #\n    # Debugging\n    #\n    lines.append(\"Debugging\")\n    lines.append(\"---------\")\n    if not board.get(\"debug\"):\n        lines.append(\n            \":ref:`piodebug` currently does not support {name} board.\".format(\n                **variables\n            )\n        )\n    else:\n        default_debug_tool = board_config.get_debug_tool_name()\n        has_onboard_debug = any(\n            t.get(\"onboard\") for (_, t) in board[\"debug\"][\"tools\"].items()\n        )\n        lines.append(\n            \"\"\"\n:ref:`piodebug` - \"1-click\" solution for debugging with a zero configuration.\n\n.. warning::\n    You will need to install debug tool drivers depending on your system.\n    Please click on compatible debug tool below for the further\n    instructions and configuration information.\n\nYou can switch between debugging :ref:`debugging_tools` using\n:ref:`projectconf_debug_tool` option in :ref:`projectconf`.\n\"\"\"\n        )\n        if has_onboard_debug:\n            lines.append(\n                \"{name} has on-board debug probe and **IS READY** for \"\n                \"debugging. You don't need to use/buy external debug probe.\".format(\n                    **variables\n                )\n            )\n        else:\n            lines.append(\n                \"{name} does not have on-board debug probe and **IS NOT \"\n                \"READY** for debugging. You will need to use/buy one of \"\n                \"external probe listed below.\".format(**variables)\n            )\n        lines.append(\n            \"\"\"\n.. list-table::\n  :header-rows:  1\n\n  * - Compatible Tools\n    - On-board\n    - Default\"\"\"\n        )\n        for tool_name, tool_data in sorted(board[\"debug\"][\"tools\"].items()):\n            lines.append(\n                \"\"\"  * - {tool}\n    - {onboard}\n    - {default}\"\"\".format(\n                    tool=f\"``{tool_name}``\"\n                    if tool_name in SKIP_DEBUG_TOOLS\n                    else f\":ref:`debugging_tool_{tool_name}`\",\n                    onboard=\"Yes\" if tool_data.get(\"onboard\") else \"\",\n                    default=\"Yes\" if tool_name == default_debug_tool else \"\",\n                )\n            )\n\n    if board[\"frameworks\"]:\n        lines.extend(generate_frameworks_contents(board[\"frameworks\"]))\n\n    with open(rst_path, \"w\") as f:\n        f.write(\"\\n\".join(lines))\n\n\ndef update_debugging():\n    tool_to_platforms = {}\n    tool_to_boards = {}\n    vendors = {}\n    platforms = []\n    frameworks = []\n    for data in PlatformPackageManager().get_installed_boards():\n        if not data.get(\"debug\"):\n            continue\n\n        for tool in data[\"debug\"][\"tools\"]:\n            tool = str(tool)\n            if tool not in tool_to_platforms:\n                tool_to_platforms[tool] = []\n            tool_to_platforms[tool].append(data[\"platform\"])\n            if tool not in tool_to_boards:\n                tool_to_boards[tool] = []\n            tool_to_boards[tool].append(data[\"id\"])\n\n        platforms.append(data[\"platform\"])\n        frameworks.extend(data[\"frameworks\"])\n        vendor = data[\"vendor\"]\n        if vendor in vendors:\n            vendors[vendor].append(data)\n        else:\n            vendors[vendor] = [data]\n\n    platforms = sorted(set(platforms))\n    frameworks = sorted(set(frameworks))\n\n    lines = [\".. _debugging_platforms:\"]\n    lines.extend(generate_platforms_contents(platforms))\n    lines.extend(generate_frameworks_contents(frameworks))\n\n    # Boards\n    lines.append(\n        \"\"\"\nBoards\n------\n\n.. note::\n    For more detailed ``board`` information please scroll tables below by horizontal.\n\"\"\"\n    )\n    for vendor, boards in sorted(vendors.items()):\n        lines.append(str(vendor))\n        lines.append(\"~\" * len(vendor))\n        lines.extend(generate_boards_table(boards))\n\n    # save\n    with open(\n        os.path.join(fs.get_source_dir(), \"..\", \"docs\", \"plus\", \"debugging.rst\"), \"r+\"\n    ) as fp:\n        content = fp.read()\n        fp.seek(0)\n        fp.truncate()\n        fp.write(\n            content[: content.index(\".. _debugging_platforms:\")] + \"\\n\".join(lines)\n        )\n\n    # Debug tools\n    for tool, platforms in tool_to_platforms.items():\n        tool_path = os.path.join(DOCS_ROOT_DIR, \"plus\", \"debug-tools\", \"%s.rst\" % tool)\n        if not os.path.isfile(tool_path):\n            if tool in SKIP_DEBUG_TOOLS:\n                click.secho(\"Skipped debug tool `%s`\" % tool, fg=\"yellow\")\n            else:\n                click.secho(\"Unknown debug tool `%s`\" % tool, fg=\"red\")\n            continue\n        platforms = sorted(set(platforms))\n\n        lines = [\".. begin_platforms\"]\n        lines.extend(generate_platforms_contents(platforms))\n        tool_frameworks = []\n        for platform in platforms:\n            for framework in frameworks:\n                if is_compat_platform_and_framework(platform, framework):\n                    tool_frameworks.append(framework)\n        lines.extend(generate_frameworks_contents(tool_frameworks))\n\n        lines.append(\n            \"\"\"\nBoards\n------\n\n.. note::\n    For more detailed ``board`` information please scroll tables below by horizontal.\n\"\"\"\n        )\n        lines.extend(\n            generate_boards_table(\n                [\n                    b\n                    for b in PlatformPackageManager().get_installed_boards()\n                    if b[\"id\"] in tool_to_boards[tool]\n                ],\n                skip_columns=None,\n            )\n        )\n\n        with open(tool_path, \"r+\") as fp:\n            content = fp.read()\n            fp.seek(0)\n            fp.truncate()\n            fp.write(content[: content.index(\".. begin_platforms\")] + \"\\n\".join(lines))\n\n\ndef update_project_examples():\n    platform_readme_tpl = \"\"\"\n# {title}: development platform for [PlatformIO](https://platformio.org)\n\n{description}\n\n* [Home](https://platformio.org/platforms/{name}) (home page in PlatformIO Registry)\n* [Documentation](https://docs.platformio.org/page/platforms/{name}.html) (advanced usage, packages, boards, frameworks, etc.)\n\n# Examples\n\n{examples}\n\"\"\"\n    framework_readme_tpl = \"\"\"\n# {title}: framework for [PlatformIO](https://platformio.org)\n\n{description}\n\n* [Home](https://platformio.org/frameworks/{name}) (home page in PlatformIO Registry)\n* [Documentation](https://docs.platformio.org/page/frameworks/{name}.html)\n\n# Examples\n\n{examples}\n\"\"\"\n\n    project_examples_dir = os.path.join(fs.get_source_dir(), \"..\", \"examples\")\n    framework_examples_md_lines = {}\n    embedded = []\n    desktop = []\n\n    for pkg in PlatformPackageManager().get_installed():\n        p = PlatformFactory.new(pkg)\n        github_url = p.repository_url[:-4]\n\n        # Platform README\n        platform_examples_dir = os.path.join(p.get_dir(), \"examples\")\n        examples_md_lines = []\n        if os.path.isdir(platform_examples_dir):\n            for item in sorted(os.listdir(platform_examples_dir)):\n                example_dir = os.path.join(platform_examples_dir, item)\n                if not os.path.isdir(example_dir) or not os.listdir(example_dir):\n                    continue\n                url = \"%s/tree/master/examples/%s\" % (github_url, item)\n                examples_md_lines.append(\"* [%s](%s)\" % (item, url))\n\n        readme_dir = os.path.join(project_examples_dir, \"platforms\", p.name)\n        if not os.path.isdir(readme_dir):\n            os.makedirs(readme_dir)\n        with open(os.path.join(readme_dir, \"README.md\"), \"w\") as fp:\n            fp.write(\n                platform_readme_tpl.format(\n                    name=p.name,\n                    title=p.title,\n                    description=p.description,\n                    examples=\"\\n\".join(examples_md_lines),\n                )\n            )\n\n        # Framework README\n        for framework in get_frameworks():\n            if not is_compat_platform_and_framework(p.name, framework[\"name\"]):\n                continue\n            if framework[\"name\"] not in framework_examples_md_lines:\n                framework_examples_md_lines[framework[\"name\"]] = []\n            lines = []\n            lines.append(\"- [%s](%s)\" % (p.title, github_url))\n            lines.extend(\"  %s\" % line for line in examples_md_lines)\n            lines.append(\"\")\n            framework_examples_md_lines[framework[\"name\"]].extend(lines)\n\n        # Root README\n        line = \"* [%s](%s)\" % (p.title, \"%s/tree/master/examples\" % github_url)\n        if p.is_embedded():\n            embedded.append(line)\n        else:\n            desktop.append(line)\n\n    # Frameworks\n    frameworks = []\n    for framework in get_frameworks():\n        if framework[\"name\"] not in framework_examples_md_lines:\n            continue\n        readme_dir = os.path.join(project_examples_dir, \"frameworks\", framework[\"name\"])\n        if not os.path.isdir(readme_dir):\n            os.makedirs(readme_dir)\n        with open(os.path.join(readme_dir, \"README.md\"), \"w\") as fp:\n            fp.write(\n                framework_readme_tpl.format(\n                    name=framework[\"name\"],\n                    title=framework[\"title\"],\n                    description=framework[\"description\"],\n                    examples=\"\\n\".join(framework_examples_md_lines[framework[\"name\"]]),\n                )\n            )\n        url = campaign_url(\n            \"https://docs.platformio.org/en/latest/frameworks/%s.html#examples\"\n            % framework[\"name\"],\n            source=\"github\",\n            medium=\"examples\",\n        )\n        frameworks.append(\"* [%s](%s)\" % (framework[\"title\"], url))\n\n    with open(os.path.join(project_examples_dir, \"README.md\"), \"w\") as fp:\n        fp.write(\n            \"\"\"# PlatformIO Project Examples\n\n- [Development platforms](#development-platforms):\n  - [Embedded](#embedded)\n  - [Desktop](#desktop)\n- [Frameworks](#frameworks)\n\n## Development platforms\n\n### Embedded\n\n%s\n\n### Desktop\n\n%s\n\n## Frameworks\n\n%s\n\"\"\"\n            % (\"\\n\".join(embedded), \"\\n\".join(desktop), \"\\n\".join(frameworks))\n        )\n\n\ndef main():\n    with tempfile.TemporaryDirectory() as tmp_dir:\n        print(\"Core directory: %s\" % tmp_dir)\n        os.environ[\"PLATFORMIO_CORE_DIR\"] = tmp_dir\n        install_platforms()\n        update_platform_docs()\n        update_framework_docs()\n        update_boards()\n        update_debugging()\n        update_project_examples()\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "scripts/fixsymlink.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom os import chdir, getcwd, readlink, remove, symlink, walk\nfrom os.path import exists, islink, join, relpath\nfrom sys import exit as sys_exit\n\n\ndef fix_symlink(root, fname, brokenlink):\n    print(root, fname, brokenlink)\n    prevcwd = getcwd()\n\n    chdir(root)\n    remove(fname)\n    symlink(relpath(brokenlink, root), fname)\n    chdir(prevcwd)\n\n\ndef main():\n    for root, dirnames, filenames in walk(\".\"):\n        for f in filenames:\n            path = join(root, f)\n            if not islink(path) or exists(path):\n                continue\n            fix_symlink(root, f, readlink(path))\n\n\nif __name__ == \"__main__\":\n    sys_exit(main())\n"
  },
  {
    "path": "scripts/install_devplatforms.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport subprocess\nimport sys\n\nimport click\n\n\n@click.command()\n@click.option(\"--desktop\", is_flag=True, default=False)\n@click.option(\n    \"--names\",\n    envvar=\"PIO_INSTALL_DEVPLATFORM_NAMES\",\n    help=\"Install specified platform (split by comma)\",\n)\n@click.option(\n    \"--ownernames\",\n    envvar=\"PIO_INSTALL_DEVPLATFORM_OWNERNAMES\",\n    help=\"Filter by ownernames (split by comma)\",\n)\ndef main(desktop, names, ownernames):\n    platforms = json.loads(\n        subprocess.check_output([\"pio\", \"platform\", \"search\", \"--json-output\"]).decode()\n    )\n    names = [n.strip() for n in (names or \"\").split(\",\") if n.strip()]\n    ownernames = [n.strip() for n in (ownernames or \"\").split(\",\") if n.strip()]\n    for platform in platforms:\n        skip = [\n            not desktop and platform[\"forDesktop\"],\n            names and platform[\"name\"] not in names,\n            ownernames and platform[\"ownername\"] not in ownernames,\n        ]\n        if any(skip):\n            continue\n        subprocess.check_call(\n            [\n                \"pio\",\n                \"pkg\",\n                \"install\",\n                \"--global\",\n                \"--skip-dependencies\",\n                \"--platform\",\n                \"{ownername}/{name}\".format(**platform),\n            ]\n        )\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": "setup.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom setuptools import find_packages, setup\n\nfrom platformio import (\n    __author__,\n    __description__,\n    __email__,\n    __license__,\n    __title__,\n    __url__,\n    __version__,\n)\nfrom platformio.dependencies import get_pip_dependencies\n\nsetup(\n    name=__title__,\n    version=__version__,\n    description=__description__,\n    long_description=open(\"README.rst\").read(),\n    author=__author__,\n    author_email=__email__,\n    url=__url__,\n    license=__license__,\n    install_requires=get_pip_dependencies(),\n    python_requires=\">=3.6\",\n    packages=find_packages(include=[\"platformio\", \"platformio.*\"]),\n    package_data={\n        \"platformio\": [\n            \"assets/system/99-platformio-udev.rules\",\n            \"project/integration/tpls/*/*.tpl\",\n            \"project/integration/tpls/*/.*.tpl\",  # include hidden files\n            \"project/integration/tpls/*/.*/*.tpl\",  # include hidden folders\n            \"project/integration/tpls/*/*/*.tpl\",  # NetBeans\n            \"project/integration/tpls/*/*/*/*.tpl\",  # NetBeans\n        ]\n    },\n    entry_points={\n        \"console_scripts\": [\n            \"platformio = platformio.__main__:main\",\n            \"pio = platformio.__main__:main\",\n            \"piodebuggdb = platformio.__main__:debug_gdb_main\",\n        ]\n    },\n    classifiers=[\n        \"Development Status :: 5 - Production/Stable\",\n        \"Environment :: Console\",\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: Apache Software License\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: C\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Topic :: Software Development\",\n        \"Topic :: Software Development :: Build Tools\",\n        \"Topic :: Software Development :: Compilers\",\n    ],\n    keywords=[\n        \"iot\",\n        \"embedded\",\n        \"arduino\",\n        \"mbed\",\n        \"esp8266\",\n        \"esp32\",\n        \"fpga\",\n        \"firmware\",\n        \"continuous-integration\",\n        \"cloud-ide\",\n        \"avr\",\n        \"arm\",\n        \"ide\",\n        \"unit-testing\",\n        \"hardware\",\n        \"verilog\",\n        \"microcontroller\",\n        \"debug\",\n    ],\n)\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/commands/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/commands/pkg/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/commands/pkg/test_exec.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport pytest\n\nfrom platformio.package.commands.exec import package_exec_cmd\nfrom platformio.util import strip_ansi_codes\n\n\ndef test_pkg_not_installed(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        package_exec_cmd,\n        [\"--\", \"openocd\"],\n    )\n    with pytest.raises(\n        AssertionError,\n        match=(\"Could not find a package with 'openocd' executable file\"),\n    ):\n        validate_cliresult(result)\n\n\ndef test_pkg_specified(clirunner, validate_cliresult, isolated_pio_core):\n    # with install\n    result = clirunner.invoke(\n        package_exec_cmd,\n        [\"-p\", \"platformio/tool-openocd\", \"--\", \"openocd\", \"--version\"],\n        obj=dict(force_click_stream=True),\n    )\n    validate_cliresult(result)\n    output = strip_ansi_codes(result.output)\n    assert \"Tool Manager: Installing platformio/tool-openocd\" in output\n    assert \"Open On-Chip Debugger\" in output\n\n\ndef test_unrecognized_options(clirunner, validate_cliresult, isolated_pio_core):\n    # unrecognized option\n    result = clirunner.invoke(\n        package_exec_cmd,\n        [\"--\", \"openocd\", \"--test-unrecognized\"],\n        obj=dict(force_click_stream=True),\n    )\n    with pytest.raises(\n        AssertionError,\n        match=(r\"openocd: (unrecognized|unknown) option\"),\n    ):\n        validate_cliresult(result)\n"
  },
  {
    "path": "tests/commands/pkg/test_install.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport os\n\nimport pytest\n\nfrom platformio import fs\nfrom platformio.dependencies import get_core_dependencies\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\n\nPROJECT_CONFIG_TPL = \"\"\"\n[env]\nplatform = platformio/atmelavr@^3.4.0\nlib_deps =\n    milesburton/DallasTemperature@^4.0.4\n    https://github.com/esphome/ESPAsyncWebServer/archive/refs/tags/v2.1.0.zip\n\n[env:baremetal]\nboard = uno\n\n[env:devkit]\nframework = arduino\nboard = attiny88\n\"\"\"\n\n\ndef pkgs_to_specs(pkgs):\n    return [\n        PackageSpec(name=pkg.metadata.name, requirements=pkg.metadata.version)\n        for pkg in pkgs\n    ]\n\n\ndef test_global_packages(\n    clirunner,\n    validate_cliresult,\n    func_isolated_pio_core,\n    get_pkg_latest_version,\n    tmp_path,\n):\n    # libraries\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"-l\",\n            \"https://github.com/milesburton/Arduino-Temperature-Control-Library.git#3.9.0\",\n            \"--skip-dependencies\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(LibraryPackageManager().get_installed()) == [\n        PackageSpec(\"DallasTemperature@3.9.0+sha.964939d\")\n    ]\n    # with dependencies\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"-l\",\n            \"https://github.com/milesburton/Arduino-Temperature-Control-Library.git#3.9.0\",\n            \"-l\",\n            \"bblanchon/ArduinoJson@^5\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(LibraryPackageManager().get_installed()) == [\n        PackageSpec(\"ArduinoJson@5.13.4\"),\n        PackageSpec(\"DallasTemperature@3.9.0+sha.964939d\"),\n        PackageSpec(\"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")),\n    ]\n    # custom storage\n    storage_dir = tmp_path / \"custom_lib_storage\"\n    storage_dir.mkdir()\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"--storage-dir\",\n            str(storage_dir),\n            \"-l\",\n            \"bblanchon/ArduinoJson@^5\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(LibraryPackageManager(storage_dir).get_installed()) == [\n        PackageSpec(\"ArduinoJson@5.13.4\")\n    ]\n\n    # tools\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-t\", \"platformio/framework-arduino-avr-attiny@^1.5.2\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n        PackageSpec(\"framework-arduino-avr-attiny@1.5.2\")\n    ]\n\n    # platforms\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-p\", \"platformio/atmelavr@^3.4.0\", \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n        PackageSpec(\"atmelavr@3.4.0\")\n    ]\n\n\ndef test_skip_dependencies(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        installed_lib_pkgs = LibraryPackageManager(\n            os.path.join(ProjectConfig().get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        ).get_installed()\n        assert pkgs_to_specs(installed_lib_pkgs) == [\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n            PackageSpec(\"ESPAsyncWebServer-esphome@2.1.0\"),\n        ]\n        assert len(ToolPackageManager().get_installed()) == 1  # SCons\n\n\ndef test_baremetal_project(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"baremetal\"],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        installed_lib_pkgs = LibraryPackageManager(\n            os.path.join(ProjectConfig().get(\"platformio\", \"libdeps_dir\"), \"baremetal\")\n        ).get_installed()\n        assert pkgs_to_specs(installed_lib_pkgs) == [\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n            PackageSpec(\"ESPAsyncWebServer-esphome@2.1.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"tool-scons@%s\" % get_core_dependencies()[\"tool-scons\"][1:]),\n            PackageSpec(\"toolchain-atmelavr@1.70300.191015\"),\n        ]\n\n\ndef test_project(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n            PackageSpec(\"ESPAsyncWebServer-esphome@2.1.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"framework-arduino-avr-attiny@1.5.2\"),\n            PackageSpec(\"tool-scons@%s\" % get_core_dependencies()[\"tool-scons\"][1:]),\n            PackageSpec(\"toolchain-atmelavr@1.70300.191015\"),\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^4.0.4\",\n            \"https://github.com/esphome/ESPAsyncWebServer/archive/refs/tags/v2.1.0.zip\",\n        ]\n\n    # test \"Already up-to-date\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    assert \"Already up-to-date\" in result.output\n\n\ndef test_private_lib_deps(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    private_lib_dir = project_dir / \"lib\" / \"private\"\n    private_lib_dir.mkdir(parents=True)\n    (private_lib_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"My Private Lib\",\n    \"version\": \"1.0.0\",\n    \"dependencies\": {\n        \"bblanchon/ArduinoJson\": \"^5\",\n        \"milesburton/DallasTemperature\": \"^4.0.4\"\n    }\n}\n\"\"\")\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:private]\nplatform = native\n\"\"\")\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n\n        # some deps were added by user manually\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\n                \"-g\",\n                \"--storage-dir\",\n                config.get(\"platformio\", \"lib_dir\"),\n                \"-l\",\n                \"paulstoffregen/OneWire@^2.3.5\",\n            ],\n        )\n        validate_cliresult(result)\n\n        # ensure all deps are installed\n        result = clirunner.invoke(package_install_cmd)\n        validate_cliresult(result)\n        installed_private_pkgs = LibraryPackageManager(\n            config.get(\"platformio\", \"lib_dir\")\n        ).get_installed()\n        assert pkgs_to_specs(installed_private_pkgs) == [\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n            PackageSpec(\"My Private Lib@1.0.0\"),\n        ]\n        installed_env_pkgs = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"private\")\n        ).get_installed()\n        assert pkgs_to_specs(installed_env_pkgs) == [\n            PackageSpec(\"ArduinoJson@5.13.4\"),\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n        ]\n\n\ndef test_remove_project_unused_libdeps(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"baremetal\"],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        storage_dir = os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"baremetal\")\n        lm = LibraryPackageManager(storage_dir)\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n            PackageSpec(\"ESPAsyncWebServer-esphome@2.1.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n\n        # add new deps\n        lib_deps = config.get(\"env:baremetal\", \"lib_deps\")\n        config.set(\"env:baremetal\", \"lib_deps\", lib_deps + [\"bblanchon/ArduinoJson@^5\"])\n        config.save()\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"baremetal\"],\n        )\n        validate_cliresult(result)\n        lm = LibraryPackageManager(storage_dir)\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\"ArduinoJson@5.13.4\"),\n            PackageSpec(\n                \"DallasTemperature@%s\"\n                % get_pkg_latest_version(\"milesburton/DallasTemperature\")\n            ),\n            PackageSpec(\"ESPAsyncWebServer-esphome@2.1.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n\n        # manually remove from configuration file\n        config.set(\"env:baremetal\", \"lib_deps\", [\"bblanchon/ArduinoJson@^5\"])\n        config.save()\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"baremetal\"],\n        )\n        validate_cliresult(result)\n        lm = LibraryPackageManager(storage_dir)\n        assert pkgs_to_specs(lm.get_installed()) == [PackageSpec(\"ArduinoJson@5.13.4\")]\n\n\ndef test_unknown_project_dependencies(\n    clirunner, validate_cliresult, isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:unknown_platform]\nplatform = unknown_platform\n\n[env:unknown_lib_deps]\nlib_deps = SPI, platformio/unknown_library\n\"\"\")\n    with fs.cd(str(project_dir)):\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"unknown_platform\"],\n        )\n        with pytest.raises(\n            AssertionError,\n            match=(\"Could not find the package with 'unknown_platform' requirements\"),\n        ):\n            validate_cliresult(result)\n\n        # unknown libraries\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"unknown_lib_deps\"],\n        )\n        with pytest.raises(\n            AssertionError,\n            match=(\n                \"Could not find the package with 'platformio/unknown_library' requirements\"\n            ),\n        ):\n            validate_cliresult(result)\n\n\ndef test_custom_project_libraries(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"bblanchon/ArduinoJson@^5\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-l\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        # try again\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-l\", spec],\n        )\n        validate_cliresult(result)\n        assert \"already installed\" in result.output\n        # try again in the silent mode\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-l\", spec, \"--silent\"],\n        )\n        validate_cliresult(result)\n        assert not result.output.strip()\n\n        # check folders\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [PackageSpec(\"ArduinoJson@5.13.4\")]\n        # do not expect any platforms/tools\n        assert not os.path.exists(config.get(\"platformio\", \"platforms_dir\"))\n        assert not os.path.exists(config.get(\"platformio\", \"packages_dir\"))\n\n        # check saved deps\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"bblanchon/ArduinoJson@^5\",\n        ]\n\n        # install library without saving to config\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-l\", \"nanopb/Nanopb@^0.4.6\", \"--no-save\"],\n        )\n        validate_cliresult(result)\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\"ArduinoJson@5.13.4\"),\n            PackageSpec(\"Nanopb@0.4.91\"),\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"bblanchon/ArduinoJson@^5\",\n        ]\n\n        # unknown libraries\n        result = clirunner.invoke(\n            package_install_cmd, [\"-l\", \"platformio/unknown_library\"]\n        )\n        with pytest.raises(\n            AssertionError,\n            match=(\n                \"Could not find the package with \"\n                \"'platformio/unknown_library' requirements\"\n            ),\n        ):\n            validate_cliresult(result)\n\n\ndef test_custom_project_tools(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"platformio/tool-openocd @ ^2\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-t\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        # try again\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-t\", spec],\n        )\n        validate_cliresult(result)\n        assert \"already installed\" in result.output\n        # try again in the silent mode\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-t\", spec, \"--silent\"],\n        )\n        validate_cliresult(result)\n        assert not result.output.strip()\n\n        config = ProjectConfig()\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"tool-openocd@2.1100.211028\")\n        ]\n        assert not LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        ).get_installed()\n        # do not expect any platforms\n        assert not os.path.exists(config.get(\"platformio\", \"platforms_dir\"))\n\n        # check saved deps\n        assert config.get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/tool-openocd@^2\",\n        ]\n\n        # install tool without saving to config\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-t\", \"platformio/tool-esptoolpy@1.20310.0\", \"--no-save\"],\n        )\n        validate_cliresult(result)\n        config = ProjectConfig()\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"tool-esptoolpy@1.20310.0\"),\n            PackageSpec(\"tool-openocd@2.1100.211028\"),\n        ]\n        assert config.get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/tool-openocd@^2\",\n        ]\n\n        # unknown tool\n        result = clirunner.invoke(\n            package_install_cmd, [\"-t\", \"platformio/unknown_tool\"]\n        )\n        with pytest.raises(\n            AssertionError,\n            match=(\n                \"Could not find the package with \"\n                \"'platformio/unknown_tool' requirements\"\n            ),\n        ):\n            validate_cliresult(result)\n\n\ndef test_custom_project_platforms(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"atmelavr@^3.4.0\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-p\", spec, \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        # try again\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-p\", spec, \"--skip-dependencies\"],\n        )\n        validate_cliresult(result)\n        assert \"already installed\" in result.output\n        # try again in the silent mode\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-p\", spec, \"--silent\", \"--skip-dependencies\"],\n        )\n        validate_cliresult(result)\n        assert not result.output.strip()\n\n        config = ProjectConfig()\n        assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n            PackageSpec(\"atmelavr@3.4.0\")\n        ]\n        assert not LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        ).get_installed()\n        # do not expect any packages\n        assert not os.path.exists(config.get(\"platformio\", \"packages_dir\"))\n\n        # unknown platform\n        result = clirunner.invoke(package_install_cmd, [\"-p\", \"unknown_platform\"])\n        with pytest.raises(\n            AssertionError,\n            match=\"Could not find the package with 'unknown_platform' requirements\",\n        ):\n            validate_cliresult(result)\n\n        # incompatible board\n        result = clirunner.invoke(package_install_cmd, [\"-e\", \"devkit\", \"-p\", \"sifive\"])\n        with pytest.raises(\n            AssertionError,\n            match=\"Unknown board ID\",\n        ):\n            validate_cliresult(result)\n"
  },
  {
    "path": "tests/commands/pkg/test_list.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.list import package_list_cmd\n\nPROJECT_CONFIG_TPL = \"\"\"\n[env]\nplatform = platformio/atmelavr@^3.4.0\n\n[env:baremetal]\nboard = uno\n\n[env:devkit]\nframework = arduino\nboard = attiny88\nlib_deps =\n    milesburton/DallasTemperature@^3.9.1\n    https://github.com/bblanchon/ArduinoJson.git#v6.19.0\n\"\"\"\n\n\ndef test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n\n    # test all envs\n    result = clirunner.invoke(\n        package_list_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    assert all(token in result.output for token in (\"baremetal\", \"devkit\"))\n    assert result.output.count(\"Platform atmelavr @ 3.4.0\") == 2\n    assert (\n        result.output.count(\n            \"toolchain-atmelavr @ 1.70300.191015 (required: \"\n            \"platformio/toolchain-atmelavr @ ~1.70300.0)\"\n        )\n        == 2\n    )\n    assert result.output.count(\"Libraries\") == 1\n    assert (\n        \"ArduinoJson @ 6.19.0+sha.9693fd2 (required: \"\n        \"git+https://github.com/bblanchon/ArduinoJson.git#v6.19.0)\"\n    ) in result.output\n    assert \"OneWire @ 2\" in result.output\n\n    # test \"baremetal\"\n    result = clirunner.invoke(\n        package_list_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"baremetal\"],\n    )\n    validate_cliresult(result)\n    assert \"Platform atmelavr @ 3\" in result.output\n    assert \"Libraries\" not in result.output\n\n    # filter by \"tool\" package\n    result = clirunner.invoke(\n        package_list_cmd,\n        [\"-d\", str(project_dir), \"-t\", \"toolchain-atmelavr@~1.70300.0\"],\n    )\n    assert \"framework-arduino\" not in result.output\n    assert \"Libraries\" not in result.output\n\n    # list only libraries\n    result = clirunner.invoke(\n        package_list_cmd,\n        [\"-d\", str(project_dir), \"--only-libraries\"],\n    )\n    assert \"Platform atmelavr\" not in result.output\n\n    # list only libraries for baremetal\n    result = clirunner.invoke(\n        package_list_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"baremetal\", \"--only-libraries\"],\n    )\n    assert \"No packages\" in result.output\n\n\ndef test_global_packages(clirunner, validate_cliresult, isolated_pio_core, tmp_path):\n    result = clirunner.invoke(package_list_cmd, [\"-g\"])\n    validate_cliresult(result)\n    assert \"atmelavr @ 3\" in result.output\n    assert \"framework-arduino-avr-attiny\" in result.output\n\n    # only tools\n    result = clirunner.invoke(package_list_cmd, [\"-g\", \"--only-tools\"])\n    validate_cliresult(result)\n    assert \"toolchain-atmelavr\" in result.output\n    assert \"Platforms\" not in result.output\n\n    # find tool package\n    result = clirunner.invoke(package_list_cmd, [\"-g\", \"-t\", \"toolchain-atmelavr\"])\n    validate_cliresult(result)\n    assert \"toolchain-atmelavr\" in result.output\n    assert \"framework-arduino-avr-attiny@\" not in result.output\n\n    # only libraries - no packages\n    result = clirunner.invoke(package_list_cmd, [\"-g\", \"--only-libraries\"])\n    validate_cliresult(result)\n    assert not result.output.strip()\n\n    # check global libs\n    result = clirunner.invoke(\n        package_install_cmd, [\"-g\", \"-l\", \"milesburton/DallasTemperature@^3.9.1\"]\n    )\n    validate_cliresult(result)\n    result = clirunner.invoke(package_list_cmd, [\"-g\", \"--only-libraries\"])\n    validate_cliresult(result)\n    assert \"DallasTemperature\" in result.output\n    assert \"OneWire\" in result.output\n\n    # filter by lib\n    result = clirunner.invoke(package_list_cmd, [\"-g\", \"-l\", \"OneWire\"])\n    validate_cliresult(result)\n    assert \"DallasTemperature\" in result.output\n    assert \"OneWire\" in result.output\n"
  },
  {
    "path": "tests/commands/pkg/test_outdated.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport re\n\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.outdated import package_outdated_cmd\n\nPROJECT_OUTDATED_CONFIG_TPL = \"\"\"\n[env:devkit]\nplatform = platformio/atmelavr@^2\nframework = arduino\nboard = attiny88\nlib_deps = milesburton/DallasTemperature@~3.9.0\n\"\"\"\n\nPROJECT_UPDATED_CONFIG_TPL = \"\"\"\n[env:devkit]\nplatform = platformio/atmelavr@<4\nframework = arduino\nboard = attiny88\nlib_deps = milesburton/DallasTemperature@^3.9.0\n\"\"\"\n\n\ndef test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_OUTDATED_CONFIG_TPL)\n    result = clirunner.invoke(package_install_cmd, [\"-d\", str(project_dir)])\n    validate_cliresult(result)\n\n    # overwrite config\n    (project_dir / \"platformio.ini\").write_text(PROJECT_UPDATED_CONFIG_TPL)\n    result = clirunner.invoke(package_outdated_cmd, [\"-d\", str(project_dir)])\n    validate_cliresult(result)\n\n    # validate output\n    assert \"Checking\" in result.output\n    assert re.search(\n        r\"^atmelavr\\s+2\\.2\\.0\\s+3\\.\\d+\\.\\d+\\s+[456789]\\.\\d+\\.\\d+\\s+Platform\\s+devkit\",\n        result.output,\n        re.MULTILINE,\n    )\n    assert re.search(\n        r\"^DallasTemperature\\s+3\\.\\d\\.1\\s+3\\.\\d+\\.\\d+\\s+4\\.\\d+\\.\\d+\\s+Library\\s+devkit\",\n        result.output,\n        re.MULTILINE,\n    )\n"
  },
  {
    "path": "tests/commands/pkg/test_search.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.package.commands.search import package_search_cmd\n\n\ndef test_empty_query(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_search_cmd,\n        [\"\"],\n    )\n    validate_cliresult(result)\n    assert all(t in result.output for t in (\"Found\", \"Official\", \"page 1 of\"))\n\n\ndef test_pagination(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_search_cmd,\n        [\"type:tool\"],\n    )\n    validate_cliresult(result)\n    assert all(t in result.output for t in (\"Verified Tool\", \"page 1 of\"))\n\n    result = clirunner.invoke(\n        package_search_cmd,\n        [\"type:tool\", \"-p\", \"10\"],\n    )\n    validate_cliresult(result)\n    assert all(t in result.output for t in (\"Tool\", \"page 10 of\"))\n\n\ndef test_sorting(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_search_cmd,\n        [\"OneWire\", \"-s\", \"popularity\"],\n    )\n    validate_cliresult(result)\n    assert \"paulstoffregen/OneWire\" in result.output\n\n\ndef test_not_found(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_search_cmd,\n        [\"name:unknown-package\"],\n    )\n    validate_cliresult(result)\n    assert \"Nothing has been found\" in result.output\n"
  },
  {
    "path": "tests/commands/pkg/test_show.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport pytest\n\nfrom platformio.exception import UserSideException\nfrom platformio.package.commands.show import package_show_cmd\n\n\ndef test_spec_name(clirunner, validate_cliresult):\n    # library\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"ArduinoJSON\"],\n    )\n    validate_cliresult(result)\n    assert \"bblanchon/ArduinoJson\" in result.output\n    assert \"Library\" in result.output\n\n    # platform\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"espressif32\"],\n    )\n    validate_cliresult(result)\n    assert \"platformio/espressif32\" in result.output\n    assert \"Platform\" in result.output\n\n    # tool\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"tool-jlink\"],\n    )\n    validate_cliresult(result)\n    assert \"platformio/tool-jlink\" in result.output\n    assert \"tool\" in result.output\n\n\ndef test_spec_owner(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"bblanchon/ArduinoJSON\"],\n    )\n    validate_cliresult(result)\n    assert \"bblanchon/ArduinoJson\" in result.output\n    assert \"Library\" in result.output\n\n    # test broken owner\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"unknown/espressif32\"],\n    )\n    with pytest.raises(UserSideException, match=\"Could not find\"):\n        raise result.exception\n\n\ndef test_complete_spec(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"bblanchon/ArduinoJSON\", \"-t\", \"library\"],\n    )\n    validate_cliresult(result)\n    assert \"bblanchon/ArduinoJson\" in result.output\n    assert \"Library\" in result.output\n\n    # tool\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"platformio/tool-jlink\", \"-t\", \"tool\"],\n    )\n    validate_cliresult(result)\n    assert \"platformio/tool-jlink\" in result.output\n    assert \"tool\" in result.output\n\n\ndef test_name_conflict(clirunner):\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"OneWire\", \"-t\", \"library\"],\n    )\n    assert \"More than one package\" in result.output\n    assert isinstance(result.exception, UserSideException)\n\n\ndef test_spec_version(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        package_show_cmd,\n        [\"bblanchon/ArduinoJSON@5.13.4\"],\n    )\n    validate_cliresult(result)\n    assert \"bblanchon/ArduinoJson\" in result.output\n    assert \"Library • 5.13.4\" in result.output\n"
  },
  {
    "path": "tests/commands/pkg/test_uninstall.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport os\n\nfrom platformio import fs\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.uninstall import package_uninstall_cmd\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.project.config import ProjectConfig\n\nPROJECT_CONFIG_TPL = \"\"\"\n[env]\nplatform = platformio/atmelavr@^3.4.0\nlib_deps = milesburton/DallasTemperature@^3.9.1\n\n[env:baremetal]\nboard = uno\n\n[env:devkit]\nframework = arduino\nboard = attiny88\n\"\"\"\n\n\ndef pkgs_to_names(pkgs):\n    return [pkg.metadata.name for pkg in pkgs]\n\n\ndef test_global_packages(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    # libraries\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"-l\",\n            \"marvinroger/Homie@^3.0.1\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(LibraryPackageManager().get_installed()) == [\n        \"ArduinoJson\",\n        \"Async TCP\",\n        \"AsyncMqttClient\",\n        \"AsyncTCP\",\n        \"AsyncTCP_RP2040W\",\n        \"Bounce2\",\n        \"ESP Async WebServer\",\n        \"ESPAsyncTCP\",\n        \"ESPAsyncTCP-esphome\",\n        \"Homie\",\n    ]\n    # uninstall all deps\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\n            \"--global\",\n            \"-l\",\n            \"Homie\",\n        ],\n    )\n    validate_cliresult(result)\n    assert not pkgs_to_names(LibraryPackageManager().get_installed())\n\n    # skip dependencies\n    validate_cliresult(\n        clirunner.invoke(\n            package_install_cmd,\n            [\n                \"--global\",\n                \"-l\",\n                \"marvinroger/Homie@^3.0.1\",\n            ],\n        )\n    )\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\"--global\", \"-l\", \"marvinroger/Homie@^3.0.1\", \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(LibraryPackageManager().get_installed()) == [\n        \"ArduinoJson\",\n        \"Async TCP\",\n        \"AsyncMqttClient\",\n        \"AsyncTCP\",\n        \"AsyncTCP_RP2040W\",\n        \"Bounce2\",\n        \"ESP Async WebServer\",\n        \"ESPAsyncTCP\",\n        \"ESPAsyncTCP-esphome\",\n    ]\n    # remove specific dependency\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\n            \"--global\",\n            \"-l\",\n            \"ESP Async WebServer\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(LibraryPackageManager().get_installed()) == [\n        \"ArduinoJson\",\n        \"AsyncMqttClient\",\n        \"AsyncTCP\",\n        \"Bounce2\",\n        \"ESPAsyncTCP\",\n    ]\n\n    # custom storage\n    storage_dir = tmp_path / \"custom_lib_storage\"\n    storage_dir.mkdir()\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"--storage-dir\",\n            str(storage_dir),\n            \"-l\",\n            \"marvinroger/Homie@^3.0.1\",\n            \"--skip-dependencies\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(LibraryPackageManager(storage_dir).get_installed()) == [\n        \"Homie\"\n    ]\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\n            \"--global\",\n            \"--storage-dir\",\n            str(storage_dir),\n            \"-l\",\n            \"marvinroger/Homie@^3.0.1\",\n        ],\n    )\n    validate_cliresult(result)\n    assert not pkgs_to_names(LibraryPackageManager(storage_dir).get_installed())\n\n    # tools\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-t\", \"platformio/framework-arduino-avr-attiny@^1.5.2\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(ToolPackageManager().get_installed()) == [\n        \"framework-arduino-avr-attiny\"\n    ]\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\"--global\", \"-t\", \"framework-arduino-avr-attiny\"],\n    )\n    validate_cliresult(result)\n    assert not pkgs_to_names(ToolPackageManager().get_installed())\n\n    # platforms\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-p\", \"platformio/atmelavr@^3.4.0\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_names(PlatformPackageManager().get_installed()) == [\"atmelavr\"]\n    assert pkgs_to_names(ToolPackageManager().get_installed()) == [\"toolchain-atmelavr\"]\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\"--global\", \"-p\", \"platformio/atmelavr@^3.4.0\"],\n    )\n    validate_cliresult(result)\n    assert not pkgs_to_names(PlatformPackageManager().get_installed())\n    assert not pkgs_to_names(ToolPackageManager().get_installed())\n\n\ndef test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_names(lm.get_installed()) == [\"DallasTemperature\", \"OneWire\"]\n        assert pkgs_to_names(ToolPackageManager().get_installed()) == [\n            \"framework-arduino-avr-attiny\",\n            \"tool-scons\",\n            \"toolchain-atmelavr\",\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n    # try again\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    assert \"Already up-to-date\" in result.output\n\n    # uninstall\n    result = clirunner.invoke(\n        package_uninstall_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert not pkgs_to_names(lm.get_installed())\n        assert pkgs_to_names(ToolPackageManager().get_installed()) == [\"tool-scons\"]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n\ndef test_custom_project_libraries(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"bblanchon/ArduinoJson@^6.19.2\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-l\", spec],\n    )\n    validate_cliresult(result)\n    assert \"Already up-to-date\" not in result.output\n    with fs.cd(str(project_dir)):\n        # check folders\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_names(lm.get_installed()) == [\"ArduinoJson\"]\n        # do not expect any platforms/tools\n        assert not os.path.exists(config.get(\"platformio\", \"platforms_dir\"))\n        assert not os.path.exists(config.get(\"platformio\", \"packages_dir\"))\n        # check saved deps\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"bblanchon/ArduinoJson@^6.19.2\",\n        ]\n        # uninstall\n        result = clirunner.invoke(\n            package_uninstall_cmd,\n            [\"-e\", \"devkit\", \"-l\", spec],\n        )\n        validate_cliresult(result)\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert not pkgs_to_names(lm.get_installed())\n        # do not expect any platforms/tools\n        assert not os.path.exists(config.get(\"platformio\", \"platforms_dir\"))\n        assert not os.path.exists(config.get(\"platformio\", \"packages_dir\"))\n        # check saved deps\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n        # install library without saving to config\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-l\", spec, \"--no-save\"],\n        )\n        validate_cliresult(result)\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_names(lm.get_installed()) == [\"ArduinoJson\"]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\",\n        ]\n        result = clirunner.invoke(\n            package_uninstall_cmd,\n            [\"-e\", \"devkit\", \"-l\", spec, \"--no-save\"],\n        )\n        validate_cliresult(result)\n        config = ProjectConfig()\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\",\n        ]\n\n        # unknown libraries\n        result = clirunner.invoke(\n            package_uninstall_cmd, [\"-l\", \"platformio/unknown_library\"]\n        )\n        assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_custom_project_tools(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"platformio/tool-openocd@^2\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-t\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        assert pkgs_to_names(ToolPackageManager().get_installed()) == [\"tool-openocd\"]\n        assert not LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        ).get_installed()\n        # do not expect any platforms\n        assert not os.path.exists(config.get(\"platformio\", \"platforms_dir\"))\n        # check saved deps\n        assert config.get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/tool-openocd@^2\",\n        ]\n        # uninstall\n        result = clirunner.invoke(\n            package_uninstall_cmd,\n            [\"-e\", \"devkit\", \"-t\", spec],\n        )\n        validate_cliresult(result)\n        assert not pkgs_to_names(ToolPackageManager().get_installed())\n        # check saved deps\n        assert not ProjectConfig().get(\"env:devkit\", \"platform_packages\")\n\n        # install tool without saving to config\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-t\", \"platformio/tool-esptoolpy@1.20310.0\"],\n        )\n        validate_cliresult(result)\n        assert pkgs_to_names(ToolPackageManager().get_installed()) == [\n            \"tool-esptoolpy\",\n        ]\n        assert ProjectConfig().get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/tool-esptoolpy@1.20310.0\",\n        ]\n        # uninstall\n        result = clirunner.invoke(\n            package_uninstall_cmd,\n            [\"-e\", \"devkit\", \"-t\", \"platformio/tool-esptoolpy@^1\", \"--no-save\"],\n        )\n        validate_cliresult(result)\n        assert not pkgs_to_names(ToolPackageManager().get_installed())\n        assert ProjectConfig().get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/tool-esptoolpy@1.20310.0\",\n        ]\n\n        # unknown tool\n        result = clirunner.invoke(\n            package_uninstall_cmd, [\"-t\", \"platformio/unknown_tool\"]\n        )\n        assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_custom_project_platforms(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    spec = \"platformio/atmelavr@^3.4.0\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-p\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        assert pkgs_to_names(PlatformPackageManager().get_installed()) == [\"atmelavr\"]\n        assert not LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        ).get_installed()\n        assert pkgs_to_names(ToolPackageManager().get_installed()) == [\n            \"framework-arduino-avr-attiny\",\n            \"toolchain-atmelavr\",\n        ]\n        # uninstall\n        result = clirunner.invoke(\n            package_uninstall_cmd,\n            [\"-e\", \"devkit\", \"-p\", spec],\n        )\n        validate_cliresult(result)\n        assert not pkgs_to_names(PlatformPackageManager().get_installed())\n        assert not pkgs_to_names(ToolPackageManager().get_installed())\n\n        # unknown platform\n        result = clirunner.invoke(package_uninstall_cmd, [\"-p\", \"unknown_platform\"])\n        assert isinstance(result.exception, UnknownPackageError)\n"
  },
  {
    "path": "tests/commands/pkg/test_update.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport os\n\nfrom platformio import fs\nfrom platformio.dependencies import get_core_dependencies\nfrom platformio.package.commands.install import package_install_cmd\nfrom platformio.package.commands.update import package_update_cmd\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\n\nDALLASTEMPERATURE_LATEST_VERSION = \"3.11.0\"\n\nPROJECT_OUTDATED_CONFIG_TPL = \"\"\"\n[env:devkit]\nplatform = platformio/atmelavr@^2\nframework = arduino\nboard = attiny88\nlib_deps = milesburton/DallasTemperature@^3.9.1\n\"\"\"\n\nPROJECT_UPDATED_CONFIG_TPL = \"\"\"\n[env:devkit]\nplatform = platformio/atmelavr@<4\nframework = arduino\nboard = attiny88\nlib_deps = milesburton/DallasTemperature@^3.9.1\n\"\"\"\n\n\ndef pkgs_to_specs(pkgs):\n    return [\n        PackageSpec(name=pkg.metadata.name, requirements=pkg.metadata.version)\n        for pkg in pkgs\n    ]\n\n\ndef test_global_packages(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    # libraries\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-l\", \"bblanchon/ArduinoJson@^5\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(LibraryPackageManager().get_installed()) == [\n        PackageSpec(\"ArduinoJson@5.13.4\")\n    ]\n    # update to the latest version\n    result = clirunner.invoke(\n        package_update_cmd,\n        [\"--global\", \"-l\", \"bblanchon/ArduinoJson\"],\n    )\n    validate_cliresult(result)\n    pkgs = LibraryPackageManager().get_installed()\n    assert len(pkgs) == 1\n    assert pkgs[0].metadata.version.major > 5\n    # custom storage\n    storage_dir = tmp_path / \"custom_lib_storage\"\n    storage_dir.mkdir()\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\n            \"--global\",\n            \"--storage-dir\",\n            str(storage_dir),\n            \"-l\",\n            \"bblanchon/ArduinoJson@^5\",\n        ],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(LibraryPackageManager(storage_dir).get_installed()) == [\n        PackageSpec(\"ArduinoJson@5.13.4\")\n    ]\n    # update to the latest version\n    result = clirunner.invoke(\n        package_update_cmd,\n        [\"--global\", \"--storage-dir\", str(storage_dir), \"-l\", \"bblanchon/ArduinoJson\"],\n    )\n    validate_cliresult(result)\n    pkgs = LibraryPackageManager(storage_dir).get_installed()\n    assert len(pkgs) == 1\n    assert pkgs[0].metadata.version.major > 5\n\n    # tools\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-t\", \"platformio/framework-arduino-avr-attiny@~1.4\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n        PackageSpec(\"framework-arduino-avr-attiny@1.4.1\")\n    ]\n    # update to the latest version\n    result = clirunner.invoke(\n        package_update_cmd,\n        [\"--global\", \"-t\", \"platformio/framework-arduino-avr-attiny@^1\"],\n    )\n    validate_cliresult(result)\n    pkgs = ToolPackageManager().get_installed()\n    assert len(pkgs) == 1\n    assert pkgs[0].metadata.version.major == 1\n    assert pkgs[0].metadata.version.minor > 4\n\n    # platforms\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"-p\", \"platformio/atmelavr@^2\", \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n        PackageSpec(\"atmelavr@2.2.0\")\n    ]\n    # update to the latest version\n    result = clirunner.invoke(\n        package_update_cmd,\n        [\"--global\", \"-p\", \"platformio/atmelavr\", \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    pkgs = PlatformPackageManager().get_installed()\n    assert len(pkgs) == 1\n    assert pkgs[0].metadata.version.major > 2\n\n    # update unknown package\n    result = clirunner.invoke(\n        package_update_cmd,\n        [\"--global\", \"-l\", \"platformio/unknown_package_for_update\"],\n    )\n    assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_project(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_OUTDATED_CONFIG_TPL)\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir)],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(f\"DallasTemperature@{DALLASTEMPERATURE_LATEST_VERSION}\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n            PackageSpec(\"atmelavr@2.2.0\")\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"framework-arduino-avr-attiny@1.3.2\"),\n            PackageSpec(\"tool-scons@%s\" % get_core_dependencies()[\"tool-scons\"][1:]),\n            PackageSpec(\"toolchain-atmelavr@1.50400.190710\"),\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n        # update packages\n        (project_dir / \"platformio.ini\").write_text(PROJECT_UPDATED_CONFIG_TPL)\n        result = clirunner.invoke(package_update_cmd)\n        validate_cliresult(result)\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        pkgs = PlatformPackageManager().get_installed()\n        assert len(pkgs) == 1\n        assert pkgs[0].metadata.name == \"atmelavr\"\n        assert pkgs[0].metadata.version.major == 3\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\"DallasTemperature@3.11.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"framework-arduino-avr-attiny@1.3.2\"),\n            PackageSpec(\"tool-scons@%s\" % get_core_dependencies()[\"tool-scons\"][1:]),\n            PackageSpec(\"toolchain-atmelavr@1.70300.191015\"),\n            PackageSpec(\"toolchain-atmelavr@1.50400.190710\"),\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n        # update again\n        result = clirunner.invoke(package_update_cmd)\n        validate_cliresult(result)\n        assert \"Already up-to-date.\" in result.output\n\n        # update again in the silent ,pde\n        result = clirunner.invoke(package_update_cmd, [\"--silent\"])\n        validate_cliresult(result)\n        assert not result.output\n\n\ndef test_custom_project_libraries(\n    clirunner, validate_cliresult, isolated_pio_core, get_pkg_latest_version, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_OUTDATED_CONFIG_TPL)\n    spec = \"milesburton/DallasTemperature@^3.9.1\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-l\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig()\n        assert config.get(\"env:devkit\", \"lib_deps\") == [spec]\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(f\"DallasTemperature@{DALLASTEMPERATURE_LATEST_VERSION}\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        # update package\n        result = clirunner.invoke(\n            package_update_cmd,\n            [\"-e\", \"devkit\", \"-l\", \"milesburton/DallasTemperature@^3.9.1\"],\n        )\n        assert ProjectConfig().get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n        # try again\n        result = clirunner.invoke(\n            package_update_cmd,\n            [\"-e\", \"devkit\", \"-l\", \"milesburton/DallasTemperature@^3.9.1\"],\n        )\n        validate_cliresult(result)\n        assert \"Already up-to-date.\" in result.output\n\n        # install library without saving to config\n        result = clirunner.invoke(\n            package_update_cmd,\n            [\"-e\", \"devkit\", \"-l\", \"milesburton/DallasTemperature@^3\", \"--no-save\"],\n        )\n        validate_cliresult(result)\n        assert \"Already up-to-date.\" in result.output\n        config = ProjectConfig()\n        lm = LibraryPackageManager(\n            os.path.join(config.get(\"platformio\", \"libdeps_dir\"), \"devkit\")\n        )\n        assert pkgs_to_specs(lm.get_installed()) == [\n            PackageSpec(\"DallasTemperature@3.11.0\"),\n            PackageSpec(\n                \"OneWire@%s\" % get_pkg_latest_version(\"paulstoffregen/OneWire\")\n            ),\n        ]\n        assert config.get(\"env:devkit\", \"lib_deps\") == [\n            \"milesburton/DallasTemperature@^3.9.1\"\n        ]\n\n        # unknown libraries\n        result = clirunner.invoke(\n            package_update_cmd, [\"-l\", \"platformio/unknown_library\"]\n        )\n        assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_custom_project_tools(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_OUTDATED_CONFIG_TPL)\n    spec = \"toolchain-atmelavr@~1.50400.0\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-t\", spec],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        assert ProjectConfig().get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/toolchain-atmelavr@~1.50400.0\"\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"toolchain-atmelavr@1.50400.190710\")\n        ]\n        result = clirunner.invoke(\n            package_update_cmd,\n            [\"-e\", \"devkit\", \"-t\", \"toolchain-atmelavr@^1\"],\n        )\n        validate_cliresult(result)\n        assert ProjectConfig().get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/toolchain-atmelavr@^1\"\n        ]\n        assert pkgs_to_specs(ToolPackageManager().get_installed()) == [\n            PackageSpec(\"toolchain-atmelavr@1.70300.191015\")\n        ]\n\n        # install without saving to config\n        result = clirunner.invoke(\n            package_update_cmd,\n            [\"-e\", \"devkit\", \"-t\", \"toolchain-atmelavr@~1.70300.191015\", \"--no-save\"],\n        )\n        validate_cliresult(result)\n        assert \"Already up-to-date.\" in result.output\n        assert ProjectConfig().get(\"env:devkit\", \"platform_packages\") == [\n            \"platformio/toolchain-atmelavr@^1\"\n        ]\n\n        # unknown tool\n        result = clirunner.invoke(package_update_cmd, [\"-t\", \"platformio/unknown_tool\"])\n        assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_custom_project_platforms(\n    clirunner, validate_cliresult, func_isolated_pio_core, tmp_path\n):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_OUTDATED_CONFIG_TPL)\n    spec = \"atmelavr@^2\"\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"devkit\", \"-p\", spec, \"--skip-dependencies\"],\n    )\n    validate_cliresult(result)\n    with fs.cd(str(project_dir)):\n        assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n            PackageSpec(\"atmelavr@2.2.0\")\n        ]\n        assert ProjectConfig().get(\"env:devkit\", \"platform\") == \"platformio/atmelavr@^2\"\n\n        # update\n        result = clirunner.invoke(\n            package_install_cmd,\n            [\"-e\", \"devkit\", \"-p\", \"platformio/atmelavr@^3\", \"--skip-dependencies\"],\n        )\n        validate_cliresult(result)\n        assert pkgs_to_specs(PlatformPackageManager().get_installed()) == [\n            PackageSpec(\"atmelavr@3.4.0\"),\n            PackageSpec(\"atmelavr@2.2.0\"),\n        ]\n        assert ProjectConfig().get(\"env:devkit\", \"platform\") == \"platformio/atmelavr@^2\"\n\n        # unknown platform\n        result = clirunner.invoke(package_install_cmd, [\"-p\", \"unknown_platform\"])\n        assert isinstance(result.exception, UnknownPackageError)\n"
  },
  {
    "path": "tests/commands/test_account_org_team.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=global-statement,unused-argument\n\nimport json\nimport os\nimport random\n\nimport pytest\nimport requests\n\nfrom platformio.account.cli import cli as cmd_account\nfrom platformio.account.org.cli import cli as cmd_org\nfrom platformio.account.team.cli import cli as cmd_team\n\npytestmark = pytest.mark.skipif(\n    not all(\n        os.environ.get(name)\n        for name in (\n            \"TEST_EMAIL_LOGIN\",\n            \"TEST_EMAIL_PASSWORD\",\n            \"TEST_EMAIL_IMAP_SERVER\",\n        )\n    ),\n    reason=(\n        \"requires TEST_EMAIL_LOGIN, TEST_EMAIL_PASSWORD, \"\n        \"and TEST_EMAIL_IMAP_SERVER environment variables\"\n    ),\n)\n\nUSER_NAME = \"test-piocore-%s\" % str(random.randint(0, 100000))\nUSER_EMAIL = os.environ.get(\"TEST_EMAIL_LOGIN\", \"\").replace(\"@\", f\"+{USER_NAME}@\")\nUSER_PASSWORD = f\"Qwerty-{random.randint(0, 100000)}\"\nUSER_FIRST_NAME = \"FirstName\"\nUSER_LAST_NAME = \"LastName\"\n\nORG_NAME = \"testorg-piocore-%s\" % str(random.randint(0, 100000))\nORG_DISPLAY_NAME = \"Test Org for PIO Core\"\nEXISTING_OWNER = \"piolabs\"\n\nTEAM_NAME = \"test-\" + str(random.randint(0, 100000))\nTEAM_DESCRIPTION = \"team for CI test\"\n\n\ndef verify_account(email_contents):\n    link = (\n        email_contents.split(\"Click on the link below to start this process.\")[1]\n        .split(\"This link will expire within 12 hours.\")[0]\n        .strip()\n    )\n    with requests.Session() as session:\n        result = session.get(link).text\n        link = result.split('<a href=\"')[1].split('\"', 1)[0]\n        link = link.replace(\"&amp;\", \"&\")\n        session.get(link)\n        session.close()\n\n\ndef test_account_register(\n    clirunner, validate_cliresult, receive_email, isolated_pio_core\n):\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"register\",\n            \"-u\",\n            USER_NAME,\n            \"-e\",\n            USER_EMAIL,\n            \"-p\",\n            USER_PASSWORD,\n            \"--firstname\",\n            USER_FIRST_NAME,\n            \"--lastname\",\n            USER_LAST_NAME,\n        ],\n    )\n    validate_cliresult(result)\n    verify_account(receive_email(USER_EMAIL))\n\n\ndef test_account_login(\n    clirunner,\n    validate_cliresult,\n    isolated_pio_core,\n):\n    result = clirunner.invoke(\n        cmd_account,\n        [\"login\", \"-u\", USER_NAME, \"-p\", USER_PASSWORD],\n    )\n    validate_cliresult(result)\n\n\ndef test_account_summary(\n    clirunner,\n    validate_cliresult,\n    isolated_pio_core,\n):\n    result = clirunner.invoke(cmd_account, [\"show\", \"--json-output\", \"--offline\"])\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert not json_result.get(\"user_id\")\n    assert json_result.get(\"profile\")\n    assert json_result.get(\"profile\").get(\"username\") == USER_NAME\n    assert json_result.get(\"profile\").get(\"email\") == USER_EMAIL\n    assert not json_result.get(\"packages\")\n    assert not json_result.get(\"subscriptions\")\n\n    result = clirunner.invoke(cmd_account, [\"show\"])\n    validate_cliresult(result)\n    assert USER_NAME in result.output\n    # assert \"100 Concurrent Remote Agents\" in result.output\n\n    result = clirunner.invoke(cmd_account, [\"show\", \"--json-output\"])\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert json_result.get(\"user_id\")\n    assert json_result.get(\"profile\")\n    assert json_result.get(\"profile\").get(\"username\") == USER_NAME\n    assert json_result.get(\"profile\").get(\"email\") == USER_EMAIL\n    assert json_result.get(\"profile\").get(\"firstname\") == USER_FIRST_NAME\n    assert json_result.get(\"profile\").get(\"lastname\") == USER_LAST_NAME\n    assert json_result.get(\"packages\")\n    assert json_result.get(\"packages\")[0].get(\"name\")\n    assert json_result.get(\"packages\")[0].get(\"path\")\n    assert json_result.get(\"subscriptions\") is not None\n\n\ndef test_account_token(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"token\",\n            \"--password\",\n            USER_PASSWORD,\n        ],\n    )\n    validate_cliresult(result)\n    assert \"Personal Authentication Token:\" in result.output\n    token = result.output.strip().split(\": \")[-1]\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"token\", \"--password\", USER_PASSWORD, \"--json-output\"],\n    )\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert json_result\n    assert json_result.get(\"status\") == \"success\"\n    assert json_result.get(\"result\") == token\n\n    # logout\n    result = clirunner.invoke(cmd_account, [\"logout\"])\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"token\",\n            \"--password\",\n            USER_PASSWORD,\n        ],\n    )\n    assert result.exit_code != 0\n    assert result.exception\n    assert \"You are not authorized!\" in str(result.exception)\n\n    # use env tokem\n    os.environ[\"PLATFORMIO_AUTH_TOKEN\"] = token\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"show\", \"--json-output\"],\n    )\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert json_result.get(\"user_id\")\n    assert json_result.get(\"profile\").get(\"username\") == USER_NAME\n    assert json_result.get(\"profile\").get(\"email\") == USER_EMAIL\n\n    os.environ.pop(\"PLATFORMIO_AUTH_TOKEN\")\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"login\", \"-u\", USER_NAME, \"-p\", USER_PASSWORD],\n    )\n    validate_cliresult(result)\n\n\ndef test_account_change_password(clirunner, validate_cliresult, isolated_pio_core):\n    new_password = \"Testpassword123\"\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"password\",\n            \"--old-password\",\n            USER_PASSWORD,\n            \"--new-password\",\n            new_password,\n        ],\n    )\n    validate_cliresult(result)\n    assert \"Password successfully changed!\" in result.output\n\n    result = clirunner.invoke(cmd_account, [\"logout\"])\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"login\", \"-u\", USER_NAME, \"-p\", new_password],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"password\",\n            \"--old-password\",\n            new_password,\n            \"--new-password\",\n            USER_PASSWORD,\n        ],\n    )\n    validate_cliresult(result)\n\n\ndef test_account_update(\n    clirunner, validate_cliresult, receive_email, isolated_pio_core\n):\n    global USER_NAME, USER_EMAIL, USER_FIRST_NAME, USER_LAST_NAME\n\n    USER_NAME = \"test-piocore-%s\" % str(random.randint(0, 100000))\n    USER_EMAIL = os.environ.get(\"TEST_EMAIL_LOGIN\", \"\").replace(\n        \"@\", f\"+new-{USER_NAME}@\"\n    )\n    USER_FIRST_NAME = \"First \" + str(random.randint(0, 100000))\n    USER_LAST_NAME = \"Last\" + str(random.randint(0, 100000))\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\n            \"update\",\n            \"--current-password\",\n            USER_PASSWORD,\n            \"--firstname\",\n            USER_FIRST_NAME,\n            \"--lastname\",\n            USER_LAST_NAME,\n            \"--username\",\n            USER_NAME,\n            \"--email\",\n            USER_EMAIL,\n        ],\n    )\n    validate_cliresult(result)\n    assert \"Profile successfully updated!\" in result.output\n    assert (\n        \"Please check your mail to verify your new email address and re-login. \"\n        in result.output\n    )\n    verify_account(receive_email(USER_EMAIL))\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"show\"],\n    )\n    assert result.exit_code > 0\n    assert result.exception\n    assert \"You are not authorized!\" in str(result.exception)\n\n    result = clirunner.invoke(\n        cmd_account,\n        [\"login\", \"-u\", USER_NAME, \"-p\", USER_PASSWORD],\n    )\n    validate_cliresult(result)\n\n\n# def _test_account_destroy_with_linked_resources(\n#     clirunner, validate_cliresult, receive_email, isolated_pio_core, tmpdir_factory\n# ):\n#     package_url = \"https://github.com/bblanchon/ArduinoJson/archive/v6.11.0.tar.gz\"\n#\n#     tmp_dir = tmpdir_factory.mktemp(\"package\")\n#     fd = FileDownloader(package_url, str(tmp_dir))\n#     pkg_dir = tmp_dir.mkdir(\"raw_package\")\n#     fd.start(with_progress=False, silent=True)\n#     with FileUnpacker(fd.get_filepath()) as unpacker:\n#         unpacker.unpack(str(pkg_dir), with_progress=False, silent=True)\n#\n#     result = clirunner.invoke(cmd_package, [\"publish\", str(pkg_dir)],)\n#     validate_cliresult(result)\n#     try:\n#         result = receive_email(email)\n#         assert \"Congrats\" in result\n#         assert \"was published\" in result\n#     except:  # pylint:disable=bare-except\n#         pass\n#\n#     result = clirunner.invoke(cmd_account, [\"destroy\"], \"y\")\n#     assert result.exit_code != 0\n#     assert (\n#         \"We can not destroy the %s account due to 1 linked resources from registry\"\n#         % username\n#     )\n#\n#     result = clirunner.invoke(cmd_package, [\"unpublish\", \"ArduinoJson\"],)\n#     validate_cliresult(result)\n\n\ndef test_org_create(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_org,\n        [\"create\", \"--email\", USER_EMAIL, \"--displayname\", ORG_DISPLAY_NAME, ORG_NAME],\n    )\n    validate_cliresult(result)\n\n\ndef test_org_list(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(cmd_org, [\"list\", \"--json-output\"])\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert json_result == [\n        {\n            \"orgname\": ORG_NAME,\n            \"displayname\": ORG_DISPLAY_NAME,\n            \"email\": USER_EMAIL,\n            \"owners\": [\n                {\n                    \"username\": USER_NAME,\n                    \"firstname\": USER_FIRST_NAME,\n                    \"lastname\": USER_LAST_NAME,\n                }\n            ],\n        }\n    ]\n\n\ndef test_org_add_owner(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(cmd_org, [\"add\", ORG_NAME, EXISTING_OWNER])\n    validate_cliresult(result)\n\n    result = clirunner.invoke(cmd_org, [\"list\", \"--json-output\"])\n    validate_cliresult(result)\n    assert EXISTING_OWNER in result.output\n\n\ndef test_org_remove_owner(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(cmd_org, [\"remove\", ORG_NAME, EXISTING_OWNER])\n    validate_cliresult(result)\n\n    result = clirunner.invoke(cmd_org, [\"list\", \"--json-output\"])\n    validate_cliresult(result)\n    assert EXISTING_OWNER not in result.output\n\n\ndef test_org_update(clirunner, validate_cliresult, isolated_pio_core):\n    new_orgname = \"neworg-piocore-%s\" % str(random.randint(0, 100000))\n    new_display_name = \"Test Org for PIO Core\"\n\n    result = clirunner.invoke(\n        cmd_org,\n        [\n            \"update\",\n            ORG_NAME,\n            \"--orgname\",\n            new_orgname,\n            \"--displayname\",\n            new_display_name,\n        ],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(cmd_org, [\"list\", \"--json-output\"])\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    assert json_result == [\n        {\n            \"orgname\": new_orgname,\n            \"displayname\": new_display_name,\n            \"email\": USER_EMAIL,\n            \"owners\": [\n                {\n                    \"username\": USER_NAME,\n                    \"firstname\": USER_FIRST_NAME,\n                    \"lastname\": USER_LAST_NAME,\n                }\n            ],\n        }\n    ]\n\n    result = clirunner.invoke(\n        cmd_org,\n        [\n            \"update\",\n            new_orgname,\n            \"--orgname\",\n            ORG_NAME,\n            \"--displayname\",\n            ORG_DISPLAY_NAME,\n        ],\n    )\n    validate_cliresult(result)\n\n\ndef test_team_create(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_team,\n        [\n            \"create\",\n            \"%s:%s\" % (ORG_NAME, TEAM_NAME),\n            \"--description\",\n            TEAM_DESCRIPTION,\n        ],\n    )\n    validate_cliresult(result)\n\n\ndef test_team_list(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_team,\n        [\"list\", \"%s\" % ORG_NAME, \"--json-output\"],\n    )\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    for item in json_result:\n        del item[\"id\"]\n    assert json_result == [\n        {\"name\": TEAM_NAME, \"description\": TEAM_DESCRIPTION, \"members\": []}\n    ]\n\n\ndef _test_team_add_member(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_team,\n        [\"add\", \"%s:%s\" % (ORG_NAME, TEAM_NAME), EXISTING_OWNER],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_team,\n        [\"list\", \"%s\" % ORG_NAME, \"--json-output\"],\n    )\n    validate_cliresult(result)\n    assert EXISTING_OWNER in result.output\n\n\ndef _test_team_remove(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_team,\n        [\"remove\", \"%s:%s\" % (ORG_NAME, TEAM_NAME), EXISTING_OWNER],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_team,\n        [\"list\", \"%s\" % ORG_NAME, \"--json-output\"],\n    )\n    validate_cliresult(result)\n    assert EXISTING_OWNER not in result.output\n\n\ndef _test_team_update(clirunner, validate_cliresult, receive_email, isolated_pio_core):\n    new_teamname = \"new-\" + str(random.randint(0, 100000))\n    newteam_description = \"Updated Description\"\n    result = clirunner.invoke(\n        cmd_team,\n        [\n            \"update\",\n            \"%s:%s\" % (ORG_NAME, TEAM_NAME),\n            \"--name\",\n            new_teamname,\n            \"--description\",\n            newteam_description,\n        ],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_team,\n        [\"list\", \"%s\" % ORG_NAME, \"--json-output\"],\n    )\n    validate_cliresult(result)\n    json_result = json.loads(result.output.strip())\n    for item in json_result:\n        del item[\"id\"]\n    assert json_result == [\n        {\"name\": new_teamname, \"description\": newteam_description, \"members\": []}\n    ]\n\n    result = clirunner.invoke(\n        cmd_team,\n        [\n            \"update\",\n            \"%s:%s\" % (ORG_NAME, new_teamname),\n            \"--name\",\n            TEAM_NAME,\n            \"--description\",\n            TEAM_DESCRIPTION,\n        ],\n    )\n    validate_cliresult(result)\n\n\ndef test_cleanup(clirunner, validate_cliresult, receive_email, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_team, [\"destroy\", \"%s:%s\" % (ORG_NAME, TEAM_NAME)], \"y\"\n    )\n    validate_cliresult(result)\n    result = clirunner.invoke(cmd_org, [\"destroy\", ORG_NAME], \"y\")\n    validate_cliresult(result)\n    result = clirunner.invoke(cmd_account, [\"destroy\"], \"y\")\n    validate_cliresult(result)\n"
  },
  {
    "path": "tests/commands/test_boards.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nfrom platformio.commands.boards import cli as cmd_boards\nfrom platformio.commands.platform import platform_search as cmd_platform_search\n\n\ndef test_board_json_output(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_boards, [\"mbed\", \"--json-output\"])\n    validate_cliresult(result)\n    boards = json.loads(result.output)\n    assert isinstance(boards, list)\n    assert any(\"mbed\" in b[\"frameworks\"] for b in boards)\n\n\ndef test_board_raw_output(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_boards, [\"espidf\"])\n    validate_cliresult(result)\n    assert \"espressif32\" in result.output\n\n\ndef test_board_options(clirunner, validate_cliresult):\n    required_opts = set([\"fcpu\", \"frameworks\", \"id\", \"mcu\", \"name\", \"platform\"])\n\n    # fetch available platforms\n    result = clirunner.invoke(cmd_platform_search, [\"--json-output\"])\n    validate_cliresult(result)\n    search_result = json.loads(result.output)\n    assert isinstance(search_result, list)\n    assert search_result\n    platforms = [item[\"name\"] for item in search_result]\n\n    result = clirunner.invoke(cmd_boards, [\"mbed\", \"--json-output\"])\n    validate_cliresult(result)\n    boards = json.loads(result.output)\n\n    for board in boards:\n        assert required_opts.issubset(set(board))\n        assert board[\"platform\"] in platforms\n"
  },
  {
    "path": "tests/commands/test_check.py",
    "content": "# Copyright (c) 2019-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=redefined-outer-name\n\nimport json\nimport os\nimport sys\n\nimport pytest\n\nfrom platformio import fs\nfrom platformio.check.cli import cli as cmd_check\n\nDEFAULT_CONFIG = \"\"\"\n[env:native]\nplatform = native\n\"\"\"\n\nTEST_CODE = \"\"\"\n#include <stdlib.h>\n\nvoid run_defects() {\n    /* Freeing a pointer twice */\n    int* doubleFreePi = (int*)malloc(sizeof(int));\n    *doubleFreePi=2;\n    free(doubleFreePi);\n    free(doubleFreePi); /* High */\n\n    /* Reading uninitialized memory */\n    int* uninitializedPi = (int*)malloc(sizeof(int));\n    *uninitializedPi++; /* High + Medium*/\n    free(uninitializedPi);\n\n    /* Delete instead of delete [] */\n    int* wrongDeletePi = new int[10];\n    wrongDeletePi++;\n    delete wrongDeletePi; /* High */\n\n    /* Index out of bounds */\n    int arr[10];\n    for(int i=0; i < 11; i++) {\n        arr[i] = 0; /* High */\n    }\n}\n\nint main() {\n    int uninitializedVar; /* Low */\n    run_defects();\n}\n\"\"\"\n\n\nPVS_STUDIO_FREE_LICENSE_HEADER = \"\"\"\n// This is an open source non-commercial project. Dear PVS-Studio, please check it.\n// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com\n\"\"\"\n\nEXPECTED_ERRORS = 5\nEXPECTED_WARNINGS = 1\nEXPECTED_STYLE = 4\nEXPECTED_DEFECTS = EXPECTED_ERRORS + EXPECTED_WARNINGS + EXPECTED_STYLE\n\n\n@pytest.fixture(scope=\"module\")\ndef check_dir(tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(TEST_CODE)\n    return tmpdir\n\n\ndef count_defects(output):\n    error, warning, style = 0, 0, 0\n    for line in output.split(\"\\n\"):\n        if \"[high:error]\" in line:\n            error += 1\n        elif \"[medium:warning]\" in line:\n            warning += 1\n        elif \"[low:style]\" in line:\n            style += 1\n    return error, warning, style\n\n\ndef test_check_cli_output(clirunner, validate_cliresult, check_dir):\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(check_dir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS\n\n\ndef test_check_json_output(clirunner, validate_cliresult, check_dir):\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(check_dir), \"--json-output\"]\n    )\n    validate_cliresult(result)\n\n    output = json.loads(result.stdout.strip())\n\n    assert isinstance(output, list)\n    assert len(output[0].get(\"defects\", [])) == EXPECTED_DEFECTS\n\n\ndef test_check_tool_defines_passed(clirunner, check_dir):\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(check_dir), \"--verbose\"])\n    output = result.output\n\n    assert \"PLATFORMIO=\" in output\n    assert \"__GNUC__\" in output\n\n\ndef test_check_tool_complex_defines_handled(\n    clirunner, validate_cliresult, tmpdir_factory\n):\n    project_dir = tmpdir_factory.mktemp(\"project_dir\")\n\n    project_dir.join(\"platformio.ini\").write(DEFAULT_CONFIG + R\"\"\"\ncheck_tool = cppcheck, clangtidy, pvs-studio\nbuild_flags =\n    -DEXTERNAL_INCLUDE_FILE=\\\"test.h\\\"\n    \"-DDEFINE_WITH_SPACE=\"Hello World!\"\"\n\"\"\")\n\n    src_dir = project_dir.mkdir(\"src\")\n    src_dir.join(\"test.h\").write(\"\"\"\n#ifndef TEST_H\n#define TEST_H\n#define ARBITRARY_CONST_VALUE 10\n#endif\n\"\"\")\n\n    src_dir.join(\"main.c\").write(PVS_STUDIO_FREE_LICENSE_HEADER + \"\"\"\n#if !defined(EXTERNAL_INCLUDE_FILE)\n#error \"EXTERNAL_INCLUDE_FILE is not declared!\"\n#else\n#include EXTERNAL_INCLUDE_FILE\n#endif\n\nint main()\n{\n    /* Index out of bounds */\n    int arr[ARBITRARY_CONST_VALUE];\n    for(int i=0; i < ARBITRARY_CONST_VALUE+1; i++) {\n        arr[i] = 0; /* High */\n    }\n    return 0;\n}\n\"\"\")\n\n    default_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(project_dir)])\n    validate_cliresult(default_result)\n\n\ndef test_check_language_standard_definition_passed(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\\nbuild_flags = -std=c++17\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(TEST_CODE)\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert \"__cplusplus=201703L\" in result.output\n    assert \"--std=c++17\" in result.output\n\n\ndef test_check_language_standard_option_is_converted(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\"\"\nbuild_flags = -std=gnu++1y\n    \"\"\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(TEST_CODE)\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert \"--std=c++14\" in result.output\n\n\ndef test_check_language_standard_is_prioritized_over_build_flags(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\"\"\ncheck_flags = --std=c++03\nbuild_flags = -std=c++17\n    \"\"\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(TEST_CODE)\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert \"--std=c++03\" in result.output\n    assert \"--std=c++17\" not in result.output\n\n\ndef test_check_language_standard_for_c_language(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\\nbuild_flags = -std=c11\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(TEST_CODE)\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert \"--std=c11\" in result.output\n    assert \"__STDC_VERSION__=201112L\" in result.output\n    assert \"__cplusplus\" not in result.output\n\n\ndef test_check_severity_threshold(clirunner, validate_cliresult, check_dir):\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(check_dir), \"--severity=high\"]\n    )\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors == EXPECTED_ERRORS\n    assert warnings == 0\n    assert style == 0\n\n\ndef test_check_includes_passed(clirunner, check_dir):\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(check_dir), \"--verbose\"])\n\n    inc_count = 0\n    for line in result.output.split(\"\\n\"):\n        if line.startswith(\"Includes:\"):\n            inc_count = line.count(\"-I\")\n\n    # at least 1 include path for default mode\n    assert inc_count > 0\n\n\ndef test_check_silent_mode(clirunner, validate_cliresult, check_dir):\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(check_dir), \"--silent\"])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors == EXPECTED_ERRORS\n    assert warnings == 0\n    assert style == 0\n\n\ndef test_check_no_source_files(clirunner, tmpdir):\n    tmpdir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n    tmpdir.mkdir(\"src\")\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert result.exit_code != 0\n    assert errors == 0\n    assert warnings == 0\n    assert style == 0\n\n\ndef test_check_bad_flag_passed(clirunner, check_dir):\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(check_dir), '\"--flags=--UNKNOWN\"']\n    )\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert result.exit_code != 0\n    assert errors == 0\n    assert warnings == 0\n    assert style == 0\n\n\ndef test_check_success_if_no_errors(clirunner, validate_cliresult, tmpdir):\n    tmpdir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\n#include <stdlib.h>\n\nvoid unused_function(){\n    int unusedVar = 0;\n    int* iP = &unusedVar;\n    *iP++;\n}\n\nint main() {\n}\n\"\"\")\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert \"[PASSED]\" in result.output\n    assert errors == 0\n    assert warnings == 1\n    assert style == 1\n\n\ndef test_check_individual_flags_passed(clirunner, validate_cliresult, tmpdir):\n    config = DEFAULT_CONFIG + \"\\ncheck_tool = cppcheck, clangtidy, pvs-studio\"\n    config += \"\"\"\\ncheck_flags =\n    cppcheck: --std=c++11\n    clangtidy: --fix-errors\n    pvs-studio: --analysis-mode=4\n\"\"\"\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(\n        PVS_STUDIO_FREE_LICENSE_HEADER + TEST_CODE\n    )\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n    validate_cliresult(result)\n\n    clang_flags_found = cppcheck_flags_found = pvs_flags_found = False\n    for l in result.output.split(\"\\n\"):\n        if \"--fix\" in l and \"clang-tidy\" in l and \"--std=c++11\" not in l:\n            clang_flags_found = True\n        elif \"--std=c++11\" in l and \"cppcheck\" in l and \"--fix\" not in l:\n            cppcheck_flags_found = True\n        elif (\n            \"--analysis-mode=4\" in l and \"pvs-studio\" in l.lower() and \"--fix\" not in l\n        ):\n            pvs_flags_found = True\n\n    assert clang_flags_found\n    assert cppcheck_flags_found\n    assert pvs_flags_found\n\n\ndef test_check_cppcheck_misra_addon(clirunner, validate_cliresult, tmpdir_factory):\n    check_dir = tmpdir_factory.mktemp(\"project\")\n    check_dir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n    check_dir.mkdir(\"src\").join(\"main.c\").write(TEST_CODE)\n    check_dir.join(\"misra.json\").write(\"\"\"\n{\n    \"script\": \"addons/misra.py\",\n    \"args\": [\"--rule-texts=rules.txt\"]\n}\n\"\"\")\n\n    check_dir.join(\"rules.txt\").write(\"\"\"\nAppendix A Summary of guidelines\nRule 3.1 Required\nR3.1 text.\nRule 4.1 Required\nR4.1 text.\nRule 10.4 Mandatory\nR10.4 text.\nRule 11.5 Advisory\nR11.5 text.\nRule 15.5 Advisory\nR15.5 text.\nRule 15.6 Required\nR15.6 text.\nRule 17.7 Required\nR17.7 text.\nRule 20.1 Advisory\nR20.1 text.\nRule 21.3 Required\nR21.3 Found MISRA defect\nRule 21.4\nR21.4 text.\n\"\"\")\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(check_dir), \"--flags=--addon=misra.json\"]\n    )\n\n    validate_cliresult(result)\n    assert \"R21.3 Found MISRA defect\" in result.output\n    assert not os.path.isfile(os.path.join(str(check_dir), \"src\", \"main.cpp.dump\"))\n\n\ndef test_check_fails_on_defects_only_with_flag(clirunner, validate_cliresult, tmpdir):\n    config = DEFAULT_CONFIG + \"\\ncheck_tool = cppcheck, clangtidy\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(TEST_CODE)\n\n    default_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n\n    result_with_flag = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"--fail-on-defect=high\"]\n    )\n\n    validate_cliresult(default_result)\n    assert result_with_flag.exit_code != 0\n\n\ndef test_check_fails_on_defects_only_on_specified_level(\n    clirunner, validate_cliresult, tmpdir\n):\n    config = DEFAULT_CONFIG + \"\\ncheck_tool = cppcheck, clangtidy\"\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\n#include <stdlib.h>\n\nvoid unused_function(){\n    int unusedVar = 0;\n    int* iP = &unusedVar;\n    *iP++;\n}\n\nint main() {\n}\n\"\"\")\n\n    high_result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"--fail-on-defect=high\"]\n    )\n    validate_cliresult(high_result)\n\n    low_result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"--fail-on-defect=low\"]\n    )\n\n    assert low_result.exit_code != 0\n\n\ndef test_check_pvs_studio_free_license(clirunner, tmpdir):\n    config = \"\"\"\n[env:test]\nplatform = teensy\nboard = teensy35\nframework = arduino\ncheck_tool = pvs-studio\n\"\"\"\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(PVS_STUDIO_FREE_LICENSE_HEADER + TEST_CODE)\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"--fail-on-defect=high\", \"-v\"]\n    )\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert result.exit_code != 0\n    assert errors != 0\n    assert warnings != 0\n    assert style == 0\n\n\ndef test_check_pvs_studio_fails_without_license(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\\ncheck_tool = pvs-studio\"\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(TEST_CODE)\n\n    default_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    verbose_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert default_result.exit_code != 0\n    assert \"failed to perform check\" in default_result.output.lower()\n\n    assert verbose_result.exit_code != 0\n    assert \"license was not entered\" in verbose_result.output.lower()\n\n\n@pytest.mark.skipif(\n    sys.platform != \"win32\",\n    reason=\"For some reason the error message is different on Windows\",\n)\ndef test_check_pvs_studio_fails_broken_license(clirunner, tmpdir):\n    config = DEFAULT_CONFIG + \"\"\"\ncheck_tool = pvs-studio\ncheck_flags = --lic-file=./pvs-studio.lic\n\"\"\"\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(TEST_CODE)\n    tmpdir.join(\"pvs-studio.lic\").write(\"\"\"\nTEST\nTEST-TEST-TEST-TEST\n\"\"\")\n\n    default_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    verbose_result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir), \"-v\"])\n\n    assert default_result.exit_code != 0\n    assert \"failed to perform check\" in default_result.output.lower()\n\n    assert verbose_result.exit_code != 0\n    assert \"license information is incorrect\" in verbose_result.output.lower()\n\n\n@pytest.mark.parametrize(\"framework\", [\"arduino\", \"stm32cube\", \"zephyr\"])\n@pytest.mark.parametrize(\"check_tool\", [\"cppcheck\", \"clangtidy\", \"pvs-studio\"])\ndef test_check_embedded_platform_all_tools(\n    clirunner, validate_cliresult, tmpdir, framework, check_tool\n):\n    config = f\"\"\"\n[env:test]\nplatform = ststm32\nboard = nucleo_f401re\nframework = {framework}\ncheck_tool = {check_tool}\n\"\"\"\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(PVS_STUDIO_FREE_LICENSE_HEADER + \"\"\"\n#include <stdlib.h>\n\nvoid unused_function(int val){\n    int unusedVar = 0;\n    int* iP = &unusedVar;\n    *iP++;\n}\n\nint main() {\n}\n\"\"\")\n\n    if framework == \"zephyr\":\n        zephyr_dir = tmpdir.mkdir(\"zephyr\")\n        zephyr_dir.join(\"prj.conf\").write(\"# nothing here\")\n        zephyr_dir.join(\"CMakeLists.txt\").write(\n            \"\"\"cmake_minimum_required(VERSION 3.16.0)\nfind_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})\nproject(hello_world)\ntarget_sources(app PRIVATE ../src/main.c)\"\"\"\n        )\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n    defects = sum(count_defects(result.output))\n    assert defects > 0, \"Not defects were found!\"\n\n\ndef test_check_skip_includes_from_packages(clirunner, validate_cliresult, tmpdir):\n    config = \"\"\"\n[env:test]\nplatform = nordicnrf52\nboard = nrf52_dk\nframework = arduino\n\"\"\"\n\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(TEST_CODE)\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"--skip-packages\", \"-v\"]\n    )\n    validate_cliresult(result)\n\n    project_path = fs.to_unix_path(str(tmpdir))\n    for line in result.output.split(\"\\n\"):\n        if not line.startswith(\"Includes:\"):\n            continue\n        for inc in line.split(\" \"):\n            if inc.startswith(\"-I\") and project_path not in inc:\n                pytest.fail(\"Detected an include path from packages: \" + inc)\n\n\ndef test_check_multiline_error(clirunner, tmpdir_factory):\n    project_dir = tmpdir_factory.mktemp(\"project\")\n    project_dir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n\n    project_dir.mkdir(\"include\").join(\"main.h\").write(\"\"\"\n#error This is a multiline error message \\\\\nthat should be correctly reported \\\\\nin both default and verbose modes.\n\"\"\")\n\n    project_dir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\n#include <stdlib.h>\n#include \"main.h\"\n\nint main() {}\n\"\"\")\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(project_dir)])\n    errors, _, _ = count_defects(result.output)\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(project_dir), \"-v\"])\n    verbose_errors, _, _ = count_defects(result.output)\n\n    assert verbose_errors == errors == 1\n\n\n@pytest.mark.parametrize(\"check_tool\", [\"cppcheck\", \"clangtidy\", \"pvs-studio\"])\ndef test_check_handles_spaces_in_paths(\n    clirunner, validate_cliresult, tmpdir_factory, check_tool\n):\n    package_dir_with_spaces = tmpdir_factory.mktemp(\"pio pkg dir\")\n    project_dir_with_spaces = tmpdir_factory.mktemp(\"project dir\")\n    config = f\"\"\"\n[platformio]\n; redirect toolchain and tool packages to a directory with whitespaces\npackages_dir = {package_dir_with_spaces}\n\n[env:test]\nplatform = atmelsam\nboard = adafruit_feather_m0\nframework = arduino\ncheck_tool = {check_tool}\n\"\"\"\n    project_dir_with_spaces.join(\"platformio.ini\").write(config)\n    project_dir_with_spaces.mkdir(\"src\").join(\"main.cpp\").write(\n        PVS_STUDIO_FREE_LICENSE_HEADER + TEST_CODE\n    )\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(project_dir_with_spaces), \"-v\"]\n    )\n\n    validate_cliresult(result)\n\n    # Make sure toolchain defines were successfully extracted\n    if check_tool != \"pvs-studio\":\n        # PVS doesn't write defines to stdout\n        assert \"__GNUC__\" in result.output\n\n\n#\n# Files filtering functionality\n#\n\n\n@pytest.mark.parametrize(\n    \"src_filter,number_of_checked_files\",\n    [\n        ([\"+<src/app.cpp>\"], 1),\n        ([\"+<tests/*.cpp>\"], 1),\n        ([\"+<src>\", \"-<src/*.cpp>\"], 2),\n        ([\"-<*> +<src/main.cpp> +<src/uart/uart.cpp> +<src/spi/spi.cpp>\"], 3),\n    ],\n    ids=[\"Single file\", \"Glob pattern\", \"Exclude pattern\", \"Filter as string\"],\n)\ndef test_check_src_filter(\n    clirunner,\n    validate_cliresult,\n    tmpdir_factory,\n    src_filter,\n    number_of_checked_files,\n):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n\n    src_dir = tmpdir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(TEST_CODE)\n    src_dir.join(\"app.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"uart\").join(\"uart.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"spi\").join(\"spi.cpp\").write(TEST_CODE)\n    tmpdir.mkdir(\"tests\").join(\"test.cpp\").write(TEST_CODE)\n\n    cmd_args = [\"--project-dir\", str(tmpdir)] + [\n        \"--src-filters=%s\" % f for f in src_filter\n    ]\n\n    result = clirunner.invoke(cmd_check, cmd_args)\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS * number_of_checked_files\n\n\ndef test_check_src_filter_from_config(clirunner, validate_cliresult, tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n\n    config = DEFAULT_CONFIG + \"\"\"\ncheck_src_filters =\n    +<src/spi/*.c*>\n    +<tests/test.cpp>\n    \"\"\"\n    tmpdir.join(\"platformio.ini\").write(config)\n\n    src_dir = tmpdir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"spi\").join(\"spi.cpp\").write(TEST_CODE)\n    tmpdir.mkdir(\"tests\").join(\"test.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS * 2\n    assert \"main.cpp\" not in result.output\n\n\ndef test_check_custom_pattern_absolute_path_legacy(\n    clirunner, validate_cliresult, tmpdir_factory\n):\n    project_dir = tmpdir_factory.mktemp(\"project\")\n    project_dir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n\n    check_dir = tmpdir_factory.mktemp(\"custom_src_dir\")\n    check_dir.join(\"main.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(project_dir), \"--pattern=\" + str(check_dir)]\n    )\n\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors == EXPECTED_ERRORS\n    assert warnings == EXPECTED_WARNINGS\n    assert style == EXPECTED_STYLE\n\n\ndef test_check_custom_pattern_relative_path_legacy(\n    clirunner, validate_cliresult, tmpdir_factory\n):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(DEFAULT_CONFIG)\n\n    src_dir = tmpdir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"uart\").join(\"uart.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"spi\").join(\"spi.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(\n        cmd_check,\n        [\"--project-dir\", str(tmpdir), \"--pattern=src/uart\", \"--pattern=src/spi\"],\n    )\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS * 2\n\n\ndef test_check_src_filter_from_config_legacy(\n    clirunner, validate_cliresult, tmpdir_factory\n):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n\n    config = DEFAULT_CONFIG + \"\"\"\ncheck_patterns =\n    src/spi/*.c*\n    tests/test.cpp\n    \"\"\"\n    tmpdir.join(\"platformio.ini\").write(config)\n\n    src_dir = tmpdir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"spi\").join(\"spi.cpp\").write(TEST_CODE)\n    tmpdir.mkdir(\"tests\").join(\"test.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS * 2\n    assert \"main.cpp\" not in result.output\n\n\ndef test_check_src_filter_multiple_envs(clirunner, validate_cliresult, tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n\n    config = \"\"\"\n[env]\ncheck_tool = cppcheck\ncheck_src_filters =\n    +<src/*>\n\n[env:check_sources]\nplatform = native\n\n[env:check_tests]\nplatform = native\ncheck_src_filters =\n    +<test/*>\n    \"\"\"\n    tmpdir.join(\"platformio.ini\").write(config)\n\n    src_dir = tmpdir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(TEST_CODE)\n    src_dir.mkdir(\"spi\").join(\"spi.cpp\").write(TEST_CODE)\n    tmpdir.mkdir(\"test\").join(\"test.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(\n        cmd_check, [\"--project-dir\", str(tmpdir), \"-e\", \"check_tests\"]\n    )\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert errors + warnings + style == EXPECTED_DEFECTS\n    assert \"test.cpp\" in result.output\n    assert \"main.cpp\" not in result.output\n\n\ndef test_check_sources_in_project_root(clirunner, validate_cliresult, tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n\n    config = \"\"\"\n[platformio]\nsrc_dir = ./\n    \"\"\" + DEFAULT_CONFIG\n    tmpdir.join(\"platformio.ini\").write(config)\n    tmpdir.join(\"main.cpp\").write(TEST_CODE)\n    tmpdir.mkdir(\"spi\").join(\"uart.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert result.exit_code == 0\n    assert errors + warnings + style == EXPECTED_DEFECTS * 2\n\n\ndef test_check_sources_in_external_dir(clirunner, validate_cliresult, tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    external_src_dir = tmpdir_factory.mktemp(\"external_src_dir\")\n\n    config = f\"\"\"\n[platformio]\nsrc_dir = {external_src_dir}\n    \"\"\" + DEFAULT_CONFIG\n    tmpdir.join(\"platformio.ini\").write(config)\n    external_src_dir.join(\"main.cpp\").write(TEST_CODE)\n\n    result = clirunner.invoke(cmd_check, [\"--project-dir\", str(tmpdir)])\n    validate_cliresult(result)\n\n    errors, warnings, style = count_defects(result.output)\n\n    assert result.exit_code == 0\n    assert errors + warnings + style == EXPECTED_DEFECTS\n"
  },
  {
    "path": "tests/commands/test_ci.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom os.path import isfile, join\n\nfrom platformio.commands.ci import cli as cmd_ci\nfrom platformio.package.commands.install import package_install_cmd\n\n\ndef test_ci_empty(clirunner):\n    result = clirunner.invoke(cmd_ci)\n    assert result.exit_code != 0\n    assert \"Invalid value: Missing argument 'src'\" in result.output\n\n\ndef test_ci_boards(clirunner, validate_cliresult):\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(\"examples\", \"wiring-blink\", \"src\", \"main.cpp\"),\n            \"-b\",\n            \"uno\",\n            \"-b\",\n            \"leonardo\",\n        ],\n    )\n    validate_cliresult(result)\n\n\ndef test_ci_build_dir(clirunner, tmpdir_factory, validate_cliresult):\n    build_dir = str(tmpdir_factory.mktemp(\"ci_build_dir\"))\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(\"examples\", \"wiring-blink\", \"src\", \"main.cpp\"),\n            \"-b\",\n            \"uno\",\n            \"--build-dir\",\n            build_dir,\n        ],\n    )\n    validate_cliresult(result)\n    assert not isfile(join(build_dir, \"platformio.ini\"))\n\n\ndef test_ci_keep_build_dir(clirunner, tmpdir_factory, validate_cliresult):\n    build_dir = str(tmpdir_factory.mktemp(\"ci_build_dir\"))\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(\"examples\", \"wiring-blink\", \"src\", \"main.cpp\"),\n            \"-b\",\n            \"uno\",\n            \"--build-dir\",\n            build_dir,\n            \"--keep-build-dir\",\n        ],\n    )\n    validate_cliresult(result)\n    assert isfile(join(build_dir, \"platformio.ini\"))\n\n    # 2nd attempt\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(\"examples\", \"wiring-blink\", \"src\", \"main.cpp\"),\n            \"-b\",\n            \"metro\",\n            \"--build-dir\",\n            build_dir,\n            \"--keep-build-dir\",\n        ],\n    )\n    validate_cliresult(result)\n\n    assert \"board: uno\" in result.output\n    assert \"board: metro\" in result.output\n\n\ndef test_ci_keep_build_dir_single_src_dir(\n    clirunner, tmpdir_factory, validate_cliresult\n):\n    build_dir = str(tmpdir_factory.mktemp(\"ci_build_dir\"))\n\n    # Run two times to detect possible \"AlreadyExists\" errors\n    for _ in range(2):\n        result = clirunner.invoke(\n            cmd_ci,\n            [\n                join(\"examples\", \"wiring-blink\", \"src\"),\n                \"-b\",\n                \"uno\",\n                \"--build-dir\",\n                build_dir,\n                \"--keep-build-dir\",\n            ],\n        )\n        validate_cliresult(result)\n\n\ndef test_ci_keep_build_dir_nested_src_dirs(\n    clirunner, tmpdir_factory, validate_cliresult\n):\n    build_dir = str(tmpdir_factory.mktemp(\"ci_build_dir\"))\n\n    # Split default Arduino project in two parts\n    src_dir1 = tmpdir_factory.mktemp(\"src_1\")\n    src_dir1.join(\"src1.cpp\").write(\"\"\"\n#include <Arduino.h>\nvoid setup() {}\n\"\"\")\n\n    src_dir2 = tmpdir_factory.mktemp(\"src_2\")\n    src_dir2.join(\"src2.cpp\").write(\"\"\"\n#include <Arduino.h>\nvoid loop() {}\n\"\"\")\n\n    src_dir1 = str(src_dir1)\n    src_dir2 = str(src_dir2)\n\n    # Run two times to detect possible \"AlreadyExists\" errors\n    for _ in range(2):\n        result = clirunner.invoke(\n            cmd_ci,\n            [\n                src_dir1,\n                src_dir2,\n                \"-b\",\n                \"teensy40\",\n                \"--build-dir\",\n                build_dir,\n                \"--keep-build-dir\",\n            ],\n        )\n\n        validate_cliresult(result)\n\n\ndef test_ci_project_conf(clirunner, validate_cliresult):\n    project_dir = join(\"examples\", \"wiring-blink\")\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(project_dir, \"src\", \"main.cpp\"),\n            \"--project-conf\",\n            join(project_dir, \"platformio.ini\"),\n        ],\n    )\n    validate_cliresult(result)\n    assert \"uno\" in result.output\n\n\ndef test_ci_lib_and_board(clirunner, tmpdir_factory, validate_cliresult):\n    storage_dir = str(tmpdir_factory.mktemp(\"lib\"))\n    result = clirunner.invoke(\n        package_install_cmd,\n        [\"--global\", \"--storage-dir\", storage_dir, \"--library\", \"1\"],\n    )\n    validate_cliresult(result)\n\n    result = clirunner.invoke(\n        cmd_ci,\n        [\n            join(\n                storage_dir,\n                \"OneWire\",\n                \"examples\",\n                \"DS2408_Switch\",\n                \"DS2408_Switch.ino\",\n            ),\n            \"-l\",\n            join(storage_dir, \"OneWire\"),\n            \"-b\",\n            \"uno\",\n        ],\n    )\n    validate_cliresult(result)\n"
  },
  {
    "path": "tests/commands/test_init.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\n\nfrom platformio import fs\nfrom platformio.commands.boards import cli as cmd_boards\nfrom platformio.project.commands.init import project_init_cmd\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import ProjectEnvsNotAvailableError\n\n\ndef validate_pioproject(pioproject_dir):\n    pioconf_path = os.path.join(pioproject_dir, \"platformio.ini\")\n    assert os.path.isfile(pioconf_path) and os.path.getsize(pioconf_path) > 0\n    assert os.path.isdir(os.path.join(pioproject_dir, \"src\")) and os.path.isdir(\n        os.path.join(pioproject_dir, \"lib\")\n    )\n\n\ndef test_init_default(clirunner, validate_cliresult):\n    with clirunner.isolated_filesystem():\n        result = clirunner.invoke(project_init_cmd)\n        validate_cliresult(result)\n        validate_pioproject(os.getcwd())\n\n\ndef test_init_duplicated_boards(clirunner, validate_cliresult, tmpdir):\n    project_dir = str(tmpdir.join(\"ext_folder\"))\n    os.makedirs(project_dir)\n\n    with fs.cd(os.path.dirname(project_dir)):\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\n                \"-d\",\n                os.path.basename(project_dir),\n                \"-b\",\n                \"uno\",\n                \"-b\",\n                \"uno\",\n                \"--no-install-dependencies\",\n            ],\n        )\n    validate_cliresult(result)\n    validate_pioproject(project_dir)\n    config = ProjectConfig(os.path.join(project_dir, \"platformio.ini\"))\n    config.validate()\n    assert set(config.sections()) == set([\"env:uno\"])\n\n\ndef test_init_ide_without_board(clirunner, tmpdir):\n    with tmpdir.as_cwd():\n        result = clirunner.invoke(project_init_cmd, [\"--ide\", \"vscode\"])\n        assert result.exit_code != 0\n        assert isinstance(result.exception, ProjectEnvsNotAvailableError)\n\n\ndef test_init_ide_vscode(clirunner, validate_cliresult, tmpdir):\n    with tmpdir.as_cwd():\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\n                \"--ide\",\n                \"vscode\",\n                \"-b\",\n                \"uno\",\n                \"-b\",\n                \"teensy31\",\n                \"--no-install-dependencies\",\n            ],\n        )\n        validate_cliresult(result)\n        validate_pioproject(str(tmpdir))\n        assert all(\n            tmpdir.join(\".vscode\").join(f).check()\n            for f in (\"c_cpp_properties.json\", \"launch.json\")\n        )\n        assert (\n            \"framework-arduino-avr\"\n            in tmpdir.join(\".vscode\").join(\"c_cpp_properties.json\").read()\n        )\n\n        # switch to NodeMCU\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\"--ide\", \"vscode\", \"-b\", \"nodemcuv2\", \"--no-install-dependencies\"],\n        )\n        validate_cliresult(result)\n        validate_pioproject(str(tmpdir))\n        assert (\n            \"framework-arduinoespressif8266\"\n            in tmpdir.join(\".vscode\").join(\"c_cpp_properties.json\").read()\n        )\n\n        # switch to teensy31 via env name\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\"--ide\", \"vscode\", \"-e\", \"teensy31\", \"--no-install-dependencies\"],\n        )\n        validate_cliresult(result)\n        validate_pioproject(str(tmpdir))\n        assert (\n            \"framework-arduinoteensy\"\n            in tmpdir.join(\".vscode\").join(\"c_cpp_properties.json\").read()\n        )\n\n        # switch to the first board\n        result = clirunner.invoke(\n            project_init_cmd, [\"--ide\", \"vscode\", \"--no-install-dependencies\"]\n        )\n        validate_cliresult(result)\n        validate_pioproject(str(tmpdir))\n        assert (\n            \"framework-arduino-avr\"\n            in tmpdir.join(\".vscode\").join(\"c_cpp_properties.json\").read()\n        )\n\n\ndef test_init_ide_eclipse(clirunner, validate_cliresult):\n    with clirunner.isolated_filesystem():\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\"-b\", \"uno\", \"--ide\", \"eclipse\", \"--no-install-dependencies\"],\n        )\n        validate_cliresult(result)\n        validate_pioproject(os.getcwd())\n        assert all(os.path.isfile(f) for f in (\".cproject\", \".project\"))\n\n\ndef test_init_special_board(clirunner, validate_cliresult):\n    with clirunner.isolated_filesystem():\n        result = clirunner.invoke(project_init_cmd, [\"-b\", \"uno\"])\n        validate_cliresult(result)\n        validate_pioproject(os.getcwd())\n\n        result = clirunner.invoke(cmd_boards, [\"Arduino Uno\", \"--json-output\"])\n        validate_cliresult(result)\n        boards = json.loads(result.output)\n\n        config = ProjectConfig(os.path.join(os.getcwd(), \"platformio.ini\"))\n        config.validate()\n\n        expected_result = dict(\n            platform=str(boards[0][\"platform\"]),\n            board=\"uno\",\n            framework=[str(boards[0][\"frameworks\"][0])],\n        )\n        assert config.has_section(\"env:uno\")\n        assert sorted(config.items(env=\"uno\", as_dict=True).items()) == sorted(\n            expected_result.items()\n        )\n\n\ndef test_init_enable_auto_uploading(clirunner, validate_cliresult):\n    with clirunner.isolated_filesystem():\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\n                \"-b\",\n                \"uno\",\n                \"--project-option\",\n                \"targets=upload\",\n                \"--no-install-dependencies\",\n            ],\n        )\n        validate_cliresult(result)\n        validate_pioproject(os.getcwd())\n        config = ProjectConfig(os.path.join(os.getcwd(), \"platformio.ini\"))\n        config.validate()\n        expected_result = dict(\n            targets=[\"upload\"], platform=\"atmelavr\", board=\"uno\", framework=[\"arduino\"]\n        )\n        assert config.has_section(\"env:uno\")\n        assert sorted(config.items(env=\"uno\", as_dict=True).items()) == sorted(\n            expected_result.items()\n        )\n\n\ndef test_init_custom_framework(clirunner, validate_cliresult):\n    with clirunner.isolated_filesystem():\n        result = clirunner.invoke(\n            project_init_cmd,\n            [\n                \"-b\",\n                \"teensy31\",\n                \"--project-option\",\n                \"framework=mbed\",\n                \"--no-install-dependencies\",\n            ],\n        )\n        validate_cliresult(result)\n        validate_pioproject(os.getcwd())\n        config = ProjectConfig(os.path.join(os.getcwd(), \"platformio.ini\"))\n        config.validate()\n        expected_result = dict(platform=\"teensy\", board=\"teensy31\", framework=[\"mbed\"])\n        assert config.has_section(\"env:teensy31\")\n        assert sorted(config.items(env=\"teensy31\", as_dict=True).items()) == sorted(\n            expected_result.items()\n        )\n\n\ndef test_init_incorrect_board(clirunner):\n    result = clirunner.invoke(project_init_cmd, [\"-b\", \"missed_board\"])\n    assert result.exit_code == 2\n    assert \"Error: Invalid value for\" in result.output\n    assert isinstance(result.exception, SystemExit)\n"
  },
  {
    "path": "tests/commands/test_lib.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport json\nimport os\n\nimport pytest\nimport semantic_version\n\nfrom platformio.commands.lib import cli as cmd_lib\nfrom platformio.package.meta import PackageType\nfrom platformio.package.vcsclient import VCSClientFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.registry.client import RegistryClient\n\n\ndef test_saving_deps(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory):\n    regclient = RegistryClient()\n    project_dir = tmpdir_factory.mktemp(\"project\")\n    project_dir.join(\"platformio.ini\").write(\"\"\"\n[env]\nlib_deps = ArduinoJson\n\n[env:one]\nboard = devkit\n\n[env:two]\nframework = foo\nlib_deps =\n    CustomLib\n    ArduinoJson @ 6.18.5\n\"\"\")\n    result = clirunner.invoke(\n        cmd_lib,\n        [\"-d\", str(project_dir), \"install\", \"64\", \"knolleary/PubSubClient@~2.7\"],\n    )\n    validate_cliresult(result)\n    aj_pkg_data = regclient.get_package(PackageType.LIBRARY, \"bblanchon\", \"ArduinoJson\")\n    config = ProjectConfig(os.path.join(str(project_dir), \"platformio.ini\"))\n    assert sorted(config.get(\"env:one\", \"lib_deps\")) == sorted(\n        [\n            \"bblanchon/ArduinoJson@^%s\" % aj_pkg_data[\"version\"][\"name\"],\n            \"knolleary/PubSubClient@~2.7\",\n        ]\n    )\n    assert sorted(config.get(\"env:two\", \"lib_deps\")) == sorted(\n        [\n            \"CustomLib\",\n            \"bblanchon/ArduinoJson@^%s\" % aj_pkg_data[\"version\"][\"name\"],\n            \"knolleary/PubSubClient@~2.7\",\n        ]\n    )\n\n    # ensure \"build\" version without NPM spec\n    result = clirunner.invoke(\n        cmd_lib,\n        [\"-d\", str(project_dir), \"-e\", \"one\", \"install\", \"mbed-sam-grove/LinkedList\"],\n    )\n    validate_cliresult(result)\n    ll_pkg_data = regclient.get_package(\n        PackageType.LIBRARY, \"mbed-sam-grove\", \"LinkedList\"\n    )\n    config = ProjectConfig(os.path.join(str(project_dir), \"platformio.ini\"))\n    assert sorted(config.get(\"env:one\", \"lib_deps\")) == sorted(\n        [\n            \"bblanchon/ArduinoJson@^%s\" % aj_pkg_data[\"version\"][\"name\"],\n            \"knolleary/PubSubClient@~2.7\",\n            \"mbed-sam-grove/LinkedList@%s\" % ll_pkg_data[\"version\"][\"name\"],\n        ]\n    )\n\n    # check external package via Git repo\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-d\",\n            str(project_dir),\n            \"-e\",\n            \"one\",\n            \"install\",\n            \"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3\",\n        ],\n    )\n    validate_cliresult(result)\n    config = ProjectConfig(os.path.join(str(project_dir), \"platformio.ini\"))\n    assert len(config.get(\"env:one\", \"lib_deps\")) == 4\n    assert config.get(\"env:one\", \"lib_deps\")[3] == (\n        \"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3\"\n    )\n\n    # test uninstalling\n    # from all envs\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(project_dir), \"uninstall\", \"ArduinoJson\"]\n    )\n    validate_cliresult(result)\n    # from \"one\" env\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-d\",\n            str(project_dir),\n            \"-e\",\n            \"one\",\n            \"uninstall\",\n            \"knolleary/PubSubClient@~2.7\",\n        ],\n    )\n    validate_cliresult(result)\n    config = ProjectConfig(os.path.join(str(project_dir), \"platformio.ini\"))\n    assert len(config.get(\"env:one\", \"lib_deps\")) == 2\n    assert len(config.get(\"env:two\", \"lib_deps\")) == 2\n    assert config.get(\"env:one\", \"lib_deps\") == [\n        \"mbed-sam-grove/LinkedList@%s\" % ll_pkg_data[\"version\"][\"name\"],\n        \"https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3 @ 0.8.3\",\n    ]\n    assert config.get(\"env:two\", \"lib_deps\") == [\n        \"CustomLib\",\n        \"knolleary/PubSubClient@~2.7\",\n    ]\n\n    # test list\n    result = clirunner.invoke(cmd_lib, [\"-d\", str(project_dir), \"list\"])\n    validate_cliresult(result)\n    assert \"AsyncMqttClient-esphome @ 0.8.3+sha.f5aa899\" in result.stdout\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(project_dir), \"list\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    data = {}\n    for key, value in json.loads(result.stdout).items():\n        data[os.path.basename(key)] = value\n    ame_lib = next(\n        item for item in data[\"one\"] if item[\"name\"] == \"AsyncMqttClient-esphome\"\n    )\n    ame_vcs = VCSClientFactory.new(ame_lib[\"__pkg_dir\"], ame_lib[\"__src_url\"])\n    assert len(data[\"two\"]) == 1\n    assert data[\"two\"][0][\"name\"] == \"PubSubClient\"\n    assert \"__pkg_dir\" in data[\"one\"][0]\n    assert (\n        ame_lib[\"__src_url\"]\n        == \"git+https://github.com/OttoWinter/async-mqtt-client.git#v0.8.3\"\n    )\n    assert ame_lib[\"version\"] == (\"0.8.3+sha.%s\" % ame_vcs.get_current_revision())\n\n\ndef test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory):\n    storage_dir = tmpdir_factory.mktemp(\"test-updates\")\n    result = clirunner.invoke(\n        cmd_lib,\n        [\"-d\", str(storage_dir), \"install\", \"ArduinoJson @ 6.18.5\", \"Blynk @ ~1.2\"],\n    )\n    validate_cliresult(result)\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(storage_dir), \"update\", \"--dry-run\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    outdated = json.loads(result.stdout)\n    assert len(outdated) == 2\n    # ArduinoJson\n    assert outdated[0][\"version\"] == \"6.18.5\"\n    assert outdated[0][\"versionWanted\"] is None\n    assert semantic_version.Version(\n        outdated[0][\"versionLatest\"]\n    ) > semantic_version.Version(\"6.18.5\")\n    # Blynk\n    assert outdated[1][\"version\"] == \"1.2.0\"\n    assert outdated[1][\"versionWanted\"] is None\n    assert semantic_version.Version(\n        outdated[1][\"versionLatest\"]\n    ) > semantic_version.Version(\"1.2.0\")\n\n    # check with spec\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-d\",\n            str(storage_dir),\n            \"update\",\n            \"--dry-run\",\n            \"--json-output\",\n            \"ArduinoJson @ ^6\",\n        ],\n    )\n    validate_cliresult(result)\n    outdated = json.loads(result.stdout)\n    assert outdated[0][\"version\"] == \"6.18.5\"\n    assert outdated[0][\"versionWanted\"] == \"6.21.5\"\n    assert semantic_version.Version(\n        outdated[0][\"versionLatest\"]\n    ) > semantic_version.Version(\"6.16.0\")\n    # update with spec\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(storage_dir), \"update\", \"--silent\", \"ArduinoJson @ ^6.18.5\"]\n    )\n    validate_cliresult(result)\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(storage_dir), \"list\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    items = json.loads(result.stdout)\n    assert len(items) == 2\n    assert items[0][\"version\"] == \"6.21.5\"\n    assert items[1][\"version\"] == \"1.2.0\"\n\n    # Check incompatible\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(storage_dir), \"update\", \"--dry-run\", \"ArduinoJson @ ^6\"]\n    )\n    with pytest.raises(\n        AssertionError,\n        match=\"This command is deprecated\",\n    ):\n        validate_cliresult(result)\n    result = clirunner.invoke(\n        cmd_lib, [\"-d\", str(storage_dir), \"update\", \"ArduinoJson @ ^6\"]\n    )\n    validate_cliresult(result)\n    assert \"ArduinoJson@6.21.5 is already up-to-date\" in result.stdout\n"
  },
  {
    "path": "tests/commands/test_lib_complex.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=line-too-long\n\nimport json\nimport re\n\nfrom platformio.cli import PlatformioCLI\nfrom platformio.commands.lib import cli as cmd_lib\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.util import strip_ansi_codes\n\nPlatformioCLI.leftover_args = [\"--json-output\"]  # hook for click\nARDUINO_JSON_VERSION = \"6.21.5\"\n\n\ndef test_search(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"search\", \"DHT22\"])\n    validate_cliresult(result)\n    match = re.search(r\"Found\\s+(\\d+)\\spackages\", result.output)\n    assert int(match.group(1)) > 2\n\n    result = clirunner.invoke(cmd_lib, [\"search\", \"DHT22\", \"--platform=timsp430\"])\n    validate_cliresult(result)\n    match = re.search(r\"Found\\s+(\\d+)\\spackages\", result.output)\n    assert int(match.group(1)) > 1\n\n\ndef test_global_install_registry(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"64\",\n            \"ArduinoJson@~6\",\n            \"547@2.7.3\",\n            \"AsyncMqttClient@<=0.8.2\",\n            \"Adafruit PN532@1.3.2\",\n        ],\n    )\n    validate_cliresult(result)\n\n    # install unknown library\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"install\", \"Unknown\"])\n    assert result.exit_code != 0\n    assert isinstance(result.exception, UnknownPackageError)\n\n    items1 = [d.basename for d in isolated_pio_core.join(\"lib\").listdir()]\n    items2 = [\n        \"ArduinoJson\",\n        f\"ArduinoJson@{ARDUINO_JSON_VERSION}\",\n        \"NeoPixelBus\",\n        \"AsyncMqttClient\",\n        \"ESPAsyncTCP\",\n        \"AsyncTCP\",\n        \"Adafruit PN532\",\n        \"Adafruit BusIO\",\n    ]\n    assert set(items1) == set(items2)\n\n\ndef test_global_install_archive(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip\",\n            \"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2\",\n            \"SomeLib=https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.11.0/DallasTemperature-3.11.0.tar.gz\",\n            \"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip\",\n        ],\n    )\n    validate_cliresult(result)\n\n    # incorrect requirements\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3\",\n        ],\n    )\n    assert result.exit_code != 0\n\n    items1 = [d.basename for d in isolated_pio_core.join(\"lib\").listdir()]\n    items2 = [\n        \"ArduinoJson\",\n        \"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81\",\n        \"SomeLib\",\n        \"OneWire\",\n        \"ESP32WebServer\",\n    ]\n    assert set(items1) >= set(items2)\n\n\ndef test_global_install_repository(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"https://github.com/gioblu/PJON.git#3.0\",\n            \"https://github.com/gioblu/PJON.git#6.2\",\n            \"https://github.com/bblanchon/ArduinoJson.git\",\n            \"https://github.com/platformio/platformio-libmirror.git\",\n            # \"https://developer.mbed.org/users/simon/code/TextLCD/\",\n            \"https://github.com/knolleary/pubsubclient#bef58148582f956dfa772687db80c44e2279a163\",\n        ],\n    )\n    validate_cliresult(result)\n    items1 = [d.basename for d in isolated_pio_core.join(\"lib\").listdir()]\n    items2 = [\n        \"PJON\",\n        \"PJON@src-79de467ebe19de18287becff0a1fb42d\",\n        \"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81\",\n        \"platformio-libmirror\",\n        \"PubSubClient\",\n    ]\n    assert set(items1) >= set(items2)\n\n\ndef test_install_duplicates(  # pylint: disable=unused-argument\n    clirunner, validate_cliresult, without_internet\n):\n    # registry\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.11.0/DallasTemperature-3.11.0.tar.gz\",\n        ],\n    )\n    validate_cliresult(result)\n    assert \"is already installed\" in result.output\n\n    # archive\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"install\",\n            \"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip\",\n        ],\n    )\n    validate_cliresult(result)\n    assert \"is already installed\" in result.output\n\n    # repository\n    result = clirunner.invoke(\n        cmd_lib,\n        [\"-g\", \"install\", \"https://github.com/platformio/platformio-libmirror.git\"],\n    )\n    validate_cliresult(result)\n    assert \"is already installed\" in result.output\n\n\ndef test_global_lib_list(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"list\"])\n    validate_cliresult(result)\n    assert all(\n        n in result.output\n        for n in (\n            \"required: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip\",\n            f\"ArduinoJson @ {ARDUINO_JSON_VERSION}\",\n            \"required: git+https://github.com/gioblu/PJON.git#3.0\",\n            \"PJON @ 3.0.0+sha.1fb26f\",\n        )\n    ), result.output\n\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"list\", \"--json-output\"])\n    assert all(\n        n in result.output\n        for n in (\n            \"__pkg_dir\",\n            '\"__src_url\": \"git+https://github.com/gioblu/PJON.git#6.2\"',\n            f'\"version\": \"{ARDUINO_JSON_VERSION}\"',\n        )\n    )\n    items1 = [i[\"name\"] for i in json.loads(result.output)]\n    items2 = [\n        \"Adafruit BusIO\",\n        \"Adafruit PN532\",\n        \"ArduinoJson\",\n        \"ArduinoJson\",\n        \"ArduinoJson\",\n        \"ArduinoJson\",\n        \"AsyncMqttClient\",\n        \"AsyncTCP\",\n        \"DallasTemperature\",\n        \"ESP32WebServer\",\n        \"ESPAsyncTCP\",\n        \"NeoPixelBus\",\n        \"OneWire\",\n        \"PJON\",\n        \"PJON\",\n        \"platformio-libmirror\",\n        \"PubSubClient\",\n    ]\n    assert sorted(items1) == sorted(items2)\n\n    versions1 = [\n        \"{name}@{version}\".format(**item) for item in json.loads(result.output)\n    ]\n    versions2 = [\n        \"ArduinoJson@5.8.2\",\n        f\"ArduinoJson@{ARDUINO_JSON_VERSION}\",\n        \"AsyncMqttClient@0.8.2\",\n        \"NeoPixelBus@2.7.3\",\n        \"PJON@6.2.0+sha.07fe9aa\",\n        \"PJON@3.0.0+sha.1fb26fd\",\n        \"PubSubClient@2.6.0+sha.bef5814\",\n        \"Adafruit PN532@1.3.2\",\n    ]\n    assert set(versions1) >= set(versions2)\n\n\ndef test_global_lib_update_check(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"update\", \"--dry-run\", \"--json-output\"])\n    validate_cliresult(result)\n    output = json.loads(result.output)\n    assert set(\n        [\"Adafruit PN532\", \"AsyncMqttClient\", \"AsyncTCP\", \"ESPAsyncTCP\", \"NeoPixelBus\"]\n    ) == set(lib[\"name\"] for lib in output)\n\n\ndef test_global_lib_update(clirunner, validate_cliresult):\n    # update library using package directory\n    result = clirunner.invoke(\n        cmd_lib, [\"-g\", \"update\", \"NeoPixelBus\", \"--dry-run\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    oudated = json.loads(result.output)\n    assert len(oudated) == 1\n    assert \"__pkg_dir\" in oudated[0]\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"update\", oudated[0][\"__pkg_dir\"]])\n    validate_cliresult(result)\n    assert \"Removing NeoPixelBus @ 2.7.3\" in strip_ansi_codes(result.output)\n\n    # update all libraries\n    result = clirunner.invoke(\n        cmd_lib,\n        [\"-g\", \"update\", \"adafruit/Adafruit PN532\", \"marvinroger/AsyncMqttClient\"],\n    )\n    validate_cliresult(result)\n\n    # update unknown library\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"update\", \"Unknown\"])\n    assert result.exit_code != 0\n    assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_global_lib_uninstall(clirunner, validate_cliresult, isolated_pio_core):\n    # uninstall using package directory\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"list\", \"--json-output\"])\n    validate_cliresult(result)\n    items = json.loads(result.output)\n    items = sorted(items, key=lambda item: item[\"__pkg_dir\"])\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"uninstall\", items[0][\"__pkg_dir\"]])\n    validate_cliresult(result)\n    assert \"Removing %s\" % items[0][\"name\"] in strip_ansi_codes(result.output)\n\n    # uninstall the rest libraries\n    result = clirunner.invoke(\n        cmd_lib,\n        [\n            \"-g\",\n            \"uninstall\",\n            \"OneWire\",\n            \"https://github.com/bblanchon/ArduinoJson.git\",\n            \"ArduinoJson@!=5.6.7\",\n            \"Adafruit PN532\",\n        ],\n    )\n    validate_cliresult(result)\n\n    items1 = [d.basename for d in isolated_pio_core.join(\"lib\").listdir()]\n    items2 = [\n        \"AsyncMqttClient\",\n        \"platformio-libmirror\",\n        \"PubSubClient\",\n        \"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81\",\n        \"ESPAsyncTCP@1.2.0\",\n        \"AsyncTCP\",\n        \"ArduinoJson\",\n        \"ESPAsyncTCP\",\n        \"ESP32WebServer\",\n        \"PJON\",\n        \"NeoPixelBus\",\n        \"PJON@src-79de467ebe19de18287becff0a1fb42d\",\n        \"SomeLib\",\n    ]\n    assert set(items1) == set(items2)\n\n    # uninstall unknown library\n    result = clirunner.invoke(cmd_lib, [\"-g\", \"uninstall\", \"Unknown\"])\n    assert result.exit_code != 0\n    assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_lib_show(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"show\", \"64\"])\n    validate_cliresult(result)\n    assert all(s in result.output for s in (\"ArduinoJson\", \"Arduino\"))\n    result = clirunner.invoke(cmd_lib, [\"show\", \"OneWire\", \"--json-output\"])\n    validate_cliresult(result)\n    assert \"OneWire\" in result.output\n\n\ndef test_lib_builtin(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"builtin\"])\n    validate_cliresult(result)\n    result = clirunner.invoke(cmd_lib, [\"builtin\", \"--json-output\"])\n    validate_cliresult(result)\n\n\ndef test_lib_stats(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_lib, [\"stats\", \"--json-output\"])\n    validate_cliresult(result)\n    assert set(\n        [\n            \"dlweek\",\n            \"added\",\n            \"updated\",\n            \"topkeywords\",\n            \"dlmonth\",\n            \"dlday\",\n            \"lastkeywords\",\n        ]\n    ) == set(json.loads(result.output).keys())\n"
  },
  {
    "path": "tests/commands/test_platform.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport json\nimport os\n\nfrom platformio.commands import platform as cli_platform\nfrom platformio.package.exception import UnknownPackageError\nfrom platformio.util import strip_ansi_codes\n\n\ndef test_search_json_output(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.platform_search, [\"arduino\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    search_result = json.loads(result.output)\n    assert isinstance(search_result, list)\n    assert search_result\n    platforms = [item[\"name\"] for item in search_result]\n    assert \"atmelsam\" in platforms\n\n\ndef test_search_raw_output(clirunner, validate_cliresult):\n    result = clirunner.invoke(cli_platform.platform_search, [\"arduino\"])\n    validate_cliresult(result)\n    assert \"atmelavr\" in result.output\n\n\ndef test_install_unknown_version(clirunner):\n    result = clirunner.invoke(cli_platform.platform_install, [\"atmelavr@99.99.99\"])\n    assert result.exit_code != 0\n    assert isinstance(result.exception, UnknownPackageError)\n\n\ndef test_install_unknown_from_registry(clirunner):\n    result = clirunner.invoke(cli_platform.platform_install, [\"unknown-platform\"])\n    assert result.exit_code != 0\n    assert isinstance(result.exception, UnknownPackageError)\n\n\n# def test_install_incompatbile(clirunner, validate_cliresult, isolated_pio_core):\n#     result = clirunner.invoke(\n#         cli_platform.platform_install, [\"atmelavr@1.2.0\", \"--skip-default-package\"],\n#     )\n#     assert result.exit_code != 0\n#     assert isinstance(result.exception, IncompatiblePlatform)\n\n\ndef test_install_core_3_dev_platform(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.platform_install,\n        [\"atmelavr@2.2.0\", \"--skip-default-package\"],\n    )\n    assert result.exit_code == 0\n\n\ndef test_install_known_version(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.platform_install,\n        [\"atmelavr@4.2.0\", \"--skip-default-package\", \"--with-package\", \"tool-avrdude\"],\n    )\n    validate_cliresult(result)\n    output = strip_ansi_codes(result.output)\n    assert \"atmelavr@4.2.0\" in output\n    assert not os.path.isdir(str(isolated_pio_core.join(\"packages\")))\n\n\ndef test_install_from_vcs(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.platform_install,\n        [\n            \"https://github.com/platformio/platform-espressif8266.git\",\n            \"--skip-default-package\",\n        ],\n    )\n    validate_cliresult(result)\n    assert \"espressif8266\" in result.output\n    assert not os.path.isdir(str(isolated_pio_core.join(\"packages\")))\n\n\ndef test_list_json_output(clirunner, validate_cliresult):\n    result = clirunner.invoke(cli_platform.platform_list, [\"--json-output\"])\n    validate_cliresult(result)\n    list_result = json.loads(result.output)\n    assert isinstance(list_result, list)\n    assert list_result\n    platforms = [item[\"name\"] for item in list_result]\n    assert set([\"atmelavr\", \"espressif8266\"]) == set(platforms)\n\n\ndef test_list_raw_output(clirunner, validate_cliresult):\n    result = clirunner.invoke(cli_platform.platform_list)\n    validate_cliresult(result)\n    assert all(s in result.output for s in (\"atmelavr\", \"espressif8266\"))\n\n\ndef test_update_check(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.package_install_cmd,\n        [\"--global\", \"--tool\", \"platformio/tool-avrdude@~1.60300.0\"],\n    )\n    validate_cliresult(result)\n    result = clirunner.invoke(\n        cli_platform.platform_update, [\"--dry-run\", \"--json-output\"]\n    )\n    validate_cliresult(result)\n    output = json.loads(result.output)\n    assert len(output) == 1\n    assert output[0][\"name\"] == \"atmelavr\"\n    assert len(isolated_pio_core.join(\"packages\").listdir()) == 1\n\n\ndef test_update_raw(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(cli_platform.platform_update, [\"atmelavr\"])\n    validate_cliresult(result)\n    output = strip_ansi_codes(result.output)\n    assert \"Removing atmelavr @ 4.2.0\" in output\n    assert \"Platform Manager: Installing platformio/atmelavr @\" in output\n    assert len(isolated_pio_core.join(\"packages\").listdir()) == 2\n\n\ndef test_uninstall(clirunner, validate_cliresult, isolated_pio_core):\n    result = clirunner.invoke(\n        cli_platform.platform_uninstall, [\"atmelavr@2.2.0\", \"atmelavr\", \"espressif8266\"]\n    )\n    validate_cliresult(result)\n    assert not isolated_pio_core.join(\"platforms\").listdir()\n"
  },
  {
    "path": "tests/commands/test_run.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom pathlib import Path\n\nfrom platformio.run.cli import cli as cmd_run\n\n\ndef test_generic_build(clirunner, validate_cliresult, tmpdir):\n    build_flags = [\n        (\"-D TEST_INT=13\", \"-DTEST_INT=13\"),\n        (\"-DTEST_SINGLE_MACRO\", \"-DTEST_SINGLE_MACRO\"),\n        ('-DTEST_STR_SPACE=\"Andrew Smith\"', '\"-DTEST_STR_SPACE=Andrew Smith\"'),\n        (\"-Iinclude\", \"-Iinclude\"),\n        (\"-include cpppath-include.h\", \"cpppath-include.h\"),\n        (\"-Iextra_inc\", \"-Iextra_inc\"),\n        (\"-Inon-existing-dir\", \"non-existing-dir\"),\n        (\n            \"-include $PROJECT_DIR/lib/component/component-forced-include.h\",\n            \"component-forced-include.h\",\n        ),\n    ]\n\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\nextra_scripts =\n    pre:pre_script.py\n    post_script.py\nlib_ldf_mode = deep+\nbuild_src_flags = -DI_AM_ONLY_SRC_FLAG\nbuild_flags =\n    ; -DCOMMENTED_MACRO\n    %s ; inline comment\n    \"\"\" % \" \".join([f[0] for f in build_flags]))\n\n    tmpdir.join(\"pre_script.py\").write(\"\"\"\nImport(\"env\")\n\ndef post_prog_action(source, target, env):\n    print(\"post_prog_action is called\")\n\nenv.AddPostAction(\"$PROGPATH\", post_prog_action)\n    \"\"\")\n    tmpdir.join(\"post_script.py\").write(\"\"\"\nImport(\"projenv\")\n\nprojenv.Append(CPPDEFINES=\"POST_SCRIPT_MACRO\")\n    \"\"\")\n\n    tmpdir.mkdir(\"extra_inc\").join(\"foo.h\").write(\"\"\"\n#define FOO\n    \"\"\")\n\n    tmpdir.mkdir(\"src\").join(\"main.cpp\").write(\"\"\"\n#include \"foo.h\"\n\n#ifndef FOO\n#error \"FOO\"\n#endif\n\n#ifdef I_AM_ONLY_SRC_FLAG\n#include <component.h>\n#else\n#error \"I_AM_ONLY_SRC_FLAG\"\n#endif\n\n#if !defined(TEST_INT) || TEST_INT != 13\n#error \"TEST_INT\"\n#endif\n\n#ifndef TEST_STR_SPACE\n#error \"TEST_STR_SPACE\"\n#endif\n\n#ifndef I_AM_COMPONENT\n#error \"I_AM_COMPONENT\"\n#endif\n\n#ifndef POST_SCRIPT_MACRO\n#error \"POST_SCRIPT_MACRO\"\n#endif\n\n#ifndef I_AM_FORCED_COMPONENT_INCLUDE\n#error \"I_AM_FORCED_COMPONENT_INCLUDE\"\n#endif\n\n#ifndef I_AM_FORCED_CPPPATH_INCLUDE\n#error \"I_AM_FORCED_CPPPATH_INCLUDE\"\n#endif\n\n#ifdef COMMENTED_MACRO\n#error \"COMMENTED_MACRO\"\n#endif\n\nint main() {\n}\n\"\"\")\n\n    tmpdir.mkdir(\"include\").join(\"cpppath-include.h\").write(\"\"\"\n#define I_AM_FORCED_CPPPATH_INCLUDE\n\"\"\")\n    component_dir = tmpdir.mkdir(\"lib\").mkdir(\"component\")\n    component_dir.join(\"component.h\").write(\"\"\"\n#define I_AM_COMPONENT\n\n#ifndef I_AM_ONLY_SRC_FLAG\n#error \"I_AM_ONLY_SRC_FLAG\"\n#endif\n\nvoid dummy(void);\n    \"\"\")\n    component_dir.join(\"component.cpp\").write(\"\"\"\n#ifdef I_AM_ONLY_SRC_FLAG\n#error \"I_AM_ONLY_SRC_FLAG\"\n#endif\n\nvoid dummy(void ) {};\n    \"\"\")\n    component_dir.join(\"component-forced-include.h\").write(\"\"\"\n#define I_AM_FORCED_COMPONENT_INCLUDE\n    \"\"\")\n\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(tmpdir), \"--verbose\"])\n    validate_cliresult(result)\n    assert \"post_prog_action is called\" in result.output\n    build_output = result.output[result.output.find(\"Scanning dependencies...\") :]\n    for flag in build_flags:\n        assert flag[1] in build_output, flag\n\n\ndef test_build_unflags(clirunner, validate_cliresult, tmpdir):\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\nbuild_unflags =\n    -DTMP_MACRO_1=45\n    -DTMP_MACRO_3=13\n    -DTMP_MACRO_4\n    -DNON_EXISTING_MACRO\n    -I.\n    -lunknownLib\n    -Os\nbuild_flags =\n    -DTMP_MACRO_3=10\nextra_scripts = pre:extra.py\n\"\"\")\n\n    tmpdir.join(\"extra.py\").write(\"\"\"\nImport(\"env\")\nenv.Append(CPPPATH=\"%s\")\nenv.Append(CPPDEFINES=\"TMP_MACRO_1\")\nenv.Append(CPPDEFINES=[\"TMP_MACRO_2\"])\nenv.Append(CPPDEFINES=[(\"TMP_MACRO_3\", 13)])\nenv.Append(CPPDEFINES=[(\"TMP_MACRO_4\", 4)])\nenv.Append(CCFLAGS=[\"-Os\"])\nenv.Append(LIBS=[\"unknownLib\"])\n    \"\"\" % str(tmpdir))\n\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\n#ifndef TMP_MACRO_1\n#error \"TMP_MACRO_1 should be defined\"\n#endif\n\n#ifndef TMP_MACRO_2\n#error \"TMP_MACRO_2 should be defined\"\n#endif\n\n#if TMP_MACRO_3 != 10\n#error \"TMP_MACRO_3 should be 10\"\n#endif\n\n#ifdef TMP_MACRO_4\n#error \"TMP_MACRO_4 should not be defined\"\n#endif\n\nint main() {\n}\n\"\"\")\n\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(tmpdir), \"--verbose\"])\n    validate_cliresult(result)\n    build_output = result.output[result.output.find(\"Scanning dependencies...\") :]\n    assert \"-DTMP_MACRO1\" not in build_output\n    assert \"-Os\" not in build_output\n    assert str(tmpdir) not in build_output\n\n\ndef test_debug_default_build_flags(clirunner, validate_cliresult, tmpdir):\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\nbuild_type = debug\n\"\"\")\n\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\nint main() {\n}\n\"\"\")\n\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(tmpdir), \"--verbose\"])\n    validate_cliresult(result)\n    build_output = result.output[result.output.find(\"Scanning dependencies...\") :]\n    for line in build_output.split(\"\\n\"):\n        if line.startswith(\"gcc\"):\n            assert all(line.count(flag) == 1 for flag in (\"-Og\", \"-g2\", \"-ggdb2\"))\n            assert all(\n                line.count(\"-%s%d\" % (flag, level)) == 0\n                for flag in (\"O\", \"g\", \"ggdb\")\n                for level in (0, 1, 3)\n            )\n            assert \"-Os\" not in line\n\n\ndef test_debug_custom_build_flags(clirunner, validate_cliresult, tmpdir):\n    custom_debug_build_flags = (\"-O3\", \"-g3\", \"-ggdb3\")\n\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\nbuild_type = debug\ndebug_build_flags = %s\n    \"\"\" % \" \".join(custom_debug_build_flags))\n\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\nint main() {\n}\n\"\"\")\n\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(tmpdir), \"--verbose\"])\n    validate_cliresult(result)\n    build_output = result.output[result.output.find(\"Scanning dependencies...\") :]\n    for line in build_output.split(\"\\n\"):\n        if line.startswith(\"gcc\"):\n            assert all(line.count(f) == 1 for f in custom_debug_build_flags)\n            assert all(\n                line.count(\"-%s%d\" % (flag, level)) == 0\n                for flag in (\"O\", \"g\", \"ggdb\")\n                for level in (0, 1, 2)\n            )\n            assert all(\"-O%s\" % optimization not in line for optimization in (\"g\", \"s\"))\n\n\ndef test_symlinked_libs(clirunner, validate_cliresult, tmp_path: Path):\n    external_pkg_dir = tmp_path / \"External\"\n    external_pkg_dir.mkdir()\n    (external_pkg_dir / \"External.h\").write_text(\"\"\"\n#define EXTERNAL 1\n\"\"\")\n    (external_pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"External\",\n    \"version\": \"1.0.0\"\n}\n\"\"\")\n\n    project_dir = tmp_path / \"project\"\n    src_dir = project_dir / \"src\"\n    src_dir.mkdir(parents=True)\n    (src_dir / \"main.c\").write_text(\"\"\"\n#include <External.h>\n#\n#if !defined(EXTERNAL)\n#error \"EXTERNAL is not defined\"\n#endif\n\nint main() {\n}\n\"\"\")\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\nlib_deps = symlink://../External\n    \"\"\")\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(project_dir)])\n    validate_cliresult(result)\n\n\ndef test_stringification(clirunner, validate_cliresult, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n    src_dir = project_dir / \"src\"\n    src_dir.mkdir(parents=True)\n    (src_dir / \"main.c\").write_text(\"\"\"\n#include <stdio.h>\nint main(void) {\n    printf(\"MACRO_1=<%s>\\\\n\", MACRO_1);\n    printf(\"MACRO_2=<%s>\\\\n\", MACRO_2);\n    printf(\"MACRO_3=<%s>\\\\n\", MACRO_3);\n    printf(\"MACRO_4=<%s>\\\\n\", MACRO_4);\n    return(0);\n}\n\"\"\")\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\nextra_scripts = script.py\nbuild_flags =\n    '-DMACRO_1=\"Hello World!\"'\n    '-DMACRO_2=\"Text is \\\\\\\\\"Quoted\\\\\\\\\"\"'\n    \"\"\")\n    (project_dir / \"script.py\").write_text(\"\"\"\nImport(\"projenv\")\n\nprojenv.Append(CPPDEFINES=[\n    (\"MACRO_3\", projenv.StringifyMacro('Hello \"World\"! Isn\\\\'t true?')),\n    (\"MACRO_4\", projenv.StringifyMacro(\"Special chars: ',(,),[,],:\"))\n])\n    \"\"\")\n    result = clirunner.invoke(\n        cmd_run, [\"--project-dir\", str(project_dir), \"-t\", \"exec\"]\n    )\n    validate_cliresult(result)\n    assert \"MACRO_1=<Hello World!>\" in result.output\n    assert 'MACRO_2=<Text is \"Quoted\">' in result.output\n    assert 'MACRO_3=<Hello \"World\"! Isn\\'t true?>' in result.output\n    assert \"MACRO_4=<Special chars: ',(,),[,],:>\" in result.output\n\n\ndef test_ldf(clirunner, validate_cliresult, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n\n    # libs\n    lib_dir = project_dir / \"lib\"\n    a_lib_dir = lib_dir / \"a\"\n    a_lib_dir.mkdir(parents=True)\n    (a_lib_dir / \"a.h\").write_text(\"\"\"\n#include <some_from_b.h>\n\"\"\")\n    # b\n    b_lib_dir = lib_dir / \"b\"\n    b_lib_dir.mkdir(parents=True)\n    (b_lib_dir / \"some_from_b.h\").write_text(\"\")\n    # c\n    c_lib_dir = lib_dir / \"c\"\n    c_lib_dir.mkdir(parents=True)\n    (c_lib_dir / \"parse_c_by_name.h\").write_text(\"\"\"\nvoid some_func();\n    \"\"\")\n    (c_lib_dir / \"parse_c_by_name.c\").write_text(\"\"\"\n#include <d.h>\n#include <parse_c_by_name.h>\n\nvoid some_func() {\n}\n    \"\"\")\n    (c_lib_dir / \"some.c\").write_text(\"\"\"\n#include <d.h>\n    \"\"\")\n    # d\n    d_lib_dir = lib_dir / \"d\"\n    d_lib_dir.mkdir(parents=True)\n    (d_lib_dir / \"d.h\").write_text(\"\")\n\n    # project\n    src_dir = project_dir / \"src\"\n    src_dir.mkdir(parents=True)\n    (src_dir / \"main.h\").write_text(\"\"\"\n#include <a.h>\n#include <parse_c_by_name.h>\n\"\"\")\n    (src_dir / \"main.c\").write_text(\"\"\"\n#include <main.h>\n\nint main() {\n}\n\"\"\")\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\n    \"\"\")\n    result = clirunner.invoke(cmd_run, [\"--project-dir\", str(project_dir)])\n    validate_cliresult(result)\n"
  },
  {
    "path": "tests/commands/test_settings.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio import app\nfrom platformio.commands.settings import cli\n\n\ndef test_settings_check(clirunner, validate_cliresult):\n    result = clirunner.invoke(cli, [\"get\"])\n    validate_cliresult(result)\n    assert result.output\n    for item in app.DEFAULT_SETTINGS.items():\n        assert item[0] in result.output\n"
  },
  {
    "path": "tests/commands/test_test.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport shutil\nimport sys\nimport xml.etree.ElementTree as ET\nfrom pathlib import Path\n\nimport pytest\n\nfrom platformio import proc\nfrom platformio.fs import load_json\nfrom platformio.test.cli import cli as pio_test_cmd\n\n\ndef test_calculator_example(tmp_path: Path):\n    junit_output_path = tmp_path / \"junit.xml\"\n    project_dir = tmp_path / \"project\"\n    shutil.copytree(\n        os.path.join(\"examples\", \"unit-testing\", \"calculator\"), str(project_dir)\n    )\n    result = proc.exec_command(\n        [\n            \"platformio\",\n            \"test\",\n            \"-d\",\n            str(project_dir),\n            \"-e\",\n            \"uno\",\n            \"-e\",\n            \"native\",\n            \"--junit-output-path\",\n            str(junit_output_path),\n        ]\n    )\n    assert result[\"returncode\"] != 0\n    # pylint: disable=unsupported-membership-test\n    assert all(\n        s in (result[\"err\"] + result[\"out\"]) for s in (\"ERRORED\", \"PASSED\", \"FAILED\")\n    ), result[\"out\"]\n\n    # test JUnit output\n    junit_testsuites = ET.parse(junit_output_path).getroot()\n    assert int(junit_testsuites.get(\"tests\")) == 11\n    assert int(junit_testsuites.get(\"errors\")) == 2\n    assert int(junit_testsuites.get(\"failures\")) == 1\n    assert len(junit_testsuites.findall(\"testsuite\")) == 6\n    junit_errored_testcase = junit_testsuites.find(\n        \".//testcase[@name='uno:test_embedded']\"\n    )\n    assert junit_errored_testcase.get(\"status\") == \"ERRORED\"\n    assert junit_errored_testcase.find(\"error\").get(\"type\") == \"UnitTestSuiteError\"\n    junit_failed_testcase = junit_testsuites.find(\n        \".//testsuite[@name='native:test_desktop']\"\n        \"/testcase[@name='test_calculator_division']\"\n    )\n    assert junit_failed_testcase.get(\"status\") == \"FAILED\"\n    assert junit_failed_testcase.find(\"failure\").get(\"message\") == \"Expected 32 Was 33\"\n\n\ndef test_list_tests(clirunner, validate_cliresult, tmp_path: Path):\n    json_output_path = tmp_path / \"report.json\"\n    project_dir = tmp_path / \"project\"\n    shutil.copytree(\n        os.path.join(\"examples\", \"unit-testing\", \"calculator\"), str(project_dir)\n    )\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"--list-tests\",\n            \"--json-output-path\",\n            str(json_output_path),\n        ],\n    )\n    validate_cliresult(result)\n    # test JSON\n    json_report = load_json(str(json_output_path))\n    assert json_report[\"testcase_nums\"] == 0\n    assert json_report[\"failure_nums\"] == 0\n    assert json_report[\"skipped_nums\"] == 0\n    assert len(json_report[\"test_suites\"]) == 6\n\n\ndef test_group_and_custom_runner(clirunner, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\ntest_framework = custom\n\"\"\")\n    test_dir = project_dir / \"test\"\n\n    # non-test folder, does not start with \"test_\"\n    disabled_dir = test_dir / \"disabled\"\n    disabled_dir.mkdir(parents=True)\n    (disabled_dir / \"main.c\").write_text(\"\"\"\n#include <stdio.h>\n\nint main() {\n    printf(\"Disabled test suite\\\\n\")\n}\n    \"\"\")\n\n    # root\n    (test_dir / \"my_extra.h\").write_text(\"\"\"\n#ifndef MY_EXTRA_H\n#define MY_EXTRA_H\n\n#include <stdio.h>\n\nvoid my_extra_fun(void);\n#endif\n\"\"\")\n    (test_dir / \"my_extra.c\").write_text(\"\"\"\n#include \"my_extra.h\"\n\nvoid my_extra_fun(void) {\n    printf(\"Called from my_extra_fun\\\\n\");\n}\n\"\"\")\n\n    # test group\n    test_group = test_dir / \"group\"\n    test_group.mkdir(parents=True)\n    (test_group / \"test_custom_runner.py\").write_text(\"\"\"\nimport click\n\nfrom platformio.test.runners.unity import UnityTestRunner\n\nclass CustomTestRunner(UnityTestRunner):\n    def teardown(self):\n        click.echo(\"CustomTestRunner::TearDown called\")\n\"\"\")\n\n    # test suite\n    test_suite_dir = test_group / \"test_nested\"\n    test_include_dir = test_suite_dir / \"include\"\n    test_include_dir.mkdir(parents=True)\n    (test_include_dir / \"my_nested.h\").write_text(\"\"\"\n#define TEST_ONE 1\n\"\"\")\n    (test_suite_dir / \"main.c\").write_text(\"\"\"\n#include <unity.h>\n#include <my_extra.h>\n#include <include/my_nested.h>\n\nvoid setUp(){\n    my_extra_fun();\n}\n\nvoid tearDown(void) {\n    // clean stuff up here\n}\n\nvoid dummy_test_passed(void) {\n    TEST_ASSERT_EQUAL(1, TEST_ONE);\n}\n\nvoid dummy_test_failed(void) {\n    TEST_ASSERT_LESS_THAN(10, 15);\n}\n\nint main() {\n    UNITY_BEGIN();\n    RUN_TEST(dummy_test_passed);\n    RUN_TEST(dummy_test_failed);\n    UNITY_END();\n}\n    \"\"\")\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"native\", \"--verbose\"],\n    )\n    assert result.exit_code != 0\n    assert \"2 Tests 1 Failures 0 Ignored\" in result.output\n    assert \"Called from my_extra_fun\" in result.output\n    assert \"CustomTestRunner::TearDown called\" in result.output\n    assert \"Disabled test suite\" not in result.output\n\n\ndef test_crashed_program(clirunner, tmpdir):\n    project_dir = tmpdir.mkdir(\"project\")\n    project_dir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\n\"\"\")\n    test_dir = project_dir.mkdir(\"test\")\n    test_dir.join(\"test_main.c\").write(\"\"\"\n#include <stdio.h>\n#include <unity.h>\n\nvoid setUp(){\n    printf(\"setUp called\");\n}\nvoid tearDown(){\n    printf(\"tearDown called\");\n}\n\nvoid dummy_test(void) {\n    TEST_ASSERT_EQUAL(1, 1);\n}\n\nint main(int argc, char *argv[]) {\n    printf(\"Address boundary error is %s\", argv[-1]);\n    UNITY_BEGIN();\n    RUN_TEST(dummy_test);\n    UNITY_END();\n    return 0;\n}\n\"\"\")\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"native\"],\n    )\n    assert result.exit_code != 0\n    assert any(\n        s in result.output for s in (\"Program received signal\", \"Program errored with\")\n    )\n\n\n# @pytest.mark.skipif(\n#     sys.platform != \"darwin\", reason=\"runs only on macOS (issue with SimAVR)\"\n# )\n# def test_custom_testing_command(clirunner, validate_cliresult, tmp_path: Path):\n#     project_dir = tmp_path / \"project\"\n#     project_dir.mkdir()\n#     (project_dir / \"platformio.ini\").write_text(\n#         \"\"\"\n# [env:uno]\n# platform = atmelavr\n# framework = arduino\n# board = uno\n\n# platform_packages =\n#     platformio/tool-simavr @ ^1\n# test_speed = 9600\n# test_testing_command =\n#     ${platformio.packages_dir}/tool-simavr/bin/simavr\n#     -m\n#     atmega328p\n#     -f\n#     16000000L\n#     ${platformio.build_dir}/${this.__env__}/firmware.elf\n# \"\"\"\n#     )\n#     test_dir = project_dir / \"test\" / \"test_dummy\"\n#     test_dir.mkdir(parents=True)\n#     (test_dir / \"test_main.cpp\").write_text(\n#         \"\"\"\n# #include <Arduino.h>\n# #include <unity.h>\n\n# void setUp(void) {\n#     // set stuff up here\n# }\n\n# void tearDown(void) {\n#     // clean stuff up here\n# }\n\n# void dummy_test(void) {\n#     TEST_ASSERT_EQUAL(1, 1);\n# }\n\n# void setup() {\n#     UNITY_BEGIN();\n#     RUN_TEST(dummy_test);\n#     UNITY_END();\n# }\n\n# void loop() {\n#     delay(1000);\n# }\n# \"\"\"\n#     )\n#     result = clirunner.invoke(\n#         pio_test_cmd,\n#         [\"-d\", str(project_dir), \"--without-uploading\"],\n#     )\n#     validate_cliresult(result)\n#     assert \"dummy_test\" in result.output\n\n\ndef test_unity_setup_teardown(clirunner, validate_cliresult, tmpdir):\n    project_dir = tmpdir.mkdir(\"project\")\n    project_dir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\n\"\"\")\n    test_dir = project_dir.mkdir(\"test\")\n    test_dir.join(\"test_main.h\").write(\"\"\"\n#include <stdio.h>\n#include <unity.h>\n    \"\"\")\n    test_dir.join(\"test_main.c\").write(\"\"\"\n#include \"test_main.h\"\n\nvoid setUp(){\n    printf(\"setUp called\");\n}\nvoid tearDown(){\n    printf(\"tearDown called\");\n}\n\nvoid dummy_test(void) {\n    TEST_ASSERT_EQUAL(1, 1);\n}\n\nint main() {\n    UNITY_BEGIN();\n    RUN_TEST(dummy_test);\n    UNITY_END();\n}\n\"\"\")\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"native\"],\n    )\n    validate_cliresult(result)\n    assert all(f in result.output for f in (\"setUp called\", \"tearDown called\"))\n\n\ndef test_unity_custom_config(clirunner, validate_cliresult, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\n\"\"\")\n    test_dir = project_dir / \"test\" / \"native\" / \"test_component\"\n    test_dir.mkdir(parents=True)\n    (test_dir.parent / \"unity_config.h\").write_text(\"\"\"\n#include <stdio.h>\n\n#define CUSTOM_UNITY_CONFIG\n\n#define UNITY_OUTPUT_CHAR(c)    putchar(c)\n#define UNITY_OUTPUT_FLUSH()    fflush(stdout)\n\"\"\")\n    (test_dir / \"test_main.c\").write_text(\"\"\"\n#include <stdio.h>\n#include <unity.h>\n\nvoid setUp(){\n#ifdef CUSTOM_UNITY_CONFIG\n    printf(\"Found custom unity_config.h\\\\n\");\n#endif\n}\nvoid tearDown(){\n}\n\nvoid dummy_test(void) {\n    TEST_ASSERT_EQUAL(1, 1);\n}\n\nint main() {\n    UNITY_BEGIN();\n    RUN_TEST(dummy_test);\n    UNITY_END();\n}\n\"\"\")\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\"-d\", str(project_dir), \"-e\", \"native\", \"--verbose\"],\n    )\n    validate_cliresult(result)\n    assert all(f in result.output for f in (\"Found custom unity_config\", \"dummy_test\"))\n\n\ndef test_legacy_unity_custom_transport(clirunner, validate_cliresult, tmpdir):\n    project_dir = tmpdir.mkdir(\"project\")\n    project_dir.join(\"platformio.ini\").write(\"\"\"\n[env:embedded]\nplatform = ststm32\nframework = stm32cube\nboard = nucleo_f401re\ntest_transport = custom\n\"\"\")\n\n    test_dir = project_dir.mkdir(\"test\")\n    test_dir.join(\"test_main.c\").write(\"\"\"\n#include <unity.h>\n\nvoid setUp(void) {\n    // set stuff up here\n}\n\nvoid tearDown(void) {\n    // clean stuff up here\n}\n\nvoid dummy_test(void) {\n    TEST_ASSERT_EQUAL(1, 1);\n}\n\nint main() {\n    UNITY_BEGIN();\n    RUN_TEST(dummy_test);\n    UNITY_END();\n}\n\"\"\")\n    test_dir.join(\"unittest_transport.h\").write(\"\"\"\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid unittest_uart_begin(){}\nvoid unittest_uart_putchar(char c){}\nvoid unittest_uart_flush(){}\nvoid unittest_uart_end(){}\n\n#ifdef __cplusplus\n}\n#endif\n\"\"\")\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"--without-testing\",\n            \"--without-uploading\",\n        ],\n    )\n    validate_cliresult(result)\n\n\n@pytest.mark.skipif(\n    sys.platform == \"win32\" and os.environ.get(\"GITHUB_ACTIONS\") == \"true\",\n    reason=\"skip Github Actions on Windows (MinGW issue)\",\n)\ndef test_doctest_framework(clirunner, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(\"\"\"\n[env:native]\nplatform = native\ntest_framework = doctest\n\"\"\")\n    test_dir = project_dir / \"test\" / \"test_dummy\"\n    test_dir.mkdir(parents=True)\n    (test_dir / \"test_main.cpp\").write_text(\"\"\"\n#define DOCTEST_CONFIG_IMPLEMENT\n#include <doctest.h>\n\nTEST_CASE(\"[math] basic stuff\")\n{\n\tCHECK(6 > 5);\n\tCHECK(6 > 7);\n}\n\nTEST_CASE(\"should be skipped \" * doctest::skip())\n{\n\tCHECK(2 > 5);\n}\n\nTEST_CASE(\"vectors can be sized and resized\")\n{\n\tstd::vector<int> v(5);\n\n\tREQUIRE(v.size() == 5);\n\tREQUIRE(v.capacity() >= 5);\n\n\tSUBCASE(\"adding to the vector increases it's size\")\n\t{\n\t\tv.push_back(1);\n\n\t\tCHECK(v.size() == 6);\n\t\tCHECK(v.capacity() >= 6);\n\t}\n\tSUBCASE(\"reserving increases just the capacity\")\n\t{\n\t\tv.reserve(6);\n\n\t\tCHECK(v.size() == 5);\n\t\tCHECK(v.capacity() >= 6);\n\t}\n}\n\nTEST_CASE(\"WARN level of asserts don't fail the test case\")\n{\n\tWARN(0);\n\tWARN_FALSE(1);\n\tWARN_EQ(1, 0);\n}\n\nTEST_SUITE(\"scoped test suite\")\n{\n\tTEST_CASE(\"part of scoped\")\n\t{\n\t\tFAIL(\"Error message\");\n\t}\n\n\tTEST_CASE(\"part of scoped 2\")\n\t{\n\t\tFAIL(\"\");\n\t}\n}\n\nint main(int argc, char **argv)\n{\n\tdoctest::Context context;\n\tcontext.setOption(\"success\", true);\n\tcontext.setOption(\"no-exitcode\", true);\n\tcontext.applyCommandLine(argc, argv);\n\treturn context.run();\n}\n\"\"\")\n    junit_output_path = tmp_path / \"junit.xml\"\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"--junit-output-path\",\n            str(junit_output_path),\n        ],\n    )\n    assert result.exit_code != 0\n    # test JUnit output\n    junit_testsuites = ET.parse(junit_output_path).getroot()\n    assert int(junit_testsuites.get(\"tests\")) == 8\n    assert int(junit_testsuites.get(\"errors\")) == 0\n    assert int(junit_testsuites.get(\"failures\")) == 3\n    assert len(junit_testsuites.findall(\"testsuite\")) == 1\n    junit_failed_testcase = junit_testsuites.find(\n        \".//testcase[@name='scoped test suite/part of scoped']\"\n    )\n    assert junit_failed_testcase.get(\"status\") == \"FAILED\"\n    assert junit_failed_testcase.find(\"failure\").get(\"message\") == \"Error message\"\n    assert \"TEST SUITE: scoped test suite\" in junit_failed_testcase.find(\"failure\").text\n\n    # test program arguments\n    json_output_path = tmp_path / \"report.json\"\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"--json-output-path\",\n            str(json_output_path),\n            \"-a\",\n            \"-aa=1\",  # fail after the 1 error\n        ],\n    )\n    assert result.exit_code != 0\n    assert \"1 test cases\" in result.output\n    # test JSON\n    json_report = load_json(str(json_output_path))\n    assert json_report[\"testcase_nums\"] == 1\n    assert json_report[\"failure_nums\"] == 1\n\n\n@pytest.mark.skipif(\n    sys.platform == \"win32\" and os.environ.get(\"GITHUB_ACTIONS\") == \"true\",\n    reason=\"skip Github Actions on Windows (MinGW issue)\",\n)\ndef test_googletest_framework(clirunner, tmp_path: Path):\n    project_dir = tmp_path / \"project\"\n    shutil.copytree(\n        os.path.join(\"examples\", \"unit-testing\", \"googletest\"), str(project_dir)\n    )\n    junit_output_path = tmp_path / \"junit.xml\"\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"-e\",\n            \"native\",\n            \"--junit-output-path\",\n            str(junit_output_path),\n        ],\n    )\n    assert result.exit_code != 0\n    # test JUnit output\n    junit_testsuites = ET.parse(junit_output_path).getroot()\n    assert int(junit_testsuites.get(\"tests\")) == 4\n    assert int(junit_testsuites.get(\"errors\")) == 0\n    assert int(junit_testsuites.get(\"failures\")) == 1\n    assert len(junit_testsuites.findall(\"testsuite\")) == 4\n    junit_failed_testcase = junit_testsuites.find(\".//testcase[@name='FooTest.Bar']\")\n    assert junit_failed_testcase.get(\"status\") == \"FAILED\"\n    assert \"test_main.cpp\" in junit_failed_testcase.get(\"file\")\n    assert junit_failed_testcase.get(\"line\") == \"26\"\n    assert junit_failed_testcase.find(\"failure\").get(\"message\") == \"Failure\"\n    assert \"Expected equality\" in junit_failed_testcase.find(\"failure\").text\n\n    # test program arguments\n    json_output_path = tmp_path / \"report.json\"\n    result = clirunner.invoke(\n        pio_test_cmd,\n        [\n            \"-d\",\n            str(project_dir),\n            \"-e\",\n            \"native\",\n            \"--json-output-path\",\n            str(json_output_path),\n            \"-a\",\n            \"--gtest_filter=-FooTest.Bar\",\n        ],\n    )\n    assert result.exit_code == 0\n    # test JSON\n    json_report = load_json(str(json_output_path))\n    assert json_report[\"testcase_nums\"] == 3\n    assert json_report[\"failure_nums\"] == 0\n    assert json_report[\"skipped_nums\"] == 1\n    assert len(json_report[\"test_suites\"]) == 4\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport email\nimport functools\nimport imaplib\nimport os\nimport time\n\nimport pytest\nfrom click.testing import CliRunner\n\nfrom platformio import http\nfrom platformio.package.meta import PackageSpec, PackageType\nfrom platformio.registry.client import RegistryClient\n\n\ndef pytest_configure(config):\n    config.addinivalue_line(\"markers\", \"skip_ci: mark a test that will not run in CI\")\n\n\n@pytest.fixture(scope=\"session\")\ndef validate_cliresult():\n    def decorator(result):\n        assert result.exit_code == 0, \"{} => {}\".format(result.exception, result.output)\n        assert not result.exception, \"{} => {}\".format(result.exception, result.output)\n\n    return decorator\n\n\n@pytest.fixture(scope=\"session\")\ndef clirunner(request, tmpdir_factory):\n    cache_dir = tmpdir_factory.mktemp(\".cache\")\n    backup_env_vars = {\n        \"PLATFORMIO_CACHE_DIR\": {\"new\": str(cache_dir)},\n        \"PLATFORMIO_WORKSPACE_DIR\": {\"new\": None},\n    }\n    for key, item in backup_env_vars.items():\n        # pylint: disable=unnecessary-dict-index-lookup\n        backup_env_vars[key][\"old\"] = os.environ.get(key)\n        if item[\"new\"] is not None:\n            os.environ[key] = item[\"new\"]\n        elif key in os.environ:\n            del os.environ[key]\n\n    def fin():\n        for key, item in backup_env_vars.items():\n            if item[\"old\"] is not None:\n                os.environ[key] = item[\"old\"]\n            elif key in os.environ:\n                del os.environ[key]\n\n    request.addfinalizer(fin)\n\n    return CliRunner()\n\n\ndef _isolated_pio_core(request, tmpdir_factory):\n    core_dir = tmpdir_factory.mktemp(\".platformio\")\n    os.environ[\"PLATFORMIO_CORE_DIR\"] = str(core_dir)\n\n    def fin():\n        if \"PLATFORMIO_CORE_DIR\" in os.environ:\n            del os.environ[\"PLATFORMIO_CORE_DIR\"]\n\n    request.addfinalizer(fin)\n    return core_dir\n\n\n@pytest.fixture(scope=\"module\")\ndef isolated_pio_core(request, tmpdir_factory):\n    return _isolated_pio_core(request, tmpdir_factory)\n\n\n@pytest.fixture(scope=\"function\")\ndef func_isolated_pio_core(request, tmpdir_factory):\n    return _isolated_pio_core(request, tmpdir_factory)\n\n\n@pytest.fixture(scope=\"function\")\ndef without_internet(monkeypatch):\n    monkeypatch.setattr(http, \"_internet_on\", lambda: False)\n\n\n@pytest.fixture\ndef receive_email():  # pylint:disable=redefined-outer-name, too-many-locals\n    def _receive_email(from_who):\n        test_email = os.environ[\"TEST_EMAIL_LOGIN\"]\n        test_password = os.environ[\"TEST_EMAIL_PASSWORD\"]\n        imap_server = os.environ[\"TEST_EMAIL_IMAP_SERVER\"]\n\n        def get_body(msg):\n            if msg.is_multipart():\n                return get_body(msg.get_payload(0))\n            return msg.get_payload(None, True)\n\n        result = None\n        start_time = time.time()\n        while not result:\n            time.sleep(5)\n            server = imaplib.IMAP4_SSL(imap_server)\n            server.login(test_email, test_password)\n            server.select(\"INBOX\")\n            _, mails = server.search(None, \"ALL\")\n            for index in mails[0].split():\n                status, data = server.fetch(index, \"(RFC822)\")\n                if status != \"OK\" or not data or not isinstance(data[0], tuple):\n                    continue\n                msg = email.message_from_string(\n                    data[0][1].decode(\"ASCII\", errors=\"surrogateescape\")\n                )\n                if from_who not in msg.get(\"To\"):\n                    continue\n                if \"gmail\" in imap_server:\n                    server.store(index, \"+X-GM-LABELS\", \"\\\\Trash\")\n                server.store(index, \"+FLAGS\", \"\\\\Deleted\")\n                server.expunge()\n                result = get_body(msg).decode()\n            if time.time() - start_time > 120:\n                break\n            server.close()\n            server.logout()\n        return result\n\n    return _receive_email\n\n\n@pytest.fixture(scope=\"session\")\ndef get_pkg_latest_version():\n    @functools.lru_cache()\n    def wrap(spec, pkg_type=None):\n        if not isinstance(spec, PackageSpec):\n            spec = PackageSpec(spec)\n        pkg_type = pkg_type or PackageType.LIBRARY\n        client = RegistryClient()\n        pkg = client.get_package(pkg_type, spec.owner, spec.name)\n        return pkg[\"version\"][\"name\"]\n\n    return wrap\n"
  },
  {
    "path": "tests/misc/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/misc/ino2cpp/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/misc/ino2cpp/examples/basic/basic.ino",
    "content": "#define SQR(a) \\\n( a * a )\n\ntypedef struct Item item;\nstruct Item {\n   byte  foo[50];\n   int   bar;\n   void (*noob)(item*);\n};\n\n// test callback\nclass Foo {\n\n    public:\n        Foo(void (*function)()) {\n            #warning \"Line number is 16\"\n        }\n\n        bool childFunc() {\n\n        }\n\n};\n\nFoo foo(&fooCallback);\n\n//\n\ntemplate<class T> T Add(T n1, T n2) {\n    return n1 + n2;\n}\n\nvoid setup() {\n    struct Item item1;\n    myFunction(&item1);\n}\n\nvoid loop() {\n\n}\n\nvoid myFunction(struct Item *item) {\n\n}\n\n#warning \"Line number is 46\"\n\nvoid fooCallback(){\n\n}\n\nextern \"C\" {\nvoid some_extern(const char *fmt, ...);\n};\n\nvoid some_extern(const char *fmt, ...) {\n\n}\n\n// юнікод\n"
  },
  {
    "path": "tests/misc/ino2cpp/examples/multifiles/bar.ino",
    "content": "unsigned  int barFunc ()  // my comment\n{\n\treturn 0;\n}"
  },
  {
    "path": "tests/misc/ino2cpp/examples/multifiles/foo.pde",
    "content": "char buf[5];\n\nvoid setup() {\n    fooFunc();\n}\n\nvoid loop() {\n\n}\n\nchar* fooFunc() {\n    return buf;\n}\n"
  },
  {
    "path": "tests/misc/ino2cpp/examples/strmultilines/main.ino",
    "content": "const char headerEndP[] PROGMEM =\n\"<meta name='viewport' content='width=device-width, initial-scale=1'>\\\n<link rel='stylesheet' href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css'>\\\n<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js'></script>\\\n<script src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js'></script></head>\";\n\nvoid setup() {\n\n}\n\nconst char javaScriptPinControlP[] PROGMEM =\n\"<div id='content'></div>\\\n<div id='pin1'></div>\\\n<script>\\\nfunction show()\\\n{\\\n$.ajax({\\\nurl: 'controlstatus',\\\ncache: false,\\\nsuccess: function(html){\\\n$('#content').html(html);\\\n}\\\n});\\\n}\\\nfunction Pin1()\\\n{\\\n$.ajax({\\\ntype: 'POST',\\\nurl: 'control',\\\ndata: '1=1',\\\nsuccess: function(data){\\\nshow();\\\n}\\\n});\\\n}\\\nfunction Auto1()\\\n{\\\n$.ajax({\\\ntype: 'POST',\\\nurl: 'control',\\\ndata: '1=2',\\\nsuccess: function(data){\\\nshow();\\\n}\\\n});\\\n}\\\nfunction Pin2()\\\n{\\\n$.ajax({\\\ntype: 'POST',\\\nurl: 'control',\\\ndata: '2=1',\\\nsuccess: function(data){\\\nshow();\\\n}\\\n});\\\n}\\\nfunction Auto2()\\\n{\\\n$.ajax({\\\ntype: 'POST',\\\nurl: 'control',\\\ndata: '2=2',\\\nsuccess: function(data){\\\nshow();\\\n}\\\n});\\\n}\\\n$(document).ready(function(){\\\nshow();\\\nsetInterval('show()',5000);\\\n});\\\n</script>\";\n\n#warning \"Line 75\"\n\nvoid loop() {\n\n}"
  },
  {
    "path": "tests/misc/ino2cpp/test_ino2cpp.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom os import listdir\nfrom os.path import dirname, isdir, join, normpath\n\nfrom platformio.commands.ci import cli as cmd_ci\n\nEXAMPLES_DIR = normpath(join(dirname(__file__), \"examples\"))\n\n\ndef pytest_generate_tests(metafunc):\n    if \"piotest_dir\" not in metafunc.fixturenames:\n        return\n    test_dirs = []\n    for name in listdir(EXAMPLES_DIR):\n        if isdir(join(EXAMPLES_DIR, name)):\n            test_dirs.append(join(EXAMPLES_DIR, name))\n    test_dirs.sort()\n    metafunc.parametrize(\"piotest_dir\", test_dirs)\n\n\ndef test_example(clirunner, validate_cliresult, piotest_dir):\n    result = clirunner.invoke(cmd_ci, [piotest_dir, \"-b\", \"uno\"])\n    validate_cliresult(result)\n\n\ndef test_warning_line(clirunner, validate_cliresult):\n    result = clirunner.invoke(cmd_ci, [join(EXAMPLES_DIR, \"basic\"), \"-b\", \"uno\"])\n    validate_cliresult(result)\n    assert 'basic.ino:16:14: warning: #warning \"Line number is 16\"' in result.output\n    assert 'basic.ino:46:2: warning: #warning \"Line number is 46\"' in result.output\n    result = clirunner.invoke(\n        cmd_ci, [join(EXAMPLES_DIR, \"strmultilines\"), \"-b\", \"uno\"]\n    )\n    validate_cliresult(result)\n    assert 'main.ino:75:2: warning: #warning \"Line 75\"' in result.output\n"
  },
  {
    "path": "tests/misc/test_maintenance.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nfrom time import time\n\nfrom platformio import app, maintenance\nfrom platformio.__main__ import cli as cli_pio\nfrom platformio.commands import upgrade as cmd_upgrade\n\n\ndef test_check_pio_upgrade(clirunner, isolated_pio_core, validate_cliresult):\n    def _patch_pio_version(version):\n        maintenance.__version__ = version\n        cmd_upgrade.VERSION = version.split(\".\", 3)\n\n    interval = int(app.get_setting(\"check_platformio_interval\")) * 3600 * 24\n    last_check = {\"platformio_upgrade\": time() - interval - 1}\n    origin_version = maintenance.__version__\n\n    # check development version\n    _patch_pio_version(\"3.0.0-a1\")\n    app.set_state_item(\"last_check\", last_check)\n    result = clirunner.invoke(cli_pio, [\"platform\", \"list\"])\n    validate_cliresult(result)\n    assert \"There is a new version\" in result.output\n    assert \"Please upgrade\" in result.output\n\n    # check stable version\n    _patch_pio_version(\"2.11.0\")\n    app.set_state_item(\"last_check\", last_check)\n    result = clirunner.invoke(cli_pio, [\"platform\", \"list\"])\n    validate_cliresult(result)\n    assert \"There is a new version\" in result.output\n    assert \"Please upgrade\" in result.output\n\n    # restore original version\n    _patch_pio_version(origin_version)\n"
  },
  {
    "path": "tests/misc/test_misc.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport pytest\nimport requests\n\nfrom platformio import __check_internet_hosts__, http, proc\nfrom platformio.registry.client import RegistryClient\n\n\ndef test_platformio_cli():\n    result = proc.exec_command([\"pio\", \"--help\"])\n    assert result[\"returncode\"] == 0\n    # pylint: disable=unsupported-membership-test\n    assert \"Usage: pio [OPTIONS] COMMAND [ARGS]...\" in result[\"out\"]\n\n\ndef test_ping_internet_ips():\n    for host in __check_internet_hosts__:\n        requests.get(\"http://%s\" % host, allow_redirects=False, timeout=2)\n\n\ndef test_api_internet_offline(without_internet, isolated_pio_core):\n    regclient = RegistryClient()\n    with pytest.raises(http.InternetConnectionError):\n        regclient.fetch_json_data(\"get\", \"/v3/search\")\n\n\ndef test_api_cache(monkeypatch, isolated_pio_core):\n    regclient = RegistryClient()\n    api_kwargs = {\"method\": \"get\", \"path\": \"/v3/search\", \"x_cache_valid\": \"10s\"}\n    result = regclient.fetch_json_data(**api_kwargs)\n    assert result and \"total\" in result\n    monkeypatch.setattr(http, \"_internet_on\", lambda: False)\n    assert regclient.fetch_json_data(**api_kwargs) == result\n"
  },
  {
    "path": "tests/package/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/package/test_manager.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=unused-argument\n\nimport logging\nimport os\nimport time\nfrom pathlib import Path\nfrom random import random\n\nimport pytest\nimport semantic_version\n\nfrom platformio import fs, util\nfrom platformio.package.exception import (\n    MissingPackageManifestError,\n    UnknownPackageError,\n)\nfrom platformio.package.manager.library import LibraryPackageManager\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.package.manager.tool import ToolPackageManager\nfrom platformio.package.meta import PackageSpec\nfrom platformio.package.pack import PackagePacker\n\n\ndef test_download(isolated_pio_core):\n    url = \"https://github.com/platformio/platformio-core/archive/v4.3.4.zip\"\n    checksum = \"69d59642cb91e64344f2cdc1d3b98c5cd57679b5f6db7accc7707bd4c5d9664a\"\n    lm = LibraryPackageManager()\n    lm.set_log_level(logging.ERROR)\n    archive_path = lm.download(url, checksum)\n    assert fs.calculate_file_hashsum(\"sha256\", archive_path) == checksum\n    lm.cleanup_expired_downloads(random())\n    assert os.path.isfile(archive_path)\n    # test outdated downloads\n    lm.set_download_utime(archive_path, time.time() - lm.DOWNLOAD_CACHE_EXPIRE - 1)\n    lm.cleanup_expired_downloads(random())\n    assert not os.path.isfile(archive_path)\n    # check that key is deleted from DB\n    with open(lm.get_download_usagedb_path(), encoding=\"utf8\") as fp:\n        assert os.path.basename(archive_path) not in fp.read()\n\n\ndef test_find_pkg_root(isolated_pio_core, tmpdir_factory):\n    # has manifest\n    pkg_dir = tmpdir_factory.mktemp(\"package-has-manifest\")\n    root_dir = pkg_dir.join(\"nested\").mkdir().join(\"folder\").mkdir()\n    root_dir.join(\"platform.json\").write(\"\")\n    pm = PlatformPackageManager()\n    found_dir = pm.find_pkg_root(str(pkg_dir), spec=None)\n    assert os.path.realpath(str(root_dir)) == os.path.realpath(found_dir)\n\n    # does not have manifest\n    pkg_dir = tmpdir_factory.mktemp(\"package-does-not-have-manifest\")\n    pkg_dir.join(\"nested\").mkdir().join(\"folder\").mkdir().join(\"readme.txt\").write(\"\")\n    pm = PlatformPackageManager()\n    with pytest.raises(MissingPackageManifestError):\n        pm.find_pkg_root(str(pkg_dir), spec=None)\n\n    # library package without manifest, should find source root\n    pkg_dir = tmpdir_factory.mktemp(\"library-package-without-manifest\")\n    root_dir = pkg_dir.join(\"nested\").mkdir().join(\"folder\").mkdir()\n    root_dir.join(\"src\").mkdir().join(\"main.cpp\").write(\"\")\n    root_dir.join(\"include\").mkdir().join(\"main.h\").write(\"\")\n    assert os.path.realpath(str(root_dir)) == os.path.realpath(\n        LibraryPackageManager.find_library_root(str(pkg_dir))\n    )\n\n    # library manager should create \"library.json\"\n    lm = LibraryPackageManager()\n    spec = PackageSpec(\"custom-name@1.0.0\")\n    pkg_root = lm.find_pkg_root(str(pkg_dir), spec)\n    manifest_path = os.path.join(pkg_root, \"library.json\")\n    assert os.path.realpath(str(root_dir)) == os.path.realpath(pkg_root)\n    assert os.path.isfile(manifest_path)\n    manifest = lm.load_manifest(pkg_root)\n    assert manifest[\"name\"] == \"custom-name\"\n    assert \"0.0.0\" in str(manifest[\"version\"])\n\n\ndef test_build_legacy_spec(isolated_pio_core, tmpdir_factory):\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    pm = PlatformPackageManager(str(storage_dir))\n    # test src manifest\n    pkg1_dir = storage_dir.join(\"pkg-1\").mkdir()\n    pkg1_dir.join(\".pio\").mkdir().join(\".piopkgmanager.json\").write(\"\"\"\n{\n    \"name\": \"StreamSpy-0.0.1.tar\",\n    \"url\": \"https://dl.platformio.org/e8936b7/StreamSpy-0.0.1.tar.gz\",\n    \"requirements\": null\n}\n\"\"\")\n    assert pm.build_legacy_spec(str(pkg1_dir)) == PackageSpec(\n        name=\"StreamSpy-0.0.1.tar\",\n        uri=\"https://dl.platformio.org/e8936b7/StreamSpy-0.0.1.tar.gz\",\n    )\n\n    # without src manifest\n    pkg2_dir = storage_dir.join(\"pkg-2\").mkdir()\n    pkg2_dir.join(\"main.cpp\").write(\"\")\n    with pytest.raises(MissingPackageManifestError):\n        pm.build_legacy_spec(str(pkg2_dir))\n\n    # with package manifest\n    pkg3_dir = storage_dir.join(\"pkg-3\").mkdir()\n    pkg3_dir.join(\"platform.json\").write('{\"name\": \"pkg3\", \"version\": \"1.2.0\"}')\n    assert pm.build_legacy_spec(str(pkg3_dir)) == PackageSpec(name=\"pkg3\")\n\n\ndef test_build_metadata(isolated_pio_core, tmpdir_factory):\n    pm = PlatformPackageManager()\n    vcs_revision = \"a2ebfd7c0f\"\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n\n    # test package without manifest\n    with pytest.raises(MissingPackageManifestError):\n        pm.load_manifest(str(pkg_dir))\n    with pytest.raises(MissingPackageManifestError):\n        pm.build_metadata(str(pkg_dir), PackageSpec(\"MyLib\"))\n\n    # with manifest\n    pkg_dir.join(\"platform.json\").write(\n        '{\"name\": \"Dev-Platform\", \"version\": \"1.2.3-alpha.1\"}'\n    )\n    metadata = pm.build_metadata(str(pkg_dir), PackageSpec(\"owner/platform-name\"))\n    assert metadata.name == \"Dev-Platform\"\n    assert str(metadata.version) == \"1.2.3-alpha.1\"\n\n    # with vcs\n    metadata = pm.build_metadata(\n        str(pkg_dir), PackageSpec(\"owner/platform-name\"), vcs_revision\n    )\n    assert str(metadata.version) == (\"1.2.3-alpha.1+sha.\" + vcs_revision)\n    assert metadata.version.build[1] == vcs_revision\n\n\ndef test_install_from_uri(isolated_pio_core, tmpdir_factory):\n    tmp_dir = tmpdir_factory.mktemp(\"tmp\")\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n\n    # install from local directory\n    src_dir = tmp_dir.join(\"local-lib-dir\").mkdir()\n    src_dir.join(\"main.cpp\").write(\"\")\n    spec = PackageSpec(\"file://%s\" % src_dir)\n    pkg = lm.install(spec)\n    assert os.path.isfile(os.path.join(pkg.path, \"main.cpp\"))\n    manifest = lm.load_manifest(pkg)\n    assert manifest[\"name\"] == \"local-lib-dir\"\n    assert manifest[\"version\"].startswith(\"0.0.0+\")\n    assert spec == pkg.metadata.spec\n\n    # install from local archive\n    src_dir = tmp_dir.join(\"archive-src\").mkdir()\n    root_dir = src_dir.mkdir(\"root\")\n    root_dir.mkdir(\"src\").join(\"main.cpp\").write(\"#include <stdio.h>\")\n    root_dir.join(\"library.json\").write(\n        '{\"name\": \"manifest-lib-name\", \"version\": \"2.0.0\"}'\n    )\n    tarball_path = PackagePacker(str(src_dir)).pack(str(tmp_dir))\n    spec = PackageSpec(\"file://%s\" % tarball_path)\n    pkg = lm.install(spec)\n    assert os.path.isfile(os.path.join(pkg.path, \"src\", \"main.cpp\"))\n    assert pkg == lm.get_package(spec)\n    assert spec == pkg.metadata.spec\n\n    # install from registry\n    src_dir = tmp_dir.join(\"registry-1\").mkdir()\n    src_dir.join(\"library.properties\").write(\"\"\"\nname = wifilib\nversion = 5.2.7\n\"\"\")\n    spec = PackageSpec(\"company/wifilib @ ^5\")\n    pkg = lm.install_from_uri(\"file://%s\" % src_dir, spec)\n    assert str(pkg.metadata.version) == \"5.2.7\"\n\n    # check package folder names\n    lm.memcache_reset()\n    assert [\"local-lib-dir\", \"manifest-lib-name\", \"wifilib\"] == [\n        os.path.basename(pkg.path) for pkg in lm.get_installed()\n    ]\n\n\ndef test_install_from_registry(isolated_pio_core, tmpdir_factory):\n    # Libraries\n    lm = LibraryPackageManager(str(tmpdir_factory.mktemp(\"lib-storage\")))\n    lm.set_log_level(logging.ERROR)\n    # library with dependencies\n    lm.install(\"AsyncMqttClient-esphome @ 0.8.6\")\n    assert len(lm.get_installed()) == 3\n    pkg = lm.get_package(\"AsyncTCP-esphome\")\n    assert pkg.metadata.spec.owner == \"esphome\"\n    assert not lm.get_package(\"non-existing-package\")\n    # mbed library\n    assert lm.install(\"wolfSSL\")\n    assert len(lm.get_installed()) == 4\n    # case sensitive author name\n    assert lm.install(\"DallasTemperature\")\n    assert lm.get_package(\"OneWire\").metadata.version.major >= 2\n    assert len(lm.get_installed()) == 6\n\n    # test conflicted names\n    lm = LibraryPackageManager(str(tmpdir_factory.mktemp(\"conflicted-storage\")))\n    lm.set_log_level(logging.ERROR)\n    lm.install(\"z3t0/IRremote\")\n    lm.install(\"mbed-yuhki50/IRremote\")\n    assert len(lm.get_installed()) == 2\n\n    # Tools\n    tm = ToolPackageManager(str(tmpdir_factory.mktemp(\"tool-storage\")))\n    tm.set_log_level(logging.ERROR)\n    pkg = tm.install(\"platformio/tool-stlink @ ~1.10400.0\")\n    manifest = tm.load_manifest(pkg)\n    assert tm.is_system_compatible(manifest.get(\"system\"))\n    assert util.get_systype() in manifest.get(\"system\", [])\n\n    # Test unknown\n    with pytest.raises(UnknownPackageError):\n        tm.install(\"unknown-package-tool @ 9.1.1\")\n    with pytest.raises(UnknownPackageError):\n        tm.install(\"owner/unknown-package-tool\")\n\n\ndef test_install_lib_depndencies(isolated_pio_core, tmpdir_factory):\n    tmp_dir = tmpdir_factory.mktemp(\"tmp\")\n\n    src_dir = tmp_dir.join(\"lib-with-deps\").mkdir()\n    root_dir = src_dir.mkdir(\"root\")\n    root_dir.mkdir(\"src\").join(\"main.cpp\").write(\"#include <stdio.h>\")\n    root_dir.join(\"library.json\").write(\"\"\"\n{\n  \"name\": \"lib-with-deps\",\n  \"version\": \"2.0.0\",\n  \"dependencies\": [\n    {\n      \"owner\": \"bblanchon\",\n      \"name\": \"ArduinoJson\",\n      \"version\": \"^6.16.1\"\n    },\n    {\n      \"name\": \"external-repo\",\n      \"version\": \"https://github.com/milesburton/Arduino-Temperature-Control-Library.git#4a0ccc1\"\n    }\n  ]\n}\n\"\"\")\n\n    lm = LibraryPackageManager(str(tmpdir_factory.mktemp(\"lib-storage\")))\n    lm.set_log_level(logging.ERROR)\n    lm.install(\"file://%s\" % str(src_dir))\n    installed = lm.get_installed()\n    assert len(installed) == 4\n    assert set([\"external-repo\", \"ArduinoJson\", \"lib-with-deps\", \"OneWire\"]) == set(\n        p.metadata.name for p in installed\n    )\n\n\ndef test_install_force(isolated_pio_core, tmpdir_factory):\n    lm = LibraryPackageManager(str(tmpdir_factory.mktemp(\"lib-storage\")))\n    lm.set_log_level(logging.ERROR)\n    # install #64 ArduinoJson\n    pkg = lm.install(\"64 @ ^5\")\n    assert pkg.metadata.version.major == 5\n    # try install the latest without specification\n    pkg = lm.install(\"64\")\n    assert pkg.metadata.version.major == 5\n    assert len(lm.get_installed()) == 1\n    # re-install the latest\n    pkg = lm.install(64, force=True)\n    assert len(lm.get_installed()) == 1\n    assert pkg.metadata.version.major > 5\n\n\ndef test_symlink(tmp_path: Path):\n    external_pkg_dir = tmp_path / \"External\"\n    external_pkg_dir.mkdir()\n    (external_pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"External\",\n    \"version\": \"1.0.0\"\n}\n\"\"\")\n\n    storage_dir = tmp_path / \"storage\"\n    installed_pkg_dir = storage_dir / \"installed\"\n    installed_pkg_dir.mkdir(parents=True)\n    (installed_pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"Installed\",\n    \"version\": \"1.0.0\"\n}\n\"\"\")\n\n    spec = \"CustomExternal=symlink://%s\" % str(external_pkg_dir)\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    pkg = lm.install(spec)\n    assert os.path.isfile(str(storage_dir / \"CustomExternal.pio-link\"))\n    assert pkg.metadata.name == \"External\"\n    assert pkg.metadata.version.major == 1\n    assert [\"External\", \"Installed\"] == [\n        pkg.metadata.name for pkg in lm.get_installed()\n    ]\n    pkg = lm.get_package(\"External\")\n    assert Path(pkg.path) == external_pkg_dir\n    assert pkg.metadata.spec.uri.startswith(\"symlink://\")\n    assert lm.get_package(spec).metadata.spec.uri.startswith(\"symlink://\")\n\n    # try to update\n    lm.update(pkg)\n\n    # uninstall\n    lm.uninstall(\"External\")\n    assert [\"Installed\"] == [pkg.metadata.name for pkg in lm.get_installed()]\n    # ensure original package was not removed\n    assert external_pkg_dir.is_dir()\n\n    # install again, remove from a disk\n    assert lm.install(\"symlink://%s\" % str(external_pkg_dir))\n    assert os.path.isfile(str(storage_dir / \"External.pio-link\"))\n    assert [\"External\", \"Installed\"] == [\n        pkg.metadata.name for pkg in lm.get_installed()\n    ]\n    fs.rmtree(str(external_pkg_dir))\n    lm.memcache_reset()\n    assert [\"Installed\"] == [pkg.metadata.name for pkg in lm.get_installed()]\n\n\ndef test_scripts(isolated_pio_core, tmp_path: Path):\n    pkg_dir = tmp_path / \"foo\"\n    scripts_dir = pkg_dir / \"scripts\"\n    scripts_dir.mkdir(parents=True)\n    (scripts_dir / \"script.py\").write_text(\"\"\"\nimport sys\nfrom pathlib import Path\n\naction = \"postinstall\" if len(sys.argv) == 1 else sys.argv[1]\nPath(\"%s.flag\" % action).touch()\n\nif action == \"preuninstall\":\n    Path(\"../%s.flag\" % action).touch()\n\"\"\")\n    (pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"foo\",\n    \"version\": \"1.0.0\",\n    \"scripts\": {\n        \"postinstall\": \"scripts/script.py\",\n        \"preuninstall2\": [\"scripts/script.py\", \"preuninstall\"]\n    }\n}\n\"\"\")\n\n    storage_dir = tmp_path / \"storage\"\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    lm.install(\"file://%s\" % str(pkg_dir))\n    assert os.path.isfile(os.path.join(lm.get_package(\"foo\").path, \"postinstall.flag\"))\n    lm.uninstall(\"foo\")\n    (storage_dir / \"preuninstall.flag\").is_file()\n\n\ndef test_install_circular_dependencies(tmp_path: Path):\n    storage_dir = tmp_path / \"storage\"\n    # Foo\n    pkg_dir = storage_dir / \"foo\"\n    pkg_dir.mkdir(parents=True)\n    (pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"Foo\",\n    \"version\": \"1.0.0\",\n    \"dependencies\": {\n        \"Bar\": \"*\"\n    }\n}\n\"\"\")\n    # Bar\n    pkg_dir = storage_dir / \"bar\"\n    pkg_dir.mkdir(parents=True)\n    (pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"Bar\",\n    \"version\": \"1.0.0\",\n    \"dependencies\": {\n        \"Foo\": \"*\"\n    }\n}\n\"\"\")\n\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    assert len(lm.get_installed()) == 2\n\n    # root library\n    pkg_dir = tmp_path / \"root\"\n    pkg_dir.mkdir(parents=True)\n    (pkg_dir / \"library.json\").write_text(\"\"\"\n{\n    \"name\": \"Root\",\n    \"version\": \"1.0.0\",\n    \"dependencies\": {\n        \"Foo\": \"^1.0.0\",\n        \"Bar\": \"^1.0.0\"\n    }\n}\n\"\"\")\n    lm.install(\"file://%s\" % str(pkg_dir))\n\n\ndef test_get_installed(isolated_pio_core, tmpdir_factory):\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    pm = ToolPackageManager(str(storage_dir))\n\n    # VCS package\n    (storage_dir.join(\"pkg-vcs\").mkdir().join(\".git\").mkdir().join(\".piopm\").write(\"\"\"\n{\n  \"name\": \"pkg-via-vcs\",\n  \"spec\": {\n    \"id\": null,\n    \"name\": \"pkg-via-vcs\",\n    \"owner\": null,\n    \"requirements\": null,\n    \"url\": \"git+https://github.com/username/repo.git\"\n  },\n  \"type\": \"tool\",\n  \"version\": \"0.0.0+sha.1ea4d5e\"\n}\n\"\"\"))\n\n    # package without metadata file\n    (\n        storage_dir.join(\"foo@3.4.5\")\n        .mkdir()\n        .join(\"package.json\")\n        .write('{\"name\": \"foo\", \"version\": \"3.4.5\"}')\n    )\n\n    # package with metadata file\n    foo_dir = storage_dir.join(\"foo\").mkdir()\n    foo_dir.join(\"package.json\").write('{\"name\": \"foo\", \"version\": \"3.6.0\"}')\n    foo_dir.join(\".piopm\").write(\"\"\"\n{\n  \"name\": \"foo\",\n  \"spec\": {\n    \"name\": \"foo\",\n    \"owner\": null,\n    \"requirements\": \"^3\"\n  },\n  \"type\": \"tool\",\n  \"version\": \"3.6.0\"\n}\n\"\"\")\n\n    # test \"system\"\n    storage_dir.join(\"pkg-incompatible-system\").mkdir().join(\"package.json\").write(\n        '{\"name\": \"check-system\", \"version\": \"4.0.0\", \"system\": [\"unknown\"]}'\n    )\n    storage_dir.join(\"pkg-compatible-system\").mkdir().join(\"package.json\").write(\n        '{\"name\": \"check-system\", \"version\": \"3.0.0\", \"system\": \"%s\"}'\n        % util.get_systype()\n    )\n\n    # invalid package\n    storage_dir.join(\"invalid-package\").mkdir().join(\"library.json\").write(\n        '{\"name\": \"SomeLib\", \"version\": \"4.0.0\"}'\n    )\n\n    installed = pm.get_installed()\n    assert len(installed) == 4\n    assert set([\"pkg-via-vcs\", \"foo\", \"check-system\"]) == set(\n        p.metadata.name for p in installed\n    )\n    assert str(pm.get_package(\"foo\").metadata.version) == \"3.6.0\"\n    assert str(pm.get_package(\"check-system\").metadata.version) == \"3.0.0\"\n\n\ndef test_uninstall(isolated_pio_core, tmpdir_factory):\n    tmp_dir = tmpdir_factory.mktemp(\"tmp\")\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n\n    # foo @ 1.0.0\n    pkg_dir = tmp_dir.join(\"foo\").mkdir()\n    pkg_dir.join(\"library.json\").write('{\"name\": \"foo\", \"version\": \"1.0.0\"}')\n    foo_1_0_0_pkg = lm.install_from_uri(\"file://%s\" % pkg_dir, \"foo\")\n    # foo @ 1.3.0\n    pkg_dir = tmp_dir.join(\"foo-1.3.0\").mkdir()\n    pkg_dir.join(\"library.json\").write('{\"name\": \"foo\", \"version\": \"1.3.0\"}')\n    lm.install_from_uri(\"file://%s\" % pkg_dir, \"foo\")\n    # bar\n    pkg_dir = tmp_dir.join(\"bar\").mkdir()\n    pkg_dir.join(\"library.json\").write('{\"name\": \"bar\", \"version\": \"1.0.0\"}')\n    bar_pkg = lm.install(\"file://%s\" % pkg_dir)\n\n    assert len(lm.get_installed()) == 3\n    assert os.path.isdir(os.path.join(str(storage_dir), \"foo\"))\n    assert os.path.isdir(os.path.join(str(storage_dir), \"foo@1.0.0\"))\n\n    # check detaching\n    assert lm.uninstall(\"FOO\")\n    assert len(lm.get_installed()) == 2\n    assert os.path.isdir(os.path.join(str(storage_dir), \"foo\"))\n    assert not os.path.isdir(os.path.join(str(storage_dir), \"foo@1.0.0\"))\n\n    # uninstall the rest\n    assert lm.uninstall(foo_1_0_0_pkg.path)\n    assert lm.uninstall(bar_pkg)\n\n    assert not lm.get_installed()\n\n    # test uninstall dependencies\n    assert lm.install(\"AsyncMqttClient-esphome\")\n    assert len(lm.get_installed()) == 3\n    assert lm.uninstall(\"AsyncMqttClient-esphome\", skip_dependencies=True)\n    assert len(lm.get_installed()) == 2\n\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    assert lm.install(\"AsyncMqttClient-esphome\")\n    assert lm.uninstall(\"AsyncMqttClient-esphome\")\n    assert not lm.get_installed()\n\n\ndef test_registry(isolated_pio_core):\n    lm = LibraryPackageManager()\n    lm.set_log_level(logging.ERROR)\n\n    # reveal ID\n    assert lm.reveal_registry_package_id(PackageSpec(id=13)) == 13\n    assert lm.reveal_registry_package_id(PackageSpec(name=\"OneWire\")) == 1\n    with pytest.raises(UnknownPackageError):\n        lm.reveal_registry_package_id(PackageSpec(name=\"/non-existing-package/\"))\n\n    # fetch package data\n    assert lm.fetch_registry_package(PackageSpec(id=1))[\"name\"] == \"OneWire\"\n    assert lm.fetch_registry_package(PackageSpec(name=\"ArduinoJson\"))[\"id\"] == 64\n    assert (\n        lm.fetch_registry_package(\n            PackageSpec(id=13, owner=\"adafruit\", name=\"Renamed library\")\n        )[\"name\"]\n        == \"Adafruit GFX Library\"\n    )\n    with pytest.raises(UnknownPackageError):\n        lm.fetch_registry_package(\n            PackageSpec(owner=\"unknown<>owner\", name=\"/non-existing-package/\")\n        )\n    with pytest.raises(UnknownPackageError):\n        lm.fetch_registry_package(PackageSpec(name=\"/non-existing-package/\"))\n\n\ndef test_update_with_metadata(isolated_pio_core, tmpdir_factory):\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n\n    # test non SemVer in registry\n    pkg = lm.install(\"adafruit/Adafruit NeoPixel @ <1.9\")\n    outdated = lm.outdated(pkg)\n    assert str(outdated.current) == \"1.8.7\"\n    assert outdated.latest > semantic_version.Version(\"1.10.0\")\n\n    pkg = lm.install(\"ArduinoJson @ 6.19.4\")\n    # test latest\n    outdated = lm.outdated(pkg)\n    assert str(outdated.current) == \"6.19.4\"\n    assert outdated.wanted is None\n    assert outdated.latest > outdated.current\n    assert outdated.latest > semantic_version.Version(\"5.99.99\")\n\n    # test wanted\n    outdated = lm.outdated(pkg, PackageSpec(\"ArduinoJson@~6\"))\n    assert str(outdated.current) == \"6.19.4\"\n    assert str(outdated.wanted) == \"6.21.5\"\n    assert outdated.latest > semantic_version.Version(\"6.16.0\")\n\n    # update to the wanted 6.x\n    new_pkg = lm.update(\"ArduinoJson@^6\", PackageSpec(\"ArduinoJson@^6\"))\n    assert str(new_pkg.metadata.version) == \"6.21.5\"\n    # check that old version is removed\n    assert len(lm.get_installed()) == 2\n\n    # update to the latest\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    pkg = lm.update(\"ArduinoJson\")\n    assert pkg.metadata.version == outdated.latest\n\n\ndef test_update_without_metadata(isolated_pio_core, tmpdir_factory):\n    storage_dir = tmpdir_factory.mktemp(\"storage\")\n    storage_dir.join(\"legacy-package\").mkdir().join(\"library.json\").write(\n        '{\"name\": \"AsyncMqttClient-esphome\", \"version\": \"0.8\"}'\n    )\n    storage_dir.join(\"legacy-dep\").mkdir().join(\"library.json\").write(\n        '{\"name\": \"AsyncTCP-esphome\", \"version\": \"1.1.1\"}'\n    )\n    lm = LibraryPackageManager(str(storage_dir))\n    pkg = lm.get_package(\"AsyncMqttClient-esphome\")\n    outdated = lm.outdated(pkg)\n    assert len(lm.get_installed()) == 2\n    assert str(pkg.metadata.version) == \"0.8.0\"\n    assert outdated.latest > semantic_version.Version(\"0.8.0\")\n\n    # update\n    lm = LibraryPackageManager(str(storage_dir))\n    lm.set_log_level(logging.ERROR)\n    new_pkg = lm.update(pkg)\n    assert len(lm.get_installed()) == 4\n    assert new_pkg.metadata.spec.owner == \"heman\"\n"
  },
  {
    "path": "tests/package/test_manifest.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport re\nimport tarfile\n\nimport jsondiff\nimport pytest\n\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.package.manifest import parser\nfrom platformio.package.manifest.schema import ManifestSchema, ManifestValidationError\n\n\ndef test_library_json_parser():\n    contents = \"\"\"\n{\n    \"name\": \"TestPackage\",\n    \"keywords\": \"kw1, KW2, kw3, KW2, kw 4, kw_5, kw-6\",\n    \"headers\": \"include1.h, Include2.hpp\",\n    \"platforms\": [\"atmelavr\", \"espressif\"],\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"http://github.com/username/repo/\"\n    },\n    \"url\": \"http://old.url.format\",\n    \"exclude\": [\".gitignore\", \"tests\"],\n    \"include\": \"mylib\",\n    \"build\": {\n        \"flags\": [\"-DHELLO\"]\n    },\n    \"examples\": [\"examples/*/*.pde\"],\n    \"dependencies\": {\n        \"deps1\": \"1.2.0\",\n        \"deps2\": \"https://github.com/username/package.git\",\n        \"owner/deps3\": \"^2.1.3\"\n    },\n    \"customField\": \"Custom Value\"\n}\n\"\"\"\n    raw_data = parser.LibraryJsonManifestParser(contents).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n    assert not jsondiff.diff(\n        raw_data,\n        {\n            \"name\": \"TestPackage\",\n            \"platforms\": [\"atmelavr\", \"espressif8266\"],\n            \"repository\": {\n                \"type\": \"git\",\n                \"url\": \"https://github.com/username/repo.git\",\n            },\n            \"export\": {\"exclude\": [\".gitignore\", \"tests\"], \"include\": [\"mylib\"]},\n            \"keywords\": [\"kw1\", \"kw2\", \"kw3\", \"kw 4\", \"kw_5\", \"kw-6\"],\n            \"headers\": [\"include1.h\", \"Include2.hpp\"],\n            \"homepage\": \"http://old.url.format\",\n            \"build\": {\"flags\": [\"-DHELLO\"]},\n            \"dependencies\": [\n                {\"name\": \"deps1\", \"version\": \"1.2.0\"},\n                {\"name\": \"deps2\", \"version\": \"https://github.com/username/package.git\"},\n                {\"owner\": \"owner\", \"name\": \"deps3\", \"version\": \"^2.1.3\"},\n            ],\n            \"customField\": \"Custom Value\",\n        },\n    )\n\n    contents = \"\"\"\n{\n    \"keywords\": [\"sound\", \"audio\", \"music\", \"SD\", \"card\", \"playback\"],\n    \"headers\": [\"include 1.h\", \"include Space.hpp\"],\n    \"frameworks\": \"arduino\",\n    \"export\": {\n        \"exclude\": \"audio_samples\"\n    },\n    \"dependencies\": [\n        {\"name\": \"deps1\", \"version\": \"1.0.0\"},\n        {\"owner\": \"owner\", \"name\": \"deps2\", \"version\": \"1.0.0\", \"platforms\": \"*\", \"frameworks\": \"arduino, espidf\"},\n        {\"name\": \"deps3\", \"version\": \"1.0.0\", \"platforms\": [\"ststm32\", \"sifive\"]}\n    ]\n}\n\"\"\"\n    raw_data = parser.LibraryJsonManifestParser(contents).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n    assert not jsondiff.diff(\n        raw_data,\n        {\n            \"keywords\": [\"sound\", \"audio\", \"music\", \"sd\", \"card\", \"playback\"],\n            \"headers\": [\"include 1.h\", \"include Space.hpp\"],\n            \"frameworks\": [\"arduino\"],\n            \"export\": {\"exclude\": [\"audio_samples\"]},\n            \"dependencies\": [\n                {\"name\": \"deps1\", \"version\": \"1.0.0\"},\n                {\n                    \"owner\": \"owner\",\n                    \"name\": \"deps2\",\n                    \"version\": \"1.0.0\",\n                    \"platforms\": [\"*\"],\n                    \"frameworks\": [\"arduino\", \"espidf\"],\n                },\n                {\n                    \"name\": \"deps3\",\n                    \"version\": \"1.0.0\",\n                    \"platforms\": [\"ststm32\", \"sifive\"],\n                },\n            ],\n        },\n    )\n\n    raw_data = parser.LibraryJsonManifestParser(\n        '{\"dependencies\": [\"dep1\", \"dep2\", \"owner/dep3@1.2.3\"]}'\n    ).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n    assert not jsondiff.diff(\n        raw_data,\n        {\n            \"dependencies\": [\n                {\"name\": \"dep1\"},\n                {\"name\": \"dep2\"},\n                {\"name\": \"owner/dep3@1.2.3\"},\n            ],\n        },\n    )\n\n    # broken dependencies\n    with pytest.raises(parser.ManifestParserError):\n        parser.LibraryJsonManifestParser({\"dependencies\": [\"deps1\", \"deps2\"]})\n\n\ndef test_module_json_parser():\n    contents = \"\"\"\n{\n  \"author\": \"Name Surname <name@surname.com>\",\n  \"description\": \"This is Yotta library\",\n  \"homepage\": \"https://yottabuild.org\",\n  \"keywords\": [\n    \"mbed\",\n    \"Yotta\"\n  ],\n  \"licenses\": [\n    {\n      \"type\": \"Apache-2.0\",\n      \"url\": \"https://spdx.org/licenses/Apache-2.0\"\n    }\n  ],\n  \"name\": \"YottaLibrary\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git@github.com:username/repo.git\"\n  },\n  \"version\": \"1.2.3\",\n  \"dependencies\": {\n    \"usefulmodule\": \"^1.2.3\",\n    \"simplelog\": \"ARMmbed/simplelog#~0.0.1\"\n  },\n  \"customField\": \"Custom Value\"\n}\n\"\"\"\n\n    raw_data = parser.ModuleJsonManifestParser(contents).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n    assert not jsondiff.diff(\n        raw_data,\n        {\n            \"name\": \"YottaLibrary\",\n            \"description\": \"This is Yotta library\",\n            \"homepage\": \"https://yottabuild.org\",\n            \"keywords\": [\"mbed\", \"yotta\"],\n            \"license\": \"Apache-2.0\",\n            \"platforms\": [\"*\"],\n            \"frameworks\": [\"mbed\"],\n            \"export\": {\"exclude\": [\"tests\", \"test\", \"*.doxyfile\", \"*.pdf\"]},\n            \"authors\": [{\"email\": \"name@surname.com\", \"name\": \"Name Surname\"}],\n            \"version\": \"1.2.3\",\n            \"repository\": {\"type\": \"git\", \"url\": \"git@github.com:username/repo.git\"},\n            \"dependencies\": [\n                {\n                    \"name\": \"simplelog\",\n                    \"version\": \"ARMmbed/simplelog#~0.0.1\",\n                    \"frameworks\": [\"mbed\"],\n                },\n                {\"name\": \"usefulmodule\", \"version\": \"^1.2.3\", \"frameworks\": [\"mbed\"]},\n            ],\n            \"customField\": \"Custom Value\",\n        },\n    )\n\n\ndef test_library_properties_parser():\n    # Base\n    contents = \"\"\"\nname=TestPackage\nversion=1.2.3\nauthor=SomeAuthor <info AT author.com>, Maintainer Author (nickname) <www.example.com>\nmaintainer=Maintainer Author (nickname) <www.example.com>\nsentence=This is Arduino library\ncategory=Signal Input/Output\ncustomField=Custom Value\ndepends=First Library (=2.0.0), Second Library (>=1.2.0), Third\nignore_empty_field=\nincludes=Arduino.h, Arduino Space.hpp\n\"\"\"\n    raw_data = parser.LibraryPropertiesManifestParser(contents).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n    assert not jsondiff.diff(\n        raw_data,\n        {\n            \"name\": \"TestPackage\",\n            \"version\": \"1.2.3\",\n            \"description\": \"This is Arduino library\",\n            \"sentence\": \"This is Arduino library\",\n            \"frameworks\": [\"arduino\"],\n            \"authors\": [\n                {\"name\": \"SomeAuthor\", \"email\": \"info@author.com\"},\n                {\"name\": \"Maintainer Author\", \"maintainer\": True},\n            ],\n            \"category\": \"Signal Input/Output\",\n            \"keywords\": [\"signal\", \"input\", \"output\"],\n            \"headers\": [\"Arduino.h\", \"Arduino Space.hpp\"],\n            \"includes\": \"Arduino.h, Arduino Space.hpp\",\n            \"customField\": \"Custom Value\",\n            \"depends\": \"First Library (=2.0.0), Second Library (>=1.2.0), Third\",\n            \"dependencies\": [\n                {\n                    \"name\": \"First Library\",\n                    \"version\": \"=2.0.0\",\n                    \"frameworks\": [\"arduino\"],\n                },\n                {\n                    \"name\": \"Second Library\",\n                    \"version\": \">=1.2.0\",\n                    \"frameworks\": [\"arduino\"],\n                },\n                {\"name\": \"Third\", \"frameworks\": [\"arduino\"]},\n            ],\n        },\n    )\n\n    # Platforms ALL\n    data = parser.LibraryPropertiesManifestParser(\n        \"architectures=*\\n\" + contents\n    ).as_dict()\n    assert data[\"platforms\"] == [\"*\"]\n\n    # Platforms specific\n    data = parser.LibraryPropertiesManifestParser(\n        \"architectures=avr, esp32\\n\" + contents\n    ).as_dict()\n    assert data[\"platforms\"] == [\"atmelavr\", \"espressif32\"]\n\n    # Remote URL\n    data = parser.LibraryPropertiesManifestParser(\n        contents,\n        remote_url=(\n            \"https://raw.githubusercontent.com/username/reponame/master/\"\n            \"libraries/TestPackage/library.properties\"\n        ),\n    ).as_dict()\n    assert data[\"export\"] == {\n        \"include\": [\"libraries/TestPackage\"],\n    }\n    assert data[\"repository\"] == {\n        \"url\": \"https://github.com/username/reponame.git\",\n        \"type\": \"git\",\n    }\n\n    # Home page\n    data = parser.LibraryPropertiesManifestParser(\n        \"url=https://github.com/username/reponame.git\\n\" + contents\n    ).as_dict()\n    assert data[\"repository\"] == {\n        \"type\": \"git\",\n        \"url\": \"https://github.com/username/reponame.git\",\n    }\n\n    # Author + Maintainer\n    data = parser.LibraryPropertiesManifestParser(\"\"\"\nauthor=Rocket Scream Electronics <broken-email.com>\nmaintainer=Rocket Scream Electronics\n\"\"\").as_dict()\n    assert data[\"authors\"] == [\n        {\"name\": \"Rocket Scream Electronics\", \"maintainer\": True}\n    ]\n    assert \"keywords\" not in data\n\n\ndef test_library_json_schema():\n    contents = \"\"\"\n{\n  \"name\": \"ArduinoJson\",\n  \"keywords\": \"JSON, rest, http, web\",\n  \"description\": \"An elegant and efficient JSON library for embedded systems\",\n  \"homepage\": \"https://arduinojson.org\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/bblanchon/ArduinoJson.git\"\n  },\n  \"version\": \"6.12.0\",\n  \"authors\": {\n    \"name\": \"Benoit Blanchon\",\n    \"url\": \"https://blog.benoitblanchon.fr\"\n  },\n  \"downloadUrl\": \"https://example.com/package.tar.gz\",\n  \"exclude\": [\n    \"fuzzing\",\n    \"scripts\",\n    \"test\",\n    \"third-party\"\n  ],\n  \"frameworks\": \"arduino\",\n  \"platforms\": \"*\",\n  \"license\": \"MIT\",\n  \"scripts\": {\n      \"postinstall\": \"script.py\"\n  },\n  \"examples\": [\n    {\n        \"name\": \"JsonConfigFile\",\n        \"base\": \"examples/JsonConfigFile\",\n        \"files\": [\"JsonConfigFile.ino\"]\n    },\n    {\n        \"name\": \"JsonHttpClient\",\n        \"base\": \"examples/JsonHttpClient\",\n        \"files\": [\"JsonHttpClient.ino\"]\n    }\n  ],\n  \"dependencies\": [\n    {\"name\": \"deps1\", \"version\": \"1.0.0\"},\n    {\"name\": \"@owner/deps2\", \"version\": \"1.0.0\", \"frameworks\": \"arduino\"},\n    {\"name\": \"deps3\", \"version\": \"1.0.0\", \"platforms\": [\"ststm32\", \"sifive\"]}\n  ]\n}\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents, parser.ManifestFileType.LIBRARY_JSON\n    ).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n\n    data = ManifestSchema().load_manifest(raw_data)\n\n    assert data[\"repository\"][\"url\"] == \"https://github.com/bblanchon/ArduinoJson.git\"\n    assert data[\"examples\"][1][\"base\"] == \"examples/JsonHttpClient\"\n    assert data[\"examples\"][1][\"files\"] == [\"JsonHttpClient.ino\"]\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"ArduinoJson\",\n            \"keywords\": [\"json\", \"rest\", \"http\", \"web\"],\n            \"description\": \"An elegant and efficient JSON library for embedded systems\",\n            \"homepage\": \"https://arduinojson.org\",\n            \"repository\": {\n                \"url\": \"https://github.com/bblanchon/ArduinoJson.git\",\n                \"type\": \"git\",\n            },\n            \"version\": \"6.12.0\",\n            \"authors\": [\n                {\"name\": \"Benoit Blanchon\", \"url\": \"https://blog.benoitblanchon.fr\"}\n            ],\n            \"downloadUrl\": \"https://example.com/package.tar.gz\",\n            \"export\": {\"exclude\": [\"fuzzing\", \"scripts\", \"test\", \"third-party\"]},\n            \"frameworks\": [\"arduino\"],\n            \"platforms\": [\"*\"],\n            \"license\": \"MIT\",\n            \"scripts\": {\"postinstall\": \"script.py\"},\n            \"examples\": [\n                {\n                    \"name\": \"JsonConfigFile\",\n                    \"base\": \"examples/JsonConfigFile\",\n                    \"files\": [\"JsonConfigFile.ino\"],\n                },\n                {\n                    \"name\": \"JsonHttpClient\",\n                    \"base\": \"examples/JsonHttpClient\",\n                    \"files\": [\"JsonHttpClient.ino\"],\n                },\n            ],\n            \"dependencies\": [\n                {\"name\": \"@owner/deps2\", \"version\": \"1.0.0\", \"frameworks\": [\"arduino\"]},\n                {\"name\": \"deps1\", \"version\": \"1.0.0\"},\n                {\n                    \"name\": \"deps3\",\n                    \"version\": \"1.0.0\",\n                    \"platforms\": [\"ststm32\", \"sifive\"],\n                },\n            ],\n        },\n    )\n\n    # legacy dependencies format\n    contents = \"\"\"\n{\n    \"name\": \"DallasTemperature\",\n    \"version\": \"3.8.0\",\n    \"dependencies\":\n    {\n        \"name\": \"OneWire\",\n        \"authors\": \"Paul Stoffregen\",\n        \"frameworks\": \"arduino\"\n    }\n}\n\"\"\"\n    raw_data = parser.LibraryJsonManifestParser(contents).as_dict()\n    data = ManifestSchema().load_manifest(raw_data)\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"DallasTemperature\",\n            \"version\": \"3.8.0\",\n            \"dependencies\": [\n                {\n                    \"name\": \"OneWire\",\n                    \"authors\": [\"Paul Stoffregen\"],\n                    \"frameworks\": [\"arduino\"],\n                }\n            ],\n        },\n    )\n\n    # test multiple licenses\n    contents = \"\"\"\n{\n    \"name\": \"MultiLicense\",\n    \"version\": \"1.0.0\",\n    \"license\": \"MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)\"\n}\n\"\"\"\n    raw_data = parser.LibraryJsonManifestParser(contents).as_dict()\n    data = ManifestSchema().load_manifest(raw_data)\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"MultiLicense\",\n            \"version\": \"1.0.0\",\n            \"license\": \"MIT AND (LGPL-2.1-or-later OR BSD-3-Clause)\",\n        },\n    )\n\n\ndef test_library_properties_schema():\n    contents = \"\"\"\nname=U8glib\nversion=1.19.1\nauthor=oliver <olikraus@gmail.com>\nmaintainer=oliver <olikraus@gmail.com>\nsentence=A library for monochrome TFTs and OLEDs\nparagraph=Supported display controller: SSD1306, SSD1309, SSD1322, SSD1325\ncategory=Display\nurl=https://github.com/olikraus/u8glib\narchitectures=avr,sam,samd\ndepends=First Library (=2.0.0), Second Library (>=1.2.0), Third\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents, parser.ManifestFileType.LIBRARY_PROPERTIES\n    ).as_dict()\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n\n    data = ManifestSchema().load_manifest(raw_data)\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"description\": (\n                \"A library for monochrome TFTs and OLEDs. Supported display \"\n                \"controller: SSD1306, SSD1309, SSD1322, SSD1325\"\n            ),\n            \"repository\": {\n                \"url\": \"https://github.com/olikraus/u8glib.git\",\n                \"type\": \"git\",\n            },\n            \"frameworks\": [\"arduino\"],\n            \"platforms\": [\"atmelavr\", \"atmelsam\"],\n            \"version\": \"1.19.1\",\n            \"authors\": [\n                {\"maintainer\": True, \"email\": \"olikraus@gmail.com\", \"name\": \"oliver\"}\n            ],\n            \"keywords\": [\"display\"],\n            \"name\": \"U8glib\",\n            \"dependencies\": [\n                {\n                    \"name\": \"First Library\",\n                    \"version\": \"=2.0.0\",\n                    \"frameworks\": [\"arduino\"],\n                },\n                {\n                    \"name\": \"Second Library\",\n                    \"version\": \">=1.2.0\",\n                    \"frameworks\": [\"arduino\"],\n                },\n                {\"name\": \"Third\", \"frameworks\": [\"arduino\"]},\n            ],\n        },\n    )\n\n    # Broken fields\n    contents = \"\"\"\nname=Mozzi\nversion=1.0.3\nauthor=Lorem Ipsum is simply dummy text of the printing and typesetting industry Lorem Ipsum has been the industry's standard dummy text ever since the 1500s  when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries  but also the leap into electronic typesetting  remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages  and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.\nmaintainer=Tim Barrass <faveflave@gmail.com>\nsentence=Sound synthesis library for Arduino\nparagraph=With Mozzi, you can construct sounds using familiar synthesis units like oscillators, delays, filters and envelopes.\ncategory=Signal Input/Output\nurl=https://sensorium.github.io/Mozzi/\narchitectures=*\ndot_a_linkage=false\nincludes=MozziGuts.h\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents,\n        parser.ManifestFileType.LIBRARY_PROPERTIES,\n        remote_url=(\n            \"https://raw.githubusercontent.com/sensorium/Mozzi/\"\n            \"master/library.properties\"\n        ),\n    ).as_dict()\n\n    errors = None\n    try:\n        ManifestSchema().load_manifest(raw_data)\n    except ManifestValidationError as exc:\n        data = exc.valid_data\n        errors = exc.messages\n\n    assert errors[\"authors\"]\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"Mozzi\",\n            \"version\": \"1.0.3\",\n            \"description\": (\n                \"Sound synthesis library for Arduino. With Mozzi, you can construct \"\n                \"sounds using familiar synthesis units like oscillators, delays, \"\n                \"filters and envelopes.\"\n            ),\n            \"repository\": {\n                \"url\": \"https://github.com/sensorium/Mozzi.git\",\n                \"type\": \"git\",\n            },\n            \"platforms\": [\"*\"],\n            \"frameworks\": [\"arduino\"],\n            \"headers\": [\"MozziGuts.h\"],\n            \"authors\": [\n                {\n                    \"maintainer\": True,\n                    \"email\": \"faveflave@gmail.com\",\n                    \"name\": \"Tim Barrass\",\n                }\n            ],\n            \"keywords\": [\"signal\", \"input\", \"output\"],\n            \"homepage\": \"https://sensorium.github.io/Mozzi/\",\n        },\n    )\n\n\ndef test_platform_json_schema():\n    contents = \"\"\"\n{\n  \"name\": \"atmelavr\",\n  \"title\": \"Atmel AVR\",\n  \"description\": \"Atmel AVR 8- and 32-bit MCUs deliver a unique combination of performance, power efficiency and design flexibility. Optimized to speed time to market-and easily adapt to new ones-they are based on the industrys most code-efficient architecture for C and assembly programming.\",\n  \"keywords\": \"arduino, atmel, avr, MCU\",\n  \"homepage\": \"http://www.atmel.com/products/microcontrollers/avr/default.aspx\",\n  \"license\": \"Apache-2.0\",\n  \"engines\": {\n    \"platformio\": \"<5\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/platformio/platform-atmelavr.git\"\n  },\n  \"version\": \"1.15.0\",\n  \"frameworks\": {\n    \"arduino\": {\n      \"package\": \"framework-arduinoavr\",\n      \"script\": \"builder/frameworks/arduino.py\"\n    },\n    \"simba\": {\n      \"package\": \"framework-simba\",\n      \"script\": \"builder/frameworks/simba.py\"\n    }\n  },\n  \"packages\": {\n    \"toolchain-atmelavr\": {\n      \"type\": \"toolchain\",\n      \"owner\": \"platformio\",\n      \"version\": \"~1.50400.0\"\n    },\n    \"framework-arduinoavr\": {\n      \"type\": \"framework\",\n      \"optional\": true,\n      \"version\": \"~4.2.0\"\n    },\n    \"tool-avrdude\": {\n      \"type\": \"uploader\",\n      \"optional\": true,\n      \"version\": \"~1.60300.0\"\n    }\n  }\n}\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents, parser.ManifestFileType.PLATFORM_JSON\n    ).as_dict()\n    raw_data[\"frameworks\"] = sorted(raw_data[\"frameworks\"])\n    raw_data[\"dependencies\"] = sorted(raw_data[\"dependencies\"], key=lambda a: a[\"name\"])\n\n    data = ManifestSchema().load_manifest(raw_data)\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"atmelavr\",\n            \"title\": \"Atmel AVR\",\n            \"description\": (\n                \"Atmel AVR 8- and 32-bit MCUs deliver a unique combination of \"\n                \"performance, power efficiency and design flexibility. Optimized to \"\n                \"speed time to market-and easily adapt to new ones-they are based \"\n                \"on the industrys most code-efficient architecture for C and \"\n                \"assembly programming.\"\n            ),\n            \"keywords\": [\"arduino\", \"atmel\", \"avr\", \"mcu\"],\n            \"homepage\": \"http://www.atmel.com/products/microcontrollers/avr/default.aspx\",\n            \"license\": \"Apache-2.0\",\n            \"repository\": {\n                \"url\": \"https://github.com/platformio/platform-atmelavr.git\",\n                \"type\": \"git\",\n            },\n            \"frameworks\": sorted([\"arduino\", \"simba\"]),\n            \"version\": \"1.15.0\",\n            \"dependencies\": [\n                {\"name\": \"framework-arduinoavr\", \"version\": \"~4.2.0\"},\n                {\"name\": \"tool-avrdude\", \"version\": \"~1.60300.0\"},\n                {\n                    \"name\": \"toolchain-atmelavr\",\n                    \"owner\": \"platformio\",\n                    \"version\": \"~1.50400.0\",\n                },\n            ],\n        },\n    )\n\n\ndef test_package_json_schema():\n    contents = \"\"\"\n{\n    \"name\": \"tool-scons\",\n    \"description\": \"SCons software construction tool\",\n    \"keywords\": \"SCons, build\",\n    \"homepage\": \"http://www.scons.org\",\n    \"system\": [\"linux_armv6l\", \"linux_armv7l\", \"linux_armv8l\", \"LINUX_ARMV7L\"],\n    \"version\": \"3.30101.0\"\n}\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents, parser.ManifestFileType.PACKAGE_JSON\n    ).as_dict()\n\n    data = ManifestSchema().load_manifest(raw_data)\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"name\": \"tool-scons\",\n            \"description\": \"SCons software construction tool\",\n            \"keywords\": [\"scons\", \"build\"],\n            \"homepage\": \"http://www.scons.org\",\n            \"system\": [\"linux_armv6l\", \"linux_armv7l\", \"linux_armv8l\"],\n            \"version\": \"3.30101.0\",\n        },\n    )\n\n    mp = parser.ManifestParserFactory.new(\n        '{\"system\": \"*\"}', parser.ManifestFileType.PACKAGE_JSON\n    )\n    assert \"system\" not in mp.as_dict()\n\n    mp = parser.ManifestParserFactory.new(\n        '{\"system\": \"all\"}', parser.ManifestFileType.PACKAGE_JSON\n    )\n    assert \"system\" not in mp.as_dict()\n\n    mp = parser.ManifestParserFactory.new(\n        '{\"system\": \"darwin_x86_64\"}', parser.ManifestFileType.PACKAGE_JSON\n    )\n    assert mp.as_dict()[\"system\"] == [\"darwin_x86_64\"]\n\n    # shortcut repository syntax (npm-style)\n    contents = \"\"\"\n{\n    \"name\": \"tool-github\",\n    \"version\": \"1.2.0\",\n    \"repository\": \"github:user/repo\"\n}\n\"\"\"\n    raw_data = parser.ManifestParserFactory.new(\n        contents, parser.ManifestFileType.PACKAGE_JSON\n    ).as_dict()\n    data = ManifestSchema().load_manifest(raw_data)\n    assert data[\"repository\"][\"url\"] == \"https://github.com/user/repo.git\"\n\n\ndef test_parser_from_dir(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    pkg_dir.join(\"package.json\").write('{\"name\": \"package.json\"}')\n    pkg_dir.join(\"library.json\").write('{\"name\": \"library.json\"}')\n    pkg_dir.join(\"library.properties\").write(\"name=library.properties\")\n\n    data = parser.ManifestParserFactory.new_from_dir(str(pkg_dir)).as_dict()\n    assert data[\"name\"] == \"library.json\"\n\n    data = parser.ManifestParserFactory.new_from_dir(\n        str(pkg_dir), remote_url=\"http://localhost/library.properties\"\n    ).as_dict()\n    assert data[\"name\"] == \"library.properties\"\n\n\ndef test_examples_from_dir(tmpdir_factory):\n    package_dir = tmpdir_factory.mktemp(\"project\")\n    package_dir.join(\"library.json\").write(\n        '{\"name\": \"pkg\", \"version\": \"1.0.0\", \"examples\": [\"examples/*/*.pde\"]}'\n    )\n    examples_dir = package_dir.mkdir(\"examples\")\n\n    # PlatformIO project #1\n    pio_dir = examples_dir.mkdir(\"PlatformIO\").mkdir(\"hello\")\n    pio_dir.join(\".vimrc\").write(\"\")\n    pio_ini = pio_dir.join(\"platformio.ini\")\n    pio_ini.write(\"\")\n    if not IS_WINDOWS:\n        pio_dir.join(\"platformio.ini.copy\").mksymlinkto(pio_ini)\n    pio_dir.mkdir(\"include\").join(\"main.h\").write(\"\")\n    pio_dir.mkdir(\"src\").join(\"main.cpp\").write(\"\")\n\n    # wiring examples\n    arduino_dir = examples_dir.mkdir(\"1. General\")\n    arduino_dir.mkdir(\"SomeSketchIno\").join(\"SomeSketchIno.ino\").write(\"\")\n    arduino_dir.mkdir(\"SomeSketchPde\").join(\"SomeSketchPde.pde\").write(\"\")\n\n    # custom examples\n    demo_dir = examples_dir.mkdir(\"demo\")\n    demo_dir.join(\"demo.cpp\").write(\"\")\n    demo_dir.join(\"demo.h\").write(\"\")\n    demo_dir.join(\"util.h\").write(\"\")\n\n    # PlatformIO project #2\n    pio_dir = examples_dir.mkdir(\"world\")\n    pio_dir.join(\"platformio.ini\").write(\"\")\n    pio_dir.join(\"README\").write(\"\")\n    pio_dir.join(\"extra.py\").write(\"\")\n    pio_dir.mkdir(\"include\").join(\"world.h\").write(\"\")\n    pio_dir.mkdir(\"src\").join(\"world.c\").write(\"\")\n\n    # example files in root\n    examples_dir.join(\"root.c\").write(\"\")\n    examples_dir.join(\"root.h\").write(\"\")\n\n    # invalid example\n    examples_dir.mkdir(\"invalid-example\").join(\"hello.json\")\n\n    # Do testing\n\n    raw_data = parser.ManifestParserFactory.new_from_dir(str(package_dir)).as_dict()\n    assert isinstance(raw_data[\"examples\"], list)\n    assert len(raw_data[\"examples\"]) == 6\n\n    def _to_unix_path(path):\n        return re.sub(r\"[\\\\/]+\", \"/\", path)\n\n    def _sort_examples(items):\n        for i, _ in enumerate(items):\n            items[i][\"base\"] = _to_unix_path(items[i][\"base\"])\n            items[i][\"files\"] = [_to_unix_path(f) for f in sorted(items[i][\"files\"])]\n        return sorted(items, key=lambda item: item[\"name\"])\n\n    raw_data[\"examples\"] = _sort_examples(raw_data[\"examples\"])\n\n    data = ManifestSchema().load_manifest(raw_data)\n\n    assert not jsondiff.diff(\n        data,\n        {\n            \"version\": \"1.0.0\",\n            \"name\": \"pkg\",\n            \"examples\": _sort_examples(\n                [\n                    {\n                        \"name\": \"PlatformIO/hello\",\n                        \"base\": os.path.join(\"examples\", \"PlatformIO\", \"hello\"),\n                        \"files\": [\n                            \"platformio.ini\",\n                            os.path.join(\"include\", \"main.h\"),\n                            os.path.join(\"src\", \"main.cpp\"),\n                        ],\n                    },\n                    {\n                        \"name\": \"1_General/SomeSketchIno\",\n                        \"base\": os.path.join(\"examples\", \"1. General\", \"SomeSketchIno\"),\n                        \"files\": [\"SomeSketchIno.ino\"],\n                    },\n                    {\n                        \"name\": \"1_General/SomeSketchPde\",\n                        \"base\": os.path.join(\"examples\", \"1. General\", \"SomeSketchPde\"),\n                        \"files\": [\"SomeSketchPde.pde\"],\n                    },\n                    {\n                        \"name\": \"demo\",\n                        \"base\": os.path.join(\"examples\", \"demo\"),\n                        \"files\": [\"demo.h\", \"util.h\", \"demo.cpp\"],\n                    },\n                    {\n                        \"name\": \"world\",\n                        \"base\": \"examples/world\",\n                        \"files\": [\n                            \"platformio.ini\",\n                            os.path.join(\"include\", \"world.h\"),\n                            os.path.join(\"src\", \"world.c\"),\n                            \"README\",\n                            \"extra.py\",\n                        ],\n                    },\n                    {\n                        \"name\": \"Examples\",\n                        \"base\": \"examples\",\n                        \"files\": [\"root.c\", \"root.h\"],\n                    },\n                ]\n            ),\n        },\n    )\n\n\ndef test_parser_from_archive(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    pkg_dir.join(\"package.json\").write('{\"name\": \"package.json\"}')\n    pkg_dir.join(\"library.json\").write('{\"name\": \"library.json\"}')\n    pkg_dir.join(\"library.properties\").write(\"name=library.properties\")\n\n    archive_path = os.path.join(str(pkg_dir), \"package.tar.gz\")\n    with tarfile.open(archive_path, mode=\"w|gz\") as tf:\n        for item in os.listdir(str(pkg_dir)):\n            tf.add(os.path.join(str(pkg_dir), item), item)\n\n    data = parser.ManifestParserFactory.new_from_archive(archive_path).as_dict()\n    assert data[\"name\"] == \"library.json\"\n\n\ndef test_broken_schemas():\n    # missing required field\n    with pytest.raises(\n        ManifestValidationError, match=(\"Invalid semantic versioning format\")\n    ) as exc_info:\n        ManifestSchema().load_manifest(dict(name=\"MyPackage\", version=\"broken_version\"))\n    assert exc_info.value.valid_data == {\"name\": \"MyPackage\"}\n\n    # invalid StrictList\n    with pytest.raises(\n        ManifestValidationError, match=(\"Invalid manifest fields.+keywords\")\n    ) as exc_info:\n        ManifestSchema().load_manifest(\n            dict(name=\"MyPackage\", version=\"1.0.0\", keywords=[\"kw1\", \"*^[]\"])\n        )\n    assert list(exc_info.value.messages.keys()) == [\"keywords\"]\n    assert exc_info.value.valid_data[\"keywords\"] == [\"kw1\"]\n\n    # broken SemVer\n    with pytest.raises(\n        ManifestValidationError, match=(\"Invalid semantic versioning format\")\n    ):\n        ManifestSchema().load_manifest(dict(name=\"MyPackage\", version=\"broken_version\"))\n    # version with leading zeros\n    with pytest.raises(\n        ManifestValidationError, match=(\"Invalid semantic versioning format\")\n    ):\n        ManifestSchema().load_manifest(dict(name=\"MyPackage\", version=\"01.02.00\"))\n\n    # broken value for Nested\n    with pytest.raises(ManifestValidationError, match=r\"authors.*Invalid input type\"):\n        ManifestSchema().load_manifest(\n            dict(\n                name=\"MyPackage\",\n                description=\"MyDescription\",\n                keywords=[\"a\", \"b\"],\n                authors=[\"should be dict here\"],\n                version=\"1.2.3\",\n            )\n        )\n\n    # invalid package name\n    with pytest.raises(ManifestValidationError, match=\"are not allowed\"):\n        ManifestSchema().load_manifest(dict(name=\"C/C++ :library\", version=\"1.2.3\"))\n"
  },
  {
    "path": "tests/package/test_meta.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\n\nimport jsondiff\nimport semantic_version\n\nfrom platformio.package.meta import (\n    PackageCompatibility,\n    PackageMetadata,\n    PackageOutdatedResult,\n    PackageSpec,\n    PackageType,\n)\n\n\ndef test_outdated_result():\n    result = PackageOutdatedResult(current=\"1.2.3\", latest=\"2.0.0\")\n    assert result.is_outdated()\n    assert result.is_outdated(allow_incompatible=True)\n    result = PackageOutdatedResult(current=\"1.2.3\", latest=\"2.0.0\", wanted=\"1.5.4\")\n    assert result.is_outdated()\n    assert result.is_outdated(allow_incompatible=True)\n    result = PackageOutdatedResult(current=\"1.2.3\", latest=\"2.0.0\", wanted=\"1.2.3\")\n    assert not result.is_outdated()\n    assert result.is_outdated(allow_incompatible=True)\n    result = PackageOutdatedResult(current=\"1.2.3\", latest=\"2.0.0\", detached=True)\n    assert not result.is_outdated()\n    assert not result.is_outdated(allow_incompatible=True)\n\n\ndef test_spec_owner():\n    assert PackageSpec(\"alice/foo library\") == PackageSpec(\n        owner=\"alice\", name=\"foo library\"\n    )\n    spec = PackageSpec(\" Bob / BarUpper \")\n    assert spec != PackageSpec(owner=\"BOB\", name=\"BARUPPER\")\n    assert spec.owner == \"Bob\"\n    assert spec.name == \"BarUpper\"\n\n\ndef test_spec_id():\n    assert PackageSpec(13) == PackageSpec(id=13)\n    assert PackageSpec(\"20\") == PackageSpec(id=20)\n    spec = PackageSpec(\"id=199\")\n    assert spec == PackageSpec(id=199)\n    assert isinstance(spec.id, int)\n\n\ndef test_spec_name():\n    assert PackageSpec(\"foo\") == PackageSpec(name=\"foo\")\n    assert PackageSpec(\" bar-24 \") == PackageSpec(name=\"bar-24\")\n\n\ndef test_spec_requirements():\n    assert PackageSpec(\"foo@1.2.3\") == PackageSpec(name=\"foo\", requirements=\"1.2.3\")\n    assert PackageSpec(\n        name=\"foo\", requirements=semantic_version.Version(\"1.2.3\")\n    ) == PackageSpec(name=\"foo\", requirements=\"1.2.3\")\n    assert PackageSpec(\"bar @ ^1.2.3\") == PackageSpec(name=\"bar\", requirements=\"^1.2.3\")\n    assert PackageSpec(\"13 @ ~2.0\") == PackageSpec(id=13, requirements=\"~2.0\")\n    assert PackageSpec(\n        name=\"hello\", requirements=semantic_version.SimpleSpec(\"~1.2.3\")\n    ) == PackageSpec(name=\"hello\", requirements=\"~1.2.3\")\n    spec = PackageSpec(\"id=20 @ !=1.2.3,<2.0\")\n    assert not spec.external\n    assert isinstance(spec.requirements, semantic_version.SimpleSpec)\n    assert semantic_version.Version(\"1.3.0-beta.1\") in spec.requirements\n    assert spec == PackageSpec(id=20, requirements=\"!=1.2.3,<2.0\")\n\n\ndef test_spec_local_urls(tmpdir_factory):\n    assert PackageSpec(\"file:///tmp/foo.tar.gz\") == PackageSpec(\n        uri=\"file:///tmp/foo.tar.gz\", name=\"foo\"\n    )\n    assert PackageSpec(\"customName=file:///tmp/bar.zip\") == PackageSpec(\n        uri=\"file:///tmp/bar.zip\", name=\"customName\"\n    )\n    assert PackageSpec(\"file:///tmp/some-lib/\") == PackageSpec(\n        uri=\"file:///tmp/some-lib/\", name=\"some-lib\"\n    )\n    assert PackageSpec(\"symlink:///tmp/soft-link/\") == PackageSpec(\n        uri=\"symlink:///tmp/soft-link/\", name=\"soft-link\"\n    )\n    # detached package\n    assert PackageSpec(\"file:///tmp/some-lib@src-67e1043a673d2\") == PackageSpec(\n        uri=\"file:///tmp/some-lib@src-67e1043a673d2\", name=\"some-lib\"\n    )\n    # detached folder without scheme\n    pkg_dir = tmpdir_factory.mktemp(\"storage\").join(\"detached@1.2.3\").mkdir()\n    assert PackageSpec(str(pkg_dir)) == PackageSpec(\n        name=\"detached\", uri=\"file://%s\" % pkg_dir\n    )\n\n\ndef test_spec_external_urls():\n    assert PackageSpec(\n        \"https://github.com/platformio/platformio-core/archive/develop.zip\"\n    ) == PackageSpec(\n        uri=\"https://github.com/platformio/platformio-core/archive/develop.zip\",\n        name=\"platformio-core\",\n    )\n    assert PackageSpec(\n        \"https://github.com/platformio/platformio-core/archive/develop.zip?param=value\"\n        \" @ !=2\"\n    ) == PackageSpec(\n        uri=\"https://github.com/platformio/platformio-core/archive/\"\n        \"develop.zip?param=value\",\n        name=\"platformio-core\",\n        requirements=\"!=2\",\n    )\n    spec = PackageSpec(\n        \"Custom-Name=\"\n        \"https://github.com/platformio/platformio-core/archive/develop.tar.gz@4.4.0\"\n    )\n    assert spec.external\n    assert spec.has_custom_name()\n    assert spec.name == \"Custom-Name\"\n    assert spec == PackageSpec(\n        uri=\"https://github.com/platformio/platformio-core/archive/develop.tar.gz\",\n        name=\"Custom-Name\",\n        requirements=\"4.4.0\",\n    )\n\n\ndef test_spec_vcs_urls():\n    assert PackageSpec(\"https://github.com/platformio/platformio-core\") == PackageSpec(\n        name=\"platformio-core\", uri=\"git+https://github.com/platformio/platformio-core\"\n    )\n    assert PackageSpec(\"https://gitlab.com/username/reponame\") == PackageSpec(\n        name=\"reponame\", uri=\"git+https://gitlab.com/username/reponame\"\n    )\n    assert PackageSpec(\n        \"wolfSSL=https://os.mbed.com/users/wolfSSL/code/wolfSSL/\"\n    ) == PackageSpec(\n        name=\"wolfSSL\", uri=\"hg+https://os.mbed.com/users/wolfSSL/code/wolfSSL/\"\n    )\n    assert PackageSpec(\n        \"https://github.com/platformio/platformio-core.git#master\"\n    ) == PackageSpec(\n        name=\"platformio-core\",\n        uri=\"git+https://github.com/platformio/platformio-core.git#master\",\n    )\n    assert PackageSpec(\n        \"core=git+ssh://github.com/platformio/platformio-core.git#v4.4.0@4.4.0\"\n    ) == PackageSpec(\n        name=\"core\",\n        uri=\"git+ssh://github.com/platformio/platformio-core.git#v4.4.0\",\n        requirements=\"4.4.0\",\n    )\n    assert PackageSpec(\n        \"username@github.com:platformio/platformio-core.git\"\n    ) == PackageSpec(\n        name=\"platformio-core\",\n        uri=\"git+username@github.com:platformio/platformio-core.git\",\n    )\n    assert PackageSpec(\n        \"pkg=git+git@github.com:platformio/platformio-core.git @ ^1.2.3,!=5\"\n    ) == PackageSpec(\n        name=\"pkg\",\n        uri=\"git+git@github.com:platformio/platformio-core.git\",\n        requirements=\"^1.2.3,!=5\",\n    )\n    assert PackageSpec(\n        owner=\"platformio\",\n        name=\"external-repo\",\n        requirements=\"https://github.com/platformio/platformio-core\",\n    ) == PackageSpec(\n        owner=\"platformio\",\n        name=\"external-repo\",\n        uri=\"git+https://github.com/platformio/platformio-core\",\n    )\n\n\ndef test_spec_as_dict():\n    assert not jsondiff.diff(\n        PackageSpec(\"bob/foo@1.2.3\").as_dict(),\n        {\n            \"owner\": \"bob\",\n            \"id\": None,\n            \"name\": \"foo\",\n            \"requirements\": \"1.2.3\",\n            \"uri\": None,\n        },\n    )\n    assert not jsondiff.diff(\n        PackageSpec(\n            \"https://github.com/platformio/platformio-core/archive/develop.zip?param=value\"\n            \" @ !=2\"\n        ).as_dict(),\n        {\n            \"owner\": None,\n            \"id\": None,\n            \"name\": \"platformio-core\",\n            \"requirements\": \"!=2\",\n            \"uri\": \"https://github.com/platformio/platformio-core/archive/develop.zip?param=value\",\n        },\n    )\n\n\ndef test_spec_as_dependency():\n    assert PackageSpec(\"owner/pkgname\").as_dependency() == \"owner/pkgname\"\n    assert PackageSpec(owner=\"owner\", name=\"pkgname\").as_dependency() == \"owner/pkgname\"\n    assert PackageSpec(\"bob/foo @ ^1.2.3\").as_dependency() == \"bob/foo@^1.2.3\"\n    assert (\n        PackageSpec(\n            \"https://github.com/o/r/a/develop.zip?param=value @ !=2\"\n        ).as_dependency()\n        == \"https://github.com/o/r/a/develop.zip?param=value @ !=2\"\n    )\n    assert (\n        PackageSpec(\n            \"wolfSSL=https://os.mbed.com/users/wolfSSL/code/wolfSSL/\"\n        ).as_dependency()\n        == \"wolfSSL=https://os.mbed.com/users/wolfSSL/code/wolfSSL/\"\n    )\n\n\ndef test_metadata_as_dict():\n    metadata = PackageMetadata(PackageType.LIBRARY, \"foo\", \"1.2.3\")\n    # test setter\n    metadata.version = \"0.1.2+12345\"\n    assert metadata.version == semantic_version.Version(\"0.1.2+12345\")\n    assert not jsondiff.diff(\n        metadata.as_dict(),\n        {\n            \"type\": PackageType.LIBRARY,\n            \"name\": \"foo\",\n            \"version\": \"0.1.2+12345\",\n            \"spec\": None,\n        },\n    )\n\n    assert not jsondiff.diff(\n        PackageMetadata(\n            PackageType.TOOL,\n            \"toolchain\",\n            \"2.0.5\",\n            PackageSpec(\"platformio/toolchain@~2.0.0\"),\n        ).as_dict(),\n        {\n            \"type\": PackageType.TOOL,\n            \"name\": \"toolchain\",\n            \"version\": \"2.0.5\",\n            \"spec\": {\n                \"owner\": \"platformio\",\n                \"id\": None,\n                \"name\": \"toolchain\",\n                \"requirements\": \"~2.0.0\",\n                \"uri\": None,\n            },\n        },\n    )\n\n\ndef test_metadata_dump(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    metadata = PackageMetadata(\n        PackageType.TOOL,\n        \"toolchain\",\n        \"2.0.5\",\n        PackageSpec(\"platformio/toolchain@~2.0.0\"),\n    )\n\n    dst = pkg_dir.join(\".piopm\")\n    metadata.dump(str(dst))\n    assert os.path.isfile(str(dst))\n    contents = dst.read()\n    assert all(s in contents for s in (\"null\", '\"~2.0.0\"'))\n\n\ndef test_metadata_load(tmpdir_factory):\n    contents = \"\"\"\n{\n  \"name\": \"foo\",\n  \"spec\": {\n    \"name\": \"foo\",\n    \"owner\": \"username\",\n    \"requirements\": \"!=3.4.5\"\n  },\n  \"type\": \"platform\",\n  \"version\": \"0.1.3\"\n}\n\"\"\"\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    dst = pkg_dir.join(\".piopm\")\n    dst.write(contents)\n    metadata = PackageMetadata.load(str(dst))\n    assert metadata.version == semantic_version.Version(\"0.1.3\")\n    assert metadata == PackageMetadata(\n        PackageType.PLATFORM,\n        \"foo\",\n        \"0.1.3\",\n        spec=PackageSpec(owner=\"username\", name=\"foo\", requirements=\"!=3.4.5\"),\n    )\n\n    piopm_path = pkg_dir.join(\".piopm\")\n    metadata = PackageMetadata(\n        PackageType.LIBRARY, \"mylib\", version=\"1.2.3\", spec=PackageSpec(\"mylib\")\n    )\n    metadata.dump(str(piopm_path))\n    restored_metadata = PackageMetadata.load(str(piopm_path))\n    assert metadata == restored_metadata\n\n\ndef test_compatibility():\n    assert PackageCompatibility().is_compatible(PackageCompatibility())\n    assert PackageCompatibility().is_compatible(\n        PackageCompatibility(platforms=[\"espressif32\"])\n    )\n    assert PackageCompatibility(frameworks=[\"arduino\"]).is_compatible(\n        PackageCompatibility(platforms=[\"espressif32\"])\n    )\n    assert PackageCompatibility(platforms=\"espressif32\").is_compatible(\n        PackageCompatibility(platforms=[\"espressif32\"])\n    )\n    assert PackageCompatibility(\n        platforms=\"espressif32\", frameworks=[\"arduino\"]\n    ).is_compatible(PackageCompatibility(platforms=None))\n    assert PackageCompatibility(\n        platforms=\"espressif32\", frameworks=[\"arduino\"]\n    ).is_compatible(PackageCompatibility(platforms=[\"*\"]))\n    assert not PackageCompatibility(\n        platforms=\"espressif32\", frameworks=[\"arduino\"]\n    ).is_compatible(PackageCompatibility(platforms=[\"atmelavr\"]))\n"
  },
  {
    "path": "tests/package/test_pack.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\nimport os\nimport tarfile\n\nimport pytest\n\nfrom platformio import fs\nfrom platformio.compat import IS_WINDOWS\nfrom platformio.package.exception import UnknownManifestError\nfrom platformio.package.pack import PackagePacker\n\n\ndef test_base(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    pkg_dir.join(\".git\").mkdir().join(\"file\").write(\"\")\n    pkg_dir.join(\".gitignore\").write(\"\")\n    pkg_dir.join(\"._hidden_file\").write(\"\")\n    pkg_dir.join(\"main.cpp\").write(\"#include <stdio.h>\")\n    p = PackagePacker(str(pkg_dir))\n    # test missed manifest\n    with pytest.raises(UnknownManifestError):\n        p.pack()\n    # minimal package\n    pkg_dir.join(\"library.json\").write('{\"name\": \"foo\", \"version\": \"1.0.0\"}')\n    pkg_dir.mkdir(\"include\").join(\"main.h\").write(\"#ifndef\")\n    with fs.cd(str(pkg_dir)):\n        p.pack()\n    with tarfile.open(os.path.join(str(pkg_dir), \"foo-1.0.0.tar.gz\"), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\".gitignore\", \"include/main.h\", \"library.json\", \"main.cpp\"]\n        )\n\n\ndef test_filters(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    src_dir = pkg_dir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(\"#include <stdio.h>\")\n    src_dir.mkdir(\"util\").join(\"helpers.cpp\").write(\"void\")\n    pkg_dir.mkdir(\"include\").join(\"main.h\").write(\"#ifndef\")\n    test_dir = pkg_dir.mkdir(\"tests\")\n    test_dir.join(\"test_1.h\").write(\"\")\n    test_dir.join(\"test_2.h\").write(\"\")\n\n    # test include with remap of root\n    pkg_dir.join(\"library.json\").write(\n        json.dumps(dict(name=\"bar\", version=\"1.2.3\", export={\"include\": \"src\"}))\n    )\n    p = PackagePacker(str(pkg_dir))\n    with tarfile.open(p.pack(str(pkg_dir)), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\"util/helpers.cpp\", \"main.cpp\", \"library.json\"]\n        )\n    os.unlink(str(src_dir.join(\"library.json\")))\n\n    # test include \"src\" and \"include\"\n    pkg_dir.join(\"library.json\").write(\n        json.dumps(\n            dict(name=\"bar\", version=\"1.2.3\", export={\"include\": [\"src\", \"include\"]})\n        )\n    )\n    p = PackagePacker(str(pkg_dir))\n    with tarfile.open(p.pack(str(pkg_dir)), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\"include/main.h\", \"library.json\", \"src/main.cpp\", \"src/util/helpers.cpp\"]\n        )\n\n    # test include & exclude\n    pkg_dir.join(\"library.json\").write(\n        json.dumps(\n            dict(\n                name=\"bar\",\n                version=\"1.2.3\",\n                export={\"include\": [\"src\", \"include\"], \"exclude\": [\"*/*.h\"]},\n            )\n        )\n    )\n    p = PackagePacker(str(pkg_dir))\n    with tarfile.open(p.pack(str(pkg_dir)), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\"library.json\", \"src/main.cpp\", \"src/util/helpers.cpp\"]\n        )\n\n\ndef test_gitgnore_filters(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    pkg_dir.join(\".git\").mkdir().join(\"file\").write(\"\")\n    pkg_dir.join(\".gitignore\").write(\"\"\"\n# comment\n\ngi_file\ngi_folder\ngi_folder_*\n\n**/main_nested.h\n\ngi_keep_file\n!gi_keep_file\nLICENSE\n\"\"\")\n    pkg_dir.join(\"LICENSE\").write(\"\")\n    pkg_dir.join(\"gi_keep_file\").write(\"\")\n    pkg_dir.join(\"gi_file\").write(\"\")\n    pkg_dir.mkdir(\"gi_folder\").join(\"main.h\").write(\"#ifndef\")\n    pkg_dir.mkdir(\"gi_folder_name\").join(\"main.h\").write(\"#ifndef\")\n    pkg_dir.mkdir(\"gi_nested_folder\").mkdir(\"a\").mkdir(\"b\").join(\"main_nested.h\").write(\n        \"#ifndef\"\n    )\n    pkg_dir.join(\"library.json\").write('{\"name\": \"foo\", \"version\": \"1.0.0\"}')\n    p = PackagePacker(str(pkg_dir))\n    with fs.cd(str(pkg_dir)):\n        p.pack()\n    with tarfile.open(os.path.join(str(pkg_dir), \"foo-1.0.0.tar.gz\"), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\"library.json\", \"LICENSE\", \".gitignore\", \"gi_keep_file\"]\n        )\n\n\ndef test_symlinks(tmpdir_factory):\n    # Windows does not support symbolic links\n    if IS_WINDOWS:\n        return\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    src_dir = pkg_dir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(\"#include <stdio.h>\")\n    pkg_dir.mkdir(\"include\").join(\"main.h\").write(\"#ifndef\")\n    src_dir.join(\"main.h\").mksymlinkto(os.path.join(\"..\", \"include\", \"main.h\"))\n    pkg_dir.join(\"library.json\").write('{\"name\": \"bar\", \"version\": \"2.0.0\"}')\n    tarball = pkg_dir.join(\"bar.tar.gz\")\n    with tarfile.open(str(tarball), \"w:gz\") as tar:\n        for item in pkg_dir.listdir():\n            tar.add(str(item), str(item.relto(pkg_dir)))\n\n    p = PackagePacker(str(tarball))\n    assert p.pack(str(pkg_dir)).endswith(\"bar-2.0.0.tar.gz\")\n    with tarfile.open(os.path.join(str(pkg_dir), \"bar-2.0.0.tar.gz\"), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set(\n            [\"include/main.h\", \"library.json\", \"src/main.cpp\", \"src/main.h\"]\n        )\n        m = tar.getmember(\"src/main.h\")\n        assert m.issym()\n\n\ndef test_source_root(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    root_dir = pkg_dir.mkdir(\"root\")\n    src_dir = root_dir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(\"#include <stdio.h>\")\n    root_dir.join(\"library.json\").write('{\"name\": \"bar\", \"version\": \"2.0.0\"}')\n    p = PackagePacker(str(pkg_dir))\n    with tarfile.open(p.pack(str(pkg_dir)), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set([\"library.json\", \"src/main.cpp\"])\n\n\ndef test_manifest_uri(tmpdir_factory):\n    pkg_dir = tmpdir_factory.mktemp(\"package\")\n    root_dir = pkg_dir.mkdir(\"root\")\n    src_dir = root_dir.mkdir(\"src\")\n    src_dir.join(\"main.cpp\").write(\"#include <stdio.h>\")\n    root_dir.join(\"library.json\").write('{\"name\": \"foo\", \"version\": \"1.0.0\"}')\n    bar_dir = root_dir.mkdir(\"library\").mkdir(\"bar\")\n    bar_dir.join(\"library.json\").write('{\"name\": \"bar\", \"version\": \"2.0.0\"}')\n    bar_dir.mkdir(\"include\").join(\"bar.h\").write(\"\")\n\n    manifest_path = pkg_dir.join(\"remote_library.json\")\n    manifest_path.write(\n        '{\"name\": \"bar\", \"version\": \"3.0.0\", \"export\": {\"include\": \"root/library/bar\"}}'\n    )\n\n    p = PackagePacker(str(pkg_dir), manifest_uri=\"file:%s\" % manifest_path)\n    p.pack(str(pkg_dir))\n    with tarfile.open(os.path.join(str(pkg_dir), \"bar-2.0.0.tar.gz\"), \"r:gz\") as tar:\n        assert set(tar.getnames()) == set([\"library.json\", \"include/bar.h\"])\n"
  },
  {
    "path": "tests/project/__init__.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "tests/project/test_config.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# pylint: disable=redefined-outer-name\n\nimport configparser\nimport os\nimport sys\nfrom pathlib import Path\n\nimport pytest\n\nfrom platformio import fs\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import (\n    InvalidEnvNameError,\n    InvalidProjectConfError,\n    UnknownEnvNamesError,\n)\n\nBASE_CONFIG = \"\"\"\n[platformio]\nenv_default = base, extra_2\nsrc_dir = ${custom.src_dir}\nextra_configs =\n  extra_envs.ini\n  extra_debug.ini\n\n# global options per [env:*]\n[env]\nmonitor_speed = 9600  ; inline comment\ncustom_monitor_speed = 115200\nlib_deps =\n    Lib1 ; inline comment in multi-line value\n    Lib2\nlib_ignore = ${custom.lib_ignore}\ncustom_builtin_option = ${env.build_type}\n\n[strict_ldf]\nlib_ldf_mode = chain+\nlib_compat_mode = strict\n\n[monitor_custom]\nmonitor_speed = ${env.custom_monitor_speed}\n\n[strict_settings]\nextends = strict_ldf, monitor_custom\nbuild_flags = -D RELEASE\n\n[custom]\nsrc_dir = source\ndebug_flags = -D RELEASE\nlib_flags = -lc -lm\nextra_flags = ${sysenv.__PIO_TEST_CNF_EXTRA_FLAGS}\nlib_ignore = LibIgnoreCustom\n\n[env:base]\nbuild_flags = ${custom.debug_flags} ${custom.extra_flags}\nlib_compat_mode = ${strict_ldf.lib_compat_mode}\ntargets =\n\n[env:test_extends]\nextends = strict_settings\n\n[env:inject_base_env]\ndebug_build_flags =\n    ${env.debug_build_flags}\n    -D CUSTOM_DEBUG_FLAG\n\n\"\"\"\n\nEXTRA_ENVS_CONFIG = \"\"\"\n[env:extra_1]\nbuild_flags =\n    -fdata-sections\n    -Wl,--gc-sections\n    ${custom.lib_flags}\n    ${custom.debug_flags}\n    -D SERIAL_BAUD_RATE=${this.monitor_speed}\nlib_install = 574\n\n[env:extra_2]\nbuild_flags = ${custom.debug_flags} ${custom.extra_flags}\nlib_ignore = ${env.lib_ignore}, Lib3\nupload_port = /dev/extra_2/port\ndebug_server = ${custom.debug_server}\n\"\"\"\n\nEXTRA_DEBUG_CONFIG = \"\"\"\n# Override original \"custom.debug_flags\"\n[custom]\ndebug_flags = -D DEBUG=1\ndebug_server =\n    ${platformio.packages_dir}/tool-openocd/openocd\n    --help\nsrc_filter = -<*>\n    +<a>\n    +<b>\n\n[env:extra_2]\nbuild_flags = -Og\nsrc_filter = ${custom.src_filter} +<c>\n\"\"\"\n\nDEFAULT_CORE_DIR = os.path.join(fs.expanduser(\"~\"), \".platformio\")\n\n\n@pytest.fixture(scope=\"module\")\ndef config(tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(BASE_CONFIG)\n    tmpdir.join(\"extra_envs.ini\").write(EXTRA_ENVS_CONFIG)\n    tmpdir.join(\"extra_debug.ini\").write(EXTRA_DEBUG_CONFIG)\n    with tmpdir.as_cwd():\n        return ProjectConfig(tmpdir.join(\"platformio.ini\").strpath)\n\n\ndef test_empty_config():\n    config = ProjectConfig(\"/non/existing/platformio.ini\")\n    # unknown section\n    with pytest.raises(InvalidProjectConfError):\n        config.get(\"unknown_section\", \"unknown_option\")\n    assert config.sections() == []\n    assert config.get(\"section\", \"option\", 13) == 13\n\n\ndef test_warnings(config):\n    config.validate([\"extra_2\", \"base\"], silent=True)\n    assert len(config.warnings) == 3\n    assert \"lib_install\" in config.warnings[1]\n\n    with pytest.raises(UnknownEnvNamesError):\n        config.validate([\"non-existing-env\"])\n\n\ndef test_defaults(config):\n    assert config.get(\"platformio\", \"core_dir\") == os.path.join(\n        os.path.expanduser(\"~\"), \".platformio\"\n    )\n    assert config.get(\"strict_ldf\", \"lib_deps\", [\"Empty\"]) == [\"Empty\"]\n    assert config.get(\"env:extra_2\", \"lib_compat_mode\") == \"soft\"\n    assert config.get(\"env:extra_2\", \"build_type\") == \"release\"\n    assert config.get(\"env:extra_2\", \"build_type\", None) is None\n    assert config.get(\"env:extra_2\", \"lib_archive\", \"no\") is False\n\n    config.expand_interpolations = False\n    with pytest.raises(\n        InvalidProjectConfError, match=\"No option 'lib_deps' in section: 'strict_ldf'\"\n    ):\n        assert config.get(\"strict_ldf\", \"lib_deps\", [\"Empty\"]) == [\"Empty\"]\n    config.expand_interpolations = True\n\n\ndef test_sections(config):\n    with pytest.raises(configparser.NoSectionError):\n        config.getraw(\"unknown_section\", \"unknown_option\")\n\n    assert config.sections() == [\n        \"platformio\",\n        \"env\",\n        \"strict_ldf\",\n        \"monitor_custom\",\n        \"strict_settings\",\n        \"custom\",\n        \"env:base\",\n        \"env:test_extends\",\n        \"env:inject_base_env\",\n        \"env:extra_1\",\n        \"env:extra_2\",\n    ]\n\n\ndef test_envs(config):\n    assert config.envs() == [\n        \"base\",\n        \"test_extends\",\n        \"inject_base_env\",\n        \"extra_1\",\n        \"extra_2\",\n    ]\n    assert config.default_envs() == [\"base\", \"extra_2\"]\n    assert config.get_default_env() == \"base\"\n\n\ndef test_options(config):\n    assert config.options(env=\"base\") == [\n        \"build_flags\",\n        \"lib_compat_mode\",\n        \"targets\",\n        \"monitor_speed\",\n        \"custom_monitor_speed\",\n        \"lib_deps\",\n        \"lib_ignore\",\n        \"custom_builtin_option\",\n    ]\n    assert config.options(env=\"test_extends\") == [\n        \"extends\",\n        \"build_flags\",\n        \"monitor_speed\",\n        \"lib_ldf_mode\",\n        \"lib_compat_mode\",\n        \"custom_monitor_speed\",\n        \"lib_deps\",\n        \"lib_ignore\",\n        \"custom_builtin_option\",\n    ]\n\n\ndef test_has_option(config):\n    assert config.has_option(\"env:base\", \"monitor_speed\")\n    assert not config.has_option(\"custom\", \"monitor_speed\")\n    assert config.has_option(\"env:extra_1\", \"lib_install\")\n    assert config.has_option(\"env:test_extends\", \"lib_compat_mode\")\n    assert config.has_option(\"env:extra_2\", \"src_filter\")\n\n\ndef test_sysenv_options(config):\n    assert config.getraw(\"custom\", \"extra_flags\") == \"\"\n    assert config.get(\"env:base\", \"build_flags\") == [\"-D DEBUG=1\"]\n    assert config.get(\"env:base\", \"upload_port\") is None\n    assert config.get(\"env:extra_2\", \"upload_port\") == \"/dev/extra_2/port\"\n    os.environ[\"PLATFORMIO_BUILD_FLAGS\"] = \"-DSYSENVDEPS1 -DSYSENVDEPS2\"\n    os.environ[\"PLATFORMIO_BUILD_UNFLAGS\"] = \"-DREMOVE_MACRO\"\n    os.environ[\"PLATFORMIO_UPLOAD_PORT\"] = \"/dev/sysenv/port\"\n    os.environ[\"__PIO_TEST_CNF_EXTRA_FLAGS\"] = \"-L /usr/local/lib\"\n    assert config.get(\"custom\", \"extra_flags\") == \"-L /usr/local/lib\"\n    assert config.get(\"env:base\", \"build_flags\") == [\n        \"-D DEBUG=1 -L /usr/local/lib\",\n        \"-DSYSENVDEPS1 -DSYSENVDEPS2\",\n    ]\n    assert config.get(\"env:base\", \"upload_port\") == \"/dev/sysenv/port\"\n    assert config.get(\"env:extra_2\", \"upload_port\") == \"/dev/sysenv/port\"\n    assert config.get(\"env:base\", \"build_unflags\") == [\"-DREMOVE_MACRO\"]\n\n    # env var as option\n    assert config.options(env=\"test_extends\") == [\n        \"extends\",\n        \"build_flags\",\n        \"monitor_speed\",\n        \"lib_ldf_mode\",\n        \"lib_compat_mode\",\n        \"custom_monitor_speed\",\n        \"lib_deps\",\n        \"lib_ignore\",\n        \"custom_builtin_option\",\n        \"build_unflags\",\n        \"upload_port\",\n    ]\n\n    # sysenv dirs\n    custom_core_dir = os.path.join(os.getcwd(), \"custom-core\")\n    custom_src_dir = os.path.join(os.getcwd(), \"custom-src\")\n    custom_build_dir = os.path.join(os.getcwd(), \"custom-build\")\n    os.environ[\"PLATFORMIO_HOME_DIR\"] = custom_core_dir\n    os.environ[\"PLATFORMIO_SRC_DIR\"] = custom_src_dir\n    os.environ[\"PLATFORMIO_BUILD_DIR\"] = custom_build_dir\n    assert os.path.realpath(config.get(\"platformio\", \"core_dir\")) == os.path.realpath(\n        custom_core_dir\n    )\n    assert os.path.realpath(config.get(\"platformio\", \"src_dir\")) == os.path.realpath(\n        custom_src_dir\n    )\n    assert os.path.realpath(config.get(\"platformio\", \"build_dir\")) == os.path.realpath(\n        custom_build_dir\n    )\n\n    # cleanup system environment variables\n    del os.environ[\"PLATFORMIO_BUILD_FLAGS\"]\n    del os.environ[\"PLATFORMIO_BUILD_UNFLAGS\"]\n    del os.environ[\"PLATFORMIO_UPLOAD_PORT\"]\n    del os.environ[\"__PIO_TEST_CNF_EXTRA_FLAGS\"]\n    del os.environ[\"PLATFORMIO_HOME_DIR\"]\n    del os.environ[\"PLATFORMIO_SRC_DIR\"]\n    del os.environ[\"PLATFORMIO_BUILD_DIR\"]\n\n\ndef test_getraw_value(config):\n    # unknown option\n    with pytest.raises(configparser.NoOptionError):\n        config.getraw(\"custom\", \"unknown_option\")\n    # unknown option even if exists in [env]\n    with pytest.raises(configparser.NoOptionError):\n        config.getraw(\"platformio\", \"monitor_speed\")\n\n    # default\n    assert config.getraw(\"unknown\", \"option\", \"default\") == \"default\"\n    assert config.getraw(\"env:base\", \"custom_builtin_option\") == \"release\"\n\n    # known\n    assert config.getraw(\"env:base\", \"targets\") == \"\"\n    assert config.getraw(\"env:extra_1\", \"lib_deps\") == \"574\"\n    assert config.getraw(\"env:extra_1\", \"build_flags\") == (\n        \"\\n-fdata-sections\\n-Wl,--gc-sections\\n\"\n        \"-lc -lm\\n-D DEBUG=1\\n-D SERIAL_BAUD_RATE=9600\"\n    )\n\n    # extended\n    assert config.getraw(\"env:test_extends\", \"lib_ldf_mode\") == \"chain+\"\n    assert config.getraw(\"env\", \"monitor_speed\") == \"9600\"\n    assert config.getraw(\"env:test_extends\", \"monitor_speed\") == \"115200\"\n\n    # dir options\n    packages_dir = os.path.join(DEFAULT_CORE_DIR, \"packages\")\n    assert config.get(\"platformio\", \"packages_dir\") == packages_dir\n    assert (\n        config.getraw(\"custom\", \"debug_server\")\n        == f\"\\n{packages_dir}/tool-openocd/openocd\\n--help\"\n    )\n\n    # renamed option\n    assert config.getraw(\"env:extra_1\", \"lib_install\") == \"574\"\n    assert config.getraw(\"env:extra_1\", \"lib_deps\") == \"574\"\n    assert config.getraw(\"env:base\", \"debug_load_cmd\") == [\"load\"]\n\n\ndef test_get_value(config):\n    assert config.get(\"custom\", \"debug_flags\") == \"-D DEBUG=1\"\n    assert config.get(\"env:extra_1\", \"build_flags\") == [\n        \"-fdata-sections\",\n        \"-Wl,--gc-sections\",\n        \"-lc -lm\",\n        \"-D DEBUG=1\",\n        \"-D SERIAL_BAUD_RATE=9600\",\n    ]\n    assert config.get(\"env:extra_2\", \"build_flags\") == [\"-Og\"]\n    assert config.get(\"env:extra_2\", \"monitor_speed\") == 9600\n    assert config.get(\"env:base\", \"build_flags\") == [\"-D DEBUG=1\"]\n\n    # get default value from ConfigOption\n    assert config.get(\"env:inject_base_env\", \"debug_build_flags\") == [\n        \"-Og\",\n        \"-g2\",\n        \"-ggdb2\",\n        \"-D CUSTOM_DEBUG_FLAG\",\n    ]\n\n    # dir options\n    assert config.get(\"platformio\", \"packages_dir\") == os.path.join(\n        DEFAULT_CORE_DIR, \"packages\"\n    )\n    assert config.get(\"env:extra_2\", \"debug_server\") == [\n        os.path.join(DEFAULT_CORE_DIR, \"packages/tool-openocd/openocd\"),\n        \"--help\",\n    ]\n    # test relative dir\n    assert config.get(\"platformio\", \"src_dir\") == os.path.abspath(\n        os.path.join(os.getcwd(), \"source\")\n    )\n\n    # renamed option\n    assert config.get(\"env:extra_1\", \"lib_install\") == [\"574\"]\n    assert config.get(\"env:extra_1\", \"lib_deps\") == [\"574\"]\n    assert config.get(\"env:base\", \"debug_load_cmd\") == [\"load\"]\n\n\ndef test_items(config):\n    assert config.items(\"custom\") == [\n        (\"src_dir\", \"source\"),\n        (\"debug_flags\", \"-D DEBUG=1\"),\n        (\"lib_flags\", \"-lc -lm\"),\n        (\"extra_flags\", \"\"),\n        (\"lib_ignore\", \"LibIgnoreCustom\"),\n        (\n            \"debug_server\",\n            \"\\n%s/tool-openocd/openocd\\n--help\"\n            % os.path.join(DEFAULT_CORE_DIR, \"packages\"),\n        ),\n        (\"src_filter\", \"-<*>\\n+<a>\\n+<b>\"),\n    ]\n    assert config.items(env=\"base\") == [\n        (\"build_flags\", [\"-D DEBUG=1\"]),\n        (\"lib_compat_mode\", \"strict\"),\n        (\"targets\", []),\n        (\"monitor_speed\", 9600),\n        (\"custom_monitor_speed\", \"115200\"),\n        (\"lib_deps\", [\"Lib1\", \"Lib2\"]),\n        (\"lib_ignore\", [\"LibIgnoreCustom\"]),\n        (\"custom_builtin_option\", \"release\"),\n    ]\n    assert config.items(env=\"extra_1\") == [\n        (\n            \"build_flags\",\n            [\n                \"-fdata-sections\",\n                \"-Wl,--gc-sections\",\n                \"-lc -lm\",\n                \"-D DEBUG=1\",\n                \"-D SERIAL_BAUD_RATE=9600\",\n            ],\n        ),\n        (\"lib_install\", [\"574\"]),\n        (\"monitor_speed\", 9600),\n        (\"custom_monitor_speed\", \"115200\"),\n        (\"lib_deps\", [\"574\"]),\n        (\"lib_ignore\", [\"LibIgnoreCustom\"]),\n        (\"custom_builtin_option\", \"release\"),\n    ]\n    assert config.items(env=\"extra_2\") == [\n        (\"build_flags\", [\"-Og\"]),\n        (\"lib_ignore\", [\"LibIgnoreCustom\", \"Lib3\"]),\n        (\"upload_port\", \"/dev/extra_2/port\"),\n        (\n            \"debug_server\",\n            [\n                \"%s/tool-openocd/openocd\" % os.path.join(DEFAULT_CORE_DIR, \"packages\"),\n                \"--help\",\n            ],\n        ),\n        (\"src_filter\", [\"-<*>\", \"+<a>\", \"+<b> +<c>\"]),\n        (\"monitor_speed\", 9600),\n        (\"custom_monitor_speed\", \"115200\"),\n        (\"lib_deps\", [\"Lib1\", \"Lib2\"]),\n        (\"custom_builtin_option\", \"release\"),\n    ]\n    assert config.items(env=\"test_extends\") == [\n        (\"extends\", [\"strict_settings\"]),\n        (\"build_flags\", [\"-D RELEASE\"]),\n        (\"monitor_speed\", 115200),\n        (\"lib_ldf_mode\", \"chain+\"),\n        (\"lib_compat_mode\", \"strict\"),\n        (\"custom_monitor_speed\", \"115200\"),\n        (\"lib_deps\", [\"Lib1\", \"Lib2\"]),\n        (\"lib_ignore\", [\"LibIgnoreCustom\"]),\n        (\"custom_builtin_option\", \"release\"),\n    ]\n\n\ndef test_update_and_save(tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[platformio]\nextra_configs = a.ini, b.ini\n\n[env:myenv]\nboard = myboard\n    \"\"\")\n    config = ProjectConfig(tmpdir.join(\"platformio.ini\").strpath)\n    assert config.envs() == [\"myenv\"]\n    assert config.as_tuple()[0][1][0][1] == [\"a.ini\", \"b.ini\"]\n\n    config.update(\n        [\n            [\"platformio\", [(\"extra_configs\", [\"extra.ini\"])]],\n            [\"env:myenv\", [(\"framework\", [\"espidf\", \"arduino\"])]],\n            [\"check_types\", [(\"float_option\", 13.99), (\"bool_option\", True)]],\n        ]\n    )\n    assert config.get(\"platformio\", \"extra_configs\") == [\"extra.ini\"]\n    config.remove_section(\"platformio\")\n    assert config.as_tuple() == [\n        (\"env:myenv\", [(\"board\", \"myboard\"), (\"framework\", [\"espidf\", \"arduino\"])]),\n        (\"check_types\", [(\"float_option\", \"13.99\"), (\"bool_option\", \"yes\")]),\n    ]\n\n    config.save()\n    contents = tmpdir.join(\"platformio.ini\").read()\n    assert contents[-4:] == \"yes\\n\"\n    lines = [\n        line.strip()\n        for line in contents.split(\"\\n\")\n        if line.strip() and not line.startswith((\";\", \"#\"))\n    ]\n    assert lines == [\n        \"[env:myenv]\",\n        \"board = myboard\",\n        \"framework =\",\n        \"espidf\",\n        \"arduino\",\n        \"[check_types]\",\n        \"float_option = 13.99\",\n        \"bool_option = yes\",\n    ]\n\n\ndef test_update_and_clear(tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[platformio]\nextra_configs = a.ini, b.ini\n\n[env:myenv]\nboard = myboard\n    \"\"\")\n    config = ProjectConfig(tmpdir.join(\"platformio.ini\").strpath)\n    assert config.sections() == [\"platformio\", \"env:myenv\"]\n    config.update([[\"mysection\", [(\"opt1\", \"value1\"), (\"opt2\", \"value2\")]]], clear=True)\n    assert config.as_tuple() == [\n        (\"mysection\", [(\"opt1\", \"value1\"), (\"opt2\", \"value2\")])\n    ]\n\n\ndef test_dump(tmpdir_factory):\n    tmpdir = tmpdir_factory.mktemp(\"project\")\n    tmpdir.join(\"platformio.ini\").write(BASE_CONFIG)\n    tmpdir.join(\"extra_envs.ini\").write(EXTRA_ENVS_CONFIG)\n    tmpdir.join(\"extra_debug.ini\").write(EXTRA_DEBUG_CONFIG)\n    config = ProjectConfig(\n        tmpdir.join(\"platformio.ini\").strpath,\n        parse_extra=False,\n        expand_interpolations=False,\n    )\n    assert config.as_tuple() == [\n        (\n            \"platformio\",\n            [\n                (\"env_default\", [\"base\", \"extra_2\"]),\n                (\"src_dir\", \"${custom.src_dir}\"),\n                (\"extra_configs\", [\"extra_envs.ini\", \"extra_debug.ini\"]),\n            ],\n        ),\n        (\n            \"env\",\n            [\n                (\"monitor_speed\", 9600),\n                (\"custom_monitor_speed\", \"115200\"),\n                (\"lib_deps\", [\"Lib1\", \"Lib2\"]),\n                (\"lib_ignore\", [\"${custom.lib_ignore}\"]),\n                (\"custom_builtin_option\", \"${env.build_type}\"),\n            ],\n        ),\n        (\"strict_ldf\", [(\"lib_ldf_mode\", \"chain+\"), (\"lib_compat_mode\", \"strict\")]),\n        (\"monitor_custom\", [(\"monitor_speed\", \"${env.custom_monitor_speed}\")]),\n        (\n            \"strict_settings\",\n            [(\"extends\", \"strict_ldf, monitor_custom\"), (\"build_flags\", \"-D RELEASE\")],\n        ),\n        (\n            \"custom\",\n            [\n                (\"src_dir\", \"source\"),\n                (\"debug_flags\", \"-D RELEASE\"),\n                (\"lib_flags\", \"-lc -lm\"),\n                (\"extra_flags\", \"${sysenv.__PIO_TEST_CNF_EXTRA_FLAGS}\"),\n                (\"lib_ignore\", \"LibIgnoreCustom\"),\n            ],\n        ),\n        (\n            \"env:base\",\n            [\n                (\"build_flags\", [\"${custom.debug_flags} ${custom.extra_flags}\"]),\n                (\"lib_compat_mode\", \"${strict_ldf.lib_compat_mode}\"),\n                (\"targets\", []),\n            ],\n        ),\n        (\"env:test_extends\", [(\"extends\", [\"strict_settings\"])]),\n        (\n            \"env:inject_base_env\",\n            [\n                (\n                    \"debug_build_flags\",\n                    [\"${env.debug_build_flags}\", \"-D CUSTOM_DEBUG_FLAG\"],\n                )\n            ],\n        ),\n    ]\n\n\n@pytest.mark.skipif(sys.platform != \"win32\", reason=\"runs only on windows\")\ndef test_win_core_root_dir(tmpdir_factory):\n    try:\n        win_core_root_dir = os.path.splitdrive(fs.expanduser(\"~\"))[0] + \"\\\\.platformio\"\n        remove_dir_at_exit = False\n        if not os.path.isdir(win_core_root_dir):\n            remove_dir_at_exit = True\n            os.makedirs(win_core_root_dir)\n\n        # Default config\n        config = ProjectConfig()\n        assert config.get(\"platformio\", \"core_dir\") == win_core_root_dir\n        assert config.get(\"platformio\", \"packages_dir\") == os.path.join(\n            win_core_root_dir, \"packages\"\n        )\n\n        # Override in config\n        tmpdir = tmpdir_factory.mktemp(\"project\")\n        tmpdir.join(\"platformio.ini\").write(\"\"\"\n[platformio]\ncore_dir = ~/.pio\n        \"\"\")\n        config = ProjectConfig(tmpdir.join(\"platformio.ini\").strpath)\n        assert config.get(\"platformio\", \"core_dir\") != win_core_root_dir\n        assert config.get(\"platformio\", \"core_dir\") == os.path.realpath(\n            fs.expanduser(\"~/.pio\")\n        )\n\n        if remove_dir_at_exit:\n            fs.rmtree(win_core_root_dir)\n    except PermissionError:\n        pass\n\n\ndef test_this(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[common]\nboard = uno\n\n[env:myenv]\nextends = common\nbuild_flags = -D${this.__env__}\ncustom_option = ${this.board}\n    \"\"\")\n    config = ProjectConfig(str(project_conf))\n    assert config.get(\"env:myenv\", \"custom_option\") == \"uno\"\n    assert config.get(\"env:myenv\", \"build_flags\") == [\"-Dmyenv\"]\n\n\ndef test_project_name(tmp_path: Path):\n    project_dir = tmp_path / \"my-project-name\"\n    project_dir.mkdir()\n    project_conf = project_dir / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[env:myenv]\n    \"\"\")\n    with fs.cd(str(project_dir)):\n        config = ProjectConfig(str(project_conf))\n        assert config.get(\"platformio\", \"name\") == \"my-project-name\"\n\n    # custom name\n    project_conf.write_text(\"\"\"\n[platformio]\nname = custom-project-name\n    \"\"\")\n    config = ProjectConfig(str(project_conf))\n    assert config.get(\"platformio\", \"name\") == \"custom-project-name\"\n\n\ndef test_nested_interpolation(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[platformio]\nbuild_dir = /tmp/pio-$PROJECT_HASH\ndata_dir = $PROJECT_DIR/assets\n\n[env:myenv]\nbuild_flags =\n    -D UTIME=${UNIX_TIME}\n    -I ${PROJECTSRC_DIR}/hal\n    -Wl,-Map,${BUILD_DIR}/${PROGNAME}.map\ntest_testing_command =\n    ${platformio.packages_dir}/tool-simavr/bin/simavr\n     -m\n     atmega328p\n     -f\n     16000000L\n     ${UPLOAD_PORT and \"-p \"+UPLOAD_PORT}\n     ${platformio.build_dir}/${this.__env__}/firmware.elf\n    \"\"\")\n    config = ProjectConfig(str(project_conf))\n    assert config.get(\"platformio\", \"data_dir\").endswith(\n        os.path.join(\"$PROJECT_DIR\", \"assets\")\n    )\n    assert config.get(\"env:myenv\", \"build_flags\")[0][-10:].isdigit()\n    assert config.get(\"env:myenv\", \"build_flags\")[1] == \"-I ${PROJECTSRC_DIR}/hal\"\n    assert (\n        config.get(\"env:myenv\", \"build_flags\")[2]\n        == \"-Wl,-Map,${BUILD_DIR}/${PROGNAME}.map\"\n    )\n    testing_command = config.get(\"env:myenv\", \"test_testing_command\")\n    assert \"$\" not in testing_command[0]\n    assert testing_command[5] == '${UPLOAD_PORT and \"-p \"+UPLOAD_PORT}'\n\n\ndef test_extends_order(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[a]\nboard = test\n\n[b]\nupload_tool = two\n\n[c]\nupload_tool = three\n\n[env:na_ti-ve13]\nextends = a, b, c\n    \"\"\")\n    config = ProjectConfig(str(project_conf))\n    assert config.get(\"env:na_ti-ve13\", \"upload_tool\") == \"three\"\n\n\ndef test_invalid_env_names(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[env:app:1]\n    \"\"\")\n    config = ProjectConfig(str(project_conf))\n    with pytest.raises(InvalidEnvNameError, match=r\".*Invalid environment name 'app:1\"):\n        config.validate()\n\n\ndef test_linting_errors(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[env:app1]\nlib_use = 1\nbroken_line\n    \"\"\")\n    result = ProjectConfig.lint(str(project_conf))\n    assert not result[\"warnings\"]\n    assert result[\"errors\"] and len(result[\"errors\"]) == 1\n    error = result[\"errors\"][0]\n    assert error[\"type\"] == \"ParsingError\"\n    assert error[\"lineno\"] == 4\n\n\ndef test_linting_warnings(tmp_path: Path):\n    project_conf = tmp_path / \"platformio.ini\"\n    project_conf.write_text(\"\"\"\n[platformio]\nbuild_dir = /tmp/pio-$PROJECT_HASH\n\n[env:app1]\nlib_use = 1\ntest_testing_command = /usr/bin/flash-tool -p $UPLOAD_PORT -b $UPLOAD_SPEED\n    \"\"\")\n    result = ProjectConfig.lint(str(project_conf))\n    assert not result[\"errors\"]\n    assert result[\"warnings\"] and len(result[\"warnings\"]) == 2\n    assert \"deprecated\" in result[\"warnings\"][0]\n    assert \"Invalid variable declaration\" in result[\"warnings\"][1]\n"
  },
  {
    "path": "tests/project/test_metadata.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport json\n\nfrom platformio.project.commands.metadata import project_metadata_cmd\n\n\ndef test_metadata_dump(clirunner, validate_cliresult, tmpdir):\n    tmpdir.join(\"platformio.ini\").write(\"\"\"\n[env:native]\nplatform = native\n\"\"\")\n\n    component_dir = tmpdir.mkdir(\"lib\").mkdir(\"component\")\n    component_dir.join(\"library.json\").write(\"\"\"\n{\n    \"name\": \"component\",\n    \"version\": \"1.0.0\"\n}\n    \"\"\")\n    component_dir.mkdir(\"include\").join(\"component.h\").write(\"\"\"\n#define I_AM_COMPONENT\n\nvoid dummy(void);\n    \"\"\")\n    component_dir.mkdir(\"src\").join(\"component.cpp\").write(\"\"\"\n#include <component.h>\n\nvoid dummy(void ) {};\n    \"\"\")\n\n    tmpdir.mkdir(\"src\").join(\"main.c\").write(\"\"\"\n#include <component.h>\n\n#ifndef I_AM_COMPONENT\n#error \"I_AM_COMPONENT\"\n#endif\n\nint main() {\n}\n\"\"\")\n\n    metadata_path = tmpdir.join(\"metadata.json\")\n    result = clirunner.invoke(\n        project_metadata_cmd,\n        [\n            \"--project-dir\",\n            str(tmpdir),\n            \"-e\",\n            \"native\",\n            \"--json-output\",\n            \"--json-output-path\",\n            str(metadata_path),\n        ],\n    )\n    validate_cliresult(result)\n    with open(str(metadata_path), encoding=\"utf8\") as fp:\n        metadata = json.load(fp)[\"native\"]\n    assert len(metadata[\"includes\"][\"build\"]) == 3\n    assert len(metadata[\"includes\"][\"compatlib\"]) == 2\n"
  },
  {
    "path": "tests/project/test_savedeps.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nfrom platformio.package.meta import PackageSpec\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.savedeps import save_project_dependencies\n\nPROJECT_CONFIG_TPL = \"\"\"\n[env]\nboard = uno\nframework = arduino\nlib_deps =\n    SPI\nplatform_packages =\n    platformio/tool-jlink@^1.75001.0\n\n[env:bare]\n\n[env:release]\nplatform = platformio/espressif32\nlib_deps =\n    milesburton/DallasTemperature@^3.8\n\n[env:debug]\nplatform = platformio/espressif32@^3.4.0\nlib_deps =\n    ${env.lib_deps}\n    milesburton/DallasTemperature@^3.9.1\n    bblanchon/ArduinoJson\nplatform_packages =\n    ${env.platform_packages}\n    platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git\n\"\"\"\n\n\ndef test_save_libraries(tmp_path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    specs = [\n        PackageSpec(\"milesburton/DallasTemperature@^3.9\"),\n        PackageSpec(\"adafruit/Adafruit GPS Library@^1.6.0\"),\n        PackageSpec(\"https://github.com/nanopb/nanopb.git\"),\n    ]\n\n    # add to the specified environment\n    save_project_dependencies(\n        str(project_dir), specs, scope=\"lib_deps\", action=\"add\", environments=[\"debug\"]\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"lib_deps\") == [\n        \"SPI\",\n        \"bblanchon/ArduinoJson\",\n        \"milesburton/DallasTemperature@^3.9\",\n        \"adafruit/Adafruit GPS Library@^1.6.0\",\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n    assert config.get(\"env:bare\", \"lib_deps\") == [\"SPI\"]\n    assert config.get(\"env:release\", \"lib_deps\") == [\n        \"milesburton/DallasTemperature@^3.8\"\n    ]\n\n    # add to the the all environments\n    save_project_dependencies(str(project_dir), specs, scope=\"lib_deps\", action=\"add\")\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"lib_deps\") == [\n        \"SPI\",\n        \"bblanchon/ArduinoJson\",\n        \"milesburton/DallasTemperature@^3.9\",\n        \"adafruit/Adafruit GPS Library@^1.6.0\",\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n    assert config.get(\"env:bare\", \"lib_deps\") == [\n        \"milesburton/DallasTemperature@^3.9\",\n        \"adafruit/Adafruit GPS Library@^1.6.0\",\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n    assert config.get(\"env:release\", \"lib_deps\") == [\n        \"milesburton/DallasTemperature@^3.9\",\n        \"adafruit/Adafruit GPS Library@^1.6.0\",\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n\n    # remove deps from env\n    save_project_dependencies(\n        str(project_dir),\n        [PackageSpec(\"milesburton/DallasTemperature\")],\n        scope=\"lib_deps\",\n        action=\"remove\",\n        environments=[\"release\"],\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:release\", \"lib_deps\") == [\n        \"adafruit/Adafruit GPS Library@^1.6.0\",\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n    # invalid requirements\n    save_project_dependencies(\n        str(project_dir),\n        [PackageSpec(\"adafruit/Adafruit GPS Library@^9.9.9\")],\n        scope=\"lib_deps\",\n        action=\"remove\",\n        environments=[\"release\"],\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:release\", \"lib_deps\") == [\n        \"https://github.com/nanopb/nanopb.git\",\n    ]\n\n    # remove deps from all envs\n    save_project_dependencies(\n        str(project_dir), specs, scope=\"lib_deps\", action=\"remove\"\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"lib_deps\") == [\n        \"SPI\",\n        \"bblanchon/ArduinoJson\",\n    ]\n    assert config.get(\"env:bare\", \"lib_deps\") == [\"SPI\"]\n    assert config.get(\"env:release\", \"lib_deps\") == [\"SPI\"]\n\n\ndef test_save_tools(tmp_path):\n    project_dir = tmp_path / \"project\"\n    project_dir.mkdir()\n    (project_dir / \"platformio.ini\").write_text(PROJECT_CONFIG_TPL)\n    specs = [\n        PackageSpec(\"platformio/framework-espidf@^2\"),\n        PackageSpec(\"platformio/tool-esptoolpy\"),\n    ]\n\n    # add to the specified environment\n    save_project_dependencies(\n        str(project_dir),\n        specs,\n        scope=\"platform_packages\",\n        action=\"add\",\n        environments=[\"debug\"],\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\",\n        \"platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git\",\n        \"platformio/framework-espidf@^2\",\n        \"platformio/tool-esptoolpy\",\n    ]\n    assert config.get(\"env:bare\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\"\n    ]\n    assert config.get(\"env:release\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\"\n    ]\n\n    # add to the the all environments\n    save_project_dependencies(\n        str(project_dir), specs, scope=\"platform_packages\", action=\"add\"\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\",\n        \"platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git\",\n        \"platformio/framework-espidf@^2\",\n        \"platformio/tool-esptoolpy\",\n    ]\n    assert config.get(\"env:bare\", \"platform_packages\") == [\n        \"platformio/framework-espidf@^2\",\n        \"platformio/tool-esptoolpy\",\n    ]\n    assert config.get(\"env:release\", \"platform_packages\") == [\n        \"platformio/framework-espidf@^2\",\n        \"platformio/tool-esptoolpy\",\n    ]\n\n    # remove deps from env\n    save_project_dependencies(\n        str(project_dir),\n        [PackageSpec(\"platformio/framework-espidf\")],\n        scope=\"platform_packages\",\n        action=\"remove\",\n        environments=[\"release\"],\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:release\", \"platform_packages\") == [\n        \"platformio/tool-esptoolpy\",\n    ]\n    # invalid requirements\n    save_project_dependencies(\n        str(project_dir),\n        [PackageSpec(\"platformio/tool-esptoolpy@9.9.9\")],\n        scope=\"platform_packages\",\n        action=\"remove\",\n        environments=[\"release\"],\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:release\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\",\n    ]\n\n    # remove deps from all envs\n    save_project_dependencies(\n        str(project_dir), specs, scope=\"platform_packages\", action=\"remove\"\n    )\n    config = ProjectConfig.get_instance(str(project_dir / \"platformio.ini\"))\n    assert config.get(\"env:debug\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\",\n        \"platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git\",\n    ]\n    assert config.get(\"env:bare\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\"\n    ]\n    assert config.get(\"env:release\", \"platform_packages\") == [\n        \"platformio/tool-jlink@^1.75001.0\"\n    ]\n"
  },
  {
    "path": "tests/test_examples.py",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport os\nimport random\nfrom glob import glob\n\nimport pytest\n\nfrom platformio import fs, proc\nfrom platformio.package.manager.platform import PlatformPackageManager\nfrom platformio.platform.factory import PlatformFactory\nfrom platformio.project.config import ProjectConfig\nfrom platformio.project.exception import ProjectError\n\n\ndef pytest_generate_tests(metafunc):\n    if \"pioproject_dir\" not in metafunc.fixturenames:\n        return\n    examples_dirs = []\n\n    # repo examples\n    examples_dirs.append(\n        os.path.normpath(os.path.join(os.path.dirname(__file__), \"..\", \"examples\"))\n    )\n\n    # dev/platforms\n    for pkg in PlatformPackageManager().get_installed():\n        p = PlatformFactory.new(pkg)\n        examples_dir = os.path.join(p.get_dir(), \"examples\")\n        if os.path.isdir(examples_dir):\n            examples_dirs.append(examples_dir)\n\n    project_dirs = []\n    for examples_dir in examples_dirs:\n        candidates = {}\n        for root, _, files in os.walk(examples_dir):\n            if \"platformio.ini\" not in files or \".skiptest\" in files:\n                continue\n            if \"mbed-legacy-examples\" in root:\n                continue\n            group = os.path.basename(root)\n            if \"-\" in group:\n                group = group.split(\"-\", 1)[0]\n            if group not in candidates:\n                candidates[group] = []\n            candidates[group].append(root)\n\n        project_dirs.extend(\n            [random.choice(examples) for examples in candidates.values() if examples]\n        )\n\n    metafunc.parametrize(\"pioproject_dir\", sorted(project_dirs))\n\n\ndef test_run(pioproject_dir):\n    with fs.cd(pioproject_dir):\n        config = ProjectConfig()\n\n        # temporary fix for unreleased dev-platforms with broken env name\n        try:\n            config.validate()\n        except ProjectError as exc:\n            pytest.skip(str(exc))\n\n        build_dir = config.get(\"platformio\", \"build_dir\")\n        if os.path.isdir(build_dir):\n            fs.rmtree(build_dir)\n\n        env_names = config.envs()\n        result = proc.exec_command(\n            [\"platformio\", \"run\", \"-e\", random.choice(env_names)]\n        )\n        if result[\"returncode\"] != 0:\n            pytest.fail(str(result))\n\n        assert os.path.isdir(build_dir)\n\n        # check .elf file\n        for item in os.listdir(build_dir):\n            if not os.path.isdir(item):\n                continue\n            assert os.path.isfile(os.path.join(build_dir, item, \"firmware.elf\"))\n            # check .hex or .bin files\n            firmwares = []\n            for ext in (\"bin\", \"hex\"):\n                firmwares += glob(os.path.join(build_dir, item, \"firmware*.%s\" % ext))\n            if not firmwares:\n                pytest.fail(\"Missed firmware file\")\n            for firmware in firmwares:\n                assert os.path.getsize(firmware) > 0\n"
  },
  {
    "path": "tox.ini",
    "content": "# Copyright (c) 2014-present PlatformIO <contact@platformio.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n[isort]\nprofile = black\nknown_third_party=OpenSSL, SCons, jsonrpc, twisted, zope\n\n[pytest]\nfilterwarnings =\n    error\n    # Bottle\n    ignore:.*'cgi' is deprecated and slated for removal\n    ignore:'protected_args' is deprecated and will be removed in Click 9.0:DeprecationWarning\n\n\n[testenv]\npassenv = *\nusedevelop = True\ndeps =\n    black\n    codespell\n    isort\n    jsondiff\n    pylint\n    pytest\n    pytest-xdist\ncommands =\n    {envpython} --version\n    pio system info\n\n[testenv:lint]\ncommands =\n    {envpython} --version\n    pylint --rcfile=./.pylintrc ./platformio\n    pylint --rcfile=./.pylintrc ./tests\n\n[testenv:testcore]\ncommands =\n    {envpython} --version\n    py.test -v --basetemp={envtmpdir} -k \"not skip_ci\" tests --ignore tests/test_examples.py\n\n[testenv:testexamples]\ncommands =\n    {envpython} scripts/install_devplatforms.py\n    py.test -v --basetemp={envtmpdir} tests/test_examples.py\n\n[testenv:docs]\ndeps =\n    sphinx-rtd-theme==3.0.2\n    sphinxcontrib-googleanalytics\n    sphinx-notfound-page\n    sphinx-copybutton\n    restructuredtext-lint\nchange_dir = docs\ncommands =\n    sphinx-build -b html . _build/html\n\n[testenv:docslinkcheck]\ndeps =\n    {[testenv:docs]deps}\nchange_dir = docs\ncommands =\n    sphinx-build -b linkcheck . _build\n"
  }
]