From 3247e661e9afb2d93169298c94c7ef3a0b931b41 Mon Sep 17 00:00:00 2001 From: Ivan Kravets Date: Sat, 14 May 2022 13:41:20 +0300 Subject: [PATCH] Regroup "pio project" command --- platformio/commands/__init__.py | 4 +- platformio/commands/ci.py | 5 +- platformio/commands/project.py | 432 +----------------------- platformio/project/commands/__init__.py | 13 + platformio/project/commands/config.py | 57 ++++ platformio/project/commands/data.py | 63 ++++ platformio/project/commands/init.py | 360 ++++++++++++++++++++ tests/commands/test_init.py | 34 +- 8 files changed, 528 insertions(+), 440 deletions(-) create mode 100644 platformio/project/commands/__init__.py create mode 100644 platformio/project/commands/config.py create mode 100644 platformio/project/commands/data.py create mode 100644 platformio/project/commands/init.py diff --git a/platformio/commands/__init__.py b/platformio/commands/__init__.py index 0b923a51..22cacc60 100644 --- a/platformio/commands/__init__.py +++ b/platformio/commands/__init__.py @@ -74,9 +74,9 @@ class PlatformioCLI(click.MultiCommand): def _handle_obsolate_command(name): # pylint: disable=import-outside-toplevel if name == "init": - from platformio.commands.project import project_init + from platformio.project.commands.init import project_init_cmd - return project_init + return project_init_cmd if name == "package": from platformio.commands.pkg import cli diff --git a/platformio/commands/ci.py b/platformio/commands/ci.py index d1a554ae..17880196 100644 --- a/platformio/commands/ci.py +++ b/platformio/commands/ci.py @@ -20,10 +20,9 @@ import tempfile import click from platformio import app, fs -from platformio.commands.project import project_init as cmd_project_init -from platformio.commands.project import validate_boards from platformio.commands.run.command import cli as cmd_run from platformio.exception import CIBuildEnvsEmpty +from platformio.project.commands.init import project_init_cmd, validate_boards from platformio.project.config import ProjectConfig @@ -109,7 +108,7 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches # initialise project ctx.invoke( - cmd_project_init, + project_init_cmd, project_dir=build_dir, board=board, project_option=project_option, diff --git a/platformio/commands/project.py b/platformio/commands/project.py index acf6da81..9c9f76cb 100644 --- a/platformio/commands/project.py +++ b/platformio/commands/project.py @@ -12,429 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=too-many-arguments,too-many-locals,too-many-branches,line-too-long - -import json -import os - import click -from tabulate import tabulate -from platformio import fs -from platformio.commands.platform import platform_install as cli_platform_install -from platformio.package.manager.platform import PlatformPackageManager -from platformio.platform.exception import UnknownBoard -from platformio.project.config import ProjectConfig -from platformio.project.exception import NotPlatformIOProjectError -from platformio.project.generator import ProjectGenerator -from platformio.project.helpers import is_platformio_project, load_project_ide_data +from platformio.project.commands.config import project_config_cmd +from platformio.project.commands.data import project_data_cmd +from platformio.project.commands.init import project_init_cmd -@click.group(short_help="Project Manager") +@click.group( + "project", + commands=[ + project_config_cmd, + project_data_cmd, + project_init_cmd, + ], + short_help="Project Manager", +) def cli(): pass - - -@cli.command("config", short_help="Show computed configuration") -@click.option( - "-d", - "--project-dir", - default=os.getcwd, - type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), -) -@click.option("--json-output", is_flag=True) -def project_config(project_dir, json_output): - if not is_platformio_project(project_dir): - raise NotPlatformIOProjectError(project_dir) - with fs.cd(project_dir): - config = ProjectConfig.get_instance() - if json_output: - return click.echo(config.to_json()) - click.echo( - "Computed project configuration for %s" % click.style(project_dir, fg="cyan") - ) - for section, options in config.as_tuple(): - click.secho(section, fg="cyan") - click.echo("-" * len(section)) - click.echo( - tabulate( - [ - (name, "=", "\n".join(value) if isinstance(value, list) else value) - for name, value in options - ], - tablefmt="plain", - ) - ) - click.echo() - return None - - -@cli.command("data", short_help="Dump data intended for IDE extensions/plugins") -@click.option( - "-d", - "--project-dir", - default=os.getcwd, - type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), -) -@click.option("-e", "--environment", multiple=True) -@click.option("--json-output", is_flag=True) -def project_data(project_dir, environment, json_output): - if not is_platformio_project(project_dir): - raise NotPlatformIOProjectError(project_dir) - with fs.cd(project_dir): - config = ProjectConfig.get_instance() - config.validate(environment) - environment = list(environment or config.envs()) - - if json_output: - return click.echo(json.dumps(load_project_ide_data(project_dir, environment))) - - for envname in environment: - click.echo("Environment: " + click.style(envname, fg="cyan", bold=True)) - click.echo("=" * (13 + len(envname))) - click.echo( - tabulate( - [ - (click.style(name, bold=True), "=", json.dumps(value, indent=2)) - for name, value in load_project_ide_data( - project_dir, envname - ).items() - ], - tablefmt="plain", - ) - ) - click.echo() - - return None - - -def validate_boards(ctx, param, value): # pylint: disable=W0613 - pm = PlatformPackageManager() - for id_ in value: - try: - pm.board_config(id_) - except UnknownBoard: - raise click.BadParameter( - "`%s`. Please search for board ID using `platformio boards` " - "command" % id_ - ) - return value - - -@cli.command("init", short_help="Initialize a project or update existing") -@click.option( - "--project-dir", - "-d", - default=os.getcwd, - type=click.Path( - exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True - ), -) -@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("-O", "--project-option", multiple=True) -@click.option("--env-prefix", default="") -@click.option("-s", "--silent", is_flag=True) -@click.pass_context -def project_init( - ctx, # pylint: disable=R0913 - project_dir, - board, - ide, - environment, - project_option, - env_prefix, - silent, -): - if not silent: - 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") - ) - - is_new_project = not is_platformio_project(project_dir) - if is_new_project: - 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 - ) - - if ide: - with fs.cd(project_dir): - config = ProjectConfig.get_instance( - os.path.join(project_dir, "platformio.ini") - ) - config.validate() - ProjectGenerator(config, environment, ide, board).generate() - - if is_new_project: - init_cvs_ignore(project_dir) - - if silent: - return - - if ide: - click.secho( - "\nProject has been successfully %s including configuration files " - "for `%s` IDE." % ("initialized" if is_new_project else "updated", ide), - fg="green", - ) - else: - click.secho( - "\nProject has been successfully %s! Useful commands:\n" - "`pio run` - process/build project from the current directory\n" - "`pio run --target upload` or `pio run -t upload` " - "- upload firmware to a target\n" - "`pio run --target clean` - clean project (remove compiled files)" - "\n`pio run --help` - additional information" - % ("initialized" if is_new_project else "updated"), - fg="green", - ) - - -def init_base_project(project_dir): - with fs.cd(project_dir): - config = ProjectConfig() - config.save() - dir_to_readme = [ - (config.get("platformio", "src_dir"), None), - (config.get("platformio", "include_dir"), init_include_readme), - (config.get("platformio", "lib_dir"), init_lib_readme), - (config.get("platformio", "test_dir"), init_test_readme), - ] - for (path, cb) in dir_to_readme: - if os.path.isdir(path): - continue - os.makedirs(path) - if cb: - cb(path) - - -def init_include_readme(include_dir): - with open(os.path.join(include_dir, "README"), mode="w", encoding="utf8") as fp: - fp.write( - """ -This directory is intended for project header files. - -A header file is a file containing C declarations and macro definitions -to be shared between several project source files. You request the use of a -header file in your project source file (C, C++, etc) located in `src` folder -by including it, with the C preprocessing directive `#include'. - -```src/main.c - -#include "header.h" - -int main (void) -{ - ... -} -``` - -Including a header file produces the same results as copying the header file -into each source file that needs it. Such copying would be time-consuming -and error-prone. With a header file, the related declarations appear -in only one place. If they need to be changed, they can be changed in one -place, and programs that include the header file will automatically use the -new version when next recompiled. The header file eliminates the labor of -finding and changing all the copies as well as the risk that a failure to -find one copy will result in inconsistencies within a program. - -In C, the usual convention is to give header files names that end with `.h'. -It is most portable to use only letters, digits, dashes, and underscores in -header file names, and at most one dot. - -Read more about using header files in official GCC documentation: - -* Include Syntax -* Include Operation -* Once-Only Headers -* Computed Includes - -https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html -""", - ) - - -def init_lib_readme(lib_dir): - with open(os.path.join(lib_dir, "README"), mode="w", encoding="utf8") as fp: - fp.write( - """ -This directory is intended for project specific (private) libraries. -PlatformIO will compile them to static libraries and link into executable file. - -The source code of each library should be placed in a an own separate directory -("lib/your_library_name/[here are source files]"). - -For example, see a structure of the following two libraries `Foo` and `Bar`: - -|--lib -| | -| |--Bar -| | |--docs -| | |--examples -| | |--src -| | |- Bar.c -| | |- Bar.h -| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html -| | -| |--Foo -| | |- Foo.c -| | |- Foo.h -| | -| |- README --> THIS FILE -| -|- platformio.ini -|--src - |- main.c - -and a contents of `src/main.c`: -``` -#include -#include - -int main (void) -{ - ... -} - -``` - -PlatformIO Library Dependency Finder will find automatically dependent -libraries scanning project source files. - -More information about PlatformIO Library Dependency Finder -- https://docs.platformio.org/page/librarymanager/ldf.html -""", - ) - - -def init_test_readme(test_dir): - with open(os.path.join(test_dir, "README"), mode="w", encoding="utf8") as fp: - fp.write( - """ -This directory is intended for PlatformIO Test Runner and project tests. - -Unit Testing is a software testing method by which individual units of -source code, sets of one or more MCU program modules together with associated -control data, usage procedures, and operating procedures, are tested to -determine whether they are fit for use. Unit testing finds problems early -in the development cycle. - -More information about PlatformIO Unit Testing: -- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html -""", - ) - - -def init_cvs_ignore(project_dir): - conf_path = os.path.join(project_dir, ".gitignore") - if os.path.isfile(conf_path): - return - with open(conf_path, mode="w", encoding="utf8") as fp: - fp.write(".pio\n") - - -def update_board_envs( - ctx, project_dir, board_ids, project_option, env_prefix, force_download -): - config = ProjectConfig( - os.path.join(project_dir, "platformio.ini"), parse_extra=False - ) - used_boards = [] - for section in config.sections(): - cond = [section.startswith("env:"), config.has_option(section, "board")] - if all(cond): - 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_) - modified = True - - envopts = {"platform": board_config["platform"], "board": id_} - # find default framework for board - frameworks = board_config.get("frameworks") - if frameworks: - envopts["framework"] = frameworks[0] - - for item in project_option: - if "=" not in item: - continue - _name, _value = item.split("=", 1) - envopts[_name.strip()] = _value.strip() - - section = "env:%s%s" % (env_prefix, id_) - config.add_section(section) - - 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 - config = ProjectConfig( - os.path.join(project_dir, "platformio.ini"), parse_extra=False - ) - - section = "env:%s" % environment - if not config.has_section(section): - config.add_section(section) - - for item in project_option: - if "=" not in item: - continue - _name, _value = item.split("=", 1) - config.set(section, _name.strip(), _value.strip()) - - config.save() diff --git a/platformio/project/commands/__init__.py b/platformio/project/commands/__init__.py new file mode 100644 index 00000000..b0514903 --- /dev/null +++ b/platformio/project/commands/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/platformio/project/commands/config.py b/platformio/project/commands/config.py new file mode 100644 index 00000000..b59ff005 --- /dev/null +++ b/platformio/project/commands/config.py @@ -0,0 +1,57 @@ +# 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 click +from tabulate import tabulate + +from platformio import fs +from platformio.project.config import ProjectConfig +from platformio.project.exception import NotPlatformIOProjectError +from platformio.project.helpers import is_platformio_project + + +@click.command("config", short_help="Show computed configuration") +@click.option( + "-d", + "--project-dir", + default=os.getcwd, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), +) +@click.option("--json-output", is_flag=True) +def project_config_cmd(project_dir, json_output): + if not is_platformio_project(project_dir): + raise NotPlatformIOProjectError(project_dir) + with fs.cd(project_dir): + config = ProjectConfig.get_instance() + if json_output: + return click.echo(config.to_json()) + click.echo( + "Computed project configuration for %s" % click.style(project_dir, fg="cyan") + ) + for section, options in config.as_tuple(): + click.secho(section, fg="cyan") + click.echo("-" * len(section)) + click.echo( + tabulate( + [ + (name, "=", "\n".join(value) if isinstance(value, list) else value) + for name, value in options + ], + tablefmt="plain", + ) + ) + click.echo() + return None diff --git a/platformio/project/commands/data.py b/platformio/project/commands/data.py new file mode 100644 index 00000000..e847f316 --- /dev/null +++ b/platformio/project/commands/data.py @@ -0,0 +1,63 @@ +# 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 +import os + +import click +from tabulate import tabulate + +from platformio import fs +from platformio.project.config import ProjectConfig +from platformio.project.exception import NotPlatformIOProjectError +from platformio.project.helpers import is_platformio_project, load_project_ide_data + + +@click.command("data", short_help="Dump data intended for IDE extensions/plugins") +@click.option( + "-d", + "--project-dir", + default=os.getcwd, + type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True), +) +@click.option("-e", "--environment", multiple=True) +@click.option("--json-output", is_flag=True) +def project_data_cmd(project_dir, environment, json_output): + if not is_platformio_project(project_dir): + raise NotPlatformIOProjectError(project_dir) + with fs.cd(project_dir): + config = ProjectConfig.get_instance() + config.validate(environment) + environment = list(environment or config.envs()) + + if json_output: + return click.echo(json.dumps(load_project_ide_data(project_dir, environment))) + + for envname in environment: + click.echo("Environment: " + click.style(envname, fg="cyan", bold=True)) + click.echo("=" * (13 + len(envname))) + click.echo( + tabulate( + [ + (click.style(name, bold=True), "=", json.dumps(value, indent=2)) + for name, value in load_project_ide_data( + project_dir, envname + ).items() + ], + tablefmt="plain", + ) + ) + click.echo() + + return None diff --git a/platformio/project/commands/init.py b/platformio/project/commands/init.py new file mode 100644 index 00000000..7e72a67c --- /dev/null +++ b/platformio/project/commands/init.py @@ -0,0 +1,360 @@ +# 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. + +# pylint: disable=line-too-long,too-many-arguments,too-many-locals + + +import json +import os + +import click + +from platformio import fs +from platformio.commands.platform import platform_install as cli_platform_install +from platformio.package.manager.platform import PlatformPackageManager +from platformio.platform.exception import UnknownBoard +from platformio.project.config import ProjectConfig +from platformio.project.generator import ProjectGenerator +from platformio.project.helpers import is_platformio_project + + +def validate_boards(ctx, param, value): # pylint: disable=W0613 + pm = PlatformPackageManager() + for id_ in value: + try: + pm.board_config(id_) + except UnknownBoard: + raise click.BadParameter( + "`%s`. Please search for board ID using `platformio boards` " + "command" % id_ + ) + return value + + +@click.command("init", short_help="Initialize a project or update existing") +@click.option( + "--project-dir", + "-d", + default=os.getcwd, + type=click.Path( + exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True + ), +) +@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("-O", "--project-option", multiple=True) +@click.option("--env-prefix", default="") +@click.option("-s", "--silent", is_flag=True) +@click.pass_context +def project_init_cmd( + ctx, + project_dir, + board, + ide, + environment, + project_option, + env_prefix, + 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: + 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 + ) + + if ide: + 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") + ) + config.validate() + ProjectGenerator(config, environment, ide, board).generate() + + if is_new_project: + init_cvs_ignore(project_dir) + + if silent: + return + + if is_new_project: + 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` " + "- upload firmware to a target\n" + "`pio run --target clean` - clean project (remove compiled files)" + "\n`pio run --help` - additional information", + fg="green", + ) + else: + click.secho( + "Project has been successfully updated!", + fg="green", + ) + + +def init_base_project(project_dir): + with fs.cd(project_dir): + config = ProjectConfig() + config.save() + dir_to_readme = [ + (config.get("platformio", "src_dir"), None), + (config.get("platformio", "include_dir"), init_include_readme), + (config.get("platformio", "lib_dir"), init_lib_readme), + (config.get("platformio", "test_dir"), init_test_readme), + ] + for (path, cb) in dir_to_readme: + if os.path.isdir(path): + continue + os.makedirs(path) + if cb: + cb(path) + + +def init_include_readme(include_dir): + with open(os.path.join(include_dir, "README"), mode="w", encoding="utf8") as fp: + fp.write( + """ +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html +""", + ) + + +def init_lib_readme(lib_dir): + with open(os.path.join(lib_dir, "README"), mode="w", encoding="utf8") as fp: + fp.write( + """ +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html +""", + ) + + +def init_test_readme(test_dir): + with open(os.path.join(test_dir, "README"), mode="w", encoding="utf8") as fp: + fp.write( + """ +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html +""", + ) + + +def init_cvs_ignore(project_dir): + conf_path = os.path.join(project_dir, ".gitignore") + if os.path.isfile(conf_path): + return + with open(conf_path, mode="w", encoding="utf8") as fp: + fp.write(".pio\n") + + +def update_board_envs( + ctx, project_dir, board_ids, project_option, env_prefix, force_download +): + config = ProjectConfig( + os.path.join(project_dir, "platformio.ini"), parse_extra=False + ) + used_boards = [] + for section in config.sections(): + cond = [section.startswith("env:"), config.has_option(section, "board")] + if all(cond): + 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_) + modified = True + + envopts = {"platform": board_config["platform"], "board": id_} + # find default framework for board + frameworks = board_config.get("frameworks") + if frameworks: + envopts["framework"] = frameworks[0] + + for item in project_option: + if "=" not in item: + continue + _name, _value = item.split("=", 1) + envopts[_name.strip()] = _value.strip() + + section = "env:%s%s" % (env_prefix, id_) + config.add_section(section) + + 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 + config = ProjectConfig( + os.path.join(project_dir, "platformio.ini"), parse_extra=False + ) + + section = "env:%s" % environment + if not config.has_section(section): + config.add_section(section) + + for item in project_option: + if "=" not in item: + continue + _name, _value = item.split("=", 1) + config.set(section, _name.strip(), _value.strip()) + + config.save() diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py index 286197fa..6706a910 100644 --- a/tests/commands/test_init.py +++ b/tests/commands/test_init.py @@ -21,7 +21,7 @@ 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.commands.project import project_init as cmd_init +from platformio.project.commands.init import project_init_cmd from platformio.project.config import ProjectConfig from platformio.project.exception import ProjectEnvsNotAvailableError @@ -34,7 +34,7 @@ def validate_pioproject(pioproject_dir): def test_init_default(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): - result = clirunner.invoke(cmd_init) + result = clirunner.invoke(project_init_cmd) validate_cliresult(result) validate_pioproject(getcwd()) @@ -43,7 +43,7 @@ def test_init_ext_folder(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): ext_folder_name = "ext_folder" makedirs(ext_folder_name) - result = clirunner.invoke(cmd_init, ["-d", ext_folder_name]) + result = clirunner.invoke(project_init_cmd, ["-d", ext_folder_name]) validate_cliresult(result) validate_pioproject(join(getcwd(), ext_folder_name)) @@ -51,7 +51,7 @@ def test_init_ext_folder(clirunner, validate_cliresult): def test_init_duplicated_boards(clirunner, validate_cliresult, tmpdir): with tmpdir.as_cwd(): for _ in range(2): - result = clirunner.invoke(cmd_init, ["-b", "uno", "-b", "uno"]) + result = clirunner.invoke(project_init_cmd, ["-b", "uno", "-b", "uno"]) validate_cliresult(result) validate_pioproject(str(tmpdir)) config = ProjectConfig(join(getcwd(), "platformio.ini")) @@ -61,7 +61,7 @@ def test_init_duplicated_boards(clirunner, validate_cliresult, tmpdir): def test_init_ide_without_board(clirunner, tmpdir): with tmpdir.as_cwd(): - result = clirunner.invoke(cmd_init, ["--ide", "atom"]) + result = clirunner.invoke(project_init_cmd, ["--ide", "atom"]) assert result.exit_code != 0 assert isinstance(result.exception, ProjectEnvsNotAvailableError) @@ -69,7 +69,7 @@ def test_init_ide_without_board(clirunner, tmpdir): def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): with tmpdir.as_cwd(): result = clirunner.invoke( - cmd_init, ["--ide", "vscode", "-b", "uno", "-b", "teensy31"] + project_init_cmd, ["--ide", "vscode", "-b", "uno", "-b", "teensy31"] ) validate_cliresult(result) validate_pioproject(str(tmpdir)) @@ -83,7 +83,9 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): ) # switch to NodeMCU - result = clirunner.invoke(cmd_init, ["--ide", "vscode", "-b", "nodemcuv2"]) + result = clirunner.invoke( + project_init_cmd, ["--ide", "vscode", "-b", "nodemcuv2"] + ) validate_cliresult(result) validate_pioproject(str(tmpdir)) assert ( @@ -92,7 +94,9 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): ) # switch to teensy31 via env name - result = clirunner.invoke(cmd_init, ["--ide", "vscode", "-e", "teensy31"]) + result = clirunner.invoke( + project_init_cmd, ["--ide", "vscode", "-e", "teensy31"] + ) validate_cliresult(result) validate_pioproject(str(tmpdir)) assert ( @@ -101,7 +105,7 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): ) # switch to the first board - result = clirunner.invoke(cmd_init, ["--ide", "vscode"]) + result = clirunner.invoke(project_init_cmd, ["--ide", "vscode"]) validate_cliresult(result) validate_pioproject(str(tmpdir)) assert ( @@ -112,7 +116,7 @@ def test_init_ide_vscode(clirunner, validate_cliresult, tmpdir): def test_init_ide_eclipse(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): - result = clirunner.invoke(cmd_init, ["-b", "uno", "--ide", "eclipse"]) + result = clirunner.invoke(project_init_cmd, ["-b", "uno", "--ide", "eclipse"]) validate_cliresult(result) validate_pioproject(getcwd()) assert all(isfile(f) for f in (".cproject", ".project")) @@ -120,7 +124,7 @@ def test_init_ide_eclipse(clirunner, validate_cliresult): def test_init_special_board(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): - result = clirunner.invoke(cmd_init, ["-b", "uno"]) + result = clirunner.invoke(project_init_cmd, ["-b", "uno"]) validate_cliresult(result) validate_pioproject(getcwd()) @@ -145,7 +149,7 @@ def test_init_special_board(clirunner, validate_cliresult): def test_init_enable_auto_uploading(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke( - cmd_init, ["-b", "uno", "--project-option", "targets=upload"] + project_init_cmd, ["-b", "uno", "--project-option", "targets=upload"] ) validate_cliresult(result) validate_pioproject(getcwd()) @@ -163,7 +167,7 @@ def test_init_enable_auto_uploading(clirunner, validate_cliresult): def test_init_custom_framework(clirunner, validate_cliresult): with clirunner.isolated_filesystem(): result = clirunner.invoke( - cmd_init, ["-b", "teensy31", "--project-option", "framework=mbed"] + project_init_cmd, ["-b", "teensy31", "--project-option", "framework=mbed"] ) validate_cliresult(result) validate_pioproject(getcwd()) @@ -177,7 +181,7 @@ def test_init_custom_framework(clirunner, validate_cliresult): def test_init_incorrect_board(clirunner): - result = clirunner.invoke(cmd_init, ["-b", "missed_board"]) + result = clirunner.invoke(project_init_cmd, ["-b", "missed_board"]) assert result.exit_code == 2 assert "Error: Invalid value for" in result.output assert isinstance(result.exception, SystemExit) @@ -205,7 +209,7 @@ def test_init_ide_clion(clirunner, isolated_pio_core, validate_cliresult, tmpdir with tmpdir.as_cwd(): result = clirunner.invoke( - cmd_init, + project_init_cmd, [ "-b", "nucleo_f401re",