diff --git a/HISTORY.rst b/HISTORY.rst index 0e8d5688..56da9f0f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -18,10 +18,11 @@ PlatformIO Core 6 6.1.7 (2023-??-??) ~~~~~~~~~~~~~~~~~~ -* Implemented a new feature to store device monitor logs in the project's ``logs`` folder, making it easier to access and review device monitor logs for your projects (`issue #4596 `_) +* Introduced a new ``--sample-code`` option to the `pio project init `__ command, which allows users to include sample code in the newly created project +* Added validation for `project working environment names `__ to ensure that they only contain lowercase letters ``a-z``, numbers ``0-9``, and special characters ``_`` (underscore) and ``-`` (hyphen) * Added the ability to show a detailed library dependency tree only in `verbose mode `__, which can help you understand the relationship between libraries and troubleshoot issues more effectively (`issue #4517 `_) * Added the ability to run only the `device monitor `__ when using the `pio run -t monitor `__ command, saving you time and resources by skipping the build process -* Added validation for `project working environment names `__ to ensure that they only contain lowercase letters ``a-z``, numbers ``0-9``, and special characters ``_`` (underscore) and ``-`` (hyphen) +* Implemented a new feature to store device monitor logs in the project's ``logs`` folder, making it easier to access and review device monitor logs for your projects (`issue #4596 `_) * Improved support for projects located on Windows network drives, including Network Shared Folder, Dropbox, OneDrive, Google Drive, and other similar services (`issue #3417 `_) * Improved source file filtering functionality for the `Static Code Analysis `__ feature, making it easier to analyze only the code you need to * Upgraded the build engine to the latest version of SCons (4.5.2) to improve build performance, reliability, and compatibility with other tools and systems (`release notes `__) diff --git a/docs b/docs index 54a797fb..89233bd2 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 54a797fb5440a069d4790d495499552e5a7edb35 +Subproject commit 89233bd239fb56b45ea33b489bfb5dc213ddabb2 diff --git a/platformio/commands/ci.py b/platformio/commands/ci.py index de346afd..5be31286 100644 --- a/platformio/commands/ci.py +++ b/platformio/commands/ci.py @@ -107,8 +107,8 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches ctx.invoke( project_init_cmd, project_dir=build_dir, - board=board, - project_option=project_option, + boards=board, + project_options=project_option, ) # process project diff --git a/platformio/home/rpc/handlers/project.py b/platformio/home/rpc/handlers/project.py index aa5ef83d..fb77f258 100644 --- a/platformio/home/rpc/handlers/project.py +++ b/platformio/home/rpc/handlers/project.py @@ -18,7 +18,7 @@ import time from ajsonrpc.core import JSONRPC20DispatchException -from platformio import exception, fs +from platformio import app, exception, fs from platformio.home.rpc.handlers.app import AppRPC from platformio.home.rpc.handlers.base import BaseRPCHandler from platformio.home.rpc.handlers.piocore import PIOCoreRPC @@ -185,83 +185,17 @@ class ProjectRPC(BaseRPCHandler): async def init(self, board, framework, project_dir): assert project_dir - state = AppRPC.load_state() if not os.path.isdir(project_dir): os.makedirs(project_dir) - args = ["init", "--board", board] + args = ["init", "--board", board, "--sample-code"] if framework: args.extend(["--project-option", "framework = %s" % framework]) - if ( - state["storage"]["coreCaller"] - and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides() - ): - args.extend(["--ide", state["storage"]["coreCaller"]]) + ide = app.get_session_var("caller_id") + if ide in ProjectGenerator.get_supported_ides(): + args.extend(["--ide", ide]) await PIOCoreRPC.call( args, options={"cwd": project_dir, "force_subprocess": True} ) - return self._generate_project_main(project_dir, board, framework) - - @staticmethod - def _generate_project_main(project_dir, board, framework): - main_content = None - if framework == "arduino": - main_content = "\n".join( - [ - "#include ", - "", - "void setup() {", - " // put your setup code here, to run once:", - "}", - "", - "void loop() {", - " // put your main code here, to run repeatedly:", - "}", - "", - ] - ) - elif framework == "mbed": - main_content = "\n".join( - [ - "#include ", - "", - "int main() {", - "", - " // put your setup code here, to run once:", - "", - " while(1) {", - " // put your main code here, to run repeatedly:", - " }", - "}", - "", - ] - ) - if not main_content: - return project_dir - - is_cpp_project = True - pm = PlatformPackageManager() - try: - board = pm.board_config(board) - platforms = board.get("platforms", board.get("platform")) - if not isinstance(platforms, list): - platforms = [platforms] - c_based_platforms = ["intel_mcs51", "ststm8"] - is_cpp_project = not set(platforms) & set(c_based_platforms) - except exception.PlatformioException: - pass - - with fs.cd(project_dir): - config = ProjectConfig() - src_dir = config.get("platformio", "src_dir") - main_path = os.path.join( - src_dir, "main.%s" % ("cpp" if is_cpp_project else "c") - ) - if os.path.isfile(main_path): - return project_dir - if not os.path.isdir(src_dir): - os.makedirs(src_dir) - with open(main_path, mode="w", encoding="utf8") as fp: - fp.write(main_content.strip()) return project_dir @staticmethod @@ -297,11 +231,9 @@ class ProjectRPC(BaseRPCHandler): args.extend( ["--project-option", "lib_extra_dirs = ~/Documents/Arduino/libraries"] ) - if ( - state["storage"]["coreCaller"] - and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides() - ): - args.extend(["--ide", state["storage"]["coreCaller"]]) + ide = app.get_session_var("caller_id") + if ide in ProjectGenerator.get_supported_ides(): + args.extend(["--ide", ide]) await PIOCoreRPC.call( args, options={"cwd": project_dir, "force_subprocess": True} ) @@ -325,13 +257,10 @@ class ProjectRPC(BaseRPCHandler): ) shutil.copytree(project_dir, new_project_dir, symlinks=True) - state = AppRPC.load_state() args = ["init"] - if ( - state["storage"]["coreCaller"] - and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides() - ): - args.extend(["--ide", state["storage"]["coreCaller"]]) + ide = app.get_session_var("caller_id") + if ide in ProjectGenerator.get_supported_ides(): + args.extend(["--ide", ide]) await PIOCoreRPC.call( args, options={"cwd": new_project_dir, "force_subprocess": True} ) diff --git a/platformio/platform/base.py b/platformio/platform/base.py index 23a66929..3231bb44 100644 --- a/platformio/platform/base.py +++ b/platformio/platform/base.py @@ -207,6 +207,15 @@ class PlatformBase( # pylint: disable=too-many-instance-attributes,too-many-pub def configure_debug_session(self, debug_config): raise NotImplementedError + def generate_sample_code(self, project_config, environment): + raise NotImplementedError + + def on_installed(self): + pass + + def on_uninstalled(self): + pass + def get_lib_storages(self): storages = {} for opts in (self.frameworks or {}).values(): @@ -227,9 +236,3 @@ class PlatformBase( # pylint: disable=too-many-instance-attributes,too-many-pub storages[libcore_dir] = "%s-core-%s" % (opts["package"], item) return [dict(name=name, path=path) for path, name in storages.items()] - - def on_installed(self): - pass - - def on_uninstalled(self): - pass diff --git a/platformio/project/commands/init.py b/platformio/project/commands/init.py index d8cd5b8d..a0176f11 100644 --- a/platformio/project/commands/init.py +++ b/platformio/project/commands/init.py @@ -24,12 +24,14 @@ from platformio import fs from platformio.package.commands.install import install_project_dependencies from platformio.package.manager.platform import PlatformPackageManager from platformio.platform.exception import UnknownBoard +from platformio.platform.factory import PlatformFactory from platformio.project.config import ProjectConfig from platformio.project.helpers import is_platformio_project from platformio.project.integration.generator import ProjectGenerator +from platformio.project.options import ProjectOptions -def validate_boards(ctx, param, value): # pylint: disable=W0613 +def validate_boards(ctx, param, value): # pylint: disable=unused-argument pm = PlatformPackageManager() for id_ in value: try: @@ -49,21 +51,31 @@ def validate_boards(ctx, param, value): # pylint: disable=W0613 default=os.getcwd, type=click.Path(exists=True, file_okay=False, dir_okay=True, writable=True), ) -@click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards) +@click.option( + "-b", "--board", "boards", multiple=True, metavar="ID", callback=validate_boards +) @click.option("--ide", type=click.Choice(ProjectGenerator.get_supported_ides())) @click.option("-e", "--environment", help="Update existing environment") -@click.option("-O", "--project-option", multiple=True) -@click.option("--env-prefix", default="") +@click.option( + "-O", + "--project-option", + "project_options", + multiple=True, + help="A `name=value` pair", +) +@click.option("--sample-code", is_flag=True) @click.option("--no-install-dependencies", is_flag=True) +@click.option("--env-prefix", default="") @click.option("-s", "--silent", is_flag=True) def project_init_cmd( project_dir, - board, + boards, ide, environment, - project_option, - env_prefix, + project_options, + sample_code, no_install_dependencies, + env_prefix, silent, ): is_new_project = not is_platformio_project(project_dir) @@ -72,23 +84,23 @@ def project_init_cmd( print_header(project_dir) init_base_project(project_dir) - if environment: - update_project_env(project_dir, environment, project_option) - elif board: - update_board_envs(project_dir, board, project_option, env_prefix) - with fs.cd(project_dir): + if environment: + update_project_env(environment, project_options) + elif boards: + update_board_envs(project_dir, boards, project_options, env_prefix) + generator = None config = ProjectConfig.get_instance(os.path.join(project_dir, "platformio.ini")) if ide: config.validate() # init generator and pick the best env if user didn't specify - generator = ProjectGenerator(config, environment, ide, board) + generator = ProjectGenerator(config, environment, ide, boards) if not environment: environment = generator.env_name # resolve project dependencies - if not no_install_dependencies and (environment or board): + if not no_install_dependencies and (environment or boards): install_project_dependencies( options=dict( project_dir=project_dir, @@ -97,6 +109,9 @@ def project_init_cmd( ) ) + if environment and sample_code: + init_sample_code(config, environment) + if generator: if not silent: click.echo( @@ -104,8 +119,8 @@ def project_init_cmd( ) generator.generate() - if is_new_project: - init_cvs_ignore(project_dir) + if is_new_project: + init_cvs_ignore() if not silent: print_footer(is_new_project) @@ -289,15 +304,15 @@ More information about PlatformIO Unit Testing: ) -def init_cvs_ignore(project_dir): - conf_path = os.path.join(project_dir, ".gitignore") +def init_cvs_ignore(): + conf_path = ".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(project_dir, board_ids, project_option, env_prefix): +def update_board_envs(project_dir, boards, extra_project_options, env_prefix): config = ProjectConfig( os.path.join(project_dir, "platformio.ini"), parse_extra=False ) @@ -309,7 +324,7 @@ def update_board_envs(project_dir, board_ids, project_option, env_prefix): pm = PlatformPackageManager() modified = False - for id_ in board_ids: + for id_ in boards: board_config = pm.board_config(id_) if id_ in used_boards: continue @@ -322,7 +337,7 @@ def update_board_envs(project_dir, board_ids, project_option, env_prefix): if frameworks: envopts["framework"] = frameworks[0] - for item in project_option: + for item in extra_project_options: if "=" not in item: continue _name, _value = item.split("=", 1) @@ -338,21 +353,74 @@ def update_board_envs(project_dir, board_ids, project_option, env_prefix): config.save() -def update_project_env(project_dir, environment, project_option): - if not project_option: +def update_project_env(environment, extra_project_options=None): + if not extra_project_options: return + env_section = "env:%s" % environment + option_to_sections = {"platformio": [], env_section: []} + for item in extra_project_options: + assert "=" in item + name, value = item.split("=", 1) + name = name.strip() + destination = env_section + for option in ProjectOptions.values(): + if option.scope in option_to_sections and option.name == name: + destination = option.scope + break + option_to_sections[destination].append((name, value.strip())) + config = ProjectConfig( - os.path.join(project_dir, "platformio.ini"), parse_extra=False + "platformio.ini", parse_extra=False, expand_interpolations=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()) + for section, options in option_to_sections.items(): + if not config.has_section(section): + config.add_section(section) + for name, value in options: + config.set(section, name, value) config.save() + + +def init_sample_code(config, environment): + platform_spec = config.get(f"env:{environment}", "platform", None) + if not platform_spec: + return None + p = PlatformFactory.new(platform_spec) + try: + return p.generate_sample_code(config, environment) + except NotImplementedError: + pass + + framework = config.get(f"env:{environment}", "framework", None) + if framework != ["arduino"]: + return None + main_content = """ +#include + +// put function declarations here: +int myFunction(int, int); + +void setup() { + // put your setup code here, to run once: + int result = myFunction(2, 3); +} + +void loop() { + // put your main code here, to run repeatedly: +} + +// put function definitions here: +int myFunction(int x, int y) { + return x + y; +} +""" + is_cpp_project = p.name not in ["intel_mcs51", "ststm8"] + src_dir = config.get("platformio", "src_dir") + main_path = os.path.join(src_dir, "main.%s" % ("cpp" if is_cpp_project else "c")) + if os.path.isfile(main_path): + return None + if not os.path.isdir(src_dir): + os.makedirs(src_dir) + with open(main_path, mode="w", encoding="utf8") as fp: + fp.write(main_content.strip()) + return True diff --git a/platformio/project/integration/generator.py b/platformio/project/integration/generator.py index 40b8e87a..654e5ac3 100644 --- a/platformio/project/integration/generator.py +++ b/platformio/project/integration/generator.py @@ -25,28 +25,28 @@ from platformio.project.helpers import load_build_metadata class ProjectGenerator: - def __init__(self, config, env_name, ide, board_ids=None): + def __init__(self, config, env_name, ide, boards=None): self.config = config self.project_dir = os.path.dirname(config.path) self.forced_env_name = env_name - self.env_name = str(env_name or self.get_best_envname(board_ids)) + self.env_name = str(env_name or self.get_best_envname(boards)) self.ide = str(ide) - def get_best_envname(self, board_ids=None): + def get_best_envname(self, boards=None): envname = None default_envs = self.config.default_envs() if default_envs: envname = default_envs[0] - if not board_ids: + if not boards: return envname for env in self.config.envs(): - if not board_ids: + if not boards: return env if not envname: envname = env items = self.config.items(env=env, as_dict=True) - if "board" in items and items.get("board") in board_ids: + if "board" in items and items.get("board") in boards: return env return envname