diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index c7c020a3..c82438d2 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -8,12 +8,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-20.04, windows-latest, macos-latest] - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.6", "3.7", "3.11", "3.12"] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" @@ -37,7 +37,7 @@ jobs: tox -e lint - name: Integration Tests - if: ${{ matrix.python-version == '3.9' }} + if: ${{ matrix.python-version == '3.11' }} run: | tox -e testcore diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 370333a5..8517d7bd 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -12,14 +12,14 @@ jobs: environment: production steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.11" - name: Install dependencies run: | @@ -35,7 +35,8 @@ jobs: tox -e testcore - name: Build Python source tarball - run: python setup.py sdist bdist_wheel + # run: python setup.py sdist bdist_wheel + run: python setup.py sdist - name: Publish package to PyPI if: ${{ github.ref == 'refs/heads/master' }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c17f6b67..f749b430 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -7,13 +7,13 @@ jobs: name: Build Docs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.11" - name: Install dependencies run: | python -m pip install --upgrade pip @@ -78,7 +78,7 @@ jobs: fi - name: Checkout latest Docs continue-on-error: true - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ env.DOCS_REPO }} path: ${{ env.DOCS_DIR }} diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 6bb258e0..2e734563 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -15,14 +15,14 @@ jobs: PIO_INSTALL_DEVPLATFORM_NAMES: "aceinna_imu,atmelavr,atmelmegaavr,atmelsam,espressif32,espressif8266,nordicnrf52,raspberrypi,ststm32,teensy" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.11" - name: Install dependencies run: | diff --git a/.github/workflows/projects.yml b/.github/workflows/projects.yml index 1d37c5a3..676efaec 100644 --- a/.github/workflows/projects.yml +++ b/.github/workflows/projects.yml @@ -40,20 +40,20 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: "recursive" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: 3.11 - name: Install PlatformIO run: pip install -U . - name: Check out ${{ matrix.project.repository }} - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: "recursive" repository: ${{ matrix.project.repository }} diff --git a/HISTORY.rst b/HISTORY.rst index 251c7b80..bfe1dcba 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -17,7 +17,38 @@ Unlock the true potential of embedded software development with PlatformIO's collaborative ecosystem, embracing declarative principles, test-driven methodologies, and modern toolchains for unrivaled success. -6.1.11 (2023-??-??) +6.1.14 (2024-??-??) +~~~~~~~~~~~~~~~~~~~ + +* 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 `_) +* Resolved an issue related to the relative package path in the `pio pkg publish `__ command + +6.1.13 (2024-01-12) +~~~~~~~~~~~~~~~~~~~ + +* Expanded support for SCons variables declared in the legacy format ``${SCONS_VARNAME}`` (`issue #4828 `_) + +6.1.12 (2024-01-10) +~~~~~~~~~~~~~~~~~~~ + +* Added support for Python 3.12 +* Introduced the capability to launch the debug server in a separate process (`issue #4722 `_) +* Introduced a warning during the verification of MCU maximum RAM usage, signaling when the allocated RAM surpasses 100% (`issue #4791 `_) +* Drastically enhanced the speed of project building when operating in verbose mode (`issue #4783 `_) +* Upgraded the build engine to the latest version of SCons (4.6.0) to improve build performance, reliability, and compatibility with other tools and systems (`release notes `__) +* Enhanced the handling of built-in variables in |PIOCONF| during |INTERPOLATION| (`issue #4695 `_) +* Enhanced PIP dependency declarations for improved reliability and extended support to include Python 3.6 (`issue #4819 `_) +* Implemented automatic installation of missing dependencies when utilizing a SOCKS proxy (`issue #4822 `_) +* Implemented a fail-safe mechanism to terminate a debugging session if an unknown CLI option is passed (`issue #4699 `_) +* Rectified an issue where ``${platformio.name}`` erroneously represented ``None`` as the default `project name `__ (`issue #4717 `_) +* Resolved an issue where the ``COMPILATIONDB_INCLUDE_TOOLCHAIN`` setting was not correctly applying to private libraries (`issue #4762 `_) +* Resolved an issue where ``get_systype()`` inaccurately returned the architecture when executed within a Docker container on a 64-bit kernel with a 32-bit userspace (`issue #4777 `_) +* Resolved an issue with incorrect handling of the ``check_src_filters`` option when used in multiple environments (`issue #4788 `_) +* Resolved an issue where running `pio project metadata `__ resulted in duplicated "include" entries (`issue #4723 `_) +* Resolved an issue where native debugging failed on the host machine (`issue #4745 `_) +* Resolved an issue where custom debug configurations were being inadvertently overwritten in VSCode's ``launch.json`` (`issue #4810 `_) + +6.1.11 (2023-08-31) ~~~~~~~~~~~~~~~~~~~ * Resolved a possible issue that may cause generated projects for `PlatformIO IDE for VSCode `__ to fail to launch a debug session because of a missing "objdump" binary when GDB is not part of the toolchain package diff --git a/platformio/__init__.py b/platformio/__init__.py index d7449e40..81161c06 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, "11a2") +VERSION = (6, 1, "14a1") __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" @@ -41,7 +41,7 @@ __pioremote_endpoint__ = "ssl:host=remote.platformio.org:port=4413" __core_packages__ = { "contrib-piohome": "~3.4.2", "contrib-pioremote": "~1.0.0", - "tool-scons": "~4.40502.0", + "tool-scons": "~4.40600.0", "tool-cppcheck": "~1.21100.0", "tool-clangtidy": "~1.150005.0", "tool-pvs-studio": "~7.18.0", @@ -52,22 +52,3 @@ __check_internet_hosts__ = [ "88.198.170.159", # platformio.org "github.com", ] + __registry_mirror_hosts__ - -__install_requires__ = [ - # Core requirements - "bottle == 0.12.*", - "click >=8.0.4, <=8.2", - "colorama", - "httpx >=0.22.0, <0.25", - "marshmallow == 3.*", - "pyelftools == 0.29", - "pyserial == 3.5.*", # keep in sync "device/monitor/terminal.py" - "semantic_version == 2.10.*", - "tabulate == 0.*", -] + [ - # PIO Home requirements - "ajsonrpc == 1.2.*", - "starlette >=0.19, <0.32", - "uvicorn >=0.16, <0.24", - "wsproto == 1.*", -] diff --git a/platformio/account/team/commands/list.py b/platformio/account/team/commands/list.py index 0118dff8..82f272b4 100644 --- a/platformio/account/team/commands/list.py +++ b/platformio/account/team/commands/list.py @@ -51,11 +51,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 a5897f8f..992676db 100644 --- a/platformio/assets/system/99-platformio-udev.rules +++ b/platformio/assets/system/99-platformio-udev.rules @@ -171,3 +171,6 @@ ATTRS{product}=="*CMSIS-DAP*", MODE="0666", ENV{ID_MM_DEVICE_IGNORE}="1", ENV{ID # Atmel AVR Dragon 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 diff --git a/platformio/builder/tools/piobuild.py b/platformio/builder/tools/piobuild.py index 4f7473cb..c71a5db3 100644 --- a/platformio/builder/tools/piobuild.py +++ b/platformio/builder/tools/piobuild.py @@ -117,6 +117,10 @@ def ProcessProgramDeps(env): # remove specified flags env.ProcessUnFlags(env.get("BUILD_UNFLAGS")) + env.ProcessCompileDbToolchainOption() + + +def ProcessCompileDbToolchainOption(env): if "compiledb" in COMMAND_LINE_TARGETS: # Resolve absolute path of toolchain for cmd in ("CC", "CXX", "AS"): @@ -129,6 +133,7 @@ def ProcessProgramDeps(env): ) if env.get("COMPILATIONDB_INCLUDE_TOOLCHAIN"): + print("Warning! `COMPILATIONDB_INCLUDE_TOOLCHAIN` is scoping") for scope, includes in env.DumpIntegrationIncludes().items(): if scope in ("toolchain",): env.Append(CPPPATH=includes) @@ -370,6 +375,7 @@ def generate(env): env.AddMethod(GetBuildType) env.AddMethod(BuildProgram) env.AddMethod(ProcessProgramDeps) + env.AddMethod(ProcessCompileDbToolchainOption) env.AddMethod(ProcessProjectDeps) env.AddMethod(ParseFlagsExtended) env.AddMethod(ProcessFlags) diff --git a/platformio/builder/tools/piointegration.py b/platformio/builder/tools/piointegration.py index db47dc79..6e217676 100644 --- a/platformio/builder/tools/piointegration.py +++ b/platformio/builder/tools/piointegration.py @@ -29,12 +29,7 @@ def IsIntegrationDump(_): def DumpIntegrationIncludes(env): result = dict(build=[], compatlib=[], toolchain=[]) - result["build"].extend( - [ - env.subst("$PROJECT_INCLUDE_DIR"), - env.subst("$PROJECT_SRC_DIR"), - ] - ) + # `env`(project) CPPPATH result["build"].extend( [os.path.abspath(env.subst(item)) for item in env.get("CPPPATH", [])] ) diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index b7c8d7f3..1395fe20 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -477,6 +477,7 @@ class LibBuilderBase: self.is_built = True self.env.PrependUnique(CPPPATH=self.get_include_dirs()) + self.env.ProcessCompileDbToolchainOption() if self.lib_ldf_mode == "off": for lb in self.env.GetLibBuilders(): @@ -791,7 +792,9 @@ class PlatformIOLibBuilder(LibBuilderBase): include_dirs.append(os.path.join(self.path, "utility")) for path in self.env.get("CPPPATH", []): - if path not in self.envorigin.get("CPPPATH", []): + if path not in include_dirs and path not in self.envorigin.get( + "CPPPATH", [] + ): include_dirs.append(self.env.subst(path)) return include_dirs 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/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index 77bf28d2..224d0b90 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -218,12 +218,11 @@ def CheckUploadSize(_, target, source, env): if int(ARGUMENTS.get("PIOVERBOSE", 0)): print(output) - # raise error - # if data_max_size and data_size > data_max_size: - # sys.stderr.write( - # "Error: The data size (%d bytes) is greater " - # "than maximum allowed (%s bytes)\n" % (data_size, data_max_size)) - # env.Exit(1) + if data_max_size and data_size > data_max_size: + sys.stderr.write( + "Warning! The data size (%d bytes) is greater " + "than maximum allowed (%s bytes)\n" % (data_size, data_max_size) + ) if program_size > program_max_size: sys.stderr.write( "Error: The program size (%d bytes) is greater " diff --git a/platformio/check/cli.py b/platformio/check/cli.py index d99da313..3e0c1759 100644 --- a/platformio/check/cli.py +++ b/platformio/check/cli.py @@ -108,7 +108,7 @@ def cli( "+<%s>" % os.path.basename(config.get("platformio", "include_dir")), ] - src_filters = ( + env_src_filters = ( src_filters or pattern or env_options.get( @@ -120,11 +120,13 @@ def cli( tool_options = dict( verbose=verbose, silent=silent, - src_filters=src_filters, + 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 +144,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 eb5facc8..d608dcce 100644 --- a/platformio/commands/upgrade.py +++ b/platformio/commands/upgrade.py @@ -18,9 +18,10 @@ import subprocess import click -from platformio import VERSION, __install_requires__, __version__, app, exception +from platformio import VERSION, __version__, app, exception from platformio.http import fetch_http_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" @@ -37,7 +38,7 @@ DEVELOP_INIT_SCRIPT_URL = ( @click.option("--verbose", "-v", is_flag=True) def cli(dev, only_dependencies, verbose): if only_dependencies: - return upgrade_pypi_dependencies(verbose) + return upgrade_pip_dependencies(verbose) update_core_packages() @@ -102,7 +103,7 @@ def cli(dev, only_dependencies, verbose): return True -def upgrade_pypi_dependencies(verbose): +def upgrade_pip_dependencies(verbose): subprocess.run( [ get_pythonexe_path(), @@ -111,7 +112,7 @@ def upgrade_pypi_dependencies(verbose): "install", "--upgrade", "pip", - *__install_requires__, + *get_pip_dependencies(), ], check=True, stdout=subprocess.PIPE if not verbose else None, diff --git a/platformio/debug/cli.py b/platformio/debug/cli.py index 5c9375ae..25fb68e1 100644 --- a/platformio/debug/cli.py +++ b/platformio/debug/cli.py @@ -55,7 +55,7 @@ from platformio.project.options import ProjectOptions @click.option("--load-mode", type=ProjectOptions["env.debug_load_mode"].type) @click.option("--verbose", "-v", is_flag=True) @click.option("--interface", type=click.Choice(["gdb"])) -@click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED) +@click.argument("client_extra_args", nargs=-1, type=click.UNPROCESSED) @click.pass_context def cli( ctx, @@ -65,10 +65,13 @@ def cli( load_mode, verbose, interface, - __unprocessed, + client_extra_args, ): app.set_session_var("custom_project_conf", project_conf) + if not interface and client_extra_args: + raise click.UsageError("Please specify debugging interface") + # use env variables from Eclipse or CLion for name in ("CWD", "PWD", "PLATFORMIO_PROJECT_DIR"): if is_platformio_project(project_dir): @@ -92,7 +95,7 @@ def cli( env_name, load_mode, verbose, - __unprocessed, + client_extra_args, ) if helpers.is_gdbmi_mode(): os.environ["PLATFORMIO_DISABLE_PROGRESSBAR"] = "true" @@ -103,19 +106,19 @@ def cli( else: debug_config = _configure(*configure_args) - _run(project_dir, debug_config, __unprocessed) + _run(project_dir, debug_config, client_extra_args) return None -def _configure(ctx, project_config, env_name, load_mode, verbose, __unprocessed): +def _configure(ctx, project_config, env_name, load_mode, verbose, client_extra_args): platform = PlatformFactory.from_env(env_name, autoinstall=True) debug_config = DebugConfigFactory.new( platform, project_config, env_name, ) - if "--version" in __unprocessed: + if "--version" in client_extra_args: raise ReturnErrorCode( subprocess.run( [debug_config.client_executable_path, "--version"], check=True @@ -161,12 +164,12 @@ def _configure(ctx, project_config, env_name, load_mode, verbose, __unprocessed) return debug_config -def _run(project_dir, debug_config, __unprocessed): +def _run(project_dir, debug_config, client_extra_args): loop = asyncio.ProactorEventLoop() if IS_WINDOWS else asyncio.get_event_loop() asyncio.set_event_loop(loop) client = GDBClientProcess(project_dir, debug_config) - coro = client.run(__unprocessed) + coro = client.run(client_extra_args) try: signal.signal(signal.SIGINT, signal.SIG_IGN) loop.run_until_complete(coro) diff --git a/platformio/debug/config/base.py b/platformio/debug/config/base.py index 69122fcf..9cd9dddc 100644 --- a/platformio/debug/config/base.py +++ b/platformio/debug/config/base.py @@ -24,7 +24,9 @@ from platformio.project.options import ProjectOptions class DebugConfigBase: # pylint: disable=too-many-instance-attributes - def __init__(self, platform, project_config, env_name, port=None): + DEFAULT_PORT = None + + def __init__(self, platform, project_config, env_name): self.platform = platform self.project_config = project_config self.env_name = env_name @@ -48,7 +50,6 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes self._load_cmds = None self._port = None - self.port = port self.server = self._configure_server() try: @@ -120,8 +121,10 @@ class DebugConfigBase: # pylint: disable=too-many-instance-attributes @property def port(self): return ( - self.env_options.get("debug_port", self.tool_settings.get("port")) - or self._port + self._port + or self.env_options.get("debug_port") + or self.tool_settings.get("port") + or self.DEFAULT_PORT ) @port.setter @@ -197,9 +200,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/debug/config/factory.py b/platformio/debug/config/factory.py index d7358b92..ea36b6a0 100644 --- a/platformio/debug/config/factory.py +++ b/platformio/debug/config/factory.py @@ -27,17 +27,13 @@ class DebugConfigFactory: @classmethod def new(cls, platform, project_config, env_name): - board_config = platform.board_config( - project_config.get("env:" + env_name, "board") - ) - tool_name = ( - board_config.get_debug_tool_name( - project_config.get("env:" + env_name, "debug_tool") - ) - if board_config - else None - ) + board_id = project_config.get("env:" + env_name, "board") config_cls = None + tool_name = None + if board_id: + tool_name = platform.board_config( + project_config.get("env:" + env_name, "board") + ).get_debug_tool_name(project_config.get("env:" + env_name, "debug_tool")) try: mod = importlib.import_module("platformio.debug.config.%s" % tool_name) config_cls = getattr(mod, cls.get_clsname(tool_name)) diff --git a/platformio/debug/config/generic.py b/platformio/debug/config/generic.py index faa5a341..8f066802 100644 --- a/platformio/debug/config/generic.py +++ b/platformio/debug/config/generic.py @@ -16,6 +16,7 @@ from platformio.debug.config.base import DebugConfigBase class GenericDebugConfig(DebugConfigBase): + DEFAULT_PORT = ":3333" GDB_INIT_SCRIPT = """ define pio_reset_halt_target monitor reset halt @@ -31,8 +32,3 @@ $LOAD_CMDS pio_reset_halt_target $INIT_BREAK """ - - def __init__(self, *args, **kwargs): - if "port" not in kwargs: - kwargs["port"] = ":3333" - super().__init__(*args, **kwargs) diff --git a/platformio/debug/config/jlink.py b/platformio/debug/config/jlink.py index a820e0aa..5e4cc9ce 100644 --- a/platformio/debug/config/jlink.py +++ b/platformio/debug/config/jlink.py @@ -16,6 +16,7 @@ from platformio.debug.config.base import DebugConfigBase class JlinkDebugConfig(DebugConfigBase): + DEFAULT_PORT = ":2331" GDB_INIT_SCRIPT = """ define pio_reset_halt_target monitor reset @@ -36,11 +37,6 @@ $LOAD_CMDS $INIT_BREAK """ - def __init__(self, *args, **kwargs): - if "port" not in kwargs: - kwargs["port"] = ":2331" - super().__init__(*args, **kwargs) - @property def server_ready_pattern(self): return super().server_ready_pattern or ("Waiting for GDB connection") diff --git a/platformio/debug/config/mspdebug.py b/platformio/debug/config/mspdebug.py index 7f4d8e1a..aac67416 100644 --- a/platformio/debug/config/mspdebug.py +++ b/platformio/debug/config/mspdebug.py @@ -16,6 +16,7 @@ from platformio.debug.config.base import DebugConfigBase class MspdebugDebugConfig(DebugConfigBase): + DEFAULT_PORT = ":2000" GDB_INIT_SCRIPT = """ define pio_reset_halt_target end @@ -29,8 +30,3 @@ $LOAD_CMDS pio_reset_halt_target $INIT_BREAK """ - - def __init__(self, *args, **kwargs): - if "port" not in kwargs: - kwargs["port"] = ":2000" - super().__init__(*args, **kwargs) diff --git a/platformio/debug/config/qemu.py b/platformio/debug/config/qemu.py index b998c782..b28a5f34 100644 --- a/platformio/debug/config/qemu.py +++ b/platformio/debug/config/qemu.py @@ -16,6 +16,7 @@ from platformio.debug.config.base import DebugConfigBase class QemuDebugConfig(DebugConfigBase): + DEFAULT_PORT = ":1234" GDB_INIT_SCRIPT = """ define pio_reset_halt_target monitor system_reset @@ -30,8 +31,3 @@ $LOAD_CMDS pio_reset_halt_target $INIT_BREAK """ - - def __init__(self, *args, **kwargs): - if "port" not in kwargs: - kwargs["port"] = ":1234" - super().__init__(*args, **kwargs) diff --git a/platformio/debug/config/renode.py b/platformio/debug/config/renode.py index f2f62d66..de7bba48 100644 --- a/platformio/debug/config/renode.py +++ b/platformio/debug/config/renode.py @@ -16,6 +16,7 @@ from platformio.debug.config.base import DebugConfigBase class RenodeDebugConfig(DebugConfigBase): + DEFAULT_PORT = ":3333" GDB_INIT_SCRIPT = """ define pio_reset_halt_target monitor machine Reset @@ -33,11 +34,6 @@ $INIT_BREAK monitor start """ - def __init__(self, *args, **kwargs): - if "port" not in kwargs: - kwargs["port"] = ":3333" - super().__init__(*args, **kwargs) - @property def server_ready_pattern(self): return super().server_ready_pattern or ( diff --git a/platformio/debug/process/server.py b/platformio/debug/process/server.py index 693dfa44..7724ddf7 100644 --- a/platformio/debug/process/server.py +++ b/platformio/debug/process/server.py @@ -62,7 +62,9 @@ class DebugServerProcess(DebugBaseProcess): openocd_pipe_allowed = all( [ - not self.debug_config.env_options.get("debug_port"), + not self.debug_config.env_options.get( + "debug_port", self.debug_config.tool_settings.get("port") + ), "gdb" in self.debug_config.client_executable_path, "openocd" in server_executable, ] 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..87f64449 100644 --- a/platformio/device/monitor/command.py +++ b/platformio/device/monitor/command.py @@ -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/fs.py b/platformio/fs.py index bb38a723..8fdc2e0f 100644 --- a/platformio/fs.py +++ b/platformio/fs.py @@ -211,7 +211,7 @@ def change_filemtime(path, mtime): def rmtree(path): - def _onerror(func, path, __): + def _onexc(func, path, _): try: st_mode = os.stat(path).st_mode if st_mode & stat.S_IREAD: @@ -224,4 +224,7 @@ def rmtree(path): err=True, ) - return shutil.rmtree(path, onerror=_onerror) + # pylint: disable=unexpected-keyword-arg, deprecated-argument + if sys.version_info < (3, 12): + return shutil.rmtree(path, onerror=_onexc) + return shutil.rmtree(path, onexc=_onexc) diff --git a/platformio/home/rpc/handlers/memusage.py b/platformio/home/rpc/handlers/memusage.py index 23a1af1c..c8d3edda 100644 --- a/platformio/home/rpc/handlers/memusage.py +++ b/platformio/home/rpc/handlers/memusage.py @@ -63,9 +63,9 @@ class MemUsageRPC(BaseRPCHandler): device=current_report["device"], trend=dict( current=current_report["memory"]["total"], - previous=previous_report["memory"]["total"] - if previous_report - else None, + previous=( + previous_report["memory"]["total"] if previous_report else None + ), ), top=dict( files=self._calculate_top_files(current_report["memory"]["files"])[ diff --git a/platformio/home/rpc/handlers/project.py b/platformio/home/rpc/handlers/project.py index c12295fa..f8182ca5 100644 --- a/platformio/home/rpc/handlers/project.py +++ b/platformio/home/rpc/handlers/project.py @@ -140,15 +140,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 e7de99e1..4ca3ad33 100644 --- a/platformio/http.py +++ b/platformio/http.py @@ -15,7 +15,6 @@ import contextlib import itertools import json -import os import socket import time @@ -24,6 +23,7 @@ import httpx from platformio import __check_internet_hosts__, app, util from platformio.cache import ContentCache, cleanup_content_cache from platformio.exception import PlatformioException, UserSideException +from platformio.pipdeps import is_proxy_set RETRIES_BACKOFF_FACTOR = 2 # 0s, 2s, 4s, 8s, etc. RETRIES_METHOD_WHITELIST = ["GET"] @@ -245,9 +245,7 @@ def _internet_on(): socket.setdefaulttimeout(timeout) for host in __check_internet_hosts__: try: - for var in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"): - if not os.getenv(var) and not os.getenv(var.lower()): - continue + if is_proxy_set(): httpx.get("http://%s" % host, follow_redirects=False, timeout=timeout) return True # try to resolve `host` for both AF_INET and AF_INET6, and then try to connect diff --git a/platformio/package/commands/install.py b/platformio/package/commands/install.py index 82c377d0..8c987c17 100644 --- a/platformio/package/commands/install.py +++ b/platformio/package/commands/install.py @@ -20,6 +20,7 @@ import click from platformio import fs from platformio.package.exception import UnknownPackageError +from platformio.package.manager.core import get_core_package_dir from platformio.package.manager.library import LibraryPackageManager from platformio.package.manager.platform import PlatformPackageManager from platformio.package.manager.tool import ToolPackageManager @@ -120,7 +121,7 @@ def install_project_env_dependencies(project_env, options=None): # custom tools if options.get("tools"): installed_conds.append(_install_project_env_custom_tools(project_env, options)) - # custom ibraries + # custom libraries if options.get("libraries"): installed_conds.append( _install_project_env_custom_libraries(project_env, options) @@ -152,6 +153,8 @@ def _install_project_env_platform(project_env, options): skip_dependencies=options.get("skip_dependencies"), force=options.get("force"), ) + # ensure SCons is installed + get_core_package_dir("tool-scons") return not already_up_to_date @@ -219,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 e5186517..428e18c0 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 with AccountClient() as client: owner = owner or client.get_logged_username() diff --git a/platformio/package/commands/search.py b/platformio/package/commands/search.py index d483595d..c3fc38cc 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..125766d3 100644 --- a/platformio/package/manager/_install.py +++ b/platformio/package/manager/_install.py @@ -98,9 +98,9 @@ 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() if compatibility else None + ), ) if not pkg or not pkg.metadata: diff --git a/platformio/package/manifest/parser.py b/platformio/package/manifest/parser.py index c3c6df2a..bac18fcc 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, ) @@ -540,6 +542,8 @@ class LibraryPropertiesManifestParser(BaseManifestParser): "esp32": "espressif32", "arc32": "intel_arc32", "stm32": "ststm32", + "nrf52": "nordicnrf52", + "rp2040": "raspberrypi", } for arch in properties.get("architectures", "").split(","): if "particle-" in arch: diff --git a/platformio/package/manifest/schema.py b/platformio/package/manifest/schema.py index 718a3b67..c3a23528 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.21" + version = "3.22" 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..b9e3750d 100644 --- a/platformio/package/meta.py +++ b/platformio/package/meta.py @@ -485,9 +485,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/pipdeps.py b/platformio/pipdeps.py new file mode 100644 index 00000000..17094b59 --- /dev/null +++ b/platformio/pipdeps.py @@ -0,0 +1,71 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import platform +import sys + +PY36 = sys.version_info[0:2] == (3, 6) + + +def get_pip_dependencies(): + core = [ + "bottle == 0.12.*", + "click >=8.0.4, <9", + "colorama", + "httpx%s >=0.22.0, <0.27" % ("[socks]" if is_proxy_set(socks=True) else ""), + "marshmallow == 3.*", + "pyelftools >=0.27, <1", + "pyserial == 3.5.*", # keep in sync "device/monitor/terminal.py" + "semantic_version == 2.10.*", + "tabulate == 0.*", + ] + + home = [ + # PIO Home requirements + "ajsonrpc == 1.2.*", + "starlette >=0.19, <0.36", + "uvicorn %s" % ("== 0.16.0" if PY36 else ">=0.16, <0.28"), + "wsproto == 1.*", + ] + + extra = [] + + # issue #4702; Broken "requests/charset_normalizer" on macOS ARM + if platform.system() == "Darwin" and "arm" in platform.machine().lower(): + extra.append("chardet>=3.0.2,<6") + + # issue 4614: urllib3 v2.0 only supports OpenSSL 1.1.1+ + try: + import ssl # pylint: disable=import-outside-toplevel + + if ssl.OPENSSL_VERSION.startswith("OpenSSL ") and ssl.OPENSSL_VERSION_INFO < ( + 1, + 1, + 1, + ): + extra.append("urllib3<2") + except ImportError: + 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/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/platform/base.py b/platformio/platform/base.py index 51b02216..0d2d57a9 100644 --- a/platformio/platform/base.py +++ b/platformio/platform/base.py @@ -169,6 +169,7 @@ class PlatformBase( # pylint: disable=too-many-instance-attributes,too-many-pub return self._BOARDS_CACHE[id_] if id_ else self._BOARDS_CACHE def board_config(self, id_): + assert id_ return self.get_boards(id_) def get_package_type(self, name): diff --git a/platformio/proc.py b/platformio/proc.py index acfce4d9..707245a1 100644 --- a/platformio/proc.py +++ b/platformio/proc.py @@ -69,7 +69,7 @@ class BuildAsyncPipe(AsyncPipeBase): print_immediately = False for char in iter(lambda: self._pipe_reader.read(1), ""): - self._buffer += char + # self._buffer += char if line and char.strip() and line[-3:] == (char * 3): print_immediately = True 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/config.py b/platformio/project/config.py index 8020fdd3..c583c0d2 100644 --- a/platformio/project/config.py +++ b/platformio/project/config.py @@ -14,14 +14,16 @@ import configparser import glob +import hashlib import json import os import re +import time import click from platformio import fs -from platformio.compat import MISSING, string_types +from platformio.compat import MISSING, hashlib_encode_data, string_types from platformio.project import exception from platformio.project.options import ProjectOptions @@ -41,7 +43,17 @@ CONFIG_HEADER = """ class ProjectConfigBase: ENVNAME_RE = re.compile(r"^[a-z\d\_\-]+$", flags=re.I) INLINE_COMMENT_RE = re.compile(r"\s+;.*$") - VARTPL_RE = re.compile(r"\$\{([^\.\}\()]+)\.([^\}]+)\}") + VARTPL_RE = re.compile(r"\$\{(?:([^\.\}\()]+)\.)?([^\}]+)\}") + + BUILTIN_VARS = { + "PROJECT_DIR": lambda: os.getcwd(), # pylint: disable=unnecessary-lambda + "PROJECT_HASH": lambda: "%s-%s" + % ( + os.path.basename(os.getcwd()), + hashlib.sha1(hashlib_encode_data(os.getcwd())).hexdigest()[:10], + ), + "UNIX_TIME": lambda: str(int(time.time())), + } CUSTOM_OPTION_PREFIXES = ("custom_", "board_") @@ -152,6 +164,7 @@ class ProjectConfigBase: @staticmethod def get_section_scope(section): + assert section return section.split(":", 1)[0] if ":" in section else section def walk_options(self, root_section): @@ -274,7 +287,7 @@ class ProjectConfigBase: value = ( default if default != MISSING else self._parser.get(section, option) ) - return self._expand_interpolations(section, value) + return self._expand_interpolations(section, option, value) if option_meta.sysenvvar: envvar_value = os.getenv(option_meta.sysenvvar) @@ -297,24 +310,50 @@ class ProjectConfigBase: if value == MISSING: return None - return self._expand_interpolations(section, value) + return self._expand_interpolations(section, option, value) - def _expand_interpolations(self, parent_section, value): - if ( - not value - or not isinstance(value, string_types) - or not all(["${" in value, "}" in value]) - ): + def _expand_interpolations(self, section, option, value): + if not value or not isinstance(value, string_types) or not "$" in value: + return value + + # legacy support for variables delclared without "${}" + legacy_vars = ["PROJECT_HASH"] + stop = False + while not stop: + stop = True + for name in legacy_vars: + x = value.find(f"${name}") + if x < 0 or value[x - 1] == "$": + continue + value = "%s${%s}%s" % (value[:x], name, value[x + len(name) + 1 :]) + stop = False + warn_msg = ( + "Invalid variable declaration. Please use " + f"`${{{name}}}` instead of `${name}`" + ) + if warn_msg not in self.warnings: + self.warnings.append(warn_msg) + + if not all(["${" in value, "}" in value]): return value return self.VARTPL_RE.sub( - lambda match: self._re_interpolation_handler(parent_section, match), value + lambda match: self._re_interpolation_handler(section, option, match), value ) - def _re_interpolation_handler(self, parent_section, match): + def _re_interpolation_handler(self, parent_section, parent_option, match): section, option = match.group(1), match.group(2) + + # handle built-in variables + if section is None: + if option in self.BUILTIN_VARS: + return self.BUILTIN_VARS[option]() + # SCons varaibles + return f"${{{option}}}" + # handle system environment variables if section == "sysenv": return os.getenv(option) + # handle ${this.*} if section == "this": section = parent_section @@ -322,21 +361,18 @@ class ProjectConfigBase: if not parent_section.startswith("env:"): raise exception.ProjectOptionValueError( f"`${{this.__env__}}` is called from the `{parent_section}` " - "section that is not valid PlatformIO environment, see", - option, - " ", - section, + "section that is not valid PlatformIO environment. Please " + f"check `{parent_option}` option in the `{section}` section" ) return parent_section[4:] + # handle nested calls try: value = self.get(section, option) except RecursionError as exc: raise exception.ProjectOptionValueError( - "Infinite recursion has been detected", - option, - " ", - section, + f"Infinite recursion has been detected for `{option}` " + f"option in the `{section}` section" ) from exc if isinstance(value, list): return "\n".join(value) @@ -363,10 +399,8 @@ class ProjectConfigBase: if not self.expand_interpolations: return value raise exception.ProjectOptionValueError( - exc.format_message(), - option, - " (%s) " % option_meta.description, - section, + "%s for `%s` option in the `%s` section (%s)" + % (exc.format_message(), option, section, option_meta.description) ) @staticmethod @@ -439,8 +473,9 @@ class ProjectConfigLintMixin: try: config = cls.get_instance(path) config.validate(silent=True) - warnings = config.warnings + warnings = config.warnings # in case "as_tuple" fails config.as_tuple() + warnings = config.warnings except Exception as exc: # pylint: disable=broad-exception-caught if exc.__cause__ is not None: exc = exc.__cause__ diff --git a/platformio/project/exception.py b/platformio/project/exception.py index 3821c865..cf97c69e 100644 --- a/platformio/project/exception.py +++ b/platformio/project/exception.py @@ -51,4 +51,4 @@ class InvalidEnvNameError(ProjectError, UserSideException): class ProjectOptionValueError(ProjectError, UserSideException): - MESSAGE = "{0} for option `{1}`{2}in section [{3}]" + pass diff --git a/platformio/project/integration/generator.py b/platformio/project/integration/generator.py index d64de579..b9397567 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/integration/tpls/vscode/.vscode/launch.json.tpl b/platformio/project/integration/tpls/vscode/.vscode/launch.json.tpl index 4bd188d2..03906ccd 100644 --- a/platformio/project/integration/tpls/vscode/.vscode/launch.json.tpl +++ b/platformio/project/integration/tpls/vscode/.vscode/launch.json.tpl @@ -1,4 +1,3 @@ -% import codecs % import json % import os % @@ -47,9 +46,14 @@ % return data % end % -% def _contains_external_configurations(launch_config): +% def _contains_custom_configurations(launch_config): +% pio_config_names = [ +% c["name"] +% for c in get_pio_configurations() +% ] % return any( % c.get("type", "") != "platformio-debug" +% or c.get("name", "") in pio_config_names % for c in launch_config.get("configurations", []) % ) % end @@ -59,10 +63,14 @@ % return launch_config % end % +% pio_config_names = [ +% c["name"] +% for c in get_pio_configurations() +% ] % external_configurations = [ -% config -% for config in launch_config["configurations"] -% if config.get("type", "") != "platformio-debug" +% c +% for c in launch_config["configurations"] +% if c.get("type", "") != "platformio-debug" or c.get("name", "") not in pio_config_names % ] % % launch_config["configurations"] = external_configurations @@ -73,11 +81,11 @@ % launch_config = {"version": "0.2.0", "configurations": []} % launch_file = os.path.join(project_dir, ".vscode", "launch.json") % if os.path.isfile(launch_file): -% with codecs.open(launch_file, "r", encoding="utf8") as fp: +% with open(launch_file, "r", encoding="utf8") as fp: % launch_data = _remove_comments(fp.readlines()) % try: % prev_config = json.loads(launch_data) -% if _contains_external_configurations(prev_config): +% if _contains_custom_configurations(prev_config): % launch_config = _remove_pio_configurations(prev_config) % end % except: @@ -91,9 +99,9 @@ % // AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY // -// PIO Unified Debugger +// PlatformIO Debugging Solution // -// Documentation: https://docs.platformio.org/page/plus/debugging.html -// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html +// Documentation: https://docs.platformio.org/en/latest/plus/debugging.html +// Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html {{ json.dumps(get_launch_configuration(), indent=4, ensure_ascii=False) }} diff --git a/platformio/project/options.py b/platformio/project/options.py index 51681ee6..51611d24 100644 --- a/platformio/project/options.py +++ b/platformio/project/options.py @@ -14,14 +14,13 @@ # pylint: disable=redefined-builtin, too-many-arguments -import hashlib import os from collections import OrderedDict import click from platformio import fs -from platformio.compat import IS_WINDOWS, hashlib_encode_data +from platformio.compat import IS_WINDOWS class ConfigOption: # pylint: disable=too-many-instance-attributes @@ -80,30 +79,6 @@ def ConfigEnvOption(*args, **kwargs): return ConfigOption("env", *args, **kwargs) -def calculate_path_hash(path): - return "%s-%s" % ( - os.path.basename(path), - hashlib.sha1(hashlib_encode_data(path.lower())).hexdigest()[:10], - ) - - -def expand_dir_templates(path): - project_dir = os.getcwd() - tpls = { - "$PROJECT_DIR": lambda: project_dir, - "$PROJECT_HASH": lambda: calculate_path_hash(project_dir), - } - done = False - while not done: - done = True - for tpl, cb in tpls.items(): - if tpl not in path: - continue - path = path.replace(tpl, cb()) - done = False - return path - - def validate_dir(path): if not path: return path @@ -112,8 +87,6 @@ def validate_dir(path): return path if path.startswith("~"): path = fs.expanduser(path) - if "$" in path: - path = expand_dir_templates(path) return os.path.abspath(path) @@ -137,6 +110,7 @@ ProjectOptions = OrderedDict( group="generic", name="name", description="A project name", + default=lambda: os.path.basename(os.getcwd()), ), ConfigPlatformioOption( group="generic", @@ -240,7 +214,7 @@ ProjectOptions = OrderedDict( "external library dependencies" ), sysenvvar="PLATFORMIO_WORKSPACE_DIR", - default=os.path.join("$PROJECT_DIR", ".pio"), + default=os.path.join("${PROJECT_DIR}", ".pio"), validate=validate_dir, ), ConfigPlatformioOption( @@ -266,17 +240,6 @@ ProjectOptions = OrderedDict( default=os.path.join("${platformio.workspace_dir}", "libdeps"), validate=validate_dir, ), - ConfigPlatformioOption( - group="directory", - name="memusage_dir", - description=( - "A location where PlatformIO Core will store " - "project memory usage reports" - ), - sysenvvar="PLATFORMIO_MEMUSAGE_DIR", - default=os.path.join("${platformio.workspace_dir}", "memusage"), - validate=validate_dir, - ), ConfigPlatformioOption( group="directory", name="include_dir", @@ -285,7 +248,7 @@ ProjectOptions = OrderedDict( "System automatically adds this path to CPPPATH scope" ), sysenvvar="PLATFORMIO_INCLUDE_DIR", - default=os.path.join("$PROJECT_DIR", "include"), + default=os.path.join("${PROJECT_DIR}", "include"), validate=validate_dir, ), ConfigPlatformioOption( @@ -296,7 +259,7 @@ ProjectOptions = OrderedDict( "project C/C++ source files" ), sysenvvar="PLATFORMIO_SRC_DIR", - default=os.path.join("$PROJECT_DIR", "src"), + default=os.path.join("${PROJECT_DIR}", "src"), validate=validate_dir, ), ConfigPlatformioOption( @@ -304,7 +267,7 @@ ProjectOptions = OrderedDict( name="lib_dir", description="A storage for the custom/private project libraries", sysenvvar="PLATFORMIO_LIB_DIR", - default=os.path.join("$PROJECT_DIR", "lib"), + default=os.path.join("${PROJECT_DIR}", "lib"), validate=validate_dir, ), ConfigPlatformioOption( @@ -315,7 +278,7 @@ ProjectOptions = OrderedDict( "file system (SPIFFS, etc.)" ), sysenvvar="PLATFORMIO_DATA_DIR", - default=os.path.join("$PROJECT_DIR", "data"), + default=os.path.join("${PROJECT_DIR}", "data"), validate=validate_dir, ), ConfigPlatformioOption( @@ -326,7 +289,7 @@ ProjectOptions = OrderedDict( "test source files" ), sysenvvar="PLATFORMIO_TEST_DIR", - default=os.path.join("$PROJECT_DIR", "test"), + default=os.path.join("${PROJECT_DIR}", "test"), validate=validate_dir, ), ConfigPlatformioOption( @@ -334,7 +297,7 @@ ProjectOptions = OrderedDict( name="boards_dir", description="A storage for custom board manifests", sysenvvar="PLATFORMIO_BOARDS_DIR", - default=os.path.join("$PROJECT_DIR", "boards"), + default=os.path.join("${PROJECT_DIR}", "boards"), validate=validate_dir, ), ConfigPlatformioOption( @@ -342,7 +305,7 @@ ProjectOptions = OrderedDict( name="monitor_dir", description="A storage for custom monitor filters", sysenvvar="PLATFORMIO_MONITOR_DIR", - default=os.path.join("$PROJECT_DIR", "monitor"), + default=os.path.join("${PROJECT_DIR}", "monitor"), validate=validate_dir, ), ConfigPlatformioOption( @@ -353,7 +316,7 @@ ProjectOptions = OrderedDict( "synchronize extra files between remote machines" ), sysenvvar="PLATFORMIO_SHARED_DIR", - default=os.path.join("$PROJECT_DIR", "shared"), + default=os.path.join("${PROJECT_DIR}", "shared"), validate=validate_dir, ), # diff --git a/platformio/registry/access/commands/list.py b/platformio/registry/access/commands/list.py index a5bd72d8..6f3019a2 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 3e8e400f..97975064 100644 --- a/platformio/registry/mirror.py +++ b/platformio/registry/mirror.py @@ -53,9 +53,11 @@ class RegistryFileMirrorIterator: "head", self._url_parts.path, follow_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=registry.allowed_private_packages(), ) stop_conditions = [ @@ -91,7 +93,7 @@ class RegistryFileMirrorIterator: endpoint = f"https://dl.{host}" if endpoint not in endpoints: endpoints.append(endpoint) - RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[ - self._mirror - ] = RegistryClient(endpoints) + RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror] = ( + RegistryClient(endpoints) + ) return RegistryFileMirrorIterator.HTTP_CLIENT_INSTANCES[self._mirror] 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/reports/json.py b/platformio/test/reports/json.py index 791224c7..3d3c6f6e 100644 --- a/platformio/test/reports/json.py +++ b/platformio/test/reports/json.py @@ -62,11 +62,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/util.py b/platformio/util.py index 004ac5c9..54db102f 100644 --- a/platformio/util.py +++ b/platformio/util.py @@ -143,6 +143,8 @@ def get_systype(): arch = "x86_" + platform.architecture()[0] if "x86" in arch: arch = "amd64" if "64" in arch else "x86" + if arch == "aarch64" and platform.architecture()[0] == "32bit": + arch = "armv7l" return "%s_%s" % (system, arch) if arch else system @@ -168,9 +170,8 @@ def items_in_list(needle, haystack): def parse_datetime(datestr): - if "T" in datestr and "Z" in datestr: - return datetime.datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ") - return datetime.datetime.strptime(datestr) + assert "T" in datestr and "Z" in datestr + return datetime.datetime.strptime(datestr, "%Y-%m-%dT%H:%M:%SZ") def merge_dicts(d1, d2, path=None): diff --git a/scripts/docspregen.py b/scripts/docspregen.py index a4d56c03..1b595b3f 100644 --- a/scripts/docspregen.py +++ b/scripts/docspregen.py @@ -39,7 +39,7 @@ RST_COPYRIGHT = """.. Copyright (c) 2014-present PlatformIO + +[env:check_sources] +platform = native + +[env:check_tests] +platform = native +check_src_filters = + + + """ + tmpdir.join("platformio.ini").write(config) + + src_dir = tmpdir.mkdir("src") + src_dir.join("main.cpp").write(TEST_CODE) + src_dir.mkdir("spi").join("spi.cpp").write(TEST_CODE) + tmpdir.mkdir("test").join("test.cpp").write(TEST_CODE) + + result = clirunner.invoke( + cmd_check, ["--project-dir", str(tmpdir), "-e", "check_tests"] + ) + validate_cliresult(result) + + errors, warnings, style = count_defects(result.output) + + assert errors + warnings + style == EXPECTED_DEFECTS + assert "test.cpp" in result.output + assert "main.cpp" not in result.output diff --git a/tests/package/test_manager.py b/tests/package/test_manager.py index c1de90b1..fa3f215c 100644 --- a/tests/package/test_manager.py +++ b/tests/package/test_manager.py @@ -651,4 +651,4 @@ def test_update_without_metadata(isolated_pio_core, tmpdir_factory): lm.set_log_level(logging.ERROR) new_pkg = lm.update(pkg) assert len(lm.get_installed()) == 4 - assert new_pkg.metadata.spec.owner == "ottowinter" + assert new_pkg.metadata.spec.owner == "heman" diff --git a/tests/project/test_config.py b/tests/project/test_config.py index 6b742855..02741b95 100644 --- a/tests/project/test_config.py +++ b/tests/project/test_config.py @@ -33,7 +33,6 @@ BASE_CONFIG = """ [platformio] env_default = base, extra_2 src_dir = ${custom.src_dir} -build_dir = ${custom.build_dir} extra_configs = extra_envs.ini extra_debug.ini @@ -61,7 +60,6 @@ build_flags = -D RELEASE [custom] src_dir = source -build_dir = ~/tmp/pio-$PROJECT_HASH debug_flags = -D RELEASE lib_flags = -lc -lm extra_flags = ${sysenv.__PIO_TEST_CNF_EXTRA_FLAGS} @@ -319,7 +317,6 @@ def test_getraw_value(config): config.getraw("custom", "debug_server") == f"\n{packages_dir}/tool-openocd/openocd\n--help" ) - assert config.getraw("platformio", "build_dir") == "~/tmp/pio-$PROJECT_HASH" # renamed option assert config.getraw("env:extra_1", "lib_install") == "574" @@ -360,7 +357,6 @@ def test_get_value(config): assert config.get("platformio", "src_dir") == os.path.abspath( os.path.join(os.getcwd(), "source") ) - assert "$PROJECT_HASH" not in config.get("platformio", "build_dir") # renamed option assert config.get("env:extra_1", "lib_install") == ["574"] @@ -371,7 +367,6 @@ def test_get_value(config): def test_items(config): assert config.items("custom") == [ ("src_dir", "source"), - ("build_dir", "~/tmp/pio-$PROJECT_HASH"), ("debug_flags", "-D DEBUG=1"), ("lib_flags", "-lc -lm"), ("extra_flags", ""), @@ -525,7 +520,6 @@ def test_dump(tmpdir_factory): [ ("env_default", ["base", "extra_2"]), ("src_dir", "${custom.src_dir}"), - ("build_dir", "${custom.build_dir}"), ("extra_configs", ["extra_envs.ini", "extra_debug.ini"]), ], ), @@ -549,7 +543,6 @@ def test_dump(tmpdir_factory): "custom", [ ("src_dir", "source"), - ("build_dir", "~/tmp/pio-$PROJECT_HASH"), ("debug_flags", "-D RELEASE"), ("lib_flags", "-lc -lm"), ("extra_flags", "${sysenv.__PIO_TEST_CNF_EXTRA_FLAGS}"), @@ -631,26 +624,66 @@ custom_option = ${this.board} assert config.get("env:myenv", "build_flags") == ["-Dmyenv"] +def test_project_name(tmp_path: Path): + project_dir = tmp_path / "my-project-name" + project_dir.mkdir() + project_conf = project_dir / "platformio.ini" + project_conf.write_text( + """ +[env:myenv] + """ + ) + with fs.cd(str(project_dir)): + config = ProjectConfig(str(project_conf)) + assert config.get("platformio", "name") == "my-project-name" + + # custom name + project_conf.write_text( + """ +[platformio] +name = custom-project-name + """ + ) + config = ProjectConfig(str(project_conf)) + assert config.get("platformio", "name") == "custom-project-name" + + def test_nested_interpolation(tmp_path: Path): project_conf = tmp_path / "platformio.ini" project_conf.write_text( """ [platformio] -build_dir = ~/tmp/pio-$PROJECT_HASH +build_dir = /tmp/pio-$PROJECT_HASH +data_dir = $PROJECT_DIR/assets [env:myenv] +build_flags = + -D UTIME=${UNIX_TIME} + -I ${PROJECTSRC_DIR}/hal + -Wl,-Map,${BUILD_DIR}/${PROGNAME}.map test_testing_command = ${platformio.packages_dir}/tool-simavr/bin/simavr -m atmega328p -f 16000000L + ${UPLOAD_PORT and "-p "+UPLOAD_PORT} ${platformio.build_dir}/${this.__env__}/firmware.elf """ ) config = ProjectConfig(str(project_conf)) + assert config.get("platformio", "data_dir").endswith( + os.path.join("$PROJECT_DIR", "assets") + ) + assert config.get("env:myenv", "build_flags")[0][-10:].isdigit() + assert config.get("env:myenv", "build_flags")[1] == "-I ${PROJECTSRC_DIR}/hal" + assert ( + config.get("env:myenv", "build_flags")[2] + == "-Wl,-Map,${BUILD_DIR}/${PROGNAME}.map" + ) testing_command = config.get("env:myenv", "test_testing_command") - assert "$" not in " ".join(testing_command) + assert "$" not in testing_command[0] + assert testing_command[5] == '${UPLOAD_PORT and "-p "+UPLOAD_PORT}' def test_extends_order(tmp_path: Path): @@ -707,11 +740,16 @@ def test_linting_warnings(tmp_path: Path): project_conf = tmp_path / "platformio.ini" project_conf.write_text( """ +[platformio] +build_dir = /tmp/pio-$PROJECT_HASH + [env:app1] lib_use = 1 +test_testing_command = /usr/bin/flash-tool -p $UPLOAD_PORT -b $UPLOAD_SPEED """ ) result = ProjectConfig.lint(str(project_conf)) assert not result["errors"] - assert result["warnings"] and len(result["warnings"]) == 1 + assert result["warnings"] and len(result["warnings"]) == 2 assert "deprecated" in result["warnings"][0] + assert "Invalid variable declaration" in result["warnings"][1] diff --git a/tests/project/test_metadata.py b/tests/project/test_metadata.py new file mode 100644 index 00000000..a536dd72 --- /dev/null +++ b/tests/project/test_metadata.py @@ -0,0 +1,82 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json + +from platformio.project.commands.metadata import project_metadata_cmd + + +def test_metadata_dump(clirunner, validate_cliresult, tmpdir): + tmpdir.join("platformio.ini").write( + """ +[env:native] +platform = native +""" + ) + + component_dir = tmpdir.mkdir("lib").mkdir("component") + component_dir.join("library.json").write( + """ +{ + "name": "component", + "version": "1.0.0" +} + """ + ) + component_dir.mkdir("include").join("component.h").write( + """ +#define I_AM_COMPONENT + +void dummy(void); + """ + ) + component_dir.mkdir("src").join("component.cpp").write( + """ +#include + +void dummy(void ) {}; + """ + ) + + tmpdir.mkdir("src").join("main.c").write( + """ +#include + +#ifndef I_AM_COMPONENT +#error "I_AM_COMPONENT" +#endif + +int main() { +} +""" + ) + + metadata_path = tmpdir.join("metadata.json") + result = clirunner.invoke( + project_metadata_cmd, + [ + "--project-dir", + str(tmpdir), + "-e", + "native", + "--json-output", + "--json-output-path", + str(metadata_path), + ], + ) + validate_cliresult(result) + with open(str(metadata_path), encoding="utf8") as fp: + metadata = json.load(fp)["native"] + assert len(metadata["includes"]["build"]) == 3 + assert len(metadata["includes"]["compatlib"]) == 2 diff --git a/tox.ini b/tox.ini index 2eda9b90..02b96152 100644 --- a/tox.ini +++ b/tox.ini @@ -55,7 +55,7 @@ commands = [testenv:docs] deps = sphinx - sphinx-rtd-theme==1.2.2 + sphinx-rtd-theme==2.0.0 sphinx-notfound-page sphinx-copybutton restructuredtext-lint