Merge branch 'feature/refactor-project-options' into develop

This commit is contained in:
Ivan Kravets
2019-05-30 22:15:16 +03:00
31 changed files with 981 additions and 786 deletions

View File

@ -21,7 +21,7 @@ matrix:
install:
- git submodule update --init --recursive
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then curl -fsSL https://bootstrap.pypa.io/get-pip.py | sudo python; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo pip install "tox==3.0.0"; else pip install -U tox; fi
- pip install -U tox
# ChipKIT issue: install 32-bit support for GCC PIC32
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install libc6-i386; fi

15
.vscode/settings.json vendored
View File

@ -1,15 +0,0 @@
{
"python.pythonPath": "${workspaceRoot}/.tox/develop/bin/python",
"python.formatting.provider": "yapf",
"files.exclude": {
"**/*.pyc": true,
"*.egg-info": true,
".cache": true,
"build": true,
"dist": true
},
"editor.rulers": [79],
"restructuredtext.builtDocumentationPath": "${workspaceRoot}/docs/_build/html",
"restructuredtext.confPath": "${workspaceRoot}/docs",
"restructuredtext.linter.executablePath": "${workspaceRoot}/.tox/docs/bin/restructuredtext-lint"
}

View File

@ -10,7 +10,7 @@ yapf:
yapf --recursive --in-place platformio/
test:
py.test -v -s -n 3 --dist=loadscope tests --ignore tests/test_examples.py --ignore tests/test_pkgmanifest.py
py.test --verbose --capture=no --exitfirst -n 3 --dist=loadscope tests --ignore tests/test_examples.py --ignore tests/test_pkgmanifest.py
before-commit: isort yapf lint test

View File

@ -16,7 +16,7 @@ import base64
import json
import sys
from os import environ
from os.path import expanduser, join
from os.path import join
from time import time
from SCons.Script import ARGUMENTS # pylint: disable=import-error
@ -33,76 +33,29 @@ from platformio import util
from platformio.compat import PY2, path_to_unicode
from platformio.proc import get_pythonexe_path
from platformio.project import helpers as project_helpers
from platformio.project.config import ProjectConfig
AllowSubstExceptions(NameError)
# allow common variables from INI file
commonvars = Variables(None)
commonvars.AddVariables(
# append CLI arguments to build environment
clivars = Variables(None)
clivars.AddVariables(
("PLATFORM_MANIFEST",),
("BUILD_SCRIPT",),
("EXTRA_SCRIPTS",),
("PROJECT_CONFIG",),
("PIOENV",),
("PIOTEST",),
("PIOPLATFORM",),
("PIOFRAMEWORK",),
# build options
("BUILD_FLAGS",),
("SRC_BUILD_FLAGS",),
("BUILD_UNFLAGS",),
("SRC_FILTER",),
# library options
("LIB_LDF_MODE",),
("LIB_COMPAT_MODE",),
("LIB_DEPS",),
("LIB_IGNORE",),
("LIB_EXTRA_DIRS",),
("LIB_ARCHIVE",),
# board options
("BOARD",),
# deprecated options, use board_{object.path} instead
("BOARD_MCU",),
("BOARD_F_CPU",),
("BOARD_F_FLASH",),
("BOARD_FLASH_MODE",),
# end of deprecated options
# upload options
("UPLOAD_PORT",),
("UPLOAD_PROTOCOL",),
("UPLOAD_SPEED",),
("UPLOAD_FLAGS",),
("UPLOAD_RESETMETHOD",),
# test options
("TEST_BUILD_PROJECT_SRC",),
# debug options
("DEBUG_TOOL",),
("DEBUG_SVD_PATH",),
("UPLOAD_PORT",)
) # yapf: disable
MULTILINE_VARS = [
"EXTRA_SCRIPTS", "PIOFRAMEWORK", "BUILD_FLAGS", "SRC_BUILD_FLAGS",
"BUILD_UNFLAGS", "UPLOAD_FLAGS", "SRC_FILTER", "LIB_DEPS", "LIB_IGNORE",
"LIB_EXTRA_DIRS"
]
DEFAULT_ENV_OPTIONS = dict(
tools=[
"ar", "gas", "gcc", "g++", "gnulink", "platformio", "pioplatform",
"piowinhooks", "piolib", "pioupload", "piomisc", "pioide"
], # yapf: disable
"pioproject", "piowinhooks", "piolib", "pioupload", "piomisc", "pioide"
],
toolpath=[join(util.get_source_dir(), "builder", "tools")],
variables=commonvars,
variables=clivars,
# Propagating External Environment
PIOVARIABLES=list(commonvars.keys()),
ENV=environ,
UNIX_TIME=int(time()),
PROJECT_DIR=project_helpers.get_project_dir(),
@ -136,38 +89,22 @@ if not int(ARGUMENTS.get("PIOVERBOSE", 0)):
env = DefaultEnvironment(**DEFAULT_ENV_OPTIONS)
# decode common variables
for k in list(commonvars.keys()):
if k in env:
env[k] = base64.b64decode(env[k])
if isinstance(env[k], bytes):
env[k] = env[k].decode()
if k in MULTILINE_VARS:
env[k] = ProjectConfig.parse_multi_values(env[k])
if env.GetOption('clean'):
env.PioClean(env.subst("$BUILD_DIR"))
env.Exit(0)
elif not int(ARGUMENTS.get("PIOVERBOSE", 0)):
print("Verbose mode can be enabled via `-v, --verbose` option")
# Handle custom variables from system environment
for var in ("BUILD_FLAGS", "SRC_BUILD_FLAGS", "SRC_FILTER", "EXTRA_SCRIPTS",
"UPLOAD_PORT", "UPLOAD_FLAGS", "LIB_EXTRA_DIRS"):
k = "PLATFORMIO_%s" % var
if k not in environ:
continue
if var in ("UPLOAD_PORT", ):
env[var] = environ.get(k)
continue
env.Append(**{var: ProjectConfig.parse_multi_values(environ.get(k))})
# Load variables from CLI
for key in list(clivars.keys()):
if key in env:
env[key] = base64.b64decode(env[key])
if isinstance(env[key], bytes):
env[key] = env[key].decode()
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']
]
env.LoadPioPlatform(commonvars)
env.GetProjectConfig().validate([env['PIOENV']], silent=True)
env.LoadProjectOptions()
env.LoadPioPlatform()
env.SConscriptChdir(0)
env.SConsignFile(

View File

@ -113,7 +113,7 @@ def _dump_defines(env):
def _get_svd_path(env):
svd_path = env.subst("$DEBUG_SVD_PATH")
svd_path = env.GetProjectOption("debug_svd_path")
if svd_path:
return abspath(svd_path)
@ -139,8 +139,7 @@ def DumpIDEData(env, projenv):
LINTCXXCOM = "$CXXFLAGS $CCFLAGS $CPPFLAGS"
data = {
"libsource_dirs":
[env.subst(l) for l in env.get("LIBSOURCE_DIRS", [])],
"libsource_dirs": [env.subst(l) for l in env.GetLibSourceDirs()],
"defines":
_dump_defines(env),
"includes":

View File

@ -23,8 +23,8 @@ import os
import re
import sys
from glob import glob
from os.path import (basename, commonprefix, dirname, isdir, isfile, join,
realpath, sep)
from os.path import (basename, commonprefix, dirname, expanduser, isdir,
isfile, join, realpath, sep)
import SCons.Scanner # pylint: disable=import-error
from SCons.Script import ARGUMENTS # pylint: disable=import-error
@ -207,17 +207,18 @@ class LibBuilderBase(object):
@property
def lib_archive(self):
return self.env.get("LIB_ARCHIVE", "") != "false"
return self.env.GetProjectOption("lib_archive", True)
@property
def lib_ldf_mode(self):
return self.validate_ldf_mode(
self.env.get("LIB_LDF_MODE", self.LDF_MODE_DEFAULT))
self.env.GetProjectOption("lib_ldf_mode", self.LDF_MODE_DEFAULT))
@property
def lib_compat_mode(self):
return self.validate_compat_mode(
self.env.get("LIB_COMPAT_MODE", self.COMPAT_MODE_DEFAULT))
self.env.GetProjectOption("lib_compat_mode",
self.COMPAT_MODE_DEFAULT))
@property
def depbuilders(self):
@ -867,7 +868,7 @@ class ProjectAsLibBuilder(LibBuilderBase):
pass
def process_dependencies(self): # pylint: disable=too-many-branches
uris = self.env.get("LIB_DEPS", [])
uris = self.env.GetProjectOption("lib_deps", [])
if not uris:
return
storage_dirs = []
@ -907,6 +908,14 @@ class ProjectAsLibBuilder(LibBuilderBase):
return result
def GetLibSourceDirs(env):
items = env.GetProjectOption("lib_extra_dirs", [])
items.extend(env['LIBSOURCE_DIRS'])
return [
expanduser(item) if item.startswith("~") else item for item in items
]
def GetLibBuilders(env): # pylint: disable=too-many-branches
if "__PIO_LIB_BUILDERS" in DefaultEnvironment():
@ -920,7 +929,7 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
def _check_lib_builder(lb):
compat_mode = lb.lib_compat_mode
if lb.name in env.get("LIB_IGNORE", []):
if lb.name in env.GetProjectOption("lib_ignore", []):
if verbose:
sys.stderr.write("Ignored library %s\n" % lb.path)
return None
@ -939,7 +948,7 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
return True
found_incompat = False
for libs_dir in env['LIBSOURCE_DIRS']:
for libs_dir in env.GetLibSourceDirs():
libs_dir = env.subst(libs_dir)
if not isdir(libs_dir):
continue
@ -1038,6 +1047,7 @@ def exists(_):
def generate(env):
env.AddMethod(GetLibSourceDirs)
env.AddMethod(GetLibBuilders)
env.AddMethod(ConfigureProjectLibBuilder)
return env

View File

@ -322,7 +322,7 @@ def ProcessTest(env):
def GetExtraScripts(env, scope):
items = []
for item in env.get("EXTRA_SCRIPTS", []):
for item in env.GetProjectOption("extra_scripts", []):
if scope == "post" and ":" not in item:
items.append(item)
elif item.startswith("%s:" % scope):

View File

@ -14,7 +14,6 @@
from __future__ import absolute_import
import base64
import sys
from os.path import isdir, isfile, join
@ -23,6 +22,7 @@ from SCons.Script import COMMAND_LINE_TARGETS # pylint: disable=import-error
from platformio import exception, util
from platformio.compat import WINDOWS
from platformio.managers.platform import PlatformFactory
from platformio.project.config import ProjectOptions
# pylint: disable=too-many-branches, too-many-locals
@ -33,10 +33,10 @@ def initPioPlatform(name):
def PioPlatform(env):
variables = {}
for name in env['PIOVARIABLES']:
if name in env:
variables[name.lower()] = env[name]
variables = env.GetProjectOptions(as_dict=True)
if "framework" in variables:
# support PIO Core 3.0 dev/platforms
variables['pioframework'] = variables['framework']
p = initPioPlatform(env['PLATFORM_MANIFEST'])
p.configure_default_packages(variables, COMMAND_LINE_TARGETS)
return p
@ -63,7 +63,7 @@ def GetFrameworkScript(env, framework):
return script_path
def LoadPioPlatform(env, variables):
def LoadPioPlatform(env):
p = env.PioPlatform()
installed_packages = p.get_installed_packages()
@ -92,36 +92,25 @@ def LoadPioPlatform(env, variables):
env.Prepend(LIBPATH=[join(p.get_dir(), "ldscripts")])
if "BOARD" not in env:
# handle _MCU and _F_CPU variables for AVR native
for key, value in variables.UnknownVariables().items():
if not key.startswith("BOARD_"):
continue
value = base64.b64decode(value)
if isinstance(value, bytes):
value = value.decode()
env.Replace(**{key.upper().replace("BUILD.", ""): value})
return
# update board manifest with a custom data
# update board manifest with overridden data from INI config
board_config = env.BoardConfig()
for key, value in variables.UnknownVariables().items():
if not key.startswith("BOARD_"):
continue
value = base64.b64decode(value)
if isinstance(value, bytes):
value = value.decode()
board_config.update(key.lower()[6:], value)
for option, value in env.GetProjectOptions():
if option.startswith("board_"):
board_config.update(option.lower()[6:], value)
# update default environment variables
for key in list(variables.keys()):
if key in env or \
not any([key.startswith("BOARD_"), key.startswith("UPLOAD_")]):
# load default variables from board config
for option_meta in ProjectOptions.values():
if not option_meta.buildenvvar or option_meta.buildenvvar in env:
continue
_opt, _val = key.lower().split("_", 1)
if _opt == "board":
_opt = "build"
if _val in board_config.get(_opt):
env.Replace(**{key: board_config.get("%s.%s" % (_opt, _val))})
data_path = (option_meta.name[6:]
if option_meta.name.startswith("board_") else
option_meta.name.replace("_", "."))
try:
env[option_meta.buildenvvar] = board_config.get(data_path)
except KeyError:
pass
if "build.ldscript" in board_config:
env.Replace(LDSCRIPT_PATH=board_config.get("build.ldscript"))
@ -165,7 +154,7 @@ def PrintConfiguration(env):
data = [
"CURRENT(%s)" % board_config.get_debug_tool_name(
env.subst("$DEBUG_TOOL"))
env.GetProjectOption("debug_tool"))
]
onboard = []
external = []

View File

@ -0,0 +1,49 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from platformio.project.config import ProjectConfig, ProjectOptions
def GetProjectConfig(env):
return ProjectConfig.get_instance(env['PROJECT_CONFIG'])
def GetProjectOptions(env, as_dict=False):
return env.GetProjectConfig().items(env=env['PIOENV'], as_dict=as_dict)
def GetProjectOption(env, option, default=None):
return env.GetProjectConfig().get("env:" + env['PIOENV'], option, default)
def LoadProjectOptions(env):
for option, value in env.GetProjectOptions():
option_meta = ProjectOptions.get("env." + option)
if not option_meta or not option_meta.buildenvvar:
continue
env[option_meta.buildenvvar] = value
def exists(_):
return True
def generate(env):
env.AddMethod(GetProjectConfig)
env.AddMethod(GetProjectOptions)
env.AddMethod(GetProjectOption)
env.AddMethod(LoadProjectOptions)
return env

View File

@ -163,22 +163,18 @@ def device_list( # pylint: disable=too-many-branches
"--environment",
help="Load configuration from `platformio.ini` and specified environment")
def device_monitor(**kwargs): # pylint: disable=too-many-branches
custom_monitor_flags = []
env_options = {}
try:
env_options = get_project_options(kwargs['project_dir'],
kwargs['environment'])
if "monitor_flags" in env_options:
custom_monitor_flags = ProjectConfig.parse_multi_values(
env_options['monitor_flags'])
if env_options:
for k in ("port", "speed", "rts", "dtr"):
k2 = "monitor_%s" % k
if k == "speed":
k = "baud"
if kwargs[k] is None and k2 in env_options:
kwargs[k] = env_options[k2]
if k != "port":
kwargs[k] = int(kwargs[k])
for k in ("port", "speed", "rts", "dtr"):
k2 = "monitor_%s" % k
if k == "speed":
k = "baud"
if kwargs[k] is None and k2 in env_options:
kwargs[k] = env_options[k2]
if k != "port":
kwargs[k] = int(kwargs[k])
except exception.NotPlatformIOProject:
pass
@ -187,12 +183,12 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
if len(ports) == 1:
kwargs['port'] = ports[0]['port']
sys.argv = ["monitor"] + custom_monitor_flags
sys.argv = ["monitor"] + env_options.get("monitor_flags", [])
for k, v in kwargs.items():
if k in ("port", "baud", "rts", "dtr", "environment", "project_dir"):
continue
k = "--" + k.replace("_", "-")
if k in custom_monitor_flags:
if k in env_options.get("monitor_flags", []):
continue
if isinstance(v, bool):
if v:

View File

@ -41,13 +41,11 @@ class ProjectRPC(object):
def _get_project_data(project_dir):
data = {"boards": [], "envLibdepsDirs": [], "libExtraDirs": []}
config = ProjectConfig(join(project_dir, "platformio.ini"))
config.validate(validate_options=False)
config.validate(silent=True)
libdeps_dir = get_project_libdeps_dir()
if config.has_section("platformio") and \
config.has_option("platformio", "lib_extra_dirs"):
data['libExtraDirs'].extend(
config.getlist("platformio", "lib_extra_dirs"))
data['libExtraDirs'].extend(
config.get("platformio", "lib_extra_dirs", []))
for section in config.sections():
if not section.startswith("env:"):
@ -55,9 +53,8 @@ class ProjectRPC(object):
data['envLibdepsDirs'].append(join(libdeps_dir, section[4:]))
if config.has_option(section, "board"):
data['boards'].append(config.get(section, "board"))
if config.has_option(section, "lib_extra_dirs"):
data['libExtraDirs'].extend(
config.getlist(section, "lib_extra_dirs"))
data['libExtraDirs'].extend(
config.get(section, "lib_extra_dirs", []))
# skip non existing folders and resolve full path
for key in ("envLibdepsDirs", "libExtraDirs"):
@ -232,11 +229,9 @@ class ProjectRPC(object):
project_description = None
try:
config = ProjectConfig(join(project_dir, "platformio.ini"))
config.validate(validate_options=False)
if config.has_section("platformio") and \
config.has_option("platformio", "description"):
project_description = config.get(
"platformio", "description")
config.validate(silent=True)
project_description = config.get("platformio",
"description")
except exception.PlatformIOProjectException:
continue

View File

@ -70,7 +70,6 @@ def cli(
project_option,
env_prefix,
silent):
if not silent:
if project_dir == getcwd():
click.secho(

View File

@ -109,9 +109,8 @@ def cli(ctx, **options):
continue
storage_dir = join(libdeps_dir, env)
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
if config.has_option("env:" + env, "lib_deps"):
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][
storage_dir] = config.getlist("env:" + env, "lib_deps")
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(
"env:" + env, "lib_deps", [])
@cli.command("install", short_help="Install library")
@ -175,8 +174,7 @@ def lib_install( # pylint: disable=too-many-arguments
if project_environments and env not in project_environments:
continue
config.expand_interpolations = False
lib_deps = (config.getlist("env:" + env, "lib_deps")
if config.has_option("env:" + env, "lib_deps") else [])
lib_deps = config.get("env:" + env, "lib_deps", [])
for library in libraries:
if library in lib_deps:
continue

View File

@ -1,352 +0,0 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os import getcwd, makedirs
from os.path import getmtime, isdir, isfile, join
from time import time
import click
from platformio import exception, telemetry, util
from platformio.commands.device import device_monitor as cmd_device_monitor
from platformio.commands.lib import (CTX_META_STORAGE_DIRS_KEY,
CTX_META_STORAGE_LIBDEPS_KEY)
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.platform import PlatformFactory
from platformio.project.config import ProjectConfig
from platformio.project.helpers import (
calculate_project_hash, find_project_dir_above, get_project_build_dir,
get_project_dir, get_project_libdeps_dir)
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
@click.command("run", short_help="Process project environments")
@click.option("-e", "--environment", multiple=True)
@click.option("-t", "--target", multiple=True)
@click.option("--upload-port")
@click.option(
"-d",
"--project-dir",
default=getcwd,
type=click.Path(
exists=True,
file_okay=True,
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, project_conf,
silent, verbose, disable_auto_clean):
# find project directory on upper level
if isfile(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(get_project_build_dir())
except: # pylint: disable=bare-except
click.secho(
"Can not remove temporary directory `%s`. Please remove "
"it manually to avoid build issues" %
get_project_build_dir(force=True),
fg="yellow")
config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini"))
config.validate(environment)
_handle_legacy_libdeps(project_dir, config)
results = []
start_time = time()
default_envs = config.default_envs()
for envname in config.envs():
skipenv = any([
environment and envname not in environment, not environment
and default_envs and envname not in default_envs
])
if skipenv:
results.append((envname, None))
continue
if not silent and any(
status is not None for (_, status) in results):
click.echo()
options = config.items(env=envname, as_dict=True)
if "piotest" not in options and "piotest" in ctx.meta:
options['piotest'] = ctx.meta['piotest']
ep = EnvironmentProcessor(ctx, envname, options, target,
upload_port, silent, verbose)
result = (envname, ep.process())
results.append(result)
if result[1] and "monitor" in ep.get_build_targets() and \
"nobuild" not in ep.get_build_targets():
ctx.invoke(
cmd_device_monitor,
environment=environment[0] if environment else None)
found_error = any(status is False for (_, status) in results)
if (found_error or not silent) and len(results) > 1:
click.echo()
print_summary(results, start_time)
if found_error:
raise exception.ReturnErrorCode(1)
return True
class EnvironmentProcessor(object):
DEFAULT_DUMP_OPTIONS = ("platform", "framework", "board")
IGNORE_BUILD_OPTIONS = [
"test_transport", "test_filter", "test_ignore", "test_port",
"test_speed", "debug_port", "debug_init_cmds", "debug_extra_cmds",
"debug_server", "debug_init_break", "debug_load_cmd",
"debug_load_mode", "monitor_port", "monitor_speed", "monitor_rts",
"monitor_dtr"
]
REMAPED_OPTIONS = {"framework": "pioframework", "platform": "pioplatform"}
def __init__(
self, # pylint: disable=R0913
cmd_ctx,
name,
options,
targets,
upload_port,
silent,
verbose):
self.cmd_ctx = cmd_ctx
self.name = name
self.options = options
self.targets = targets
self.upload_port = upload_port
self.silent = silent
self.verbose = verbose
def process(self):
terminal_width, _ = click.get_terminal_size()
start_time = time()
env_dump = []
for k, v in self.options.items():
self.options[k] = self.options[k].strip()
if self.verbose or k in self.DEFAULT_DUMP_OPTIONS:
env_dump.append("%s: %s" % (k, ", ".join(
ProjectConfig.parse_multi_values(v))))
if not self.silent:
click.echo("Processing %s (%s)" % (click.style(
self.name, fg="cyan", bold=True), "; ".join(env_dump)))
click.secho("-" * terminal_width, bold=True)
result = self._run()
is_error = result['returncode'] != 0
if self.silent and not is_error:
return True
if is_error or "piotest_processor" not in self.cmd_ctx.meta:
print_header(
"[%s] Took %.2f seconds" % (
(click.style("ERROR", fg="red", bold=True) if is_error else
click.style("SUCCESS", fg="green", bold=True)),
time() - start_time),
is_error=is_error)
return not is_error
def get_build_variables(self):
variables = {"pioenv": self.name}
if self.upload_port:
variables['upload_port'] = self.upload_port
for k, v in self.options.items():
if k in self.REMAPED_OPTIONS:
k = self.REMAPED_OPTIONS[k]
if k in self.IGNORE_BUILD_OPTIONS:
continue
if k == "targets" or (k == "upload_port" and self.upload_port):
continue
variables[k] = v
return variables
def get_build_targets(self):
targets = []
if self.targets:
targets = [t for t in self.targets]
elif "targets" in self.options:
targets = self.options['targets'].split(", ")
return targets
def _run(self):
if "platform" not in self.options:
raise exception.UndefinedEnvPlatform(self.name)
build_vars = self.get_build_variables()
build_targets = self.get_build_targets()
telemetry.on_run_environment(self.options, build_targets)
# skip monitor target, we call it above
if "monitor" in build_targets:
build_targets.remove("monitor")
if "nobuild" not in build_targets:
# install dependent libraries
if "lib_install" in self.options:
_autoinstall_libdeps(self.cmd_ctx, self.name, [
int(d.strip())
for d in self.options['lib_install'].split(",")
if d.strip()
], self.verbose)
if "lib_deps" in self.options:
_autoinstall_libdeps(
self.cmd_ctx, self.name,
ProjectConfig.parse_multi_values(self.options['lib_deps']),
self.verbose)
try:
p = PlatformFactory.newPlatform(self.options['platform'])
except exception.UnknownPlatform:
self.cmd_ctx.invoke(
cmd_platform_install,
platforms=[self.options['platform']],
skip_default_package=True)
p = PlatformFactory.newPlatform(self.options['platform'])
return p.run(build_vars, build_targets, self.silent, self.verbose)
def _handle_legacy_libdeps(project_dir, config):
legacy_libdeps_dir = join(project_dir, ".piolibdeps")
if (not isdir(legacy_libdeps_dir)
or legacy_libdeps_dir == get_project_libdeps_dir()):
return
if not config.has_section("env"):
config.add_section("env")
lib_extra_dirs = []
if config.has_option("env", "lib_extra_dirs"):
lib_extra_dirs = config.getlist("env", "lib_extra_dirs")
lib_extra_dirs.append(legacy_libdeps_dir)
config.set("env", "lib_extra_dirs", lib_extra_dirs)
click.secho(
"DEPRECATED! A legacy library storage `{0}` has been found in a "
"project. \nPlease declare project dependencies in `platformio.ini`"
" file using `lib_deps` option and remove `{0}` folder."
"\nMore details -> http://docs.platformio.org/page/projectconf/"
"section_env_library.html#lib-deps".format(legacy_libdeps_dir),
fg="yellow")
def _autoinstall_libdeps(ctx, envname, libraries, verbose=False):
if not libraries:
return
libdeps_dir = join(get_project_libdeps_dir(), envname)
ctx.meta.update({
CTX_META_STORAGE_DIRS_KEY: [libdeps_dir],
CTX_META_STORAGE_LIBDEPS_KEY: {
libdeps_dir: libraries
}
})
try:
ctx.invoke(cmd_lib_install, silent=not verbose)
except exception.InternetIsOffline as e:
click.secho(str(e), fg="yellow")
def _clean_build_dir(build_dir):
# remove legacy ".pioenvs" folder
legacy_build_dir = join(get_project_dir(), ".pioenvs")
if isdir(legacy_build_dir) and legacy_build_dir != build_dir:
util.rmtree_(legacy_build_dir)
structhash_file = join(build_dir, "structure.hash")
proj_hash = calculate_project_hash()
# if project's config is modified
if (isdir(build_dir) and getmtime(
join(get_project_dir(), "platformio.ini")) > getmtime(build_dir)):
util.rmtree_(build_dir)
# check project structure
if isdir(build_dir) and isfile(structhash_file):
with open(structhash_file) as f:
if f.read() == proj_hash:
return
util.rmtree_(build_dir)
if not isdir(build_dir):
makedirs(build_dir)
with open(structhash_file, "w") as f:
f.write(proj_hash)
def print_header(label, is_error=False, fg=None):
terminal_width, _ = click.get_terminal_size()
width = len(click.unstyle(label))
half_line = "=" * int((terminal_width - width - 2) / 2)
click.secho(
"%s %s %s" % (half_line, label, half_line), fg=fg, err=is_error)
def print_summary(results, start_time):
print_header("[%s]" % click.style("SUMMARY"))
envname_max_len = 0
for (envname, _) in results:
if len(envname) > envname_max_len:
envname_max_len = len(envname)
successed = True
for (envname, status) in results:
status_str = click.style("SUCCESS", fg="green")
if status is False:
successed = False
status_str = click.style("ERROR", fg="red")
elif status is None:
status_str = click.style("SKIP", fg="yellow")
format_str = (
"Environment {0:<" + str(envname_max_len + 9) + "}\t[{1}]")
click.echo(
format_str.format(click.style(envname, fg="cyan"), status_str),
err=status is False)
print_header(
"[%s] Took %.2f seconds" % (
(click.style("SUCCESS", fg="green", bold=True) if successed else
click.style("ERROR", fg="red", bold=True)), time() - start_time),
is_error=not successed)

View File

@ -0,0 +1,16 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from platformio.commands.run.command import cli
from platformio.commands.run.helpers import print_header

View File

@ -0,0 +1,119 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os import getcwd
from os.path import isfile, join
from time import time
import click
from platformio import exception, util
from platformio.commands.device import device_monitor as cmd_device_monitor
from platformio.commands.run.helpers import (
_clean_build_dir, _handle_legacy_libdeps, print_summary)
from platformio.commands.run.processor import EnvironmentProcessor
from platformio.project.config import ProjectConfig
from platformio.project.helpers import (find_project_dir_above,
get_project_build_dir)
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches
@click.command("run", short_help="Process project environments")
@click.option("-e", "--environment", multiple=True)
@click.option("-t", "--target", multiple=True)
@click.option("--upload-port")
@click.option(
"-d",
"--project-dir",
default=getcwd,
type=click.Path(
exists=True,
file_okay=True,
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, project_conf,
silent, verbose, disable_auto_clean):
# find project directory on upper level
if isfile(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(get_project_build_dir())
except: # pylint: disable=bare-except
click.secho(
"Can not remove temporary directory `%s`. Please remove "
"it manually to avoid build issues" %
get_project_build_dir(force=True),
fg="yellow")
config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini"))
config.validate(environment)
_handle_legacy_libdeps(project_dir, config)
results = []
start_time = time()
default_envs = config.default_envs()
for envname in config.envs():
skipenv = any([
environment and envname not in environment, not environment
and default_envs and envname not in default_envs
])
if skipenv:
results.append((envname, None))
continue
if not silent and any(
status is not None for (_, status) in results):
click.echo()
ep = EnvironmentProcessor(ctx, envname, config, target,
upload_port, silent, verbose)
result = (envname, ep.process())
results.append(result)
if result[1] and "monitor" in ep.get_build_targets() and \
"nobuild" not in ep.get_build_targets():
ctx.invoke(
cmd_device_monitor,
environment=environment[0] if environment else None)
found_error = any(status is False for (_, status) in results)
if (found_error or not silent) and len(results) > 1:
click.echo()
print_summary(results, start_time)
if found_error:
raise exception.ReturnErrorCode(1)
return True

View File

@ -0,0 +1,127 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os import makedirs
from os.path import getmtime, isdir, isfile, join
from time import time
import click
from platformio import exception, util
from platformio.commands.lib import (CTX_META_STORAGE_DIRS_KEY,
CTX_META_STORAGE_LIBDEPS_KEY)
from platformio.commands.lib import lib_install as cmd_lib_install
from platformio.project.helpers import (
calculate_project_hash, get_project_dir, get_project_libdeps_dir)
def _handle_legacy_libdeps(project_dir, config):
legacy_libdeps_dir = join(project_dir, ".piolibdeps")
if (not isdir(legacy_libdeps_dir)
or legacy_libdeps_dir == get_project_libdeps_dir()):
return
if not config.has_section("env"):
config.add_section("env")
lib_extra_dirs = config.get("env", "lib_extra_dirs", [])
lib_extra_dirs.append(legacy_libdeps_dir)
config.set("env", "lib_extra_dirs", lib_extra_dirs)
click.secho(
"DEPRECATED! A legacy library storage `{0}` has been found in a "
"project. \nPlease declare project dependencies in `platformio.ini`"
" file using `lib_deps` option and remove `{0}` folder."
"\nMore details -> http://docs.platformio.org/page/projectconf/"
"section_env_library.html#lib-deps".format(legacy_libdeps_dir),
fg="yellow")
def _autoinstall_libdeps(ctx, envname, libraries, verbose=False):
if not libraries:
return
libdeps_dir = join(get_project_libdeps_dir(), envname)
ctx.meta.update({
CTX_META_STORAGE_DIRS_KEY: [libdeps_dir],
CTX_META_STORAGE_LIBDEPS_KEY: {
libdeps_dir: libraries
}
})
try:
ctx.invoke(cmd_lib_install, silent=not verbose)
except exception.InternetIsOffline as e:
click.secho(str(e), fg="yellow")
def _clean_build_dir(build_dir):
# remove legacy ".pioenvs" folder
legacy_build_dir = join(get_project_dir(), ".pioenvs")
if isdir(legacy_build_dir) and legacy_build_dir != build_dir:
util.rmtree_(legacy_build_dir)
structhash_file = join(build_dir, "structure.hash")
proj_hash = calculate_project_hash()
# if project's config is modified
if (isdir(build_dir) and getmtime(
join(get_project_dir(), "platformio.ini")) > getmtime(build_dir)):
util.rmtree_(build_dir)
# check project structure
if isdir(build_dir) and isfile(structhash_file):
with open(structhash_file) as f:
if f.read() == proj_hash:
return
util.rmtree_(build_dir)
if not isdir(build_dir):
makedirs(build_dir)
with open(structhash_file, "w") as f:
f.write(proj_hash)
def print_header(label, is_error=False, fg=None):
terminal_width, _ = click.get_terminal_size()
width = len(click.unstyle(label))
half_line = "=" * int((terminal_width - width - 2) / 2)
click.secho(
"%s %s %s" % (half_line, label, half_line), fg=fg, err=is_error)
def print_summary(results, start_time):
print_header("[%s]" % click.style("SUMMARY"))
succeeded_nums = 0
failed_nums = 0
envname_max_len = max(
[len(click.style(envname, fg="cyan")) for (envname, _) in results])
for (envname, status) in results:
if status is False:
failed_nums += 1
status_str = click.style("FAILED", fg="red")
elif status is None:
status_str = click.style("IGNORED", fg="yellow")
else:
succeeded_nums += 1
status_str = click.style("SUCCESS", fg="green")
format_str = "Environment {0:<%d}\t[{1}]" % envname_max_len
click.echo(
format_str.format(click.style(envname, fg="cyan"), status_str),
err=status is False)
print_header(
"%s%d succeeded in %.2f seconds" %
("%d failed, " % failed_nums if failed_nums else "", succeeded_nums,
time() - start_time),
is_error=failed_nums,
fg="red" if failed_nums else "green")

View File

@ -0,0 +1,115 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from time import time
import click
from platformio import exception, telemetry
from platformio.commands.platform import \
platform_install as cmd_platform_install
from platformio.commands.run.helpers import _autoinstall_libdeps, print_header
from platformio.managers.platform import PlatformFactory
# pylint: disable=too-many-instance-attributes
class EnvironmentProcessor(object):
DEFAULT_PRINT_OPTIONS = ("platform", "framework", "board")
def __init__( # pylint: disable=too-many-arguments
self, cmd_ctx, name, config, targets, upload_port, silent,
verbose):
self.cmd_ctx = cmd_ctx
self.name = name
self.config = config
self.targets = targets
self.upload_port = upload_port
self.silent = silent
self.verbose = verbose
self.options = config.items(env=name, as_dict=True)
def process(self):
terminal_width, _ = click.get_terminal_size()
start_time = time()
env_dump = []
for k, v in self.options.items():
if self.verbose or k in self.DEFAULT_PRINT_OPTIONS:
env_dump.append(
"%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v))
if not self.silent:
click.echo("Processing %s (%s)" % (click.style(
self.name, fg="cyan", bold=True), "; ".join(env_dump)))
click.secho("-" * terminal_width, bold=True)
result = self._run_platform()
is_error = result['returncode'] != 0
if self.silent and not is_error:
return True
if is_error or "piotest_processor" not in self.cmd_ctx.meta:
print_header(
"[%s] Took %.2f seconds" % (
(click.style("ERROR", fg="red", bold=True) if is_error else
click.style("SUCCESS", fg="green", bold=True)),
time() - start_time),
is_error=is_error)
return not is_error
def get_build_variables(self):
variables = {"pioenv": self.name, "project_config": self.config.path}
if "piotest" in self.cmd_ctx.meta:
variables['piotest'] = self.cmd_ctx.meta['piotest']
if self.upload_port:
# override upload port with a custom from CLI
variables['upload_port'] = self.upload_port
return variables
def get_build_targets(self):
if self.targets:
return [t for t in self.targets]
return self.config.get("env:" + self.name, "targets", [])
def _run_platform(self):
if "platform" not in self.options:
raise exception.UndefinedEnvPlatform(self.name)
build_vars = self.get_build_variables()
build_targets = self.get_build_targets()
telemetry.on_run_environment(self.options, build_targets)
# skip monitor target, we call it above
if "monitor" in build_targets:
build_targets.remove("monitor")
if "nobuild" not in build_targets and "lib_deps" in self.options:
_autoinstall_libdeps(
self.cmd_ctx, self.name,
self.config.get("env:" + self.name, "lib_deps"), self.verbose)
try:
p = PlatformFactory.newPlatform(self.options['platform'])
except exception.UnknownPlatform:
self.cmd_ctx.invoke(
cmd_platform_install,
platforms=[self.options['platform']],
skip_default_package=True)
p = PlatformFactory.newPlatform(self.options['platform'])
return p.run(build_vars, build_targets, self.silent, self.verbose)

View File

@ -22,7 +22,7 @@ from time import time
import click
from platformio import exception, util
from platformio.commands.run import print_header
from platformio.commands.run.helpers import print_header
from platformio.commands.test.embedded import EmbeddedTestProcessor
from platformio.commands.test.native import NativeTestProcessor
from platformio.project.config import ProjectConfig
@ -107,9 +107,8 @@ def cli( # pylint: disable=redefined-builtin
# filter and ignore patterns
patterns = dict(filter=list(filter), ignore=list(ignore))
for key in patterns:
if config.has_option(section, "test_%s" % key):
patterns[key].extend(
config.getlist(section, "test_%s" % key))
patterns[key].extend(
config.get(section, "test_%s" % key, []))
skip_conditions = [
environment and envname not in environment,
@ -167,10 +166,11 @@ def cli( # pylint: disable=redefined-builtin
passed_nums += 1
status_str = click.style("PASSED", fg="green")
format_str = "test/{:<%d} > {:<%d}\t[{}]" % (testname_max_len,
envname_max_len)
click.echo(
("test/{:<%d} > {:<%d}\t[{}]" %
(testname_max_len, envname_max_len)).format(
testname, click.style(envname, fg="cyan"), status_str),
format_str.format(testname, click.style(envname, fg="cyan"),
status_str),
err=status is False)
print_header(

View File

@ -32,7 +32,8 @@ class EmbeddedTestProcessor(TestProcessorBase):
target = ["__test"]
if self.options['without_uploading']:
target.append("checkprogsize")
self.build_or_upload(target)
if not self.build_or_upload(target):
return False
if not self.options['without_uploading']:
self.print_progress("Uploading... (2/3)")
@ -41,7 +42,8 @@ class EmbeddedTestProcessor(TestProcessorBase):
target.append("nobuild")
else:
target.append("__test")
self.build_or_upload(target)
if not self.build_or_upload(target):
return False
if self.options['without_testing']:
return None

View File

@ -25,7 +25,8 @@ class NativeTestProcessor(TestProcessorBase):
def process(self):
if not self.options['without_building']:
self.print_progress("Building... (1/2)")
self.build_or_upload(["__test"])
if not self.build_or_upload(["__test"]):
return False
if self.options['without_testing']:
return None
self.print_progress("Testing... (2/2)")

View File

@ -21,7 +21,7 @@ import click
from platformio import exception
from platformio.commands.run import cli as cmd_run
from platformio.commands.run import print_header
from platformio.commands.run.helpers import print_header
from platformio.project.helpers import get_project_test_dir
TRANSPORT_OPTIONS = {
@ -82,7 +82,7 @@ class TestProcessorBase(object):
def __init__(self, cmd_ctx, testname, envname, options):
self.cmd_ctx = cmd_ctx
self.cmd_ctx.meta['piotest_processor'] = True
self.cmd_ctx.meta['piotest_processor'] = True # FIXME
self.test_name = testname
self.options = options
self.env_name = envname
@ -92,9 +92,10 @@ class TestProcessorBase(object):
self._outputcpp_generated = False
def get_transport(self):
transport = self.env_options.get("framework")
if self.env_options.get("platform") == "native":
transport = "native"
elif "framework" in self.env_options:
transport = self.env_options.get("framework")[0]
if "test_transport" in self.env_options:
transport = self.env_options['test_transport']
if transport not in TRANSPORT_OPTIONS:
@ -108,8 +109,9 @@ class TestProcessorBase(object):
def print_progress(self, text, is_error=False):
click.echo()
print_header(
"[test/%s] %s" % (click.style(
self.test_name, fg="yellow", bold=True), text),
"[test/%s > %s] %s" % (click.style(self.test_name, fg="yellow"),
click.style(self.env_name, fg="cyan"),
text),
is_error=is_error)
def build_or_upload(self, target):
@ -118,19 +120,22 @@ class TestProcessorBase(object):
self._outputcpp_generated = True
if self.test_name != "*":
self.cmd_ctx.meta['piotest'] = self.test_name
self.cmd_ctx.meta['piotest'] = self.test_name # FIXME
if not self.options['verbose']:
click.echo("Please wait...")
return self.cmd_ctx.invoke(
cmd_run,
project_dir=self.options['project_dir'],
upload_port=self.options['upload_port'],
silent=not self.options['verbose'],
environment=[self.env_name],
disable_auto_clean="nobuild" in target,
target=target)
try:
return self.cmd_ctx.invoke(
cmd_run,
project_dir=self.options['project_dir'],
upload_port=self.options['upload_port'],
silent=not self.options['verbose'],
environment=[self.env_name],
disable_auto_clean="nobuild" in target,
target=target)
except exception.ReturnErrorCode:
return False
def process(self):
raise NotImplementedError

View File

@ -29,6 +29,7 @@ from platformio.managers.core import get_core_package_dir
from platformio.managers.package import BasePkgManager, PackageManager
from platformio.proc import (BuildAsyncPipe, copy_pythonpath_to_osenv,
exec_command, get_pythonexe_path)
from platformio.project.config import ProjectConfig
from platformio.project.helpers import (
get_project_boards_dir, get_project_core_dir, get_project_packages_dir,
get_project_platforms_dir)
@ -358,7 +359,12 @@ class PlatformRunMixin(object):
assert isinstance(variables, dict)
assert isinstance(targets, list)
self.configure_default_packages(variables, targets)
config = ProjectConfig.get_instance(variables['project_config'])
options = config.items(env=variables['pioenv'], as_dict=True)
if "framework" in options:
# support PIO Core 3.0 dev/platforms
options['pioframework'] = options['framework']
self.configure_default_packages(options, targets)
self.install_packages(silent=True)
self.silent = silent
@ -611,12 +617,9 @@ class PlatformBase( # pylint: disable=too-many-public-methods
def get_package_type(self, name):
return self.packages[name].get("type")
def configure_default_packages(self, variables, targets):
def configure_default_packages(self, options, targets):
# enable used frameworks
frameworks = variables.get("pioframework", [])
if not isinstance(frameworks, list):
frameworks = frameworks.split(", ")
for framework in frameworks:
for framework in options.get("framework", []):
if not self.frameworks:
continue
framework = framework.lower().strip()

View File

@ -21,6 +21,7 @@ from os.path import isfile
import click
from platformio import exception
from platformio.project.options import ProjectOptions
try:
import ConfigParser as ConfigParser
@ -39,98 +40,6 @@ CONFIG_HEADER = """;PlatformIO Project Configuration File
"""
KNOWN_PLATFORMIO_OPTIONS = [
"description",
"env_default",
"extra_configs",
# Dirs
"core_dir",
"globallib_dir",
"platforms_dir",
"packages_dir",
"cache_dir",
"workspace_dir",
"build_dir",
"libdeps_dir",
"lib_dir",
"include_dir",
"src_dir",
"test_dir",
"boards_dir",
"data_dir"
]
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",
"monitor_flags",
# 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):
@ -191,11 +100,7 @@ class ProjectConfig(object):
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 pattern in self.get("platformio", "extra_configs", []):
for item in glob.glob(pattern):
self.read(item)
@ -212,6 +117,14 @@ class ProjectConfig(object):
if option not in options:
options.append(option)
# handle system environment variables
scope = section.split(":", 1)[0]
for option_meta in ProjectOptions.values():
if option_meta.scope != scope or option_meta.name in options:
continue
if option_meta.sysenvvar and option_meta.sysenvvar in os.environ:
options.append(option_meta.name)
return options
def has_option(self, section, option):
@ -239,39 +152,84 @@ class ProjectConfig(object):
value = "\n" + value # start from a new line
self._parser.set(section, option, value)
def get(self, section, option):
def getraw(self, section, option):
if not self.expand_interpolations:
return self._parser.get(section, option)
try:
value = self._parser.get(section, option)
except ConfigParser.NoOptionError:
except ConfigParser.NoOptionError as e:
if not section.startswith("env:"):
raise e
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)
return self.VARTPL_RE.sub(self._re_interpolation_handler, value)
def _re_sub_handler(self, match):
def _re_interpolation_handler(self, match):
section, option = match.group(1), match.group(2)
if section == "sysenv":
return os.getenv(option)
return self.get(section, option)
return self.getraw(section, option)
def getlist(self, section, option):
return self.parse_multi_values(self.get(section, option))
def get(self, section, option, default=None):
value = None
try:
value = self.getraw(section, option)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
pass # handle value from system environment
except ConfigParser.Error as e:
raise exception.InvalidProjectConf(self.path, str(e))
option_meta = ProjectOptions.get(
"%s.%s" % (section.split(":", 1)[0], option))
if not option_meta:
return value or default
if value and option_meta.multiple:
value = self.parse_multi_values(value)
if option_meta.sysenvvar:
envvar_value = os.getenv(option_meta.sysenvvar)
if not envvar_value and option_meta.oldnames:
for oldoption in option_meta.oldnames:
envvar_value = os.getenv("PLATFORMIO_" + oldoption.upper())
if envvar_value:
break
if envvar_value and option_meta.multiple:
value = value or []
value.extend(self.parse_multi_values(envvar_value))
elif envvar_value and not value:
value = envvar_value
# option is not specified by user
if value is None:
return default
return self._covert_value(value, option_meta.type)
@staticmethod
def _covert_value(value, to_type):
items = value
if not isinstance(value, (list, tuple)):
items = [value]
for i, v in enumerate(items):
if to_type == bool:
items[i] = v in ("1", "true", "yes", "y")
elif to_type == int:
items[i] = int(v)
elif to_type == float:
items[i] = float(v)
return items if isinstance(value, (list, tuple)) else items[0]
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")
return self.get("platformio", "env_default", [])
def validate(self, envs=None, validate_options=True):
def validate(self, envs=None, silent=False):
if not isfile(self.path):
raise exception.NotPlatformIOProject(self.path)
# check envs
@ -283,74 +241,65 @@ class ProjectConfig(object):
if unknown:
raise exception.UnknownEnvNames(", ".join(unknown),
", ".join(known))
return self.validate_options() if validate_options else True
return self.validate_options(silent)
def validate_options(self):
return (self._validate_platformio_options()
and self._validate_env_options())
def _validate_platformio_options(self):
if not self._parser.has_section("platformio"):
return True
warnings = set()
# legacy `lib_extra_dirs`
if self._parser.has_option("platformio", "lib_extra_dirs"):
def validate_options(self, silent=False):
warnings = []
# legacy `lib_extra_dirs` in [platformio]
if (self._parser.has_section("platformio")
and self._parser.has_option("platformio", "lib_extra_dirs")):
if not self._parser.has_section("env"):
self._parser.add_section("env")
self._parser.set("env", "lib_extra_dirs",
self._parser.get("platformio", "lib_extra_dirs"))
self._parser.remove_option("platformio", "lib_extra_dirs")
warnings.add(
"`lib_extra_dirs` option is deprecated in section "
"`platformio`! Please move it to global `env` section")
warnings.append(
"`lib_extra_dirs` configuration option is deprecated in "
"section [platformio]! Please move it to global `env` section")
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))
warnings.extend(self._validate_unknown_options())
for warning in warnings:
click.secho("Warning! %s" % warning, fg="yellow")
if not silent:
for warning in warnings:
click.secho("Warning! %s" % warning, fg="yellow")
return True
return warnings
def _validate_env_options(self):
warnings = set()
def _validate_unknown_options(self):
warnings = []
renamed_options = {}
for option in ProjectOptions.values():
if option.oldnames:
renamed_options.update(
{name: option.name
for name in option.oldnames})
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]))
if option in renamed_options:
warnings.append(
"`%s` configuration 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.set(section, renamed_options[option],
self._parser.get(section, option))
self._parser.remove_option(section, option)
continue
# unknown
scope = section.split(":", 1)[0]
unknown_conditions = [
option not in KNOWN_ENV_OPTIONS,
not option.startswith("custom_"),
not option.startswith("board_")
("%s.%s" % (scope, option)) not in ProjectOptions,
scope != "env" or
not option.startswith(("custom_", "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
warnings.append("Ignore unknown configuration option `%s` "
"in section [%s]" % (option, section))
return warnings
def to_json(self):
result = {}

View File

@ -15,7 +15,7 @@
import os
from hashlib import sha1
from os import walk
from os.path import (abspath, dirname, expanduser, isdir, isfile, join,
from os.path import (dirname, expanduser, isdir, isfile, join, realpath,
splitdrive)
from platformio import __version__
@ -44,46 +44,31 @@ def find_project_dir_above(path):
def get_project_optional_dir(name, default=None):
paths = None
project_dir = get_project_dir()
config = ProjectConfig.get_instance(join(project_dir, "platformio.ini"))
optional_dir = config.get("platformio", name)
# check for system environment variable
var_name = "PLATFORMIO_%s" % name.upper()
if var_name in os.environ:
paths = os.getenv(var_name)
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:
if not optional_dir:
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(
if "$PROJECT_HASH" in optional_dir:
optional_dir = optional_dir.replace(
"$PROJECT_HASH",
sha1(project_dir if PY2 else project_dir.encode()).hexdigest()
[:10])
return paths
if optional_dir.startswith("~"):
optional_dir = expanduser(optional_dir)
return realpath(optional_dir)
def get_project_core_dir():
default = join(expanduser("~"), ".platformio")
core_dir = get_project_optional_dir(
"core_dir",
get_project_optional_dir("home_dir",
join(expanduser("~"), ".platformio")))
"core_dir", get_project_optional_dir("home_dir", default))
win_core_dir = None
if WINDOWS:
if WINDOWS and core_dir == default:
win_core_dir = splitdrive(core_dir)[0] + "\\.platformio"
if isdir(win_core_dir):
core_dir = win_core_dir
@ -91,10 +76,12 @@ def get_project_core_dir():
if not isdir(core_dir):
try:
os.makedirs(core_dir)
except: # pylint: disable=bare-except
except OSError as e:
if win_core_dir:
os.makedirs(win_core_dir)
core_dir = win_core_dir
else:
raise e
assert isdir(core_dir)
return core_dir

View File

@ -0,0 +1,198 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=redefined-builtin, too-many-arguments
from collections import OrderedDict, namedtuple
ConfigOptionClass = namedtuple("ConfigOption", [
"scope", "name", "type", "multiple", "sysenvvar", "buildenvvar", "oldnames"
])
def ConfigOption(scope,
name,
type=str,
multiple=False,
sysenvvar=None,
buildenvvar=None,
oldnames=None):
return ConfigOptionClass(scope, name, type, multiple, sysenvvar,
buildenvvar, oldnames)
def ConfigPlatformioOption(*args, **kwargs):
return ConfigOption("platformio", *args, **kwargs)
def ConfigEnvOption(*args, **kwargs):
return ConfigOption("env", *args, **kwargs)
ProjectOptions = OrderedDict([
("%s.%s" % (option.scope, option.name), option) for option in [
#
# [platformio]
#
ConfigPlatformioOption(name="description"),
ConfigPlatformioOption(
name="env_default",
multiple=True,
sysenvvar="PLATFORMIO_ENV_DEFAULT"),
ConfigPlatformioOption(name="extra_configs", multiple=True),
# Dirs
ConfigPlatformioOption(
name="core_dir",
oldnames=["home_dir"],
sysenvvar="PLATFORMIO_CORE_DIR"),
ConfigPlatformioOption(
name="globallib_dir", sysenvvar="PLATFORMIO_GLOBALLIB_DIR"),
ConfigPlatformioOption(
name="platforms_dir", sysenvvar="PLATFORMIO_PLATFORMS_DIR"),
ConfigPlatformioOption(
name="packages_dir", sysenvvar="PLATFORMIO_PACKAGES_DIR"),
ConfigPlatformioOption(
name="cache_dir", sysenvvar="PLATFORMIO_CACHE_DIR"),
ConfigPlatformioOption(
name="workspace_dir", sysenvvar="PLATFORMIO_WORKSPACE_DIR"),
ConfigPlatformioOption(
name="build_dir", sysenvvar="PLATFORMIO_BUILD_DIR"),
ConfigPlatformioOption(
name="libdeps_dir", sysenvvar="PLATFORMIO_LIBDEPS_DIR"),
ConfigPlatformioOption(name="lib_dir", sysenvvar="PLATFORMIO_LIB_DIR"),
ConfigPlatformioOption(
name="include_dir", sysenvvar="PLATFORMIO_INCLUDE_DIR"),
ConfigPlatformioOption(name="src_dir", sysenvvar="PLATFORMIO_SRC_DIR"),
ConfigPlatformioOption(
name="test_dir", sysenvvar="PLATFORMIO_TEST_DIR"),
ConfigPlatformioOption(
name="boards_dir", sysenvvar="PLATFORMIO_BOARDS_DIR"),
ConfigPlatformioOption(
name="data_dir", sysenvvar="PLATFORMIO_DATA_DIR"),
#
# [env]
#
# Generic
ConfigEnvOption(name="platform", buildenvvar="PIOPLATFORM"),
ConfigEnvOption(
name="framework", multiple=True, buildenvvar="PIOFRAMEWORK"),
ConfigEnvOption(name="targets", multiple=True),
# Board
ConfigEnvOption(name="board", buildenvvar="BOARD"),
ConfigEnvOption(
name="board_build.mcu",
oldnames=["board_mcu"],
buildenvvar="BOARD_MCU"),
ConfigEnvOption(
name="board_build.f_cpu",
oldnames=["board_f_cpu"],
buildenvvar="BOARD_F_CPU"),
ConfigEnvOption(
name="board_build.f_flash",
oldnames=["board_f_flash"],
buildenvvar="BOARD_F_FLASH"),
ConfigEnvOption(
name="board_build.flash_mode",
oldnames=["board_flash_mode"],
buildenvvar="BOARD_FLASH_MODE"),
# Build
ConfigEnvOption(
name="build_flags",
multiple=True,
sysenvvar="PLATFORMIO_BUILD_FLAGS",
buildenvvar="BUILD_FLAGS"),
ConfigEnvOption(
name="src_build_flags",
multiple=True,
sysenvvar="PLATFORMIO_SRC_BUILD_FLAGS",
buildenvvar="SRC_BUILD_FLAGS"),
ConfigEnvOption(
name="build_unflags",
multiple=True,
sysenvvar="PLATFORMIO_BUILD_UNFLAGS",
buildenvvar="BUILD_UNFLAGS"),
ConfigEnvOption(
name="src_filter",
multiple=True,
sysenvvar="PLATFORMIO_SRC_FILTER",
buildenvvar="SRC_FILTER"),
# Upload
ConfigEnvOption(
name="upload_port",
sysenvvar="PLATFORMIO_UPLOAD_PORT",
buildenvvar="UPLOAD_PORT"),
ConfigEnvOption(name="upload_protocol", buildenvvar="UPLOAD_PROTOCOL"),
ConfigEnvOption(name="upload_speed", buildenvvar="UPLOAD_SPEED"),
ConfigEnvOption(
name="upload_flags",
multiple=True,
sysenvvar="PLATFORMIO_UPLOAD_FLAGS",
buildenvvar="UPLOAD_FLAGS"),
ConfigEnvOption(
name="upload_resetmethod", buildenvvar="UPLOAD_RESETMETHOD"),
# Monitor
ConfigEnvOption(name="monitor_port"),
ConfigEnvOption(name="monitor_speed", oldnames=["monitor_baud"]),
ConfigEnvOption(name="monitor_rts"),
ConfigEnvOption(name="monitor_dtr"),
ConfigEnvOption(name="monitor_flags", multiple=True),
# Library
ConfigEnvOption(
name="lib_deps",
oldnames=["lib_use", "lib_force", "lib_install"],
multiple=True),
ConfigEnvOption(name="lib_ignore", multiple=True),
ConfigEnvOption(
name="lib_extra_dirs",
multiple=True,
sysenvvar="PLATFORMIO_LIB_EXTRA_DIRS"),
ConfigEnvOption(name="lib_ldf_mode"),
ConfigEnvOption(name="lib_compat_mode"),
ConfigEnvOption(name="lib_archive", type=bool),
# Test
ConfigEnvOption(name="test_filter", multiple=True),
ConfigEnvOption(name="test_ignore", multiple=True),
ConfigEnvOption(name="test_port"),
ConfigEnvOption(name="test_speed"),
ConfigEnvOption(name="test_transport"),
ConfigEnvOption(name="test_build_project_src"),
# Debug
ConfigEnvOption(name="debug_tool"),
ConfigEnvOption(name="debug_init_break"),
ConfigEnvOption(name="debug_init_cmds", multiple=True),
ConfigEnvOption(name="debug_extra_cmds", multiple=True),
ConfigEnvOption(name="debug_load_cmd"),
ConfigEnvOption(name="debug_load_mode"),
ConfigEnvOption(name="debug_server"),
ConfigEnvOption(name="debug_port"),
ConfigEnvOption(name="debug_svd_path"),
# Other
ConfigEnvOption(
name="extra_scripts",
oldnames=["extra_script"],
multiple=True,
sysenvvar="PLATFORMIO_EXTRA_SCRIPTS")
]
])

View File

@ -310,12 +310,15 @@ def measure_ci():
def on_run_environment(options, targets):
opts = [
"%s=%s" % (opt, value.replace("\n", ", ") if "\n" in value else value)
for opt, value in sorted(options.items())
]
non_sensative_values = ["board", "platform", "framework"]
safe_options = []
for key, value in sorted(options.items()):
if key in non_sensative_values:
safe_options.append("%s=%s" % (key, value))
else:
safe_options.append(key)
targets = [t.title() for t in targets or ["run"]]
on_event("Env", " ".join(targets), "&".join(opts))
on_event("Env", " ".join(targets), "&".join(safe_options))
def on_event(category, action, label=None, value=None, screen_name=None):

View File

@ -20,7 +20,7 @@ from platformio.commands.lib import cli as cmd_lib
def test_ci_empty(clirunner):
result = clirunner.invoke(cmd_ci)
assert result.exit_code == 2
assert result.exit_code != 0
assert "Invalid value: Missing argument 'src'" in result.output

View File

@ -16,7 +16,7 @@ import json
from os import getcwd, makedirs
from os.path import getsize, isdir, isfile, join
from platformio import exception, util
from platformio import exception
from platformio.commands.boards import cli as cmd_boards
from platformio.commands.init import cli as cmd_init
from platformio.project.config import ProjectConfig
@ -109,13 +109,13 @@ def test_init_special_board(clirunner, validate_cliresult):
config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate()
expected_result = [("platform", str(boards[0]['platform'])),
("framework",
str(boards[0]['frameworks'][0])), ("board", "uno")]
expected_result = dict(platform=str(boards[0]['platform']),
board="uno",
framework=[str(boards[0]['frameworks'][0])])
assert config.has_section("env:uno")
assert not set(expected_result).symmetric_difference(
set(config.items("env:uno")))
assert sorted(config.items(env="uno", as_dict=True).items()) == sorted(
expected_result.items())
def test_init_enable_auto_uploading(clirunner, validate_cliresult):
@ -126,11 +126,13 @@ def test_init_enable_auto_uploading(clirunner, validate_cliresult):
validate_pioproject(getcwd())
config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate()
expected_result = [("platform", "atmelavr"), ("framework", "arduino"),
("board", "uno"), ("targets", "upload")]
expected_result = dict(targets=["upload"],
platform="atmelavr",
board="uno",
framework=["arduino"])
assert config.has_section("env:uno")
assert not set(expected_result).symmetric_difference(
set(config.items("env:uno")))
assert sorted(config.items(env="uno", as_dict=True).items()) == sorted(
expected_result.items())
def test_init_custom_framework(clirunner, validate_cliresult):
@ -141,11 +143,13 @@ def test_init_custom_framework(clirunner, validate_cliresult):
validate_pioproject(getcwd())
config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate()
expected_result = [("platform", "teensy"), ("framework", "mbed"),
("board", "teensy31")]
expected_result = dict(platform="teensy",
board="teensy31",
framework=["mbed"])
assert config.has_section("env:teensy31")
assert not set(expected_result).symmetric_difference(
set(config.items("env:teensy31")))
assert sorted(config.items(env="teensy31",
as_dict=True).items()) == sorted(
expected_result.items())
def test_init_incorrect_board(clirunner):

View File

@ -14,7 +14,9 @@
import os
from platformio.project.config import ProjectConfig
import pytest
from platformio.project.config import ConfigParser, ProjectConfig
BASE_CONFIG = """
[platformio]
@ -26,7 +28,9 @@ extra_configs =
# global options per [env:*]
[env]
monitor_speed = 115200
lib_deps = Lib1, Lib2
lib_deps =
Lib1
Lib2
lib_ignore = ${custom.lib_ignore}
[custom]
@ -46,6 +50,7 @@ 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
upload_port = /dev/extra_2/port
"""
EXTRA_DEBUG_CONFIG = """
@ -58,7 +63,7 @@ build_flags = -Og
"""
def test_parser(tmpdir):
def test_real_config(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)
@ -68,6 +73,16 @@ def test_parser(tmpdir):
config = ProjectConfig(tmpdir.join("platformio.ini").strpath)
assert config
# unknown section
with pytest.raises(ConfigParser.NoSectionError):
config.getraw("unknown_section", "unknown_option")
# unknown option
with pytest.raises(ConfigParser.NoOptionError):
config.getraw("custom", "unknown_option")
# unknown option even if exists in [env]
with pytest.raises(ConfigParser.NoOptionError):
config.getraw("platformio", "monitor_speed")
# sections
assert config.sections() == [
"platformio", "env", "custom", "env:base", "env:extra_1", "env:extra_2"
@ -87,30 +102,76 @@ def test_parser(tmpdir):
assert not config.has_option("custom", "monitor_speed")
# sysenv
assert config.get("custom", "extra_flags") == ""
assert config.get("custom", "extra_flags") is None
assert config.get("env:base", "build_flags") == ["-D DEBUG=1"]
assert config.get("env:base", "upload_port") is None
assert config.get("env:extra_2", "upload_port") == "/dev/extra_2/port"
os.environ["PLATFORMIO_BUILD_FLAGS"] = "-DSYSENVDEPS1 -DSYSENVDEPS2"
os.environ["PLATFORMIO_UPLOAD_PORT"] = "/dev/sysenv/port"
os.environ["__PIO_TEST_CNF_EXTRA_FLAGS"] = "-L /usr/local/lib"
assert config.get("custom", "extra_flags") == "-L /usr/local/lib"
assert config.get("env:base", "build_flags") == [
"-D DEBUG=1 -L /usr/local/lib", "-DSYSENVDEPS1 -DSYSENVDEPS2"
]
assert config.get("env:base", "upload_port") == "/dev/sysenv/port"
assert config.get("env:extra_2", "upload_port") == "/dev/extra_2/port"
# getraw
assert config.getraw("env:extra_1", "lib_deps") == "\nLib1\nLib2"
assert config.getraw("env:extra_1", "build_flags") == "-lc -lm -D DEBUG=1"
# 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_1", "build_flags") == [
"-lc -lm -D DEBUG=1", "-DSYSENVDEPS1 -DSYSENVDEPS2"
]
assert config.get("env:extra_2", "build_flags") == [
"-Og", "-DSYSENVDEPS1 -DSYSENVDEPS2"]
assert config.get("env:extra_2", "monitor_speed") == "115200"
assert config.get("env:base",
"build_flags") == ("-D DEBUG=1 -L /usr/local/lib")
assert config.get("env:base", "build_flags") == ([
"-D DEBUG=1 -L /usr/local/lib", "-DSYSENVDEPS1 -DSYSENVDEPS2"
])
# 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")]
assert config.items("custom") == [
("debug_flags", "-D DEBUG=1"),
("lib_flags", "-lc -lm"),
("extra_flags", "-L /usr/local/lib"),
("lib_ignore", "LibIgnoreCustom")
] # yapf: disable
assert config.items(env="extra_1") == [
("build_flags", ["-lc -lm -D DEBUG=1", "-DSYSENVDEPS1 -DSYSENVDEPS2"]),
("monitor_speed", "115200"),
("lib_deps", ["Lib1", "Lib2"]),
("lib_ignore", ["LibIgnoreCustom"]),
("upload_port", "/dev/sysenv/port")
] # yapf: disable
assert config.items(env="extra_2") == [
("build_flags", ["-Og", "-DSYSENVDEPS1 -DSYSENVDEPS2"]),
("lib_ignore", ["LibIgnoreCustom", "Lib3"]),
("upload_port", "/dev/extra_2/port"),
("monitor_speed", "115200"),
("lib_deps", ["Lib1", "Lib2"])
] # yapf: disable
# cleanup system environment variables
del os.environ["PLATFORMIO_BUILD_FLAGS"]
del os.environ["PLATFORMIO_UPLOAD_PORT"]
del os.environ["__PIO_TEST_CNF_EXTRA_FLAGS"]
def test_empty_config():
config = ProjectConfig("/non/existing/platformio.ini")
# unknown section
with pytest.raises(ConfigParser.NoSectionError):
config.getraw("unknown_section", "unknown_option")
assert config.sections() == []
assert config.get("section", "option") is None
assert config.get("section", "option", 13) == 13
# sysenv
os.environ["PLATFORMIO_HOME_DIR"] = "/custom/core/dir"
assert config.get("platformio", "core_dir") == "/custom/core/dir"
del os.environ["PLATFORMIO_HOME_DIR"]