From ce62514a1700f3b48f3725e244cbaf5a8ac3c192 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Sat, 14 May 2022 16:31:08 +0300 Subject: [PATCH] Resolve project dependencies with `pio project init` command --- HISTORY.rst | 1 + docs | 2 +- platformio/project/commands/init.py | 117 +++++++++---------- tests/commands/test_init.py | 170 ++++++++++++++++------------ 4 files changed, 158 insertions(+), 132 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index ddf6f7dd..09d43582 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -87,6 +87,7 @@ Please check the `Migration guide from 5.x to 6.0 `_) - Changed a default path for compilation database `compile_commands.json `__ to the project root + - Enhanced integration for Qt Creator (`issue #3046 `_) * **Project Configuration** diff --git a/docs b/docs index a9aad82c..def7ca7a 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit a9aad82cb9be70aec1ea3c9864491e387976acd6 +Subproject commit def7ca7a230292f94be4579b6c46b3c2d2f938b6 diff --git a/platformio/project/commands/init.py b/platformio/project/commands/init.py index 7e72a67c..7c3b77cb 100644 --- a/platformio/project/commands/init.py +++ b/platformio/project/commands/init.py @@ -21,7 +21,7 @@ import os import click from platformio import fs -from platformio.commands.platform import platform_install as cli_platform_install +from platformio.package.commands.install import install_project_dependencies from platformio.package.manager.platform import PlatformPackageManager from platformio.platform.exception import UnknownBoard from platformio.project.config import ProjectConfig @@ -53,61 +53,47 @@ def validate_boards(ctx, param, value): # pylint: disable=W0613 ) @click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards) @click.option("--ide", type=click.Choice(ProjectGenerator.get_supported_ides())) -@click.option("-e", "--environment", help="Update using existing environment") +@click.option("-e", "--environment", help="Update existing environment") @click.option("-O", "--project-option", multiple=True) @click.option("--env-prefix", default="") +@click.option("--no-install-dependencies", is_flag=True) @click.option("-s", "--silent", is_flag=True) -@click.pass_context def project_init_cmd( - ctx, project_dir, board, ide, environment, project_option, env_prefix, + no_install_dependencies, silent, ): is_new_project = not is_platformio_project(project_dir) - if not silent and is_new_project: - if project_dir == os.getcwd(): - click.secho("\nThe current working directory ", fg="yellow", nl=False) - try: - click.secho(project_dir, fg="cyan", nl=False) - except UnicodeEncodeError: - click.secho(json.dumps(project_dir), fg="cyan", nl=False) - click.secho(" will be used for the project.", fg="yellow") - click.echo("") - - click.echo("The next files/directories have been created in ", nl=False) - try: - click.secho(project_dir, fg="cyan") - except UnicodeEncodeError: - click.secho(json.dumps(project_dir), fg="cyan") - click.echo( - "%s - Put project header files here" % click.style("include", fg="cyan") - ) - click.echo( - "%s - Put here project specific (private) libraries" - % click.style("lib", fg="cyan") - ) - click.echo("%s - Put project source files here" % click.style("src", fg="cyan")) - click.echo( - "%s - Project Configuration File" % click.style("platformio.ini", fg="cyan") - ) - if is_new_project: + if not silent: + print_header(project_dir) init_base_project(project_dir) if environment: update_project_env(project_dir, environment, project_option) elif board: - update_board_envs( - ctx, project_dir, board, project_option, env_prefix, ide is not None + update_board_envs(project_dir, board, project_option, env_prefix) + + # resolve project dependencies + if not no_install_dependencies and (environment or board): + install_project_dependencies( + options=dict( + project_dir=project_dir, + environments=[environment] if environment else [], + silent=silent, + ) ) if ide: - click.echo("Updating metadata for the %s IDE..." % click.style(ide, fg="cyan")) + if not silent: + click.echo( + "Updating metadata for the %s IDE..." % click.style(ide, fg="cyan") + ) with fs.cd(project_dir): config = ProjectConfig.get_instance( os.path.join(project_dir, "platformio.ini") @@ -118,11 +104,39 @@ def project_init_cmd( if is_new_project: init_cvs_ignore(project_dir) - if silent: - return + if not silent: + print_footer(is_new_project) + +def print_header(project_dir): + if project_dir == os.getcwd(): + click.secho("\nThe current working directory ", fg="yellow", nl=False) + try: + click.secho(project_dir, fg="cyan", nl=False) + except UnicodeEncodeError: + click.secho(json.dumps(project_dir), fg="cyan", nl=False) + click.secho(" will be used for the project.", fg="yellow") + click.echo("") + + click.echo("The next files/directories have been created in ", nl=False) + try: + click.secho(project_dir, fg="cyan") + except UnicodeEncodeError: + click.secho(json.dumps(project_dir), fg="cyan") + click.echo("%s - Put project header files here" % click.style("include", fg="cyan")) + click.echo( + "%s - Put here project specific (private) libraries" + % click.style("lib", fg="cyan") + ) + click.echo("%s - Put project source files here" % click.style("src", fg="cyan")) + click.echo( + "%s - Project Configuration File" % click.style("platformio.ini", fg="cyan") + ) + + +def print_footer(is_new_project): if is_new_project: - click.secho( + return click.secho( "\nProject has been successfully initialized! Useful commands:\n" "`pio run` - process/build project from the current directory\n" "`pio run --target upload` or `pio run -t upload` " @@ -131,11 +145,10 @@ def project_init_cmd( "\n`pio run --help` - additional information", fg="green", ) - else: - click.secho( - "Project has been successfully updated!", - fg="green", - ) + return click.secho( + "Project has been successfully updated!", + fg="green", + ) def init_base_project(project_dir): @@ -281,9 +294,7 @@ def init_cvs_ignore(project_dir): fp.write(".pio\n") -def update_board_envs( - ctx, project_dir, board_ids, project_option, env_prefix, force_download -): +def update_board_envs(project_dir, board_ids, project_option, env_prefix): config = ProjectConfig( os.path.join(project_dir, "platformio.ini"), parse_extra=False ) @@ -294,11 +305,9 @@ def update_board_envs( used_boards.append(config.get(section, "board")) pm = PlatformPackageManager() - used_platforms = [] modified = False for id_ in board_ids: board_config = pm.board_config(id_) - used_platforms.append(board_config["platform"]) if id_ in used_boards: continue used_boards.append(id_) @@ -322,24 +331,10 @@ def update_board_envs( for option, value in envopts.items(): config.set(section, option, value) - if force_download and used_platforms: - _install_dependent_platforms(ctx, used_platforms) - if modified: config.save() -def _install_dependent_platforms(ctx, platforms): - installed_platforms = [ - pkg.metadata.name for pkg in PlatformPackageManager().get_installed() - ] - if set(platforms) <= set(installed_platforms): - return - ctx.invoke( - cli_platform_install, platforms=list(set(platforms) - set(installed_platforms)) - ) - - def update_project_env(project_dir, environment, project_option): if not project_option: return diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py index 6706a910..b3d9f013 100644 --- a/tests/commands/test_init.py +++ b/tests/commands/test_init.py @@ -13,48 +13,49 @@ # limitations under the License. import json -from os import getcwd, makedirs -from os.path import getsize, isdir, isfile, join +import os -import pytest - -from platformio import proc -from platformio.commands import platform as cli_platform from platformio.commands.boards import cli as cmd_boards +from platformio.package.commands.exec import package_exec_cmd from platformio.project.commands.init import project_init_cmd from platformio.project.config import ProjectConfig from platformio.project.exception import ProjectEnvsNotAvailableError def validate_pioproject(pioproject_dir): - pioconf_path = join(pioproject_dir, "platformio.ini") - assert isfile(pioconf_path) and getsize(pioconf_path) > 0 - assert isdir(join(pioproject_dir, "src")) and isdir(join(pioproject_dir, "lib")) + pioconf_path = os.path.join(pioproject_dir, "platformio.ini") + assert os.path.isfile(pioconf_path) and os.path.getsize(pioconf_path) > 0 + assert os.path.isdir(os.path.join(pioproject_dir, "src")) and os.path.isdir( + os.path.join(pioproject_dir, "lib") + ) def test_init_default(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke(project_init_cmd) validate_cliresult(result) - validate_pioproject(getcwd()) + validate_pioproject(os.getcwd()) def test_init_ext_folder(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): ext_folder_name = "ext_folder" - makedirs(ext_folder_name) + os.makedirs(ext_folder_name) result = clirunner.invoke(project_init_cmd, ["-d", ext_folder_name]) validate_cliresult(result) - validate_pioproject(join(getcwd(), ext_folder_name)) + 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"]) + result = clirunner.invoke( + project_init_cmd, + ["-b", "uno", "-b", "uno", "--no-install-dependencies"], + ) validate_cliresult(result) validate_pioproject(str(tmpdir)) - config = ProjectConfig(join(getcwd(), "platformio.ini")) + config = ProjectConfig(os.path.join(os.getcwd(), "platformio.ini")) config.validate() assert set(config.sections()) == set(["env:uno"]) @@ -69,7 +70,16 @@ def test_init_ide_without_board(clirunner, tmpdir): def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): with tmpdir.as_cwd(): result = clirunner.invoke( - project_init_cmd, ["--ide", "vscode", "-b", "uno", "-b", "teensy31"] + project_init_cmd, + [ + "--ide", + "vscode", + "-b", + "uno", + "-b", + "teensy31", + "--no-install-dependencies", + ], ) validate_cliresult(result) validate_pioproject(str(tmpdir)) @@ -84,7 +94,8 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): # switch to NodeMCU result = clirunner.invoke( - project_init_cmd, ["--ide", "vscode", "-b", "nodemcuv2"] + project_init_cmd, + ["--ide", "vscode", "-b", "nodemcuv2", "--no-install-dependencies"], ) validate_cliresult(result) validate_pioproject(str(tmpdir)) @@ -95,7 +106,8 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): # switch to teensy31 via env name result = clirunner.invoke( - project_init_cmd, ["--ide", "vscode", "-e", "teensy31"] + project_init_cmd, + ["--ide", "vscode", "-e", "teensy31", "--no-install-dependencies"], ) validate_cliresult(result) validate_pioproject(str(tmpdir)) @@ -105,7 +117,9 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): ) # switch to the first board - result = clirunner.invoke(project_init_cmd, ["--ide", "vscode"]) + result = clirunner.invoke( + project_init_cmd, ["--ide", "vscode", "--no-install-dependencies"] + ) validate_cliresult(result) validate_pioproject(str(tmpdir)) assert ( @@ -116,23 +130,26 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): def test_init_ide_eclipse(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): - result = clirunner.invoke(project_init_cmd, ["-b", "uno", "--ide", "eclipse"]) + result = clirunner.invoke( + project_init_cmd, + ["-b", "uno", "--ide", "eclipse", "--no-install-dependencies"], + ) validate_cliresult(result) - validate_pioproject(getcwd()) - assert all(isfile(f) for f in (".cproject", ".project")) + validate_pioproject(os.getcwd()) + assert all(os.path.isfile(f) for f in (".cproject", ".project")) def test_init_special_board(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke(project_init_cmd, ["-b", "uno"]) validate_cliresult(result) - validate_pioproject(getcwd()) + validate_pioproject(os.getcwd()) result = clirunner.invoke(cmd_boards, ["Arduino Uno", "--json-output"]) validate_cliresult(result) boards = json.loads(result.output) - config = ProjectConfig(join(getcwd(), "platformio.ini")) + config = ProjectConfig(os.path.join(os.getcwd(), "platformio.ini")) config.validate() expected_result = dict( @@ -149,11 +166,18 @@ def test_init_special_board(clirunner, validate_cliresult): def test_init_enable_auto_uploading(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke( - project_init_cmd, ["-b", "uno", "--project-option", "targets=upload"] + project_init_cmd, + [ + "-b", + "uno", + "--project-option", + "targets=upload", + "--no-install-dependencies", + ], ) validate_cliresult(result) - validate_pioproject(getcwd()) - config = ProjectConfig(join(getcwd(), "platformio.ini")) + validate_pioproject(os.getcwd()) + config = ProjectConfig(os.path.join(os.getcwd(), "platformio.ini")) config.validate() expected_result = dict( targets=["upload"], platform="atmelavr", board="uno", framework=["arduino"] @@ -167,11 +191,18 @@ def test_init_enable_auto_uploading(clirunner, validate_cliresult): def test_init_custom_framework(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke( - project_init_cmd, ["-b", "teensy31", "--project-option", "framework=mbed"] + project_init_cmd, + [ + "-b", + "teensy31", + "--project-option", + "framework=mbed", + "--no-install-dependencies", + ], ) validate_cliresult(result) - validate_pioproject(getcwd()) - config = ProjectConfig(join(getcwd(), "platformio.ini")) + validate_pioproject(os.getcwd()) + config = ProjectConfig(os.path.join(os.getcwd(), "platformio.ini")) config.validate() expected_result = dict(platform="teensy", board="teensy31", framework=["mbed"]) assert config.has_section("env:teensy31") @@ -187,81 +218,80 @@ def test_init_incorrect_board(clirunner): assert isinstance(result.exception, SystemExit) -@pytest.mark.skipif(not proc.is_ci(), reason="runs on CI") -def test_init_ide_clion(clirunner, isolated_pio_core, validate_cliresult, tmpdir): - result = clirunner.invoke( - cli_platform.platform_install, - [ - "ststm32", - "--skip-default-package", - "--with-package", - "tool-cmake", - "--with-package", - "tool-ninja", - ], - ) - +def test_init_ide_clion(clirunner, validate_cliresult, tmpdir): + project_dir = tmpdir.join("project").mkdir() # Add extra libraries to cover cases with possible unwanted backslashes - lib_extra_dirs = isolated_pio_core.join("extra_libs").mkdir() + lib_extra_dirs = tmpdir.join("extra_libs").mkdir() extra_lib = lib_extra_dirs.join("extra_lib").mkdir() extra_lib.join("extra_lib.h").write(" ") extra_lib.join("extra_lib.cpp").write(" ") - with tmpdir.as_cwd(): + with project_dir.as_cwd(): result = clirunner.invoke( project_init_cmd, [ "-b", - "nucleo_f401re", + "uno", "--ide", "clion", "--project-option", "framework=arduino", "--project-option", + "platform_packages=platformio/tool-ninja", + "--project-option", "lib_extra_dirs=%s" % str(lib_extra_dirs), ], ) validate_cliresult(result) - assert all(isfile(f) for f in ("CMakeLists.txt", "CMakeListsPrivate.txt")) + assert all( + os.path.isfile(f) for f in ("CMakeLists.txt", "CMakeListsPrivate.txt") + ) - tmpdir.join("src").join("main.cpp").write( + project_dir.join("src").join("main.cpp").write( """#include #include "extra_lib.h" void setup(){} void loop(){} """ ) - cmake_path = str( - isolated_pio_core.join("packages") - .join("tool-cmake") - .join("bin") - .join("cmake") - ) - tmpdir.join("build_dir").mkdir() - result = proc.exec_command( + project_dir.join("build_dir").mkdir() + result = clirunner.invoke( + package_exec_cmd, [ - cmake_path, - "-DCMAKE_BUILD_TYPE=nucleo_f401re", + "-p", + "tool-cmake", + "--", + "cmake", + "-DCMAKE_BUILD_TYPE=uno", "-DCMAKE_MAKE_PROGRAM=%s" - % str( - isolated_pio_core.join("packages").join("tool-ninja").join("ninja") + % os.path.join( + ProjectConfig().get("platformio", "packages_dir"), + "tool-ninja", + "ninja", ), "-G", "Ninja", "-S", - str(tmpdir), + str(project_dir), "-B", "build_dir", - ] + ], ) + validate_cliresult(result) - # Check if CMake was able to generate a native project for Ninja - assert result["returncode"] == 0, result["out"] - - result = proc.exec_command( - [cmake_path, "--build", "build_dir", "--target", "Debug"] + # build + result = clirunner.invoke( + package_exec_cmd, + [ + "-p", + "tool-cmake", + "--", + "cmake", + "--build", + "build_dir", + "--target", + "Debug", + ], ) - - assert result["returncode"] == 0 - assert "[SUCCESS]" in str(result["out"]) + validate_cliresult(result)