forked from platformio/platformio-core
Merge branch 'release/v6.1.8'
This commit is contained in:
commit
bbcd92b7c6
103 files changed
+969
-699
No files matched your search
@@ -6,9 +6,8 @@ What kind of issue is this?
|
||||
use [Community Forums](https://community.platformio.org) or [Premium Support](https://platformio.org/support)
|
||||
|
||||
- [ ] **PlatformIO IDE**.
|
||||
All issues related to PlatformIO IDE should be reported to appropriate repository:
|
||||
[PlatformIO IDE for Atom](https://github.com/platformio/platformio-atom-ide/issues) or
|
||||
[PlatformIO IDE for VSCode](https://github.com/platformio/platformio-vscode-ide/issues)
|
||||
All issues related to PlatformIO IDE should be reported to the
|
||||
[PlatformIO IDE for VSCode](https://github.com/platformio/platformio-vscode-ide/issues) repository
|
||||
|
||||
- [ ] **Development Platform or Board**.
|
||||
All issues (building, uploading, adding new boards, etc.) related to PlatformIO development platforms
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
tox -e testcore
|
||||
|
||||
- name: Build Python source tarball
|
||||
run: python setup.py sdist
|
||||
run: python setup.py sdist bdist_wheel
|
||||
|
||||
- name: Publish package to PyPI
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
folder: "Marlin"
|
||||
config_dir: "Marlin"
|
||||
env_name: "mega2560"
|
||||
# - esphome:
|
||||
# repository: "esphome/esphome"
|
||||
# folder: "esphome"
|
||||
# config_dir: "esphome"
|
||||
# env_name: "esp32-arduino"
|
||||
- esphome:
|
||||
repository: "esphome/esphome"
|
||||
folder: "esphome"
|
||||
config_dir: "esphome"
|
||||
env_name: "esp32-arduino"
|
||||
- smartknob:
|
||||
repository: "scottbez1/smartknob"
|
||||
folder: "smartknob"
|
||||
|
||||
+5
-4
@@ -6,12 +6,13 @@ To get started, <a href="https://cla-assistant.io/platformio/platformio-core">si
|
||||
1. Fork the repository on GitHub
|
||||
2. Clone repository `git clone --recursive https://github.com/YourGithubUsername/platformio-core.git`
|
||||
3. Run `pip install tox`
|
||||
4. Go to the root of project where is located `tox.ini` and run `tox -e py37`
|
||||
4. Go to the root of the PlatformIO Core project where `tox.ini` is located (``cd platformio-core``) and run `tox -e py39`.
|
||||
You can replace `py39` with your own Python version. For example, `py311` means Python 3.11.
|
||||
5. Activate current development environment:
|
||||
|
||||
* Windows: `.tox\py37\Scripts\activate`
|
||||
* Bash/ZSH: `source .tox/py37/bin/activate`
|
||||
* Fish: `source .tox/py37/bin/activate.fish`
|
||||
* Windows: `.tox\py39\Scripts\activate`
|
||||
* Bash/ZSH: `source .tox/py39/bin/activate`
|
||||
* Fish: `source .tox/py39/bin/activate.fish`
|
||||
|
||||
6. Make changes to code, documentation, etc.
|
||||
7. Lint source code `make before-commit`
|
||||
|
||||
+13
@@ -15,6 +15,19 @@ PlatformIO Core 6
|
||||
|
||||
**A professional collaborative platform for declarative, safety-critical, and test-driven embedded development.**
|
||||
|
||||
6.1.8 (2023-07-05)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Added a new ``--lint`` option to the `pio project config <https://docs.platformio.org/en/latest/core/userguide/project/cmd_config.html>`__ command, enabling users to efficiently perform linting on the |PIOCONF|
|
||||
* Enhanced the parsing of the |PIOCONF| to provide comprehensive diagnostic information
|
||||
* Expanded the functionality of the |LIBRARYJSON| manifest by allowing the use of the underscore symbol in the `keywords <https://docs.platformio.org/en/latest/manifests/library-json/fields/keywords.html>`__ field
|
||||
* Optimized project integration templates to address the issue of long paths on Windows (`issue #4652 <https://github.com/platformio/platformio-core/issues/4652>`_)
|
||||
* Refactored |UNITTESTING| engine to resolve compiler warnings with "-Wpedantic" option (`pull #4671 <https://github.com/platformio/platformio-core/pull/4671>`_)
|
||||
* Eliminated erroneous warning regarding the use of obsolete PlatformIO Core when downgrading to the stable version (`issue #4664 <https://github.com/platformio/platformio-core/issues/4664>`_)
|
||||
* Updated the `pio project metadata <https://docs.platformio.org/en/latest/core/userguide/project/cmd_metadata.html>`__ command to return C/C++ flags as parsed Unix shell arguments when dumping project build metadata
|
||||
* Resolved a critical issue related to the usage of the ``-include`` flag within the `build_flags <https://docs.platformio.org/en/latest/projectconf/sections/env/options/build/build_flags.html>`__ option, specifically when employing dynamic variables (`issue #4682 <https://github.com/platformio/platformio-core/issues/4682>`_)
|
||||
* Removed PlatformIO IDE for Atom from the documentation as `Atom has been deprecated <https://github.blog/2022-06-08-sunsetting-atom/>`__
|
||||
|
||||
6.1.7 (2023-05-08)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+1
-1
Submodule docs updated: 98609771ba...3f462c9ae6.
+1
-1
Submodule examples updated: 3e23b5ac43...4b572ec9fe.
@@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
VERSION = (6, 1, 7)
|
||||
VERSION = (6, 1, 8)
|
||||
__version__ = ".".join([str(s) for s in VERSION])
|
||||
|
||||
__title__ = "platformio"
|
||||
|
||||
+10
-7
@@ -14,7 +14,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from traceback import format_exc
|
||||
import traceback
|
||||
|
||||
import click
|
||||
|
||||
@@ -53,13 +53,13 @@ def cli(ctx, force, caller, no_ansi): # pylint: disable=unused-argument
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
|
||||
maintenance.on_platformio_start(ctx, caller)
|
||||
maintenance.on_cmd_start(ctx, caller)
|
||||
|
||||
|
||||
@cli.result_callback()
|
||||
@click.pass_context
|
||||
def process_result(ctx, result, *_, **__):
|
||||
maintenance.on_platformio_end(ctx, result)
|
||||
def process_result(*_, **__):
|
||||
maintenance.on_cmd_end()
|
||||
|
||||
|
||||
def configure():
|
||||
@@ -96,6 +96,7 @@ def main(argv=None):
|
||||
if argv:
|
||||
assert isinstance(argv, list)
|
||||
sys.argv = argv
|
||||
|
||||
try:
|
||||
ensure_python3(raise_exception=True)
|
||||
configure()
|
||||
@@ -106,18 +107,18 @@ def main(argv=None):
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
if not isinstance(exc, exception.ReturnErrorCode):
|
||||
maintenance.on_platformio_exception(exc)
|
||||
error_str = "Error: "
|
||||
error_str = f"{exc.__class__.__name__}: "
|
||||
if isinstance(exc, exception.PlatformioException):
|
||||
error_str += str(exc)
|
||||
else:
|
||||
error_str += format_exc()
|
||||
error_str += traceback.format_exc()
|
||||
error_str += """
|
||||
============================================================
|
||||
|
||||
An unexpected error occurred. Further steps:
|
||||
|
||||
* Verify that you have the latest version of PlatformIO using
|
||||
`pip install -U platformio` command
|
||||
`python -m pip install -U platformio` command
|
||||
|
||||
* Try to find answer in FAQ Troubleshooting section
|
||||
https://docs.platformio.org/page/faq/index.html
|
||||
@@ -129,6 +130,8 @@ An unexpected error occurred. Further steps:
|
||||
"""
|
||||
click.secho(error_str, fg="red", err=True)
|
||||
exit_code = int(str(exc)) if str(exc).isdigit() else 1
|
||||
|
||||
maintenance.on_platformio_exit()
|
||||
sys.argv = prev_sys_argv
|
||||
return exit_code
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import os
|
||||
import time
|
||||
|
||||
from platformio import __accounts_api__, app
|
||||
from platformio.exception import PlatformioException
|
||||
from platformio.exception import PlatformioException, UserSideException
|
||||
from platformio.http import HTTPClient, HTTPClientError
|
||||
|
||||
|
||||
@@ -24,11 +24,11 @@ class AccountError(PlatformioException):
|
||||
MESSAGE = "{0}"
|
||||
|
||||
|
||||
class AccountNotAuthorized(AccountError):
|
||||
class AccountNotAuthorized(AccountError, UserSideException):
|
||||
MESSAGE = "You are not authorized! Please log in to PlatformIO Account."
|
||||
|
||||
|
||||
class AccountAlreadyAuthorized(AccountError):
|
||||
class AccountAlreadyAuthorized(AccountError, UserSideException):
|
||||
MESSAGE = "You are already authorized with {0} account."
|
||||
|
||||
|
||||
|
||||
+11
-4
@@ -18,6 +18,7 @@ import json
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
import uuid
|
||||
|
||||
from platformio import __version__, exception, fs, proc
|
||||
@@ -68,18 +69,23 @@ SESSION_VARS = {
|
||||
"command_ctx": None,
|
||||
"caller_id": None,
|
||||
"custom_project_conf": None,
|
||||
"pause_telemetry": False,
|
||||
}
|
||||
|
||||
|
||||
def resolve_state_path(conf_option_dir, file_name, ensure_dir_exists=True):
|
||||
state_dir = ProjectConfig.get_instance().get("platformio", conf_option_dir)
|
||||
if ensure_dir_exists and not os.path.isdir(state_dir):
|
||||
os.makedirs(state_dir)
|
||||
return os.path.join(state_dir, file_name)
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, path=None, lock=False):
|
||||
self.path = path
|
||||
self.lock = lock
|
||||
if not self.path:
|
||||
core_dir = ProjectConfig.get_instance().get("platformio", "core_dir")
|
||||
if not os.path.isdir(core_dir):
|
||||
os.makedirs(core_dir)
|
||||
self.path = os.path.join(core_dir, "appstate.json")
|
||||
self.path = resolve_state_path("core_dir", "appstate.json")
|
||||
self._storage = {}
|
||||
self._lockfile = None
|
||||
self.modified = False
|
||||
@@ -248,6 +254,7 @@ def get_cid():
|
||||
cid = str(cid)
|
||||
if IS_WINDOWS or os.getuid() > 0: # pylint: disable=no-member
|
||||
set_state_item("cid", cid)
|
||||
set_state_item("created_at", int(time.time()))
|
||||
return cid
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
% for include in filter_includes(includes):
|
||||
-I{{include}}
|
||||
% end
|
||||
% for define in defines:
|
||||
-D{{!define}}
|
||||
% end
|
||||
@@ -1,9 +0,0 @@
|
||||
% _defines = " ".join(["-D%s" % d.replace(" ", "\\\\ ") for d in defines])
|
||||
{
|
||||
"execPath": "{{ cxx_path }}",
|
||||
"gccDefaultCFlags": "-fsyntax-only {{! to_unix_path(cc_flags).replace(' -MMD ', ' ').replace('"', '\\"') }} {{ !_defines.replace('"', '\\"') }}",
|
||||
"gccDefaultCppFlags": "-fsyntax-only {{! to_unix_path(cxx_flags).replace(' -MMD ', ' ').replace('"', '\\"') }} {{ !_defines.replace('"', '\\"') }}",
|
||||
"gccErrorLimit": 15,
|
||||
"gccIncludePaths": "{{ ','.join(filter_includes(includes)) }}",
|
||||
"gccSuppressWarnings": false
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.pio
|
||||
.clang_complete
|
||||
.gcc-flags.json
|
||||
@@ -1 +0,0 @@
|
||||
{{cc_flags.replace('-mlongcalls', '-mlong-calls')}}
|
||||
@@ -1 +0,0 @@
|
||||
{{cxx_flags.replace('-mlongcalls', '-mlong-calls')}}
|
||||
@@ -72,7 +72,8 @@ DEFAULT_ENV_OPTIONS = dict(
|
||||
variables=clivars,
|
||||
# Propagating External Environment
|
||||
ENV=os.environ,
|
||||
UNIX_TIME=int(time()),
|
||||
TIMESTAMP=int(time()),
|
||||
UNIX_TIME="$TIMESTAMP", # deprecated
|
||||
BUILD_DIR=os.path.join("$PROJECT_BUILD_DIR", "$PIOENV"),
|
||||
BUILD_SRC_DIR=os.path.join("$BUILD_DIR", "src"),
|
||||
BUILD_TEST_DIR=os.path.join("$BUILD_DIR", "test"),
|
||||
|
||||
@@ -200,13 +200,16 @@ def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
|
||||
# fix relative CPPPATH & LIBPATH
|
||||
for k in ("CPPPATH", "LIBPATH"):
|
||||
for i, p in enumerate(result.get(k, [])):
|
||||
p = env.subst(p)
|
||||
if os.path.isdir(p):
|
||||
result[k][i] = os.path.abspath(p)
|
||||
|
||||
# fix relative path for "-include"
|
||||
for i, f in enumerate(result.get("CCFLAGS", [])):
|
||||
if isinstance(f, tuple) and f[0] == "-include":
|
||||
result["CCFLAGS"][i] = (f[0], env.File(os.path.abspath(f[1].get_path())))
|
||||
p = env.subst(f[1].get_path())
|
||||
if os.path.exists(p):
|
||||
result["CCFLAGS"][i] = (f[0], os.path.abspath(p))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import glob
|
||||
import os
|
||||
|
||||
import click
|
||||
import SCons.Defaults # pylint: disable=import-error
|
||||
import SCons.Subst # pylint: disable=import-error
|
||||
from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
|
||||
@@ -154,8 +155,12 @@ def DumpIntegrationData(*args):
|
||||
],
|
||||
"defines": dump_defines(projenv),
|
||||
"includes": projenv.DumpIntegrationIncludes(),
|
||||
"cc_flags": _subst_cmd(projenv, "$CFLAGS $CCFLAGS $CPPFLAGS"),
|
||||
"cxx_flags": _subst_cmd(projenv, "$CXXFLAGS $CCFLAGS $CPPFLAGS"),
|
||||
"cc_flags": click.parser.split_arg_string(
|
||||
_subst_cmd(projenv, "$CFLAGS $CCFLAGS $CPPFLAGS")
|
||||
),
|
||||
"cxx_flags": click.parser.split_arg_string(
|
||||
_subst_cmd(projenv, "$CXXFLAGS $CCFLAGS $CPPFLAGS")
|
||||
),
|
||||
"cc_path": where_is_program(
|
||||
globalenv.subst("$CC"), globalenv.subst("${ENV['PATH']}")
|
||||
),
|
||||
|
||||
@@ -60,8 +60,8 @@ class CheckToolBase: # pylint: disable=too-many-instance-attributes
|
||||
data = load_build_metadata(self.project_dir, self.envname)
|
||||
if not data:
|
||||
return
|
||||
self.cc_flags = click.parser.split_arg_string(data.get("cc_flags", ""))
|
||||
self.cxx_flags = click.parser.split_arg_string(data.get("cxx_flags", ""))
|
||||
self.cc_flags = data.get("cc_flags", [])
|
||||
self.cxx_flags = data.get("cxx_flags", [])
|
||||
self.cpp_includes = self._dump_includes(data.get("includes", {}))
|
||||
self.cpp_defines = data.get("defines", [])
|
||||
self.cc_path = data.get("cc_path")
|
||||
|
||||
@@ -63,6 +63,21 @@ class PlatformioCLI(click.MultiCommand):
|
||||
]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def reveal_cmd_path_args(cls, ctx):
|
||||
result = []
|
||||
group = ctx.command
|
||||
args = cls.leftover_args[::]
|
||||
while args:
|
||||
cmd_name = args.pop(0)
|
||||
next_group = group.get_command(ctx, cmd_name)
|
||||
if next_group:
|
||||
group = next_group
|
||||
result.append(cmd_name)
|
||||
if not hasattr(group, "get_command"):
|
||||
break
|
||||
return result
|
||||
|
||||
def invoke(self, ctx):
|
||||
PlatformioCLI.leftover_args = ctx.args
|
||||
if hasattr(ctx, "protected_args"):
|
||||
|
||||
@@ -53,16 +53,15 @@ def cli(dev, verbose):
|
||||
subprocess.run(
|
||||
[python_exe, "-m", "pip", "install", "--upgrade", pkg_spec],
|
||||
check=True,
|
||||
capture_output=not verbose,
|
||||
stdout=subprocess.PIPE if not verbose else None,
|
||||
)
|
||||
r = subprocess.run(
|
||||
output = subprocess.run(
|
||||
[python_exe, "-m", "platformio", "--version"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert "version" in r.stdout
|
||||
actual_version = r.stdout.split("version", 1)[1].strip()
|
||||
stdout=subprocess.PIPE,
|
||||
).stdout.decode()
|
||||
assert "version" in output
|
||||
actual_version = output.split("version", 1)[1].strip()
|
||||
click.secho(
|
||||
"PlatformIO has been successfully upgraded to %s" % actual_version,
|
||||
fg="green",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import importlib.util
|
||||
import inspect
|
||||
import locale
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from platformio.exception import UserSideException
|
||||
@@ -29,6 +30,20 @@ else:
|
||||
from asyncio import get_event_loop as aio_get_running_loop
|
||||
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from shlex import join as shlex_join
|
||||
else:
|
||||
|
||||
def shlex_join(split_command):
|
||||
return " ".join(shlex.quote(arg) for arg in split_command)
|
||||
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from asyncio import to_thread as aio_to_thread
|
||||
else:
|
||||
from starlette.concurrency import run_in_threadpool as aio_to_thread
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2 # DO NOT REMOVE IT. ESP8266/ESP32 depend on it
|
||||
IS_CYGWIN = sys.platform.startswith("cygwin")
|
||||
IS_WINDOWS = WINDOWS = sys.platform.startswith("win")
|
||||
|
||||
@@ -30,3 +30,7 @@ class DebugSupportError(DebugError, UserSideException):
|
||||
|
||||
class DebugInvalidOptionsError(DebugError, UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
class DebugInitError(DebugError, UserSideException):
|
||||
pass
|
||||
@@ -13,13 +13,13 @@
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import time
|
||||
|
||||
from platformio import telemetry
|
||||
from platformio.compat import aio_get_running_loop, is_bytes
|
||||
from platformio.debug import helpers
|
||||
from platformio.debug.exception import DebugInitError
|
||||
from platformio.debug.process.client import DebugClientProcess
|
||||
|
||||
|
||||
@@ -130,11 +130,7 @@ class GDBClientProcess(DebugClientProcess):
|
||||
self._handle_error(data)
|
||||
# go to init break automatically
|
||||
if self.INIT_COMPLETED_BANNER.encode() in data:
|
||||
telemetry.send_event(
|
||||
"Debug",
|
||||
"Started",
|
||||
telemetry.dump_run_environment(self.debug_config.env_options),
|
||||
)
|
||||
telemetry.log_debug_started(self.debug_config)
|
||||
self._auto_exec_continue()
|
||||
|
||||
def console_log(self, msg):
|
||||
@@ -179,14 +175,7 @@ class GDBClientProcess(DebugClientProcess):
|
||||
and b"Error in sourced" in self._errors_buffer
|
||||
):
|
||||
return
|
||||
|
||||
last_erros = self._errors_buffer.decode()
|
||||
last_erros = " ".join(reversed(last_erros.split("\n")))
|
||||
last_erros = re.sub(r'((~|&)"|\\n\"|\\t)', " ", last_erros, flags=re.M)
|
||||
|
||||
err = "%s -> %s" % (
|
||||
telemetry.dump_run_environment(self.debug_config.env_options),
|
||||
last_erros,
|
||||
telemetry.log_debug_exception(
|
||||
DebugInitError(self._errors_buffer.decode()), self.debug_config
|
||||
)
|
||||
telemetry.send_exception("DebugInitError: %s" % err)
|
||||
self.transport.close()
|
||||
@@ -163,7 +163,7 @@ class SerialPortFinder:
|
||||
for item in list_serial_ports(as_objects=True):
|
||||
if item.vid == device.vid and item.pid == device.pid:
|
||||
candidates.append(item)
|
||||
if len(candidates) == 1:
|
||||
if len(candidates) <= 1:
|
||||
return device.device
|
||||
for item in candidates:
|
||||
if ("GDB" if self.prefer_gdb_port else "UART") in item.description:
|
||||
|
||||
@@ -81,7 +81,7 @@ class InvalidSettingValue(UserSideException):
|
||||
MESSAGE = "Invalid value '{0}' for the setting '{1}'"
|
||||
|
||||
|
||||
class InvalidJSONFile(PlatformioException):
|
||||
class InvalidJSONFile(ValueError, UserSideException):
|
||||
MESSAGE = "Could not load broken JSON: {0}"
|
||||
|
||||
|
||||
|
||||
@@ -12,12 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from platformio import __version__, app, fs, util
|
||||
from platformio.home.rpc.handlers.base import BaseRPCHandler
|
||||
from platformio.project.config import ProjectConfig
|
||||
from platformio.project.helpers import is_platformio_project
|
||||
|
||||
|
||||
@@ -32,16 +30,11 @@ class AppRPC(BaseRPCHandler):
|
||||
"projectsDir",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_state_path():
|
||||
core_dir = ProjectConfig.get_instance().get("platformio", "core_dir")
|
||||
if not os.path.isdir(core_dir):
|
||||
os.makedirs(core_dir)
|
||||
return os.path.join(core_dir, "homestate.json")
|
||||
|
||||
@staticmethod
|
||||
def load_state():
|
||||
with app.State(AppRPC.get_state_path(), lock=True) as state:
|
||||
with app.State(
|
||||
app.resolve_state_path("core_dir", "homestate.json"), lock=True
|
||||
) as state:
|
||||
storage = state.get("storage", {})
|
||||
|
||||
# base data
|
||||
@@ -81,7 +74,9 @@ class AppRPC(BaseRPCHandler):
|
||||
|
||||
@staticmethod
|
||||
def save_state(state):
|
||||
with app.State(AppRPC.get_state_path(), lock=True) as s:
|
||||
with app.State(
|
||||
app.resolve_state_path("core_dir", "homestate.json"), lock=True
|
||||
) as s:
|
||||
s.clear()
|
||||
s.update(state)
|
||||
storage = s.get("storage", {})
|
||||
|
||||
@@ -19,10 +19,10 @@ import shutil
|
||||
from functools import cmp_to_key
|
||||
|
||||
import click
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from platformio import fs
|
||||
from platformio.cache import ContentCache
|
||||
from platformio.compat import aio_to_thread
|
||||
from platformio.device.list.util import list_logical_devices
|
||||
from platformio.home.rpc.handlers.base import BaseRPCHandler
|
||||
from platformio.http import HTTPSession, ensure_internet_on
|
||||
@@ -33,12 +33,14 @@ class HTTPAsyncSession(HTTPSession):
|
||||
self, *args, **kwargs
|
||||
):
|
||||
func = super().request
|
||||
return await run_in_threadpool(func, *args, **kwargs)
|
||||
return await aio_to_thread(func, *args, **kwargs)
|
||||
|
||||
|
||||
class OSRPC(BaseRPCHandler):
|
||||
@staticmethod
|
||||
async def fetch_content(url, data=None, headers=None, cache_valid=None):
|
||||
_http_session = None
|
||||
|
||||
@classmethod
|
||||
async def fetch_content(cls, url, data=None, headers=None, cache_valid=None):
|
||||
if not headers:
|
||||
headers = {
|
||||
"User-Agent": (
|
||||
@@ -57,11 +59,13 @@ class OSRPC(BaseRPCHandler):
|
||||
# check internet before and resolve issue with 60 seconds timeout
|
||||
ensure_internet_on(raise_exception=True)
|
||||
|
||||
session = HTTPAsyncSession()
|
||||
if not cls._http_session:
|
||||
cls._http_session = HTTPAsyncSession()
|
||||
|
||||
if data:
|
||||
r = await session.post(url, data=data, headers=headers)
|
||||
r = await cls._http_session.post(url, data=data, headers=headers)
|
||||
else:
|
||||
r = await session.get(url, headers=headers)
|
||||
r = await cls._http_session.get(url, headers=headers)
|
||||
|
||||
r.raise_for_status()
|
||||
result = r.text
|
||||
@@ -73,9 +77,9 @@ class OSRPC(BaseRPCHandler):
|
||||
async def request_content(self, uri, data=None, headers=None, cache_valid=None):
|
||||
if uri.startswith("http"):
|
||||
return await self.fetch_content(uri, data, headers, cache_valid)
|
||||
if os.path.isfile(uri):
|
||||
with io.open(uri, encoding="utf-8") as fp:
|
||||
return fp.read()
|
||||
local_path = uri[7:] if uri.startswith("file://") else uri
|
||||
with io.open(local_path, encoding="utf-8") as fp:
|
||||
return fp.read()
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -22,13 +22,13 @@ import threading
|
||||
|
||||
import click
|
||||
from ajsonrpc.core import JSONRPC20DispatchException
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from platformio import __main__, __version__, app, fs, proc, util
|
||||
from platformio.compat import (
|
||||
IS_WINDOWS,
|
||||
aio_create_task,
|
||||
aio_get_running_loop,
|
||||
aio_to_thread,
|
||||
get_locale_encoding,
|
||||
is_bytes,
|
||||
)
|
||||
@@ -177,7 +177,7 @@ class PIOCoreRPC(BaseRPCHandler):
|
||||
|
||||
@staticmethod
|
||||
async def _call_subprocess(args, options):
|
||||
result = await run_in_threadpool(
|
||||
result = await aio_to_thread(
|
||||
proc.exec_command,
|
||||
[get_core_fullpath()] + args,
|
||||
cwd=options.get("cwd") or os.getcwd(),
|
||||
@@ -197,7 +197,7 @@ class PIOCoreRPC(BaseRPCHandler):
|
||||
exit_code,
|
||||
)
|
||||
|
||||
return await run_in_threadpool(
|
||||
return await aio_to_thread(
|
||||
_thread_safe_call, args=args, cwd=options.get("cwd") or os.getcwd()
|
||||
)
|
||||
|
||||
|
||||
@@ -12,21 +12,55 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os.path
|
||||
|
||||
from platformio.compat import aio_to_thread
|
||||
from platformio.home.rpc.handlers.base import BaseRPCHandler
|
||||
from platformio.package.manager.platform import PlatformPackageManager
|
||||
from platformio.package.manifest.parser import ManifestParserFactory
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.platform.factory import PlatformFactory
|
||||
|
||||
|
||||
class PlatformRPC(BaseRPCHandler):
|
||||
async def fetch_platforms(self, search_query=None, page=0, force_installed=False):
|
||||
if force_installed:
|
||||
return {
|
||||
"items": await aio_to_thread(
|
||||
self._load_installed_platforms, search_query
|
||||
)
|
||||
}
|
||||
|
||||
search_result = await self.factory.manager.dispatcher["registry.call_client"](
|
||||
method="list_packages",
|
||||
query=search_query,
|
||||
qualifiers={
|
||||
"types": ["platform"],
|
||||
},
|
||||
page=page,
|
||||
)
|
||||
return {
|
||||
"page": search_result["page"],
|
||||
"limit": search_result["limit"],
|
||||
"total": search_result["total"],
|
||||
"items": [
|
||||
{
|
||||
"id": item["id"],
|
||||
"ownername": item["owner"]["username"],
|
||||
"name": item["name"],
|
||||
"version": item["version"]["name"],
|
||||
"description": item["description"],
|
||||
"tier": item["tier"],
|
||||
}
|
||||
for item in search_result["items"]
|
||||
],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def list_installed(options=None):
|
||||
result = []
|
||||
options = options or {}
|
||||
def _load_installed_platforms(search_query=None):
|
||||
search_query = (search_query or "").strip()
|
||||
|
||||
def _matchSearchQuery(p):
|
||||
searchQuery = options.get("searchQuery")
|
||||
if not searchQuery:
|
||||
return True
|
||||
content_blocks = [p.name, p.title, p.description]
|
||||
if p.frameworks:
|
||||
content_blocks.append(" ".join(p.frameworks.keys()))
|
||||
@@ -34,28 +68,73 @@ class PlatformRPC(BaseRPCHandler):
|
||||
board_data = board.get_brief_data()
|
||||
for key in ("id", "mcu", "vendor"):
|
||||
content_blocks.append(board_data.get(key))
|
||||
return searchQuery.strip() in " ".join(content_blocks)
|
||||
return search_query in " ".join(content_blocks)
|
||||
|
||||
items = []
|
||||
pm = PlatformPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
p = PlatformFactory.new(pkg)
|
||||
if not _matchSearchQuery(p):
|
||||
if search_query and not _matchSearchQuery(p):
|
||||
continue
|
||||
result.append(
|
||||
dict(
|
||||
__pkg_path=pkg.path,
|
||||
__pkg_meta=pkg.metadata.as_dict(),
|
||||
name=p.name,
|
||||
title=p.title,
|
||||
description=p.description,
|
||||
)
|
||||
items.append(
|
||||
{
|
||||
"__pkg_path": pkg.path,
|
||||
"ownername": pkg.metadata.spec.owner if pkg.metadata.spec else None,
|
||||
"name": p.name,
|
||||
"version": str(pkg.metadata.version),
|
||||
"title": p.title,
|
||||
"description": p.description,
|
||||
}
|
||||
)
|
||||
return result
|
||||
return items
|
||||
|
||||
async def fetch_boards(self, platform_spec):
|
||||
spec = PackageSpec(platform_spec)
|
||||
if spec.owner:
|
||||
return await self.factory.manager.dispatcher["registry.call_client"](
|
||||
method="get_package",
|
||||
typex="platform",
|
||||
owner=spec.owner,
|
||||
name=spec.name,
|
||||
extra_path="/boards",
|
||||
)
|
||||
return await aio_to_thread(self._load_installed_boards, spec)
|
||||
|
||||
@staticmethod
|
||||
def get_boards(spec):
|
||||
p = PlatformFactory.new(spec)
|
||||
def _load_installed_boards(platform_spec):
|
||||
p = PlatformFactory.new(platform_spec)
|
||||
return sorted(
|
||||
[b.get_brief_data() for b in p.get_boards().values()],
|
||||
key=lambda item: item["name"],
|
||||
)
|
||||
|
||||
async def fetch_examples(self, platform_spec):
|
||||
spec = PackageSpec(platform_spec)
|
||||
if spec.owner:
|
||||
return await self.factory.manager.dispatcher["registry.call_client"](
|
||||
method="get_package",
|
||||
typex="platform",
|
||||
owner=spec.owner,
|
||||
name=spec.name,
|
||||
extra_path="/examples",
|
||||
)
|
||||
return await aio_to_thread(self._load_installed_examples, spec)
|
||||
|
||||
@staticmethod
|
||||
def _load_installed_examples(platform_spec):
|
||||
platform = PlatformFactory.new(platform_spec)
|
||||
platform_dir = platform.get_dir()
|
||||
parser = ManifestParserFactory.new_from_dir(platform_dir)
|
||||
result = parser.as_dict().get("examples") or []
|
||||
for example in result:
|
||||
example["files"] = [
|
||||
{
|
||||
"path": item,
|
||||
"url": (
|
||||
"file://%s"
|
||||
+ os.path.join(platform_dir, "examples", example["name"], item)
|
||||
),
|
||||
}
|
||||
for item in example["files"]
|
||||
]
|
||||
return result
|
||||
@@ -15,6 +15,7 @@
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import semantic_version
|
||||
from ajsonrpc.core import JSONRPC20DispatchException
|
||||
@@ -267,15 +268,39 @@ class ProjectRPC(BaseRPCHandler):
|
||||
)
|
||||
return new_project_dir
|
||||
|
||||
async def create_empty(self, configuration, options=None):
|
||||
async def init_v2(self, configuration, options=None):
|
||||
project_dir = os.path.join(configuration["location"], configuration["name"])
|
||||
if not os.path.isdir(project_dir):
|
||||
os.makedirs(project_dir)
|
||||
|
||||
envclone = os.environ.copy()
|
||||
envclone["PLATFORMIO_FORCE_ANSI"] = "true"
|
||||
options = options or {}
|
||||
options["spawn"] = {"env": envclone, "cwd": project_dir}
|
||||
|
||||
args = ["project", "init"]
|
||||
ide = app.get_session_var("caller_id")
|
||||
if ide in ProjectGenerator.get_supported_ides():
|
||||
args.extend(["--ide", ide])
|
||||
|
||||
if configuration.get("example"):
|
||||
await self.factory.notify_clients(
|
||||
method=options.get("stdoutNotificationMethod"),
|
||||
params=["Copying example files...\n"],
|
||||
actor="frontend",
|
||||
)
|
||||
await self._pre_init_example(configuration, project_dir)
|
||||
else:
|
||||
args.extend(self._pre_init_empty(configuration))
|
||||
|
||||
return await self.factory.manager.dispatcher["core.exec"](args, options=options)
|
||||
|
||||
@staticmethod
|
||||
def _pre_init_empty(configuration):
|
||||
project_options = []
|
||||
platform = configuration["platform"]
|
||||
board = configuration.get("board", {}).get("id")
|
||||
env_name = board or platform["name"]
|
||||
board_id = configuration.get("board", {}).get("id")
|
||||
env_name = board_id or platform["name"]
|
||||
if configuration.get("description"):
|
||||
project_options.append(("description", configuration.get("description")))
|
||||
try:
|
||||
@@ -288,20 +313,25 @@ class ProjectRPC(BaseRPCHandler):
|
||||
project_options.append(
|
||||
("platform", "{name} @ {version}".format(**platform))
|
||||
)
|
||||
if board:
|
||||
project_options.append(("board", board))
|
||||
if board_id:
|
||||
project_options.append(("board", board_id))
|
||||
if configuration.get("framework"):
|
||||
project_options.append(("framework", configuration["framework"]["name"]))
|
||||
|
||||
args = ["project", "init", "-e", env_name, "--sample-code"]
|
||||
ide = app.get_session_var("caller_id")
|
||||
if ide in ProjectGenerator.get_supported_ides():
|
||||
args.extend(["--ide", ide])
|
||||
args = ["-e", env_name, "--sample-code"]
|
||||
for name, value in project_options:
|
||||
args.extend(["-O", f"{name}={value}"])
|
||||
return args
|
||||
|
||||
envclone = os.environ.copy()
|
||||
envclone["PLATFORMIO_FORCE_ANSI"] = "true"
|
||||
options = options or {}
|
||||
options["spawn"] = {"env": envclone, "cwd": project_dir}
|
||||
return await self.factory.manager.dispatcher["core.exec"](args, options=options)
|
||||
async def _pre_init_example(self, configuration, project_dir):
|
||||
for item in configuration["example"]["files"]:
|
||||
p = Path(project_dir).joinpath(item["path"])
|
||||
if not p.parent.is_dir():
|
||||
p.parent.mkdir(parents=True)
|
||||
p.write_text(
|
||||
await self.factory.manager.dispatcher["os.request_content"](
|
||||
item["url"]
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return []
|
||||
@@ -13,8 +13,8 @@
|
||||
# limitations under the License.
|
||||
|
||||
from ajsonrpc.core import JSONRPC20DispatchException
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
|
||||
from platformio.compat import aio_to_thread
|
||||
from platformio.home.rpc.handlers.base import BaseRPCHandler
|
||||
from platformio.registry.client import RegistryClient
|
||||
|
||||
@@ -24,7 +24,7 @@ class RegistryRPC(BaseRPCHandler):
|
||||
async def call_client(method, *args, **kwargs):
|
||||
try:
|
||||
client = RegistryClient()
|
||||
return await run_in_threadpool(getattr(client, method), *args, **kwargs)
|
||||
return await aio_to_thread(getattr(client, method), *args, **kwargs)
|
||||
except Exception as exc: # pylint: disable=bare-except
|
||||
raise JSONRPC20DispatchException(
|
||||
code=5000, message="Registry Call Error", data=str(exc)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
import ajsonrpc.utils
|
||||
import click
|
||||
from ajsonrpc.core import JSONRPC20Error, JSONRPC20Request
|
||||
from ajsonrpc.dispatcher import Dispatcher
|
||||
@@ -24,6 +25,10 @@ from platformio.compat import aio_create_task, aio_get_running_loop
|
||||
from platformio.http import InternetConnectionError
|
||||
from platformio.proc import force_exit
|
||||
|
||||
# Remove this line when PR is merged
|
||||
# https://github.com/pavlov99/ajsonrpc/pull/22
|
||||
ajsonrpc.utils.is_invalid_params = lambda: False
|
||||
|
||||
|
||||
class JSONRPCServerFactoryBase:
|
||||
connection_nums = 0
|
||||
|
||||
+10
-4
@@ -18,7 +18,7 @@ import socket
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import requests.adapters
|
||||
from requests.packages.urllib3.util.retry import Retry # pylint:disable=import-error
|
||||
from urllib3.util.retry import Retry
|
||||
|
||||
from platformio import __check_internet_hosts__, app, util
|
||||
from platformio.cache import ContentCache, cleanup_content_cache
|
||||
@@ -27,7 +27,7 @@ from platformio.exception import PlatformioException, UserSideException
|
||||
__default_requests_timeout__ = (10, None) # (connect, read)
|
||||
|
||||
|
||||
class HTTPClientError(PlatformioException):
|
||||
class HTTPClientError(UserSideException):
|
||||
def __init__(self, message, response=None):
|
||||
super().__init__()
|
||||
self.message = message
|
||||
@@ -50,7 +50,10 @@ class HTTPSession(requests.Session):
|
||||
self._x_base_url = kwargs.pop("x_base_url") if "x_base_url" in kwargs else None
|
||||
super().__init__(*args, **kwargs)
|
||||
self.headers.update({"User-Agent": app.get_user_agent()})
|
||||
self.verify = app.get_setting("enable_proxy_strict_ssl")
|
||||
try:
|
||||
self.verify = app.get_setting("enable_proxy_strict_ssl")
|
||||
except PlatformioException:
|
||||
self.verify = True
|
||||
|
||||
def request( # pylint: disable=signature-differs,arguments-differ
|
||||
self, method, url, *args, **kwargs
|
||||
@@ -154,7 +157,10 @@ class HTTPClient:
|
||||
with ContentCache("http") as cc:
|
||||
result = cc.get(cache_key)
|
||||
if result is not None:
|
||||
return json.loads(result)
|
||||
try:
|
||||
return json.loads(result)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
response = self.send_request(method, path, **kwargs)
|
||||
data = self._parse_json_response(response)
|
||||
cc.set(cache_key, response.text, cache_valid)
|
||||
|
||||
+74
-61
@@ -25,23 +25,20 @@ from platformio.cli import PlatformioCLI
|
||||
from platformio.commands.upgrade import get_latest_version
|
||||
from platformio.http import HTTPClientError, InternetConnectionError, ensure_internet_on
|
||||
from platformio.package.manager.core import update_core_packages
|
||||
from platformio.package.manager.tool import ToolPackageManager
|
||||
from platformio.package.meta import PackageSpec
|
||||
from platformio.package.version import pepver_to_semver
|
||||
from platformio.system.prune import calculate_unnecessary_system_data
|
||||
|
||||
|
||||
def on_platformio_start(ctx, caller):
|
||||
def on_cmd_start(ctx, caller):
|
||||
app.set_session_var("command_ctx", ctx)
|
||||
set_caller(caller)
|
||||
telemetry.on_command()
|
||||
|
||||
telemetry.on_cmd_start(ctx)
|
||||
if PlatformioCLI.in_silence():
|
||||
return
|
||||
after_upgrade(ctx)
|
||||
|
||||
|
||||
def on_platformio_end(ctx, result): # pylint: disable=unused-argument
|
||||
def on_cmd_end():
|
||||
if PlatformioCLI.in_silence():
|
||||
return
|
||||
|
||||
@@ -60,8 +57,12 @@ def on_platformio_end(ctx, result): # pylint: disable=unused-argument
|
||||
)
|
||||
|
||||
|
||||
def on_platformio_exception(e):
|
||||
telemetry.on_exception(e)
|
||||
def on_platformio_exception(exc):
|
||||
telemetry.log_exception(exc)
|
||||
|
||||
|
||||
def on_platformio_exit():
|
||||
telemetry.on_exit()
|
||||
|
||||
|
||||
def set_caller(caller=None):
|
||||
@@ -79,11 +80,10 @@ def set_caller(caller=None):
|
||||
|
||||
class Upgrader:
|
||||
def __init__(self, from_version, to_version):
|
||||
self.from_version = pepver_to_semver(from_version)
|
||||
self.to_version = pepver_to_semver(to_version)
|
||||
|
||||
self.from_version = from_version
|
||||
self.to_version = to_version
|
||||
self._upgraders = [
|
||||
(semantic_version.Version("4.4.0-a.8"), self._update_pkg_metadata),
|
||||
(semantic_version.Version("6.1.8-a.1"), self._appstate_migration),
|
||||
]
|
||||
|
||||
def run(self, ctx):
|
||||
@@ -99,37 +99,43 @@ class Upgrader:
|
||||
return all(result)
|
||||
|
||||
@staticmethod
|
||||
def _update_pkg_metadata(_):
|
||||
pm = ToolPackageManager()
|
||||
for pkg in pm.get_installed():
|
||||
if not pkg.metadata or pkg.metadata.spec.external or pkg.metadata.spec.id:
|
||||
continue
|
||||
result = pm.search_registry_packages(PackageSpec(name=pkg.metadata.name))
|
||||
if len(result) != 1:
|
||||
continue
|
||||
result = result[0]
|
||||
pkg.metadata.spec = PackageSpec(
|
||||
id=result["id"],
|
||||
owner=result["owner"]["username"],
|
||||
name=result["name"],
|
||||
def _appstate_migration(_):
|
||||
state_path = app.resolve_state_path("core_dir", "appstate.json")
|
||||
if not os.path.isfile(state_path):
|
||||
return True
|
||||
app.delete_state_item("telemetry")
|
||||
created_at = app.get_state_item("created_at", None)
|
||||
if not created_at:
|
||||
state_stat = os.stat(state_path)
|
||||
app.set_state_item(
|
||||
"created_at",
|
||||
int(
|
||||
state_stat.st_birthtime
|
||||
if hasattr(state_stat, "st_birthtime")
|
||||
else state_stat.st_ctime
|
||||
),
|
||||
)
|
||||
pkg.dump_meta()
|
||||
return True
|
||||
|
||||
|
||||
def after_upgrade(ctx):
|
||||
terminal_width = shutil.get_terminal_size().columns
|
||||
last_version = app.get_state_item("last_version", "0.0.0")
|
||||
if last_version == __version__:
|
||||
return
|
||||
last_version_str = app.get_state_item("last_version", "0.0.0")
|
||||
if last_version_str == __version__:
|
||||
return None
|
||||
|
||||
if last_version == "0.0.0":
|
||||
if last_version_str == "0.0.0":
|
||||
app.set_state_item("last_version", __version__)
|
||||
elif pepver_to_semver(last_version) > pepver_to_semver(__version__):
|
||||
return print_welcome_banner()
|
||||
|
||||
last_version = pepver_to_semver(last_version_str)
|
||||
current_version = pepver_to_semver(__version__)
|
||||
|
||||
if last_version > current_version and not last_version.prerelease:
|
||||
click.secho("*" * terminal_width, fg="yellow")
|
||||
click.secho(
|
||||
"Obsolete PIO Core v%s is used (previous was %s)"
|
||||
% (__version__, last_version),
|
||||
% (__version__, last_version_str),
|
||||
fg="yellow",
|
||||
)
|
||||
click.secho("Please remove multiple PIO Cores from a system:", fg="yellow")
|
||||
@@ -139,43 +145,50 @@ def after_upgrade(ctx):
|
||||
fg="cyan",
|
||||
)
|
||||
click.secho("*" * terminal_width, fg="yellow")
|
||||
return
|
||||
else:
|
||||
click.secho("Please wait while upgrading PlatformIO...", fg="yellow")
|
||||
return None
|
||||
|
||||
# Update PlatformIO's Core packages
|
||||
cleanup_content_cache("http")
|
||||
update_core_packages()
|
||||
click.secho("Please wait while upgrading PlatformIO...", fg="yellow")
|
||||
|
||||
u = Upgrader(last_version, __version__)
|
||||
if u.run(ctx):
|
||||
app.set_state_item("last_version", __version__)
|
||||
click.secho(
|
||||
"PlatformIO has been successfully upgraded to %s!\n" % __version__,
|
||||
fg="green",
|
||||
)
|
||||
telemetry.send_event(
|
||||
category="Auto",
|
||||
action="Upgrade",
|
||||
label="%s > %s" % (last_version, __version__),
|
||||
)
|
||||
# Update PlatformIO's Core packages
|
||||
cleanup_content_cache("http")
|
||||
update_core_packages()
|
||||
|
||||
# PlatformIO banner
|
||||
u = Upgrader(last_version, current_version)
|
||||
if u.run(ctx):
|
||||
app.set_state_item("last_version", __version__)
|
||||
click.secho(
|
||||
"PlatformIO has been successfully upgraded to %s!\n" % __version__,
|
||||
fg="green",
|
||||
)
|
||||
telemetry.log_event(
|
||||
"pio_upgrade_core",
|
||||
{
|
||||
"label": "%s > %s" % (last_version_str, __version__),
|
||||
"from_version": last_version_str,
|
||||
"to_version": __version__,
|
||||
},
|
||||
)
|
||||
|
||||
return print_welcome_banner()
|
||||
|
||||
|
||||
def print_welcome_banner():
|
||||
terminal_width = shutil.get_terminal_size().columns
|
||||
click.echo("*" * terminal_width)
|
||||
click.echo("If you like %s, please:" % (click.style("PlatformIO", fg="cyan")))
|
||||
click.echo(
|
||||
"- %s us on Twitter to stay up-to-date "
|
||||
"on the latest project news > %s"
|
||||
% (
|
||||
click.style("follow", fg="cyan"),
|
||||
click.style("https://twitter.com/PlatformIO_Org", fg="cyan"),
|
||||
)
|
||||
)
|
||||
click.echo(
|
||||
"- %s it on GitHub > %s"
|
||||
% (
|
||||
click.style("star", fg="cyan"),
|
||||
click.style("https://github.com/platformio/platformio", fg="cyan"),
|
||||
click.style("https://github.com/platformio/platformio-core", fg="cyan"),
|
||||
)
|
||||
)
|
||||
click.echo(
|
||||
"- %s us on LinkedIn to stay up-to-date "
|
||||
"on the latest project news > %s"
|
||||
% (
|
||||
click.style("follow", fg="cyan"),
|
||||
click.style("https://www.linkedin.com/company/platformio/", fg="cyan"),
|
||||
)
|
||||
)
|
||||
if not os.getenv("PLATFORMIO_IDE"):
|
||||
@@ -228,7 +241,7 @@ def check_platformio_upgrade():
|
||||
else:
|
||||
click.secho("platformio upgrade", fg="cyan", nl=False)
|
||||
click.secho("` or `", fg="yellow", nl=False)
|
||||
click.secho("pip install -U platformio", fg="cyan", nl=False)
|
||||
click.secho("python -m pip install -U platformio", fg="cyan", nl=False)
|
||||
click.secho("` command.", fg="yellow")
|
||||
click.secho("Changes: ", fg="yellow", nl=False)
|
||||
click.secho("https://docs.platformio.org/en/latest/history.html", fg="cyan")
|
||||
|
||||
@@ -59,7 +59,8 @@ def humanize_package(pkg, spec=None, verbose=False):
|
||||
if spec and not isinstance(spec, PackageSpec):
|
||||
spec = PackageSpec(spec)
|
||||
data = [
|
||||
click.style("{name} @ {version}".format(**pkg.metadata.as_dict()), fg="cyan")
|
||||
click.style(pkg.metadata.name, fg="cyan"),
|
||||
click.style(f"@ {str(pkg.metadata.version)}", bold=True),
|
||||
]
|
||||
extra_data = ["required: %s" % (spec.humanize() if spec else "Any")]
|
||||
if verbose:
|
||||
@@ -135,20 +136,20 @@ def list_global_packages(options):
|
||||
("libraries", LibraryPackageManager(options.get("storage_dir"))),
|
||||
]
|
||||
only_packages = any(
|
||||
options.get(type_) or options.get(f"only_{type_}") for (type_, _) in data
|
||||
options.get(typex) or options.get(f"only_{typex}") for (typex, _) in data
|
||||
)
|
||||
for type_, pm in data:
|
||||
for typex, pm in data:
|
||||
skip_conds = [
|
||||
only_packages
|
||||
and not options.get(type_)
|
||||
and not options.get(f"only_{type_}"),
|
||||
and not options.get(typex)
|
||||
and not options.get(f"only_{typex}"),
|
||||
not pm.get_installed(),
|
||||
]
|
||||
if any(skip_conds):
|
||||
continue
|
||||
click.secho(type_.capitalize(), bold=True)
|
||||
click.secho(typex.capitalize(), bold=True)
|
||||
print_dependency_tree(
|
||||
pm, filter_specs=options.get(type_), verbose=options.get("verbose")
|
||||
pm, filter_specs=options.get(typex), verbose=options.get("verbose")
|
||||
)
|
||||
click.echo()
|
||||
|
||||
@@ -156,12 +157,12 @@ def list_global_packages(options):
|
||||
def list_project_packages(options):
|
||||
environments = options["environments"]
|
||||
only_packages = any(
|
||||
options.get(type_) or options.get(f"only_{type_}")
|
||||
for type_ in ("platforms", "tools", "libraries")
|
||||
options.get(typex) or options.get(f"only_{typex}")
|
||||
for typex in ("platforms", "tools", "libraries")
|
||||
)
|
||||
only_platform_packages = any(
|
||||
options.get(type_) or options.get(f"only_{type_}")
|
||||
for type_ in ("platforms", "tools")
|
||||
options.get(typex) or options.get(f"only_{typex}")
|
||||
for typex in ("platforms", "tools")
|
||||
)
|
||||
only_library_packages = options.get("libraries") or options.get("only_libraries")
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ def validate_datetime(ctx, param, value): # pylint: disable=unused-argument
|
||||
)
|
||||
@click.option(
|
||||
"--type",
|
||||
"type_",
|
||||
"typex",
|
||||
type=click.Choice(list(PackageType.items().values())),
|
||||
help="Custom package type",
|
||||
)
|
||||
@@ -83,7 +83,7 @@ def validate_datetime(ctx, param, value): # pylint: disable=unused-argument
|
||||
hidden=True,
|
||||
)
|
||||
def package_publish_cmd( # pylint: disable=too-many-arguments, too-many-locals
|
||||
package, owner, type_, released_at, private, notify, no_interactive, non_interactive
|
||||
package, owner, typex, released_at, private, notify, no_interactive, non_interactive
|
||||
):
|
||||
click.secho("Preparing a package...", fg="cyan")
|
||||
no_interactive = no_interactive or non_interactive
|
||||
@@ -103,14 +103,14 @@ def package_publish_cmd( # pylint: disable=too-many-arguments, too-many-locals
|
||||
p = PackagePacker(package)
|
||||
archive_path = p.pack()
|
||||
|
||||
type_ = type_ or PackageType.from_archive(archive_path)
|
||||
typex = typex or PackageType.from_archive(archive_path)
|
||||
manifest = ManifestSchema().load_manifest(
|
||||
ManifestParserFactory.new_from_archive(archive_path).as_dict()
|
||||
)
|
||||
name = manifest.get("name")
|
||||
version = manifest.get("version")
|
||||
data = [
|
||||
("Type:", type_),
|
||||
("Type:", typex),
|
||||
("Owner:", owner),
|
||||
("Name:", name),
|
||||
("Version:", version),
|
||||
@@ -124,13 +124,13 @@ def package_publish_cmd( # pylint: disable=too-many-arguments, too-many-locals
|
||||
check_archive_file_names(archive_path)
|
||||
|
||||
# look for duplicates
|
||||
check_package_duplicates(owner, type_, name, version, manifest.get("system"))
|
||||
check_package_duplicates(owner, typex, name, version, manifest.get("system"))
|
||||
|
||||
if not no_interactive:
|
||||
click.confirm(
|
||||
"Are you sure you want to publish the %s %s to the registry?\n"
|
||||
% (
|
||||
type_,
|
||||
typex,
|
||||
click.style(
|
||||
"%s/%s@%s" % (owner, name, version),
|
||||
fg="cyan",
|
||||
@@ -146,7 +146,7 @@ def package_publish_cmd( # pylint: disable=too-many-arguments, too-many-locals
|
||||
)
|
||||
click.echo("Publishing...")
|
||||
response = RegistryClient().publish_package(
|
||||
owner, type_, archive_path, released_at, private, notify
|
||||
owner, typex, archive_path, released_at, private, notify
|
||||
)
|
||||
if not do_not_pack:
|
||||
os.remove(archive_path)
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
# limitations under the License.
|
||||
|
||||
from platformio import util
|
||||
from platformio.exception import PlatformioException, UserSideException
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
|
||||
class PackageException(PlatformioException):
|
||||
class PackageException(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -51,14 +51,14 @@ class MissingPackageManifestError(ManifestException):
|
||||
MESSAGE = "Could not find one of '{0}' manifest files in the package"
|
||||
|
||||
|
||||
class UnknownPackageError(UserSideException):
|
||||
class UnknownPackageError(PackageException):
|
||||
MESSAGE = (
|
||||
"Could not find the package with '{0}' requirements for your system '%s'"
|
||||
% util.get_systype()
|
||||
)
|
||||
|
||||
|
||||
class NotGlobalLibDir(UserSideException):
|
||||
class NotGlobalLibDir(PackageException):
|
||||
MESSAGE = (
|
||||
"The `{0}` is not a PlatformIO project.\n\n"
|
||||
"To manage libraries in global storage `{1}`,\n"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import os
|
||||
from time import sleep, time
|
||||
|
||||
from platformio.exception import PlatformioException
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
LOCKFILE_TIMEOUT = 3600 # in seconds, 1 hour
|
||||
LOCKFILE_DELAY = 0.2
|
||||
@@ -36,11 +36,11 @@ except ImportError:
|
||||
LOCKFILE_CURRENT_INTERFACE = None
|
||||
|
||||
|
||||
class LockFileExists(PlatformioException):
|
||||
class LockFileExists(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
class LockFileTimeoutError(PlatformioException):
|
||||
class LockFileTimeoutError(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -25,12 +25,13 @@ from platformio.registry.mirror import RegistryFileMirrorIterator
|
||||
|
||||
class PackageManagerRegistryMixin:
|
||||
def install_from_registry(self, spec, search_qualifiers=None):
|
||||
package = version = None
|
||||
if spec.owner and spec.name and not search_qualifiers:
|
||||
package = self.fetch_registry_package(spec)
|
||||
if not package:
|
||||
raise UnknownPackageError(spec.humanize())
|
||||
version = self.pick_best_registry_version(package["versions"], spec)
|
||||
else:
|
||||
elif spec.id or spec.name:
|
||||
packages = self.search_registry_packages(spec, search_qualifiers)
|
||||
if not packages:
|
||||
raise UnknownPackageError(spec.humanize())
|
||||
|
||||
@@ -183,7 +183,7 @@ class ManifestSchema(BaseSchema):
|
||||
validate=[
|
||||
validate.Length(min=1, max=50),
|
||||
validate.Regexp(
|
||||
r"^[a-z\d\-\+\. ]+$", error="Only [a-z0-9-+. ] chars are allowed"
|
||||
r"^[a-z\d\-_\+\. ]+$", error="Only [a-z0-9+_-. ] chars are allowed"
|
||||
),
|
||||
]
|
||||
)
|
||||
@@ -276,9 +276,9 @@ class ManifestSchema(BaseSchema):
|
||||
@staticmethod
|
||||
@memoized(expire="1h")
|
||||
def load_spdx_licenses():
|
||||
version = "3.20"
|
||||
version = "3.21"
|
||||
spdx_data_url = (
|
||||
"https://raw.githubusercontent.com/spdx/license-list-data/"
|
||||
"v%s/json/licenses.json" % version
|
||||
f"v{version}/json/licenses.json"
|
||||
)
|
||||
return json.loads(fetch_remote_content(spdx_data_url))
|
||||
@@ -24,7 +24,7 @@ import semantic_version
|
||||
from platformio import fs
|
||||
from platformio.compat import get_object_members, hashlib_encode_data, string_types
|
||||
from platformio.package.manifest.parser import ManifestFileType
|
||||
from platformio.package.version import cast_version_to_semver
|
||||
from platformio.package.version import SemanticVersionError, cast_version_to_semver
|
||||
from platformio.util import items_in_list
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ class PackageSpec: # pylint: disable=too-many-instance-attributes
|
||||
if requirements:
|
||||
try:
|
||||
self.requirements = requirements
|
||||
except ValueError as exc:
|
||||
except SemanticVersionError as exc:
|
||||
if not self.name or self.uri or self.raw:
|
||||
raise exc
|
||||
self.raw = "%s=%s" % (self.name, requirements)
|
||||
@@ -224,11 +224,14 @@ class PackageSpec: # pylint: disable=too-many-instance-attributes
|
||||
if not value:
|
||||
self._requirements = None
|
||||
return
|
||||
self._requirements = (
|
||||
value
|
||||
if isinstance(value, semantic_version.SimpleSpec)
|
||||
else semantic_version.SimpleSpec(str(value))
|
||||
)
|
||||
try:
|
||||
self._requirements = (
|
||||
value
|
||||
if isinstance(value, semantic_version.SimpleSpec)
|
||||
else semantic_version.SimpleSpec(str(value))
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise SemanticVersionError(exc) from exc
|
||||
|
||||
def humanize(self):
|
||||
result = ""
|
||||
|
||||
@@ -18,14 +18,10 @@ import subprocess
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from platformio import proc
|
||||
from platformio.package.exception import (
|
||||
PackageException,
|
||||
PlatformioException,
|
||||
UserSideException,
|
||||
)
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
|
||||
class VCSBaseException(PackageException):
|
||||
class VCSBaseException(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -74,8 +70,8 @@ class VCSClientBase:
|
||||
self.get_cmd_output(["--version"])
|
||||
else:
|
||||
assert self.run_cmd(["--version"])
|
||||
except (AssertionError, OSError, PlatformioException) as exc:
|
||||
raise UserSideException(
|
||||
except (AssertionError, OSError) as exc:
|
||||
raise VCSBaseException(
|
||||
"VCS: `%s` client is not installed in your system" % self.command
|
||||
) from exc
|
||||
return True
|
||||
|
||||
@@ -16,6 +16,12 @@ import re
|
||||
|
||||
import semantic_version
|
||||
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
|
||||
class SemanticVersionError(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
def cast_version_to_semver(value, force=True, raise_exception=False):
|
||||
assert value
|
||||
@@ -29,7 +35,7 @@ def cast_version_to_semver(value, force=True, raise_exception=False):
|
||||
except ValueError:
|
||||
pass
|
||||
if raise_exception:
|
||||
raise ValueError("Invalid SemVer version %s" % value)
|
||||
raise SemanticVersionError("Invalid SemVer version %s" % value)
|
||||
# parse commit hash
|
||||
if re.match(r"^[\da-f]+$", value, flags=re.I):
|
||||
return semantic_version.Version("0.0.0+sha." + value)
|
||||
|
||||
@@ -52,7 +52,6 @@ class PlatformRunMixin:
|
||||
|
||||
self.ensure_engine_compatible()
|
||||
self.configure_project_packages(variables["pioenv"], targets)
|
||||
self._report_non_sensitive_data(variables["pioenv"], targets)
|
||||
|
||||
self.silent = silent
|
||||
self.verbose = verbose or app.get_setting("force_verbose")
|
||||
@@ -64,20 +63,13 @@ class PlatformRunMixin:
|
||||
if not os.path.isfile(variables["build_script"]):
|
||||
raise BuildScriptNotFound(variables["build_script"])
|
||||
|
||||
telemetry.log_platform_run(self, self.config, variables["pioenv"], targets)
|
||||
result = self._run_scons(variables, targets, jobs)
|
||||
|
||||
assert "returncode" in result
|
||||
|
||||
return result
|
||||
|
||||
def _report_non_sensitive_data(self, env, targets):
|
||||
options = self.config.items(env=env, as_dict=True)
|
||||
options["platform_packages"] = [
|
||||
dict(name=item["name"], version=item["version"])
|
||||
for item in self.dump_used_packages()
|
||||
]
|
||||
options["platform"] = {"name": self.name, "version": self.version}
|
||||
telemetry.send_run_environment(options, targets)
|
||||
|
||||
def _run_scons(self, variables, targets, jobs):
|
||||
scons_dir = get_core_package_dir("tool-scons")
|
||||
args = [
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
import os
|
||||
|
||||
from platformio import fs, telemetry, util
|
||||
from platformio import fs, util
|
||||
from platformio.compat import MISSING
|
||||
from platformio.debug.exception import DebugInvalidOptionsError, DebugSupportError
|
||||
from platformio.exception import UserSideException
|
||||
from platformio.exception import InvalidJSONFile, UserSideException
|
||||
from platformio.platform.exception import InvalidBoardManifest
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class PlatformBoardConfig:
|
||||
self.manifest_path = manifest_path
|
||||
try:
|
||||
self._manifest = fs.load_json(manifest_path)
|
||||
except ValueError as exc:
|
||||
except InvalidJSONFile as exc:
|
||||
raise InvalidBoardManifest(manifest_path) from exc
|
||||
if not set(["name", "url", "vendor"]) <= set(self._manifest):
|
||||
raise UserSideException(
|
||||
@@ -119,7 +119,6 @@ class PlatformBoardConfig:
|
||||
if tool_name == "custom":
|
||||
return tool_name
|
||||
if not debug_tools:
|
||||
telemetry.send_event("Debug", "Request", self.id)
|
||||
raise DebugSupportError(self._manifest["name"])
|
||||
if tool_name:
|
||||
if tool_name in debug_tools:
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from platformio.exception import PlatformioException
|
||||
from platformio.exception import UserSideException
|
||||
|
||||
|
||||
class PlatformException(PlatformioException):
|
||||
class PlatformException(UserSideException):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -30,16 +30,23 @@ from platformio.project.helpers import is_platformio_project
|
||||
default=os.getcwd,
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||
)
|
||||
@click.option("--lint", is_flag=True)
|
||||
@click.option("--json-output", is_flag=True)
|
||||
def project_config_cmd(project_dir, json_output):
|
||||
def project_config_cmd(project_dir, lint, json_output):
|
||||
if not is_platformio_project(project_dir):
|
||||
raise NotPlatformIOProjectError(project_dir)
|
||||
with fs.cd(project_dir):
|
||||
config = ProjectConfig.get_instance()
|
||||
if lint:
|
||||
return lint_configuration(json_output)
|
||||
return print_configuration(json_output)
|
||||
|
||||
|
||||
def print_configuration(json_output=False):
|
||||
config = ProjectConfig.get_instance()
|
||||
if json_output:
|
||||
return click.echo(config.to_json())
|
||||
click.echo(
|
||||
"Computed project configuration for %s" % click.style(project_dir, fg="cyan")
|
||||
"Computed project configuration for %s" % click.style(os.getcwd(), fg="cyan")
|
||||
)
|
||||
for section, options in config.as_tuple():
|
||||
click.secho(section, fg="cyan")
|
||||
@@ -55,3 +62,43 @@ def project_config_cmd(project_dir, json_output):
|
||||
)
|
||||
click.echo()
|
||||
return None
|
||||
|
||||
|
||||
def lint_configuration(json_output=False):
|
||||
result = ProjectConfig.lint()
|
||||
errors = result["errors"]
|
||||
warnings = result["warnings"]
|
||||
if json_output:
|
||||
return click.echo(result)
|
||||
if not errors and not warnings:
|
||||
return click.secho(
|
||||
'The "platformio.ini" configuration file is free from linting errors.',
|
||||
fg="green",
|
||||
)
|
||||
if errors:
|
||||
click.echo(
|
||||
tabulate(
|
||||
[
|
||||
(
|
||||
click.style(error["type"], fg="red"),
|
||||
error["message"],
|
||||
error.get("source", "") + (f":{error.get('lineno')}")
|
||||
if "lineno" in error
|
||||
else "",
|
||||
)
|
||||
for error in errors
|
||||
],
|
||||
tablefmt="plain",
|
||||
)
|
||||
)
|
||||
if warnings:
|
||||
click.echo(
|
||||
tabulate(
|
||||
[
|
||||
(click.style("Warning", fg="yellow"), warning)
|
||||
for warning in warnings
|
||||
],
|
||||
tablefmt="plain",
|
||||
)
|
||||
)
|
||||
return None
|
||||
@@ -37,6 +37,7 @@ from platformio.project.helpers import load_build_metadata
|
||||
@click.option("--json-output", is_flag=True)
|
||||
@click.option("--json-output-path", type=click.Path())
|
||||
def project_metadata_cmd(project_dir, environments, json_output, json_output_path):
|
||||
project_dir = os.path.abspath(project_dir)
|
||||
with fs.cd(project_dir):
|
||||
config = ProjectConfig.get_instance()
|
||||
config.validate(environments)
|
||||
|
||||
@@ -86,7 +86,7 @@ class ProjectConfigBase:
|
||||
if path and os.path.isfile(path):
|
||||
self.read(path, parse_extra)
|
||||
|
||||
self._maintain_renaimed_options()
|
||||
self._maintain_renamed_options()
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._parser, name)
|
||||
@@ -98,7 +98,7 @@ class ProjectConfigBase:
|
||||
try:
|
||||
self._parser.read(path, "utf-8")
|
||||
except configparser.Error as exc:
|
||||
raise exception.InvalidProjectConfError(path, str(exc))
|
||||
raise exception.InvalidProjectConfError(path, str(exc)) from exc
|
||||
|
||||
if not parse_extra:
|
||||
return
|
||||
@@ -110,7 +110,7 @@ class ProjectConfigBase:
|
||||
for item in glob.glob(pattern, recursive=True):
|
||||
self.read(item)
|
||||
|
||||
def _maintain_renaimed_options(self):
|
||||
def _maintain_renamed_options(self):
|
||||
renamed_options = {}
|
||||
for option in ProjectOptions.values():
|
||||
if option.oldnames:
|
||||
@@ -324,6 +324,7 @@ class ProjectConfigBase:
|
||||
f"`${{this.__env__}}` is called from the `{parent_section}` "
|
||||
"section that is not valid PlatformIO environment, see",
|
||||
option,
|
||||
" ",
|
||||
section,
|
||||
)
|
||||
return parent_section[4:]
|
||||
@@ -332,7 +333,10 @@ class ProjectConfigBase:
|
||||
value = self.get(section, option)
|
||||
except RecursionError as exc:
|
||||
raise exception.ProjectOptionValueError(
|
||||
"Infinite recursion has been detected", option, section
|
||||
"Infinite recursion has been detected",
|
||||
option,
|
||||
" ",
|
||||
section,
|
||||
) from exc
|
||||
if isinstance(value, list):
|
||||
return "\n".join(value)
|
||||
@@ -359,7 +363,10 @@ class ProjectConfigBase:
|
||||
if not self.expand_interpolations:
|
||||
return value
|
||||
raise exception.ProjectOptionValueError(
|
||||
exc.format_message(), option, section
|
||||
exc.format_message(),
|
||||
option,
|
||||
" (%s) " % option_meta.description,
|
||||
section,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -424,16 +431,51 @@ class ProjectConfigBase:
|
||||
return True
|
||||
|
||||
|
||||
class ProjectConfigLintMixin:
|
||||
@classmethod
|
||||
def lint(cls, path=None):
|
||||
errors = []
|
||||
warnings = []
|
||||
try:
|
||||
config = cls.get_instance(path)
|
||||
config.validate(silent=True)
|
||||
warnings = config.warnings
|
||||
config.as_tuple()
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
if exc.__cause__ is not None:
|
||||
exc = exc.__cause__
|
||||
|
||||
item = {"type": exc.__class__.__name__, "message": str(exc)}
|
||||
for attr in ("lineno", "source"):
|
||||
if hasattr(exc, attr):
|
||||
item[attr] = getattr(exc, attr)
|
||||
|
||||
if item["type"] == "ParsingError" and hasattr(exc, "errors"):
|
||||
for lineno, line in getattr(exc, "errors"):
|
||||
errors.append(
|
||||
{
|
||||
"type": item["type"],
|
||||
"message": f"Parsing error: {line}",
|
||||
"lineno": lineno,
|
||||
"source": item["source"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
errors.append(item)
|
||||
return {"errors": errors, "warnings": warnings}
|
||||
|
||||
|
||||
class ProjectConfigDirsMixin:
|
||||
def get_optional_dir(self, name):
|
||||
"""
|
||||
Deprecated, used by platformio-node-helpers.project.observer.fetchLibDirs
|
||||
PlatformIO IDE for Atom depends on platformio-node-helpers@~7.2.0
|
||||
PIO Home 3.0 Project Inspection depends on it
|
||||
"""
|
||||
return self.get("platformio", f"{name}_dir")
|
||||
|
||||
|
||||
class ProjectConfig(ProjectConfigBase, ProjectConfigDirsMixin):
|
||||
class ProjectConfig(ProjectConfigBase, ProjectConfigLintMixin, ProjectConfigDirsMixin):
|
||||
_instances = {}
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -51,4 +51,4 @@ class InvalidEnvNameError(ProjectError, UserSideException):
|
||||
|
||||
|
||||
class ProjectOptionValueError(ProjectError, UserSideException):
|
||||
MESSAGE = "{0} for option `{1}` in section [{2}]"
|
||||
MESSAGE = "{0} for option `{1}`{2}in section [{3}]"
|
||||
@@ -164,6 +164,7 @@ load_project_ide_data = load_build_metadata
|
||||
|
||||
def _load_build_metadata(project_dir, env_names, debug=False):
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from platformio import app
|
||||
from platformio.run.cli import cli as cmd_run
|
||||
|
||||
args = ["--project-dir", project_dir, "--target", "__idedata"]
|
||||
@@ -171,13 +172,15 @@ def _load_build_metadata(project_dir, env_names, debug=False):
|
||||
args.extend(["--target", "__debug"])
|
||||
for name in env_names:
|
||||
args.extend(["-e", name])
|
||||
app.set_session_var("pause_telemetry", True)
|
||||
result = CliRunner().invoke(cmd_run, args)
|
||||
app.set_session_var("pause_telemetry", False)
|
||||
if result.exit_code != 0 and not isinstance(
|
||||
result.exception, exception.ReturnErrorCode
|
||||
):
|
||||
raise result.exception
|
||||
if '"includes":' not in result.output:
|
||||
raise exception.PlatformioException(result.output)
|
||||
raise exception.UserSideException(result.output)
|
||||
return _get_cached_build_metadata(project_dir, env_names)
|
||||
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class ProjectGenerator:
|
||||
|
||||
@staticmethod
|
||||
def get_ide_tpls_dir():
|
||||
return os.path.join(fs.get_assets_dir(), "templates", "ide-projects")
|
||||
return os.path.join(os.path.dirname(__file__), "tpls")
|
||||
|
||||
@classmethod
|
||||
def get_supported_ides(cls):
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+7
-7
@@ -8,6 +8,7 @@
|
||||
% import os
|
||||
% import re
|
||||
%
|
||||
% from platformio.compat import shlex_join
|
||||
% from platformio.project.helpers import load_build_metadata
|
||||
%
|
||||
% def _normalize_path(path):
|
||||
@@ -64,17 +65,16 @@ set(CLION_SVD_FILE_PATH "{{ _normalize_path(svd_path) }}" CACHE FILEPATH "Periph
|
||||
|
||||
SET(CMAKE_C_COMPILER "{{ _normalize_path(cc_path) }}")
|
||||
SET(CMAKE_CXX_COMPILER "{{ _normalize_path(cxx_path) }}")
|
||||
SET(CMAKE_CXX_FLAGS "{{ _normalize_path(to_unix_path(cxx_flags)) }}")
|
||||
SET(CMAKE_C_FLAGS "{{ _normalize_path(to_unix_path(cc_flags)) }}")
|
||||
SET(CMAKE_CXX_FLAGS {{ _normalize_path(to_unix_path(shlex_join(cxx_flags))) }})
|
||||
SET(CMAKE_C_FLAGS {{ _normalize_path(to_unix_path(shlex_join(cc_flags))) }})
|
||||
|
||||
% STD_RE = re.compile(r"\-std=[a-z\+]+(\w+)")
|
||||
% cc_stds = STD_RE.findall(cc_flags)
|
||||
% cxx_stds = STD_RE.findall(cxx_flags)
|
||||
% cc_stds = [arg for arg in cc_flags if arg.startswith("-std=")]
|
||||
% cxx_stds = [arg for arg in cxx_flags if arg.startswith("-std=")]
|
||||
% if cc_stds:
|
||||
SET(CMAKE_C_STANDARD {{ cc_stds[-1] }})
|
||||
SET(CMAKE_C_STANDARD {{ cc_stds[-1][-2:] }})
|
||||
% end
|
||||
% if cxx_stds:
|
||||
set(CMAKE_CXX_STANDARD {{ cxx_stds[-1] }})
|
||||
set(CMAKE_CXX_STANDARD {{ cxx_stds[-1][-2:] }})
|
||||
% end
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES "{{ env_name }}")
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-3
@@ -1,6 +1,4 @@
|
||||
% import re
|
||||
% STD_RE = re.compile(r"(\-std=[a-z\+]+\w+)")
|
||||
% cxx_stds = STD_RE.findall(cxx_flags)
|
||||
% cxx_stds = [arg for arg in cxx_flags if arg.startswith("-std=")]
|
||||
% cxx_std = cxx_stds[-1] if cxx_stds else ""
|
||||
%
|
||||
% if cxx_path.startswith(user_home_dir):
|
||||
File renamed without changes.
+4
-2
@@ -1,7 +1,9 @@
|
||||
% from platformio.compat import shlex_join
|
||||
%
|
||||
clang
|
||||
|
||||
{{"%c"}} {{ !cc_flags }}
|
||||
{{"%cpp"}} {{ !cxx_flags }}
|
||||
{{"%c"}} {{ shlex_join(cc_flags) }}
|
||||
{{"%cpp"}} {{ shlex_join(cxx_flags) }}
|
||||
|
||||
% for include in filter_includes(includes):
|
||||
-I{{ !include }}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,3 @@
|
||||
% from platformio.compat import shlex_join
|
||||
%
|
||||
{{shlex_join(cc_flags).replace('-mlongcalls', '-mlong-calls')}}
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,3 @@
|
||||
% from platformio.compat import shlex_join
|
||||
%
|
||||
{{shlex_join(cxx_flags).replace('-mlongcalls', '-mlong-calls')}}
|
||||
File renamed without changes.
File renamed without changes.
+4
-2
@@ -1,7 +1,9 @@
|
||||
% from platformio.compat import shlex_join
|
||||
%
|
||||
clang
|
||||
|
||||
{{"%c"}} {{ !cc_flags }}
|
||||
{{"%cpp"}} {{ !cxx_flags }}
|
||||
{{"%c"}} {{ shlex_join(cc_flags) }}
|
||||
{{"%cpp"}} {{ shlex_join(cxx_flags) }}
|
||||
|
||||
% for include in filter_includes(includes):
|
||||
-I{{ !include }}
|
||||
File renamed without changes.
+4
-2
@@ -1,7 +1,9 @@
|
||||
% from platformio.compat import shlex_join
|
||||
%
|
||||
clang
|
||||
|
||||
{{"%c"}} {{ !cc_flags }}
|
||||
{{"%cpp"}} {{ !cxx_flags }}
|
||||
{{"%c"}} {{ shlex_join(cc_flags) }}
|
||||
{{"%cpp"}} {{ shlex_join(cxx_flags) }}
|
||||
|
||||
% for include in filter_includes(includes):
|
||||
-I{{ !include }}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+27
-31
@@ -1,27 +1,25 @@
|
||||
% import os
|
||||
% import platform
|
||||
% import re
|
||||
%
|
||||
% import click
|
||||
%
|
||||
% systype = platform.system().lower()
|
||||
%
|
||||
% cpp_standards_remap = {
|
||||
% "0x": "11",
|
||||
% "1y": "14",
|
||||
% "1z": "17",
|
||||
% "2a": "20",
|
||||
% "2b": "23"
|
||||
% "c++0x": "c++11",
|
||||
% "c++1y": "c++14",
|
||||
% "c++1z": "c++17",
|
||||
% "c++2a": "c++20",
|
||||
% "c++2b": "c++23",
|
||||
% "gnu++0x": "gnu++11",
|
||||
% "gnu++1y": "gnu++14",
|
||||
% "gnu++1z": "gnu++17",
|
||||
% "gnu++2a": "gnu++20",
|
||||
% "gnu++2b": "gnu++23"
|
||||
% }
|
||||
%
|
||||
% def _escape(text):
|
||||
% return to_unix_path(text).replace('"', '\\"')
|
||||
% end
|
||||
%
|
||||
% def split_args(args_string):
|
||||
% return click.parser.split_arg_string(to_unix_path(args_string))
|
||||
% end
|
||||
%
|
||||
% def filter_args(args, allowed, ignore=None):
|
||||
% if not allowed:
|
||||
% return []
|
||||
@@ -31,18 +29,18 @@
|
||||
% result = []
|
||||
% i = 0
|
||||
% length = len(args)
|
||||
% while(i < length):
|
||||
% if any(args[i].startswith(f) for f in allowed) and not any(
|
||||
% args[i].startswith(f) for f in ignore):
|
||||
% result.append(args[i])
|
||||
% if i + 1 < length and not args[i + 1].startswith("-"):
|
||||
% i += 1
|
||||
% result.append(args[i])
|
||||
% end
|
||||
% while(i < length):
|
||||
% if any(args[i].startswith(f) for f in allowed) and not any(
|
||||
% args[i].startswith(f) for f in ignore):
|
||||
% result.append(args[i])
|
||||
% if i + 1 < length and not args[i + 1].startswith("-"):
|
||||
% i += 1
|
||||
% result.append(args[i])
|
||||
% end
|
||||
% i += 1
|
||||
% end
|
||||
% return result
|
||||
% end
|
||||
% i += 1
|
||||
% end
|
||||
% return result
|
||||
% end
|
||||
%
|
||||
% def _find_abs_path(inc, inc_paths):
|
||||
@@ -76,12 +74,10 @@
|
||||
%
|
||||
% cleaned_includes = filter_includes(includes, ["toolchain"])
|
||||
%
|
||||
% STD_RE = re.compile(r"\-std=[a-z\+]+(\w+)")
|
||||
% cc_stds = STD_RE.findall(cc_flags)
|
||||
% cxx_stds = STD_RE.findall(cxx_flags)
|
||||
% cc_m_flags = split_args(cc_flags)
|
||||
% cc_stds = [arg[5:] for arg in cc_flags if arg.startswith("-std=")]
|
||||
% cxx_stds = [arg[5:] for arg in cxx_flags if arg.startswith("-std=")]
|
||||
% forced_includes = _find_forced_includes(
|
||||
% filter_args(cc_m_flags, ["-include", "-imacros"]), cleaned_includes)
|
||||
% filter_args(cc_flags, ["-include", "-imacros"]), cleaned_includes)
|
||||
%
|
||||
//
|
||||
// !!! WARNING !!! AUTO-GENERATED FILE!
|
||||
@@ -114,10 +110,10 @@
|
||||
""
|
||||
],
|
||||
% if cc_stds:
|
||||
"cStandard": "c{{ cc_stds[-1] }}",
|
||||
"cStandard": "{{ cc_stds[-1] }}",
|
||||
% end
|
||||
% if cxx_stds:
|
||||
"cppStandard": "c++{{ cpp_standards_remap.get(cxx_stds[-1], cxx_stds[-1]) }}",
|
||||
"cppStandard": "{{ cpp_standards_remap.get(cxx_stds[-1], cxx_stds[-1]) }}",
|
||||
% end
|
||||
% if forced_includes:
|
||||
"forcedInclude": [
|
||||
@@ -130,7 +126,7 @@
|
||||
"compilerPath": "{{ cc_path }}",
|
||||
"compilerArgs": [
|
||||
% for flag in [
|
||||
% f for f in filter_args(cc_m_flags, ["-m", "-i", "@"], ["-include", "-imacros"])
|
||||
% f for f in filter_args(cc_flags, ["-m", "-i", "@"], ["-include", "-imacros"])
|
||||
% ]:
|
||||
"{{ flag }}",
|
||||
% end
|
||||
File renamed without changes.
File renamed without changes.
@@ -142,12 +142,12 @@ class RegistryClient(HTTPClient):
|
||||
x_with_authorization=self.allowed_private_packages(),
|
||||
)
|
||||
|
||||
def get_package(self, type_, owner, name, version=None, extra_path=None):
|
||||
def get_package(self, typex, owner, name, version=None, extra_path=None):
|
||||
try:
|
||||
return self.fetch_json_data(
|
||||
"get",
|
||||
"/v3/packages/{owner}/{type}/{name}{extra_path}".format(
|
||||
type=type_,
|
||||
type=typex,
|
||||
owner=owner.lower(),
|
||||
name=name.lower(),
|
||||
extra_path=extra_path or "",
|
||||
|
||||
@@ -77,7 +77,6 @@ def system_info_cmd(json_output):
|
||||
).get_installed()
|
||||
),
|
||||
}
|
||||
|
||||
click.echo(
|
||||
json.dumps(data)
|
||||
if json_output
|
||||
|
||||
@@ -29,8 +29,10 @@ class ShellType(Enum):
|
||||
|
||||
|
||||
def get_bash_version():
|
||||
result = subprocess.run(["bash", "--version"], capture_output=True, check=True)
|
||||
match = re.search(r"version\s+(\d+)\.(\d+)", result.stdout.decode(), re.IGNORECASE)
|
||||
output = subprocess.run(
|
||||
["bash", "--version"], check=True, stdout=subprocess.PIPE
|
||||
).stdout.decode()
|
||||
match = re.search(r"version\s+(\d+)\.(\d+)", output, re.IGNORECASE)
|
||||
if match:
|
||||
return (int(match.group(1)), int(match.group(2)))
|
||||
return (0, 0)
|
||||
|
||||
+267
-324
@@ -14,299 +14,181 @@
|
||||
|
||||
import atexit
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from collections import deque
|
||||
from time import sleep, time
|
||||
from traceback import format_exc
|
||||
|
||||
import requests
|
||||
|
||||
from platformio import __version__, app, exception, util
|
||||
from platformio import __title__, __version__, app, exception, fs, util
|
||||
from platformio.cli import PlatformioCLI
|
||||
from platformio.compat import hashlib_encode_data, string_types
|
||||
from platformio.http import HTTPSession
|
||||
from platformio.proc import is_ci, is_container
|
||||
from platformio.project.helpers import is_platformio_project
|
||||
from platformio.compat import hashlib_encode_data
|
||||
from platformio.debug.config.base import DebugConfigBase
|
||||
from platformio.http import HTTPSession, ensure_internet_on
|
||||
from platformio.proc import is_ci
|
||||
|
||||
KEEP_MAX_REPORTS = 100
|
||||
SEND_MAX_EVENTS = 25
|
||||
|
||||
|
||||
class TelemetryBase:
|
||||
def __init__(self):
|
||||
self._params = {}
|
||||
class MeasurementProtocol:
|
||||
def __init__(self, events=None):
|
||||
self.client_id = app.get_cid()
|
||||
self._events = events or []
|
||||
self._user_properties = {}
|
||||
|
||||
def __getitem__(self, name):
|
||||
return self._params.get(name, None)
|
||||
self.set_user_property("systype", util.get_systype())
|
||||
created_at = app.get_state_item("created_at", None)
|
||||
if created_at:
|
||||
self.set_user_property("created_at", int(created_at))
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
self._params[name] = value
|
||||
@staticmethod
|
||||
def event_to_dict(name, params, timestamp=None):
|
||||
event = {"name": name, "params": params}
|
||||
if timestamp is not None:
|
||||
event["timestamp"] = timestamp
|
||||
return event
|
||||
|
||||
def __delitem__(self, name):
|
||||
if name in self._params:
|
||||
del self._params[name]
|
||||
def set_user_property(self, name, value):
|
||||
self._user_properties[name] = value
|
||||
|
||||
def send(self, hittype):
|
||||
raise NotImplementedError()
|
||||
def add_event(self, name, params):
|
||||
self._events.append(self.event_to_dict(name, params))
|
||||
|
||||
|
||||
class MeasurementProtocol(TelemetryBase):
|
||||
TID = "UA-1768265-9"
|
||||
PARAMS_MAP = {
|
||||
"screen_name": "cd",
|
||||
"event_category": "ec",
|
||||
"event_action": "ea",
|
||||
"event_label": "el",
|
||||
"event_value": "ev",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self["v"] = 1
|
||||
self["tid"] = self.TID
|
||||
self["cid"] = app.get_cid()
|
||||
|
||||
try:
|
||||
self["sr"] = "%dx%d" % shutil.get_terminal_size()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self._prefill_screen_name()
|
||||
self._prefill_appinfo()
|
||||
self._prefill_sysargs()
|
||||
self._prefill_custom_data()
|
||||
|
||||
def __getitem__(self, name):
|
||||
if name in self.PARAMS_MAP:
|
||||
name = self.PARAMS_MAP[name]
|
||||
return super().__getitem__(name)
|
||||
|
||||
def __setitem__(self, name, value):
|
||||
if name in self.PARAMS_MAP:
|
||||
name = self.PARAMS_MAP[name]
|
||||
super().__setitem__(name, value)
|
||||
|
||||
def _prefill_appinfo(self):
|
||||
self["av"] = __version__
|
||||
self["an"] = app.get_user_agent()
|
||||
|
||||
def _prefill_sysargs(self):
|
||||
args = []
|
||||
for arg in sys.argv[1:]:
|
||||
arg = str(arg)
|
||||
if arg == "account": # ignore account cmd which can contain username
|
||||
return
|
||||
if any(("@" in arg, "/" in arg, "\\" in arg)):
|
||||
arg = "***"
|
||||
args.append(arg.lower())
|
||||
self["cd3"] = " ".join(args)
|
||||
|
||||
def _prefill_custom_data(self):
|
||||
caller_id = str(app.get_session_var("caller_id"))
|
||||
self["cd1"] = util.get_systype()
|
||||
self["cd4"] = 1 if (not is_ci() and (caller_id or not is_container())) else 0
|
||||
if caller_id:
|
||||
self["cd5"] = caller_id.lower()
|
||||
|
||||
def _prefill_screen_name(self):
|
||||
def _first_arg_from_list(args_, list_):
|
||||
for _arg in args_:
|
||||
if _arg in list_:
|
||||
return _arg
|
||||
return None
|
||||
|
||||
args = []
|
||||
for arg in PlatformioCLI.leftover_args:
|
||||
if not isinstance(arg, string_types):
|
||||
arg = str(arg)
|
||||
if not arg.startswith("-"):
|
||||
args.append(arg.lower())
|
||||
if not args:
|
||||
return
|
||||
|
||||
cmd_path = args[:1]
|
||||
if args[0] in (
|
||||
"access",
|
||||
"account",
|
||||
"device",
|
||||
"org",
|
||||
"package",
|
||||
"pkg",
|
||||
"platform",
|
||||
"project",
|
||||
"settings",
|
||||
"system",
|
||||
"team",
|
||||
):
|
||||
cmd_path = args[:2]
|
||||
if args[0] == "lib" and len(args) > 1:
|
||||
lib_subcmds = (
|
||||
"builtin",
|
||||
"install",
|
||||
"list",
|
||||
"register",
|
||||
"search",
|
||||
"show",
|
||||
"stats",
|
||||
"uninstall",
|
||||
"update",
|
||||
)
|
||||
sub_cmd = _first_arg_from_list(args[1:], lib_subcmds)
|
||||
if sub_cmd:
|
||||
cmd_path.append(sub_cmd)
|
||||
elif args[0] == "remote" and len(args) > 1:
|
||||
remote_subcmds = ("agent", "device", "run", "test")
|
||||
sub_cmd = _first_arg_from_list(args[1:], remote_subcmds)
|
||||
if sub_cmd:
|
||||
cmd_path.append(sub_cmd)
|
||||
if len(args) > 2 and sub_cmd in ("agent", "device"):
|
||||
remote2_subcmds = ("list", "start", "monitor")
|
||||
sub_cmd = _first_arg_from_list(args[2:], remote2_subcmds)
|
||||
if sub_cmd:
|
||||
cmd_path.append(sub_cmd)
|
||||
self["screen_name"] = " ".join([p.title() for p in cmd_path])
|
||||
|
||||
def _ignore_hit(self):
|
||||
if not app.get_setting("enable_telemetry"):
|
||||
return True
|
||||
if self["ea"] in ("Idedata", "__Idedata"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def send(self, hittype):
|
||||
if self._ignore_hit():
|
||||
return
|
||||
self["t"] = hittype
|
||||
# correct queue time
|
||||
if "qt" in self._params and isinstance(self["qt"], float):
|
||||
self["qt"] = int((time() - self["qt"]) * 1000)
|
||||
MPDataPusher().push(self._params)
|
||||
def to_payload(self):
|
||||
return {
|
||||
"client_id": self.client_id,
|
||||
"user_properties": self._user_properties,
|
||||
"events": self._events,
|
||||
}
|
||||
|
||||
|
||||
@util.singleton
|
||||
class MPDataPusher:
|
||||
MAX_WORKERS = 5
|
||||
|
||||
class TelemetryLogger:
|
||||
def __init__(self):
|
||||
self._queue = queue.LifoQueue()
|
||||
self._failedque = deque()
|
||||
self._events = deque()
|
||||
|
||||
self._sender_thread = None
|
||||
self._sender_queue = queue.Queue()
|
||||
self._sender_terminated = False
|
||||
|
||||
self._http_session = HTTPSession()
|
||||
self._http_offline = False
|
||||
self._workers = []
|
||||
|
||||
def push(self, item):
|
||||
# if network is off-line
|
||||
if self._http_offline:
|
||||
if "qt" not in item:
|
||||
item["qt"] = time()
|
||||
self._failedque.append(item)
|
||||
def close(self):
|
||||
self._http_session.close()
|
||||
|
||||
def log_event(self, name, params, timestamp=None, instant_sending=False):
|
||||
if not app.get_setting("enable_telemetry") or app.get_session_var(
|
||||
"pause_telemetry"
|
||||
):
|
||||
return None
|
||||
timestamp = timestamp or int(time.time())
|
||||
self._events.append(
|
||||
MeasurementProtocol.event_to_dict(name, params, timestamp=timestamp)
|
||||
)
|
||||
if self._http_offline: # if network is off-line
|
||||
return False
|
||||
if instant_sending:
|
||||
self.send()
|
||||
return True
|
||||
|
||||
def send(self):
|
||||
if not self._events or self._sender_terminated:
|
||||
return
|
||||
|
||||
self._queue.put(item)
|
||||
self._tune_workers()
|
||||
|
||||
def in_wait(self):
|
||||
return self._queue.unfinished_tasks
|
||||
|
||||
def get_items(self):
|
||||
items = list(self._failedque)
|
||||
try:
|
||||
while True:
|
||||
items.append(self._queue.get_nowait())
|
||||
except queue.Empty:
|
||||
pass
|
||||
return items
|
||||
|
||||
def _tune_workers(self):
|
||||
for i, w in enumerate(self._workers):
|
||||
if not w.is_alive():
|
||||
del self._workers[i]
|
||||
|
||||
need_nums = min(self._queue.qsize(), self.MAX_WORKERS)
|
||||
active_nums = len(self._workers)
|
||||
if need_nums <= active_nums:
|
||||
return
|
||||
|
||||
for i in range(need_nums - active_nums):
|
||||
t = threading.Thread(target=self._worker)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
self._workers.append(t)
|
||||
|
||||
def _worker(self):
|
||||
while True:
|
||||
if not self._sender_thread:
|
||||
self._sender_thread = threading.Thread(
|
||||
target=self._sender_worker, daemon=True
|
||||
)
|
||||
self._sender_thread.start()
|
||||
while self._events:
|
||||
events = []
|
||||
try:
|
||||
item = self._queue.get()
|
||||
_item = item.copy()
|
||||
if "qt" not in _item:
|
||||
_item["qt"] = time()
|
||||
self._failedque.append(_item)
|
||||
if self._send_data(item):
|
||||
self._failedque.remove(_item)
|
||||
self._queue.task_done()
|
||||
except: # pylint: disable=W0702
|
||||
while len(events) < SEND_MAX_EVENTS:
|
||||
events.append(self._events.popleft())
|
||||
except IndexError:
|
||||
pass
|
||||
self._sender_queue.put(events)
|
||||
|
||||
def _sender_worker(self):
|
||||
while True:
|
||||
if self._sender_terminated:
|
||||
return
|
||||
try:
|
||||
events = self._sender_queue.get()
|
||||
if not self._commit_events(events):
|
||||
self._events.extend(events)
|
||||
self._sender_queue.task_done()
|
||||
except (queue.Empty, ValueError):
|
||||
pass
|
||||
|
||||
def _send_data(self, data):
|
||||
def _commit_events(self, events):
|
||||
if self._http_offline:
|
||||
return False
|
||||
mp = MeasurementProtocol(events)
|
||||
payload = mp.to_payload()
|
||||
# print("_commit_payload", payload)
|
||||
try:
|
||||
r = self._http_session.post(
|
||||
"https://ssl.google-analytics.com/collect",
|
||||
data=data,
|
||||
timeout=1,
|
||||
"https://telemetry.platformio.org/collect",
|
||||
json=payload,
|
||||
timeout=(2, 5), # connect, read
|
||||
)
|
||||
r.raise_for_status()
|
||||
return True
|
||||
except requests.exceptions.HTTPError as exc:
|
||||
# skip Bad Request
|
||||
if 400 >= exc.response.status_code < 500:
|
||||
if exc.response.status_code >= 400 and exc.response.status_code < 500:
|
||||
return True
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
self._http_offline = True
|
||||
return False
|
||||
|
||||
def terminate_sender(self):
|
||||
self._sender_terminated = True
|
||||
|
||||
def on_command():
|
||||
resend_backuped_reports()
|
||||
def is_sending(self):
|
||||
return self._sender_queue.unfinished_tasks
|
||||
|
||||
mp = MeasurementProtocol()
|
||||
mp.send("screenview")
|
||||
def get_unsent_events(self):
|
||||
result = list(self._events)
|
||||
try:
|
||||
while True:
|
||||
result.extend(self._sender_queue.get_nowait())
|
||||
except queue.Empty:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def log_event(name, params, instant_sending=False):
|
||||
TelemetryLogger().log_event(name, params, instant_sending=instant_sending)
|
||||
|
||||
|
||||
def on_cmd_start(cmd_ctx):
|
||||
process_postponed_logs()
|
||||
log_command(cmd_ctx)
|
||||
|
||||
|
||||
def on_exit():
|
||||
TelemetryLogger().send()
|
||||
|
||||
|
||||
def log_command(ctx):
|
||||
params = {
|
||||
"path_args": PlatformioCLI.reveal_cmd_path_args(ctx),
|
||||
}
|
||||
if is_ci():
|
||||
measure_ci()
|
||||
params["ci_actor"] = resolve_ci_actor() or "Unknown"
|
||||
log_event("cmd_run", params)
|
||||
|
||||
|
||||
def on_exception(e):
|
||||
skip_conditions = [
|
||||
isinstance(e, cls)
|
||||
for cls in (
|
||||
IOError,
|
||||
exception.ReturnErrorCode,
|
||||
exception.UserSideException,
|
||||
)
|
||||
]
|
||||
if any(skip_conditions):
|
||||
return
|
||||
is_fatal = any(
|
||||
[
|
||||
not isinstance(e, exception.PlatformioException),
|
||||
"Error" in e.__class__.__name__,
|
||||
]
|
||||
)
|
||||
description = "%s: %s" % (
|
||||
type(e).__name__,
|
||||
" ".join(reversed(format_exc().split("\n"))) if is_fatal else str(e),
|
||||
)
|
||||
send_exception(description, is_fatal)
|
||||
|
||||
|
||||
def measure_ci():
|
||||
event = {"category": "CI", "action": "NoName", "label": None}
|
||||
def resolve_ci_actor():
|
||||
known_cis = (
|
||||
"GITHUB_ACTIONS",
|
||||
"TRAVIS",
|
||||
@@ -318,123 +200,184 @@ def measure_ci():
|
||||
)
|
||||
for name in known_cis:
|
||||
if os.getenv(name, "false").lower() == "true":
|
||||
event["action"] = name
|
||||
break
|
||||
send_event(**event)
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def dump_run_environment(options):
|
||||
def dump_project_env_params(config, env, platform):
|
||||
non_sensitive_data = [
|
||||
"platform",
|
||||
"platform_packages",
|
||||
"framework",
|
||||
"board",
|
||||
"upload_protocol",
|
||||
"check_tool",
|
||||
"debug_tool",
|
||||
"monitor_filters",
|
||||
"test_framework",
|
||||
]
|
||||
safe_options = {k: v for k, v in options.items() if k in non_sensitive_data}
|
||||
if is_platformio_project(os.getcwd()):
|
||||
phash = hashlib.sha1(hashlib_encode_data(app.get_cid()))
|
||||
safe_options["pid"] = phash.hexdigest()
|
||||
return json.dumps(safe_options, sort_keys=True, ensure_ascii=False)
|
||||
section = f"env:{env}"
|
||||
params = {
|
||||
option: config.get(section, option)
|
||||
for option in non_sensitive_data
|
||||
if config.has_option(section, option)
|
||||
}
|
||||
params["pid"] = hashlib.sha1(hashlib_encode_data(config.path)).hexdigest()
|
||||
params["platform_name"] = platform.name
|
||||
params["platform_version"] = platform.version
|
||||
return params
|
||||
|
||||
|
||||
def send_run_environment(options, targets):
|
||||
send_event(
|
||||
"Env",
|
||||
" ".join([t.title() for t in targets or ["run"]]),
|
||||
dump_run_environment(options),
|
||||
def log_platform_run(platform, project_config, project_env, targets=None):
|
||||
params = dump_project_env_params(project_config, project_env, platform)
|
||||
if targets:
|
||||
params["targets"] = targets
|
||||
log_event("platform_run", params, instant_sending=True)
|
||||
|
||||
|
||||
def log_exception(exc):
|
||||
skip_conditions = [
|
||||
isinstance(exc, cls)
|
||||
for cls in (
|
||||
IOError,
|
||||
exception.ReturnErrorCode,
|
||||
exception.UserSideException,
|
||||
)
|
||||
]
|
||||
skip_conditions.append(not isinstance(exc, Exception))
|
||||
if any(skip_conditions):
|
||||
return
|
||||
is_fatal = any(
|
||||
[
|
||||
not isinstance(exc, exception.PlatformioException),
|
||||
"Error" in exc.__class__.__name__,
|
||||
]
|
||||
)
|
||||
|
||||
def _strip_module_path(match):
|
||||
module_path = match.group(1).replace(fs.get_source_dir() + os.sep, "")
|
||||
sp_folder_name = "site-packages"
|
||||
sp_pos = module_path.find(sp_folder_name)
|
||||
if sp_pos != -1:
|
||||
module_path = module_path[sp_pos + len(sp_folder_name) + 1 :]
|
||||
module_path = fs.to_unix_path(module_path)
|
||||
return f'File "{module_path}",'
|
||||
|
||||
trace = re.sub(
|
||||
r'File "([^"]+)",',
|
||||
_strip_module_path,
|
||||
traceback.format_exc(),
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
|
||||
params = {
|
||||
"name": exc.__class__.__name__,
|
||||
"description": str(exc),
|
||||
"traceback": trace,
|
||||
"cmd_args": sys.argv[1:],
|
||||
"is_fatal": is_fatal,
|
||||
}
|
||||
log_event("exception", params)
|
||||
|
||||
|
||||
def log_debug_started(debug_config: DebugConfigBase):
|
||||
log_event(
|
||||
"debug_started",
|
||||
dump_project_env_params(
|
||||
debug_config.project_config, debug_config.env_name, debug_config.platform
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def send_event(category, action, label=None, value=None, screen_name=None):
|
||||
mp = MeasurementProtocol()
|
||||
mp["event_category"] = category[:150]
|
||||
mp["event_action"] = action[:500]
|
||||
if label:
|
||||
mp["event_label"] = label[:500]
|
||||
if value:
|
||||
mp["event_value"] = int(value)
|
||||
if screen_name:
|
||||
mp["screen_name"] = screen_name[:2048]
|
||||
mp.send("event")
|
||||
|
||||
|
||||
def send_exception(description, is_fatal=False):
|
||||
def log_debug_exception(exc, debug_config: DebugConfigBase):
|
||||
# cleanup sensitive information, such as paths
|
||||
description = description.replace("Traceback (most recent call last):", "")
|
||||
description = description.replace("\\", "/")
|
||||
description = fs.to_unix_path(str(exc))
|
||||
description = re.sub(
|
||||
r'(^|\s+|")(?:[a-z]\:)?((/[^"/]+)+)(\s+|"|$)',
|
||||
lambda m: " %s " % os.path.join(*m.group(2).split("/")[-2:]),
|
||||
description,
|
||||
re.I | re.M,
|
||||
)
|
||||
description = re.sub(r"\s+", " ", description, flags=re.M)
|
||||
|
||||
mp = MeasurementProtocol()
|
||||
mp["exd"] = description[:8192].strip()
|
||||
mp["exf"] = 1 if is_fatal else 0
|
||||
mp.send("exception")
|
||||
params = {
|
||||
"name": exc.__class__.__name__,
|
||||
"description": description.strip(),
|
||||
}
|
||||
params.update(
|
||||
dump_project_env_params(
|
||||
debug_config.project_config, debug_config.env_name, debug_config.platform
|
||||
)
|
||||
)
|
||||
log_event("debug_exception", params)
|
||||
|
||||
|
||||
@atexit.register
|
||||
def _finalize():
|
||||
timeout = 1000 # msec
|
||||
elapsed = 0
|
||||
telemetry = TelemetryLogger()
|
||||
telemetry.terminate_sender()
|
||||
try:
|
||||
while elapsed < timeout:
|
||||
if not MPDataPusher().in_wait():
|
||||
if not telemetry.is_sending():
|
||||
break
|
||||
sleep(0.2)
|
||||
time.sleep(0.2)
|
||||
elapsed += 200
|
||||
backup_reports(MPDataPusher().get_items())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
postpone_events(telemetry.get_unsent_events())
|
||||
telemetry.close()
|
||||
|
||||
|
||||
def backup_reports(items):
|
||||
if not items:
|
||||
return
|
||||
|
||||
KEEP_MAX_REPORTS = 100
|
||||
tm = app.get_state_item("telemetry", {})
|
||||
if "backup" not in tm:
|
||||
tm["backup"] = []
|
||||
|
||||
for params in items:
|
||||
# skip static options
|
||||
for key in list(params.keys()):
|
||||
if key in ("v", "tid", "cid", "cd1", "cd2", "sr", "an"):
|
||||
del params[key]
|
||||
|
||||
# store time in UNIX format
|
||||
if "qt" not in params:
|
||||
params["qt"] = time()
|
||||
elif not isinstance(params["qt"], float):
|
||||
params["qt"] = time() - (params["qt"] / 1000)
|
||||
|
||||
tm["backup"].append(params)
|
||||
|
||||
tm["backup"] = tm["backup"][KEEP_MAX_REPORTS * -1 :]
|
||||
app.set_state_item("telemetry", tm)
|
||||
def load_postponed_events():
|
||||
state_path = app.resolve_state_path(
|
||||
"cache_dir", "telemetry.json", ensure_dir_exists=False
|
||||
)
|
||||
if not os.path.isfile(state_path):
|
||||
return []
|
||||
with app.State(state_path) as state:
|
||||
return state.get("events", [])
|
||||
|
||||
|
||||
def resend_backuped_reports():
|
||||
tm = app.get_state_item("telemetry", {})
|
||||
if "backup" not in tm or not tm["backup"]:
|
||||
return False
|
||||
|
||||
for report in tm["backup"]:
|
||||
mp = MeasurementProtocol()
|
||||
for key, value in report.items():
|
||||
mp[key] = value
|
||||
mp.send(report["t"])
|
||||
|
||||
# clean
|
||||
tm["backup"] = []
|
||||
app.set_state_item("telemetry", tm)
|
||||
def save_postponed_events(events):
|
||||
state_path = app.resolve_state_path("cache_dir", "telemetry.json")
|
||||
if not events:
|
||||
try:
|
||||
if os.path.isfile(state_path):
|
||||
os.remove(state_path)
|
||||
except: # pylint: disable=bare-except
|
||||
pass
|
||||
return None
|
||||
with app.State(state_path, lock=True) as state:
|
||||
state["events"] = events
|
||||
state.modified = True
|
||||
return True
|
||||
|
||||
|
||||
def postpone_events(events):
|
||||
if not events:
|
||||
return None
|
||||
postponed_events = load_postponed_events() or []
|
||||
timestamp = int(time.time())
|
||||
for event in events:
|
||||
if "timestamp" not in event:
|
||||
event["timestamp"] = timestamp
|
||||
postponed_events.append(event)
|
||||
save_postponed_events(postponed_events[KEEP_MAX_REPORTS * -1 :])
|
||||
return True
|
||||
|
||||
|
||||
def process_postponed_logs():
|
||||
if not ensure_internet_on():
|
||||
return None
|
||||
events = load_postponed_events()
|
||||
if not events:
|
||||
return None
|
||||
save_postponed_events([]) # clean
|
||||
telemetry = TelemetryLogger()
|
||||
for event in events:
|
||||
if set(["name", "params", "timestamp"]) <= set(event.keys()):
|
||||
telemetry.log_event(
|
||||
event["name"],
|
||||
event["params"],
|
||||
timestamp=event["timestamp"],
|
||||
instant_sending=False,
|
||||
)
|
||||
return True
|
||||
@@ -55,8 +55,8 @@ extern "C"
|
||||
|
||||
void unityOutputStart(unsigned long);
|
||||
void unityOutputChar(unsigned int);
|
||||
void unityOutputFlush();
|
||||
void unityOutputComplete();
|
||||
void unityOutputFlush(void);
|
||||
void unityOutputComplete(void);
|
||||
|
||||
#define UNITY_OUTPUT_START() unityOutputStart((unsigned long) $baudrate)
|
||||
#define UNITY_OUTPUT_CHAR(c) unityOutputChar(c)
|
||||
@@ -246,18 +246,20 @@ void unityOutputComplete(void) { unittest_uart_end(); }
|
||||
unity_h = dst_dir / "unity_config.h"
|
||||
if not unity_h.is_file():
|
||||
unity_h.write_text(
|
||||
string.Template(self.UNITY_CONFIG_H).substitute(
|
||||
baudrate=self.get_test_speed()
|
||||
),
|
||||
string.Template(self.UNITY_CONFIG_H)
|
||||
.substitute(baudrate=self.get_test_speed())
|
||||
.strip()
|
||||
+ "\n",
|
||||
encoding="utf8",
|
||||
)
|
||||
framework_config = self.get_unity_framework_config()
|
||||
unity_c = dst_dir / ("unity_config.%s" % framework_config.get("language", "c"))
|
||||
if not unity_c.is_file():
|
||||
unity_c.write_text(
|
||||
string.Template(self.UNITY_CONFIG_C).substitute(
|
||||
framework_config_code=framework_config["code"]
|
||||
),
|
||||
string.Template(self.UNITY_CONFIG_C)
|
||||
.substitute(framework_config_code=framework_config["code"])
|
||||
.strip()
|
||||
+ "\n",
|
||||
encoding="utf8",
|
||||
)
|
||||
|
||||
|
||||
+3
-3
@@ -12,13 +12,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import math
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import click
|
||||
|
||||
@@ -169,8 +169,8 @@ def items_in_list(needle, haystack):
|
||||
|
||||
def parse_datetime(datestr):
|
||||
if "T" in datestr and "Z" in datestr:
|
||||
return datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return datetime.strptime(datestr)
|
||||
return datetime.datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ")
|
||||
return datetime.datetime.strptime(datestr)
|
||||
|
||||
|
||||
def merge_dicts(d1, d2, path=None):
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import sys
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
from platformio import (
|
||||
@@ -25,31 +24,48 @@ from platformio import (
|
||||
__version__,
|
||||
)
|
||||
|
||||
PY36 = sys.version_info < (3, 7)
|
||||
|
||||
env_marker_below_37 = "python_version < '3.7'"
|
||||
env_marker_gte_37 = "python_version >= '3.7'"
|
||||
|
||||
minimal_requirements = [
|
||||
"bottle==0.12.*",
|
||||
"click%s" % ("==8.0.4" if PY36 else ">=8.0.4,<9"),
|
||||
"click==8.0.4; " + env_marker_below_37,
|
||||
"click==8.1.*; " + env_marker_gte_37,
|
||||
"colorama",
|
||||
"marshmallow==%s" % ("3.14.1" if PY36 else "3.*"),
|
||||
"pyelftools>=0.27,<1",
|
||||
"marshmallow==3.14.1; " + env_marker_below_37,
|
||||
"marshmallow==3.19.*; " + env_marker_gte_37,
|
||||
"pyelftools==0.29",
|
||||
"pyserial==3.5.*", # keep in sync "device/monitor/terminal.py"
|
||||
"requests==2.*",
|
||||
"urllib3<2", # issue 4614: urllib3 v2.0 only supports OpenSSL 1.1.1+
|
||||
"requests==%s" % ("2.27.1" if PY36 else "2.*"),
|
||||
"semantic_version==2.10.*",
|
||||
"tabulate==%s" % ("0.8.10" if PY36 else "0.9.*"),
|
||||
"tabulate==0.*",
|
||||
]
|
||||
|
||||
home_requirements = [
|
||||
"aiofiles==%s" % ("0.8.0" if PY36 else "23.1.*"),
|
||||
"ajsonrpc==1.*",
|
||||
"starlette==%s" % ("0.19.1" if PY36 else "0.26.*"),
|
||||
"uvicorn==%s" % ("0.16.0" if PY36 else "0.22.*"),
|
||||
"wsproto==%s" % ("1.0.0" if PY36 else "1.2.*"),
|
||||
"aiofiles>=0.8.0",
|
||||
"ajsonrpc==1.2.*",
|
||||
"starlette==0.19.1; " + env_marker_below_37,
|
||||
"starlette==0.28.*; " + env_marker_gte_37,
|
||||
"uvicorn==0.16.0; " + env_marker_below_37,
|
||||
"uvicorn==0.22.*; " + env_marker_gte_37,
|
||||
"wsproto==1.0.0; " + env_marker_below_37,
|
||||
"wsproto==1.2.*; " + env_marker_gte_37,
|
||||
]
|
||||
|
||||
# issue 4614: urllib3 v2.0 only supports OpenSSL 1.1.1+
|
||||
try:
|
||||
import ssl
|
||||
|
||||
if ssl.OPENSSL_VERSION.startswith("OpenSSL ") and ssl.OPENSSL_VERSION_INFO < (
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
):
|
||||
minimal_requirements.append("urllib3<2")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
setup(
|
||||
name=__title__,
|
||||
version=__version__,
|
||||
@@ -65,11 +81,11 @@ setup(
|
||||
package_data={
|
||||
"platformio": [
|
||||
"assets/system/99-platformio-udev.rules",
|
||||
"assets/templates/ide-projects/*/*.tpl",
|
||||
"assets/templates/ide-projects/*/.*.tpl", # include hidden files
|
||||
"assets/templates/ide-projects/*/.*/*.tpl", # include hidden folders
|
||||
"assets/templates/ide-projects/*/*/*.tpl", # NetBeans
|
||||
"assets/templates/ide-projects/*/*/*/*.tpl", # NetBeans
|
||||
"project/integration/tpls/*/*.tpl",
|
||||
"project/integration/tpls/*/.*.tpl", # include hidden files
|
||||
"project/integration/tpls/*/.*/*.tpl", # include hidden folders
|
||||
"project/integration/tpls/*/*/*.tpl", # NetBeans
|
||||
"project/integration/tpls/*/*/*/*.tpl", # NetBeans
|
||||
]
|
||||
},
|
||||
entry_points={
|
||||
|
||||
@@ -469,7 +469,7 @@ def test_custom_project_tools(
|
||||
project_dir = tmp_path / "project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL)
|
||||
spec = "platformio/tool-openocd"
|
||||
spec = "platformio/tool-openocd @ ^2"
|
||||
result = clirunner.invoke(
|
||||
package_install_cmd,
|
||||
["-d", str(project_dir), "-e", "devkit", "-t", spec],
|
||||
@@ -503,7 +503,7 @@ def test_custom_project_tools(
|
||||
|
||||
# check saved deps
|
||||
assert config.get("env:devkit", "platform_packages") == [
|
||||
"platformio/tool-openocd@^2.1100.211028",
|
||||
"platformio/tool-openocd@^2",
|
||||
]
|
||||
|
||||
# install tool without saving to config
|
||||
@@ -518,7 +518,7 @@ def test_custom_project_tools(
|
||||
PackageSpec("tool-openocd@2.1100.211028"),
|
||||
]
|
||||
assert config.get("env:devkit", "platform_packages") == [
|
||||
"platformio/tool-openocd@^2.1100.211028",
|
||||
"platformio/tool-openocd@^2",
|
||||
]
|
||||
|
||||
# unknown tool
|
||||
|
||||
@@ -313,7 +313,7 @@ def test_custom_project_tools(
|
||||
project_dir = tmp_path / "project"
|
||||
project_dir.mkdir()
|
||||
(project_dir / "platformio.ini").write_text(PROJECT_CONFIG_TPL)
|
||||
spec = "platformio/tool-openocd"
|
||||
spec = "platformio/tool-openocd@^2"
|
||||
result = clirunner.invoke(
|
||||
package_install_cmd,
|
||||
["-d", str(project_dir), "-e", "devkit", "-t", spec],
|
||||
@@ -329,7 +329,7 @@ def test_custom_project_tools(
|
||||
assert not os.path.exists(config.get("platformio", "platforms_dir"))
|
||||
# check saved deps
|
||||
assert config.get("env:devkit", "platform_packages") == [
|
||||
"platformio/tool-openocd@^2.1100.211028",
|
||||
"platformio/tool-openocd@^2",
|
||||
]
|
||||
# uninstall
|
||||
result = clirunner.invoke(
|
||||
|
||||
@@ -62,7 +62,7 @@ def test_init_duplicated_boards(clirunner, validate_cliresult, tmpdir):
|
||||
|
||||
def test_init_ide_without_board(clirunner, tmpdir):
|
||||
with tmpdir.as_cwd():
|
||||
result = clirunner.invoke(project_init_cmd, ["--ide", "atom"])
|
||||
result = clirunner.invoke(project_init_cmd, ["--ide", "vscode"])
|
||||
assert result.exit_code != 0
|
||||
assert isinstance(result.exception, ProjectEnvsNotAvailableError)
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ def test_generic_build(clirunner, validate_cliresult, tmpdir):
|
||||
("-DTEST_SINGLE_MACRO", "-DTEST_SINGLE_MACRO"),
|
||||
('-DTEST_STR_SPACE="Andrew Smith"', '"-DTEST_STR_SPACE=Andrew Smith"'),
|
||||
("-Iextra_inc", "-Iextra_inc"),
|
||||
(
|
||||
"-include $PROJECT_DIR/lib/component/component-forced-include.h",
|
||||
"component-forced-include.h",
|
||||
),
|
||||
]
|
||||
|
||||
tmpdir.join("platformio.ini").write(
|
||||
@@ -95,6 +99,10 @@ projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO")
|
||||
#error "POST_SCRIPT_MACRO"
|
||||
#endif
|
||||
|
||||
#ifndef I_AM_FORCED_COMPONENT_INCLUDE
|
||||
#error "I_AM_FORCED_COMPONENT_INCLUDE"
|
||||
#endif
|
||||
|
||||
#ifdef COMMENTED_MACRO
|
||||
#error "COMMENTED_MACRO"
|
||||
#endif
|
||||
@@ -124,6 +132,11 @@ void dummy(void);
|
||||
void dummy(void ) {};
|
||||
"""
|
||||
)
|
||||
component_dir.join("component-forced-include.h").write(
|
||||
"""
|
||||
#define I_AM_FORCED_COMPONENT_INCLUDE
|
||||
"""
|
||||
)
|
||||
|
||||
result = clirunner.invoke(cmd_run, ["--project-dir", str(tmpdir), "--verbose"])
|
||||
validate_cliresult(result)
|
||||
|
||||
@@ -36,13 +36,13 @@ def test_ping_internet_ips():
|
||||
def test_api_internet_offline(without_internet, isolated_pio_core):
|
||||
regclient = RegistryClient()
|
||||
with pytest.raises(http.InternetConnectionError):
|
||||
regclient.fetch_json_data("get", "/v2/stats")
|
||||
regclient.fetch_json_data("get", "/v3/search")
|
||||
|
||||
|
||||
def test_api_cache(monkeypatch, isolated_pio_core):
|
||||
regclient = RegistryClient()
|
||||
api_kwargs = {"method": "get", "path": "/v2/stats", "x_cache_valid": "10s"}
|
||||
api_kwargs = {"method": "get", "path": "/v3/search", "x_cache_valid": "10s"}
|
||||
result = regclient.fetch_json_data(**api_kwargs)
|
||||
assert result and "boards" in result
|
||||
assert result and "total" in result
|
||||
monkeypatch.setattr(http, "_internet_on", lambda: False)
|
||||
assert regclient.fetch_json_data(**api_kwargs) == result
|
||||
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user