mirror of
https://github.com/platformio/platformio-core.git
synced 2025-07-29 17:47:14 +02:00
Regroup "pio project" command
This commit is contained in:
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
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()
|
||||
|
13
platformio/project/commands/__init__.py
Normal file
13
platformio/project/commands/__init__.py
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# 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.
|
57
platformio/project/commands/config.py
Normal file
57
platformio/project/commands/config.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# 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
|
63
platformio/project/commands/data.py
Normal file
63
platformio/project/commands/data.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# 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
|
360
platformio/project/commands/init.py
Normal file
360
platformio/project/commands/init.py
Normal file
@ -0,0 +1,360 @@
|
||||
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
|
||||
#
|
||||
# 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 <Foo.h>
|
||||
#include <Bar.h>
|
||||
|
||||
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()
|
@ -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",
|
||||
|
Reference in New Issue
Block a user