Switch to the new ProjectConfig API

This commit is contained in:
Ivan Kravets
2019-05-07 17:51:50 +03:00
parent 1b4f945907
commit c235974eb6
11 changed files with 354 additions and 223 deletions

View File

@@ -25,6 +25,7 @@ from platformio.commands.init import cli as cmd_init
from platformio.commands.init import validate_boards from platformio.commands.init import validate_boards
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
from platformio.exception import CIBuildEnvsEmpty from platformio.exception import CIBuildEnvsEmpty
from platformio.project.config import ProjectConfig
def validate_path(ctx, param, value): # pylint: disable=unused-argument def validate_path(ctx, param, value): # pylint: disable=unused-argument
@@ -161,8 +162,8 @@ def _exclude_contents(dst_dir, patterns):
def _copy_project_conf(build_dir, project_conf): 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"): if config.has_section("platformio"):
config.remove_section("platformio") config.remove_section("platformio")
with open(join(build_dir, "platformio.ini"), "w") as fp: with open(join(build_dir, "platformio.ini"), "wb") as fp:
config.write(fp) config.write(fp)

View File

@@ -15,11 +15,13 @@
import json import json
import sys import sys
from os import getcwd from os import getcwd
from os.path import join
import click import click
from serial.tools import miniterm from serial.tools import miniterm
from platformio import exception, util from platformio import exception, util
from platformio.project.config import ProjectConfig
@click.group(short_help="Monitor device or list existing") @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") help="Load configuration from `platformio.ini` and specified environment")
def device_monitor(**kwargs): # pylint: disable=too-many-branches def device_monitor(**kwargs): # pylint: disable=too-many-branches
try: try:
project_options = get_project_options(kwargs['project_dir'], monitor_options = get_project_options(kwargs['project_dir'],
kwargs['environment']) kwargs['environment'])
monitor_options = {k: v for k, v in project_options or []}
if monitor_options: if monitor_options:
for k in ("port", "baud", "speed", "rts", "dtr"): for k in ("port", "speed", "rts", "dtr"):
k2 = "monitor_%s" % k k2 = "monitor_%s" % k
if k == "speed": if k == "speed":
k = "baud" k = "baud"
@@ -205,24 +206,13 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
raise exception.MinitermException(e) raise exception.MinitermException(e)
def get_project_options(project_dir, environment): def get_project_options(project_dir, environment=None):
config = util.load_project_config(project_dir) config = ProjectConfig.get_instance(join(project_dir, "platformio.ini"))
if not config.sections(): config.validate(envs=[environment] if environment else None)
return None if not environment:
default_envs = config.default_envs()
known_envs = [s[4:] for s in config.sections() if s.startswith("env:")] if default_envs:
if environment: environment = default_envs[0]
if environment in known_envs: else:
return config.items("env:%s" % environment) environment = config.envs()[0]
raise exception.UnknownEnvNames(environment, ", ".join(known_envs)) return {k: v for k, v in config.items(env=environment)}
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])

View File

@@ -23,9 +23,9 @@ import click
from platformio import exception, util from platformio import exception, util
from platformio.commands.platform import \ from platformio.commands.platform import \
platform_install as cli_platform_install platform_install as cli_platform_install
from platformio.commands.run import check_project_envs
from platformio.ide.projectgenerator import ProjectGenerator from platformio.ide.projectgenerator import ProjectGenerator
from platformio.managers.platform import PlatformManager from platformio.managers.platform import PlatformManager
from platformio.project.config import ProjectConfig
def validate_boards(ctx, param, value): # pylint: disable=W0613 def validate_boards(ctx, param, value): # pylint: disable=W0613
@@ -128,14 +128,11 @@ def cli(
def get_best_envname(project_dir, boards=None): def get_best_envname(project_dir, boards=None):
config = util.load_project_config(project_dir) config = ProjectConfig(join(project_dir, "platformio.ini"))
env_default = None config.validate()
if config.has_option("platformio", "env_default"): default_envs = config.default_envs()
env_default = util.parse_conf_multi_values( if default_envs:
config.get("platformio", "env_default")) return default_envs[0]
check_project_envs(config, env_default)
if env_default:
return env_default[0]
section = None section = None
for section in config.sections(): for section in config.sections():
if not section.startswith("env:"): if not section.startswith("env:"):
@@ -369,7 +366,8 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix,
used_boards = [] used_boards = []
used_platforms = [] used_platforms = []
config = util.load_project_config(project_dir) config_path = join(project_dir, "platformio.ini")
config = util.load_project_config(config_path)
for section in config.sections(): for section in config.sections():
cond = [ cond = [
section.startswith("env:"), section.startswith("env:"),
@@ -409,10 +407,12 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix,
if not content: if not content:
return return
with open(join(project_dir, "platformio.ini"), "a") as f: with open(config_path, "a") as f:
content.append("") content.append("")
f.write("\n".join(content)) f.write("\n".join(content))
ProjectConfig.reset_instances()
def _install_dependent_platforms(ctx, platforms): def _install_dependent_platforms(ctx, platforms):
installed_platforms = [ installed_platforms = [

View File

@@ -26,6 +26,7 @@ from platformio.commands.platform import \
platform_install as cmd_platform_install platform_install as cmd_platform_install
from platformio.managers.lib import LibraryManager, is_builtin_lib from platformio.managers.lib import LibraryManager, is_builtin_lib
from platformio.managers.platform import PlatformFactory from platformio.managers.platform import PlatformFactory
from platformio.project.config import ProjectConfig
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches # pylint: disable=too-many-arguments,too-many-locals,too-many-branches
@@ -54,9 +55,6 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose,
if isfile(project_dir): if isfile(project_dir):
project_dir = util.find_project_dir_above(project_dir) project_dir = util.find_project_dir_above(project_dir)
if not util.is_platformio_project(project_dir):
raise exception.NotPlatformIOProject(project_dir)
with util.cd(project_dir): with util.cd(project_dir):
# clean obsolete build dir # clean obsolete build dir
if not disable_auto_clean: if not disable_auto_clean:
@@ -69,25 +67,17 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose,
util.get_projectbuild_dir(force=True), util.get_projectbuild_dir(force=True),
fg="yellow") fg="yellow")
config = util.load_project_config() config = ProjectConfig.get_instance(
env_default = None join(project_dir, "platformio.ini"))
if config.has_option("platformio", "env_default"): config.validate()
env_default = util.parse_conf_multi_values(
config.get("platformio", "env_default"))
check_project_defopts(config)
check_project_envs(config, environment or env_default)
results = [] results = []
start_time = time() start_time = time()
for section in config.sections(): default_envs = config.default_envs()
if not section.startswith("env:"): for envname in config.envs():
continue
envname = section[4:]
skipenv = any([ skipenv = any([
environment and envname not in environment, not environment 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: if skipenv:
results.append((envname, None)) results.append((envname, None))
@@ -97,7 +87,7 @@ def cli(ctx, environment, target, upload_port, project_dir, silent, verbose,
click.echo() click.echo()
options = {} options = {}
for k, v in config.items(section): for k, v in config.items(env=envname):
options[k] = v options[k] = v
if "piotest" not in options and "piotest" in ctx.meta: if "piotest" not in options and "piotest" in ctx.meta:
options['piotest'] = ctx.meta['piotest'] options['piotest'] = ctx.meta['piotest']
@@ -127,26 +117,6 @@ class EnvironmentProcessor(object):
DEFAULT_DUMP_OPTIONS = ("platform", "framework", "board") 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", "extra_configs"
]
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 = [ IGNORE_BUILD_OPTIONS = [
"test_transport", "test_filter", "test_ignore", "test_port", "test_transport", "test_filter", "test_ignore", "test_port",
"test_speed", "debug_port", "debug_init_cmds", "debug_extra_cmds", "test_speed", "debug_port", "debug_init_cmds", "debug_extra_cmds",
@@ -157,17 +127,6 @@ class EnvironmentProcessor(object):
REMAPED_OPTIONS = {"framework": "pioframework", "platform": "pioplatform"} 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"
}
def __init__( def __init__(
self, # pylint: disable=R0913 self, # pylint: disable=R0913
cmd_ctx, cmd_ctx,
@@ -201,7 +160,6 @@ class EnvironmentProcessor(object):
self.name, fg="cyan", bold=True), "; ".join(env_dump))) self.name, fg="cyan", bold=True), "; ".join(env_dump)))
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
self.options = self._validate_options(self.options)
result = self._run() result = self._run()
is_error = result['returncode'] != 0 is_error = result['returncode'] != 0
@@ -218,31 +176,6 @@ class EnvironmentProcessor(object):
return not is_error 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]
# 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): def get_build_variables(self):
variables = {"pioenv": self.name} variables = {"pioenv": self.name}
if self.upload_port: if self.upload_port:
@@ -381,21 +314,7 @@ def print_summary(results, start_time):
is_error=not successed) is_error=not successed)
def check_project_defopts(config): def check_project_envs(config, environments=None): # FIXME: Remove
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):
if not config.sections(): if not config.sections():
raise exception.ProjectEnvsNotAvailable() raise exception.ProjectEnvsNotAvailable()

View File

@@ -42,11 +42,16 @@ class UserSideException(PlatformioException):
pass pass
class AbortedByUser(PlatformioException): class AbortedByUser(UserSideException):
MESSAGE = "Aborted by user" MESSAGE = "Aborted by user"
#
# Development Platform
#
class UnknownPlatform(PlatformioException): class UnknownPlatform(PlatformioException):
MESSAGE = "Unknown development platform '{0}'" MESSAGE = "Unknown development platform '{0}'"
@@ -85,54 +90,75 @@ class UnknownFramework(PlatformioException):
MESSAGE = "Unknown framework '{0}'" MESSAGE = "Unknown framework '{0}'"
class UnknownPackage(PlatformioException): # Package Manager
class PlatformIOPackageException(PlatformioException):
pass
class UnknownPackage(PlatformIOPackageException):
MESSAGE = "Detected unknown package '{0}'" MESSAGE = "Detected unknown package '{0}'"
class MissingPackageManifest(PlatformioException): class MissingPackageManifest(PlatformIOPackageException):
MESSAGE = "Could not find one of '{0}' manifest files in the package" 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}'" MESSAGE = ("Could not find a version that satisfies the requirement '{0}'"
" for your system '{1}'") " for your system '{1}'")
class PackageInstallError(PlatformioException): class PackageInstallError(PlatformIOPackageException):
MESSAGE = ("Could not install '{0}' with version requirements '{1}' " MESSAGE = ("Could not install '{0}' with version requirements '{1}' "
"for your system '{2}'.\n\n" "for your system '{2}'.\n\n"
"Please try this solution -> http://bit.ly/faq-package-manager") "Please try this solution -> http://bit.ly/faq-package-manager")
class ExtractArchiveItemError(PlatformioException): class ExtractArchiveItemError(PlatformIOPackageException):
MESSAGE = ( MESSAGE = (
"Could not extract `{0}` to `{1}`. Try to disable antivirus " "Could not extract `{0}` to `{1}`. Try to disable antivirus "
"tool or check this solution -> http://bit.ly/faq-package-manager") "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}" 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}' " MESSAGE = ("The size ({0:d} bytes) of downloaded file '{1}' "
"is not equal to remote size ({2:d} bytes)") "is not equal to remote size ({2:d} bytes)")
class FDSHASumMismatch(PlatformioException): class FDSHASumMismatch(PlatformIOPackageException):
MESSAGE = ("The 'sha1' sum '{0}' of downloaded file '{1}' " MESSAGE = ("The 'sha1' sum '{0}' of downloaded file '{1}' "
"is not equal to remote '{2}'") "is not equal to remote '{2}'")
class NotPlatformIOProject(PlatformioException): #
# Project
#
class PlatformIOProjectException(PlatformioException):
pass
class NotPlatformIOProject(PlatformIOProjectException):
MESSAGE = ( MESSAGE = (
"Not a PlatformIO project. `platformio.ini` file has not been " "Not a PlatformIO project. `platformio.ini` file has not been "
@@ -140,26 +166,82 @@ class NotPlatformIOProject(PlatformioException):
"please use `platformio init` command") "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" MESSAGE = "Please specify platform for '{0}' environment"
class UnsupportedArchiveType(PlatformioException): class ProjectEnvsNotAvailable(PlatformIOProjectException):
MESSAGE = "Can not unpack file '{0}'"
class ProjectEnvsNotAvailable(PlatformioException):
MESSAGE = "Please setup environments in `platformio.ini` file" 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}'" 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): class GetSerialPortsError(PlatformioException):
MESSAGE = "No implementation for your platform ('{0}') available" MESSAGE = "No implementation for your platform ('{0}') available"
@@ -175,7 +257,7 @@ class APIRequestError(PlatformioException):
MESSAGE = "[API] {0}" MESSAGE = "[API] {0}"
class InternetIsOffline(PlatformioException): class InternetIsOffline(UserSideException):
MESSAGE = ( MESSAGE = (
"You are not connected to the Internet.\n" "You are not connected to the Internet.\n"
@@ -183,33 +265,6 @@ class InternetIsOffline(PlatformioException):
"to install all dependencies and toolchains.") "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 '{0}' (project configuration file): '{1}'")
class BuildScriptNotFound(PlatformioException): class BuildScriptNotFound(PlatformioException):
MESSAGE = "Invalid path '{0}' to build script" MESSAGE = "Invalid path '{0}' to build script"
@@ -237,25 +292,6 @@ class CIBuildEnvsEmpty(PlatformioException):
"predefined environments using `--project-conf` option") "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): class UpgradeError(PlatformioException):
MESSAGE = """{0} MESSAGE = """{0}
@@ -290,5 +326,4 @@ class DebugSupportError(PlatformioException):
class DebugInvalidOptions(PlatformioException): class DebugInvalidOptions(PlatformioException):
pass pass

View File

@@ -23,6 +23,7 @@ from click.testing import CliRunner
from platformio import exception, util from platformio import exception, util
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
from platformio.project.config import ProjectConfig
class ProjectGenerator(object): class ProjectGenerator(object):
@@ -44,14 +45,13 @@ class ProjectGenerator(object):
@util.memoized() @util.memoized()
def get_project_env(self): def get_project_env(self):
data = {} data = {}
config = util.load_project_config(self.project_dir) config = ProjectConfig.get_instance(
for section in config.sections(): join(self.project_dir, "platformio.ini"))
if not section.startswith("env:"): for env in config.envs():
if self.env_name != env:
continue continue
if self.env_name != section[4:]: data = {"env_name": self.env_name}
continue for k, v in config.items(env=env):
data = {"env_name": section[4:]}
for k, v in config.items(section):
data[k] = v data[k] = v
return data return data

View File

@@ -15,6 +15,7 @@
import glob import glob
import os import os
import re import re
from os.path import isfile
import click import click
@@ -25,11 +26,99 @@ try:
except ImportError: except ImportError:
import configparser as ConfigParser import configparser as ConfigParser
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): class ProjectConfig(object):
VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}") VARTPL_RE = re.compile(r"\$\{([^\.\}]+)\.([^\}]+)\}")
_instances = {}
_parser = None _parser = None
_parsed = [] _parsed = []
@@ -49,13 +138,30 @@ class ProjectConfig(object):
result.append(item) result.append(item)
return result return result
def __init__(self, path): @staticmethod
def get_instance(path):
if not isfile(path):
raise exception.NotPlatformIOProject(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):
if not isfile(path):
raise exception.NotPlatformIOProject(path)
self.path = path self.path = path
self._parsed = [] self._parsed = []
self._parser = ConfigParser.ConfigParser() self._parser = ConfigParser.ConfigParser()
self.read(path) self.read(path, parse_extra)
def read(self, path): def __getattr__(self, name):
return getattr(self._parser, name)
def read(self, path, parse_extra=True):
if path in self._parsed: if path in self._parsed:
return return
self._parsed.append(path) self._parsed.append(path)
@@ -64,6 +170,9 @@ class ProjectConfig(object):
except ConfigParser.Error as e: except ConfigParser.Error as e:
raise exception.InvalidProjectConf(path, str(e)) raise exception.InvalidProjectConf(path, str(e))
if not parse_extra:
return
# load extra configs # load extra configs
if (not self._parser.has_section("platformio") if (not self._parser.has_section("platformio")
or not self._parser.has_option("platformio", "extra_configs")): or not self._parser.has_option("platformio", "extra_configs")):
@@ -74,14 +183,18 @@ class ProjectConfig(object):
for item in glob.glob(pattern): for item in glob.glob(pattern):
self.read(item) self.read(item)
def __getattr__(self, name): def options(self, section=None, env=None):
return getattr(self._parser, name) assert section or env
if not section:
section = "env:" + env
return self._parser.options(section)
def items(self, section): def items(self, section=None, env=None):
items = [] assert section or env
for option in self._parser.options(section): if not section:
items.append((option, self.get(section, option))) section = "env:" + env
return items return [(option, self.get(section, option))
for option in self.options(section)]
def get(self, section, option): def get(self, section, option):
try: try:
@@ -95,7 +208,7 @@ class ProjectConfig(object):
def _re_sub_handler(self, match): def _re_sub_handler(self, match):
section, option = match.group(1), match.group(2) section, option = match.group(1), match.group(2)
if section in ("env", if section in ("env",
"sysenv") and not self.has_section(section): "sysenv") and not self._parser.has_section(section):
if section == "env": if section == "env":
click.secho( click.secho(
"Warning! Access to system environment variable via " "Warning! Access to system environment variable via "
@@ -104,3 +217,67 @@ class ProjectConfig(object):
fg="yellow") fg="yellow")
return os.getenv(option) return os.getenv(option)
return self.get(section, option) return 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.parse_multi_values(self.get("platformio", "env_default"))
def validate(self, envs=None):
# 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 `[platformio]` section" %
", ".join(unknown))
# check [env:*] sections
for section in self._parser.sections():
if not section.startswith("env:"):
continue
for option in self._parser.options(section):
# obsolete
if option in RENAMED_OPTIONS:
warnings.add(
"`%s` option in `[%s]` section 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

View File

@@ -342,12 +342,9 @@ def on_exception(e):
return text.strip() return text.strip()
skip_conditions = [ skip_conditions = [
isinstance(e, cls) isinstance(e, cls) for cls in (IOError, exception.ReturnErrorCode,
for cls in (IOError, exception.ReturnErrorCode, exception.UserSideException,
exception.AbortedByUser, exception.NotGlobalLibDir, exception.PlatformIOProjectException)
exception.InternetIsOffline,
exception.NotPlatformIOProject,
exception.UserSideException)
] ]
try: try:
skip_conditions.append("[API] Account: " in str(e)) skip_conditions.append("[API] Account: " in str(e))

View File

@@ -180,7 +180,8 @@ def get_project_optional_dir(name, default=None):
paths = os.getenv(var_name) paths = os.getenv(var_name)
else: else:
try: try:
config = load_project_config() config = ProjectConfig.get_instance(
join(get_project_dir(), "platformio.ini"))
if (config.has_section("platformio") if (config.has_section("platformio")
and config.has_option("platformio", name)): and config.has_option("platformio", name)):
paths = config.get("platformio", name) paths = config.get("platformio", name)
@@ -312,7 +313,7 @@ def get_projectdata_dir():
"data")) "data"))
def load_project_config(path=None): # FIXME: def load_project_config(path=None): # FIXME: Remove
if not path or isdir(path): if not path or isdir(path):
path = join(path or get_project_dir(), "platformio.ini") path = join(path or get_project_dir(), "platformio.ini")
if not isfile(path): if not isfile(path):
@@ -321,7 +322,7 @@ def load_project_config(path=None): # FIXME:
return ProjectConfig(path) return ProjectConfig(path)
def parse_conf_multi_values(items): # FIXME: def parse_conf_multi_values(items): # FIXME: Remove
return ProjectConfig.parse_multi_values(items) return ProjectConfig.parse_multi_values(items)

View File

@@ -24,8 +24,10 @@ from platformio import util
def validate_cliresult(): def validate_cliresult():
def decorator(result): def decorator(result):
assert result.exit_code == 0, result.output assert result.exit_code == 0, "{} => {}".format(
assert not result.exception, result.output result.exception, result.output)
assert not result.exception, "{} => {}".format(result.exception,
result.output)
return decorator return decorator

View File

@@ -13,10 +13,12 @@
# limitations under the License. # limitations under the License.
import os import os
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
BASE_CONFIG = """ BASE_CONFIG = """
[platformio] [platformio]
env_default = esp32dev, lolin32
extra_configs = extra_configs =
extra_envs.ini extra_envs.ini
extra_debug.ini extra_debug.ini
@@ -86,8 +88,15 @@ def test_parser(tmpdir):
"build_flags") == ("-D DEBUG=1 -L /usr/local/lib") "build_flags") == ("-D DEBUG=1 -L /usr/local/lib")
# items # items
assert config.items("env:esp32dev") == [("platform", "espressif32"), assert config.items("common") == [("debug_flags", "-D DEBUG=1"),
("lib_flags", "-lc -lm"),
("extra_flags", "-L /usr/local/lib")]
assert config.items(env="esp32dev") == [("platform", "espressif32"),
("framework", "espidf"), ("framework", "espidf"),
("board", "esp32dev"), ("board", "esp32dev"),
("build_flags", ("build_flags",
"-lc -lm -D DEBUG=1")] "-lc -lm -D DEBUG=1")]
# envs
assert config.envs() == ["esp-wrover-kit", "esp32dev", "lolin32"]
assert config.default_envs() == ["esp32dev", "lolin32"]