diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index d7035d5a..629a06d5 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -4,8 +4,6 @@ on: push: branches: - "release/**" - tags: - - v* jobs: deployment: @@ -35,8 +33,11 @@ jobs: run: | tox -e testcore + - name: Build Python source tarball + run: python setup.py sdist + - name: Publish package to PyPI - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + if: ${{ github.ref == 'refs/heads/master' }} uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ diff --git a/HISTORY.rst b/HISTORY.rst index 7ad9d795..75db03f0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -13,6 +13,16 @@ PlatformIO Core 6 **A professional collaborative platform for declarative, safety-critical, and test-driven embedded development.** +6.1.1 (2022-07-11) +~~~~~~~~~~~~~~~~~~ + +* Added new ``monitor_encoding`` project configuration option to configure `Device Monitor `__ (`issue #4350 `_) +* Allowed specifying project environments for `pio ci `__ command (`issue #4347 `_) +* Show "TimeoutError" only in the verbose mode when can not find a serial port +* Fixed an issue when a serial port was not automatically detected if the board has predefined HWIDs +* Fixed an issue with endless scanning of project dependencies (`issue #4349 `_) +* Fixed an issue with |LDF| when incompatible libraries were used for the working project environment with the missed framework (`pull #4346 `_) + 6.1.0 (2022-07-06) ~~~~~~~~~~~~~~~~~~ diff --git a/docs b/docs index f5958b87..f4accb77 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit f5958b875629eac7b9b95932d524952731e79480 +Subproject commit f4accb77c85da86b6bd60670f6e02719db22235c diff --git a/platformio/__init__.py b/platformio/__init__.py index 1ceb17a7..0e360ce3 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -14,7 +14,7 @@ import sys -VERSION = (6, 1, 0) +VERSION = (6, 1, 1) __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index c830c881..bd9e319a 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -30,7 +30,7 @@ from SCons.Script import DefaultEnvironment # pylint: disable=import-error from platformio import exception, fs from platformio.builder.tools import platformio as piotool -from platformio.compat import IS_WINDOWS, hashlib_encode_data, string_types +from platformio.compat import IS_WINDOWS, MISSING, hashlib_encode_data, string_types from platformio.http import HTTPClientError, InternetIsOffline from platformio.package.exception import ( MissingPackageManifestError, @@ -143,7 +143,7 @@ class LibBuilderBase: self._deps_are_processed = False self._circular_deps = [] - self._processed_files = [] + self._processed_search_files = [] # reset source filter, could be overridden with extra script self.env["SRC_FILTER"] = "" @@ -154,20 +154,27 @@ class LibBuilderBase: def __repr__(self): return "%s(%r)" % (self.__class__, self.path) - def __contains__(self, path): - p1 = self.path - p2 = path + def __contains__(self, child_path): + return self.is_common_builder(self.path, child_path) + + def is_common_builder(self, root_path, child_path): if IS_WINDOWS: - p1 = p1.lower() - p2 = p2.lower() - if p1 == p2: + root_path = root_path.lower() + child_path = child_path.lower() + if root_path == child_path: return True - if os.path.commonprefix([p1 + os.path.sep, p2]) == p1 + os.path.sep: + if ( + os.path.commonprefix([root_path + os.path.sep, child_path]) + == root_path + os.path.sep + ): return True # try to resolve paths - p1 = os.path.os.path.realpath(p1) - p2 = os.path.os.path.realpath(p2) - return os.path.commonprefix([p1 + os.path.sep, p2]) == p1 + os.path.sep + root_path = os.path.realpath(root_path) + child_path = os.path.realpath(child_path) + return ( + os.path.commonprefix([root_path + os.path.sep, child_path]) + == root_path + os.path.sep + ) @property def name(self): @@ -324,7 +331,7 @@ class LibBuilderBase: ) ] - def _get_found_includes( # pylint: disable=too-many-branches + def get_implicit_includes( # pylint: disable=too-many-branches self, search_files=None ): # all include directories @@ -345,15 +352,17 @@ class LibBuilderBase: include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE) result = [] - for path in search_files or []: - if path in self._processed_files: + search_files = search_files or [] + while search_files: + node = self.env.File(search_files.pop(0)) + if node.get_abspath() in self._processed_search_files: continue - self._processed_files.append(path) + self._processed_search_files.append(node.get_abspath()) try: assert "+" in self.lib_ldf_mode candidates = LibBuilderBase.CCONDITIONAL_SCANNER( - self.env.File(path), + node, self.env, tuple(include_dirs), depth=self.CCONDITIONAL_SCANNER_DEPTH, @@ -363,39 +372,35 @@ class LibBuilderBase: if self.verbose and "+" in self.lib_ldf_mode: sys.stderr.write( "Warning! Classic Pre Processor is used for `%s`, " - "advanced has failed with `%s`\n" % (path, exc) + "advanced has failed with `%s`\n" % (node.get_abspath(), exc) ) - candidates = self.env.File(path).get_implicit_deps( - self.env, - LibBuilderBase.CLASSIC_SCANNER, - lambda _: tuple(include_dirs), + candidates = LibBuilderBase.CLASSIC_SCANNER( + node, self.env, tuple(include_dirs) ) - # mark candidates already processed - self._processed_files.extend( - [ - c.get_abspath() - for c in candidates - if c.get_abspath() not in self._processed_files - ] - ) - - # print(path, [c.get_abspath() for c in candidates]) + # print(node.get_abspath(), [c.get_abspath() for c in candidates]) for item in candidates: + item_path = item.get_abspath() + # process internal files recursively + if ( + item_path not in self._processed_search_files + and item_path not in search_files + and item_path in self + ): + search_files.append(item_path) if item not in result: result.append(item) if not self.PARSE_SRC_BY_H_NAME: continue - _h_path = item.get_abspath() - if not fs.path_endswith_ext(_h_path, piotool.SRC_HEADER_EXT): + if not fs.path_endswith_ext(item_path, piotool.SRC_HEADER_EXT): continue - _f_part = _h_path[: _h_path.rindex(".")] + item_fname = item_path[: item_path.rindex(".")] for ext in piotool.SRC_C_EXT + piotool.SRC_CXX_EXT: - if not os.path.isfile("%s.%s" % (_f_part, ext)): + if not os.path.isfile("%s.%s" % (item_fname, ext)): continue - _c_path = self.env.File("%s.%s" % (_f_part, ext)) - if _c_path not in result: - result.append(_c_path) + item_c_node = self.env.File("%s.%s" % (item_fname, ext)) + if item_c_node not in result: + result.append(item_c_node) return result @@ -410,7 +415,7 @@ class LibBuilderBase: search_files = self.get_search_files() lib_inc_map = {} - for inc in self._get_found_includes(search_files): + for inc in self.get_implicit_includes(search_files): inc_path = inc.get_abspath() for lb in self.env.GetLibBuilders(): if inc_path in lb: @@ -571,11 +576,10 @@ class ArduinoLibBuilder(LibBuilderBase): # pylint: disable=no-member if not self._manifest.get("dependencies"): return LibBuilderBase.lib_ldf_mode.fget(self) - missing = object() global_value = self.env.GetProjectConfig().getraw( - "env:" + self.env["PIOENV"], "lib_ldf_mode", missing + "env:" + self.env["PIOENV"], "lib_ldf_mode", MISSING ) - if global_value != missing: + if global_value != MISSING: return LibBuilderBase.lib_ldf_mode.fget(self) # automatically enable C++ Preprocessing in runtime # (Arduino IDE has this behavior) @@ -827,11 +831,10 @@ class PlatformIOLibBuilder(LibBuilderBase): @property def lib_archive(self): - missing = object() global_value = self.env.GetProjectConfig().getraw( - "env:" + self.env["PIOENV"], "lib_archive", missing + "env:" + self.env["PIOENV"], "lib_archive", MISSING ) - if global_value != missing: + if global_value != MISSING: return self.env.GetProjectConfig().get( "env:" + self.env["PIOENV"], "lib_archive" ) @@ -881,6 +884,12 @@ class ProjectAsLibBuilder(LibBuilderBase): if export_projenv: env.Export(dict(projenv=self.env)) + def __contains__(self, child_path): + for root_path in (self.include_dir, self.src_dir, self.test_dir): + if root_path and self.is_common_builder(root_path, child_path): + return True + return False + @property def include_dir(self): include_dir = self.env.subst("$PROJECT_INCLUDE_DIR") @@ -890,6 +899,10 @@ class ProjectAsLibBuilder(LibBuilderBase): def src_dir(self): return self.env.subst("$PROJECT_SRC_DIR") + @property + def test_dir(self): + return self.env.subst("$PROJECT_TEST_DIR") + def get_search_files(self): items = [] build_type = self.env.GetBuildType() @@ -1035,7 +1048,7 @@ def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))) sys.stderr.write("Platform incompatible library %s\n" % lb.path) return False if compat_mode in ("soft", "strict") and not lb.is_frameworks_compatible( - env.get("PIOFRAMEWORK", []) + env.get("PIOFRAMEWORK", "__noframework__") ): if verbose: sys.stderr.write("Framework incompatible library %s\n" % lb.path) diff --git a/platformio/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index 2229a9fb..d46b1590 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -117,6 +117,7 @@ def AutodetectUploadPort(*args, **kwargs): board_config=env.BoardConfig() if "BOARD" in env else None, upload_protocol=upload_protocol, prefer_gdb_port="blackmagic" in upload_protocol, + verbose=int(ARGUMENTS.get("PIOVERBOSE", 0)), ) ) diff --git a/platformio/commands/ci.py b/platformio/commands/ci.py index 9364d1e9..e62d1ca9 100644 --- a/platformio/commands/ci.py +++ b/platformio/commands/ci.py @@ -62,6 +62,7 @@ def validate_path(ctx, param, value): # pylint: disable=unused-argument ), ) @click.option("-O", "--project-option", multiple=True) +@click.option("-e", "--environment", "environments", multiple=True) @click.option("-v", "--verbose", is_flag=True) @click.pass_context def cli( # pylint: disable=too-many-arguments, too-many-branches @@ -74,9 +75,9 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches keep_build_dir, project_conf, project_option, + environments, verbose, ): - if not src and os.getenv("PLATFORMIO_CI_SRC"): src = validate_path(ctx, None, os.getenv("PLATFORMIO_CI_SRC").split(":")) if not src: @@ -115,7 +116,9 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches ) # process project - ctx.invoke(cmd_run, project_dir=build_dir, verbose=verbose) + ctx.invoke( + cmd_run, project_dir=build_dir, environment=environments, verbose=verbose + ) finally: if not keep_build_dir: fs.rmtree(build_dir) diff --git a/platformio/debug/config/base.py b/platformio/debug/config/base.py index a5867340..7e966cc5 100644 --- a/platformio/debug/config/base.py +++ b/platformio/debug/config/base.py @@ -16,7 +16,7 @@ import json import os from platformio import fs, proc, util -from platformio.compat import string_types +from platformio.compat import MISSING, string_types from platformio.debug.exception import DebugInvalidOptionsError from platformio.project.config import ProjectConfig from platformio.project.helpers import load_build_metadata @@ -96,9 +96,8 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes @property def init_break(self): - missed = object() - result = self.env_options.get("debug_init_break", missed) - if result != missed: + result = self.env_options.get("debug_init_break", MISSING) + if result != MISSING: return result result = None if not result: diff --git a/platformio/device/finder.py b/platformio/device/finder.py index 98aef4f6..0d7eba90 100644 --- a/platformio/device/finder.py +++ b/platformio/device/finder.py @@ -88,6 +88,7 @@ def find_serial_port( # pylint: disable=too-many-arguments ensure_ready=False, prefer_gdb_port=False, timeout=2, + verbose=False, ): if initial_port: if not is_pattern_port(initial_port): @@ -96,9 +97,11 @@ def find_serial_port( # pylint: disable=too-many-arguments if upload_protocol and upload_protocol.startswith("blackmagic"): return find_blackmagic_serial_port(prefer_gdb_port, timeout) + port = None if board_config and board_config.get("build.hwids", []): - return find_board_serial_port(board_config, timeout) - port = find_known_uart_port(ensure_ready, timeout) + port = find_board_serial_port(board_config, timeout, verbose) + if not port: + port = find_known_uart_port(ensure_ready, timeout, verbose) if port: return port @@ -158,7 +161,7 @@ def find_blackmagic_serial_port(prefer_gdb_port=False, timeout=0): return None -def find_board_serial_port(board_config, timeout=0): +def find_board_serial_port(board_config, timeout=0, verbose=False): hwids = board_config.get("build.hwids", []) try: @@ -175,18 +178,19 @@ def find_board_serial_port(board_config, timeout=0): except retry.RetryStopException: pass - click.secho( - "TimeoutError: Could not automatically find serial port " - "for the `%s` board based on the declared HWIDs=%s" - % (board_config.get("name", "unknown"), hwids), - fg="yellow", - err=True, - ) + if verbose: + click.secho( + "TimeoutError: Could not automatically find serial port " + "for the `%s` board based on the declared HWIDs=%s" + % (board_config.get("name", "unknown"), hwids), + fg="yellow", + err=True, + ) return None -def find_known_uart_port(ensure_ready=False, timeout=0): +def find_known_uart_port(ensure_ready=False, timeout=0, verbose=False): known_hwids = list(BLACK_MAGIC_HWIDS) # load from UDEV rules @@ -222,12 +226,13 @@ def find_known_uart_port(ensure_ready=False, timeout=0): except retry.RetryStopException: pass - click.secho( - "TimeoutError: Could not automatically find serial port " - "based on the known UART bridges", - fg="yellow", - err=True, - ) + if verbose: + click.secho( + "TimeoutError: Could not automatically find serial port " + "based on the known UART bridges", + fg="yellow", + err=True, + ) return None diff --git a/platformio/device/monitor/command.py b/platformio/device/monitor/command.py index 44df4043..0a484eee 100644 --- a/platformio/device/monitor/command.py +++ b/platformio/device/monitor/command.py @@ -56,9 +56,11 @@ from platformio.project.options import ProjectOptions @click.option("--echo", is_flag=True, help="Enable local echo") @click.option( "--encoding", - default="UTF-8", - show_default=True, - help="Set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8)", + help=( + "Set the encoding for the serial port " + "(e.g. hexlify, Latin1, UTF-8) [default=%s]" + % ProjectOptions["env.monitor_encoding"].default + ), ) @click.option( "-f", diff --git a/platformio/project/options.py b/platformio/project/options.py index 4c29b925..e1aa8280 100644 --- a/platformio/project/options.py +++ b/platformio/project/options.py @@ -567,6 +567,12 @@ ProjectOptions = OrderedDict( type=click.BOOL, default=False, ), + ConfigEnvOption( + group="monitor", + name="monitor_encoding", + description="Custom encoding (e.g. hexlify, Latin1, UTF-8)", + default="UTF-8", + ), # Library ConfigEnvOption( group="library", diff --git a/platformio/test/runners/readers/serial.py b/platformio/test/runners/readers/serial.py index da4b1a05..ac7765bc 100644 --- a/platformio/test/runners/readers/serial.py +++ b/platformio/test/runners/readers/serial.py @@ -73,6 +73,7 @@ class SerialTestOutputReader: ), upload_protocol=project_options.get("upload_protocol"), ensure_ready=True, + verbose=self.test_runner.options.verbose, ) if port: return port diff --git a/scripts/99-platformio-udev.rules b/scripts/99-platformio-udev.rules index 05c6ab45..8004f998 100644 --- a/scripts/99-platformio-udev.rules +++ b/scripts/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 CH9102 USB-Serial adapter +ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" # Arduino boards ATTRS{idVendor}=="2341", ATTRS{idProduct}=="[08][023]*", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID_MM_PORT_IGNORE}="1" diff --git a/tests/commands/test_run.py b/tests/commands/test_run.py index 23d1e096..009f33c9 100644 --- a/tests/commands/test_run.py +++ b/tests/commands/test_run.py @@ -335,3 +335,73 @@ projenv.Append(CPPDEFINES=[ assert 'MACRO_2=' in result.output assert 'MACRO_3=' in result.output assert "MACRO_4=" in result.output + + +def test_ldf(clirunner, validate_cliresult, tmp_path: Path): + project_dir = tmp_path / "project" + + # libs + lib_dir = project_dir / "lib" + a_lib_dir = lib_dir / "a" + a_lib_dir.mkdir(parents=True) + (a_lib_dir / "a.h").write_text( + """ +#include +""" + ) + # b + b_lib_dir = lib_dir / "b" + b_lib_dir.mkdir(parents=True) + (b_lib_dir / "some_from_b.h").write_text("") + # c + c_lib_dir = lib_dir / "c" + c_lib_dir.mkdir(parents=True) + (c_lib_dir / "parse_c_by_name.h").write_text( + """ +void some_func(); + """ + ) + (c_lib_dir / "parse_c_by_name.c").write_text( + """ +#include +#include + +void some_func() { +} + """ + ) + (c_lib_dir / "some.c").write_text( + """ +#include + """ + ) + # d + d_lib_dir = lib_dir / "d" + d_lib_dir.mkdir(parents=True) + (d_lib_dir / "d.h").write_text("") + + # project + src_dir = project_dir / "src" + src_dir.mkdir(parents=True) + (src_dir / "main.h").write_text( + """ +#include +#include +""" + ) + (src_dir / "main.c").write_text( + """ +#include + +int main() { +} +""" + ) + (project_dir / "platformio.ini").write_text( + """ +[env:native] +platform = native + """ + ) + result = clirunner.invoke(cmd_run, ["--project-dir", str(project_dir)]) + validate_cliresult(result)