Resolve project dependencies with pio project init command

This commit is contained in:
Ivan Kravets
2022-05-14 16:31:08 +03:00
parent 4a4ba5594b
commit ce62514a17
4 changed files with 158 additions and 132 deletions

View File

@ -87,6 +87,7 @@ Please check the `Migration guide from 5.x to 6.0 <https://docs.platformio.org/e
- Added a new build variable (``COMPILATIONDB_INCLUDE_TOOLCHAIN``) to include toolchain paths in the compilation database (`issue #3735 <https://github.com/platformio/platformio-core/issues/3735>`_)
- Changed a default path for compilation database `compile_commands.json <https://docs.platformio.org/en/latest/integration/compile_commands.html>`__ to the project root
- Enhanced integration for Qt Creator (`issue #3046 <https://github.com/platformio/platformio-core/issues/3046>`_)
* **Project Configuration**

2
docs

Submodule docs updated: a9aad82cb9...def7ca7a23

View File

@ -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

View File

@ -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 <Arduino.h>
#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)