Introduce Black to automate code formatting

This commit is contained in:
Ivan Kravets
2019-09-23 23:13:48 +03:00
parent 5e144a2c98
commit 7c41c7c2f3
90 changed files with 4064 additions and 3367 deletions

View File

@ -1,3 +1,3 @@
[settings] [settings]
line_length=79 line_length=88
known_third_party=bottle,click,pytest,requests,SCons,semantic_version,serial,twisted,autobahn,jsonrpc,tabulate known_third_party=bottle,click,pytest,requests,SCons,semantic_version,serial,twisted,autobahn,jsonrpc,tabulate

View File

@ -1,5 +1,7 @@
[MESSAGES CONTROL] [MESSAGES CONTROL]
disable= disable=
bad-continuation,
bad-whitespace,
missing-docstring, missing-docstring,
ungrouped-imports, ungrouped-imports,
invalid-name, invalid-name,

View File

@ -5,13 +5,14 @@ isort:
isort -rc ./platformio isort -rc ./platformio
isort -rc ./tests isort -rc ./tests
yapf: black:
yapf --recursive --in-place platformio/ black --target-version py27 ./platformio
black --target-version py27 ./tests
test: test:
py.test --verbose --capture=no --exitfirst -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 before-commit: isort black lint test
clean-docs: clean-docs:
rm -rf docs/_build rm -rf docs/_build

View File

@ -21,7 +21,8 @@ __description__ = (
"Cross-platform IDE and unified debugger. " "Cross-platform IDE and unified debugger. "
"Remote unit testing and firmware updates. " "Remote unit testing and firmware updates. "
"Arduino, ARM mbed, Espressif (ESP8266/ESP32), STM32, PIC32, nRF51/nRF52, " "Arduino, ARM mbed, Espressif (ESP8266/ESP32), STM32, PIC32, nRF51/nRF52, "
"FPGA, CMSIS, SPL, AVR, Samsung ARTIK, libOpenCM3") "FPGA, CMSIS, SPL, AVR, Samsung ARTIK, libOpenCM3"
)
__url__ = "https://platformio.org" __url__ = "https://platformio.org"
__author__ = "PlatformIO" __author__ = "PlatformIO"

View File

@ -23,27 +23,31 @@ from platformio.commands import PlatformioCLI
from platformio.compat import CYGWIN from platformio.compat import CYGWIN
@click.command(cls=PlatformioCLI, @click.command(
context_settings=dict(help_option_names=["-h", "--help"])) cls=PlatformioCLI, context_settings=dict(help_option_names=["-h", "--help"])
)
@click.version_option(__version__, prog_name="PlatformIO") @click.version_option(__version__, prog_name="PlatformIO")
@click.option("--force", "-f", is_flag=True, help="DEPRECATE") @click.option("--force", "-f", is_flag=True, help="DEPRECATE")
@click.option("--caller", "-c", help="Caller ID (service)") @click.option("--caller", "-c", help="Caller ID (service)")
@click.option("--no-ansi", @click.option("--no-ansi", is_flag=True, help="Do not print ANSI control characters")
is_flag=True,
help="Do not print ANSI control characters")
@click.pass_context @click.pass_context
def cli(ctx, force, caller, no_ansi): def cli(ctx, force, caller, no_ansi):
try: try:
if (no_ansi or str( if (
os.getenv( no_ansi
"PLATFORMIO_NO_ANSI", or str(
os.getenv("PLATFORMIO_DISABLE_COLOR"))).lower() == "true"): os.getenv("PLATFORMIO_NO_ANSI", os.getenv("PLATFORMIO_DISABLE_COLOR"))
).lower()
== "true"
):
# pylint: disable=protected-access # pylint: disable=protected-access
click._compat.isatty = lambda stream: False click._compat.isatty = lambda stream: False
elif str( elif (
os.getenv( str(
"PLATFORMIO_FORCE_ANSI", os.getenv("PLATFORMIO_FORCE_ANSI", os.getenv("PLATFORMIO_FORCE_COLOR"))
os.getenv("PLATFORMIO_FORCE_COLOR"))).lower() == "true": ).lower()
== "true"
):
# pylint: disable=protected-access # pylint: disable=protected-access
click._compat.isatty = lambda stream: True click._compat.isatty = lambda stream: True
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
@ -67,6 +71,7 @@ def configure():
# /en/latest/security.html#insecureplatformwarning # /en/latest/security.html#insecureplatformwarning
try: try:
import urllib3 import urllib3
urllib3.disable_warnings() urllib3.disable_warnings()
except (AttributeError, ImportError): except (AttributeError, ImportError):
pass pass
@ -79,7 +84,8 @@ def configure():
click_echo_origin[origin](*args, **kwargs) click_echo_origin[origin](*args, **kwargs)
except IOError: except IOError:
(sys.stderr.write if kwargs.get("err") else sys.stdout.write)( (sys.stderr.write if kwargs.get("err") else sys.stdout.write)(
"%s\n" % (args[0] if args else "")) "%s\n" % (args[0] if args else "")
)
click.echo = lambda *args, **kwargs: _safe_echo(0, *args, **kwargs) click.echo = lambda *args, **kwargs: _safe_echo(0, *args, **kwargs)
click.secho = lambda *args, **kwargs: _safe_echo(1, *args, **kwargs) click.secho = lambda *args, **kwargs: _safe_echo(1, *args, **kwargs)

View File

@ -23,11 +23,9 @@ from time import time
import requests import requests
from platformio import exception, fs, lockfile from platformio import exception, fs, lockfile
from platformio.compat import (WINDOWS, dump_json_to_unicode, from platformio.compat import WINDOWS, dump_json_to_unicode, hashlib_encode_data
hashlib_encode_data)
from platformio.proc import is_ci from platformio.proc import is_ci
from platformio.project.helpers import (get_project_cache_dir, from platformio.project.helpers import get_project_cache_dir, get_project_core_dir
get_project_core_dir)
def get_default_projects_dir(): def get_default_projects_dir():
@ -35,6 +33,7 @@ def get_default_projects_dir():
try: try:
assert WINDOWS assert WINDOWS
import ctypes.wintypes import ctypes.wintypes
buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) buf = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, buf) ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, buf)
docs_dir = buf.value docs_dir = buf.value
@ -51,45 +50,41 @@ def projects_dir_validate(projects_dir):
DEFAULT_SETTINGS = { DEFAULT_SETTINGS = {
"auto_update_libraries": { "auto_update_libraries": {
"description": "Automatically update libraries (Yes/No)", "description": "Automatically update libraries (Yes/No)",
"value": False "value": False,
}, },
"auto_update_platforms": { "auto_update_platforms": {
"description": "Automatically update platforms (Yes/No)", "description": "Automatically update platforms (Yes/No)",
"value": False "value": False,
}, },
"check_libraries_interval": { "check_libraries_interval": {
"description": "Check for the library updates interval (days)", "description": "Check for the library updates interval (days)",
"value": 7 "value": 7,
}, },
"check_platformio_interval": { "check_platformio_interval": {
"description": "Check for the new PlatformIO interval (days)", "description": "Check for the new PlatformIO interval (days)",
"value": 3 "value": 3,
}, },
"check_platforms_interval": { "check_platforms_interval": {
"description": "Check for the platform updates interval (days)", "description": "Check for the platform updates interval (days)",
"value": 7 "value": 7,
}, },
"enable_cache": { "enable_cache": {
"description": "Enable caching for API requests and Library Manager", "description": "Enable caching for API requests and Library Manager",
"value": True "value": True,
},
"strict_ssl": {
"description": "Strict SSL for PlatformIO Services",
"value": False
}, },
"strict_ssl": {"description": "Strict SSL for PlatformIO Services", "value": False},
"enable_telemetry": { "enable_telemetry": {
"description": "description": ("Telemetry service <http://bit.ly/pio-telemetry> (Yes/No)"),
("Telemetry service <http://bit.ly/pio-telemetry> (Yes/No)"), "value": True,
"value": True
}, },
"force_verbose": { "force_verbose": {
"description": "Force verbose output when processing environments", "description": "Force verbose output when processing environments",
"value": False "value": False,
}, },
"projects_dir": { "projects_dir": {
"description": "Default location for PlatformIO projects (PIO Home)", "description": "Default location for PlatformIO projects (PIO Home)",
"value": get_default_projects_dir(), "value": get_default_projects_dir(),
"validator": projects_dir_validate "validator": projects_dir_validate,
}, },
} }
@ -97,7 +92,6 @@ SESSION_VARS = {"command_ctx": None, "force_option": False, "caller_id": None}
class State(object): class State(object):
def __init__(self, path=None, lock=False): def __init__(self, path=None, lock=False):
self.path = path self.path = path
self.lock = lock self.lock = lock
@ -113,8 +107,12 @@ class State(object):
if isfile(self.path): if isfile(self.path):
self._storage = fs.load_json(self.path) self._storage = fs.load_json(self.path)
assert isinstance(self._storage, dict) assert isinstance(self._storage, dict)
except (AssertionError, ValueError, UnicodeDecodeError, except (
exception.InvalidJSONFile): AssertionError,
ValueError,
UnicodeDecodeError,
exception.InvalidJSONFile,
):
self._storage = {} self._storage = {}
return self return self
@ -174,7 +172,6 @@ class State(object):
class ContentCache(object): class ContentCache(object):
def __init__(self, cache_dir=None): def __init__(self, cache_dir=None):
self.cache_dir = None self.cache_dir = None
self._db_path = None self._db_path = None
@ -277,8 +274,11 @@ class ContentCache(object):
continue continue
expire, path = line.split("=") expire, path = line.split("=")
try: try:
if time() < int(expire) and isfile(path) and \ if (
path not in paths_for_delete: time() < int(expire)
and isfile(path)
and path not in paths_for_delete
):
newlines.append(line) newlines.append(line)
continue continue
except ValueError: except ValueError:
@ -317,11 +317,11 @@ def sanitize_setting(name, value):
defdata = DEFAULT_SETTINGS[name] defdata = DEFAULT_SETTINGS[name]
try: try:
if "validator" in defdata: if "validator" in defdata:
value = defdata['validator'](value) value = defdata["validator"](value)
elif isinstance(defdata['value'], bool): elif isinstance(defdata["value"], bool):
if not isinstance(value, bool): if not isinstance(value, bool):
value = str(value).lower() in ("true", "yes", "y", "1") value = str(value).lower() in ("true", "yes", "y", "1")
elif isinstance(defdata['value'], int): elif isinstance(defdata["value"], int):
value = int(value) value = int(value)
except Exception: except Exception:
raise exception.InvalidSettingValue(value, name) raise exception.InvalidSettingValue(value, name)
@ -351,24 +351,24 @@ def get_setting(name):
return sanitize_setting(name, getenv(_env_name)) return sanitize_setting(name, getenv(_env_name))
with State() as state: with State() as state:
if "settings" in state and name in state['settings']: if "settings" in state and name in state["settings"]:
return state['settings'][name] return state["settings"][name]
return DEFAULT_SETTINGS[name]['value'] return DEFAULT_SETTINGS[name]["value"]
def set_setting(name, value): def set_setting(name, value):
with State(lock=True) as state: with State(lock=True) as state:
if "settings" not in state: if "settings" not in state:
state['settings'] = {} state["settings"] = {}
state['settings'][name] = sanitize_setting(name, value) state["settings"][name] = sanitize_setting(name, value)
state.modified = True state.modified = True
def reset_settings(): def reset_settings():
with State(lock=True) as state: with State(lock=True) as state:
if "settings" in state: if "settings" in state:
del state['settings'] del state["settings"]
def get_session_var(name, default=None): def get_session_var(name, default=None):
@ -381,11 +381,13 @@ def set_session_var(name, value):
def is_disabled_progressbar(): def is_disabled_progressbar():
return any([ return any(
get_session_var("force_option"), [
is_ci(), get_session_var("force_option"),
getenv("PLATFORMIO_DISABLE_PROGRESSBAR") == "true" is_ci(),
]) getenv("PLATFORMIO_DISABLE_PROGRESSBAR") == "true",
]
)
def get_cid(): def get_cid():
@ -397,9 +399,16 @@ def get_cid():
uid = getenv("C9_UID") uid = getenv("C9_UID")
elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")): elif getenv("CHE_API", getenv("CHE_API_ENDPOINT")):
try: try:
uid = requests.get("{api}/user?token={token}".format( uid = (
api=getenv("CHE_API", getenv("CHE_API_ENDPOINT")), requests.get(
token=getenv("USER_TOKEN"))).json().get("id") "{api}/user?token={token}".format(
api=getenv("CHE_API", getenv("CHE_API_ENDPOINT")),
token=getenv("USER_TOKEN"),
)
)
.json()
.get("id")
)
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass
if not uid: if not uid:

View File

@ -43,17 +43,27 @@ clivars.AddVariables(
("PROJECT_CONFIG",), ("PROJECT_CONFIG",),
("PIOENV",), ("PIOENV",),
("PIOTEST_RUNNING_NAME",), ("PIOTEST_RUNNING_NAME",),
("UPLOAD_PORT",) ("UPLOAD_PORT",),
) # yapf: disable ) # yapf: disable
DEFAULT_ENV_OPTIONS = dict( DEFAULT_ENV_OPTIONS = dict(
tools=[ tools=[
"ar", "gas", "gcc", "g++", "gnulink", "platformio", "pioplatform", "ar",
"pioproject", "piowinhooks", "piolib", "pioupload", "piomisc", "pioide" "gas",
"gcc",
"g++",
"gnulink",
"platformio",
"pioplatform",
"pioproject",
"piowinhooks",
"piolib",
"pioupload",
"piomisc",
"pioide",
], ],
toolpath=[join(fs.get_source_dir(), "builder", "tools")], toolpath=[join(fs.get_source_dir(), "builder", "tools")],
variables=clivars, variables=clivars,
# Propagating External Environment # Propagating External Environment
ENV=environ, ENV=environ,
UNIX_TIME=int(time()), UNIX_TIME=int(time()),
@ -75,16 +85,17 @@ DEFAULT_ENV_OPTIONS = dict(
LIBSOURCE_DIRS=[ LIBSOURCE_DIRS=[
project_helpers.get_project_lib_dir(), project_helpers.get_project_lib_dir(),
join("$PROJECTLIBDEPS_DIR", "$PIOENV"), join("$PROJECTLIBDEPS_DIR", "$PIOENV"),
project_helpers.get_project_global_lib_dir() project_helpers.get_project_global_lib_dir(),
], ],
PROGNAME="program", PROGNAME="program",
PROG_PATH=join("$BUILD_DIR", "$PROGNAME$PROGSUFFIX"), PROG_PATH=join("$BUILD_DIR", "$PROGNAME$PROGSUFFIX"),
PYTHONEXE=get_pythonexe_path()) PYTHONEXE=get_pythonexe_path(),
)
if not int(ARGUMENTS.get("PIOVERBOSE", 0)): if not int(ARGUMENTS.get("PIOVERBOSE", 0)):
DEFAULT_ENV_OPTIONS['ARCOMSTR'] = "Archiving $TARGET" DEFAULT_ENV_OPTIONS["ARCOMSTR"] = "Archiving $TARGET"
DEFAULT_ENV_OPTIONS['LINKCOMSTR'] = "Linking $TARGET" DEFAULT_ENV_OPTIONS["LINKCOMSTR"] = "Linking $TARGET"
DEFAULT_ENV_OPTIONS['RANLIBCOMSTR'] = "Indexing $TARGET" DEFAULT_ENV_OPTIONS["RANLIBCOMSTR"] = "Indexing $TARGET"
for k in ("ASCOMSTR", "ASPPCOMSTR", "CCCOMSTR", "CXXCOMSTR"): for k in ("ASCOMSTR", "ASPPCOMSTR", "CCCOMSTR", "CXXCOMSTR"):
DEFAULT_ENV_OPTIONS[k] = "Compiling $TARGET" DEFAULT_ENV_OPTIONS[k] = "Compiling $TARGET"
@ -94,8 +105,10 @@ env = DefaultEnvironment(**DEFAULT_ENV_OPTIONS)
env.Replace( env.Replace(
**{ **{
key: PlatformBase.decode_scons_arg(env[key]) key: PlatformBase.decode_scons_arg(env[key])
for key in list(clivars.keys()) if key in env for key in list(clivars.keys())
}) if key in env
}
)
if env.subst("$BUILDCACHE_DIR"): if env.subst("$BUILDCACHE_DIR"):
if not isdir(env.subst("$BUILDCACHE_DIR")): if not isdir(env.subst("$BUILDCACHE_DIR")):
@ -106,18 +119,17 @@ if int(ARGUMENTS.get("ISATTY", 0)):
# pylint: disable=protected-access # pylint: disable=protected-access
click._compat.isatty = lambda stream: True click._compat.isatty = lambda stream: True
if env.GetOption('clean'): if env.GetOption("clean"):
env.PioClean(env.subst("$BUILD_DIR")) env.PioClean(env.subst("$BUILD_DIR"))
env.Exit(0) env.Exit(0)
elif not int(ARGUMENTS.get("PIOVERBOSE", 0)): elif not int(ARGUMENTS.get("PIOVERBOSE", 0)):
print("Verbose mode can be enabled via `-v, --verbose` option") print ("Verbose mode can be enabled via `-v, --verbose` option")
env.LoadProjectOptions() env.LoadProjectOptions()
env.LoadPioPlatform() env.LoadPioPlatform()
env.SConscriptChdir(0) env.SConscriptChdir(0)
env.SConsignFile( env.SConsignFile(join("$BUILD_DIR", ".sconsign.dblite" if PY2 else ".sconsign3.dblite"))
join("$BUILD_DIR", ".sconsign.dblite" if PY2 else ".sconsign3.dblite"))
for item in env.GetExtraScripts("pre"): for item in env.GetExtraScripts("pre"):
env.SConscript(item, exports="env") env.SConscript(item, exports="env")
@ -144,10 +156,13 @@ if env.get("SIZETOOL") and "nobuild" not in COMMAND_LINE_TARGETS:
Default("checkprogsize") Default("checkprogsize")
# Print configured protocols # Print configured protocols
env.AddPreAction(["upload", "program"], env.AddPreAction(
env.VerboseAction( ["upload", "program"],
lambda source, target, env: env.PrintUploadInfo(), env.VerboseAction(
"Configuring upload protocol...")) lambda source, target, env: env.PrintUploadInfo(),
"Configuring upload protocol...",
),
)
AlwaysBuild(env.Alias("debug", DEFAULT_TARGETS)) AlwaysBuild(env.Alias("debug", DEFAULT_TARGETS))
AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS)) AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
@ -155,12 +170,15 @@ AlwaysBuild(env.Alias("__test", DEFAULT_TARGETS))
############################################################################## ##############################################################################
if "envdump" in COMMAND_LINE_TARGETS: if "envdump" in COMMAND_LINE_TARGETS:
print(env.Dump()) print (env.Dump())
env.Exit(0) env.Exit(0)
if "idedata" in COMMAND_LINE_TARGETS: if "idedata" in COMMAND_LINE_TARGETS:
Import("projenv") Import("projenv")
print("\n%s\n" % dump_json_to_unicode( print (
env.DumpIDEData(projenv) # pylint: disable=undefined-variable "\n%s\n"
)) % dump_json_to_unicode(
env.DumpIDEData(projenv) # pylint: disable=undefined-variable
)
)
env.Exit(0) env.Exit(0)

View File

@ -45,7 +45,7 @@ def _dump_includes(env, projenv):
join(toolchain_dir, "*", "include*"), join(toolchain_dir, "*", "include*"),
join(toolchain_dir, "*", "include", "c++", "*"), join(toolchain_dir, "*", "include", "c++", "*"),
join(toolchain_dir, "*", "include", "c++", "*", "*-*-*"), join(toolchain_dir, "*", "include", "c++", "*", "*-*-*"),
join(toolchain_dir, "lib", "gcc", "*", "*", "include*") join(toolchain_dir, "lib", "gcc", "*", "*", "include*"),
] ]
for g in toolchain_incglobs: for g in toolchain_incglobs:
includes.extend(glob(g)) includes.extend(glob(g))
@ -54,9 +54,7 @@ def _dump_includes(env, projenv):
if unity_dir: if unity_dir:
includes.append(unity_dir) includes.append(unity_dir)
includes.extend( includes.extend([env.subst("$PROJECTINCLUDE_DIR"), env.subst("$PROJECTSRC_DIR")])
[env.subst("$PROJECTINCLUDE_DIR"),
env.subst("$PROJECTSRC_DIR")])
# remove duplicates # remove duplicates
result = [] result = []
@ -71,15 +69,15 @@ def _get_gcc_defines(env):
items = [] items = []
try: try:
sysenv = environ.copy() sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH']) sysenv["PATH"] = str(env["ENV"]["PATH"])
result = exec_command("echo | %s -dM -E -" % env.subst("$CC"), result = exec_command(
env=sysenv, "echo | %s -dM -E -" % env.subst("$CC"), env=sysenv, shell=True
shell=True) )
except OSError: except OSError:
return items return items
if result['returncode'] != 0: if result["returncode"] != 0:
return items return items
for line in result['out'].split("\n"): for line in result["out"].split("\n"):
tokens = line.strip().split(" ", 2) tokens = line.strip().split(" ", 2)
if not tokens or tokens[0] != "#define": if not tokens or tokens[0] != "#define":
continue continue
@ -94,17 +92,22 @@ def _dump_defines(env):
defines = [] defines = []
# global symbols # global symbols
for item in processDefines(env.get("CPPDEFINES", [])): for item in processDefines(env.get("CPPDEFINES", [])):
defines.append(env.subst(item).replace('\\', '')) defines.append(env.subst(item).replace("\\", ""))
# special symbol for Atmel AVR MCU # special symbol for Atmel AVR MCU
if env['PIOPLATFORM'] == "atmelavr": if env["PIOPLATFORM"] == "atmelavr":
board_mcu = env.get("BOARD_MCU") board_mcu = env.get("BOARD_MCU")
if not board_mcu and "BOARD" in env: if not board_mcu and "BOARD" in env:
board_mcu = env.BoardConfig().get("build.mcu") board_mcu = env.BoardConfig().get("build.mcu")
if board_mcu: if board_mcu:
defines.append( defines.append(
str("__AVR_%s__" % board_mcu.upper().replace( str(
"ATMEGA", "ATmega").replace("ATTINY", "ATtiny"))) "__AVR_%s__"
% board_mcu.upper()
.replace("ATMEGA", "ATmega")
.replace("ATTINY", "ATtiny")
)
)
# built-in GCC marcos # built-in GCC marcos
# if env.GetCompilerType() == "gcc": # if env.GetCompilerType() == "gcc":
@ -140,33 +143,22 @@ def DumpIDEData(env, projenv):
LINTCXXCOM = "$CXXFLAGS $CCFLAGS $CPPFLAGS" LINTCXXCOM = "$CXXFLAGS $CCFLAGS $CPPFLAGS"
data = { data = {
"env_name": "env_name": env["PIOENV"],
env['PIOENV'],
"libsource_dirs": [env.subst(l) for l in env.GetLibSourceDirs()], "libsource_dirs": [env.subst(l) for l in env.GetLibSourceDirs()],
"defines": "defines": _dump_defines(env),
_dump_defines(env), "includes": _dump_includes(env, projenv),
"includes": "cc_flags": env.subst(LINTCCOM),
_dump_includes(env, projenv), "cxx_flags": env.subst(LINTCXXCOM),
"cc_flags": "cc_path": where_is_program(env.subst("$CC"), env.subst("${ENV['PATH']}")),
env.subst(LINTCCOM), "cxx_path": where_is_program(env.subst("$CXX"), env.subst("${ENV['PATH']}")),
"cxx_flags": "gdb_path": where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")),
env.subst(LINTCXXCOM), "prog_path": env.subst("$PROG_PATH"),
"cc_path": "flash_extra_images": [
where_is_program(env.subst("$CC"), env.subst("${ENV['PATH']}")), {"offset": item[0], "path": env.subst(item[1])}
"cxx_path": for item in env.get("FLASH_EXTRA_IMAGES", [])
where_is_program(env.subst("$CXX"), env.subst("${ENV['PATH']}")), ],
"gdb_path": "svd_path": _get_svd_path(env),
where_is_program(env.subst("$GDB"), env.subst("${ENV['PATH']}")), "compiler_type": env.GetCompilerType(),
"prog_path":
env.subst("$PROG_PATH"),
"flash_extra_images": [{
"offset": item[0],
"path": env.subst(item[1])
} for item in env.get("FLASH_EXTRA_IMAGES", [])],
"svd_path":
_get_svd_path(env),
"compiler_type":
env.GetCompilerType()
} }
env_ = env.Clone() env_ = env.Clone()
@ -180,10 +172,7 @@ def DumpIDEData(env, projenv):
_new_defines.append(item) _new_defines.append(item)
env_.Replace(CPPDEFINES=_new_defines) env_.Replace(CPPDEFINES=_new_defines)
data.update({ data.update({"cc_flags": env_.subst(LINTCCOM), "cxx_flags": env_.subst(LINTCXXCOM)})
"cc_flags": env_.subst(LINTCCOM),
"cxx_flags": env_.subst(LINTCXXCOM)
})
return data return data

View File

@ -22,8 +22,16 @@ import hashlib
import os import os
import re import re
import sys import sys
from os.path import (basename, commonprefix, expanduser, isdir, isfile, join, from os.path import (
realpath, sep) basename,
commonprefix,
expanduser,
isdir,
isfile,
join,
realpath,
sep,
)
import click import click
import SCons.Scanner # pylint: disable=import-error import SCons.Scanner # pylint: disable=import-error
@ -33,13 +41,16 @@ from SCons.Script import DefaultEnvironment # pylint: disable=import-error
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.builder.tools import platformio as piotool from platformio.builder.tools import platformio as piotool
from platformio.compat import (WINDOWS, get_file_contents, hashlib_encode_data, from platformio.compat import (
string_types) WINDOWS,
get_file_contents,
hashlib_encode_data,
string_types,
)
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
class LibBuilderFactory(object): class LibBuilderFactory(object):
@staticmethod @staticmethod
def new(env, path, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))): def new(env, path, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))):
clsname = "UnknownLibBuilder" clsname = "UnknownLibBuilder"
@ -47,31 +58,30 @@ class LibBuilderFactory(object):
clsname = "PlatformIOLibBuilder" clsname = "PlatformIOLibBuilder"
else: else:
used_frameworks = LibBuilderFactory.get_used_frameworks(env, path) used_frameworks = LibBuilderFactory.get_used_frameworks(env, path)
common_frameworks = (set(env.get("PIOFRAMEWORK", [])) common_frameworks = set(env.get("PIOFRAMEWORK", [])) & set(used_frameworks)
& set(used_frameworks))
if common_frameworks: if common_frameworks:
clsname = "%sLibBuilder" % list(common_frameworks)[0].title() clsname = "%sLibBuilder" % list(common_frameworks)[0].title()
elif used_frameworks: elif used_frameworks:
clsname = "%sLibBuilder" % used_frameworks[0].title() clsname = "%sLibBuilder" % used_frameworks[0].title()
obj = getattr(sys.modules[__name__], clsname)(env, obj = getattr(sys.modules[__name__], clsname)(env, path, verbose=verbose)
path,
verbose=verbose)
assert isinstance(obj, LibBuilderBase) assert isinstance(obj, LibBuilderBase)
return obj return obj
@staticmethod @staticmethod
def get_used_frameworks(env, path): def get_used_frameworks(env, path):
if any( if any(
isfile(join(path, fname)) isfile(join(path, fname))
for fname in ("library.properties", "keywords.txt")): for fname in ("library.properties", "keywords.txt")
):
return ["arduino"] return ["arduino"]
if isfile(join(path, "module.json")): if isfile(join(path, "module.json")):
return ["mbed"] return ["mbed"]
include_re = re.compile(r'^#include\s+(<|")(Arduino|mbed)\.h(<|")', include_re = re.compile(
flags=re.MULTILINE) r'^#include\s+(<|")(Arduino|mbed)\.h(<|")', flags=re.MULTILINE
)
# check source files # check source files
for root, _, files in os.walk(path, followlinks=True): for root, _, files in os.walk(path, followlinks=True):
@ -79,7 +89,8 @@ class LibBuilderFactory(object):
return ["mbed"] return ["mbed"]
for fname in files: for fname in files:
if not fs.path_endswith_ext( if not fs.path_endswith_ext(
fname, piotool.SRC_BUILD_EXT + piotool.SRC_HEADER_EXT): fname, piotool.SRC_BUILD_EXT + piotool.SRC_HEADER_EXT
):
continue continue
content = get_file_contents(join(root, fname)) content = get_file_contents(join(root, fname))
if not content: if not content:
@ -124,7 +135,7 @@ class LibBuilderBase(object):
self._processed_files = list() self._processed_files = list()
# reset source filter, could be overridden with extra script # reset source filter, could be overridden with extra script
self.env['SRC_FILTER'] = "" self.env["SRC_FILTER"] = ""
# process extra options and append to build environment # process extra options and append to build environment
self.process_extra_options() self.process_extra_options()
@ -153,7 +164,8 @@ class LibBuilderBase(object):
@property @property
def dependencies(self): def dependencies(self):
return LibraryManager.normalize_dependencies( return LibraryManager.normalize_dependencies(
self._manifest.get("dependencies", [])) self._manifest.get("dependencies", [])
)
@property @property
def src_filter(self): def src_filter(self):
@ -161,7 +173,7 @@ class LibBuilderBase(object):
"-<example%s>" % os.sep, "-<example%s>" % os.sep,
"-<examples%s>" % os.sep, "-<examples%s>" % os.sep,
"-<test%s>" % os.sep, "-<test%s>" % os.sep,
"-<tests%s>" % os.sep "-<tests%s>" % os.sep,
] ]
@property @property
@ -172,8 +184,7 @@ class LibBuilderBase(object):
@property @property
def src_dir(self): def src_dir(self):
return (join(self.path, "src") return join(self.path, "src") if isdir(join(self.path, "src")) else self.path
if isdir(join(self.path, "src")) else self.path)
def get_include_dirs(self): def get_include_dirs(self):
items = [] items = []
@ -234,8 +245,7 @@ class LibBuilderBase(object):
@property @property
def lib_compat_mode(self): def lib_compat_mode(self):
return self.env.GetProjectOption("lib_compat_mode", return self.env.GetProjectOption("lib_compat_mode", self.COMPAT_MODE_DEFAULT)
self.COMPAT_MODE_DEFAULT)
@staticmethod @staticmethod
def validate_compat_mode(mode): def validate_compat_mode(mode):
@ -263,11 +273,10 @@ class LibBuilderBase(object):
self.env.ProcessFlags(self.build_flags) self.env.ProcessFlags(self.build_flags)
if self.extra_script: if self.extra_script:
self.env.SConscriptChdir(1) self.env.SConscriptChdir(1)
self.env.SConscript(realpath(self.extra_script), self.env.SConscript(
exports={ realpath(self.extra_script),
"env": self.env, exports={"env": self.env, "pio_lib_builder": self},
"pio_lib_builder": self )
})
self.env.ProcessUnFlags(self.build_unflags) self.env.ProcessUnFlags(self.build_unflags)
def process_dependencies(self): def process_dependencies(self):
@ -276,7 +285,7 @@ class LibBuilderBase(object):
for item in self.dependencies: for item in self.dependencies:
found = False found = False
for lb in self.env.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if item['name'] != lb.name: if item["name"] != lb.name:
continue continue
found = True found = True
if lb not in self.depbuilders: if lb not in self.depbuilders:
@ -284,37 +293,43 @@ class LibBuilderBase(object):
break break
if not found and self.verbose: if not found and self.verbose:
sys.stderr.write("Warning: Ignored `%s` dependency for `%s` " sys.stderr.write(
"library\n" % (item['name'], self.name)) "Warning: Ignored `%s` dependency for `%s` "
"library\n" % (item["name"], self.name)
)
def get_search_files(self): def get_search_files(self):
items = [ items = [
join(self.src_dir, item) for item in self.env.MatchSourceFiles( join(self.src_dir, item)
self.src_dir, self.src_filter) for item in self.env.MatchSourceFiles(self.src_dir, self.src_filter)
] ]
include_dir = self.include_dir include_dir = self.include_dir
if include_dir: if include_dir:
items.extend([ items.extend(
join(include_dir, item) [
for item in self.env.MatchSourceFiles(include_dir) join(include_dir, item)
]) for item in self.env.MatchSourceFiles(include_dir)
]
)
return items return items
def _get_found_includes( # pylint: disable=too-many-branches def _get_found_includes( # pylint: disable=too-many-branches
self, search_files=None): self, search_files=None
):
# all include directories # all include directories
if not LibBuilderBase._INCLUDE_DIRS_CACHE: if not LibBuilderBase._INCLUDE_DIRS_CACHE:
LibBuilderBase._INCLUDE_DIRS_CACHE = [] LibBuilderBase._INCLUDE_DIRS_CACHE = []
for lb in self.env.GetLibBuilders(): for lb in self.env.GetLibBuilders():
LibBuilderBase._INCLUDE_DIRS_CACHE.extend( LibBuilderBase._INCLUDE_DIRS_CACHE.extend(
[self.env.Dir(d) for d in lb.get_include_dirs()]) [self.env.Dir(d) for d in lb.get_include_dirs()]
)
# append self include directories # append self include directories
include_dirs = [self.env.Dir(d) for d in self.get_include_dirs()] include_dirs = [self.env.Dir(d) for d in self.get_include_dirs()]
include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE) include_dirs.extend(LibBuilderBase._INCLUDE_DIRS_CACHE)
result = [] result = []
for path in (search_files or []): for path in search_files or []:
if path in self._processed_files: if path in self._processed_files:
continue continue
self._processed_files.append(path) self._processed_files.append(path)
@ -325,19 +340,25 @@ class LibBuilderBase(object):
self.env.File(path), self.env.File(path),
self.env, self.env,
tuple(include_dirs), tuple(include_dirs),
depth=self.CCONDITIONAL_SCANNER_DEPTH) depth=self.CCONDITIONAL_SCANNER_DEPTH,
)
# mark candidates already processed via Conditional Scanner # mark candidates already processed via Conditional Scanner
self._processed_files.extend([ self._processed_files.extend(
c.get_abspath() for c in candidates [
if c.get_abspath() not in self._processed_files c.get_abspath()
]) for c in candidates
if c.get_abspath() not in self._processed_files
]
)
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
if self.verbose and "+" in self.lib_ldf_mode: if self.verbose and "+" in self.lib_ldf_mode:
sys.stderr.write( sys.stderr.write(
"Warning! Classic Pre Processor is used for `%s`, " "Warning! Classic Pre Processor is used for `%s`, "
"advanced has failed with `%s`\n" % (path, e)) "advanced has failed with `%s`\n" % (path, e)
)
candidates = LibBuilderBase.CLASSIC_SCANNER( candidates = LibBuilderBase.CLASSIC_SCANNER(
self.env.File(path), self.env, tuple(include_dirs)) self.env.File(path), self.env, tuple(include_dirs)
)
# print(path, map(lambda n: n.get_abspath(), candidates)) # print(path, map(lambda n: n.get_abspath(), candidates))
for item in candidates: for item in candidates:
@ -348,7 +369,7 @@ class LibBuilderBase(object):
_h_path = item.get_abspath() _h_path = item.get_abspath()
if not fs.path_endswith_ext(_h_path, piotool.SRC_HEADER_EXT): if not fs.path_endswith_ext(_h_path, piotool.SRC_HEADER_EXT):
continue continue
_f_part = _h_path[:_h_path.rindex(".")] _f_part = _h_path[: _h_path.rindex(".")]
for ext in piotool.SRC_C_EXT: for ext in piotool.SRC_C_EXT:
if not isfile("%s.%s" % (_f_part, ext)): if not isfile("%s.%s" % (_f_part, ext)):
continue continue
@ -359,7 +380,6 @@ class LibBuilderBase(object):
return result return result
def depend_recursive(self, lb, search_files=None): def depend_recursive(self, lb, search_files=None):
def _already_depends(_lb): def _already_depends(_lb):
if self in _lb.depbuilders: if self in _lb.depbuilders:
return True return True
@ -372,9 +392,10 @@ class LibBuilderBase(object):
if self != lb: if self != lb:
if _already_depends(lb): if _already_depends(lb):
if self.verbose: if self.verbose:
sys.stderr.write("Warning! Circular dependencies detected " sys.stderr.write(
"between `%s` and `%s`\n" % "Warning! Circular dependencies detected "
(self.path, lb.path)) "between `%s` and `%s`\n" % (self.path, lb.path)
)
self._circular_deps.append(lb) self._circular_deps.append(lb)
elif lb not in self._depbuilders: elif lb not in self._depbuilders:
self._depbuilders.append(lb) self._depbuilders.append(lb)
@ -431,11 +452,10 @@ class LibBuilderBase(object):
if self.lib_archive: if self.lib_archive:
libs.append( libs.append(
self.env.BuildLibrary(self.build_dir, self.src_dir, self.env.BuildLibrary(self.build_dir, self.src_dir, self.src_filter)
self.src_filter)) )
else: else:
self.env.BuildSources(self.build_dir, self.src_dir, self.env.BuildSources(self.build_dir, self.src_dir, self.src_filter)
self.src_filter)
return libs return libs
@ -444,7 +464,6 @@ class UnknownLibBuilder(LibBuilderBase):
class ArduinoLibBuilder(LibBuilderBase): class ArduinoLibBuilder(LibBuilderBase):
def load_manifest(self): def load_manifest(self):
manifest = {} manifest = {}
if not isfile(join(self.path, "library.properties")): if not isfile(join(self.path, "library.properties")):
@ -508,7 +527,7 @@ class ArduinoLibBuilder(LibBuilderBase):
"esp32": ["espressif32"], "esp32": ["espressif32"],
"arc32": ["intel_arc32"], "arc32": ["intel_arc32"],
"stm32": ["ststm32"], "stm32": ["ststm32"],
"nrf5": ["nordicnrf51", "nordicnrf52"] "nrf5": ["nordicnrf51", "nordicnrf52"],
} }
items = [] items = []
for arch in self._manifest.get("architectures", "").split(","): for arch in self._manifest.get("architectures", "").split(","):
@ -524,7 +543,6 @@ class ArduinoLibBuilder(LibBuilderBase):
class MbedLibBuilder(LibBuilderBase): class MbedLibBuilder(LibBuilderBase):
def load_manifest(self): def load_manifest(self):
if not isfile(join(self.path, "module.json")): if not isfile(join(self.path, "module.json")):
return {} return {}
@ -611,14 +629,15 @@ class MbedLibBuilder(LibBuilderBase):
# default macros # default macros
for macro in manifest.get("macros", []): for macro in manifest.get("macros", []):
macro = self._mbed_normalize_macro(macro) macro = self._mbed_normalize_macro(macro)
macros[macro['name']] = macro macros[macro["name"]] = macro
# configuration items # configuration items
for key, options in manifest.get("config", {}).items(): for key, options in manifest.get("config", {}).items():
if "value" not in options: if "value" not in options:
continue continue
macros[key] = dict(name=options.get("macro_name"), macros[key] = dict(
value=options.get("value")) name=options.get("macro_name"), value=options.get("value")
)
# overrode items per target # overrode items per target
for target, options in manifest.get("target_overrides", {}).items(): for target, options in manifest.get("target_overrides", {}).items():
@ -626,25 +645,23 @@ class MbedLibBuilder(LibBuilderBase):
continue continue
for macro in options.get("target.macros_add", []): for macro in options.get("target.macros_add", []):
macro = self._mbed_normalize_macro(macro) macro = self._mbed_normalize_macro(macro)
macros[macro['name']] = macro macros[macro["name"]] = macro
for key, value in options.items(): for key, value in options.items():
if not key.startswith("target.") and key in macros: if not key.startswith("target.") and key in macros:
macros[key]['value'] = value macros[key]["value"] = value
# normalize macro names # normalize macro names
for key, macro in macros.items(): for key, macro in macros.items():
if not macro['name']: if not macro["name"]:
macro['name'] = key macro["name"] = key
if "." not in macro['name']: if "." not in macro["name"]:
macro['name'] = "%s.%s" % (manifest.get("name"), macro["name"] = "%s.%s" % (manifest.get("name"), macro["name"])
macro['name']) macro["name"] = re.sub(
macro['name'] = re.sub(r"[^a-z\d]+", r"[^a-z\d]+", "_", macro["name"], flags=re.I
"_", ).upper()
macro['name'], macro["name"] = "MBED_CONF_" + macro["name"]
flags=re.I).upper() if isinstance(macro["value"], bool):
macro['name'] = "MBED_CONF_" + macro['name'] macro["value"] = 1 if macro["value"] else 0
if isinstance(macro['value'], bool):
macro['value'] = 1 if macro['value'] else 0
return {macro["name"]: macro["value"] for macro in macros.values()} return {macro["name"]: macro["value"] for macro in macros.values()}
@ -654,13 +671,13 @@ class MbedLibBuilder(LibBuilderBase):
for line in fp.readlines(): for line in fp.readlines():
line = line.strip() line = line.strip()
if line == "#endif": if line == "#endif":
lines.append( lines.append("// PlatformIO Library Dependency Finder (LDF)")
"// PlatformIO Library Dependency Finder (LDF)") lines.extend(
lines.extend([ [
"#define %s %s" % "#define %s %s" % (name, value if value is not None else "")
(name, value if value is not None else "") for name, value in macros.items()
for name, value in macros.items() ]
]) )
lines.append("") lines.append("")
if not line.startswith("#define"): if not line.startswith("#define"):
lines.append(line) lines.append(line)
@ -674,7 +691,6 @@ class MbedLibBuilder(LibBuilderBase):
class PlatformIOLibBuilder(LibBuilderBase): class PlatformIOLibBuilder(LibBuilderBase):
def load_manifest(self): def load_manifest(self):
assert isfile(join(self.path, "library.json")) assert isfile(join(self.path, "library.json"))
manifest = fs.load_json(join(self.path, "library.json")) manifest = fs.load_json(join(self.path, "library.json"))
@ -682,9 +698,9 @@ class PlatformIOLibBuilder(LibBuilderBase):
# replace "espressif" old name dev/platform with ESP8266 # replace "espressif" old name dev/platform with ESP8266
if "platforms" in manifest: if "platforms" in manifest:
manifest['platforms'] = [ manifest["platforms"] = [
"espressif8266" if p == "espressif" else p "espressif8266" if p == "espressif" else p
for p in util.items_to_list(manifest['platforms']) for p in util.items_to_list(manifest["platforms"])
] ]
return manifest return manifest
@ -710,8 +726,8 @@ class PlatformIOLibBuilder(LibBuilderBase):
def src_filter(self): def src_filter(self):
if "srcFilter" in self._manifest.get("build", {}): if "srcFilter" in self._manifest.get("build", {}):
return self._manifest.get("build").get("srcFilter") return self._manifest.get("build").get("srcFilter")
if self.env['SRC_FILTER']: if self.env["SRC_FILTER"]:
return self.env['SRC_FILTER'] return self.env["SRC_FILTER"]
if self._is_arduino_manifest(): if self._is_arduino_manifest():
return ArduinoLibBuilder.src_filter.fget(self) return ArduinoLibBuilder.src_filter.fget(self)
return LibBuilderBase.src_filter.fget(self) return LibBuilderBase.src_filter.fget(self)
@ -740,7 +756,8 @@ class PlatformIOLibBuilder(LibBuilderBase):
if global_value is not None: if global_value is not None:
return global_value return global_value
return self._manifest.get("build", {}).get( return self._manifest.get("build", {}).get(
"libArchive", LibBuilderBase.lib_archive.fget(self)) "libArchive", LibBuilderBase.lib_archive.fget(self)
)
@property @property
def lib_ldf_mode(self): def lib_ldf_mode(self):
@ -748,7 +765,10 @@ class PlatformIOLibBuilder(LibBuilderBase):
self.env.GetProjectOption( self.env.GetProjectOption(
"lib_ldf_mode", "lib_ldf_mode",
self._manifest.get("build", {}).get( self._manifest.get("build", {}).get(
"libLDFMode", LibBuilderBase.lib_ldf_mode.fget(self)))) "libLDFMode", LibBuilderBase.lib_ldf_mode.fget(self)
),
)
)
@property @property
def lib_compat_mode(self): def lib_compat_mode(self):
@ -756,8 +776,10 @@ class PlatformIOLibBuilder(LibBuilderBase):
self.env.GetProjectOption( self.env.GetProjectOption(
"lib_compat_mode", "lib_compat_mode",
self._manifest.get("build", {}).get( self._manifest.get("build", {}).get(
"libCompatMode", "libCompatMode", LibBuilderBase.lib_compat_mode.fget(self)
LibBuilderBase.lib_compat_mode.fget(self)))) ),
)
)
def is_platforms_compatible(self, platforms): def is_platforms_compatible(self, platforms):
items = self._manifest.get("platforms") items = self._manifest.get("platforms")
@ -775,9 +797,12 @@ class PlatformIOLibBuilder(LibBuilderBase):
include_dirs = LibBuilderBase.get_include_dirs(self) include_dirs = LibBuilderBase.get_include_dirs(self)
# backwards compatibility with PlatformIO 2.0 # backwards compatibility with PlatformIO 2.0
if ("build" not in self._manifest and self._is_arduino_manifest() if (
and not isdir(join(self.path, "src")) "build" not in self._manifest
and isdir(join(self.path, "utility"))): and self._is_arduino_manifest()
and not isdir(join(self.path, "src"))
and isdir(join(self.path, "utility"))
):
include_dirs.append(join(self.path, "utility")) include_dirs.append(join(self.path, "utility"))
for path in self.env.get("CPPPATH", []): for path in self.env.get("CPPPATH", []):
@ -788,12 +813,11 @@ class PlatformIOLibBuilder(LibBuilderBase):
class ProjectAsLibBuilder(LibBuilderBase): class ProjectAsLibBuilder(LibBuilderBase):
def __init__(self, env, *args, **kwargs): def __init__(self, env, *args, **kwargs):
# backup original value, will be reset in base.__init__ # backup original value, will be reset in base.__init__
project_src_filter = env.get("SRC_FILTER") project_src_filter = env.get("SRC_FILTER")
super(ProjectAsLibBuilder, self).__init__(env, *args, **kwargs) super(ProjectAsLibBuilder, self).__init__(env, *args, **kwargs)
self.env['SRC_FILTER'] = project_src_filter self.env["SRC_FILTER"] = project_src_filter
@property @property
def include_dir(self): def include_dir(self):
@ -819,11 +843,14 @@ class ProjectAsLibBuilder(LibBuilderBase):
items = LibBuilderBase.get_search_files(self) items = LibBuilderBase.get_search_files(self)
# test files # test files
if "__test" in COMMAND_LINE_TARGETS: if "__test" in COMMAND_LINE_TARGETS:
items.extend([ items.extend(
join("$PROJECTTEST_DIR", [
item) for item in self.env.MatchSourceFiles( join("$PROJECTTEST_DIR", item)
"$PROJECTTEST_DIR", "$PIOTEST_SRC_FILTER") for item in self.env.MatchSourceFiles(
]) "$PROJECTTEST_DIR", "$PIOTEST_SRC_FILTER"
)
]
)
return items return items
@property @property
@ -836,8 +863,7 @@ class ProjectAsLibBuilder(LibBuilderBase):
@property @property
def src_filter(self): def src_filter(self):
return (self.env.get("SRC_FILTER") return self.env.get("SRC_FILTER") or LibBuilderBase.src_filter.fget(self)
or LibBuilderBase.src_filter.fget(self))
@property @property
def dependencies(self): def dependencies(self):
@ -848,7 +874,6 @@ class ProjectAsLibBuilder(LibBuilderBase):
pass pass
def install_dependencies(self): def install_dependencies(self):
def _is_builtin(uri): def _is_builtin(uri):
for lb in self.env.GetLibBuilders(): for lb in self.env.GetLibBuilders():
if lb.name == uri: if lb.name == uri:
@ -871,8 +896,7 @@ class ProjectAsLibBuilder(LibBuilderBase):
not_found_uri.append(uri) not_found_uri.append(uri)
did_install = False did_install = False
lm = LibraryManager( lm = LibraryManager(self.env.subst(join("$PROJECTLIBDEPS_DIR", "$PIOENV")))
self.env.subst(join("$PROJECTLIBDEPS_DIR", "$PIOENV")))
for uri in not_found_uri: for uri in not_found_uri:
try: try:
lm.install(uri) lm.install(uri)
@ -923,28 +947,27 @@ class ProjectAsLibBuilder(LibBuilderBase):
def GetLibSourceDirs(env): def GetLibSourceDirs(env):
items = env.GetProjectOption("lib_extra_dirs", []) items = env.GetProjectOption("lib_extra_dirs", [])
items.extend(env['LIBSOURCE_DIRS']) items.extend(env["LIBSOURCE_DIRS"])
return [ return [
env.subst(expanduser(item) if item.startswith("~") else item) env.subst(expanduser(item) if item.startswith("~") else item) for item in items
for item in items
] ]
def IsCompatibleLibBuilder(env, def IsCompatibleLibBuilder(env, lb, verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))):
lb,
verbose=int(ARGUMENTS.get("PIOVERBOSE", 0))):
compat_mode = lb.lib_compat_mode compat_mode = lb.lib_compat_mode
if lb.name in env.GetProjectOption("lib_ignore", []): if lb.name in env.GetProjectOption("lib_ignore", []):
if verbose: if verbose:
sys.stderr.write("Ignored library %s\n" % lb.path) sys.stderr.write("Ignored library %s\n" % lb.path)
return None return None
if compat_mode == "strict" and not lb.is_platforms_compatible( if compat_mode == "strict" and not lb.is_platforms_compatible(env["PIOPLATFORM"]):
env['PIOPLATFORM']):
if verbose: if verbose:
sys.stderr.write("Platform incompatible library %s\n" % lb.path) sys.stderr.write("Platform incompatible library %s\n" % lb.path)
return False return False
if (compat_mode in ("soft", "strict") and "PIOFRAMEWORK" in env if (
and not lb.is_frameworks_compatible(env.get("PIOFRAMEWORK", []))): compat_mode in ("soft", "strict")
and "PIOFRAMEWORK" in env
and not lb.is_frameworks_compatible(env.get("PIOFRAMEWORK", []))
):
if verbose: if verbose:
sys.stderr.write("Framework incompatible library %s\n" % lb.path) sys.stderr.write("Framework incompatible library %s\n" % lb.path)
return False return False
@ -953,8 +976,10 @@ def IsCompatibleLibBuilder(env,
def GetLibBuilders(env): # pylint: disable=too-many-branches def GetLibBuilders(env): # pylint: disable=too-many-branches
if DefaultEnvironment().get("__PIO_LIB_BUILDERS", None) is not None: if DefaultEnvironment().get("__PIO_LIB_BUILDERS", None) is not None:
return sorted(DefaultEnvironment()['__PIO_LIB_BUILDERS'], return sorted(
key=lambda lb: 0 if lb.dependent else 1) DefaultEnvironment()["__PIO_LIB_BUILDERS"],
key=lambda lb: 0 if lb.dependent else 1,
)
DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=[]) DefaultEnvironment().Replace(__PIO_LIB_BUILDERS=[])
@ -974,7 +999,8 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
except exception.InvalidJSONFile: except exception.InvalidJSONFile:
if verbose: if verbose:
sys.stderr.write( sys.stderr.write(
"Skip library with broken manifest: %s\n" % lib_dir) "Skip library with broken manifest: %s\n" % lib_dir
)
continue continue
if env.IsCompatibleLibBuilder(lb): if env.IsCompatibleLibBuilder(lb):
DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb]) DefaultEnvironment().Append(__PIO_LIB_BUILDERS=[lb])
@ -989,15 +1015,15 @@ def GetLibBuilders(env): # pylint: disable=too-many-branches
if verbose and found_incompat: if verbose and found_incompat:
sys.stderr.write( sys.stderr.write(
"More details about \"Library Compatibility Mode\": " 'More details about "Library Compatibility Mode": '
"https://docs.platformio.org/page/librarymanager/ldf.html#" "https://docs.platformio.org/page/librarymanager/ldf.html#"
"ldf-compat-mode\n") "ldf-compat-mode\n"
)
return DefaultEnvironment()['__PIO_LIB_BUILDERS'] return DefaultEnvironment()["__PIO_LIB_BUILDERS"]
def ConfigureProjectLibBuilder(env): def ConfigureProjectLibBuilder(env):
def _get_vcs_info(lb): def _get_vcs_info(lb):
path = LibraryManager.get_src_manifest_path(lb.path) path = LibraryManager.get_src_manifest_path(lb.path)
return fs.load_json(path) if path else None return fs.load_json(path) if path else None
@ -1036,26 +1062,28 @@ def ConfigureProjectLibBuilder(env):
project = ProjectAsLibBuilder(env, "$PROJECT_DIR") project = ProjectAsLibBuilder(env, "$PROJECT_DIR")
ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project) ldf_mode = LibBuilderBase.lib_ldf_mode.fget(project)
print("LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf") print ("LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf")
print("LDF Modes: Finder ~ %s, Compatibility ~ %s" % print (
(ldf_mode, project.lib_compat_mode)) "LDF Modes: Finder ~ %s, Compatibility ~ %s"
% (ldf_mode, project.lib_compat_mode)
)
project.install_dependencies() project.install_dependencies()
lib_builders = env.GetLibBuilders() lib_builders = env.GetLibBuilders()
print("Found %d compatible libraries" % len(lib_builders)) print ("Found %d compatible libraries" % len(lib_builders))
print("Scanning dependencies...") print ("Scanning dependencies...")
project.search_deps_recursive() project.search_deps_recursive()
if ldf_mode.startswith("chain") and project.depbuilders: if ldf_mode.startswith("chain") and project.depbuilders:
_correct_found_libs(lib_builders) _correct_found_libs(lib_builders)
if project.depbuilders: if project.depbuilders:
print("Dependency Graph") print ("Dependency Graph")
_print_deps_tree(project) _print_deps_tree(project)
else: else:
print("No dependencies") print ("No dependencies")
return project return project

View File

@ -39,7 +39,9 @@ class InoToCPPConverter(object):
([a-z_\d]+\s*) # name of prototype ([a-z_\d]+\s*) # name of prototype
\([a-z_,\.\*\&\[\]\s\d]*\) # arguments \([a-z_,\.\*\&\[\]\s\d]*\) # arguments
)\s*(\{|;) # must end with `{` or `;` )\s*(\{|;) # must end with `{` or `;`
""", re.X | re.M | re.I) """,
re.X | re.M | re.I,
)
DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I) DETECTMAIN_RE = re.compile(r"void\s+(setup|loop)\s*\(", re.M | re.I)
PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)" PROTOPTRS_TPLRE = r"\([^&\(]*&(%s)[^\)]*\)"
@ -61,9 +63,7 @@ class InoToCPPConverter(object):
lines = [] lines = []
for node in nodes: for node in nodes:
contents = get_file_contents(node.get_path()) contents = get_file_contents(node.get_path())
_lines = [ _lines = ['# 1 "%s"' % node.get_path().replace("\\", "/"), contents]
'# 1 "%s"' % node.get_path().replace("\\", "/"), contents
]
if self.is_main_node(contents): if self.is_main_node(contents):
lines = _lines + lines lines = _lines + lines
self._main_ino = node.get_path() self._main_ino = node.get_path()
@ -91,8 +91,11 @@ class InoToCPPConverter(object):
self.env.Execute( self.env.Execute(
self.env.VerboseAction( self.env.VerboseAction(
'$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format( '$CXX -o "{0}" -x c++ -fpreprocessed -dD -E "{1}"'.format(
out_file, tmp_path), out_file, tmp_path
"Converting " + basename(out_file[:-4]))) ),
"Converting " + basename(out_file[:-4]),
)
)
atexit.register(_delete_file, tmp_path) atexit.register(_delete_file, tmp_path)
return isfile(out_file) return isfile(out_file)
@ -120,8 +123,9 @@ class InoToCPPConverter(object):
elif stropen and line.endswith(('",', '";')): elif stropen and line.endswith(('",', '";')):
newlines[len(newlines) - 1] += line newlines[len(newlines) - 1] += line
stropen = False stropen = False
newlines.append('#line %d "%s"' % newlines.append(
(linenum, self._main_ino.replace("\\", "/"))) '#line %d "%s"' % (linenum, self._main_ino.replace("\\", "/"))
)
continue continue
newlines.append(line) newlines.append(line)
@ -141,8 +145,10 @@ class InoToCPPConverter(object):
prototypes = [] prototypes = []
reserved_keywords = set(["if", "else", "while"]) reserved_keywords = set(["if", "else", "while"])
for match in self.PROTOTYPE_RE.finditer(contents): for match in self.PROTOTYPE_RE.finditer(contents):
if (set([match.group(2).strip(), if (
match.group(3).strip()]) & reserved_keywords): set([match.group(2).strip(), match.group(3).strip()])
& reserved_keywords
):
continue continue
prototypes.append(match) prototypes.append(match)
return prototypes return prototypes
@ -162,11 +168,8 @@ class InoToCPPConverter(object):
prototypes = self._parse_prototypes(contents) or [] prototypes = self._parse_prototypes(contents) or []
# skip already declared prototypes # skip already declared prototypes
declared = set( declared = set(m.group(1).strip() for m in prototypes if m.group(4) == ";")
m.group(1).strip() for m in prototypes if m.group(4) == ";") prototypes = [m for m in prototypes if m.group(1).strip() not in declared]
prototypes = [
m for m in prototypes if m.group(1).strip() not in declared
]
if not prototypes: if not prototypes:
return contents return contents
@ -175,23 +178,29 @@ class InoToCPPConverter(object):
split_pos = prototypes[0].start() split_pos = prototypes[0].start()
match_ptrs = re.search( match_ptrs = re.search(
self.PROTOPTRS_TPLRE % ("|".join(prototype_names)), self.PROTOPTRS_TPLRE % ("|".join(prototype_names)),
contents[:split_pos], re.M) contents[:split_pos],
re.M,
)
if match_ptrs: if match_ptrs:
split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1 split_pos = contents.rfind("\n", 0, match_ptrs.start()) + 1
result = [] result = []
result.append(contents[:split_pos].strip()) result.append(contents[:split_pos].strip())
result.append("%s;" % ";\n".join([m.group(1) for m in prototypes])) result.append("%s;" % ";\n".join([m.group(1) for m in prototypes]))
result.append('#line %d "%s"' % (self._get_total_lines( result.append(
contents[:split_pos]), self._main_ino.replace("\\", "/"))) '#line %d "%s"'
% (
self._get_total_lines(contents[:split_pos]),
self._main_ino.replace("\\", "/"),
)
)
result.append(contents[split_pos:].strip()) result.append(contents[split_pos:].strip())
return "\n".join(result) return "\n".join(result)
def ConvertInoToCpp(env): def ConvertInoToCpp(env):
src_dir = glob_escape(env.subst("$PROJECTSRC_DIR")) src_dir = glob_escape(env.subst("$PROJECTSRC_DIR"))
ino_nodes = (env.Glob(join(src_dir, "*.ino")) + ino_nodes = env.Glob(join(src_dir, "*.ino")) + env.Glob(join(src_dir, "*.pde"))
env.Glob(join(src_dir, "*.pde")))
if not ino_nodes: if not ino_nodes:
return return
c = InoToCPPConverter(env) c = InoToCPPConverter(env)
@ -214,13 +223,13 @@ def _get_compiler_type(env):
return "gcc" return "gcc"
try: try:
sysenv = environ.copy() sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH']) sysenv["PATH"] = str(env["ENV"]["PATH"])
result = exec_command([env.subst("$CC"), "-v"], env=sysenv) result = exec_command([env.subst("$CC"), "-v"], env=sysenv)
except OSError: except OSError:
return None return None
if result['returncode'] != 0: if result["returncode"] != 0:
return None return None
output = "".join([result['out'], result['err']]).lower() output = "".join([result["out"], result["err"]]).lower()
if "clang" in output and "LLVM" in output: if "clang" in output and "LLVM" in output:
return "clang" return "clang"
if "gcc" in output: if "gcc" in output:
@ -233,7 +242,6 @@ def GetCompilerType(env):
def GetActualLDScript(env): def GetActualLDScript(env):
def _lookup_in_ldpath(script): def _lookup_in_ldpath(script):
for d in env.get("LIBPATH", []): for d in env.get("LIBPATH", []):
path = join(env.subst(d), script) path = join(env.subst(d), script)
@ -264,12 +272,13 @@ def GetActualLDScript(env):
if script: if script:
sys.stderr.write( sys.stderr.write(
"Error: Could not find '%s' LD script in LDPATH '%s'\n" % "Error: Could not find '%s' LD script in LDPATH '%s'\n"
(script, env.subst("$LIBPATH"))) % (script, env.subst("$LIBPATH"))
)
env.Exit(1) env.Exit(1)
if not script and "LDSCRIPT_PATH" in env: if not script and "LDSCRIPT_PATH" in env:
path = _lookup_in_ldpath(env['LDSCRIPT_PATH']) path = _lookup_in_ldpath(env["LDSCRIPT_PATH"])
if path: if path:
return path return path
@ -285,16 +294,17 @@ def VerboseAction(_, act, actstr):
def PioClean(env, clean_dir): def PioClean(env, clean_dir):
if not isdir(clean_dir): if not isdir(clean_dir):
print("Build environment is clean") print ("Build environment is clean")
env.Exit(0) env.Exit(0)
clean_rel_path = relpath(clean_dir) clean_rel_path = relpath(clean_dir)
for root, _, files in walk(clean_dir): for root, _, files in walk(clean_dir):
for f in files: for f in files:
dst = join(root, f) dst = join(root, f)
remove(dst) remove(dst)
print("Removed %s" % print (
(dst if clean_rel_path.startswith(".") else relpath(dst))) "Removed %s" % (dst if clean_rel_path.startswith(".") else relpath(dst))
print("Done cleaning") )
print ("Done cleaning")
fs.rmtree(clean_dir) fs.rmtree(clean_dir)
env.Exit(0) env.Exit(0)
@ -302,8 +312,9 @@ def PioClean(env, clean_dir):
def ProcessDebug(env): def ProcessDebug(env):
if not env.subst("$PIODEBUGFLAGS"): if not env.subst("$PIODEBUGFLAGS"):
env.Replace(PIODEBUGFLAGS=["-Og", "-g3", "-ggdb3"]) env.Replace(PIODEBUGFLAGS=["-Og", "-g3", "-ggdb3"])
env.Append(BUILD_FLAGS=list(env['PIODEBUGFLAGS']) + env.Append(
["-D__PLATFORMIO_BUILD_DEBUG__"]) BUILD_FLAGS=list(env["PIODEBUGFLAGS"]) + ["-D__PLATFORMIO_BUILD_DEBUG__"]
)
unflags = ["-Os"] unflags = ["-Os"]
for level in [0, 1, 2]: for level in [0, 1, 2]:
for flag in ("O", "g", "ggdb"): for flag in ("O", "g", "ggdb"):
@ -312,15 +323,18 @@ def ProcessDebug(env):
def ProcessTest(env): def ProcessTest(env):
env.Append(CPPDEFINES=["UNIT_TEST", "UNITY_INCLUDE_CONFIG_H"], env.Append(
CPPPATH=[join("$BUILD_DIR", "UnityTestLib")]) CPPDEFINES=["UNIT_TEST", "UNITY_INCLUDE_CONFIG_H"],
unitylib = env.BuildLibrary(join("$BUILD_DIR", "UnityTestLib"), CPPPATH=[join("$BUILD_DIR", "UnityTestLib")],
get_core_package_dir("tool-unity")) )
unitylib = env.BuildLibrary(
join("$BUILD_DIR", "UnityTestLib"), get_core_package_dir("tool-unity")
)
env.Prepend(LIBS=[unitylib]) env.Prepend(LIBS=[unitylib])
src_filter = ["+<*.cpp>", "+<*.c>"] src_filter = ["+<*.cpp>", "+<*.c>"]
if "PIOTEST_RUNNING_NAME" in env: if "PIOTEST_RUNNING_NAME" in env:
src_filter.append("+<%s%s>" % (env['PIOTEST_RUNNING_NAME'], sep)) src_filter.append("+<%s%s>" % (env["PIOTEST_RUNNING_NAME"], sep))
env.Replace(PIOTEST_SRC_FILTER=src_filter) env.Replace(PIOTEST_SRC_FILTER=src_filter)
@ -330,7 +344,7 @@ def GetExtraScripts(env, scope):
if scope == "post" and ":" not in item: if scope == "post" and ":" not in item:
items.append(item) items.append(item)
elif item.startswith("%s:" % scope): elif item.startswith("%s:" % scope):
items.append(item[len(scope) + 1:]) items.append(item[len(scope) + 1 :])
if not items: if not items:
return items return items
with fs.cd(env.subst("$PROJECT_DIR")): with fs.cd(env.subst("$PROJECT_DIR")):

View File

@ -33,8 +33,8 @@ def PioPlatform(env):
variables = env.GetProjectOptions(as_dict=True) variables = env.GetProjectOptions(as_dict=True)
if "framework" in variables: if "framework" in variables:
# support PIO Core 3.0 dev/platforms # support PIO Core 3.0 dev/platforms
variables['pioframework'] = variables['framework'] variables["pioframework"] = variables["framework"]
p = PlatformFactory.newPlatform(env['PLATFORM_MANIFEST']) p = PlatformFactory.newPlatform(env["PLATFORM_MANIFEST"])
p.configure_default_packages(variables, COMMAND_LINE_TARGETS) p.configure_default_packages(variables, COMMAND_LINE_TARGETS)
return p return p
@ -54,7 +54,7 @@ def BoardConfig(env, board=None):
def GetFrameworkScript(env, framework): def GetFrameworkScript(env, framework):
p = env.PioPlatform() p = env.PioPlatform()
assert p.frameworks and framework in p.frameworks assert p.frameworks and framework in p.frameworks
script_path = env.subst(p.frameworks[framework]['script']) script_path = env.subst(p.frameworks[framework]["script"])
if not isfile(script_path): if not isfile(script_path):
script_path = join(p.get_dir(), script_path) script_path = join(p.get_dir(), script_path)
return script_path return script_path
@ -65,7 +65,7 @@ def LoadPioPlatform(env):
installed_packages = p.get_installed_packages() installed_packages = p.get_installed_packages()
# Ensure real platform name # Ensure real platform name
env['PIOPLATFORM'] = p.name env["PIOPLATFORM"] = p.name
# Add toolchains and uploaders to $PATH and $*_LIBRARY_PATH # Add toolchains and uploaders to $PATH and $*_LIBRARY_PATH
systype = util.get_systype() systype = util.get_systype()
@ -75,14 +75,13 @@ def LoadPioPlatform(env):
continue continue
pkg_dir = p.get_package_dir(name) pkg_dir = p.get_package_dir(name)
env.PrependENVPath( env.PrependENVPath(
"PATH", "PATH", join(pkg_dir, "bin") if isdir(join(pkg_dir, "bin")) else pkg_dir
join(pkg_dir, "bin") if isdir(join(pkg_dir, "bin")) else pkg_dir) )
if (not WINDOWS and isdir(join(pkg_dir, "lib")) if not WINDOWS and isdir(join(pkg_dir, "lib")) and type_ != "toolchain":
and type_ != "toolchain"):
env.PrependENVPath( env.PrependENVPath(
"DYLD_LIBRARY_PATH" "DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH",
if "darwin" in systype else "LD_LIBRARY_PATH", join(pkg_dir, "lib"),
join(pkg_dir, "lib")) )
# Platform specific LD Scripts # Platform specific LD Scripts
if isdir(join(p.get_dir(), "ldscripts")): if isdir(join(p.get_dir(), "ldscripts")):
@ -101,9 +100,11 @@ def LoadPioPlatform(env):
for option_meta in ProjectOptions.values(): for option_meta in ProjectOptions.values():
if not option_meta.buildenvvar or option_meta.buildenvvar in env: if not option_meta.buildenvvar or option_meta.buildenvvar in env:
continue continue
data_path = (option_meta.name[6:] data_path = (
if option_meta.name.startswith("board_") else option_meta.name[6:]
option_meta.name.replace("_", ".")) if option_meta.name.startswith("board_")
else option_meta.name.replace("_", ".")
)
try: try:
env[option_meta.buildenvvar] = board_config.get(data_path) env[option_meta.buildenvvar] = board_config.get(data_path)
except KeyError: except KeyError:
@ -118,22 +119,25 @@ def PrintConfiguration(env): # pylint: disable=too-many-statements
board_config = env.BoardConfig() if "BOARD" in env else None board_config = env.BoardConfig() if "BOARD" in env else None
def _get_configuration_data(): def _get_configuration_data():
return None if not board_config else [ return (
"CONFIGURATION:", None
"https://docs.platformio.org/page/boards/%s/%s.html" % if not board_config
(platform.name, board_config.id) else [
] "CONFIGURATION:",
"https://docs.platformio.org/page/boards/%s/%s.html"
% (platform.name, board_config.id),
]
)
def _get_plaform_data(): def _get_plaform_data():
data = ["PLATFORM: %s %s" % (platform.title, platform.version)] data = ["PLATFORM: %s %s" % (platform.title, platform.version)]
src_manifest_path = platform.pm.get_src_manifest_path( src_manifest_path = platform.pm.get_src_manifest_path(platform.get_dir())
platform.get_dir())
if src_manifest_path: if src_manifest_path:
src_manifest = fs.load_json(src_manifest_path) src_manifest = fs.load_json(src_manifest_path)
if "version" in src_manifest: if "version" in src_manifest:
data.append("#" + src_manifest['version']) data.append("#" + src_manifest["version"])
if int(ARGUMENTS.get("PIOVERBOSE", 0)): if int(ARGUMENTS.get("PIOVERBOSE", 0)):
data.append("(%s)" % src_manifest['url']) data.append("(%s)" % src_manifest["url"])
if board_config: if board_config:
data.extend([">", board_config.get("name")]) data.extend([">", board_config.get("name")])
return data return data
@ -151,19 +155,22 @@ def PrintConfiguration(env): # pylint: disable=too-many-statements
return data return data
ram = board_config.get("upload", {}).get("maximum_ram_size") ram = board_config.get("upload", {}).get("maximum_ram_size")
flash = board_config.get("upload", {}).get("maximum_size") flash = board_config.get("upload", {}).get("maximum_size")
data.append("%s RAM, %s Flash" % data.append(
(fs.format_filesize(ram), fs.format_filesize(flash))) "%s RAM, %s Flash" % (fs.format_filesize(ram), fs.format_filesize(flash))
)
return data return data
def _get_debug_data(): def _get_debug_data():
debug_tools = board_config.get( debug_tools = (
"debug", {}).get("tools") if board_config else None board_config.get("debug", {}).get("tools") if board_config else None
)
if not debug_tools: if not debug_tools:
return None return None
data = [ data = [
"DEBUG:", "Current", "DEBUG:",
"(%s)" % board_config.get_debug_tool_name( "Current",
env.GetProjectOption("debug_tool")) "(%s)"
% board_config.get_debug_tool_name(env.GetProjectOption("debug_tool")),
] ]
onboard = [] onboard = []
external = [] external = []
@ -187,23 +194,27 @@ def PrintConfiguration(env): # pylint: disable=too-many-statements
if not pkg_dir: if not pkg_dir:
continue continue
manifest = platform.pm.load_manifest(pkg_dir) manifest = platform.pm.load_manifest(pkg_dir)
original_version = util.get_original_version(manifest['version']) original_version = util.get_original_version(manifest["version"])
info = "%s %s" % (manifest['name'], manifest['version']) info = "%s %s" % (manifest["name"], manifest["version"])
extra = [] extra = []
if original_version: if original_version:
extra.append(original_version) extra.append(original_version)
if "__src_url" in manifest and int(ARGUMENTS.get("PIOVERBOSE", 0)): if "__src_url" in manifest and int(ARGUMENTS.get("PIOVERBOSE", 0)):
extra.append(manifest['__src_url']) extra.append(manifest["__src_url"])
if extra: if extra:
info += " (%s)" % ", ".join(extra) info += " (%s)" % ", ".join(extra)
data.append(info) data.append(info)
return ["PACKAGES:", ", ".join(data)] return ["PACKAGES:", ", ".join(data)]
for data in (_get_configuration_data(), _get_plaform_data(), for data in (
_get_hardware_data(), _get_debug_data(), _get_configuration_data(),
_get_packages_data()): _get_plaform_data(),
_get_hardware_data(),
_get_debug_data(),
_get_packages_data(),
):
if data and len(data) > 1: if data and len(data) > 1:
print(" ".join(data)) print (" ".join(data))
def exists(_): def exists(_):

View File

@ -18,22 +18,25 @@ from platformio.project.config import ProjectConfig, ProjectOptions
def GetProjectConfig(env): def GetProjectConfig(env):
return ProjectConfig.get_instance(env['PROJECT_CONFIG']) return ProjectConfig.get_instance(env["PROJECT_CONFIG"])
def GetProjectOptions(env, as_dict=False): def GetProjectOptions(env, as_dict=False):
return env.GetProjectConfig().items(env=env['PIOENV'], as_dict=as_dict) return env.GetProjectConfig().items(env=env["PIOENV"], as_dict=as_dict)
def GetProjectOption(env, option, default=None): def GetProjectOption(env, option, default=None):
return env.GetProjectConfig().get("env:" + env['PIOENV'], option, default) return env.GetProjectConfig().get("env:" + env["PIOENV"], option, default)
def LoadProjectOptions(env): def LoadProjectOptions(env):
for option, value in env.GetProjectOptions(): for option, value in env.GetProjectOptions():
option_meta = ProjectOptions.get("env." + option) option_meta = ProjectOptions.get("env." + option)
if (not option_meta or not option_meta.buildenvvar if (
or option_meta.buildenvvar in env): not option_meta
or not option_meta.buildenvvar
or option_meta.buildenvvar in env
):
continue continue
env[option_meta.buildenvvar] = value env[option_meta.buildenvvar] = value

View File

@ -45,7 +45,7 @@ def FlushSerialBuffer(env, port):
def TouchSerialPort(env, port, baudrate): def TouchSerialPort(env, port, baudrate):
port = env.subst(port) port = env.subst(port)
print("Forcing reset using %dbps open/close on port %s" % (baudrate, port)) print ("Forcing reset using %dbps open/close on port %s" % (baudrate, port))
try: try:
s = Serial(port=port, baudrate=baudrate) s = Serial(port=port, baudrate=baudrate)
s.setDTR(False) s.setDTR(False)
@ -56,13 +56,13 @@ def TouchSerialPort(env, port, baudrate):
def WaitForNewSerialPort(env, before): def WaitForNewSerialPort(env, before):
print("Waiting for the new upload port...") print ("Waiting for the new upload port...")
prev_port = env.subst("$UPLOAD_PORT") prev_port = env.subst("$UPLOAD_PORT")
new_port = None new_port = None
elapsed = 0 elapsed = 0
before = [p['port'] for p in before] before = [p["port"] for p in before]
while elapsed < 5 and new_port is None: while elapsed < 5 and new_port is None:
now = [p['port'] for p in util.get_serial_ports()] now = [p["port"] for p in util.get_serial_ports()]
for p in now: for p in now:
if p not in before: if p not in before:
new_port = p new_port = p
@ -84,10 +84,12 @@ def WaitForNewSerialPort(env, before):
sleep(1) sleep(1)
if not new_port: if not new_port:
sys.stderr.write("Error: Couldn't find a board on the selected port. " sys.stderr.write(
"Check that you have the correct port selected. " "Error: Couldn't find a board on the selected port. "
"If it is correct, try pressing the board's reset " "Check that you have the correct port selected. "
"button after initiating the upload.\n") "If it is correct, try pressing the board's reset "
"button after initiating the upload.\n"
)
env.Exit(1) env.Exit(1)
return new_port return new_port
@ -99,8 +101,8 @@ def AutodetectUploadPort(*args, **kwargs):
def _get_pattern(): def _get_pattern():
if "UPLOAD_PORT" not in env: if "UPLOAD_PORT" not in env:
return None return None
if set(["*", "?", "[", "]"]) & set(env['UPLOAD_PORT']): if set(["*", "?", "[", "]"]) & set(env["UPLOAD_PORT"]):
return env['UPLOAD_PORT'] return env["UPLOAD_PORT"]
return None return None
def _is_match_pattern(port): def _is_match_pattern(port):
@ -112,17 +114,13 @@ def AutodetectUploadPort(*args, **kwargs):
def _look_for_mbed_disk(): def _look_for_mbed_disk():
msdlabels = ("mbed", "nucleo", "frdm", "microbit") msdlabels = ("mbed", "nucleo", "frdm", "microbit")
for item in util.get_logical_devices(): for item in util.get_logical_devices():
if item['path'].startswith("/net") or not _is_match_pattern( if item["path"].startswith("/net") or not _is_match_pattern(item["path"]):
item['path']):
continue continue
mbed_pages = [ mbed_pages = [join(item["path"], n) for n in ("mbed.htm", "mbed.html")]
join(item['path'], n) for n in ("mbed.htm", "mbed.html")
]
if any(isfile(p) for p in mbed_pages): if any(isfile(p) for p in mbed_pages):
return item['path'] return item["path"]
if item['name'] \ if item["name"] and any(l in item["name"].lower() for l in msdlabels):
and any(l in item['name'].lower() for l in msdlabels): return item["path"]
return item['path']
return None return None
def _look_for_serial_port(): def _look_for_serial_port():
@ -132,27 +130,27 @@ def AutodetectUploadPort(*args, **kwargs):
if "BOARD" in env and "build.hwids" in env.BoardConfig(): if "BOARD" in env and "build.hwids" in env.BoardConfig():
board_hwids = env.BoardConfig().get("build.hwids") board_hwids = env.BoardConfig().get("build.hwids")
for item in util.get_serial_ports(filter_hwid=True): for item in util.get_serial_ports(filter_hwid=True):
if not _is_match_pattern(item['port']): if not _is_match_pattern(item["port"]):
continue continue
port = item['port'] port = item["port"]
if upload_protocol.startswith("blackmagic"): if upload_protocol.startswith("blackmagic"):
if WINDOWS and port.startswith("COM") and len(port) > 4: if WINDOWS and port.startswith("COM") and len(port) > 4:
port = "\\\\.\\%s" % port port = "\\\\.\\%s" % port
if "GDB" in item['description']: if "GDB" in item["description"]:
return port return port
for hwid in board_hwids: for hwid in board_hwids:
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "") hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
if hwid_str in item['hwid']: if hwid_str in item["hwid"]:
return port return port
return port return port
if "UPLOAD_PORT" in env and not _get_pattern(): if "UPLOAD_PORT" in env and not _get_pattern():
print(env.subst("Use manually specified: $UPLOAD_PORT")) print (env.subst("Use manually specified: $UPLOAD_PORT"))
return return
if (env.subst("$UPLOAD_PROTOCOL") == "mbed" if env.subst("$UPLOAD_PROTOCOL") == "mbed" or (
or ("mbed" in env.subst("$PIOFRAMEWORK") "mbed" in env.subst("$PIOFRAMEWORK") and not env.subst("$UPLOAD_PROTOCOL")
and not env.subst("$UPLOAD_PROTOCOL"))): ):
env.Replace(UPLOAD_PORT=_look_for_mbed_disk()) env.Replace(UPLOAD_PORT=_look_for_mbed_disk())
else: else:
try: try:
@ -162,13 +160,14 @@ def AutodetectUploadPort(*args, **kwargs):
env.Replace(UPLOAD_PORT=_look_for_serial_port()) env.Replace(UPLOAD_PORT=_look_for_serial_port())
if env.subst("$UPLOAD_PORT"): if env.subst("$UPLOAD_PORT"):
print(env.subst("Auto-detected: $UPLOAD_PORT")) print (env.subst("Auto-detected: $UPLOAD_PORT"))
else: else:
sys.stderr.write( sys.stderr.write(
"Error: Please specify `upload_port` for environment or use " "Error: Please specify `upload_port` for environment or use "
"global `--upload-port` option.\n" "global `--upload-port` option.\n"
"For some development platforms it can be a USB flash " "For some development platforms it can be a USB flash "
"drive (i.e. /media/<user>/<device name>)\n") "drive (i.e. /media/<user>/<device name>)\n"
)
env.Exit(1) env.Exit(1)
@ -179,16 +178,17 @@ def UploadToDisk(_, target, source, env):
fpath = join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext)) fpath = join(env.subst("$BUILD_DIR"), "%s.%s" % (progname, ext))
if not isfile(fpath): if not isfile(fpath):
continue continue
copyfile(fpath, copyfile(fpath, join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext)))
join(env.subst("$UPLOAD_PORT"), "%s.%s" % (progname, ext))) print (
print("Firmware has been successfully uploaded.\n" "Firmware has been successfully uploaded.\n"
"(Some boards may require manual hard reset)") "(Some boards may require manual hard reset)"
)
def CheckUploadSize(_, target, source, env): def CheckUploadSize(_, target, source, env):
check_conditions = [ check_conditions = [
env.get("BOARD"), env.get("BOARD"),
env.get("SIZETOOL") or env.get("SIZECHECKCMD") env.get("SIZETOOL") or env.get("SIZECHECKCMD"),
] ]
if not all(check_conditions): if not all(check_conditions):
return return
@ -198,9 +198,11 @@ def CheckUploadSize(_, target, source, env):
return return
def _configure_defaults(): def _configure_defaults():
env.Replace(SIZECHECKCMD="$SIZETOOL -B -d $SOURCES", env.Replace(
SIZEPROGREGEXP=r"^(\d+)\s+(\d+)\s+\d+\s", SIZECHECKCMD="$SIZETOOL -B -d $SOURCES",
SIZEDATAREGEXP=r"^\d+\s+(\d+)\s+(\d+)\s+\d+") SIZEPROGREGEXP=r"^(\d+)\s+(\d+)\s+\d+\s",
SIZEDATAREGEXP=r"^\d+\s+(\d+)\s+(\d+)\s+\d+",
)
def _get_size_output(): def _get_size_output():
cmd = env.get("SIZECHECKCMD") cmd = env.get("SIZECHECKCMD")
@ -210,11 +212,11 @@ def CheckUploadSize(_, target, source, env):
cmd = cmd.split() cmd = cmd.split()
cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg] cmd = [arg.replace("$SOURCES", str(source[0])) for arg in cmd if arg]
sysenv = environ.copy() sysenv = environ.copy()
sysenv['PATH'] = str(env['ENV']['PATH']) sysenv["PATH"] = str(env["ENV"]["PATH"])
result = exec_command(env.subst(cmd), env=sysenv) result = exec_command(env.subst(cmd), env=sysenv)
if result['returncode'] != 0: if result["returncode"] != 0:
return None return None
return result['out'].strip() return result["out"].strip()
def _calculate_size(output, pattern): def _calculate_size(output, pattern):
if not output or not pattern: if not output or not pattern:
@ -238,7 +240,8 @@ def CheckUploadSize(_, target, source, env):
if used_blocks > blocks_per_progress: if used_blocks > blocks_per_progress:
used_blocks = blocks_per_progress used_blocks = blocks_per_progress
return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format( return "[{:{}}] {: 6.1%} (used {:d} bytes from {:d} bytes)".format(
"=" * used_blocks, blocks_per_progress, percent_raw, value, total) "=" * used_blocks, blocks_per_progress, percent_raw, value, total
)
if not env.get("SIZECHECKCMD") and not env.get("SIZEPROGREGEXP"): if not env.get("SIZECHECKCMD") and not env.get("SIZEPROGREGEXP"):
_configure_defaults() _configure_defaults()
@ -246,14 +249,13 @@ def CheckUploadSize(_, target, source, env):
program_size = _calculate_size(output, env.get("SIZEPROGREGEXP")) program_size = _calculate_size(output, env.get("SIZEPROGREGEXP"))
data_size = _calculate_size(output, env.get("SIZEDATAREGEXP")) data_size = _calculate_size(output, env.get("SIZEDATAREGEXP"))
print("Memory Usage -> http://bit.ly/pio-memory-usage") print ("Memory Usage -> http://bit.ly/pio-memory-usage")
if data_max_size and data_size > -1: if data_max_size and data_size > -1:
print("DATA: %s" % _format_availale_bytes(data_size, data_max_size)) print ("DATA: %s" % _format_availale_bytes(data_size, data_max_size))
if program_size > -1: if program_size > -1:
print("PROGRAM: %s" % print ("PROGRAM: %s" % _format_availale_bytes(program_size, program_max_size))
_format_availale_bytes(program_size, program_max_size))
if int(ARGUMENTS.get("PIOVERBOSE", 0)): if int(ARGUMENTS.get("PIOVERBOSE", 0)):
print(output) print (output)
# raise error # raise error
# if data_max_size and data_size > data_max_size: # if data_max_size and data_size > data_max_size:
@ -262,9 +264,10 @@ def CheckUploadSize(_, target, source, env):
# "than maximum allowed (%s bytes)\n" % (data_size, data_max_size)) # "than maximum allowed (%s bytes)\n" % (data_size, data_max_size))
# env.Exit(1) # env.Exit(1)
if program_size > program_max_size: if program_size > program_max_size:
sys.stderr.write("Error: The program size (%d bytes) is greater " sys.stderr.write(
"than maximum allowed (%s bytes)\n" % "Error: The program size (%d bytes) is greater "
(program_size, program_max_size)) "than maximum allowed (%s bytes)\n" % (program_size, program_max_size)
)
env.Exit(1) env.Exit(1)
@ -272,12 +275,11 @@ def PrintUploadInfo(env):
configured = env.subst("$UPLOAD_PROTOCOL") configured = env.subst("$UPLOAD_PROTOCOL")
available = [configured] if configured else [] available = [configured] if configured else []
if "BOARD" in env: if "BOARD" in env:
available.extend(env.BoardConfig().get("upload", available.extend(env.BoardConfig().get("upload", {}).get("protocols", []))
{}).get("protocols", []))
if available: if available:
print("AVAILABLE: %s" % ", ".join(sorted(set(available)))) print ("AVAILABLE: %s" % ", ".join(sorted(set(available))))
if configured: if configured:
print("CURRENT: upload_protocol = %s" % configured) print ("CURRENT: upload_protocol = %s" % configured)
def exists(_): def exists(_):

View File

@ -61,8 +61,9 @@ def _file_long_data(env, data):
build_dir = env.subst("$BUILD_DIR") build_dir = env.subst("$BUILD_DIR")
if not isdir(build_dir): if not isdir(build_dir):
makedirs(build_dir) makedirs(build_dir)
tmp_file = join(build_dir, tmp_file = join(
"longcmd-%s" % md5(hashlib_encode_data(data)).hexdigest()) build_dir, "longcmd-%s" % md5(hashlib_encode_data(data)).hexdigest()
)
if isfile(tmp_file): if isfile(tmp_file):
return tmp_file return tmp_file
with open(tmp_file, "w") as fp: with open(tmp_file, "w") as fp:
@ -83,10 +84,12 @@ def generate(env):
coms = {} coms = {}
for key in ("ARCOM", "LINKCOM"): for key in ("ARCOM", "LINKCOM"):
coms[key] = env.get(key, "").replace( coms[key] = env.get(key, "").replace(
"$SOURCES", "${_long_sources_hook(__env__, SOURCES)}") "$SOURCES", "${_long_sources_hook(__env__, SOURCES)}"
)
for key in ("_CCCOMCOM", "ASPPCOM"): for key in ("_CCCOMCOM", "ASPPCOM"):
coms[key] = env.get(key, "").replace( coms[key] = env.get(key, "").replace(
"$_CPPINCFLAGS", "${_long_incflags_hook(__env__, _CPPINCFLAGS)}") "$_CPPINCFLAGS", "${_long_incflags_hook(__env__, _CPPINCFLAGS)}"
)
env.Replace(**coms) env.Replace(**coms)
return env return env

View File

@ -54,7 +54,8 @@ def _build_project_deps(env):
key: project_lib_builder.env.get(key) key: project_lib_builder.env.get(key)
for key in ("LIBS", "LIBPATH", "LINKFLAGS") for key in ("LIBS", "LIBPATH", "LINKFLAGS")
if project_lib_builder.env.get(key) if project_lib_builder.env.get(key)
}) }
)
projenv = env.Clone() projenv = env.Clone()
@ -65,27 +66,32 @@ def _build_project_deps(env):
is_test = "__test" in COMMAND_LINE_TARGETS is_test = "__test" in COMMAND_LINE_TARGETS
if is_test: if is_test:
projenv.BuildSources("$BUILDTEST_DIR", "$PROJECTTEST_DIR", projenv.BuildSources(
"$PIOTEST_SRC_FILTER") "$BUILDTEST_DIR", "$PROJECTTEST_DIR", "$PIOTEST_SRC_FILTER"
)
if not is_test or env.GetProjectOption("test_build_project_src", False): if not is_test or env.GetProjectOption("test_build_project_src", False):
projenv.BuildSources("$BUILDSRC_DIR", "$PROJECTSRC_DIR", projenv.BuildSources("$BUILDSRC_DIR", "$PROJECTSRC_DIR", env.get("SRC_FILTER"))
env.get("SRC_FILTER"))
if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS: if not env.get("PIOBUILDFILES") and not COMMAND_LINE_TARGETS:
sys.stderr.write( sys.stderr.write(
"Error: Nothing to build. Please put your source code files " "Error: Nothing to build. Please put your source code files "
"to '%s' folder\n" % env.subst("$PROJECTSRC_DIR")) "to '%s' folder\n" % env.subst("$PROJECTSRC_DIR")
)
env.Exit(1) env.Exit(1)
Export("projenv") Export("projenv")
def BuildProgram(env): def BuildProgram(env):
def _append_pio_macros(): def _append_pio_macros():
env.AppendUnique(CPPDEFINES=[( env.AppendUnique(
"PLATFORMIO", CPPDEFINES=[
int("{0:02d}{1:02d}{2:02d}".format(*pioversion_to_intstr())))]) (
"PLATFORMIO",
int("{0:02d}{1:02d}{2:02d}".format(*pioversion_to_intstr())),
)
]
)
_append_pio_macros() _append_pio_macros()
@ -95,8 +101,7 @@ def BuildProgram(env):
if not Util.case_sensitive_suffixes(".s", ".S"): if not Util.case_sensitive_suffixes(".s", ".S"):
env.Replace(AS="$CC", ASCOM="$ASPPCOM") env.Replace(AS="$CC", ASCOM="$ASPPCOM")
if ("debug" in COMMAND_LINE_TARGETS if "debug" in COMMAND_LINE_TARGETS or env.GetProjectOption("build_type") == "debug":
or env.GetProjectOption("build_type") == "debug"):
env.ProcessDebug() env.ProcessDebug()
# process extra flags from board # process extra flags from board
@ -122,8 +127,7 @@ def BuildProgram(env):
_build_project_deps(env) _build_project_deps(env)
# append into the beginning a main LD script # append into the beginning a main LD script
if (env.get("LDSCRIPT_PATH") if env.get("LDSCRIPT_PATH") and not any("-Wl,-T" in f for f in env["LINKFLAGS"]):
and not any("-Wl,-T" in f for f in env['LINKFLAGS'])):
env.Prepend(LINKFLAGS=["-T", "$LDSCRIPT_PATH"]) env.Prepend(LINKFLAGS=["-T", "$LDSCRIPT_PATH"])
# enable "cyclic reference" for linker # enable "cyclic reference" for linker
@ -131,15 +135,18 @@ def BuildProgram(env):
env.Prepend(_LIBFLAGS="-Wl,--start-group ") env.Prepend(_LIBFLAGS="-Wl,--start-group ")
env.Append(_LIBFLAGS=" -Wl,--end-group") env.Append(_LIBFLAGS=" -Wl,--end-group")
program = env.Program(join("$BUILD_DIR", env.subst("$PROGNAME")), program = env.Program(
env['PIOBUILDFILES']) join("$BUILD_DIR", env.subst("$PROGNAME")), env["PIOBUILDFILES"]
)
env.Replace(PIOMAINPROG=program) env.Replace(PIOMAINPROG=program)
AlwaysBuild( AlwaysBuild(
env.Alias( env.Alias(
"checkprogsize", program, "checkprogsize",
env.VerboseAction(env.CheckUploadSize, program,
"Checking size $PIOMAINPROG"))) env.VerboseAction(env.CheckUploadSize, "Checking size $PIOMAINPROG"),
)
)
return program return program
@ -155,19 +162,19 @@ def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
result[key].extend(value) result[key].extend(value)
cppdefines = [] cppdefines = []
for item in result['CPPDEFINES']: for item in result["CPPDEFINES"]:
if not Util.is_Sequence(item): if not Util.is_Sequence(item):
cppdefines.append(item) cppdefines.append(item)
continue continue
name, value = item[:2] name, value = item[:2]
if '\"' in value: if '"' in value:
value = value.replace('\"', '\\\"') value = value.replace('"', '\\"')
elif value.isdigit(): elif value.isdigit():
value = int(value) value = int(value)
elif value.replace(".", "", 1).isdigit(): elif value.replace(".", "", 1).isdigit():
value = float(value) value = float(value)
cppdefines.append((name, value)) cppdefines.append((name, value))
result['CPPDEFINES'] = cppdefines result["CPPDEFINES"] = cppdefines
# fix relative CPPPATH & LIBPATH # fix relative CPPPATH & LIBPATH
for k in ("CPPPATH", "LIBPATH"): for k in ("CPPPATH", "LIBPATH"):
@ -178,7 +185,7 @@ def ParseFlagsExtended(env, flags): # pylint: disable=too-many-branches
# fix relative path for "-include" # fix relative path for "-include"
for i, f in enumerate(result.get("CCFLAGS", [])): for i, f in enumerate(result.get("CCFLAGS", [])):
if isinstance(f, tuple) and f[0] == "-include": if isinstance(f, tuple) and f[0] == "-include":
result['CCFLAGS'][i] = (f[0], env.File(realpath(f[1].get_path()))) result["CCFLAGS"][i] = (f[0], env.File(realpath(f[1].get_path())))
return result return result
@ -191,14 +198,15 @@ def ProcessFlags(env, flags): # pylint: disable=too-many-branches
# Cancel any previous definition of name, either built in or # Cancel any previous definition of name, either built in or
# provided with a -U option // Issue #191 # provided with a -U option // Issue #191
undefines = [ undefines = [
u for u in env.get("CCFLAGS", []) u
for u in env.get("CCFLAGS", [])
if isinstance(u, string_types) and u.startswith("-U") if isinstance(u, string_types) and u.startswith("-U")
] ]
if undefines: if undefines:
for undef in undefines: for undef in undefines:
env['CCFLAGS'].remove(undef) env["CCFLAGS"].remove(undef)
if undef[2:] in env['CPPDEFINES']: if undef[2:] in env["CPPDEFINES"]:
env['CPPDEFINES'].remove(undef[2:]) env["CPPDEFINES"].remove(undef[2:])
env.Append(_CPPDEFFLAGS=" %s" % " ".join(undefines)) env.Append(_CPPDEFFLAGS=" %s" % " ".join(undefines))
@ -221,8 +229,7 @@ def ProcessUnFlags(env, flags):
for current in env.get(key, []): for current in env.get(key, []):
conditions = [ conditions = [
unflag == current, unflag == current,
isinstance(current, (tuple, list)) isinstance(current, (tuple, list)) and unflag[0] == current[0],
and unflag[0] == current[0]
] ]
if any(conditions): if any(conditions):
env[key].remove(current) env[key].remove(current)
@ -231,15 +238,12 @@ def ProcessUnFlags(env, flags):
def MatchSourceFiles(env, src_dir, src_filter=None): def MatchSourceFiles(env, src_dir, src_filter=None):
src_filter = env.subst(src_filter) if src_filter else None src_filter = env.subst(src_filter) if src_filter else None
src_filter = src_filter or SRC_FILTER_DEFAULT src_filter = src_filter or SRC_FILTER_DEFAULT
return fs.match_src_files(env.subst(src_dir), src_filter, return fs.match_src_files(
SRC_BUILD_EXT + SRC_HEADER_EXT) env.subst(src_dir), src_filter, SRC_BUILD_EXT + SRC_HEADER_EXT
)
def CollectBuildFiles(env, def CollectBuildFiles(env, variant_dir, src_dir, src_filter=None, duplicate=False):
variant_dir,
src_dir,
src_filter=None,
duplicate=False):
sources = [] sources = []
variants = [] variants = []
@ -267,8 +271,10 @@ def BuildFrameworks(env, frameworks):
return return
if "BOARD" not in env: if "BOARD" not in env:
sys.stderr.write("Please specify `board` in `platformio.ini` to use " sys.stderr.write(
"with '%s' framework\n" % ", ".join(frameworks)) "Please specify `board` in `platformio.ini` to use "
"with '%s' framework\n" % ", ".join(frameworks)
)
env.Exit(1) env.Exit(1)
board_frameworks = env.BoardConfig().get("frameworks", []) board_frameworks = env.BoardConfig().get("frameworks", [])
@ -276,8 +282,7 @@ def BuildFrameworks(env, frameworks):
if board_frameworks: if board_frameworks:
frameworks.insert(0, board_frameworks[0]) frameworks.insert(0, board_frameworks[0])
else: else:
sys.stderr.write( sys.stderr.write("Error: Please specify `board` in `platformio.ini`\n")
"Error: Please specify `board` in `platformio.ini`\n")
env.Exit(1) env.Exit(1)
for f in frameworks: for f in frameworks:
@ -290,22 +295,20 @@ def BuildFrameworks(env, frameworks):
if f in board_frameworks: if f in board_frameworks:
SConscript(env.GetFrameworkScript(f), exports="env") SConscript(env.GetFrameworkScript(f), exports="env")
else: else:
sys.stderr.write( sys.stderr.write("Error: This board doesn't support %s framework!\n" % f)
"Error: This board doesn't support %s framework!\n" % f)
env.Exit(1) env.Exit(1)
def BuildLibrary(env, variant_dir, src_dir, src_filter=None): def BuildLibrary(env, variant_dir, src_dir, src_filter=None):
env.ProcessUnFlags(env.get("BUILD_UNFLAGS")) env.ProcessUnFlags(env.get("BUILD_UNFLAGS"))
return env.StaticLibrary( return env.StaticLibrary(
env.subst(variant_dir), env.subst(variant_dir), env.CollectBuildFiles(variant_dir, src_dir, src_filter)
env.CollectBuildFiles(variant_dir, src_dir, src_filter)) )
def BuildSources(env, variant_dir, src_dir, src_filter=None): def BuildSources(env, variant_dir, src_dir, src_filter=None):
nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter) nodes = env.CollectBuildFiles(variant_dir, src_dir, src_filter)
DefaultEnvironment().Append( DefaultEnvironment().Append(PIOBUILDFILES=[env.Object(node) for node in nodes])
PIOBUILDFILES=[env.Object(node) for node in nodes])
def exists(_): def exists(_):

View File

@ -25,10 +25,14 @@ class PlatformioCLI(click.MultiCommand):
@staticmethod @staticmethod
def in_silence(): def in_silence():
args = PlatformioCLI.leftover_args args = PlatformioCLI.leftover_args
return args and any([ return args and any(
args[0] == "debug" and "--interpreter" in " ".join(args), [
args[0] == "upgrade", "--json-output" in args, "--version" in args args[0] == "debug" and "--interpreter" in " ".join(args),
]) args[0] == "upgrade",
"--json-output" in args,
"--version" in args,
]
)
def invoke(self, ctx): def invoke(self, ctx):
PlatformioCLI.leftover_args = ctx.args PlatformioCLI.leftover_args = ctx.args
@ -52,8 +56,7 @@ class PlatformioCLI(click.MultiCommand):
def get_command(self, ctx, cmd_name): def get_command(self, ctx, cmd_name):
mod = None mod = None
try: try:
mod = __import__("platformio.commands." + cmd_name, None, None, mod = __import__("platformio.commands." + cmd_name, None, None, ["cli"])
["cli"])
except ImportError: except ImportError:
try: try:
return self._handle_obsolate_command(cmd_name) return self._handle_obsolate_command(cmd_name)
@ -65,8 +68,10 @@ class PlatformioCLI(click.MultiCommand):
def _handle_obsolate_command(name): def _handle_obsolate_command(name):
if name == "platforms": if name == "platforms":
from platformio.commands import platform from platformio.commands import platform
return platform.cli return platform.cli
if name == "serialports": if name == "serialports":
from platformio.commands import device from platformio.commands import device
return device.cli return device.cli
raise AttributeError() raise AttributeError()

View File

@ -34,9 +34,9 @@ def cli(query, installed, json_output): # pylint: disable=R0912
for board in _get_boards(installed): for board in _get_boards(installed):
if query and query.lower() not in json.dumps(board).lower(): if query and query.lower() not in json.dumps(board).lower():
continue continue
if board['platform'] not in grpboards: if board["platform"] not in grpboards:
grpboards[board['platform']] = [] grpboards[board["platform"]] = []
grpboards[board['platform']].append(board) grpboards[board["platform"]].append(board)
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
for (platform, boards) in sorted(grpboards.items()): for (platform, boards) in sorted(grpboards.items()):
@ -50,11 +50,21 @@ def cli(query, installed, json_output): # pylint: disable=R0912
def print_boards(boards): def print_boards(boards):
click.echo( click.echo(
tabulate([(click.style(b['id'], fg="cyan"), b['mcu'], "%dMHz" % tabulate(
(b['fcpu'] / 1000000), fs.format_filesize( [
b['rom']), fs.format_filesize(b['ram']), b['name']) (
for b in boards], click.style(b["id"], fg="cyan"),
headers=["ID", "MCU", "Frequency", "Flash", "RAM", "Name"])) b["mcu"],
"%dMHz" % (b["fcpu"] / 1000000),
fs.format_filesize(b["rom"]),
fs.format_filesize(b["ram"]),
b["name"],
)
for b in boards
],
headers=["ID", "MCU", "Frequency", "Flash", "RAM", "Name"],
)
)
def _get_boards(installed=False): def _get_boards(installed=False):
@ -66,7 +76,7 @@ def _print_boards_json(query, installed=False):
result = [] result = []
for board in _get_boards(installed): for board in _get_boards(installed):
if query: if query:
search_data = "%s %s" % (board['id'], json.dumps(board).lower()) search_data = "%s %s" % (board["id"], json.dumps(board).lower())
if query.lower() not in search_data.lower(): if query.lower() not in search_data.lower():
continue continue
result.append(board) result.append(board)

View File

@ -28,39 +28,50 @@ from platformio.commands.check.defect import DefectItem
from platformio.commands.check.tools import CheckToolFactory from platformio.commands.check.tools import CheckToolFactory
from platformio.compat import dump_json_to_unicode from platformio.compat import dump_json_to_unicode
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (find_project_dir_above, from platformio.project.helpers import (
get_project_dir, find_project_dir_above,
get_project_include_dir, get_project_dir,
get_project_src_dir) get_project_include_dir,
get_project_src_dir,
)
@click.command("check", short_help="Run a static analysis tool on code") @click.command("check", short_help="Run a static analysis tool on code")
@click.option("-e", "--environment", multiple=True) @click.option("-e", "--environment", multiple=True)
@click.option("-d", @click.option(
"--project-dir", "-d",
default=os.getcwd, "--project-dir",
type=click.Path(exists=True, default=os.getcwd,
file_okay=True, type=click.Path(
dir_okay=True, exists=True, file_okay=True, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True)) )
@click.option("-c", @click.option(
"--project-conf", "-c",
type=click.Path(exists=True, "--project-conf",
file_okay=True, type=click.Path(
dir_okay=False, exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
readable=True, ),
resolve_path=True)) )
@click.option("--filter", multiple=True, help="Pattern: +<include> -<exclude>") @click.option("--filter", multiple=True, help="Pattern: +<include> -<exclude>")
@click.option("--flags", multiple=True) @click.option("--flags", multiple=True)
@click.option("--severity", @click.option(
multiple=True, "--severity", multiple=True, type=click.Choice(DefectItem.SEVERITY_LABELS.values())
type=click.Choice(DefectItem.SEVERITY_LABELS.values())) )
@click.option("-s", "--silent", is_flag=True) @click.option("-s", "--silent", is_flag=True)
@click.option("-v", "--verbose", is_flag=True) @click.option("-v", "--verbose", is_flag=True)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def cli(environment, project_dir, project_conf, filter, flags, severity, def cli(
silent, verbose, json_output): environment,
project_dir,
project_conf,
filter,
flags,
severity,
silent,
verbose,
json_output,
):
# find project directory on upper level # find project directory on upper level
if isfile(project_dir): if isfile(project_dir):
project_dir = find_project_dir_above(project_dir) project_dir = find_project_dir_above(project_dir)
@ -68,15 +79,18 @@ def cli(environment, project_dir, project_conf, filter, flags, severity,
results = [] results = []
with fs.cd(project_dir): with fs.cd(project_dir):
config = ProjectConfig.get_instance( config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini")) project_conf or join(project_dir, "platformio.ini")
)
config.validate(environment) config.validate(environment)
default_envs = config.default_envs() default_envs = config.default_envs()
for envname in config.envs(): for envname in config.envs():
skipenv = any([ skipenv = any(
environment and envname not in environment, not environment [
and default_envs and envname not in default_envs environment and envname not in environment,
]) not environment and default_envs and envname not in default_envs,
]
)
env_options = config.items(env=envname, as_dict=True) env_options = config.items(env=envname, as_dict=True)
env_dump = [] env_dump = []
@ -84,7 +98,8 @@ def cli(environment, project_dir, project_conf, filter, flags, severity,
if k not in ("platform", "framework", "board"): if k not in ("platform", "framework", "board"):
continue continue
env_dump.append( env_dump.append(
"%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v)) "%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v)
)
default_filter = [ default_filter = [
"+<%s/>" % basename(d) "+<%s/>" % basename(d)
@ -94,13 +109,12 @@ def cli(environment, project_dir, project_conf, filter, flags, severity,
tool_options = dict( tool_options = dict(
verbose=verbose, verbose=verbose,
silent=silent, silent=silent,
filter=filter filter=filter or env_options.get("check_filter", default_filter),
or env_options.get("check_filter", default_filter),
flags=flags or env_options.get("check_flags"), flags=flags or env_options.get("check_flags"),
severity=[ severity=[DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH]]
DefectItem.SEVERITY_LABELS[DefectItem.SEVERITY_HIGH] if silent
] if silent else else (severity or env_options.get("check_severity")),
(severity or env_options.get("check_severity"))) )
for tool in env_options.get("check_tool", ["cppcheck"]): for tool in env_options.get("check_tool", ["cppcheck"]):
if skipenv: if skipenv:
@ -109,26 +123,29 @@ def cli(environment, project_dir, project_conf, filter, flags, severity,
if not silent and not json_output: if not silent and not json_output:
print_processing_header(tool, envname, env_dump) print_processing_header(tool, envname, env_dump)
ct = CheckToolFactory.new(tool, project_dir, config, envname, ct = CheckToolFactory.new(
tool_options) tool, project_dir, config, envname, tool_options
)
result = {"env": envname, "tool": tool, "duration": time()} result = {"env": envname, "tool": tool, "duration": time()}
rc = ct.check(on_defect_callback=None if ( rc = ct.check(
json_output or verbose on_defect_callback=None
) else lambda defect: click.echo(repr(defect))) if (json_output or verbose)
else lambda defect: click.echo(repr(defect))
)
result['defects'] = ct.get_defects() result["defects"] = ct.get_defects()
result['duration'] = time() - result['duration'] result["duration"] = time() - result["duration"]
result['succeeded'] = ( result["succeeded"] = rc == 0 and not any(
rc == 0 and not any(d.severity == DefectItem.SEVERITY_HIGH d.severity == DefectItem.SEVERITY_HIGH for d in result["defects"]
for d in result['defects'])) )
results.append(result) results.append(result)
if verbose: if verbose:
click.echo("\n".join(repr(d) for d in result['defects'])) click.echo("\n".join(repr(d) for d in result["defects"]))
if not json_output and not silent: if not json_output and not silent:
if not result['defects']: if not result["defects"]:
click.echo("No defects found") click.echo("No defects found")
print_processing_footer(result) print_processing_footer(result)
@ -145,11 +162,13 @@ def cli(environment, project_dir, project_conf, filter, flags, severity,
def results_to_json(raw): def results_to_json(raw):
results = [] results = []
for item in raw: for item in raw:
item.update({ item.update(
"ignored": item.get("succeeded") is None, {
"succeeded": bool(item.get("succeeded")), "ignored": item.get("succeeded") is None,
"defects": [d.to_json() for d in item.get("defects", [])] "succeeded": bool(item.get("succeeded")),
}) "defects": [d.to_json() for d in item.get("defects", [])],
}
)
results.append(item) results.append(item)
return results return results
@ -157,8 +176,9 @@ def results_to_json(raw):
def print_processing_header(tool, envname, envdump): def print_processing_header(tool, envname, envdump):
click.echo( click.echo(
"Checking %s > %s (%s)" % "Checking %s > %s (%s)"
(click.style(envname, fg="cyan", bold=True), tool, "; ".join(envdump))) % (click.style(envname, fg="cyan", bold=True), tool, "; ".join(envdump))
)
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
@ -166,10 +186,17 @@ def print_processing_header(tool, envname, envdump):
def print_processing_footer(result): def print_processing_footer(result):
is_failed = not result.get("succeeded") is_failed = not result.get("succeeded")
util.print_labeled_bar( util.print_labeled_bar(
"[%s] Took %.2f seconds" % "[%s] Took %.2f seconds"
((click.style("FAILED", fg="red", bold=True) if is_failed else % (
click.style("PASSED", fg="green", bold=True)), result['duration']), (
is_error=is_failed) click.style("FAILED", fg="red", bold=True)
if is_failed
else click.style("PASSED", fg="green", bold=True)
),
result["duration"],
),
is_error=is_failed,
)
def print_defects_stats(results): def print_defects_stats(results):
@ -178,8 +205,7 @@ def print_defects_stats(results):
def _append_defect(component, defect): def _append_defect(component, defect):
if not components.get(component): if not components.get(component):
components[component] = Counter() components[component] = Counter()
components[component].update( components[component].update({DefectItem.SEVERITY_LABELS[defect.severity]: 1})
{DefectItem.SEVERITY_LABELS[defect.severity]: 1})
for result in results: for result in results:
for defect in result.get("defects", []): for defect in result.get("defects", []):
@ -235,20 +261,32 @@ def print_check_summary(results):
status_str = click.style("PASSED", fg="green") status_str = click.style("PASSED", fg="green")
tabular_data.append( tabular_data.append(
(click.style(result['env'], fg="cyan"), result['tool'], status_str, (
util.humanize_duration_time(result.get("duration")))) click.style(result["env"], fg="cyan"),
result["tool"],
status_str,
util.humanize_duration_time(result.get("duration")),
)
)
click.echo(tabulate(tabular_data, click.echo(
headers=[ tabulate(
click.style(s, bold=True) tabular_data,
for s in ("Environment", "Tool", "Status", headers=[
"Duration") click.style(s, bold=True)
]), for s in ("Environment", "Tool", "Status", "Duration")
err=failed_nums) ],
),
err=failed_nums,
)
util.print_labeled_bar( util.print_labeled_bar(
"%s%d succeeded in %s" % "%s%d succeeded in %s"
("%d failed, " % failed_nums if failed_nums else "", succeeded_nums, % (
util.humanize_duration_time(duration)), "%d failed, " % failed_nums if failed_nums else "",
succeeded_nums,
util.humanize_duration_time(duration),
),
is_error=failed_nums, is_error=failed_nums,
fg="red" if failed_nums else "green") fg="red" if failed_nums else "green",
)

View File

@ -29,18 +29,19 @@ class DefectItem(object):
SEVERITY_LOW = 4 SEVERITY_LOW = 4
SEVERITY_LABELS = {4: "low", 2: "medium", 1: "high"} SEVERITY_LABELS = {4: "low", 2: "medium", 1: "high"}
def __init__(self, def __init__(
severity, self,
category, severity,
message, category,
file="unknown", message,
line=0, file="unknown",
column=0, line=0,
id=None, column=0,
callstack=None, id=None,
cwe=None): callstack=None,
assert severity in (self.SEVERITY_HIGH, self.SEVERITY_MEDIUM, cwe=None,
self.SEVERITY_LOW) ):
assert severity in (self.SEVERITY_HIGH, self.SEVERITY_MEDIUM, self.SEVERITY_LOW)
self.severity = severity self.severity = severity
self.category = category self.category = category
self.message = message self.message = message
@ -61,14 +62,14 @@ class DefectItem(object):
defect_color = "yellow" defect_color = "yellow"
format_str = "{file}:{line}: [{severity}:{category}] {message} {id}" format_str = "{file}:{line}: [{severity}:{category}] {message} {id}"
return format_str.format(severity=click.style( return format_str.format(
self.SEVERITY_LABELS[self.severity], fg=defect_color), severity=click.style(self.SEVERITY_LABELS[self.severity], fg=defect_color),
category=click.style(self.category.lower(), category=click.style(self.category.lower(), fg=defect_color),
fg=defect_color), file=click.style(self.file, bold=True),
file=click.style(self.file, bold=True), message=self.message,
message=self.message, line=self.line,
line=self.line, id="%s" % "[%s]" % self.id if self.id else "",
id="%s" % "[%s]" % self.id if self.id else "") )
def __or__(self, defect): def __or__(self, defect):
return self.severity | defect.severity return self.severity | defect.severity
@ -90,5 +91,5 @@ class DefectItem(object):
"column": self.column, "column": self.column,
"callstack": self.callstack, "callstack": self.callstack,
"id": self.id, "id": self.id,
"cwe": self.cwe "cwe": self.cwe,
} }

View File

@ -18,7 +18,6 @@ from platformio.commands.check.tools.cppcheck import CppcheckCheckTool
class CheckToolFactory(object): class CheckToolFactory(object):
@staticmethod @staticmethod
def new(tool, project_dir, config, envname, options): def new(tool, project_dir, config, envname, options):
cls = None cls = None
@ -27,6 +26,5 @@ class CheckToolFactory(object):
elif tool == "clangtidy": elif tool == "clangtidy":
cls = ClangtidyCheckTool cls = ClangtidyCheckTool
else: else:
raise exception.PlatformioException("Unknown check tool `%s`" % raise exception.PlatformioException("Unknown check tool `%s`" % tool)
tool)
return cls(project_dir, config, envname, options) return cls(project_dir, config, envname, options)

View File

@ -20,7 +20,6 @@ from platformio.project.helpers import get_project_dir, load_project_ide_data
class CheckToolBase(object): # pylint: disable=too-many-instance-attributes class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
def __init__(self, project_dir, config, envname, options): def __init__(self, project_dir, config, envname, options):
self.config = config self.config = config
self.envname = envname self.envname = envname
@ -35,14 +34,15 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
# detect all defects by default # detect all defects by default
if not self.options.get("severity"): if not self.options.get("severity"):
self.options['severity'] = [ self.options["severity"] = [
DefectItem.SEVERITY_LOW, DefectItem.SEVERITY_MEDIUM, DefectItem.SEVERITY_LOW,
DefectItem.SEVERITY_HIGH DefectItem.SEVERITY_MEDIUM,
DefectItem.SEVERITY_HIGH,
] ]
# cast to severity by ids # cast to severity by ids
self.options['severity'] = [ self.options["severity"] = [
s if isinstance(s, int) else DefectItem.severity_to_int(s) s if isinstance(s, int) else DefectItem.severity_to_int(s)
for s in self.options['severity'] for s in self.options["severity"]
] ]
def _load_cpp_data(self, project_dir, envname): def _load_cpp_data(self, project_dir, envname):
@ -51,8 +51,7 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
return return
self.cpp_includes = data.get("includes", []) self.cpp_includes = data.get("includes", [])
self.cpp_defines = data.get("defines", []) self.cpp_defines = data.get("defines", [])
self.cpp_defines.extend( self.cpp_defines.extend(self._get_toolchain_defines(data.get("cc_path")))
self._get_toolchain_defines(data.get("cc_path")))
def get_flags(self, tool): def get_flags(self, tool):
result = [] result = []
@ -61,18 +60,16 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
if ":" not in flag: if ":" not in flag:
result.extend([f for f in flag.split(" ") if f]) result.extend([f for f in flag.split(" ") if f])
elif flag.startswith("%s:" % tool): elif flag.startswith("%s:" % tool):
result.extend( result.extend([f for f in flag.split(":", 1)[1].split(" ") if f])
[f for f in flag.split(":", 1)[1].split(" ") if f])
return result return result
@staticmethod @staticmethod
def _get_toolchain_defines(cc_path): def _get_toolchain_defines(cc_path):
defines = [] defines = []
result = proc.exec_command("echo | %s -dM -E -x c++ -" % cc_path, result = proc.exec_command("echo | %s -dM -E -x c++ -" % cc_path, shell=True)
shell=True)
for line in result['out'].split("\n"): for line in result["out"].split("\n"):
tokens = line.strip().split(" ", 2) tokens = line.strip().split(" ", 2)
if not tokens or tokens[0] != "#define": if not tokens or tokens[0] != "#define":
continue continue
@ -105,7 +102,7 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
click.echo(line) click.echo(line)
return return
if defect.severity not in self.options['severity']: if defect.severity not in self.options["severity"]:
return return
self._defects.append(defect) self._defects.append(defect)
@ -125,8 +122,9 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
def get_project_src_files(self): def get_project_src_files(self):
file_extensions = ["h", "hpp", "c", "cc", "cpp", "ino"] file_extensions = ["h", "hpp", "c", "cc", "cpp", "ino"]
return fs.match_src_files(get_project_dir(), return fs.match_src_files(
self.options.get("filter"), file_extensions) get_project_dir(), self.options.get("filter"), file_extensions
)
def check(self, on_defect_callback=None): def check(self, on_defect_callback=None):
self._on_defect_callback = on_defect_callback self._on_defect_callback = on_defect_callback
@ -137,7 +135,8 @@ class CheckToolBase(object): # pylint: disable=too-many-instance-attributes
proc.exec_command( proc.exec_command(
cmd, cmd,
stdout=proc.LineBufferedAsyncPipe(self.on_tool_output), stdout=proc.LineBufferedAsyncPipe(self.on_tool_output),
stderr=proc.LineBufferedAsyncPipe(self.on_tool_output)) stderr=proc.LineBufferedAsyncPipe(self.on_tool_output),
)
self.clean_up() self.clean_up()

View File

@ -21,10 +21,8 @@ from platformio.managers.core import get_core_package_dir
class ClangtidyCheckTool(CheckToolBase): class ClangtidyCheckTool(CheckToolBase):
def tool_output_filter(self, line): def tool_output_filter(self, line):
if not self.options.get( if not self.options.get("verbose") and "[clang-diagnostic-error]" in line:
"verbose") and "[clang-diagnostic-error]" in line:
return "" return ""
if "[CommonOptionsParser]" in line: if "[CommonOptionsParser]" in line:
@ -37,8 +35,7 @@ class ClangtidyCheckTool(CheckToolBase):
return "" return ""
def parse_defect(self, raw_line): def parse_defect(self, raw_line):
match = re.match(r"^(.*):(\d+):(\d+):\s+([^:]+):\s(.+)\[([^]]+)\]$", match = re.match(r"^(.*):(\d+):(\d+):\s+([^:]+):\s(.+)\[([^]]+)\]$", raw_line)
raw_line)
if not match: if not match:
return raw_line return raw_line
@ -50,8 +47,7 @@ class ClangtidyCheckTool(CheckToolBase):
elif category == "warning": elif category == "warning":
severity = DefectItem.SEVERITY_MEDIUM severity = DefectItem.SEVERITY_MEDIUM
return DefectItem(severity, category, message, file_, line, column, return DefectItem(severity, category, message, file_, line, column, defect_id)
defect_id)
def configure_command(self): def configure_command(self):
tool_path = join(get_core_package_dir("tool-clangtidy"), "clang-tidy") tool_path = join(get_core_package_dir("tool-clangtidy"), "clang-tidy")

View File

@ -23,29 +23,42 @@ from platformio.project.helpers import get_project_core_dir
class CppcheckCheckTool(CheckToolBase): class CppcheckCheckTool(CheckToolBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._tmp_files = [] self._tmp_files = []
self.defect_fields = [ self.defect_fields = [
"severity", "message", "file", "line", "column", "callstack", "severity",
"cwe", "id" "message",
"file",
"line",
"column",
"callstack",
"cwe",
"id",
] ]
super(CppcheckCheckTool, self).__init__(*args, **kwargs) super(CppcheckCheckTool, self).__init__(*args, **kwargs)
def tool_output_filter(self, line): def tool_output_filter(self, line):
if not self.options.get( if (
"verbose") and "--suppress=unmatchedSuppression:" in line: not self.options.get("verbose")
and "--suppress=unmatchedSuppression:" in line
):
return "" return ""
if any(msg in line for msg in ("No C or C++ source files found", if any(
"unrecognized command line option")): msg in line
for msg in (
"No C or C++ source files found",
"unrecognized command line option",
)
):
self._bad_input = True self._bad_input = True
return line return line
def parse_defect(self, raw_line): def parse_defect(self, raw_line):
if "<&PIO&>" not in raw_line or any(f not in raw_line if "<&PIO&>" not in raw_line or any(
for f in self.defect_fields): f not in raw_line for f in self.defect_fields
):
return None return None
args = dict() args = dict()
@ -54,13 +67,13 @@ class CppcheckCheckTool(CheckToolBase):
name, value = field.split("=", 1) name, value = field.split("=", 1)
args[name] = value args[name] = value
args['category'] = args['severity'] args["category"] = args["severity"]
if args['severity'] == "error": if args["severity"] == "error":
args['severity'] = DefectItem.SEVERITY_HIGH args["severity"] = DefectItem.SEVERITY_HIGH
elif args['severity'] == "warning": elif args["severity"] == "warning":
args['severity'] = DefectItem.SEVERITY_MEDIUM args["severity"] = DefectItem.SEVERITY_MEDIUM
else: else:
args['severity'] = DefectItem.SEVERITY_LOW args["severity"] = DefectItem.SEVERITY_LOW
return DefectItem(**args) return DefectItem(**args)
@ -68,20 +81,26 @@ class CppcheckCheckTool(CheckToolBase):
tool_path = join(get_core_package_dir("tool-cppcheck"), "cppcheck") tool_path = join(get_core_package_dir("tool-cppcheck"), "cppcheck")
cmd = [ cmd = [
tool_path, "--error-exitcode=1", tool_path,
"--verbose" if self.options.get("verbose") else "--quiet" "--error-exitcode=1",
"--verbose" if self.options.get("verbose") else "--quiet",
] ]
cmd.append('--template="%s"' % "<&PIO&>".join( cmd.append(
["{0}={{{0}}}".format(f) for f in self.defect_fields])) '--template="%s"'
% "<&PIO&>".join(["{0}={{{0}}}".format(f) for f in self.defect_fields])
)
flags = self.get_flags("cppcheck") flags = self.get_flags("cppcheck")
if not self.is_flag_set("--platform", flags): if not self.is_flag_set("--platform", flags):
cmd.append("--platform=unspecified") cmd.append("--platform=unspecified")
if not self.is_flag_set("--enable", flags): if not self.is_flag_set("--enable", flags):
enabled_checks = [ enabled_checks = [
"warning", "style", "performance", "portability", "warning",
"unusedFunction" "style",
"performance",
"portability",
"unusedFunction",
] ]
cmd.append("--enable=%s" % ",".join(enabled_checks)) cmd.append("--enable=%s" % ",".join(enabled_checks))

View File

@ -48,37 +48,37 @@ def validate_path(ctx, param, value): # pylint: disable=unused-argument
@click.command("ci", short_help="Continuous Integration") @click.command("ci", short_help="Continuous Integration")
@click.argument("src", nargs=-1, callback=validate_path) @click.argument("src", nargs=-1, callback=validate_path)
@click.option("-l", @click.option("-l", "--lib", multiple=True, callback=validate_path, metavar="DIRECTORY")
"--lib",
multiple=True,
callback=validate_path,
metavar="DIRECTORY")
@click.option("--exclude", multiple=True) @click.option("--exclude", multiple=True)
@click.option("-b", @click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards)
"--board", @click.option(
multiple=True, "--build-dir",
metavar="ID", default=mkdtemp,
callback=validate_boards) type=click.Path(file_okay=False, dir_okay=True, writable=True, resolve_path=True),
@click.option("--build-dir", )
default=mkdtemp,
type=click.Path(file_okay=False,
dir_okay=True,
writable=True,
resolve_path=True))
@click.option("--keep-build-dir", is_flag=True) @click.option("--keep-build-dir", is_flag=True)
@click.option("-c", @click.option(
"--project-conf", "-c",
type=click.Path(exists=True, "--project-conf",
file_okay=True, type=click.Path(
dir_okay=False, exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
readable=True, ),
resolve_path=True)) )
@click.option("-O", "--project-option", multiple=True) @click.option("-O", "--project-option", multiple=True)
@click.option("-v", "--verbose", is_flag=True) @click.option("-v", "--verbose", is_flag=True)
@click.pass_context @click.pass_context
def cli( # pylint: disable=too-many-arguments, too-many-branches def cli( # pylint: disable=too-many-arguments, too-many-branches
ctx, src, lib, exclude, board, build_dir, keep_build_dir, project_conf, ctx,
project_option, verbose): src,
lib,
exclude,
board,
build_dir,
keep_build_dir,
project_conf,
project_option,
verbose,
):
if not src and getenv("PLATFORMIO_CI_SRC"): if not src and getenv("PLATFORMIO_CI_SRC"):
src = validate_path(ctx, None, getenv("PLATFORMIO_CI_SRC").split(":")) src = validate_path(ctx, None, getenv("PLATFORMIO_CI_SRC").split(":"))
@ -110,10 +110,9 @@ def cli( # pylint: disable=too-many-arguments, too-many-branches
_exclude_contents(build_dir, exclude) _exclude_contents(build_dir, exclude)
# initialise project # initialise project
ctx.invoke(cmd_init, ctx.invoke(
project_dir=build_dir, cmd_init, project_dir=build_dir, board=board, project_option=project_option
board=board, )
project_option=project_option)
# process project # process project
ctx.invoke(cmd_run, project_dir=build_dir, verbose=verbose) ctx.invoke(cmd_run, project_dir=build_dir, verbose=verbose)
@ -127,27 +126,27 @@ def _copy_contents(dst_dir, contents):
for path in contents: for path in contents:
if isdir(path): if isdir(path):
items['dirs'].add(path) items["dirs"].add(path)
elif isfile(path): elif isfile(path):
items['files'].add(path) items["files"].add(path)
dst_dir_name = basename(dst_dir) dst_dir_name = basename(dst_dir)
if dst_dir_name == "src" and len(items['dirs']) == 1: if dst_dir_name == "src" and len(items["dirs"]) == 1:
copytree(list(items['dirs']).pop(), dst_dir, symlinks=True) copytree(list(items["dirs"]).pop(), dst_dir, symlinks=True)
else: else:
if not isdir(dst_dir): if not isdir(dst_dir):
makedirs(dst_dir) makedirs(dst_dir)
for d in items['dirs']: for d in items["dirs"]:
copytree(d, join(dst_dir, basename(d)), symlinks=True) copytree(d, join(dst_dir, basename(d)), symlinks=True)
if not items['files']: if not items["files"]:
return return
if dst_dir_name == "lib": if dst_dir_name == "lib":
dst_dir = join(dst_dir, mkdtemp(dir=dst_dir)) dst_dir = join(dst_dir, mkdtemp(dir=dst_dir))
for f in items['files']: for f in items["files"]:
dst_file = join(dst_dir, basename(f)) dst_file = join(dst_dir, basename(f))
if f == dst_file: if f == dst_file:
continue continue

View File

@ -53,8 +53,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
if not isdir(get_project_cache_dir()): if not isdir(get_project_cache_dir()):
os.makedirs(get_project_cache_dir()) os.makedirs(get_project_cache_dir())
self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), self._gdbsrc_dir = mkdtemp(dir=get_project_cache_dir(), prefix=".piodebug-")
prefix=".piodebug-")
self._target_is_run = False self._target_is_run = False
self._last_server_activity = 0 self._last_server_activity = 0
@ -70,25 +69,28 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
"PROG_PATH": prog_path, "PROG_PATH": prog_path,
"PROG_DIR": dirname(prog_path), "PROG_DIR": dirname(prog_path),
"PROG_NAME": basename(splitext(prog_path)[0]), "PROG_NAME": basename(splitext(prog_path)[0]),
"DEBUG_PORT": self.debug_options['port'], "DEBUG_PORT": self.debug_options["port"],
"UPLOAD_PROTOCOL": self.debug_options['upload_protocol'], "UPLOAD_PROTOCOL": self.debug_options["upload_protocol"],
"INIT_BREAK": self.debug_options['init_break'] or "", "INIT_BREAK": self.debug_options["init_break"] or "",
"LOAD_CMDS": "\n".join(self.debug_options['load_cmds'] or []), "LOAD_CMDS": "\n".join(self.debug_options["load_cmds"] or []),
} }
self._debug_server.spawn(patterns) self._debug_server.spawn(patterns)
if not patterns['DEBUG_PORT']: if not patterns["DEBUG_PORT"]:
patterns['DEBUG_PORT'] = self._debug_server.get_debug_port() patterns["DEBUG_PORT"] = self._debug_server.get_debug_port()
self.generate_pioinit(self._gdbsrc_dir, patterns) self.generate_pioinit(self._gdbsrc_dir, patterns)
# start GDB client # start GDB client
args = [ args = [
"piogdb", "piogdb",
"-q", "-q",
"--directory", self._gdbsrc_dir, "--directory",
"--directory", self.project_dir, self._gdbsrc_dir,
"-l", "10" "--directory",
self.project_dir,
"-l",
"10",
] # yapf: disable ] # yapf: disable
args.extend(self.args) args.extend(self.args)
if not gdb_path: if not gdb_path:
@ -96,13 +98,11 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
gdb_data_dir = self._get_data_dir(gdb_path) gdb_data_dir = self._get_data_dir(gdb_path)
if gdb_data_dir: if gdb_data_dir:
args.extend(["--data-directory", gdb_data_dir]) args.extend(["--data-directory", gdb_data_dir])
args.append(patterns['PROG_PATH']) args.append(patterns["PROG_PATH"])
return reactor.spawnProcess(self, return reactor.spawnProcess(
gdb_path, self, gdb_path, args, path=self.project_dir, env=os.environ
args, )
path=self.project_dir,
env=os.environ)
@staticmethod @staticmethod
def _get_data_dir(gdb_path): def _get_data_dir(gdb_path):
@ -112,8 +112,9 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
return gdb_data_dir if isdir(gdb_data_dir) else None return gdb_data_dir if isdir(gdb_data_dir) else None
def generate_pioinit(self, dst_dir, patterns): def generate_pioinit(self, dst_dir, patterns):
server_exe = (self.debug_options.get("server") server_exe = (
or {}).get("executable", "").lower() (self.debug_options.get("server") or {}).get("executable", "").lower()
)
if "jlink" in server_exe: if "jlink" in server_exe:
cfg = initcfgs.GDB_JLINK_INIT_CONFIG cfg = initcfgs.GDB_JLINK_INIT_CONFIG
elif "st-util" in server_exe: elif "st-util" in server_exe:
@ -122,43 +123,43 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
cfg = initcfgs.GDB_MSPDEBUG_INIT_CONFIG cfg = initcfgs.GDB_MSPDEBUG_INIT_CONFIG
elif "qemu" in server_exe: elif "qemu" in server_exe:
cfg = initcfgs.GDB_QEMU_INIT_CONFIG cfg = initcfgs.GDB_QEMU_INIT_CONFIG
elif self.debug_options['require_debug_port']: elif self.debug_options["require_debug_port"]:
cfg = initcfgs.GDB_BLACKMAGIC_INIT_CONFIG cfg = initcfgs.GDB_BLACKMAGIC_INIT_CONFIG
else: else:
cfg = initcfgs.GDB_DEFAULT_INIT_CONFIG cfg = initcfgs.GDB_DEFAULT_INIT_CONFIG
commands = cfg.split("\n") commands = cfg.split("\n")
if self.debug_options['init_cmds']: if self.debug_options["init_cmds"]:
commands = self.debug_options['init_cmds'] commands = self.debug_options["init_cmds"]
commands.extend(self.debug_options['extra_cmds']) commands.extend(self.debug_options["extra_cmds"])
if not any("define pio_reset_target" in cmd for cmd in commands): if not any("define pio_reset_target" in cmd for cmd in commands):
commands = [ commands = [
"define pio_reset_target", "define pio_reset_target",
" echo Warning! Undefined pio_reset_target command\\n", " echo Warning! Undefined pio_reset_target command\\n",
" mon reset", " mon reset",
"end" "end",
] + commands # yapf: disable ] + commands # yapf: disable
if not any("define pio_reset_halt_target" in cmd for cmd in commands): if not any("define pio_reset_halt_target" in cmd for cmd in commands):
commands = [ commands = [
"define pio_reset_halt_target", "define pio_reset_halt_target",
" echo Warning! Undefined pio_reset_halt_target command\\n", " echo Warning! Undefined pio_reset_halt_target command\\n",
" mon reset halt", " mon reset halt",
"end" "end",
] + commands # yapf: disable ] + commands # yapf: disable
if not any("define pio_restart_target" in cmd for cmd in commands): if not any("define pio_restart_target" in cmd for cmd in commands):
commands += [ commands += [
"define pio_restart_target", "define pio_restart_target",
" pio_reset_halt_target", " pio_reset_halt_target",
" $INIT_BREAK", " $INIT_BREAK",
" %s" % ("continue" if patterns['INIT_BREAK'] else "next"), " %s" % ("continue" if patterns["INIT_BREAK"] else "next"),
"end" "end",
] # yapf: disable ] # yapf: disable
banner = [ banner = [
"echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n", "echo PlatformIO Unified Debugger -> http://bit.ly/pio-debug\\n",
"echo PlatformIO: debug_tool = %s\\n" % self.debug_options['tool'], "echo PlatformIO: debug_tool = %s\\n" % self.debug_options["tool"],
"echo PlatformIO: Initializing remote target...\\n" "echo PlatformIO: Initializing remote target...\\n",
] ]
footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER] footer = ["echo %s\\n" % self.INIT_COMPLETED_BANNER]
commands = banner + commands + footer commands = banner + commands + footer
@ -214,8 +215,7 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
self._handle_error(data) self._handle_error(data)
# go to init break automatically # go to init break automatically
if self.INIT_COMPLETED_BANNER.encode() in data: if self.INIT_COMPLETED_BANNER.encode() in data:
self._auto_continue_timer = task.LoopingCall( self._auto_continue_timer = task.LoopingCall(self._auto_exec_continue)
self._auto_exec_continue)
self._auto_continue_timer.start(0.1) self._auto_continue_timer.start(0.1)
def errReceived(self, data): def errReceived(self, data):
@ -236,29 +236,34 @@ class GDBClient(BaseProcess): # pylint: disable=too-many-instance-attributes
self._auto_continue_timer.stop() self._auto_continue_timer.stop()
self._auto_continue_timer = None self._auto_continue_timer = None
if not self.debug_options['init_break'] or self._target_is_run: if not self.debug_options["init_break"] or self._target_is_run:
return return
self.console_log( self.console_log(
"PlatformIO: Resume the execution to `debug_init_break = %s`" % "PlatformIO: Resume the execution to `debug_init_break = %s`"
self.debug_options['init_break']) % self.debug_options["init_break"]
self.console_log("PlatformIO: More configuration options -> " )
"http://bit.ly/pio-debug") self.console_log(
self.transport.write(b"0-exec-continue\n" if helpers. "PlatformIO: More configuration options -> " "http://bit.ly/pio-debug"
is_mi_mode(self.args) else b"continue\n") )
self.transport.write(
b"0-exec-continue\n" if helpers.is_mi_mode(self.args) else b"continue\n"
)
self._target_is_run = True self._target_is_run = True
def _handle_error(self, data): def _handle_error(self, data):
if (self.PIO_SRC_NAME.encode() not in data if self.PIO_SRC_NAME.encode() not in data or b"Error in sourced" not in data:
or b"Error in sourced" not in data):
return return
configuration = {"debug": self.debug_options, "env": self.env_options} configuration = {"debug": self.debug_options, "env": self.env_options}
exd = re.sub(r'\\(?!")', "/", json.dumps(configuration)) exd = re.sub(r'\\(?!")', "/", json.dumps(configuration))
exd = re.sub(r'"(?:[a-z]\:)?((/[^"/]+)+)"', exd = re.sub(
lambda m: '"%s"' % join(*m.group(1).split("/")[-2:]), exd, r'"(?:[a-z]\:)?((/[^"/]+)+)"',
re.I | re.M) lambda m: '"%s"' % join(*m.group(1).split("/")[-2:]),
exd,
re.I | re.M,
)
mp = MeasurementProtocol() mp = MeasurementProtocol()
mp['exd'] = "DebugGDBPioInitError: %s" % exd mp["exd"] = "DebugGDBPioInitError: %s" % exd
mp['exf'] = 1 mp["exf"] = 1
mp.send("exception") mp.send("exception")
self.transport.loseConnection() self.transport.loseConnection()

View File

@ -25,35 +25,35 @@ from platformio import exception, fs, proc, util
from platformio.commands.debug import helpers from platformio.commands.debug import helpers
from platformio.managers.core import inject_contrib_pysite from platformio.managers.core import inject_contrib_pysite
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (is_platformio_project, from platformio.project.helpers import is_platformio_project, load_project_ide_data
load_project_ide_data)
@click.command("debug", @click.command(
context_settings=dict(ignore_unknown_options=True), "debug",
short_help="PIO Unified Debugger") context_settings=dict(ignore_unknown_options=True),
@click.option("-d", short_help="PIO Unified Debugger",
"--project-dir", )
default=os.getcwd, @click.option(
type=click.Path(exists=True, "-d",
file_okay=False, "--project-dir",
dir_okay=True, default=os.getcwd,
writable=True, type=click.Path(
resolve_path=True)) exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
@click.option("-c", ),
"--project-conf", )
type=click.Path(exists=True, @click.option(
file_okay=True, "-c",
dir_okay=False, "--project-conf",
readable=True, type=click.Path(
resolve_path=True)) exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
),
)
@click.option("--environment", "-e", metavar="<environment>") @click.option("--environment", "-e", metavar="<environment>")
@click.option("--verbose", "-v", is_flag=True) @click.option("--verbose", "-v", is_flag=True)
@click.option("--interface", type=click.Choice(["gdb"])) @click.option("--interface", type=click.Choice(["gdb"]))
@click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED) @click.argument("__unprocessed", nargs=-1, type=click.UNPROCESSED)
@click.pass_context @click.pass_context
def cli(ctx, project_dir, project_conf, environment, verbose, interface, def cli(ctx, project_dir, project_conf, environment, verbose, interface, __unprocessed):
__unprocessed):
# use env variables from Eclipse or CLion # use env variables from Eclipse or CLion
for sysenv in ("CWD", "PWD", "PLATFORMIO_PROJECT_DIR"): for sysenv in ("CWD", "PWD", "PLATFORMIO_PROJECT_DIR"):
if is_platformio_project(project_dir): if is_platformio_project(project_dir):
@ -63,7 +63,8 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface,
with fs.cd(project_dir): with fs.cd(project_dir):
config = ProjectConfig.get_instance( config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini")) project_conf or join(project_dir, "platformio.ini")
)
config.validate(envs=[environment] if environment else None) config.validate(envs=[environment] if environment else None)
env_name = environment or helpers.get_default_debug_env(config) env_name = environment or helpers.get_default_debug_env(config)
@ -74,68 +75,64 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface,
assert debug_options assert debug_options
if not interface: if not interface:
return helpers.predebug_project(ctx, project_dir, env_name, False, return helpers.predebug_project(ctx, project_dir, env_name, False, verbose)
verbose)
configuration = load_project_ide_data(project_dir, env_name) configuration = load_project_ide_data(project_dir, env_name)
if not configuration: if not configuration:
raise exception.DebugInvalidOptions( raise exception.DebugInvalidOptions("Could not load debug configuration")
"Could not load debug configuration")
if "--version" in __unprocessed: if "--version" in __unprocessed:
result = proc.exec_command([configuration['gdb_path'], "--version"]) result = proc.exec_command([configuration["gdb_path"], "--version"])
if result['returncode'] == 0: if result["returncode"] == 0:
return click.echo(result['out']) return click.echo(result["out"])
raise exception.PlatformioException("\n".join( raise exception.PlatformioException("\n".join([result["out"], result["err"]]))
[result['out'], result['err']]))
try: try:
fs.ensure_udev_rules() fs.ensure_udev_rules()
except exception.InvalidUdevRules as e: except exception.InvalidUdevRules as e:
for line in str(e).split("\n") + [""]: for line in str(e).split("\n") + [""]:
click.echo( click.echo(
('~"%s\\n"' if helpers.is_mi_mode(__unprocessed) else "%s") % ('~"%s\\n"' if helpers.is_mi_mode(__unprocessed) else "%s") % line
line) )
debug_options['load_cmds'] = helpers.configure_esp32_load_cmds( debug_options["load_cmds"] = helpers.configure_esp32_load_cmds(
debug_options, configuration) debug_options, configuration
)
rebuild_prog = False rebuild_prog = False
preload = debug_options['load_cmds'] == ["preload"] preload = debug_options["load_cmds"] == ["preload"]
load_mode = debug_options['load_mode'] load_mode = debug_options["load_mode"]
if load_mode == "always": if load_mode == "always":
rebuild_prog = ( rebuild_prog = preload or not helpers.has_debug_symbols(
preload configuration["prog_path"]
or not helpers.has_debug_symbols(configuration['prog_path'])) )
elif load_mode == "modified": elif load_mode == "modified":
rebuild_prog = ( rebuild_prog = helpers.is_prog_obsolete(
helpers.is_prog_obsolete(configuration['prog_path']) configuration["prog_path"]
or not helpers.has_debug_symbols(configuration['prog_path'])) ) or not helpers.has_debug_symbols(configuration["prog_path"])
else: else:
rebuild_prog = not isfile(configuration['prog_path']) rebuild_prog = not isfile(configuration["prog_path"])
if preload or (not rebuild_prog and load_mode != "always"): if preload or (not rebuild_prog and load_mode != "always"):
# don't load firmware through debug server # don't load firmware through debug server
debug_options['load_cmds'] = [] debug_options["load_cmds"] = []
if rebuild_prog: if rebuild_prog:
if helpers.is_mi_mode(__unprocessed): if helpers.is_mi_mode(__unprocessed):
click.echo('~"Preparing firmware for debugging...\\n"') click.echo('~"Preparing firmware for debugging...\\n"')
output = helpers.GDBBytesIO() output = helpers.GDBBytesIO()
with util.capture_std_streams(output): with util.capture_std_streams(output):
helpers.predebug_project(ctx, project_dir, env_name, preload, helpers.predebug_project(ctx, project_dir, env_name, preload, verbose)
verbose)
output.close() output.close()
else: else:
click.echo("Preparing firmware for debugging...") click.echo("Preparing firmware for debugging...")
helpers.predebug_project(ctx, project_dir, env_name, preload, helpers.predebug_project(ctx, project_dir, env_name, preload, verbose)
verbose)
# save SHA sum of newly created prog # save SHA sum of newly created prog
if load_mode == "modified": if load_mode == "modified":
helpers.is_prog_obsolete(configuration['prog_path']) helpers.is_prog_obsolete(configuration["prog_path"])
if not isfile(configuration['prog_path']): if not isfile(configuration["prog_path"]):
raise exception.DebugInvalidOptions("Program/firmware is missed") raise exception.DebugInvalidOptions("Program/firmware is missed")
# run debugging client # run debugging client
@ -143,7 +140,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface,
from platformio.commands.debug.client import GDBClient, reactor from platformio.commands.debug.client import GDBClient, reactor
client = GDBClient(project_dir, __unprocessed, debug_options, env_options) client = GDBClient(project_dir, __unprocessed, debug_options, env_options)
client.spawn(configuration['gdb_path'], configuration['prog_path']) client.spawn(configuration["gdb_path"], configuration["prog_path"])
signal.signal(signal.SIGINT, lambda *args, **kwargs: None) signal.signal(signal.SIGINT, lambda *args, **kwargs: None)
reactor.run() reactor.run()

View File

@ -20,8 +20,7 @@ from io import BytesIO
from os.path import isfile from os.path import isfile
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.commands.platform import \ from platformio.commands.platform import platform_install as cmd_platform_install
platform_install as cmd_platform_install
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
from platformio.managers.platform import PlatformFactory from platformio.managers.platform import PlatformFactory
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
@ -57,41 +56,41 @@ def get_default_debug_env(config):
def predebug_project(ctx, project_dir, env_name, preload, verbose): def predebug_project(ctx, project_dir, env_name, preload, verbose):
ctx.invoke(cmd_run, ctx.invoke(
project_dir=project_dir, cmd_run,
environment=[env_name], project_dir=project_dir,
target=["debug"] + (["upload"] if preload else []), environment=[env_name],
verbose=verbose) target=["debug"] + (["upload"] if preload else []),
verbose=verbose,
)
if preload: if preload:
time.sleep(5) time.sleep(5)
def validate_debug_options(cmd_ctx, env_options): def validate_debug_options(cmd_ctx, env_options):
def _cleanup_cmds(items): def _cleanup_cmds(items):
items = ProjectConfig.parse_multi_values(items) items = ProjectConfig.parse_multi_values(items)
return [ return ["$LOAD_CMDS" if item == "$LOAD_CMD" else item for item in items]
"$LOAD_CMDS" if item == "$LOAD_CMD" else item for item in items
]
try: try:
platform = PlatformFactory.newPlatform(env_options['platform']) platform = PlatformFactory.newPlatform(env_options["platform"])
except exception.UnknownPlatform: except exception.UnknownPlatform:
cmd_ctx.invoke(cmd_platform_install, cmd_ctx.invoke(
platforms=[env_options['platform']], cmd_platform_install,
skip_default_package=True) platforms=[env_options["platform"]],
platform = PlatformFactory.newPlatform(env_options['platform']) skip_default_package=True,
)
platform = PlatformFactory.newPlatform(env_options["platform"])
board_config = platform.board_config(env_options['board']) board_config = platform.board_config(env_options["board"])
tool_name = board_config.get_debug_tool_name(env_options.get("debug_tool")) tool_name = board_config.get_debug_tool_name(env_options.get("debug_tool"))
tool_settings = board_config.get("debug", {}).get("tools", tool_settings = board_config.get("debug", {}).get("tools", {}).get(tool_name, {})
{}).get(tool_name, {})
server_options = None server_options = None
# specific server per a system # specific server per a system
if isinstance(tool_settings.get("server", {}), list): if isinstance(tool_settings.get("server", {}), list):
for item in tool_settings['server'][:]: for item in tool_settings["server"][:]:
tool_settings['server'] = item tool_settings["server"] = item
if util.get_systype() in item.get("system", []): if util.get_systype() in item.get("system", []):
break break
@ -100,76 +99,87 @@ def validate_debug_options(cmd_ctx, env_options):
server_options = { server_options = {
"cwd": None, "cwd": None,
"executable": None, "executable": None,
"arguments": env_options.get("debug_server") "arguments": env_options.get("debug_server"),
} }
server_options['executable'] = server_options['arguments'][0] server_options["executable"] = server_options["arguments"][0]
server_options['arguments'] = server_options['arguments'][1:] server_options["arguments"] = server_options["arguments"][1:]
elif "server" in tool_settings: elif "server" in tool_settings:
server_package = tool_settings['server'].get("package") server_package = tool_settings["server"].get("package")
server_package_dir = platform.get_package_dir( server_package_dir = (
server_package) if server_package else None platform.get_package_dir(server_package) if server_package else None
)
if server_package and not server_package_dir: if server_package and not server_package_dir:
platform.install_packages(with_packages=[server_package], platform.install_packages(
skip_default_package=True, with_packages=[server_package], skip_default_package=True, silent=True
silent=True) )
server_package_dir = platform.get_package_dir(server_package) server_package_dir = platform.get_package_dir(server_package)
server_options = dict( server_options = dict(
cwd=server_package_dir if server_package else None, cwd=server_package_dir if server_package else None,
executable=tool_settings['server'].get("executable"), executable=tool_settings["server"].get("executable"),
arguments=[ arguments=[
a.replace("$PACKAGE_DIR", server_package_dir) a.replace("$PACKAGE_DIR", server_package_dir)
if server_package_dir else a if server_package_dir
for a in tool_settings['server'].get("arguments", []) else a
]) for a in tool_settings["server"].get("arguments", [])
],
)
extra_cmds = _cleanup_cmds(env_options.get("debug_extra_cmds")) extra_cmds = _cleanup_cmds(env_options.get("debug_extra_cmds"))
extra_cmds.extend(_cleanup_cmds(tool_settings.get("extra_cmds"))) extra_cmds.extend(_cleanup_cmds(tool_settings.get("extra_cmds")))
result = dict( result = dict(
tool=tool_name, tool=tool_name,
upload_protocol=env_options.get( upload_protocol=env_options.get(
"upload_protocol", "upload_protocol", board_config.get("upload", {}).get("protocol")
board_config.get("upload", {}).get("protocol")), ),
load_cmds=_cleanup_cmds( load_cmds=_cleanup_cmds(
env_options.get( env_options.get(
"debug_load_cmds", "debug_load_cmds",
tool_settings.get("load_cmds", tool_settings.get("load_cmds", tool_settings.get("load_cmd", "load")),
tool_settings.get("load_cmd", "load")))), )
load_mode=env_options.get("debug_load_mode", ),
tool_settings.get("load_mode", "always")), load_mode=env_options.get(
"debug_load_mode", tool_settings.get("load_mode", "always")
),
init_break=env_options.get( init_break=env_options.get(
"debug_init_break", tool_settings.get("init_break", "debug_init_break", tool_settings.get("init_break", "tbreak main")
"tbreak main")), ),
init_cmds=_cleanup_cmds( init_cmds=_cleanup_cmds(
env_options.get("debug_init_cmds", env_options.get("debug_init_cmds", tool_settings.get("init_cmds"))
tool_settings.get("init_cmds"))), ),
extra_cmds=extra_cmds, extra_cmds=extra_cmds,
require_debug_port=tool_settings.get("require_debug_port", False), require_debug_port=tool_settings.get("require_debug_port", False),
port=reveal_debug_port( port=reveal_debug_port(
env_options.get("debug_port", tool_settings.get("port")), env_options.get("debug_port", tool_settings.get("port")),
tool_name, tool_settings), tool_name,
server=server_options) tool_settings,
),
server=server_options,
)
return result return result
def configure_esp32_load_cmds(debug_options, configuration): def configure_esp32_load_cmds(debug_options, configuration):
ignore_conds = [ ignore_conds = [
debug_options['load_cmds'] != ["load"], debug_options["load_cmds"] != ["load"],
"xtensa-esp32" not in configuration.get("cc_path", ""), "xtensa-esp32" not in configuration.get("cc_path", ""),
not configuration.get("flash_extra_images"), not all([ not configuration.get("flash_extra_images"),
isfile(item['path']) not all(
for item in configuration.get("flash_extra_images") [isfile(item["path"]) for item in configuration.get("flash_extra_images")]
]) ),
] ]
if any(ignore_conds): if any(ignore_conds):
return debug_options['load_cmds'] return debug_options["load_cmds"]
mon_cmds = [ mon_cmds = [
'monitor program_esp32 "{{{path}}}" {offset} verify'.format( 'monitor program_esp32 "{{{path}}}" {offset} verify'.format(
path=fs.to_unix_path(item['path']), offset=item['offset']) path=fs.to_unix_path(item["path"]), offset=item["offset"]
)
for item in configuration.get("flash_extra_images") for item in configuration.get("flash_extra_images")
] ]
mon_cmds.append('monitor program_esp32 "{%s.bin}" 0x10000 verify' % mon_cmds.append(
fs.to_unix_path(configuration['prog_path'][:-4])) 'monitor program_esp32 "{%s.bin}" 0x10000 verify'
% fs.to_unix_path(configuration["prog_path"][:-4])
)
return mon_cmds return mon_cmds
@ -181,7 +191,7 @@ def has_debug_symbols(prog_path):
b".debug_abbrev": False, b".debug_abbrev": False,
b" -Og": False, b" -Og": False,
b" -g": False, b" -g": False,
b"__PLATFORMIO_BUILD_DEBUG__": False b"__PLATFORMIO_BUILD_DEBUG__": False,
} }
with open(prog_path, "rb") as fp: with open(prog_path, "rb") as fp:
last_data = b"" last_data = b""
@ -222,7 +232,6 @@ def is_prog_obsolete(prog_path):
def reveal_debug_port(env_debug_port, tool_name, tool_settings): def reveal_debug_port(env_debug_port, tool_name, tool_settings):
def _get_pattern(): def _get_pattern():
if not env_debug_port: if not env_debug_port:
return None return None
@ -238,18 +247,21 @@ def reveal_debug_port(env_debug_port, tool_name, tool_settings):
def _look_for_serial_port(hwids): def _look_for_serial_port(hwids):
for item in util.get_serialports(filter_hwid=True): for item in util.get_serialports(filter_hwid=True):
if not _is_match_pattern(item['port']): if not _is_match_pattern(item["port"]):
continue continue
port = item['port'] port = item["port"]
if tool_name.startswith("blackmagic"): if tool_name.startswith("blackmagic"):
if "windows" in util.get_systype() and \ if (
port.startswith("COM") and len(port) > 4: "windows" in util.get_systype()
and port.startswith("COM")
and len(port) > 4
):
port = "\\\\.\\%s" % port port = "\\\\.\\%s" % port
if "GDB" in item['description']: if "GDB" in item["description"]:
return port return port
for hwid in hwids: for hwid in hwids:
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "") hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
if hwid_str in item['hwid']: if hwid_str in item["hwid"]:
return port return port
return None return None
@ -261,5 +273,6 @@ def reveal_debug_port(env_debug_port, tool_name, tool_settings):
debug_port = _look_for_serial_port(tool_settings.get("hwids", [])) debug_port = _look_for_serial_port(tool_settings.get("hwids", []))
if not debug_port: if not debug_port:
raise exception.DebugInvalidOptions( raise exception.DebugInvalidOptions(
"Please specify `debug_port` for environment") "Please specify `debug_port` for environment"
)
return debug_port return debug_port

View File

@ -32,7 +32,7 @@ class BaseProcess(protocol.ProcessProtocol, object):
COMMON_PATTERNS = { COMMON_PATTERNS = {
"PLATFORMIO_HOME_DIR": get_project_core_dir(), "PLATFORMIO_HOME_DIR": get_project_core_dir(),
"PLATFORMIO_CORE_DIR": get_project_core_dir(), "PLATFORMIO_CORE_DIR": get_project_core_dir(),
"PYTHONEXE": get_pythonexe_path() "PYTHONEXE": get_pythonexe_path(),
} }
def apply_patterns(self, source, patterns=None): def apply_patterns(self, source, patterns=None):
@ -52,8 +52,7 @@ class BaseProcess(protocol.ProcessProtocol, object):
if isinstance(source, string_types): if isinstance(source, string_types):
source = _replace(source) source = _replace(source)
elif isinstance(source, (list, dict)): elif isinstance(source, (list, dict)):
items = enumerate(source) if isinstance(source, items = enumerate(source) if isinstance(source, list) else source.items()
list) else source.items()
for key, value in items: for key, value in items:
if isinstance(value, string_types): if isinstance(value, string_types):
source[key] = _replace(value) source[key] = _replace(value)
@ -67,9 +66,9 @@ class BaseProcess(protocol.ProcessProtocol, object):
with open(LOG_FILE, "ab") as fp: with open(LOG_FILE, "ab") as fp:
fp.write(data) fp.write(data)
while data: while data:
chunk = data[:self.STDOUT_CHUNK_SIZE] chunk = data[: self.STDOUT_CHUNK_SIZE]
click.echo(chunk, nl=False) click.echo(chunk, nl=False)
data = data[self.STDOUT_CHUNK_SIZE:] data = data[self.STDOUT_CHUNK_SIZE :]
@staticmethod @staticmethod
def errReceived(data): def errReceived(data):

View File

@ -24,7 +24,6 @@ from platformio.proc import where_is_program
class DebugServer(BaseProcess): class DebugServer(BaseProcess):
def __init__(self, debug_options, env_options): def __init__(self, debug_options, env_options):
self.debug_options = debug_options self.debug_options = debug_options
self.env_options = env_options self.env_options = env_options
@ -39,13 +38,16 @@ class DebugServer(BaseProcess):
if not server: if not server:
return None return None
server = self.apply_patterns(server, patterns) server = self.apply_patterns(server, patterns)
server_executable = server['executable'] server_executable = server["executable"]
if not server_executable: if not server_executable:
return None return None
if server['cwd']: if server["cwd"]:
server_executable = join(server['cwd'], server_executable) server_executable = join(server["cwd"], server_executable)
if ("windows" in systype and not server_executable.endswith(".exe") if (
and isfile(server_executable + ".exe")): "windows" in systype
and not server_executable.endswith(".exe")
and isfile(server_executable + ".exe")
):
server_executable = server_executable + ".exe" server_executable = server_executable + ".exe"
if not isfile(server_executable): if not isfile(server_executable):
@ -55,48 +57,56 @@ class DebugServer(BaseProcess):
"\nCould not launch Debug Server '%s'. Please check that it " "\nCould not launch Debug Server '%s'. Please check that it "
"is installed and is included in a system PATH\n\n" "is installed and is included in a system PATH\n\n"
"See documentation or contact contact@platformio.org:\n" "See documentation or contact contact@platformio.org:\n"
"http://docs.platformio.org/page/plus/debugging.html\n" % "http://docs.platformio.org/page/plus/debugging.html\n"
server_executable) % server_executable
)
self._debug_port = ":3333" self._debug_port = ":3333"
openocd_pipe_allowed = all([ openocd_pipe_allowed = all(
not self.debug_options['port'], [not self.debug_options["port"], "openocd" in server_executable]
"openocd" in server_executable ) # yapf: disable
]) # yapf: disable
if openocd_pipe_allowed: if openocd_pipe_allowed:
args = [] args = []
if server['cwd']: if server["cwd"]:
args.extend(["-s", server['cwd']]) args.extend(["-s", server["cwd"]])
args.extend([ args.extend(
"-c", "gdb_port pipe; tcl_port disabled; telnet_port disabled" ["-c", "gdb_port pipe; tcl_port disabled; telnet_port disabled"]
]) )
args.extend(server['arguments']) args.extend(server["arguments"])
str_args = " ".join( str_args = " ".join(
[arg if arg.startswith("-") else '"%s"' % arg for arg in args]) [arg if arg.startswith("-") else '"%s"' % arg for arg in args]
)
self._debug_port = '| "%s" %s' % (server_executable, str_args) self._debug_port = '| "%s" %s' % (server_executable, str_args)
self._debug_port = fs.to_unix_path(self._debug_port) self._debug_port = fs.to_unix_path(self._debug_port)
else: else:
env = os.environ.copy() env = os.environ.copy()
# prepend server "lib" folder to LD path # prepend server "lib" folder to LD path
if ("windows" not in systype and server['cwd'] if (
and isdir(join(server['cwd'], "lib"))): "windows" not in systype
ld_key = ("DYLD_LIBRARY_PATH" and server["cwd"]
if "darwin" in systype else "LD_LIBRARY_PATH") and isdir(join(server["cwd"], "lib"))
env[ld_key] = join(server['cwd'], "lib") ):
ld_key = (
"DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH"
)
env[ld_key] = join(server["cwd"], "lib")
if os.environ.get(ld_key): if os.environ.get(ld_key):
env[ld_key] = "%s:%s" % (env[ld_key], env[ld_key] = "%s:%s" % (env[ld_key], os.environ.get(ld_key))
os.environ.get(ld_key))
# prepend BIN to PATH # prepend BIN to PATH
if server['cwd'] and isdir(join(server['cwd'], "bin")): if server["cwd"] and isdir(join(server["cwd"], "bin")):
env['PATH'] = "%s%s%s" % ( env["PATH"] = "%s%s%s" % (
join(server['cwd'], "bin"), os.pathsep, join(server["cwd"], "bin"),
os.environ.get("PATH", os.environ.get("Path", ""))) os.pathsep,
os.environ.get("PATH", os.environ.get("Path", "")),
)
self._transport = reactor.spawnProcess( self._transport = reactor.spawnProcess(
self, self,
server_executable, [server_executable] + server['arguments'], server_executable,
path=server['cwd'], [server_executable] + server["arguments"],
env=env) path=server["cwd"],
env=env,
)
if "mspdebug" in server_executable.lower(): if "mspdebug" in server_executable.lower():
self._debug_port = ":2000" self._debug_port = ":2000"
elif "jlink" in server_executable.lower(): elif "jlink" in server_executable.lower():

View File

@ -36,27 +36,29 @@ def cli():
@click.option("--mdns", is_flag=True, help="List multicast DNS services") @click.option("--mdns", is_flag=True, help="List multicast DNS services")
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def device_list( # pylint: disable=too-many-branches def device_list( # pylint: disable=too-many-branches
serial, logical, mdns, json_output): serial, logical, mdns, json_output
):
if not logical and not mdns: if not logical and not mdns:
serial = True serial = True
data = {} data = {}
if serial: if serial:
data['serial'] = util.get_serial_ports() data["serial"] = util.get_serial_ports()
if logical: if logical:
data['logical'] = util.get_logical_devices() data["logical"] = util.get_logical_devices()
if mdns: if mdns:
data['mdns'] = util.get_mdns_services() data["mdns"] = util.get_mdns_services()
single_key = list(data)[0] if len(list(data)) == 1 else None single_key = list(data)[0] if len(list(data)) == 1 else None
if json_output: if json_output:
return click.echo( return click.echo(
dump_json_to_unicode(data[single_key] if single_key else data)) dump_json_to_unicode(data[single_key] if single_key else data)
)
titles = { titles = {
"serial": "Serial Ports", "serial": "Serial Ports",
"logical": "Logical Devices", "logical": "Logical Devices",
"mdns": "Multicast DNS Services" "mdns": "Multicast DNS Services",
} }
for key, value in data.items(): for key, value in data.items():
@ -66,31 +68,38 @@ def device_list( # pylint: disable=too-many-branches
if key == "serial": if key == "serial":
for item in value: for item in value:
click.secho(item['port'], fg="cyan") click.secho(item["port"], fg="cyan")
click.echo("-" * len(item['port'])) click.echo("-" * len(item["port"]))
click.echo("Hardware ID: %s" % item['hwid']) click.echo("Hardware ID: %s" % item["hwid"])
click.echo("Description: %s" % item['description']) click.echo("Description: %s" % item["description"])
click.echo("") click.echo("")
if key == "logical": if key == "logical":
for item in value: for item in value:
click.secho(item['path'], fg="cyan") click.secho(item["path"], fg="cyan")
click.echo("-" * len(item['path'])) click.echo("-" * len(item["path"]))
click.echo("Name: %s" % item['name']) click.echo("Name: %s" % item["name"])
click.echo("") click.echo("")
if key == "mdns": if key == "mdns":
for item in value: for item in value:
click.secho(item['name'], fg="cyan") click.secho(item["name"], fg="cyan")
click.echo("-" * len(item['name'])) click.echo("-" * len(item["name"]))
click.echo("Type: %s" % item['type']) click.echo("Type: %s" % item["type"])
click.echo("IP: %s" % item['ip']) click.echo("IP: %s" % item["ip"])
click.echo("Port: %s" % item['port']) click.echo("Port: %s" % item["port"])
if item['properties']: if item["properties"]:
click.echo("Properties: %s" % ("; ".join([ click.echo(
"%s=%s" % (k, v) "Properties: %s"
for k, v in item['properties'].items() % (
]))) "; ".join(
[
"%s=%s" % (k, v)
for k, v in item["properties"].items()
]
)
)
)
click.echo("") click.echo("")
if single_key: if single_key:
@ -102,66 +111,71 @@ def device_list( # pylint: disable=too-many-branches
@cli.command("monitor", short_help="Monitor device (Serial)") @cli.command("monitor", short_help="Monitor device (Serial)")
@click.option("--port", "-p", help="Port, a number or a device name") @click.option("--port", "-p", help="Port, a number or a device name")
@click.option("--baud", "-b", type=int, help="Set baud rate, default=9600") @click.option("--baud", "-b", type=int, help="Set baud rate, default=9600")
@click.option("--parity", @click.option(
default="N", "--parity",
type=click.Choice(["N", "E", "O", "S", "M"]), default="N",
help="Set parity, default=N") type=click.Choice(["N", "E", "O", "S", "M"]),
@click.option("--rtscts", help="Set parity, default=N",
is_flag=True, )
help="Enable RTS/CTS flow control, default=Off") @click.option("--rtscts", is_flag=True, help="Enable RTS/CTS flow control, default=Off")
@click.option("--xonxoff", @click.option(
is_flag=True, "--xonxoff", is_flag=True, help="Enable software flow control, default=Off"
help="Enable software flow control, default=Off") )
@click.option("--rts", @click.option(
default=None, "--rts", default=None, type=click.IntRange(0, 1), help="Set initial RTS line state"
type=click.IntRange(0, 1), )
help="Set initial RTS line state") @click.option(
@click.option("--dtr", "--dtr", default=None, type=click.IntRange(0, 1), help="Set initial DTR line state"
default=None, )
type=click.IntRange(0, 1),
help="Set initial DTR line state")
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off") @click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
@click.option("--encoding", @click.option(
default="UTF-8", "--encoding",
help="Set the encoding for the serial port (e.g. hexlify, " default="UTF-8",
"Latin1, UTF-8), default: UTF-8") help="Set the encoding for the serial port (e.g. hexlify, "
"Latin1, UTF-8), default: UTF-8",
)
@click.option("--filter", "-f", multiple=True, help="Add text transformation") @click.option("--filter", "-f", multiple=True, help="Add text transformation")
@click.option("--eol", @click.option(
default="CRLF", "--eol",
type=click.Choice(["CR", "LF", "CRLF"]), default="CRLF",
help="End of line mode, default=CRLF") type=click.Choice(["CR", "LF", "CRLF"]),
@click.option("--raw", help="End of line mode, default=CRLF",
is_flag=True, )
help="Do not apply any encodings/transformations") @click.option("--raw", is_flag=True, help="Do not apply any encodings/transformations")
@click.option("--exit-char", @click.option(
type=int, "--exit-char",
default=3, type=int,
help="ASCII code of special character that is used to exit " default=3,
"the application, default=3 (Ctrl+C)") help="ASCII code of special character that is used to exit "
@click.option("--menu-char", "the application, default=3 (Ctrl+C)",
type=int, )
default=20, @click.option(
help="ASCII code of special character that is used to " "--menu-char",
"control miniterm (menu), default=20 (DEC)") type=int,
@click.option("--quiet", default=20,
is_flag=True, help="ASCII code of special character that is used to "
help="Diagnostics: suppress non-error messages, default=Off") "control miniterm (menu), default=20 (DEC)",
@click.option("-d", )
"--project-dir", @click.option(
default=getcwd, "--quiet",
type=click.Path(exists=True, is_flag=True,
file_okay=False, help="Diagnostics: suppress non-error messages, default=Off",
dir_okay=True, )
resolve_path=True)) @click.option(
"-d",
"--project-dir",
default=getcwd,
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
)
@click.option( @click.option(
"-e", "-e",
"--environment", "--environment",
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
env_options = {} env_options = {}
try: try:
env_options = get_project_options(kwargs['project_dir'], env_options = get_project_options(kwargs["project_dir"], kwargs["environment"])
kwargs['environment'])
for k in ("port", "speed", "rts", "dtr"): for k in ("port", "speed", "rts", "dtr"):
k2 = "monitor_%s" % k k2 = "monitor_%s" % k
if k == "speed": if k == "speed":
@ -173,10 +187,10 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
except exception.NotPlatformIOProject: except exception.NotPlatformIOProject:
pass pass
if not kwargs['port']: if not kwargs["port"]:
ports = util.get_serial_ports(filter_hwid=True) ports = util.get_serial_ports(filter_hwid=True)
if len(ports) == 1: if len(ports) == 1:
kwargs['port'] = ports[0]['port'] kwargs["port"] = ports[0]["port"]
sys.argv = ["monitor"] + env_options.get("monitor_flags", []) sys.argv = ["monitor"] + env_options.get("monitor_flags", [])
for k, v in kwargs.items(): for k, v in kwargs.items():
@ -194,17 +208,19 @@ def device_monitor(**kwargs): # pylint: disable=too-many-branches
else: else:
sys.argv.extend([k, str(v)]) sys.argv.extend([k, str(v)])
if kwargs['port'] and (set(["*", "?", "[", "]"]) & set(kwargs['port'])): if kwargs["port"] and (set(["*", "?", "[", "]"]) & set(kwargs["port"])):
for item in util.get_serial_ports(): for item in util.get_serial_ports():
if fnmatch(item['port'], kwargs['port']): if fnmatch(item["port"], kwargs["port"]):
kwargs['port'] = item['port'] kwargs["port"] = item["port"]
break break
try: try:
miniterm.main(default_port=kwargs['port'], miniterm.main(
default_baudrate=kwargs['baud'] or 9600, default_port=kwargs["port"],
default_rts=kwargs['rts'], default_baudrate=kwargs["baud"] or 9600,
default_dtr=kwargs['dtr']) default_rts=kwargs["rts"],
default_dtr=kwargs["dtr"],
)
except Exception as e: except Exception as e:
raise exception.MinitermException(e) raise exception.MinitermException(e)

View File

@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# pylint: disable=too-many-locals
import mimetypes import mimetypes
import socket import socket
from os.path import isdir from os.path import isdir
@ -19,8 +21,7 @@ from os.path import isdir
import click import click
from platformio import exception from platformio import exception
from platformio.managers.core import (get_core_package_dir, from platformio.managers.core import get_core_package_dir, inject_contrib_pysite
inject_contrib_pysite)
@click.command("home", short_help="PIO Home") @click.command("home", short_help="PIO Home")
@ -28,9 +29,12 @@ from platformio.managers.core import (get_core_package_dir,
@click.option( @click.option(
"--host", "--host",
default="127.0.0.1", default="127.0.0.1",
help="HTTP host, default=127.0.0.1. " help=(
"You can open PIO Home for inbound connections with --host=0.0.0.0") "HTTP host, default=127.0.0.1. You can open PIO Home for inbound "
@click.option("--no-open", is_flag=True) # pylint: disable=too-many-locals "connections with --host=0.0.0.0"
),
)
@click.option("--no-open", is_flag=True)
def cli(port, host, no_open): def cli(port, host, no_open):
# import contrib modules # import contrib modules
inject_contrib_pysite() inject_contrib_pysite()
@ -38,6 +42,7 @@ def cli(port, host, no_open):
from autobahn.twisted.resource import WebSocketResource from autobahn.twisted.resource import WebSocketResource
from twisted.internet import reactor from twisted.internet import reactor
from twisted.web import server from twisted.web import server
# pylint: enable=import-error # pylint: enable=import-error
from platformio.commands.home.rpc.handlers.app import AppRPC from platformio.commands.home.rpc.handlers.app import AppRPC
from platformio.commands.home.rpc.handlers.ide import IDERPC from platformio.commands.home.rpc.handlers.ide import IDERPC
@ -89,14 +94,18 @@ def cli(port, host, no_open):
else: else:
reactor.callLater(1, lambda: click.launch(home_url)) reactor.callLater(1, lambda: click.launch(home_url))
click.echo("\n".join([ click.echo(
"", "\n".join(
" ___I_", [
" /\\-_--\\ PlatformIO Home", "",
"/ \\_-__\\", " ___I_",
"|[]| [] | %s" % home_url, " /\\-_--\\ PlatformIO Home",
"|__|____|______________%s" % ("_" * len(host)), "/ \\_-__\\",
])) "|[]| [] | %s" % home_url,
"|__|____|______________%s" % ("_" * len(host)),
]
)
)
click.echo("") click.echo("")
click.echo("Open PIO Home in your browser by this URL => %s" % home_url) click.echo("Open PIO Home in your browser by this URL => %s" % home_url)

View File

@ -27,7 +27,6 @@ from platformio.proc import where_is_program
class AsyncSession(requests.Session): class AsyncSession(requests.Session):
def __init__(self, n=None, *args, **kwargs): def __init__(self, n=None, *args, **kwargs):
if n: if n:
pool = reactor.getThreadPool() pool = reactor.getThreadPool()
@ -51,7 +50,8 @@ def requests_session():
@util.memoized(expire="60s") @util.memoized(expire="60s")
def get_core_fullpath(): def get_core_fullpath():
return where_is_program( return where_is_program(
"platformio" + (".exe" if "windows" in util.get_systype() else "")) "platformio" + (".exe" if "windows" in util.get_systype() else "")
)
@util.memoized(expire="10s") @util.memoized(expire="10s")
@ -60,9 +60,7 @@ def is_twitter_blocked():
timeout = 2 timeout = 2
try: try:
if os.getenv("HTTP_PROXY", os.getenv("HTTPS_PROXY")): if os.getenv("HTTP_PROXY", os.getenv("HTTPS_PROXY")):
requests.get("http://%s" % ip, requests.get("http://%s" % ip, allow_redirects=False, timeout=timeout)
allow_redirects=False,
timeout=timeout)
else: else:
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((ip, 80)) socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((ip, 80))
return False return False

View File

@ -17,8 +17,7 @@ from __future__ import absolute_import
from os.path import expanduser, join from os.path import expanduser, join
from platformio import __version__, app, util from platformio import __version__, app, util
from platformio.project.helpers import (get_project_core_dir, from platformio.project.helpers import get_project_core_dir, is_platformio_project
is_platformio_project)
class AppRPC(object): class AppRPC(object):
@ -26,8 +25,13 @@ class AppRPC(object):
APPSTATE_PATH = join(get_project_core_dir(), "homestate.json") APPSTATE_PATH = join(get_project_core_dir(), "homestate.json")
IGNORE_STORAGE_KEYS = [ IGNORE_STORAGE_KEYS = [
"cid", "coreVersion", "coreSystype", "coreCaller", "coreSettings", "cid",
"homeDir", "projectsDir" "coreVersion",
"coreSystype",
"coreCaller",
"coreSettings",
"homeDir",
"projectsDir",
] ]
@staticmethod @staticmethod
@ -37,31 +41,28 @@ class AppRPC(object):
# base data # base data
caller_id = app.get_session_var("caller_id") caller_id = app.get_session_var("caller_id")
storage['cid'] = app.get_cid() storage["cid"] = app.get_cid()
storage['coreVersion'] = __version__ storage["coreVersion"] = __version__
storage['coreSystype'] = util.get_systype() storage["coreSystype"] = util.get_systype()
storage['coreCaller'] = (str(caller_id).lower() storage["coreCaller"] = str(caller_id).lower() if caller_id else None
if caller_id else None) storage["coreSettings"] = {
storage['coreSettings'] = {
name: { name: {
"description": data['description'], "description": data["description"],
"default_value": data['value'], "default_value": data["value"],
"value": app.get_setting(name) "value": app.get_setting(name),
} }
for name, data in app.DEFAULT_SETTINGS.items() for name, data in app.DEFAULT_SETTINGS.items()
} }
storage['homeDir'] = expanduser("~") storage["homeDir"] = expanduser("~")
storage['projectsDir'] = storage['coreSettings']['projects_dir'][ storage["projectsDir"] = storage["coreSettings"]["projects_dir"]["value"]
'value']
# skip non-existing recent projects # skip non-existing recent projects
storage['recentProjects'] = [ storage["recentProjects"] = [
p for p in storage.get("recentProjects", []) p for p in storage.get("recentProjects", []) if is_platformio_project(p)
if is_platformio_project(p)
] ]
state['storage'] = storage state["storage"] = storage
state.modified = False # skip saving extra fields state.modified = False # skip saving extra fields
return state.as_dict() return state.as_dict()

View File

@ -19,20 +19,18 @@ from twisted.internet import defer # pylint: disable=import-error
class IDERPC(object): class IDERPC(object):
def __init__(self): def __init__(self):
self._queue = {} self._queue = {}
def send_command(self, command, params, sid=0): def send_command(self, command, params, sid=0):
if not self._queue.get(sid): if not self._queue.get(sid):
raise jsonrpc.exceptions.JSONRPCDispatchException( raise jsonrpc.exceptions.JSONRPCDispatchException(
code=4005, message="PIO Home IDE agent is not started") code=4005, message="PIO Home IDE agent is not started"
)
while self._queue[sid]: while self._queue[sid]:
self._queue[sid].pop().callback({ self._queue[sid].pop().callback(
"id": time.time(), {"id": time.time(), "method": command, "params": params}
"method": command, )
"params": params
})
def listen_commands(self, sid=0): def listen_commands(self, sid=0):
if sid not in self._queue: if sid not in self._queue:

View File

@ -22,7 +22,6 @@ from platformio.commands.home.rpc.handlers.os import OSRPC
class MiscRPC(object): class MiscRPC(object):
def load_latest_tweets(self, username): def load_latest_tweets(self, username):
cache_key = "piohome_latest_tweets_" + str(username) cache_key = "piohome_latest_tweets_" + str(username)
cache_valid = "7d" cache_valid = "7d"
@ -31,10 +30,11 @@ class MiscRPC(object):
if cache_data: if cache_data:
cache_data = json.loads(cache_data) cache_data = json.loads(cache_data)
# automatically update cache in background every 12 hours # automatically update cache in background every 12 hours
if cache_data['time'] < (time.time() - (3600 * 12)): if cache_data["time"] < (time.time() - (3600 * 12)):
reactor.callLater(5, self._preload_latest_tweets, username, reactor.callLater(
cache_key, cache_valid) 5, self._preload_latest_tweets, username, cache_key, cache_valid
return cache_data['result'] )
return cache_data["result"]
result = self._preload_latest_tweets(username, cache_key, cache_valid) result = self._preload_latest_tweets(username, cache_key, cache_valid)
return result return result
@ -43,12 +43,13 @@ class MiscRPC(object):
@defer.inlineCallbacks @defer.inlineCallbacks
def _preload_latest_tweets(username, cache_key, cache_valid): def _preload_latest_tweets(username, cache_key, cache_valid):
result = yield OSRPC.fetch_content( result = yield OSRPC.fetch_content(
"https://api.platformio.org/tweets/" + username) "https://api.platformio.org/tweets/" + username
)
result = json.loads(result) result = json.loads(result)
with app.ContentCache() as cc: with app.ContentCache() as cc:
cc.set(cache_key, cc.set(
json.dumps({ cache_key,
"time": int(time.time()), json.dumps({"time": int(time.time()), "result": result}),
"result": result cache_valid,
}), cache_valid) )
defer.returnValue(result) defer.returnValue(result)

View File

@ -30,19 +30,18 @@ from platformio.compat import PY2, get_filesystem_encoding
class OSRPC(object): class OSRPC(object):
@staticmethod @staticmethod
@defer.inlineCallbacks @defer.inlineCallbacks
def fetch_content(uri, data=None, headers=None, cache_valid=None): def fetch_content(uri, data=None, headers=None, cache_valid=None):
if not headers: if not headers:
headers = { headers = {
"User-Agent": "User-Agent": (
("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) "
"AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 " "AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 "
"Safari/603.3.8") "Safari/603.3.8"
)
} }
cache_key = (app.ContentCache.key_from_args(uri, data) cache_key = app.ContentCache.key_from_args(uri, data) if cache_valid else None
if cache_valid else None)
with app.ContentCache() as cc: with app.ContentCache() as cc:
if cache_key: if cache_key:
result = cc.get(cache_key) result = cc.get(cache_key)
@ -66,7 +65,7 @@ class OSRPC(object):
defer.returnValue(result) defer.returnValue(result)
def request_content(self, uri, data=None, headers=None, cache_valid=None): def request_content(self, uri, data=None, headers=None, cache_valid=None):
if uri.startswith('http'): if uri.startswith("http"):
return self.fetch_content(uri, data, headers, cache_valid) return self.fetch_content(uri, data, headers, cache_valid)
if not isfile(uri): if not isfile(uri):
return None return None
@ -80,8 +79,8 @@ class OSRPC(object):
@staticmethod @staticmethod
def reveal_file(path): def reveal_file(path):
return click.launch( return click.launch(
path.encode(get_filesystem_encoding()) if PY2 else path, path.encode(get_filesystem_encoding()) if PY2 else path, locate=True
locate=True) )
@staticmethod @staticmethod
def is_file(path): def is_file(path):
@ -109,13 +108,11 @@ class OSRPC(object):
pathnames = [pathnames] pathnames = [pathnames]
result = set() result = set()
for pathname in pathnames: for pathname in pathnames:
result |= set( result |= set(glob.glob(join(root, pathname) if root else pathname))
glob.glob(join(root, pathname) if root else pathname))
return list(result) return list(result)
@staticmethod @staticmethod
def list_dir(path): def list_dir(path):
def _cmp(x, y): def _cmp(x, y):
if x[1] and not y[1]: if x[1] and not y[1]:
return -1 return -1
@ -146,7 +143,7 @@ class OSRPC(object):
def get_logical_devices(): def get_logical_devices():
items = [] items = []
for item in util.get_logical_devices(): for item in util.get_logical_devices():
if item['name']: if item["name"]:
item['name'] = item['name'] item["name"] = item["name"]
items.append(item) items.append(item)
return items return items

View File

@ -27,8 +27,7 @@ from twisted.internet import utils # pylint: disable=import-error
from platformio import __main__, __version__, fs from platformio import __main__, __version__, fs
from platformio.commands.home import helpers from platformio.commands.home import helpers
from platformio.compat import (PY2, get_filesystem_encoding, is_bytes, from platformio.compat import PY2, get_filesystem_encoding, is_bytes, string_types
string_types)
try: try:
from thread import get_ident as thread_get_ident from thread import get_ident as thread_get_ident
@ -37,7 +36,6 @@ except ImportError:
class MultiThreadingStdStream(object): class MultiThreadingStdStream(object):
def __init__(self, parent_stream): def __init__(self, parent_stream):
self._buffers = {thread_get_ident(): parent_stream} self._buffers = {thread_get_ident(): parent_stream}
@ -54,7 +52,8 @@ class MultiThreadingStdStream(object):
thread_id = thread_get_ident() thread_id = thread_get_ident()
self._ensure_thread_buffer(thread_id) self._ensure_thread_buffer(thread_id)
return self._buffers[thread_id].write( return self._buffers[thread_id].write(
value.decode() if is_bytes(value) else value) value.decode() if is_bytes(value) else value
)
def get_value_and_reset(self): def get_value_and_reset(self):
result = "" result = ""
@ -68,7 +67,6 @@ class MultiThreadingStdStream(object):
class PIOCoreRPC(object): class PIOCoreRPC(object):
@staticmethod @staticmethod
def version(): def version():
return __version__ return __version__
@ -104,16 +102,15 @@ class PIOCoreRPC(object):
else: else:
result = yield PIOCoreRPC._call_inline(args, options) result = yield PIOCoreRPC._call_inline(args, options)
try: try:
defer.returnValue( defer.returnValue(PIOCoreRPC._process_result(result, to_json))
PIOCoreRPC._process_result(result, to_json))
except ValueError: except ValueError:
# fall-back to subprocess method # fall-back to subprocess method
result = yield PIOCoreRPC._call_subprocess(args, options) result = yield PIOCoreRPC._call_subprocess(args, options)
defer.returnValue( defer.returnValue(PIOCoreRPC._process_result(result, to_json))
PIOCoreRPC._process_result(result, to_json))
except Exception as e: # pylint: disable=bare-except except Exception as e: # pylint: disable=bare-except
raise jsonrpc.exceptions.JSONRPCDispatchException( raise jsonrpc.exceptions.JSONRPCDispatchException(
code=4003, message="PIO Core Call Error", data=str(e)) code=4003, message="PIO Core Call Error", data=str(e)
)
@staticmethod @staticmethod
def _call_inline(args, options): def _call_inline(args, options):
@ -123,8 +120,11 @@ class PIOCoreRPC(object):
def _thread_task(): def _thread_task():
with fs.cd(cwd): with fs.cd(cwd):
exit_code = __main__.main(["-c"] + args) exit_code = __main__.main(["-c"] + args)
return (PIOCoreRPC.thread_stdout.get_value_and_reset(), return (
PIOCoreRPC.thread_stderr.get_value_and_reset(), exit_code) PIOCoreRPC.thread_stdout.get_value_and_reset(),
PIOCoreRPC.thread_stderr.get_value_and_reset(),
exit_code,
)
return threads.deferToThread(_thread_task) return threads.deferToThread(_thread_task)
@ -135,8 +135,8 @@ class PIOCoreRPC(object):
helpers.get_core_fullpath(), helpers.get_core_fullpath(),
args, args,
path=cwd, path=cwd,
env={k: v env={k: v for k, v in os.environ.items() if "%" not in k},
for k, v in os.environ.items() if "%" not in k}) )
@staticmethod @staticmethod
def _process_result(result, to_json=False): def _process_result(result, to_json=False):

View File

@ -17,8 +17,7 @@ from __future__ import absolute_import
import os import os
import shutil import shutil
import time import time
from os.path import (basename, expanduser, getmtime, isdir, isfile, join, from os.path import basename, expanduser, getmtime, isdir, isfile, join, realpath, sep
realpath, sep)
import jsonrpc # pylint: disable=import-error import jsonrpc # pylint: disable=import-error
@ -29,38 +28,37 @@ from platformio.compat import PY2, get_filesystem_encoding
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 from platformio.project.config import ProjectConfig
from platformio.project.helpers import (get_project_libdeps_dir, from platformio.project.helpers import (
get_project_src_dir, get_project_libdeps_dir,
is_platformio_project) get_project_src_dir,
is_platformio_project,
)
class ProjectRPC(object): class ProjectRPC(object):
@staticmethod @staticmethod
def _get_projects(project_dirs=None): def _get_projects(project_dirs=None):
def _get_project_data(project_dir): def _get_project_data(project_dir):
data = {"boards": [], "envLibdepsDirs": [], "libExtraDirs": []} data = {"boards": [], "envLibdepsDirs": [], "libExtraDirs": []}
config = ProjectConfig(join(project_dir, "platformio.ini")) config = ProjectConfig(join(project_dir, "platformio.ini"))
libdeps_dir = get_project_libdeps_dir() libdeps_dir = get_project_libdeps_dir()
data['libExtraDirs'].extend( data["libExtraDirs"].extend(config.get("platformio", "lib_extra_dirs", []))
config.get("platformio", "lib_extra_dirs", []))
for section in config.sections(): for section in config.sections():
if not section.startswith("env:"): if not section.startswith("env:"):
continue continue
data['envLibdepsDirs'].append(join(libdeps_dir, section[4:])) data["envLibdepsDirs"].append(join(libdeps_dir, section[4:]))
if config.has_option(section, "board"): if config.has_option(section, "board"):
data['boards'].append(config.get(section, "board")) data["boards"].append(config.get(section, "board"))
data['libExtraDirs'].extend( data["libExtraDirs"].extend(config.get(section, "lib_extra_dirs", []))
config.get(section, "lib_extra_dirs", []))
# skip non existing folders and resolve full path # skip non existing folders and resolve full path
for key in ("envLibdepsDirs", "libExtraDirs"): for key in ("envLibdepsDirs", "libExtraDirs"):
data[key] = [ data[key] = [
expanduser(d) if d.startswith("~") else realpath(d) expanduser(d) if d.startswith("~") else realpath(d)
for d in data[key] if isdir(d) for d in data[key]
if isdir(d)
] ]
return data return data
@ -69,7 +67,7 @@ class ProjectRPC(object):
return (sep).join(path.split(sep)[-2:]) return (sep).join(path.split(sep)[-2:])
if not project_dirs: if not project_dirs:
project_dirs = AppRPC.load_state()['storage']['recentProjects'] project_dirs = AppRPC.load_state()["storage"]["recentProjects"]
result = [] result = []
pm = PlatformManager() pm = PlatformManager()
@ -85,29 +83,27 @@ class ProjectRPC(object):
for board_id in data.get("boards", []): for board_id in data.get("boards", []):
name = board_id name = board_id
try: try:
name = pm.board_config(board_id)['name'] name = pm.board_config(board_id)["name"]
except exception.PlatformioException: except exception.PlatformioException:
pass pass
boards.append({"id": board_id, "name": name}) boards.append({"id": board_id, "name": name})
result.append({ result.append(
"path": {
project_dir, "path": project_dir,
"name": "name": _path_to_name(project_dir),
_path_to_name(project_dir), "modified": int(getmtime(project_dir)),
"modified": "boards": boards,
int(getmtime(project_dir)), "envLibStorages": [
"boards": {"name": basename(d), "path": d}
boards, for d in data.get("envLibdepsDirs", [])
"envLibStorages": [{ ],
"name": basename(d), "extraLibStorages": [
"path": d {"name": _path_to_name(d), "path": d}
} for d in data.get("envLibdepsDirs", [])], for d in data.get("libExtraDirs", [])
"extraLibStorages": [{ ],
"name": _path_to_name(d), }
"path": d )
} for d in data.get("libExtraDirs", [])]
})
return result return result
def get_projects(self, project_dirs=None): def get_projects(self, project_dirs=None):
@ -117,7 +113,7 @@ class ProjectRPC(object):
def get_project_examples(): def get_project_examples():
result = [] result = []
for manifest in PlatformManager().get_installed(): for manifest in PlatformManager().get_installed():
examples_dir = join(manifest['__pkg_dir'], "examples") examples_dir = join(manifest["__pkg_dir"], "examples")
if not isdir(examples_dir): if not isdir(examples_dir):
continue continue
items = [] items = []
@ -126,28 +122,30 @@ class ProjectRPC(object):
try: try:
config = ProjectConfig(join(project_dir, "platformio.ini")) config = ProjectConfig(join(project_dir, "platformio.ini"))
config.validate(silent=True) config.validate(silent=True)
project_description = config.get("platformio", project_description = config.get("platformio", "description")
"description")
except exception.PlatformIOProjectException: except exception.PlatformIOProjectException:
continue continue
path_tokens = project_dir.split(sep) path_tokens = project_dir.split(sep)
items.append({ items.append(
"name": {
"/".join(path_tokens[path_tokens.index("examples") + 1:]), "name": "/".join(
"path": path_tokens[path_tokens.index("examples") + 1 :]
project_dir, ),
"description": "path": project_dir,
project_description "description": project_description,
}) }
result.append({ )
"platform": { result.append(
"title": manifest['title'], {
"version": manifest['version'] "platform": {
}, "title": manifest["title"],
"items": sorted(items, key=lambda item: item['name']) "version": manifest["version"],
}) },
return sorted(result, key=lambda data: data['platform']['title']) "items": sorted(items, key=lambda item: item["name"]),
}
)
return sorted(result, key=lambda data: data["platform"]["title"])
def init(self, board, framework, project_dir): def init(self, board, framework, project_dir):
assert project_dir assert project_dir
@ -157,9 +155,11 @@ class ProjectRPC(object):
args = ["init", "--board", board] args = ["init", "--board", board]
if framework: if framework:
args.extend(["--project-option", "framework = %s" % framework]) args.extend(["--project-option", "framework = %s" % framework])
if (state['storage']['coreCaller'] and state['storage']['coreCaller'] if (
in ProjectGenerator.get_supported_ides()): state["storage"]["coreCaller"]
args.extend(["--ide", state['storage']['coreCaller']]) and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides()
):
args.extend(["--ide", state["storage"]["coreCaller"]])
d = PIOCoreRPC.call(args, options={"cwd": project_dir}) d = PIOCoreRPC.call(args, options={"cwd": project_dir})
d.addCallback(self._generate_project_main, project_dir, framework) d.addCallback(self._generate_project_main, project_dir, framework)
return d return d
@ -168,32 +168,35 @@ class ProjectRPC(object):
def _generate_project_main(_, project_dir, framework): def _generate_project_main(_, project_dir, framework):
main_content = None main_content = None
if framework == "arduino": if framework == "arduino":
main_content = "\n".join([ main_content = "\n".join(
"#include <Arduino.h>", [
"", "#include <Arduino.h>",
"void setup() {", "",
" // put your setup code here, to run once:", "void setup() {",
"}", " // put your setup code here, to run once:",
"", "}",
"void loop() {", "",
" // put your main code here, to run repeatedly:", "void loop() {",
"}" " // put your main code here, to run repeatedly:",
"" "}" "",
]) # yapf: disable ]
) # yapf: disable
elif framework == "mbed": elif framework == "mbed":
main_content = "\n".join([ main_content = "\n".join(
"#include <mbed.h>", [
"", "#include <mbed.h>",
"int main() {", "",
"", "int main() {",
" // put your setup code here, to run once:", "",
"", " // put your setup code here, to run once:",
" while(1) {", "",
" // put your main code here, to run repeatedly:", " while(1) {",
" }", " // put your main code here, to run repeatedly:",
"}", " }",
"" "}",
]) # yapf: disable "",
]
) # yapf: disable
if not main_content: if not main_content:
return project_dir return project_dir
with fs.cd(project_dir): with fs.cd(project_dir):
@ -210,41 +213,46 @@ class ProjectRPC(object):
def import_arduino(self, board, use_arduino_libs, arduino_project_dir): def import_arduino(self, board, use_arduino_libs, arduino_project_dir):
board = str(board) board = str(board)
if arduino_project_dir and PY2: if arduino_project_dir and PY2:
arduino_project_dir = arduino_project_dir.encode( arduino_project_dir = arduino_project_dir.encode(get_filesystem_encoding())
get_filesystem_encoding())
# don't import PIO Project # don't import PIO Project
if is_platformio_project(arduino_project_dir): if is_platformio_project(arduino_project_dir):
return arduino_project_dir return arduino_project_dir
is_arduino_project = any([ is_arduino_project = any(
isfile( [
join(arduino_project_dir, isfile(
"%s.%s" % (basename(arduino_project_dir), ext))) join(
for ext in ("ino", "pde") arduino_project_dir,
]) "%s.%s" % (basename(arduino_project_dir), ext),
)
)
for ext in ("ino", "pde")
]
)
if not is_arduino_project: if not is_arduino_project:
raise jsonrpc.exceptions.JSONRPCDispatchException( raise jsonrpc.exceptions.JSONRPCDispatchException(
code=4000, code=4000, message="Not an Arduino project: %s" % arduino_project_dir
message="Not an Arduino project: %s" % arduino_project_dir) )
state = AppRPC.load_state() state = AppRPC.load_state()
project_dir = join(state['storage']['projectsDir'], project_dir = join(
time.strftime("%y%m%d-%H%M%S-") + board) state["storage"]["projectsDir"], time.strftime("%y%m%d-%H%M%S-") + board
)
if not isdir(project_dir): if not isdir(project_dir):
os.makedirs(project_dir) os.makedirs(project_dir)
args = ["init", "--board", board] args = ["init", "--board", board]
args.extend(["--project-option", "framework = arduino"]) args.extend(["--project-option", "framework = arduino"])
if use_arduino_libs: if use_arduino_libs:
args.extend([ args.extend(
"--project-option", ["--project-option", "lib_extra_dirs = ~/Documents/Arduino/libraries"]
"lib_extra_dirs = ~/Documents/Arduino/libraries" )
]) if (
if (state['storage']['coreCaller'] and state['storage']['coreCaller'] state["storage"]["coreCaller"]
in ProjectGenerator.get_supported_ides()): and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides()
args.extend(["--ide", state['storage']['coreCaller']]) ):
args.extend(["--ide", state["storage"]["coreCaller"]])
d = PIOCoreRPC.call(args, options={"cwd": project_dir}) d = PIOCoreRPC.call(args, options={"cwd": project_dir})
d.addCallback(self._finalize_arduino_import, project_dir, d.addCallback(self._finalize_arduino_import, project_dir, arduino_project_dir)
arduino_project_dir)
return d return d
@staticmethod @staticmethod
@ -260,18 +268,21 @@ class ProjectRPC(object):
def import_pio(project_dir): def import_pio(project_dir):
if not project_dir or not is_platformio_project(project_dir): if not project_dir or not is_platformio_project(project_dir):
raise jsonrpc.exceptions.JSONRPCDispatchException( raise jsonrpc.exceptions.JSONRPCDispatchException(
code=4001, code=4001, message="Not an PlatformIO project: %s" % project_dir
message="Not an PlatformIO project: %s" % project_dir) )
new_project_dir = join( new_project_dir = join(
AppRPC.load_state()['storage']['projectsDir'], AppRPC.load_state()["storage"]["projectsDir"],
time.strftime("%y%m%d-%H%M%S-") + basename(project_dir)) time.strftime("%y%m%d-%H%M%S-") + basename(project_dir),
)
shutil.copytree(project_dir, new_project_dir) shutil.copytree(project_dir, new_project_dir)
state = AppRPC.load_state() state = AppRPC.load_state()
args = ["init"] args = ["init"]
if (state['storage']['coreCaller'] and state['storage']['coreCaller'] if (
in ProjectGenerator.get_supported_ides()): state["storage"]["coreCaller"]
args.extend(["--ide", state['storage']['coreCaller']]) and state["storage"]["coreCaller"] in ProjectGenerator.get_supported_ides()
):
args.extend(["--ide", state["storage"]["coreCaller"]])
d = PIOCoreRPC.call(args, options={"cwd": new_project_dir}) d = PIOCoreRPC.call(args, options={"cwd": new_project_dir})
d.addCallback(lambda _: new_project_dir) d.addCallback(lambda _: new_project_dir)
return d return d

View File

@ -16,8 +16,7 @@
import click import click
import jsonrpc import jsonrpc
from autobahn.twisted.websocket import (WebSocketServerFactory, from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
WebSocketServerProtocol)
from jsonrpc.exceptions import JSONRPCDispatchException from jsonrpc.exceptions import JSONRPCDispatchException
from twisted.internet import defer from twisted.internet import defer
@ -25,40 +24,39 @@ from platformio.compat import PY2, dump_json_to_unicode, is_bytes
class JSONRPCServerProtocol(WebSocketServerProtocol): class JSONRPCServerProtocol(WebSocketServerProtocol):
def onMessage(self, payload, isBinary): # pylint: disable=unused-argument def onMessage(self, payload, isBinary): # pylint: disable=unused-argument
# click.echo("> %s" % payload) # click.echo("> %s" % payload)
response = jsonrpc.JSONRPCResponseManager.handle( response = jsonrpc.JSONRPCResponseManager.handle(
payload, self.factory.dispatcher).data payload, self.factory.dispatcher
).data
# if error # if error
if "result" not in response: if "result" not in response:
self.sendJSONResponse(response) self.sendJSONResponse(response)
return None return None
d = defer.maybeDeferred(lambda: response['result']) d = defer.maybeDeferred(lambda: response["result"])
d.addCallback(self._callback, response) d.addCallback(self._callback, response)
d.addErrback(self._errback, response) d.addErrback(self._errback, response)
return None return None
def _callback(self, result, response): def _callback(self, result, response):
response['result'] = result response["result"] = result
self.sendJSONResponse(response) self.sendJSONResponse(response)
def _errback(self, failure, response): def _errback(self, failure, response):
if isinstance(failure.value, JSONRPCDispatchException): if isinstance(failure.value, JSONRPCDispatchException):
e = failure.value e = failure.value
else: else:
e = JSONRPCDispatchException(code=4999, e = JSONRPCDispatchException(code=4999, message=failure.getErrorMessage())
message=failure.getErrorMessage())
del response["result"] del response["result"]
response['error'] = e.error._data # pylint: disable=protected-access response["error"] = e.error._data # pylint: disable=protected-access
self.sendJSONResponse(response) self.sendJSONResponse(response)
def sendJSONResponse(self, response): def sendJSONResponse(self, response):
# click.echo("< %s" % response) # click.echo("< %s" % response)
if "error" in response: if "error" in response:
click.secho("Error: %s" % response['error'], fg="red", err=True) click.secho("Error: %s" % response["error"], fg="red", err=True)
response = dump_json_to_unicode(response) response = dump_json_to_unicode(response)
if not PY2 and not is_bytes(response): if not PY2 and not is_bytes(response):
response = response.encode("utf-8") response = response.encode("utf-8")

View File

@ -17,14 +17,12 @@ from twisted.web import static # pylint: disable=import-error
class WebRoot(static.File): class WebRoot(static.File):
def render_GET(self, request): def render_GET(self, request):
if request.args.get("__shutdown__", False): if request.args.get("__shutdown__", False):
reactor.stop() reactor.stop()
return "Server has been stopped" return "Server has been stopped"
request.setHeader("cache-control", request.setHeader("cache-control", "no-cache, no-store, must-revalidate")
"no-cache, no-store, must-revalidate")
request.setHeader("pragma", "no-cache") request.setHeader("pragma", "no-cache")
request.setHeader("expires", "0") request.setHeader("expires", "0")
return static.File.render_GET(self, request) return static.File.render_GET(self, request)

View File

@ -20,16 +20,17 @@ from os.path import isdir, isfile, join
import click import click
from platformio import exception, fs from platformio import exception, fs
from platformio.commands.platform import \ from platformio.commands.platform import platform_install as cli_platform_install
platform_install as cli_platform_install
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 from platformio.project.config import ProjectConfig
from platformio.project.helpers import (get_project_include_dir, from platformio.project.helpers import (
get_project_lib_dir, get_project_include_dir,
get_project_src_dir, get_project_lib_dir,
get_project_test_dir, get_project_src_dir,
is_platformio_project) get_project_test_dir,
is_platformio_project,
)
def validate_boards(ctx, param, value): # pylint: disable=W0613 def validate_boards(ctx, param, value): # pylint: disable=W0613
@ -40,66 +41,66 @@ def validate_boards(ctx, param, value): # pylint: disable=W0613
except exception.UnknownBoard: except exception.UnknownBoard:
raise click.BadParameter( raise click.BadParameter(
"`%s`. Please search for board ID using `platformio boards` " "`%s`. Please search for board ID using `platformio boards` "
"command" % id_) "command" % id_
)
return value return value
@click.command("init", @click.command("init", short_help="Initialize PlatformIO project or update existing")
short_help="Initialize PlatformIO project or update existing") @click.option(
@click.option("--project-dir", "--project-dir",
"-d", "-d",
default=getcwd, default=getcwd,
type=click.Path(exists=True, type=click.Path(
file_okay=False, exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
dir_okay=True, ),
writable=True, )
resolve_path=True)) @click.option("-b", "--board", multiple=True, metavar="ID", callback=validate_boards)
@click.option("-b", @click.option("--ide", type=click.Choice(ProjectGenerator.get_supported_ides()))
"--board",
multiple=True,
metavar="ID",
callback=validate_boards)
@click.option("--ide",
type=click.Choice(ProjectGenerator.get_supported_ides()))
@click.option("-O", "--project-option", multiple=True) @click.option("-O", "--project-option", multiple=True)
@click.option("--env-prefix", default="") @click.option("--env-prefix", default="")
@click.option("-s", "--silent", is_flag=True) @click.option("-s", "--silent", is_flag=True)
@click.pass_context @click.pass_context
def cli( def cli(
ctx, # pylint: disable=R0913 ctx, # pylint: disable=R0913
project_dir, project_dir,
board, board,
ide, ide,
project_option, project_option,
env_prefix, env_prefix,
silent): silent,
):
if not silent: if not silent:
if project_dir == getcwd(): if project_dir == getcwd():
click.secho("\nThe current working directory", click.secho("\nThe current working directory", fg="yellow", nl=False)
fg="yellow",
nl=False)
click.secho(" %s " % project_dir, fg="cyan", nl=False) click.secho(" %s " % project_dir, fg="cyan", nl=False)
click.secho("will be used for the project.", fg="yellow") click.secho("will be used for the project.", fg="yellow")
click.echo("") click.echo("")
click.echo("The next files/directories have been created in %s" % click.echo(
click.style(project_dir, fg="cyan")) "The next files/directories have been created in %s"
click.echo("%s - Put project header files here" % % click.style(project_dir, fg="cyan")
click.style("include", fg="cyan")) )
click.echo("%s - Put here project specific (private) libraries" % click.echo(
click.style("lib", fg="cyan")) "%s - Put project header files here" % click.style("include", fg="cyan")
click.echo("%s - Put project source files here" % )
click.style("src", fg="cyan")) click.echo(
click.echo("%s - Project Configuration File" % "%s - Put here project specific (private) libraries"
click.style("platformio.ini", fg="cyan")) % click.style("lib", fg="cyan")
)
click.echo("%s - Put project source files here" % click.style("src", fg="cyan"))
click.echo(
"%s - Project Configuration File" % click.style("platformio.ini", fg="cyan")
)
is_new_project = not is_platformio_project(project_dir) is_new_project = not is_platformio_project(project_dir)
if is_new_project: if is_new_project:
init_base_project(project_dir) init_base_project(project_dir)
if board: if board:
fill_project_envs(ctx, project_dir, board, project_option, env_prefix, fill_project_envs(
ide is not None) ctx, project_dir, board, project_option, env_prefix, ide is not None
)
if ide: if ide:
pg = ProjectGenerator(project_dir, ide, board) pg = ProjectGenerator(project_dir, ide, board)
@ -115,9 +116,9 @@ def cli(
if ide: if ide:
click.secho( click.secho(
"\nProject has been successfully %s including configuration files " "\nProject has been successfully %s including configuration files "
"for `%s` IDE." % "for `%s` IDE." % ("initialized" if is_new_project else "updated", ide),
("initialized" if is_new_project else "updated", ide), fg="green",
fg="green") )
else: else:
click.secho( click.secho(
"\nProject has been successfully %s! Useful commands:\n" "\nProject has been successfully %s! Useful commands:\n"
@ -125,9 +126,10 @@ def cli(
"`pio run --target upload` or `pio run -t upload` " "`pio run --target upload` or `pio run -t upload` "
"- upload firmware to a target\n" "- upload firmware to a target\n"
"`pio run --target clean` - clean project (remove compiled files)" "`pio run --target clean` - clean project (remove compiled files)"
"\n`pio run --help` - additional information" % "\n`pio run --help` - additional information"
("initialized" if is_new_project else "updated"), % ("initialized" if is_new_project else "updated"),
fg="green") fg="green",
)
def init_base_project(project_dir): def init_base_project(project_dir):
@ -149,7 +151,8 @@ def init_base_project(project_dir):
def init_include_readme(include_dir): def init_include_readme(include_dir):
with open(join(include_dir, "README"), "w") as f: with open(join(include_dir, "README"), "w") as f:
f.write(""" f.write(
"""
This directory is intended for project header files. This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions A header file is a file containing C declarations and macro definitions
@ -188,12 +191,15 @@ Read more about using header files in official GCC documentation:
* Computed Includes * Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
""") """
)
def init_lib_readme(lib_dir): def init_lib_readme(lib_dir):
with open(join(lib_dir, "README"), "w") as f: with open(join(lib_dir, "README"), "w") as f:
f.write(""" # pylint: disable=line-too-long
f.write(
"""
This directory is intended for project specific (private) libraries. This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file. PlatformIO will compile them to static libraries and link into executable file.
@ -239,12 +245,14 @@ libraries scanning project source files.
More information about PlatformIO Library Dependency Finder More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html - https://docs.platformio.org/page/librarymanager/ldf.html
""") """
)
def init_test_readme(test_dir): def init_test_readme(test_dir):
with open(join(test_dir, "README"), "w") as f: with open(join(test_dir, "README"), "w") as f:
f.write(""" f.write(
"""
This directory is intended for PIO Unit Testing and project tests. This directory is intended for PIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of Unit Testing is a software testing method by which individual units of
@ -255,7 +263,8 @@ in the development cycle.
More information about PIO Unit Testing: More information about PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html - https://docs.platformio.org/page/plus/unit-testing.html
""") """
)
def init_ci_conf(project_dir): def init_ci_conf(project_dir):
@ -263,7 +272,8 @@ def init_ci_conf(project_dir):
if isfile(conf_path): if isfile(conf_path):
return return
with open(conf_path, "w") as f: with open(conf_path, "w") as f:
f.write("""# Continuous Integration (CI) is the practice, in software f.write(
"""# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline # engineering, of merging all developer working copies with a shared mainline
# several times a day < https://docs.platformio.org/page/ci/index.html > # several times a day < https://docs.platformio.org/page/ci/index.html >
# #
@ -330,7 +340,8 @@ def init_ci_conf(project_dir):
# #
# script: # script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N
""") """
)
def init_cvs_ignore(project_dir): def init_cvs_ignore(project_dir):
@ -341,16 +352,13 @@ def init_cvs_ignore(project_dir):
fp.write(".pio\n") fp.write(".pio\n")
def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix, def fill_project_envs(
force_download): ctx, project_dir, board_ids, project_option, env_prefix, force_download
config = ProjectConfig(join(project_dir, "platformio.ini"), ):
parse_extra=False) config = ProjectConfig(join(project_dir, "platformio.ini"), parse_extra=False)
used_boards = [] used_boards = []
for section in config.sections(): for section in config.sections():
cond = [ cond = [section.startswith("env:"), config.has_option(section, "board")]
section.startswith("env:"),
config.has_option(section, "board")
]
if all(cond): if all(cond):
used_boards.append(config.get(section, "board")) used_boards.append(config.get(section, "board"))
@ -359,17 +367,17 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix,
modified = False modified = False
for id_ in board_ids: for id_ in board_ids:
board_config = pm.board_config(id_) board_config = pm.board_config(id_)
used_platforms.append(board_config['platform']) used_platforms.append(board_config["platform"])
if id_ in used_boards: if id_ in used_boards:
continue continue
used_boards.append(id_) used_boards.append(id_)
modified = True modified = True
envopts = {"platform": board_config['platform'], "board": id_} envopts = {"platform": board_config["platform"], "board": id_}
# find default framework for board # find default framework for board
frameworks = board_config.get("frameworks") frameworks = board_config.get("frameworks")
if frameworks: if frameworks:
envopts['framework'] = frameworks[0] envopts["framework"] = frameworks[0]
for item in project_option: for item in project_option:
if "=" not in item: if "=" not in item:
@ -391,10 +399,9 @@ def fill_project_envs(ctx, project_dir, board_ids, project_option, env_prefix,
def _install_dependent_platforms(ctx, platforms): def _install_dependent_platforms(ctx, platforms):
installed_platforms = [ installed_platforms = [p["name"] for p in PlatformManager().get_installed()]
p['name'] for p in PlatformManager().get_installed()
]
if set(platforms) <= set(installed_platforms): if set(platforms) <= set(installed_platforms):
return return
ctx.invoke(cli_platform_install, ctx.invoke(
platforms=list(set(platforms) - set(installed_platforms))) cli_platform_install, platforms=list(set(platforms) - set(installed_platforms))
)

View File

@ -24,14 +24,15 @@ from tabulate import tabulate
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.commands import PlatformioCLI from platformio.commands import PlatformioCLI
from platformio.compat import dump_json_to_unicode from platformio.compat import dump_json_to_unicode
from platformio.managers.lib import (LibraryManager, get_builtin_libs, from platformio.managers.lib import LibraryManager, get_builtin_libs, is_builtin_lib
is_builtin_lib)
from platformio.proc import is_ci from platformio.proc import is_ci
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (get_project_dir, from platformio.project.helpers import (
get_project_global_lib_dir, get_project_dir,
get_project_libdeps_dir, get_project_global_lib_dir,
is_platformio_project) get_project_libdeps_dir,
is_platformio_project,
)
try: try:
from urllib.parse import quote from urllib.parse import quote
@ -45,35 +46,38 @@ CTX_META_STORAGE_LIBDEPS_KEY = __name__ + ".storage_lib_deps"
@click.group(short_help="Library Manager") @click.group(short_help="Library Manager")
@click.option("-d", @click.option(
"--storage-dir", "-d",
multiple=True, "--storage-dir",
default=None, multiple=True,
type=click.Path(exists=True, default=None,
file_okay=False, type=click.Path(
dir_okay=True, exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True), help="Manage custom library storage",
help="Manage custom library storage") )
@click.option("-g", @click.option(
"--global", "-g", "--global", is_flag=True, help="Manage global PlatformIO library storage"
is_flag=True, )
help="Manage global PlatformIO library storage")
@click.option( @click.option(
"-e", "-e",
"--environment", "--environment",
multiple=True, multiple=True,
help=("Manage libraries for the specific project build environments " help=(
"declared in `platformio.ini`")) "Manage libraries for the specific project build environments "
"declared in `platformio.ini`"
),
)
@click.pass_context @click.pass_context
def cli(ctx, **options): def cli(ctx, **options):
storage_cmds = ("install", "uninstall", "update", "list") storage_cmds = ("install", "uninstall", "update", "list")
# skip commands that don't need storage folder # skip commands that don't need storage folder
if ctx.invoked_subcommand not in storage_cmds or \ if ctx.invoked_subcommand not in storage_cmds or (
(len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")): len(ctx.args) == 2 and ctx.args[1] in ("-h", "--help")
):
return return
storage_dirs = list(options['storage_dir']) storage_dirs = list(options["storage_dir"])
if options['global']: if options["global"]:
storage_dirs.append(get_project_global_lib_dir()) storage_dirs.append(get_project_global_lib_dir())
if not storage_dirs: if not storage_dirs:
if is_platformio_project(): if is_platformio_project():
@ -84,15 +88,16 @@ def cli(ctx, **options):
"Warning! Global library storage is used automatically. " "Warning! Global library storage is used automatically. "
"Please use `platformio lib --global %s` command to remove " "Please use `platformio lib --global %s` command to remove "
"this warning." % ctx.invoked_subcommand, "this warning." % ctx.invoked_subcommand,
fg="yellow") fg="yellow",
)
if not storage_dirs: if not storage_dirs:
raise exception.NotGlobalLibDir(get_project_dir(), raise exception.NotGlobalLibDir(
get_project_global_lib_dir(), get_project_dir(), get_project_global_lib_dir(), ctx.invoked_subcommand
ctx.invoked_subcommand) )
in_silence = PlatformioCLI.in_silence() in_silence = PlatformioCLI.in_silence()
ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options['environment'] ctx.meta[CTX_META_PROJECT_ENVIRONMENTS_KEY] = options["environment"]
ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs ctx.meta[CTX_META_INPUT_DIRS_KEY] = storage_dirs
ctx.meta[CTX_META_STORAGE_DIRS_KEY] = [] ctx.meta[CTX_META_STORAGE_DIRS_KEY] = []
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {} ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY] = {}
@ -102,16 +107,16 @@ def cli(ctx, **options):
continue continue
with fs.cd(storage_dir): with fs.cd(storage_dir):
libdeps_dir = get_project_libdeps_dir() libdeps_dir = get_project_libdeps_dir()
config = ProjectConfig.get_instance(join(storage_dir, config = ProjectConfig.get_instance(join(storage_dir, "platformio.ini"))
"platformio.ini")) config.validate(options["environment"], silent=in_silence)
config.validate(options['environment'], silent=in_silence)
for env in config.envs(): for env in config.envs():
if options['environment'] and env not in options['environment']: if options["environment"] and env not in options["environment"]:
continue continue
storage_dir = join(libdeps_dir, env) storage_dir = join(libdeps_dir, env)
ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir) ctx.meta[CTX_META_STORAGE_DIRS_KEY].append(storage_dir)
ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get( ctx.meta[CTX_META_STORAGE_LIBDEPS_KEY][storage_dir] = config.get(
"env:" + env, "lib_deps", []) "env:" + env, "lib_deps", []
)
@cli.command("install", short_help="Install library") @cli.command("install", short_help="Install library")
@ -119,21 +124,19 @@ def cli(ctx, **options):
@click.option( @click.option(
"--save", "--save",
is_flag=True, is_flag=True,
help="Save installed libraries into the `platformio.ini` dependency list") help="Save installed libraries into the `platformio.ini` dependency list",
@click.option("-s", )
"--silent", @click.option("-s", "--silent", is_flag=True, help="Suppress progress reporting")
is_flag=True, @click.option(
help="Suppress progress reporting") "--interactive", is_flag=True, help="Allow to make a choice for all prompts"
@click.option("--interactive", )
is_flag=True, @click.option(
help="Allow to make a choice for all prompts") "-f", "--force", is_flag=True, help="Reinstall/redownload library if exists"
@click.option("-f", )
"--force",
is_flag=True,
help="Reinstall/redownload library if exists")
@click.pass_context @click.pass_context
def lib_install( # pylint: disable=too-many-arguments def lib_install( # pylint: disable=too-many-arguments
ctx, libraries, save, silent, interactive, force): ctx, libraries, save, silent, interactive, force
):
storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY] storage_dirs = ctx.meta[CTX_META_STORAGE_DIRS_KEY]
storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, []) storage_libdeps = ctx.meta.get(CTX_META_STORAGE_LIBDEPS_KEY, [])
@ -144,25 +147,22 @@ def lib_install( # pylint: disable=too-many-arguments
lm = LibraryManager(storage_dir) lm = LibraryManager(storage_dir)
if libraries: if libraries:
for library in libraries: for library in libraries:
pkg_dir = lm.install(library, pkg_dir = lm.install(
silent=silent, library, silent=silent, interactive=interactive, force=force
interactive=interactive, )
force=force)
installed_manifests[library] = lm.load_manifest(pkg_dir) installed_manifests[library] = lm.load_manifest(pkg_dir)
elif storage_dir in storage_libdeps: elif storage_dir in storage_libdeps:
builtin_lib_storages = None builtin_lib_storages = None
for library in storage_libdeps[storage_dir]: for library in storage_libdeps[storage_dir]:
try: try:
pkg_dir = lm.install(library, pkg_dir = lm.install(
silent=silent, library, silent=silent, interactive=interactive, force=force
interactive=interactive, )
force=force)
installed_manifests[library] = lm.load_manifest(pkg_dir) installed_manifests[library] = lm.load_manifest(pkg_dir)
except exception.LibNotFound as e: except exception.LibNotFound as e:
if builtin_lib_storages is None: if builtin_lib_storages is None:
builtin_lib_storages = get_builtin_libs() builtin_lib_storages = get_builtin_libs()
if not silent or not is_builtin_lib( if not silent or not is_builtin_lib(builtin_lib_storages, library):
builtin_lib_storages, library):
click.secho("Warning! %s" % e, fg="yellow") click.secho("Warning! %s" % e, fg="yellow")
if not save or not libraries: if not save or not libraries:
@ -183,8 +183,8 @@ def lib_install( # pylint: disable=too-many-arguments
continue continue
manifest = installed_manifests[library] manifest = installed_manifests[library]
try: try:
assert library.lower() == manifest['name'].lower() assert library.lower() == manifest["name"].lower()
assert semantic_version.Version(manifest['version']) assert semantic_version.Version(manifest["version"])
lib_deps.append("{name}@^{version}".format(**manifest)) lib_deps.append("{name}@^{version}".format(**manifest))
except (AssertionError, ValueError): except (AssertionError, ValueError):
lib_deps.append(library) lib_deps.append(library)
@ -206,13 +206,15 @@ def lib_uninstall(ctx, libraries):
@cli.command("update", short_help="Update installed libraries") @cli.command("update", short_help="Update installed libraries")
@click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]") @click.argument("libraries", required=False, nargs=-1, metavar="[LIBRARY...]")
@click.option("-c", @click.option(
"--only-check", "-c",
is_flag=True, "--only-check",
help="DEPRECATED. Please use `--dry-run` instead") is_flag=True,
@click.option("--dry-run", help="DEPRECATED. Please use `--dry-run` instead",
is_flag=True, )
help="Do not update, only check for the new versions") @click.option(
"--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
@click.pass_context @click.pass_context
def lib_update(ctx, libraries, only_check, dry_run, json_output): def lib_update(ctx, libraries, only_check, dry_run, json_output):
@ -226,9 +228,7 @@ def lib_update(ctx, libraries, only_check, dry_run, json_output):
_libraries = libraries _libraries = libraries
if not _libraries: if not _libraries:
_libraries = [ _libraries = [manifest["__pkg_dir"] for manifest in lm.get_installed()]
manifest['__pkg_dir'] for manifest in lm.get_installed()
]
if only_check and json_output: if only_check and json_output:
result = [] result = []
@ -245,7 +245,7 @@ def lib_update(ctx, libraries, only_check, dry_run, json_output):
if not latest: if not latest:
continue continue
manifest = lm.load_manifest(pkg_dir) manifest = lm.load_manifest(pkg_dir)
manifest['versionLatest'] = latest manifest["versionLatest"] = latest
result.append(manifest) result.append(manifest)
json_result[storage_dir] = result json_result[storage_dir] = result
else: else:
@ -254,8 +254,10 @@ def lib_update(ctx, libraries, only_check, dry_run, json_output):
if json_output: if json_output:
return click.echo( return click.echo(
dump_json_to_unicode(json_result[storage_dirs[0]] dump_json_to_unicode(
if len(storage_dirs) == 1 else json_result)) json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
)
)
return True return True
@ -274,15 +276,17 @@ def lib_list(ctx, json_output):
if json_output: if json_output:
json_result[storage_dir] = items json_result[storage_dir] = items
elif items: elif items:
for item in sorted(items, key=lambda i: i['name']): for item in sorted(items, key=lambda i: i["name"]):
print_lib_item(item) print_lib_item(item)
else: else:
click.echo("No items found") click.echo("No items found")
if json_output: if json_output:
return click.echo( return click.echo(
dump_json_to_unicode(json_result[storage_dirs[0]] dump_json_to_unicode(
if len(storage_dirs) == 1 else json_result)) json_result[storage_dirs[0]] if len(storage_dirs) == 1 else json_result
)
)
return True return True
@ -298,9 +302,11 @@ def lib_list(ctx, json_output):
@click.option("-f", "--framework", multiple=True) @click.option("-f", "--framework", multiple=True)
@click.option("-p", "--platform", multiple=True) @click.option("-p", "--platform", multiple=True)
@click.option("-i", "--header", multiple=True) @click.option("-i", "--header", multiple=True)
@click.option("--noninteractive", @click.option(
is_flag=True, "--noninteractive",
help="Do not prompt, automatically paginate with delay") is_flag=True,
help="Do not prompt, automatically paginate with delay",
)
def lib_search(query, json_output, page, noninteractive, **filters): def lib_search(query, json_output, page, noninteractive, **filters):
if not query: if not query:
query = [] query = []
@ -311,55 +317,61 @@ def lib_search(query, json_output, page, noninteractive, **filters):
for value in values: for value in values:
query.append('%s:"%s"' % (key, value)) query.append('%s:"%s"' % (key, value))
result = util.get_api_result("/v2/lib/search", result = util.get_api_result(
dict(query=" ".join(query), page=page), "/v2/lib/search", dict(query=" ".join(query), page=page), cache_valid="1d"
cache_valid="1d") )
if json_output: if json_output:
click.echo(dump_json_to_unicode(result)) click.echo(dump_json_to_unicode(result))
return return
if result['total'] == 0: if result["total"] == 0:
click.secho( click.secho(
"Nothing has been found by your request\n" "Nothing has been found by your request\n"
"Try a less-specific search or use truncation (or wildcard) " "Try a less-specific search or use truncation (or wildcard) "
"operator", "operator",
fg="yellow", fg="yellow",
nl=False) nl=False,
)
click.secho(" *", fg="green") click.secho(" *", fg="green")
click.secho("For example: DS*, PCA*, DHT* and etc.\n", fg="yellow") click.secho("For example: DS*, PCA*, DHT* and etc.\n", fg="yellow")
click.echo("For more examples and advanced search syntax, " click.echo(
"please use documentation:") "For more examples and advanced search syntax, " "please use documentation:"
)
click.secho( click.secho(
"https://docs.platformio.org/page/userguide/lib/cmd_search.html\n", "https://docs.platformio.org/page/userguide/lib/cmd_search.html\n",
fg="cyan") fg="cyan",
)
return return
click.secho("Found %d libraries:\n" % result['total'], click.secho(
fg="green" if result['total'] else "yellow") "Found %d libraries:\n" % result["total"],
fg="green" if result["total"] else "yellow",
)
while True: while True:
for item in result['items']: for item in result["items"]:
print_lib_item(item) print_lib_item(item)
if (int(result['page']) * int(result['perpage']) >= int( if int(result["page"]) * int(result["perpage"]) >= int(result["total"]):
result['total'])):
break break
if noninteractive: if noninteractive:
click.echo() click.echo()
click.secho("Loading next %d libraries... Press Ctrl+C to stop!" % click.secho(
result['perpage'], "Loading next %d libraries... Press Ctrl+C to stop!"
fg="yellow") % result["perpage"],
fg="yellow",
)
click.echo() click.echo()
time.sleep(5) time.sleep(5)
elif not click.confirm("Show next libraries?"): elif not click.confirm("Show next libraries?"):
break break
result = util.get_api_result("/v2/lib/search", { result = util.get_api_result(
"query": " ".join(query), "/v2/lib/search",
"page": int(result['page']) + 1 {"query": " ".join(query), "page": int(result["page"]) + 1},
}, cache_valid="1d",
cache_valid="1d") )
@cli.command("builtin", short_help="List built-in libraries") @cli.command("builtin", short_help="List built-in libraries")
@ -371,13 +383,13 @@ def lib_builtin(storage, json_output):
return click.echo(dump_json_to_unicode(items)) return click.echo(dump_json_to_unicode(items))
for storage_ in items: for storage_ in items:
if not storage_['items']: if not storage_["items"]:
continue continue
click.secho(storage_['name'], fg="green") click.secho(storage_["name"], fg="green")
click.echo("*" * len(storage_['name'])) click.echo("*" * len(storage_["name"]))
click.echo() click.echo()
for item in sorted(storage_['items'], key=lambda i: i['name']): for item in sorted(storage_["items"], key=lambda i: i["name"]):
print_lib_item(item) print_lib_item(item)
return True return True
@ -389,27 +401,29 @@ def lib_builtin(storage, json_output):
def lib_show(library, json_output): def lib_show(library, json_output):
lm = LibraryManager() lm = LibraryManager()
name, requirements, _ = lm.parse_pkg_uri(library) name, requirements, _ = lm.parse_pkg_uri(library)
lib_id = lm.search_lib_id({ lib_id = lm.search_lib_id(
"name": name, {"name": name, "requirements": requirements},
"requirements": requirements silent=json_output,
}, interactive=not json_output,
silent=json_output, )
interactive=not json_output)
lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d") lib = util.get_api_result("/lib/info/%d" % lib_id, cache_valid="1d")
if json_output: if json_output:
return click.echo(dump_json_to_unicode(lib)) return click.echo(dump_json_to_unicode(lib))
click.secho(lib['name'], fg="cyan") click.secho(lib["name"], fg="cyan")
click.echo("=" * len(lib['name'])) click.echo("=" * len(lib["name"]))
click.secho("#ID: %d" % lib['id'], bold=True) click.secho("#ID: %d" % lib["id"], bold=True)
click.echo(lib['description']) click.echo(lib["description"])
click.echo() click.echo()
click.echo( click.echo(
"Version: %s, released %s" % "Version: %s, released %s"
(lib['version']['name'], % (
time.strftime("%c", util.parse_date(lib['version']['released'])))) lib["version"]["name"],
click.echo("Manifest: %s" % lib['confurl']) time.strftime("%c", util.parse_date(lib["version"]["released"])),
)
)
click.echo("Manifest: %s" % lib["confurl"])
for key in ("homepage", "repository", "license"): for key in ("homepage", "repository", "license"):
if key not in lib or not lib[key]: if key not in lib or not lib[key]:
continue continue
@ -436,23 +450,33 @@ def lib_show(library, json_output):
if _authors: if _authors:
blocks.append(("Authors", _authors)) blocks.append(("Authors", _authors))
blocks.append(("Keywords", lib['keywords'])) blocks.append(("Keywords", lib["keywords"]))
for key in ("frameworks", "platforms"): for key in ("frameworks", "platforms"):
if key not in lib or not lib[key]: if key not in lib or not lib[key]:
continue continue
blocks.append(("Compatible %s" % key, [i['title'] for i in lib[key]])) blocks.append(("Compatible %s" % key, [i["title"] for i in lib[key]]))
blocks.append(("Headers", lib['headers'])) blocks.append(("Headers", lib["headers"]))
blocks.append(("Examples", lib['examples'])) blocks.append(("Examples", lib["examples"]))
blocks.append(("Versions", [ blocks.append(
"%s, released %s" % (
(v['name'], time.strftime("%c", util.parse_date(v['released']))) "Versions",
for v in lib['versions'] [
])) "%s, released %s"
blocks.append(("Unique Downloads", [ % (v["name"], time.strftime("%c", util.parse_date(v["released"])))
"Today: %s" % lib['dlstats']['day'], for v in lib["versions"]
"Week: %s" % lib['dlstats']['week'], ],
"Month: %s" % lib['dlstats']['month'] )
])) )
blocks.append(
(
"Unique Downloads",
[
"Today: %s" % lib["dlstats"]["day"],
"Week: %s" % lib["dlstats"]["week"],
"Month: %s" % lib["dlstats"]["month"],
],
)
)
for (title, rows) in blocks: for (title, rows) in blocks:
click.echo() click.echo()
@ -467,16 +491,15 @@ def lib_show(library, json_output):
@cli.command("register", short_help="Register a new library") @cli.command("register", short_help="Register a new library")
@click.argument("config_url") @click.argument("config_url")
def lib_register(config_url): def lib_register(config_url):
if (not config_url.startswith("http://") if not config_url.startswith("http://") and not config_url.startswith("https://"):
and not config_url.startswith("https://")):
raise exception.InvalidLibConfURL(config_url) raise exception.InvalidLibConfURL(config_url)
result = util.get_api_result("/lib/register", result = util.get_api_result("/lib/register", data=dict(config_url=config_url))
data=dict(config_url=config_url)) if "message" in result and result["message"]:
if "message" in result and result['message']: click.secho(
click.secho(result['message'], result["message"],
fg="green" if "successed" in result and result['successed'] fg="green" if "successed" in result and result["successed"] else "red",
else "red") )
@cli.command("stats", short_help="Library Registry Statistics") @cli.command("stats", short_help="Library Registry Statistics")
@ -488,46 +511,56 @@ def lib_stats(json_output):
return click.echo(dump_json_to_unicode(result)) return click.echo(dump_json_to_unicode(result))
for key in ("updated", "added"): for key in ("updated", "added"):
tabular_data = [(click.style(item['name'], fg="cyan"), tabular_data = [
time.strftime("%c", util.parse_date(item['date'])), (
"https://platformio.org/lib/show/%s/%s" % click.style(item["name"], fg="cyan"),
(item['id'], quote(item['name']))) time.strftime("%c", util.parse_date(item["date"])),
for item in result.get(key, [])] "https://platformio.org/lib/show/%s/%s"
table = tabulate(tabular_data, % (item["id"], quote(item["name"])),
headers=[ )
click.style("RECENTLY " + key.upper(), bold=True), for item in result.get(key, [])
"Date", "URL" ]
]) table = tabulate(
tabular_data,
headers=[click.style("RECENTLY " + key.upper(), bold=True), "Date", "URL"],
)
click.echo(table) click.echo(table)
click.echo() click.echo()
for key in ("lastkeywords", "topkeywords"): for key in ("lastkeywords", "topkeywords"):
tabular_data = [(click.style(name, fg="cyan"), tabular_data = [
"https://platformio.org/lib/search?query=" + (
quote("keyword:%s" % name)) click.style(name, fg="cyan"),
for name in result.get(key, [])] "https://platformio.org/lib/search?query=" + quote("keyword:%s" % name),
)
for name in result.get(key, [])
]
table = tabulate( table = tabulate(
tabular_data, tabular_data,
headers=[ headers=[
click.style( click.style(
("RECENT" if key == "lastkeywords" else "POPULAR") + ("RECENT" if key == "lastkeywords" else "POPULAR") + " KEYWORDS",
" KEYWORDS", bold=True,
bold=True), "URL" ),
]) "URL",
],
)
click.echo(table) click.echo(table)
click.echo() click.echo()
for key, title in (("dlday", "Today"), ("dlweek", "Week"), ("dlmonth", for key, title in (("dlday", "Today"), ("dlweek", "Week"), ("dlmonth", "Month")):
"Month")): tabular_data = [
tabular_data = [(click.style(item['name'], fg="cyan"), (
"https://platformio.org/lib/show/%s/%s" % click.style(item["name"], fg="cyan"),
(item['id'], quote(item['name']))) "https://platformio.org/lib/show/%s/%s"
for item in result.get(key, [])] % (item["id"], quote(item["name"])),
table = tabulate(tabular_data, )
headers=[ for item in result.get(key, [])
click.style("FEATURED: " + title.upper(), ]
bold=True), "URL" table = tabulate(
]) tabular_data,
headers=[click.style("FEATURED: " + title.upper(), bold=True), "URL"],
)
click.echo(table) click.echo(table)
click.echo() click.echo()
@ -538,15 +571,16 @@ def print_storage_header(storage_dirs, storage_dir):
if storage_dirs and storage_dirs[0] != storage_dir: if storage_dirs and storage_dirs[0] != storage_dir:
click.echo("") click.echo("")
click.echo( click.echo(
click.style("Library Storage: ", bold=True) + click.style("Library Storage: ", bold=True)
click.style(storage_dir, fg="blue")) + click.style(storage_dir, fg="blue")
)
def print_lib_item(item): def print_lib_item(item):
click.secho(item['name'], fg="cyan") click.secho(item["name"], fg="cyan")
click.echo("=" * len(item['name'])) click.echo("=" * len(item["name"]))
if "id" in item: if "id" in item:
click.secho("#ID: %d" % item['id'], bold=True) click.secho("#ID: %d" % item["id"], bold=True)
if "description" in item or "url" in item: if "description" in item or "url" in item:
click.echo(item.get("description", item.get("url", ""))) click.echo(item.get("description", item.get("url", "")))
click.echo() click.echo()
@ -562,14 +596,26 @@ def print_lib_item(item):
for key in ("frameworks", "platforms"): for key in ("frameworks", "platforms"):
if key not in item: if key not in item:
continue continue
click.echo("Compatible %s: %s" % (key, ", ".join( click.echo(
[i['title'] if isinstance(i, dict) else i for i in item[key]]))) "Compatible %s: %s"
% (
key,
", ".join(
[i["title"] if isinstance(i, dict) else i for i in item[key]]
),
)
)
if "authors" in item or "authornames" in item: if "authors" in item or "authornames" in item:
click.echo("Authors: %s" % ", ".join( click.echo(
item.get("authornames", "Authors: %s"
[a.get("name", "") for a in item.get("authors", [])]))) % ", ".join(
item.get(
"authornames", [a.get("name", "") for a in item.get("authors", [])]
)
)
)
if "__src_url" in item: if "__src_url" in item:
click.secho("Source: %s" % item['__src_url']) click.secho("Source: %s" % item["__src_url"])
click.echo() click.echo()

View File

@ -29,24 +29,27 @@ def cli():
def _print_platforms(platforms): def _print_platforms(platforms):
for platform in platforms: for platform in platforms:
click.echo("{name} ~ {title}".format(name=click.style(platform['name'], click.echo(
fg="cyan"), "{name} ~ {title}".format(
title=platform['title'])) name=click.style(platform["name"], fg="cyan"), title=platform["title"]
click.echo("=" * (3 + len(platform['name'] + platform['title']))) )
click.echo(platform['description']) )
click.echo("=" * (3 + len(platform["name"] + platform["title"])))
click.echo(platform["description"])
click.echo() click.echo()
if "homepage" in platform: if "homepage" in platform:
click.echo("Home: %s" % platform['homepage']) click.echo("Home: %s" % platform["homepage"])
if "frameworks" in platform and platform['frameworks']: if "frameworks" in platform and platform["frameworks"]:
click.echo("Frameworks: %s" % ", ".join(platform['frameworks'])) click.echo("Frameworks: %s" % ", ".join(platform["frameworks"]))
if "packages" in platform: if "packages" in platform:
click.echo("Packages: %s" % ", ".join(platform['packages'])) click.echo("Packages: %s" % ", ".join(platform["packages"]))
if "version" in platform: if "version" in platform:
if "__src_url" in platform: if "__src_url" in platform:
click.echo("Version: #%s (%s)" % click.echo(
(platform['version'], platform['__src_url'])) "Version: #%s (%s)" % (platform["version"], platform["__src_url"])
)
else: else:
click.echo("Version: " + platform['version']) click.echo("Version: " + platform["version"])
click.echo() click.echo()
@ -54,7 +57,7 @@ def _get_registry_platforms():
platforms = util.get_api_result("/platforms", cache_valid="7d") platforms = util.get_api_result("/platforms", cache_valid="7d")
pm = PlatformManager() pm = PlatformManager()
for platform in platforms or []: for platform in platforms or []:
platform['versions'] = pm.get_all_repo_versions(platform['name']) platform["versions"] = pm.get_all_repo_versions(platform["name"])
return platforms return platforms
@ -65,22 +68,22 @@ def _get_platform_data(*args, **kwargs):
return _get_registry_platform_data(*args, **kwargs) return _get_registry_platform_data(*args, **kwargs)
def _get_installed_platform_data(platform, def _get_installed_platform_data(platform, with_boards=True, expose_packages=True):
with_boards=True,
expose_packages=True):
p = PlatformFactory.newPlatform(platform) p = PlatformFactory.newPlatform(platform)
data = dict(name=p.name, data = dict(
title=p.title, name=p.name,
description=p.description, title=p.title,
version=p.version, description=p.description,
homepage=p.homepage, version=p.version,
repository=p.repository_url, homepage=p.homepage,
url=p.vendor_url, repository=p.repository_url,
docs=p.docs_url, url=p.vendor_url,
license=p.license, docs=p.docs_url,
forDesktop=not p.is_embedded(), license=p.license,
frameworks=sorted(list(p.frameworks) if p.frameworks else []), forDesktop=not p.is_embedded(),
packages=list(p.packages) if p.packages else []) frameworks=sorted(list(p.frameworks) if p.frameworks else []),
packages=list(p.packages) if p.packages else [],
)
# if dump to API # if dump to API
# del data['version'] # del data['version']
@ -94,18 +97,20 @@ def _get_installed_platform_data(platform,
data[key] = manifest[key] data[key] = manifest[key]
if with_boards: if with_boards:
data['boards'] = [c.get_brief_data() for c in p.get_boards().values()] data["boards"] = [c.get_brief_data() for c in p.get_boards().values()]
if not data['packages'] or not expose_packages: if not data["packages"] or not expose_packages:
return data return data
data['packages'] = [] data["packages"] = []
installed_pkgs = p.get_installed_packages() installed_pkgs = p.get_installed_packages()
for name, opts in p.packages.items(): for name, opts in p.packages.items():
item = dict(name=name, item = dict(
type=p.get_package_type(name), name=name,
requirements=opts.get("version"), type=p.get_package_type(name),
optional=opts.get("optional") is True) requirements=opts.get("version"),
optional=opts.get("optional") is True,
)
if name in installed_pkgs: if name in installed_pkgs:
for key, value in installed_pkgs[name].items(): for key, value in installed_pkgs[name].items():
if key not in ("url", "version", "description"): if key not in ("url", "version", "description"):
@ -113,40 +118,42 @@ def _get_installed_platform_data(platform,
item[key] = value item[key] = value
if key == "version": if key == "version":
item["originalVersion"] = util.get_original_version(value) item["originalVersion"] = util.get_original_version(value)
data['packages'].append(item) data["packages"].append(item)
return data return data
def _get_registry_platform_data( # pylint: disable=unused-argument def _get_registry_platform_data( # pylint: disable=unused-argument
platform, platform, with_boards=True, expose_packages=True
with_boards=True, ):
expose_packages=True):
_data = None _data = None
for p in _get_registry_platforms(): for p in _get_registry_platforms():
if p['name'] == platform: if p["name"] == platform:
_data = p _data = p
break break
if not _data: if not _data:
return None return None
data = dict(name=_data['name'], data = dict(
title=_data['title'], name=_data["name"],
description=_data['description'], title=_data["title"],
homepage=_data['homepage'], description=_data["description"],
repository=_data['repository'], homepage=_data["homepage"],
url=_data['url'], repository=_data["repository"],
license=_data['license'], url=_data["url"],
forDesktop=_data['forDesktop'], license=_data["license"],
frameworks=_data['frameworks'], forDesktop=_data["forDesktop"],
packages=_data['packages'], frameworks=_data["frameworks"],
versions=_data['versions']) packages=_data["packages"],
versions=_data["versions"],
)
if with_boards: if with_boards:
data['boards'] = [ data["boards"] = [
board for board in PlatformManager().get_registered_boards() board
if board['platform'] == _data['name'] for board in PlatformManager().get_registered_boards()
if board["platform"] == _data["name"]
] ]
return data return data
@ -164,9 +171,10 @@ def platform_search(query, json_output):
if query and query.lower() not in search_data.lower(): if query and query.lower() not in search_data.lower():
continue continue
platforms.append( platforms.append(
_get_registry_platform_data(platform['name'], _get_registry_platform_data(
with_boards=False, platform["name"], with_boards=False, expose_packages=False
expose_packages=False)) )
)
if json_output: if json_output:
click.echo(dump_json_to_unicode(platforms)) click.echo(dump_json_to_unicode(platforms))
@ -185,15 +193,15 @@ def platform_frameworks(query, json_output):
search_data = dump_json_to_unicode(framework) search_data = dump_json_to_unicode(framework)
if query and query.lower() not in search_data.lower(): if query and query.lower() not in search_data.lower():
continue continue
framework['homepage'] = ("https://platformio.org/frameworks/" + framework["homepage"] = "https://platformio.org/frameworks/" + framework["name"]
framework['name']) framework["platforms"] = [
framework['platforms'] = [ platform["name"]
platform['name'] for platform in _get_registry_platforms() for platform in _get_registry_platforms()
if framework['name'] in platform['frameworks'] if framework["name"] in platform["frameworks"]
] ]
frameworks.append(framework) frameworks.append(framework)
frameworks = sorted(frameworks, key=lambda manifest: manifest['name']) frameworks = sorted(frameworks, key=lambda manifest: manifest["name"])
if json_output: if json_output:
click.echo(dump_json_to_unicode(frameworks)) click.echo(dump_json_to_unicode(frameworks))
else: else:
@ -207,11 +215,12 @@ def platform_list(json_output):
pm = PlatformManager() pm = PlatformManager()
for manifest in pm.get_installed(): for manifest in pm.get_installed():
platforms.append( platforms.append(
_get_installed_platform_data(manifest['__pkg_dir'], _get_installed_platform_data(
with_boards=False, manifest["__pkg_dir"], with_boards=False, expose_packages=False
expose_packages=False)) )
)
platforms = sorted(platforms, key=lambda manifest: manifest['name']) platforms = sorted(platforms, key=lambda manifest: manifest["name"])
if json_output: if json_output:
click.echo(dump_json_to_unicode(platforms)) click.echo(dump_json_to_unicode(platforms))
else: else:
@ -228,55 +237,58 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches
if json_output: if json_output:
return click.echo(dump_json_to_unicode(data)) return click.echo(dump_json_to_unicode(data))
click.echo("{name} ~ {title}".format(name=click.style(data['name'], click.echo(
fg="cyan"), "{name} ~ {title}".format(
title=data['title'])) name=click.style(data["name"], fg="cyan"), title=data["title"]
click.echo("=" * (3 + len(data['name'] + data['title']))) )
click.echo(data['description']) )
click.echo("=" * (3 + len(data["name"] + data["title"])))
click.echo(data["description"])
click.echo() click.echo()
if "version" in data: if "version" in data:
click.echo("Version: %s" % data['version']) click.echo("Version: %s" % data["version"])
if data['homepage']: if data["homepage"]:
click.echo("Home: %s" % data['homepage']) click.echo("Home: %s" % data["homepage"])
if data['repository']: if data["repository"]:
click.echo("Repository: %s" % data['repository']) click.echo("Repository: %s" % data["repository"])
if data['url']: if data["url"]:
click.echo("Vendor: %s" % data['url']) click.echo("Vendor: %s" % data["url"])
if data['license']: if data["license"]:
click.echo("License: %s" % data['license']) click.echo("License: %s" % data["license"])
if data['frameworks']: if data["frameworks"]:
click.echo("Frameworks: %s" % ", ".join(data['frameworks'])) click.echo("Frameworks: %s" % ", ".join(data["frameworks"]))
if not data['packages']: if not data["packages"]:
return None return None
if not isinstance(data['packages'][0], dict): if not isinstance(data["packages"][0], dict):
click.echo("Packages: %s" % ", ".join(data['packages'])) click.echo("Packages: %s" % ", ".join(data["packages"]))
else: else:
click.echo() click.echo()
click.secho("Packages", bold=True) click.secho("Packages", bold=True)
click.echo("--------") click.echo("--------")
for item in data['packages']: for item in data["packages"]:
click.echo() click.echo()
click.echo("Package %s" % click.style(item['name'], fg="yellow")) click.echo("Package %s" % click.style(item["name"], fg="yellow"))
click.echo("-" * (8 + len(item['name']))) click.echo("-" * (8 + len(item["name"])))
if item['type']: if item["type"]:
click.echo("Type: %s" % item['type']) click.echo("Type: %s" % item["type"])
click.echo("Requirements: %s" % item['requirements']) click.echo("Requirements: %s" % item["requirements"])
click.echo("Installed: %s" % click.echo(
("Yes" if item.get("version") else "No (optional)")) "Installed: %s" % ("Yes" if item.get("version") else "No (optional)")
)
if "version" in item: if "version" in item:
click.echo("Version: %s" % item['version']) click.echo("Version: %s" % item["version"])
if "originalVersion" in item: if "originalVersion" in item:
click.echo("Original version: %s" % item['originalVersion']) click.echo("Original version: %s" % item["originalVersion"])
if "description" in item: if "description" in item:
click.echo("Description: %s" % item['description']) click.echo("Description: %s" % item["description"])
if data['boards']: if data["boards"]:
click.echo() click.echo()
click.secho("Boards", bold=True) click.secho("Boards", bold=True)
click.echo("------") click.echo("------")
print_boards(data['boards']) print_boards(data["boards"])
return True return True
@ -290,20 +302,26 @@ def platform_show(platform, json_output): # pylint: disable=too-many-branches
"-f", "-f",
"--force", "--force",
is_flag=True, is_flag=True,
help="Reinstall/redownload dev/platform and its packages if exist") help="Reinstall/redownload dev/platform and its packages if exist",
def platform_install(platforms, with_package, without_package, )
skip_default_package, force): def platform_install(
platforms, with_package, without_package, skip_default_package, force
):
pm = PlatformManager() pm = PlatformManager()
for platform in platforms: for platform in platforms:
if pm.install(name=platform, if pm.install(
with_packages=with_package, name=platform,
without_packages=without_package, with_packages=with_package,
skip_default_package=skip_default_package, without_packages=without_package,
force=force): skip_default_package=skip_default_package,
click.secho("The platform '%s' has been successfully installed!\n" force=force,
"The rest of packages will be installed automatically " ):
"depending on your build environment." % platform, click.secho(
fg="green") "The platform '%s' has been successfully installed!\n"
"The rest of packages will be installed automatically "
"depending on your build environment." % platform,
fg="green",
)
@cli.command("uninstall", short_help="Uninstall development platform") @cli.command("uninstall", short_help="Uninstall development platform")
@ -312,35 +330,39 @@ def platform_uninstall(platforms):
pm = PlatformManager() pm = PlatformManager()
for platform in platforms: for platform in platforms:
if pm.uninstall(platform): if pm.uninstall(platform):
click.secho("The platform '%s' has been successfully " click.secho(
"uninstalled!" % platform, "The platform '%s' has been successfully " "uninstalled!" % platform,
fg="green") fg="green",
)
@cli.command("update", short_help="Update installed development platforms") @cli.command("update", short_help="Update installed development platforms")
@click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]") @click.argument("platforms", nargs=-1, required=False, metavar="[PLATFORM...]")
@click.option("-p", @click.option(
"--only-packages", "-p", "--only-packages", is_flag=True, help="Update only the platform packages"
is_flag=True, )
help="Update only the platform packages") @click.option(
@click.option("-c", "-c",
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="DEPRECATED. Please use `--dry-run` instead") help="DEPRECATED. Please use `--dry-run` instead",
@click.option("--dry-run", )
is_flag=True, @click.option(
help="Do not update, only check for the new versions") "--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
@click.option("--json-output", is_flag=True) @click.option("--json-output", is_flag=True)
def platform_update( # pylint: disable=too-many-locals def platform_update( # pylint: disable=too-many-locals
platforms, only_packages, only_check, dry_run, json_output): platforms, only_packages, only_check, dry_run, json_output
):
pm = PlatformManager() pm = PlatformManager()
pkg_dir_to_name = {} pkg_dir_to_name = {}
if not platforms: if not platforms:
platforms = [] platforms = []
for manifest in pm.get_installed(): for manifest in pm.get_installed():
platforms.append(manifest['__pkg_dir']) platforms.append(manifest["__pkg_dir"])
pkg_dir_to_name[manifest['__pkg_dir']] = manifest.get( pkg_dir_to_name[manifest["__pkg_dir"]] = manifest.get(
"title", manifest['name']) "title", manifest["name"]
)
only_check = dry_run or only_check only_check = dry_run or only_check
@ -356,14 +378,16 @@ def platform_update( # pylint: disable=too-many-locals
if not pkg_dir: if not pkg_dir:
continue continue
latest = pm.outdated(pkg_dir, requirements) latest = pm.outdated(pkg_dir, requirements)
if (not latest and not PlatformFactory.newPlatform( if (
pkg_dir).are_outdated_packages()): not latest
and not PlatformFactory.newPlatform(pkg_dir).are_outdated_packages()
):
continue continue
data = _get_installed_platform_data(pkg_dir, data = _get_installed_platform_data(
with_boards=False, pkg_dir, with_boards=False, expose_packages=False
expose_packages=False) )
if latest: if latest:
data['versionLatest'] = latest data["versionLatest"] = latest
result.append(data) result.append(data)
return click.echo(dump_json_to_unicode(result)) return click.echo(dump_json_to_unicode(result))
@ -371,8 +395,9 @@ def platform_update( # pylint: disable=too-many-locals
app.clean_cache() app.clean_cache()
for platform in platforms: for platform in platforms:
click.echo( click.echo(
"Platform %s" % "Platform %s"
click.style(pkg_dir_to_name.get(platform, platform), fg="cyan")) % click.style(pkg_dir_to_name.get(platform, platform), fg="cyan")
)
click.echo("--------") click.echo("--------")
pm.update(platform, only_packages=only_packages, only_check=only_check) pm.update(platform, only_packages=only_packages, only_check=only_check)
click.echo() click.echo()

View File

@ -43,13 +43,12 @@ def remote_agent():
@remote_agent.command("start", short_help="Start agent") @remote_agent.command("start", short_help="Start agent")
@click.option("-n", "--name") @click.option("-n", "--name")
@click.option("-s", "--share", multiple=True, metavar="E-MAIL") @click.option("-s", "--share", multiple=True, metavar="E-MAIL")
@click.option("-d", @click.option(
"--working-dir", "-d",
envvar="PLATFORMIO_REMOTE_AGENT_DIR", "--working-dir",
type=click.Path(file_okay=False, envvar="PLATFORMIO_REMOTE_AGENT_DIR",
dir_okay=True, type=click.Path(file_okay=False, dir_okay=True, writable=True, resolve_path=True),
writable=True, )
resolve_path=True))
def remote_agent_start(**kwargs): def remote_agent_start(**kwargs):
pioplus_call(sys.argv[1:]) pioplus_call(sys.argv[1:])
@ -64,15 +63,16 @@ def remote_agent_list():
pioplus_call(sys.argv[1:]) pioplus_call(sys.argv[1:])
@cli.command("update", @cli.command("update", short_help="Update installed Platforms, Packages and Libraries")
short_help="Update installed Platforms, Packages and Libraries") @click.option(
@click.option("-c", "-c",
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="DEPRECATED. Please use `--dry-run` instead") help="DEPRECATED. Please use `--dry-run` instead",
@click.option("--dry-run", )
is_flag=True, @click.option(
help="Do not update, only check for the new versions") "--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
def remote_update(only_check, dry_run): def remote_update(only_check, dry_run):
pioplus_call(sys.argv[1:]) pioplus_call(sys.argv[1:])
@ -81,14 +81,14 @@ def remote_update(only_check, dry_run):
@click.option("-e", "--environment", multiple=True) @click.option("-e", "--environment", multiple=True)
@click.option("-t", "--target", multiple=True) @click.option("-t", "--target", multiple=True)
@click.option("--upload-port") @click.option("--upload-port")
@click.option("-d", @click.option(
"--project-dir", "-d",
default=getcwd, "--project-dir",
type=click.Path(exists=True, default=getcwd,
file_okay=True, type=click.Path(
dir_okay=True, exists=True, file_okay=True, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True)) )
@click.option("--disable-auto-clean", is_flag=True) @click.option("--disable-auto-clean", is_flag=True)
@click.option("-r", "--force-remote", is_flag=True) @click.option("-r", "--force-remote", is_flag=True)
@click.option("-s", "--silent", is_flag=True) @click.option("-s", "--silent", is_flag=True)
@ -102,14 +102,14 @@ def remote_run(**kwargs):
@click.option("--ignore", "-i", multiple=True, metavar="<pattern>") @click.option("--ignore", "-i", multiple=True, metavar="<pattern>")
@click.option("--upload-port") @click.option("--upload-port")
@click.option("--test-port") @click.option("--test-port")
@click.option("-d", @click.option(
"--project-dir", "-d",
default=getcwd, "--project-dir",
type=click.Path(exists=True, default=getcwd,
file_okay=False, type=click.Path(
dir_okay=True, exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True)) )
@click.option("-r", "--force-remote", is_flag=True) @click.option("-r", "--force-remote", is_flag=True)
@click.option("--without-building", is_flag=True) @click.option("--without-building", is_flag=True)
@click.option("--without-uploading", is_flag=True) @click.option("--without-uploading", is_flag=True)
@ -131,58 +131,61 @@ def device_list(json_output):
@remote_device.command("monitor", short_help="Monitor remote device") @remote_device.command("monitor", short_help="Monitor remote device")
@click.option("--port", "-p", help="Port, a number or a device name") @click.option("--port", "-p", help="Port, a number or a device name")
@click.option("--baud", @click.option(
"-b", "--baud", "-b", type=int, default=9600, help="Set baud rate, default=9600"
type=int, )
default=9600, @click.option(
help="Set baud rate, default=9600") "--parity",
@click.option("--parity", default="N",
default="N", type=click.Choice(["N", "E", "O", "S", "M"]),
type=click.Choice(["N", "E", "O", "S", "M"]), help="Set parity, default=N",
help="Set parity, default=N") )
@click.option("--rtscts", @click.option("--rtscts", is_flag=True, help="Enable RTS/CTS flow control, default=Off")
is_flag=True, @click.option(
help="Enable RTS/CTS flow control, default=Off") "--xonxoff", is_flag=True, help="Enable software flow control, default=Off"
@click.option("--xonxoff", )
is_flag=True, @click.option(
help="Enable software flow control, default=Off") "--rts", default=None, type=click.IntRange(0, 1), help="Set initial RTS line state"
@click.option("--rts", )
default=None, @click.option(
type=click.IntRange(0, 1), "--dtr", default=None, type=click.IntRange(0, 1), help="Set initial DTR line state"
help="Set initial RTS line state") )
@click.option("--dtr",
default=None,
type=click.IntRange(0, 1),
help="Set initial DTR line state")
@click.option("--echo", is_flag=True, help="Enable local echo, default=Off") @click.option("--echo", is_flag=True, help="Enable local echo, default=Off")
@click.option("--encoding", @click.option(
default="UTF-8", "--encoding",
help="Set the encoding for the serial port (e.g. hexlify, " default="UTF-8",
"Latin1, UTF-8), default: UTF-8") help="Set the encoding for the serial port (e.g. hexlify, "
"Latin1, UTF-8), default: UTF-8",
)
@click.option("--filter", "-f", multiple=True, help="Add text transformation") @click.option("--filter", "-f", multiple=True, help="Add text transformation")
@click.option("--eol", @click.option(
default="CRLF", "--eol",
type=click.Choice(["CR", "LF", "CRLF"]), default="CRLF",
help="End of line mode, default=CRLF") type=click.Choice(["CR", "LF", "CRLF"]),
@click.option("--raw", help="End of line mode, default=CRLF",
is_flag=True, )
help="Do not apply any encodings/transformations") @click.option("--raw", is_flag=True, help="Do not apply any encodings/transformations")
@click.option("--exit-char", @click.option(
type=int, "--exit-char",
default=3, type=int,
help="ASCII code of special character that is used to exit " default=3,
"the application, default=3 (Ctrl+C)") help="ASCII code of special character that is used to exit "
@click.option("--menu-char", "the application, default=3 (Ctrl+C)",
type=int, )
default=20, @click.option(
help="ASCII code of special character that is used to " "--menu-char",
"control miniterm (menu), default=20 (DEC)") type=int,
@click.option("--quiet", default=20,
is_flag=True, help="ASCII code of special character that is used to "
help="Diagnostics: suppress non-error messages, default=Off") "control miniterm (menu), default=20 (DEC)",
)
@click.option(
"--quiet",
is_flag=True,
help="Diagnostics: suppress non-error messages, default=Off",
)
@click.pass_context @click.pass_context
def device_monitor(ctx, **kwargs): def device_monitor(ctx, **kwargs):
def _tx_target(sock_dir): def _tx_target(sock_dir):
try: try:
pioplus_call(sys.argv[1:] + ["--sock", sock_dir]) pioplus_call(sys.argv[1:] + ["--sock", sock_dir])
@ -192,13 +195,13 @@ def device_monitor(ctx, **kwargs):
sock_dir = mkdtemp(suffix="pioplus") sock_dir = mkdtemp(suffix="pioplus")
sock_file = join(sock_dir, "sock") sock_file = join(sock_dir, "sock")
try: try:
t = threading.Thread(target=_tx_target, args=(sock_dir, )) t = threading.Thread(target=_tx_target, args=(sock_dir,))
t.start() t.start()
while t.is_alive() and not isfile(sock_file): while t.is_alive() and not isfile(sock_file):
sleep(0.1) sleep(0.1)
if not t.is_alive(): if not t.is_alive():
return return
kwargs['port'] = get_file_contents(sock_file) kwargs["port"] = get_file_contents(sock_file)
ctx.invoke(cmd_device_monitor, **kwargs) ctx.invoke(cmd_device_monitor, **kwargs)
t.join(2) t.join(2)
finally: finally:

View File

@ -22,13 +22,11 @@ from tabulate import tabulate
from platformio import exception, fs, util from platformio import exception, fs, util
from platformio.commands.device import device_monitor as cmd_device_monitor from platformio.commands.device import device_monitor as cmd_device_monitor
from platformio.commands.run.helpers import (clean_build_dir, from platformio.commands.run.helpers import clean_build_dir, handle_legacy_libdeps
handle_legacy_libdeps)
from platformio.commands.run.processor import EnvironmentProcessor from platformio.commands.run.processor import EnvironmentProcessor
from platformio.commands.test.processor import CTX_META_TEST_IS_RUNNING from platformio.commands.test.processor import CTX_META_TEST_IS_RUNNING
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (find_project_dir_above, from platformio.project.helpers import find_project_dir_above, get_project_build_dir
get_project_build_dir)
# pylint: disable=too-many-arguments,too-many-locals,too-many-branches # pylint: disable=too-many-arguments,too-many-locals,too-many-branches
@ -42,34 +40,47 @@ except NotImplementedError:
@click.option("-e", "--environment", multiple=True) @click.option("-e", "--environment", multiple=True)
@click.option("-t", "--target", multiple=True) @click.option("-t", "--target", multiple=True)
@click.option("--upload-port") @click.option("--upload-port")
@click.option("-d", @click.option(
"--project-dir", "-d",
default=getcwd, "--project-dir",
type=click.Path(exists=True, default=getcwd,
file_okay=True, type=click.Path(
dir_okay=True, exists=True, file_okay=True, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True)) )
@click.option("-c", @click.option(
"--project-conf", "-c",
type=click.Path(exists=True, "--project-conf",
file_okay=True, type=click.Path(
dir_okay=False, exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
readable=True, ),
resolve_path=True)) )
@click.option("-j", @click.option(
"--jobs", "-j",
type=int, "--jobs",
default=DEFAULT_JOB_NUMS, type=int,
help=("Allow N jobs at once. " default=DEFAULT_JOB_NUMS,
"Default is a number of CPUs in a system (N=%d)" % help=(
DEFAULT_JOB_NUMS)) "Allow N jobs at once. "
"Default is a number of CPUs in a system (N=%d)" % DEFAULT_JOB_NUMS
),
)
@click.option("-s", "--silent", is_flag=True) @click.option("-s", "--silent", is_flag=True)
@click.option("-v", "--verbose", is_flag=True) @click.option("-v", "--verbose", is_flag=True)
@click.option("--disable-auto-clean", is_flag=True) @click.option("--disable-auto-clean", is_flag=True)
@click.pass_context @click.pass_context
def cli(ctx, environment, target, upload_port, project_dir, project_conf, jobs, def cli(
silent, verbose, disable_auto_clean): ctx,
environment,
target,
upload_port,
project_dir,
project_conf,
jobs,
silent,
verbose,
disable_auto_clean,
):
# find project directory on upper level # find project directory on upper level
if isfile(project_dir): if isfile(project_dir):
project_dir = find_project_dir_above(project_dir) project_dir = find_project_dir_above(project_dir)
@ -78,7 +89,8 @@ def cli(ctx, environment, target, upload_port, project_dir, project_conf, jobs,
with fs.cd(project_dir): with fs.cd(project_dir):
config = ProjectConfig.get_instance( config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini")) project_conf or join(project_dir, "platformio.ini")
)
config.validate(environment) config.validate(environment)
# clean obsolete build dir # clean obsolete build dir
@ -88,36 +100,48 @@ def cli(ctx, environment, target, upload_port, project_dir, project_conf, jobs,
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
click.secho( click.secho(
"Can not remove temporary directory `%s`. Please remove " "Can not remove temporary directory `%s`. Please remove "
"it manually to avoid build issues" % "it manually to avoid build issues"
get_project_build_dir(force=True), % get_project_build_dir(force=True),
fg="yellow") fg="yellow",
)
handle_legacy_libdeps(project_dir, config) handle_legacy_libdeps(project_dir, config)
default_envs = config.default_envs() default_envs = config.default_envs()
results = [] results = []
for env in config.envs(): for env in config.envs():
skipenv = any([ skipenv = any(
environment and env not in environment, not environment [
and default_envs and env not in default_envs environment and env not in environment,
]) not environment and default_envs and env not in default_envs,
]
)
if skipenv: if skipenv:
results.append({"env": env}) results.append({"env": env})
continue continue
# print empty line between multi environment project # print empty line between multi environment project
if not silent and any( if not silent and any(r.get("succeeded") is not None for r in results):
r.get("succeeded") is not None for r in results):
click.echo() click.echo()
results.append( results.append(
process_env(ctx, env, config, environment, target, upload_port, process_env(
silent, verbose, jobs, is_test_running)) ctx,
env,
config,
environment,
target,
upload_port,
silent,
verbose,
jobs,
is_test_running,
)
)
command_failed = any(r.get("succeeded") is False for r in results) command_failed = any(r.get("succeeded") is False for r in results)
if (not is_test_running and (command_failed or not silent) if not is_test_running and (command_failed or not silent) and len(results) > 1:
and len(results) > 1):
print_processing_summary(results) print_processing_summary(results)
if command_failed: if command_failed:
@ -125,24 +149,39 @@ def cli(ctx, environment, target, upload_port, project_dir, project_conf, jobs,
return True return True
def process_env(ctx, name, config, environments, targets, upload_port, silent, def process_env(
verbose, jobs, is_test_running): ctx,
name,
config,
environments,
targets,
upload_port,
silent,
verbose,
jobs,
is_test_running,
):
if not is_test_running and not silent: if not is_test_running and not silent:
print_processing_header(name, config, verbose) print_processing_header(name, config, verbose)
ep = EnvironmentProcessor(ctx, name, config, targets, upload_port, silent, ep = EnvironmentProcessor(
verbose, jobs) ctx, name, config, targets, upload_port, silent, verbose, jobs
)
result = {"env": name, "duration": time(), "succeeded": ep.process()} result = {"env": name, "duration": time(), "succeeded": ep.process()}
result['duration'] = time() - result['duration'] result["duration"] = time() - result["duration"]
# print footer on error or when is not unit testing # print footer on error or when is not unit testing
if not is_test_running and (not silent or not result['succeeded']): if not is_test_running and (not silent or not result["succeeded"]):
print_processing_footer(result) print_processing_footer(result)
if (result['succeeded'] and "monitor" in ep.get_build_targets() if (
and "nobuild" not in ep.get_build_targets()): result["succeeded"]
ctx.invoke(cmd_device_monitor, and "monitor" in ep.get_build_targets()
environment=environments[0] if environments else None) and "nobuild" not in ep.get_build_targets()
):
ctx.invoke(
cmd_device_monitor, environment=environments[0] if environments else None
)
return result return result
@ -151,10 +190,11 @@ def print_processing_header(env, config, verbose=False):
env_dump = [] env_dump = []
for k, v in config.items(env=env): for k, v in config.items(env=env):
if verbose or k in ("platform", "framework", "board"): if verbose or k in ("platform", "framework", "board"):
env_dump.append("%s: %s" % env_dump.append("%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v))
(k, ", ".join(v) if isinstance(v, list) else v)) click.echo(
click.echo("Processing %s (%s)" % "Processing %s (%s)"
(click.style(env, fg="cyan", bold=True), "; ".join(env_dump))) % (click.style(env, fg="cyan", bold=True), "; ".join(env_dump))
)
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
@ -162,10 +202,17 @@ def print_processing_header(env, config, verbose=False):
def print_processing_footer(result): def print_processing_footer(result):
is_failed = not result.get("succeeded") is_failed = not result.get("succeeded")
util.print_labeled_bar( util.print_labeled_bar(
"[%s] Took %.2f seconds" % "[%s] Took %.2f seconds"
((click.style("FAILED", fg="red", bold=True) if is_failed else % (
click.style("SUCCESS", fg="green", bold=True)), result['duration']), (
is_error=is_failed) click.style("FAILED", fg="red", bold=True)
if is_failed
else click.style("SUCCESS", fg="green", bold=True)
),
result["duration"],
),
is_error=is_failed,
)
def print_processing_summary(results): def print_processing_summary(results):
@ -186,20 +233,31 @@ def print_processing_summary(results):
status_str = click.style("SUCCESS", fg="green") status_str = click.style("SUCCESS", fg="green")
tabular_data.append( tabular_data.append(
(click.style(result['env'], fg="cyan"), status_str, (
util.humanize_duration_time(result.get("duration")))) click.style(result["env"], fg="cyan"),
status_str,
util.humanize_duration_time(result.get("duration")),
)
)
click.echo() click.echo()
click.echo(tabulate(tabular_data, click.echo(
headers=[ tabulate(
click.style(s, bold=True) tabular_data,
for s in ("Environment", "Status", "Duration") headers=[
]), click.style(s, bold=True) for s in ("Environment", "Status", "Duration")
err=failed_nums) ],
),
err=failed_nums,
)
util.print_labeled_bar( util.print_labeled_bar(
"%s%d succeeded in %s" % "%s%d succeeded in %s"
("%d failed, " % failed_nums if failed_nums else "", succeeded_nums, % (
util.humanize_duration_time(duration)), "%d failed, " % failed_nums if failed_nums else "",
succeeded_nums,
util.humanize_duration_time(duration),
),
is_error=failed_nums, is_error=failed_nums,
fg="red" if failed_nums else "green") fg="red" if failed_nums else "green",
)

View File

@ -18,15 +18,16 @@ from os.path import isdir, isfile, join
import click import click
from platformio import fs from platformio import fs
from platformio.project.helpers import (compute_project_checksum, from platformio.project.helpers import (
get_project_dir, compute_project_checksum,
get_project_libdeps_dir) get_project_dir,
get_project_libdeps_dir,
)
def handle_legacy_libdeps(project_dir, config): def handle_legacy_libdeps(project_dir, config):
legacy_libdeps_dir = join(project_dir, ".piolibdeps") legacy_libdeps_dir = join(project_dir, ".piolibdeps")
if (not isdir(legacy_libdeps_dir) if not isdir(legacy_libdeps_dir) or legacy_libdeps_dir == get_project_libdeps_dir():
or legacy_libdeps_dir == get_project_libdeps_dir()):
return return
if not config.has_section("env"): if not config.has_section("env"):
config.add_section("env") config.add_section("env")
@ -39,7 +40,8 @@ def handle_legacy_libdeps(project_dir, config):
" file using `lib_deps` option and remove `{0}` folder." " file using `lib_deps` option and remove `{0}` folder."
"\nMore details -> http://docs.platformio.org/page/projectconf/" "\nMore details -> http://docs.platformio.org/page/projectconf/"
"section_env_library.html#lib-deps".format(legacy_libdeps_dir), "section_env_library.html#lib-deps".format(legacy_libdeps_dir),
fg="yellow") fg="yellow",
)
def clean_build_dir(build_dir, config): def clean_build_dir(build_dir, config):

View File

@ -13,8 +13,7 @@
# limitations under the License. # limitations under the License.
from platformio import exception, telemetry from platformio import exception, telemetry
from platformio.commands.platform import \ from platformio.commands.platform import platform_install as cmd_platform_install
platform_install as cmd_platform_install
from platformio.commands.test.processor import CTX_META_TEST_RUNNING_NAME from platformio.commands.test.processor import CTX_META_TEST_RUNNING_NAME
from platformio.managers.platform import PlatformFactory from platformio.managers.platform import PlatformFactory
@ -22,10 +21,9 @@ from platformio.managers.platform import PlatformFactory
class EnvironmentProcessor(object): class EnvironmentProcessor(object):
def __init__( # pylint: disable=too-many-arguments def __init__( # pylint: disable=too-many-arguments
self, cmd_ctx, name, config, targets, upload_port, silent, verbose, self, cmd_ctx, name, config, targets, upload_port, silent, verbose, jobs
jobs): ):
self.cmd_ctx = cmd_ctx self.cmd_ctx = cmd_ctx
self.name = name self.name = name
self.config = config self.config = config
@ -40,12 +38,13 @@ class EnvironmentProcessor(object):
variables = {"pioenv": self.name, "project_config": self.config.path} variables = {"pioenv": self.name, "project_config": self.config.path}
if CTX_META_TEST_RUNNING_NAME in self.cmd_ctx.meta: if CTX_META_TEST_RUNNING_NAME in self.cmd_ctx.meta:
variables['piotest_running_name'] = self.cmd_ctx.meta[ variables["piotest_running_name"] = self.cmd_ctx.meta[
CTX_META_TEST_RUNNING_NAME] CTX_META_TEST_RUNNING_NAME
]
if self.upload_port: if self.upload_port:
# override upload port with a custom from CLI # override upload port with a custom from CLI
variables['upload_port'] = self.upload_port variables["upload_port"] = self.upload_port
return variables return variables
def get_build_targets(self): def get_build_targets(self):
@ -67,13 +66,14 @@ class EnvironmentProcessor(object):
build_targets.remove("monitor") build_targets.remove("monitor")
try: try:
p = PlatformFactory.newPlatform(self.options['platform']) p = PlatformFactory.newPlatform(self.options["platform"])
except exception.UnknownPlatform: except exception.UnknownPlatform:
self.cmd_ctx.invoke(cmd_platform_install, self.cmd_ctx.invoke(
platforms=[self.options['platform']], cmd_platform_install,
skip_default_package=True) platforms=[self.options["platform"]],
p = PlatformFactory.newPlatform(self.options['platform']) skip_default_package=True,
)
p = PlatformFactory.newPlatform(self.options["platform"])
result = p.run(build_vars, build_targets, self.silent, self.verbose, result = p.run(build_vars, build_targets, self.silent, self.verbose, self.jobs)
self.jobs) return result["returncode"] == 0
return result['returncode'] == 0

View File

@ -42,20 +42,24 @@ def settings_get(name):
raw_value = app.get_setting(key) raw_value = app.get_setting(key)
formatted_value = format_value(raw_value) formatted_value = format_value(raw_value)
if raw_value != options['value']: if raw_value != options["value"]:
default_formatted_value = format_value(options['value']) default_formatted_value = format_value(options["value"])
formatted_value += "%s" % ( formatted_value += "%s" % (
"\n" if len(default_formatted_value) > 10 else " ") "\n" if len(default_formatted_value) > 10 else " "
formatted_value += "[%s]" % click.style(default_formatted_value, )
fg="yellow") formatted_value += "[%s]" % click.style(
default_formatted_value, fg="yellow"
)
tabular_data.append( tabular_data.append(
(click.style(key, (click.style(key, fg="cyan"), formatted_value, options["description"])
fg="cyan"), formatted_value, options['description'])) )
click.echo( click.echo(
tabulate(tabular_data, tabulate(
headers=["Name", "Current value [Default]", "Description"])) tabular_data, headers=["Name", "Current value [Default]", "Description"]
)
)
@cli.command("set", short_help="Set new value for the setting") @cli.command("set", short_help="Set new value for the setting")

View File

@ -31,51 +31,72 @@ from platformio.project.helpers import get_project_test_dir
@click.command("test", short_help="Unit Testing") @click.command("test", short_help="Unit Testing")
@click.option("--environment", "-e", multiple=True, metavar="<environment>") @click.option("--environment", "-e", multiple=True, metavar="<environment>")
@click.option("--filter", @click.option(
"-f", "--filter",
multiple=True, "-f",
metavar="<pattern>", multiple=True,
help="Filter tests by a pattern") metavar="<pattern>",
@click.option("--ignore", help="Filter tests by a pattern",
"-i", )
multiple=True, @click.option(
metavar="<pattern>", "--ignore",
help="Ignore tests by a pattern") "-i",
multiple=True,
metavar="<pattern>",
help="Ignore tests by a pattern",
)
@click.option("--upload-port") @click.option("--upload-port")
@click.option("--test-port") @click.option("--test-port")
@click.option("-d", @click.option(
"--project-dir", "-d",
default=getcwd, "--project-dir",
type=click.Path(exists=True, default=getcwd,
file_okay=False, type=click.Path(
dir_okay=True, exists=True, file_okay=False, dir_okay=True, writable=True, resolve_path=True
writable=True, ),
resolve_path=True)) )
@click.option("-c", @click.option(
"--project-conf", "-c",
type=click.Path(exists=True, "--project-conf",
file_okay=True, type=click.Path(
dir_okay=False, exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True
readable=True, ),
resolve_path=True)) )
@click.option("--without-building", is_flag=True) @click.option("--without-building", is_flag=True)
@click.option("--without-uploading", is_flag=True) @click.option("--without-uploading", is_flag=True)
@click.option("--without-testing", is_flag=True) @click.option("--without-testing", is_flag=True)
@click.option("--no-reset", is_flag=True) @click.option("--no-reset", is_flag=True)
@click.option("--monitor-rts", @click.option(
default=None, "--monitor-rts",
type=click.IntRange(0, 1), default=None,
help="Set initial RTS line state for Serial Monitor") type=click.IntRange(0, 1),
@click.option("--monitor-dtr", help="Set initial RTS line state for Serial Monitor",
default=None, )
type=click.IntRange(0, 1), @click.option(
help="Set initial DTR line state for Serial Monitor") "--monitor-dtr",
default=None,
type=click.IntRange(0, 1),
help="Set initial DTR line state for Serial Monitor",
)
@click.option("--verbose", "-v", is_flag=True) @click.option("--verbose", "-v", is_flag=True)
@click.pass_context @click.pass_context
def cli( # pylint: disable=redefined-builtin def cli( # pylint: disable=redefined-builtin
ctx, environment, ignore, filter, upload_port, test_port, project_dir, ctx,
project_conf, without_building, without_uploading, without_testing, environment,
no_reset, monitor_rts, monitor_dtr, verbose): ignore,
filter,
upload_port,
test_port,
project_dir,
project_conf,
without_building,
without_uploading,
without_testing,
no_reset,
monitor_rts,
monitor_dtr,
verbose,
):
with fs.cd(project_dir): with fs.cd(project_dir):
test_dir = get_project_test_dir() test_dir = get_project_test_dir()
if not isdir(test_dir): if not isdir(test_dir):
@ -83,7 +104,8 @@ def cli( # pylint: disable=redefined-builtin
test_names = get_test_names(test_dir) test_names = get_test_names(test_dir)
config = ProjectConfig.get_instance( config = ProjectConfig.get_instance(
project_conf or join(project_dir, "platformio.ini")) project_conf or join(project_dir, "platformio.ini")
)
config.validate(envs=environment) config.validate(envs=environment)
click.echo("Verbose mode can be enabled via `-v, --verbose` option") click.echo("Verbose mode can be enabled via `-v, --verbose` option")
@ -99,19 +121,16 @@ def cli( # pylint: disable=redefined-builtin
# filter and ignore patterns # filter and ignore patterns
patterns = dict(filter=list(filter), ignore=list(ignore)) patterns = dict(filter=list(filter), ignore=list(ignore))
for key in patterns: for key in patterns:
patterns[key].extend( patterns[key].extend(config.get(section, "test_%s" % key, []))
config.get(section, "test_%s" % key, []))
skip_conditions = [ skip_conditions = [
environment and envname not in environment, environment and envname not in environment,
not environment and default_envs not environment and default_envs and envname not in default_envs,
and envname not in default_envs,
testname != "*" and patterns['filter'] and
not any([fnmatch(testname, p)
for p in patterns['filter']]),
testname != "*" testname != "*"
and any([fnmatch(testname, p) and patterns["filter"]
for p in patterns['ignore']]), and not any([fnmatch(testname, p) for p in patterns["filter"]]),
testname != "*"
and any([fnmatch(testname, p) for p in patterns["ignore"]]),
] ]
if any(skip_conditions): if any(skip_conditions):
results.append({"env": envname, "test": testname}) results.append({"env": envname, "test": testname})
@ -120,29 +139,36 @@ def cli( # pylint: disable=redefined-builtin
click.echo() click.echo()
print_processing_header(testname, envname) print_processing_header(testname, envname)
cls = (NativeTestProcessor cls = (
if config.get(section, "platform") == "native" else NativeTestProcessor
EmbeddedTestProcessor) if config.get(section, "platform") == "native"
else EmbeddedTestProcessor
)
tp = cls( tp = cls(
ctx, testname, envname, ctx,
dict(project_config=config, testname,
project_dir=project_dir, envname,
upload_port=upload_port, dict(
test_port=test_port, project_config=config,
without_building=without_building, project_dir=project_dir,
without_uploading=without_uploading, upload_port=upload_port,
without_testing=without_testing, test_port=test_port,
no_reset=no_reset, without_building=without_building,
monitor_rts=monitor_rts, without_uploading=without_uploading,
monitor_dtr=monitor_dtr, without_testing=without_testing,
verbose=verbose)) no_reset=no_reset,
monitor_rts=monitor_rts,
monitor_dtr=monitor_dtr,
verbose=verbose,
),
)
result = { result = {
"env": envname, "env": envname,
"test": testname, "test": testname,
"duration": time(), "duration": time(),
"succeeded": tp.process() "succeeded": tp.process(),
} }
result['duration'] = time() - result['duration'] result["duration"] = time() - result["duration"]
results.append(result) results.append(result)
print_processing_footer(result) print_processing_footer(result)
@ -168,8 +194,13 @@ def get_test_names(test_dir):
def print_processing_header(test, env): def print_processing_header(test, env):
click.echo("Processing %s in %s environment" % (click.style( click.echo(
test, fg="yellow", bold=True), click.style(env, fg="cyan", bold=True))) "Processing %s in %s environment"
% (
click.style(test, fg="yellow", bold=True),
click.style(env, fg="cyan", bold=True),
)
)
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
click.secho("-" * terminal_width, bold=True) click.secho("-" * terminal_width, bold=True)
@ -177,10 +208,17 @@ def print_processing_header(test, env):
def print_processing_footer(result): def print_processing_footer(result):
is_failed = not result.get("succeeded") is_failed = not result.get("succeeded")
util.print_labeled_bar( util.print_labeled_bar(
"[%s] Took %.2f seconds" % "[%s] Took %.2f seconds"
((click.style("FAILED", fg="red", bold=True) if is_failed else % (
click.style("PASSED", fg="green", bold=True)), result['duration']), (
is_error=is_failed) click.style("FAILED", fg="red", bold=True)
if is_failed
else click.style("PASSED", fg="green", bold=True)
),
result["duration"],
),
is_error=is_failed,
)
def print_testing_summary(results): def print_testing_summary(results):
@ -203,20 +241,32 @@ def print_testing_summary(results):
status_str = click.style("PASSED", fg="green") status_str = click.style("PASSED", fg="green")
tabular_data.append( tabular_data.append(
(result['test'], click.style(result['env'], fg="cyan"), status_str, (
util.humanize_duration_time(result.get("duration")))) result["test"],
click.style(result["env"], fg="cyan"),
status_str,
util.humanize_duration_time(result.get("duration")),
)
)
click.echo(tabulate(tabular_data, click.echo(
headers=[ tabulate(
click.style(s, bold=True) tabular_data,
for s in ("Test", "Environment", "Status", headers=[
"Duration") click.style(s, bold=True)
]), for s in ("Test", "Environment", "Status", "Duration")
err=failed_nums) ],
),
err=failed_nums,
)
util.print_labeled_bar( util.print_labeled_bar(
"%s%d succeeded in %s" % "%s%d succeeded in %s"
("%d failed, " % failed_nums if failed_nums else "", succeeded_nums, % (
util.humanize_duration_time(duration)), "%d failed, " % failed_nums if failed_nums else "",
succeeded_nums,
util.humanize_duration_time(duration),
),
is_error=failed_nums, is_error=failed_nums,
fg="red" if failed_nums else "green") fg="red" if failed_nums else "green",
)

View File

@ -27,47 +27,50 @@ class EmbeddedTestProcessor(TestProcessorBase):
SERIAL_TIMEOUT = 600 SERIAL_TIMEOUT = 600
def process(self): def process(self):
if not self.options['without_building']: if not self.options["without_building"]:
self.print_progress("Building...") self.print_progress("Building...")
target = ["__test"] target = ["__test"]
if self.options['without_uploading']: if self.options["without_uploading"]:
target.append("checkprogsize") target.append("checkprogsize")
if not self.build_or_upload(target): if not self.build_or_upload(target):
return False return False
if not self.options['without_uploading']: if not self.options["without_uploading"]:
self.print_progress("Uploading...") self.print_progress("Uploading...")
target = ["upload"] target = ["upload"]
if self.options['without_building']: if self.options["without_building"]:
target.append("nobuild") target.append("nobuild")
else: else:
target.append("__test") target.append("__test")
if not self.build_or_upload(target): if not self.build_or_upload(target):
return False return False
if self.options['without_testing']: if self.options["without_testing"]:
return None return None
self.print_progress("Testing...") self.print_progress("Testing...")
return self.run() return self.run()
def run(self): def run(self):
click.echo("If you don't see any output for the first 10 secs, " click.echo(
"please reset board (press reset button)") "If you don't see any output for the first 10 secs, "
"please reset board (press reset button)"
)
click.echo() click.echo()
try: try:
ser = serial.Serial(baudrate=self.get_baudrate(), ser = serial.Serial(
timeout=self.SERIAL_TIMEOUT) baudrate=self.get_baudrate(), timeout=self.SERIAL_TIMEOUT
)
ser.port = self.get_test_port() ser.port = self.get_test_port()
ser.rts = self.options['monitor_rts'] ser.rts = self.options["monitor_rts"]
ser.dtr = self.options['monitor_dtr'] ser.dtr = self.options["monitor_dtr"]
ser.open() ser.open()
except serial.SerialException as e: except serial.SerialException as e:
click.secho(str(e), fg="red", err=True) click.secho(str(e), fg="red", err=True)
return False return False
if not self.options['no_reset']: if not self.options["no_reset"]:
ser.flushInput() ser.flushInput()
ser.setDTR(False) ser.setDTR(False)
ser.setRTS(False) ser.setRTS(False)
@ -105,17 +108,16 @@ class EmbeddedTestProcessor(TestProcessorBase):
return self.env_options.get("test_port") return self.env_options.get("test_port")
assert set(["platform", "board"]) & set(self.env_options.keys()) assert set(["platform", "board"]) & set(self.env_options.keys())
p = PlatformFactory.newPlatform(self.env_options['platform']) p = PlatformFactory.newPlatform(self.env_options["platform"])
board_hwids = p.board_config(self.env_options['board']).get( board_hwids = p.board_config(self.env_options["board"]).get("build.hwids", [])
"build.hwids", [])
port = None port = None
elapsed = 0 elapsed = 0
while elapsed < 5 and not port: while elapsed < 5 and not port:
for item in util.get_serialports(): for item in util.get_serialports():
port = item['port'] port = item["port"]
for hwid in board_hwids: for hwid in board_hwids:
hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "") hwid_str = ("%s:%s" % (hwid[0], hwid[1])).replace("0x", "")
if hwid_str in item['hwid']: if hwid_str in item["hwid"]:
return port return port
# check if port is already configured # check if port is already configured
@ -131,5 +133,6 @@ class EmbeddedTestProcessor(TestProcessorBase):
if not port: if not port:
raise exception.PlatformioException( raise exception.PlatformioException(
"Please specify `test_port` for environment or use " "Please specify `test_port` for environment or use "
"global `--test-port` option.") "global `--test-port` option."
)
return port return port

View File

@ -21,23 +21,23 @@ from platformio.project.helpers import get_project_build_dir
class NativeTestProcessor(TestProcessorBase): class NativeTestProcessor(TestProcessorBase):
def process(self): def process(self):
if not self.options['without_building']: if not self.options["without_building"]:
self.print_progress("Building...") self.print_progress("Building...")
if not self.build_or_upload(["__test"]): if not self.build_or_upload(["__test"]):
return False return False
if self.options['without_testing']: if self.options["without_testing"]:
return None return None
self.print_progress("Testing...") self.print_progress("Testing...")
return self.run() return self.run()
def run(self): def run(self):
with fs.cd(self.options['project_dir']): with fs.cd(self.options["project_dir"]):
build_dir = get_project_build_dir() build_dir = get_project_build_dir()
result = proc.exec_command( result = proc.exec_command(
[join(build_dir, self.env_name, "program")], [join(build_dir, self.env_name, "program")],
stdout=LineBufferedAsyncPipe(self.on_run_out), stdout=LineBufferedAsyncPipe(self.on_run_out),
stderr=LineBufferedAsyncPipe(self.on_run_out)) stderr=LineBufferedAsyncPipe(self.on_run_out),
)
assert "returncode" in result assert "returncode" in result
return result['returncode'] == 0 and not self._run_failed return result["returncode"] == 0 and not self._run_failed

View File

@ -29,7 +29,7 @@ TRANSPORT_OPTIONS = {
"putchar": "Serial.write(c)", "putchar": "Serial.write(c)",
"flush": "Serial.flush()", "flush": "Serial.flush()",
"begin": "Serial.begin($baudrate)", "begin": "Serial.begin($baudrate)",
"end": "Serial.end()" "end": "Serial.end()",
}, },
"mbed": { "mbed": {
"include": "#include <mbed.h>", "include": "#include <mbed.h>",
@ -37,7 +37,7 @@ TRANSPORT_OPTIONS = {
"putchar": "pc.putc(c)", "putchar": "pc.putc(c)",
"flush": "", "flush": "",
"begin": "pc.baud($baudrate)", "begin": "pc.baud($baudrate)",
"end": "" "end": "",
}, },
"espidf": { "espidf": {
"include": "#include <stdio.h>", "include": "#include <stdio.h>",
@ -45,7 +45,7 @@ TRANSPORT_OPTIONS = {
"putchar": "putchar(c)", "putchar": "putchar(c)",
"flush": "fflush(stdout)", "flush": "fflush(stdout)",
"begin": "", "begin": "",
"end": "" "end": "",
}, },
"native": { "native": {
"include": "#include <stdio.h>", "include": "#include <stdio.h>",
@ -53,7 +53,7 @@ TRANSPORT_OPTIONS = {
"putchar": "putchar(c)", "putchar": "putchar(c)",
"flush": "fflush(stdout)", "flush": "fflush(stdout)",
"begin": "", "begin": "",
"end": "" "end": "",
}, },
"custom": { "custom": {
"include": '#include "unittest_transport.h"', "include": '#include "unittest_transport.h"',
@ -61,8 +61,8 @@ TRANSPORT_OPTIONS = {
"putchar": "unittest_uart_putchar(c)", "putchar": "unittest_uart_putchar(c)",
"flush": "unittest_uart_flush()", "flush": "unittest_uart_flush()",
"begin": "unittest_uart_begin()", "begin": "unittest_uart_begin()",
"end": "unittest_uart_end()" "end": "unittest_uart_end()",
} },
} }
CTX_META_TEST_IS_RUNNING = __name__ + ".test_running" CTX_META_TEST_IS_RUNNING = __name__ + ".test_running"
@ -79,8 +79,7 @@ class TestProcessorBase(object):
self.test_name = testname self.test_name = testname
self.options = options self.options = options
self.env_name = envname self.env_name = envname
self.env_options = options['project_config'].items(env=envname, self.env_options = options["project_config"].items(env=envname, as_dict=True)
as_dict=True)
self._run_failed = False self._run_failed = False
self._outputcpp_generated = False self._outputcpp_generated = False
@ -90,10 +89,11 @@ class TestProcessorBase(object):
elif "framework" in self.env_options: elif "framework" in self.env_options:
transport = self.env_options.get("framework")[0] transport = self.env_options.get("framework")[0]
if "test_transport" in self.env_options: if "test_transport" in self.env_options:
transport = self.env_options['test_transport'] transport = self.env_options["test_transport"]
if transport not in TRANSPORT_OPTIONS: if transport not in TRANSPORT_OPTIONS:
raise exception.PlatformioException( raise exception.PlatformioException(
"Unknown Unit Test transport `%s`" % transport) "Unknown Unit Test transport `%s`" % transport
)
return transport.lower() return transport.lower()
def get_baudrate(self): def get_baudrate(self):
@ -112,13 +112,16 @@ class TestProcessorBase(object):
try: try:
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
return self.cmd_ctx.invoke(cmd_run,
project_dir=self.options['project_dir'], return self.cmd_ctx.invoke(
upload_port=self.options['upload_port'], cmd_run,
silent=not self.options['verbose'], project_dir=self.options["project_dir"],
environment=[self.env_name], upload_port=self.options["upload_port"],
disable_auto_clean="nobuild" in target, silent=not self.options["verbose"],
target=target) environment=[self.env_name],
disable_auto_clean="nobuild" in target,
target=target,
)
except exception.ReturnErrorCode: except exception.ReturnErrorCode:
return False return False
@ -131,8 +134,7 @@ class TestProcessorBase(object):
def on_run_out(self, line): def on_run_out(self, line):
line = line.strip() line = line.strip()
if line.endswith(":PASS"): if line.endswith(":PASS"):
click.echo("%s\t[%s]" % click.echo("%s\t[%s]" % (line[:-5], click.style("PASSED", fg="green")))
(line[:-5], click.style("PASSED", fg="green")))
elif ":FAIL" in line: elif ":FAIL" in line:
self._run_failed = True self._run_failed = True
click.echo("%s\t[%s]" % (line, click.style("FAILED", fg="red"))) click.echo("%s\t[%s]" % (line, click.style("FAILED", fg="red")))
@ -142,36 +144,38 @@ class TestProcessorBase(object):
def generate_outputcpp(self, test_dir): def generate_outputcpp(self, test_dir):
assert isdir(test_dir) assert isdir(test_dir)
cpp_tpl = "\n".join([ cpp_tpl = "\n".join(
"$include", [
"#include <output_export.h>", "$include",
"", "#include <output_export.h>",
"$object", "",
"", "$object",
"#ifdef __GNUC__", "",
"void output_start(unsigned int baudrate __attribute__((unused)))", "#ifdef __GNUC__",
"#else", "void output_start(unsigned int baudrate __attribute__((unused)))",
"void output_start(unsigned int baudrate)", "#else",
"#endif", "void output_start(unsigned int baudrate)",
"{", "#endif",
" $begin;", "{",
"}", " $begin;",
"", "}",
"void output_char(int c)", "",
"{", "void output_char(int c)",
" $putchar;", "{",
"}", " $putchar;",
"", "}",
"void output_flush(void)", "",
"{", "void output_flush(void)",
" $flush;", "{",
"}", " $flush;",
"", "}",
"void output_complete(void)", "",
"{", "void output_complete(void)",
" $end;", "{",
"}" " $end;",
]) # yapf: disable "}",
]
) # yapf: disable
def delete_tmptest_file(file_): def delete_tmptest_file(file_):
try: try:
@ -181,10 +185,10 @@ class TestProcessorBase(object):
click.secho( click.secho(
"Warning: Could not remove temporary file '%s'. " "Warning: Could not remove temporary file '%s'. "
"Please remove it manually." % file_, "Please remove it manually." % file_,
fg="yellow") fg="yellow",
)
tpl = Template(cpp_tpl).substitute( tpl = Template(cpp_tpl).substitute(TRANSPORT_OPTIONS[self.get_transport()])
TRANSPORT_OPTIONS[self.get_transport()])
data = Template(tpl).substitute(baudrate=self.get_baudrate()) data = Template(tpl).substitute(baudrate=self.get_baudrate())
tmp_file = join(test_dir, "output_export.cpp") tmp_file = join(test_dir, "output_export.cpp")

View File

@ -22,18 +22,19 @@ from platformio.managers.core import update_core_packages
from platformio.managers.lib import LibraryManager from platformio.managers.lib import LibraryManager
@click.command("update", @click.command(
short_help="Update installed platforms, packages and libraries") "update", short_help="Update installed platforms, packages and libraries"
@click.option("--core-packages", )
is_flag=True, @click.option("--core-packages", is_flag=True, help="Update only the core packages")
help="Update only the core packages") @click.option(
@click.option("-c", "-c",
"--only-check", "--only-check",
is_flag=True, is_flag=True,
help="DEPRECATED. Please use `--dry-run` instead") help="DEPRECATED. Please use `--dry-run` instead",
@click.option("--dry-run", )
is_flag=True, @click.option(
help="Do not update, only check for the new versions") "--dry-run", is_flag=True, help="Do not update, only check for the new versions"
)
@click.pass_context @click.pass_context
def cli(ctx, core_packages, only_check, dry_run): def cli(ctx, core_packages, only_check, dry_run):
# cleanup lib search results, cached board and platform lists # cleanup lib search results, cached board and platform lists

View File

@ -25,21 +25,23 @@ from platformio.proc import exec_command, get_pythonexe_path
from platformio.project.helpers import get_project_cache_dir from platformio.project.helpers import get_project_cache_dir
@click.command("upgrade", @click.command("upgrade", short_help="Upgrade PlatformIO to the latest version")
short_help="Upgrade PlatformIO to the latest version")
@click.option("--dev", is_flag=True, help="Use development branch") @click.option("--dev", is_flag=True, help="Use development branch")
def cli(dev): def cli(dev):
if not dev and __version__ == get_latest_version(): if not dev and __version__ == get_latest_version():
return click.secho( return click.secho(
"You're up-to-date!\nPlatformIO %s is currently the " "You're up-to-date!\nPlatformIO %s is currently the "
"newest version available." % __version__, "newest version available." % __version__,
fg="green") fg="green",
)
click.secho("Please wait while upgrading PlatformIO ...", fg="yellow") click.secho("Please wait while upgrading PlatformIO ...", fg="yellow")
to_develop = dev or not all(c.isdigit() for c in __version__ if c != ".") to_develop = dev or not all(c.isdigit() for c in __version__ if c != ".")
cmds = (["pip", "install", "--upgrade", cmds = (
get_pip_package(to_develop)], ["platformio", "--version"]) ["pip", "install", "--upgrade", get_pip_package(to_develop)],
["platformio", "--version"],
)
cmd = None cmd = None
r = {} r = {}
@ -49,26 +51,26 @@ def cli(dev):
r = exec_command(cmd) r = exec_command(cmd)
# try pip with disabled cache # try pip with disabled cache
if r['returncode'] != 0 and cmd[2] == "pip": if r["returncode"] != 0 and cmd[2] == "pip":
cmd.insert(3, "--no-cache-dir") cmd.insert(3, "--no-cache-dir")
r = exec_command(cmd) r = exec_command(cmd)
assert r['returncode'] == 0 assert r["returncode"] == 0
assert "version" in r['out'] assert "version" in r["out"]
actual_version = r['out'].strip().split("version", 1)[1].strip() actual_version = r["out"].strip().split("version", 1)[1].strip()
click.secho("PlatformIO has been successfully upgraded to %s" % click.secho(
actual_version, "PlatformIO has been successfully upgraded to %s" % actual_version,
fg="green") fg="green",
)
click.echo("Release notes: ", nl=False) click.echo("Release notes: ", nl=False)
click.secho("https://docs.platformio.org/en/latest/history.html", click.secho("https://docs.platformio.org/en/latest/history.html", fg="cyan")
fg="cyan")
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
if not r: if not r:
raise exception.UpgradeError("\n".join([str(cmd), str(e)])) raise exception.UpgradeError("\n".join([str(cmd), str(e)]))
permission_errors = ("permission denied", "not permitted") permission_errors = ("permission denied", "not permitted")
if (any(m in r['err'].lower() for m in permission_errors) if any(m in r["err"].lower() for m in permission_errors) and not WINDOWS:
and not WINDOWS): click.secho(
click.secho(""" """
----------------- -----------------
Permission denied Permission denied
----------------- -----------------
@ -78,10 +80,11 @@ You need the `sudo` permission to install Python packages. Try
WARNING! Don't use `sudo` for the rest PlatformIO commands. WARNING! Don't use `sudo` for the rest PlatformIO commands.
""", """,
fg="yellow", fg="yellow",
err=True) err=True,
)
raise exception.ReturnErrorCode(1) raise exception.ReturnErrorCode(1)
raise exception.UpgradeError("\n".join([str(cmd), r['out'], r['err']])) raise exception.UpgradeError("\n".join([str(cmd), r["out"], r["err"]]))
return True return True
@ -89,18 +92,17 @@ WARNING! Don't use `sudo` for the rest PlatformIO commands.
def get_pip_package(to_develop): def get_pip_package(to_develop):
if not to_develop: if not to_develop:
return "platformio" return "platformio"
dl_url = ("https://github.com/platformio/" dl_url = "https://github.com/platformio/" "platformio-core/archive/develop.zip"
"platformio-core/archive/develop.zip")
cache_dir = get_project_cache_dir() cache_dir = get_project_cache_dir()
if not os.path.isdir(cache_dir): if not os.path.isdir(cache_dir):
os.makedirs(cache_dir) os.makedirs(cache_dir)
pkg_name = os.path.join(cache_dir, "piocoredevelop.zip") pkg_name = os.path.join(cache_dir, "piocoredevelop.zip")
try: try:
with open(pkg_name, "w") as fp: with open(pkg_name, "w") as fp:
r = exec_command(["curl", "-fsSL", dl_url], r = exec_command(
stdout=fp, ["curl", "-fsSL", dl_url], stdout=fp, universal_newlines=True
universal_newlines=True) )
assert r['returncode'] == 0 assert r["returncode"] == 0
# check ZIP structure # check ZIP structure
with ZipFile(pkg_name) as zp: with ZipFile(pkg_name) as zp:
assert zp.testzip() is None assert zp.testzip() is None
@ -127,7 +129,8 @@ def get_develop_latest_version():
r = requests.get( r = requests.get(
"https://raw.githubusercontent.com/platformio/platformio" "https://raw.githubusercontent.com/platformio/platformio"
"/develop/platformio/__init__.py", "/develop/platformio/__init__.py",
headers=util.get_request_defheaders()) headers=util.get_request_defheaders(),
)
r.raise_for_status() r.raise_for_status()
for line in r.text.split("\n"): for line in r.text.split("\n"):
line = line.strip() line = line.strip()
@ -145,7 +148,8 @@ def get_develop_latest_version():
def get_pypi_latest_version(): def get_pypi_latest_version():
r = requests.get("https://pypi.org/pypi/platformio/json", r = requests.get(
headers=util.get_request_defheaders()) "https://pypi.org/pypi/platformio/json", headers=util.get_request_defheaders()
)
r.raise_for_status() r.raise_for_status()
return r.json()['info']['version'] return r.json()["info"]["version"]

View File

@ -20,8 +20,8 @@ import re
import sys import sys
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2
CYGWIN = sys.platform.startswith('cygwin') CYGWIN = sys.platform.startswith("cygwin")
WINDOWS = sys.platform.startswith('win') WINDOWS = sys.platform.startswith("win")
def get_filesystem_encoding(): def get_filesystem_encoding():
@ -56,13 +56,12 @@ if PY2:
def dump_json_to_unicode(obj): def dump_json_to_unicode(obj):
if isinstance(obj, unicode): if isinstance(obj, unicode):
return obj return obj
return json.dumps(obj, return json.dumps(
encoding=get_filesystem_encoding(), obj, encoding=get_filesystem_encoding(), ensure_ascii=False, sort_keys=True
ensure_ascii=False, ).encode("utf8")
sort_keys=True).encode("utf8")
_magic_check = re.compile('([*?[])') _magic_check = re.compile("([*?[])")
_magic_check_bytes = re.compile(b'([*?[])') _magic_check_bytes = re.compile(b"([*?[])")
def glob_escape(pathname): def glob_escape(pathname):
"""Escape all special characters.""" """Escape all special characters."""
@ -72,14 +71,16 @@ if PY2:
# escaped. # escaped.
drive, pathname = os.path.splitdrive(pathname) drive, pathname = os.path.splitdrive(pathname)
if isinstance(pathname, bytes): if isinstance(pathname, bytes):
pathname = _magic_check_bytes.sub(br'[\1]', pathname) pathname = _magic_check_bytes.sub(br"[\1]", pathname)
else: else:
pathname = _magic_check.sub(r'[\1]', pathname) pathname = _magic_check.sub(r"[\1]", pathname)
return drive + pathname return drive + pathname
else: else:
from glob import escape as glob_escape # pylint: disable=no-name-in-module from glob import escape as glob_escape # pylint: disable=no-name-in-module
string_types = (str, ) string_types = (str,)
def is_bytes(x): def is_bytes(x):
return isinstance(x, (bytes, memoryview, bytearray)) return isinstance(x, (bytes, memoryview, bytearray))

View File

@ -22,8 +22,11 @@ import click
import requests import requests
from platformio import util from platformio import util
from platformio.exception import (FDSHASumMismatch, FDSizeMismatch, from platformio.exception import (
FDUnrecognizedStatusCode) FDSHASumMismatch,
FDSizeMismatch,
FDUnrecognizedStatusCode,
)
from platformio.proc import exec_command from platformio.proc import exec_command
@ -34,17 +37,22 @@ class FileDownloader(object):
def __init__(self, url, dest_dir=None): def __init__(self, url, dest_dir=None):
self._request = None self._request = None
# make connection # make connection
self._request = requests.get(url, self._request = requests.get(
stream=True, url,
headers=util.get_request_defheaders(), stream=True,
verify=version_info >= (2, 7, 9)) headers=util.get_request_defheaders(),
verify=version_info >= (2, 7, 9),
)
if self._request.status_code != 200: if self._request.status_code != 200:
raise FDUnrecognizedStatusCode(self._request.status_code, url) raise FDUnrecognizedStatusCode(self._request.status_code, url)
disposition = self._request.headers.get("content-disposition") disposition = self._request.headers.get("content-disposition")
if disposition and "filename=" in disposition: if disposition and "filename=" in disposition:
self._fname = disposition[disposition.index("filename=") + self._fname = (
9:].replace('"', "").replace("'", "") disposition[disposition.index("filename=") + 9 :]
.replace('"', "")
.replace("'", "")
)
else: else:
self._fname = [p for p in url.split("/") if p][-1] self._fname = [p for p in url.split("/") if p][-1]
self._fname = str(self._fname) self._fname = str(self._fname)
@ -64,7 +72,7 @@ class FileDownloader(object):
def get_size(self): def get_size(self):
if "content-length" not in self._request.headers: if "content-length" not in self._request.headers:
return -1 return -1
return int(self._request.headers['content-length']) return int(self._request.headers["content-length"])
def start(self, with_progress=True): def start(self, with_progress=True):
label = "Downloading" label = "Downloading"
@ -101,11 +109,11 @@ class FileDownloader(object):
dlsha1 = None dlsha1 = None
try: try:
result = exec_command(["sha1sum", self._destination]) result = exec_command(["sha1sum", self._destination])
dlsha1 = result['out'] dlsha1 = result["out"]
except (OSError, ValueError): except (OSError, ValueError):
try: try:
result = exec_command(["shasum", "-a", "1", self._destination]) result = exec_command(["shasum", "-a", "1", self._destination])
dlsha1 = result['out'] dlsha1 = result["out"]
except (OSError, ValueError): except (OSError, ValueError):
pass pass
if not dlsha1: if not dlsha1:

View File

@ -64,8 +64,10 @@ class IncompatiblePlatform(PlatformioException):
class PlatformNotInstalledYet(PlatformioException): class PlatformNotInstalledYet(PlatformioException):
MESSAGE = ("The platform '{0}' has not been installed yet. " MESSAGE = (
"Use `platformio platform install {0}` command") "The platform '{0}' has not been installed yet. "
"Use `platformio platform install {0}` command"
)
class UnknownBoard(PlatformioException): class UnknownBoard(PlatformioException):
@ -102,22 +104,27 @@ class MissingPackageManifest(PlatformIOPackageException):
class UndefinedPackageVersion(PlatformIOPackageException): class UndefinedPackageVersion(PlatformIOPackageException):
MESSAGE = ("Could not find a version that satisfies the requirement '{0}'" MESSAGE = (
" for your system '{1}'") "Could not find a version that satisfies the requirement '{0}'"
" for your system '{1}'"
)
class PackageInstallError(PlatformIOPackageException): class PackageInstallError(PlatformIOPackageException):
MESSAGE = ("Could not install '{0}' with version requirements '{1}' " MESSAGE = (
"for your system '{2}'.\n\n" "Could not install '{0}' with version requirements '{1}' "
"Please try this solution -> http://bit.ly/faq-package-manager") "for your system '{2}'.\n\n"
"Please try this solution -> http://bit.ly/faq-package-manager"
)
class ExtractArchiveItemError(PlatformIOPackageException): 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 UnsupportedArchiveType(PlatformIOPackageException): class UnsupportedArchiveType(PlatformIOPackageException):
@ -132,14 +139,17 @@ class FDUnrecognizedStatusCode(PlatformIOPackageException):
class FDSizeMismatch(PlatformIOPackageException): class FDSizeMismatch(PlatformIOPackageException):
MESSAGE = ("The size ({0:d} bytes) of downloaded file '{1}' " MESSAGE = (
"is not equal to remote size ({2:d} bytes)") "The size ({0:d} bytes) of downloaded file '{1}' "
"is not equal to remote size ({2:d} bytes)"
)
class FDSHASumMismatch(PlatformIOPackageException): class FDSHASumMismatch(PlatformIOPackageException):
MESSAGE = ("The 'sha1' sum '{0}' of downloaded file '{1}' " MESSAGE = (
"is not equal to remote '{2}'") "The 'sha1' sum '{0}' of downloaded file '{1}' " "is not equal to remote '{2}'"
)
# #
@ -156,12 +166,13 @@ 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 "
"found in current working directory ({0}). To initialize new project " "found in current working directory ({0}). To initialize new project "
"please use `platformio init` command") "please use `platformio init` command"
)
class InvalidProjectConf(PlatformIOProjectException): class InvalidProjectConf(PlatformIOProjectException):
MESSAGE = ("Invalid '{0}' (project configuration file): '{1}'") MESSAGE = "Invalid '{0}' (project configuration file): '{1}'"
class UndefinedEnvPlatform(PlatformIOProjectException): class UndefinedEnvPlatform(PlatformIOProjectException):
@ -191,9 +202,11 @@ class ProjectOptionValueError(PlatformIOProjectException):
class LibNotFound(PlatformioException): class LibNotFound(PlatformioException):
MESSAGE = ("Library `{0}` has not been found in PlatformIO Registry.\n" MESSAGE = (
"You can ignore this message, if `{0}` is a built-in library " "Library `{0}` has not been found in PlatformIO Registry.\n"
"(included in framework, SDK). E.g., SPI, Wire, etc.") "You can ignore this message, if `{0}` is a built-in library "
"(included in framework, SDK). E.g., SPI, Wire, etc."
)
class NotGlobalLibDir(UserSideException): class NotGlobalLibDir(UserSideException):
@ -203,7 +216,8 @@ class NotGlobalLibDir(UserSideException):
"To manage libraries in global storage `{1}`,\n" "To manage libraries in global storage `{1}`,\n"
"please use `platformio lib --global {2}` or specify custom storage " "please use `platformio lib --global {2}` or specify custom storage "
"`platformio lib --storage-dir /path/to/storage/ {2}`.\n" "`platformio lib --storage-dir /path/to/storage/ {2}`.\n"
"Check `platformio lib --help` for details.") "Check `platformio lib --help` for details."
)
class InvalidLibConfURL(PlatformioException): class InvalidLibConfURL(PlatformioException):
@ -224,7 +238,8 @@ class MissedUdevRules(InvalidUdevRules):
MESSAGE = ( MESSAGE = (
"Warning! Please install `99-platformio-udev.rules`. \nMode details: " "Warning! Please install `99-platformio-udev.rules`. \nMode details: "
"https://docs.platformio.org/en/latest/faq.html#platformio-udev-rules") "https://docs.platformio.org/en/latest/faq.html#platformio-udev-rules"
)
class OutdatedUdevRules(InvalidUdevRules): class OutdatedUdevRules(InvalidUdevRules):
@ -232,7 +247,8 @@ class OutdatedUdevRules(InvalidUdevRules):
MESSAGE = ( MESSAGE = (
"Warning! Your `{0}` are outdated. Please update or reinstall them." "Warning! Your `{0}` are outdated. Please update or reinstall them."
"\n Mode details: https://docs.platformio.org" "\n Mode details: https://docs.platformio.org"
"/en/latest/faq.html#platformio-udev-rules") "/en/latest/faq.html#platformio-udev-rules"
)
# #
@ -260,7 +276,8 @@ class InternetIsOffline(UserSideException):
MESSAGE = ( MESSAGE = (
"You are not connected to the Internet.\n" "You are not connected to the Internet.\n"
"If you build a project first time, we need Internet connection " "If you build a project first time, we need Internet connection "
"to install all dependencies and toolchains.") "to install all dependencies and toolchains."
)
class BuildScriptNotFound(PlatformioException): class BuildScriptNotFound(PlatformioException):
@ -285,9 +302,11 @@ class InvalidJSONFile(PlatformioException):
class CIBuildEnvsEmpty(PlatformioException): class CIBuildEnvsEmpty(PlatformioException):
MESSAGE = ("Can't find PlatformIO build environments.\n" MESSAGE = (
"Please specify `--board` or path to `platformio.ini` with " "Can't find PlatformIO build environments.\n"
"predefined environments using `--project-conf` option") "Please specify `--board` or path to `platformio.ini` with "
"predefined environments using `--project-conf` option"
)
class UpgradeError(PlatformioException): class UpgradeError(PlatformioException):
@ -307,13 +326,16 @@ class HomeDirPermissionsError(PlatformioException):
"current user and PlatformIO can not store configuration data.\n" "current user and PlatformIO can not store configuration data.\n"
"Please check the permissions and owner of that directory.\n" "Please check the permissions and owner of that directory.\n"
"Otherwise, please remove manually `{0}` directory and PlatformIO " "Otherwise, please remove manually `{0}` directory and PlatformIO "
"will create new from the current user.") "will create new from the current user."
)
class CygwinEnvDetected(PlatformioException): class CygwinEnvDetected(PlatformioException):
MESSAGE = ("PlatformIO does not work within Cygwin environment. " MESSAGE = (
"Use native Terminal instead.") "PlatformIO does not work within Cygwin environment. "
"Use native Terminal instead."
)
class DebugSupportError(PlatformioException): class DebugSupportError(PlatformioException):
@ -322,7 +344,8 @@ class DebugSupportError(PlatformioException):
"Currently, PlatformIO does not support debugging for `{0}`.\n" "Currently, PlatformIO does not support debugging for `{0}`.\n"
"Please request support at https://github.com/platformio/" "Please request support at https://github.com/platformio/"
"platformio-core/issues \nor visit -> https://docs.platformio.org" "platformio-core/issues \nor visit -> https://docs.platformio.org"
"/page/plus/debugging.html") "/page/plus/debugging.html"
)
class DebugInvalidOptions(PlatformioException): class DebugInvalidOptions(PlatformioException):
@ -331,8 +354,10 @@ class DebugInvalidOptions(PlatformioException):
class TestDirNotExists(PlatformioException): class TestDirNotExists(PlatformioException):
MESSAGE = "A test folder '{0}' does not exist.\nPlease create 'test' "\ MESSAGE = (
"directory in project's root and put a test set.\n"\ "A test folder '{0}' does not exist.\nPlease create 'test' "
"More details about Unit "\ "directory in project's root and put a test set.\n"
"Testing: http://docs.platformio.org/page/plus/"\ "More details about Unit "
"unit-testing.html" "Testing: http://docs.platformio.org/page/plus/"
"unit-testing.html"
)

View File

@ -27,7 +27,6 @@ from platformio.compat import WINDOWS, get_file_contents, glob_escape
class cd(object): class cd(object):
def __init__(self, new_path): def __init__(self, new_path):
self.new_path = new_path self.new_path = new_path
self.prev_path = os.getcwd() self.prev_path = os.getcwd()
@ -65,10 +64,10 @@ def format_filesize(filesize):
if filesize < base: if filesize < base:
return "%d%s" % (filesize, suffix) return "%d%s" % (filesize, suffix)
for i, suffix in enumerate("KMGTPEZY"): for i, suffix in enumerate("KMGTPEZY"):
unit = base**(i + 2) unit = base ** (i + 2)
if filesize >= unit: if filesize >= unit:
continue continue
if filesize % (base**(i + 1)): if filesize % (base ** (i + 1)):
return "%.2f%sB" % ((base * filesize / unit), suffix) return "%.2f%sB" % ((base * filesize / unit), suffix)
break break
return "%d%sB" % ((base * filesize / unit), suffix) return "%d%sB" % ((base * filesize / unit), suffix)
@ -78,21 +77,24 @@ def ensure_udev_rules():
from platformio.util import get_systype from platformio.util import get_systype
def _rules_to_set(rules_path): def _rules_to_set(rules_path):
return set(l.strip() for l in get_file_contents(rules_path).split("\n") return set(
if l.strip() and not l.startswith("#")) l.strip()
for l in get_file_contents(rules_path).split("\n")
if l.strip() and not l.startswith("#")
)
if "linux" not in get_systype(): if "linux" not in get_systype():
return None return None
installed_rules = [ installed_rules = [
"/etc/udev/rules.d/99-platformio-udev.rules", "/etc/udev/rules.d/99-platformio-udev.rules",
"/lib/udev/rules.d/99-platformio-udev.rules" "/lib/udev/rules.d/99-platformio-udev.rules",
] ]
if not any(os.path.isfile(p) for p in installed_rules): if not any(os.path.isfile(p) for p in installed_rules):
raise exception.MissedUdevRules raise exception.MissedUdevRules
origin_path = os.path.abspath( origin_path = os.path.abspath(
os.path.join(get_source_dir(), "..", "scripts", os.path.join(get_source_dir(), "..", "scripts", "99-platformio-udev.rules")
"99-platformio-udev.rules")) )
if not os.path.isfile(origin_path): if not os.path.isfile(origin_path):
return None return None
@ -117,7 +119,6 @@ def path_endswith_ext(path, extensions):
def match_src_files(src_dir, src_filter=None, src_exts=None): def match_src_files(src_dir, src_filter=None, src_exts=None):
def _append_build_item(items, item, src_dir): def _append_build_item(items, item, src_dir):
if not src_exts or path_endswith_ext(item, src_exts): if not src_exts or path_endswith_ext(item, src_exts):
items.add(item.replace(src_dir + os.sep, "")) items.add(item.replace(src_dir + os.sep, ""))
@ -135,8 +136,7 @@ def match_src_files(src_dir, src_filter=None, src_exts=None):
if os.path.isdir(item): if os.path.isdir(item):
for root, _, files in os.walk(item, followlinks=True): for root, _, files in os.walk(item, followlinks=True):
for f in files: for f in files:
_append_build_item(items, os.path.join(root, f), _append_build_item(items, os.path.join(root, f), src_dir)
src_dir)
else: else:
_append_build_item(items, item, src_dir) _append_build_item(items, item, src_dir)
if action == "+": if action == "+":
@ -153,7 +153,6 @@ def to_unix_path(path):
def rmtree(path): def rmtree(path):
def _onerror(func, path, __): def _onerror(func, path, __):
try: try:
st_mode = os.stat(path).st_mode st_mode = os.stat(path).st_mode
@ -161,9 +160,10 @@ def rmtree(path):
os.chmod(path, st_mode | stat.S_IWRITE) os.chmod(path, st_mode | stat.S_IWRITE)
func(path) func(path)
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
click.secho("%s \nPlease manually remove the file `%s`" % click.secho(
(str(e), path), "%s \nPlease manually remove the file `%s`" % (str(e), path),
fg="red", fg="red",
err=True) err=True,
)
return shutil.rmtree(path, onerror=_onerror) return shutil.rmtree(path, onerror=_onerror)

View File

@ -23,17 +23,17 @@ from platformio import fs, util
from platformio.compat import get_file_contents from platformio.compat import get_file_contents
from platformio.proc import where_is_program from platformio.proc import where_is_program
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (get_project_lib_dir, from platformio.project.helpers import (
get_project_libdeps_dir, get_project_lib_dir,
get_project_src_dir, get_project_libdeps_dir,
load_project_ide_data) get_project_src_dir,
load_project_ide_data,
)
class ProjectGenerator(object): class ProjectGenerator(object):
def __init__(self, project_dir, ide, boards): def __init__(self, project_dir, ide, boards):
self.config = ProjectConfig.get_instance( self.config = ProjectConfig.get_instance(join(project_dir, "platformio.ini"))
join(project_dir, "platformio.ini"))
self.config.validate() self.config.validate()
self.project_dir = project_dir self.project_dir = project_dir
self.ide = str(ide) self.ide = str(ide)
@ -42,8 +42,7 @@ class ProjectGenerator(object):
@staticmethod @staticmethod
def get_supported_ides(): def get_supported_ides():
tpls_dir = join(fs.get_source_dir(), "ide", "tpls") tpls_dir = join(fs.get_source_dir(), "ide", "tpls")
return sorted( return sorted([d for d in os.listdir(tpls_dir) if isdir(join(tpls_dir, d))])
[d for d in os.listdir(tpls_dir) if isdir(join(tpls_dir, d))])
def get_best_envname(self, boards=None): def get_best_envname(self, boards=None):
envname = None envname = None
@ -72,28 +71,29 @@ class ProjectGenerator(object):
"project_dir": self.project_dir, "project_dir": self.project_dir,
"env_name": self.env_name, "env_name": self.env_name,
"user_home_dir": abspath(expanduser("~")), "user_home_dir": abspath(expanduser("~")),
"platformio_path": "platformio_path": sys.argv[0]
sys.argv[0] if isfile(sys.argv[0]) if isfile(sys.argv[0])
else where_is_program("platformio"), else where_is_program("platformio"),
"env_path": os.getenv("PATH"), "env_path": os.getenv("PATH"),
"env_pathsep": os.pathsep "env_pathsep": os.pathsep,
} # yapf: disable } # yapf: disable
# default env configuration # default env configuration
tpl_vars.update(self.config.items(env=self.env_name, as_dict=True)) tpl_vars.update(self.config.items(env=self.env_name, as_dict=True))
# build data # build data
tpl_vars.update( tpl_vars.update(load_project_ide_data(self.project_dir, self.env_name) or {})
load_project_ide_data(self.project_dir, self.env_name) or {})
with fs.cd(self.project_dir): with fs.cd(self.project_dir):
tpl_vars.update({ tpl_vars.update(
"src_files": self.get_src_files(), {
"project_src_dir": get_project_src_dir(), "src_files": self.get_src_files(),
"project_lib_dir": get_project_lib_dir(), "project_src_dir": get_project_src_dir(),
"project_libdeps_dir": join( "project_lib_dir": get_project_lib_dir(),
get_project_libdeps_dir(), self.env_name) "project_libdeps_dir": join(
get_project_libdeps_dir(), self.env_name
}) # yapf: disable ),
}
) # yapf: disable
for key, value in tpl_vars.items(): for key, value in tpl_vars.items():
if key.endswith(("_path", "_dir")): if key.endswith(("_path", "_dir")):
@ -103,7 +103,7 @@ class ProjectGenerator(object):
continue continue
tpl_vars[key] = [fs.to_unix_path(inc) for inc in tpl_vars[key]] tpl_vars[key] = [fs.to_unix_path(inc) for inc in tpl_vars[key]]
tpl_vars['to_unix_path'] = fs.to_unix_path tpl_vars["to_unix_path"] = fs.to_unix_path
return tpl_vars return tpl_vars
def get_src_files(self): def get_src_files(self):

View File

@ -26,10 +26,12 @@ LOCKFILE_INTERFACE_MSVCRT = 2
try: try:
import fcntl import fcntl
LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_FCNTL LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_FCNTL
except ImportError: except ImportError:
try: try:
import msvcrt import msvcrt
LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_MSVCRT LOCKFILE_CURRENT_INTERFACE = LOCKFILE_INTERFACE_MSVCRT
except ImportError: except ImportError:
LOCKFILE_CURRENT_INTERFACE = None LOCKFILE_CURRENT_INTERFACE = None
@ -40,7 +42,6 @@ class LockFileExists(Exception):
class LockFile(object): class LockFile(object):
def __init__(self, path, timeout=LOCKFILE_TIMEOUT, delay=LOCKFILE_DELAY): def __init__(self, path, timeout=LOCKFILE_TIMEOUT, delay=LOCKFILE_DELAY):
self.timeout = timeout self.timeout = timeout
self.delay = delay self.delay = delay

View File

@ -49,12 +49,16 @@ def on_platformio_end(ctx, result): # pylint: disable=unused-argument
check_platformio_upgrade() check_platformio_upgrade()
check_internal_updates(ctx, "platforms") check_internal_updates(ctx, "platforms")
check_internal_updates(ctx, "libraries") check_internal_updates(ctx, "libraries")
except (exception.InternetIsOffline, exception.GetLatestVersionError, except (
exception.APIRequestError): exception.InternetIsOffline,
exception.GetLatestVersionError,
exception.APIRequestError,
):
click.secho( click.secho(
"Failed to check for PlatformIO upgrades. " "Failed to check for PlatformIO upgrades. "
"Please check your Internet connection.", "Please check your Internet connection.",
fg="red") fg="red",
)
def on_platformio_exception(e): def on_platformio_exception(e):
@ -78,15 +82,17 @@ def set_caller(caller=None):
class Upgrader(object): class Upgrader(object):
def __init__(self, from_version, to_version): def __init__(self, from_version, to_version):
self.from_version = semantic_version.Version.coerce( self.from_version = semantic_version.Version.coerce(
util.pepver_to_semver(from_version)) util.pepver_to_semver(from_version)
)
self.to_version = semantic_version.Version.coerce( self.to_version = semantic_version.Version.coerce(
util.pepver_to_semver(to_version)) util.pepver_to_semver(to_version)
)
self._upgraders = [(semantic_version.Version("3.5.0-a.2"), self._upgraders = [
self._update_dev_platforms)] (semantic_version.Version("3.5.0-a.2"), self._update_dev_platforms)
]
def run(self, ctx): def run(self, ctx):
if self.from_version > self.to_version: if self.from_version > self.to_version:
@ -114,19 +120,21 @@ def after_upgrade(ctx):
if last_version == "0.0.0": if last_version == "0.0.0":
app.set_state_item("last_version", __version__) app.set_state_item("last_version", __version__)
elif semantic_version.Version.coerce(util.pepver_to_semver( elif semantic_version.Version.coerce(
last_version)) > semantic_version.Version.coerce( util.pepver_to_semver(last_version)
util.pepver_to_semver(__version__)): ) > semantic_version.Version.coerce(util.pepver_to_semver(__version__)):
click.secho("*" * terminal_width, fg="yellow") click.secho("*" * terminal_width, fg="yellow")
click.secho("Obsolete PIO Core v%s is used (previous was %s)" % click.secho(
(__version__, last_version), "Obsolete PIO Core v%s is used (previous was %s)"
fg="yellow") % (__version__, last_version),
click.secho("Please remove multiple PIO Cores from a system:", fg="yellow",
fg="yellow") )
click.secho("Please remove multiple PIO Cores from a system:", fg="yellow")
click.secho( click.secho(
"https://docs.platformio.org/page/faq.html" "https://docs.platformio.org/page/faq.html"
"#multiple-pio-cores-in-a-system", "#multiple-pio-cores-in-a-system",
fg="cyan") fg="cyan",
)
click.secho("*" * terminal_width, fg="yellow") click.secho("*" * terminal_width, fg="yellow")
return return
else: else:
@ -139,37 +147,53 @@ def after_upgrade(ctx):
u = Upgrader(last_version, __version__) u = Upgrader(last_version, __version__)
if u.run(ctx): if u.run(ctx):
app.set_state_item("last_version", __version__) app.set_state_item("last_version", __version__)
click.secho("PlatformIO has been successfully upgraded to %s!\n" % click.secho(
__version__, "PlatformIO has been successfully upgraded to %s!\n" % __version__,
fg="green") fg="green",
telemetry.on_event(category="Auto", )
action="Upgrade", telemetry.on_event(
label="%s > %s" % (last_version, __version__)) category="Auto",
action="Upgrade",
label="%s > %s" % (last_version, __version__),
)
else: else:
raise exception.UpgradeError("Auto upgrading...") raise exception.UpgradeError("Auto upgrading...")
click.echo("") click.echo("")
# PlatformIO banner # PlatformIO banner
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("If you like %s, please:" % click.echo("If you like %s, please:" % (click.style("PlatformIO", fg="cyan")))
(click.style("PlatformIO", fg="cyan")))
click.echo("- %s us on Twitter to stay up-to-date "
"on the latest project news > %s" %
(click.style("follow", fg="cyan"),
click.style("https://twitter.com/PlatformIO_Org", fg="cyan")))
click.echo( click.echo(
"- %s it on GitHub > %s" % "- %s us on Twitter to stay up-to-date "
(click.style("star", fg="cyan"), "on the latest project news > %s"
click.style("https://github.com/platformio/platformio", fg="cyan"))) % (
click.style("follow", fg="cyan"),
click.style("https://twitter.com/PlatformIO_Org", fg="cyan"),
)
)
click.echo(
"- %s it on GitHub > %s"
% (
click.style("star", fg="cyan"),
click.style("https://github.com/platformio/platformio", fg="cyan"),
)
)
if not getenv("PLATFORMIO_IDE"): if not getenv("PLATFORMIO_IDE"):
click.echo( click.echo(
"- %s PlatformIO IDE for IoT development > %s" % "- %s PlatformIO IDE for IoT development > %s"
(click.style("try", fg="cyan"), % (
click.style("https://platformio.org/platformio-ide", fg="cyan"))) click.style("try", fg="cyan"),
click.style("https://platformio.org/platformio-ide", fg="cyan"),
)
)
if not is_ci(): if not is_ci():
click.echo("- %s us with PlatformIO Plus > %s" % click.echo(
(click.style("support", fg="cyan"), "- %s us with PlatformIO Plus > %s"
click.style("https://pioplus.com", fg="cyan"))) % (
click.style("support", fg="cyan"),
click.style("https://pioplus.com", fg="cyan"),
)
)
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("") click.echo("")
@ -181,7 +205,7 @@ def check_platformio_upgrade():
if (time() - interval) < last_check.get("platformio_upgrade", 0): if (time() - interval) < last_check.get("platformio_upgrade", 0):
return return
last_check['platformio_upgrade'] = int(time()) last_check["platformio_upgrade"] = int(time())
app.set_state_item("last_check", last_check) app.set_state_item("last_check", last_check)
util.internet_on(raise_exception=True) util.internet_on(raise_exception=True)
@ -190,23 +214,23 @@ def check_platformio_upgrade():
update_core_packages(silent=True) update_core_packages(silent=True)
latest_version = get_latest_version() latest_version = get_latest_version()
if semantic_version.Version.coerce(util.pepver_to_semver( if semantic_version.Version.coerce(
latest_version)) <= semantic_version.Version.coerce( util.pepver_to_semver(latest_version)
util.pepver_to_semver(__version__)): ) <= semantic_version.Version.coerce(util.pepver_to_semver(__version__)):
return return
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
click.echo("") click.echo("")
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.secho("There is a new version %s of PlatformIO available.\n" click.secho(
"Please upgrade it via `" % latest_version, "There is a new version %s of PlatformIO available.\n"
fg="yellow", "Please upgrade it via `" % latest_version,
nl=False) fg="yellow",
nl=False,
)
if getenv("PLATFORMIO_IDE"): if getenv("PLATFORMIO_IDE"):
click.secho("PlatformIO IDE Menu: Upgrade PlatformIO", click.secho("PlatformIO IDE Menu: Upgrade PlatformIO", fg="cyan", nl=False)
fg="cyan",
nl=False)
click.secho("`.", fg="yellow") click.secho("`.", fg="yellow")
elif join("Cellar", "platformio") in fs.get_source_dir(): elif join("Cellar", "platformio") in fs.get_source_dir():
click.secho("brew update && brew upgrade", fg="cyan", nl=False) click.secho("brew update && brew upgrade", fg="cyan", nl=False)
@ -217,8 +241,7 @@ def check_platformio_upgrade():
click.secho("pip install -U platformio", fg="cyan", nl=False) click.secho("pip install -U platformio", fg="cyan", nl=False)
click.secho("` command.", fg="yellow") click.secho("` command.", fg="yellow")
click.secho("Changes: ", fg="yellow", nl=False) click.secho("Changes: ", fg="yellow", nl=False)
click.secho("https://docs.platformio.org/en/latest/history.html", click.secho("https://docs.platformio.org/en/latest/history.html", fg="cyan")
fg="cyan")
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("") click.echo("")
@ -229,7 +252,7 @@ def check_internal_updates(ctx, what):
if (time() - interval) < last_check.get(what + "_update", 0): if (time() - interval) < last_check.get(what + "_update", 0):
return return
last_check[what + '_update'] = int(time()) last_check[what + "_update"] = int(time())
app.set_state_item("last_check", last_check) app.set_state_item("last_check", last_check)
util.internet_on(raise_exception=True) util.internet_on(raise_exception=True)
@ -237,15 +260,17 @@ def check_internal_updates(ctx, what):
pm = PlatformManager() if what == "platforms" else LibraryManager() pm = PlatformManager() if what == "platforms" else LibraryManager()
outdated_items = [] outdated_items = []
for manifest in pm.get_installed(): for manifest in pm.get_installed():
if manifest['name'] in outdated_items: if manifest["name"] in outdated_items:
continue continue
conds = [ conds = [
pm.outdated(manifest['__pkg_dir']), what == "platforms" pm.outdated(manifest["__pkg_dir"]),
what == "platforms"
and PlatformFactory.newPlatform( and PlatformFactory.newPlatform(
manifest['__pkg_dir']).are_outdated_packages() manifest["__pkg_dir"]
).are_outdated_packages(),
] ]
if any(conds): if any(conds):
outdated_items.append(manifest['name']) outdated_items.append(manifest["name"])
if not outdated_items: if not outdated_items:
return return
@ -254,26 +279,32 @@ def check_internal_updates(ctx, what):
click.echo("") click.echo("")
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.secho("There are the new updates for %s (%s)" % click.secho(
(what, ", ".join(outdated_items)), "There are the new updates for %s (%s)" % (what, ", ".join(outdated_items)),
fg="yellow") fg="yellow",
)
if not app.get_setting("auto_update_" + what): if not app.get_setting("auto_update_" + what):
click.secho("Please update them via ", fg="yellow", nl=False) click.secho("Please update them via ", fg="yellow", nl=False)
click.secho("`platformio %s update`" % click.secho(
("lib --global" if what == "libraries" else "platform"), "`platformio %s update`"
fg="cyan", % ("lib --global" if what == "libraries" else "platform"),
nl=False) fg="cyan",
nl=False,
)
click.secho(" command.\n", fg="yellow") click.secho(" command.\n", fg="yellow")
click.secho( click.secho(
"If you want to manually check for the new versions " "If you want to manually check for the new versions "
"without updating, please use ", "without updating, please use ",
fg="yellow", fg="yellow",
nl=False) nl=False,
click.secho("`platformio %s update --dry-run`" % )
("lib --global" if what == "libraries" else "platform"), click.secho(
fg="cyan", "`platformio %s update --dry-run`"
nl=False) % ("lib --global" if what == "libraries" else "platform"),
fg="cyan",
nl=False,
)
click.secho(" command.", fg="yellow") click.secho(" command.", fg="yellow")
else: else:
click.secho("Please wait while updating %s ..." % what, fg="yellow") click.secho("Please wait while updating %s ..." % what, fg="yellow")
@ -284,9 +315,7 @@ def check_internal_updates(ctx, what):
ctx.invoke(cmd_lib_update, libraries=outdated_items) ctx.invoke(cmd_lib_update, libraries=outdated_items)
click.echo() click.echo()
telemetry.on_event(category="Auto", telemetry.on_event(category="Auto", action="Update", label=what.title())
action="Update",
label=what.title())
click.echo("*" * terminal_width) click.echo("*" * terminal_width)
click.echo("") click.echo("")

View File

@ -25,13 +25,12 @@ from platformio.project.helpers import get_project_packages_dir
CORE_PACKAGES = { CORE_PACKAGES = {
"contrib-piohome": "^2.3.2", "contrib-piohome": "^2.3.2",
"contrib-pysite": "contrib-pysite": "~2.%d%d.190418" % (sys.version_info[0], sys.version_info[1]),
"~2.%d%d.190418" % (sys.version_info[0], sys.version_info[1]),
"tool-pioplus": "^2.5.2", "tool-pioplus": "^2.5.2",
"tool-unity": "~1.20403.0", "tool-unity": "~1.20403.0",
"tool-scons": "~2.20501.7" if PY2 else "~3.30101.0", "tool-scons": "~2.20501.7" if PY2 else "~3.30101.0",
"tool-cppcheck": "~1.189.0", "tool-cppcheck": "~1.189.0",
"tool-clangtidy": "^1.80000.0" "tool-clangtidy": "^1.80000.0",
} }
PIOPLUS_AUTO_UPDATES_MAX = 100 PIOPLUS_AUTO_UPDATES_MAX = 100
@ -40,20 +39,19 @@ PIOPLUS_AUTO_UPDATES_MAX = 100
class CorePackageManager(PackageManager): class CorePackageManager(PackageManager):
def __init__(self): def __init__(self):
super(CorePackageManager, self).__init__(get_project_packages_dir(), [ super(CorePackageManager, self).__init__(
"https://dl.bintray.com/platformio/dl-packages/manifest.json", get_project_packages_dir(),
"http%s://dl.platformio.org/packages/manifest.json" % [
("" if sys.version_info < (2, 7, 9) else "s") "https://dl.bintray.com/platformio/dl-packages/manifest.json",
]) "http%s://dl.platformio.org/packages/manifest.json"
% ("" if sys.version_info < (2, 7, 9) else "s"),
],
)
def install( # pylint: disable=keyword-arg-before-vararg def install( # pylint: disable=keyword-arg-before-vararg
self, self, name, requirements=None, *args, **kwargs
name, ):
requirements=None,
*args,
**kwargs):
PackageManager.install(self, name, requirements, *args, **kwargs) PackageManager.install(self, name, requirements, *args, **kwargs)
self.cleanup_packages() self.cleanup_packages()
return self.get_package_dir(name, requirements) return self.get_package_dir(name, requirements)
@ -70,12 +68,12 @@ class CorePackageManager(PackageManager):
pkg_dir = self.get_package_dir(name, requirements) pkg_dir = self.get_package_dir(name, requirements)
if not pkg_dir: if not pkg_dir:
continue continue
best_pkg_versions[name] = self.load_manifest(pkg_dir)['version'] best_pkg_versions[name] = self.load_manifest(pkg_dir)["version"]
for manifest in self.get_installed(): for manifest in self.get_installed():
if manifest['name'] not in best_pkg_versions: if manifest["name"] not in best_pkg_versions:
continue continue
if manifest['version'] != best_pkg_versions[manifest['name']]: if manifest["version"] != best_pkg_versions[manifest["name"]]:
self.uninstall(manifest['__pkg_dir'], after_update=True) self.uninstall(manifest["__pkg_dir"], after_update=True)
self.cache_reset() self.cache_reset()
return True return True
@ -104,6 +102,7 @@ def update_core_packages(only_check=False, silent=False):
def inject_contrib_pysite(): def inject_contrib_pysite():
from site import addsitedir from site import addsitedir
contrib_pysite_dir = get_core_package_dir("contrib-pysite") contrib_pysite_dir = get_core_package_dir("contrib-pysite")
if contrib_pysite_dir in sys.path: if contrib_pysite_dir in sys.path:
return return
@ -116,16 +115,18 @@ def pioplus_call(args, **kwargs):
raise exception.PlatformioException( raise exception.PlatformioException(
"PlatformIO Core Plus v%s does not run under Python version %s.\n" "PlatformIO Core Plus v%s does not run under Python version %s.\n"
"Minimum supported version is 2.7.6, please upgrade Python.\n" "Minimum supported version is 2.7.6, please upgrade Python.\n"
"Python 3 is not yet supported.\n" % (__version__, sys.version)) "Python 3 is not yet supported.\n" % (__version__, sys.version)
)
pioplus_path = join(get_core_package_dir("tool-pioplus"), "pioplus") pioplus_path = join(get_core_package_dir("tool-pioplus"), "pioplus")
pythonexe_path = get_pythonexe_path() pythonexe_path = get_pythonexe_path()
os.environ['PYTHONEXEPATH'] = pythonexe_path os.environ["PYTHONEXEPATH"] = pythonexe_path
os.environ['PYTHONPYSITEDIR'] = get_core_package_dir("contrib-pysite") os.environ["PYTHONPYSITEDIR"] = get_core_package_dir("contrib-pysite")
os.environ['PIOCOREPYSITEDIR'] = dirname(fs.get_source_dir() or "") os.environ["PIOCOREPYSITEDIR"] = dirname(fs.get_source_dir() or "")
if dirname(pythonexe_path) not in os.environ['PATH'].split(os.pathsep): if dirname(pythonexe_path) not in os.environ["PATH"].split(os.pathsep):
os.environ['PATH'] = (os.pathsep).join( os.environ["PATH"] = (os.pathsep).join(
[dirname(pythonexe_path), os.environ['PATH']]) [dirname(pythonexe_path), os.environ["PATH"]]
)
copy_pythonpath_to_osenv() copy_pythonpath_to_osenv()
code = subprocess.call([pioplus_path] + args, **kwargs) code = subprocess.call([pioplus_path] + args, **kwargs)

View File

@ -41,10 +41,7 @@ class LibraryManager(BasePkgManager):
@property @property
def manifest_names(self): def manifest_names(self):
return [ return [".library.json", "library.json", "library.properties", "module.json"]
".library.json", "library.json", "library.properties",
"module.json"
]
def get_manifest_path(self, pkg_dir): def get_manifest_path(self, pkg_dir):
path = BasePkgManager.get_manifest_path(self, pkg_dir) path = BasePkgManager.get_manifest_path(self, pkg_dir)
@ -71,36 +68,37 @@ class LibraryManager(BasePkgManager):
# if Arduino library.properties # if Arduino library.properties
if "sentence" in manifest: if "sentence" in manifest:
manifest['frameworks'] = ["arduino"] manifest["frameworks"] = ["arduino"]
manifest['description'] = manifest['sentence'] manifest["description"] = manifest["sentence"]
del manifest['sentence'] del manifest["sentence"]
if "author" in manifest: if "author" in manifest:
if isinstance(manifest['author'], dict): if isinstance(manifest["author"], dict):
manifest['authors'] = [manifest['author']] manifest["authors"] = [manifest["author"]]
else: else:
manifest['authors'] = [{"name": manifest['author']}] manifest["authors"] = [{"name": manifest["author"]}]
del manifest['author'] del manifest["author"]
if "authors" in manifest and not isinstance(manifest['authors'], list): if "authors" in manifest and not isinstance(manifest["authors"], list):
manifest['authors'] = [manifest['authors']] manifest["authors"] = [manifest["authors"]]
if "keywords" not in manifest: if "keywords" not in manifest:
keywords = [] keywords = []
for keyword in re.split(r"[\s/]+", for keyword in re.split(
manifest.get("category", "Uncategorized")): r"[\s/]+", manifest.get("category", "Uncategorized")
):
keyword = keyword.strip() keyword = keyword.strip()
if not keyword: if not keyword:
continue continue
keywords.append(keyword.lower()) keywords.append(keyword.lower())
manifest['keywords'] = keywords manifest["keywords"] = keywords
if "category" in manifest: if "category" in manifest:
del manifest['category'] del manifest["category"]
# don't replace VCS URL # don't replace VCS URL
if "url" in manifest and "description" in manifest: if "url" in manifest and "description" in manifest:
manifest['homepage'] = manifest['url'] manifest["homepage"] = manifest["url"]
del manifest['url'] del manifest["url"]
if "architectures" in manifest: if "architectures" in manifest:
platforms = [] platforms = []
@ -110,26 +108,23 @@ class LibraryManager(BasePkgManager):
"samd": "atmelsam", "samd": "atmelsam",
"esp8266": "espressif8266", "esp8266": "espressif8266",
"esp32": "espressif32", "esp32": "espressif32",
"arc32": "intel_arc32" "arc32": "intel_arc32",
} }
for arch in manifest['architectures'].split(","): for arch in manifest["architectures"].split(","):
arch = arch.strip() arch = arch.strip()
if arch == "*": if arch == "*":
platforms = "*" platforms = "*"
break break
if arch in platforms_map: if arch in platforms_map:
platforms.append(platforms_map[arch]) platforms.append(platforms_map[arch])
manifest['platforms'] = platforms manifest["platforms"] = platforms
del manifest['architectures'] del manifest["architectures"]
# convert listed items via comma to array # convert listed items via comma to array
for key in ("keywords", "frameworks", "platforms"): for key in ("keywords", "frameworks", "platforms"):
if key not in manifest or \ if key not in manifest or not isinstance(manifest[key], string_types):
not isinstance(manifest[key], string_types):
continue continue
manifest[key] = [ manifest[key] = [i.strip() for i in manifest[key].split(",") if i.strip()]
i.strip() for i in manifest[key].split(",") if i.strip()
]
return manifest return manifest
@ -153,13 +148,10 @@ class LibraryManager(BasePkgManager):
if item[k] == "*": if item[k] == "*":
del item[k] del item[k]
elif isinstance(item[k], string_types): elif isinstance(item[k], string_types):
item[k] = [ item[k] = [i.strip() for i in item[k].split(",") if i.strip()]
i.strip() for i in item[k].split(",") if i.strip()
]
return items return items
def max_satisfying_repo_version(self, versions, requirements=None): def max_satisfying_repo_version(self, versions, requirements=None):
def _cmp_dates(datestr1, datestr2): def _cmp_dates(datestr1, datestr2):
date1 = util.parse_date(datestr1) date1 = util.parse_date(datestr1)
date2 = util.parse_date(datestr2) date2 = util.parse_date(datestr2)
@ -169,61 +161,66 @@ class LibraryManager(BasePkgManager):
semver_spec = None semver_spec = None
try: try:
semver_spec = semantic_version.SimpleSpec( semver_spec = (
requirements) if requirements else None semantic_version.SimpleSpec(requirements) if requirements else None
)
except ValueError: except ValueError:
pass pass
item = {} item = {}
for v in versions: for v in versions:
semver_new = self.parse_semver_version(v['name']) semver_new = self.parse_semver_version(v["name"])
if semver_spec: if semver_spec:
if not semver_new or semver_new not in semver_spec: if not semver_new or semver_new not in semver_spec:
continue continue
if not item or self.parse_semver_version( if not item or self.parse_semver_version(item["name"]) < semver_new:
item['name']) < semver_new:
item = v item = v
elif requirements: elif requirements:
if requirements == v['name']: if requirements == v["name"]:
return v return v
else: else:
if not item or _cmp_dates(item['released'], if not item or _cmp_dates(item["released"], v["released"]) == -1:
v['released']) == -1:
item = v item = v
return item return item
def get_latest_repo_version(self, name, requirements, silent=False): def get_latest_repo_version(self, name, requirements, silent=False):
item = self.max_satisfying_repo_version( item = self.max_satisfying_repo_version(
util.get_api_result("/lib/info/%d" % self.search_lib_id( util.get_api_result(
{ "/lib/info/%d"
"name": name, % self.search_lib_id(
"requirements": requirements {"name": name, "requirements": requirements}, silent=silent
}, silent=silent), ),
cache_valid="1h")['versions'], requirements) cache_valid="1h",
return item['name'] if item else None )["versions"],
requirements,
)
return item["name"] if item else None
def _install_from_piorepo(self, name, requirements): def _install_from_piorepo(self, name, requirements):
assert name.startswith("id="), name assert name.startswith("id="), name
version = self.get_latest_repo_version(name, requirements) version = self.get_latest_repo_version(name, requirements)
if not version: if not version:
raise exception.UndefinedPackageVersion(requirements or "latest", raise exception.UndefinedPackageVersion(
util.get_systype()) requirements or "latest", util.get_systype()
dl_data = util.get_api_result("/lib/download/" + str(name[3:]), )
dict(version=version), dl_data = util.get_api_result(
cache_valid="30d") "/lib/download/" + str(name[3:]), dict(version=version), cache_valid="30d"
)
assert dl_data assert dl_data
return self._install_from_url( return self._install_from_url(
name, dl_data['url'].replace("http://", "https://") name,
if app.get_setting("strict_ssl") else dl_data['url'], requirements) dl_data["url"].replace("http://", "https://")
if app.get_setting("strict_ssl")
else dl_data["url"],
requirements,
)
def search_lib_id( # pylint: disable=too-many-branches def search_lib_id( # pylint: disable=too-many-branches
self, self, filters, silent=False, interactive=False
filters, ):
silent=False,
interactive=False):
assert isinstance(filters, dict) assert isinstance(filters, dict)
assert "name" in filters assert "name" in filters
@ -234,8 +231,10 @@ class LibraryManager(BasePkgManager):
# looking in PIO Library Registry # looking in PIO Library Registry
if not silent: if not silent:
click.echo("Looking for %s library in registry" % click.echo(
click.style(filters['name'], fg="cyan")) "Looking for %s library in registry"
% click.style(filters["name"], fg="cyan")
)
query = [] query = []
for key in filters: for key in filters:
if key not in ("name", "authors", "frameworks", "platforms"): if key not in ("name", "authors", "frameworks", "platforms"):
@ -244,25 +243,29 @@ class LibraryManager(BasePkgManager):
if not isinstance(values, list): if not isinstance(values, list):
values = [v.strip() for v in values.split(",") if v] values = [v.strip() for v in values.split(",") if v]
for value in values: for value in values:
query.append('%s:"%s"' % query.append(
(key[:-1] if key.endswith("s") else key, value)) '%s:"%s"' % (key[:-1] if key.endswith("s") else key, value)
)
lib_info = None lib_info = None
result = util.get_api_result("/v2/lib/search", result = util.get_api_result(
dict(query=" ".join(query)), "/v2/lib/search", dict(query=" ".join(query)), cache_valid="1h"
cache_valid="1h") )
if result['total'] == 1: if result["total"] == 1:
lib_info = result['items'][0] lib_info = result["items"][0]
elif result['total'] > 1: elif result["total"] > 1:
if silent and not interactive: if silent and not interactive:
lib_info = result['items'][0] lib_info = result["items"][0]
else: else:
click.secho("Conflict: More than one library has been found " click.secho(
"by request %s:" % json.dumps(filters), "Conflict: More than one library has been found "
fg="yellow", "by request %s:" % json.dumps(filters),
err=True) fg="yellow",
err=True,
)
from platformio.commands.lib import print_lib_item from platformio.commands.lib import print_lib_item
for item in result['items']:
for item in result["items"]:
print_lib_item(item) print_lib_item(item)
if not interactive: if not interactive:
@ -270,36 +273,39 @@ class LibraryManager(BasePkgManager):
"Automatically chose the first available library " "Automatically chose the first available library "
"(use `--interactive` option to make a choice)", "(use `--interactive` option to make a choice)",
fg="yellow", fg="yellow",
err=True) err=True,
lib_info = result['items'][0] )
lib_info = result["items"][0]
else: else:
deplib_id = click.prompt("Please choose library ID", deplib_id = click.prompt(
type=click.Choice([ "Please choose library ID",
str(i['id']) type=click.Choice([str(i["id"]) for i in result["items"]]),
for i in result['items'] )
])) for item in result["items"]:
for item in result['items']: if item["id"] == int(deplib_id):
if item['id'] == int(deplib_id):
lib_info = item lib_info = item
break break
if not lib_info: if not lib_info:
if list(filters) == ["name"]: if list(filters) == ["name"]:
raise exception.LibNotFound(filters['name']) raise exception.LibNotFound(filters["name"])
raise exception.LibNotFound(str(filters)) raise exception.LibNotFound(str(filters))
if not silent: if not silent:
click.echo("Found: %s" % click.style( click.echo(
"https://platformio.org/lib/show/{id}/{name}".format( "Found: %s"
**lib_info), % click.style(
fg="blue")) "https://platformio.org/lib/show/{id}/{name}".format(**lib_info),
return int(lib_info['id']) fg="blue",
)
)
return int(lib_info["id"])
def _get_lib_id_from_installed(self, filters): def _get_lib_id_from_installed(self, filters):
if filters['name'].startswith("id="): if filters["name"].startswith("id="):
return int(filters['name'][3:]) return int(filters["name"][3:])
package_dir = self.get_package_dir( package_dir = self.get_package_dir(
filters['name'], filters.get("requirements", filters["name"], filters.get("requirements", filters.get("version"))
filters.get("version"))) )
if not package_dir: if not package_dir:
return None return None
manifest = self.load_manifest(package_dir) manifest = self.load_manifest(package_dir)
@ -311,52 +317,55 @@ class LibraryManager(BasePkgManager):
continue continue
if key not in manifest: if key not in manifest:
return None return None
if not util.items_in_list(util.items_to_list(filters[key]), if not util.items_in_list(
util.items_to_list(manifest[key])): util.items_to_list(filters[key]), util.items_to_list(manifest[key])
):
return None return None
if "authors" in filters: if "authors" in filters:
if "authors" not in manifest: if "authors" not in manifest:
return None return None
manifest_authors = manifest['authors'] manifest_authors = manifest["authors"]
if not isinstance(manifest_authors, list): if not isinstance(manifest_authors, list):
manifest_authors = [manifest_authors] manifest_authors = [manifest_authors]
manifest_authors = [ manifest_authors = [
a['name'] for a in manifest_authors a["name"]
for a in manifest_authors
if isinstance(a, dict) and "name" in a if isinstance(a, dict) and "name" in a
] ]
filter_authors = filters['authors'] filter_authors = filters["authors"]
if not isinstance(filter_authors, list): if not isinstance(filter_authors, list):
filter_authors = [filter_authors] filter_authors = [filter_authors]
if not set(filter_authors) <= set(manifest_authors): if not set(filter_authors) <= set(manifest_authors):
return None return None
return int(manifest['id']) return int(manifest["id"])
def install( # pylint: disable=arguments-differ def install( # pylint: disable=arguments-differ
self, self,
name, name,
requirements=None, requirements=None,
silent=False, silent=False,
after_update=False, after_update=False,
interactive=False, interactive=False,
force=False): force=False,
):
_name, _requirements, _url = self.parse_pkg_uri(name, requirements) _name, _requirements, _url = self.parse_pkg_uri(name, requirements)
if not _url: if not _url:
name = "id=%d" % self.search_lib_id( name = "id=%d" % self.search_lib_id(
{ {"name": _name, "requirements": _requirements},
"name": _name,
"requirements": _requirements
},
silent=silent, silent=silent,
interactive=interactive) interactive=interactive,
)
requirements = _requirements requirements = _requirements
pkg_dir = BasePkgManager.install(self, pkg_dir = BasePkgManager.install(
name, self,
requirements, name,
silent=silent, requirements,
after_update=after_update, silent=silent,
force=force) after_update=after_update,
force=force,
)
if not pkg_dir: if not pkg_dir:
return None return None
@ -369,7 +378,7 @@ class LibraryManager(BasePkgManager):
click.secho("Installing dependencies", fg="yellow") click.secho("Installing dependencies", fg="yellow")
builtin_lib_storages = None builtin_lib_storages = None
for filters in self.normalize_dependencies(manifest['dependencies']): for filters in self.normalize_dependencies(manifest["dependencies"]):
assert "name" in filters assert "name" in filters
# avoid circle dependencies # avoid circle dependencies
@ -381,35 +390,42 @@ class LibraryManager(BasePkgManager):
self.INSTALL_HISTORY.append(history_key) self.INSTALL_HISTORY.append(history_key)
if any(s in filters.get("version", "") for s in ("\\", "/")): if any(s in filters.get("version", "") for s in ("\\", "/")):
self.install("{name}={version}".format(**filters), self.install(
silent=silent, "{name}={version}".format(**filters),
after_update=after_update, silent=silent,
interactive=interactive, after_update=after_update,
force=force) interactive=interactive,
force=force,
)
else: else:
try: try:
lib_id = self.search_lib_id(filters, silent, interactive) lib_id = self.search_lib_id(filters, silent, interactive)
except exception.LibNotFound as e: except exception.LibNotFound as e:
if builtin_lib_storages is None: if builtin_lib_storages is None:
builtin_lib_storages = get_builtin_libs() builtin_lib_storages = get_builtin_libs()
if not silent or is_builtin_lib(builtin_lib_storages, if not silent or is_builtin_lib(
filters['name']): builtin_lib_storages, filters["name"]
):
click.secho("Warning! %s" % e, fg="yellow") click.secho("Warning! %s" % e, fg="yellow")
continue continue
if filters.get("version"): if filters.get("version"):
self.install(lib_id, self.install(
filters.get("version"), lib_id,
silent=silent, filters.get("version"),
after_update=after_update, silent=silent,
interactive=interactive, after_update=after_update,
force=force) interactive=interactive,
force=force,
)
else: else:
self.install(lib_id, self.install(
silent=silent, lib_id,
after_update=after_update, silent=silent,
interactive=interactive, after_update=after_update,
force=force) interactive=interactive,
force=force,
)
return pkg_dir return pkg_dir
@ -418,21 +434,23 @@ def get_builtin_libs(storage_names=None):
storage_names = storage_names or [] storage_names = storage_names or []
pm = PlatformManager() pm = PlatformManager()
for manifest in pm.get_installed(): for manifest in pm.get_installed():
p = PlatformFactory.newPlatform(manifest['__pkg_dir']) p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
for storage in p.get_lib_storages(): for storage in p.get_lib_storages():
if storage_names and storage['name'] not in storage_names: if storage_names and storage["name"] not in storage_names:
continue continue
lm = LibraryManager(storage['path']) lm = LibraryManager(storage["path"])
items.append({ items.append(
"name": storage['name'], {
"path": storage['path'], "name": storage["name"],
"items": lm.get_installed() "path": storage["path"],
}) "items": lm.get_installed(),
}
)
return items return items
def is_builtin_lib(storages, name): def is_builtin_lib(storages, name):
for storage in storages or []: for storage in storages or []:
if any(l.get("name") == name for l in storage['items']): if any(l.get("name") == name for l in storage["items"]):
return True return True
return False return False

View File

@ -36,7 +36,6 @@ from platformio.vcsclient import VCSClientFactory
class PackageRepoIterator(object): class PackageRepoIterator(object):
def __init__(self, package, repositories): def __init__(self, package, repositories):
assert isinstance(repositories, list) assert isinstance(repositories, list)
self.package = package self.package = package
@ -87,8 +86,9 @@ class PkgRepoMixin(object):
item = None item = None
reqspec = None reqspec = None
try: try:
reqspec = semantic_version.SimpleSpec( reqspec = (
requirements) if requirements else None semantic_version.SimpleSpec(requirements) if requirements else None
)
except ValueError: except ValueError:
pass pass
@ -99,33 +99,32 @@ class PkgRepoMixin(object):
# if PkgRepoMixin.PIO_VERSION not in requirements.SimpleSpec( # if PkgRepoMixin.PIO_VERSION not in requirements.SimpleSpec(
# v['engines']['platformio']): # v['engines']['platformio']):
# continue # continue
specver = semantic_version.Version(v['version']) specver = semantic_version.Version(v["version"])
if reqspec and specver not in reqspec: if reqspec and specver not in reqspec:
continue continue
if not item or semantic_version.Version(item['version']) < specver: if not item or semantic_version.Version(item["version"]) < specver:
item = v item = v
return item return item
def get_latest_repo_version( # pylint: disable=unused-argument def get_latest_repo_version( # pylint: disable=unused-argument
self, self, name, requirements, silent=False
name, ):
requirements,
silent=False):
version = None version = None
for versions in PackageRepoIterator(name, self.repositories): for versions in PackageRepoIterator(name, self.repositories):
pkgdata = self.max_satisfying_repo_version(versions, requirements) pkgdata = self.max_satisfying_repo_version(versions, requirements)
if not pkgdata: if not pkgdata:
continue continue
if not version or semantic_version.compare(pkgdata['version'], if (
version) == 1: not version
version = pkgdata['version'] or semantic_version.compare(pkgdata["version"], version) == 1
):
version = pkgdata["version"]
return version return version
def get_all_repo_versions(self, name): def get_all_repo_versions(self, name):
result = [] result = []
for versions in PackageRepoIterator(name, self.repositories): for versions in PackageRepoIterator(name, self.repositories):
result.extend( result.extend([semantic_version.Version(v["version"]) for v in versions])
[semantic_version.Version(v['version']) for v in versions])
return [str(v) for v in sorted(set(result))] return [str(v) for v in sorted(set(result))]
@ -154,7 +153,8 @@ class PkgInstallerMixin(object):
if result: if result:
return result return result
result = [ result = [
join(src_dir, name) for name in sorted(os.listdir(src_dir)) join(src_dir, name)
for name in sorted(os.listdir(src_dir))
if isdir(join(src_dir, name)) if isdir(join(src_dir, name))
] ]
self.cache_set(cache_key, result) self.cache_set(cache_key, result)
@ -189,14 +189,17 @@ class PkgInstallerMixin(object):
click.secho( click.secho(
"Error: Please read http://bit.ly/package-manager-ioerror", "Error: Please read http://bit.ly/package-manager-ioerror",
fg="red", fg="red",
err=True) err=True,
)
raise e raise e
if sha1: if sha1:
fd.verify(sha1) fd.verify(sha1)
dst_path = fd.get_filepath() dst_path = fd.get_filepath()
if not self.FILE_CACHE_VALID or getsize( if (
dst_path) > PkgInstallerMixin.FILE_CACHE_MAX_SIZE: not self.FILE_CACHE_VALID
or getsize(dst_path) > PkgInstallerMixin.FILE_CACHE_MAX_SIZE
):
return dst_path return dst_path
with app.ContentCache() as cc: with app.ContentCache() as cc:
@ -232,15 +235,15 @@ class PkgInstallerMixin(object):
return None return None
@staticmethod @staticmethod
def parse_pkg_uri( # pylint: disable=too-many-branches def parse_pkg_uri(text, requirements=None): # pylint: disable=too-many-branches
text, requirements=None):
text = str(text) text = str(text)
name, url = None, None name, url = None, None
# Parse requirements # Parse requirements
req_conditions = [ req_conditions = [
"@" in text, not requirements, ":" not in text "@" in text,
or text.rfind("/") < text.rfind("@") not requirements,
":" not in text or text.rfind("/") < text.rfind("@"),
] ]
if all(req_conditions): if all(req_conditions):
text, requirements = text.rsplit("@", 1) text, requirements = text.rsplit("@", 1)
@ -259,17 +262,16 @@ class PkgInstallerMixin(object):
elif "/" in text or "\\" in text: elif "/" in text or "\\" in text:
git_conditions = [ git_conditions = [
# Handle GitHub URL (https://github.com/user/package) # Handle GitHub URL (https://github.com/user/package)
text.startswith("https://github.com/") and not text.endswith( text.startswith("https://github.com/")
(".zip", ".tar.gz")), and not text.endswith((".zip", ".tar.gz")),
(text.split("#", 1)[0] (text.split("#", 1)[0] if "#" in text else text).endswith(".git"),
if "#" in text else text).endswith(".git")
] ]
hg_conditions = [ hg_conditions = [
# Handle Developer Mbed URL # Handle Developer Mbed URL
# (https://developer.mbed.org/users/user/code/package/) # (https://developer.mbed.org/users/user/code/package/)
# (https://os.mbed.com/users/user/code/package/) # (https://os.mbed.com/users/user/code/package/)
text.startswith("https://developer.mbed.org"), text.startswith("https://developer.mbed.org"),
text.startswith("https://os.mbed.com") text.startswith("https://os.mbed.com"),
] ]
if any(git_conditions): if any(git_conditions):
url = "git+" + text url = "git+" + text
@ -296,9 +298,9 @@ class PkgInstallerMixin(object):
@staticmethod @staticmethod
def get_install_dirname(manifest): def get_install_dirname(manifest):
name = re.sub(r"[^\da-z\_\-\. ]", "_", manifest['name'], flags=re.I) name = re.sub(r"[^\da-z\_\-\. ]", "_", manifest["name"], flags=re.I)
if "id" in manifest: if "id" in manifest:
name += "_ID%d" % manifest['id'] name += "_ID%d" % manifest["id"]
return str(name) return str(name)
@classmethod @classmethod
@ -322,8 +324,7 @@ class PkgInstallerMixin(object):
return None return None
def manifest_exists(self, pkg_dir): def manifest_exists(self, pkg_dir):
return self.get_manifest_path(pkg_dir) or \ return self.get_manifest_path(pkg_dir) or self.get_src_manifest_path(pkg_dir)
self.get_src_manifest_path(pkg_dir)
def load_manifest(self, pkg_dir): def load_manifest(self, pkg_dir):
cache_key = "load_manifest-%s" % pkg_dir cache_key = "load_manifest-%s" % pkg_dir
@ -353,19 +354,19 @@ class PkgInstallerMixin(object):
if src_manifest: if src_manifest:
if "version" in src_manifest: if "version" in src_manifest:
manifest['version'] = src_manifest['version'] manifest["version"] = src_manifest["version"]
manifest['__src_url'] = src_manifest['url'] manifest["__src_url"] = src_manifest["url"]
# handle a custom package name # handle a custom package name
autogen_name = self.parse_pkg_uri(manifest['__src_url'])[0] autogen_name = self.parse_pkg_uri(manifest["__src_url"])[0]
if "name" not in manifest or autogen_name != src_manifest['name']: if "name" not in manifest or autogen_name != src_manifest["name"]:
manifest['name'] = src_manifest['name'] manifest["name"] = src_manifest["name"]
if "name" not in manifest: if "name" not in manifest:
manifest['name'] = basename(pkg_dir) manifest["name"] = basename(pkg_dir)
if "version" not in manifest: if "version" not in manifest:
manifest['version'] = "0.0.0" manifest["version"] = "0.0.0"
manifest['__pkg_dir'] = pkg_dir manifest["__pkg_dir"] = pkg_dir
self.cache_set(cache_key, manifest) self.cache_set(cache_key, manifest)
return manifest return manifest
@ -390,25 +391,24 @@ class PkgInstallerMixin(object):
continue continue
elif pkg_id and manifest.get("id") != pkg_id: elif pkg_id and manifest.get("id") != pkg_id:
continue continue
elif not pkg_id and manifest['name'] != name: elif not pkg_id and manifest["name"] != name:
continue continue
elif not PkgRepoMixin.is_system_compatible(manifest.get("system")): elif not PkgRepoMixin.is_system_compatible(manifest.get("system")):
continue continue
# strict version or VCS HASH # strict version or VCS HASH
if requirements and requirements == manifest['version']: if requirements and requirements == manifest["version"]:
return manifest return manifest
try: try:
if requirements and not semantic_version.SimpleSpec( if requirements and not semantic_version.SimpleSpec(requirements).match(
requirements).match( self.parse_semver_version(manifest["version"], raise_exception=True)
self.parse_semver_version(manifest['version'], ):
raise_exception=True)):
continue continue
elif not best or (self.parse_semver_version( elif not best or (
manifest['version'], raise_exception=True) > self.parse_semver_version(manifest["version"], raise_exception=True)
self.parse_semver_version( > self.parse_semver_version(best["version"], raise_exception=True)
best['version'], raise_exception=True)): ):
best = manifest best = manifest
except ValueError: except ValueError:
pass pass
@ -417,12 +417,15 @@ class PkgInstallerMixin(object):
def get_package_dir(self, name, requirements=None, url=None): def get_package_dir(self, name, requirements=None, url=None):
manifest = self.get_package(name, requirements, url) manifest = self.get_package(name, requirements, url)
return manifest.get("__pkg_dir") if manifest and isdir( return (
manifest.get("__pkg_dir")) else None manifest.get("__pkg_dir")
if manifest and isdir(manifest.get("__pkg_dir"))
else None
)
def get_package_by_dir(self, pkg_dir): def get_package_by_dir(self, pkg_dir):
for manifest in self.get_installed(): for manifest in self.get_installed():
if manifest['__pkg_dir'] == abspath(pkg_dir): if manifest["__pkg_dir"] == abspath(pkg_dir):
return manifest return manifest
return None return None
@ -443,9 +446,9 @@ class PkgInstallerMixin(object):
if not pkgdata: if not pkgdata:
continue continue
try: try:
pkg_dir = self._install_from_url(name, pkgdata['url'], pkg_dir = self._install_from_url(
requirements, name, pkgdata["url"], requirements, pkgdata.get("sha1")
pkgdata.get("sha1")) )
break break
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
click.secho("Warning! Package Mirror: %s" % e, fg="yellow") click.secho("Warning! Package Mirror: %s" % e, fg="yellow")
@ -455,16 +458,12 @@ class PkgInstallerMixin(object):
util.internet_on(raise_exception=True) util.internet_on(raise_exception=True)
raise exception.UnknownPackage(name) raise exception.UnknownPackage(name)
if not pkgdata: if not pkgdata:
raise exception.UndefinedPackageVersion(requirements or "latest", raise exception.UndefinedPackageVersion(
util.get_systype()) requirements or "latest", util.get_systype()
)
return pkg_dir return pkg_dir
def _install_from_url(self, def _install_from_url(self, name, url, requirements=None, sha1=None, track=False):
name,
url,
requirements=None,
sha1=None,
track=False):
tmp_dir = mkdtemp("-package", self.TMP_FOLDER_PREFIX, self.package_dir) tmp_dir = mkdtemp("-package", self.TMP_FOLDER_PREFIX, self.package_dir)
src_manifest_dir = None src_manifest_dir = None
src_manifest = {"name": name, "url": url, "requirements": requirements} src_manifest = {"name": name, "url": url, "requirements": requirements}
@ -486,7 +485,7 @@ class PkgInstallerMixin(object):
vcs = VCSClientFactory.newClient(tmp_dir, url) vcs = VCSClientFactory.newClient(tmp_dir, url)
assert vcs.export() assert vcs.export()
src_manifest_dir = vcs.storage_dir src_manifest_dir = vcs.storage_dir
src_manifest['version'] = vcs.get_current_revision() src_manifest["version"] = vcs.get_current_revision()
_tmp_dir = tmp_dir _tmp_dir = tmp_dir
if not src_manifest_dir: if not src_manifest_dir:
@ -515,7 +514,8 @@ class PkgInstallerMixin(object):
json.dump(_data, fp) json.dump(_data, fp)
def _install_from_tmp_dir( # pylint: disable=too-many-branches def _install_from_tmp_dir( # pylint: disable=too-many-branches
self, tmp_dir, requirements=None): self, tmp_dir, requirements=None
):
tmp_manifest = self.load_manifest(tmp_dir) tmp_manifest = self.load_manifest(tmp_dir)
assert set(["name", "version"]) <= set(tmp_manifest) assert set(["name", "version"]) <= set(tmp_manifest)
@ -523,28 +523,30 @@ class PkgInstallerMixin(object):
pkg_dir = join(self.package_dir, pkg_dirname) pkg_dir = join(self.package_dir, pkg_dirname)
cur_manifest = self.load_manifest(pkg_dir) cur_manifest = self.load_manifest(pkg_dir)
tmp_semver = self.parse_semver_version(tmp_manifest['version']) tmp_semver = self.parse_semver_version(tmp_manifest["version"])
cur_semver = None cur_semver = None
if cur_manifest: if cur_manifest:
cur_semver = self.parse_semver_version(cur_manifest['version']) cur_semver = self.parse_semver_version(cur_manifest["version"])
# package should satisfy requirements # package should satisfy requirements
if requirements: if requirements:
mismatch_error = ( mismatch_error = "Package version %s doesn't satisfy requirements %s" % (
"Package version %s doesn't satisfy requirements %s" % tmp_manifest["version"],
(tmp_manifest['version'], requirements)) requirements,
)
try: try:
assert tmp_semver and tmp_semver in semantic_version.SimpleSpec( assert tmp_semver and tmp_semver in semantic_version.SimpleSpec(
requirements), mismatch_error requirements
), mismatch_error
except (AssertionError, ValueError): except (AssertionError, ValueError):
assert tmp_manifest['version'] == requirements, mismatch_error assert tmp_manifest["version"] == requirements, mismatch_error
# check if package already exists # check if package already exists
if cur_manifest: if cur_manifest:
# 0-overwrite, 1-rename, 2-fix to a version # 0-overwrite, 1-rename, 2-fix to a version
action = 0 action = 0
if "__src_url" in cur_manifest: if "__src_url" in cur_manifest:
if cur_manifest['__src_url'] != tmp_manifest.get("__src_url"): if cur_manifest["__src_url"] != tmp_manifest.get("__src_url"):
action = 1 action = 1
elif "__src_url" in tmp_manifest: elif "__src_url" in tmp_manifest:
action = 2 action = 2
@ -556,25 +558,25 @@ class PkgInstallerMixin(object):
# rename # rename
if action == 1: if action == 1:
target_dirname = "%s@%s" % (pkg_dirname, target_dirname = "%s@%s" % (pkg_dirname, cur_manifest["version"])
cur_manifest['version'])
if "__src_url" in cur_manifest: if "__src_url" in cur_manifest:
target_dirname = "%s@src-%s" % ( target_dirname = "%s@src-%s" % (
pkg_dirname, pkg_dirname,
hashlib.md5( hashlib.md5(
hashlib_encode_data( hashlib_encode_data(cur_manifest["__src_url"])
cur_manifest['__src_url'])).hexdigest()) ).hexdigest(),
)
shutil.move(pkg_dir, join(self.package_dir, target_dirname)) shutil.move(pkg_dir, join(self.package_dir, target_dirname))
# fix to a version # fix to a version
elif action == 2: elif action == 2:
target_dirname = "%s@%s" % (pkg_dirname, target_dirname = "%s@%s" % (pkg_dirname, tmp_manifest["version"])
tmp_manifest['version'])
if "__src_url" in tmp_manifest: if "__src_url" in tmp_manifest:
target_dirname = "%s@src-%s" % ( target_dirname = "%s@src-%s" % (
pkg_dirname, pkg_dirname,
hashlib.md5( hashlib.md5(
hashlib_encode_data( hashlib_encode_data(tmp_manifest["__src_url"])
tmp_manifest['__src_url'])).hexdigest()) ).hexdigest(),
)
pkg_dir = join(self.package_dir, target_dirname) pkg_dir = join(self.package_dir, target_dirname)
# remove previous/not-satisfied package # remove previous/not-satisfied package
@ -622,9 +624,9 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if "__src_url" in manifest: if "__src_url" in manifest:
try: try:
vcs = VCSClientFactory.newClient(pkg_dir, vcs = VCSClientFactory.newClient(
manifest['__src_url'], pkg_dir, manifest["__src_url"], silent=True
silent=True) )
except (AttributeError, exception.PlatformioException): except (AttributeError, exception.PlatformioException):
return None return None
if not vcs.can_be_updated: if not vcs.can_be_updated:
@ -633,10 +635,10 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
else: else:
try: try:
latest = self.get_latest_repo_version( latest = self.get_latest_repo_version(
"id=%d" % "id=%d" % manifest["id"] if "id" in manifest else manifest["name"],
manifest['id'] if "id" in manifest else manifest['name'],
requirements, requirements,
silent=True) silent=True,
)
except (exception.PlatformioException, ValueError): except (exception.PlatformioException, ValueError):
return None return None
@ -646,21 +648,17 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
up_to_date = False up_to_date = False
try: try:
assert "__src_url" not in manifest assert "__src_url" not in manifest
up_to_date = (self.parse_semver_version(manifest['version'], up_to_date = self.parse_semver_version(
raise_exception=True) >= manifest["version"], raise_exception=True
self.parse_semver_version(latest, ) >= self.parse_semver_version(latest, raise_exception=True)
raise_exception=True))
except (AssertionError, ValueError): except (AssertionError, ValueError):
up_to_date = latest == manifest['version'] up_to_date = latest == manifest["version"]
return False if up_to_date else latest return False if up_to_date else latest
def install(self, def install(
name, self, name, requirements=None, silent=False, after_update=False, force=False
requirements=None, ):
silent=False,
after_update=False,
force=False):
pkg_dir = None pkg_dir = None
# interprocess lock # interprocess lock
with LockFile(self.package_dir): with LockFile(self.package_dir):
@ -690,34 +688,38 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if not silent: if not silent:
click.secho( click.secho(
"{name} @ {version} is already installed".format( "{name} @ {version} is already installed".format(
**self.load_manifest(package_dir)), **self.load_manifest(package_dir)
fg="yellow") ),
fg="yellow",
)
return package_dir return package_dir
if url: if url:
pkg_dir = self._install_from_url(name, pkg_dir = self._install_from_url(name, url, requirements, track=True)
url,
requirements,
track=True)
else: else:
pkg_dir = self._install_from_piorepo(name, requirements) pkg_dir = self._install_from_piorepo(name, requirements)
if not pkg_dir or not self.manifest_exists(pkg_dir): if not pkg_dir or not self.manifest_exists(pkg_dir):
raise exception.PackageInstallError(name, requirements or "*", raise exception.PackageInstallError(
util.get_systype()) name, requirements or "*", util.get_systype()
)
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
assert manifest assert manifest
if not after_update: if not after_update:
telemetry.on_event(category=self.__class__.__name__, telemetry.on_event(
action="Install", category=self.__class__.__name__,
label=manifest['name']) action="Install",
label=manifest["name"],
)
click.secho( click.secho(
"{name} @ {version} has been successfully installed!".format( "{name} @ {version} has been successfully installed!".format(
**manifest), **manifest
fg="green") ),
fg="green",
)
return pkg_dir return pkg_dir
@ -729,18 +731,20 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
if isdir(package) and self.get_package_by_dir(package): if isdir(package) and self.get_package_by_dir(package):
pkg_dir = package pkg_dir = package
else: else:
name, requirements, url = self.parse_pkg_uri( name, requirements, url = self.parse_pkg_uri(package, requirements)
package, requirements)
pkg_dir = self.get_package_dir(name, requirements, url) pkg_dir = self.get_package_dir(name, requirements, url)
if not pkg_dir: if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" % raise exception.UnknownPackage(
(package, requirements or "*")) "%s @ %s" % (package, requirements or "*")
)
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
click.echo("Uninstalling %s @ %s: \t" % (click.style( click.echo(
manifest['name'], fg="cyan"), manifest['version']), "Uninstalling %s @ %s: \t"
nl=False) % (click.style(manifest["name"], fg="cyan"), manifest["version"]),
nl=False,
)
if islink(pkg_dir): if islink(pkg_dir):
os.unlink(pkg_dir) os.unlink(pkg_dir)
@ -749,19 +753,21 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
self.cache_reset() self.cache_reset()
# unfix package with the same name # unfix package with the same name
pkg_dir = self.get_package_dir(manifest['name']) pkg_dir = self.get_package_dir(manifest["name"])
if pkg_dir and "@" in pkg_dir: if pkg_dir and "@" in pkg_dir:
shutil.move( shutil.move(
pkg_dir, pkg_dir, join(self.package_dir, self.get_install_dirname(manifest))
join(self.package_dir, self.get_install_dirname(manifest))) )
self.cache_reset() self.cache_reset()
click.echo("[%s]" % click.style("OK", fg="green")) click.echo("[%s]" % click.style("OK", fg="green"))
if not after_update: if not after_update:
telemetry.on_event(category=self.__class__.__name__, telemetry.on_event(
action="Uninstall", category=self.__class__.__name__,
label=manifest['name']) action="Uninstall",
label=manifest["name"],
)
return True return True
@ -773,16 +779,19 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
pkg_dir = self.get_package_dir(*self.parse_pkg_uri(package)) pkg_dir = self.get_package_dir(*self.parse_pkg_uri(package))
if not pkg_dir: if not pkg_dir:
raise exception.UnknownPackage("%s @ %s" % raise exception.UnknownPackage("%s @ %s" % (package, requirements or "*"))
(package, requirements or "*"))
manifest = self.load_manifest(pkg_dir) manifest = self.load_manifest(pkg_dir)
name = manifest['name'] name = manifest["name"]
click.echo("{} {:<40} @ {:<15}".format( click.echo(
"Checking" if only_check else "Updating", "{} {:<40} @ {:<15}".format(
click.style(manifest['name'], fg="cyan"), manifest['version']), "Checking" if only_check else "Updating",
nl=False) click.style(manifest["name"], fg="cyan"),
manifest["version"],
),
nl=False,
)
if not util.internet_on(): if not util.internet_on():
click.echo("[%s]" % (click.style("Off-line", fg="yellow"))) click.echo("[%s]" % (click.style("Off-line", fg="yellow")))
return None return None
@ -799,22 +808,22 @@ class BasePkgManager(PkgRepoMixin, PkgInstallerMixin):
return True return True
if "__src_url" in manifest: if "__src_url" in manifest:
vcs = VCSClientFactory.newClient(pkg_dir, manifest['__src_url']) vcs = VCSClientFactory.newClient(pkg_dir, manifest["__src_url"])
assert vcs.update() assert vcs.update()
self._update_src_manifest(dict(version=vcs.get_current_revision()), self._update_src_manifest(
vcs.storage_dir) dict(version=vcs.get_current_revision()), vcs.storage_dir
)
else: else:
self.uninstall(pkg_dir, after_update=True) self.uninstall(pkg_dir, after_update=True)
self.install(name, latest, after_update=True) self.install(name, latest, after_update=True)
telemetry.on_event(category=self.__class__.__name__, telemetry.on_event(
action="Update", category=self.__class__.__name__, action="Update", label=manifest["name"]
label=manifest['name']) )
return True return True
class PackageManager(BasePkgManager): class PackageManager(BasePkgManager):
@property @property
def manifest_names(self): def manifest_names(self):
return ["package.json"] return ["package.json"]

View File

@ -26,13 +26,19 @@ from platformio import __version__, app, exception, fs, util
from platformio.compat import PY2, hashlib_encode_data, is_bytes from platformio.compat import PY2, hashlib_encode_data, is_bytes
from platformio.managers.core import get_core_package_dir from platformio.managers.core import get_core_package_dir
from platformio.managers.package import BasePkgManager, PackageManager from platformio.managers.package import BasePkgManager, PackageManager
from platformio.proc import (BuildAsyncPipe, copy_pythonpath_to_osenv, from platformio.proc import (
exec_command, get_pythonexe_path) BuildAsyncPipe,
copy_pythonpath_to_osenv,
exec_command,
get_pythonexe_path,
)
from platformio.project.config import ProjectConfig from platformio.project.config import ProjectConfig
from platformio.project.helpers import (get_project_boards_dir, from platformio.project.helpers import (
get_project_core_dir, get_project_boards_dir,
get_project_packages_dir, get_project_core_dir,
get_project_platforms_dir) get_project_packages_dir,
get_project_platforms_dir,
)
try: try:
from urllib.parse import quote from urllib.parse import quote
@ -41,16 +47,17 @@ except ImportError:
class PlatformManager(BasePkgManager): class PlatformManager(BasePkgManager):
def __init__(self, package_dir=None, repositories=None): def __init__(self, package_dir=None, repositories=None):
if not repositories: if not repositories:
repositories = [ repositories = [
"https://dl.bintray.com/platformio/dl-platforms/manifest.json", "https://dl.bintray.com/platformio/dl-platforms/manifest.json",
"{0}://dl.platformio.org/platforms/manifest.json".format( "{0}://dl.platformio.org/platforms/manifest.json".format(
"https" if app.get_setting("strict_ssl") else "http") "https" if app.get_setting("strict_ssl") else "http"
),
] ]
BasePkgManager.__init__(self, package_dir BasePkgManager.__init__(
or get_project_platforms_dir(), repositories) self, package_dir or get_project_platforms_dir(), repositories
)
@property @property
def manifest_names(self): def manifest_names(self):
@ -65,21 +72,21 @@ class PlatformManager(BasePkgManager):
return manifest_path return manifest_path
return None return None
def install(self, def install(
name, self,
requirements=None, name,
with_packages=None, requirements=None,
without_packages=None, with_packages=None,
skip_default_package=False, without_packages=None,
after_update=False, skip_default_package=False,
silent=False, after_update=False,
force=False, silent=False,
**_): # pylint: disable=too-many-arguments, arguments-differ force=False,
platform_dir = BasePkgManager.install(self, **_
name, ): # pylint: disable=too-many-arguments, arguments-differ
requirements, platform_dir = BasePkgManager.install(
silent=silent, self, name, requirements, silent=silent, force=force
force=force) )
p = PlatformFactory.newPlatform(platform_dir) p = PlatformFactory.newPlatform(platform_dir)
# don't cleanup packages or install them after update # don't cleanup packages or install them after update
@ -87,11 +94,13 @@ class PlatformManager(BasePkgManager):
if after_update: if after_update:
return True return True
p.install_packages(with_packages, p.install_packages(
without_packages, with_packages,
skip_default_package, without_packages,
silent=silent, skip_default_package,
force=force) silent=silent,
force=force,
)
return self.cleanup_packages(list(p.packages)) return self.cleanup_packages(list(p.packages))
def uninstall(self, package, requirements=None, after_update=False): def uninstall(self, package, requirements=None, after_update=False):
@ -115,11 +124,8 @@ class PlatformManager(BasePkgManager):
return self.cleanup_packages(list(p.packages)) return self.cleanup_packages(list(p.packages))
def update( # pylint: disable=arguments-differ def update( # pylint: disable=arguments-differ
self, self, package, requirements=None, only_check=False, only_packages=False
package, ):
requirements=None,
only_check=False,
only_packages=False):
if isdir(package): if isdir(package):
pkg_dir = package pkg_dir = package
else: else:
@ -143,8 +149,9 @@ class PlatformManager(BasePkgManager):
self.cleanup_packages(list(p.packages)) self.cleanup_packages(list(p.packages))
if missed_pkgs: if missed_pkgs:
p.install_packages(with_packages=list(missed_pkgs), p.install_packages(
skip_default_package=True) with_packages=list(missed_pkgs), skip_default_package=True
)
return True return True
@ -152,20 +159,22 @@ class PlatformManager(BasePkgManager):
self.cache_reset() self.cache_reset()
deppkgs = {} deppkgs = {}
for manifest in PlatformManager().get_installed(): for manifest in PlatformManager().get_installed():
p = PlatformFactory.newPlatform(manifest['__pkg_dir']) p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
for pkgname, pkgmanifest in p.get_installed_packages().items(): for pkgname, pkgmanifest in p.get_installed_packages().items():
if pkgname not in deppkgs: if pkgname not in deppkgs:
deppkgs[pkgname] = set() deppkgs[pkgname] = set()
deppkgs[pkgname].add(pkgmanifest['version']) deppkgs[pkgname].add(pkgmanifest["version"])
pm = PackageManager(get_project_packages_dir()) pm = PackageManager(get_project_packages_dir())
for manifest in pm.get_installed(): for manifest in pm.get_installed():
if manifest['name'] not in names: if manifest["name"] not in names:
continue continue
if (manifest['name'] not in deppkgs if (
or manifest['version'] not in deppkgs[manifest['name']]): manifest["name"] not in deppkgs
or manifest["version"] not in deppkgs[manifest["name"]]
):
try: try:
pm.uninstall(manifest['__pkg_dir'], after_update=True) pm.uninstall(manifest["__pkg_dir"], after_update=True)
except exception.UnknownPackage: except exception.UnknownPackage:
pass pass
@ -176,7 +185,7 @@ class PlatformManager(BasePkgManager):
def get_installed_boards(self): def get_installed_boards(self):
boards = [] boards = []
for manifest in self.get_installed(): for manifest in self.get_installed():
p = PlatformFactory.newPlatform(manifest['__pkg_dir']) p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
for config in p.get_boards().values(): for config in p.get_boards().values():
board = config.get_brief_data() board = config.get_brief_data()
if board not in boards: if board not in boards:
@ -189,30 +198,31 @@ class PlatformManager(BasePkgManager):
def get_all_boards(self): def get_all_boards(self):
boards = self.get_installed_boards() boards = self.get_installed_boards()
know_boards = ["%s:%s" % (b['platform'], b['id']) for b in boards] know_boards = ["%s:%s" % (b["platform"], b["id"]) for b in boards]
try: try:
for board in self.get_registered_boards(): for board in self.get_registered_boards():
key = "%s:%s" % (board['platform'], board['id']) key = "%s:%s" % (board["platform"], board["id"])
if key not in know_boards: if key not in know_boards:
boards.append(board) boards.append(board)
except (exception.APIRequestError, exception.InternetIsOffline): except (exception.APIRequestError, exception.InternetIsOffline):
pass pass
return sorted(boards, key=lambda b: b['name']) return sorted(boards, key=lambda b: b["name"])
def board_config(self, id_, platform=None): def board_config(self, id_, platform=None):
for manifest in self.get_installed_boards(): for manifest in self.get_installed_boards():
if manifest['id'] == id_ and (not platform if manifest["id"] == id_ and (
or manifest['platform'] == platform): not platform or manifest["platform"] == platform
):
return manifest return manifest
for manifest in self.get_registered_boards(): for manifest in self.get_registered_boards():
if manifest['id'] == id_ and (not platform if manifest["id"] == id_ and (
or manifest['platform'] == platform): not platform or manifest["platform"] == platform
):
return manifest return manifest
raise exception.UnknownBoard(id_) raise exception.UnknownBoard(id_)
class PlatformFactory(object): class PlatformFactory(object):
@staticmethod @staticmethod
def get_clsname(name): def get_clsname(name):
name = re.sub(r"[^\da-z\_]+", "", name, flags=re.I) name = re.sub(r"[^\da-z\_]+", "", name, flags=re.I)
@ -222,8 +232,7 @@ class PlatformFactory(object):
def load_module(name, path): def load_module(name, path):
module = None module = None
try: try:
module = load_source("platformio.managers.platform.%s" % name, module = load_source("platformio.managers.platform.%s" % name, path)
path)
except ImportError: except ImportError:
raise exception.UnknownPlatform(name) raise exception.UnknownPlatform(name)
return module return module
@ -234,28 +243,29 @@ class PlatformFactory(object):
platform_dir = None platform_dir = None
if isdir(name): if isdir(name):
platform_dir = name platform_dir = name
name = pm.load_manifest(platform_dir)['name'] name = pm.load_manifest(platform_dir)["name"]
elif name.endswith("platform.json") and isfile(name): elif name.endswith("platform.json") and isfile(name):
platform_dir = dirname(name) platform_dir = dirname(name)
name = fs.load_json(name)['name'] name = fs.load_json(name)["name"]
else: else:
name, requirements, url = pm.parse_pkg_uri(name, requirements) name, requirements, url = pm.parse_pkg_uri(name, requirements)
platform_dir = pm.get_package_dir(name, requirements, url) platform_dir = pm.get_package_dir(name, requirements, url)
if platform_dir: if platform_dir:
name = pm.load_manifest(platform_dir)['name'] name = pm.load_manifest(platform_dir)["name"]
if not platform_dir: if not platform_dir:
raise exception.UnknownPlatform( raise exception.UnknownPlatform(
name if not requirements else "%s@%s" % (name, requirements)) name if not requirements else "%s@%s" % (name, requirements)
)
platform_cls = None platform_cls = None
if isfile(join(platform_dir, "platform.py")): if isfile(join(platform_dir, "platform.py")):
platform_cls = getattr( platform_cls = getattr(
cls.load_module(name, join(platform_dir, "platform.py")), cls.load_module(name, join(platform_dir, "platform.py")),
cls.get_clsname(name)) cls.get_clsname(name),
)
else: else:
platform_cls = type(str(cls.get_clsname(name)), (PlatformBase, ), platform_cls = type(str(cls.get_clsname(name)), (PlatformBase,), {})
{})
_instance = platform_cls(join(platform_dir, "platform.json")) _instance = platform_cls(join(platform_dir, "platform.json"))
assert isinstance(_instance, PlatformBase) assert isinstance(_instance, PlatformBase)
@ -263,14 +273,14 @@ class PlatformFactory(object):
class PlatformPackagesMixin(object): class PlatformPackagesMixin(object):
def install_packages( # pylint: disable=too-many-arguments def install_packages( # pylint: disable=too-many-arguments
self, self,
with_packages=None, with_packages=None,
without_packages=None, without_packages=None,
skip_default_package=False, skip_default_package=False,
silent=False, silent=False,
force=False): force=False,
):
with_packages = set(self.find_pkg_names(with_packages or [])) with_packages = set(self.find_pkg_names(with_packages or []))
without_packages = set(self.find_pkg_names(without_packages or [])) without_packages = set(self.find_pkg_names(without_packages or []))
@ -283,12 +293,13 @@ class PlatformPackagesMixin(object):
version = opts.get("version", "") version = opts.get("version", "")
if name in without_packages: if name in without_packages:
continue continue
elif (name in with_packages or elif name in with_packages or not (
not (skip_default_package or opts.get("optional", False))): skip_default_package or opts.get("optional", False)
):
if ":" in version: if ":" in version:
self.pm.install("%s=%s" % (name, version), self.pm.install(
silent=silent, "%s=%s" % (name, version), silent=silent, force=force
force=force) )
else: else:
self.pm.install(name, version, silent=silent, force=force) self.pm.install(name, version, silent=silent, force=force)
@ -305,9 +316,12 @@ class PlatformPackagesMixin(object):
result.append(_name) result.append(_name)
found = True found = True
if (self.frameworks and candidate.startswith("framework-") if (
and candidate[10:] in self.frameworks): self.frameworks
result.append(self.frameworks[candidate[10:]]['package']) and candidate.startswith("framework-")
and candidate[10:] in self.frameworks
):
result.append(self.frameworks[candidate[10:]]["package"])
found = True found = True
if not found: if not found:
@ -320,7 +334,7 @@ class PlatformPackagesMixin(object):
requirements = self.packages[name].get("version", "") requirements = self.packages[name].get("version", "")
if ":" in requirements: if ":" in requirements:
_, requirements, __ = self.pm.parse_pkg_uri(requirements) _, requirements, __ = self.pm.parse_pkg_uri(requirements)
self.pm.update(manifest['__pkg_dir'], requirements, only_check) self.pm.update(manifest["__pkg_dir"], requirements, only_check)
def get_installed_packages(self): def get_installed_packages(self):
items = {} items = {}
@ -335,7 +349,7 @@ class PlatformPackagesMixin(object):
requirements = self.packages[name].get("version", "") requirements = self.packages[name].get("version", "")
if ":" in requirements: if ":" in requirements:
_, requirements, __ = self.pm.parse_pkg_uri(requirements) _, requirements, __ = self.pm.parse_pkg_uri(requirements)
if self.pm.outdated(manifest['__pkg_dir'], requirements): if self.pm.outdated(manifest["__pkg_dir"], requirements):
return True return True
return False return False
@ -343,7 +357,8 @@ class PlatformPackagesMixin(object):
version = self.packages[name].get("version", "") version = self.packages[name].get("version", "")
if ":" in version: if ":" in version:
return self.pm.get_package_dir( return self.pm.get_package_dir(
*self.pm.parse_pkg_uri("%s=%s" % (name, version))) *self.pm.parse_pkg_uri("%s=%s" % (name, version))
)
return self.pm.get_package_dir(name, version) return self.pm.get_package_dir(name, version)
def get_package_version(self, name): def get_package_version(self, name):
@ -368,15 +383,16 @@ class PlatformRunMixin(object):
return value.decode() if is_bytes(value) else value return value.decode() if is_bytes(value) else value
def run( # pylint: disable=too-many-arguments def run( # pylint: disable=too-many-arguments
self, variables, targets, silent, verbose, jobs): self, variables, targets, silent, verbose, jobs
):
assert isinstance(variables, dict) assert isinstance(variables, dict)
assert isinstance(targets, list) assert isinstance(targets, list)
config = ProjectConfig.get_instance(variables['project_config']) config = ProjectConfig.get_instance(variables["project_config"])
options = config.items(env=variables['pioenv'], as_dict=True) options = config.items(env=variables["pioenv"], as_dict=True)
if "framework" in options: if "framework" in options:
# support PIO Core 3.0 dev/platforms # support PIO Core 3.0 dev/platforms
options['pioframework'] = options['framework'] options["pioframework"] = options["framework"]
self.configure_default_packages(options, targets) self.configure_default_packages(options, targets)
self.install_packages(silent=True) self.install_packages(silent=True)
@ -386,12 +402,12 @@ class PlatformRunMixin(object):
if "clean" in targets: if "clean" in targets:
targets = ["-c", "."] targets = ["-c", "."]
variables['platform_manifest'] = self.manifest_path variables["platform_manifest"] = self.manifest_path
if "build_script" not in variables: if "build_script" not in variables:
variables['build_script'] = self.get_build_script() variables["build_script"] = self.get_build_script()
if not isfile(variables['build_script']): if not isfile(variables["build_script"]):
raise exception.BuildScriptNotFound(variables['build_script']) raise exception.BuildScriptNotFound(variables["build_script"])
result = self._run_scons(variables, targets, jobs) result = self._run_scons(variables, targets, jobs)
assert "returncode" in result assert "returncode" in result
@ -402,14 +418,16 @@ class PlatformRunMixin(object):
args = [ args = [
get_pythonexe_path(), get_pythonexe_path(),
join(get_core_package_dir("tool-scons"), "script", "scons"), join(get_core_package_dir("tool-scons"), "script", "scons"),
"-Q", "--warn=no-no-parallel-support", "-Q",
"--jobs", str(jobs), "--warn=no-no-parallel-support",
"--sconstruct", join(fs.get_source_dir(), "builder", "main.py") "--jobs",
str(jobs),
"--sconstruct",
join(fs.get_source_dir(), "builder", "main.py"),
] # yapf: disable ] # yapf: disable
args.append("PIOVERBOSE=%d" % (1 if self.verbose else 0)) args.append("PIOVERBOSE=%d" % (1 if self.verbose else 0))
# pylint: disable=protected-access # pylint: disable=protected-access
args.append("ISATTY=%d" % args.append("ISATTY=%d" % (1 if click._compat.isatty(sys.stdout) else 0))
(1 if click._compat.isatty(sys.stdout) else 0))
args += targets args += targets
# encode and append variables # encode and append variables
@ -428,10 +446,13 @@ class PlatformRunMixin(object):
args, args,
stdout=BuildAsyncPipe( stdout=BuildAsyncPipe(
line_callback=self._on_stdout_line, line_callback=self._on_stdout_line,
data_callback=lambda data: _write_and_flush(sys.stdout, data)), data_callback=lambda data: _write_and_flush(sys.stdout, data),
),
stderr=BuildAsyncPipe( stderr=BuildAsyncPipe(
line_callback=self._on_stderr_line, line_callback=self._on_stderr_line,
data_callback=lambda data: _write_and_flush(sys.stderr, data))) data_callback=lambda data: _write_and_flush(sys.stderr, data),
),
)
return result return result
def _on_stdout_line(self, line): def _on_stdout_line(self, line):
@ -447,7 +468,7 @@ class PlatformRunMixin(object):
b_pos = line.rfind(": No such file or directory") b_pos = line.rfind(": No such file or directory")
if a_pos == -1 or b_pos == -1: if a_pos == -1 or b_pos == -1:
return return
self._echo_missed_dependency(line[a_pos + 12:b_pos].strip()) self._echo_missed_dependency(line[a_pos + 12 : b_pos].strip())
def _echo_line(self, line, level): def _echo_line(self, line, level):
if line.startswith("scons: "): if line.startswith("scons: "):
@ -472,18 +493,22 @@ class PlatformRunMixin(object):
* Web > {link} * Web > {link}
* *
{dots} {dots}
""".format(filename=filename, """.format(
filename_styled=click.style(filename, fg="cyan"), filename=filename,
link=click.style( filename_styled=click.style(filename, fg="cyan"),
"https://platformio.org/lib/search?query=header:%s" % link=click.style(
quote(filename, safe=""), "https://platformio.org/lib/search?query=header:%s"
fg="blue"), % quote(filename, safe=""),
dots="*" * (56 + len(filename))) fg="blue",
),
dots="*" * (56 + len(filename)),
)
click.echo(banner, err=True) click.echo(banner, err=True)
class PlatformBase( # pylint: disable=too-many-public-methods class PlatformBase( # pylint: disable=too-many-public-methods
PlatformPackagesMixin, PlatformRunMixin): PlatformPackagesMixin, PlatformRunMixin
):
PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__)) PIO_VERSION = semantic_version.Version(util.pepver_to_semver(__version__))
_BOARDS_CACHE = {} _BOARDS_CACHE = {}
@ -497,8 +522,7 @@ class PlatformBase( # pylint: disable=too-many-public-methods
self._manifest = fs.load_json(manifest_path) self._manifest = fs.load_json(manifest_path)
self._custom_packages = None self._custom_packages = None
self.pm = PackageManager(get_project_packages_dir(), self.pm = PackageManager(get_project_packages_dir(), self.package_repositories)
self.package_repositories)
# if self.engines and "platformio" in self.engines: # if self.engines and "platformio" in self.engines:
# if self.PIO_VERSION not in semantic_version.SimpleSpec( # if self.PIO_VERSION not in semantic_version.SimpleSpec(
@ -508,19 +532,19 @@ class PlatformBase( # pylint: disable=too-many-public-methods
@property @property
def name(self): def name(self):
return self._manifest['name'] return self._manifest["name"]
@property @property
def title(self): def title(self):
return self._manifest['title'] return self._manifest["title"]
@property @property
def description(self): def description(self):
return self._manifest['description'] return self._manifest["description"]
@property @property
def version(self): def version(self):
return self._manifest['version'] return self._manifest["version"]
@property @property
def homepage(self): def homepage(self):
@ -561,7 +585,7 @@ class PlatformBase( # pylint: disable=too-many-public-methods
@property @property
def packages(self): def packages(self):
packages = self._manifest.get("packages", {}) packages = self._manifest.get("packages", {})
for item in (self._custom_packages or []): for item in self._custom_packages or []:
name = item name = item
version = "*" version = "*"
if "@" in item: if "@" in item:
@ -569,10 +593,7 @@ class PlatformBase( # pylint: disable=too-many-public-methods
name = name.strip() name = name.strip()
if name not in packages: if name not in packages:
packages[name] = {} packages[name] = {}
packages[name].update({ packages[name].update({"version": version.strip(), "optional": False})
"version": version.strip(),
"optional": False
})
return packages return packages
def get_dir(self): def get_dir(self):
@ -591,15 +612,13 @@ class PlatformBase( # pylint: disable=too-many-public-methods
return False return False
def get_boards(self, id_=None): def get_boards(self, id_=None):
def _append_board(board_id, manifest_path): def _append_board(board_id, manifest_path):
config = PlatformBoardConfig(manifest_path) config = PlatformBoardConfig(manifest_path)
if "platform" in config and config.get("platform") != self.name: if "platform" in config and config.get("platform") != self.name:
return return
if "platforms" in config \ if "platforms" in config and self.name not in config.get("platforms"):
and self.name not in config.get("platforms"):
return return
config.manifest['platform'] = self.name config.manifest["platform"] = self.name
self._BOARDS_CACHE[board_id] = config self._BOARDS_CACHE[board_id] = config
bdirs = [ bdirs = [
@ -649,28 +668,28 @@ class PlatformBase( # pylint: disable=too-many-public-methods
continue continue
_pkg_name = self.frameworks[framework].get("package") _pkg_name = self.frameworks[framework].get("package")
if _pkg_name: if _pkg_name:
self.packages[_pkg_name]['optional'] = False self.packages[_pkg_name]["optional"] = False
# enable upload tools for upload targets # enable upload tools for upload targets
if any(["upload" in t for t in targets] + ["program" in targets]): if any(["upload" in t for t in targets] + ["program" in targets]):
for name, opts in self.packages.items(): for name, opts in self.packages.items():
if opts.get("type") == "uploader": if opts.get("type") == "uploader":
self.packages[name]['optional'] = False self.packages[name]["optional"] = False
# skip all packages in "nobuild" mode # skip all packages in "nobuild" mode
# allow only upload tools and frameworks # allow only upload tools and frameworks
elif "nobuild" in targets and opts.get("type") != "framework": elif "nobuild" in targets and opts.get("type") != "framework":
self.packages[name]['optional'] = True self.packages[name]["optional"] = True
def get_lib_storages(self): def get_lib_storages(self):
storages = [] storages = []
for opts in (self.frameworks or {}).values(): for opts in (self.frameworks or {}).values():
if "package" not in opts: if "package" not in opts:
continue continue
pkg_dir = self.get_package_dir(opts['package']) pkg_dir = self.get_package_dir(opts["package"])
if not pkg_dir or not isdir(join(pkg_dir, "libraries")): if not pkg_dir or not isdir(join(pkg_dir, "libraries")):
continue continue
libs_dir = join(pkg_dir, "libraries") libs_dir = join(pkg_dir, "libraries")
storages.append({"name": opts['package'], "path": libs_dir}) storages.append({"name": opts["package"], "path": libs_dir})
libcores_dir = join(libs_dir, "__cores__") libcores_dir = join(libs_dir, "__cores__")
if not isdir(libcores_dir): if not isdir(libcores_dir):
continue continue
@ -678,16 +697,17 @@ class PlatformBase( # pylint: disable=too-many-public-methods
libcore_dir = join(libcores_dir, item) libcore_dir = join(libcores_dir, item)
if not isdir(libcore_dir): if not isdir(libcore_dir):
continue continue
storages.append({ storages.append(
"name": "%s-core-%s" % (opts['package'], item), {
"path": libcore_dir "name": "%s-core-%s" % (opts["package"], item),
}) "path": libcore_dir,
}
)
return storages return storages
class PlatformBoardConfig(object): class PlatformBoardConfig(object):
def __init__(self, manifest_path): def __init__(self, manifest_path):
self._id = basename(manifest_path)[:-5] self._id = basename(manifest_path)[:-5]
assert isfile(manifest_path) assert isfile(manifest_path)
@ -698,8 +718,8 @@ class PlatformBoardConfig(object):
raise exception.InvalidBoardManifest(manifest_path) raise exception.InvalidBoardManifest(manifest_path)
if not set(["name", "url", "vendor"]) <= set(self._manifest): if not set(["name", "url", "vendor"]) <= set(self._manifest):
raise exception.PlatformioException( raise exception.PlatformioException(
"Please specify name, url and vendor fields for " + "Please specify name, url and vendor fields for " + manifest_path
manifest_path) )
def get(self, path, default=None): def get(self, path, default=None):
try: try:
@ -751,41 +771,33 @@ class PlatformBoardConfig(object):
def get_brief_data(self): def get_brief_data(self):
return { return {
"id": "id": self.id,
self.id, "name": self._manifest["name"],
"name": "platform": self._manifest.get("platform"),
self._manifest['name'], "mcu": self._manifest.get("build", {}).get("mcu", "").upper(),
"platform": "fcpu": int(
self._manifest.get("platform"), "".join(
"mcu": [
self._manifest.get("build", {}).get("mcu", "").upper(), c
"fcpu": for c in str(self._manifest.get("build", {}).get("f_cpu", "0L"))
int("".join([ if c.isdigit()
c for c in str( ]
self._manifest.get("build", {}).get("f_cpu", "0L")) )
if c.isdigit() ),
])), "ram": self._manifest.get("upload", {}).get("maximum_ram_size", 0),
"ram": "rom": self._manifest.get("upload", {}).get("maximum_size", 0),
self._manifest.get("upload", {}).get("maximum_ram_size", 0), "connectivity": self._manifest.get("connectivity"),
"rom": "frameworks": self._manifest.get("frameworks"),
self._manifest.get("upload", {}).get("maximum_size", 0), "debug": self.get_debug_data(),
"connectivity": "vendor": self._manifest["vendor"],
self._manifest.get("connectivity"), "url": self._manifest["url"],
"frameworks":
self._manifest.get("frameworks"),
"debug":
self.get_debug_data(),
"vendor":
self._manifest['vendor'],
"url":
self._manifest['url']
} }
def get_debug_data(self): def get_debug_data(self):
if not self._manifest.get("debug", {}).get("tools"): if not self._manifest.get("debug", {}).get("tools"):
return None return None
tools = {} tools = {}
for name, options in self._manifest['debug']['tools'].items(): for name, options in self._manifest["debug"]["tools"].items():
tools[name] = {} tools[name] = {}
for key, value in options.items(): for key, value in options.items():
if key in ("default", "onboard"): if key in ("default", "onboard"):
@ -798,22 +810,23 @@ class PlatformBoardConfig(object):
if tool_name == "custom": if tool_name == "custom":
return tool_name return tool_name
if not debug_tools: if not debug_tools:
raise exception.DebugSupportError(self._manifest['name']) raise exception.DebugSupportError(self._manifest["name"])
if tool_name: if tool_name:
if tool_name in debug_tools: if tool_name in debug_tools:
return tool_name return tool_name
raise exception.DebugInvalidOptions( raise exception.DebugInvalidOptions(
"Unknown debug tool `%s`. Please use one of `%s` or `custom`" % "Unknown debug tool `%s`. Please use one of `%s` or `custom`"
(tool_name, ", ".join(sorted(list(debug_tools))))) % (tool_name, ", ".join(sorted(list(debug_tools))))
)
# automatically select best tool # automatically select best tool
data = {"default": [], "onboard": [], "external": []} data = {"default": [], "onboard": [], "external": []}
for key, value in debug_tools.items(): for key, value in debug_tools.items():
if value.get("default"): if value.get("default"):
data['default'].append(key) data["default"].append(key)
elif value.get("onboard"): elif value.get("onboard"):
data['onboard'].append(key) data["onboard"].append(key)
data['external'].append(key) data["external"].append(key)
for key, value in data.items(): for key, value in data.items():
if not value: if not value:

View File

@ -23,7 +23,6 @@ from platformio.compat import WINDOWS, string_types
class AsyncPipeBase(object): class AsyncPipeBase(object):
def __init__(self): def __init__(self):
self._fd_read, self._fd_write = os.pipe() self._fd_read, self._fd_write = os.pipe()
self._pipe_reader = os.fdopen(self._fd_read) self._pipe_reader = os.fdopen(self._fd_read)
@ -53,7 +52,6 @@ class AsyncPipeBase(object):
class BuildAsyncPipe(AsyncPipeBase): class BuildAsyncPipe(AsyncPipeBase):
def __init__(self, line_callback, data_callback): def __init__(self, line_callback, data_callback):
self.line_callback = line_callback self.line_callback = line_callback
self.data_callback = data_callback self.data_callback = data_callback
@ -88,7 +86,6 @@ class BuildAsyncPipe(AsyncPipeBase):
class LineBufferedAsyncPipe(AsyncPipeBase): class LineBufferedAsyncPipe(AsyncPipeBase):
def __init__(self, line_callback): def __init__(self, line_callback):
self.line_callback = line_callback self.line_callback = line_callback
super(LineBufferedAsyncPipe, self).__init__() super(LineBufferedAsyncPipe, self).__init__()
@ -109,8 +106,8 @@ def exec_command(*args, **kwargs):
p = subprocess.Popen(*args, **kwargs) p = subprocess.Popen(*args, **kwargs)
try: try:
result['out'], result['err'] = p.communicate() result["out"], result["err"] = p.communicate()
result['returncode'] = p.returncode result["returncode"] = p.returncode
except KeyboardInterrupt: except KeyboardInterrupt:
raise exception.AbortedByUser() raise exception.AbortedByUser()
finally: finally:
@ -160,24 +157,22 @@ def copy_pythonpath_to_osenv():
for p in os.sys.path: for p in os.sys.path:
conditions = [p not in _PYTHONPATH] conditions = [p not in _PYTHONPATH]
if not WINDOWS: if not WINDOWS:
conditions.append( conditions.append(isdir(join(p, "click")) or isdir(join(p, "platformio")))
isdir(join(p, "click")) or isdir(join(p, "platformio")))
if all(conditions): if all(conditions):
_PYTHONPATH.append(p) _PYTHONPATH.append(p)
os.environ['PYTHONPATH'] = os.pathsep.join(_PYTHONPATH) os.environ["PYTHONPATH"] = os.pathsep.join(_PYTHONPATH)
def where_is_program(program, envpath=None): def where_is_program(program, envpath=None):
env = os.environ env = os.environ
if envpath: if envpath:
env['PATH'] = envpath env["PATH"] = envpath
# try OS's built-in commands # try OS's built-in commands
try: try:
result = exec_command(["where" if WINDOWS else "which", program], result = exec_command(["where" if WINDOWS else "which", program], env=env)
env=env) if result["returncode"] == 0 and isfile(result["out"].strip()):
if result['returncode'] == 0 and isfile(result['out'].strip()): return result["out"].strip()
return result['out'].strip()
except OSError: except OSError:
pass pass

View File

@ -119,23 +119,26 @@ class ProjectConfig(object):
def _maintain_renaimed_options(self): def _maintain_renaimed_options(self):
# legacy `lib_extra_dirs` in [platformio] # legacy `lib_extra_dirs` in [platformio]
if (self._parser.has_section("platformio") if self._parser.has_section("platformio") and self._parser.has_option(
and self._parser.has_option("platformio", "lib_extra_dirs")): "platformio", "lib_extra_dirs"
):
if not self._parser.has_section("env"): if not self._parser.has_section("env"):
self._parser.add_section("env") self._parser.add_section("env")
self._parser.set("env", "lib_extra_dirs", self._parser.set(
self._parser.get("platformio", "lib_extra_dirs")) "env",
"lib_extra_dirs",
self._parser.get("platformio", "lib_extra_dirs"),
)
self._parser.remove_option("platformio", "lib_extra_dirs") self._parser.remove_option("platformio", "lib_extra_dirs")
self.warnings.append( self.warnings.append(
"`lib_extra_dirs` configuration option is deprecated in " "`lib_extra_dirs` configuration option is deprecated in "
"section [platformio]! Please move it to global `env` section") "section [platformio]! Please move it to global `env` section"
)
renamed_options = {} renamed_options = {}
for option in ProjectOptions.values(): for option in ProjectOptions.values():
if option.oldnames: if option.oldnames:
renamed_options.update( renamed_options.update({name: option.name for name in option.oldnames})
{name: option.name
for name in option.oldnames})
for section in self._parser.sections(): for section in self._parser.sections():
scope = section.split(":", 1)[0] scope = section.split(":", 1)[0]
@ -146,29 +149,34 @@ class ProjectConfig(object):
self.warnings.append( self.warnings.append(
"`%s` configuration option in section [%s] is " "`%s` configuration option in section [%s] is "
"deprecated and will be removed in the next release! " "deprecated and will be removed in the next release! "
"Please use `%s` instead" % "Please use `%s` instead"
(option, section, renamed_options[option])) % (option, section, renamed_options[option])
)
# rename on-the-fly # rename on-the-fly
self._parser.set(section, renamed_options[option], self._parser.set(
self._parser.get(section, option)) section,
renamed_options[option],
self._parser.get(section, option),
)
self._parser.remove_option(section, option) self._parser.remove_option(section, option)
continue continue
# unknown # unknown
unknown_conditions = [ unknown_conditions = [
("%s.%s" % (scope, option)) not in ProjectOptions, ("%s.%s" % (scope, option)) not in ProjectOptions,
scope != "env" or scope != "env" or not option.startswith(("custom_", "board_")),
not option.startswith(("custom_", "board_"))
] # yapf: disable ] # yapf: disable
if all(unknown_conditions): if all(unknown_conditions):
self.warnings.append( self.warnings.append(
"Ignore unknown configuration option `%s` " "Ignore unknown configuration option `%s` "
"in section [%s]" % (option, section)) "in section [%s]" % (option, section)
)
return True return True
def walk_options(self, root_section): def walk_options(self, root_section):
extends_queue = (["env", root_section] if extends_queue = (
root_section.startswith("env:") else [root_section]) ["env", root_section] if root_section.startswith("env:") else [root_section]
)
extends_done = [] extends_done = []
while extends_queue: while extends_queue:
section = extends_queue.pop() section = extends_queue.pop()
@ -179,8 +187,8 @@ class ProjectConfig(object):
yield (section, option) yield (section, option)
if self._parser.has_option(section, "extends"): if self._parser.has_option(section, "extends"):
extends_queue.extend( extends_queue.extend(
self.parse_multi_values( self.parse_multi_values(self._parser.get(section, "extends"))[::-1]
self._parser.get(section, "extends"))[::-1]) )
def options(self, section=None, env=None): def options(self, section=None, env=None):
result = [] result = []
@ -213,11 +221,9 @@ class ProjectConfig(object):
section = "env:" + env section = "env:" + env
if as_dict: if as_dict:
return { return {
option: self.get(section, option) option: self.get(section, option) for option in self.options(section)
for option in self.options(section)
} }
return [(option, self.get(section, option)) return [(option, self.get(section, option)) for option in self.options(section)]
for option in self.options(section)]
def set(self, section, option, value): def set(self, section, option, value):
if isinstance(value, (list, tuple)): if isinstance(value, (list, tuple)):
@ -260,8 +266,7 @@ class ProjectConfig(object):
except ConfigParser.Error as e: except ConfigParser.Error as e:
raise exception.InvalidProjectConf(self.path, str(e)) raise exception.InvalidProjectConf(self.path, str(e))
option_meta = ProjectOptions.get("%s.%s" % option_meta = ProjectOptions.get("%s.%s" % (section.split(":", 1)[0], option))
(section.split(":", 1)[0], option))
if not option_meta: if not option_meta:
return value or default return value or default
@ -288,8 +293,7 @@ class ProjectConfig(object):
try: try:
return self._cast_to(value, option_meta.type) return self._cast_to(value, option_meta.type)
except click.BadParameter as e: except click.BadParameter as e:
raise exception.ProjectOptionValueError(e.format_message(), option, raise exception.ProjectOptionValueError(e.format_message(), option, section)
section)
@staticmethod @staticmethod
def _cast_to(value, to_type): def _cast_to(value, to_type):
@ -317,8 +321,7 @@ class ProjectConfig(object):
raise exception.ProjectEnvsNotAvailable() raise exception.ProjectEnvsNotAvailable()
unknown = set(list(envs or []) + self.default_envs()) - known unknown = set(list(envs or []) + self.default_envs()) - known
if unknown: if unknown:
raise exception.UnknownEnvNames(", ".join(unknown), raise exception.UnknownEnvNames(", ".join(unknown), ", ".join(known))
", ".join(known))
if not silent: if not silent:
for warning in self.warnings: for warning in self.warnings:
click.secho("Warning! %s" % warning, fg="yellow") click.secho("Warning! %s" % warning, fg="yellow")

View File

@ -16,8 +16,16 @@ import json
import os import os
from hashlib import sha1 from hashlib import sha1
from os import walk from os import walk
from os.path import (basename, dirname, expanduser, isdir, isfile, join, from os.path import (
realpath, splitdrive) basename,
dirname,
expanduser,
isdir,
isfile,
join,
realpath,
splitdrive,
)
from click.testing import CliRunner from click.testing import CliRunner
@ -56,9 +64,13 @@ def get_project_optional_dir(name, default=None):
if "$PROJECT_HASH" in optional_dir: if "$PROJECT_HASH" in optional_dir:
optional_dir = optional_dir.replace( optional_dir = optional_dir.replace(
"$PROJECT_HASH", "%s-%s" % "$PROJECT_HASH",
(basename(project_dir), sha1( "%s-%s"
hashlib_encode_data(project_dir)).hexdigest()[:10])) % (
basename(project_dir),
sha1(hashlib_encode_data(project_dir)).hexdigest()[:10],
),
)
if optional_dir.startswith("~"): if optional_dir.startswith("~"):
optional_dir = expanduser(optional_dir) optional_dir = expanduser(optional_dir)
@ -69,7 +81,8 @@ def get_project_optional_dir(name, default=None):
def get_project_core_dir(): def get_project_core_dir():
default = join(expanduser("~"), ".platformio") default = join(expanduser("~"), ".platformio")
core_dir = get_project_optional_dir( core_dir = get_project_optional_dir(
"core_dir", get_project_optional_dir("home_dir", default)) "core_dir", get_project_optional_dir("home_dir", default)
)
win_core_dir = None win_core_dir = None
if WINDOWS and core_dir == default: if WINDOWS and core_dir == default:
win_core_dir = splitdrive(core_dir)[0] + "\\.platformio" win_core_dir = splitdrive(core_dir)[0] + "\\.platformio"
@ -91,33 +104,35 @@ def get_project_core_dir():
def get_project_global_lib_dir(): def get_project_global_lib_dir():
return get_project_optional_dir("globallib_dir", return get_project_optional_dir(
join(get_project_core_dir(), "lib")) "globallib_dir", join(get_project_core_dir(), "lib")
)
def get_project_platforms_dir(): def get_project_platforms_dir():
return get_project_optional_dir("platforms_dir", return get_project_optional_dir(
join(get_project_core_dir(), "platforms")) "platforms_dir", join(get_project_core_dir(), "platforms")
)
def get_project_packages_dir(): def get_project_packages_dir():
return get_project_optional_dir("packages_dir", return get_project_optional_dir(
join(get_project_core_dir(), "packages")) "packages_dir", join(get_project_core_dir(), "packages")
)
def get_project_cache_dir(): def get_project_cache_dir():
return get_project_optional_dir("cache_dir", return get_project_optional_dir("cache_dir", join(get_project_core_dir(), ".cache"))
join(get_project_core_dir(), ".cache"))
def get_project_workspace_dir(): def get_project_workspace_dir():
return get_project_optional_dir("workspace_dir", return get_project_optional_dir("workspace_dir", join(get_project_dir(), ".pio"))
join(get_project_dir(), ".pio"))
def get_project_build_dir(force=False): def get_project_build_dir(force=False):
path = get_project_optional_dir("build_dir", path = get_project_optional_dir(
join(get_project_workspace_dir(), "build")) "build_dir", join(get_project_workspace_dir(), "build")
)
try: try:
if not isdir(path): if not isdir(path):
os.makedirs(path) os.makedirs(path)
@ -129,7 +144,8 @@ def get_project_build_dir(force=False):
def get_project_libdeps_dir(): def get_project_libdeps_dir():
return get_project_optional_dir( return get_project_optional_dir(
"libdeps_dir", join(get_project_workspace_dir(), "libdeps")) "libdeps_dir", join(get_project_workspace_dir(), "libdeps")
)
def get_project_lib_dir(): def get_project_lib_dir():
@ -137,8 +153,7 @@ def get_project_lib_dir():
def get_project_include_dir(): def get_project_include_dir():
return get_project_optional_dir("include_dir", return get_project_optional_dir("include_dir", join(get_project_dir(), "include"))
join(get_project_dir(), "include"))
def get_project_src_dir(): def get_project_src_dir():
@ -146,23 +161,19 @@ def get_project_src_dir():
def get_project_test_dir(): def get_project_test_dir():
return get_project_optional_dir("test_dir", join(get_project_dir(), return get_project_optional_dir("test_dir", join(get_project_dir(), "test"))
"test"))
def get_project_boards_dir(): def get_project_boards_dir():
return get_project_optional_dir("boards_dir", return get_project_optional_dir("boards_dir", join(get_project_dir(), "boards"))
join(get_project_dir(), "boards"))
def get_project_data_dir(): def get_project_data_dir():
return get_project_optional_dir("data_dir", join(get_project_dir(), return get_project_optional_dir("data_dir", join(get_project_dir(), "data"))
"data"))
def get_project_shared_dir(): def get_project_shared_dir():
return get_project_optional_dir("shared_dir", return get_project_optional_dir("shared_dir", join(get_project_dir(), "shared"))
join(get_project_dir(), "shared"))
def compute_project_checksum(config): def compute_project_checksum(config):
@ -174,8 +185,7 @@ def compute_project_checksum(config):
# project file structure # project file structure
check_suffixes = (".c", ".cc", ".cpp", ".h", ".hpp", ".s", ".S") check_suffixes = (".c", ".cc", ".cpp", ".h", ".hpp", ".s", ".S")
for d in (get_project_include_dir(), get_project_src_dir(), for d in (get_project_include_dir(), get_project_src_dir(), get_project_lib_dir()):
get_project_lib_dir()):
if not isdir(d): if not isdir(d):
continue continue
chunks = [] chunks = []
@ -196,6 +206,7 @@ def compute_project_checksum(config):
def load_project_ide_data(project_dir, env_or_envs): def load_project_ide_data(project_dir, env_or_envs):
from platformio.commands.run import cli as cmd_run from platformio.commands.run import cli as cmd_run
assert env_or_envs assert env_or_envs
envs = env_or_envs envs = env_or_envs
if not isinstance(envs, list): if not isinstance(envs, list):
@ -204,8 +215,9 @@ def load_project_ide_data(project_dir, env_or_envs):
for env in envs: for env in envs:
args.extend(["-e", env]) args.extend(["-e", env])
result = CliRunner().invoke(cmd_run, args) result = CliRunner().invoke(cmd_run, args)
if result.exit_code != 0 and not isinstance(result.exception, if result.exit_code != 0 and not isinstance(
exception.ReturnErrorCode): result.exception, exception.ReturnErrorCode
):
raise result.exception raise result.exception
if '"includes":' not in result.output: if '"includes":' not in result.output:
raise exception.PlatformioException(result.output) raise exception.PlatformioException(result.output)
@ -213,11 +225,10 @@ def load_project_ide_data(project_dir, env_or_envs):
data = {} data = {}
for line in result.output.split("\n"): for line in result.output.split("\n"):
line = line.strip() line = line.strip()
if (line.startswith('{"') and line.endswith("}") if line.startswith('{"') and line.endswith("}") and "env_name" in line:
and "env_name" in line):
_data = json.loads(line) _data = json.loads(line)
if "env_name" in _data: if "env_name" in _data:
data[_data['env_name']] = _data data[_data["env_name"]] = _data
if not isinstance(env_or_envs, list) and env_or_envs in data: if not isinstance(env_or_envs, list) and env_or_envs in data:
return data[env_or_envs] return data[env_or_envs]
return data or None return data or None

View File

@ -18,20 +18,24 @@ from collections import OrderedDict, namedtuple
import click import click
ConfigOptionClass = namedtuple("ConfigOption", [ ConfigOptionClass = namedtuple(
"scope", "name", "type", "multiple", "sysenvvar", "buildenvvar", "oldnames" "ConfigOption",
]) ["scope", "name", "type", "multiple", "sysenvvar", "buildenvvar", "oldnames"],
)
def ConfigOption(scope, def ConfigOption(
name, scope,
type=str, name,
multiple=False, type=str,
sysenvvar=None, multiple=False,
buildenvvar=None, sysenvvar=None,
oldnames=None): buildenvvar=None,
return ConfigOptionClass(scope, name, type, multiple, sysenvvar, oldnames=None,
buildenvvar, oldnames) ):
return ConfigOptionClass(
scope, name, type, multiple, sysenvvar, buildenvvar, oldnames
)
def ConfigPlatformioOption(*args, **kwargs): def ConfigPlatformioOption(*args, **kwargs):
@ -42,171 +46,202 @@ def ConfigEnvOption(*args, **kwargs):
return ConfigOption("env", *args, **kwargs) return ConfigOption("env", *args, **kwargs)
ProjectOptions = OrderedDict([ ProjectOptions = OrderedDict(
("%s.%s" % (option.scope, option.name), option) for option in [ [
# ("%s.%s" % (option.scope, option.name), option)
# [platformio] for option in [
# #
ConfigPlatformioOption(name="description"), # [platformio]
ConfigPlatformioOption(name="default_envs", #
oldnames=["env_default"], ConfigPlatformioOption(name="description"),
multiple=True, ConfigPlatformioOption(
sysenvvar="PLATFORMIO_DEFAULT_ENVS"), name="default_envs",
ConfigPlatformioOption(name="extra_configs", multiple=True), oldnames=["env_default"],
multiple=True,
# Dirs sysenvvar="PLATFORMIO_DEFAULT_ENVS",
ConfigPlatformioOption(name="core_dir", ),
oldnames=["home_dir"], ConfigPlatformioOption(name="extra_configs", multiple=True),
sysenvvar="PLATFORMIO_CORE_DIR"), # Dirs
ConfigPlatformioOption(name="globallib_dir", ConfigPlatformioOption(
sysenvvar="PLATFORMIO_GLOBALLIB_DIR"), name="core_dir", oldnames=["home_dir"], sysenvvar="PLATFORMIO_CORE_DIR"
ConfigPlatformioOption(name="platforms_dir", ),
sysenvvar="PLATFORMIO_PLATFORMS_DIR"), ConfigPlatformioOption(
ConfigPlatformioOption(name="packages_dir", name="globallib_dir", sysenvvar="PLATFORMIO_GLOBALLIB_DIR"
sysenvvar="PLATFORMIO_PACKAGES_DIR"), ),
ConfigPlatformioOption(name="cache_dir", ConfigPlatformioOption(
sysenvvar="PLATFORMIO_CACHE_DIR"), name="platforms_dir", sysenvvar="PLATFORMIO_PLATFORMS_DIR"
ConfigPlatformioOption(name="build_cache_dir", ),
sysenvvar="PLATFORMIO_BUILD_CACHE_DIR"), ConfigPlatformioOption(
ConfigPlatformioOption(name="workspace_dir", name="packages_dir", sysenvvar="PLATFORMIO_PACKAGES_DIR"
sysenvvar="PLATFORMIO_WORKSPACE_DIR"), ),
ConfigPlatformioOption(name="build_dir", ConfigPlatformioOption(name="cache_dir", sysenvvar="PLATFORMIO_CACHE_DIR"),
sysenvvar="PLATFORMIO_BUILD_DIR"), ConfigPlatformioOption(
ConfigPlatformioOption(name="libdeps_dir", name="build_cache_dir", sysenvvar="PLATFORMIO_BUILD_CACHE_DIR"
sysenvvar="PLATFORMIO_LIBDEPS_DIR"), ),
ConfigPlatformioOption(name="lib_dir", sysenvvar="PLATFORMIO_LIB_DIR"), ConfigPlatformioOption(
ConfigPlatformioOption(name="include_dir", name="workspace_dir", sysenvvar="PLATFORMIO_WORKSPACE_DIR"
sysenvvar="PLATFORMIO_INCLUDE_DIR"), ),
ConfigPlatformioOption(name="src_dir", sysenvvar="PLATFORMIO_SRC_DIR"), ConfigPlatformioOption(name="build_dir", sysenvvar="PLATFORMIO_BUILD_DIR"),
ConfigPlatformioOption(name="test_dir", ConfigPlatformioOption(
sysenvvar="PLATFORMIO_TEST_DIR"), name="libdeps_dir", sysenvvar="PLATFORMIO_LIBDEPS_DIR"
ConfigPlatformioOption(name="boards_dir", ),
sysenvvar="PLATFORMIO_BOARDS_DIR"), ConfigPlatformioOption(name="lib_dir", sysenvvar="PLATFORMIO_LIB_DIR"),
ConfigPlatformioOption(name="data_dir", ConfigPlatformioOption(
sysenvvar="PLATFORMIO_DATA_DIR"), name="include_dir", sysenvvar="PLATFORMIO_INCLUDE_DIR"
ConfigPlatformioOption(name="shared_dir", ),
sysenvvar="PLATFORMIO_SHARED_DIR"), ConfigPlatformioOption(name="src_dir", sysenvvar="PLATFORMIO_SRC_DIR"),
ConfigPlatformioOption(name="test_dir", sysenvvar="PLATFORMIO_TEST_DIR"),
# ConfigPlatformioOption(
# [env] name="boards_dir", sysenvvar="PLATFORMIO_BOARDS_DIR"
# ),
ConfigEnvOption(name="extends", multiple=True), ConfigPlatformioOption(name="data_dir", sysenvvar="PLATFORMIO_DATA_DIR"),
ConfigPlatformioOption(
# Generic name="shared_dir", sysenvvar="PLATFORMIO_SHARED_DIR"
ConfigEnvOption(name="platform", buildenvvar="PIOPLATFORM"), ),
ConfigEnvOption(name="platform_packages", multiple=True), #
ConfigEnvOption( # [env]
name="framework", multiple=True, buildenvvar="PIOFRAMEWORK"), #
ConfigEnvOption(name="extends", multiple=True),
# Board # Generic
ConfigEnvOption(name="board", buildenvvar="BOARD"), ConfigEnvOption(name="platform", buildenvvar="PIOPLATFORM"),
ConfigEnvOption(name="board_build.mcu", ConfigEnvOption(name="platform_packages", multiple=True),
oldnames=["board_mcu"], ConfigEnvOption(
buildenvvar="BOARD_MCU"), name="framework", multiple=True, buildenvvar="PIOFRAMEWORK"
ConfigEnvOption(name="board_build.f_cpu", ),
oldnames=["board_f_cpu"], # Board
buildenvvar="BOARD_F_CPU"), ConfigEnvOption(name="board", buildenvvar="BOARD"),
ConfigEnvOption(name="board_build.f_flash", ConfigEnvOption(
oldnames=["board_f_flash"], name="board_build.mcu", oldnames=["board_mcu"], buildenvvar="BOARD_MCU"
buildenvvar="BOARD_F_FLASH"), ),
ConfigEnvOption(name="board_build.flash_mode", ConfigEnvOption(
oldnames=["board_flash_mode"], name="board_build.f_cpu",
buildenvvar="BOARD_FLASH_MODE"), oldnames=["board_f_cpu"],
buildenvvar="BOARD_F_CPU",
# Build ),
ConfigEnvOption(name="build_type", ConfigEnvOption(
type=click.Choice(["release", "debug"])), name="board_build.f_flash",
ConfigEnvOption(name="build_flags", oldnames=["board_f_flash"],
multiple=True, buildenvvar="BOARD_F_FLASH",
sysenvvar="PLATFORMIO_BUILD_FLAGS", ),
buildenvvar="BUILD_FLAGS"), ConfigEnvOption(
ConfigEnvOption(name="src_build_flags", name="board_build.flash_mode",
multiple=True, oldnames=["board_flash_mode"],
sysenvvar="PLATFORMIO_SRC_BUILD_FLAGS", buildenvvar="BOARD_FLASH_MODE",
buildenvvar="SRC_BUILD_FLAGS"), ),
ConfigEnvOption(name="build_unflags", # Build
multiple=True, ConfigEnvOption(name="build_type", type=click.Choice(["release", "debug"])),
sysenvvar="PLATFORMIO_BUILD_UNFLAGS", ConfigEnvOption(
buildenvvar="BUILD_UNFLAGS"), name="build_flags",
ConfigEnvOption(name="src_filter", multiple=True,
multiple=True, sysenvvar="PLATFORMIO_BUILD_FLAGS",
sysenvvar="PLATFORMIO_SRC_FILTER", buildenvvar="BUILD_FLAGS",
buildenvvar="SRC_FILTER"), ),
ConfigEnvOption(name="targets", multiple=True), ConfigEnvOption(
name="src_build_flags",
# Upload multiple=True,
ConfigEnvOption(name="upload_port", sysenvvar="PLATFORMIO_SRC_BUILD_FLAGS",
sysenvvar="PLATFORMIO_UPLOAD_PORT", buildenvvar="SRC_BUILD_FLAGS",
buildenvvar="UPLOAD_PORT"), ),
ConfigEnvOption(name="upload_protocol", buildenvvar="UPLOAD_PROTOCOL"), ConfigEnvOption(
ConfigEnvOption( name="build_unflags",
name="upload_speed", type=click.INT, buildenvvar="UPLOAD_SPEED"), multiple=True,
ConfigEnvOption(name="upload_flags", sysenvvar="PLATFORMIO_BUILD_UNFLAGS",
multiple=True, buildenvvar="BUILD_UNFLAGS",
sysenvvar="PLATFORMIO_UPLOAD_FLAGS", ),
buildenvvar="UPLOAD_FLAGS"), ConfigEnvOption(
ConfigEnvOption(name="upload_resetmethod", name="src_filter",
buildenvvar="UPLOAD_RESETMETHOD"), multiple=True,
ConfigEnvOption(name="upload_command", buildenvvar="UPLOADCMD"), sysenvvar="PLATFORMIO_SRC_FILTER",
buildenvvar="SRC_FILTER",
# Monitor ),
ConfigEnvOption(name="monitor_port"), ConfigEnvOption(name="targets", multiple=True),
ConfigEnvOption(name="monitor_speed", oldnames=["monitor_baud"]), # Upload
ConfigEnvOption(name="monitor_rts", type=click.IntRange(0, 1)), ConfigEnvOption(
ConfigEnvOption(name="monitor_dtr", type=click.IntRange(0, 1)), name="upload_port",
ConfigEnvOption(name="monitor_flags", multiple=True), sysenvvar="PLATFORMIO_UPLOAD_PORT",
buildenvvar="UPLOAD_PORT",
# Library ),
ConfigEnvOption(name="lib_deps", ConfigEnvOption(name="upload_protocol", buildenvvar="UPLOAD_PROTOCOL"),
oldnames=["lib_use", "lib_force", "lib_install"], ConfigEnvOption(
multiple=True), name="upload_speed", type=click.INT, buildenvvar="UPLOAD_SPEED"
ConfigEnvOption(name="lib_ignore", multiple=True), ),
ConfigEnvOption(name="lib_extra_dirs", ConfigEnvOption(
multiple=True, name="upload_flags",
sysenvvar="PLATFORMIO_LIB_EXTRA_DIRS"), multiple=True,
ConfigEnvOption(name="lib_ldf_mode", sysenvvar="PLATFORMIO_UPLOAD_FLAGS",
type=click.Choice( buildenvvar="UPLOAD_FLAGS",
["off", "chain", "deep", "chain+", "deep+"])), ),
ConfigEnvOption(name="lib_compat_mode", ConfigEnvOption(
type=click.Choice(["off", "soft", "strict"])), name="upload_resetmethod", buildenvvar="UPLOAD_RESETMETHOD"
ConfigEnvOption(name="lib_archive", type=click.BOOL), ),
ConfigEnvOption(name="upload_command", buildenvvar="UPLOADCMD"),
# Test # Monitor
ConfigEnvOption(name="test_filter", multiple=True), ConfigEnvOption(name="monitor_port"),
ConfigEnvOption(name="test_ignore", multiple=True), ConfigEnvOption(name="monitor_speed", oldnames=["monitor_baud"]),
ConfigEnvOption(name="test_port"), ConfigEnvOption(name="monitor_rts", type=click.IntRange(0, 1)),
ConfigEnvOption(name="test_speed", type=click.INT), ConfigEnvOption(name="monitor_dtr", type=click.IntRange(0, 1)),
ConfigEnvOption(name="test_transport"), ConfigEnvOption(name="monitor_flags", multiple=True),
ConfigEnvOption(name="test_build_project_src", type=click.BOOL), # Library
ConfigEnvOption(
# Debug name="lib_deps",
ConfigEnvOption(name="debug_tool"), oldnames=["lib_use", "lib_force", "lib_install"],
ConfigEnvOption(name="debug_init_break"), multiple=True,
ConfigEnvOption(name="debug_init_cmds", multiple=True), ),
ConfigEnvOption(name="debug_extra_cmds", multiple=True), ConfigEnvOption(name="lib_ignore", multiple=True),
ConfigEnvOption(name="debug_load_cmds", ConfigEnvOption(
oldnames=["debug_load_cmd"], name="lib_extra_dirs",
multiple=True), multiple=True,
ConfigEnvOption(name="debug_load_mode", sysenvvar="PLATFORMIO_LIB_EXTRA_DIRS",
type=click.Choice(["always", "modified", "manual"])), ),
ConfigEnvOption(name="debug_server", multiple=True), ConfigEnvOption(
ConfigEnvOption(name="debug_port"), name="lib_ldf_mode",
ConfigEnvOption(name="debug_svd_path", type=click.Choice(["off", "chain", "deep", "chain+", "deep+"]),
type=click.Path( ),
exists=True, file_okay=True, dir_okay=False)), ConfigEnvOption(
name="lib_compat_mode", type=click.Choice(["off", "soft", "strict"])
# Check ),
ConfigEnvOption(name="check_tool", multiple=True), ConfigEnvOption(name="lib_archive", type=click.BOOL),
ConfigEnvOption(name="check_filter", multiple=True), # Test
ConfigEnvOption(name="check_flags", multiple=True), ConfigEnvOption(name="test_filter", multiple=True),
ConfigEnvOption(name="check_severity", ConfigEnvOption(name="test_ignore", multiple=True),
multiple=True, ConfigEnvOption(name="test_port"),
type=click.Choice(["low", "medium", "high"])), ConfigEnvOption(name="test_speed", type=click.INT),
ConfigEnvOption(name="test_transport"),
# Other ConfigEnvOption(name="test_build_project_src", type=click.BOOL),
ConfigEnvOption(name="extra_scripts", # Debug
oldnames=["extra_script"], ConfigEnvOption(name="debug_tool"),
multiple=True, ConfigEnvOption(name="debug_init_break"),
sysenvvar="PLATFORMIO_EXTRA_SCRIPTS") ConfigEnvOption(name="debug_init_cmds", multiple=True),
ConfigEnvOption(name="debug_extra_cmds", multiple=True),
ConfigEnvOption(
name="debug_load_cmds", oldnames=["debug_load_cmd"], multiple=True
),
ConfigEnvOption(
name="debug_load_mode",
type=click.Choice(["always", "modified", "manual"]),
),
ConfigEnvOption(name="debug_server", multiple=True),
ConfigEnvOption(name="debug_port"),
ConfigEnvOption(
name="debug_svd_path",
type=click.Path(exists=True, file_okay=True, dir_okay=False),
),
# Check
ConfigEnvOption(name="check_tool", multiple=True),
ConfigEnvOption(name="check_filter", multiple=True),
ConfigEnvOption(name="check_flags", multiple=True),
ConfigEnvOption(
name="check_severity",
multiple=True,
type=click.Choice(["low", "medium", "high"]),
),
# Other
ConfigEnvOption(
name="extra_scripts",
oldnames=["extra_script"],
multiple=True,
sysenvvar="PLATFORMIO_EXTRA_SCRIPTS",
),
]
] ]
]) )

View File

@ -38,7 +38,6 @@ except ImportError:
class TelemetryBase(object): class TelemetryBase(object):
def __init__(self): def __init__(self):
self._params = {} self._params = {}
@ -64,17 +63,17 @@ class MeasurementProtocol(TelemetryBase):
"event_category": "ec", "event_category": "ec",
"event_action": "ea", "event_action": "ea",
"event_label": "el", "event_label": "el",
"event_value": "ev" "event_value": "ev",
} }
def __init__(self): def __init__(self):
super(MeasurementProtocol, self).__init__() super(MeasurementProtocol, self).__init__()
self['v'] = 1 self["v"] = 1
self['tid'] = self.TID self["tid"] = self.TID
self['cid'] = app.get_cid() self["cid"] = app.get_cid()
try: try:
self['sr'] = "%dx%d" % click.get_terminal_size() self["sr"] = "%dx%d" % click.get_terminal_size()
except ValueError: except ValueError:
pass pass
@ -93,7 +92,7 @@ class MeasurementProtocol(TelemetryBase):
super(MeasurementProtocol, self).__setitem__(name, value) super(MeasurementProtocol, self).__setitem__(name, value)
def _prefill_appinfo(self): def _prefill_appinfo(self):
self['av'] = __version__ self["av"] = __version__
# gather dependent packages # gather dependent packages
dpdata = [] dpdata = []
@ -102,10 +101,9 @@ class MeasurementProtocol(TelemetryBase):
dpdata.append("Caller/%s" % app.get_session_var("caller_id")) dpdata.append("Caller/%s" % app.get_session_var("caller_id"))
if getenv("PLATFORMIO_IDE"): if getenv("PLATFORMIO_IDE"):
dpdata.append("IDE/%s" % getenv("PLATFORMIO_IDE")) dpdata.append("IDE/%s" % getenv("PLATFORMIO_IDE"))
self['an'] = " ".join(dpdata) self["an"] = " ".join(dpdata)
def _prefill_custom_data(self): def _prefill_custom_data(self):
def _filter_args(items): def _filter_args(items):
result = [] result = []
stop = False stop = False
@ -119,17 +117,16 @@ class MeasurementProtocol(TelemetryBase):
return result return result
caller_id = str(app.get_session_var("caller_id")) caller_id = str(app.get_session_var("caller_id"))
self['cd1'] = util.get_systype() self["cd1"] = util.get_systype()
self['cd2'] = "Python/%s %s" % (platform.python_version(), self["cd2"] = "Python/%s %s" % (platform.python_version(), platform.platform())
platform.platform())
# self['cd3'] = " ".join(_filter_args(sys.argv[1:])) # self['cd3'] = " ".join(_filter_args(sys.argv[1:]))
self['cd4'] = 1 if (not util.is_ci() and self["cd4"] = (
(caller_id or not is_container())) else 0 1 if (not util.is_ci() and (caller_id or not is_container())) else 0
)
if caller_id: if caller_id:
self['cd5'] = caller_id.lower() self["cd5"] = caller_id.lower()
def _prefill_screen_name(self): def _prefill_screen_name(self):
def _first_arg_from_list(args_, list_): def _first_arg_from_list(args_, list_):
for _arg in args_: for _arg in args_:
if _arg in list_: if _arg in list_:
@ -146,12 +143,27 @@ class MeasurementProtocol(TelemetryBase):
return return
cmd_path = args[:1] cmd_path = args[:1]
if args[0] in ("platform", "platforms", "serialports", "device", if args[0] in (
"settings", "account"): "platform",
"platforms",
"serialports",
"device",
"settings",
"account",
):
cmd_path = args[:2] cmd_path = args[:2]
if args[0] == "lib" and len(args) > 1: if args[0] == "lib" and len(args) > 1:
lib_subcmds = ("builtin", "install", "list", "register", "search", lib_subcmds = (
"show", "stats", "uninstall", "update") "builtin",
"install",
"list",
"register",
"search",
"show",
"stats",
"uninstall",
"update",
)
sub_cmd = _first_arg_from_list(args[1:], lib_subcmds) sub_cmd = _first_arg_from_list(args[1:], lib_subcmds)
if sub_cmd: if sub_cmd:
cmd_path.append(sub_cmd) cmd_path.append(sub_cmd)
@ -165,24 +177,25 @@ class MeasurementProtocol(TelemetryBase):
sub_cmd = _first_arg_from_list(args[2:], remote2_subcmds) sub_cmd = _first_arg_from_list(args[2:], remote2_subcmds)
if sub_cmd: if sub_cmd:
cmd_path.append(sub_cmd) cmd_path.append(sub_cmd)
self['screen_name'] = " ".join([p.title() for p in cmd_path]) self["screen_name"] = " ".join([p.title() for p in cmd_path])
@staticmethod @staticmethod
def _ignore_hit(): def _ignore_hit():
if not app.get_setting("enable_telemetry"): if not app.get_setting("enable_telemetry"):
return True return True
if app.get_session_var("caller_id") and \ if app.get_session_var("caller_id") and all(
all(c in sys.argv for c in ("run", "idedata")): c in sys.argv for c in ("run", "idedata")
):
return True return True
return False return False
def send(self, hittype): def send(self, hittype):
if self._ignore_hit(): if self._ignore_hit():
return return
self['t'] = hittype self["t"] = hittype
# correct queue time # correct queue time
if "qt" in self._params and isinstance(self['qt'], float): if "qt" in self._params and isinstance(self["qt"], float):
self['qt'] = int((time() - self['qt']) * 1000) self["qt"] = int((time() - self["qt"]) * 1000)
MPDataPusher().push(self._params) MPDataPusher().push(self._params)
@ -202,7 +215,7 @@ class MPDataPusher(object):
# if network is off-line # if network is off-line
if self._http_offline: if self._http_offline:
if "qt" not in item: if "qt" not in item:
item['qt'] = time() item["qt"] = time()
self._failedque.append(item) self._failedque.append(item)
return return
@ -243,7 +256,7 @@ class MPDataPusher(object):
item = self._queue.get() item = self._queue.get()
_item = item.copy() _item = item.copy()
if "qt" not in _item: if "qt" not in _item:
_item['qt'] = time() _item["qt"] = time()
self._failedque.append(_item) self._failedque.append(_item)
if self._send_data(item): if self._send_data(item):
self._failedque.remove(_item) self._failedque.remove(_item)
@ -259,7 +272,8 @@ class MPDataPusher(object):
"https://ssl.google-analytics.com/collect", "https://ssl.google-analytics.com/collect",
data=data, data=data,
headers=util.get_request_defheaders(), headers=util.get_request_defheaders(),
timeout=1) timeout=1,
)
r.raise_for_status() r.raise_for_status()
return True return True
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
@ -284,11 +298,10 @@ def on_command():
def measure_ci(): def measure_ci():
event = {"category": "CI", "action": "NoName", "label": None} event = {"category": "CI", "action": "NoName", "label": None}
known_cis = ("TRAVIS", "APPVEYOR", "GITLAB_CI", "CIRCLECI", "SHIPPABLE", known_cis = ("TRAVIS", "APPVEYOR", "GITLAB_CI", "CIRCLECI", "SHIPPABLE", "DRONE")
"DRONE")
for name in known_cis: for name in known_cis:
if getenv(name, "false").lower() == "true": if getenv(name, "false").lower() == "true":
event['action'] = name event["action"] = name
break break
on_event(**event) on_event(**event)
@ -307,32 +320,37 @@ def on_run_environment(options, targets):
def on_event(category, action, label=None, value=None, screen_name=None): def on_event(category, action, label=None, value=None, screen_name=None):
mp = MeasurementProtocol() mp = MeasurementProtocol()
mp['event_category'] = category[:150] mp["event_category"] = category[:150]
mp['event_action'] = action[:500] mp["event_action"] = action[:500]
if label: if label:
mp['event_label'] = label[:500] mp["event_label"] = label[:500]
if value: if value:
mp['event_value'] = int(value) mp["event_value"] = int(value)
if screen_name: if screen_name:
mp['screen_name'] = screen_name[:2048] mp["screen_name"] = screen_name[:2048]
mp.send("event") mp.send("event")
def on_exception(e): def on_exception(e):
def _cleanup_description(text): def _cleanup_description(text):
text = text.replace("Traceback (most recent call last):", "") text = text.replace("Traceback (most recent call last):", "")
text = re.sub(r'File "([^"]+)"', text = re.sub(
lambda m: join(*m.group(1).split(sep)[-2:]), r'File "([^"]+)"',
text, lambda m: join(*m.group(1).split(sep)[-2:]),
flags=re.M) text,
flags=re.M,
)
text = re.sub(r"\s+", " ", text, flags=re.M) text = re.sub(r"\s+", " ", text, flags=re.M)
return text.strip() return text.strip()
skip_conditions = [ skip_conditions = [
isinstance(e, cls) for cls in (IOError, exception.ReturnErrorCode, isinstance(e, cls)
exception.UserSideException, for cls in (
exception.PlatformIOProjectException) IOError,
exception.ReturnErrorCode,
exception.UserSideException,
exception.PlatformIOProjectException,
)
] ]
try: try:
skip_conditions.append("[API] Account: " in str(e)) skip_conditions.append("[API] Account: " in str(e))
@ -340,14 +358,16 @@ def on_exception(e):
e = ue e = ue
if any(skip_conditions): if any(skip_conditions):
return return
is_crash = any([ is_crash = any(
not isinstance(e, exception.PlatformioException), [
"Error" in e.__class__.__name__ not isinstance(e, exception.PlatformioException),
]) "Error" in e.__class__.__name__,
]
)
mp = MeasurementProtocol() mp = MeasurementProtocol()
description = _cleanup_description(format_exc() if is_crash else str(e)) description = _cleanup_description(format_exc() if is_crash else str(e))
mp['exd'] = ("%s: %s" % (type(e).__name__, description))[:2048] mp["exd"] = ("%s: %s" % (type(e).__name__, description))[:2048]
mp['exf'] = 1 if is_crash else 0 mp["exf"] = 1 if is_crash else 0
mp.send("exception") mp.send("exception")
@ -373,7 +393,7 @@ def backup_reports(items):
KEEP_MAX_REPORTS = 100 KEEP_MAX_REPORTS = 100
tm = app.get_state_item("telemetry", {}) tm = app.get_state_item("telemetry", {})
if "backup" not in tm: if "backup" not in tm:
tm['backup'] = [] tm["backup"] = []
for params in items: for params in items:
# skip static options # skip static options
@ -383,28 +403,28 @@ def backup_reports(items):
# store time in UNIX format # store time in UNIX format
if "qt" not in params: if "qt" not in params:
params['qt'] = time() params["qt"] = time()
elif not isinstance(params['qt'], float): elif not isinstance(params["qt"], float):
params['qt'] = time() - (params['qt'] / 1000) params["qt"] = time() - (params["qt"] / 1000)
tm['backup'].append(params) tm["backup"].append(params)
tm['backup'] = tm['backup'][KEEP_MAX_REPORTS * -1:] tm["backup"] = tm["backup"][KEEP_MAX_REPORTS * -1 :]
app.set_state_item("telemetry", tm) app.set_state_item("telemetry", tm)
def resend_backuped_reports(): def resend_backuped_reports():
tm = app.get_state_item("telemetry", {}) tm = app.get_state_item("telemetry", {})
if "backup" not in tm or not tm['backup']: if "backup" not in tm or not tm["backup"]:
return False return False
for report in tm['backup']: for report in tm["backup"]:
mp = MeasurementProtocol() mp = MeasurementProtocol()
for key, value in report.items(): for key, value in report.items():
mp[key] = value mp[key] = value
mp.send(report['t']) mp.send(report["t"])
# clean # clean
tm['backup'] = [] tm["backup"] = []
app.set_state_item("telemetry", tm) app.set_state_item("telemetry", tm)
return True return True

View File

@ -24,7 +24,6 @@ from platformio import exception, util
class ArchiveBase(object): class ArchiveBase(object):
def __init__(self, arhfileobj): def __init__(self, arhfileobj):
self._afo = arhfileobj self._afo = arhfileobj
@ -46,7 +45,6 @@ class ArchiveBase(object):
class TARArchive(ArchiveBase): class TARArchive(ArchiveBase):
def __init__(self, archpath): def __init__(self, archpath):
super(TARArchive, self).__init__(tarfile_open(archpath)) super(TARArchive, self).__init__(tarfile_open(archpath))
@ -62,7 +60,6 @@ class TARArchive(ArchiveBase):
class ZIPArchive(ArchiveBase): class ZIPArchive(ArchiveBase):
def __init__(self, archpath): def __init__(self, archpath):
super(ZIPArchive, self).__init__(ZipFile(archpath)) super(ZIPArchive, self).__init__(ZipFile(archpath))
@ -74,8 +71,10 @@ class ZIPArchive(ArchiveBase):
@staticmethod @staticmethod
def preserve_mtime(item, dest_dir): def preserve_mtime(item, dest_dir):
util.change_filemtime(join(dest_dir, item.filename), util.change_filemtime(
mktime(tuple(item.date_time) + tuple([0, 0, 0]))) join(dest_dir, item.filename),
mktime(tuple(item.date_time) + tuple([0, 0, 0])),
)
def get_items(self): def get_items(self):
return self._afo.infolist() return self._afo.infolist()
@ -92,7 +91,6 @@ class ZIPArchive(ArchiveBase):
class FileUnpacker(object): class FileUnpacker(object):
def __init__(self, archpath): def __init__(self, archpath):
self.archpath = archpath self.archpath = archpath
self._unpacker = None self._unpacker = None

View File

@ -40,7 +40,6 @@ from platformio.proc import is_ci # pylint: disable=unused-import
class memoized(object): class memoized(object):
def __init__(self, expire=0): def __init__(self, expire=0):
expire = str(expire) expire = str(expire)
if expire.isdigit(): if expire.isdigit():
@ -51,13 +50,12 @@ class memoized(object):
self.cache = {} self.cache = {}
def __call__(self, func): def __call__(self, func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
key = str(args) + str(kwargs) key = str(args) + str(kwargs)
if (key not in self.cache if key not in self.cache or (
or (self.expire > 0 self.expire > 0 and self.cache[key][0] < time.time() - self.expire
and self.cache[key][0] < time.time() - self.expire)): ):
self.cache[key] = (time.time(), func(*args, **kwargs)) self.cache[key] = (time.time(), func(*args, **kwargs))
return self.cache[key][1] return self.cache[key][1]
@ -69,13 +67,11 @@ class memoized(object):
class throttle(object): class throttle(object):
def __init__(self, threshhold): def __init__(self, threshhold):
self.threshhold = threshhold # milliseconds self.threshhold = threshhold # milliseconds
self.last = 0 self.last = 0
def __call__(self, func): def __call__(self, func):
@wraps(func) @wraps(func)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
diff = int(round((time.time() - self.last) * 1000)) diff = int(round((time.time() - self.last) * 1000))
@ -166,17 +162,14 @@ def get_logical_devices():
if WINDOWS: if WINDOWS:
try: try:
result = exec_command( result = exec_command(
["wmic", "logicaldisk", "get", ["wmic", "logicaldisk", "get", "name,VolumeName"]
"name,VolumeName"]).get("out", "") ).get("out", "")
devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?") devicenamere = re.compile(r"^([A-Z]{1}\:)\s*(\S+)?")
for line in result.split("\n"): for line in result.split("\n"):
match = devicenamere.match(line.strip()) match = devicenamere.match(line.strip())
if not match: if not match:
continue continue
items.append({ items.append({"path": match.group(1) + "\\", "name": match.group(2)})
"path": match.group(1) + "\\",
"name": match.group(2)
})
return items return items
except WindowsError: # pylint: disable=undefined-variable except WindowsError: # pylint: disable=undefined-variable
pass pass
@ -192,10 +185,7 @@ def get_logical_devices():
match = devicenamere.match(line.strip()) match = devicenamere.match(line.strip())
if not match: if not match:
continue continue
items.append({ items.append({"path": match.group(1), "name": os.path.basename(match.group(1))})
"path": match.group(1),
"name": os.path.basename(match.group(1))
})
return items return items
@ -205,22 +195,20 @@ def get_mdns_services():
except ImportError: except ImportError:
from site import addsitedir from site import addsitedir
from platformio.managers.core import get_core_package_dir from platformio.managers.core import get_core_package_dir
contrib_pysite_dir = get_core_package_dir("contrib-pysite") contrib_pysite_dir = get_core_package_dir("contrib-pysite")
addsitedir(contrib_pysite_dir) addsitedir(contrib_pysite_dir)
sys.path.insert(0, contrib_pysite_dir) sys.path.insert(0, contrib_pysite_dir)
import zeroconf import zeroconf
class mDNSListener(object): class mDNSListener(object):
def __init__(self): def __init__(self):
self._zc = zeroconf.Zeroconf( self._zc = zeroconf.Zeroconf(interfaces=zeroconf.InterfaceChoice.All)
interfaces=zeroconf.InterfaceChoice.All)
self._found_types = [] self._found_types = []
self._found_services = [] self._found_services = []
def __enter__(self): def __enter__(self):
zeroconf.ServiceBrowser(self._zc, "_services._dns-sd._udp.local.", zeroconf.ServiceBrowser(self._zc, "_services._dns-sd._udp.local.", self)
self)
return self return self
def __exit__(self, etype, value, traceback): def __exit__(self, etype, value, traceback):
@ -233,8 +221,7 @@ def get_mdns_services():
try: try:
assert zeroconf.service_type_name(name) assert zeroconf.service_type_name(name)
assert str(name) assert str(name)
except (AssertionError, UnicodeError, except (AssertionError, UnicodeError, zeroconf.BadTypeInNameException):
zeroconf.BadTypeInNameException):
return return
if name not in self._found_types: if name not in self._found_types:
self._found_types.append(name) self._found_types.append(name)
@ -255,29 +242,29 @@ def get_mdns_services():
if service.properties: if service.properties:
try: try:
properties = { properties = {
k.decode("utf8"): k.decode("utf8"): v.decode("utf8")
v.decode("utf8") if isinstance(v, bytes) else v if isinstance(v, bytes)
else v
for k, v in service.properties.items() for k, v in service.properties.items()
} }
json.dumps(properties) json.dumps(properties)
except UnicodeDecodeError: except UnicodeDecodeError:
properties = None properties = None
items.append({ items.append(
"type": {
service.type, "type": service.type,
"name": "name": service.name,
service.name, "ip": ".".join(
"ip": [
".".join([ str(c if isinstance(c, int) else ord(c))
str(c if isinstance(c, int) else ord(c)) for c in service.address
for c in service.address ]
]), ),
"port": "port": service.port,
service.port, "properties": properties,
"properties": }
properties )
})
return items return items
@ -293,10 +280,8 @@ def _api_request_session():
@throttle(500) @throttle(500)
def _get_api_result( def _get_api_result(
url, # pylint: disable=too-many-branches url, params=None, data=None, auth=None # pylint: disable=too-many-branches
params=None, ):
data=None,
auth=None):
from platformio.app import get_setting from platformio.app import get_setting
result = {} result = {}
@ -311,30 +296,29 @@ def _get_api_result(
try: try:
if data: if data:
r = _api_request_session().post(url, r = _api_request_session().post(
params=params, url,
data=data, params=params,
headers=headers, data=data,
auth=auth, headers=headers,
verify=verify_ssl) auth=auth,
verify=verify_ssl,
)
else: else:
r = _api_request_session().get(url, r = _api_request_session().get(
params=params, url, params=params, headers=headers, auth=auth, verify=verify_ssl
headers=headers, )
auth=auth,
verify=verify_ssl)
result = r.json() result = r.json()
r.raise_for_status() r.raise_for_status()
return r.text return r.text
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
if result and "message" in result: if result and "message" in result:
raise exception.APIRequestError(result['message']) raise exception.APIRequestError(result["message"])
if result and "errors" in result: if result and "errors" in result:
raise exception.APIRequestError(result['errors'][0]['title']) raise exception.APIRequestError(result["errors"][0]["title"])
raise exception.APIRequestError(e) raise exception.APIRequestError(e)
except ValueError: except ValueError:
raise exception.APIRequestError("Invalid response: %s" % raise exception.APIRequestError("Invalid response: %s" % r.text.encode("utf-8"))
r.text.encode("utf-8"))
finally: finally:
if r: if r:
r.close() r.close()
@ -343,10 +327,12 @@ def _get_api_result(
def get_api_result(url, params=None, data=None, auth=None, cache_valid=None): def get_api_result(url, params=None, data=None, auth=None, cache_valid=None):
from platformio.app import ContentCache from platformio.app import ContentCache
total = 0 total = 0
max_retries = 5 max_retries = 5
cache_key = (ContentCache.key_from_args(url, params, data, auth) cache_key = (
if cache_valid else None) ContentCache.key_from_args(url, params, data, auth) if cache_valid else None
)
while total < max_retries: while total < max_retries:
try: try:
with ContentCache() as cc: with ContentCache() as cc:
@ -363,24 +349,24 @@ def get_api_result(url, params=None, data=None, auth=None, cache_valid=None):
with ContentCache() as cc: with ContentCache() as cc:
cc.set(cache_key, result, cache_valid) cc.set(cache_key, result, cache_valid)
return json.loads(result) return json.loads(result)
except (requests.exceptions.ConnectionError, except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as e:
requests.exceptions.Timeout) as e:
total += 1 total += 1
if not PlatformioCLI.in_silence(): if not PlatformioCLI.in_silence():
click.secho( click.secho(
"[API] ConnectionError: {0} (incremented retry: max={1}, " "[API] ConnectionError: {0} (incremented retry: max={1}, "
"total={2})".format(e, max_retries, total), "total={2})".format(e, max_retries, total),
fg="yellow") fg="yellow",
)
time.sleep(2 * total) time.sleep(2 * total)
raise exception.APIRequestError( raise exception.APIRequestError(
"Could not connect to PlatformIO API Service. " "Could not connect to PlatformIO API Service. " "Please try later."
"Please try later.") )
PING_INTERNET_IPS = [ PING_INTERNET_IPS = [
"192.30.253.113", # github.com "192.30.253.113", # github.com
"193.222.52.25" # dl.platformio.org "193.222.52.25", # dl.platformio.org
] ]
@ -391,12 +377,9 @@ def _internet_on():
for ip in PING_INTERNET_IPS: for ip in PING_INTERNET_IPS:
try: try:
if os.getenv("HTTP_PROXY", os.getenv("HTTPS_PROXY")): if os.getenv("HTTP_PROXY", os.getenv("HTTPS_PROXY")):
requests.get("http://%s" % ip, requests.get("http://%s" % ip, allow_redirects=False, timeout=timeout)
allow_redirects=False,
timeout=timeout)
else: else:
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect( socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((ip, 80))
(ip, 80))
return True return True
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
pass pass
@ -438,8 +421,7 @@ def merge_dicts(d1, d2, path=None):
if path is None: if path is None:
path = [] path = []
for key in d2: for key in d2:
if (key in d1 and isinstance(d1[key], dict) if key in d1 and isinstance(d1[key], dict) and isinstance(d2[key], dict):
and isinstance(d2[key], dict)):
merge_dicts(d1[key], d2[key], path + [str(key)]) merge_dicts(d1[key], d2[key], path + [str(key)])
else: else:
d1[key] = d2[key] d1[key] = d2[key]
@ -450,9 +432,7 @@ def print_labeled_bar(label, is_error=False, fg=None):
terminal_width, _ = click.get_terminal_size() terminal_width, _ = click.get_terminal_size()
width = len(click.unstyle(label)) width = len(click.unstyle(label))
half_line = "=" * int((terminal_width - width - 2) / 2) half_line = "=" * int((terminal_width - width - 2) / 2)
click.secho("%s %s %s" % (half_line, label, half_line), click.secho("%s %s %s" % (half_line, label, half_line), fg=fg, err=is_error)
fg=fg,
err=is_error)
def humanize_duration_time(duration): def humanize_duration_time(duration):

View File

@ -27,7 +27,6 @@ except ImportError:
class VCSClientFactory(object): class VCSClientFactory(object):
@staticmethod @staticmethod
def newClient(src_dir, remote_url, silent=False): def newClient(src_dir, remote_url, silent=False):
result = urlparse(remote_url) result = urlparse(remote_url)
@ -38,15 +37,14 @@ class VCSClientFactory(object):
remote_url = remote_url[4:] remote_url = remote_url[4:]
elif "+" in result.scheme: elif "+" in result.scheme:
type_, _ = result.scheme.split("+", 1) type_, _ = result.scheme.split("+", 1)
remote_url = remote_url[len(type_) + 1:] remote_url = remote_url[len(type_) + 1 :]
if "#" in remote_url: if "#" in remote_url:
remote_url, tag = remote_url.rsplit("#", 1) remote_url, tag = remote_url.rsplit("#", 1)
if not type_: if not type_:
raise PlatformioException("VCS: Unknown repository type %s" % raise PlatformioException("VCS: Unknown repository type %s" % remote_url)
remote_url) obj = getattr(modules[__name__], "%sClient" % type_.title())(
obj = getattr(modules[__name__], src_dir, remote_url, tag, silent
"%sClient" % type_.title())(src_dir, remote_url, tag, )
silent)
assert isinstance(obj, VCSClientBase) assert isinstance(obj, VCSClientBase)
return obj return obj
@ -71,8 +69,8 @@ class VCSClientBase(object):
assert self.run_cmd(["--version"]) assert self.run_cmd(["--version"])
except (AssertionError, OSError, PlatformioException): except (AssertionError, OSError, PlatformioException):
raise UserSideException( raise UserSideException(
"VCS: `%s` client is not installed in your system" % "VCS: `%s` client is not installed in your system" % self.command
self.command) )
return True return True
@property @property
@ -98,24 +96,23 @@ class VCSClientBase(object):
def run_cmd(self, args, **kwargs): def run_cmd(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
kwargs['cwd'] = self.src_dir kwargs["cwd"] = self.src_dir
try: try:
check_call(args, **kwargs) check_call(args, **kwargs)
return True return True
except CalledProcessError as e: except CalledProcessError as e:
raise PlatformioException("VCS: Could not process command %s" % raise PlatformioException("VCS: Could not process command %s" % e.cmd)
e.cmd)
def get_cmd_output(self, args, **kwargs): def get_cmd_output(self, args, **kwargs):
args = [self.command] + args args = [self.command] + args
if "cwd" not in kwargs: if "cwd" not in kwargs:
kwargs['cwd'] = self.src_dir kwargs["cwd"] = self.src_dir
result = exec_command(args, **kwargs) result = exec_command(args, **kwargs)
if result['returncode'] == 0: if result["returncode"] == 0:
return result['out'].strip() return result["out"].strip()
raise PlatformioException( raise PlatformioException(
"VCS: Could not receive an output from `%s` command (%s)" % "VCS: Could not receive an output from `%s` command (%s)" % (args, result)
(args, result)) )
class GitClient(VCSClientBase): class GitClient(VCSClientBase):
@ -127,7 +124,8 @@ class GitClient(VCSClientBase):
return VCSClientBase.check_client(self) return VCSClientBase.check_client(self)
except UserSideException: except UserSideException:
raise UserSideException( raise UserSideException(
"Please install Git client from https://git-scm.com/downloads") "Please install Git client from https://git-scm.com/downloads"
)
def get_branches(self): def get_branches(self):
output = self.get_cmd_output(["branch"]) output = self.get_cmd_output(["branch"])
@ -232,7 +230,8 @@ class SvnClient(VCSClientBase):
def get_current_revision(self): def get_current_revision(self):
output = self.get_cmd_output( output = self.get_cmd_output(
["info", "--non-interactive", "--trust-server-cert", "-r", "HEAD"]) ["info", "--non-interactive", "--trust-server-cert", "-r", "HEAD"]
)
for line in output.split("\n"): for line in output.split("\n"):
line = line.strip() line = line.strip()
if line.startswith("Revision:"): if line.startswith("Revision:"):

View File

@ -23,7 +23,7 @@ def test_board_json_output(clirunner, validate_cliresult):
validate_cliresult(result) validate_cliresult(result)
boards = json.loads(result.output) boards = json.loads(result.output)
assert isinstance(boards, list) assert isinstance(boards, list)
assert any(["mbed" in b['frameworks'] for b in boards]) assert any(["mbed" in b["frameworks"] for b in boards])
def test_board_raw_output(clirunner, validate_cliresult): def test_board_raw_output(clirunner, validate_cliresult):
@ -33,8 +33,7 @@ def test_board_raw_output(clirunner, validate_cliresult):
def test_board_options(clirunner, validate_cliresult): def test_board_options(clirunner, validate_cliresult):
required_opts = set( required_opts = set(["fcpu", "frameworks", "id", "mcu", "name", "platform"])
["fcpu", "frameworks", "id", "mcu", "name", "platform"])
# fetch available platforms # fetch available platforms
result = clirunner.invoke(cmd_platform_search, ["--json-output"]) result = clirunner.invoke(cmd_platform_search, ["--json-output"])
@ -42,7 +41,7 @@ def test_board_options(clirunner, validate_cliresult):
search_result = json.loads(result.output) search_result = json.loads(result.output)
assert isinstance(search_result, list) assert isinstance(search_result, list)
assert len(search_result) assert len(search_result)
platforms = [item['name'] for item in search_result] platforms = [item["name"] for item in search_result]
result = clirunner.invoke(cmd_boards, ["mbed", "--json-output"]) result = clirunner.invoke(cmd_boards, ["mbed", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
@ -50,4 +49,4 @@ def test_board_options(clirunner, validate_cliresult):
for board in boards: for board in boards:
assert required_opts.issubset(set(board)) assert required_opts.issubset(set(board))
assert board['platform'] in platforms assert board["platform"] in platforms

View File

@ -90,47 +90,43 @@ def test_check_cli_output(clirunner, check_dir):
errors, warnings, style = count_defects(result.output) errors, warnings, style = count_defects(result.output)
assert (result.exit_code != 0) assert result.exit_code != 0
assert (errors + warnings + style == EXPECTED_DEFECTS) assert errors + warnings + style == EXPECTED_DEFECTS
def test_check_json_output(clirunner, check_dir): def test_check_json_output(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(
cmd_check, cmd_check, ["--project-dir", str(check_dir), "--json-output"]
["--project-dir", str(check_dir), "--json-output"]) )
output = json.loads(result.stdout.strip()) output = json.loads(result.stdout.strip())
assert isinstance(output, list) assert isinstance(output, list)
assert (len(output[0].get("defects", [])) == EXPECTED_DEFECTS) assert len(output[0].get("defects", [])) == EXPECTED_DEFECTS
def test_check_tool_defines_passed(clirunner, check_dir): def test_check_tool_defines_passed(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir), "--verbose"])
cmd_check,
["--project-dir", str(check_dir), "--verbose"])
output = result.output output = result.output
assert ("PLATFORMIO=" in output) assert "PLATFORMIO=" in output
assert ("__GNUC__" in output) assert "__GNUC__" in output
def test_check_severity_threshold(clirunner, check_dir): def test_check_severity_threshold(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(
cmd_check, cmd_check, ["--project-dir", str(check_dir), "--severity=high"]
["--project-dir", str(check_dir), "--severity=high"]) )
errors, warnings, style = count_defects(result.output) errors, warnings, style = count_defects(result.output)
assert (result.exit_code != 0) assert result.exit_code != 0
assert (errors == EXPECTED_ERRORS) assert errors == EXPECTED_ERRORS
assert (warnings == 0) assert warnings == 0
assert (style == 0) assert style == 0
def test_check_includes_passed(clirunner, check_dir): def test_check_includes_passed(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir), "--verbose"])
cmd_check,
["--project-dir", str(check_dir), "--verbose"])
output = result.output output = result.output
inc_count = 0 inc_count = 0
@ -139,13 +135,11 @@ def test_check_includes_passed(clirunner, check_dir):
inc_count = l.count("-I") inc_count = l.count("-I")
# at least 1 include path for default mode # at least 1 include path for default mode
assert (inc_count > 1) assert inc_count > 1
def test_check_silent_mode(clirunner, check_dir): def test_check_silent_mode(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(cmd_check, ["--project-dir", str(check_dir), "--silent"])
cmd_check,
["--project-dir", str(check_dir), "--silent"])
errors, warnings, style = count_defects(result.output) errors, warnings, style = count_defects(result.output)
@ -159,9 +153,8 @@ def test_check_filter_sources(clirunner, check_dir):
check_dir.mkdir(join("src", "app")).join("additional.cpp").write(TEST_CODE) check_dir.mkdir(join("src", "app")).join("additional.cpp").write(TEST_CODE)
result = clirunner.invoke( result = clirunner.invoke(
cmd_check, cmd_check, ["--project-dir", str(check_dir), "--filter=-<*> +<src/app/>"]
["--project-dir", )
str(check_dir), "--filter=-<*> +<src/app/>"])
errors, warnings, style = count_defects(result.output) errors, warnings, style = count_defects(result.output)
@ -187,8 +180,8 @@ def test_check_failed_if_no_source_files(clirunner, tmpdir):
def test_check_failed_if_bad_flag_passed(clirunner, check_dir): def test_check_failed_if_bad_flag_passed(clirunner, check_dir):
result = clirunner.invoke( result = clirunner.invoke(
cmd_check, ["--project-dir", cmd_check, ["--project-dir", str(check_dir), '"--flags=--UNKNOWN"']
str(check_dir), '"--flags=--UNKNOWN"']) )
errors, warnings, style = count_defects(result.output) errors, warnings, style = count_defects(result.output)
@ -200,7 +193,8 @@ def test_check_failed_if_bad_flag_passed(clirunner, check_dir):
def test_check_success_if_no_errors(clirunner, tmpdir): def test_check_success_if_no_errors(clirunner, tmpdir):
tmpdir.join("platformio.ini").write(DEFAULT_CONFIG) tmpdir.join("platformio.ini").write(DEFAULT_CONFIG)
tmpdir.mkdir("src").join("main.c").write(""" tmpdir.mkdir("src").join("main.c").write(
"""
#include <stdlib.h> #include <stdlib.h>
void unused_functin(){ void unused_functin(){
@ -211,7 +205,8 @@ void unused_functin(){
int main() { int main() {
} }
""") """
)
result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)]) result = clirunner.invoke(cmd_check, ["--project-dir", str(tmpdir)])
@ -243,14 +238,17 @@ def test_check_individual_flags_passed(clirunner, tmpdir):
def test_check_cppcheck_misra_addon(clirunner, check_dir): def test_check_cppcheck_misra_addon(clirunner, check_dir):
check_dir.join("misra.json").write(""" check_dir.join("misra.json").write(
"""
{ {
"script": "addons/misra.py", "script": "addons/misra.py",
"args": ["--rule-texts=rules.txt"] "args": ["--rule-texts=rules.txt"]
} }
""") """
)
check_dir.join("rules.txt").write(""" check_dir.join("rules.txt").write(
"""
Appendix A Summary of guidelines Appendix A Summary of guidelines
Rule 3.1 Required Rule 3.1 Required
R3.1 text. R3.1 text.
@ -272,12 +270,12 @@ Rule 21.3 Required
R21.3 Found MISRA defect R21.3 Found MISRA defect
Rule 21.4 Rule 21.4
R21.4 text. R21.4 text.
""") """
)
result = clirunner.invoke( result = clirunner.invoke(
cmd_check, cmd_check, ["--project-dir", str(check_dir), "--flags=--addon=misra.json"]
["--project-dir", )
str(check_dir), "--flags=--addon=misra.json"])
assert result.exit_code != 0 assert result.exit_code != 0
assert "R21.3 Found MISRA defect" in result.output assert "R21.3 Found MISRA defect" in result.output

View File

@ -25,37 +25,63 @@ def test_ci_empty(clirunner):
def test_ci_boards(clirunner, validate_cliresult): def test_ci_boards(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join("examples", "wiring-blink", "src", "main.cpp"), "-b", "uno", "-b", cmd_ci,
"leonardo" [
]) join("examples", "wiring-blink", "src", "main.cpp"),
"-b",
"uno",
"-b",
"leonardo",
],
)
validate_cliresult(result) validate_cliresult(result)
def test_ci_build_dir(clirunner, tmpdir_factory, validate_cliresult): def test_ci_build_dir(clirunner, tmpdir_factory, validate_cliresult):
build_dir = str(tmpdir_factory.mktemp("ci_build_dir")) build_dir = str(tmpdir_factory.mktemp("ci_build_dir"))
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join("examples", "wiring-blink", "src", "main.cpp"), "-b", "uno", cmd_ci,
"--build-dir", build_dir [
]) join("examples", "wiring-blink", "src", "main.cpp"),
"-b",
"uno",
"--build-dir",
build_dir,
],
)
validate_cliresult(result) validate_cliresult(result)
assert not isfile(join(build_dir, "platformio.ini")) assert not isfile(join(build_dir, "platformio.ini"))
def test_ci_keep_build_dir(clirunner, tmpdir_factory, validate_cliresult): def test_ci_keep_build_dir(clirunner, tmpdir_factory, validate_cliresult):
build_dir = str(tmpdir_factory.mktemp("ci_build_dir")) build_dir = str(tmpdir_factory.mktemp("ci_build_dir"))
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join("examples", "wiring-blink", "src", "main.cpp"), "-b", "uno", cmd_ci,
"--build-dir", build_dir, "--keep-build-dir" [
]) join("examples", "wiring-blink", "src", "main.cpp"),
"-b",
"uno",
"--build-dir",
build_dir,
"--keep-build-dir",
],
)
validate_cliresult(result) validate_cliresult(result)
assert isfile(join(build_dir, "platformio.ini")) assert isfile(join(build_dir, "platformio.ini"))
# 2nd attempt # 2nd attempt
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join("examples", "wiring-blink", "src", "main.cpp"), "-b", "metro", cmd_ci,
"--build-dir", build_dir, "--keep-build-dir" [
]) join("examples", "wiring-blink", "src", "main.cpp"),
"-b",
"metro",
"--build-dir",
build_dir,
"--keep-build-dir",
],
)
validate_cliresult(result) validate_cliresult(result)
assert "board: uno" in result.output assert "board: uno" in result.output
@ -64,10 +90,14 @@ def test_ci_keep_build_dir(clirunner, tmpdir_factory, validate_cliresult):
def test_ci_project_conf(clirunner, validate_cliresult): def test_ci_project_conf(clirunner, validate_cliresult):
project_dir = join("examples", "wiring-blink") project_dir = join("examples", "wiring-blink")
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join(project_dir, "src", "main.cpp"), "--project-conf", cmd_ci,
join(project_dir, "platformio.ini") [
]) join(project_dir, "src", "main.cpp"),
"--project-conf",
join(project_dir, "platformio.ini"),
],
)
validate_cliresult(result) validate_cliresult(result)
assert "uno" in result.output assert "uno" in result.output
@ -75,12 +105,24 @@ def test_ci_project_conf(clirunner, validate_cliresult):
def test_ci_lib_and_board(clirunner, tmpdir_factory, validate_cliresult): def test_ci_lib_and_board(clirunner, tmpdir_factory, validate_cliresult):
storage_dir = str(tmpdir_factory.mktemp("lib")) storage_dir = str(tmpdir_factory.mktemp("lib"))
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, ["--storage-dir", storage_dir, "install", "1@2.3.2"]) cmd_lib, ["--storage-dir", storage_dir, "install", "1@2.3.2"]
)
validate_cliresult(result) validate_cliresult(result)
result = clirunner.invoke(cmd_ci, [ result = clirunner.invoke(
join(storage_dir, "OneWire_ID1", "examples", "DS2408_Switch", cmd_ci,
"DS2408_Switch.pde"), "-l", [
join(storage_dir, "OneWire_ID1"), "-b", "uno" join(
]) storage_dir,
"OneWire_ID1",
"examples",
"DS2408_Switch",
"DS2408_Switch.pde",
),
"-l",
join(storage_dir, "OneWire_ID1"),
"-b",
"uno",
],
)
validate_cliresult(result) validate_cliresult(result)

View File

@ -25,8 +25,7 @@ from platformio.project.config import ProjectConfig
def validate_pioproject(pioproject_dir): def validate_pioproject(pioproject_dir):
pioconf_path = join(pioproject_dir, "platformio.ini") pioconf_path = join(pioproject_dir, "platformio.ini")
assert isfile(pioconf_path) and getsize(pioconf_path) > 0 assert isfile(pioconf_path) and getsize(pioconf_path) > 0
assert isdir(join(pioproject_dir, "src")) and isdir( assert isdir(join(pioproject_dir, "src")) and isdir(join(pioproject_dir, "lib"))
join(pioproject_dir, "lib"))
def test_init_default(clirunner, validate_cliresult): def test_init_default(clirunner, validate_cliresult):
@ -66,18 +65,17 @@ def test_init_ide_without_board(clirunner, tmpdir):
def test_init_ide_atom(clirunner, validate_cliresult, tmpdir): def test_init_ide_atom(clirunner, validate_cliresult, tmpdir):
with tmpdir.as_cwd(): with tmpdir.as_cwd():
result = clirunner.invoke( result = clirunner.invoke(
cmd_init, ["--ide", "atom", "-b", "uno", "-b", "teensy31"]) cmd_init, ["--ide", "atom", "-b", "uno", "-b", "teensy31"]
)
validate_cliresult(result) validate_cliresult(result)
validate_pioproject(str(tmpdir)) validate_pioproject(str(tmpdir))
assert all([ assert all(
tmpdir.join(f).check() [tmpdir.join(f).check() for f in (".clang_complete", ".gcc-flags.json")]
for f in (".clang_complete", ".gcc-flags.json") )
])
assert "arduinoavr" in tmpdir.join(".clang_complete").read() assert "arduinoavr" in tmpdir.join(".clang_complete").read()
# switch to NodeMCU # switch to NodeMCU
result = clirunner.invoke(cmd_init, result = clirunner.invoke(cmd_init, ["--ide", "atom", "-b", "nodemcuv2"])
["--ide", "atom", "-b", "nodemcuv2"])
validate_cliresult(result) validate_cliresult(result)
validate_pioproject(str(tmpdir)) validate_pioproject(str(tmpdir))
assert "arduinoespressif" in tmpdir.join(".clang_complete").read() assert "arduinoespressif" in tmpdir.join(".clang_complete").read()
@ -110,46 +108,49 @@ def test_init_special_board(clirunner, validate_cliresult):
config = ProjectConfig(join(getcwd(), "platformio.ini")) config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate() config.validate()
expected_result = dict(platform=str(boards[0]['platform']), expected_result = dict(
board="uno", platform=str(boards[0]["platform"]),
framework=[str(boards[0]['frameworks'][0])]) board="uno",
framework=[str(boards[0]["frameworks"][0])],
)
assert config.has_section("env:uno") assert config.has_section("env:uno")
assert sorted(config.items(env="uno", as_dict=True).items()) == sorted( assert sorted(config.items(env="uno", as_dict=True).items()) == sorted(
expected_result.items()) expected_result.items()
)
def test_init_enable_auto_uploading(clirunner, validate_cliresult): def test_init_enable_auto_uploading(clirunner, validate_cliresult):
with clirunner.isolated_filesystem(): with clirunner.isolated_filesystem():
result = clirunner.invoke( result = clirunner.invoke(
cmd_init, ["-b", "uno", "--project-option", "targets=upload"]) cmd_init, ["-b", "uno", "--project-option", "targets=upload"]
)
validate_cliresult(result) validate_cliresult(result)
validate_pioproject(getcwd()) validate_pioproject(getcwd())
config = ProjectConfig(join(getcwd(), "platformio.ini")) config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate() config.validate()
expected_result = dict(targets=["upload"], expected_result = dict(
platform="atmelavr", targets=["upload"], platform="atmelavr", board="uno", framework=["arduino"]
board="uno", )
framework=["arduino"])
assert config.has_section("env:uno") assert config.has_section("env:uno")
assert sorted(config.items(env="uno", as_dict=True).items()) == sorted( assert sorted(config.items(env="uno", as_dict=True).items()) == sorted(
expected_result.items()) expected_result.items()
)
def test_init_custom_framework(clirunner, validate_cliresult): def test_init_custom_framework(clirunner, validate_cliresult):
with clirunner.isolated_filesystem(): with clirunner.isolated_filesystem():
result = clirunner.invoke( result = clirunner.invoke(
cmd_init, ["-b", "teensy31", "--project-option", "framework=mbed"]) cmd_init, ["-b", "teensy31", "--project-option", "framework=mbed"]
)
validate_cliresult(result) validate_cliresult(result)
validate_pioproject(getcwd()) validate_pioproject(getcwd())
config = ProjectConfig(join(getcwd(), "platformio.ini")) config = ProjectConfig(join(getcwd(), "platformio.ini"))
config.validate() config.validate()
expected_result = dict(platform="teensy", expected_result = dict(platform="teensy", board="teensy31", framework=["mbed"])
board="teensy31",
framework=["mbed"])
assert config.has_section("env:teensy31") assert config.has_section("env:teensy31")
assert sorted(config.items(env="teensy31", assert sorted(config.items(env="teensy31", as_dict=True).items()) == sorted(
as_dict=True).items()) == sorted( expected_result.items()
expected_result.items()) )
def test_init_incorrect_board(clirunner): def test_init_incorrect_board(clirunner):

View File

@ -28,19 +28,25 @@ def test_search(clirunner, validate_cliresult):
match = re.search(r"Found\s+(\d+)\slibraries:", result.output) match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 2 assert int(match.group(1)) > 2
result = clirunner.invoke(cmd_lib, result = clirunner.invoke(cmd_lib, ["search", "DHT22", "--platform=timsp430"])
["search", "DHT22", "--platform=timsp430"])
validate_cliresult(result) validate_cliresult(result)
match = re.search(r"Found\s+(\d+)\slibraries:", result.output) match = re.search(r"Found\s+(\d+)\slibraries:", result.output)
assert int(match.group(1)) > 1 assert int(match.group(1)) > 1
def test_global_install_registry(clirunner, validate_cliresult, def test_global_install_registry(clirunner, validate_cliresult, isolated_pio_home):
isolated_pio_home): result = clirunner.invoke(
result = clirunner.invoke(cmd_lib, [ cmd_lib,
"-g", "install", "64", "ArduinoJson@~5.10.0", "547@2.2.4", [
"AsyncMqttClient@<=0.8.2", "999@77d4eb3f8a" "-g",
]) "install",
"64",
"ArduinoJson@~5.10.0",
"547@2.2.4",
"AsyncMqttClient@<=0.8.2",
"999@77d4eb3f8a",
],
)
validate_cliresult(result) validate_cliresult(result)
# install unknown library # install unknown library
@ -50,29 +56,40 @@ def test_global_install_registry(clirunner, validate_cliresult,
items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()]
items2 = [ items2 = [
"ArduinoJson_ID64", "ArduinoJson_ID64@5.10.1", "NeoPixelBus_ID547", "ArduinoJson_ID64",
"AsyncMqttClient_ID346", "ESPAsyncTCP_ID305", "AsyncTCP_ID1826", "ArduinoJson_ID64@5.10.1",
"RFcontrol_ID999" "NeoPixelBus_ID547",
"AsyncMqttClient_ID346",
"ESPAsyncTCP_ID305",
"AsyncTCP_ID1826",
"RFcontrol_ID999",
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
def test_global_install_archive(clirunner, validate_cliresult, def test_global_install_archive(clirunner, validate_cliresult, isolated_pio_home):
isolated_pio_home): result = clirunner.invoke(
result = clirunner.invoke(cmd_lib, [ cmd_lib,
"-g", "install", [
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip", "-g",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2", "install",
"SomeLib=http://dl.platformio.org/libraries/archives/0/9540.tar.gz", "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip" "https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@5.8.2",
]) "SomeLib=http://dl.platformio.org/libraries/archives/0/9540.tar.gz",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
],
)
validate_cliresult(result) validate_cliresult(result)
# incorrect requirements # incorrect requirements
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(
"-g", "install", cmd_lib,
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3" [
]) "-g",
"install",
"https://github.com/bblanchon/ArduinoJson/archive/v5.8.2.zip@1.2.3",
],
)
assert result.exit_code != 0 assert result.exit_code != 0
items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()]
@ -80,8 +97,7 @@ def test_global_install_archive(clirunner, validate_cliresult,
assert set(items1) >= set(items2) assert set(items1) >= set(items2)
def test_global_install_repository(clirunner, validate_cliresult, def test_global_install_repository(clirunner, validate_cliresult, isolated_pio_home):
isolated_pio_home):
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, cmd_lib,
[ [
@ -93,24 +109,28 @@ def test_global_install_repository(clirunner, validate_cliresult,
"https://gitlab.com/ivankravets/rs485-nodeproto.git", "https://gitlab.com/ivankravets/rs485-nodeproto.git",
"https://github.com/platformio/platformio-libmirror.git", "https://github.com/platformio/platformio-libmirror.git",
# "https://developer.mbed.org/users/simon/code/TextLCD/", # "https://developer.mbed.org/users/simon/code/TextLCD/",
"knolleary/pubsubclient#bef58148582f956dfa772687db80c44e2279a163" "knolleary/pubsubclient#bef58148582f956dfa772687db80c44e2279a163",
]) ],
)
validate_cliresult(result) validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()]
items2 = [ items2 = [
"PJON", "PJON@src-79de467ebe19de18287becff0a1fb42d", "PJON",
"ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", "rs485-nodeproto", "PJON@src-79de467ebe19de18287becff0a1fb42d",
"platformio-libmirror", "PubSubClient" "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"rs485-nodeproto",
"platformio-libmirror",
"PubSubClient",
] ]
assert set(items1) >= set(items2) assert set(items1) >= set(items2)
def test_install_duplicates(clirunner, validate_cliresult, without_internet): def test_install_duplicates(clirunner, validate_cliresult, without_internet):
# registry # registry
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(
"-g", "install", cmd_lib,
"http://dl.platformio.org/libraries/archives/0/9540.tar.gz" ["-g", "install", "http://dl.platformio.org/libraries/archives/0/9540.tar.gz"],
]) )
validate_cliresult(result) validate_cliresult(result)
assert "is already installed" in result.output assert "is already installed" in result.output
@ -120,18 +140,22 @@ def test_install_duplicates(clirunner, validate_cliresult, without_internet):
assert "is already installed" in result.output assert "is already installed" in result.output
# archive # archive
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(
"-g", "install", cmd_lib,
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip" [
]) "-g",
"install",
"https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
],
)
validate_cliresult(result) validate_cliresult(result)
assert "is already installed" in result.output assert "is already installed" in result.output
# repository # repository
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(
"-g", "install", cmd_lib,
"https://github.com/platformio/platformio-libmirror.git" ["-g", "install", "https://github.com/platformio/platformio-libmirror.git"],
]) )
validate_cliresult(result) validate_cliresult(result)
assert "is already installed" in result.output assert "is already installed" in result.output
@ -139,27 +163,48 @@ def test_install_duplicates(clirunner, validate_cliresult, without_internet):
def test_global_lib_list(clirunner, validate_cliresult): def test_global_lib_list(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["-g", "list"]) result = clirunner.invoke(cmd_lib, ["-g", "list"])
validate_cliresult(result) validate_cliresult(result)
assert all([ assert all(
n in result.output for n in [
("Source: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip", n in result.output
"Version: 5.10.1", for n in (
"Source: git+https://github.com/gioblu/PJON.git#3.0", "Source: https://github.com/Pedroalbuquerque/ESP32WebServer/archive/master.zip",
"Version: 1fb26fd") "Version: 5.10.1",
]) "Source: git+https://github.com/gioblu/PJON.git#3.0",
"Version: 1fb26fd",
)
]
)
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"]) result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
assert all([ assert all(
n in result.output for n in [
("__pkg_dir", n in result.output
'"__src_url": "git+https://gitlab.com/ivankravets/rs485-nodeproto.git"', for n in (
'"version": "5.10.1"') "__pkg_dir",
]) '"__src_url": "git+https://gitlab.com/ivankravets/rs485-nodeproto.git"',
items1 = [i['name'] for i in json.loads(result.output)] '"version": "5.10.1"',
)
]
)
items1 = [i["name"] for i in json.loads(result.output)]
items2 = [ items2 = [
"ESP32WebServer", "ArduinoJson", "ArduinoJson", "ArduinoJson", "ESP32WebServer",
"ArduinoJson", "AsyncMqttClient", "AsyncTCP", "SomeLib", "ESPAsyncTCP", "ArduinoJson",
"NeoPixelBus", "OneWire", "PJON", "PJON", "PubSubClient", "RFcontrol", "ArduinoJson",
"platformio-libmirror", "rs485-nodeproto" "ArduinoJson",
"ArduinoJson",
"AsyncMqttClient",
"AsyncTCP",
"SomeLib",
"ESPAsyncTCP",
"NeoPixelBus",
"OneWire",
"PJON",
"PJON",
"PubSubClient",
"RFcontrol",
"platformio-libmirror",
"rs485-nodeproto",
] ]
assert sorted(items1) == sorted(items2) assert sorted(items1) == sorted(items2)
@ -167,33 +212,37 @@ def test_global_lib_list(clirunner, validate_cliresult):
"{name}@{version}".format(**item) for item in json.loads(result.output) "{name}@{version}".format(**item) for item in json.loads(result.output)
] ]
versions2 = [ versions2 = [
"ArduinoJson@5.8.2", "ArduinoJson@5.10.1", "AsyncMqttClient@0.8.2", "ArduinoJson@5.8.2",
"NeoPixelBus@2.2.4", "PJON@07fe9aa", "PJON@1fb26fd", "ArduinoJson@5.10.1",
"PubSubClient@bef5814", "RFcontrol@77d4eb3f8a" "AsyncMqttClient@0.8.2",
"NeoPixelBus@2.2.4",
"PJON@07fe9aa",
"PJON@1fb26fd",
"PubSubClient@bef5814",
"RFcontrol@77d4eb3f8a",
] ]
assert set(versions1) >= set(versions2) assert set(versions1) >= set(versions2)
def test_global_lib_update_check(clirunner, validate_cliresult): def test_global_lib_update_check(clirunner, validate_cliresult):
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, ["-g", "update", "--only-check", "--json-output"]) cmd_lib, ["-g", "update", "--only-check", "--json-output"]
)
validate_cliresult(result) validate_cliresult(result)
output = json.loads(result.output) output = json.loads(result.output)
assert set(["RFcontrol", assert set(["RFcontrol", "NeoPixelBus"]) == set([l["name"] for l in output])
"NeoPixelBus"]) == set([l['name'] for l in output])
def test_global_lib_update(clirunner, validate_cliresult): def test_global_lib_update(clirunner, validate_cliresult):
# update library using package directory # update library using package directory
result = clirunner.invoke( result = clirunner.invoke(
cmd_lib, cmd_lib, ["-g", "update", "NeoPixelBus", "--only-check", "--json-output"]
["-g", "update", "NeoPixelBus", "--only-check", "--json-output"]) )
validate_cliresult(result) validate_cliresult(result)
oudated = json.loads(result.output) oudated = json.loads(result.output)
assert len(oudated) == 1 assert len(oudated) == 1
assert "__pkg_dir" in oudated[0] assert "__pkg_dir" in oudated[0]
result = clirunner.invoke(cmd_lib, result = clirunner.invoke(cmd_lib, ["-g", "update", oudated[0]["__pkg_dir"]])
["-g", "update", oudated[0]['__pkg_dir']])
validate_cliresult(result) validate_cliresult(result)
assert "Uninstalling NeoPixelBus @ 2.2.4" in result.output assert "Uninstalling NeoPixelBus @ 2.2.4" in result.output
@ -210,31 +259,43 @@ def test_global_lib_update(clirunner, validate_cliresult):
assert isinstance(result.exception, exception.UnknownPackage) assert isinstance(result.exception, exception.UnknownPackage)
def test_global_lib_uninstall(clirunner, validate_cliresult, def test_global_lib_uninstall(clirunner, validate_cliresult, isolated_pio_home):
isolated_pio_home):
# uninstall using package directory # uninstall using package directory
result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"]) result = clirunner.invoke(cmd_lib, ["-g", "list", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
items = json.loads(result.output) items = json.loads(result.output)
result = clirunner.invoke(cmd_lib, result = clirunner.invoke(cmd_lib, ["-g", "uninstall", items[5]["__pkg_dir"]])
["-g", "uninstall", items[5]['__pkg_dir']])
validate_cliresult(result) validate_cliresult(result)
assert "Uninstalling AsyncTCP" in result.output assert "Uninstalling AsyncTCP" in result.output
# uninstall the rest libraries # uninstall the rest libraries
result = clirunner.invoke(cmd_lib, [ result = clirunner.invoke(
"-g", "uninstall", "1", "https://github.com/bblanchon/ArduinoJson.git", cmd_lib,
"ArduinoJson@!=5.6.7", "RFcontrol" [
]) "-g",
"uninstall",
"1",
"https://github.com/bblanchon/ArduinoJson.git",
"ArduinoJson@!=5.6.7",
"RFcontrol",
],
)
validate_cliresult(result) validate_cliresult(result)
items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()] items1 = [d.basename for d in isolated_pio_home.join("lib").listdir()]
items2 = [ items2 = [
"rs485-nodeproto", "platformio-libmirror", "rs485-nodeproto",
"PubSubClient", "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81", "platformio-libmirror",
"ESPAsyncTCP_ID305", "SomeLib_ID54", "NeoPixelBus_ID547", "PJON", "PubSubClient",
"AsyncMqttClient_ID346", "ArduinoJson_ID64", "ArduinoJson@src-69ebddd821f771debe7ee734d3c7fa81",
"PJON@src-79de467ebe19de18287becff0a1fb42d", "ESP32WebServer" "ESPAsyncTCP_ID305",
"SomeLib_ID54",
"NeoPixelBus_ID547",
"PJON",
"AsyncMqttClient_ID346",
"ArduinoJson_ID64",
"PJON@src-79de467ebe19de18287becff0a1fb42d",
"ESP32WebServer",
] ]
assert set(items1) == set(items2) assert set(items1) == set(items2)
@ -247,8 +308,7 @@ def test_global_lib_uninstall(clirunner, validate_cliresult,
def test_lib_show(clirunner, validate_cliresult): def test_lib_show(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["show", "64"]) result = clirunner.invoke(cmd_lib, ["show", "64"])
validate_cliresult(result) validate_cliresult(result)
assert all( assert all([s in result.output for s in ("ArduinoJson", "Arduino", "Atmel AVR")])
[s in result.output for s in ("ArduinoJson", "Arduino", "Atmel AVR")])
result = clirunner.invoke(cmd_lib, ["show", "OneWire", "--json-output"]) result = clirunner.invoke(cmd_lib, ["show", "OneWire", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
assert "OneWire" in result.output assert "OneWire" in result.output
@ -264,14 +324,23 @@ def test_lib_builtin(clirunner, validate_cliresult):
def test_lib_stats(clirunner, validate_cliresult): def test_lib_stats(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_lib, ["stats"]) result = clirunner.invoke(cmd_lib, ["stats"])
validate_cliresult(result) validate_cliresult(result)
assert all([ assert all(
s in result.output [
for s in ("UPDATED", "POPULAR", "https://platformio.org/lib/show") s in result.output
]) for s in ("UPDATED", "POPULAR", "https://platformio.org/lib/show")
]
)
result = clirunner.invoke(cmd_lib, ["stats", "--json-output"]) result = clirunner.invoke(cmd_lib, ["stats", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
assert set([ assert set(
"dlweek", "added", "updated", "topkeywords", "dlmonth", "dlday", [
"lastkeywords" "dlweek",
]) == set(json.loads(result.output).keys()) "added",
"updated",
"topkeywords",
"dlmonth",
"dlday",
"lastkeywords",
]
) == set(json.loads(result.output).keys())

View File

@ -19,13 +19,14 @@ from platformio.commands import platform as cli_platform
def test_search_json_output(clirunner, validate_cliresult, isolated_pio_home): def test_search_json_output(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_search, result = clirunner.invoke(
["arduino", "--json-output"]) cli_platform.platform_search, ["arduino", "--json-output"]
)
validate_cliresult(result) validate_cliresult(result)
search_result = json.loads(result.output) search_result = json.loads(result.output)
assert isinstance(search_result, list) assert isinstance(search_result, list)
assert search_result assert search_result
platforms = [item['name'] for item in search_result] platforms = [item["name"] for item in search_result]
assert "atmelsam" in platforms assert "atmelsam" in platforms
@ -36,25 +37,22 @@ def test_search_raw_output(clirunner, validate_cliresult):
def test_install_unknown_version(clirunner): def test_install_unknown_version(clirunner):
result = clirunner.invoke(cli_platform.platform_install, result = clirunner.invoke(cli_platform.platform_install, ["atmelavr@99.99.99"])
["atmelavr@99.99.99"])
assert result.exit_code != 0 assert result.exit_code != 0
assert isinstance(result.exception, exception.UndefinedPackageVersion) assert isinstance(result.exception, exception.UndefinedPackageVersion)
def test_install_unknown_from_registry(clirunner): def test_install_unknown_from_registry(clirunner):
result = clirunner.invoke(cli_platform.platform_install, result = clirunner.invoke(cli_platform.platform_install, ["unknown-platform"])
["unknown-platform"])
assert result.exit_code != 0 assert result.exit_code != 0
assert isinstance(result.exception, exception.UnknownPackage) assert isinstance(result.exception, exception.UnknownPackage)
def test_install_known_version(clirunner, validate_cliresult, def test_install_known_version(clirunner, validate_cliresult, isolated_pio_home):
isolated_pio_home): result = clirunner.invoke(
result = clirunner.invoke(cli_platform.platform_install, [ cli_platform.platform_install,
"atmelavr@1.2.0", "--skip-default-package", "--with-package", ["atmelavr@1.2.0", "--skip-default-package", "--with-package", "tool-avrdude"],
"tool-avrdude" )
])
validate_cliresult(result) validate_cliresult(result)
assert "atmelavr @ 1.2.0" in result.output assert "atmelavr @ 1.2.0" in result.output
assert "Installing tool-avrdude @" in result.output assert "Installing tool-avrdude @" in result.output
@ -62,10 +60,13 @@ def test_install_known_version(clirunner, validate_cliresult,
def test_install_from_vcs(clirunner, validate_cliresult, isolated_pio_home): def test_install_from_vcs(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_install, [ result = clirunner.invoke(
"https://github.com/platformio/" cli_platform.platform_install,
"platform-espressif8266.git#feature/stage", "--skip-default-package" [
]) "https://github.com/platformio/" "platform-espressif8266.git#feature/stage",
"--skip-default-package",
],
)
validate_cliresult(result) validate_cliresult(result)
assert "espressif8266" in result.output assert "espressif8266" in result.output
assert len(isolated_pio_home.join("packages").listdir()) == 1 assert len(isolated_pio_home.join("packages").listdir()) == 1
@ -77,24 +78,24 @@ def test_list_json_output(clirunner, validate_cliresult):
list_result = json.loads(result.output) list_result = json.loads(result.output)
assert isinstance(list_result, list) assert isinstance(list_result, list)
assert list_result assert list_result
platforms = [item['name'] for item in list_result] platforms = [item["name"] for item in list_result]
assert set(["atmelavr", "espressif8266"]) == set(platforms) assert set(["atmelavr", "espressif8266"]) == set(platforms)
def test_list_raw_output(clirunner, validate_cliresult): def test_list_raw_output(clirunner, validate_cliresult):
result = clirunner.invoke(cli_platform.platform_list) result = clirunner.invoke(cli_platform.platform_list)
validate_cliresult(result) validate_cliresult(result)
assert all( assert all([s in result.output for s in ("atmelavr", "espressif8266")])
[s in result.output for s in ("atmelavr", "espressif8266")])
def test_update_check(clirunner, validate_cliresult, isolated_pio_home): def test_update_check(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_update, result = clirunner.invoke(
["--only-check", "--json-output"]) cli_platform.platform_update, ["--only-check", "--json-output"]
)
validate_cliresult(result) validate_cliresult(result)
output = json.loads(result.output) output = json.loads(result.output)
assert len(output) == 1 assert len(output) == 1
assert output[0]['name'] == "atmelavr" assert output[0]["name"] == "atmelavr"
assert len(isolated_pio_home.join("packages").listdir()) == 1 assert len(isolated_pio_home.join("packages").listdir()) == 1
@ -107,7 +108,8 @@ def test_update_raw(clirunner, validate_cliresult, isolated_pio_home):
def test_uninstall(clirunner, validate_cliresult, isolated_pio_home): def test_uninstall(clirunner, validate_cliresult, isolated_pio_home):
result = clirunner.invoke(cli_platform.platform_uninstall, result = clirunner.invoke(
["atmelavr", "espressif8266"]) cli_platform.platform_uninstall, ["atmelavr", "espressif8266"]
)
validate_cliresult(result) validate_cliresult(result)
assert not isolated_pio_home.join("platforms").listdir() assert not isolated_pio_home.join("platforms").listdir()

View File

@ -20,11 +20,18 @@ from platformio import util
def test_local_env(): def test_local_env():
result = util.exec_command([ result = util.exec_command(
"platformio", "test", "-d", [
join("examples", "unit-testing", "calculator"), "-e", "native" "platformio",
]) "test",
if result['returncode'] != 1: "-d",
join("examples", "unit-testing", "calculator"),
"-e",
"native",
]
)
if result["returncode"] != 1:
pytest.fail(result) pytest.fail(result)
assert all([s in result['err'] assert all([s in result["err"] for s in ("PASSED", "IGNORED", "FAILED")]), result[
for s in ("PASSED", "IGNORED", "FAILED")]), result['out'] "out"
]

View File

@ -22,12 +22,9 @@ from platformio import util
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def validate_cliresult(): def validate_cliresult():
def decorator(result): def decorator(result):
assert result.exit_code == 0, "{} => {}".format( assert result.exit_code == 0, "{} => {}".format(result.exception, result.output)
result.exception, result.output) assert not result.exception, "{} => {}".format(result.exception, result.output)
assert not result.exception, "{} => {}".format(result.exception,
result.output)
return decorator return decorator
@ -40,10 +37,10 @@ def clirunner():
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def isolated_pio_home(request, tmpdir_factory): def isolated_pio_home(request, tmpdir_factory):
home_dir = tmpdir_factory.mktemp(".platformio") home_dir = tmpdir_factory.mktemp(".platformio")
os.environ['PLATFORMIO_CORE_DIR'] = str(home_dir) os.environ["PLATFORMIO_CORE_DIR"] = str(home_dir)
def fin(): def fin():
del os.environ['PLATFORMIO_CORE_DIR'] del os.environ["PLATFORMIO_CORE_DIR"]
request.addfinalizer(fin) request.addfinalizer(fin)
return home_dir return home_dir

View File

@ -16,27 +16,34 @@ from platformio.commands.run import cli as cmd_run
def test_build_flags(clirunner, validate_cliresult, tmpdir): def test_build_flags(clirunner, validate_cliresult, tmpdir):
build_flags = [("-D TEST_INT=13", "-DTEST_INT=13"), build_flags = [
("-DTEST_SINGLE_MACRO", "-DTEST_SINGLE_MACRO"), ("-D TEST_INT=13", "-DTEST_INT=13"),
('-DTEST_STR_SPACE="Andrew Smith"', ("-DTEST_SINGLE_MACRO", "-DTEST_SINGLE_MACRO"),
'"-DTEST_STR_SPACE=Andrew Smith"')] ('-DTEST_STR_SPACE="Andrew Smith"', '"-DTEST_STR_SPACE=Andrew Smith"'),
]
tmpdir.join("platformio.ini").write(""" tmpdir.join("platformio.ini").write(
"""
[env:native] [env:native]
platform = native platform = native
extra_scripts = extra.py extra_scripts = extra.py
build_flags = build_flags =
; -DCOMMENTED_MACRO ; -DCOMMENTED_MACRO
%s ; inline comment %s ; inline comment
""" % " ".join([f[0] for f in build_flags])) """
% " ".join([f[0] for f in build_flags])
)
tmpdir.join("extra.py").write(""" tmpdir.join("extra.py").write(
"""
Import("projenv") Import("projenv")
projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO") projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO")
""") """
)
tmpdir.mkdir("src").join("main.cpp").write(""" tmpdir.mkdir("src").join("main.cpp").write(
"""
#if !defined(TEST_INT) || TEST_INT != 13 #if !defined(TEST_INT) || TEST_INT != 13
#error "TEST_INT" #error "TEST_INT"
#endif #endif
@ -55,26 +62,28 @@ projenv.Append(CPPDEFINES="POST_SCRIPT_MACRO")
int main() { int main() {
} }
""") """
)
result = clirunner.invoke( result = clirunner.invoke(cmd_run, ["--project-dir", str(tmpdir), "--verbose"])
cmd_run, ["--project-dir", str(tmpdir), "--verbose"])
validate_cliresult(result) validate_cliresult(result)
build_output = result.output[result.output.find( build_output = result.output[result.output.find("Scanning dependencies...") :]
"Scanning dependencies..."):]
for flag in build_flags: for flag in build_flags:
assert flag[1] in build_output, flag assert flag[1] in build_output, flag
def test_build_unflags(clirunner, validate_cliresult, tmpdir): def test_build_unflags(clirunner, validate_cliresult, tmpdir):
tmpdir.join("platformio.ini").write(""" tmpdir.join("platformio.ini").write(
"""
[env:native] [env:native]
platform = native platform = native
build_unflags = -DTMP_MACRO1=45 -I. -DNON_EXISTING_MACRO -lunknownLib -Os build_unflags = -DTMP_MACRO1=45 -I. -DNON_EXISTING_MACRO -lunknownLib -Os
extra_scripts = pre:extra.py extra_scripts = pre:extra.py
""") """
)
tmpdir.join("extra.py").write(""" tmpdir.join("extra.py").write(
"""
Import("env") Import("env")
env.Append(CPPPATH="%s") env.Append(CPPPATH="%s")
env.Append(CPPDEFINES="TMP_MACRO1") env.Append(CPPDEFINES="TMP_MACRO1")
@ -82,22 +91,24 @@ env.Append(CPPDEFINES=["TMP_MACRO2"])
env.Append(CPPDEFINES=("TMP_MACRO3", 13)) env.Append(CPPDEFINES=("TMP_MACRO3", 13))
env.Append(CCFLAGS=["-Os"]) env.Append(CCFLAGS=["-Os"])
env.Append(LIBS=["unknownLib"]) env.Append(LIBS=["unknownLib"])
""" % str(tmpdir)) """
% str(tmpdir)
)
tmpdir.mkdir("src").join("main.c").write(""" tmpdir.mkdir("src").join("main.c").write(
"""
#ifdef TMP_MACRO1 #ifdef TMP_MACRO1
#error "TMP_MACRO1 should be removed" #error "TMP_MACRO1 should be removed"
#endif #endif
int main() { int main() {
} }
""") """
)
result = clirunner.invoke( result = clirunner.invoke(cmd_run, ["--project-dir", str(tmpdir), "--verbose"])
cmd_run, ["--project-dir", str(tmpdir), "--verbose"])
validate_cliresult(result) validate_cliresult(result)
build_output = result.output[result.output.find( build_output = result.output[result.output.find("Scanning dependencies...") :]
"Scanning dependencies..."):]
assert "-DTMP_MACRO1" not in build_output assert "-DTMP_MACRO1" not in build_output
assert "-Os" not in build_output assert "-Os" not in build_output
assert str(tmpdir) not in build_output assert str(tmpdir) not in build_output

View File

@ -35,12 +35,12 @@ def pytest_generate_tests(metafunc):
# dev/platforms # dev/platforms
for manifest in PlatformManager().get_installed(): for manifest in PlatformManager().get_installed():
p = PlatformFactory.newPlatform(manifest['__pkg_dir']) p = PlatformFactory.newPlatform(manifest["__pkg_dir"])
ignore_conds = [ ignore_conds = [
not p.is_embedded(), not p.is_embedded(),
p.name == "ststm8", p.name == "ststm8",
# issue with "version `CXXABI_1.3.9' not found (required by sdcc)" # issue with "version `CXXABI_1.3.9' not found (required by sdcc)"
"linux" in util.get_systype() and p.name == "intel_mcs51" "linux" in util.get_systype() and p.name == "intel_mcs51",
] ]
if any(ignore_conds): if any(ignore_conds):
continue continue
@ -61,10 +61,9 @@ def pytest_generate_tests(metafunc):
candidates[group] = [] candidates[group] = []
candidates[group].append(root) candidates[group].append(root)
project_dirs.extend([ project_dirs.extend(
random.choice(examples) for examples in candidates.values() [random.choice(examples) for examples in candidates.values() if examples]
if examples )
])
metafunc.parametrize("pioproject_dir", sorted(project_dirs)) metafunc.parametrize("pioproject_dir", sorted(project_dirs))
@ -76,12 +75,11 @@ def test_run(pioproject_dir):
if isdir(build_dir): if isdir(build_dir):
util.rmtree_(build_dir) util.rmtree_(build_dir)
env_names = ProjectConfig(join(pioproject_dir, env_names = ProjectConfig(join(pioproject_dir, "platformio.ini")).envs()
"platformio.ini")).envs()
result = util.exec_command( result = util.exec_command(
["platformio", "run", "-e", ["platformio", "run", "-e", random.choice(env_names)]
random.choice(env_names)]) )
if result['returncode'] != 0: if result["returncode"] != 0:
pytest.fail(str(result)) pytest.fail(str(result))
assert isdir(build_dir) assert isdir(build_dir)

View File

@ -37,14 +37,10 @@ def test_example(clirunner, validate_cliresult, piotest_dir):
def test_warning_line(clirunner, validate_cliresult): def test_warning_line(clirunner, validate_cliresult):
result = clirunner.invoke(cmd_ci, result = clirunner.invoke(cmd_ci, [join(INOTEST_DIR, "basic"), "-b", "uno"])
[join(INOTEST_DIR, "basic"), "-b", "uno"])
validate_cliresult(result) validate_cliresult(result)
assert ('basic.ino:16:14: warning: #warning "Line number is 16"' in assert 'basic.ino:16:14: warning: #warning "Line number is 16"' in result.output
result.output) assert 'basic.ino:46:2: warning: #warning "Line number is 46"' in result.output
assert ('basic.ino:46:2: warning: #warning "Line number is 46"' in result = clirunner.invoke(cmd_ci, [join(INOTEST_DIR, "strmultilines"), "-b", "uno"])
result.output)
result = clirunner.invoke(
cmd_ci, [join(INOTEST_DIR, "strmultilines"), "-b", "uno"])
validate_cliresult(result) validate_cliresult(result)
assert ('main.ino:75:2: warning: #warning "Line 75"' in result.output) assert 'main.ino:75:2: warning: #warning "Line 75"' in result.output

View File

@ -23,7 +23,6 @@ from platformio.managers.platform import PlatformManager
def test_check_pio_upgrade(clirunner, isolated_pio_home, validate_cliresult): def test_check_pio_upgrade(clirunner, isolated_pio_home, validate_cliresult):
def _patch_pio_version(version): def _patch_pio_version(version):
maintenance.__version__ = version maintenance.__version__ = version
cmd_upgrade.VERSION = version.split(".", 3) cmd_upgrade.VERSION = version.split(".", 3)
@ -54,8 +53,7 @@ def test_check_pio_upgrade(clirunner, isolated_pio_home, validate_cliresult):
def test_check_lib_updates(clirunner, isolated_pio_home, validate_cliresult): def test_check_lib_updates(clirunner, isolated_pio_home, validate_cliresult):
# install obsolete library # install obsolete library
result = clirunner.invoke(cli_pio, result = clirunner.invoke(cli_pio, ["lib", "-g", "install", "ArduinoJson@<5.7"])
["lib", "-g", "install", "ArduinoJson@<5.7"])
validate_cliresult(result) validate_cliresult(result)
# reset check time # reset check time
@ -65,15 +63,14 @@ def test_check_lib_updates(clirunner, isolated_pio_home, validate_cliresult):
result = clirunner.invoke(cli_pio, ["lib", "-g", "list"]) result = clirunner.invoke(cli_pio, ["lib", "-g", "list"])
validate_cliresult(result) validate_cliresult(result)
assert ("There are the new updates for libraries (ArduinoJson)" in assert "There are the new updates for libraries (ArduinoJson)" in result.output
result.output)
def test_check_and_update_libraries(clirunner, isolated_pio_home, def test_check_and_update_libraries(clirunner, isolated_pio_home, validate_cliresult):
validate_cliresult):
# enable library auto-updates # enable library auto-updates
result = clirunner.invoke( result = clirunner.invoke(
cli_pio, ["settings", "set", "auto_update_libraries", "Yes"]) cli_pio, ["settings", "set", "auto_update_libraries", "Yes"]
)
# reset check time # reset check time
interval = int(app.get_setting("check_libraries_interval")) * 3600 * 24 interval = int(app.get_setting("check_libraries_interval")) * 3600 * 24
@ -89,27 +86,23 @@ def test_check_and_update_libraries(clirunner, isolated_pio_home,
# initiate auto-updating # initiate auto-updating
result = clirunner.invoke(cli_pio, ["lib", "-g", "show", "ArduinoJson"]) result = clirunner.invoke(cli_pio, ["lib", "-g", "show", "ArduinoJson"])
validate_cliresult(result) validate_cliresult(result)
assert ("There are the new updates for libraries (ArduinoJson)" in assert "There are the new updates for libraries (ArduinoJson)" in result.output
result.output)
assert "Please wait while updating libraries" in result.output assert "Please wait while updating libraries" in result.output
assert re.search(r"Updating ArduinoJson\s+@ 5.6.7\s+\[[\d\.]+\]", assert re.search(r"Updating ArduinoJson\s+@ 5.6.7\s+\[[\d\.]+\]", result.output)
result.output)
# check updated version # check updated version
result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["lib", "-g", "list", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
assert prev_data[0]['version'] != json.loads(result.output)[0]['version'] assert prev_data[0]["version"] != json.loads(result.output)[0]["version"]
def test_check_platform_updates(clirunner, isolated_pio_home, def test_check_platform_updates(clirunner, isolated_pio_home, validate_cliresult):
validate_cliresult):
# install obsolete platform # install obsolete platform
result = clirunner.invoke(cli_pio, ["platform", "install", "native"]) result = clirunner.invoke(cli_pio, ["platform", "install", "native"])
validate_cliresult(result) validate_cliresult(result)
manifest_path = isolated_pio_home.join("platforms", "native", manifest_path = isolated_pio_home.join("platforms", "native", "platform.json")
"platform.json")
manifest = json.loads(manifest_path.read()) manifest = json.loads(manifest_path.read())
manifest['version'] = "0.0.0" manifest["version"] = "0.0.0"
manifest_path.write(json.dumps(manifest)) manifest_path.write(json.dumps(manifest))
# reset cached manifests # reset cached manifests
PlatformManager().cache_reset() PlatformManager().cache_reset()
@ -124,11 +117,11 @@ def test_check_platform_updates(clirunner, isolated_pio_home,
assert "There are the new updates for platforms (native)" in result.output assert "There are the new updates for platforms (native)" in result.output
def test_check_and_update_platforms(clirunner, isolated_pio_home, def test_check_and_update_platforms(clirunner, isolated_pio_home, validate_cliresult):
validate_cliresult):
# enable library auto-updates # enable library auto-updates
result = clirunner.invoke( result = clirunner.invoke(
cli_pio, ["settings", "set", "auto_update_platforms", "Yes"]) cli_pio, ["settings", "set", "auto_update_platforms", "Yes"]
)
# reset check time # reset check time
interval = int(app.get_setting("check_platforms_interval")) * 3600 * 24 interval = int(app.get_setting("check_platforms_interval")) * 3600 * 24
@ -151,4 +144,4 @@ def test_check_and_update_platforms(clirunner, isolated_pio_home,
# check updated version # check updated version
result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"]) result = clirunner.invoke(cli_pio, ["platform", "list", "--json-output"])
validate_cliresult(result) validate_cliresult(result)
assert prev_data[0]['version'] != json.loads(result.output)[0]['version'] assert prev_data[0]["version"] != json.loads(result.output)[0]["version"]

View File

@ -29,126 +29,134 @@ def test_pkg_input_parser():
["id=13@~1.2.3", ("id=13", "~1.2.3", None)], ["id=13@~1.2.3", ("id=13", "~1.2.3", None)],
[ [
get_project_core_dir(), get_project_core_dir(),
(".platformio", None, "file://" + get_project_core_dir()) (".platformio", None, "file://" + get_project_core_dir()),
], ],
[ [
"LocalName=" + get_project_core_dir(), "LocalName=" + get_project_core_dir(),
("LocalName", None, "file://" + get_project_core_dir()) ("LocalName", None, "file://" + get_project_core_dir()),
], ],
[ [
"LocalName=%s@>2.3.0" % get_project_core_dir(), "LocalName=%s@>2.3.0" % get_project_core_dir(),
("LocalName", ">2.3.0", "file://" + get_project_core_dir()) ("LocalName", ">2.3.0", "file://" + get_project_core_dir()),
], ],
[ [
"https://github.com/user/package.git", "https://github.com/user/package.git",
("package", None, "git+https://github.com/user/package.git") ("package", None, "git+https://github.com/user/package.git"),
], ],
[ [
"MyPackage=https://gitlab.com/user/package.git", "MyPackage=https://gitlab.com/user/package.git",
("MyPackage", None, "git+https://gitlab.com/user/package.git") ("MyPackage", None, "git+https://gitlab.com/user/package.git"),
], ],
[ [
"MyPackage=https://gitlab.com/user/package.git@3.2.1,!=2", "MyPackage=https://gitlab.com/user/package.git@3.2.1,!=2",
("MyPackage", "3.2.1,!=2", ("MyPackage", "3.2.1,!=2", "git+https://gitlab.com/user/package.git"),
"git+https://gitlab.com/user/package.git")
], ],
[ [
"https://somedomain.com/path/LibraryName-1.2.3.zip", "https://somedomain.com/path/LibraryName-1.2.3.zip",
("LibraryName-1.2.3", None, (
"https://somedomain.com/path/LibraryName-1.2.3.zip") "LibraryName-1.2.3",
None,
"https://somedomain.com/path/LibraryName-1.2.3.zip",
),
], ],
[ [
"https://github.com/user/package/archive/branch.zip", "https://github.com/user/package/archive/branch.zip",
("branch", None, ("branch", None, "https://github.com/user/package/archive/branch.zip"),
"https://github.com/user/package/archive/branch.zip")
], ],
[ [
"https://github.com/user/package/archive/branch.zip@~1.2.3", "https://github.com/user/package/archive/branch.zip@~1.2.3",
("branch", "~1.2.3", ("branch", "~1.2.3", "https://github.com/user/package/archive/branch.zip"),
"https://github.com/user/package/archive/branch.zip")
], ],
[ [
"https://github.com/user/package/archive/branch.tar.gz", "https://github.com/user/package/archive/branch.tar.gz",
("branch.tar", None, (
"https://github.com/user/package/archive/branch.tar.gz") "branch.tar",
None,
"https://github.com/user/package/archive/branch.tar.gz",
),
], ],
[ [
"https://github.com/user/package/archive/branch.tar.gz@!=5", "https://github.com/user/package/archive/branch.tar.gz@!=5",
("branch.tar", "!=5", (
"https://github.com/user/package/archive/branch.tar.gz") "branch.tar",
"!=5",
"https://github.com/user/package/archive/branch.tar.gz",
),
], ],
[ [
"https://developer.mbed.org/users/user/code/package/", "https://developer.mbed.org/users/user/code/package/",
("package", None, ("package", None, "hg+https://developer.mbed.org/users/user/code/package/"),
"hg+https://developer.mbed.org/users/user/code/package/")
], ],
[ [
"https://os.mbed.com/users/user/code/package/", "https://os.mbed.com/users/user/code/package/",
("package", None, ("package", None, "hg+https://os.mbed.com/users/user/code/package/"),
"hg+https://os.mbed.com/users/user/code/package/")
], ],
[ [
"https://github.com/user/package#v1.2.3", "https://github.com/user/package#v1.2.3",
("package", None, "git+https://github.com/user/package#v1.2.3") ("package", None, "git+https://github.com/user/package#v1.2.3"),
], ],
[ [
"https://github.com/user/package.git#branch", "https://github.com/user/package.git#branch",
("package", None, "git+https://github.com/user/package.git#branch") ("package", None, "git+https://github.com/user/package.git#branch"),
], ],
[ [
"PkgName=https://github.com/user/package.git#a13d344fg56", "PkgName=https://github.com/user/package.git#a13d344fg56",
("PkgName", None, ("PkgName", None, "git+https://github.com/user/package.git#a13d344fg56"),
"git+https://github.com/user/package.git#a13d344fg56")
],
[
"user/package",
("package", None, "git+https://github.com/user/package")
], ],
["user/package", ("package", None, "git+https://github.com/user/package")],
[ [
"PkgName=user/package", "PkgName=user/package",
("PkgName", None, "git+https://github.com/user/package") ("PkgName", None, "git+https://github.com/user/package"),
], ],
[ [
"PkgName=user/package#master", "PkgName=user/package#master",
("PkgName", None, "git+https://github.com/user/package#master") ("PkgName", None, "git+https://github.com/user/package#master"),
], ],
[ [
"git+https://github.com/user/package", "git+https://github.com/user/package",
("package", None, "git+https://github.com/user/package") ("package", None, "git+https://github.com/user/package"),
], ],
[ [
"hg+https://example.com/user/package", "hg+https://example.com/user/package",
("package", None, "hg+https://example.com/user/package") ("package", None, "hg+https://example.com/user/package"),
], ],
[ [
"git@github.com:user/package.git", "git@github.com:user/package.git",
("package", None, "git+git@github.com:user/package.git") ("package", None, "git+git@github.com:user/package.git"),
], ],
[ [
"git@github.com:user/package.git#v1.2.0", "git@github.com:user/package.git#v1.2.0",
("package", None, "git+git@github.com:user/package.git#v1.2.0") ("package", None, "git+git@github.com:user/package.git#v1.2.0"),
], ],
[ [
"LocalName=git@github.com:user/package.git#v1.2.0@~1.2.0", "LocalName=git@github.com:user/package.git#v1.2.0@~1.2.0",
("LocalName", "~1.2.0", ("LocalName", "~1.2.0", "git+git@github.com:user/package.git#v1.2.0"),
"git+git@github.com:user/package.git#v1.2.0")
], ],
[ [
"git+ssh://git@gitlab.private-server.com/user/package#1.2.0", "git+ssh://git@gitlab.private-server.com/user/package#1.2.0",
("package", None, (
"git+ssh://git@gitlab.private-server.com/user/package#1.2.0") "package",
None,
"git+ssh://git@gitlab.private-server.com/user/package#1.2.0",
),
], ],
[ [
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0", "git+ssh://user@gitlab.private-server.com:1234/package#1.2.0",
("package", None, (
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0") "package",
None,
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0",
),
], ],
[ [
"LocalName=git+ssh://user@gitlab.private-server.com:1234" "LocalName=git+ssh://user@gitlab.private-server.com:1234"
"/package#1.2.0@!=13", "/package#1.2.0@!=13",
("LocalName", "!=13", (
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0") "LocalName",
] "!=13",
"git+ssh://user@gitlab.private-server.com:1234/package#1.2.0",
),
],
] ]
for params, result in items: for params, result in items:
if isinstance(params, tuple): if isinstance(params, tuple):
@ -165,62 +173,55 @@ def test_install_packages(isolated_pio_home, tmpdir):
dict(id=1, name="name_1", version="1.2"), dict(id=1, name="name_1", version="1.2"),
dict(id=1, name="name_1", version="1.0.0"), dict(id=1, name="name_1", version="1.0.0"),
dict(name="name_2", version="1.0.0"), dict(name="name_2", version="1.0.0"),
dict(name="name_2", dict(name="name_2", version="2.0.0", __src_url="git+https://github.com"),
version="2.0.0", dict(name="name_2", version="3.0.0", __src_url="git+https://github2.com"),
__src_url="git+https://github.com"), dict(name="name_2", version="4.0.0", __src_url="git+https://github2.com"),
dict(name="name_2",
version="3.0.0",
__src_url="git+https://github2.com"),
dict(name="name_2",
version="4.0.0",
__src_url="git+https://github2.com")
] ]
pm = PackageManager(join(get_project_core_dir(), "packages")) pm = PackageManager(join(get_project_core_dir(), "packages"))
for package in packages: for package in packages:
tmp_dir = tmpdir.mkdir("tmp-package") tmp_dir = tmpdir.mkdir("tmp-package")
tmp_dir.join("package.json").write(json.dumps(package)) tmp_dir.join("package.json").write(json.dumps(package))
pm._install_from_url(package['name'], "file://%s" % str(tmp_dir)) pm._install_from_url(package["name"], "file://%s" % str(tmp_dir))
tmp_dir.remove(rec=1) tmp_dir.remove(rec=1)
assert len(pm.get_installed()) == len(packages) - 1 assert len(pm.get_installed()) == len(packages) - 1
pkg_dirnames = [ pkg_dirnames = [
'name_1_ID1', 'name_1_ID1@1.0.0', 'name_1_ID1@1.2', 'name_1_ID1@2.0.0', "name_1_ID1",
'name_1_ID1@shasum', 'name_2', "name_1_ID1@1.0.0",
'name_2@src-177cbce1f0705580d17790fda1cc2ef5', "name_1_ID1@1.2",
'name_2@src-f863b537ab00f4c7b5011fc44b120e1f' "name_1_ID1@2.0.0",
"name_1_ID1@shasum",
"name_2",
"name_2@src-177cbce1f0705580d17790fda1cc2ef5",
"name_2@src-f863b537ab00f4c7b5011fc44b120e1f",
] ]
assert set([ assert set(
p.basename for p in isolated_pio_home.join("packages").listdir() [p.basename for p in isolated_pio_home.join("packages").listdir()]
]) == set(pkg_dirnames) ) == set(pkg_dirnames)
def test_get_package(): def test_get_package():
tests = [ tests = [
[("unknown", ), None], [("unknown",), None],
[("1", ), None], [("1",), None],
[("id=1", "shasum"), [("id=1", "shasum"), dict(id=1, name="name_1", version="shasum")],
dict(id=1, name="name_1", version="shasum")], [("id=1", "*"), dict(id=1, name="name_1", version="2.1.0")],
[("id=1", "*"), [("id=1", "^1"), dict(id=1, name="name_1", version="1.2")],
dict(id=1, name="name_1", version="2.1.0")], [("id=1", "^1"), dict(id=1, name="name_1", version="1.2")],
[("id=1", "^1"), [("name_1", "<2"), dict(id=1, name="name_1", version="1.2")],
dict(id=1, name="name_1", version="1.2")],
[("id=1", "^1"),
dict(id=1, name="name_1", version="1.2")],
[("name_1", "<2"),
dict(id=1, name="name_1", version="1.2")],
[("name_1", ">2"), None], [("name_1", ">2"), None],
[("name_1", "2-0-0"), None], [("name_1", "2-0-0"), None],
[("name_2", ), dict(name="name_2", version="4.0.0")], [("name_2",), dict(name="name_2", version="4.0.0")],
[("url_has_higher_priority", None, "git+https://github.com"), [
dict(name="name_2", ("url_has_higher_priority", None, "git+https://github.com"),
version="2.0.0", dict(name="name_2", version="2.0.0", __src_url="git+https://github.com"),
__src_url="git+https://github.com")], ],
[("name_2", None, "git+https://github.com"), [
dict(name="name_2", ("name_2", None, "git+https://github.com"),
version="2.0.0", dict(name="name_2", version="2.0.0", __src_url="git+https://github.com"),
__src_url="git+https://github.com")], ],
] ]
pm = PackageManager(join(get_project_core_dir(), "packages")) pm = PackageManager(join(get_project_core_dir(), "packages"))

View File

@ -20,8 +20,8 @@ from platformio import exception, util
def test_platformio_cli(): def test_platformio_cli():
result = util.exec_command(["pio", "--help"]) result = util.exec_command(["pio", "--help"])
assert result['returncode'] == 0 assert result["returncode"] == 0
assert "Usage: pio [OPTIONS] COMMAND [ARGS]..." in result['out'] assert "Usage: pio [OPTIONS] COMMAND [ARGS]..." in result["out"]
def test_ping_internet_ips(): def test_ping_internet_ips():
@ -38,5 +38,5 @@ def test_api_cache(monkeypatch, isolated_pio_home):
api_kwargs = {"url": "/stats", "cache_valid": "10s"} api_kwargs = {"url": "/stats", "cache_valid": "10s"}
result = util.get_api_result(**api_kwargs) result = util.get_api_result(**api_kwargs)
assert result and "boards" in result assert result and "boards" in result
monkeypatch.setattr(util, '_internet_on', lambda: False) monkeypatch.setattr(util, "_internet_on", lambda: False)
assert util.get_api_result(**api_kwargs) == result assert util.get_api_result(**api_kwargs) == result

View File

@ -18,14 +18,14 @@ import requests
def validate_response(r): def validate_response(r):
assert r.status_code == 200, r.url assert r.status_code == 200, r.url
assert int(r.headers['Content-Length']) > 0, r.url assert int(r.headers["Content-Length"]) > 0, r.url
assert r.headers['Content-Type'] in ("application/gzip", assert r.headers["Content-Type"] in ("application/gzip", "application/octet-stream")
"application/octet-stream")
def test_packages(): def test_packages():
pkgs_manifest = requests.get( pkgs_manifest = requests.get(
"https://dl.bintray.com/platformio/dl-packages/manifest.json").json() "https://dl.bintray.com/platformio/dl-packages/manifest.json"
).json()
assert isinstance(pkgs_manifest, dict) assert isinstance(pkgs_manifest, dict)
items = [] items = []
for _, variants in pkgs_manifest.items(): for _, variants in pkgs_manifest.items():
@ -33,12 +33,12 @@ def test_packages():
items.append(item) items.append(item)
for item in items: for item in items:
assert item['url'].endswith(".tar.gz"), item assert item["url"].endswith(".tar.gz"), item
r = requests.head(item['url'], allow_redirects=True) r = requests.head(item["url"], allow_redirects=True)
validate_response(r) validate_response(r)
if "X-Checksum-Sha1" not in r.headers: if "X-Checksum-Sha1" not in r.headers:
return pytest.skip("X-Checksum-Sha1 is not provided") return pytest.skip("X-Checksum-Sha1 is not provided")
assert item['sha1'] == r.headers.get("X-Checksum-Sha1")[0:40], item assert item["sha1"] == r.headers.get("X-Checksum-Sha1")[0:40], item

View File

@ -117,8 +117,16 @@ def test_sections(config):
config.getraw("unknown_section", "unknown_option") config.getraw("unknown_section", "unknown_option")
assert config.sections() == [ assert config.sections() == [
"platformio", "env", "strict_ldf", "monitor_custom", "strict_settings", "platformio",
"custom", "env:base", "env:test_extends", "env:extra_1", "env:extra_2" "env",
"strict_ldf",
"monitor_custom",
"strict_settings",
"custom",
"env:base",
"env:test_extends",
"env:extra_1",
"env:extra_2",
] ]
@ -129,11 +137,20 @@ def test_envs(config):
def test_options(config): def test_options(config):
assert config.options(env="base") == [ assert config.options(env="base") == [
"build_flags", "targets", "monitor_speed", "lib_deps", "lib_ignore" "build_flags",
"targets",
"monitor_speed",
"lib_deps",
"lib_ignore",
] ]
assert config.options(env="test_extends") == [ assert config.options(env="test_extends") == [
"extends", "build_flags", "lib_ldf_mode", "lib_compat_mode", "extends",
"monitor_speed", "lib_deps", "lib_ignore" "build_flags",
"lib_ldf_mode",
"lib_compat_mode",
"monitor_speed",
"lib_deps",
"lib_ignore",
] ]
@ -154,15 +171,22 @@ def test_sysenv_options(config):
os.environ["__PIO_TEST_CNF_EXTRA_FLAGS"] = "-L /usr/local/lib" os.environ["__PIO_TEST_CNF_EXTRA_FLAGS"] = "-L /usr/local/lib"
assert config.get("custom", "extra_flags") == "-L /usr/local/lib" assert config.get("custom", "extra_flags") == "-L /usr/local/lib"
assert config.get("env:base", "build_flags") == [ assert config.get("env:base", "build_flags") == [
"-D DEBUG=1 -L /usr/local/lib", "-DSYSENVDEPS1 -DSYSENVDEPS2" "-D DEBUG=1 -L /usr/local/lib",
"-DSYSENVDEPS1 -DSYSENVDEPS2",
] ]
assert config.get("env:base", "upload_port") == "/dev/sysenv/port" assert config.get("env:base", "upload_port") == "/dev/sysenv/port"
assert config.get("env:extra_2", "upload_port") == "/dev/extra_2/port" assert config.get("env:extra_2", "upload_port") == "/dev/extra_2/port"
# env var as option # env var as option
assert config.options(env="test_extends") == [ assert config.options(env="test_extends") == [
"extends", "build_flags", "lib_ldf_mode", "lib_compat_mode", "extends",
"monitor_speed", "lib_deps", "lib_ignore", "upload_port" "build_flags",
"lib_ldf_mode",
"lib_compat_mode",
"monitor_speed",
"lib_deps",
"lib_ignore",
"upload_port",
] ]
# sysenv # sysenv
@ -208,7 +232,7 @@ def test_items(config):
("debug_flags", "-D DEBUG=1"), ("debug_flags", "-D DEBUG=1"),
("lib_flags", "-lc -lm"), ("lib_flags", "-lc -lm"),
("extra_flags", None), ("extra_flags", None),
("lib_ignore", "LibIgnoreCustom") ("lib_ignore", "LibIgnoreCustom"),
] # yapf: disable ] # yapf: disable
assert config.items(env="base") == [ assert config.items(env="base") == [
("build_flags", ["-D DEBUG=1"]), ("build_flags", ["-D DEBUG=1"]),
@ -228,7 +252,7 @@ def test_items(config):
("lib_ignore", ["LibIgnoreCustom", "Lib3"]), ("lib_ignore", ["LibIgnoreCustom", "Lib3"]),
("upload_port", "/dev/extra_2/port"), ("upload_port", "/dev/extra_2/port"),
("monitor_speed", "115200"), ("monitor_speed", "115200"),
("lib_deps", ["Lib1", "Lib2"]) ("lib_deps", ["Lib1", "Lib2"]),
] # yapf: disable ] # yapf: disable
assert config.items(env="test_extends") == [ assert config.items(env="test_extends") == [
("extends", ["strict_settings"]), ("extends", ["strict_settings"]),
@ -237,5 +261,5 @@ def test_items(config):
("lib_compat_mode", "strict"), ("lib_compat_mode", "strict"),
("monitor_speed", "9600"), ("monitor_speed", "9600"),
("lib_deps", ["Lib1", "Lib2"]), ("lib_deps", ["Lib1", "Lib2"]),
("lib_ignore", ["LibIgnoreCustom"]) ("lib_ignore", ["LibIgnoreCustom"]),
] # yapf: disable ] # yapf: disable

View File

@ -20,7 +20,7 @@ passenv = *
usedevelop = True usedevelop = True
deps = deps =
isort isort
yapf black
pylint pylint
pytest pytest
pytest-xdist pytest-xdist