diff --git a/HISTORY.rst b/HISTORY.rst index f26bad63..6c83cbb5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,6 +7,7 @@ Release Notes .. |INTERPOLATION| replace:: `Interpolation of Values `__ .. |UNITTESTING| replace:: `Unit Testing `__ .. |DEBUGGING| replace:: `Debugging `__ +.. |STATICCODEANALYSIS| replace:: `Static Code Analysis `__ .. _release_notes_6: @@ -17,6 +18,19 @@ Unlock the true potential of embedded software development with PlatformIO's collaborative ecosystem, embracing declarative principles, test-driven methodologies, and modern toolchains for unrivaled success. +6.1.14 (2024-03-21) +~~~~~~~~~~~~~~~~~~~ + +* Introduced the ``--json-output`` option to the `pio test `__ command, enabling users to generate test results in the JSON format +* Upgraded the build engine to the latest version of SCons (4.7.0) to improve build performance, reliability, and compatibility with other tools and systems (`release notes `__) +* Broadened version support for the ``pyelftools`` dependency, enabling compatibility with lower versions and facilitating integration with a wider range of third-party tools (`issue #4834 `_) +* Addressed an issue where passing a relative path (``--project-dir``) to the `pio project init `__ command resulted in an error (`issue #4847 `_) +* Enhanced |STATICCODEANALYSIS| to accommodate scenarios where custom ``src_dir`` or ``include_dir`` are located outside the project folder (`pull #4874 `_) +* Corrected the validation of ``symlink://`` `package specifications `__ , resolving an issue that caused the package manager to repeatedly reinstall dependencies (`pull #4870 `_) +* Resolved an issue related to the relative package path in the `pio pkg publish `__ command +* Resolved an issue where the |LDF| selected an incorrect library version (`issue #4860 `_) +* Resolved an issue with the ``hexlify`` filter in the `device monitor `__ command, ensuring proper representation of characters with Unicode code points higher than 127 (`issue #4732 `_) + 6.1.13 (2024-01-12) ~~~~~~~~~~~~~~~~~~~ diff --git a/docs b/docs index 3f021525..670721e9 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 3f02152561334a92bd8cae5c49e35cc194f86721 +Subproject commit 670721e9231cdedd1e28f9826759f7db70cab0e8 diff --git a/platformio/__init__.py b/platformio/__init__.py index 7024104f..9bb65b5a 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = (6, 1, 13) +VERSION = (6, 1, 14) __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" @@ -38,15 +38,6 @@ __registry_mirror_hosts__ = [ ] __pioremote_endpoint__ = "ssl:host=remote.platformio.org:port=4413" -__core_packages__ = { - "contrib-piohome": "~3.4.2", - "contrib-pioremote": "~1.0.0", - "tool-scons": "~4.40600.0", - "tool-cppcheck": "~1.21100.0", - "tool-clangtidy": "~1.150005.0", - "tool-pvs-studio": "~7.18.0", -} - __check_internet_hosts__ = [ "185.199.110.153", # Github.com "88.198.170.159", # platformio.org diff --git a/platformio/account/team/commands/list.py b/platformio/account/team/commands/list.py index d0395d7c..5bdc8c14 100644 --- a/platformio/account/team/commands/list.py +++ b/platformio/account/team/commands/list.py @@ -48,11 +48,13 @@ def team_list_cmd(orgname, json_output): table_data.append( ( "Members:", - ", ".join( - (member.get("username") for member in team.get("members")) - ) - if team.get("members") - else "-", + ( + ", ".join( + (member.get("username") for member in team.get("members")) + ) + if team.get("members") + else "-" + ), ) ) click.echo(tabulate(table_data, tablefmt="plain")) diff --git a/platformio/assets/system/99-platformio-udev.rules b/platformio/assets/system/99-platformio-udev.rules index 992676db..d17569e7 100644 --- a/platformio/assets/system/99-platformio-udev.rules +++ b/platformio/assets/system/99-platformio-udev.rules @@ -36,6 +36,8 @@ ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", MODE:="0666", ENV{ID_MM_DEVIC # QinHeng Electronics HL-340 USB-Serial adapter ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" +# QinHeng Electronics CH343 USB-Serial adapter +ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d3", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" # QinHeng Electronics CH9102 USB-Serial adapter ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" @@ -85,6 +87,8 @@ ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="[01]*", MODE:="0666", ENV{ID_MM_DEVI # AIR32F103 ATTRS{idVendor}=="0d28", ATTRS{idProduct}=="0204", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" +# STM32 virtual COM port +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" # # Debuggers @@ -173,4 +177,4 @@ ATTRS{product}=="*CMSIS-DAP*", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="2107", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" # Espressif USB JTAG/serial debug unit -ATTRS{idVendor}=="303a", ATTR{idProduct}=="1001", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" \ No newline at end of file +ATTRS{idVendor}=="303a", ATTR{idProduct}=="1001", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" diff --git a/platformio/builder/tools/piobuild.py b/platformio/builder/tools/piobuild.py index 047090c7..b3d650a5 100644 --- a/platformio/builder/tools/piobuild.py +++ b/platformio/builder/tools/piobuild.py @@ -54,7 +54,7 @@ def GetBuildType(env): modes.append("debug") if "__test" in COMMAND_LINE_TARGETS or env.GetProjectOption("build_type") == "test": modes.append("test") - return "+".join(modes or ["release"]) + return ", ".join(modes or ["release"]) def BuildProgram(env): diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index aa18cd1a..d970344e 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -309,10 +309,10 @@ class LibBuilderBase: if not self.dependencies or self._deps_are_processed: return self._deps_are_processed = True - for item in self.dependencies: + for dependency in self.dependencies: found = False for lb in self.env.GetLibBuilders(): - if item["name"] != lb.name: + if not lb.is_dependency_compatible(dependency): continue found = True if lb not in self.depbuilders: @@ -322,9 +322,20 @@ class LibBuilderBase: if not found and self.verbose: sys.stderr.write( "Warning: Ignored `%s` dependency for `%s` " - "library\n" % (item["name"], self.name) + "library\n" % (dependency["name"], self.name) ) + def is_dependency_compatible(self, dependency): + pkg = PackageItem(self.path) + qualifiers = {"name": self.name, "version": self.version} + if pkg.metadata: + qualifiers = {"name": pkg.metadata.name, "version": pkg.metadata.version} + if pkg.metadata.spec and pkg.metadata.spec.owner: + qualifiers["owner"] = pkg.metadata.spec.owner + return PackageCompatibility.from_dependency( + {k: v for k, v in dependency.items() if k in ("owner", "name", "version")} + ).is_compatible(PackageCompatibility(**qualifiers)) + def get_search_files(self): return [ os.path.join(self.src_dir, item) diff --git a/platformio/builder/tools/piomaxlen.py b/platformio/builder/tools/piomaxlen.py index d20879eb..8d45317c 100644 --- a/platformio/builder/tools/piomaxlen.py +++ b/platformio/builder/tools/piomaxlen.py @@ -23,10 +23,10 @@ from SCons.Subst import quote_spaces # pylint: disable=import-error from platformio.compat import IS_WINDOWS, hashlib_encode_data # There are the next limits depending on a platform: -# - Windows = 8192 +# - Windows = 8191 # - Unix = 131072 # We need ~512 characters for compiler and temporary file paths -MAX_LINE_LENGTH = (8192 if IS_WINDOWS else 131072) - 512 +MAX_LINE_LENGTH = (8191 if IS_WINDOWS else 131072) - 512 WINPATHSEP_RE = re.compile(r"\\([^\"'\\]|$)") diff --git a/platformio/builder/tools/pioplatform.py b/platformio/builder/tools/pioplatform.py index 4ce4b91d..f99a6ec5 100644 --- a/platformio/builder/tools/pioplatform.py +++ b/platformio/builder/tools/pioplatform.py @@ -75,9 +75,11 @@ def LoadPioPlatform(env): continue env.PrependENVPath( "PATH", - os.path.join(pkg.path, "bin") - if os.path.isdir(os.path.join(pkg.path, "bin")) - else pkg.path, + ( + os.path.join(pkg.path, "bin") + if os.path.isdir(os.path.join(pkg.path, "bin")) + else pkg.path + ), ) if ( not IS_WINDOWS diff --git a/platformio/check/cli.py b/platformio/check/cli.py index 1deced87..9adf7fb9 100644 --- a/platformio/check/cli.py +++ b/platformio/check/cli.py @@ -103,10 +103,21 @@ def cli( "%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v) ) - default_src_filters = [ - "+<%s>" % os.path.basename(config.get("platformio", "src_dir")), - "+<%s>" % os.path.basename(config.get("platformio", "include_dir")), - ] + default_src_filters = [] + for d in ( + config.get("platformio", "src_dir"), + config.get("platformio", "include_dir"), + ): + try: + default_src_filters.append("+<%s>" % os.path.relpath(d)) + except ValueError as exc: + # On Windows if sources are located on a different logical drive + if not json_output and not silent: + click.echo( + "Error: Project cannot be analyzed! The project folder `%s`" + " is located on a different logical drive\n" % d + ) + raise exception.ReturnErrorCode(1) from exc env_src_filters = ( src_filters @@ -122,9 +133,11 @@ def cli( silent=silent, src_filters=env_src_filters, flags=flags or env_options.get("check_flags"), - severity=[DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH]] - if silent - else severity or config.get("env:" + envname, "check_severity"), + severity=( + [DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH]] + if silent + else severity or config.get("env:" + envname, "check_severity") + ), skip_packages=skip_packages or env_options.get("check_skip_packages"), platform_packages=env_options.get("platform_packages"), ) @@ -142,9 +155,11 @@ def cli( result = {"env": envname, "tool": tool, "duration": time()} rc = ct.check( - on_defect_callback=None - if (json_output or verbose) - else lambda defect: click.echo(repr(defect)) + on_defect_callback=( + None + if (json_output or verbose) + else lambda defect: click.echo(repr(defect)) + ) ) result["defects"] = ct.get_defects() diff --git a/platformio/commands/upgrade.py b/platformio/commands/upgrade.py index 129e3fa1..d8ba386e 100644 --- a/platformio/commands/upgrade.py +++ b/platformio/commands/upgrade.py @@ -19,9 +19,9 @@ import subprocess import click from platformio import VERSION, __version__, app, exception +from platformio.dependencies import get_pip_dependencies from platformio.http import fetch_remote_content from platformio.package.manager.core import update_core_packages -from platformio.pipdeps import get_pip_dependencies from platformio.proc import get_pythonexe_path PYPI_JSON_URL = "https://pypi.org/pypi/platformio/json" diff --git a/platformio/compat.py b/platformio/compat.py index 008dc7ae..d5455099 100644 --- a/platformio/compat.py +++ b/platformio/compat.py @@ -17,6 +17,7 @@ import importlib.util import inspect import locale +import os import shlex import sys @@ -41,10 +42,14 @@ else: 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 + try: + from starlette.concurrency import run_in_threadpool as aio_to_thread + except ImportError: + pass PY2 = sys.version_info[0] == 2 # DO NOT REMOVE IT. ESP8266/ESP32 depend on it +PY36 = sys.version_info[0:2] == (3, 6) IS_CYGWIN = sys.platform.startswith("cygwin") IS_WINDOWS = WINDOWS = sys.platform.startswith("win") IS_MACOS = sys.platform.startswith("darwin") @@ -132,3 +137,12 @@ def path_to_unicode(path): and custom device monitor filters """ return path + + +def is_proxy_set(socks=False): + for var in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"): + value = os.getenv(var, os.getenv(var.lower())) + if not value or (socks and not value.startswith("socks5://")): + continue + return True + return False diff --git a/platformio/debug/config/base.py b/platformio/debug/config/base.py index 212d00fa..1caae23f 100644 --- a/platformio/debug/config/base.py +++ b/platformio/debug/config/base.py @@ -148,7 +148,9 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes ) def _load_build_data(self): - data = load_build_metadata(os.getcwd(), self.env_name, cache=True, debug=True) + data = load_build_metadata( + os.getcwd(), self.env_name, cache=True, build_type="debug" + ) if not data: raise DebugInvalidOptionsError("Could not load a build configuration") return data @@ -194,9 +196,11 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes cwd=server_package_dir if server_package else None, executable=result.get("executable"), arguments=[ - a.replace("$PACKAGE_DIR", server_package_dir) - if server_package_dir - else a + ( + a.replace("$PACKAGE_DIR", server_package_dir) + if server_package_dir + else a + ) for a in result.get("arguments", []) ], ) diff --git a/platformio/pipdeps.py b/platformio/dependencies.py similarity index 78% rename from platformio/pipdeps.py rename to platformio/dependencies.py index 677d8c9d..5fd40a1d 100644 --- a/platformio/pipdeps.py +++ b/platformio/dependencies.py @@ -12,11 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import platform -import sys -PY36 = sys.version_info[0:2] == (3, 6) +from platformio.compat import PY36, is_proxy_set + + +def get_core_dependencies(): + return { + "contrib-piohome": "~3.4.2", + "contrib-pioremote": "~1.0.0", + "tool-scons": "~4.40700.0", + "tool-cppcheck": "~1.21100.0", + "tool-clangtidy": "~1.150005.0", + "tool-pvs-studio": "~7.18.0", + } def get_pip_dependencies(): @@ -25,7 +34,7 @@ def get_pip_dependencies(): "click >=8.0.4, <9", "colorama", "marshmallow == 3.*", - "pyelftools == 0.30", + "pyelftools >=0.27, <1", "pyserial == 3.5.*", # keep in sync "device/monitor/terminal.py" "requests%s == 2.*" % ("[socks]" if is_proxy_set(socks=True) else ""), "semantic_version == 2.10.*", @@ -35,8 +44,8 @@ def get_pip_dependencies(): home = [ # PIO Home requirements "ajsonrpc == 1.2.*", - "starlette >=0.19, <0.36", - "uvicorn %s" % ("== 0.16.0" if PY36 else ">=0.16, <0.26"), + "starlette >=0.19, <0.38", + "uvicorn %s" % ("== 0.16.0" if PY36 else ">=0.16, <0.30"), "wsproto == 1.*", ] @@ -60,12 +69,3 @@ def get_pip_dependencies(): pass return core + home + extra - - -def is_proxy_set(socks=False): - for var in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"): - value = os.getenv(var, os.getenv(var.lower())) - if not value or (socks and not value.startswith("socks5://")): - continue - return True - return False diff --git a/platformio/device/list/util.py b/platformio/device/list/util.py index 3589ee26..3847969c 100644 --- a/platformio/device/list/util.py +++ b/platformio/device/list/util.py @@ -144,9 +144,9 @@ def list_mdns_services(): if service.properties: try: properties = { - k.decode("utf8"): v.decode("utf8") - if isinstance(v, bytes) - else v + k.decode("utf8"): ( + v.decode("utf8") if isinstance(v, bytes) else v + ) for k, v in service.properties.items() } json.dumps(properties) diff --git a/platformio/device/monitor/command.py b/platformio/device/monitor/command.py index 3d3190fe..b3dc0a0e 100644 --- a/platformio/device/monitor/command.py +++ b/platformio/device/monitor/command.py @@ -58,7 +58,7 @@ from platformio.project.options import ProjectOptions "--encoding", help=( "Set the encoding for the serial port " - "(e.g. hexlify, Latin1, UTF-8) [default=%s]" + "(e.g. hexlify, Latin-1, UTF-8) [default=%s]" % ProjectOptions["env.monitor_encoding"].default ), ) @@ -125,9 +125,11 @@ def device_monitor_cmd(**options): options = apply_project_monitor_options(options, project_options) register_filters(platform=platform, options=options) options["port"] = SerialPortFinder( - board_config=platform.board_config(project_options.get("board")) - if platform and project_options.get("board") - else None, + board_config=( + platform.board_config(project_options.get("board")) + if platform and project_options.get("board") + else None + ), upload_protocol=project_options.get("upload_protocol"), ensure_ready=True, ).find(initial_port=options["port"]) diff --git a/platformio/device/monitor/filters/base.py b/platformio/device/monitor/filters/base.py index e773ed65..5a65f800 100644 --- a/platformio/device/monitor/filters/base.py +++ b/platformio/device/monitor/filters/base.py @@ -25,11 +25,12 @@ from platformio.project.config import ProjectConfig class DeviceMonitorFilterBase(miniterm.Transform): def __init__(self, options=None): """Called by PlatformIO to pass context""" - miniterm.Transform.__init__(self) + super().__init__() self.options = options or {} self.project_dir = self.options.get("project_dir") self.environment = self.options.get("environment") + self._running_terminal = None self.config = ProjectConfig.get_instance() if not self.environment: @@ -47,6 +48,12 @@ class DeviceMonitorFilterBase(miniterm.Transform): def NAME(self): raise NotImplementedError("Please declare NAME attribute for the filter class") + def set_running_terminal(self, terminal): + self._running_terminal = terminal + + def get_running_terminal(self): + return self._running_terminal + def register_filters(platform=None, options=None): # project filters diff --git a/platformio/device/monitor/filters/hexlify.py b/platformio/device/monitor/filters/hexlify.py index 28e83bfb..40fc203e 100644 --- a/platformio/device/monitor/filters/hexlify.py +++ b/platformio/device/monitor/filters/hexlify.py @@ -24,12 +24,18 @@ class Hexlify(DeviceMonitorFilterBase): super().__init__(*args, **kwargs) self._counter = 0 + def set_running_terminal(self, terminal): + # force to Latin-1, issue #4732 + if terminal.input_encoding == "UTF-8": + terminal.set_rx_encoding("Latin-1") + super().set_running_terminal(terminal) + def rx(self, text): result = "" - for b in serial.iterbytes(text): + for c in serial.iterbytes(text): if (self._counter % 16) == 0: result += "\n{:04X} | ".format(self._counter) - asciicode = ord(b) + asciicode = ord(c) if asciicode <= 255: result += "{:02X} ".format(asciicode) else: diff --git a/platformio/device/monitor/terminal.py b/platformio/device/monitor/terminal.py index 7b2a9b91..a3abf857 100644 --- a/platformio/device/monitor/terminal.py +++ b/platformio/device/monitor/terminal.py @@ -110,6 +110,12 @@ def new_terminal(options): term.raw = options["raw"] term.set_rx_encoding(options["encoding"]) term.set_tx_encoding(options["encoding"]) + for ts in (term.tx_transformations, term.rx_transformations): + for t in ts: + try: + t.set_running_terminal(term) + except AttributeError: + pass return term diff --git a/platformio/home/rpc/handlers/project.py b/platformio/home/rpc/handlers/project.py index 2c03a7c9..817bb210 100644 --- a/platformio/home/rpc/handlers/project.py +++ b/platformio/home/rpc/handlers/project.py @@ -372,15 +372,19 @@ class ProjectRPC(BaseRPCHandler): return dict( platform=dict( - ownername=platform_pkg.metadata.spec.owner - if platform_pkg.metadata.spec - else None, + ownername=( + platform_pkg.metadata.spec.owner + if platform_pkg.metadata.spec + else None + ), name=platform.name, title=platform.title, version=str(platform_pkg.metadata.version), ), - board=platform.board_config(board_id).get_brief_data() - if board_id - else None, + board=( + platform.board_config(board_id).get_brief_data() + if board_id + else None + ), frameworks=frameworks or None, ) diff --git a/platformio/http.py b/platformio/http.py index 563f982f..d82e4703 100644 --- a/platformio/http.py +++ b/platformio/http.py @@ -21,8 +21,8 @@ from urllib3.util.retry import Retry from platformio import __check_internet_hosts__, app, util from platformio.cache import ContentCache, cleanup_content_cache +from platformio.compat import is_proxy_set from platformio.exception import PlatformioException, UserSideException -from platformio.pipdeps import is_proxy_set __default_requests_timeout__ = (10, None) # (connect, read) @@ -63,9 +63,11 @@ class HTTPSession(requests.Session): kwargs["timeout"] = __default_requests_timeout__ return super().request( method, - url - if url.startswith("http") or not self._x_base_url - else urljoin(self._x_base_url, url), + ( + url + if url.startswith("http") or not self._x_base_url + else urljoin(self._x_base_url, url) + ), *args, **kwargs ) diff --git a/platformio/package/commands/install.py b/platformio/package/commands/install.py index d2976644..8c987c17 100644 --- a/platformio/package/commands/install.py +++ b/platformio/package/commands/install.py @@ -222,9 +222,11 @@ def _install_project_env_libraries(project_env, options): env_lm = LibraryPackageManager( os.path.join(config.get("platformio", "libdeps_dir"), project_env), - compatibility=PackageCompatibility(**compatibility_qualifiers) - if compatibility_qualifiers - else None, + compatibility=( + PackageCompatibility(**compatibility_qualifiers) + if compatibility_qualifiers + else None + ), ) private_lm = LibraryPackageManager( os.path.join(config.get("platformio", "lib_dir")) diff --git a/platformio/package/commands/publish.py b/platformio/package/commands/publish.py index c8b4ea77..ff0f6507 100644 --- a/platformio/package/commands/publish.py +++ b/platformio/package/commands/publish.py @@ -86,6 +86,7 @@ def package_publish_cmd( # pylint: disable=too-many-arguments, too-many-locals package, owner, typex, released_at, private, notify, no_interactive, non_interactive ): click.secho("Preparing a package...", fg="cyan") + package = os.path.abspath(package) no_interactive = no_interactive or non_interactive owner = owner or AccountClient().get_logged_username() do_not_pack = ( diff --git a/platformio/package/commands/search.py b/platformio/package/commands/search.py index ac71ef4c..c3f7d5cd 100644 --- a/platformio/package/commands/search.py +++ b/platformio/package/commands/search.py @@ -65,10 +65,12 @@ def print_search_item(item): click.echo( "%s • %s • Published on %s" % ( - item["type"].capitalize() - if item["tier"] == "community" - else click.style( - ("%s %s" % (item["tier"], item["type"])).title(), bold=True + ( + item["type"].capitalize() + if item["tier"] == "community" + else click.style( + ("%s %s" % (item["tier"], item["type"])).title(), bold=True + ) ), item["version"]["name"], util.parse_datetime(item["version"]["released_at"]).strftime("%c"), diff --git a/platformio/package/manager/_install.py b/platformio/package/manager/_install.py index fc4d5ddb..12c116d1 100644 --- a/platformio/package/manager/_install.py +++ b/platformio/package/manager/_install.py @@ -98,9 +98,13 @@ class PackageManagerInstallMixin: else: pkg = self.install_from_registry( spec, - search_qualifiers=compatibility.to_search_qualifiers() - if compatibility - else None, + search_qualifiers=( + compatibility.to_search_qualifiers( + ["platforms", "frameworks", "authors"] + ) + if compatibility + else None + ), ) if not pkg or not pkg.metadata: diff --git a/platformio/package/manager/base.py b/platformio/package/manager/base.py index f197ea12..a4ad07ba 100644 --- a/platformio/package/manager/base.py +++ b/platformio/package/manager/base.py @@ -280,11 +280,15 @@ class BasePackageManager( # pylint: disable=too-many-public-methods,too-many-in # external "URL" mismatch if spec.external: - # local folder mismatch - if os.path.abspath(spec.uri) == os.path.abspath(pkg.path) or ( + # local/symlinked folder mismatch + check_conds = [ + os.path.abspath(spec.uri) == os.path.abspath(pkg.path), spec.uri.startswith("file://") - and os.path.abspath(pkg.path) == os.path.abspath(spec.uri[7:]) - ): + and os.path.abspath(pkg.path) == os.path.abspath(spec.uri[7:]), + spec.uri.startswith("symlink://") + and os.path.abspath(pkg.path) == os.path.abspath(spec.uri[10:]), + ] + if any(check_conds): return True if spec.uri != pkg.metadata.spec.uri: return False diff --git a/platformio/package/manager/core.py b/platformio/package/manager/core.py index ca5c5e01..f4e0f50e 100644 --- a/platformio/package/manager/core.py +++ b/platformio/package/manager/core.py @@ -14,7 +14,8 @@ import os -from platformio import __core_packages__, exception +from platformio import exception +from platformio.dependencies import get_core_dependencies from platformio.package.exception import UnknownPackageError from platformio.package.manager.tool import ToolPackageManager from platformio.package.meta import PackageSpec @@ -23,7 +24,7 @@ from platformio.package.meta import PackageSpec def get_installed_core_packages(): result = [] pm = ToolPackageManager() - for name, requirements in __core_packages__.items(): + for name, requirements in get_core_dependencies().items(): spec = PackageSpec(owner="platformio", name=name, requirements=requirements) pkg = pm.get_package(spec) if pkg: @@ -32,11 +33,11 @@ def get_installed_core_packages(): def get_core_package_dir(name, spec=None, auto_install=True): - if name not in __core_packages__: + if name not in get_core_dependencies(): raise exception.PlatformioException("Please upgrade PlatformIO Core") pm = ToolPackageManager() spec = spec or PackageSpec( - owner="platformio", name=name, requirements=__core_packages__[name] + owner="platformio", name=name, requirements=get_core_dependencies()[name] ) pkg = pm.get_package(spec) if pkg: @@ -50,7 +51,7 @@ def get_core_package_dir(name, spec=None, auto_install=True): def update_core_packages(): pm = ToolPackageManager() - for name, requirements in __core_packages__.items(): + for name, requirements in get_core_dependencies().items(): spec = PackageSpec(owner="platformio", name=name, requirements=requirements) try: pm.update(spec, spec) @@ -65,7 +66,7 @@ def remove_unnecessary_core_packages(dry_run=False): pm = ToolPackageManager() best_pkg_versions = {} - for name, requirements in __core_packages__.items(): + for name, requirements in get_core_dependencies().items(): spec = PackageSpec(owner="platformio", name=name, requirements=requirements) pkg = pm.get_package(spec) if not pkg: diff --git a/platformio/package/manifest/parser.py b/platformio/package/manifest/parser.py index d3fe6bfa..7694c8a8 100644 --- a/platformio/package/manifest/parser.py +++ b/platformio/package/manifest/parser.py @@ -294,9 +294,11 @@ class BaseManifestParser: if not matched_files: continue result[root] = dict( - name="Examples" - if root == examples_dir - else os.path.relpath(root, examples_dir), + name=( + "Examples" + if root == examples_dir + else os.path.relpath(root, examples_dir) + ), base=os.path.relpath(root, package_dir), files=matched_files, ) diff --git a/platformio/package/manifest/schema.py b/platformio/package/manifest/schema.py index eeffd83e..c1d22ab5 100644 --- a/platformio/package/manifest/schema.py +++ b/platformio/package/manifest/schema.py @@ -276,7 +276,7 @@ class ManifestSchema(BaseSchema): @staticmethod @memoized(expire="1h") def load_spdx_licenses(): - version = "3.22" + version = "3.23" spdx_data_url = ( "https://raw.githubusercontent.com/spdx/license-list-data/" f"v{version}/json/licenses.json" diff --git a/platformio/package/meta.py b/platformio/package/meta.py index b1c491d7..7597148e 100644 --- a/platformio/package/meta.py +++ b/platformio/package/meta.py @@ -65,7 +65,14 @@ class PackageType: class PackageCompatibility: - KNOWN_QUALIFIERS = ("platforms", "frameworks", "authors") + KNOWN_QUALIFIERS = ( + "owner", + "name", + "version", + "platforms", + "frameworks", + "authors", + ) @classmethod def from_dependency(cls, dependency): @@ -89,19 +96,45 @@ class PackageCompatibility: def __repr__(self): return "PackageCompatibility <%s>" % self.qualifiers - def to_search_qualifiers(self): - return self.qualifiers + def to_search_qualifiers(self, fields=None): + result = {} + for name, value in self.qualifiers.items(): + if not fields or name in fields: + result[name] = value + return result def is_compatible(self, other): assert isinstance(other, PackageCompatibility) - for key, value in self.qualifiers.items(): + for key, current_value in self.qualifiers.items(): other_value = other.qualifiers.get(key) - if not value or not other_value: + if not current_value or not other_value: continue - if not items_in_list(value, other_value): + if any(isinstance(v, list) for v in (current_value, other_value)): + if not items_in_list(current_value, other_value): + return False + continue + if key == "version": + if not self._compare_versions(current_value, other_value): + return False + continue + if current_value != other_value: return False return True + def _compare_versions(self, current, other): + if current == other: + return True + try: + version = ( + other + if isinstance(other, semantic_version.Version) + else cast_version_to_semver(other) + ) + return version in semantic_version.SimpleSpec(current) + except ValueError: + pass + return False + class PackageOutdatedResult: UPDATE_INCREMENT_MAJOR = "major" @@ -485,9 +518,11 @@ class PackageItem: def __eq__(self, other): conds = [ - os.path.realpath(self.path) == os.path.realpath(other.path) - if self.path and other.path - else self.path == other.path, + ( + os.path.realpath(self.path) == os.path.realpath(other.path) + if self.path and other.path + else self.path == other.path + ), self.metadata == other.metadata, ] return all(conds) diff --git a/platformio/package/unpack.py b/platformio/package/unpack.py index 9d8919c3..f819fd2f 100644 --- a/platformio/package/unpack.py +++ b/platformio/package/unpack.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import sys from tarfile import open as tarfile_open from time import mktime from zipfile import ZipFile @@ -82,19 +83,23 @@ class TARArchiver(BaseArchiver): ).startswith(base) def extract_item(self, item, dest_dir): + if sys.version_info >= (3, 12): + self._afo.extract(item, dest_dir, filter="data") + return self.after_extract(item, dest_dir) + + # apply custom security logic dest_dir = self.resolve_path(dest_dir) bad_conds = [ self.is_bad_path(item.name, dest_dir), self.is_link(item) and self.is_bad_link(item, dest_dir), ] - if not any(bad_conds): - super().extract_item(item, dest_dir) - else: - click.secho( + if any(bad_conds): + return click.secho( "Blocked insecure item `%s` from TAR archive" % item.name, fg="red", err=True, ) + return super().extract_item(item, dest_dir) class ZIPArchiver(BaseArchiver): diff --git a/platformio/platform/_run.py b/platformio/platform/_run.py index 949a452d..2912371d 100644 --- a/platformio/platform/_run.py +++ b/platformio/platform/_run.py @@ -116,9 +116,9 @@ class PlatformRunMixin: args, stdout=proc.BuildAsyncPipe( line_callback=self._on_stdout_line, - data_callback=lambda data: None - if self.silent - else _write_and_flush(sys.stdout, data), + data_callback=lambda data: ( + None if self.silent else _write_and_flush(sys.stdout, data) + ), ), stderr=proc.BuildAsyncPipe( line_callback=self._on_stderr_line, diff --git a/platformio/project/commands/config.py b/platformio/project/commands/config.py index 5e4ed21f..214b0db1 100644 --- a/platformio/project/commands/config.py +++ b/platformio/project/commands/config.py @@ -82,9 +82,11 @@ def lint_configuration(json_output=False): ( click.style(error["type"], fg="red"), error["message"], - error.get("source", "") + (f":{error.get('lineno')}") - if "lineno" in error - else "", + ( + error.get("source", "") + (f":{error.get('lineno')}") + if "lineno" in error + else "" + ), ) for error in errors ], diff --git a/platformio/project/commands/init.py b/platformio/project/commands/init.py index 51ae46a4..bc3ac61a 100644 --- a/platformio/project/commands/init.py +++ b/platformio/project/commands/init.py @@ -79,6 +79,7 @@ def project_init_cmd( env_prefix, silent, ): + project_dir = os.path.abspath(project_dir) is_new_project = not is_platformio_project(project_dir) if is_new_project: if not silent: @@ -223,7 +224,7 @@ def init_lib_readme(lib_dir): This directory is intended for project specific (private) libraries. PlatformIO will compile them to static libraries and link into executable file. -The source code of each library should be placed in a an own separate directory +The source code of each library should be placed in an own separate directory ("lib/your_library_name/[here are source files]"). For example, see a structure of the following two libraries `Foo` and `Bar`: diff --git a/platformio/project/helpers.py b/platformio/project/helpers.py index cb004d23..4d153492 100644 --- a/platformio/project/helpers.py +++ b/platformio/project/helpers.py @@ -131,27 +131,27 @@ def compute_project_checksum(config): return checksum.hexdigest() -def load_build_metadata(project_dir, env_or_envs, cache=False, debug=False): +def load_build_metadata(project_dir, env_or_envs, cache=False, build_type=None): assert env_or_envs env_names = env_or_envs if not isinstance(env_names, list): env_names = [env_names] with fs.cd(project_dir): - result = _get_cached_build_metadata(project_dir, env_names) if cache else {} + result = _get_cached_build_metadata(env_names) if cache else {} # incompatible build-type data - for name in list(result.keys()): - build_type = result[name].get("build_type", "") - outdated_conds = [ - not build_type, - debug and "debug" not in build_type, - not debug and "debug" in build_type, - ] - if any(outdated_conds): - del result[name] + for env_name in list(result.keys()): + if build_type is None: + build_type = ProjectConfig.get_instance().get( + f"env:{env_name}", "build_type" + ) + if result[env_name].get("build_type", "") != build_type: + del result[env_name] missed_env_names = set(env_names) - set(result.keys()) if missed_env_names: - result.update(_load_build_metadata(project_dir, missed_env_names, debug)) + result.update( + _load_build_metadata(project_dir, missed_env_names, build_type) + ) if not isinstance(env_or_envs, list) and env_or_envs in result: return result[env_or_envs] @@ -162,14 +162,16 @@ def load_build_metadata(project_dir, env_or_envs, cache=False, debug=False): load_project_ide_data = load_build_metadata -def _load_build_metadata(project_dir, env_names, debug=False): +def _load_build_metadata(project_dir, env_names, build_type=None): # 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"] - if debug: + if build_type == "debug": args.extend(["--target", "__debug"]) + # if build_type == "test": + # args.extend(["--target", "__test"]) for name in env_names: args.extend(["-e", name]) app.set_session_var("pause_telemetry", True) @@ -181,16 +183,16 @@ def _load_build_metadata(project_dir, env_names, debug=False): raise result.exception if '"includes":' not in result.output: raise exception.UserSideException(result.output) - return _get_cached_build_metadata(project_dir, env_names) + return _get_cached_build_metadata(env_names) -def _get_cached_build_metadata(project_dir, env_names): - build_dir = ProjectConfig.get_instance( - os.path.join(project_dir, "platformio.ini") - ).get("platformio", "build_dir") +def _get_cached_build_metadata(env_names): + build_dir = ProjectConfig.get_instance().get("platformio", "build_dir") result = {} - for name in env_names: - if not os.path.isfile(os.path.join(build_dir, name, "idedata.json")): + for env_name in env_names: + if not os.path.isfile(os.path.join(build_dir, env_name, "idedata.json")): continue - result[name] = fs.load_json(os.path.join(build_dir, name, "idedata.json")) + result[env_name] = fs.load_json( + os.path.join(build_dir, env_name, "idedata.json") + ) return result diff --git a/platformio/project/integration/generator.py b/platformio/project/integration/generator.py index d55f9aa7..3cb677f1 100644 --- a/platformio/project/integration/generator.py +++ b/platformio/project/integration/generator.py @@ -91,9 +91,11 @@ class ProjectGenerator: "default_debug_env_name": get_default_debug_env(self.config), "env_name": self.env_name, "user_home_dir": os.path.abspath(fs.expanduser("~")), - "platformio_path": sys.argv[0] - if os.path.isfile(sys.argv[0]) - else where_is_program("platformio"), + "platformio_path": ( + sys.argv[0] + if os.path.isfile(sys.argv[0]) + else where_is_program("platformio") + ), "env_path": os.getenv("PATH"), "env_pathsep": os.pathsep, } diff --git a/platformio/project/options.py b/platformio/project/options.py index 51611d24..3d9c8969 100644 --- a/platformio/project/options.py +++ b/platformio/project/options.py @@ -549,7 +549,7 @@ ProjectOptions = OrderedDict( ConfigEnvOption( group="monitor", name="monitor_encoding", - description="Custom encoding (e.g. hexlify, Latin1, UTF-8)", + description="Custom encoding (e.g. hexlify, Latin-1, UTF-8)", default="UTF-8", ), # Library diff --git a/platformio/registry/access/commands/list.py b/platformio/registry/access/commands/list.py index ce9d8a6a..50073031 100644 --- a/platformio/registry/access/commands/list.py +++ b/platformio/registry/access/commands/list.py @@ -41,9 +41,11 @@ def access_list_cmd(owner, urn_type, json_output): # pylint: disable=unused-arg table_data.append( ( "Access:", - click.style("Private", fg="red") - if resource.get("private", False) - else "Public", + ( + click.style("Private", fg="red") + if resource.get("private", False) + else "Public" + ), ) ) table_data.append( diff --git a/platformio/registry/mirror.py b/platformio/registry/mirror.py index 4b4508f6..8805ffe5 100644 --- a/platformio/registry/mirror.py +++ b/platformio/registry/mirror.py @@ -54,9 +54,11 @@ class RegistryFileMirrorIterator: "head", self._url_parts.path, allow_redirects=False, - params=dict(bypass=",".join(self._visited_mirrors)) - if self._visited_mirrors - else None, + params=( + dict(bypass=",".join(self._visited_mirrors)) + if self._visited_mirrors + else None + ), x_with_authorization=RegistryClient.allowed_private_packages(), ) stop_conditions = [ diff --git a/platformio/remote/client/device_monitor.py b/platformio/remote/client/device_monitor.py index 1499dd58..4e1ccb2f 100644 --- a/platformio/remote/client/device_monitor.py +++ b/platformio/remote/client/device_monitor.py @@ -123,9 +123,11 @@ class DeviceMonitorClient( # pylint: disable=too-many-instance-attributes index=i + 1, host=device[0] + ":" if len(result) > 1 else "", port=device[1]["port"], - description=device[1]["description"] - if device[1]["description"] != "n/a" - else "", + description=( + device[1]["description"] + if device[1]["description"] != "n/a" + else "" + ), ) ) device_index = click.prompt( diff --git a/platformio/test/cli.py b/platformio/test/cli.py index 748580c8..9760545a 100644 --- a/platformio/test/cli.py +++ b/platformio/test/cli.py @@ -14,6 +14,7 @@ import os import shutil +import subprocess import click @@ -79,6 +80,7 @@ from platformio.test.runners.factory import TestRunnerFactory help="A program argument (multiple are allowed)", ) @click.option("--list-tests", is_flag=True) +@click.option("--json-output", is_flag=True) @click.option("--json-output-path", type=click.Path()) @click.option("--junit-output-path", type=click.Path()) @click.option( @@ -105,6 +107,7 @@ def cli( # pylint: disable=too-many-arguments,too-many-locals,redefined-builtin monitor_dtr, program_args, list_tests, + json_output, json_output_path, junit_output_path, verbose, @@ -156,6 +159,7 @@ def cli( # pylint: disable=too-many-arguments,too-many-locals,redefined-builtin stdout_report.generate(verbose=verbose or list_tests) for output_format, output_path in [ + ("json", subprocess.STDOUT if json_output else None), ("json", json_output_path), ("junit", junit_output_path), ]: diff --git a/platformio/test/reports/json.py b/platformio/test/reports/json.py index 791224c7..8d834b38 100644 --- a/platformio/test/reports/json.py +++ b/platformio/test/reports/json.py @@ -15,6 +15,7 @@ import datetime import json import os +import subprocess import click @@ -24,6 +25,9 @@ from platformio.test.result import TestStatus class JsonTestReport(TestReportBase): def generate(self, output_path, verbose=False): + if output_path == subprocess.STDOUT: + return click.echo("\n\n" + json.dumps(self.to_json())) + if os.path.isdir(output_path): output_path = os.path.join( output_path, @@ -40,6 +44,8 @@ class JsonTestReport(TestReportBase): if verbose: click.secho(f"Saved JSON report to the {output_path}", fg="green") + return True + def to_json(self): result = dict( version="1.0", @@ -62,11 +68,13 @@ class JsonTestReport(TestReportBase): test_dir=test_suite.test_dir, status=test_suite.status.name, duration=test_suite.duration, - timestamp=datetime.datetime.fromtimestamp(test_suite.timestamp).strftime( - "%Y-%m-%dT%H:%M:%S" - ) - if test_suite.timestamp - else None, + timestamp=( + datetime.datetime.fromtimestamp(test_suite.timestamp).strftime( + "%Y-%m-%dT%H:%M:%S" + ) + if test_suite.timestamp + else None + ), testcase_nums=len(test_suite.cases), error_nums=test_suite.get_status_nums(TestStatus.ERRORED), failure_nums=test_suite.get_status_nums(TestStatus.FAILED), diff --git a/platformio/test/runners/unity.py b/platformio/test/runners/unity.py index 935a1328..0d0d9e82 100644 --- a/platformio/test/runners/unity.py +++ b/platformio/test/runners/unity.py @@ -184,10 +184,6 @@ void unityOutputComplete(void) { unittest_uart_end(); } ), ) - def __init__(self, *args, **kwargs): - """Delete when Unity > 2.5.2 is released""" - super().__init__(*args, **kwargs) - def get_unity_framework_config(self): if not self.platform.is_embedded(): return self.UNITY_FRAMEWORK_CONFIG["native"] diff --git a/setup.py b/setup.py index 7a7c5076..e4eae238 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ from platformio import ( __url__, __version__, ) -from platformio.pipdeps import get_pip_dependencies +from platformio.dependencies import get_pip_dependencies setup( name=__title__, diff --git a/tests/commands/pkg/test_install.py b/tests/commands/pkg/test_install.py index 22963ef9..2c623314 100644 --- a/tests/commands/pkg/test_install.py +++ b/tests/commands/pkg/test_install.py @@ -18,7 +18,8 @@ import os import pytest -from platformio import __core_packages__, fs +from platformio import fs +from platformio.dependencies import get_core_dependencies from platformio.package.commands.install import package_install_cmd from platformio.package.manager.library import LibraryPackageManager from platformio.package.manager.platform import PlatformPackageManager @@ -177,7 +178,7 @@ def test_baremetal_project( ), ] assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ - PackageSpec("tool-scons@%s" % __core_packages__["tool-scons"][1:]), + PackageSpec("tool-scons@%s" % get_core_dependencies()["tool-scons"][1:]), PackageSpec("toolchain-atmelavr@1.70300.191015"), ] @@ -210,7 +211,7 @@ def test_project( ] assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ PackageSpec("framework-arduino-avr-attiny@1.5.2"), - PackageSpec("tool-scons@%s" % __core_packages__["tool-scons"][1:]), + PackageSpec("tool-scons@%s" % get_core_dependencies()["tool-scons"][1:]), PackageSpec("toolchain-atmelavr@1.70300.191015"), ] assert config.get("env:devkit", "lib_deps") == [ diff --git a/tests/commands/pkg/test_outdated.py b/tests/commands/pkg/test_outdated.py index 91180b42..58f3b0f7 100644 --- a/tests/commands/pkg/test_outdated.py +++ b/tests/commands/pkg/test_outdated.py @@ -24,7 +24,7 @@ PROJECT_OUTDATED_CONFIG_TPL = """ platform = platformio/atmelavr@^2 framework = arduino board = attiny88 -lib_deps = milesburton/DallasTemperature@~3.8.0 +lib_deps = milesburton/DallasTemperature@~3.9.0 """ PROJECT_UPDATED_CONFIG_TPL = """ @@ -32,7 +32,7 @@ PROJECT_UPDATED_CONFIG_TPL = """ platform = platformio/atmelavr@<4 framework = arduino board = attiny88 -lib_deps = milesburton/DallasTemperature@^3.8.0 +lib_deps = milesburton/DallasTemperature@^3.9.0 """ @@ -56,7 +56,7 @@ def test_project(clirunner, validate_cliresult, isolated_pio_core, tmp_path): re.MULTILINE, ) assert re.search( - r"^DallasTemperature\s+3\.8\.1\s+3\.\d+\.\d+\s+3\.\d+\.\d+\s+Library\s+devkit", + r"^DallasTemperature\s+3\.\d\.1\s+3\.\d+\.\d+\s+3\.\d+\.\d+\s+Library\s+devkit", result.output, re.MULTILINE, ) diff --git a/tests/commands/pkg/test_update.py b/tests/commands/pkg/test_update.py index 5656daac..4ee4366a 100644 --- a/tests/commands/pkg/test_update.py +++ b/tests/commands/pkg/test_update.py @@ -16,7 +16,8 @@ import os -from platformio import __core_packages__, fs +from platformio import fs +from platformio.dependencies import get_core_dependencies from platformio.package.commands.install import package_install_cmd from platformio.package.commands.update import package_update_cmd from platformio.package.exception import UnknownPackageError @@ -26,12 +27,14 @@ from platformio.package.manager.tool import ToolPackageManager from platformio.package.meta import PackageSpec from platformio.project.config import ProjectConfig +DALLASTEMPERATURE_LATEST_VERSION = "3.11.0" + PROJECT_OUTDATED_CONFIG_TPL = """ [env:devkit] platform = platformio/atmelavr@^2 framework = arduino board = attiny88 -lib_deps = milesburton/DallasTemperature@~3.8.0 +lib_deps = milesburton/DallasTemperature@^3.8.0 """ PROJECT_UPDATED_CONFIG_TPL = """ @@ -162,7 +165,7 @@ def test_project( os.path.join(config.get("platformio", "libdeps_dir"), "devkit") ) assert pkgs_to_specs(lm.get_installed()) == [ - PackageSpec("DallasTemperature@3.8.1"), + PackageSpec(f"DallasTemperature@{DALLASTEMPERATURE_LATEST_VERSION}"), PackageSpec( "OneWire@%s" % get_pkg_latest_version("paulstoffregen/OneWire") ), @@ -172,11 +175,11 @@ def test_project( ] assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ PackageSpec("framework-arduino-avr-attiny@1.3.2"), - PackageSpec("tool-scons@%s" % __core_packages__["tool-scons"][1:]), + PackageSpec("tool-scons@%s" % get_core_dependencies()["tool-scons"][1:]), PackageSpec("toolchain-atmelavr@1.50400.190710"), ] assert config.get("env:devkit", "lib_deps") == [ - "milesburton/DallasTemperature@~3.8.0" + "milesburton/DallasTemperature@^3.8.0" ] # update packages @@ -202,7 +205,7 @@ def test_project( ] assert pkgs_to_specs(ToolPackageManager().get_installed()) == [ PackageSpec("framework-arduino-avr-attiny@1.3.2"), - PackageSpec("tool-scons@%s" % __core_packages__["tool-scons"][1:]), + PackageSpec("tool-scons@%s" % get_core_dependencies()["tool-scons"][1:]), PackageSpec("toolchain-atmelavr@1.70300.191015"), PackageSpec("toolchain-atmelavr@1.50400.190710"), ] @@ -227,7 +230,7 @@ def test_custom_project_libraries( project_dir = tmp_path / "project" project_dir.mkdir() (project_dir / "platformio.ini").write_text(PROJECT_OUTDATED_CONFIG_TPL) - spec = "milesburton/DallasTemperature@~3.8.0" + spec = "milesburton/DallasTemperature@^3.8.0" result = clirunner.invoke( package_install_cmd, ["-d", str(project_dir), "-e", "devkit", "-l", spec], @@ -240,7 +243,7 @@ def test_custom_project_libraries( os.path.join(config.get("platformio", "libdeps_dir"), "devkit") ) assert pkgs_to_specs(lm.get_installed()) == [ - PackageSpec("DallasTemperature@3.8.1"), + PackageSpec(f"DallasTemperature@{DALLASTEMPERATURE_LATEST_VERSION}"), PackageSpec( "OneWire@%s" % get_pkg_latest_version("paulstoffregen/OneWire") ), diff --git a/tests/commands/test_check.py b/tests/commands/test_check.py index a9a99d65..5f32e77d 100644 --- a/tests/commands/test_check.py +++ b/tests/commands/test_check.py @@ -803,3 +803,49 @@ check_src_filters = assert errors + warnings + style == EXPECTED_DEFECTS assert "test.cpp" in result.output assert "main.cpp" not in result.output + + +def test_check_sources_in_project_root(clirunner, validate_cliresult, tmpdir_factory): + tmpdir = tmpdir_factory.mktemp("project") + + config = ( + """ +[platformio] +src_dir = ./ + """ + + DEFAULT_CONFIG + ) + tmpdir.join("platformio.ini").write(config) + tmpdir.join("main.cpp").write(TEST_CODE) + tmpdir.mkdir("spi").join("uart.cpp").write(TEST_CODE) + + result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) + validate_cliresult(result) + + errors, warnings, style = count_defects(result.output) + + assert result.exit_code == 0 + assert errors + warnings + style == EXPECTED_DEFECTS * 2 + + +def test_check_sources_in_external_dir(clirunner, validate_cliresult, tmpdir_factory): + tmpdir = tmpdir_factory.mktemp("project") + external_src_dir = tmpdir_factory.mktemp("external_src_dir") + + config = ( + f""" +[platformio] +src_dir = {external_src_dir} + """ + + DEFAULT_CONFIG + ) + tmpdir.join("platformio.ini").write(config) + external_src_dir.join("main.cpp").write(TEST_CODE) + + result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) + validate_cliresult(result) + + errors, warnings, style = count_defects(result.output) + + assert result.exit_code == 0 + assert errors + warnings + style == EXPECTED_DEFECTS diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py index 651cf579..a8bffc5d 100644 --- a/tests/commands/test_init.py +++ b/tests/commands/test_init.py @@ -15,6 +15,7 @@ import json import os +from platformio import fs from platformio.commands.boards import cli as cmd_boards from platformio.project.commands.init import project_init_cmd from platformio.project.config import ProjectConfig @@ -36,27 +37,28 @@ def test_init_default(clirunner, validate_cliresult): validate_pioproject(os.getcwd()) -def test_init_ext_folder(clirunner, validate_cliresult): - with clirunner.isolated_filesystem(): - ext_folder_name = "ext_folder" - os.makedirs(ext_folder_name) - result = clirunner.invoke(project_init_cmd, ["-d", ext_folder_name]) - validate_cliresult(result) - validate_pioproject(os.path.join(os.getcwd(), ext_folder_name)) - - def test_init_duplicated_boards(clirunner, validate_cliresult, tmpdir): - with tmpdir.as_cwd(): - for _ in range(2): - result = clirunner.invoke( - project_init_cmd, - ["-b", "uno", "-b", "uno", "--no-install-dependencies"], - ) - validate_cliresult(result) - validate_pioproject(str(tmpdir)) - config = ProjectConfig(os.path.join(os.getcwd(), "platformio.ini")) - config.validate() - assert set(config.sections()) == set(["env:uno"]) + project_dir = str(tmpdir.join("ext_folder")) + os.makedirs(project_dir) + + with fs.cd(os.path.dirname(project_dir)): + result = clirunner.invoke( + project_init_cmd, + [ + "-d", + os.path.basename(project_dir), + "-b", + "uno", + "-b", + "uno", + "--no-install-dependencies", + ], + ) + validate_cliresult(result) + validate_pioproject(project_dir) + config = ProjectConfig(os.path.join(project_dir, "platformio.ini")) + config.validate() + assert set(config.sections()) == set(["env:uno"]) def test_init_ide_without_board(clirunner, tmpdir): diff --git a/tests/commands/test_lib.py b/tests/commands/test_lib.py index 8f47ad61..221d788f 100644 --- a/tests/commands/test_lib.py +++ b/tests/commands/test_lib.py @@ -42,7 +42,7 @@ board = devkit framework = foo lib_deps = CustomLib - ArduinoJson @ 5.10.1 + ArduinoJson @ 6.18.5 """ ) result = clirunner.invoke( @@ -163,7 +163,7 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory storage_dir = tmpdir_factory.mktemp("test-updates") result = clirunner.invoke( cmd_lib, - ["-d", str(storage_dir), "install", "ArduinoJson @ 5.10.1", "Blynk @ ~0.5.0"], + ["-d", str(storage_dir), "install", "ArduinoJson @ 6.18.5", "Blynk @ ~1.2"], ) validate_cliresult(result) result = clirunner.invoke( @@ -173,17 +173,17 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory outdated = json.loads(result.stdout) assert len(outdated) == 2 # ArduinoJson - assert outdated[0]["version"] == "5.10.1" + assert outdated[0]["version"] == "6.18.5" assert outdated[0]["versionWanted"] is None assert semantic_version.Version( outdated[0]["versionLatest"] - ) > semantic_version.Version("6.16.0") + ) > semantic_version.Version("6.18.5") # Blynk - assert outdated[1]["version"] == "0.5.4" + assert outdated[1]["version"] == "1.2.0" assert outdated[1]["versionWanted"] is None assert semantic_version.Version( outdated[1]["versionLatest"] - ) > semantic_version.Version("0.6.0") + ) > semantic_version.Version("1.2.0") # check with spec result = clirunner.invoke( @@ -194,19 +194,19 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory "update", "--dry-run", "--json-output", - "ArduinoJson @ ^5", + "ArduinoJson @ ^6", ], ) validate_cliresult(result) outdated = json.loads(result.stdout) - assert outdated[0]["version"] == "5.10.1" - assert outdated[0]["versionWanted"] == "5.13.4" + assert outdated[0]["version"] == "6.18.5" + assert outdated[0]["versionWanted"] == "6.21.5" assert semantic_version.Version( outdated[0]["versionLatest"] ) > semantic_version.Version("6.16.0") # update with spec result = clirunner.invoke( - cmd_lib, ["-d", str(storage_dir), "update", "--silent", "ArduinoJson @ ^5.10.1"] + cmd_lib, ["-d", str(storage_dir), "update", "--silent", "ArduinoJson @ ^6.18.5"] ) validate_cliresult(result) result = clirunner.invoke( @@ -215,12 +215,12 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory validate_cliresult(result) items = json.loads(result.stdout) assert len(items) == 2 - assert items[0]["version"] == "5.13.4" - assert items[1]["version"] == "0.5.4" + assert items[0]["version"] == "6.21.5" + assert items[1]["version"] == "1.2.0" # Check incompatible result = clirunner.invoke( - cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "ArduinoJson @ ^5"] + cmd_lib, ["-d", str(storage_dir), "update", "--dry-run", "ArduinoJson @ ^6"] ) with pytest.raises( AssertionError, @@ -228,7 +228,7 @@ def test_update(clirunner, validate_cliresult, isolated_pio_core, tmpdir_factory ): validate_cliresult(result) result = clirunner.invoke( - cmd_lib, ["-d", str(storage_dir), "update", "ArduinoJson @ ^5"] + cmd_lib, ["-d", str(storage_dir), "update", "ArduinoJson @ ^6"] ) validate_cliresult(result) - assert "ArduinoJson@5.13.4 is already up-to-date" in result.stdout + assert "ArduinoJson@6.21.5 is already up-to-date" in result.stdout diff --git a/tests/commands/test_lib_complex.py b/tests/commands/test_lib_complex.py index eb56c5b7..55563be5 100644 --- a/tests/commands/test_lib_complex.py +++ b/tests/commands/test_lib_complex.py @@ -23,6 +23,7 @@ from platformio.package.exception import UnknownPackageError from platformio.util import strip_ansi_codes PlatformioCLI.leftover_args = ["--json-output"] # hook for click +ARDUINO_JSON_VERSION = "6.21.5" def test_search(clirunner, validate_cliresult): @@ -44,10 +45,10 @@ def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_cor "-g", "install", "64", - "ArduinoJson@~5.10.0", - "547@2.2.4", + "ArduinoJson@~6", + "547@2.7.3", "AsyncMqttClient@<=0.8.2", - "Adafruit PN532@1.2.0", + "Adafruit PN532@1.3.2", ], ) validate_cliresult(result) @@ -60,7 +61,7 @@ def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_cor items1 = [d.basename for d in isolated_pio_core.join("lib").listdir()] items2 = [ "ArduinoJson", - "ArduinoJson@5.10.1", + f"ArduinoJson@{ARDUINO_JSON_VERSION}", "NeoPixelBus", "AsyncMqttClient", "ESPAsyncTCP", @@ -79,7 +80,7 @@ def test_global_install_archive(clirunner, validate_cliresult, isolated_pio_core "install", "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip", "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2", - "SomeLib=https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.8.1/DallasTemperature-3.8.1.tar.gz", + "SomeLib=https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.11.0/DallasTemperature-3.11.0.tar.gz", "https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip", ], ) @@ -142,7 +143,7 @@ def test_install_duplicates( # pylint: disable=unused-argument [ "-g", "install", - "https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.8.1/DallasTemperature-3.8.1.tar.gz", + "https://dl.registry.platformio.org/download/milesburton/library/DallasTemperature/3.11.0/DallasTemperature-3.11.0.tar.gz", ], ) validate_cliresult(result) @@ -176,11 +177,11 @@ def test_global_lib_list(clirunner, validate_cliresult): n in result.output for n in ( "required: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip", - "ArduinoJson @ 5.10.1", + f"ArduinoJson @ {ARDUINO_JSON_VERSION}", "required: git+https://github.com/gioblu/PJON.git#3.0", "PJON @ 3.0.0+sha.1fb26f", ) - ) + ), result.output result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"]) assert all( @@ -188,7 +189,7 @@ def test_global_lib_list(clirunner, validate_cliresult): for n in ( "__pkg_dir", '"__src_url": "git+https://github.com/gioblu/PJON.git#6.2"', - '"version": "5.10.1"', + f'"version": "{ARDUINO_JSON_VERSION}"', ) ) items1 = [i["name"] for i in json.loads(result.output)] @@ -218,13 +219,13 @@ def test_global_lib_list(clirunner, validate_cliresult): ] versions2 = [ "ArduinoJson@5.8.2", - "ArduinoJson@5.10.1", + f"ArduinoJson@{ARDUINO_JSON_VERSION}", "AsyncMqttClient@0.8.2", - "NeoPixelBus@2.2.4", + "NeoPixelBus@2.7.3", "PJON@6.2.0+sha.07fe9aa", "PJON@3.0.0+sha.1fb26fd", "PubSubClient@2.6.0+sha.bef5814", - "Adafruit PN532@1.2.0", + "Adafruit PN532@1.3.2", ] assert set(versions1) >= set(versions2) @@ -249,7 +250,7 @@ def test_global_lib_update(clirunner, validate_cliresult): assert "__pkg_dir" in oudated[0] result = clirunner.invoke(cmd_lib, ["-g", "update", oudated[0]["__pkg_dir"]]) validate_cliresult(result) - assert "Removing NeoPixelBus @ 2.2.4" in strip_ansi_codes(result.output) + assert "Removing NeoPixelBus @ 2.7.3" in strip_ansi_codes(result.output) # update all libraries result = clirunner.invoke( diff --git a/tests/commands/test_platform.py b/tests/commands/test_platform.py index 967bf51e..b832a366 100644 --- a/tests/commands/test_platform.py +++ b/tests/commands/test_platform.py @@ -63,7 +63,7 @@ def test_install_unknown_from_registry(clirunner): def test_install_core_3_dev_platform(clirunner, validate_cliresult, isolated_pio_core): result = clirunner.invoke( cli_platform.platform_install, - ["atmelavr@1.2.0", "--skip-default-package"], + ["atmelavr@2.2.0", "--skip-default-package"], ) assert result.exit_code == 0 @@ -71,11 +71,11 @@ def test_install_core_3_dev_platform(clirunner, validate_cliresult, isolated_pio def test_install_known_version(clirunner, validate_cliresult, isolated_pio_core): result = clirunner.invoke( cli_platform.platform_install, - ["atmelavr@2.0.0", "--skip-default-package", "--with-package", "tool-avrdude"], + ["atmelavr@4.2.0", "--skip-default-package", "--with-package", "tool-avrdude"], ) validate_cliresult(result) output = strip_ansi_codes(result.output) - assert "atmelavr @ 2.0.0" in output + assert "atmelavr@4.2.0" in output assert not os.path.isdir(str(isolated_pio_core.join("packages"))) @@ -128,14 +128,14 @@ def test_update_raw(clirunner, validate_cliresult, isolated_pio_core): result = clirunner.invoke(cli_platform.platform_update, ["atmelavr"]) validate_cliresult(result) output = strip_ansi_codes(result.output) - assert "Removing atmelavr @ 2.0.0" in output + assert "Removing atmelavr @ 4.2.0" in output assert "Platform Manager: Installing platformio/atmelavr @" in output assert len(isolated_pio_core.join("packages").listdir()) == 2 def test_uninstall(clirunner, validate_cliresult, isolated_pio_core): result = clirunner.invoke( - cli_platform.platform_uninstall, ["atmelavr@1.2.0", "atmelavr", "espressif8266"] + cli_platform.platform_uninstall, ["atmelavr@2.2.0", "atmelavr", "espressif8266"] ) validate_cliresult(result) assert not isolated_pio_core.join("platforms").listdir() diff --git a/tests/package/test_manager.py b/tests/package/test_manager.py index fa3f215c..fc210467 100644 --- a/tests/package/test_manager.py +++ b/tests/package/test_manager.py @@ -219,7 +219,7 @@ def test_install_from_registry(isolated_pio_core, tmpdir_factory): # test conflicted names lm = LibraryPackageManager(str(tmpdir_factory.mktemp("conflicted-storage"))) lm.set_log_level(logging.ERROR) - lm.install("z3t0/IRremote@2.6.1") + lm.install("z3t0/IRremote") lm.install("mbed-yuhki50/IRremote") assert len(lm.get_installed()) == 2 @@ -554,14 +554,14 @@ def test_uninstall(isolated_pio_core, tmpdir_factory): assert not lm.get_installed() # test uninstall dependencies - assert lm.install("AsyncMqttClient-esphome @ 0.8.4") + assert lm.install("AsyncMqttClient-esphome") assert len(lm.get_installed()) == 3 assert lm.uninstall("AsyncMqttClient-esphome", skip_dependencies=True) assert len(lm.get_installed()) == 2 lm = LibraryPackageManager(str(storage_dir)) lm.set_log_level(logging.ERROR) - assert lm.install("AsyncMqttClient-esphome @ 0.8.4") + assert lm.install("AsyncMqttClient-esphome") assert lm.uninstall("AsyncMqttClient-esphome") assert not lm.get_installed() @@ -604,23 +604,23 @@ def test_update_with_metadata(isolated_pio_core, tmpdir_factory): assert str(outdated.current) == "1.8.7" assert outdated.latest > semantic_version.Version("1.10.0") - pkg = lm.install("ArduinoJson @ 5.10.1") + pkg = lm.install("ArduinoJson @ 6.19.4") # test latest outdated = lm.outdated(pkg) - assert str(outdated.current) == "5.10.1" + assert str(outdated.current) == "6.19.4" assert outdated.wanted is None assert outdated.latest > outdated.current assert outdated.latest > semantic_version.Version("5.99.99") # test wanted - outdated = lm.outdated(pkg, PackageSpec("ArduinoJson@~5")) - assert str(outdated.current) == "5.10.1" - assert str(outdated.wanted) == "5.13.4" + outdated = lm.outdated(pkg, PackageSpec("ArduinoJson@~6")) + assert str(outdated.current) == "6.19.4" + assert str(outdated.wanted) == "6.21.5" assert outdated.latest > semantic_version.Version("6.16.0") - # update to the wanted 5.x - new_pkg = lm.update("ArduinoJson@^5", PackageSpec("ArduinoJson@^5")) - assert str(new_pkg.metadata.version) == "5.13.4" + # update to the wanted 6.x + new_pkg = lm.update("ArduinoJson@^6", PackageSpec("ArduinoJson@^6")) + assert str(new_pkg.metadata.version) == "6.21.5" # check that old version is removed assert len(lm.get_installed()) == 2