diff --git a/.pylintrc b/.pylintrc index 09569a4f..180a05b8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,23 +1,12 @@ [MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -# disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,round-builtin,hex-method,nonzero-method,map-builtin-not-iterating - -disable=locally-disabled,missing-docstring,invalid-name,too-few-public-methods,redefined-variable-type,import-error,similarities,unsupported-membership-test,unsubscriptable-object,ungrouped-imports,cyclic-import,superfluous-parens,useless-object-inheritance,useless-import-alias +disable= + missing-docstring, + ungrouped-imports, + invalid-name, + cyclic-import, + duplicate-code, + superfluous-parens, + too-few-public-methods, + useless-object-inheritance, + useless-import-alias, + fixme diff --git a/HISTORY.rst b/HISTORY.rst index 99c41cb5..f5a953f6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,15 +7,31 @@ PlatformIO 4.0 4.0.0 (2019-??-??) ~~~~~~~~~~~~~~~~~~ -* Added Python 3.5+ support +* Python 3 support (`issue #895 `_) +* Share common (global) options between declared build environments using ``[env]`` section in `"platformio.ini" (Project Configuration File) `__ + (`issue #1643 `_) +* Include external configuration files in `"platformio.ini" (Project Configuration File) `__ with `extra_configs `__ option + (`issue #1590 `_) +* Override default `"platformio.ini" (Project Configuration File) `__ with a custom using ``-c, --project-conf`` option for `platformio run `__, `platformio debug `__, or `platformio test `__ commands + (`issue #1913 `_) +* Override default source and include directories for a library via `library.json `__ manifest using ``includeDir`` and ``srcDir`` fields +* Added support for the latest Python "Click" package (CLI Builder) + (`issue #349 `_) PlatformIO 3.0 -------------- -3.6.7 (2019-??-??) +3.6.8 (2019-??-??) ~~~~~~~~~~~~~~~~~~ +* Fixed "systemd-udevd" warnings in `99-platformio-udev.rules `__ + (`issue #2442 `_) + +3.6.7 (2019-04-23) +~~~~~~~~~~~~~~~~~~ + +* `PIO Unified Debugger `__: improved debugging in ``debug_load_mode = modified`` and fixed an issue with useless project rebuilding * Project Generator: fixed a VSCode C/C++'s "Cannot find" warning when CPPPATH folder does not exist * Fixed an "IndexError: list index out of range" for Arduino sketch preprocessor (`issue #2268 `_) @@ -30,7 +46,7 @@ PlatformIO 3.0 * Fixed an issue when PlatformIO Build System does not pick up "mbed_lib.json" files from libraries (`issue #2164 `_) * Fixed an error with conflicting declaration of a prototype (Arduino sketch preprocessor) -* Fixed "FileExistsError" when `platformio ci `__ command is used in pair with ``--keep-build-dir`` option +* Fixed "FileExistsError" when `platformio ci `__ command is used in pair with ``--keep-build-dir`` option * Fixed an issue with incorrect order of project "include" and "src" paths in ``CPPPATH`` (`issue #1914 `_) @@ -40,7 +56,7 @@ PlatformIO 3.0 * Project Generator: added new targets for CLion IDE "BUILD_VERBOSE" and "MONITOR" (serial port monitor) (`issue #359 `_) * Fixed an issue with slow updating of PlatformIO Core packages on Windows -* Fixed an issue when `platformio ci `__ recompiles project if ``--keep-build-dir`` option is passed +* Fixed an issue when `platformio ci `__ recompiles project if ``--keep-build-dir`` option is passed (`issue #2109 `_) * Fixed an issue when ``$PROJECT_HASH`` template was not expanded for the other directory ``***_dir`` options in `"platformio.ini" (Project Configuration File) `__ (`issue #2170 `_) @@ -88,9 +104,9 @@ PlatformIO 3.0 * Generate an `include `__ and `test `__ directories with a README file when initializing a new project * Support in-line comments for multi-line value (``lib_deps``, ``build_flags``, etc) in `"platformio.ini" (Project Configuration File) `__ -* Added ``$PROJECT_HASH`` template variable for `build_dir `__. One of the use cases is setting a global storage for project artifacts using `PLATFORMIO_BUILD_DIR `__ system environment variable. For example, ``/tmp/pio-build/$PROJECT_HASH`` (Unix) or ``$[sysenv.TEMP}/pio-build/$PROJECT_HASH`` (Windows) +* Added ``$PROJECT_HASH`` template variable for `build_dir `__. One of the use cases is setting a global storage for project artifacts using `PLATFORMIO_BUILD_DIR `__ system environment variable. For example, ``/tmp/pio-build/$PROJECT_HASH`` (Unix) or ``$[sysenv.TEMP}/pio-build/$PROJECT_HASH`` (Windows) * Improved a loading speed of PIO Home "Recent News" -* Improved `PIO Unified Debugger `__ for "mbed" framework and fixed issue with missed local variables +* Improved `PIO Unified Debugger `__ for "mbed" framework and fixed issue with missed local variables * Introduced `"Release" and "Debug" Build Configurations `__ * Build project in "Debug Mode" including debugging information with a new ``debug`` target using `platformio run `__ command or `targets `__ option in ``platformio.ini``. The last option allows avoiding project rebuilding between "Run/Debug" modes. (`issue #1833 `_) @@ -148,7 +164,7 @@ PlatformIO 3.0 build environment (`issue #1665 `_) * Handle "architectures" data from "library.properties" manifest in - `lib_compat_mode = strict `__ + `lib_compat_mode = strict `__ * Added workaround for Python SemVer package's `issue #61 `_ with caret range and pre-releases * Replaced conflicted "env" pattern by "sysenv" for `"platformio.ini" Dynamic Variables" `__ (`issue #1705 `_) @@ -176,7 +192,7 @@ PlatformIO 3.0 (`issue #1612 `_) * Configure a custom path to SVD file using `debug_svd_path `__ option -* Custom project `description `_ +* Custom project `description `_ which will be used by `PlatformIO Home `_ * Updated Unity tool to 2.4.3 * Improved support for Black Magic Probe in "uploader" mode @@ -202,9 +218,9 @@ PlatformIO 3.0 - Multiple themes (Dark & Light) - Ability to specify a name for new project -* Control `PIO Unified Debugger `__ +* Control `PIO Unified Debugger `__ and its firmware loading mode using - `debug_load_mode `__ option + `debug_load_mode `__ option * Added aliases (off, light, strict) for `LDF Compatibility Mode `__ * Search for a library using PIO Library Registry ID ``id:X`` (e.g. ``pio lib search id:13``) diff --git a/README.rst b/README.rst index 76e1665c..ee1f6896 100644 --- a/README.rst +++ b/README.rst @@ -78,6 +78,7 @@ Registry Development Platforms --------------------- +* `Aceinna IMU `_ * `Atmel AVR `_ * `Atmel SAM `_ * `Espressif 32 `_ @@ -86,6 +87,7 @@ Development Platforms * `Infineon XMC `_ * `Intel ARC32 `_ * `Intel MCS-51 (8051) `_ +* `Kendryte K210 `_ * `Lattice iCE40 `_ * `Maxim 32 `_ * `Microchip PIC32 `_ @@ -93,6 +95,7 @@ Development Platforms * `Nordic nRF52 `_ * `NXP LPC `_ * `RISC-V `_ +* `RISC-V GAP `_ * `Samsung ARTIK `_ * `Silicon Labs EFM32 `_ * `ST STM32 `_ @@ -112,8 +115,11 @@ Frameworks * `ESP-IDF `_ * `ESP8266 Non-OS SDK `_ * `ESP8266 RTOS SDK `_ +* `Freedom E SDK `_ +* `Kendryte Standalone SDK `_ * `libOpenCM3 `_ * `mbed `_ +* `PULP OS `_ * `Pumbaa `_ * `Simba `_ * `SPL `_ diff --git a/docs b/docs index 1532572c..c5a33bc0 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 1532572c6259b98b687ca61abe80b35f8bc00093 +Subproject commit c5a33bc006dfedfa2d1a2ab1a340615467b885fd diff --git a/examples b/examples index 9c16f551..46bac491 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 9c16f551d72e37aa4c2f28555487e2fb23408b5d +Subproject commit 46bac491dc59bfdc3baf8edafe091cd8b30b0d10 diff --git a/platformio/__init__.py b/platformio/__init__.py index 584acb8b..232433f9 100644 --- a/platformio/__init__.py +++ b/platformio/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -VERSION = (4, 0, "0a7") +VERSION = (4, 0, "0a12") __version__ = ".".join([str(s) for s in VERSION]) __title__ = "platformio" diff --git a/platformio/__main__.py b/platformio/__main__.py index 0252aba6..9138fe3c 100644 --- a/platformio/__main__.py +++ b/platformio/__main__.py @@ -14,53 +14,13 @@ import os import sys -from os.path import isdir, isfile, join from platform import system from traceback import format_exc import click from platformio import __version__, exception, maintenance -from platformio.util import get_source_dir - - -class PlatformioCLI(click.MultiCommand): - - def list_commands(self, ctx): - cmds = [] - commands_dir = join(get_source_dir(), "commands") - for name in os.listdir(commands_dir): - if name.startswith("__init__"): - continue - if (isdir(join(commands_dir, name)) - and isfile(join(commands_dir, name, "command.py"))): - cmds.append(name) - elif name.endswith(".py"): - cmds.append(name[:-3]) - cmds.sort() - return cmds - - def get_command(self, ctx, cmd_name): - mod = None - try: - mod = __import__("platformio.commands." + cmd_name, None, None, - ["cli"]) - except ImportError: - try: - return self._handle_obsolate_command(cmd_name) - except AttributeError: - raise click.UsageError('No such command "%s"' % cmd_name, ctx) - return mod.cli - - @staticmethod - def _handle_obsolate_command(name): - if name == "platforms": - from platformio.commands import platform - return platform.cli - if name == "serialports": - from platformio.commands import device - return device.cli - raise AttributeError() +from platformio.commands import PlatformioCLI @click.command( diff --git a/platformio/builder/main.py b/platformio/builder/main.py index 2afb2289..c656da0b 100644 --- a/platformio/builder/main.py +++ b/platformio/builder/main.py @@ -19,11 +19,20 @@ from os import environ from os.path import expanduser, join from time import time -from SCons.Script import (ARGUMENTS, COMMAND_LINE_TARGETS, DEFAULT_TARGETS, - AllowSubstExceptions, AlwaysBuild, Default, - DefaultEnvironment, Variables) +from SCons.Script import ARGUMENTS # pylint: disable=import-error +from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error +from SCons.Script import DEFAULT_TARGETS # pylint: disable=import-error +from SCons.Script import AllowSubstExceptions # pylint: disable=import-error +from SCons.Script import AlwaysBuild # pylint: disable=import-error +from SCons.Script import Default # pylint: disable=import-error +from SCons.Script import DefaultEnvironment # pylint: disable=import-error +from SCons.Script import Variables # pylint: disable=import-error from platformio import util +from platformio.project.helpers import ( + get_project_dir, get_project_optional_dir, get_projectbuild_dir, + get_projectdata_dir, get_projectinclude_dir, get_projectlib_dir, + get_projectlibdeps_dir, get_projectsrc_dir, get_projecttest_dir) AllowSubstExceptions(NameError) @@ -96,19 +105,19 @@ DEFAULT_ENV_OPTIONS = dict( ENV=environ, UNIX_TIME=int(time()), PIOHOME_DIR=util.get_home_dir(), - PROJECT_DIR=util.get_project_dir(), - PROJECTINCLUDE_DIR=util.get_projectinclude_dir(), - PROJECTSRC_DIR=util.get_projectsrc_dir(), - PROJECTTEST_DIR=util.get_projecttest_dir(), - PROJECTDATA_DIR=util.get_projectdata_dir(), - PROJECTBUILD_DIR=util.get_projectbuild_dir(), + PROJECT_DIR=get_project_dir(), + PROJECTINCLUDE_DIR=get_projectinclude_dir(), + PROJECTSRC_DIR=get_projectsrc_dir(), + PROJECTTEST_DIR=get_projecttest_dir(), + PROJECTDATA_DIR=get_projectdata_dir(), + PROJECTBUILD_DIR=get_projectbuild_dir(), BUILD_DIR=join("$PROJECTBUILD_DIR", "$PIOENV"), BUILDSRC_DIR=join("$BUILD_DIR", "src"), BUILDTEST_DIR=join("$BUILD_DIR", "test"), LIBPATH=["$BUILD_DIR"], LIBSOURCE_DIRS=[ - util.get_projectlib_dir(), - util.get_projectlibdeps_dir(), + get_projectlib_dir(), + get_projectlibdeps_dir(), join("$PIOHOME_DIR", "lib") ], PROGNAME="program", @@ -151,10 +160,10 @@ for var in ("BUILD_FLAGS", "SRC_BUILD_FLAGS", "SRC_FILTER", "EXTRA_SCRIPTS", env.Append(**{var: util.parse_conf_multi_values(environ.get(k))}) # Configure extra library source directories for LDF -if util.get_project_optional_dir("lib_extra_dirs"): +if get_project_optional_dir("lib_extra_dirs"): env.Prepend( LIBSOURCE_DIRS=util.parse_conf_multi_values( - util.get_project_optional_dir("lib_extra_dirs"))) + get_project_optional_dir("lib_extra_dirs"))) env.Prepend(LIBSOURCE_DIRS=env.get("LIB_EXTRA_DIRS", [])) env['LIBSOURCE_DIRS'] = [ expanduser(d) if d.startswith("~") else d for d in env['LIBSOURCE_DIRS'] diff --git a/platformio/builder/tools/pioide.py b/platformio/builder/tools/pioide.py index 0e6bf31a..db496741 100644 --- a/platformio/builder/tools/pioide.py +++ b/platformio/builder/tools/pioide.py @@ -18,7 +18,7 @@ from glob import glob from os import environ from os.path import abspath, isfile, join -from SCons.Defaults import processDefines +from SCons.Defaults import processDefines # pylint: disable=import-error from platformio import util from platformio.managers.core import get_core_package_dir diff --git a/platformio/builder/tools/piolib.py b/platformio/builder/tools/piolib.py index 221df7e8..e1a0b50b 100644 --- a/platformio/builder/tools/piolib.py +++ b/platformio/builder/tools/piolib.py @@ -26,8 +26,10 @@ from glob import glob from os.path import (basename, commonprefix, dirname, isdir, isfile, join, realpath, sep) -import SCons.Scanner -from SCons.Script import ARGUMENTS, COMMAND_LINE_TARGETS, DefaultEnvironment +import SCons.Scanner # pylint: disable=import-error +from SCons.Script import ARGUMENTS # pylint: disable=import-error +from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error +from SCons.Script import DefaultEnvironment # pylint: disable=import-error from platformio import exception, util from platformio.builder.tools import platformio as piotool @@ -44,8 +46,8 @@ class LibBuilderFactory(object): clsname = "PlatformIOLibBuilder" else: used_frameworks = LibBuilderFactory.get_used_frameworks(env, path) - common_frameworks = ( - set(env.get("PIOFRAMEWORK", [])) & set(used_frameworks)) + common_frameworks = (set(env.get("PIOFRAMEWORK", [])) + & set(used_frameworks)) if common_frameworks: clsname = "%sLibBuilder" % list(common_frameworks)[0].title() elif used_frameworks: @@ -719,6 +721,18 @@ class PlatformIOLibBuilder(LibBuilderBase): def _is_arduino_manifest(self): return isfile(join(self.path, "library.properties")) + @property + def include_dir(self): + if "includeDir" in self._manifest.get("build", {}): + return self._manifest.get("build").get("includeDir") + return LibBuilderBase.include_dir.fget(self) + + @property + def src_dir(self): + if "srcDir" in self._manifest.get("build", {}): + return self._manifest.get("build").get("srcDir") + return LibBuilderBase.src_dir.fget(self) + @property def src_filter(self): if "srcFilter" in self._manifest.get("build", {}): diff --git a/platformio/builder/tools/piomisc.py b/platformio/builder/tools/piomisc.py index 45212415..01a797ea 100644 --- a/platformio/builder/tools/piomisc.py +++ b/platformio/builder/tools/piomisc.py @@ -21,8 +21,8 @@ from os import environ, remove, walk from os.path import basename, isdir, isfile, join, realpath, relpath, sep from tempfile import mkstemp -from SCons.Action import Action -from SCons.Script import ARGUMENTS +from SCons.Action import Action # pylint: disable=import-error +from SCons.Script import ARGUMENTS # pylint: disable=import-error from platformio import util from platformio.managers.core import get_core_package_dir @@ -296,8 +296,7 @@ def ProcessDebug(env): if not env.subst("$PIODEBUGFLAGS"): env.Replace(PIODEBUGFLAGS=["-Og", "-g3", "-ggdb3"]) env.Append( - PIODEBUGFLAGS=["-D__PLATFORMIO_DEBUG__"], - BUILD_FLAGS=env.get("PIODEBUGFLAGS", [])) + BUILD_FLAGS=list(env['PIODEBUGFLAGS']) + ["-D__PLATFORMIO_DEBUG__"]) unflags = ["-Os"] for level in [0, 1, 2]: for flag in ("O", "g", "ggdb"): diff --git a/platformio/builder/tools/pioplatform.py b/platformio/builder/tools/pioplatform.py index 358aee7d..6427b33c 100644 --- a/platformio/builder/tools/pioplatform.py +++ b/platformio/builder/tools/pioplatform.py @@ -18,7 +18,7 @@ import base64 import sys from os.path import isdir, isfile, join -from SCons.Script import COMMAND_LINE_TARGETS +from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error from platformio import exception, util from platformio.managers.platform import PlatformFactory diff --git a/platformio/builder/tools/pioupload.py b/platformio/builder/tools/pioupload.py index f9628085..0d5a55ea 100644 --- a/platformio/builder/tools/pioupload.py +++ b/platformio/builder/tools/pioupload.py @@ -22,7 +22,7 @@ from os.path import isfile, join from shutil import copyfile from time import sleep -from SCons.Script import ARGUMENTS +from SCons.Script import ARGUMENTS # pylint: disable=import-error from serial import Serial, SerialException from platformio import exception, util diff --git a/platformio/builder/tools/platformio.py b/platformio/builder/tools/platformio.py index 2a539e10..41f32700 100644 --- a/platformio/builder/tools/platformio.py +++ b/platformio/builder/tools/platformio.py @@ -20,9 +20,12 @@ from glob import glob from os import sep, walk from os.path import basename, dirname, isdir, join, realpath -from SCons import Builder, Util -from SCons.Script import (COMMAND_LINE_TARGETS, AlwaysBuild, - DefaultEnvironment, Export, SConscript) +from SCons import Builder, Util # pylint: disable=import-error +from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error +from SCons.Script import AlwaysBuild # pylint: disable=import-error +from SCons.Script import DefaultEnvironment # pylint: disable=import-error +from SCons.Script import Export # pylint: disable=import-error +from SCons.Script import SConscript # pylint: disable=import-error from platformio.util import glob_escape, pioversion_to_intstr, string_types diff --git a/platformio/commands/__init__.py b/platformio/commands/__init__.py index b0514903..83c88e11 100644 --- a/platformio/commands/__init__.py +++ b/platformio/commands/__init__.py @@ -11,3 +11,51 @@ # 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 +from os.path import dirname + +import click + + +class PlatformioCLI(click.MultiCommand): + + leftover_args = [] + + def invoke(self, ctx): + PlatformioCLI.leftover_args = ctx.args + if hasattr(ctx, "protected_args"): + PlatformioCLI.leftover_args = ctx.protected_args + ctx.args + return super(PlatformioCLI, self).invoke(ctx) + + def list_commands(self, ctx): + cmds = [] + for filename in os.listdir(dirname(__file__)): + if filename.startswith("__init__"): + continue + if filename.endswith(".py"): + cmds.append(filename[:-3]) + cmds.sort() + return cmds + + def get_command(self, ctx, cmd_name): + mod = None + try: + mod = __import__("platformio.commands." + cmd_name, None, None, + ["cli"]) + except ImportError: + try: + return self._handle_obsolate_command(cmd_name) + except AttributeError: + raise click.UsageError('No such command "%s"' % cmd_name, ctx) + return mod.cli + + @staticmethod + def _handle_obsolate_command(name): + if name == "platforms": + from platformio.commands import platform + return platform.cli + if name == "serialports": + from platformio.commands import device + return device.cli + raise AttributeError() diff --git a/platformio/commands/ci.py b/platformio/commands/ci.py index e1b60087..9fd157e3 100644 --- a/platformio/commands/ci.py +++ b/platformio/commands/ci.py @@ -25,6 +25,7 @@ from platformio.commands.init import cli as cmd_init from platformio.commands.init import validate_boards from platformio.commands.run import cli as cmd_run from platformio.exception import CIBuildEnvsEmpty +from platformio.project.config import ProjectConfig def validate_path(ctx, param, value): # pylint: disable=unused-argument @@ -58,7 +59,7 @@ def validate_path(ctx, param, value): # pylint: disable=unused-argument file_okay=False, dir_okay=True, writable=True, resolve_path=True)) @click.option("--keep-build-dir", is_flag=True) @click.option( - "-C", + "-c", "--project-conf", type=click.Path( exists=True, @@ -161,8 +162,7 @@ def _exclude_contents(dst_dir, patterns): def _copy_project_conf(build_dir, project_conf): - config = util.load_project_config(project_conf) + config = ProjectConfig(project_conf, parse_extra=False) if config.has_section("platformio"): config.remove_section("platformio") - with open(join(build_dir, "platformio.ini"), "w") as fp: - config.write(fp) + config.save(join(build_dir, "platformio.ini")) diff --git a/platformio/commands/device.py b/platformio/commands/device.py index 0a8ea38f..e75660ea 100644 --- a/platformio/commands/device.py +++ b/platformio/commands/device.py @@ -15,11 +15,13 @@ import json import sys from os import getcwd +from os.path import join import click from serial.tools import miniterm from platformio import exception, util +from platformio.project.config import ProjectConfig @click.group(short_help="Monitor device or list existing") @@ -161,11 +163,10 @@ def device_list( # pylint: disable=too-many-branches help="Load configuration from `platformio.ini` and specified environment") def device_monitor(**kwargs): # pylint: disable=too-many-branches try: - project_options = get_project_options(kwargs['project_dir'], + monitor_options = get_project_options(kwargs['project_dir'], kwargs['environment']) - monitor_options = {k: v for k, v in project_options or []} if monitor_options: - for k in ("port", "baud", "speed", "rts", "dtr"): + for k in ("port", "speed", "rts", "dtr"): k2 = "monitor_%s" % k if k == "speed": k = "baud" @@ -205,24 +206,13 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches raise exception.MinitermException(e) -def get_project_options(project_dir, environment): - config = util.load_project_config(project_dir) - if not config.sections(): - return None - - known_envs = [s[4:] for s in config.sections() if s.startswith("env:")] - if environment: - if environment in known_envs: - return config.items("env:%s" % environment) - raise exception.UnknownEnvNames(environment, ", ".join(known_envs)) - - if not known_envs: - return None - - if config.has_option("platformio", "env_default"): - env_default = config.get("platformio", - "env_default").split(", ")[0].strip() - if env_default and env_default in known_envs: - return config.items("env:%s" % env_default) - - return config.items("env:%s" % known_envs[0]) +def get_project_options(project_dir, environment=None): + config = ProjectConfig.get_instance(join(project_dir, "platformio.ini")) + config.validate(envs=[environment] if environment else None) + if not environment: + default_envs = config.default_envs() + if default_envs: + environment = default_envs[0] + else: + environment = config.envs()[0] + return config.items(env=environment, as_dict=True) diff --git a/platformio/commands/init.py b/platformio/commands/init.py index e7127313..70f8a8e4 100644 --- a/platformio/commands/init.py +++ b/platformio/commands/init.py @@ -16,16 +16,18 @@ from os import getcwd, makedirs from os.path import isdir, isfile, join -from shutil import copyfile import click from platformio import exception, util from platformio.commands.platform import \ platform_install as cli_platform_install -from platformio.commands.run import check_project_envs from platformio.ide.projectgenerator import ProjectGenerator from platformio.managers.platform import PlatformManager +from platformio.project.config import ProjectConfig +from platformio.project.helpers import ( + get_projectinclude_dir, get_projectlib_dir, get_projectsrc_dir, + get_projecttest_dir, is_platformio_project) def validate_boards(ctx, param, value): # pylint: disable=W0613 @@ -88,18 +90,17 @@ def cli( click.echo("%s - Project Configuration File" % click.style( "platformio.ini", fg="cyan")) - is_new_project = not util.is_platformio_project(project_dir) - init_base_project(project_dir) + is_new_project = not is_platformio_project(project_dir) + if is_new_project: + init_base_project(project_dir) if board: fill_project_envs(ctx, project_dir, board, project_option, env_prefix, ide is not None) if ide: - env_name = get_best_envname(project_dir, board) - if not env_name: - raise exception.BoardNotDefined() - pg = ProjectGenerator(project_dir, ide, env_name) + pg = ProjectGenerator(project_dir, ide, + get_best_envname(project_dir, board)) pg.generate() if is_new_project: @@ -128,38 +129,36 @@ def cli( def get_best_envname(project_dir, boards=None): - config = util.load_project_config(project_dir) - env_default = None - if config.has_option("platformio", "env_default"): - env_default = util.parse_conf_multi_values( - config.get("platformio", "env_default")) - check_project_envs(config, env_default) - if env_default: - return env_default[0] - section = None - for section in config.sections(): - if not section.startswith("env:"): - continue - elif config.has_option(section, "board") and (not boards or config.get( - section, "board") in boards): - break - return section[4:] if section else None + config = ProjectConfig(join(project_dir, "platformio.ini")) + config.validate() + + envname = None + default_envs = config.default_envs() + if default_envs: + envname = default_envs[0] + if not boards: + return envname + + for env in config.envs(): + if not boards: + return env + if not envname: + envname = env + items = config.items(env=env, as_dict=True) + if "board" in items and items.get("board") in boards: + return env + + return envname def init_base_project(project_dir): - if util.is_platformio_project(project_dir): - return - - copyfile( - join(util.get_source_dir(), "projectconftpl.ini"), - join(project_dir, "platformio.ini")) - + ProjectConfig(join(project_dir, "platformio.ini")).save() with util.cd(project_dir): dir_to_readme = [ - (util.get_projectsrc_dir(), None), - (util.get_projectinclude_dir(), init_include_readme), - (util.get_projectlib_dir(), init_lib_readme), - (util.get_projecttest_dir(), init_test_readme), + (get_projectsrc_dir(), None), + (get_projectinclude_dir(), init_include_readme), + (get_projectlib_dir(), init_lib_readme), + (get_projecttest_dir(), init_test_readme), ] for (path, cb) in dir_to_readme: if isdir(path): @@ -365,11 +364,9 @@ def init_cvs_ignore(project_dir): def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix, force_download): - content = [] + config = ProjectConfig( + join(project_dir, "platformio.ini"), parse_extra=False) used_boards = [] - used_platforms = [] - - config = util.load_project_config(project_dir) for section in config.sections(): cond = [ section.startswith("env:"), @@ -379,12 +376,15 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix, used_boards.append(config.get(section, "board")) pm = PlatformManager() + 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 @@ -398,20 +398,18 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix, _name, _value = item.split("=", 1) envopts[_name.strip()] = _value.strip() - content.append("") - content.append("[env:%s%s]" % (env_prefix, id_)) - for name, value in envopts.items(): - content.append("%s = %s" % (name, value)) + 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 not content: - return - - with open(join(project_dir, "platformio.ini"), "a") as f: - content.append("") - f.write("\n".join(content)) + if modified: + config.save() + config.reset_instances() def _install_dependent_platforms(ctx, platforms): diff --git a/platformio/commands/lib.py b/platformio/commands/lib.py index 2f09cd9c..c52350b1 100644 --- a/platformio/commands/lib.py +++ b/platformio/commands/lib.py @@ -21,8 +21,10 @@ from os.path import isdir, join import click from platformio import exception, util +from platformio.commands import PlatformioCLI from platformio.managers.lib import LibraryManager, get_builtin_libs -from platformio.util import get_api_result +from platformio.project.helpers import ( + get_project_dir, get_projectlibdeps_dir, is_platformio_project) try: from urllib.parse import quote @@ -58,8 +60,8 @@ def cli(ctx, **options): if not storage_dir: if options['global']: storage_dir = join(util.get_home_dir(), "lib") - elif util.is_platformio_project(): - storage_dir = util.get_projectlibdeps_dir() + elif is_platformio_project(): + storage_dir = get_projectlibdeps_dir() elif util.is_ci(): storage_dir = join(util.get_home_dir(), "lib") click.secho( @@ -67,17 +69,17 @@ def cli(ctx, **options): "Please use `platformio lib --global %s` command to remove " "this warning." % ctx.invoked_subcommand, fg="yellow") - elif util.is_platformio_project(storage_dir): + elif is_platformio_project(storage_dir): with util.cd(storage_dir): - storage_dir = util.get_projectlibdeps_dir() + storage_dir = get_projectlibdeps_dir() - if not storage_dir and not util.is_platformio_project(): - raise exception.NotGlobalLibDir(util.get_project_dir(), + if not storage_dir and not is_platformio_project(): + raise exception.NotGlobalLibDir(get_project_dir(), join(util.get_home_dir(), "lib"), ctx.invoked_subcommand) ctx.obj = LibraryManager(storage_dir) - if "--json-output" not in ctx.args: + if "--json-output" not in PlatformioCLI.leftover_args: click.echo("Library Storage: " + storage_dir) @@ -211,7 +213,7 @@ def lib_search(query, json_output, page, noninteractive, **filters): for value in values: query.append('%s:"%s"' % (key, value)) - result = get_api_result( + result = util.get_api_result( "/v2/lib/search", dict(query=" ".join(query), page=page), cache_valid="1d") @@ -258,7 +260,7 @@ def lib_search(query, json_output, page, noninteractive, **filters): time.sleep(5) elif not click.confirm("Show next libraries?"): break - result = get_api_result( + result = util.get_api_result( "/v2/lib/search", { "query": " ".join(query), "page": int(result['page']) + 1 @@ -317,7 +319,7 @@ def lib_show(library, json_output): }, silent=json_output, interactive=not json_output) - lib = get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") + lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") if json_output: return click.echo(json.dumps(lib)) @@ -393,7 +395,8 @@ def lib_register(config_url): and not config_url.startswith("https://")): raise exception.InvalidLibConfURL(config_url) - result = get_api_result("/lib/register", data=dict(config_url=config_url)) + result = util.get_api_result( + "/lib/register", data=dict(config_url=config_url)) if "message" in result and result['message']: click.secho( result['message'], @@ -404,7 +407,7 @@ def lib_register(config_url): @cli.command("stats", short_help="Library Registry Statistics") @click.option("--json-output", is_flag=True) def lib_stats(json_output): - result = get_api_result("/lib/stats", cache_valid="1h") + result = util.get_api_result("/lib/stats", cache_valid="1h") if json_output: return click.echo(json.dumps(result)) diff --git a/platformio/commands/run.py b/platformio/commands/run.py index 7cac3eca..fe497f3b 100644 --- a/platformio/commands/run.py +++ b/platformio/commands/run.py @@ -12,20 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -from hashlib import sha1 -from os import getcwd, makedirs, walk +from os import getcwd, makedirs from os.path import getmtime, isdir, isfile, join from time import time import click -from platformio import __version__, exception, telemetry, util +from platformio import exception, telemetry, util from platformio.commands.device import device_monitor as cmd_device_monitor from platformio.commands.lib import lib_install as cmd_lib_install from platformio.commands.platform import \ platform_install as cmd_platform_install from platformio.managers.lib import LibraryManager, is_builtin_lib from platformio.managers.platform import PlatformFactory +from platformio.project.config import ProjectConfig +from platformio.project.helpers import ( + calculate_project_hash, find_project_dir_above, get_project_dir, + get_projectbuild_dir, get_projectlibdeps_dir) # pylint: disable=too-many-arguments,too-many-locals,too-many-branches @@ -44,50 +47,48 @@ from platformio.managers.platform import PlatformFactory dir_okay=True, writable=True, resolve_path=True)) +@click.option( + "-c", + "--project-conf", + type=click.Path( + exists=True, + file_okay=True, + dir_okay=False, + readable=True, + resolve_path=True)) @click.option("-s", "--silent", is_flag=True) @click.option("-v", "--verbose", is_flag=True) @click.option("--disable-auto-clean", is_flag=True) @click.pass_context -def cli(ctx, environment, target, upload_port, project_dir, silent, verbose, - disable_auto_clean): +def cli(ctx, environment, target, upload_port, project_dir, project_conf, + silent, verbose, disable_auto_clean): # find project directory on upper level if isfile(project_dir): - project_dir = util.find_project_dir_above(project_dir) - - if not util.is_platformio_project(project_dir): - raise exception.NotPlatformIOProject(project_dir) + project_dir = find_project_dir_above(project_dir) with util.cd(project_dir): # clean obsolete build dir if not disable_auto_clean: try: - _clean_build_dir(util.get_projectbuild_dir()) + _clean_build_dir(get_projectbuild_dir()) except: # pylint: disable=bare-except click.secho( "Can not remove temporary directory `%s`. Please remove " "it manually to avoid build issues" % - util.get_projectbuild_dir(force=True), + get_projectbuild_dir(force=True), fg="yellow") - config = util.load_project_config() - env_default = None - if config.has_option("platformio", "env_default"): - env_default = util.parse_conf_multi_values( - config.get("platformio", "env_default")) - - check_project_defopts(config) - check_project_envs(config, environment or env_default) + config = ProjectConfig.get_instance( + project_conf or join(project_dir, "platformio.ini")) + config.validate() results = [] start_time = time() - for section in config.sections(): - if not section.startswith("env:"): - continue - - envname = section[4:] + default_envs = config.default_envs() + for envname in config.envs(): skipenv = any([ environment and envname not in environment, not environment - and env_default and envname not in env_default + and default_envs and envname not in default_envs ]) if skipenv: results.append((envname, None)) @@ -96,9 +97,7 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose, if not silent and results: click.echo() - options = {} - for k, v in config.items(section): - options[k] = v + options = config.items(env=envname, as_dict=True) if "piotest" not in options and "piotest" in ctx.meta: options['piotest'] = ctx.meta['piotest'] @@ -127,26 +126,6 @@ class EnvironmentProcessor(object): DEFAULT_DUMP_OPTIONS = ("platform", "framework", "board") - KNOWN_PLATFORMIO_OPTIONS = [ - "description", "env_default", "home_dir", "lib_dir", "libdeps_dir", - "include_dir", "src_dir", "build_dir", "data_dir", "test_dir", - "boards_dir", "lib_extra_dirs" - ] - - KNOWN_ENV_OPTIONS = [ - "platform", "framework", "board", "build_flags", "src_build_flags", - "build_unflags", "src_filter", "extra_scripts", "targets", - "upload_port", "upload_protocol", "upload_speed", "upload_flags", - "upload_resetmethod", "lib_deps", "lib_ignore", "lib_extra_dirs", - "lib_ldf_mode", "lib_compat_mode", "lib_archive", "piotest", - "test_transport", "test_filter", "test_ignore", "test_port", - "test_speed", "test_build_project_src", "debug_tool", "debug_port", - "debug_init_cmds", "debug_extra_cmds", "debug_server", - "debug_init_break", "debug_load_cmd", "debug_load_mode", - "debug_svd_path", "monitor_port", "monitor_speed", "monitor_rts", - "monitor_dtr" - ] - IGNORE_BUILD_OPTIONS = [ "test_transport", "test_filter", "test_ignore", "test_port", "test_speed", "debug_port", "debug_init_cmds", "debug_extra_cmds", @@ -157,19 +136,6 @@ class EnvironmentProcessor(object): REMAPED_OPTIONS = {"framework": "pioframework", "platform": "pioplatform"} - RENAMED_OPTIONS = { - "lib_use": "lib_deps", - "lib_force": "lib_deps", - "extra_script": "extra_scripts", - "monitor_baud": "monitor_speed", - "board_mcu": "board_build.mcu", - "board_f_cpu": "board_build.f_cpu", - "board_f_flash": "board_build.f_flash", - "board_flash_mode": "board_build.flash_mode" - } - - RENAMED_PLATFORMS = {"espressif": "espressif8266"} - def __init__( self, # pylint: disable=R0913 cmd_ctx, @@ -203,7 +169,6 @@ class EnvironmentProcessor(object): self.name, fg="cyan", bold=True), "; ".join(env_dump))) click.secho("-" * terminal_width, bold=True) - self.options = self._validate_options(self.options) result = self._run() is_error = result['returncode'] != 0 @@ -220,39 +185,6 @@ class EnvironmentProcessor(object): return not is_error - def _validate_options(self, options): - result = {} - for k, v in options.items(): - # process obsolete options - if k in self.RENAMED_OPTIONS: - click.secho( - "Warning! `%s` option is deprecated and will be " - "removed in the next release! Please use " - "`%s` instead." % (k, self.RENAMED_OPTIONS[k]), - fg="yellow") - k = self.RENAMED_OPTIONS[k] - # process renamed platforms - if k == "platform" and v in self.RENAMED_PLATFORMS: - click.secho( - "Warning! Platform `%s` is deprecated and will be " - "removed in the next release! Please use " - "`%s` instead." % (v, self.RENAMED_PLATFORMS[v]), - fg="yellow") - v = self.RENAMED_PLATFORMS[v] - - # warn about unknown options - unknown_conditions = [ - k not in self.KNOWN_ENV_OPTIONS, not k.startswith("custom_"), - not k.startswith("board_") - ] - if all(unknown_conditions): - click.secho( - "Detected non-PlatformIO `%s` option in `[env:%s]` section" - % (k, self.name), - fg="yellow") - result[k] = v - return result - def get_build_variables(self): variables = {"pioenv": self.name} if self.upload_port: @@ -316,7 +248,7 @@ class EnvironmentProcessor(object): def _autoinstall_libdeps(ctx, libraries, verbose=False): if not libraries: return - storage_dir = util.get_projectlibdeps_dir() + storage_dir = get_projectlibdeps_dir() ctx.obj = LibraryManager(storage_dir) if verbose: click.echo("Library Storage: " + storage_dir) @@ -335,9 +267,8 @@ def _clean_build_dir(build_dir): proj_hash = calculate_project_hash() # if project's config is modified - if (isdir(build_dir) - and getmtime(join(util.get_project_dir(), - "platformio.ini")) > getmtime(build_dir)): + if (isdir(build_dir) and getmtime( + join(get_project_dir(), "platformio.ini")) > getmtime(build_dir)): util.rmtree_(build_dir) # check project structure @@ -391,21 +322,7 @@ def print_summary(results, start_time): is_error=not successed) -def check_project_defopts(config): - if not config.has_section("platformio"): - return True - unknown = set(k for k, _ in config.items("platformio")) - set( - EnvironmentProcessor.KNOWN_PLATFORMIO_OPTIONS) - if not unknown: - return True - click.secho( - "Warning! Ignore unknown `%s` option in `[platformio]` section" % - ", ".join(unknown), - fg="yellow") - return False - - -def check_project_envs(config, environments=None): +def check_project_envs(config, environments=None): # FIXME: Remove if not config.sections(): raise exception.ProjectEnvsNotAvailable() @@ -414,23 +331,3 @@ def check_project_envs(config, environments=None): if unknown: raise exception.UnknownEnvNames(", ".join(unknown), ", ".join(known)) return True - - -def calculate_project_hash(): - check_suffixes = (".c", ".cc", ".cpp", ".h", ".hpp", ".s", ".S") - chunks = [__version__] - for d in (util.get_projectsrc_dir(), util.get_projectlib_dir()): - if not isdir(d): - continue - for root, _, files in walk(d): - for f in files: - path = join(root, f) - if path.endswith(check_suffixes): - chunks.append(path) - chunks_to_str = ",".join(sorted(chunks)) - if "windows" in util.get_systype(): - # Fix issue with useless project rebuilding for case insensitive FS. - # A case of disk drive can differ... - chunks_to_str = chunks_to_str.lower() - return sha1( - chunks_to_str if util.PY2 else chunks_to_str.encode()).hexdigest() diff --git a/platformio/commands/upgrade.py b/platformio/commands/upgrade.py index ec4fe864..c47ff8dc 100644 --- a/platformio/commands/upgrade.py +++ b/platformio/commands/upgrade.py @@ -43,11 +43,10 @@ def cli(dev): get_pip_package(to_develop)], ["platformio", "--version"]) cmd = None - r = None + r = {} try: for cmd in cmds: cmd = [util.get_pythonexe_path(), "-m"] + cmd - r = None r = util.exec_command(cmd) # try pip with disabled cache diff --git a/platformio/downloader.py b/platformio/downloader.py index d8b2fd24..0b0ef5df 100644 --- a/platformio/downloader.py +++ b/platformio/downloader.py @@ -100,7 +100,7 @@ class FileDownloader(object): raise FDSizeMismatch(_dlsize, self._fname, self.get_size()) if not sha1: - return + return None dlsha1 = None try: @@ -113,11 +113,12 @@ class FileDownloader(object): dlsha1 = result['out'] except (OSError, ValueError): pass - - if dlsha1: - dlsha1 = dlsha1[1:41] if dlsha1.startswith("\\") else dlsha1[:40] - if sha1 != dlsha1: - raise FDSHASumMismatch(dlsha1, self._fname, sha1) + if not dlsha1: + return None + dlsha1 = dlsha1[1:41] if dlsha1.startswith("\\") else dlsha1[:40] + if sha1.lower() != dlsha1.lower(): + raise FDSHASumMismatch(dlsha1, self._fname, sha1) + return True def _preserve_filemtime(self, lmdate): timedata = parsedate_tz(lmdate) diff --git a/platformio/exception.py b/platformio/exception.py index 77837d7a..9af0f0fe 100644 --- a/platformio/exception.py +++ b/platformio/exception.py @@ -12,8 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# pylint: disable=not-an-iterable - class PlatformioException(Exception): @@ -21,7 +19,9 @@ class PlatformioException(Exception): def __str__(self): # pragma: no cover if self.MESSAGE: + # pylint: disable=not-an-iterable return self.MESSAGE.format(*self.args) + return super(PlatformioException, self).__str__() @@ -42,11 +42,16 @@ class UserSideException(PlatformioException): pass -class AbortedByUser(PlatformioException): +class AbortedByUser(UserSideException): MESSAGE = "Aborted by user" +# +# Development Platform +# + + class UnknownPlatform(PlatformioException): MESSAGE = "Unknown development platform '{0}'" @@ -63,13 +68,6 @@ class PlatformNotInstalledYet(PlatformioException): "Use `platformio platform install {0}` command") -class BoardNotDefined(PlatformioException): - - MESSAGE = ( - "You need to specify board ID using `-b` or `--board` option. " - "Supported boards list is available via `platformio boards` command") - - class UnknownBoard(PlatformioException): MESSAGE = "Unknown board ID '{0}'" @@ -85,54 +83,75 @@ class UnknownFramework(PlatformioException): MESSAGE = "Unknown framework '{0}'" -class UnknownPackage(PlatformioException): +# Package Manager + + +class PlatformIOPackageException(PlatformioException): + pass + + +class UnknownPackage(PlatformIOPackageException): MESSAGE = "Detected unknown package '{0}'" -class MissingPackageManifest(PlatformioException): +class MissingPackageManifest(PlatformIOPackageException): MESSAGE = "Could not find one of '{0}' manifest files in the package" -class UndefinedPackageVersion(PlatformioException): +class UndefinedPackageVersion(PlatformIOPackageException): MESSAGE = ("Could not find a version that satisfies the requirement '{0}'" " for your system '{1}'") -class PackageInstallError(PlatformioException): +class PackageInstallError(PlatformIOPackageException): MESSAGE = ("Could not install '{0}' with version requirements '{1}' " "for your system '{2}'.\n\n" "Please try this solution -> http://bit.ly/faq-package-manager") -class ExtractArchiveItemError(PlatformioException): +class ExtractArchiveItemError(PlatformIOPackageException): MESSAGE = ( "Could not extract `{0}` to `{1}`. Try to disable antivirus " "tool or check this solution -> http://bit.ly/faq-package-manager") -class FDUnrecognizedStatusCode(PlatformioException): +class UnsupportedArchiveType(PlatformIOPackageException): + + MESSAGE = "Can not unpack file '{0}'" + + +class FDUnrecognizedStatusCode(PlatformIOPackageException): MESSAGE = "Got an unrecognized status code '{0}' when downloaded {1}" -class FDSizeMismatch(PlatformioException): +class FDSizeMismatch(PlatformIOPackageException): MESSAGE = ("The size ({0:d} bytes) of downloaded file '{1}' " "is not equal to remote size ({2:d} bytes)") -class FDSHASumMismatch(PlatformioException): +class FDSHASumMismatch(PlatformIOPackageException): MESSAGE = ("The 'sha1' sum '{0}' of downloaded file '{1}' " "is not equal to remote '{2}'") -class NotPlatformIOProject(PlatformioException): +# +# Project +# + + +class PlatformIOProjectException(PlatformioException): + pass + + +class NotPlatformIOProject(PlatformIOProjectException): MESSAGE = ( "Not a PlatformIO project. `platformio.ini` file has not been " @@ -140,26 +159,82 @@ class NotPlatformIOProject(PlatformioException): "please use `platformio init` command") -class UndefinedEnvPlatform(PlatformioException): +class InvalidProjectConf(PlatformIOProjectException): + + MESSAGE = ("Invalid '{0}' (project configuration file): '{1}'") + + +class UndefinedEnvPlatform(PlatformIOProjectException): MESSAGE = "Please specify platform for '{0}' environment" -class UnsupportedArchiveType(PlatformioException): - - MESSAGE = "Can not unpack file '{0}'" - - -class ProjectEnvsNotAvailable(PlatformioException): +class ProjectEnvsNotAvailable(PlatformIOProjectException): MESSAGE = "Please setup environments in `platformio.ini` file" -class UnknownEnvNames(PlatformioException): +class UnknownEnvNames(PlatformIOProjectException): # FIXME: UnknownProjectEnvs MESSAGE = "Unknown environment names '{0}'. Valid names are '{1}'" +# +# Library +# + + +class LibNotFound(PlatformioException): + + MESSAGE = ("Library `{0}` has not been found in PlatformIO Registry.\n" + "You can ignore this message, if `{0}` is a built-in library " + "(included in framework, SDK). E.g., SPI, Wire, etc.") + + +class NotGlobalLibDir(UserSideException): + + MESSAGE = ( + "The `{0}` is not a PlatformIO project.\n\n" + "To manage libraries in global storage `{1}`,\n" + "please use `platformio lib --global {2}` or specify custom storage " + "`platformio lib --storage-dir /path/to/storage/ {2}`.\n" + "Check `platformio lib --help` for details.") + + +class InvalidLibConfURL(PlatformioException): + + MESSAGE = "Invalid library config URL '{0}'" + + +# +# UDEV Rules +# + + +class InvalidUdevRules(PlatformioException): + pass + + +class MissedUdevRules(InvalidUdevRules): + + MESSAGE = ( + "Warning! Please install `99-platformio-udev.rules`. \nMode details: " + "https://docs.platformio.org/en/latest/faq.html#platformio-udev-rules") + + +class OutdatedUdevRules(InvalidUdevRules): + + MESSAGE = ( + "Warning! Your `{0}` are outdated. Please update or reinstall them." + "\n Mode details: https://docs.platformio.org" + "/en/latest/faq.html#platformio-udev-rules") + + +# +# Misc +# + + class GetSerialPortsError(PlatformioException): MESSAGE = "No implementation for your platform ('{0}') available" @@ -175,7 +250,7 @@ class APIRequestError(PlatformioException): MESSAGE = "[API] {0}" -class InternetIsOffline(PlatformioException): +class InternetIsOffline(UserSideException): MESSAGE = ( "You are not connected to the Internet.\n" @@ -183,33 +258,6 @@ class InternetIsOffline(PlatformioException): "to install all dependencies and toolchains.") -class LibNotFound(PlatformioException): - - MESSAGE = ("Library `{0}` has not been found in PlatformIO Registry.\n" - "You can ignore this message, if `{0}` is a built-in library " - "(included in framework, SDK). E.g., SPI, Wire, etc.") - - -class NotGlobalLibDir(PlatformioException): - - MESSAGE = ( - "The `{0}` is not a PlatformIO project.\n\n" - "To manage libraries in global storage `{1}`,\n" - "please use `platformio lib --global {2}` or specify custom storage " - "`platformio lib --storage-dir /path/to/storage/ {2}`.\n" - "Check `platformio lib --help` for details.") - - -class InvalidLibConfURL(PlatformioException): - - MESSAGE = "Invalid library config URL '{0}'" - - -class InvalidProjectConf(PlatformioException): - - MESSAGE = "Invalid `platformio.ini`, project configuration file: '{0}'" - - class BuildScriptNotFound(PlatformioException): MESSAGE = "Invalid path '{0}' to build script" @@ -237,25 +285,6 @@ class CIBuildEnvsEmpty(PlatformioException): "predefined environments using `--project-conf` option") -class InvalidUdevRules(PlatformioException): - pass - - -class MissedUdevRules(InvalidUdevRules): - - MESSAGE = ( - "Warning! Please install `99-platformio-udev.rules`. \nMode details: " - "https://docs.platformio.org/en/latest/faq.html#platformio-udev-rules") - - -class OutdatedUdevRules(InvalidUdevRules): - - MESSAGE = ( - "Warning! Your `{0}` are outdated. Please update or reinstall them." - "\n Mode details: https://docs.platformio.org" - "/en/latest/faq.html#platformio-udev-rules") - - class UpgradeError(PlatformioException): MESSAGE = """{0} @@ -290,7 +319,6 @@ class DebugSupportError(PlatformioException): class DebugInvalidOptions(PlatformioException): - pass diff --git a/platformio/ide/projectgenerator.py b/platformio/ide/projectgenerator.py index cd48ca61..9cbf3028 100644 --- a/platformio/ide/projectgenerator.py +++ b/platformio/ide/projectgenerator.py @@ -23,6 +23,9 @@ from click.testing import CliRunner from platformio import exception, util from platformio.commands.run import cli as cmd_run +from platformio.project.config import ProjectConfig +from platformio.project.helpers import ( + get_projectlib_dir, get_projectlibdeps_dir, get_projectsrc_dir) class ProjectGenerator(object): @@ -44,15 +47,13 @@ class ProjectGenerator(object): @util.memoized() def get_project_env(self): data = {} - config = util.load_project_config(self.project_dir) - for section in config.sections(): - if not section.startswith("env:"): + config = ProjectConfig.get_instance( + join(self.project_dir, "platformio.ini")) + for env in config.envs(): + if self.env_name != env: continue - if self.env_name != section[4:]: - continue - data = {"env_name": section[4:]} - for k, v in config.items(section): - data[k] = v + data = config.items(env=env, as_dict=True) + data['env_name'] = self.env_name return data def get_project_build_data(self): @@ -89,7 +90,7 @@ class ProjectGenerator(object): def get_src_files(self): result = [] with util.cd(self.project_dir): - for root, _, files in os.walk(util.get_projectsrc_dir()): + for root, _, files in os.walk(get_projectsrc_dir()): for f in files: result.append(relpath(join(root, f))) return result @@ -141,9 +142,9 @@ class ProjectGenerator(object): "src_files": self.get_src_files(), "user_home_dir": abspath(expanduser("~")), "project_dir": self.project_dir, - "project_src_dir": util.get_projectsrc_dir(), - "project_lib_dir": util.get_projectlib_dir(), - "project_libdeps_dir": util.get_projectlibdeps_dir(), + "project_src_dir": get_projectsrc_dir(), + "project_lib_dir": get_projectlib_dir(), + "project_libdeps_dir": get_projectlibdeps_dir(), "systype": util.get_systype(), "platformio_path": self._fix_os_path( sys.argv[0] if isfile(sys.argv[0]) diff --git a/platformio/maintenance.py b/platformio/maintenance.py index 47105a5b..8841b7f5 100644 --- a/platformio/maintenance.py +++ b/platformio/maintenance.py @@ -22,6 +22,7 @@ import click import semantic_version from platformio import __version__, app, exception, telemetry, util +from platformio.commands import PlatformioCLI from platformio.commands.lib import lib_update as cmd_lib_update from platformio.commands.platform import \ platform_install as cmd_platform_install @@ -40,12 +41,12 @@ def on_platformio_start(ctx, force, caller): set_caller(caller) telemetry.on_command() - if not in_silence(ctx): + if not in_silence(): after_upgrade(ctx) -def on_platformio_end(ctx, result): # pylint: disable=W0613 - if in_silence(ctx): +def on_platformio_end(ctx, result): # pylint: disable=unused-argument + if in_silence(): return try: @@ -64,14 +65,11 @@ def on_platformio_exception(e): telemetry.on_exception(e) -def in_silence(ctx=None): - ctx = ctx or app.get_session_var("command_ctx") - if not ctx: - return True - return ctx.args and any([ - ctx.args[0] == "debug" and "--interpreter" in " ".join(ctx.args), - ctx.args[0] == "upgrade", "--json-output" in ctx.args, - "--version" in ctx.args +def in_silence(): + args = PlatformioCLI.leftover_args + return args and any([ + args[0] == "debug" and "--interpreter" in " ".join(args), + args[0] == "upgrade", "--json-output" in args, "--version" in args ]) diff --git a/platformio/managers/core.py b/platformio/managers/core.py index a6598361..1e469a03 100644 --- a/platformio/managers/core.py +++ b/platformio/managers/core.py @@ -24,11 +24,12 @@ from platformio import __version__, exception, util from platformio.managers.package import PackageManager CORE_PACKAGES = { - "contrib-piohome": "^2.0.0", - "contrib-pysite": "~2.%d%d.0" % (sys.version_info[0], sys.version_info[1]), - "tool-pioplus": "^2.0.2", + "contrib-piohome": "^2.0.1", + "contrib-pysite": + "~2.%d%d.190418" % (sys.version_info[0], sys.version_info[1]), + "tool-pioplus": "^2.2.0", "tool-unity": "~1.20403.0", - "tool-scons": "~2.20501.7" if util.PY2 else "~3.30003.0" + "tool-scons": "~2.20501.7" if util.PY2 else "~3.30005.0" } PIOPLUS_AUTO_UPDATES_MAX = 100 diff --git a/platformio/managers/lib.py b/platformio/managers/lib.py index 3ca89d49..4cbb34a7 100644 --- a/platformio/managers/lib.py +++ b/platformio/managers/lib.py @@ -164,7 +164,7 @@ class LibraryManager(BasePkgManager): semver_spec = self.parse_semver_spec( requirements) if requirements else None - item = None + item = {} for v in versions: semver_new = self.parse_semver_version(v['name']) diff --git a/platformio/managers/platform.py b/platformio/managers/platform.py index 851011a9..bfcda3f2 100644 --- a/platformio/managers/platform.py +++ b/platformio/managers/platform.py @@ -25,6 +25,7 @@ import semantic_version from platformio import __version__, app, exception, util from platformio.managers.core import get_core_package_dir from platformio.managers.package import BasePkgManager, PackageManager +from platformio.project.helpers import get_projectboards_dir try: from urllib.parse import quote @@ -566,7 +567,7 @@ class PlatformBase( # pylint: disable=too-many-public-methods self._BOARDS_CACHE[board_id] = config bdirs = [ - util.get_projectboards_dir(), + get_projectboards_dir(), join(util.get_home_dir(), "boards"), join(self.get_dir(), "boards"), ] diff --git a/platformio/project/__init__.py b/platformio/project/__init__.py new file mode 100644 index 00000000..b0514903 --- /dev/null +++ b/platformio/project/__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/config.py b/platformio/project/config.py new file mode 100644 index 00000000..d2e77bb7 --- /dev/null +++ b/platformio/project/config.py @@ -0,0 +1,329 @@ +# 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 glob +import json +import os +import re +from os.path import isfile + +import click + +from platformio import exception + +try: + import ConfigParser as ConfigParser +except ImportError: + import configparser as ConfigParser + +CONFIG_HEADER = """;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +""" + +KNOWN_PLATFORMIO_OPTIONS = [ + "description", + "env_default", + "extra_configs", + + # Dirs + "home_dir", + "lib_dir", + "libdeps_dir", + "include_dir", + "src_dir", + "build_dir", + "data_dir", + "test_dir", + "boards_dir", + "lib_extra_dirs" +] + +KNOWN_ENV_OPTIONS = [ + # Generic + "platform", + "framework", + "board", + "targets", + + # Build + "build_flags", + "src_build_flags", + "build_unflags", + "src_filter", + + # Upload + "upload_port", + "upload_protocol", + "upload_speed", + "upload_flags", + "upload_resetmethod", + + # Monitor + "monitor_port", + "monitor_speed", + "monitor_rts", + "monitor_dtr", + + # Library + "lib_deps", + "lib_ignore", + "lib_extra_dirs", + "lib_ldf_mode", + "lib_compat_mode", + "lib_archive", + + # Test + "piotest", + "test_filter", + "test_ignore", + "test_port", + "test_speed", + "test_transport", + "test_build_project_src", + + # Debug + "debug_tool", + "debug_init_break", + "debug_init_cmds", + "debug_extra_cmds", + "debug_load_cmd", + "debug_load_mode", + "debug_server", + "debug_port", + "debug_svd_path", + + # Other + "extra_scripts" +] + +RENAMED_OPTIONS = { + "lib_use": "lib_deps", + "lib_force": "lib_deps", + "extra_script": "extra_scripts", + "monitor_baud": "monitor_speed", + "board_mcu": "board_build.mcu", + "board_f_cpu": "board_build.f_cpu", + "board_f_flash": "board_build.f_flash", + "board_flash_mode": "board_build.flash_mode" +} + + +class ProjectConfig(object): + + VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}") + + expand_interpolations = True + _instances = {} + _parser = None + _parsed = [] + + @staticmethod + def parse_multi_values(items): + result = [] + if not items: + return result + inline_comment_re = re.compile(r"\s+;.*$") + for item in items.split("\n" if "\n" in items else ", "): + item = item.strip() + # comment + if not item or item.startswith((";", "#")): + continue + if ";" in item: + item = inline_comment_re.sub("", item).strip() + result.append(item) + return result + + @staticmethod + def get_instance(path): + if path not in ProjectConfig._instances: + ProjectConfig._instances[path] = ProjectConfig(path) + return ProjectConfig._instances[path] + + @staticmethod + def reset_instances(): + ProjectConfig._instances = {} + + def __init__(self, path, parse_extra=True, expand_interpolations=True): + self.path = path + self.expand_interpolations = expand_interpolations + self._parsed = [] + self._parser = ConfigParser.ConfigParser() + if isfile(path): + self.read(path, parse_extra) + + def __getattr__(self, name): + return getattr(self._parser, name) + + def read(self, path, parse_extra=True): + if path in self._parsed: + return + self._parsed.append(path) + try: + self._parser.read(path) + except ConfigParser.Error as e: + raise exception.InvalidProjectConf(path, str(e)) + + if not parse_extra: + return + + # load extra configs + if (not self._parser.has_section("platformio") + or not self._parser.has_option("platformio", "extra_configs")): + return + extra_configs = self.getlist("platformio", "extra_configs") + for pattern in extra_configs: + for item in glob.glob(pattern): + self.read(item) + + def options(self, section=None, env=None): + assert section or env + if not section: + section = "env:" + env + options = self._parser.options(section) + + # handle global options from [env] + if ((env or section.startswith("env:")) + and self._parser.has_section("env")): + for option in self._parser.options("env"): + if option not in options: + options.append(option) + + return options + + def has_option(self, section, option): + if self._parser.has_option(section, option): + return True + return (section.startswith("env:") and self._parser.has_section("env") + and self._parser.has_option("env", option)) + + def items(self, section=None, env=None, as_dict=False): + assert section or env + if not section: + section = "env:" + env + if as_dict: + return { + option: self.get(section, option) + for option in self.options(section) + } + return [(option, self.get(section, option)) + for option in self.options(section)] + + def get(self, section, option): + if not self.expand_interpolations: + return self._parser.get(section, option) + + try: + value = self._parser.get(section, option) + except ConfigParser.NoOptionError: + value = self._parser.get("env", option) + except ConfigParser.Error as e: + raise exception.InvalidProjectConf(self.path, str(e)) + + if "${" not in value or "}" not in value: + return value + return self.VARTPL_RE.sub(self._re_sub_handler, value) + + def _re_sub_handler(self, match): + section, option = match.group(1), match.group(2) + if section == "sysenv": + return os.getenv(option) + return self.get(section, option) + + def getlist(self, section, option): + return self.parse_multi_values(self.get(section, option)) + + def envs(self): + return [s[4:] for s in self._parser.sections() if s.startswith("env:")] + + def default_envs(self): + if not self._parser.has_option("platformio", "env_default"): + return [] + return self.getlist("platformio", "env_default") + + def validate(self, envs=None): + if not isfile(self.path): + raise exception.NotPlatformIOProject(self.path) + # check envs + known = set(self.envs()) + if not known: + raise exception.ProjectEnvsNotAvailable() + + unknown = set((envs or []) + self.default_envs()) - known + if unknown: + raise exception.UnknownEnvNames(", ".join(unknown), + ", ".join(known)) + return self.validate_options() + + def validate_options(self): + warnings = set() + # check [platformio] section + if self._parser.has_section("platformio"): + unknown = set(k for k, _ in self.items("platformio")) - set( + KNOWN_PLATFORMIO_OPTIONS) + if unknown: + warnings.add( + "Ignore unknown `%s` options in section `[platformio]`" % + ", ".join(unknown)) + + # check [env:*] sections + for section in self._parser.sections(): + if section != "env" and not section.startswith("env:"): + continue + for option in self._parser.options(section): + # obsolete + if option in RENAMED_OPTIONS: + warnings.add( + "`%s` option in section `[%s]` is deprecated and will " + "be removed in the next release! Please use `%s` " + "instead" % (option, section, RENAMED_OPTIONS[option])) + # rename on-the-fly + self._parser.set(section, RENAMED_OPTIONS[option], + self._parser.get(section, option)) + self._parser.remove_option(section, option) + continue + + # unknown + unknown_conditions = [ + option not in KNOWN_ENV_OPTIONS, + not option.startswith("custom_"), + not option.startswith("board_") + ] # yapf: disable + if all(unknown_conditions): + warnings.add( + "Detected non-PlatformIO `%s` option in `[%s]` section" + % (option, section)) + + for warning in warnings: + click.secho("Warning! %s" % warning, fg="yellow") + + return True + + def to_json(self): + result = {} + for section in self.sections(): + result[section] = self.items(section, as_dict=True) + return json.dumps(result) + + def save(self, path=None): + with open(path or self.path, "w") as fp: + fp.write(CONFIG_HEADER) + self._parser.write(fp) diff --git a/platformio/project/helpers.py b/platformio/project/helpers.py new file mode 100644 index 00000000..d7c19595 --- /dev/null +++ b/platformio/project/helpers.py @@ -0,0 +1,139 @@ +# 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 sys +from hashlib import sha1 +from os import walk +from os.path import abspath, dirname, expanduser, isdir, isfile, join + +from platformio import __version__ +from platformio.project.config import ProjectConfig + +PY2 = sys.version_info[0] == 2 + + +def get_project_dir(): + return os.getcwd() + + +def is_platformio_project(project_dir=None): + if not project_dir: + project_dir = get_project_dir() + return isfile(join(project_dir, "platformio.ini")) + + +def find_project_dir_above(path): + if isfile(path): + path = dirname(path) + if is_platformio_project(path): + return path + if isdir(dirname(path)): + return find_project_dir_above(dirname(path)) + return None + + +def get_project_optional_dir(name, default=None): + paths = None + var_name = "PLATFORMIO_%s" % name.upper() + if var_name in os.environ: + paths = os.getenv(var_name) + else: + config = ProjectConfig.get_instance( + join(get_project_dir(), "platformio.ini")) + if (config.has_section("platformio") + and config.has_option("platformio", name)): + paths = config.get("platformio", name) + if not paths: + return default + + items = [] + for item in paths.split(", "): + if item.startswith("~"): + item = expanduser(item) + items.append(abspath(item)) + paths = ", ".join(items) + + while "$PROJECT_HASH" in paths: + project_dir = get_project_dir() + paths = paths.replace( + "$PROJECT_HASH", + sha1(project_dir if PY2 else project_dir.encode()).hexdigest() + [:10]) + + return paths + + +def get_projectlib_dir(): + return get_project_optional_dir("lib_dir", join(get_project_dir(), "lib")) + + +def get_projectlibdeps_dir(): + return get_project_optional_dir("libdeps_dir", + join(get_project_dir(), ".piolibdeps")) + + +def get_projectsrc_dir(): + return get_project_optional_dir("src_dir", join(get_project_dir(), "src")) + + +def get_projectinclude_dir(): + return get_project_optional_dir("include_dir", + join(get_project_dir(), "include")) + + +def get_projecttest_dir(): + return get_project_optional_dir("test_dir", join(get_project_dir(), + "test")) + + +def get_projectboards_dir(): + return get_project_optional_dir("boards_dir", + join(get_project_dir(), "boards")) + + +def get_projectbuild_dir(force=False): + path = get_project_optional_dir("build_dir", + join(get_project_dir(), ".pioenvs")) + try: + if not isdir(path): + os.makedirs(path) + except Exception as e: # pylint: disable=broad-except + if not force: + raise Exception(e) + return path + + +def get_projectdata_dir(): + return get_project_optional_dir("data_dir", join(get_project_dir(), + "data")) + + +def calculate_project_hash(): + check_suffixes = (".c", ".cc", ".cpp", ".h", ".hpp", ".s", ".S") + chunks = [__version__] + for d in (get_projectsrc_dir(), get_projectlib_dir()): + if not isdir(d): + continue + for root, _, files in walk(d): + for f in files: + path = join(root, f) + if path.endswith(check_suffixes): + chunks.append(path) + chunks_to_str = ",".join(sorted(chunks)) + if sys.platform == "win32": + # Fix issue with useless project rebuilding for case insensitive FS. + # A case of disk drive can differ... + chunks_to_str = chunks_to_str.lower() + return sha1(chunks_to_str if PY2 else chunks_to_str.encode()).hexdigest() diff --git a/platformio/projectconftpl.ini b/platformio/projectconftpl.ini deleted file mode 100644 index ebc510b2..00000000 --- a/platformio/projectconftpl.ini +++ /dev/null @@ -1,9 +0,0 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html diff --git a/platformio/telemetry.py b/platformio/telemetry.py index 3f9937cf..987d5899 100644 --- a/platformio/telemetry.py +++ b/platformio/telemetry.py @@ -27,6 +27,7 @@ import click import requests from platformio import __version__, app, exception, util +from platformio.commands import PlatformioCLI try: import queue @@ -133,10 +134,10 @@ class MeasurementProtocol(TelemetryBase): return _arg return None - if not app.get_session_var("command_ctx"): - return - ctx_args = app.get_session_var("command_ctx").args - args = [str(s).lower() for s in ctx_args if not str(s).startswith("-")] + args = [ + str(arg).lower() for arg in PlatformioCLI.leftover_args + if not str(arg).startswith("-") + ] if not args: return cmd_path = args[:1] @@ -342,12 +343,9 @@ def on_exception(e): return text.strip() skip_conditions = [ - isinstance(e, cls) - for cls in (IOError, exception.ReturnErrorCode, - exception.AbortedByUser, exception.NotGlobalLibDir, - exception.InternetIsOffline, - exception.NotPlatformIOProject, - exception.UserSideException) + isinstance(e, cls) for cls in (IOError, exception.ReturnErrorCode, + exception.UserSideException, + exception.PlatformIOProjectException) ] try: skip_conditions.append("[API] Account: " in str(e)) diff --git a/platformio/util.py b/platformio/util.py index 08592355..bf8292d7 100644 --- a/platformio/util.py +++ b/platformio/util.py @@ -23,7 +23,6 @@ import sys import time from functools import wraps from glob import glob -from hashlib import sha1 from os.path import (abspath, basename, dirname, expanduser, isdir, isfile, join, normpath, splitdrive) from shutil import rmtree @@ -33,52 +32,21 @@ import click import requests from platformio import __apiurl__, __version__, exception +from platformio.project.config import ProjectConfig +from platformio.project.helpers import ( # pylint: disable=unused-import + get_project_dir, get_project_optional_dir, get_projectboards_dir, + get_projectbuild_dir, get_projectdata_dir, get_projectlib_dir, + get_projectsrc_dir, get_projecttest_dir, is_platformio_project) -# pylint: disable=too-many-ancestors +# FIXME: check platformio.project.helpers imports PY2 = sys.version_info[0] == 2 if PY2: - import ConfigParser as ConfigParser string_types = basestring # pylint: disable=undefined-variable else: - import configparser as ConfigParser string_types = str -class ProjectConfig(ConfigParser.ConfigParser): - - VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}") - - def items(self, section, **_): # pylint: disable=arguments-differ - items = [] - for option in ConfigParser.ConfigParser.options(self, section): - items.append((option, self.get(section, option))) - return items - - def get( # pylint: disable=arguments-differ - self, section, option, **kwargs): - try: - value = ConfigParser.ConfigParser.get(self, section, option, - **kwargs) - except ConfigParser.Error as e: - raise exception.InvalidProjectConf(str(e)) - if "${" not in value or "}" not in value: - return value - return self.VARTPL_RE.sub(self._re_sub_handler, value) - - def _re_sub_handler(self, match): - section, option = match.group(1), match.group(2) - if section in ("env", "sysenv") and not self.has_section(section): - if section == "env": - click.secho( - "Warning! Access to system environment variable via " - "`${{env.{0}}}` is deprecated. Please use " - "`${{sysenv.{0}}}` instead".format(option), - fg="yellow") - return os.getenv(option) - return self.get(section, option) - - class AsyncPipe(Thread): def __init__(self, outcallback=None): @@ -208,40 +176,6 @@ def pioversion_to_intstr(): return [int(i) for i in vermatch.group(1).split(".")[:3]] -def get_project_optional_dir(name, default=None): - paths = None - var_name = "PLATFORMIO_%s" % name.upper() - if var_name in os.environ: - paths = os.getenv(var_name) - else: - try: - config = load_project_config() - if (config.has_section("platformio") - and config.has_option("platformio", name)): - paths = config.get("platformio", name) - except exception.NotPlatformIOProject: - pass - - if not paths: - return default - - items = [] - for item in paths.split(", "): - if item.startswith("~"): - item = expanduser(item) - items.append(abspath(item)) - paths = ", ".join(items) - - while "$PROJECT_HASH" in paths: - project_dir = get_project_dir() - paths = paths.replace( - "$PROJECT_HASH", - sha1(project_dir if PY2 else project_dir.encode()).hexdigest() - [:10]) - - return paths - - def get_home_dir(): home_dir = get_project_optional_dir("home_dir", join(expanduser("~"), ".platformio")) @@ -278,103 +212,17 @@ def get_source_dir(): return dirname(curpath) -def get_project_dir(): - return os.getcwd() - - -def find_project_dir_above(path): - if isfile(path): - path = dirname(path) - if is_platformio_project(path): - return path - if isdir(dirname(path)): - return find_project_dir_above(dirname(path)) - return None - - -def is_platformio_project(project_dir=None): - if not project_dir: - project_dir = get_project_dir() - return isfile(join(project_dir, "platformio.ini")) - - -def get_projectlib_dir(): - return get_project_optional_dir("lib_dir", join(get_project_dir(), "lib")) - - -def get_projectlibdeps_dir(): - return get_project_optional_dir("libdeps_dir", - join(get_project_dir(), ".piolibdeps")) - - -def get_projectsrc_dir(): - return get_project_optional_dir("src_dir", join(get_project_dir(), "src")) - - -def get_projectinclude_dir(): - return get_project_optional_dir("include_dir", - join(get_project_dir(), "include")) - - -def get_projecttest_dir(): - return get_project_optional_dir("test_dir", join(get_project_dir(), - "test")) - - -def get_projectboards_dir(): - return get_project_optional_dir("boards_dir", - join(get_project_dir(), "boards")) - - -def get_projectbuild_dir(force=False): - path = get_project_optional_dir("build_dir", - join(get_project_dir(), ".pioenvs")) - try: - if not isdir(path): - os.makedirs(path) - except Exception as e: # pylint: disable=broad-except - if not force: - raise Exception(e) - return path - - -# compatibility with PIO Core+ -get_projectpioenvs_dir = get_projectbuild_dir - - -def get_projectdata_dir(): - return get_project_optional_dir("data_dir", join(get_project_dir(), - "data")) - - -def load_project_config(path=None): +def load_project_config(path=None): # FIXME: Remove if not path or isdir(path): path = join(path or get_project_dir(), "platformio.ini") if not isfile(path): raise exception.NotPlatformIOProject( dirname(path) if path.endswith("platformio.ini") else path) - cp = ProjectConfig() - try: - cp.read(path) - except ConfigParser.Error as e: - raise exception.InvalidProjectConf(str(e)) - return cp + return ProjectConfig(path) -def parse_conf_multi_values(items): - result = [] - if not items: - return result - inline_comment_re = re.compile(r"\s+;.*$") - for item in items.split("\n" if "\n" in items else ", "): - item = item.strip() - # comment - if not item or item.startswith((";", "#")): - continue - if ";" in item: - item = inline_comment_re.sub("", item).strip() - result.append(item) - return result +def parse_conf_multi_values(items): # FIXME: Remove + return ProjectConfig.parse_multi_values(items) def change_filemtime(path, mtime): @@ -611,7 +459,7 @@ def _get_api_result( auth=None): from platformio.app import get_setting - result = None + result = {} r = None verify_ssl = sys.version_info >= (2, 7, 9) diff --git a/scripts/99-platformio-udev.rules b/scripts/99-platformio-udev.rules index 99755db1..f3249682 100644 --- a/scripts/99-platformio-udev.rules +++ b/scripts/99-platformio-udev.rules @@ -84,176 +84,176 @@ SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic GDB Server" SUBSYSTEM=="tty", ATTRS{interface}=="Black Magic UART Port" # opendous and estick -ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="03eb", ATTRS{idProduct}=="204f", MODE="0666" # Original FT232/FT245 VID:PID -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666" # Original FT2232 VID:PID -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0666" # Original FT4232 VID:PID -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6011", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6011", MODE="0666" # Original FT232H VID:PID -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6014", MODE="0666" # DISTORTEC JTAG-lock-pick Tiny 2 -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8220", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8220", MODE="0666" # TUMPA, TUMPA Lite -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8a98", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8a99", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8a98", MODE="0666" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="8a99", MODE="0666" # XDS100v2 -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="a6d0", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="a6d0", MODE="0666" # Xverve Signalyzer Tool (DT-USB-ST), Signalyzer LITE (DT-USB-SLITE) -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bca0", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bca1", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bca0", MODE="0666" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bca1", MODE="0666" # TI/Luminary Stellaris Evaluation Board FTDI (several) -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bcd9", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bcd9", MODE="0666" # TI/Luminary Stellaris In-Circuit Debug Interface FTDI (ICDI) Board -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bcda", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bcda", MODE="0666" # egnite Turtelizer 2 -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bdc8", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="bdc8", MODE="0666" # Section5 ICEbear -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c140", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c141", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c140", MODE="0666" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="c141", MODE="0666" # Amontec JTAGkey and JTAGkey-tiny -ATTRS{idVendor}=="0403", ATTRS{idProduct}=="cff8", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0403", ATTRS{idProduct}=="cff8", MODE="0666" # TI ICDI -ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0451", ATTRS{idProduct}=="c32a", MODE="0666" # STLink v1 -ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3744", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3744", MODE="0666" # STLink v2 -ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE="0666" # STLink v2-1 -ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE="0666" # Hilscher NXHX Boards -ATTRS{idVendor}=="0640", ATTRS{idProduct}=="0028", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0640", ATTRS{idProduct}=="0028", MODE="0666" # Hitex STR9-comStick -ATTRS{idVendor}=="0640", ATTRS{idProduct}=="002c", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0640", ATTRS{idProduct}=="002c", MODE="0666" # Hitex STM32-PerformanceStick -ATTRS{idVendor}=="0640", ATTRS{idProduct}=="002d", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0640", ATTRS{idProduct}=="002d", MODE="0666" # Altera USB Blaster -ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="09fb", ATTRS{idProduct}=="6001", MODE="0666" # Amontec JTAGkey-HiSpeed -ATTRS{idVendor}=="0fbb", ATTRS{idProduct}=="1000", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="0fbb", ATTRS{idProduct}=="1000", MODE="0666" # SEGGER J-Link -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0101", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0102", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0103", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0104", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0105", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0107", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0108", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1010", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1011", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1012", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1013", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1014", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1016", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1017", MODE="660", GROUP="plugdev", TAG+="uaccess" -ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1018", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0101", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0102", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0103", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0104", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0105", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0107", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="0108", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1010", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1011", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1012", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1013", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1014", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1015", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1016", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1017", MODE="0666" +ATTRS{idVendor}=="1366", ATTRS{idProduct}=="1018", MODE="0666" # Raisonance RLink -ATTRS{idVendor}=="138e", ATTRS{idProduct}=="9000", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="138e", ATTRS{idProduct}=="9000", MODE="0666" # Debug Board for Neo1973 -ATTRS{idVendor}=="1457", ATTRS{idProduct}=="5118", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1457", ATTRS{idProduct}=="5118", MODE="0666" # Olimex ARM-USB-OCD -ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="0003", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="0003", MODE="0666" # Olimex ARM-USB-OCD-TINY -ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="0004", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="0004", MODE="0666" # Olimex ARM-JTAG-EW -ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="001e", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="001e", MODE="0666" # Olimex ARM-USB-OCD-TINY-H -ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="002a", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="002a", MODE="0666" # Olimex ARM-USB-OCD-H -ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="002b", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="15ba", ATTRS{idProduct}=="002b", MODE="0666" # USBprog with OpenOCD firmware -ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c63", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1781", ATTRS{idProduct}=="0c63", MODE="0666" # TI/Luminary Stellaris In-Circuit Debug Interface (ICDI) Board -ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="1cbe", ATTRS{idProduct}=="00fd", MODE="0666" # Marvell Sheevaplug -ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="9e88", ATTRS{idProduct}=="9e8f", MODE="0666" # Keil Software, Inc. ULink -ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2710", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2710", MODE="0666" # CMSIS-DAP compatible adapters -ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess" +ATTRS{product}=="*CMSIS-DAP*", MODE="0666" #SEGGER J-LIK -ATTR{idProduct}=="1001", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1002", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1003", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1004", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1005", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1006", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1007", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1008", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1009", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100a", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100b", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100c", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100d", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100e", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="100f", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1010", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1011", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1012", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1013", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1014", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1015", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1016", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1017", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1018", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1019", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101a", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101b", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101c", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101d", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101e", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="101f", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1020", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1021", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1022", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1023", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1024", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1025", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1026", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1027", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1028", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="1029", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102a", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102b", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102c", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102d", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102e", ATTR{idVendor}=="1366", MODE="666" -ATTR{idProduct}=="102f", ATTR{idVendor}=="1366", MODE="666" +ATTR{idProduct}=="1001", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1002", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1003", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1004", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1005", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1006", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1007", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1008", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1009", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100a", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100b", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100c", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100d", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100e", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="100f", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1010", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1011", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1012", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1013", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1014", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1015", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1016", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1017", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1018", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1019", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101a", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101b", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101c", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101d", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101e", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="101f", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1020", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1021", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1022", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1023", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1024", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1025", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1026", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1027", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1028", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="1029", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102a", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102b", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102c", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102d", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102e", ATTR{idVendor}=="1366", MODE="0666" +ATTR{idProduct}=="102f", ATTR{idVendor}=="1366", MODE="0666" diff --git a/setup.py b/setup.py index 747d6cc2..eb667d72 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ from platformio import (__author__, __description__, __email__, __license__, install_requires = [ "bottle<0.13", - "click>=5,<6", + "click>=5,<8", "colorama", "pyserial>=3,<4,!=3.3", "requests>=2.4.0,<3", @@ -41,7 +41,6 @@ setup( packages=find_packages() + ["scripts"], package_data={ "platformio": [ - "projectconftpl.ini", "ide/tpls/*/.*.tpl", "ide/tpls/*/*.tpl", "ide/tpls/*/*/*.tpl", diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py index eecb25f3..4e8eac09 100644 --- a/tests/commands/test_init.py +++ b/tests/commands/test_init.py @@ -57,8 +57,8 @@ 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"]) - assert result.exit_code == -1 - assert isinstance(result.exception, exception.BoardNotDefined) + assert result.exit_code != 0 + assert isinstance(result.exception, exception.ProjectEnvsNotAvailable) def test_init_ide_atom(clirunner, validate_cliresult, tmpdir): diff --git a/tests/commands/test_lib.py b/tests/commands/test_lib.py index b67cec39..4b75d81b 100644 --- a/tests/commands/test_lib.py +++ b/tests/commands/test_lib.py @@ -16,8 +16,11 @@ import json import re from platformio import exception +from platformio.commands import PlatformioCLI from platformio.commands.lib import cli as cmd_lib +PlatformioCLI.leftover_args = ["--json-output"] # hook for click + def test_search(clirunner, validate_cliresult): result = clirunner.invoke(cmd_lib, ["search", "DHT22"]) @@ -58,7 +61,6 @@ def test_global_install_archive(clirunner, validate_cliresult, isolated_pio_home): result = clirunner.invoke(cmd_lib, [ "-g", "install", - "http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.62.zip", "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip", "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2", "SomeLib=http://dl.platformio.org/libraries/archives/0/9540.tar.gz", @@ -74,10 +76,7 @@ def test_global_install_archive(clirunner, validate_cliresult, assert result.exit_code != 0 items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] - items2 = [ - "RadioHead-1.62", "ArduinoJson", "SomeLib_ID54", - "OneWire_ID1", "ESP32WebServer" - ] + items2 = ["ArduinoJson", "SomeLib_ID54", "OneWire_ID1", "ESP32WebServer"] assert set(items1) >= set(items2) @@ -123,7 +122,7 @@ def test_install_duplicates(clirunner, validate_cliresult, without_internet): # archive result = clirunner.invoke(cmd_lib, [ "-g", "install", - "http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.62.zip" + "https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip" ]) validate_cliresult(result) assert "is already installed" in result.output @@ -145,7 +144,7 @@ def test_global_lib_list(clirunner, validate_cliresult): ("Source: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip", "Version: 5.10.1", "Source: git+https://github.com/gioblu/PJON.git#3.0", - "Version: 1fb26fd", "RadioHead-1.62") + "Version: 1fb26fd") ]) result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"]) @@ -158,10 +157,9 @@ def test_global_lib_list(clirunner, validate_cliresult): items1 = [i['name'] for i in json.loads(result.output)] items2 = [ "ESP32WebServer", "ArduinoJson", "ArduinoJson", "ArduinoJson", - "ArduinoJson", "AsyncMqttClient", "AsyncTCP", "SomeLib", - "ESPAsyncTCP", "NeoPixelBus", "OneWire", "PJON", "PJON", - "PubSubClient", "RFcontrol", "RadioHead-1.62", "platformio-libmirror", - "rs485-nodeproto" + "ArduinoJson", "AsyncMqttClient", "AsyncTCP", "SomeLib", "ESPAsyncTCP", + "NeoPixelBus", "OneWire", "PJON", "PJON", "PubSubClient", "RFcontrol", + "platformio-libmirror", "rs485-nodeproto" ] assert sorted(items1) == sorted(items2) @@ -169,9 +167,9 @@ def test_global_lib_list(clirunner, validate_cliresult): "{name}@{version}".format(**item) for item in json.loads(result.output) ] versions2 = [ - 'ArduinoJson@5.8.2', 'ArduinoJson@5.10.1', 'AsyncMqttClient@0.8.2', - 'NeoPixelBus@2.2.4', 'PJON@07fe9aa', 'PJON@1fb26fd', - 'PubSubClient@bef5814', 'RFcontrol@77d4eb3f8a', 'RadioHead-1.62@0.0.0' + "ArduinoJson@5.8.2", "ArduinoJson@5.10.1", "AsyncMqttClient@0.8.2", + "NeoPixelBus@2.2.4", "PJON@07fe9aa", "PJON@1fb26fd", + "PubSubClient@bef5814", "RFcontrol@77d4eb3f8a" ] assert set(versions1) >= set(versions2) @@ -202,7 +200,7 @@ def test_global_lib_update(clirunner, validate_cliresult): # update rest libraries result = clirunner.invoke(cmd_lib, ["-g", "update"]) validate_cliresult(result) - assert result.output.count("[Detached]") == 6 + assert result.output.count("[Detached]") == 5 assert result.output.count("[Up-to-date]") == 11 assert "Uninstalling RFcontrol @ 77d4eb3f8a" in result.output @@ -232,10 +230,10 @@ def test_global_lib_uninstall(clirunner, validate_cliresult, items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] items2 = [ - "RadioHead-1.62", "rs485-nodeproto", "platformio-libmirror", + "rs485-nodeproto", "platformio-libmirror", "PubSubClient", "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", - "ESPAsyncTCP_ID305", "SomeLib_ID54", "NeoPixelBus_ID547", - "PJON", "AsyncMqttClient_ID346", "ArduinoJson_ID64", + "ESPAsyncTCP_ID305", "SomeLib_ID54", "NeoPixelBus_ID547", "PJON", + "AsyncMqttClient_ID346", "ArduinoJson_ID64", "PJON@src-79de467ebe19de18287becff0a1fb42d", "ESP32WebServer" ] assert set(items1) == set(items2) diff --git a/tests/commands/test_platform.py b/tests/commands/test_platform.py index d327ef37..72941574 100644 --- a/tests/commands/test_platform.py +++ b/tests/commands/test_platform.py @@ -38,14 +38,14 @@ def test_search_raw_output(clirunner, validate_cliresult): def test_install_unknown_version(clirunner): result = clirunner.invoke(cli_platform.platform_install, ["atmelavr@99.99.99"]) - assert result.exit_code == -1 + assert result.exit_code != 0 assert isinstance(result.exception, exception.UndefinedPackageVersion) def test_install_unknown_from_registry(clirunner): result = clirunner.invoke(cli_platform.platform_install, ["unknown-platform"]) - assert result.exit_code == -1 + assert result.exit_code != 0 assert isinstance(result.exception, exception.UnknownPackage) diff --git a/tests/conftest.py b/tests/conftest.py index 02d16069..d53d92e8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,8 +24,10 @@ from platformio import util def validate_cliresult(): def decorator(result): - assert result.exit_code == 0, result.output - assert not result.exception, result.output + assert result.exit_code == 0, "{} => {}".format( + result.exception, result.output) + assert not result.exception, "{} => {}".format(result.exception, + result.output) return decorator diff --git a/tests/test_misc.py b/tests/test_misc.py index b918767b..8f6fccf2 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -18,6 +18,12 @@ import requests from platformio import exception, util +def test_platformio_cli(): + result = util.exec_command(["pio", "--help"]) + assert result['returncode'] == 0 + assert "Usage: pio [OPTIONS] COMMAND [ARGS]..." in result['out'] + + def test_ping_internet_ips(): for ip in util.PING_INTERNET_IPS: requests.get("http://%s" % ip, allow_redirects=False, timeout=2) diff --git a/tests/test_projectconf.py b/tests/test_projectconf.py new file mode 100644 index 00000000..d58ee0ce --- /dev/null +++ b/tests/test_projectconf.py @@ -0,0 +1,116 @@ +# 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 + +from platformio.project.config import ProjectConfig + +BASE_CONFIG = """ +[platformio] +env_default = base, extra_2 +extra_configs = + extra_envs.ini + extra_debug.ini + +# global options per [env:*] +[env] +monitor_speed = 115200 +lib_deps = Lib1, Lib2 +lib_ignore = ${custom.lib_ignore} + +[custom] +debug_flags = -D RELEASE +lib_flags = -lc -lm +extra_flags = ${sysenv.__PIO_TEST_CNF_EXTRA_FLAGS} +lib_ignore = LibIgnoreCustom + +[env:base] +build_flags = ${custom.debug_flags} ${custom.extra_flags} +""" + +EXTRA_ENVS_CONFIG = """ +[env:extra_1] +build_flags = ${custom.lib_flags} ${custom.debug_flags} + +[env:extra_2] +build_flags = ${custom.debug_flags} ${custom.extra_flags} +lib_ignore = ${env.lib_ignore}, Lib3 +""" + +EXTRA_DEBUG_CONFIG = """ +# Override original "custom.debug_flags" +[custom] +debug_flags = -D DEBUG=1 + +[env:extra_2] +build_flags = -Og +""" + + +def test_parser(tmpdir): + tmpdir.join("platformio.ini").write(BASE_CONFIG) + tmpdir.join("extra_envs.ini").write(EXTRA_ENVS_CONFIG) + tmpdir.join("extra_debug.ini").write(EXTRA_DEBUG_CONFIG) + + config = None + with tmpdir.as_cwd(): + config = ProjectConfig(tmpdir.join("platformio.ini").strpath) + assert config + + # sections + assert config.sections() == [ + "platformio", "env", "custom", "env:base", "env:extra_1", "env:extra_2" + ] + + # envs + assert config.envs() == ["base", "extra_1", "extra_2"] + assert config.default_envs() == ["base", "extra_2"] + + # options + assert config.options(env="base") == [ + "build_flags", "monitor_speed", "lib_deps", "lib_ignore" + ] + + # has_option + assert config.has_option("env:base", "monitor_speed") + assert not config.has_option("custom", "monitor_speed") + + # sysenv + assert config.get("custom", "extra_flags") == "" + os.environ["__PIO_TEST_CNF_EXTRA_FLAGS"] = "-L /usr/local/lib" + assert config.get("custom", "extra_flags") == "-L /usr/local/lib" + + # get + assert config.get("custom", "debug_flags") == "-D DEBUG=1" + assert config.get("env:extra_1", "build_flags") == "-lc -lm -D DEBUG=1" + assert config.get("env:extra_2", "build_flags") == "-Og" + assert config.get("env:extra_2", "monitor_speed") == "115200" + assert config.get("env:base", + "build_flags") == ("-D DEBUG=1 -L /usr/local/lib") + + # items + assert config.items("custom") == [("debug_flags", "-D DEBUG=1"), + ("lib_flags", "-lc -lm"), + ("extra_flags", "-L /usr/local/lib"), + ("lib_ignore", "LibIgnoreCustom")] + assert config.items(env="extra_1") == [("build_flags", + "-lc -lm -D DEBUG=1"), + ("monitor_speed", "115200"), + ("lib_deps", "Lib1, Lib2"), + ("lib_ignore", "LibIgnoreCustom")] + assert config.items(env="extra_2") == [("build_flags", "-Og"), + ("lib_ignore", + "LibIgnoreCustom, Lib3"), + ("monitor_speed", "115200"), + ("lib_deps", "Lib1, Lib2")]